Skip to content

Latest commit

 

History

History
488 lines (371 loc) · 10.9 KB

File metadata and controls

488 lines (371 loc) · 10.9 KB

FPBInject CLI Tool

A lightweight command-line interface for ARM binary patching designed for AI agent integration.

Overview

fpb_cli.py is a pure CLI tool located at Tools/WebServer/fpb_cli.py. All commands output JSON for easy parsing by AI assistants or scripts.

Requirements

  • Python 3.8+
  • ARM GCC toolchain (arm-none-eabi-gcc)
  • pyserial (pip install pyserial) for device communication
  • Optional: Ghidra for decompilation

Installation

cd Tools/WebServer
pip install pyserial

Global Options

fpb_cli.py [OPTIONS] <command> [args...]

Options:
  -v, --verbose              Enable verbose output
  --version                  Show version
  --port, -p <device>        Serial port (e.g., /dev/ttyACM0, COM3)
  --baudrate, -b <rate>      Serial baudrate (default: 115200)
  --elf <path>               Path to ELF file (global default)
  --compile-commands <path>  Path to compile_commands.json
  --tx-chunk-size <bytes>    TX chunk size for serial commands (0=disabled, default: 0)
  --tx-chunk-delay <secs>    Delay between TX chunks in seconds (default: 0.005)
  --max-retries <num>        Maximum retry attempts for file transfer (default: 10)
  --direct                   Force direct serial connection (skip WebServer proxy detection)
  --server-url <url>         WebServer URL for proxy mode (default: http://localhost:5500)

Commands

Offline Commands (No Device Required)

1. analyze - Analyze a function

fpb_cli.py analyze <elf_path> <func_name>

Output:

{
  "success": true,
  "analysis": {
    "func_name": "digitalWrite",
    "addr": "0x08001234",
    "signature": "void digitalWrite(uint8_t, uint8_t)",
    "asm_lines": 12
  }
}

2. disasm - Get disassembly

fpb_cli.py disasm <elf_path> <func_name>

Output:

{
  "success": true,
  "func_name": "digitalWrite",
  "disasm": "push {r7, lr}\nmov r7, sp\n...",
  "language": "arm_asm"
}

3. decompile - Decompile to pseudo-C

fpb_cli.py decompile <elf_path> <func_name>

Requires Ghidra. Set ghidra_path in config or ensure analyzeHeadless is in PATH.

4. signature - Get function signature

fpb_cli.py signature <elf_path> <func_name>

5. search - Search for functions

fpb_cli.py search <elf_path> <pattern>

Output:

{
  "success": true,
  "pattern": "gpio",
  "count": 5,
  "symbols": [
    {"name": "gpio_init", "addr": "0x08001000"},
    {"name": "gpio_write", "addr": "0x08001020"}
  ]
}

6. get-symbols - Get all symbols from ELF

More comprehensive than search — returns all symbol types via nm, with optional filtering.

fpb_cli.py get-symbols <elf_path> [--filter <pattern>] [--limit <num>]

Options:

  • --filter <pattern> — Case-insensitive substring filter (default: all symbols)
  • --limit <num> — Maximum results, 0 for unlimited (default: 0)

Output:

{
  "success": true,
  "count": 3,
  "total": 150,
  "symbols": [
    {"name": "gpio_init", "addr": "0x08001000", "type": "func"},
    {"name": "gpio_pin_map", "addr": "0x08002000", "type": "other"}
  ]
}

7. compile - Compile patch source (offline validation)

fpb_cli.py compile <source_file> --elf <elf> --compile-commands <path> [--addr <base_addr>]

Options:

  • --addr <base_addr> — Base address for patch code (default: 0x20001000)

Output:

{
  "success": true,
  "binary_size": 55,
  "base_addr": "0x20001000",
  "symbols": {"digitalWrite": "0x20001000"}
}

Connection Commands

8. connect - Connect to device

fpb_cli.py --port /dev/ttyACM0 connect

Establishes a serial connection. Required before any online command when using --direct mode.

9. disconnect - Disconnect from device

fpb_cli.py disconnect

10. server-stop - Stop CLI-launched WebServer

fpb_cli.py server-stop [--server-port <port>]

Terminates a WebServer background process that was auto-launched by the CLI. PID files are stored per-port in the WebServer directory (.cli_server_<port>.pid), so multiple CLI servers on different ports can coexist.

If --server-port is omitted and only one CLI server is running, it auto-detects. If multiple are running, you must specify which port to stop.

Output:

{"success": true, "message": "Server on port 5500 (PID 12345) terminated"}

If no CLI-launched server is running:

{"success": false, "error": "No CLI-launched server is running"}

Online Commands (Device Required)

11. info - Get device FPB info

fpb_cli.py --port /dev/ttyACM0 info

Output:

{
  "success": true,
  "info": {
    "slots": [...],
    "total_slots": 6,
    "active_slots": 0
  }
}

12. inject - Inject patch to device

fpb_cli.py --port <device> --elf <elf> --compile-commands <path> \
    inject <target_func> <source_file> [options]

Options:
  --mode <mode>   Patch mode: trampoline|debugmon|direct (default: trampoline)
  --comp <num>    FPB slot number (-1 for auto, default: -1)
  --verify        Verify patch after injection

Example:

fpb_cli.py --port /dev/ttyACM0 --elf firmware.elf \
    --compile-commands build/compile_commands.json \
    inject digitalWrite patch_digitalWrite.c

Output:

{
  "success": true,
  "result": {
    "compile_time": 0.03,
    "upload_time": 0.02,
    "total_time": 0.13,
    "code_size": 55,
    "inject_func": "digitalWrite",
    "target_addr": "0x08008608",
    "inject_addr": "0x20000250",
    "slot": 0,
    "patch_mode": "trampoline"
  }
}

13. unpatch - Remove patch

fpb_cli.py --port /dev/ttyACM0 unpatch --comp <slot>
fpb_cli.py --port /dev/ttyACM0 unpatch --all

14. test-serial - Test serial throughput

3-phase probing to find optimal transfer parameters.

fpb_cli.py --port /dev/ttyACM0 test-serial [options]

Options:
  --start-size <bytes>  Starting test size (default: 16)
  --max-size <bytes>    Maximum test size (default: 4096)
  --timeout <secs>      Timeout per test (default: 2.0)

Serial I/O Commands (Device Required)

15. serial-send - Send data to device

fpb_cli.py --port /dev/ttyACM0 serial-send <data> [options]

Options:
  --no-read          Don't read response after sending
  --timeout <secs>   Response read timeout (default: 1.0)

Output:

{
  "success": true,
  "sent": "ps",
  "response": "  PID GROUP PRI POLICY   TYPE    NPX STATE   ..."
}

WARNING: Avoid sending fl commands directly — use inject/unpatch/info instead.

16. serial-read - Read serial output

fpb_cli.py --port /dev/ttyACM0 serial-read [options]

Options:
  --timeout <secs>   How long to wait for data (default: 1.0)
  --lines <num>      Max log lines to return (default: 50)

Output:

{
  "success": true,
  "new_data": "Patched: pin=13 val=1\r\n",
  "log": ["Patched: pin=13 val=1"],
  "log_count": 1,
  "total_buffered": 1
}

Memory Access Commands (Device Required)

17. mem-read - Read device memory

fpb_cli.py --port /dev/ttyACM0 mem-read <addr> <length> [--fmt hex|raw|u32]

Example:

fpb_cli.py --port /dev/ttyACM0 mem-read 0x20000000 64 --fmt hex

18. mem-write - Write to device memory

fpb_cli.py --port /dev/ttyACM0 mem-write <addr> <hex_data>

Example:

fpb_cli.py --port /dev/ttyACM0 mem-write 0x20001000 DEADBEEF01020304

19. mem-dump - Dump memory to file

fpb_cli.py --port /dev/ttyACM0 mem-dump <addr> <length> <output_file>

Example:

fpb_cli.py --port /dev/ttyACM0 mem-dump 0x20000000 4096 /tmp/ram.bin

File Transfer Commands (Device Required)

20. file-list - List device directory

fpb_cli.py --port /dev/ttyACM0 file-list [path]

Default path is /.

21. file-stat - Get file info

fpb_cli.py --port /dev/ttyACM0 file-stat <path>

Returns size, modification time, and type (file/dir).

22. file-download - Download file from device

fpb_cli.py --port /dev/ttyACM0 file-download <remote_path> <local_path>

Transfers via chunked Base64 encoding with CRC verification.

Example:

fpb_cli.py --port /dev/ttyACM0 file-download /data/log.bin /tmp/log.bin

Typical Workflow

# Step 1: Search for target functions (offline)
fpb_cli.py search firmware.elf "write"

# Step 2: Analyze the target function
fpb_cli.py analyze firmware.elf digitalWrite

# Step 3: Get disassembly for understanding
fpb_cli.py disasm firmware.elf digitalWrite

# Step 4: Compile and validate patch offline
fpb_cli.py compile patch.c --elf firmware.elf --compile-commands build/compile_commands.json

# Step 5: Inject to device
fpb_cli.py --port /dev/ttyACM0 --elf firmware.elf \
    --compile-commands build/compile_commands.json \
    inject digitalWrite patch.c

# Step 6: Verify or rollback
fpb_cli.py --port /dev/ttyACM0 info
fpb_cli.py --port /dev/ttyACM0 unpatch --comp 0

Writing Patch Code

Create a source file with /* FPB_INJECT */ marker:

// patch_digitalWrite.c
#include <stdint.h>
#include <stdio.h>

/* FPB_INJECT */
__attribute__((section(".fpb.text"), used))
void digitalWrite(uint8_t pin, uint8_t val) {
    printf("Patched: pin=%d val=%d\r\n", (int)pin, (int)val);
}

Note: Calling the original function from injected code is NOT supported due to FPB hardware limitations.

Common Patch Patterns

Replace and log parameters:

/* FPB_INJECT */
__attribute__((section(".fpb.text"), used))
void myFunc(int a, int b) {
    printf("myFunc: a=%d, b=%d\r\n", a, b);
    // Original function is NOT called - this code replaces it entirely
}

Log and continue (trampoline mode):

// In trampoline mode, after your inject function returns,
// the original function is automatically called
/* FPB_INJECT */
__attribute__((section(".fpb.text"), used))
void myFunc(int a, int b) {
    printf("myFunc called\r\n");
    // Trampoline will redirect to original automatically
}

Skip original function:

/* FPB_INJECT */
__attribute__((section(".fpb.text"), used))
void myFunc(void) {
    // Return without doing anything - effectively disables the function
    return;
}

Output Format

All commands return JSON:

// Success
{"success": true, "data": {...}}

// Error
{"success": false, "error": "Error message"}

Tips for AI Agents

  1. Always check the success field before processing results
  2. Use jq for parsing complex JSON outputs:
    fpb_cli.py search firmware.elf gpio | jq '.symbols[].name'
  3. Use -v verbose mode for debugging
  4. FPB slots range from 0-5 (6 slots typical)
  5. Patch functions MUST include the /* FPB_INJECT */ comment marker
  6. Use \r\n line endings for proper serial output display

Related Documentation