6.7 KiB
Embedded Systems Reverse Engineering
Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
Non-Credit Practice Exercise 2: Reverse Engineer gpio_set_pulls with GDB
Objective
Use GDB to disassemble the gpio_set_pulls function, trace its register writes to identify the PADS_BANK0 hardware register it modifies, and document how the Pico 2 configures internal pull-up and pull-down resistors at the hardware level.
Prerequisites
- Completed Week 6 tutorial (GDB section)
0x0014_static-variables.elfbinary available in your build directory- GDB (
arm-none-eabi-gdb) and OpenOCD installed - Understanding of GPIO pull-up resistors from Week 6 Part 2
- Understanding of function inlining from Week 6 Part 3
Task Description
You will use GDB to locate the gpio_set_pulls function (remember: gpio_pull_up is inlined and becomes gpio_set_pulls), disassemble it, step through it instruction by instruction, and identify the PADS_BANK0 register address it writes to. You will document the bit fields being set and explain how the hardware implements pull-up and pull-down resistors.
Step-by-Step Instructions
Step 1: Start the Debug Session
Terminal 1 - Start OpenOCD:
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"
Terminal 2 - Start GDB:
arm-none-eabi-gdb build\0x0014_static-variables.elf
Connect to target:
(gdb) target remote :3333
(gdb) monitor reset halt
Step 2: Find Where gpio_set_pulls is Called
From the tutorial, we know gpio_set_pulls is called at 0x1000024e with arguments for GPIO 15:
(gdb) x/5i 0x10000240
You should see:
0x10000240: movs r0, #15 ; GPIO pin 15
0x10000242: mov.w r3, #0 ; GPIO_IN (direction)
0x10000246: mcrr 0, 4, r0, r3, cr4 ; gpio_set_dir via coprocessor
0x1000024a: movs r2, #0 ; down = false
0x1000024c: movs r1, #1 ; up = true
0x1000024e: bl 0x100002d8 ; gpio_set_pulls
Step 3: Disassemble gpio_set_pulls
Now examine the function itself:
(gdb) x/30i 0x100002d8
Study the disassembly carefully. Look for:
- Address calculations (base address of PADS_BANK0)
- Register loads and stores
- Bit manipulation instructions (AND, OR, BIC)
- The write to the hardware register
Step 4: Set a Breakpoint at gpio_set_pulls
(gdb) b *0x100002d8
(gdb) monitor reset halt
(gdb) c
When the breakpoint hits, examine the arguments:
(gdb) info registers r0 r1 r2
You should see:
r0 = 15(GPIO pin)r1 = 1(pull-up enable)r2 = 0(pull-down disable)
Step 5: Step Through the Function
Use si (step instruction) to execute one instruction at a time:
(gdb) si
(gdb) info registers
Repeat, watching the registers change. Pay attention to:
- Address calculation: The function computes the PADS_BANK0 register address for GPIO 15. The base address is
0x40038000, and each GPIO pad has a 4-byte register. GPIO 0 starts at offset0x04, so GPIO 15 is at:
0x40038000 + 0x04 + (15 \times 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040
- Read the current register value: The function reads the existing pad configuration
- Modify the pull bits: It sets or clears the pull-up (bit 3) and pull-down (bit 2) bits
- Write back: It stores the modified value
Step 6: Examine the PADS_BANK0 Register
After the function completes, examine the result:
(gdb) x/1wx 0x40038040
Document the value you see. The relevant bits are:
| Bit | Name | Value | Meaning |
|---|---|---|---|
| 3 | PUE | 1 | Pull-up enable |
| 2 | PDE | 0 | Pull-down enable |
| 1 | SCHMITT | 1 | Schmitt trigger enabled |
| 0 | SLEWFAST | 0 | Slow slew rate |
Step 7: Compare with gpio_set_pulls for the LED Pin
Continue execution until gpio_set_pulls is called again (if it is). Or, examine the pad register for GPIO 16 (the LED pin):
(gdb) x/1wx 0x40038044
Compare the values. The LED pin (output) should NOT have pull-up enabled.
Step 8: Document Your Findings
Create a table documenting the function's behavior:
| Step | Instruction(s) | Register Changes |
|---|---|---|
| Load PADS base address | ldr rX, =... |
rX = 0x40038000 |
| Calculate pad offset | adds, lsls |
offset = 0x04 + pin * 4 |
| Read current pad config | ldr rX, [addr] |
rX = current register value |
| Clear pull bits | bic rX, rX, #0xC |
Clear bits 2 and 3 |
| Set pull-up bit | orr rX, rX, #0x8 |
Set bit 3 (PUE) |
| Write back | str rX, [addr] |
Updated pad register |
Expected Output
After completing this exercise, you should be able to:
- Disassemble and trace through a hardware configuration function
- Identify PADS_BANK0 register addresses for any GPIO pin
- Understand how pull-up and pull-down resistors are controlled at the register level
- Explain why
gpio_pull_up(pin)becomesgpio_set_pulls(pin, true, false)in the binary
Questions for Reflection
Question 1: Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first?
Question 2: The pad register for GPIO 15 is at offset 0x40 from the PADS base. How is this offset calculated? What would the offset be for GPIO 0?
Question 3: What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?
Question 4: The compiler inlines gpio_pull_up into gpio_set_pulls. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis?
Tips and Hints
- PADS_BANK0 base address on RP2350 is
0x40038000 - Each GPIO has a 4-byte pad control register starting at offset
0x04 - The
bicinstruction clears bits (Bit Clear);orrsets bits (OR) - Use
x/1wxto examine a 32-bit word; usex/1tbto see individual bits - The RP2350 datasheet section on PADS_BANK0 has the full register bit map
Next Steps
- Proceed to Exercise 3 to reverse engineer the
eor(XOR) instruction in the GPIO logic - Try enabling pull-down instead of pull-up by modifying the
gpio_set_pullsarguments in GDB:set $r1 = 0andset $r2 = 1before the function call - Examine the PADS registers for all GPIOs to see which pins have pull-ups enabled after boot