4.5 KiB
Embedded Systems Reverse Engineering
Week 5
Integers and Floats in Embedded Systems: Debugging and Hacking Integers and Floats w/ Intermediate GPIO Output Assembler Analysis
Non-Credit Practice Exercise 3 Solution: Analyze the Double Binary in Ghidra
Answers
Register Pair for 42.52525
| Register | Value | Role |
|---|---|---|
| r2 | 0x645A1CAC | Low 32 bits |
| r3 | 0x4045433B | High 32 bits |
Full double: 0x4045433B645A1CAC
IEEE 754 Decoding
r3 = 0x4045433B = 0100 0000 0100 0101 0100 0011 0011 1011
r2 = 0x645A1CAC = 0110 0100 0101 1010 0001 1100 1010 1100
Sign bit (bit 63): 0 → Positive
Exponent (bits 62-52): 10000000100 = 1028 → 1028 - 1023 = 5
Mantissa (bits 51-0): 0101010000110011101101100100010110100001110010101100
Value = 1.0101010000110011...₂ × 2⁵ = 101010.10000110011...₂
Integer part: 101010₂ = 32 + 8 + 2 = 42
Fractional part: .10000110011... ≈ 0.52525
Result: 42.52525 ✓
Float vs Double Comparison
| Item | Float (42.5) | Double (42.52525) |
|---|---|---|
| r2 (low word) | 0x00000000 | 0x645A1CAC |
| r3 (high word) | 0x40454000 | 0x4045433B |
| Low word is zero? | Yes | No |
| Words to patch | 1 (r3 only) | 2 (both r2 and r3) |
| Format specifier | %f | %lf |
| Assembly load instruction | movs + ldr | ldrd (load double) |
Key Assembly
10000238 push {r3,r4,r5,lr}
1000023a adr r5, [0x10000254]
1000023c ldrd r4, r5, [r5, #0x0] ; r4 = 0x645A1CAC, r5 = 0x4045433B
10000240 bl stdio_init_all
10000244 mov r2, r4 ; r2 = low word
10000246 mov r3, r5 ; r3 = high word
Reflection Answers
-
Why does
42.5have a zero low word but42.52525does not? The fractional part determines whether the low word is zero. 0.5 in binary is exactly 2⁻¹ =0.1₂—a single bit. After normalization, the mantissa for 42.5 is010101000...0, needing only 6 significant bits, all fitting in the top 20 mantissa bits within r3. In contrast, 0.52525 is a repeating binary fraction that cannot be represented exactly—it requires all 52 mantissa bits to approximate as closely as possible. The lower 32 bits in r2 (0x645A1CAC) carry the additional precision needed for this approximation. -
The assembly uses
ldrd r4, r5, [r5, #0x0]instead of two separateldrinstructions. What is the advantage?ldrd(Load Register Double) loads two consecutive 32-bit words from memory in a single instruction, completing in one memory access cycle (or two back-to-back aligned accesses on the bus). Using two separateldrinstructions would require two instruction fetches, two decode cycles, and two memory accesses.ldrdreduces code size by 4 bytes (one 4-byte instruction vs. two) and improves performance by allowing the memory controller to pipeline both loads. For 64-bit doubles that are always loaded in pairs,ldrdis the optimal choice. -
Both the float and double programs have the same exponent (stored as 1028, real exponent 5). Why? Both 42.5 and 42.52525 fall in the same range: between 32 (2⁵) and 64 (2⁶). Normalization produces
1.xxx × 2⁵for both values. The exponent is determined solely by the magnitude (which power of 2 the number falls between), not by the fractional precision. Any number from 32.0 to 63.999... would have real exponent 5 (stored as 1028). The mantissa captures the differences—42.5 has mantissa010101000...while 42.52525 has0101010000110011.... -
If you were patching this double, how many data constants would you need to modify compared to the float exercise? Two data constants—both
DAT_10000254(low word, r2) andDAT_10000258(high word, r3). In the float exercise (42.5), only one constant needed patching because r2 was zero and stayed zero when patching to 99.0. For the double 42.52525, since the low word is already non-zero (0x645A1CAC), any new value with a different repeating fraction will require changing both words. The only exception would be patching to a value whose low word happens to also be0x645A1CAC(virtually impossible for an arbitrary target value).