11 KiB
Embedded Systems Reverse Engineering
Week 1
Introduction and Overview of Embedded Reverse Engineering: Ethics, Scoping, and Basic Concepts
Non-Credit Practice Exercise 4: Connect GDB & Basic Exploration
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 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