The Velo CE Primer

Chapter 2Toolchain and Emulator

Install

velo-toolchain is a CMake toolchain that compiles C with clang and links with lld. A Python script, tools/mkpe.py, then turns the linked ELF file into a Windows CE executable. Apple's clang can't target MIPS, so you need Homebrew's LLVM:

brew install llvm lld cmake

On Debian or Ubuntu, sudo apt install clang lld llvm cmake does the same. Then point an environment variable at your checkout, so your projects can find it:

export VELO_TOOLCHAIN=~/Development/velo-toolchain

Build velo-emu by following its README.md, and put the ROMs where it says. Set VELO_EMU to that checkout too. For CE 2.0, VELO_CE2_ROM is the CE 2.0 nk.bin (or velo-emu's merged image) and VELO_CE2_SYSTEM_CARD its system card image, if the ROM isn't merged.

Your First Program

Make a folder with two files. The program shows a message box and exits:

#include <windows.h>

int WINAPI WinMain(HINSTANCE instance, HINSTANCE previous, LPWSTR command_line, int show) {
    MessageBoxW(0, L"Hello from velo-toolchain!", L"Hello", MB_OK | MB_ICONINFORMATION);
    return 0;
}
cmake_minimum_required(VERSION 3.20)
project(hello C)

include(VeloCE)

velo_add_executable(hello hello.c)

Configure and build it once for each version of CE:

cmake -S . -B build/ce1 -DCMAKE_TOOLCHAIN_FILE=$VELO_TOOLCHAIN/cmake/velo-ce.cmake -DVELO_CE_VERSION=1
cmake --build build/ce1
cmake -S . -B build/ce2 -DCMAKE_TOOLCHAIN_FILE=$VELO_TOOLCHAIN/cmake/velo-ce.cmake -DVELO_CE_VERSION=2
cmake --build build/ce2

You get build/ce1/hello.exe and build/ce2/hello.exe, each about 3.5 KB, with a hello.elf beside it that holds the symbols for debugging. A Makefile with a target that runs those four lines saves typing: the toolchain's Makefile does exactly that for its examples.

What WINAPI WinMain means

WinMain is where a Windows program starts. CE passes it the program's instance handle (needed to load resources and create windows), an unused handle, the command line without the program's name, and how to show the first window. Chapter 3 explains the rest.

Adding to a project

In CMakeLists.txtDoes
velo_add_executable(app a.c b.c)Builds app.exe, starting at WinMain.
ICON app.icoGives it an icon (resource 1), shown in the file manager and on shortcuts. CE 1.0 only shows 2-bit (four grey) icons.
RESOURCES app.rc resource.hCompiles menus, dialogs, strings, bitmaps and accelerators from a .rc file with llvm-rc. Other files listed are dependencies.
target_link_libraries(app PRIVATE velo::commctrl)Links a ROM DLL: commctrl for the command bar and common controls, winsock for networking. coredll is always linked.
velo_add_library(lib lib.c EXPORTS Name)Builds a DLL. See examples/dll.
-DCMAKE_BUILD_TYPE=DebugBuilds without optimisation and with debug information, for stepping through in GDB. The default is MinSizeRel.

Run It in the Emulator

There are three ways to get a program into velo-emu, from simplest to quickest to repeat.

On a card

velo-emu comes with a script that makes a PC Card image from files and folders:

mkdir -p card/HELLO
cp build/ce1/hello.exe card/HELLO/
$VELO_EMU/tools/mkcard.sh hello.img 16 card/HELLO

In velo-emu, insert hello.img from the Card menu, then choose Start > Run and type \PC Card\HELLO\hello.exe (\Storage Card\... on CE 2.0). This is also how you'd carry a program to a real Velo: copy it onto a CompactFlash card in a PC Card adapter.

With the debug manager

debugmgr is a small agent that runs on the emulated Velo and copies and starts programs for you, in milliseconds. Make a saved emulator state with it already running, once:

make debug-state

That needs the settings listed under "Examples and tests" in the toolchain's README.md. Then start the emulator from that state with an agent socket, and drive it with tools/velo-debug:

$VELO_EMU/headless $VELO_EMU/rom/nk.bin --load=build/debugmgr/ce1/debug-desktop.state --agent=agent.sock --seconds=100000 &
export VELO_AGENT=agent.sock
tools/velo-debug put build/ce1/hello.exe /Windows/hello.exe
tools/velo-debug run /Windows/hello.exe

velo-debug also has get, ls, rm, mkdir, mv and kill. --seconds is emulated time, which passes much faster than real time while the Velo is idle, so give it plenty and stop the emulator with Ctrl+C when you're done.

In the debugger

Each executable gets a GDB script beside its .elf that loads the symbols and defines velo-load, which uploads the program through debugmgr. tools/velo-emulator starts velo-emu from the debug state with its GDB server listening on port 2001 (CE 1.0) or 2002 (CE 2.0):

tools/velo-emulator --ce 1 &
gdb -x build/ce1/hello.elf.gdb -ex "target extended-remote :2001" -ex "set target-wide-charset UTF-16LE" -ex velo-load -ex "break WinMain" -ex run

Breakpoints, stepping, bt and printing variables all work, and WCHAR strings print as text. Build with -DCMAKE_BUILD_TYPE=Debug first, or the optimiser will have rearranged everything. On macOS, Homebrew's gdb understands MIPS. On Linux, use gdb-multiarch.

In VS Code

Copy vscode/launch.json and vscode/tasks.json from the toolchain into your project's .vscode folder, install the C/C++ and CMake Tools extensions, and configure the project with the toolchain file and CMAKE_BUILD_TYPE=Debug. A CMakePresets.json keeps those settings in one place:

{
    "version": 3,
    "configurePresets": [
        {
            "name": "ce1-debug",
            "binaryDir": "${sourceDir}/build/debug-ce1",
            "toolchainFile": "$env{VELO_TOOLCHAIN}/cmake/velo-ce.cmake",
            "cacheVariables": {
                "CMAKE_BUILD_TYPE": "Debug",
                "VELO_CE_VERSION": "1"
            }
        }
    ]
}

Copy vscode/settings.json too: it makes IntelliSense use the include paths from CMake Tools, so it sees the toolchain's headers and shows their documentation when you hover over a function. VS Code needs VELO_TOOLCHAIN, VELO_EMU and, for CE 2.0, VELO_CE2_ROM in its environment. Press F5 and it starts the emulator if needed, uploads the program, runs it, and stops at your breakpoints. Only the .exe is uploaded: copy any DLL it loads with remote put in the debug console.

Debug Output and Crashes

OutputDebugStringW(L"got here\r\n") is your printf. Its output appears in GDB, in the windowed emulator under Machine > Show Debug Output, and on headless's standard error with --debug-output.

When a program crashes, CE prints the registers, with addresses that mean nothing by themselves. tools/velo-symbolize turns them into functions and line numbers:

$VELO_EMU/headless $VELO_EMU/rom/nk.bin ... --debug-output 2>&1 | tools/velo-symbolize build/ce1/app.exe
debug: AKY=00000003 PC=000110bc [deep crash.c:7] RA=000110ac [deep crash.c:7] BVA=00000000

PC is where it crashed. BVA, the bad address, is 00000000 here: a null pointer. Under GDB, a crash stops the program at the faulting line, with the stack intact.

Automated Screenshots

tests/emulator.py builds a card from a build folder, starts each program in velo-emu from Start > Run, and saves a screenshot. Your project can use it as this primer does, from a few lines of Python:

import os
import sys

sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..", "..", "tests"))

import emulator

emulator.EXTRA_EVENTS = {"fetch.exe": ["--net=1", "--realtime"], "window.exe": ["--tap={at}:200:120"]}
emulator.ARGUMENTS = {"fetch.exe": "http://example.com/"}

if __name__ == "__main__":
    emulator.main("Run each primer example in velo-emu and screenshot it")

ARGUMENTS gives a program its command line, and EXTRA_EVENTS adds options to the emulator's command line for one program: here, a network connection in real time for the fetch example. Run it with a build folder, --ce 1 or --ce 2, and --output for where the pictures go.