diff --git a/debug-server.ps1 b/debug-server.ps1 index 650c424..b5e9a4c 100644 --- a/debug-server.ps1 +++ b/debug-server.ps1 @@ -1,277 +1,279 @@ -<# -.SYNOPSIS - Start OpenOCD as a live GDB server for the RP2350 via the Pico Debug Probe. - -.DESCRIPTION - Starts OpenOCD in the foreground as a long-running GDB server. It exposes - rp2350.dap.core0 on 127.0.0.1:3333 and leaves the core RUNNING, so that a - debugger (Binary Ninja, or plain GDB) can attach to a target that is already - executing and therefore has sane registers. - - This script is deliberately NOT flash.ps1. flash.ps1 programs flash and - exits; this one claims the probe and stays up so you can single-step, read - memory, and set breakpoints. Do not run both at once -- exactly one process - may own the debug probe. - - The script ends with "reset run" rather than OpenOCD's default halt. This is - the single most important line in the file; see "Why reset run" below. - -.PARAMETER OCD - Optional. Directory containing openocd.exe AND its scripts\ directory. - Overrides the PICO_OPENOCD environment variable. - -.ENVIRONMENT - PICO_OPENOCD Directory containing openocd.exe AND its scripts\ directory. - Default: $env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev - The path is used for BOTH the executable and the -s scripts - argument. This must be an OpenOCD build matching your machine - architecture (x64). - ADAPTER_SPEED SWD clock in kHz. Default: 24000. - This is a read-mostly debug session, so a fast clock is fine - and makes stepping noticeably smoother. Drop it (10000 or - lower) if the link is flaky or you are using long dupont - wires instead of the probe's own connector. - USE_CORE Which cores to expose to the client. Default: 0 (core0 only). - Accepted values: - 0 core0 only <- use this with Binary Ninja - 1 core1 only <- rarely useful - SMP both cores as hwthreads <- see "Why USE_CORE=0" - Do not change this to SMP when driving Binary Ninja. The - explanation below is the whole reason this default is 0. - BP_ADDR OPTIONAL address to halt at during the startup run, as 0x - prefixed hex, e.g. 0x10000234 for main. Unset by default, - which is the normal "attach to a running target" behaviour. - Arms a 2-byte hardware execute breakpoint just before the - final "reset run", so the core runs from the vector table - and stops there on its own, with no client attached yet. - See "Why BP_ADDR is one-shot" below for the important - limitation. Arming a length of 2 is mandatory: the Cortex-M33 - comparators are halfword-based and reject anything else. - -.NOTES - Requirements: - * Pico Debug Probe (or any CMSIS-DAP / SWD adapter) connected to the target. - * OpenOCD with the rp2350 target script installed. See PICO_OPENOCD. - * Exactly one process may own the debug probe. - * A firmware image already programmed into flash (use flash.ps1 first). - * The Debug Probe must use the WinUSB driver. If OpenOCD reports - "unable to open CMSIS-DAP device", install it with Zadig - (https://zadig.akeo.ie/), selecting "Debug Probe (CMSIS-DAP)" -> WinUSB. - - Why USE_CORE=0 (core1 must be hidden from the client) - The RP2350 has two Cortex-M33 cores. Core1 does not start on its own: - nothing in a Pico SDK application releases it from reset unless the program - explicitly does so. If you expose it anyway (USE_CORE=SMP), its registers - read back as meaningless reset defaults: - - core1: pc 0x000000ec sp 0xf0000000 xpsr 0x09000000 lr 0x00000147 - core0: pc 0x1000320c sp 0x20081f38 xpsr 0xa9000000 lr 0x100030ab - - Binary Ninja's register widget renders a memory preview for every register, - which means it treats each register value as an ADDRESS and reads it. The - core1 values above are not real addresses. Each read data-aborts, and - OpenOCD responds by tearing down and re-establishing the SWD debug port, - logging a pair of lines per fault: - - Error: Failed to read memory at 0xf0000000 - Info : SWD DPIDR 0x4c013477 - - That becomes a self-sustaining loop of roughly 450 fault-and-recover cycles - every two seconds, which makes the session unusable. It looks exactly like a - broken debugger or a broken firmware. It is neither: it is a configuration - mismatch, and hiding core1 removes it completely. - - Why reset run (the target must be released, not halted) - On RP2350, halting during reset stops the core at the boot ROM stub BEFORE - the stack pointer is loaded: - - xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 lr: 0xffffffff - - Those values are garbage for every core, including core0. Attaching in that - window triggers the identical fault storm described above. Ending this - script with "reset run" means the core starts from the vector table and is - executing normally by the time you attach, so registers read back correct. - - Consequence for the client: ATTACH WHILE THE TARGET IS RUNNING. Do not - press Reset/Restart in Binary Ninja -- it performs reset-halt and puts you - back in the garbage window. If you must reset, send "reset run" over the - OpenOCD telnet port (4444) instead. See WEEK04\WEEK04-BN.md. - - Why BP_ADDR is one-shot (read this before relying on it) - A breakpoint armed here DOES fire during the startup "reset run" and halts - the core at your address, so the server comes up parked there and your - debugger can simply attach and look at it. That part works. - - What you do NOT get is a reusable breakpoint. As soon as any GDB client - connects, OpenOCD unconditionally flushes every breakpoint it is holding: - - Info : accepting 'gdb' connection on tcp/3333 - Debug: breakpoints.c:328 breakpoint_remove_all_internal(): - [rp2350.dap.core0] Delete all breakpoints - - So by the time Binary Ninja is up, the comparator is gone -- reading - 0xE0002000 shows zeros, not your address. Consequences: - - * The startup stop is single-use. You cannot Resume and re-catch the - same address. - * You cannot use BP_ADDR to stop in a loop that is already running, - because the core only passes that point once per reset. - * main (0x10000234) is a good BP_ADDR value precisely because it is - reached exactly once, right after reset. - - To arm anything further, or to re-arm main, do it from the OpenOCD command - port AFTER your debugger has connected -- the order is the whole trick: - - nc 127.0.0.1 4444 - > bp 0x1000023e 2 hw - > reset run - - Arming before the client connects does not work (flushed above), and in - plain GDB the equivalent "hbreak" is cleared by "detach" -- both were - verified by reading the FPB comparator registers back. - - Related Binary Ninja bug: Binary Ninja's own breakpoints cannot be used on - this target at all. It sends Z0,,1 -- a 1-byte packet -- and - OpenOCD answers "only breakpoints of two bytes length supported". Both - Toggle Breakpoint (F2) and Add Hardware Breakpoint (F3) fail, at every - address, and the dialog's Size field is disabled so there is no UI way - around it. gdb_breakpoint_override hard does not change this. Hence the - command-port workflow described above. - - Why the remaining OpenOCD flags are set - gdb_breakpoint_override hard - Force every client breakpoint onto the Cortex-M33 hardware comparators - (the RP2350 has 8 breakpoints and 4 watchpoints). Without this, a - client may try to write a BKPT instruction into flash at 0x10000000, - which is read-only XIP memory, and the write fails. - gdb_memory_map disable - Stops the client probing the entire 32 MiB flash map on connect. Pure - Raspberry Pi guidance for suppressing spurious "Failed to read memory" - reports during target discovery. - cortex_m reset_config sysresetreq - The Cortex-M33 in the RP2350 has no VECTRESET. Without this, OpenOCD - warns on every reset: - VECTRESET is not supported on this Cortex-M core, using SYSRESETREQ - instead - NOTE: this MUST be the generic "cortex_m" command, not - "rp2350.dap.core1 cortex_m ...". With USE_CORE=0 the core1 target does - not exist, so a core1-scoped command aborts OpenOCD before "init" runs, - and the script exits silently having printed nothing useful. - adapter speed - See ADAPTER_SPEED above. - -.EXAMPLE - .\debug-server.ps1 - -.EXAMPLE - $env:ADAPTER_SPEED=10000; .\debug-server.ps1 - -.EXAMPLE - $env:PICO_OPENOCD="C:\openocd\bin"; .\debug-server.ps1 - -.EXAMPLE - $env:BP_ADDR="0x10000234"; .\debug-server.ps1 - Comes up halted at main (one-shot; see "Why BP_ADDR is one-shot"). - -.NOTES - Exit status: - * propagated from OpenOCD. A clean shutdown via Ctrl-C exits 0; an - OpenOCD configuration error exits non-zero. - - Related scripts: - flash.ps1 one-shot raw .bin programmer (exits when done) - - See also: - WEEK04\WEEK04-BN.md the full walkthrough this script belongs to -#> - -[CmdletBinding()] -param( - [Parameter()] - [string]$OCD -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -# --- configuration --------------------------------------------------------- - -if (-not $OCD) { $OCD = $env:PICO_OPENOCD } -if (-not $OCD) { $OCD = "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev" } - -$SPEED = if ($env:ADAPTER_SPEED) { $env:ADAPTER_SPEED } else { "24000" } -$USE_CORE = if ($env:USE_CORE) { $env:USE_CORE } else { "0" } -$BP_ADDR = if ($env:BP_ADDR) { $env:BP_ADDR.Trim() } else { "" } - -if ($BP_ADDR -and $BP_ADDR -notmatch '^0x[0-9a-fA-F]+$') { - Write-Error "BP_ADDR must be 0x-prefixed hex, e.g. 0x10000234 (got '$BP_ADDR')" - exit 1 -} - -if (-not (Test-Path "$OCD\openocd.exe")) { - Write-Error "OpenOCD not found at $OCD\openocd.exe (set PICO_OPENOCD or -OCD to the directory containing openocd.exe)" - exit 1 -} - -# --- announce, so the operator can verify intent before the target is touched -- - -Write-Host "Starting OpenOCD GDB server on 127.0.0.1:3333 using $OCD\openocd.exe" -Write-Host "SWD adapter speed: $SPEED kHz" -Write-Host "Cores exposed to GDB (USE_CORE): $USE_CORE" -Write-Host "Target will be reset and released (reset run) - attach while it is running." - -if ($BP_ADDR) { - Write-Host "Startup breakpoint at $BP_ADDR (2-byte hardware execute, one-shot)." - Write-Host " It fires during this startup reset run. Any client connecting later" - Write-Host " causes OpenOCD to delete it, so arm further breakpoints on port 4444" - Write-Host " after attaching: bp 2 hw" -} - -# --- start ----------------------------------------------------------------- -# -# Order matters: USE_CORE must be set BEFORE -f target/rp2350.cfg is read, -# because the target script branches on it when creating the DAP targets. -# -# "reset run" is last so it happens after init and after the target is -# examined, releasing the core rather than halting it. See .NOTES for why. -# -# Any BP_ADDR breakpoint is inserted after "init" (the target must exist before -# a comparator can be programmed) but before "reset run" (so the core is already -# armed when it starts). The length must be 2: Cortex-M33 comparators reject -# other widths. - -$ocdArgs = @( - "-s", "$OCD\scripts", - "-f", "interface/cmsis-dap.cfg", - "-c", "set USE_CORE $USE_CORE", - "-f", "target/rp2350.cfg", - # Drop the hwthread RTOS the RP2350 target script attaches to core0. - # - # 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 GDB packet "vCont;s" and no thread id, i.e. thread 0. OpenOCD's - # gdb_server sees rtos->current_thread (1) != thread_id (0) and takes its - # "fake step" path, replying with a stop without ever stepping the core: - # - # gdb_server.c gdb_handle_vcont_packet(): fake step thread 0 - # - # The result is that Step Into / Step Over in Binary Ninja does nothing: the - # PC never moves. Clearing the RTOS removes the mismatch so the step is real. - # Harmless for single-core use, which is all this lab does (USE_CORE=0). - "-c", "rp2350.dap.core0 configure -rtos none", - "-c", "adapter speed $SPEED", - "-c", "gdb_memory_map disable", - "-c", "gdb_breakpoint_override hard", - "-c", "cortex_m reset_config sysresetreq", - "-c", "init" -) - -if ($BP_ADDR) { - $ocdArgs += @("-c", "bp $BP_ADDR 2 hw") -} - -$ocdArgs += @("-c", "reset run") - -& "$OCD\openocd.exe" @ocdArgs - -exit $LASTEXITCODE +<# +.SYNOPSIS + Start OpenOCD as a live GDB server for the RP2350 via the Pico Debug Probe. + +.DESCRIPTION + Starts OpenOCD in the foreground as a long-running GDB server. It exposes + rp2350.dap.core0 on 127.0.0.1:3333 and leaves the core RUNNING, so that a + debugger (Binary Ninja, or plain GDB) can attach to a target that is already + executing and therefore has sane registers. + + This script is deliberately NOT flash.ps1. flash.ps1 programs flash and + exits; this one claims the probe and stays up so you can single-step, read + memory, and set breakpoints. Do not run both at once -- exactly one process + may own the debug probe. + + The script ends with "reset run" rather than OpenOCD's default halt. This is + the single most important line in the file; see "Why reset run" below. + +.PARAMETER OCD + Optional. Directory containing openocd.exe AND its scripts\ directory. + Overrides the PICO_OPENOCD environment variable. + +.ENVIRONMENT + PICO_OPENOCD Directory containing openocd.exe AND its scripts\ directory. + Default: $env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev + The path is used for BOTH the executable and the -s scripts + argument. This must be an OpenOCD build matching your machine + architecture (x64). + ADAPTER_SPEED SWD clock in kHz. Default: 24000. + This is a read-mostly debug session, so a fast clock is fine + and makes stepping noticeably smoother. Drop it (10000 or + lower) if the link is flaky or you are using long dupont + wires instead of the probe's own connector. + USE_CORE Which cores to expose to the client. Default: 0 (core0 only). + Accepted values: + 0 core0 only <- use this with Binary Ninja + 1 core1 only <- rarely useful + SMP both cores as hwthreads <- see "Why USE_CORE=0" + Do not change this to SMP when driving Binary Ninja. The + explanation below is the whole reason this default is 0. + BP_ADDR OPTIONAL address to halt at during the startup run, as 0x + prefixed hex, e.g. 0x10000234 for main. Unset by default, + which is the normal "attach to a running target" behaviour. + Arms a 2-byte hardware execute breakpoint just before the + final "reset run", so the core runs from the vector table + and stops there on its own, with no client attached yet. + See "Why BP_ADDR is one-shot" below for the important + limitation. Arming a length of 2 is mandatory: the Cortex-M33 + comparators are halfword-based and reject anything else. + +.NOTES + Requirements: + * Pico Debug Probe (or any CMSIS-DAP / SWD adapter) connected to the target. + * OpenOCD with the rp2350 target script installed. See PICO_OPENOCD. + * Exactly one process may own the debug probe. + * A firmware image already programmed into flash (use flash.ps1 first). + * The Debug Probe must use the WinUSB driver. If OpenOCD reports + "unable to open CMSIS-DAP device", install it with Zadig + (https://zadig.akeo.ie/), selecting "Debug Probe (CMSIS-DAP)" -> WinUSB. + + Why USE_CORE=0 (core1 must be hidden from the client) + The RP2350 has two Cortex-M33 cores. Core1 does not start on its own: + nothing in a Pico SDK application releases it from reset unless the program + explicitly does so. If you expose it anyway (USE_CORE=SMP), its registers + read back as meaningless reset defaults: + + core1: pc 0x000000ec sp 0xf0000000 xpsr 0x09000000 lr 0x00000147 + core0: pc 0x1000320c sp 0x20081f38 xpsr 0xa9000000 lr 0x100030ab + + Binary Ninja's register widget renders a memory preview for every register, + which means it treats each register value as an ADDRESS and reads it. The + core1 values above are not real addresses. Each read data-aborts, and + OpenOCD responds by tearing down and re-establishing the SWD debug port, + logging a pair of lines per fault: + + Error: Failed to read memory at 0xf0000000 + Info : SWD DPIDR 0x4c013477 + + That becomes a self-sustaining loop of roughly 450 fault-and-recover cycles + every two seconds, which makes the session unusable. It looks exactly like a + broken debugger or a broken firmware. It is neither: it is a configuration + mismatch, and hiding core1 removes it completely. + + Why reset run (the target must be released, not halted) + On RP2350, halting during reset stops the core at the boot ROM stub BEFORE + the stack pointer is loaded: + + xPSR: 0xf9000000 pc: 0x00000088 msp: 0xf0000000 lr: 0xffffffff + + Those values are garbage for every core, including core0. Attaching in that + window triggers the identical fault storm described above. Ending this + script with "reset run" means the core starts from the vector table and is + executing normally by the time you attach, so registers read back correct. + + Consequence for the client: ATTACH WHILE THE TARGET IS RUNNING. Do not + press Reset/Restart in Binary Ninja -- it performs reset-halt and puts you + back in the garbage window. If you must reset, send "reset run" over the + OpenOCD telnet port (4444) instead. See WEEK04\WEEK04-BN.md. + + Why BP_ADDR is one-shot (read this before relying on it) + A breakpoint armed here DOES fire during the startup "reset run" and halts + the core at your address, so the server comes up parked there and your + debugger can simply attach and look at it. That part works. + + What you do NOT get is a reusable breakpoint. As soon as any GDB client + connects, OpenOCD unconditionally flushes every breakpoint it is holding: + + Info : accepting 'gdb' connection on tcp/3333 + Debug: breakpoints.c:328 breakpoint_remove_all_internal(): + [rp2350.dap.core0] Delete all breakpoints + + So by the time Binary Ninja is up, the comparator is gone -- reading + 0xE0002000 shows zeros, not your address. Consequences: + + * The startup stop is single-use. You cannot Resume and re-catch the + same address. + * You cannot use BP_ADDR to stop in a loop that is already running, + because the core only passes that point once per reset. + * main (0x10000234) is a good BP_ADDR value precisely because it is + reached exactly once, right after reset. + + To arm anything further, or to re-arm main, do it from the OpenOCD command + port AFTER your debugger has connected -- the order is the whole trick: + + nc 127.0.0.1 4444 + > bp 0x1000023e 2 hw + > reset run + + Arming before the client connects does not work (flushed above), and in + plain GDB the equivalent "hbreak" is cleared by "detach" -- both were + verified by reading the FPB comparator registers back. + + Related Binary Ninja bug: Binary Ninja's own breakpoints cannot be used on + this target at all. It sends Z0,,1 -- a 1-byte packet -- and + OpenOCD answers "only breakpoints of two bytes length supported". Both + Toggle Breakpoint (F2) and Add Hardware Breakpoint (F3) fail, at every + address, and the dialog's Size field is disabled so there is no UI way + around it. gdb_breakpoint_override hard does not change this. Hence the + command-port workflow described above. + + Why the remaining OpenOCD flags are set + gdb_breakpoint_override hard + Force every client breakpoint onto the Cortex-M33 hardware comparators + (the RP2350 has 8 breakpoints and 4 watchpoints). Without this, a + client may try to write a BKPT instruction into flash at 0x10000000, + which is read-only XIP memory, and the write fails. + gdb_memory_map disable + Stops the client probing the entire 32 MiB flash map on connect. Pure + Raspberry Pi guidance for suppressing spurious "Failed to read memory" + reports during target discovery. + cortex_m reset_config sysresetreq + The Cortex-M33 in the RP2350 has no VECTRESET. Without this, OpenOCD + warns on every reset: + VECTRESET is not supported on this Cortex-M core, using SYSRESETREQ + instead + NOTE: this MUST be the generic "cortex_m" command, not + "rp2350.dap.core1 cortex_m ...". With USE_CORE=0 the core1 target does + not exist, so a core1-scoped command aborts OpenOCD before "init" runs, + and the script exits silently having printed nothing useful. + adapter speed + See ADAPTER_SPEED above. + +.EXAMPLE + .\debug-server.ps1 + +.EXAMPLE + $env:ADAPTER_SPEED=10000; .\debug-server.ps1 + +.EXAMPLE + $env:PICO_OPENOCD="C:\openocd\bin"; .\debug-server.ps1 + +.EXAMPLE + $env:BP_ADDR="0x10000234"; .\debug-server.ps1 + Comes up halted at main (one-shot; see "Why BP_ADDR is one-shot"). + +.NOTES + Exit status: + * propagated from OpenOCD. A clean shutdown via Ctrl-C exits 0; an + OpenOCD configuration error exits non-zero. + + Related scripts: + flash.ps1 one-shot raw .bin programmer (exits when done) + + See also: + WEEK04\WEEK04-BN.md the full walkthrough this script belongs to +#> + +[CmdletBinding()] +param( + [Parameter()] + [string]$OCD +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# --- configuration --------------------------------------------------------- + +if (-not $OCD) { $OCD = $env:PICO_OPENOCD } +if (-not $OCD) { $OCD = "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev" } + +$SPEED = if ($env:ADAPTER_SPEED) { $env:ADAPTER_SPEED } else { "24000" } +$USE_CORE = if ($env:USE_CORE) { $env:USE_CORE } else { "0" } +$BP_ADDR = if ($env:BP_ADDR) { $env:BP_ADDR.Trim() } else { "" } + +if ($BP_ADDR -and $BP_ADDR -notmatch '^0x[0-9a-fA-F]+$') { + Write-Error "BP_ADDR must be 0x-prefixed hex, e.g. 0x10000234 (got '$BP_ADDR')" + exit 1 +} + +if (-not (Test-Path "$OCD\openocd.exe")) { + Write-Error "OpenOCD not found at $OCD\openocd.exe (set PICO_OPENOCD or -OCD to the directory containing openocd.exe)" + exit 1 +} + +# --- announce, so the operator can verify intent before the target is touched -- + +Write-Host "Starting OpenOCD GDB server on 127.0.0.1:3333 using $OCD\openocd.exe" +Write-Host "SWD adapter speed: $SPEED kHz" +Write-Host "Cores exposed to GDB (USE_CORE): $USE_CORE" +Write-Host "Target will be reset and released (reset run) - attach while it is running." + +if ($BP_ADDR) { + Write-Host "Startup breakpoint at $BP_ADDR (2-byte hardware execute, one-shot)." + Write-Host " It fires during this startup reset run. Any client connecting later" + Write-Host " causes OpenOCD to delete it, so arm further breakpoints on port 4444" + Write-Host " after attaching: bp 2 hw" +} + +# --- start ----------------------------------------------------------------- +# +# Order matters: USE_CORE must be set BEFORE -f target/rp2350.cfg is read, +# because the target script branches on it when creating the DAP targets. +# +# "reset run" is last so it happens after init and after the target is +# examined, releasing the core rather than halting it. See .NOTES for why. +# +# Any BP_ADDR breakpoint is inserted after "init" (the target must exist before +# a comparator can be programmed) but before "reset run" (so the core is already +# armed when it starts). The length must be 2: Cortex-M33 comparators reject +# other widths. + +$ocdArgs = @( + "-s", "$OCD\scripts", + "-f", "interface/cmsis-dap.cfg", + "-c", "set USE_CORE $USE_CORE", + "-f", "target/rp2350.cfg", + # Drop the hwthread RTOS the RP2350 target script attaches to core0. + # + # 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 GDB packet "vCont;s" and no thread id, i.e. thread 0. OpenOCD's + # gdb_server sees rtos->current_thread (1) != thread_id (0) and takes its + # "fake step" path, replying with a stop without ever stepping the core: + # + # gdb_server.c gdb_handle_vcont_packet(): fake step thread 0 + # + # The result is that Step Into / Step Over in Binary Ninja does nothing: the + # PC never moves. Clearing the RTOS removes the mismatch so the step is real. + # Harmless for single-core use, which is all this lab does (USE_CORE=0). + # The core's name differs between OpenOCD builds (rp2350.dap.core0 vs rp2350.cm0 + # in the Pico SDK's Windows build), so look it up instead of hard-coding it. + "-c", "[lindex [target names] 0] configure -rtos none", + "-c", "adapter speed $SPEED", + "-c", "gdb_memory_map disable", + "-c", "gdb_breakpoint_override hard", + "-c", "cortex_m reset_config sysresetreq", + "-c", "init" +) + +if ($BP_ADDR) { + $ocdArgs += @("-c", "bp $BP_ADDR 2 hw") +} + +$ocdArgs += @("-c", "reset run") + +& "$OCD\openocd.exe" @ocdArgs + +exit $LASTEXITCODE diff --git a/flash.ps1 b/flash.ps1 index 7493d24..23d0a41 100644 --- a/flash.ps1 +++ b/flash.ps1 @@ -1,128 +1,132 @@ -<# -.SYNOPSIS - Program a raw .bin into RP2350 XIP flash via the Pico Debug Probe and OpenOCD. - -.DESCRIPTION - Writes a headerless raw binary to the RP2350's external XIP flash starting at - physical address 0x10000000, verifies the written bytes by reading them back, - then releases the core so the freshly programmed firmware runs. - - A raw .bin carries no address information, no entry point and no section - table, so the load address MUST be supplied out of band. On the RP2350 the - only correct value is 0x10000000: that is where the boot ROM jumps after - pulling the reset vector out of the on-chip XIP window. Flashing anywhere - else produces a board that enumerates over USB and then does nothing. - - The target must already be running the stock RP2350 bootrom (the default - state after power-up, or after any BOOTSEL + UF2 reflash). OpenOCD attaches - to the bootrom over SWD, halts it, programs flash, verifies, and resets. - - This is the macOS / Linux equivalent of flash.sh. The two must stay in - lockstep: same base address, same verify, same conservative SWD clock. - -.PARAMETER Bin - Mandatory. Path to the raw .bin image to program. - -.ENVIRONMENT - PICO_OPENOCD Directory containing openocd.exe AND its scripts\ directory. - Default: $env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev - The path is used for BOTH the executable and the -s scripts - argument. - ADAPTER_SPEED SWD clock in kHz. Default: 5000. - 5000 kHz is deliberately conservative. This is a write path, - not a read-only debug session, and a marginal USB cable or - long dupont run produces spurious verify failures at higher - clocks. Raise it (24000) for read-only work; if - "Error: target not halted" or verify mismatches appear, - lower it to 1000. - -.EXAMPLE - .\flash.ps1 -Bin 0x0005_intro-to-variables\build\0x0005_intro-to-variables.bin - -.EXAMPLE - $env:ADAPTER_SPEED=1000; .\flash.ps1 -Bin build\hacked.bin - -.EXAMPLE - $env:PICO_OPENOCD="C:\openocd\bin"; .\flash.ps1 -Bin build\hacked.bin - -.NOTES - Requirements: - * Pico Debug Probe (or any CMSIS-DAP / SWD adapter) connected to the target. - * OpenOCD with the rp2350 target script installed. - * The Debug Probe must use the WinUSB driver. If OpenOCD reports - "unable to open CMSIS-DAP device", install it with Zadig - (https://zadig.akeo.ie/), selecting "Debug Probe (CMSIS-DAP)" -> WinUSB. - * Exactly one process may own the debug probe. Close any other OpenOCD, - GDB, or IDE debug session first. - - Exit status: - 0 flash written, verified, and core released - 1 input file missing or OpenOCD not found - * any other status is propagated from OpenOCD, so a failed verify or a - write error is visible to the caller rather than being swallowed. - - Related scripts: - debug-server.ps1 long-running GDB server for live debugging with - Binary Ninja. Use that instead of this script when you - need to single-step. - - See also: - WEEK04\WEEK04-BN.md the full walkthrough this script belongs to -#> - -[CmdletBinding()] -param( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$Bin -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = 'Stop' - -# --- argument validation --------------------------------------------------- - -if (-not (Test-Path -PathType Leaf $Bin)) { - Write-Error "file not found: $Bin" - Write-Host "build it first: cmake -B build -G Ninja -DPICO_BOARD=pico2 -DPICO_PLATFORM=rp2350 ; cmake --build build" - exit 1 -} - -# --- toolchain resolution -------------------------------------------------- - -$OCD = if ($env:PICO_OPENOCD) { $env:PICO_OPENOCD } else { "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev" } -if (-not (Test-Path "$OCD\openocd.exe")) { - Write-Error "OpenOCD not found at $OCD\openocd.exe (set PICO_OPENOCD to the directory containing openocd.exe)" - exit 1 -} - -$SPEED = if ($env:ADAPTER_SPEED) { $env:ADAPTER_SPEED } else { "5000" } - -# --- program --------------------------------------------------------------- - -# OpenOCD flag notes: -# -f interface/cmsis-dap.cfg the Pico Debug Probe is a CMSIS-DAP v1 device -# -f target/rp2350.cfg RP2350 dual Cortex-M33 + RP2350B0-style DAP; -# sets USE_CORE=SMP by default, which is fine here -# because we never hand uninitialised core1 -# registers to a debugger. For live debugging use -# debug-server.ps1, which forces USE_CORE=0. -# -c "program BIN 0x10000000 verify reset exit" -# program the write -# 0x10000000 base address (see .DESCRIPTION) -# verify read back and compare every byte; a mismatch aborts -# reset reset the core so the new image starts at its vectors -# exit release the probe and return to the shell -# -# The adapter speed is deliberately lower here than in debug-server.ps1; see -# ADAPTER_SPEED in .ENVIRONMENT. -Write-Host "Flashing $Bin -> 0x10000000 using $OCD\openocd.exe (SWD $SPEED kHz)" - -& "$OCD\openocd.exe" ` - -s "$OCD\scripts" ` - -f interface/cmsis-dap.cfg ` - -f target/rp2350.cfg ` - -c "adapter speed $SPEED" ` - -c "program $Bin 0x10000000 verify reset exit" - -exit $LASTEXITCODE +<# +.SYNOPSIS + Program a raw .bin into RP2350 XIP flash via the Pico Debug Probe and OpenOCD. + +.DESCRIPTION + Writes a headerless raw binary to the RP2350's external XIP flash starting at + physical address 0x10000000, verifies the written bytes by reading them back, + then releases the core so the freshly programmed firmware runs. + + A raw .bin carries no address information, no entry point and no section + table, so the load address MUST be supplied out of band. On the RP2350 the + only correct value is 0x10000000: that is where the boot ROM jumps after + pulling the reset vector out of the on-chip XIP window. Flashing anywhere + else produces a board that enumerates over USB and then does nothing. + + The target must already be running the stock RP2350 bootrom (the default + state after power-up, or after any BOOTSEL + UF2 reflash). OpenOCD attaches + to the bootrom over SWD, halts it, programs flash, verifies, and resets. + + This is the macOS / Linux equivalent of flash.sh. The two must stay in + lockstep: same base address, same verify, same conservative SWD clock. + +.PARAMETER Bin + Mandatory. Path to the raw .bin image to program. + +.ENVIRONMENT + PICO_OPENOCD Directory containing openocd.exe AND its scripts\ directory. + Default: $env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev + The path is used for BOTH the executable and the -s scripts + argument. + ADAPTER_SPEED SWD clock in kHz. Default: 5000. + 5000 kHz is deliberately conservative. This is a write path, + not a read-only debug session, and a marginal USB cable or + long dupont run produces spurious verify failures at higher + clocks. Raise it (24000) for read-only work; if + "Error: target not halted" or verify mismatches appear, + lower it to 1000. + +.EXAMPLE + .\flash.ps1 -Bin 0x0005_intro-to-variables\build\0x0005_intro-to-variables.bin + +.EXAMPLE + $env:ADAPTER_SPEED=1000; .\flash.ps1 -Bin build\hacked.bin + +.EXAMPLE + $env:PICO_OPENOCD="C:\openocd\bin"; .\flash.ps1 -Bin build\hacked.bin + +.NOTES + Requirements: + * Pico Debug Probe (or any CMSIS-DAP / SWD adapter) connected to the target. + * OpenOCD with the rp2350 target script installed. + * The Debug Probe must use the WinUSB driver. If OpenOCD reports + "unable to open CMSIS-DAP device", install it with Zadig + (https://zadig.akeo.ie/), selecting "Debug Probe (CMSIS-DAP)" -> WinUSB. + * Exactly one process may own the debug probe. Close any other OpenOCD, + GDB, or IDE debug session first. + + Exit status: + 0 flash written, verified, and core released + 1 input file missing or OpenOCD not found + * any other status is propagated from OpenOCD, so a failed verify or a + write error is visible to the caller rather than being swallowed. + + Related scripts: + debug-server.ps1 long-running GDB server for live debugging with + Binary Ninja. Use that instead of this script when you + need to single-step. + + See also: + WEEK04\WEEK04-BN.md the full walkthrough this script belongs to +#> + +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Bin +) + +Set-StrictMode -Version Latest +$ErrorActionPreference = 'Stop' + +# --- argument validation --------------------------------------------------- + +if (-not (Test-Path -PathType Leaf $Bin)) { + Write-Error "file not found: $Bin" + Write-Host "build it first: cmake -B build -G Ninja -DPICO_BOARD=pico2 -DPICO_PLATFORM=rp2350 ; cmake --build build" + exit 1 +} + +# --- toolchain resolution -------------------------------------------------- + +$OCD = if ($env:PICO_OPENOCD) { $env:PICO_OPENOCD } else { "$env:USERPROFILE\.pico-sdk\openocd\0.12.0+dev" } +if (-not (Test-Path "$OCD\openocd.exe")) { + Write-Error "OpenOCD not found at $OCD\openocd.exe (set PICO_OPENOCD to the directory containing openocd.exe)" + exit 1 +} + +$SPEED = if ($env:ADAPTER_SPEED) { $env:ADAPTER_SPEED } else { "5000" } + +# --- program --------------------------------------------------------------- + +# OpenOCD flag notes: +# -f interface/cmsis-dap.cfg the Pico Debug Probe is a CMSIS-DAP v1 device +# -f target/rp2350.cfg RP2350 dual Cortex-M33 + RP2350B0-style DAP; +# sets USE_CORE=SMP by default, which is fine here +# because we never hand uninitialised core1 +# registers to a debugger. For live debugging use +# debug-server.ps1, which forces USE_CORE=0. +# -c "program BIN 0x10000000 verify reset exit" +# program the write +# 0x10000000 base address (see .DESCRIPTION) +# verify read back and compare every byte; a mismatch aborts +# reset reset the core so the new image starts at its vectors +# exit release the probe and return to the shell +# +# The adapter speed is deliberately lower here than in debug-server.ps1; see +# ADAPTER_SPEED in .ENVIRONMENT. +Write-Host "Flashing $Bin -> 0x10000000 using $OCD\openocd.exe (SWD $SPEED kHz)" + +# OpenOCD parses -c strings as Tcl, which eats backslashes ("\build" -> backspace+"uild"). +# Hand it an absolute path with forward slashes, braced so spaces survive. +$BinTcl = (Resolve-Path $Bin).Path -replace '\\', '/' + +& "$OCD\openocd.exe" ` + -s "$OCD\scripts" ` + -f interface/cmsis-dap.cfg ` + -f target/rp2350.cfg ` + -c "adapter speed $SPEED" ` + -c "program {$BinTcl} 0x10000000 verify reset exit" + +exit $LASTEXITCODE