Overhall w/ slides

This commit is contained in:
Kevin Thomas
2026-03-15 10:40:20 -04:00
parent d3b2ca3e47
commit 5b149048fd
61 changed files with 6305 additions and 240 deletions
+155
View File
@@ -0,0 +1,155 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 10
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
### Exercise 1: Change Servo Angle Range
#### Objective
Find the IEEE-754 floating-point value `0x43340000` (180.0f) in the `0x0020_dynamic-conditionals` binary using GDB, calculate the file offset, patch it to `0x43070000` (135.0f) using a hex editor, then find and patch the `0x00000000` (0.0f) value to `0x42340000` (45.0f), and verify on hardware that the servo now sweeps from 45° to 135° instead of 0° to 180°.
#### Prerequisites
- Completed Week 10 tutorial (GDB and hex editor sections)
- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
- A hex editor (HxD, ImHex, or similar)
- Python installed (for UF2 conversion)
- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6
#### Task Description
The program uses `servo_set_angle(0.0f)` and `servo_set_angle(180.0f)` to sweep the servo across its full range. The float values `0.0` (`0x00000000`) and `180.0` (`0x43340000`) are loaded from the literal pool into registers before the `bl servo_set_angle` calls. You will locate these values in the disassembly, find the corresponding bytes in the `.bin` file, and patch them to `45.0` (`0x42340000`) and `135.0` (`0x43070000`) so the servo sweeps a narrower 90° range centered at 90°.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
**Terminal 2 - Start GDB:**
```powershell
arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Find the servo_set_angle Calls
Disassemble main and look for the `servo_set_angle` calls:
```gdb
(gdb) disassemble 0x10000234,+250
```
Look for instruction patterns like:
```
ldr r0, [pc, #offset] ; load float from literal pool
bl servo_set_angle
```
There will be multiple pairs — one loading `0x00000000` (0.0f) and another loading `0x43340000` (180.0f).
##### Step 3: Examine the Literal Pool Values
Once you find the `ldr r0, [pc, #offset]` instructions, examine the literal pool entries they reference:
```gdb
(gdb) x/wx <literal_pool_address_for_180>
```
You should see `0x43340000` (180.0f in IEEE-754).
```gdb
(gdb) x/wx <literal_pool_address_for_0>
```
You should see `0x00000000` (0.0f in IEEE-754).
##### Step 4: Calculate the File Offsets
```
file_offset = literal_pool_address - 0x10000000
```
Note the file offsets for both 4-byte words.
##### Step 5: Encode the New Values
**IEEE-754 Encoding:**
| Angle | IEEE-754 Hex | Little-Endian Bytes |
| ----- | -------------- | ------------------- |
| 0.0f | `0x00000000` | `00 00 00 00` |
| 45.0f | `0x42340000` | `00 00 34 42` |
| 135.0f| `0x43070000` | `00 00 07 43` |
| 180.0f| `0x43340000` | `00 00 34 43` |
##### Step 6: Patch with HxD
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin`
2. Press **Ctrl+G** and enter the file offset for the 180.0f value
3. You should see: `00 00 34 43`
4. Replace with: `00 00 07 43` (135.0f)
5. Press **Ctrl+G** and enter the file offset for the 0.0f value
6. You should see: `00 00 00 00`
7. Replace with: `00 00 34 42` (45.0f)
###### Question 1: How many literal pool entries reference `0x43340000`? Do you need to patch all of them?
##### Step 7: Save and Convert
1. Click **File****Save As**`0x0020_dynamic-conditionals-h.bin`
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals
python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### Step 8: Flash and Verify
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
**Check the servo behavior:**
- Press '1' → servo sweeps from **45°** to **135°** (was 0° to 180°)
- Press '2' → servo sweeps from **135°** to **45°** (was 180° to 0°)
- The total sweep range should be noticeably smaller
#### Expected Output
After completing this exercise, you should be able to:
- Locate IEEE-754 floating-point values in the literal pool
- Understand the IEEE-754 single-precision encoding format
- Patch floating-point angle constants in embedded firmware
- Calculate file offsets from memory addresses
#### Questions for Reflection
###### Question 1: The value `180.0f` is encoded as `0x43340000` in IEEE-754. Break this down: what are the sign bit, exponent, and mantissa fields?
###### Question 2: Why is `0.0f` stored as `0x00000000` and not some other pattern? What makes zero special in IEEE-754?
###### Question 3: If you wanted to set the servo to exactly 90° (center), what IEEE-754 hex value would you use? Show the calculation.
###### Question 4: Could the compiler optimize `0.0f` differently — for example, using `movs r0, #0` instead of a literal pool load? How would that affect your patching strategy?
#### Tips and Hints
- IEEE-754 for 90.0f: sign=0, exponent=127+6=133=0x85, mantissa=0x340000 → `0x42b40000`
- There may be multiple references to `0x43340000` in the literal pool — the case '1' and case '2' branches each load both angles
- Be careful with `0x00000000` — make sure you are patching a float literal pool entry and not zeroed data
- Use `x/4wx <address>` in GDB to view several literal pool words at once
+159
View File
@@ -0,0 +1,159 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 10
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
### Exercise 2: Add a Third Command
#### Objective
Find the comparison instruction `cmp r4, #0x32` ('2') in the `0x0020_dynamic-conditionals` binary using GDB, locate the branch target for case '2', and modify existing code so that pressing '3' (0x33) moves the servo to the center position (90°) by patching one of the existing comparisons and its corresponding angle value to `0x42b40000` (90.0f).
#### Prerequisites
- Completed Week 10 tutorial (GDB and hex editor sections)
- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
- A hex editor (HxD, ImHex, or similar)
- Python installed (for UF2 conversion)
- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6
#### Task Description
The program has two active commands: '1' (0x31) moves the servo 0°→180° and '2' (0x32) moves it 180°→0°. Adding a completely new code path would require rewriting branch offsets throughout the binary. Instead, you will repurpose the '2' command by changing its comparison value from `0x32` ('2') to `0x33` ('3') and patching both of its angle values to `0x42b40000` (90.0f), so pressing '3' moves the servo to center and holds it there.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
**Terminal 2 - Start GDB:**
```powershell
arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Find the Comparison Instructions
Disassemble main and locate both `cmp` instructions:
```gdb
(gdb) disassemble 0x10000234,+250
```
Look for:
```
cmp r4, #0x31 ; compare with '1'
beq <target1>
cmp r4, #0x32 ; compare with '2'
beq <target2>
```
Note the address of the `cmp r4, #0x32` instruction.
##### Step 3: Examine the Comparison Byte
The `cmp r4, #0x32` instruction encodes `0x32` as an immediate. Examine the instruction bytes:
```gdb
(gdb) x/2bx <address_of_cmp_0x32>
```
You should see a byte containing `0x32`.
##### Step 4: Find the Angle Values for Case '2'
Follow the `beq` target for case '2' and look for the literal pool loads:
```gdb
(gdb) x/wx <literal_pool_for_case2_angle1>
(gdb) x/wx <literal_pool_for_case2_angle2>
```
Case '2' loads `0x43340000` (180.0f) first, then `0x00000000` (0.0f).
##### Step 5: Calculate the File Offsets
```
file_offset = address - 0x10000000
```
Note offsets for:
1. The `0x32` byte in the `cmp` instruction
2. Both angle literal pool entries for case '2'
##### Step 6: Encode the New Values
| Patch Target | Original | New | Purpose |
| ---------------- | ---------------- | ---------------- | ------------------------- |
| Compare byte | `32` | `33` | Match '3' instead of '2' |
| Angle 1 (180.0f) | `00 00 34 43` | `00 00 b4 42` | 90.0f center position |
| Angle 2 (0.0f) | `00 00 00 00` | `00 00 b4 42` | 90.0f center position |
##### Step 7: Patch with HxD
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin`
2. Press **Ctrl+G** and go to the compare byte offset
3. Change `32` to `33`
4. Go to the first angle offset for case '2' and replace `00 00 34 43` with `00 00 b4 42`
5. Go to the second angle offset for case '2' and replace `00 00 00 00` with `00 00 b4 42`
###### Question 1: Why do we set both angle values to 90.0f instead of just one?
##### Step 8: Save and Convert
1. Click **File****Save As**`0x0020_dynamic-conditionals-h.bin`
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals
python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### Step 9: Flash and Verify
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
**Check the behavior:**
- Press '1' → servo sweeps 0° to 180° (unchanged)
- Press '3' → servo moves to **90° center** and stays
- Press '2' → now falls through to default "??" (no longer mapped)
#### Expected Output
After completing this exercise, you should be able to:
- Locate and patch comparison immediate values in ARM Thumb instructions
- Repurpose existing code paths instead of adding new ones
- Understand how `cmp` immediates are encoded in the instruction stream
- Combine multiple patches (comparison + data) for a single behavior change
#### Questions for Reflection
###### Question 1: Why is it easier to repurpose an existing command than to add a truly new third code path in the binary?
###### Question 2: The `cmp` instruction uses an 8-bit immediate field. What range of ASCII characters could you use as command keys?
###### Question 3: After your patch, pressing '2' now triggers the default "??" branch. Could you keep both '2' AND '3' working? What would that require?
###### Question 4: What would happen if you changed the comparison to `0x00`? Could a user ever trigger that command via `getchar()`?
#### Tips and Hints
- The `cmp rN, #imm8` instruction in Thumb has the immediate in the lower byte of the instruction word
- IEEE-754 for 90.0f: `0x42b40000` → little-endian `00 00 b4 42`
- Be careful not to confuse literal pool entries between case '1' and case '2' — trace the branch targets carefully
- The `getchar()` function reads one byte from UART, so any byte value 0x00-0xFF is theoretically possible
+141
View File
@@ -0,0 +1,141 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 10
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
### Exercise 3: Reverse the Servo Direction
#### Objective
Find the branch targets for case '1' and case '2' in the `0x0020_dynamic-conditionals` binary using GDB, identify where each case loads its angle values from the literal pool, and swap the angle pairs so that pressing '1' now does what '2' originally did (180°→0°) and pressing '2' does what '1' originally did (0°→180°).
#### Prerequisites
- Completed Week 10 tutorial (GDB and hex editor sections)
- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
- A hex editor (HxD, ImHex, or similar)
- Python installed (for UF2 conversion)
- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6
#### Task Description
Currently, case '1' calls `servo_set_angle(0.0f)` then `servo_set_angle(180.0f)`, while case '2' calls `servo_set_angle(180.0f)` then `servo_set_angle(0.0f)`. To reverse the servo direction, you will swap the literal pool angle values between the two cases. This means patching case '1' to load 180.0f first and 0.0f second, and case '2' to load 0.0f first and 180.0f second.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
**Terminal 2 - Start GDB:**
```powershell
arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Map Both Case Branch Targets
Disassemble main and trace both paths:
```gdb
(gdb) disassemble 0x10000234,+250
```
Identify:
- Case '1' branch target → first `ldr r0` (loads 0.0f), `bl servo_set_angle`, second `ldr r0` (loads 180.0f), `bl servo_set_angle`
- Case '2' branch target → first `ldr r0` (loads 180.0f), `bl servo_set_angle`, second `ldr r0` (loads 0.0f), `bl servo_set_angle`
##### Step 3: Find All Four Literal Pool Entries
Examine the literal pool entries for each angle load:
```gdb
(gdb) x/wx <case1_angle1_literal>
(gdb) x/wx <case1_angle2_literal>
(gdb) x/wx <case2_angle1_literal>
(gdb) x/wx <case2_angle2_literal>
```
Record which addresses contain `0x00000000` (0.0f) and which contain `0x43340000` (180.0f).
##### Step 4: Calculate All File Offsets
```
file_offset = literal_pool_address - 0x10000000
```
You need four offsets — two for case '1' angles and two for case '2' angles.
##### Step 5: Plan the Swap
| Location | Original | New |
| -------------- | ---------------- | ---------------- |
| Case 1 Angle 1 | `00 00 00 00` (0.0f) | `00 00 34 43` (180.0f) |
| Case 1 Angle 2 | `00 00 34 43` (180.0f) | `00 00 00 00` (0.0f) |
| Case 2 Angle 1 | `00 00 34 43` (180.0f) | `00 00 00 00` (0.0f) |
| Case 2 Angle 2 | `00 00 00 00` (0.0f) | `00 00 34 43` (180.0f) |
###### Question 1: Could the compiler share a single literal pool entry for all references to `0x43340000`? How would that affect your patching plan?
##### Step 6: Patch with HxD
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin`
2. For each of the four literal pool entries, navigate to its file offset and swap the values as planned
3. Be methodical — patch one at a time and verify each before moving to the next
##### Step 7: Save and Convert
1. Click **File****Save As**`0x0020_dynamic-conditionals-h.bin`
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals
python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### Step 8: Flash and Verify
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
**Check the behavior:**
- Press '1' → servo sweeps from **180° to 0°** (was 0° to 180°)
- Press '2' → servo sweeps from **0° to 180°** (was 180° to 0°)
- The text output still says "one" and "two" — only the physical behavior changed
#### Expected Output
After completing this exercise, you should be able to:
- Trace multiple branch paths to their literal pool references
- Understand how data (angles) and code (branches) are separate concerns
- Swap data values to reverse physical behavior while keeping code structure intact
- Recognize shared vs. separate literal pool entries
#### Questions for Reflection
###### Question 1: The terminal still prints "one" and "two" with the original meanings, but the servo does the opposite. Why is this a security concern in real embedded systems?
###### Question 2: Instead of swapping literal pool values, could you swap the branch targets themselves? What are the pros and cons of each approach?
###### Question 3: If the literal pool entries are shared between cases (one `0x43340000` word referenced by both), how would your patch strategy change?
###### Question 4: What tool could you use to confirm the swapped behavior without physical hardware — just by reading the patched disassembly?
#### Tips and Hints
- Use `x/4wx <literal_pool_start>` to dump the entire literal pool at once and see all angle values together
- If the compiler shares a single literal pool entry for 180.0f across both cases, swapping it would affect both — you may need to create a duplicate entry
- The simplest approach: if each case has its own literal pool entries, just swap the 4-byte values at each offset
- Verify by disassembling the patched binary in GDB to confirm the `ldr` instructions now reference the swapped values
+144
View File
@@ -0,0 +1,144 @@
# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)
## Week 10
Conditionals in Embedded Systems: Debugging and Hacking Static & Dynamic Conditionals w/ SG90 Servo Motor PWM Basics
### Exercise 4: Speed Profile
#### Objective
Find both `sleep_ms(500)` calls in the `0x0020_dynamic-conditionals` binary using GDB, identify the literal pool values `0x1f4` (500) loaded into `r0` before each `bl sleep_ms`, calculate the file offsets, and patch case '1' to use `0x64` (100ms) for fast movement and case '2' to use `0x3e8` (1000ms) for slow movement, then verify on hardware that the two keys produce visibly different servo speeds.
#### Prerequisites
- Completed Week 10 tutorial (GDB and hex editor sections)
- `0x0020_dynamic-conditionals.elf` and `0x0020_dynamic-conditionals.bin` available in your build directory
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
- A hex editor (HxD, ImHex, or similar)
- Python installed (for UF2 conversion)
- Raspberry Pi Pico 2 with SG90 servo motor connected on GPIO 6
#### Task Description
Both case '1' and case '2' call `sleep_ms(500)` between their two `servo_set_angle` calls. The value `500` (`0x1F4`) is loaded from the literal pool into `r0` before each `bl sleep_ms`. You will find both `sleep_ms` literal pool entries — one in case '1' and one in case '2' — and patch them to different values: `100` (`0x64`) for fast snapping and `1000` (`0x3E8`) for slow sweeping, creating distinct speed profiles per key.
#### Step-by-Step Instructions
##### Step 1: Start the Debug Session
**Terminal 1 - Start OpenOCD:**
```powershell
openocd ^
-s "C:\Users\flare-vm\.pico-sdk\openocd\0.12.0+dev\scripts" ^
-f interface/cmsis-dap.cfg ^
-f target/rp2350.cfg ^
-c "adapter speed 5000"
```
**Terminal 2 - Start GDB:**
```powershell
arm-none-eabi-gdb build\0x0020_dynamic-conditionals.elf
```
**Connect to target:**
```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```
##### Step 2: Find Both sleep_ms Calls
Disassemble main and locate the `sleep_ms` calls:
```gdb
(gdb) disassemble 0x10000234,+250
```
Look for the pattern repeated in both cases:
```
ldr r0, [pc, #offset] ; load delay value
bl sleep_ms
```
Each case has at least one `sleep_ms` call. Identify which belongs to case '1' and which to case '2' by tracing the branch targets.
##### Step 3: Examine the Literal Pool Entries
For each `sleep_ms` call, examine the referenced literal pool entry:
```gdb
(gdb) x/wx <case1_sleep_literal>
(gdb) x/wx <case2_sleep_literal>
```
Both should show `0x000001f4` (500).
##### Step 4: Calculate the File Offsets
```
file_offset = literal_pool_address - 0x10000000
```
Note the file offsets for both 4-byte sleep values.
##### Step 5: Encode the New Values
| Case | Original | New | Speed |
| ------ | ----------------- | ----------------- | -------- |
| Case 1 | `F4 01 00 00` (500ms) | `64 00 00 00` (100ms) | Fast snap |
| Case 2 | `F4 01 00 00` (500ms) | `E8 03 00 00` (1000ms) | Slow sweep |
###### Question 1: Each case calls `sleep_ms` twice (once between the first and second `servo_set_angle`). Do both share the same literal pool entry, or does each have its own?
##### Step 6: Patch with HxD
1. In HxD, open `C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals\build\0x0020_dynamic-conditionals.bin`
2. Press **Ctrl+G** and go to the case '1' sleep value offset
3. Replace `F4 01 00 00` with `64 00 00 00` (100ms)
4. Press **Ctrl+G** and go to the case '2' sleep value offset
5. Replace `F4 01 00 00` with `E8 03 00 00` (1000ms)
##### Step 7: Save and Convert
1. Click **File****Save As**`0x0020_dynamic-conditionals-h.bin`
```powershell
cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0020_dynamic-conditionals
python ..\uf2conv.py build\0x0020_dynamic-conditionals-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2
```
##### Step 8: Flash and Verify
1. Hold BOOTSEL and plug in your Pico 2
2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive
**Check the behavior:**
- Press '1' → servo snaps **fast** (100ms between angles) — almost instant jump
- Press '2' → servo moves **slow** (1000ms between angles) — takes a full second at each position
- The speed difference should be very obvious visually and audibly
#### Expected Output
After completing this exercise, you should be able to:
- Distinguish between multiple literal pool entries for the same value
- Trace which code path references which literal pool entry
- Patch timing constants independently per branch
- Understand how sleep duration affects perceived motor behavior
#### Questions for Reflection
###### Question 1: Why does 100ms make the servo appear to "snap" while 1000ms makes it appear to "sweep"? Is the servo actually moving faster, or is it about the pause between commands?
###### Question 2: If the compiler uses a single shared literal pool entry for all `sleep_ms(500)` calls, what alternative patching strategy would you need to create different speeds per case?
###### Question 3: What is the minimum `sleep_ms` value that would still allow the servo to physically reach its target angle before the next command? How would you determine this experimentally?
###### Question 4: Could you set the sleep to `0` (`00 00 00 00`)? What would happen to the servo behavior?
#### Tips and Hints
- `100` decimal = `0x64`, fits in one byte: `64 00 00 00` in little-endian
- `1000` decimal = `0x3E8`: `E8 03 00 00` in little-endian
- If both `sleep_ms` calls share one literal pool word, you cannot give them different values by patching data alone — you would need to patch one `ldr` instruction to point to a different pool entry or use a `movs` immediate
- The SG90 servo takes about 200-300ms to traverse its full range, so 100ms will cause it to not quite reach the endpoint before the next command fires
Binary file not shown.
+1529
View File
File diff suppressed because it is too large Load Diff