11 KiB
Embedded Systems Reverse Engineering
Week 1: Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
Exercise 4: Connect GDB (Preparation for Week 2)
Objective
Set up GDB (GNU Debugger) to dynamically analyze the "hello, world" program running on your Pico 2, verifying that your debugging setup works correctly.
Prerequisites
- Raspberry Pi Pico 2 with "hello-world" binary already flashed
- OpenOCD installed and working
- GDB (arm-none-eabi-gdb) installed
- Your Pico 2 connected to your computer via USB CMSIS-DAP interface
- CMake build artifacts available (
.elffile from compilation)
Task Description
In this exercise, you'll:
- Start OpenOCD to provide a debug server
- Connect GDB to the Pico 2 via OpenOCD
- Set a breakpoint at the main function
- Examine registers and memory while the program is running
- Verify that your dynamic debugging setup works
Important Setup Notes
Before you start, make sure:
- Your Pico 2 is powered on and connected to your computer
- You have OpenOCD installed for ARM debugging
- You have GDB (specifically
arm-none-eabi-gdb) installed - Your binary file (
0x0001_hello-world.elf) is available in thebuild/directory
Step-by-Step Instructions
Step 1: Start OpenOCD in Terminal 1
Open a new terminal window (PowerShell, Command Prompt, or WSL):
On Windows (PowerShell/Command Prompt):
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
Expected Output:
Open On-Chip Debugger 0.12.0+dev
...
Info : CMSIS-DAP: SWD detected
Info : RP2350 (dual core) detected
Info : Using JTAG interface
...
Info : accepting 'gdb' connection on tcp/3333
Step 2: Start GDB in Terminal 2
Open a second terminal window and navigate to your project directory:
arm-none-eabi-gdb -q build/0x0001_hello-world.elf
Expected Output:
Reading symbols from build/0x0001_hello-world.elf...
(gdb)
Step 3: Connect GDB to OpenOCD
At the GDB prompt (gdb), type:
target extended-remote localhost:3333
Expected Output:
Remote debugging using localhost:3333
(gdb)
(The warning is normal - you already loaded the .elf file, so it doesn't matter)
Step 4: Reset and Halt the Target
To reset the Pico 2 and prepare for debugging, type:
monitor reset halt
Expected Output:
(gdb)
(This resets the processor and halts it, preventing execution until you tell it to run)
Step 5: Set a Breakpoint at main
To stop execution at the beginning of the main function:
b main
Expected Output:
Breakpoint 1 at 0x10000234: file ../0x0001_hello-world.c, line 4.
(gdb)
What this means:
- Breakpoint 1 is set at address
0x10000234 - That's in the file
../0x0001_hello-world.cat line 4 - The breakpoint is at the
mainfunction
Step 6: Continue Execution to the Breakpoint
Now let the program run until it hits your breakpoint:
c
Expected Output:
Continuing.
Breakpoint 1, main () at ../0x0001_hello-world.c:4
4 stdio_init_all();
(gdb)
Great! Your program is now halted at the beginning of main().
Step 7: Examine the Assembly with disas
To see the assembly language of the current function:
disas
Expected Output:
Dump of assembler code for function main:
=> 0x10000234 <+0>: push {r3, lr}
0x10000236 <+2>: bl 0x1000156c <stdio_init_all>
0x1000023a <+6>: ldr r0, [pc, #8] @ (0x10000244 <main+16>)
0x1000023c <+8>: bl 0x100015fc <__wrap_puts>
0x10000240 <+12>: b.n 0x1000023a <main+6>
0x10000242 <+14>: nop
0x10000244 <+16>: adds r4, r1, r7
0x10000246 <+18>: asrs r0, r0, #32
End of assembler dump.
(gdb)
Interpretation:
- The
=>arrow shows where we're currently stopped (at0x10000234) - We can see the
push,bl(branch and link),ldr, andb.n(branch) instructions - This is the exact code you analyzed in the Ghidra exercises!
Step 8: View All Registers with i r
To see the current state of all CPU registers:
i r
Expected Output:
r0 0x0 0
r1 0x10000235 268436021
r2 0x80808080 -2139062144
r3 0xe000ed08 -536810232
r4 0x100001d0 268435920
r5 0x88526891 -2007865199
r6 0x4f54710 83183376
r7 0x400e0014 1074659348
r8 0x43280035 1126694965
r9 0x0 0
r10 0x10000000 268435456
r11 0x62707361 1651536737
r12 0xed07f600 -318245376
sp 0x20082000 0x20082000
lr 0x1000018f 268435855
pc 0x10000234 0x10000234 <main>
xpsr 0x69000000 1761607680
Key Registers to Understand:
| Register | Value | Meaning |
|---|---|---|
pc |
0x10000234 |
Program Counter - we're at the start of main |
sp |
0x20082000 |
Stack Pointer - top of our stack in RAM |
lr |
0x1000018f |
Link Register - where we return from main |
r0-r3 |
Various | Will hold function arguments and return values |
Step 9: Step Into the First Instruction
To execute one assembly instruction:
si
Expected Output:
0x10000236 in main () at ../0x0001_hello-world.c:5
5 stdio_init_all();
(gdb)
The pc should now be at 0x10000236, which is the next instruction.
Step 10: Answer These Questions
Based on what you've observed:
Question 1: GDB Connection
- Was GDB able to connect to OpenOCD? (Yes/No)
- Did the program stop at your breakpoint? (Yes/No)
-
Question 2: Breakpoint Address
- What is the memory address of the
mainfunction's first instruction? -
- Is this in Flash memory (0x100...) or RAM (0x200...)?
-
Question 3: Stack Pointer
- What is the value of the Stack Pointer (sp) when you're at
main? -
- Is this in Flash or RAM?
-
Question 4: First Instruction
- What is the first instruction in
main? -
- What does it do? (Hint:
push= save to stack) -
Question 5: Disassembly Comparison
- Look at the disassembly from GDB (Step 7)
- Compare it to the disassembly from Ghidra (Exercise 1)
- Are they the same?
-
Deeper Exploration (Optional Challenge)
Challenge 1: Step Through stdio_init_all
- Continue stepping:
si(step into) orni(next instruction) - Eventually, you'll reach
bl 0x1000156c <stdio_init_all> - Use
sito step into that function - What instructions do you see?
- What registers are being modified?
Challenge 2: View Specific Registers
Instead of viewing all registers, you can view just a few:
i r pc sp lr r0 r1 r2
This shows only the registers you care about.
Challenge 3: Examine Memory
To examine memory at a specific address (e.g., where the string is):
x/16b 0x100019cc
This displays 16 bytes (b = byte) starting at address 0x100019cc. Can you see the "hello, world" string?
Challenge 4: Set a Conditional Breakpoint
Set a breakpoint that only triggers after a certain condition:
b *0x1000023a if $r0 != 0
This is useful when you want to break on a condition rather than every time.
Questions for Reflection
-
Why does GDB show both the C source line AND the assembly?
- This is because the .elf file contains debug symbols
- What would happen if we used a stripped binary?
-
How does GDB know the assembly for each instruction?
- It disassembles the binary on-the-fly based on the architecture
-
Why is the Stack Pointer so high (0x20082000)?
- It's at the top of RAM and grows downward
- Can you calculate how much RAM this Pico 2 has?
-
What's the difference between
si(step into) andni(next instruction)?sisteps into function callsniexecutes entire functions without stopping inside them
Important GDB Commands Reference
| Command | Short Form | What It Does |
|---|---|---|
target extended-remote localhost:3333 |
Connect to OpenOCD | |
monitor reset halt |
Reset and halt the processor | |
break main |
b main |
Set a breakpoint at main function |
continue |
c |
Continue until breakpoint |
step instruction |
si |
Step one instruction (into calls) |
next instruction |
ni |
Step one instruction (over calls) |
disassemble |
disas |
Show assembly for current function |
info registers |
i r |
Show all register values |
x/Nxy ADDRESS |
x |
Examine memory (N=count, x=format, y=size) |
quit |
q |
Exit GDB |
Examples for x command:
x/10i $pc- examine 10 instructions at program counterx/16b 0x20000000- examine 16 bytes starting at RAM addressx/4w 0x10000000- examine 4 words (4-byte values) starting at Flash address
Troubleshooting
Problem: "OpenOCD not found"
Solution: Make sure OpenOCD is in your PATH or use the full path to the executable
Problem: "Target not responding"
Solution:
- Check that your Pico 2 is properly connected
- Make sure OpenOCD is running and shows "accepting 'gdb' connection"
- Restart both OpenOCD and GDB
Problem: "Cannot find breakpoint at main"
Solution:
- Make sure you compiled with debug symbols
- The .elf file must include symbol information
- Try breaking at an address instead:
b *0x10000234
Problem: GDB shows "No source available"
Solution:
- This happens with stripped binaries
- You can still see assembly with
disas - You can still examine memory and registers
Summary
By completing this exercise, you've:
- ✅ Set up OpenOCD as a debug server
- ✅ Connected GDB to a Pico 2 board
- ✅ Set a breakpoint and halted execution
- ✅ Examined assembly language in a live debugger
- ✅ Viewed CPU registers and their values
- ✅ Verified your dynamic debugging setup works
You're now ready for Week 2, where you'll:
- Step through code line by line
- Watch variables and memory change
- Understand program flow in detail
- Use this knowledge to modify running code
Next Steps
- Close GDB: Type
quitorqto exit - Close OpenOCD: Type
Ctrl+Cin the OpenOCD terminal - Review: Go back to the Ghidra exercises and compare static vs. dynamic analysis
- Prepare: Read through Week 2 materials to understand what's coming next