4.7 KiB
Embedded Systems Reverse Engineering
Week 3
Embedded System Analysis: Understanding the RP2350 Architecture w/ Comprehensive Firmware Analysis
Non-Credit Practice Exercise 4 Solution: Find Your Main Function and Trace Back
Answers
Main Function Location
(gdb) info functions main
0x10000234 int main(void);
main() is at address 0x10000234.
Disassembly of main()
(gdb) x/10i 0x10000234
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>
First Function Call
The first function call is at offset +6:
0x1000023a <main+6>: bl 0x100012c4 <stdio_init_all>
stdio_init_all() initializes all standard I/O systems (USB CDC, UART) so printf() and puts() can output to the serial console.
Link Register (Caller Identification)
(gdb) b main
(gdb) c
(gdb) info registers lr
lr 0x1000018b 268435851
LR = 0x1000018b (Thumb address), actual return address = 0x1000018a.
Caller Disassembly
(gdb) x/10i 0x10000186
0x10000186 <platform_entry>: ldr r1, [pc, #80]
0x10000188 <platform_entry+2>: blx r1 → calls runtime_init()
0x1000018a <platform_entry+4>: ldr r1, [pc, #80] → LR points here (return from main)
0x1000018c <platform_entry+6>: blx r1 → THIS called main()
0x1000018e <platform_entry+8>: ldr r1, [pc, #80]
0x10000190 <platform_entry+10>: blx r1 → calls exit()
0x10000192 <platform_entry+12>: bkpt 0x0000
Complete Boot Chain
Power On
→ Bootrom (0x00000000)
→ Vector Table (0x10000000)
→ _reset_handler (0x1000015c)
→ Data Copy & BSS Clear
→ platform_entry (0x10000186)
→ runtime_init() (first blx)
→ main() (second blx) ← 0x10000234
→ stdio_init_all() (first call in main)
→ puts() loop (infinite)
Reflection Answers
-
Why does the link register point 4 bytes after the
blxinstruction that called main? The LR stores the return address—the instruction to execute aftermain()returns. Theblx r1instruction at0x10000188(which callsruntime_init) is 2 bytes, and theblx r1at0x1000018c(which callsmain) is also 2 bytes. The LR is set to the instruction immediately following theblxthat calledmain(), which is0x1000018a(theldrafterruntime_init's call). Actually,platform_entry+4at0x1000018ais where execution resumes, and the actualblxthat calls main is at+6(0x1000018c), so LR =0x1000018e+ Thumb bit. The key point: LR always points to the next instruction after the branch, so the caller can resume where it left off. -
What would happen if
main()tried to return (instead of looping forever)? Execution would return toplatform_entryat the address stored in LR. Looking at the disassembly,platform_entrywould proceed to execute the thirdblx r1at offset +10, which callsexit(). Theexit()function would perform cleanup and ultimately halt the processor. Afterexit(), there's abkptinstruction as a safety net. In practice on bare-metal embedded systems, returning frommain()is generally avoided because there's no OS to return to. -
How can you tell from the disassembly that main contains an infinite loop? The instruction at
0x1000024aisb.n 0x1000023e <main+10>, which is an unconditional branch back to themovw r0, #404instruction that loads the string address. This is ab(branch) with no condition code, meaning it always jumps backward. There is no path that reaches the end of the function or apop {r7, pc}(return). Thepush {r7, lr}at the start saves the return address but it's never restored—the function loops forever. -
Why is
stdio_init_all()called before the printf loop?stdio_init_all()configures the hardware interfaces (USB CDC and/or UART) thatprintf()/puts()uses for output. Without this initialization, the serial output drivers are not set up—writes to stdout would go nowhere or cause a fault. It must be called exactly once before any I/O operation. Calling it inside the loop would repeatedly reinitialize the hardware, wasting time and potentially disrupting active connections.