Files
Embedded-Hacking/WEEK05/WEEK05-03-S.md
2026-03-19 15:01:07 -04:00

4.5 KiB
Raw Permalink Blame History

Embedded Systems Reverse Engineering

Repository

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

  1. Why does 42.5 have a zero low word but 42.52525 does 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 is 010101000...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.

  2. The assembly uses ldrd r4, r5, [r5, #0x0] instead of two separate ldr instructions. 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 separate ldr instructions would require two instruction fetches, two decode cycles, and two memory accesses. ldrd reduces 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, ldrd is the optimal choice.

  3. 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 mantissa 010101000... while 42.52525 has 0101010000110011....

  4. 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) and DAT_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 be 0x645A1CAC (virtually impossible for an arbitrary target value).