mirror of
https://github.com/mytechnotalent/Embedded-Hacking.git
synced 2026-05-24 08:24:11 +02:00
Refactor E and S
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 1 Solution: Analyze Variable Storage in Ghidra
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Main Function Analysis
|
||||
|
||||
| Item | Value/Location | Notes |
|
||||
|-----------------------|---------------|---------------------------------|
|
||||
| Main function address | 0x10000234 | Entry point of program |
|
||||
| Age value (hex) | 0x2b | Optimized constant |
|
||||
| Age value (decimal) | 43 | Original variable value |
|
||||
| Variable in memory? | No | Compiler optimized it away |
|
||||
| printf address | 0x10003100 | Standard library function |
|
||||
| stdio_init_all addr | 0x100030cc | I/O initialization |
|
||||
| Format string | "age: %d\r\n" | Located in .rodata section |
|
||||
|
||||
##### Decompiled main() After Renaming
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
do {
|
||||
printf("age: %d\r\n", 0x2b);
|
||||
} while (true);
|
||||
}
|
||||
```
|
||||
|
||||
##### Hex-to-Decimal Conversion
|
||||
|
||||
```
|
||||
0x2b = (2 × 16) + 11 = 32 + 11 = 43
|
||||
```
|
||||
|
||||
The compiler replaced both `age = 42` and `age = 43` with the final constant value `0x2b` (43) as an immediate operand in `movs r1, #0x2b`.
|
||||
|
||||
##### Assembly Listing
|
||||
|
||||
```assembly
|
||||
movs r1, #0x2b ; Load 43 directly into r1 (printf argument)
|
||||
ldr r0, [pc, #...] ; Load format string address
|
||||
bl printf ; Call printf
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why did the compiler optimize away the `age` variable?**
|
||||
The compiler performs **dead store elimination** and **constant propagation**. The initial assignment `age = 42` is immediately overwritten by `age = 43` with no intervening reads of the value 42. Since the only value ever observed is 43, the compiler replaces all references to `age` with the constant `0x2b` (43) as an immediate operand. No memory allocation is needed—the value lives entirely in the instruction encoding (`movs r1, #0x2b`).
|
||||
|
||||
2. **In what memory section would `age` have been stored if it wasn't optimized away?**
|
||||
As a local variable declared inside `main()`, `age` would have been stored on the **stack** (in RAM at `0x2000xxxx`). The compiler would allocate space by subtracting from SP, store the value with a `str` instruction, and load it back with `ldr` before passing it to `printf`. If `age` were declared as a global initialized variable, it would be placed in the **`.data`** section (RAM, initialized from flash at boot). If declared as `static` or global but uninitialized, it would go in the **`.bss`** section (RAM, zeroed at boot).
|
||||
|
||||
3. **Where is the string "age: %d\r\n" stored, and why can't it be in RAM?**
|
||||
The format string is stored in the **`.rodata`** (read-only data) section in flash memory at `0x1000xxxx`. It cannot be in RAM because: (a) string literals are constants that never change, so storing them in limited RAM would waste space; (b) flash is non-volatile—the string persists across power cycles without needing to be copied from anywhere; (c) the XIP (Execute In Place) mechanism allows the CPU to read directly from flash, so `.rodata` access is efficient.
|
||||
|
||||
4. **What would happen if we had used `age` in a calculation before reassigning it to 43?**
|
||||
The compiler would be forced to preserve the value 42 because it's actually read before being overwritten. For example, `printf("before: %d\n", age); age = 43;` would require the compiler to generate both `movs r1, #0x2a` (42) for the first print and `movs r1, #0x2b` (43) for subsequent uses. Alternatively, a calculation like `age = age + 1` would allow the compiler to constant-fold `42 + 1 = 43` at compile time and still emit just `#0x2b`. Only if the value depends on runtime input would the variable require actual memory allocation.
|
||||
+9
-9
@@ -1,10 +1,10 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 1: Analyze Variable Storage in Ghidra
|
||||
### Non-Credit Practice Exercise 1: Analyze Variable Storage in Ghidra
|
||||
|
||||
#### Objective
|
||||
Import and analyze the `0x0005_intro-to-variables.bin` binary in Ghidra to understand where variables are stored, identify memory sections, and trace how the compiler optimizes variable usage.
|
||||
@@ -26,7 +26,7 @@ You will import the binary into Ghidra, configure it for ARM Cortex-M33, analyze
|
||||
ghidraRun
|
||||
```
|
||||
|
||||
1. Click **File** → **New Project**
|
||||
1. Click **File** ? **New Project**
|
||||
2. Select **Non-Shared Project**
|
||||
3. Click **Next**
|
||||
4. Enter Project Name: `week04-ex01-intro-to-variables`
|
||||
@@ -43,12 +43,12 @@ ghidraRun
|
||||
|
||||
When the import dialog appears:
|
||||
|
||||
1. Click the three dots (**…**) next to **Language**
|
||||
1. Click the three dots (**…**) next to **Language**
|
||||
2. Search for: `Cortex`
|
||||
3. Select: **ARM Cortex 32 little endian default**
|
||||
4. Click **OK**
|
||||
|
||||
Now click **Options…** button:
|
||||
Now click **Options…** button:
|
||||
1. Change **Block Name** to: `.text`
|
||||
2. Change **Base Address** to: `10000000` (XIP flash base)
|
||||
3. Click **OK**
|
||||
@@ -107,7 +107,7 @@ Let's figure out what `0x2b` means:
|
||||
|
||||
**Manual calculation:**
|
||||
- `0x2b` in hexadecimal
|
||||
- `2 × 16 + 11 = 32 + 11 = 43` in decimal
|
||||
- `2 × 16 + 11 = 32 + 11 = 43` in decimal
|
||||
|
||||
**In GDB (alternative method):**
|
||||
```gdb
|
||||
@@ -135,14 +135,14 @@ Let's rename the functions to their actual names:
|
||||
|
||||
**Rename FUN_100030cc to stdio_init_all:**
|
||||
1. Click on `FUN_100030cc` in the decompile window
|
||||
2. Right-click → **Edit Function Signature**
|
||||
2. Right-click ? **Edit Function Signature**
|
||||
3. Change name to: `stdio_init_all`
|
||||
4. Change signature to: `bool stdio_init_all(void)`
|
||||
5. Click **OK**
|
||||
|
||||
**Rename FUN_10003100 to printf:**
|
||||
1. Click on `FUN_10003100`
|
||||
2. Right-click → **Edit Function Signature**
|
||||
2. Right-click ? **Edit Function Signature**
|
||||
3. Change name to: `printf`
|
||||
4. Check the **Varargs** checkbox (printf accepts variable arguments)
|
||||
5. Click **OK**
|
||||
@@ -230,7 +230,7 @@ After completing this exercise, you should be able to:
|
||||
- The **Data Type Manager** window shows all recognized data types
|
||||
- If Ghidra's decompiler output looks wrong, try re-analyzing with different options
|
||||
- Remember: optimized code often looks very different from source code
|
||||
- The **Display** → **Function Graph** shows control flow visually
|
||||
- The **Display** ? **Function Graph** shows control flow visually
|
||||
|
||||
#### Next Steps
|
||||
- Proceed to Exercise 2 to learn binary patching
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 2 Solution: Patch Binary to Change Variable Value
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch Details
|
||||
|
||||
| Item | Original | Patched |
|
||||
|--------------------|-------------- |---------------|
|
||||
| Instruction | movs r1, #0x2b | movs r1, #0x46 |
|
||||
| Hex bytes | 21 2b | 21 46 |
|
||||
| Decimal value | 43 | 70 |
|
||||
| Output | age: 43 | age: 70 |
|
||||
|
||||
##### Patching Steps
|
||||
|
||||
1. Located `movs r1, #0x2b` in Ghidra Listing view
|
||||
2. Right-click → **Patch Instruction** → changed `#0x2b` to `#0x46`
|
||||
3. Verified in Decompile window: `printf("age: %d\r\n", 0x46)`
|
||||
4. Exported: **File → Export Program → Binary** → `0x0005_intro-to-variables-h.bin`
|
||||
5. Converted to UF2:
|
||||
```powershell
|
||||
python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
|
||||
```
|
||||
6. Flashed via BOOTSEL → RPI-RP2 drive
|
||||
|
||||
##### Serial Output
|
||||
|
||||
```
|
||||
age: 70
|
||||
age: 70
|
||||
age: 70
|
||||
...
|
||||
```
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why do we need to convert to UF2 format instead of flashing the raw .bin file?**
|
||||
The RP2350 bootloader (accessed via BOOTSEL) expects **UF2 (USB Flashing Format)** files. UF2 is a container format that includes metadata: the target flash address for each 256-byte block, a family ID (`0xe48bff59` for RP2350), and checksums. A raw `.bin` file contains only code bytes with no addressing information—the bootloader wouldn't know where in flash to write the data. UF2 also supports partial updates and is self-describing, making it safer for USB mass storage flashing.
|
||||
|
||||
2. **What is the significance of the base address 0x10000000 in the conversion command?**
|
||||
`0x10000000` is the **XIP (Execute In Place) flash base address** on the RP2350. This tells the UF2 converter that byte 0 of the binary should be written to flash address `0x10000000`. The CPU fetches instructions directly from this address range via the XIP controller. If the base address were wrong (e.g., `0x20000000` for RAM), the code would be written to the wrong location and the processor would fail to boot because the vector table wouldn't be found at the expected address.
|
||||
|
||||
3. **What would happen if you patched the wrong instruction by mistake?**
|
||||
The consequences depend on what was changed: (a) Patching a different `movs` might corrupt an unrelated function parameter, causing incorrect behavior or a crash. (b) Patching opcode bytes (not just the immediate) could create an invalid instruction, triggering a HardFault or UsageFault. (c) Patching inside a multi-byte instruction could split it into two unintended instructions, corrupting the entire subsequent instruction stream. The program would likely crash, output garbage, or hang—requiring reflashing with the original UF2 to recover.
|
||||
|
||||
4. **How can you verify a patch was applied correctly before exporting?**
|
||||
Multiple verification methods: (a) Check the **Decompile window**—it should reflect the new value (e.g., `printf("age: %d\r\n", 0x46)`). (b) Inspect the **Listing window** bytes—confirm the instruction bytes changed from `21 2b` to `21 46`. (c) Use **right-click → Highlight → Highlight Difference** to see patched bytes highlighted. (d) Compare the patched instruction against the ARM Thumb encoding reference to verify the encoding is valid. (e) Check surrounding instructions are unchanged—patches should not accidentally modify adjacent code.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 2: Patch Binary to Change Variable Value
|
||||
### Non-Credit Practice Exercise 2: Patch Binary to Change Variable Value
|
||||
|
||||
#### Objective
|
||||
Use Ghidra's patching capabilities to modify the compiled binary, changing the value printed by the program from 43 to a different value of your choice, then convert and flash the modified binary to the Pico 2.
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 3 Solution: Analyze and Understand GPIO Control
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Main Function Decompiled (After Renaming)
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
gpio_init(0x10); // Initialize GPIO 16
|
||||
gpio_set_dir(0x10, 1); // Set as output (GPIO_OUT = 1)
|
||||
|
||||
while (true) {
|
||||
printf("age: %d\r\n", 0); // Uninitialized variable = 0
|
||||
gpio_put(0x10, 1); // LED ON
|
||||
sleep_ms(0x1f4); // Wait 500ms
|
||||
gpio_put(0x10, 0); // LED OFF
|
||||
sleep_ms(0x1f4); // Wait 500ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### GPIO Function Identification Table
|
||||
|
||||
| Auto-Generated Name | Actual Function | Parameters | Identification Method |
|
||||
|---------------------|-----------------|--------------------------------|--------------------------------------|
|
||||
| FUN_100030cc | stdio_init_all | (void) | Called first, no parameters |
|
||||
| FUN_xxxxxxxx | gpio_init | (uint gpio) | Single parameter = pin number (0x10) |
|
||||
| FUN_xxxxxxxx | gpio_set_dir | (uint gpio, bool out) | Two params: pin + direction (1=out) |
|
||||
| FUN_xxxxxxxx | gpio_put | (uint gpio, bool value) | Two params: pin + value (0 or 1) |
|
||||
| FUN_xxxxxxxx | sleep_ms | (uint32_t ms) | Single param = delay (0x1f4 = 500) |
|
||||
| FUN_10003100 | printf | (const char *fmt, ...) | Format string + varargs |
|
||||
|
||||
##### Key Value Conversions
|
||||
|
||||
| Hex | Decimal | Binary | Purpose |
|
||||
|--------|---------|-------------|------------------------|
|
||||
| 0x10 | 16 | 0000 1000 | GPIO pin number (red LED) |
|
||||
| 0x1f4 | 500 | — | Sleep duration (ms) |
|
||||
| 0x01 | 1 | 0000 0001 | GPIO_OUT / LED ON |
|
||||
| 0x00 | 0 | 0000 0000 | GPIO_IN / LED OFF |
|
||||
|
||||
```
|
||||
0x10 = (1 × 16) + 0 = 16
|
||||
0x1f4 = (1 × 256) + (15 × 16) + 4 = 256 + 240 + 4 = 500
|
||||
```
|
||||
|
||||
##### GPIO Memory Map
|
||||
|
||||
| Address | Register | Purpose |
|
||||
|-------------|-------------|------------------------------|
|
||||
| 0x40028000 | IO_BANK0 | GPIO function selection |
|
||||
| 0x40038000 | PADS_BANK0 | GPIO pad configuration |
|
||||
| 0xd0000000 | SIO | Single-cycle I/O |
|
||||
|
||||
GPIO 16 specific:
|
||||
- Pad control: `0x40038000 + (16 × 4) = 0x40038040`
|
||||
- Function select: `0x40028000 + (16 × 4) = 0x40028040`
|
||||
|
||||
##### gpio_put Coprocessor Instruction
|
||||
|
||||
```assembly
|
||||
mcrr p0, #4, r4, r5, c0
|
||||
```
|
||||
|
||||
- `mcrr` = Move to Coprocessor from two ARM Registers
|
||||
- `p0` = Coprocessor 0 (GPIO coprocessor on RP2350)
|
||||
- `r4` = GPIO pin number, `r5` = value (0 or 1)
|
||||
- Single-cycle GPIO operation
|
||||
|
||||
##### Blink Timing Analysis
|
||||
|
||||
- ON duration: `sleep_ms(0x1f4)` = 500ms
|
||||
- OFF duration: `sleep_ms(0x1f4)` = 500ms
|
||||
- Total cycle: 1000ms = 1 second
|
||||
- Blink rate: **1 Hz**
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why does gpio_init() need to configure both PADS_BANK0 and IO_BANK0 registers?**
|
||||
These registers control different aspects of GPIO operation. **PADS_BANK0** (`0x40038000`) configures the physical pad properties: input enable (IE), output disable (OD), pull-up/pull-down resistors, drive strength, and slew rate. **IO_BANK0** (`0x40028000`) configures the function multiplexer (FUNCSEL), selecting which internal peripheral drives the pin—SIO (function 5) for software control, UART, SPI, I2C, PWM, etc. Both must be configured: PADS sets the electrical characteristics of the physical pin, while IO_BANK0 routes the correct internal signal to it.
|
||||
|
||||
2. **What is the advantage of using the GPIO coprocessor instruction (`mcrr`) instead of writing to memory-mapped registers?**
|
||||
The `mcrr` coprocessor instruction completes in a **single CPU cycle**, whereas writing to memory-mapped GPIO registers requires multiple cycles: an address load, a data load, and a store instruction (plus potential bus wait states). On the RP2350, the SIO coprocessor provides deterministic, single-cycle access to GPIO outputs, which is critical for bit-banging protocols (like SPI or custom serial) where precise timing is required. The coprocessor path bypasses the AHB/APB bus entirely.
|
||||
|
||||
3. **If you wanted to blink the LED at 10 Hz instead of 1 Hz, what value should `sleep_ms()` use?**
|
||||
At 10 Hz, each full on/off cycle is 100ms. Since the loop has two `sleep_ms()` calls (one for ON, one for OFF), each should be `100 / 2 = 50ms`. In hex: `50 = 0x32`. So both `sleep_ms()` calls should use `sleep_ms(0x32)`.
|
||||
|
||||
4. **What would happen if you called `gpio_put()` on a pin that hasn't been initialized with `gpio_init()` first?**
|
||||
The GPIO pin's function would still be set to its reset default (typically NULL/function 0), not SIO (function 5). The `gpio_put()` coprocessor instruction would update the SIO output register internally, but since the pin's function multiplexer isn't routing SIO to the physical pad, the electrical state of the pin wouldn't change. The LED would remain off. Additionally, without pad configuration, input enable and output disable bits may not be set correctly, further preventing any observable output.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 3: Analyze and Understand GPIO Control
|
||||
### Non-Credit Practice Exercise 3: Analyze and Understand GPIO Control
|
||||
|
||||
#### Objective
|
||||
Import the `0x0008_uninitialized-variables.bin` binary, analyze the GPIO initialization and control sequences, understand how `gpio_init()`, `gpio_set_dir()`, and `gpio_put()` work at the assembly level.
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
# Embedded Systems Reverse Engineering
|
||||
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
|
||||
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Non-Credit Practice Exercise 4 Solution: Patch GPIO Binary to Change LED Pin
|
||||
|
||||
#### Answers
|
||||
|
||||
##### Patch Summary
|
||||
|
||||
| Item | Original | Patched | Hex Change |
|
||||
|---------------|-----------------|-----------------|---------------|
|
||||
| LED pin | GPIO 16 | GPIO 17 | 0x10 → 0x11 |
|
||||
| Printed value | 0 (uninitialized)| 66 | 0x00 → 0x42 |
|
||||
| Blink timing | 500ms | 100ms | 0x1f4 → 0x64 |
|
||||
|
||||
##### Detailed Patch Locations
|
||||
|
||||
**1. gpio_init parameter:**
|
||||
```assembly
|
||||
; Before: movs r0, #0x10 (bytes: 10 20)
|
||||
; After: movs r0, #0x11 (bytes: 11 20)
|
||||
```
|
||||
|
||||
**2. gpio_set_dir parameter:**
|
||||
```assembly
|
||||
; Before: movs r3, #0x10 (bytes: 10 23)
|
||||
; After: movs r3, #0x11 (bytes: 11 23)
|
||||
```
|
||||
|
||||
**3. gpio_put (LED ON) parameter:**
|
||||
```assembly
|
||||
; Before: movs r4, #0x10 (bytes: 10 24)
|
||||
; After: movs r4, #0x11 (bytes: 11 24)
|
||||
```
|
||||
|
||||
**4. gpio_put (LED OFF) parameter:**
|
||||
```assembly
|
||||
; Before: movs r4, #0x10 (bytes: 10 24)
|
||||
; After: movs r4, #0x11 (bytes: 11 24)
|
||||
```
|
||||
|
||||
**5. printf value:**
|
||||
```assembly
|
||||
; Before: movs r1, #0x00 (bytes: 00 21)
|
||||
; After: movs r1, #0x42 (bytes: 42 21)
|
||||
```
|
||||
|
||||
**6. sleep_ms (both calls):**
|
||||
```assembly
|
||||
; Before: loads 0x1f4 (500ms)
|
||||
; After: movs r0, #0x64 (100ms)
|
||||
```
|
||||
|
||||
##### Hex Conversions
|
||||
|
||||
```
|
||||
GPIO 17: 17 = 0x11 = 0001 0001 binary
|
||||
Value 66: 66 = 0x42 = 0100 0010 binary
|
||||
100ms: 100 = 0x64 = 0110 0100 binary
|
||||
```
|
||||
|
||||
##### Decompiled Result After All Patches
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
{
|
||||
stdio_init_all();
|
||||
gpio_init(0x11); // GPIO 17 (green LED)
|
||||
gpio_set_dir(0x11, 1); // Output
|
||||
|
||||
while (true) {
|
||||
printf("age: %d\r\n", 0x42); // Prints 66
|
||||
gpio_put(0x11, 1); // Green LED ON
|
||||
sleep_ms(0x64); // 100ms
|
||||
gpio_put(0x11, 0); // Green LED OFF
|
||||
sleep_ms(0x64); // 100ms
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### Hardware Verification
|
||||
|
||||
- GREEN LED (GPIO 17) blinks at 10 Hz (100ms on, 100ms off)
|
||||
- RED LED (GPIO 16) remains off
|
||||
- Serial output: `age: 66` repeating
|
||||
|
||||
#### Reflection Answers
|
||||
|
||||
1. **Why did we need to patch GPIO 16 in multiple places (gpio_init, gpio_set_dir, gpio_put)?**
|
||||
Each function takes the GPIO pin number as a separate parameter. `gpio_init(16)` configures the pad and function mux for pin 16. `gpio_set_dir(16, 1)` sets pin 16's direction to output. `gpio_put(16, value)` toggles pin 16's output state. These are independent function calls with independent immediate values in the instruction stream—the compiler doesn't share or reuse a single "pin number variable." Each `movs rN, #0x10` loads the pin number fresh for its respective function call. Missing any one patch would result in a mismatch: e.g., initializing pin 17 but toggling pin 16.
|
||||
|
||||
2. **What would happen if you forgot to patch one of the gpio_put calls?**
|
||||
You would get asymmetric behavior. For example, if you patched the "LED ON" `gpio_put` to pin 17 but left the "LED OFF" at pin 16: GPIO 17 (green) would turn on but never turn off (staying permanently lit), while GPIO 16 (red) would receive the "off" command for a pin that was never initialized—which would have no visible effect. The result: green LED stuck on, no blinking.
|
||||
|
||||
3. **How does the instruction encoding differ for immediate values less than 256 vs. greater than 255?**
|
||||
In 16-bit Thumb encoding, `movs Rd, #imm8` can only encode immediate values 0–255 in a single 2-byte instruction. For values > 255 (like 500 = 0x1f4), the compiler must use either: (a) a 32-bit Thumb-2 `movw Rd, #imm16` instruction (4 bytes, can encode 0–65535), (b) a multi-instruction sequence that constructs the value (e.g., `movs` + `lsls` + `add`), or (c) an `ldr Rd, [pc, #offset]` that loads the constant from a literal pool in flash. This is why patching `sleep_ms(500)` may be more complex than patching `gpio_put(16, 1)`.
|
||||
|
||||
4. **If you wanted to make the LED blink at exactly 5 Hz, what sleep_ms value would you use?**
|
||||
At 5 Hz, each complete cycle is `1000 / 5 = 200ms`. With two `sleep_ms()` calls per cycle (ON and OFF), each call should be `200 / 2 = 100ms`. In hex: `100 = 0x64`. So `sleep_ms(0x64)` for each call—which is exactly the value used in this exercise's patch. For a different duty cycle (e.g., 150ms on, 50ms off), you'd use different values for each call while keeping the total at 200ms.
|
||||
+1
-1
@@ -4,7 +4,7 @@
|
||||
## Week 4
|
||||
Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics
|
||||
|
||||
### Exercise 4: Patch GPIO Binary to Change LED Pin
|
||||
### Non-Credit Practice Exercise 4: Patch GPIO Binary to Change LED Pin
|
||||
|
||||
#### Objective
|
||||
Patch the `0x0008_uninitialized-variables.bin` binary to change which LED blinks, modify the printed value, and change the blink timing, then verify all changes work correctly on hardware.
|
||||
|
||||
Reference in New Issue
Block a user