mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-16 13:19:14 +02:00
Added WEEK03
This commit is contained in:
@@ -84,6 +84,16 @@ This chapter covers the hacking of our firmware for the Pico 2 MCU hello, world
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Week 3 Notebook [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03.md)
|
||||
|
||||
#### Exercise 1: Trace a Reset [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-01.md)
|
||||
|
||||
#### Exercise 2: Find the Stack Size [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-02.md)
|
||||
|
||||
#### Exercise 3: Examine All Vectors [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-03.md)
|
||||
|
||||
#### Exercise 4: Find Your Main Function [HERE](https://github.com/mytechnotalent/Embedded-Hacking/blob/main/WEEK03/WEEK03-04.md)
|
||||
|
||||
### Chapter 4: Embedded System Analysis
|
||||
This chapter covers a comprehensive embedded system analysis reviewing parts of the RP2350 datasheet and helpful firmware analysis tools.
|
||||
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 1: Trace a Reset
|
||||
|
||||
#### Objective
|
||||
Single-step through the first 10 instructions of the reset handler to understand exactly what happens when the RP2350 powers on or resets.
|
||||
|
||||
#### Prerequisites
|
||||
- Raspberry Pi Pico 2 with debug probe connected
|
||||
- OpenOCD and `arm-none-eabi-gdb` available in your PATH
|
||||
- `build/0x0001_hello-world.elf` present and flashed to the board
|
||||
- Week 3 environment setup completed (OpenOCD running, GDB connected)
|
||||
|
||||
#### Task Description
|
||||
You will set a breakpoint at the reset handler (`0x1000015c`), trigger a reset, and step through each instruction one at a time while documenting what each instruction does.
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Start OpenOCD
|
||||
|
||||
```bash
|
||||
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"
|
||||
```
|
||||
|
||||
##### Step 2: Launch GDB
|
||||
|
||||
```bash
|
||||
arm-none-eabi-gdb build/0x0001_hello-world.elf
|
||||
```
|
||||
|
||||
##### Step 3: Connect to Target
|
||||
|
||||
```gdb
|
||||
(gdb) target extended-remote :3333
|
||||
```
|
||||
|
||||
##### Step 4: Set Breakpoint at Reset Handler
|
||||
|
||||
```gdb
|
||||
(gdb) b *0x1000015c
|
||||
```
|
||||
|
||||
**What this does:** Places a breakpoint at the very first instruction of the reset handler (the entry point after bootrom).
|
||||
|
||||
##### Step 5: Reset and Break
|
||||
|
||||
```gdb
|
||||
(gdb) monitor reset halt
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
**What this does:**
|
||||
- `monitor reset halt` resets the chip and immediately halts it
|
||||
- `c` continues execution until the breakpoint at the reset handler is hit
|
||||
|
||||
##### Step 6: Single-Step Through Instructions
|
||||
|
||||
Now step through the first 10 instructions, one at a time:
|
||||
|
||||
```gdb
|
||||
(gdb) si
|
||||
(gdb) disas $pc,+2
|
||||
(gdb) info registers r0
|
||||
```
|
||||
|
||||
Repeat `si` nine more times, examining each instruction.
|
||||
|
||||
**Example of what you'll see:**
|
||||
|
||||
**Instruction 1:**
|
||||
```
|
||||
0x1000015c <_reset_handler>: mov.w r0, #3489660928 @ 0xd0000000
|
||||
```
|
||||
**What it does:** Loads the SIO base address (0xd0000000) into r0
|
||||
|
||||
**Instruction 2:**
|
||||
```
|
||||
0x10000160 <_reset_handler+4>: ldr r0, [r0, #0]
|
||||
```
|
||||
**What it does:** Reads the CPUID register to determine which core is running
|
||||
|
||||
**Instruction 3:**
|
||||
```
|
||||
0x10000162 <_reset_handler+6>: cbz r0, 0x1000016a
|
||||
```
|
||||
**What it does:** If CPUID is 0 (Core 0), branch ahead to continue boot; otherwise handle Core 1
|
||||
|
||||
##### Step 7: Document Your Observations
|
||||
|
||||
For each of the 10 instructions:
|
||||
1. Write down the address
|
||||
2. Write down the assembly instruction
|
||||
3. Explain what it does
|
||||
4. Note any register changes using `info registers`
|
||||
|
||||
#### Expected Output
|
||||
- You should see the reset handler check which core is running
|
||||
- If you're on Core 0, you'll see it jump to the data copy section
|
||||
- Register `r0` will contain CPUID value (should be 0)
|
||||
- PC (program counter) advances with each `si` command
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### Question 1: Why does the reset handler check the CPUID before doing anything else?
|
||||
|
||||
###### Question 2: What would happen if Core 1 tried to run the same initialization code as Core 0?
|
||||
|
||||
###### Question 3: Which registers are used in the first 10 instructions, and why those specific ones?
|
||||
|
||||
#### Tips and Hints
|
||||
- Use `disas $pc,+20` to see upcoming instructions without stepping through them
|
||||
- Use `info registers` to see all register values at any point
|
||||
- If you step past where you wanted to stop, just `monitor reset halt` and start over
|
||||
- Keep notes as you go—this is detective work!
|
||||
|
||||
#### Next Steps
|
||||
- Try stepping all the way through to the data copy loop
|
||||
- Set a breakpoint at `0x1000016c` (the data copy loop) and continue there directly
|
||||
- Move on to Exercise 2 to calculate the stack size from the vector table
|
||||
|
||||
#### Additional Challenge
|
||||
Set a breakpoint at `0x10000178` (the BSS clear phase) and continue execution to see how the reset handler transitions from data copying to BSS clearing.
|
||||
@@ -0,0 +1,167 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 2: Find the Stack Size
|
||||
|
||||
#### Objective
|
||||
Calculate the size of the stack by examining the vector table, understanding the linker script's memory layout, and performing manual calculations.
|
||||
|
||||
#### Prerequisites
|
||||
- Raspberry Pi Pico 2 with debug probe connected
|
||||
- OpenOCD and `arm-none-eabi-gdb` available
|
||||
- `build/0x0001_hello-world.elf` flashed to the board
|
||||
- Understanding of memory regions from Week 3 Part 5 (Linker Script)
|
||||
|
||||
#### Task Description
|
||||
You will examine the initial stack pointer value from the vector table, identify the stack limit, calculate the total stack size in bytes and kilobytes, and verify your calculations.
|
||||
|
||||
#### Background Information
|
||||
|
||||
From the Week 3 lesson, we learned:
|
||||
- The initial stack pointer is stored at `0x10000000` (first entry in vector table)
|
||||
- The linker script defines: `SCRATCH_Y: ORIGIN = 0x20081000, LENGTH = 4k`
|
||||
- Stack top is calculated as: `ORIGIN + LENGTH = 0x20082000`
|
||||
- The stack grows downward from high addresses to low addresses
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Connect and Halt
|
||||
|
||||
```gdb
|
||||
(gdb) target extended-remote :3333
|
||||
(gdb) monitor reset halt
|
||||
```
|
||||
|
||||
##### Step 2: Examine the Initial Stack Pointer
|
||||
|
||||
```gdb
|
||||
(gdb) x/x 0x10000000
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
0x10000000 <__vectors>: 0x20082000
|
||||
```
|
||||
|
||||
This is the **top of the stack** (where the stack starts before growing downward).
|
||||
|
||||
##### Step 3: Find the Stack Limit
|
||||
|
||||
The stack limit is defined in the linker script and can be found by examining stack-related symbols or calculating from memory regions.
|
||||
|
||||
From the Week 3 lesson, the stack limit is `0x20078000`.
|
||||
|
||||
You can verify this in GDB:
|
||||
|
||||
```gdb
|
||||
(gdb) info symbol __StackLimit
|
||||
```
|
||||
|
||||
or check registers after boot:
|
||||
|
||||
```gdb
|
||||
(gdb) info registers
|
||||
```
|
||||
|
||||
Look for stack limit values or calculate: The main RAM starts at `0x20000000`, and SCRATCH_Y starts at `0x20081000`.
|
||||
|
||||
##### Step 4: Calculate Stack Size in Bytes
|
||||
|
||||
**Formula:**
|
||||
```
|
||||
Stack Size = Stack Top - Stack Limit
|
||||
Stack Size = 0x20082000 - 0x20078000
|
||||
```
|
||||
|
||||
Let's convert to decimal:
|
||||
- `0x20082000` = 537,108,480 decimal
|
||||
- `0x20078000` = 537,067,520 decimal
|
||||
- Difference = 40,960 bytes
|
||||
|
||||
**Alternative hex calculation:**
|
||||
```
|
||||
0x20082000
|
||||
- 0x20078000
|
||||
-----------
|
||||
0x0000A000 = 40,960 bytes
|
||||
```
|
||||
|
||||
##### Step 5: Convert to Kilobytes
|
||||
|
||||
```
|
||||
Bytes to KB = 40,960 ÷ 1,024 = 40 KB
|
||||
```
|
||||
|
||||
So the stack is **40 KB** in size.
|
||||
|
||||
##### Step 6: Verify Using Memory Regions
|
||||
|
||||
Cross-check with the memory layout:
|
||||
- **RAM**: `0x20000000` - `0x20080000` (512 KB)
|
||||
- **SCRATCH_X**: `0x20080000` - `0x20081000` (4 KB)
|
||||
- **SCRATCH_Y**: `0x20081000` - `0x20082000` (4 KB) ← Stack lives here
|
||||
- **Stack range**: `0x20078000` - `0x20082000` (40 KB)
|
||||
|
||||
The stack extends from SCRATCH_Y down into the upper portion of main RAM.
|
||||
|
||||
##### Step 7: Examine Stack Usage at Runtime
|
||||
|
||||
You can see the current stack pointer value:
|
||||
|
||||
```gdb
|
||||
(gdb) b main
|
||||
(gdb) c
|
||||
(gdb) info registers sp
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
sp 0x20081fc8 0x20081fc8
|
||||
```
|
||||
|
||||
This shows the stack has used:
|
||||
```
|
||||
0x20082000 - 0x20081fc8 = 0x38 = 56 bytes
|
||||
```
|
||||
|
||||
#### Expected Output
|
||||
- Initial stack pointer: `0x20082000`
|
||||
- Stack limit: `0x20078000`
|
||||
- Stack size: **40,960 bytes** or **40 KB**
|
||||
- Current stack usage (at main): approximately 56 bytes
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### Question 1: Why is the stack 40 KB instead of just fitting in the 4 KB SCRATCH_Y region?
|
||||
|
||||
###### Question 2: What happens if the stack grows beyond 0x20078000?
|
||||
|
||||
###### Question 3: How would you detect a stack overflow during runtime?
|
||||
|
||||
###### Question 4: Why does the stack grow downward instead of upward?
|
||||
|
||||
#### Tips and Hints
|
||||
- Use Windows Calculator in Programmer mode to convert hex to decimal
|
||||
- Remember: 1 KB = 1,024 bytes (not 1,000)
|
||||
- The stack pointer (SP) decreases as the stack grows (push operations)
|
||||
- Use `bt` (backtrace) in GDB to see how much stack is currently in use
|
||||
|
||||
#### Next Steps
|
||||
- Monitor the stack pointer as you step through functions to see it change
|
||||
- Calculate stack usage for specific function calls
|
||||
- Move on to Exercise 3 to examine all vector table entries
|
||||
|
||||
#### Additional Challenge
|
||||
Write a GDB command to automatically calculate and display stack usage:
|
||||
|
||||
```gdb
|
||||
(gdb) define stackusage
|
||||
> set $used = 0x20082000 - $sp
|
||||
> printf "Stack used: %d bytes (%d KB)\n", $used, $used/1024
|
||||
> end
|
||||
|
||||
(gdb) stackusage
|
||||
```
|
||||
@@ -0,0 +1,209 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### 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-gdb` available
|
||||
- `build/0x0001_hello-world.elf` loaded
|
||||
- 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
|
||||
(gdb) target extended-remote :3333
|
||||
(gdb) monitor reset halt
|
||||
```
|
||||
|
||||
##### Step 2: Examine 16 Vector Table Entries
|
||||
|
||||
```gdb
|
||||
(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
|
||||
(gdb) info symbol 0x1000015c
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
_reset_handler in section .text
|
||||
```
|
||||
|
||||
Repeat for other addresses:
|
||||
|
||||
```gdb
|
||||
(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
|
||||
(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 `bkpt` instructions (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 0x10000000` to see even more vectors (up to 48)
|
||||
- Remember to subtract 1 from addresses before disassembling (remove Thumb bit)
|
||||
- Use `info functions` to 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
|
||||
(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
|
||||
```
|
||||
@@ -0,0 +1,238 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 3
|
||||
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
|
||||
|
||||
### Exercise 4: Find Your Main Function and Trace Back
|
||||
|
||||
#### Objective
|
||||
Locate the `main()` function, examine its first instructions, identify the first function call, and trace backward to discover where `main()` was called from.
|
||||
|
||||
#### Prerequisites
|
||||
- Raspberry Pi Pico 2 with debug probe connected
|
||||
- OpenOCD and `arm-none-eabi-gdb` available
|
||||
- `build/0x0001_hello-world.elf` loaded
|
||||
- Understanding of function calls and the link register (LR) from previous weeks
|
||||
|
||||
#### Task Description
|
||||
You will use GDB to find `main()`, examine its disassembly, identify the initial function call (`stdio_init_all`), and use the link register to trace backward through the boot sequence.
|
||||
|
||||
#### Background Information
|
||||
|
||||
Key concepts:
|
||||
- **Link Register (LR)**: Stores the return address when a function is called
|
||||
- **Program Counter (PC)**: Points to the currently executing instruction
|
||||
- **Function prologue**: The setup code at the start of every function
|
||||
- **bl instruction**: "Branch with Link" - calls a function and stores return address in LR
|
||||
|
||||
#### Step-by-Step Instructions
|
||||
|
||||
##### Step 1: Connect and Halt
|
||||
|
||||
```gdb
|
||||
(gdb) target extended-remote :3333
|
||||
(gdb) monitor reset halt
|
||||
```
|
||||
|
||||
##### Step 2: Find the Main Function
|
||||
|
||||
```gdb
|
||||
(gdb) info functions main
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
All functions matching regular expression "main":
|
||||
|
||||
File 0x0001_hello-world.c:
|
||||
0x10000234 int main(void);
|
||||
|
||||
Non-debugging symbols:
|
||||
0x10000186 platform_entry_arm_a
|
||||
...
|
||||
```
|
||||
|
||||
Note the address of `main`: **`0x10000234`**
|
||||
|
||||
##### Step 3: Examine Instructions at Main
|
||||
|
||||
```gdb
|
||||
(gdb) x/10i 0x10000234
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
0x10000234 <main>: push {r7, lr}
|
||||
0x10000236 <main+2>: sub sp, #8
|
||||
0x10000238 <main+4>: add r7, sp, #0
|
||||
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
|
||||
0x1000023e <main+10>: movw r0, #404 @ 0x194
|
||||
0x10000242 <main+14>: movt r0, #4096 @ 0x1000
|
||||
0x10000246 <main+18>: bl 0x1000023c <__wrap_puts>
|
||||
0x1000024a <main+22>: b.n 0x1000023e <main+10>
|
||||
0x1000024c <runtime_init>: push {r3, r4, r5, r6, r7, lr}
|
||||
```
|
||||
|
||||
##### Step 4: Identify the First Function Call
|
||||
|
||||
The first function call in `main()` is:
|
||||
```
|
||||
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
|
||||
```
|
||||
|
||||
**What does this function do?**
|
||||
|
||||
```gdb
|
||||
(gdb) info functions stdio_init_all
|
||||
```
|
||||
|
||||
**Answer:** `stdio_init_all()` initializes all standard I/O systems (USB, UART, etc.) so `printf()` works.
|
||||
|
||||
##### Step 5: Set a Breakpoint at Main
|
||||
|
||||
```gdb
|
||||
(gdb) b main
|
||||
(gdb) c
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
Breakpoint 1, main () at 0x0001_hello-world.c:5
|
||||
5 stdio_init_all();
|
||||
```
|
||||
|
||||
##### Step 6: Examine the Link Register
|
||||
|
||||
When stopped at `main()`, check what's in the link register:
|
||||
|
||||
```gdb
|
||||
(gdb) info registers lr
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
lr 0x1000018b 268435851
|
||||
```
|
||||
|
||||
The LR contains the return address - where execution will go when `main()` returns.
|
||||
|
||||
##### Step 7: Disassemble the Caller
|
||||
|
||||
Subtract 1 to remove the Thumb bit and disassemble:
|
||||
|
||||
```gdb
|
||||
(gdb) x/10i 0x1000018a
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
0x10000186 <platform_entry>: ldr r1, [pc, #80]
|
||||
0x10000188 <platform_entry+2>: blx r1
|
||||
0x1000018a <platform_entry+4>: ldr r1, [pc, #80] ← LR points here
|
||||
0x1000018c <platform_entry+6>: blx r1 ← This called main
|
||||
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
|
||||
0x10000190 <platform_entry+10>: blx r1
|
||||
0x10000192 <platform_entry+12>: bkpt 0x0000
|
||||
```
|
||||
|
||||
##### Step 8: Understand the Call Chain
|
||||
|
||||
Working backward from `main()`:
|
||||
|
||||
```
|
||||
platform_entry (0x10000186)
|
||||
↓ calls (blx at +2)
|
||||
runtime_init() (0x1000024c)
|
||||
↓ calls (blx at +6)
|
||||
main() (0x10000234) ← We are here
|
||||
↓ will call (blx at +6)
|
||||
stdio_init_all() (0x100012c4)
|
||||
```
|
||||
|
||||
##### Step 9: Verify Platform Entry Calls Main
|
||||
|
||||
Look at what `platform_entry` loads before the `blx`:
|
||||
|
||||
```gdb
|
||||
(gdb) x/x 0x100001dc
|
||||
```
|
||||
|
||||
This is the address loaded into r1 before calling `blx`. It should point to `main()`.
|
||||
|
||||
**Expected output:**
|
||||
```
|
||||
0x100001dc <data_cpy_table+60>: 0x10000235
|
||||
```
|
||||
|
||||
Note: `0x10000235` = `0x10000234` + 1 (Thumb bit), which is the address of `main()`!
|
||||
|
||||
##### Step 10: Complete the Boot Trace
|
||||
|
||||
You've now traced the complete path:
|
||||
|
||||
```
|
||||
1. Reset (Power-on)
|
||||
↓
|
||||
2. Bootrom (0x00000000)
|
||||
↓
|
||||
3. Vector Table (0x10000000)
|
||||
↓
|
||||
4. _reset_handler (0x1000015c)
|
||||
↓
|
||||
5. Data Copy & BSS Clear
|
||||
↓
|
||||
6. platform_entry (0x10000186)
|
||||
↓
|
||||
7. runtime_init() (first call)
|
||||
↓
|
||||
8. main() (second call) ← Exercise focus
|
||||
↓
|
||||
9. stdio_init_all() (first line of main)
|
||||
```
|
||||
|
||||
#### Expected Output
|
||||
- `main()` is at address `0x10000234`
|
||||
- First function call is `stdio_init_all()` at offset +6
|
||||
- Link register points to `platform_entry+4` (0x1000018a)
|
||||
- `platform_entry` makes three function calls: runtime_init, main, and exit
|
||||
|
||||
#### Questions for Reflection
|
||||
|
||||
###### Question 1: Why does the link register point 4 bytes after the `blx` instruction that called main?
|
||||
|
||||
###### Question 2: What would happen if `main()` tried to return (instead of looping forever)?
|
||||
|
||||
###### Question 3: How can you tell from the disassembly that main contains an infinite loop?
|
||||
|
||||
###### Question 4: Why is `stdio_init_all()` called before the printf loop?
|
||||
|
||||
#### Tips and Hints
|
||||
- Use `bt` (backtrace) to see the call stack
|
||||
- Remember to account for Thumb mode when reading addresses from LR
|
||||
- Use `info frame` to see detailed information about the current stack frame
|
||||
- The `push {r7, lr}` at the start of main saves the return address
|
||||
|
||||
#### Next Steps
|
||||
- Set a breakpoint at `stdio_init_all()` and step through its initialization
|
||||
- Examine what happens after `main()` by looking at `exit()` function
|
||||
- Try Exercise 5 in Ghidra for static analysis of the boot sequence
|
||||
|
||||
#### Additional Challenge
|
||||
|
||||
Create a GDB command to automatically trace the call chain:
|
||||
|
||||
```gdb
|
||||
(gdb) define calltrace
|
||||
> set $depth = 0
|
||||
> set $addr = $pc
|
||||
> while $depth < 10
|
||||
> printf "%d: ", $depth
|
||||
> info symbol $addr
|
||||
> set $addr = *(int*)($lr - 4)
|
||||
> set $depth = $depth + 1
|
||||
> end
|
||||
> end
|
||||
```
|
||||
|
||||
Then try stepping through functions and running `calltrace` at each level to build a complete call graph.
|
||||
+1362
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user