pdb command reference
pdb is the Picodroid Debug Bridge CLI: it talks to a flashed device over USB CDC, or to a running simulator over its socket. Build and run it with cargo run -p pdb --, or use the ./scripts/pdb.sh wrapper. It picks a device the way adb does:
pdb -s E6633861A37B9038 <command> # the device with this serial (pdb devices lists them)pdb -s emulator-2696762 <command> # a simulator, by its serialPICODROID_SERIAL=E6633861A37B9038 pdb <command> # ANDROID_SERIAL's counterpartpdb -d <command> # the one USB devicepdb -e <command> # the one running simulator (also -s sim)pdb <command> # the only device or simulator there isEvery device has a serial: a board’s is its chip’s unique ID, the same value its USB descriptor reports (lsusb -v, /dev/serial/by-id/) and Build.getSerial() returns; a simulator’s is emulator-<pid>. A serial finds the device whatever /dev node it is on, and finds it again after it reboots onto another one. A port or socket path still works in its place (-s /dev/ttyACM1, -s /dev/cu.usbmodem102, -s COM3, or the socket a simulator prints at boot: [sim] pdb: listening on /tmp/picodroid-sim/pdb-<pid>.sock). With nothing named and several attached, pdb stops with error: more than one device/emulator and lists their serials. Firmware older than picodroid/2.4 has no serial; name it by its port.
Running pdb with no arguments prints the up-to-date usage text.
On a bench with several boards, ./scripts/pdb.sh takes --board <name> (or --slot <name>) in place of -s: it takes that board’s lease, reads the serial of the board on that slot and passes it to the binary. With one board configured, or when the session already holds one, the name can be left out. devices, the help text and a simulator target (-e, -s sim, -s emulator-<pid>, -s <socket>) take no lease. See the shared bench.
devices
pdb devices [-l]Lists every picodroid on a USB port and every running simulator, one row each, under adb devices’ header and a row naming the columns:
List of devices attachedSERIAL STATE BOARD SPACE AVAILABLEE66258881755602D device testbench_rp2040 764 KB68B1A1217192254D device testbench_rp2350w 1024 KB1C05163AC1ACB0AA device pico_touch_kit 9720 KBemulator-2696762 device testbench_rp2350 1528 KB| Column | Meaning |
|---|---|
| SERIAL | What -s takes: the board’s serial, emulator-<pid> for a simulator |
| STATE | device when it answers a ping; offline when the host lists the port but nothing answers (mid-boot, wedged, busy installing) |
| BOARD | The board the firmware was built for |
| SPACE AVAILABLE | The biggest app an install can take right now: on multi-app firmware the largest free run of the app region (anything bigger needs a compaction or an uninstall first); on single-app firmware (RP2040) the whole region, since an install replaces the app |
Columns are padded to line up whatever the lengths, and an offline row has - where only a ping can answer, so every row has the same fields. adb prints only <serial>\t<state>; the serial and state are still the first two fields, so a script written for adb reads them the same way.
-l adds PORT (the tty, or a simulator’s socket), RELEASE and, on Linux, USB, the board’s USB bus position:
List of devices attachedSERIAL STATE BOARD SPACE AVAILABLE PORT RELEASE USB1C05163AC1ACB0AA device pico_touch_kit 9720 KB /dev/ttyACM3 0.38.0 1-8.3.4emulator-2696762 device testbench_rp2350 1528 KB /tmp/picodroid-sim/pdb-2696762.sock 0.38.0 -A board on firmware older than picodroid/2.4 is listed by its port in place of a serial. Only ports with picodroid’s USB vendor and product ID are opened: a debug probe’s UART is never written to.
get-serialno
pdb [-s <serial>] get-serialnoPrints the device’s serial, or unknown for one without.
wait-for-device
timeout 30 pdb -s E6633861A37B9038 wait-for-deviceReturns once the device answers a ping: with -s, that device; otherwise any one. It waits for as long as it takes, as adb wait-for-device does, so bound it with timeout.
ping
pdb -s <serial> pingRound-trips a greeting to confirm the device is alive and the protocol versions match. The greeting carries the largest PAPK the app region could hold, the firmware’s framework-map-version, and — on multi-app firmware (picodroid/2.2, every RP2350 board) — the state of the package directory: apps 3/8, free 1428 KB (largest 1024 KB). A single-app board (RP2040) reports single-app.
install
pdb -s <serial> install build/apks/<app>.papkHot-swaps an app: writes the PAPK to flash and restarts the JVM, without reflashing firmware. The walkthrough lives in Hot-swap with pdb.
A PAPK is checked twice on the host before the install is sent. Its structure first: the header, the format version, the manifest, and the class section with every class’s link table compared against what the class bytes derive to. This check cannot be skipped; a file that fails it is refused with Refusing to install: PAPK file is not a valid PAPK: <error>.
Then compatibility: its framework-map-version must be less than or equal to the firmware’s active version (see the shrinker reference).
On multi-app firmware the device also places the app before it erases anything. The PAPK’s package-name (the manifest’s package=) is its identity: a package that is already installed is upgraded — the new copy lands beside the old one and the old one is erased only after the new one commits, or, when there is no room beside it, over it — and a new package takes the first free run of the app region. If the free space is there but not in one piece the device compacts the region first, which can take up to half a minute; pdb waits. When even that cannot make room, or the directory is full, the device answers STATUS_NO_ROOM with the numbers and nothing has been erased:
Refusing to install: device rejected install: STATUS_NO_ROOM — no room: need 380 KB, largest free 220 KB, total free 300 KB, apps 5/8 Free room with `pdb uninstall <package>`; `pdb list` shows what is installed.A PAPK without a package-name is refused on the host before the device is asked (papk-pack --repack <file> --package-name <name> adds one). On a single-app board the install replaces whatever is installed, as before.
Against the simulator the install runs the same code — the JVM is parked, the app region is placed and written, and the simulator “reboots”: it restarts its own process from a dump of the region (a warm boot), so the boot policy, the rescan and the launcher all run again. pdb sees it come back within the second.
| Flag | Effect |
|---|---|
--skip-host-check | Skip the host-side compat pre-flight (HIL test knob — exercises the device-side rejection path) |
--expect-rejected | Invert exit codes: success when the install is rejected. Used by HIL install-reject-* test rows |
list
pdb -s <port> listPrints the package directory of a multi-app device — every installed app’s sector, package, version-code, version, size, whether it is the boot app, and its label — then the system apps built into the firmware, the free space, and the package that is running:
SECTOR PACKAGE CODE VERSION SIZE BOOT LABEL0 helloworld 1 1.0 1 KB yes helloworld2 imagedemo 1 1.0 11 KB - Image DemoSYSTEM picodroid.launcher 1 1.0 10 KB - Launcherfree: largest 1480 KB, total 1480 KB, apps 2/8running: picodroid.launcherA SYSTEM row is an app linked into the firmware (the launcher, the settings app): it takes no sector, does not count toward apps, and cannot be uninstalled. The boot app is the one flash.sh --app baked in (a reinstall of the same package keeps the flag); flash.sh --boot and a board’s boot_package can pick another — see the launcher guide. On single-app firmware the command reports that there is no directory to list.
uninstall
pdb -s <port> uninstall <package>Erases an installed app’s whole run — its boot-meta sector and image — removes its data (/data/<package> on the LittleFS volume), and reboots the device, then waits for it to come back. NOT_FOUND if no such package is installed; a system app cannot be uninstalled. Uninstalling the boot app leaves the device waiting for the next pdb install.
sysmon
pdb -s <port> sysmonShows live system stats — heap usage, the FreeRTOS task table, CPU% — followed by the JVM heap block. With the memory-diagnostics build it also carries the [memmon] counters; see Debugging. The simulator answers from its hosted FreeRTOS kernel and its modeled device heap; it generates no run-time statistics, so its CPU% column reads N/A.
input
pdb -s <port> input keyevent <KEYCODE|number> # e.g. KEYCODE_DPAD_UP or 19pdb -s <port> input keyevent --longpress <KEYCODE> # hold past the long-press timeout (Android's flag)pdb -s <port> input keyevent --down <KEYCODE> # press only; --up releases (hold a key across commands)pdb -s <port> input dpad <up|down|left|right|center>pdb -s <port> input backpdb -s <port> input tap <x> <y>pdb -s <port> input swipe <x1> <y1> <x2> <y2> [ms] # default 300 msInjects synthetic input, Android-adb-style. Keycode names are case-insensitive and the KEYCODE_ prefix is optional; bare integers are forwarded verbatim. The keycode→pin mapping is resolved on the device against the board’s button table, and injection happens at the HAL layer — so focus navigation, BACK routing, and MotionEvent dispatch all behave exactly as they would for physical input.
Errors: ERR (no such key) for a keycode the board doesn’t map; ERR (no touch panel) for tap/swipe on a board without touch.
pdb -s sim input … drives the simulator through the very same handler. The simulator’s control channel accepts the same verbs too (input tap 40 60, input dpad down, … via ./scripts/sim-ctrl.sh), so an input sequence rehearsed headlessly in the sim replays verbatim on hardware. Details in Debugging.
logcat
./scripts/sim.sh --app foo | pdb logcat --stdin --tag Foo --level WFilters already-decoded Picodroid log text on stdin by tag and level.
| Flag | Effect |
|---|---|
--stdin | Read log text from stdin (required) |
--tag <T> | Keep only lines tagged <T> ([T] sim format or T: RTT format) |
--level <V|D|I|W|E> | Keep only lines at this level or higher |