6.2 KiB
Embedded Systems Reverse Engineering
Week 3
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
Non-Credit Practice Exercise 3: Examine All Vectors
Objective
Examine the first 16 entries of the vector table to understand the exception handler layout, identify valid code addresses, and recognize the Thumb mode addressing convention.
Prerequisites
- Raspberry Pi Pico 2 with debug probe connected
- OpenOCD and
arm-none-eabi-gdbavailable build\0x0001_hello-world.elfloaded- Understanding of the vector table from Week 3 Part 4
- Knowledge of Thumb mode addressing (LSB = 1 indicates Thumb code)
Task Description
You will examine 16 consecutive 32-bit values from the vector table, decode each entry, determine if it's a valid code address, and identify which exception handler it points to.
Background Information
The ARM Cortex-M vector table structure:
| Offset | Vector # | Handler Name | Purpose |
|---|---|---|---|
| 0x00 | - | Initial SP | Stack pointer initialization |
| 0x04 | 1 | Reset | Power-on/reset entry point |
| 0x08 | 2 | NMI | Non-Maskable Interrupt |
| 0x0C | 3 | HardFault | Serious errors |
| 0x10 | 4 | MemManage | Memory protection fault |
| 0x14 | 5 | BusFault | Bus error |
| 0x18 | 6 | UsageFault | Undefined instruction, etc. |
| 0x1C-0x28 | 7-10 | Reserved | Not used |
| 0x2C | 11 | SVCall | Supervisor call |
| 0x30 | 12 | Debug Monitor | Debug events |
| 0x34 | 13 | Reserved | Not used |
| 0x38 | 14 | PendSV | Pendable service call |
| 0x3C | 15 | SysTick | System tick timer |
Step-by-Step Instructions
Step 1: Connect and Halt
(gdb) target extended-remote :3333
(gdb) monitor reset halt
Step 2: Examine 16 Vector Table Entries
(gdb) x/16x 0x10000000
Expected output (example):
0x10000000 <__vectors>: 0x20082000 0x1000015d 0x1000011b 0x1000011d
0x10000010 <__vectors+16>: 0x1000011f 0x10000121 0x10000123 0x00000000
0x10000020 <__vectors+32>: 0x00000000 0x00000000 0x00000000 0x10000125
0x10000030 <__vectors+48>: 0x00000000 0x00000000 0x10000127 0x10000129
Step 3: Analyze Each Entry
Create a table documenting each entry:
Entry 1 (Offset 0x00):
Address: 0x10000000
Value: 0x20082000
Valid Code Address? NO - This is the stack pointer (in RAM region 0x2xxxxxxx)
Handler: Initial Stack Pointer
Entry 2 (Offset 0x04):
Address: 0x10000004
Value: 0x1000015d
Valid Code Address? YES (starts with 0x1000...)
Thumb Mode? YES (LSB = 1, so actual address is 0x1000015c)
Handler: Reset Handler (_reset_handler)
Entry 3 (Offset 0x08):
Address: 0x10000008
Value: 0x1000011b
Valid Code Address? YES
Thumb Mode? YES (actual address: 0x1000011a)
Handler: NMI Handler (isr_nmi)
Continue this analysis for all 16 entries...
Step 4: Verify Handlers with Symbols
For each code address, check what function it points to:
(gdb) info symbol 0x1000015c
Expected output:
_reset_handler in section .text
Repeat for other addresses:
(gdb) info symbol 0x1000011a
(gdb) info symbol 0x1000011c
(gdb) info symbol 0x1000011e
Step 5: Examine Handler Code
Look at the actual code at each handler:
(gdb) x/3i 0x1000011a
Expected output for NMI handler:
0x1000011a <isr_nmi>: bkpt 0x0000
0x1000011c <isr_hardfault>: bkpt 0x0000
0x1000011e <isr_svcall>: bkpt 0x0000
Step 6: Identify Reserved Entries
Note any entries with value 0x00000000:
0x00000000 = Reserved/Unused vector
These slots are reserved by ARM and not used on Cortex-M33.
Step 7: Create a Complete Map
Document all 16 entries in this format:
| Offset | Value | Address Type | Actual Addr | Handler Name |
|---|---|---|---|---|
| 0x00 | 0x20082000 | Stack Ptr | N/A | __StackTop |
| 0x04 | 0x1000015d | Code (Thumb) | 0x1000015c | _reset_handler |
| 0x08 | 0x1000011b | Code (Thumb) | 0x1000011a | isr_nmi |
| 0x0C | 0x1000011d | Code (Thumb) | 0x1000011c | isr_hardfault |
| ... | ... | ... | ... | ... |
Expected Output
- First entry is the stack pointer in RAM (0x2xxxxxxx range)
- Entries 2-16 are mostly code addresses in flash (0x1000xxxx range)
- Code addresses have LSB = 1 (Thumb mode indicator)
- Reserved entries show 0x00000000
- Most handlers point to simple
bkptinstructions (default handlers)
Questions for Reflection
Question 1: Why do all the code addresses end in odd numbers (LSB = 1)?
Question 2: What happens if an exception occurs for a reserved/null vector entry?
Question 3: Why do most exception handlers just contain bkpt instructions?
Question 4: How would you replace a default handler with your own custom handler?
Tips and Hints
- Use
x/32x 0x10000000to see even more vectors (up to 48) - Remember to subtract 1 from addresses before disassembling (remove Thumb bit)
- Use
info functionsto see all available handler symbols - Compare GDB output with Ghidra's vector table view
Next Steps
- Set breakpoints at different exception handlers to see if they're ever called
- Trigger a fault intentionally to see which handler executes
- Move on to Exercise 4 to analyze your main function
Additional Challenge
Write a GDB script to automatically decode and display all vector table entries:
(gdb) define vectors
> set $i = 0
> while $i < 16
> set $addr = 0x10000000 + ($i * 4)
> set $val = *(int*)$addr
> printf "[%2d] 0x%08x: 0x%08x", $i, $addr, $val
> if $i == 0
> printf " (Stack Pointer)\n"
> else
> if $val != 0
> if ($val & 0x10000000) == 0x10000000
> printf " -> 0x%08x\n", $val & 0xFFFFFFFE
> else
> printf " (Invalid/Reserved)\n"
> end
> else
> printf " (Reserved)\n"
> end
> end
> set $i = $i + 1
> end
> end