Refactor E and S

This commit is contained in:
Kevin Thomas
2026-03-19 15:01:07 -04:00
parent f524f5b86b
commit 1784a107ae
81 changed files with 2986 additions and 247 deletions
+53
View File
@@ -0,0 +1,53 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Non-Credit Practice Exercise 1 Solution: Change the Static Variable Initial Value from 42 to 100
#### Answers
##### Static Variable Location
| Item | Value | Notes |
|---------------------------|-------------|--------------------------------|
| RAM address (runtime) | 0x200005a8 | Where variable lives at runtime |
| Flash address (init value)| Calculated | In .data section of flash |
| Original value (hex) | 0x2A | 42 decimal |
| Patched value (hex) | 0x64 | 100 decimal |
| File offset | flash_addr - 0x10000000 | Binary base subtraction |
##### GDB Session
```gdb
(gdb) x/1db 0x200005a8
0x200005a8: 42
(gdb) find /b 0x10000000, 0x10010000, 0x2a
```
##### Serial Output After Patch
```
regular_fav_num: 42
static_fav_num: 100
regular_fav_num: 42
static_fav_num: 101
regular_fav_num: 42
static_fav_num: 102
...
```
#### Reflection Answers
1. **Why does the initial value live in flash AND get copied to RAM? Why not just use flash directly?**
Static variables need to be **modifiable** at runtime—the program increments `static_fav_num` each iteration. Flash memory is read-only during normal execution (it requires a special erase/program sequence to modify). So the initial value is stored in flash as a template, and the startup code (`crt0.S`) copies the entire `.data` section from flash to RAM before `main()` runs. This gives the variable its correct starting value (42) in writable RAM where subsequent `adds` and `strb` instructions can modify it freely.
2. **The static variable wraps around at 255 (since it's `uint8_t`). After patching the initial value to 100, after how many iterations will it overflow back to 0?**
A `uint8_t` overflows from 255 to 0. Starting at 100 and incrementing by 1: it takes `255 - 100 = 155` increments to reach 255, then one more to wrap to 0. So it overflows after **156 iterations**. Compare to the original: starting at 42, it takes `255 - 42 + 1 = 214` iterations.
3. **If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?**
No. `regular_fav_num` is a **local variable** that the compiler optimized to an immediate constant (`movs r1, #0x2a`), just like Week 4's `age` variable. It's encoded directly in the instruction opcode in the `.text` section, not in the `.data` section. You would need to find the `movs r1, #0x2a` instruction in the code and patch the immediate byte from `0x2a` to your desired value. The `.data` section only contains initialized static/global variables.
4. **What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?**
Yes. The linker places all initialized static variables contiguously in the `.data` section. Their initial values are stored in the same order in flash, packed adjacent to each other (possibly with alignment padding). The startup code performs a single `memcpy`-like loop that copies the entire `.data` block from flash to RAM. So if you had `static uint8_t a = 42;` and `static uint8_t b = 99;`, the bytes `0x2A` and `0x63` would be adjacent (or nearly so) in flash, and both would be copied to their respective RAM addresses during boot.
+7 -7
View File
@@ -1,10 +1,10 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 1: Change the Static Variable Initial Value from 42 to 100
### Non-Credit Practice Exercise 1: Change the Static Variable Initial Value from 42 to 100
#### Objective
Use GDB to locate the static variable `static_fav_num` in the `.data` section of the `0x0014_static-variables` binary, calculate the corresponding file offset, patch the initial value from `42` (`0x2A`) to `100` (`0x64`) using a hex editor, convert the patched binary to UF2 format, and flash it to the Pico 2 to verify the change.
@@ -68,7 +68,7 @@ Examine the disassembly to find data copy references. The initial value `42` (`0
(gdb) find /b 0x10000000, 0x10010000, 0x2a
```
This searches the flash region for the byte `0x2A`. You may get multiple hits — look for one that is in the data initialization area (typically near the end of the code section).
This searches the flash region for the byte `0x2A`. You may get multiple hits — look for one that is in the data initialization area (typically near the end of the code section).
##### Step 4: Confirm the Address
@@ -98,7 +98,7 @@ For example, if the initial value is at `0x10004xxx`:
3. Enter the calculated offset
4. You should see the byte `2A` at this position
5. Change `2A` to `64` (100 in decimal)
6. Click **File** **Save As** `0x0014_static-variables-h.bin` (in the same `build` directory)
6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
##### Step 7: Convert to UF2 and Flash
@@ -116,7 +116,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam
**Expected serial output:**
```
regular_fav_num: 42
static_fav_num: 100 Starts at 100 now!
static_fav_num: 100 ? Starts at 100 now!
regular_fav_num: 42
static_fav_num: 101
regular_fav_num: 42
@@ -143,13 +143,13 @@ After completing this exercise, you should be able to:
###### Question 3: If you also wanted to change the `regular_fav_num` constant from 42, would you patch the same area of the binary? Why or why not?
###### Question 4: What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?
###### Question 4: What would happen if the `.data` section had TWO static variables — would their initial values be adjacent in flash?
#### Tips and Hints
- The `find` command in GDB can search for bytes, halfwords, or words in any memory range
- Static variables with initial values are in `.data`; without initial values they're in `.bss`
- The startup code (`crt0`) copies the entire `.data` section from flash to RAM before calling `main()`
- HxD shows both hex and ASCII — the value `0x2A` is the ASCII character `*`
- HxD shows both hex and ASCII — the value `0x2A` is the ASCII character `*`
#### Next Steps
- Proceed to Exercise 2 to try a more complex hack (reversing GPIO logic)
+63
View File
@@ -0,0 +1,63 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Non-Credit Practice Exercise 2 Solution: Reverse Engineer gpio_set_pulls with GDB
#### Answers
##### Function Arguments
```gdb
(gdb) b *0x100002d8
(gdb) c
(gdb) info registers r0 r1 r2
r0 = 15 ; GPIO pin
r1 = 1 ; pull-up enable (true)
r2 = 0 ; pull-down disable (false)
```
##### PADS_BANK0 Address Calculation
```
Base: 0x40038000 (PADS_BANK0)
GPIO 0 offset: 0x04 (first pad register)
GPIO N offset: 0x04 + (N × 4)
GPIO 15: 0x40038000 + 0x04 + (15 × 4) = 0x40038000 + 0x04 + 0x3C = 0x40038040
```
##### Function Behavior Summary
| Step | Instruction(s) | Effect |
|------------------------|---------------------|-----------------------------------|
| Load PADS base address | `ldr rX, =0x40038000` | rX = PADS_BANK0 base |
| Calculate pad offset | `adds`, `lsls` | offset = 0x04 + pin × 4 |
| Read current config | `ldr rX, [addr]` | Read existing pad register value |
| Clear pull bits | `bic rX, rX, #0xC` | Clear bits 2 (PDE) and 3 (PUE) |
| Set pull-up bit | `orr rX, rX, #0x8` | Set bit 3 (PUE = pull-up enable) |
| Write back | `str rX, [addr]` | Write updated config to hardware |
##### Pad Register Bits
| Bit | Name | Value | Meaning |
|-----|----------|-------|-------------------------|
| 3 | PUE | 1 | Pull-up enable |
| 2 | PDE | 0 | Pull-down disable |
| 1 | SCHMITT | 1 | Schmitt trigger enabled |
| 0 | SLEWFAST | 0 | Slow slew rate |
#### Reflection Answers
1. **Why does the function read-modify-write the register instead of just writing a new value? What would happen if it just wrote without reading first?**
The pad register contains multiple configuration fields (drive strength, slew rate, Schmitt trigger, input enable, output disable, pull-up, pull-down). If the function wrote a new value without reading first, it would overwrite all other fields with zeros or arbitrary values, potentially disabling the Schmitt trigger, changing drive strength, or disabling input/output. The read-modify-write pattern uses `bic` to clear only the pull bits (2 and 3) and `orr` to set the desired pull configuration, leaving all other bits untouched.
2. **The pad register for GPIO 15 is at offset `0x40` from the PADS base. How is this offset calculated? What would the offset be for GPIO 0?**
The formula is: offset = `0x04 + (GPIO_number × 4)`. For GPIO 15: `0x04 + (15 × 4) = 0x04 + 0x3C = 0x40`. The `0x04` initial offset exists because offset `0x00` is the VOLTAGE_SELECT register, not a pad register. For GPIO 0: offset = `0x04 + (0 × 4) = 0x04`. So GPIO 0's pad register is at `0x40038004`.
3. **What would happen if you enabled BOTH pull-up and pull-down at the same time (bits 2 and 3 both set)?**
Enabling both creates a **resistive voltage divider** between VDD and GND through the internal pull resistors. On the RP2350, both pull resistors are typically ~50kΩ. The pin voltage would settle at approximately VDD/2 (1.65V for 3.3V supply), which is in the undefined region between logic HIGH and LOW thresholds. This makes the digital input unreliable—the Schmitt trigger may oscillate or read randomly. While not damaging to the hardware, it wastes power and produces unpredictable input reads. The SDK intentionally never sets both bits simultaneously.
4. **The compiler inlines `gpio_pull_up` into `gpio_set_pulls`. What does this tell you about the compiler's optimization level? How does inlining affect binary analysis?**
This indicates at least `-O1` or higher optimization (the Pico SDK defaults to `-O2`). The `gpio_pull_up` function is declared `static inline` in the SDK header, and the compiler eliminates the function call overhead by inserting `gpio_set_pulls(pin, true, false)` directly. For binary analysis, inlining means: (a) the original function name `gpio_pull_up` doesn't appear in the symbol table, (b) you see `gpio_set_pulls` called directly with hardcoded arguments, making it harder to identify the programmer's original intent, and (c) multiple calls to `gpio_pull_up` with different pins each become separate `gpio_set_pulls` calls rather than referencing a single function body.
+2 -2
View File
@@ -1,10 +1,10 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 2: Reverse Engineer gpio_set_pulls with GDB
### Non-Credit Practice Exercise 2: Reverse Engineer gpio_set_pulls with GDB
#### Objective
Use GDB to disassemble the `gpio_set_pulls` function, trace its register writes to identify the PADS_BANK0 hardware register it modifies, and document how the Pico 2 configures internal pull-up and pull-down resistors at the hardware level.
+57
View File
@@ -0,0 +1,57 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Non-Credit Practice Exercise 3 Solution: Make the Overflow Happen Faster
#### Answers
##### Patch Details
| Item | Original | Patched |
|--------------------|-----------------|-----------------|
| Instruction | adds r3, #0x1 | adds r3, #0xa |
| Address | 0x1000027c | 0x1000027c |
| Hex bytes | 01 33 | 0A 33 |
| Increment value | 1 | 10 |
| File offset | 0x27c | 0x27c |
##### Instruction Encoding
```
Thumb adds rD, #imm8:
Byte 0: immediate value (0x01 → 0x0A)
Byte 1: opcode + register (0x33 = adds r3)
```
##### Serial Output After Patch
```
regular_fav_num: 42
static_fav_num: 42
regular_fav_num: 42
static_fav_num: 52
regular_fav_num: 42
static_fav_num: 62
...
regular_fav_num: 42
static_fav_num: 252
regular_fav_num: 42
static_fav_num: 6 ← Overflow! 252 + 10 = 262 mod 256 = 6
```
#### Reflection Answers
1. **The overflow now wraps to 6 instead of 0. Explain why, using the modular arithmetic of a `uint8_t` (range 0-255).**
A `uint8_t` stores values modulo 256. Starting at 42 and incrementing by 10: the sequence passes through 42, 52, 62, ..., 242, 252. The next value is 252 + 10 = 262. Since `uint8_t` can only hold 0255: $262 \bmod 256 = 6$. The wrap value is non-zero because the increment (10) does not evenly divide into 256. With increment 1, the value hits exactly 255, and $255 + 1 = 256 \bmod 256 = 0$. With increment 10, it skips from 252 directly to 262, bypassing 0 and landing on 6.
2. **What is the maximum value you could change the increment to while still using `adds r3, #imm8`? What would happen if you needed an increment larger than 255?**
The maximum is **255** (`0xFF`). The `adds rD, #imm8` Thumb encoding has an 8-bit immediate field, so valid values are 0255. For an increment larger than 255, you would need to: (a) use a 32-bit Thumb-2 `adds.w` instruction which supports a wider range of modified immediates, (b) use a `movs` + `adds` two-instruction sequence to load a larger value, or (c) load the value from a literal pool with `ldr` then use a register-register `add`. Each approach requires different instruction sizes, so patching in a hex editor becomes more complex—you may need to shift code or use NOP padding.
3. **If you changed the increment to 128 (`0x80`), how many iterations would it take to wrap, and what value would it wrap to?**
Starting at 42, incrementing by 128: 42 → 170 → 42 → 170 → ... Wait, let's compute: $42 + 128 = 170$ (first iteration), $170 + 128 = 298 \bmod 256 = 42$ (second iteration). It wraps after **2 iterations** back to 42. The variable alternates between 42 and 170 forever because $2 \times 128 = 256$, and $42 + 256 = 42 \bmod 256$. The value never reaches 0—it cycles between exactly two values.
4. **Could you achieve the same speedup by changing the `strb` (store byte) to `strh` (store halfword)? Why or why not?**
No. Changing `strb` to `strh` would store 16 bits instead of 8, which means the variable would be treated as a `uint16_t` (range 065535). This would actually **slow down** overflow—it would take 65,535 42 = 65,493 iterations to wrap instead of 213. Additionally, `strh` writes 2 bytes to RAM, potentially corrupting the adjacent byte at `0x200005a9`. The proper way to speed up overflow is to increase the increment value, not change the storage width. The `strb` truncation to 8 bits is what enforces the `uint8_t` modular arithmetic.
+10 -10
View File
@@ -1,13 +1,13 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 3: Make the Overflow Happen Faster
### Non-Credit Practice Exercise 3: Make the Overflow Happen Faster
#### Objective
Patch the `adds r3, #0x1` instruction — which increments `static_fav_num` by 1 each loop iteration — to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner.
Patch the `adds r3, #0x1` instruction — which increments `static_fav_num` by 1 each loop iteration — to `adds r3, #0xa` so the variable increments by 10 instead. Use GDB to locate the instruction, calculate the hex editor file offset, patch the binary, and verify on hardware that the `uint8_t` overflow occurs roughly 10 times sooner.
#### Prerequisites
- Completed Week 6 tutorial (GDB and hex editor sections)
@@ -61,7 +61,7 @@ Look for this sequence:
```
0x10000278: ldrb r3, [r4, #0] ; Load static_fav_num from RAM
0x1000027a: movs r2, #16 ; LED GPIO pin number
0x1000027c: adds r3, #1 ; Increment by 1 THIS IS OUR TARGET
0x1000027c: adds r3, #1 ; Increment by 1 ? THIS IS OUR TARGET
0x1000027e: strb r3, [r4, #0] ; Store back to RAM
```
@@ -116,10 +116,10 @@ For the `adds r3, #0x1` instruction at its address, calculate the offset.
2. Press **Ctrl+G** (Go to offset) and enter the calculated offset
3. You should see the byte `01` followed by `33`
4. Change `01` to `0A` (10 in decimal)
5. Verify: the bytes should now read `0A 33` — encoding `adds r3, #0xa`
6. Click **File** **Save As** `0x0014_static-variables-h.bin` (in the same `build` directory)
5. Verify: the bytes should now read `0A 33` — encoding `adds r3, #0xa`
6. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
> 🔍 **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe.
> ?? **Why this works:** In Thumb `adds rD, #imm8` encoding, the immediate value is stored in the first byte. The `#imm8` field accepts values 0-255, so changing 1 to 10 is safe.
##### Step 7: Convert to UF2 and Flash
@@ -139,7 +139,7 @@ python ..\uf2conv.py build\0x0014_static-variables-h.bin --base 0x10000000 --fam
regular_fav_num: 42
static_fav_num: 42
regular_fav_num: 42
static_fav_num: 52 Jumped by 10!
static_fav_num: 52 ? Jumped by 10!
regular_fav_num: 42
static_fav_num: 62
...
@@ -148,10 +148,10 @@ static_fav_num: 242
regular_fav_num: 42
static_fav_num: 252
regular_fav_num: 42
static_fav_num: 6 Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6
static_fav_num: 6 ? Overflow! 252 + 10 = 262, but uint8_t wraps: 262 - 256 = 6
```
Notice the overflow now happens much sooner, and the wrap value is no longer 0 — it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`.
Notice the overflow now happens much sooner, and the wrap value is no longer 0 — it's 6 because `252 + 10 = 262` which wraps to `262 mod 256 = 6`.
#### Expected Output
+60
View File
@@ -0,0 +1,60 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Non-Credit Practice Exercise 4 Solution: Invert the Button Logic with XOR
#### Answers
##### Patch Details
| Item | Original | Patched |
|--------------------|-----------------------|-----------------------|
| Instruction | eor.w r3, r3, #1 | eor.w r3, r3, #0 |
| Address | 0x10000286 | 0x10000286 |
| Hex bytes | 83 F0 01 03 | 83 F0 00 03 |
| Patched byte offset| 0x288 (3rd byte) | 01 → 00 |
##### Logic Table Comparison
**Original (EOR #1):**
| Button State | GPIO 15 | After UBFX | After EOR #1 | LED (GPIO 16) |
|-------------|---------|------------|-------------|---------------|
| Released | 1 (HIGH)| 1 | 0 | OFF |
| Pressed | 0 (LOW) | 0 | 1 | ON |
**Patched (EOR #0):**
| Button State | GPIO 15 | After UBFX | After EOR #0 | LED (GPIO 16) |
|-------------|---------|------------|-------------|---------------|
| Released | 1 (HIGH)| 1 | 1 | **ON** |
| Pressed | 0 (LOW) | 0 | 0 | **OFF** |
##### Hardware Result
- Button NOT pressed: LED **ON** (was OFF)
- Button PRESSED: LED **OFF** (was ON)
- Behavior completely reversed by changing a single byte (01 → 00)
#### Reflection Answers
1. **Why does XOR with 1 act as a NOT for single-bit values? Write out the truth table for `x XOR 1` and `x XOR 0` where x is 0 or 1.**
| x | x XOR 1 | x XOR 0 |
|---|---------|---------|
| 0 | 1 | 0 |
| 1 | 0 | 1 |
`x XOR 1` always flips the bit (acts as NOT): 0→1, 1→0. `x XOR 0` always preserves the bit (acts as identity): 0→0, 1→1. This works because XOR returns 1 when inputs differ and 0 when they match. XOR with 1 forces a difference; XOR with 0 forces a match. This property only applies to the single affected bit—for multi-bit values, each bit is XORed independently.
2. **Instead of changing `eor.w r3, r3, #1` to `eor.w r3, r3, #0`, could you achieve the same result by NOPing (removing) the instruction entirely? What bytes encode a NOP in Thumb?**
Yes. Removing the EOR instruction entirely would have the same effect as EOR #0—the value passes through unchanged. A Thumb NOP is encoded as `00 BF` (2 bytes). Since `eor.w` is a 32-bit Thumb-2 instruction (4 bytes), you would need **two** NOPs to replace it: `00 BF 00 BF`. In the hex editor, replace bytes at offset 0x2860x289 from `83 F0 01 03` to `00 BF 00 BF`. Both approaches yield identical behavior, but the EOR #0 patch is "cleaner" because it preserves the instruction structure—making the modification more obvious during reverse engineering.
3. **The pull-up resistor means "pressed = LOW." If you removed the pull-up (changed `gpio_pull_up` to no pull), would the button still work? Why or why not?**
It would be unreliable. Without a pull-up or pull-down resistor, the GPIO input is **floating** when the button is not pressed—there's no defined voltage on the pin. The input would pick up electrical noise, stray capacitance, and electromagnetic interference, causing random readings (0 or 1 unpredictably). When the button IS pressed, it connects to ground (LOW), which works. But when released, the pin has no path to any voltage, so `gpio_get(15)` returns garbage. The pull-up provides a defined HIGH state when the button circuit is open.
4. **The `ubfx r3, r3, #0xf, #0x1` instruction extracts bit 15. If you changed `#0xf` to `#0x10` (bit 16), what GPIO pin would you be reading? What value would you get if nothing is connected to that pin?**
You would be reading **GPIO 16**, which is the LED output pin. Since GPIO 16 is configured as an output (not input), reading its input register returns the current output state—either 0 or 1 depending on whether the LED is currently on or off. This would create a **feedback loop**: the LED's current state determines its next state (after the XOR), causing unpredictable oscillation or a stuck state. If GPIO 16 had nothing connected and was unconfigured, the floating input would return random values, similar to Q3's scenario.
+14 -14
View File
@@ -1,13 +1,13 @@
# Embedded Systems Reverse Engineering
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 6
Static Variables in Embedded Systems: Debugging and Hacking Static Variables w/ GPIO Input Basics
### Exercise 4: Invert the Button Logic with XOR
### Non-Credit Practice Exercise 4: Invert the Button Logic with XOR
#### Objective
Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released — the opposite of the original behavior.
Find the `eor.w r3, r3, #1` instruction that implements the ternary operator's button inversion, patch it to `eor.w r3, r3, #0` using a hex editor to reverse the LED behavior, and verify that the LED is now ON when the button is pressed and OFF when released — the opposite of the original behavior.
#### Prerequisites
- Completed Week 6 tutorial (all GDB and hex editor sections)
@@ -61,7 +61,7 @@ Look for this sequence:
0x10000274: mov.w r1, #0xd0000000 ; SIO base address
0x10000280: ldr r3, [r1, #4] ; Read GPIO input register
0x10000282: ubfx r3, r3, #15, #1 ; Extract bit 15 (button state)
0x10000286: eor.w r3, r3, #1 ; XOR with 1 — INVERT OUR TARGET
0x10000286: eor.w r3, r3, #1 ; XOR with 1 — INVERT ? OUR TARGET
0x1000028a: mcrr 0, 4, r2, r3, cr0 ; Write to GPIO output
```
@@ -93,8 +93,8 @@ When it hits, check what value is about to be XORed:
(gdb) info registers r3
```
- If button is **released**: `r3 = 1` after EOR: `r3 = 0`
- If button is **pressed**: `r3 = 0` after EOR: `r3 = 1`
- If button is **released**: `r3 = 1` ? after EOR: `r3 = 0`
- If button is **pressed**: `r3 = 0` ? after EOR: `r3 = 1`
##### Step 5: Test the Patch in GDB
@@ -118,9 +118,9 @@ Look at the raw bytes:
```
The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as:
- `0x83 0xF0` — opcode + source register (r3)
- `0x01`**the immediate value (`#1`)** this is what we change
- `0x03` — destination register (r3)
- `0x83 0xF0` — opcode + source register (r3)
- `0x01`**the immediate value (`#1`)** ? this is what we change
- `0x03` — destination register (r3)
##### Step 7: Patch with the Hex Editor
@@ -128,11 +128,11 @@ The `eor.w` instruction is a 32-bit Thumb-2 encoding. The 4 bytes break down as:
2. The instruction starts at file offset: `0x10000286 - 0x10000000 = 0x286`
3. The immediate byte is the 3rd byte: offset `0x286 + 2 = 0x288`
4. Press **Ctrl+G** and enter offset: `288`
5. You should see `01` — change it to `00`
5. You should see `01` — change it to `00`
6. Verify the surrounding bytes (`83 F0` before and `03` after) are unchanged
7. Click **File** **Save As** `0x0014_static-variables-h.bin` (in the same `build` directory)
7. Click **File** ? **Save As** ? `0x0014_static-variables-h.bin` (in the same `build` directory)
> 🔍 **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`.
> ?? **Why offset `0x288`?** The 4-byte instruction starts at `0x286`, but the immediate value `#1` is in the **third byte** (index 2), so it's at `0x286 + 2 = 0x288`.
##### Step 8: Predict the New Behavior
@@ -183,9 +183,9 @@ After completing this exercise, you should be able to:
#### Tips and Hints
- `eor.w r3, r3, #1` is a 32-bit Thumb-2 instruction (4 bytes), not a 16-bit Thumb instruction
- A Thumb NOP is `00 bf` (2 bytes) — you would need two NOPs to replace a 4-byte instruction
- A Thumb NOP is `00 bf` (2 bytes) — you would need two NOPs to replace a 4-byte instruction
- Use GDB `x/1tw` to view a word in binary format, making bit manipulation easier to see
- The SIO base address `0xd0000000` provides single-cycle access to GPIO — it's separate from the IO_BANK0 registers at `0x40028000`
- The SIO base address `0xd0000000` provides single-cycle access to GPIO — it's separate from the IO_BANK0 registers at `0x40028000`
#### Next Steps
- Review all four exercises and verify you can patch any part of the binary: data values, arithmetic operations, and logic operations