diff --git a/WEEK04/WEEK04-BN.md b/WEEK04/WEEK04-BN.md index e683e97..637f646 100644 --- a/WEEK04/WEEK04-BN.md +++ b/WEEK04/WEEK04-BN.md @@ -114,7 +114,7 @@ file "$(which cmake)" To make it permanent, add that `export` to `~/.zshrc`. Do not use Rosetta as a fix; OpenOCD and GDB are exactly the kind of programs where a translation layer produces failures that look like debugger bugs. -**`telnet` is special.** macOS no longer ships `telnet`, and the Homebrew build is often the Intel one, so `telnet 127.0.0.1 4444` fails with `bad CPU type in executable`. Your `brew` command itself may also be the Intel one: if `brew install telnet` fails with `.../portable-ruby/.../ruby: Bad CPU type in executable`, you are running the Intel Homebrew. Call the Apple Silicon Homebrew explicitly: +**`telnet` is special — and optional.** The GDB MI workflow does not need it; it is only used by the command-port fallback. macOS no longer ships `telnet`, and the Homebrew build is often the Intel one, so `telnet 127.0.0.1 4444` fails with `bad CPU type in executable`. Your `brew` command itself may also be the Intel one: if `brew install telnet` fails with `.../portable-ruby/.../ruby: Bad CPU type in executable`, you are running the Intel Homebrew. Call the Apple Silicon Homebrew explicitly: ```bash /opt/homebrew/bin/brew install telnet @@ -443,189 +443,50 @@ Bit 0 of a vector is the Thumb bit, so `0x1000015d` means "start at `0x1000015c` **The middle `blx` at `0x1000018c` is the call to `main`.** `platform_entry` is byte-identical in both projects, so `0x1000018c` catches `main` no matter where the linker placed it. The literal pool at `0x100001dc` holds `main | 1`, and clearing bit 0 gives `0x10000234`. -### Step 13: Set a hardware breakpoint (from OpenOCD, because the GUI cannot) +### Step 13: Set a hardware breakpoint in the GUI -#### First: decide where you want to stop +With the **GDB MI** adapter, Binary Ninja sets breakpoints through real GDB, which sends the correct 2-byte length, so you set them **in the UI**. There is no command port here. -There are two different jobs, and they use **different addresses and different methods**. Mixing them up is the most common source of confusion in this lab. +> **Why older drafts used the command port.** Binary Ninja's **GDB RSP** adapter is its own minimal RSP client and sends a **1-byte** breakpoint (`Z0,,1`); the Cortex-M33 FPB comparators need 2 bytes, so OpenOCD rejected it with `only breakpoints of two bytes length supported`. The old workaround was to arm breakpoints by hand over telnet. **The GDB MI adapter does not have this problem** — it drives real `arm-none-eabi-gdb`, which sends the right length. So everything below is done in the GUI. The command port still exists as a fallback (see the end of this step), but you do not need it. + +#### Where you can stop | You want to stop at | Address | How | Repeatable? | | --- | --- | --- | --- | -| **`main`** | `0x10000234` | Start the server with `BP_ADDR=0x10000234 ./debug-server.sh`, then connect Binary Ninja. **No `nc`, no `bp`.** | No — `main` runs once per reset, so this is a one-shot first stop. | -| **The loop** (`printf` call) | Project 1 `0x1000023e`, Project 2 `0x1000024e` | Connect Binary Ninja **first**, then `nc 127.0.0.1 4444` and `bp 2 hw`, then click **Resume**. | Yes — fires on every iteration. | +| **`main`** | `0x10000234` | The server starts parked there with `BP_ADDR=0x10000234` (Step 10), so Binary Ninja is already stopped at `main` when it connects. | No — `main` runs once per reset. | +| **The loop** (`printf` call) | Project 1 `0x1000023e`, Project 2 `0x1000024e` | Set a hardware breakpoint in the GUI, then click **Resume**. | Yes — fires on every iteration. | -**For `main`, use `BP_ADDR`.** It is one command and Binary Ninja shows `Stopped at 0x10000234` immediately when it connects: +#### Set the loop breakpoint in the GUI -```bash -BP_ADDR=0x10000234 ./debug-server.sh -``` +1. Press `G`, type the loop address (`0x1000023e` for Project 1, `0x1000024e` for Project 2), and press Enter. +2. Set a **hardware execution** breakpoint at that address, either way: + - `Debugger -> Add Hardware Breakpoint...`, or + - click the line and press `F2` (`Debugger -> Toggle Breakpoint`). +3. Click **Resume**. The core is already running the loop, so the breakpoint fires on the next iteration. Binary Ninja stops with the PC at the loop address and reports it as a **Breakpoint** — verified: `Stopped (Breakpoint) at 0x1000023e`. -Then `Debugger -> Connect to Remote Process -> GDB RSP -> Accept`. That is the whole procedure for `main`. +> **No breakpoints before you connect.** With GDB MI, a breakpoint set before the connection hangs the session (Step 11). Start parked with `BP_ADDR`, connect, *then* add breakpoints. -**There is a trap if you instead arm `main` over the command port while Binary Ninja is already connected.** It does stop the core at `0x10000234` (verified: `pc=0x10000234`), but Binary Ninja's status bar keeps showing its *previous* stop location, e.g. `Stopped (InitialBreakpoint) at 0x1000320c`, because it never saw a stop event for `main`. The core and the display disagree. To resync Binary Ninja you must **Detach and reconnect** (verified: the status then reads `Stopped (InitialBreakpoint) at 0x10000234`). `BP_ADDR` avoids this entirely because Binary Ninja connects while the core is already parked at `main`, so the first thing it reads is the truth. +#### Stepping -Everything below is the loop workflow, which is what you want for stepping and for the live `r1` hack. +With the target halted at the breakpoint, **Step Into** (`F7`) and **Step Over** (`F8`) run through real GDB and move the PC. Verified: `0x1000023e -> 0x100030e4 -> 0x100030e6 -> ...`. -> **Known Binary Ninja bug (2026-10-02, BN 6.0.10601): you cannot set a breakpoint from the GUI on this target.** Binary Ninja sends the RSP packet `Z0,10000234,1` — a **1-byte** breakpoint. The Cortex-M33 FPB comparators are halfword-based, so OpenOCD rejects it: -> -> ``` -> Info : cortex_m.c:1908 cortex_m_add_breakpoint(): [rp2350.dap.core0] only breakpoints of two bytes length supported -> Error: breakpoints.c:86 breakpoint_add_internal(): [rp2350.dap.core0] can't add breakpoint: resource not available -> ``` -> -> This affects **every** address and **both** methods below — `Debugger -> Toggle Breakpoint` (`F2`) and `Debugger -> Add Hardware Breakpoint...` (`F3`) with **Type** `Hardware Execute`. The dialog's **Size** field is disabled and hard-coded to `1`, so there is no UI escape hatch, and `gdb_breakpoint_override hard` changes nothing (both the `Z0` and `Z1` paths end in the same rejected call). Plain GDB works because `hbreak` sends a length of `2`. +> **Step Over on the raw `.bin` steps *into* calls.** The raw image has no symbol for `__wrap_printf`, so **Step Over** at the `printf` call behaves like **Step Into**. When the lab needs to execute the call and then stop, it moves the breakpoint to the return site and clicks **Resume** instead (Step 14 shows this). -**Do not fight the dialog. Arm the breakpoint from the OpenOCD command port instead.** The order matters — see the warning below. +> **Never use Binary Ninja's Restart button.** On RP2350 it resets and halts inside the boot ROM (`pc=0x88`, `sp=0xf0000000`). To reset cleanly, restart the server with `BP_ADDR` and reconnect. -1. Leave Binary Ninja connected (Step 11). Do **not** detach. -2. Open the OpenOCD command port in a second terminal: - - ```sh - nc 127.0.0.1 4444 # or: telnet 127.0.0.1 4444 - ``` - -3. Arm a 2-byte **execute** breakpoint at `main`: - - ``` - bp 0x10000234 2 hw - ``` - - `bp 2 hw` sets an execute-type hardware comparator. - -4. **Do not run `reset run` from the port while Binary Ninja is connected.** `main` runs once per reset, so a breakpoint on `main` only fires if the core resets *after* it is armed — but a reset driven from the command port makes Binary Ninja miss the stop event. The target halts at `0x10000234` while Binary Ninja's view keeps showing wherever it last stopped, so the Step/Resume buttons act on the wrong address. This is the single most common "it worked for a second and then stopped" symptom. The reliable way to stop at `main` is to arm it **before** Binary Ninja connects, with `BP_ADDR=0x10000234` (Step 10): Binary Ninja's first read is then already the truth. If you did reset while connected, **Detach and reconnect** to resync (verified: the status line then reads `Stopped at 0x10000234`). - -> **Arm the breakpoint only *after* Binary Ninja is connected.** Any breakpoint set before a client attaches is destroyed the moment that client connects. OpenOCD logs it explicitly: -> -> ``` -> Info : accepting 'gdb' connection on tcp/3333 -> Debug: breakpoints.c:328 breakpoint_remove_all_internal(): [rp2350.dap.core0] Delete all breakpoints -> ``` -> -> This affects every arrangement: -> -> - **`hbreak` in GDB, then `detach`** — detaching zeroes the comparators; `mdw 0xE0002000` reads back all zeros. -> - **Arming over telnet before Binary Ninja connects** — the connect flushes it. -> - **`BP_ADDR` on the startup command line (Step 10)** — it does fire and does park the core at your address, but it is flushed on connect, so it is a single-use first stop. Verified by reading the comparators: `0x10000235` before attach, all zeros after. -> -> Attach first, then arm. Verified working at `0x10000234` and again at `0x1000023e`. -> -> #### What "attach first, then arm" actually means -> -> Two different channels are in play, and they are easy to confuse: -> -> | Channel | Port | What it is | How you use it | -> | --- | --- | --- | --- | -> | GDB server | `3333` | What Binary Ninja talks to | You never type in this one. BN connects to it via the GUI. | -> | Telnet command port | `4444` | A plain text prompt for driving OpenOCD by hand | You type commands here, at an `OpenOCD>` prompt. | -> -> So the sequence is literally: -> -> 1. **Terminal 1** — run `./debug-server.sh` and leave it running. -> 2. **Binary Ninja GUI** — the menu bar has **Debugger -> Connect to Remote Process**. Pick **GDB RSP** in the adapter dropdown, then **Accept**. (This is the "attach" step. It is a GUI menu item, not something you type at a prompt.) -> 3. **Terminal 2** — open the command port and get a prompt: -> ``` -> nc 127.0.0.1 4444 -> ``` -> You should see an `OpenOCD>` prompt. -> 4. **At that prompt**, type this one line and press Enter: -> ``` -> bp 0x1000023e 2 hw -> ``` -> Expect `breakpoint set at 0x1000023e`. Then click **Resume** in Binary Ninja and the loop breakpoint fires on the next iteration. -> -> **What the command means, and what `rbp` is for:** -> -> | Command | Full name | What it does | -> | --- | --- | --- | -> | `bp 2 hw` | **b**reak**p**oint | Arms a hardware breakpoint at that address. The `2` is the instruction length in bytes, and `hw` means hardware rather than software. | -> | `rbp ` | **r**emove **b**reak**p**oint | Deletes the breakpoint at that address. Address only — no `2`, no `hw`. | -> | `rbp all` | remove all | Deletes every breakpoint. | -> -> The `2` is not optional decoration. Binary Ninja sends `1`, and Cortex-M rejects that with `only breakpoints of two bytes length supported`, which is the whole reason this lab arms breakpoints here instead of using the GUI. -> -> Use `0x1000024e` instead of `0x1000023e` on Project 2. -> -> **`no breakpoint at address ... found` is not a problem.** You will only need `rbp` to *move* a breakpoint you set earlier. On a fresh run there is nothing to remove, and `rbp` answers with: -> -> ``` -> [rp2350.dap.core0] no breakpoint at address 0x10000234 found -> Error during removal of breakpoint at address 0x10000234 -> ``` -> -> That is OpenOCD saying "there was nothing there", not a failure. Ignore it and carry on with the `bp` line. Confirmed live: after that error, `bp 0x1000023e 2 hw` armed cleanly and the comparator read `0x1000023f`. -> 5. When you are done, `quit` at the `OpenOCD>` prompt. Closing the prompt does not kill the server. -> -> To confirm the breakpoint is really armed, run `mdw 0xE0002000 4` at the prompt. You want `0x1000023f` in the third word — that is `0x1000023e | 1`, where the low bit marks the address as Thumb. An all-zero result means it got wiped, which means you armed it before Binary Ninja connected. -> -> Verified live: comparator read `1000023f` after arming, `00000000` the moment Binary Ninja connected (proving the wipe), then `1000023f` again after re-arming over the prompt. **Resume** landed at `pc=0x1000023e, r1=0x2b` and re-caught on every subsequent **Resume**. - -> **What you will and will not see.** Binary Ninja labels these stops `SingleStep` rather than `Breakpoint`, because it has no idea a breakpoint exists, and the **Breakpoints** widget stays empty. That is expected and harmless — the core really is halted on a hardware comparator you armed. To confirm what is armed, read the FPB comparator registers on the command port: each armed breakpoint appears at `0xE0002008 + 4n` as `
`. - -> **Never use Binary Ninja's Restart button.** On RP2350 it resets and halts inside the boot ROM (`pc=0x88`, `sp=0xf0000000`). To reset cleanly, use `BP_ADDR` on a fresh server start, or **Detach**, send `reset run` from the command port, and reconnect — never `reset run` while attached (it desyncs Binary Ninja's view; see Step 13). - -> **You often do not need a reset.** `main` is an infinite loop, so its body from `0x1000023a` to `0x10000242` runs forever. Arm a breakpoint inside that loop, such as the `printf` call at `0x1000023e`, then click **Resume** in Binary Ninja — it fires on the next iteration with no reset at all. Step 14 uses exactly that. - -#### Stepping: two bugs that stop it working, and the fixes - -If **Step Into** / **Step Over** in Binary Ninja do nothing — the PC stays exactly where it is, no matter how many times you click — there are two independent causes, both confirmed on this setup by reading the OpenOCD GDB log (`log_output ` + `debug_level 3`). - -**Cause 1: the `hwthread` RTOS makes OpenOCD fake the step.** `target/rp2350.cfg` creates core0 with `-rtos hwthread`, which registers a fake RTOS whose current thread is `coreid + 1 = 1`. Binary Ninja single-steps with the packet `vCont;s` and no thread id, i.e. thread 0. OpenOCD's `gdb_server.c` sees `rtos->current_thread (1) != thread_id (0)` and takes its "fake step" path, sending a stop reply **without ever stepping the core**: - -``` -Debug: gdb_server.c:3094 gdb_handle_vcont_packet(): target rp2350.dap.core0 single-step thread 0 -Debug: gdb_server.c:3112 gdb_handle_vcont_packet(): fake step thread 0 -Debug: gdb_server.c:397 gdb_log_outgoing_packet(): sending packet: $T05thread:0000000000000000;#a6 -``` - -The fix is to drop the RTOS. `debug-server.sh` and `debug-server.ps1` now pass this automatically, right after the target config is read: - -``` -rp2350.dap.core0 configure -rtos none -``` - -If you start OpenOCD by hand or with an older copy of the script, add that line. With the RTOS gone, `vCont;s` reaches `cortex_m_step()` and the core really moves. - -**Cause 2: a breakpoint sitting on the current PC blocks stepping.** Binary Ninja's step is passed to OpenOCD as a step *over a breakpoint* (`target_step(..., current_pc=1, ...)`). When a breakpoint is already armed at the address you are halted on, OpenOCD tries to add its own breakpoint at that same address and fails: - -``` -Error: breakpoints.c:56 breakpoint_add_internal(): [rp2350.dap.core0] Duplicate Breakpoint address: 0x1000023e (BP 9) -Debug: cortex_m.c:884 cortex_m_debug_entry(): entered debug state ... at PC 0x1000023e -``` - -The core steps and immediately re-traps on the same comparator, so the PC appears not to move. The fix is to **remove the breakpoint before you step**: - -``` -rbp 0x1000023e -``` - -Then click **Step Into** or **Step Over**; the PC advances normally. Verified live: after `rbp 0x1000023e`, Step Into walked `0x1000023e -> 0x100030e4 -> 0x100030e6 -> 0x100030e8 -> ...`. This is why Step 14 below removes the breakpoint before stepping over the `printf` call. - -> **Note:** both causes look identical from the GUI — a click that does nothing. The PC never moving, with no error dialog, is the signature. Check the OpenOCD log for `fake step` (cause 1) or `Duplicate Breakpoint` (cause 2) to tell them apart. +> **If you ever need the command port.** It is still there — `nc 127.0.0.1 4444`, and `bp 2 hw` still arms a breakpoint, `rbp ` / `rbp all` still remove them. It is the fallback if you switch back to the **GDB RSP** adapter, whose 1-byte breakpoints the GUI cannot set. With GDB MI you do not need it for this lab. ### Step 14: HACK IT LIVE — change the printed value `main` loads the constant `0x2b` (43) into `r1` and calls `printf` on every iteration. We break on that call in the GUI and change it live. 1. Press `G`, go to `0x1000023e` (the `bl __wrap_printf`). -2. In the terminal, connect to the OpenOCD command port if you are not already there: - ``` - nc 127.0.0.1 4444 - ``` - Then type this line at the `OpenOCD>` prompt and press Enter: - ``` - bp 0x1000023e 2 hw - ``` - Expect `breakpoint set at 0x1000023e`. If you previously armed a breakpoint elsewhere, clear it first with `rbp ` (`rbp all` clears every one). `rbp` means *remove breakpoint* and takes an address only; running it when nothing is armed prints `no breakpoint at address ... found`, which is harmless. -3. Click **Resume** in Binary Ninja. The target is already running the loop, so the comparator fires on the next iteration. Binary Ninja stops with the program counter at `0x1000023e` and `r1 = 0x2b`. +2. Set a hardware execution breakpoint there: `Debugger -> Add Hardware Breakpoint...`, or click the line and press `F2`. +3. Click **Resume** in Binary Ninja. The target is already running the loop, so the breakpoint fires on the next iteration. Binary Ninja stops with the program counter at `0x1000023e` and `r1 = 0x2b`. 4. Open the **Registers** widget (bug icon -> **Registers**). 5. Find `r1`. Its value is `0x2b`. 6. **Double-click the value, type `46`, and press Enter.** Binary Ninja parses the new value as hex, so `46` means `0x46` (70). The edited value turns **orange**. -7. **Move the breakpoint past the call, then Resume.** You want `printf` to run once and then stop, so put the breakpoint on the instruction *after* the call. At the `OpenOCD>` prompt: - ``` - rbp 0x1000023e - bp 0x10000242 2 hw - ``` - `0x10000242` is the `b.n` that closes the loop. Two reasons not to just click **Step Over** here: a breakpoint left on the current PC re-traps the step (see the stepping note in Step 13), and Binary Ninja's **Step Over** steps *into* `__wrap_printf` on this raw `.bin` because the image carries no symbol for the call. Moving the breakpoint to the return site is deterministic. +7. **Move the breakpoint past the call.** You want `printf` to run once and then stop, so move the breakpoint from `0x1000023e` to the instruction *after* the call, `0x10000242` (the `b.n` that closes the loop): remove the breakpoint at `0x1000023e` and set a hardware breakpoint at `0x10000242`. Two reasons not to just click **Step Over** here: a breakpoint left on the current PC re-traps the step, and Binary Ninja's **Step Over** steps *into* `__wrap_printf` on this raw `.bin` because the image carries no symbol for the call. Moving the breakpoint to the return site is deterministic. 8. Click **Resume** in Binary Ninja. The core executes `bl __wrap_printf` with `r1 = 0x46`, so this iteration prints `age: 70`, then stops at `0x10000242`. 9. Look at your serial monitor — the `screen` session on the Pico's USB serial port — and at the **Target** tab in Binary Ninja: @@ -648,12 +509,7 @@ The text `"age: %d\r\n"` lives in flash (`.rodata`) at `0x100034a0`, and flash i ``` That writes the bytes `66 6f 6f 3a 20 25 64 0d 0a 00` = `"foo: %d\r\n\0"` (three little-endian words). 3. In the **Registers** widget, double-click `r0` and set it to `0x20080000`. It turns orange. -4. Move the breakpoint past the call and Resume: - ``` - rbp 0x1000023e - bp 0x10000242 2 hw - ``` - The core runs `printf` with `r0` pointing at your RAM string and `r1 = 0x2b`, so this iteration prints: +4. Move the breakpoint past the call in the GUI (remove it at `0x1000023e`, set one at `0x10000242`) and click **Resume**. The core runs `printf` with `r0` pointing at your RAM string and `r1 = 0x2b`, so this iteration prints: ``` foo: 43 ``` @@ -868,19 +724,19 @@ Part 4 left the Pico running the patched Project 1 image. Put the original Proje 3. Start the debug server again (Step 10) and wait for `Listening on port 3333`. 4. Open `0x0008_uninitialized-variables/build/0x0008_uninitialized-variables.bin` with options (`thumb2`, `thumb2`, `0x10000000`) and save a `.bndb`. -5. Connect Binary Ninja again (Step 11): adapter **GDB RSP**, IP `127.0.0.1`, port `3333`. +5. Connect Binary Ninja again (Step 11): adapter **GDB MI**, IP `127.0.0.1`, port `3333`. Confirm the Pico prints `age: 0` and blinks the red LED. ### Step 23: Break at `main` -`main` is at `0x10000234` in this project too. The GUI cannot set breakpoints here (Step 13), and you should not use `reset run` from the port while Binary Ninja is attached (it desyncs Binary Ninja's view — Step 13). Use `BP_ADDR`, which arms `main` before Binary Ninja connects: +`main` is at `0x10000234` in this project too. The GUI sets breakpoints fine (Step 13); the only caution is not to drive `reset run` from the port while Binary Ninja is attached (it desyncs Binary Ninja's view). Use `BP_ADDR`, which arms `main` before Binary Ninja connects: 1. Stop the server (Ctrl-C), then start it parked at `main`: ``` BP_ADDR=0x10000234 ./debug-server.sh ``` -2. Connect Binary Ninja (Step 11): adapter **GDB RSP**, IP `127.0.0.1`, port `3333`. +2. Connect Binary Ninja (Step 11): adapter **GDB MI**, IP `127.0.0.1`, port `3333`. The target is already halted at `main` when Binary Ninja connects, and the sidebar reads `Stopped at 0x10000234`. @@ -930,15 +786,10 @@ Step Over through `0x10000254` (`mcrr 0, 4, r4, r5, cr0`) and watch the SIO outp ### Step 25: HACK IT LIVE — change the printed value 1. Press `G`, go to `0x1000024e` (the `bl __wrap_printf`). -2. In the terminal, connect to the OpenOCD command port if you are not already there: `nc 127.0.0.1 4444`. Then at the `OpenOCD>` prompt type `bp 0x1000024e 2 hw` and press Enter. Note `0x1000024e` — Project 2's loop sits at a different address than Project 1's. -3. Click **Resume** in Binary Ninja. The target is already looping, so the comparator fires on the next pass. Binary Ninja stops with `r1 = 0`. +2. Set a hardware execution breakpoint at `0x1000024e` in the GUI (`Debugger -> Add Hardware Breakpoint...`, or click the line and press `F2`). Note `0x1000024e` — Project 2's loop sits at a different address than Project 1's. +3. Click **Resume** in Binary Ninja. The target is already looping, so the breakpoint fires on the next pass. Binary Ninja stops with `r1 = 0`. 4. In the **Registers** widget, double-click `r1`, type `42`, and press Enter (`0x42` = 66). The value turns orange. -5. **Move the breakpoint past the call, then Resume.** At the `OpenOCD>` prompt type: - ``` - rbp 0x1000024e - bp 0x10000252 2 hw - ``` - `0x10000252` is the instruction right after the `bl __wrap_printf`. Then click **Resume**. The core runs `printf` with `r1 = 0x42` and stops at `0x10000252`. (Not **Step Over** — it steps into the call on this symbol-less `.bin`, and a breakpoint left on the current PC re-traps the step; Step 13 explains both.) +5. **Move the breakpoint past the call.** `0x10000252` is the instruction right after the `bl __wrap_printf`. Remove the breakpoint at `0x1000024e` and set a hardware breakpoint at `0x10000252`, then click **Resume**. The core runs `printf` with `r1 = 0x42` and stops at `0x10000252`. (Not **Step Over** — it steps into the call on this symbol-less `.bin`, and a breakpoint left on the current PC re-traps the step; Step 13 explains both.) 6. Look at your serial monitor and the **Target** tab: ``` @@ -960,12 +811,7 @@ Same idea as Project 1, different addresses. Here the format string is at `0x100 ``` Bytes `66 6f 6f 3a 20 25 64 0d 0a 00` = `"foo: %d\r\n\0"`. 3. In the **Registers** widget, set `r0` to `0x20080000`. -4. Move the breakpoint past the call and Resume: - ``` - rbp 0x1000024e - bp 0x10000252 2 hw - ``` - This iteration prints: +4. Move the breakpoint past the call in the GUI (remove it at `0x1000024e`, set one at `0x10000252`) and click **Resume**. This iteration prints: ``` foo: 0 ``` @@ -1147,28 +993,29 @@ The **green LED on GPIO 17** now blinks instead of the red one. | Enable hex editing | Toggle the lock in the status bar | | Reanalyze after a patch | Right-click function -> `Reanalyze` | | Edit a register live | Double-click the value in the **Registers** widget, type hex, Enter | -| Set a breakpoint | **Command port only** — `bp 2 hw`. The GUI cannot set breakpoints (Step 13). | -| Move a breakpoint | `rbp ` then `bp 2 hw`. `rbp` takes an address only. | -| Confirm what is armed | `mdw 0xE0002000 8` — each armed breakpoint shows as `` | +| Set a breakpoint | Click the line and press `F2`, or `Debugger -> Add Hardware Breakpoint...` (GDB MI adapter) | +| Move a breakpoint | Remove it and set it at the new address in the GUI (command-port fallback: `rbp ` then `bp 2 hw`) | +| Confirm what is armed | The **Breakpoints** widget lists it (command-port fallback: `mdw 0xE0002000 8`, each armed breakpoint shows as ``) | | Apply the ELF symbol map | Paste the Python snippet from Step 16 / 26 into the Python Console | ### OpenOCD server and reset -The server runs with `gdb_breakpoint_override hard` so that flash-writes are never attempted, but that flag is **not** what makes breakpoints work — Binary Ninja's own breakpoints are rejected on length grounds before this setting matters (Step 13). Breakpoints in this lab are armed over the command port with `bp 2 hw`, which bypasses the GUI entirely. +The server runs with `gdb_breakpoint_override hard` so that flash-writes are never attempted. Breakpoints in this lab are set in the Binary Ninja GUI through the **GDB MI** adapter (Step 13). The command-port rows below are the fallback if you use the **GDB RSP** adapter instead. | Action | Command | | ------ | ------- | -| Connect to the OpenOCD prompt | `nc 127.0.0.1 4444` (or `telnet 127.0.0.1 4444`) | -| Reset and run | `reset run` | -| Check core state | `targets` | -| Add a breakpoint without the GUI | `bp 2 hw` | +| Connect to the OpenOCD prompt (fallback) | `nc 127.0.0.1 4444` (or `telnet 127.0.0.1 4444`) | +| Reset and run (command port) | `reset run` | +| Check core state (command port) | `targets` | +| Set a breakpoint in the GUI | `Debugger -> Add Hardware Breakpoint...`, or click the line and press `F2` | +| (fallback) Add a breakpoint without the GUI | `bp 2 hw` | | Remove one breakpoint | `rbp ` — **address only, no length, no `hw`** | | Remove every breakpoint | `rbp all` | | Start the server parked at `main` | `BP_ADDR=0x10000234 ./debug-server.sh` (one-shot; `$env:BP_ADDR` on Windows) | | Start the server parked in the loop | `BP_ADDR=0x1000023e ./debug-server.sh` — `0x1000024e` for Project 2 | -| Break on the loop in a running target | connect first, then `rbp `, `bp 0x1000023e 2 hw` (or `0x1000024e` on Project 2), then **Resume** — repeatable | +| Break on the loop in a running target | set a hardware breakpoint in the GUI at the loop address, then **Resume** — repeatable | | Make Binary Ninja stepping work | `rp2350.dap.core0 configure -rtos none` (already in the scripts) | -| Step without re-trapping | `rbp ` first, then **Step Into**/**Step Over** | +| Step without re-trapping | move the breakpoint off the current PC first, then **Step Into**/**Step Over** | | Reset without desyncing Binary Ninja | **Detach**, `reset run` on the port, reconnect — never `reset run` while attached | ### Every address and byte we changed @@ -1203,17 +1050,21 @@ The server runs with `gdb_breakpoint_override hard` so that flash-writes are nev ### Binary Ninja hangs or crashes when you connect (macOS 27) -On macOS 27 with Binary Ninja 6.0.10601, the **GDB MI** and **LLDB** adapters abort inside the debugger core or hang forever at `0%` on "The debugger is connecting to the target and preparing the debugger binary view." The crash signature is a macOS crash report for `binaryninja` with `EXC_CRASH (SIGABRT)` and a stack ending in `libdebuggercore.dylib` -> `std::terminate()` -> `abort()`. For the hang, `lsof` shows that GDB *did* connect to OpenOCD (`.../arm-none-eabi-gdb --interpreter=mi2` to `127.0.0.1:3333`, `ESTABLISHED`) and the target halted, yet Binary Ninja never progresses; `sample ` shows it blocked in `libdebuggercore.dylib`/`libdebuggerui.dylib`. A related issue, [Vector35/debugger #1098](https://github.com/Vector35/debugger/issues/1098), was the bundled **LLDB** crashing on macOS 27 and was fixed before 6.0. +Three different causes have been seen on this setup; check them in this order. -**Use the GDB RSP adapter.** It is a different code path and it connects cleanly on this setup, so Step 11 already assumes it. If it ever fails, fall back to `arm-none-eabi-gdb` against the same server — the addresses and register values are identical to the GUI steps — and report the GUI failure at . +- **A breakpoint set before connecting.** With the **GDB MI** adapter, if the binary view already has a breakpoint, the session hangs. Start parked with `BP_ADDR`, connect, then add breakpoints (see the next entry). +- **The wrong GDB executable.** Point **Full GDB Executable Path** at the **14.2.rel1** toolchain (Step 11). The 13.3.rel1 build (what `/opt/homebrew/bin/arm-none-eabi-gdb` symlinks to) did not connect in testing. +- **The LLDB adapter.** A crash report with `libdebuggercore.dylib -> std::terminate() -> abort()` and `liblldb` in the stack is the **LLDB** adapter, not GDB MI. Avoid LLDB on this setup. -If Binary Ninja hangs, you must force-quit it; the connect dialog has no working Cancel. The static steps (resolve, patch, export, flash) never touch the debugger and always work. +**Use GDB MI**, with the 14.2.rel1 path above. If it still fails, fall back to plain `arm-none-eabi-gdb` against the same server — the addresses and register values are identical to the GUI steps. -### The GUI refuses to set a breakpoint +If Binary Ninja hangs, force-quit it; the connect dialog has no working Cancel. The static steps (resolve, patch, export, flash) never touch the debugger and always work. -This is a **Binary Ninja bug, not a misconfiguration**, and it is expected on BN 6.0.10601. Binary Ninja sends `Z0,,1`; the Cortex-M33 comparators require 2 bytes, so OpenOCD answers `only breakpoints of two bytes length supported`. It affects every address, both `Toggle Breakpoint` and `Add Hardware Breakpoint`, and the dialog's **Size** field is disabled. Set `gdb_breakpoint_override` either way — no effect. +### The GUI refuses to set a breakpoint (GDB RSP adapter only) -Work around it by arming the breakpoint from the OpenOCD command port **after** Binary Ninja is connected. Full procedure in Step 13. +If you are on the **GDB RSP** adapter, the GUI cannot set breakpoints on this target. That adapter is Binary Ninja's own minimal RSP client and sends a **1-byte** breakpoint (`Z0,,1`); the Cortex-M33 comparators need 2 bytes, so OpenOCD answers `only breakpoints of two bytes length supported`. It affects every address, both `Toggle Breakpoint` and `Add Hardware Breakpoint`, and the dialog's **Size** field is disabled. `gdb_breakpoint_override` makes no difference. + +**Fix: use the GDB MI adapter** (Step 11). It drives real GDB, which sends the correct length, so GUI breakpoints just work. If you must stay on GDB RSP, arm breakpoints from the command port after connecting (`bp 2 hw`) — but the lab uses GDB MI and does not need that. ### GDB MI hangs when you connect (a breakpoint already existed) @@ -1227,12 +1078,12 @@ Never have a breakpoint in the binary view before the GDB MI connection. If it h ### Step Into / Step Over does nothing (PC never moves) -Two independent causes, both fixed. Full explanation in Step 13. +Two causes have been seen on this target. -1. **`hwthread` RTOS makes OpenOCD fake the step.** Binary Ninja sends `vCont;s` with thread id 0; the RP2350 config's `-rtos hwthread` makes the current thread id 1, so OpenOCD logs `fake step thread 0` and replies with a stop without stepping. Fix: `rp2350.dap.core0 configure -rtos none`. The launcher scripts already pass this. -2. **A breakpoint on the current PC re-traps the step.** OpenOCD's step-over-breakpoint logic fails with `Duplicate Breakpoint address` and the PC stays put. Fix: `rbp ` before stepping. +1. **A breakpoint on the current PC re-traps the step.** OpenOCD's step-over-breakpoint logic fails with `Duplicate Breakpoint address` and the PC stays put. Fix: move the breakpoint off the current PC (in the GUI), then step. +2. **The `hwthread` RTOS (GDB RSP adapter only).** With the **GDB RSP** adapter, OpenOCD can log `fake step thread 0` and reply without stepping, because the RP2350 config's `-rtos hwthread` makes the current thread id 1 while Binary Ninja sends thread id 0. Fix: `rp2350.dap.core0 configure -rtos none` (the launcher scripts already pass this). **GDB MI does not hit this.** -To tell them apart, turn on OpenOCD logging (`log_output /tmp/ocd.log` then `debug_level 3` on the command port) and look for `fake step` versus `Duplicate Breakpoint`. +To tell them apart, turn on OpenOCD logging (`log_output /tmp/ocd.log`, then `debug_level 3` on the command port) and look for `fake step` versus `Duplicate Breakpoint`. ### `zsh: bad CPU type in executable: cmake` @@ -1244,7 +1095,7 @@ You probably built `Debug`. This lesson is a `Release` build. Re-run Step 3 with ### A breakpoint never fires -First, confirm you actually armed one. The GUI cannot set breakpoints on this target (Step 13): Binary Ninja sends `Z0,,1` and OpenOCD rejects the 1-byte length, so `Debugger -> Toggle Breakpoint` (`F2`) and `Debugger -> Add Hardware Breakpoint...` (`F3`) both fail with `only breakpoints of two bytes length supported` and nothing lands in the **Breakpoints** widget. Arm it on the command port instead. +First, confirm you actually set one. With the **GDB MI** adapter the GUI sets breakpoints normally (Step 13), so `Debugger -> Toggle Breakpoint` (`F2`) or `Debugger -> Add Hardware Breakpoint...` should land in the **Breakpoints** widget. If it does not, check that you are on **GDB MI**, not **GDB RSP** — the GDB RSP adapter cannot set breakpoints on this target. Then check the order and the state: @@ -1252,20 +1103,20 @@ Then check the order and the state: - **Verify it is armed:** `mdw 0xE0002000 8`. You should see your address with the low bit set (`0x10000234` -> `0x10000235`). All zeros means nothing is armed — re-read this first, because it distinguishes "not armed" from "armed but never reached". - **Is the core running?** `poll` on the command port should not report a halt. If it is stopped, click **Resume**. - **Does the address get reached again?** `main` runs once per reset, so use `BP_ADDR` at startup (Step 10) rather than `reset run` while attached. Loop addresses such as `0x1000023e` fire on the next pass with no reset — arm them and click **Resume** in Binary Ninja. -- **Binary Ninja reports these stops as `SingleStep`, not `Breakpoint`,** and leaves the **Breakpoints** widget empty. That is expected — the core really is halted on a comparator Binary Ninja knows nothing about. +- **With GDB MI the stop is reported as `Breakpoint`** and appears in the **Breakpoints** widget, because GDB really did set it. (On the old **GDB RSP** workaround the stop showed as `SingleStep` with an empty widget, because the breakpoint was armed behind Binary Ninja's back.) ### It worked for a second, then stopped (Binary Ninja's view desyncs) This is the most common failure, and it has one main cause: **driving the core from the OpenOCD command port while Binary Ninja is connected.** -- If you send `reset run` from the port while attached, the core resets, runs, and halts at your breakpoint — but Binary Ninja never receives the stop event. Its sidebar keeps showing the *previous* location, so **Step** and **Resume** act on a stale PC and appear to do nothing. Verified: target at `0x1000023e` while the sidebar still read `Stopped (SingleStep) at 0x10003020`. +- If you send `reset run` from the port while attached, the core resets, runs, and halts at your breakpoint — but Binary Ninja never receives the stop event. Its sidebar keeps showing the *previous* location, so **Step** and **Resume** act on a stale PC and appear to do nothing. Verified: target at `0x1000023e` while the sidebar still read `Stopped at 0x10003020`. - If the OpenOCD process dies (or you restart it) while attached, Binary Ninja keeps believing it is connected: the sidebar stays, but the menu shows **Pause** enabled and **Resume**/**Step** disabled because Binary Ninja last saw the target *running*. Recovery: **Detach, then reconnect.** If Detach does nothing (the connection is already dead), restart Binary Ninja — its menu still shows a session that no longer exists. Prevention: - Stop at `main` with `BP_ADDR` on a fresh server start, not with `reset run` while attached. -- For loop addresses, arm the comparator and click **Resume** in Binary Ninja. Let Binary Ninja be the thing that starts the core. +- For loop addresses, set the breakpoint in the GUI and click **Resume**. Let Binary Ninja be the thing that starts the core. - If you must reset, **Detach first**, `reset run`, then reconnect. - Never leave a breakpoint on the PC you are about to step or resume from (see the stepping section above). @@ -1368,7 +1219,7 @@ set $r1 = 0x42 stepi ``` -`hbreak` sets a hardware breakpoint, which is required for read-only flash. It works from plain GDB because GDB sends the 2-byte length the Cortex-M33 comparators need — the same length Binary Ninja gets wrong, which is why the GUI cannot set breakpoints at all here (Step 13). +`hbreak` sets a hardware breakpoint, which is required for read-only flash. It works from plain GDB because GDB sends the 2-byte length the Cortex-M33 comparators need. Binary Ninja's **GDB MI** adapter goes through the same GDB, so its GUI breakpoints work too; the old **GDB RSP** adapter was the one that sent a 1-byte length and could not set breakpoints here. ## Glossary diff --git a/WEEK04/WEEK04-BN.pdf b/WEEK04/WEEK04-BN.pdf index e1999c9..ba0de24 100644 Binary files a/WEEK04/WEEK04-BN.pdf and b/WEEK04/WEEK04-BN.pdf differ