# Embedded Systems Reverse Engineering
[Repository](https://github.com/mytechnotalent/Embedded-Hacking)

## Week 7
Constants in Embedded Systems: Debugging and Hacking Constants w/ 1602 LCD I2C Basics

### Exercise 3: Trace the I²C Struct Pointer Chain

#### Objective
Use GDB to follow the `i2c1_inst` struct pointer chain from the code instruction that loads it, through the struct in SRAM, to the hardware registers at `0x40098000`. Document every step of the chain: `I2C_PORT` → `i2c1` → `&i2c1_inst` → `hw` → `0x40098000`, and verify each pointer and value in memory.

#### Prerequisites
- Completed Week 7 tutorial (Parts 3-4 on structs and the macro chain)
- `0x0017_constants.elf` binary available in your build directory
- GDB (`arm-none-eabi-gdb`) and OpenOCD installed
- Understanding of C pointers and structs

#### Task Description
The Pico SDK uses a chain of macros and structs to abstract hardware access. When you write `I2C_PORT` in C, it expands through multiple macro definitions to ultimately become a pointer to an `i2c_inst_t` struct in SRAM, which in turn contains a pointer to the I²C hardware registers. You will trace this entire chain in GDB, examining each link to understand how the SDK connects your code to silicon.

#### Step-by-Step Instructions

##### Step 1: Start the Debug Session

**Terminal 1 - Start OpenOCD:**

```bash
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:**

```bash
arm-none-eabi-gdb build/0x0017_constants.elf
```

**Connect to target:**

```gdb
(gdb) target remote :3333
(gdb) monitor reset halt
```

##### Step 2: Find the i2c_init Call

Run the program to allow initialization to complete, then halt:

```gdb
(gdb) b *0x10000234
(gdb) c
```

Now step through to just before the `i2c_init` call:

```gdb
(gdb) x/10i 0x1000023c
```

Look for the instruction that loads the `i2c1_inst` pointer into `r0`:

```
0x1000023c: ldr r0, [pc, #offset]    ; Load &i2c1_inst into r0
0x1000023e: ldr r1, =0x186A0         ; 100000 (baud rate)
0x10000240: bl  i2c_init             ; Call i2c_init(i2c1, 100000)
```

##### Step 3: Follow the PC-Relative Load

The `ldr r0, [pc, #offset]` instruction loads a value from a **literal pool** — a data area near the code. Examine what's at the literal pool:

```gdb
(gdb) x/4wx 0x100002a8
```

Look for a value in the `0x2000xxxx` range — this is the SRAM address of `i2c1_inst`. It should be `0x2000062c`.

##### Step 4: Examine the i2c1_inst Struct in SRAM

Now examine the struct at that SRAM address:

```gdb
(gdb) x/2wx 0x2000062c
```

Expected output:
```
0x2000062c: 0x40098000    0x00000000
```

This maps to the `i2c_inst_t` struct:

| Offset | Field             | Value        | Meaning                          |
| ------ | ----------------- | ------------ | -------------------------------- |
| `+0x00` | `hw`             | `0x40098000` | Pointer to I²C1 hardware regs    |
| `+0x04` | `restart_on_next` | `0x00000000` | `false` (no pending restart)     |

##### Step 5: Follow the hw Pointer to Hardware Registers

The first member of the struct (`hw`) points to `0x40098000` — the I²C1 hardware register block. Examine it:

```gdb
(gdb) x/8wx 0x40098000
```

You should see the I²C1 control and status registers:

| Offset | Register       | Description                    |
| ------ | -------------- | ------------------------------ |
| `+0x00` | IC_CON        | I²C control register           |
| `+0x04` | IC_TAR        | Target address register        |
| `+0x08` | IC_SAR        | Slave address register         |
| `+0x0C` | (reserved)    | —                              |
| `+0x10` | IC_DATA_CMD   | Data command register          |

##### Step 6: Verify the I²C Target Address

After `i2c_init` and `lcd_i2c_init` have run, check the target address register:

Let the program run past initialization:

```gdb
(gdb) delete
(gdb) b *<address_after_lcd_init>
(gdb) c
```

Then examine IC_TAR:

```gdb
(gdb) x/1wx 0x40098004
```

You should see `0x27` (or a value containing 0x27) — this is the LCD's I²C address!

##### Step 7: Document the Complete Chain

Create a diagram of the complete pointer chain:

```
Your Code: I2C_PORT
    │
    ▼ (preprocessor macro)
i2c1
    │
    ▼ (macro: #define i2c1 (&i2c1_inst))
&i2c1_inst = 0x2000062c (SRAM address)
    │
    ▼ (struct member access)
i2c1_inst.hw = 0x40098000 (hardware register base)
    │
    ▼ (memory-mapped I/O)
I²C1 Hardware Registers (silicon)
    │
    ├── IC_CON  at 0x40098000
    ├── IC_TAR  at 0x40098004
    ├── IC_DATA_CMD at 0x40098010
    └── ...
```

##### Step 8: Compare with I²C0

The Pico 2 has two I²C controllers. Find the `i2c0_inst` struct and compare:

```gdb
(gdb) x/2wx 0x20000628
```

If I²C0's struct is at a nearby address, you should see:
- `hw` pointing to `0x40090000` (I²C0 base, different from I²C1's `0x40098000`)
- `restart_on_next` = 0

This demonstrates how the SDK uses the same struct layout for both I²C controllers, with only the hardware pointer changing.

#### Expected Output

After completing this exercise, you should be able to:
- Trace pointer chains from high-level code to hardware registers
- Understand how the Pico SDK uses structs to abstract hardware
- Read struct members from raw memory using GDB
- Navigate from SRAM data structures to memory-mapped I/O registers

#### Questions for Reflection

###### Question 1: Why does the SDK use a struct with a pointer to hardware registers instead of accessing `0x40098000` directly? What advantage does this abstraction provide?

###### Question 2: The `hw` pointer stores `0x40098000`. In the binary, this appears as bytes `00 80 09 40`. Why is the byte order reversed from how we write the address?

###### Question 3: If you changed the `hw` pointer at `0x2000062c` from `0x40098000` to `0x40090000` using GDB (`set {int}0x2000062c = 0x40090000`), what I²C controller would the program use? What would happen to the LCD?

###### Question 4: The macro chain has 4 levels of indirection (I2C_PORT → i2c1 → &i2c1_inst → hw → registers). Is this typical for embedded SDKs? What are the trade-offs of this approach?

#### Tips and Hints
- Use `x/wx` to examine 32-bit words (pointers are 32 bits on ARM Cortex-M33)
- SRAM addresses start with `0x20xxxxxx`; hardware register addresses start with `0x40xxxxxx`
- The literal pool (where PC-relative loads get their data) is usually right after the function's code
- `i2c_inst_t` is only 8 bytes: 4-byte pointer + 4-byte bool (padded to 4 bytes for alignment)
- I²C0 base = `0x40090000`, I²C1 base = `0x40098000` — they are `0x8000` bytes apart

#### Next Steps
- Proceed to Exercise 4 to patch the LCD to display your own custom message
- Try modifying the `restart_on_next` field in GDB and observe if it changes I²C behavior
- Explore the I²C hardware registers at `0x40098000` — can you read the IC_STATUS register to see if the bus is active?
