Repository navigation
Expand file tree
/
Copy pathdebug-server.sh
More file actions
executable file
·266 lines (253 loc) · 12.5 KB
/
Copy pathdebug-server.sh
File metadata and controls
executable file
·266 lines (253 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
#!/usr/bin/env bash
#
# debug-server.sh - start OpenOCD as a live GDB server for the RP2350 via the Pico Debug Probe.
#
# Synopsis:
# ./debug-server.sh
#
# 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.sh. flash.sh 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.
#
# 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.sh first).
#
# Environment variables:
# PICO_OPENOCD Directory containing the openocd binary AND its scripts/
# directory. Default: $HOME/.pico-sdk/openocd/0.12.0+dev
# macOS note: an OpenOCD on PATH is frequently the x86_64
# Homebrew build, which will not run under Rosetta on some
# setups and cannot talk to the ARM64 firmware tooling. The
# Pico SDK ships an arm64 build; point this at it explicitly.
# 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.
# 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" behavior.
# 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.
# 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.
#
# 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.
#
# Note on Core 0 register previews (pico-gdb-memory.gdb):
# Even with USE_CORE=0, whenever a Core 0 register holds an arbitrary integer
# or unmapped value (e.g. 0x69000000), Binary Ninja's UI probes that address
# for memory previews. In OpenOCD, failed SWD reads trigger the same SWD DPIDR
# reconnect behavior. Source "pico-gdb-memory.gdb" in Binary Ninja's Debugger
# Console to restrict GDB to valid Flash and SRAM, filtering invalid reads locally.
#
# 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:
#
# nc 127.0.0.1 4444 then type: reset run
#
# ...or use debug-server.sh's restart instructions in 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,<addr>,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.
#
# Examples:
# ./debug-server.sh
# ADAPTER_SPEED=10000 ./debug-server.sh
# PICO_OPENOCD=/opt/homebrew/bin ./debug-server.sh
# BP_ADDR=0x10000234 ./debug-server.sh
# Comes up halted at main (one-shot; see "Why BP_ADDR is one-shot").
#
# Exit status:
# * propagated from OpenOCD. A clean shutdown via Ctrl-C exits 0; an
# OpenOCD configuration error exits non-zero.
#
# Related scripts:
# flash.sh / flash.ps1 one-shot raw .bin programmer (exits when done)
#
# See also:
# WEEK04/WEEK04-BN.md the full walkthrough this script belongs to
set -euo pipefail
# --- configuration ---------------------------------------------------------
OCD="${PICO_OPENOCD:-$HOME/.pico-sdk/openocd/0.12.0+dev}"
SPEED="${ADAPTER_SPEED:-24000}"
USE_CORE="${USE_CORE:-0}"
BP_ADDR="${BP_ADDR:-}"
if [ -n "$BP_ADDR" ] && ! printf '%s' "$BP_ADDR" | grep -qiE '^0x[0-9a-f]+$'; then
echo "error: BP_ADDR must be 0x-prefixed hex, e.g. 0x10000234 (got '$BP_ADDR')" >&2
exit 1
fi
if [ ! -x "$OCD/openocd" ]; then
echo "error: OpenOCD not found at $OCD/openocd" >&2
echo " set PICO_OPENOCD=/path/to/openocd (the directory containing the openocd binary)" >&2
exit 1
fi
# --- announce, so the operator can verify intent before the target is touched --
echo "Starting OpenOCD GDB server on 127.0.0.1:3333 using $OCD/openocd"
echo "SWD adapter speed: ${SPEED} kHz"
echo "Cores exposed to GDB (USE_CORE): ${USE_CORE}"
echo "Target will be reset and released (reset run) - attach while it is running."
if [ -n "$BP_ADDR" ]; then
echo "Startup breakpoint at ${BP_ADDR} (2-byte hardware execute, one-shot)."
echo " It fires during this startup reset run. Any client connecting later"
echo " causes OpenOCD to delete it, so arm further breakpoints on port 4444"
echo " after attaching: bp <addr> 2 hw"
fi
# --- 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 the header 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.
ocd_args=(
-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 [ -n "$BP_ADDR" ]; then
ocd_args+=(-c "bp ${BP_ADDR} 2 hw")
fi
ocd_args+=(-c "reset run")
exec "$OCD/openocd" "${ocd_args[@]}"