# Week 4: Variables in Embedded Systems: Debugging and Hacking Variables w/ GPIO Output Basics ## 🎯 What You'll Learn This Week By the end of this tutorial, you will be able to: - Understand what variables are and how they're stored in memory - Know the difference between initialized, uninitialized, and constant variables - Use Ghidra to analyze binaries without debug symbols - Patch binary files to change program behavior permanently - Control GPIO pins to blink LEDs on the Pico 2 - Convert patched binaries to UF2 format for flashing - Understand the `.data`, `.bss`, and `.rodata` memory sections --- ## 📚 Part 1: Understanding Variables ### What is a Variable? A **variable** is like a labeled box where you can store information. Imagine you have a row of boxes numbered 0 to 9. Each box can hold one item. In programming: - The **boxes** are memory locations (addresses in SRAM) - The **items** are the values you store - The **labels** are the variable names you choose ``` ┌─────────────────────────────────────────────────────────────────┐ │ Memory (SRAM) - Like a row of numbered boxes │ │ │ │ Box 0 Box 1 Box 2 Box 3 Box 4 ... │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │ 42 │ │ 17 │ │ 0 │ │255 │ │ 99 │ │ │ └────┘ └────┘ └────┘ └────┘ └────┘ │ │ age score count max temp │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### Declaration vs Definition When working with variables, there are two important concepts: | Concept | What It Does | Example | | ------------------ | ------------------------------------ | -------------------------- | | **Declaration** | Tells the compiler the name and type | `uint8_t age;` | | **Definition** | Allocates memory for the variable | (happens with declaration) | | **Initialization** | Assigns an initial value | `uint8_t age = 42;` | **Important Rule:** You must declare a variable BEFORE you use it! ### Understanding Data Types The **data type** tells the compiler how much memory to allocate: | Type | Size | Range | Description | | ---------- | ------- | ------------------------------- | ----------------------- | | `uint8_t` | 1 byte | 0 to 255 | Unsigned 8-bit integer | | `int8_t` | 1 byte | -128 to 127 | Signed 8-bit integer | | `uint16_t` | 2 bytes | 0 to 65,535 | Unsigned 16-bit integer | | `int16_t` | 2 bytes | -32,768 to 32,767 | Signed 16-bit integer | | `uint32_t` | 4 bytes | 0 to 4,294,967,295 | Unsigned 32-bit integer | | `int32_t` | 4 bytes | -2,147,483,648 to 2,147,483,647 | Signed 32-bit integer | ### Anatomy of a Variable Declaration Let's break down this line of code: ```c uint8_t age = 42; ``` | Part | Meaning | | --------- | ----------------------------------------------------- | | `uint8_t` | Data type - unsigned 8-bit integer (1 byte) | | `age` | Variable name - how we refer to this storage location | | `=` | Assignment operator - puts a value into the variable | | `42` | The initial value | | `;` | Semicolon - tells compiler the statement is complete | --- ## 📚 Part 2: Memory Sections - Where Variables Live ### The Three Main Sections When your program is compiled, variables go to different places depending on how they're declared: ``` ┌─────────────────────────────────────────────────────────────────┐ │ .data Section (Flash → copied to RAM at startup) │ │ Contains: Initialized global/static variables │ │ Example: int counter = 42; │ ├─────────────────────────────────────────────────────────────────┤ │ .bss Section (RAM - zeroed at startup) │ │ Contains: Uninitialized global/static variables │ │ Example: int counter; (will be 0) │ ├─────────────────────────────────────────────────────────────────┤ │ .rodata Section (Flash - read only) │ │ Contains: Constants, string literals │ │ Example: const int MAX = 100; │ │ Example: "hello, world" │ └─────────────────────────────────────────────────────────────────┘ ``` ### What Happens to Uninitialized Variables? In older C compilers, uninitialized variables could contain "garbage" - random leftover data. But modern compilers (including the Pico SDK) are smarter: 1. Uninitialized global variables go into the `.bss` section 2. The `.bss` section is **NOT stored in the binary** (saves space!) 3. At boot, the startup code uses `memset` to **zero out** all of `.bss` 4. So uninitialized variables are always `0`! This is why in our code: ```c uint8_t age; // This will be 0, not garbage! ``` --- ## 📚 Part 3: Understanding GPIO (General Purpose Input/Output) ### What is GPIO? **GPIO** stands for **General Purpose Input/Output**. These are pins on the microcontroller that you can control with software. Think of them as tiny switches you can turn on and off. ``` ┌─────────────────────────────────────────────────────────────────┐ │ Raspberry Pi Pico 2 │ │ │ │ GPIO 16 ───────► Red LED │ │ GPIO 17 ───────► Green LED │ │ GPIO 18 ───────► Blue LED │ │ ... │ │ GPIO 25 ───────► Onboard LED │ └─────────────────────────────────────────────────────────────────┘ ``` ### GPIO Functions in the Pico SDK The Pico SDK provides simple functions to control GPIO pins: | Function | Purpose | | ------------------------------ | ------------------------------- | | `gpio_init(pin)` | Initialize a GPIO pin for use | | `gpio_set_dir(pin, direction)` | Set pin as INPUT or OUTPUT | | `gpio_put(pin, value)` | Set pin HIGH (1) or LOW (0) | | `sleep_ms(ms)` | Wait for specified milliseconds | ### Basic LED Blink Code ```c #include #include "pico/stdlib.h" #define LED_PIN 16 int main(void) { gpio_init(LED_PIN); // Initialize GPIO 16 gpio_set_dir(LED_PIN, GPIO_OUT); // Set as output while (true) { gpio_put(LED_PIN, 1); // LED ON sleep_ms(500); // Wait 500ms gpio_put(LED_PIN, 0); // LED OFF sleep_ms(500); // Wait 500ms } } ``` ### What Happens Behind the Scenes? Each high-level function calls lower-level code. Let's trace `gpio_init()`: ``` gpio_init(LED_PIN) ↓ gpio_set_dir(LED_PIN, GPIO_IN) // Initially set as input ↓ gpio_put(LED_PIN, 0) // Set output value to 0 ↓ gpio_set_function(LED_PIN, GPIO_FUNC_SIO) // Connect to SIO block ``` The SIO (Single-cycle I/O) block is a special hardware unit in the RP2350 that provides fast GPIO control! --- ## 📚 Part 4: Setting Up Your Environment ### Prerequisites Before we start, make sure you have: 1. A Raspberry Pi Pico 2 board 2. Ghidra installed (for static analysis) 3. Python installed (for UF2 conversion) 4. The sample projects: - `0x0005_intro-to-variables` - `0x0008_uninitialized-variables` 5. A serial monitor (PuTTY, minicom, or screen) ### Project Structure ``` Embedded-Hacking/ ├── 0x0005_intro-to-variables/ │ ├── build/ │ │ ├── 0x0005_intro-to-variables.uf2 │ │ └── 0x0005_intro-to-variables.bin │ └── 0x0005_intro-to-variables.c ├── 0x0008_uninitialized-variables/ │ ├── build/ │ │ ├── 0x0008_uninitialized-variables.uf2 │ │ └── 0x0008_uninitialized-variables.bin │ └── 0x0008_uninitialized-variables.c └── uf2conv.py ``` --- ## 🔬 Part 5: Hands-On Tutorial - Analyzing Variables in Ghidra ### Step 1: Review the Source Code First, let's look at the code we'll be analyzing: **File: `0x0005_intro-to-variables.c`** ```c #include #include "pico/stdlib.h" int main(void) { uint8_t age = 42; age = 43; stdio_init_all(); while (true) printf("age: %d\r\n", age); } ``` **What this code does:** 1. Declares a variable `age` and initializes it to `42` 2. Changes `age` to `43` 3. Initializes the serial output 4. Prints `age` forever in a loop ### Step 2: Flash the Binary to Your Pico 2 1. Hold the BOOTSEL button on your Pico 2 2. Plug in the USB cable (while holding BOOTSEL) 3. Release BOOTSEL - a drive called "RPI-RP2" appears 4. Drag and drop `0x0005_intro-to-variables.uf2` onto the drive 5. The Pico will reboot and start running! ### Step 3: Verify It's Working Open your serial monitor (PuTTY, minicom, or screen) and you should see: ``` age: 43 age: 43 age: 43 ... ``` The program is printing `43` because that's what we assigned after the initial `42`. --- ## 🔬 Part 6: Setting Up Ghidra for Binary Analysis ### Step 4: Start Ghidra **Open a terminal and type:** ```powershell ghidraRun ``` Ghidra will open. Now we need to create a new project. ### Step 5: Create a New Project 1. Click **File** → **New Project** 2. Select **Non-Shared Project** 3. Click **Next** 4. Enter Project Name: `0x0005_intro-to-variables` 5. Click **Finish** ### Step 6: Import the Binary 1. Open your file explorer 2. Navigate to the `Embedded-Hacking` folder 3. Find `0x0005_intro-to-variables.bin` 4. Select Cortex M Little Endian 32 5. Select Options and set up the .text and offset 10000000 6. **Drag and drop** the `.bin` file into Ghidra's project window ### Step 7: Configure the Binary Format A dialog appears. The file is identified as a "BIN" (raw binary without debug symbols). **Click the three dots (…) next to "Language" and:** 1. Search for "Cortex" 2. Select **ARM Cortex 32 little endian default** 3. Click **OK** **Click the "Options…" button and:** 1. Change **Block Name** to `.text` 2. Change **Base Address** to `10000000` (the XIP address!) 3. Click **OK** ### Step 8: Open and Analyze 1. Double-click on the file in the project window 2. A dialog asks "Analyze now?" - Click **Yes** 3. Use default analysis options and click **Analyze** Wait for analysis to complete (watch the progress bar in the bottom right). --- ## 🔬 Part 7: Navigating and Resolving Functions ### Step 9: Find the Functions Look at the **Symbol Tree** panel on the left. Expand **Functions**. You'll see function names like: - `FUN_1000019a` - `FUN_10000210` - `FUN_10000234` These are auto-generated names because we imported a raw binary without symbols! ### Step 10: Resolve Known Functions From our previous chapters, we know what some of these functions are: | Ghidra Name | Actual Name | How We Know | | -------------- | ------------- | -------------------------- | | `FUN_1000019a` | `data_cpy` | From Week 3 boot analysis | | `FUN_10000210` | `frame_dummy` | From Week 3 boot analysis | | `FUN_10000234` | `main` | This is where our code is! | **To rename `FUN_1000019a` to `data_cpy`:** 1. Click on `FUN_1000019a` in the Symbol Tree 2. In the Decompile window, right-click on the function name 3. Select **Edit Function Signature** 4. Change the name to `data_cpy` 5. Click **OK** **Repeat for the other functions:** - Rename `FUN_10000210` to `frame_dummy` - Rename `FUN_10000234` to `main` ### Step 11: Update Main's Signature For `main`, let's also fix the return type: 1. Right-click on `main` in the Decompile window 2. Select **Edit Function Signature** 3. Change to: `int main(void)` 4. Click **OK** --- ## 🔬 Part 8: Analyzing the Main Function ### Step 12: Examine Main in Ghidra Click on `main` (or `FUN_10000234`). Look at the **Decompile** window: You'll see something like: ```c void FUN_10000234(void) { FUN_10002f54(); do { FUN_100030e4(DAT_10000244,0x2b); } while( true ); } ``` ### Step 13: Resolve stdio_init_all 1. Click on `FUN_10002f54` 2. Right-click → **Edit Function Signature** 3. Change to: `bool stdio_init_all(void)` 4. Click **OK** ### Step 14: Resolve printf 1. Click on `FUN_100030e4` 2. Right-click → **Edit Function Signature** 3. Change the name to `printf` 4. Check the **Varargs** checkbox (printf takes variable arguments!) 5. Click **OK** ### Step 15: Understand the Optimization Look at the decompiled code. This will look different if you resolved your functions however do you notice something interesting? ```c void FUN_10000234(void) { FUN_10002f54(); do { FUN_100030e4(DAT_10000244,0x2b); } while( true ); } ``` **Where's `uint8_t age = 42`?** It's gone! The compiler **optimized it out**! Here's what happened: 1. Original code: `age = 42`, then `age = 43` 2. Compiler sees: "The `42` is never used, only `43` matters" 3. Compiler removes the unused `42` and just uses `43` directly **What is `0x2b`?** Let's check: - `0x2b` in hexadecimal = `43` in decimal ✓ The compiler replaced our variable with the constant value! --- ## 🔬 Part 9: Patching the Binary - Changing the Value ### Step 16: Find the Value to Patch Look at the **Listing** window (assembly view). Find the instruction that loads `0x2b`: ```assembly 1000023a 2b 21 movs r1,#0x2b ``` This instruction loads the value `0x2b` (43) into register `r1` before calling `printf`. ### Step 17: Patch the Instruction We're going to change `0x2b` (43) to `0x46` (70)! 1. Click on the instruction `movs r1,#0x2b` 2. Right-click and select **Patch Instruction** 3. Change `0x2b` to `0x46` 4. Press Enter The instruction now reads: ```assembly 1000023a 46 21 movs r1,#0x46 ``` ### Step 18: Export the Patched Binary 1. Click **File** → **Export Program** 2. Set **Format** to **Raw Bytes** 3. Navigate to your build directory 4. Name the file `0x0005_intro-to-variables-h.bin` 5. Click **OK** --- ## 🔬 Part 10: Converting and Flashing the Hacked Binary ### Step 19: Convert to UF2 Format The Pico 2 expects UF2 files, not raw BIN files. We need to convert it! **Open a terminal and navigate to your project directory:** ```powershell cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0005_intro-to-variables ``` **Run the conversion command:** ```powershell python ..\uf2conv.py build\0x0005_intro-to-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` **What this command means:** - `uf2conv.py` = the conversion script - `--base 0x10000000` = the XIP base address - `--family 0xe48bff59` = the RP2350 family ID - `--output build\hacked.uf2` = the output filename ### Step 20: Flash the Hacked Binary 1. Hold BOOTSEL and plug in your Pico 2 2. Drag and drop `hacked.uf2` onto the RPI-RP2 drive 3. Open your serial monitor **You should see:** ``` age: 70 age: 70 age: 70 ... ``` 🎉 **BOOM! We hacked it!** The value changed from 43 to 70! --- ## 🔬 Part 11: Uninitialized Variables and GPIO Now let's work with a more complex example that includes GPIO control. ### Step 21: Review the Uninitialized Variables Code **File: `0x0008_uninitialized-variables.c`** ```c #include #include "pico/stdlib.h" #define LED_PIN 16 int main(void) { uint8_t age; // Uninitialized! stdio_init_all(); gpio_init(LED_PIN); gpio_set_dir(LED_PIN, GPIO_OUT); while (true) { printf("age: %d\r\n", age); gpio_put(LED_PIN, 1); sleep_ms(500); gpio_put(LED_PIN, 0); sleep_ms(500); } } ``` **What this code does:** 1. Declares `age` without initializing it (will be 0 due to BSS zeroing) 2. Initializes GPIO 16 as an output 3. In a loop: prints age, blinks the LED ### Step 22: Flash and Verify 1. Flash `0x0008_uninitialized-variables.uf2` to your Pico 2 2. Open your serial monitor **You should see:** ``` age: 0 age: 0 age: 0 ... ``` And the **red LED on GPIO 16 should be blinking**! The value is `0` because uninitialized variables in the `.bss` section are zeroed at startup. --- ## 🔬 Part 12: Analyzing GPIO Code in Ghidra ### Step 23: Set Up Ghidra for the New Binary 1. Create a new project: `0x0008_uninitialized-variables` 2. Import `0x0008_uninitialized-variables.bin` 3. Set Language to **ARM Cortex 32 little endian** 4. Set Base Address to `10000000` 5. Auto-analyze ### Step 24: Resolve the Functions Find and rename these functions: | Ghidra Name | Actual Name | | -------------- | ---------------- | | `FUN_10000234` | `main` | | `FUN_100030cc` | `stdio_init_all` | | `FUN_100002b4` | `gpio_init` | | `FUN_1000325c` | `printf` | For `gpio_init`, set the signature to: ```c void gpio_init(uint gpio) ``` ### Step 25: Examine the Main Function The decompiled main should look something like: ```c void FUN_10000234(void) { undefined4 extraout_r1; undefined4 extraout_r2; undefined4 in_cr0; undefined4 in_cr4; FUN_100030cc(); FUN_100002b4(0x10); coprocessor_moveto2(0,4,0x10,1,in_cr4); do { FUN_1000325c(DAT_10000274,0); coprocessor_moveto2(0,4,0x10,1,in_cr0); FUN_10000d10(500); coprocessor_moveto2(0,4,0x10,0,in_cr0); FUN_10000d10(500,extraout_r1,extraout_r2,0); } while( true ); } ``` --- ## 🔬 Part 13: Hacking GPIO - Changing the LED Pin ### Step 26: Find the GPIO Pin Value Look in the assembly for instructions that use `0x10` (which is 16 in decimal - our LED pin): ```assembly 1000023a 10 20 movs r0,#0x10 ``` This is where `gpio_init(LED_PIN)` is called with GPIO 16. ### Step 27: Patch GPIO 16 to GPIO 17 We'll change the red LED (GPIO 16) to the green LED (GPIO 17)! 1. Find the instruction `movs r0,#0x10` 2. Right-click → **Patch Instruction** 3. Change `0x10` to `0x11` (17 in hex) 4. Click **OK** ### Step 28: Find All GPIO 16 References There are more places that use GPIO 16. Look for: ```assembly 10000244 10 23 movs r3,#0x10 ``` This is used in `gpio_set_dir`. Patch this to `0x11` as well. ```assembly 10000252 10 24 movs r4,#0x10 ``` This is inside the loop for `gpio_put`. Patch this to `0x11` as well. ### Step 29: Bonus - Change the Printed Value Let's also change the printed value from `0` to `0x42` (66 in decimal): ```assembly 1000024a 00 21 movs r1,#0x0 ``` 1. Right-click → **Patch Instruction** 2. Change `0x0` to `0x42` 3. Click **OK** --- ## 🔬 Part 14: Export and Test the Hacked GPIO ### Step 30: Export the Patched Binary 1. Click **File** → **Export Program** 2. Format: **Raw Bytes** 3. Filename: `0x0008_uninitialized-variables-h.bin` 4. Click **OK** ### Step 31: Convert to UF2 ```powershell cd C:\Users\flare-vm\Desktop\Embedded-Hacking-main\0x0008_uninitialized-variables python ..\uf2conv.py build\0x0008_uninitialized-variables-h.bin --base 0x10000000 --family 0xe48bff59 --output build\hacked.uf2 ``` ### Step 32: Flash and Verify 1. Flash `hacked.uf2` to your Pico 2 2. Check your serial monitor **You should see:** ``` age: 66 age: 66 age: 66 ... ``` And now the **GREEN LED on GPIO 17** should be blinking instead of the red one! 🎉 **We successfully:** 1. Changed the printed value from 0 to 66 2. Changed which LED blinks from red (GPIO 16) to green (GPIO 17) --- ## 📚 Part 15: Deep Dive - GPIO at the Assembly Level ### Understanding the GPIO Coprocessor The RP2350 has a special **GPIO coprocessor** that provides fast, single-cycle GPIO control. This is different from the RP2040! The coprocessor is accessed using special ARM instructions: ```assembly mcrr p0, #4, r4, r5, c0 ; GPIO output control mcrr p0, #4, r4, r5, c4 ; GPIO direction control ``` **What this means:** - `mcrr` = Move to Coprocessor from two ARM Registers - `p0` = Coprocessor 0 (the GPIO coprocessor) - `r4` = Contains the GPIO pin number - `r5` = Contains the value (0 or 1) - `c0` = Output value register - `c4` = Output enable register ### The Full GPIO Initialization Sequence When you call `gpio_init(16)`, here's what actually happens: ``` Step 1: Configure pad (address 0x40038044) ┌─────────────────────────────────────────────────────────────────┐ │ - Clear OD bit (output disable) │ │ - Set IE bit (input enable) │ │ - Clear ISO bit (isolation) │ └─────────────────────────────────────────────────────────────────┘ Step 2: Set function (address 0x40028084) ┌─────────────────────────────────────────────────────────────────┐ │ - Set FUNCSEL to 5 (SIO - Software I/O) │ └─────────────────────────────────────────────────────────────────┘ Step 3: Enable output (via coprocessor) ┌─────────────────────────────────────────────────────────────────┐ │ - mcrr p0, #4, r4, r5, c4 (where r4=16, r5=1) │ └─────────────────────────────────────────────────────────────────┘ ``` ### Raw Assembly LED Blink Here's what a completely hand-written assembly LED blink looks like: ```assembly ; Initialize GPIO 16 as output movs r4, #0x10 ; GPIO 16 movs r5, #0x01 ; Enable mcrr p0, #4, r4, r5, c4 ; Set as output ; Configure pad registers ldr r3, =0x40038044 ; Pad control for GPIO 16 ldr r2, [r3] ; Load current config bic r2, r2, #0x80 ; Clear OD (output disable) orr r2, r2, #0x40 ; Set IE (input enable) str r2, [r3] ; Store config ; Set GPIO function to SIO ldr r3, =0x40028084 ; IO bank control for GPIO 16 movs r2, #5 ; FUNCSEL = SIO str r2, [r3] ; Set function ; Main loop loop: ; LED ON movs r4, #0x10 ; GPIO 16 movs r5, #0x01 ; High mcrr p0, #4, r4, r5, c0 ; Delay ldr r2, =0x17D7840 ; ~25 million iterations delay1: subs r2, r2, #1 bne delay1 ; LED OFF movs r4, #0x10 ; GPIO 16 movs r5, #0x00 ; Low mcrr p0, #4, r4, r5, c0 ; Delay ldr r2, =0x17D7840 delay2: subs r2, r2, #1 bne delay2 b loop ; Repeat forever ``` --- ## 📊 Part 16: Summary and Review ### What We Accomplished 1. **Learned about variables** - How they're declared, initialized, and stored 2. **Understood memory sections** - `.data`, `.bss`, and `.rodata` 3. **Analyzed binaries in Ghidra** - Without debug symbols! 4. **Patched binaries** - Changed values directly in the binary 5. **Controlled GPIO** - Made LEDs blink 6. **Changed program behavior** - Different LED, different value ### The Binary Patching Workflow ``` ┌─────────────────────────────────────────────────────────────────┐ │ 1. Import .bin file into Ghidra │ │ - Set language to ARM Cortex │ │ - Set base address to 0x10000000 │ ├─────────────────────────────────────────────────────────────────┤ │ 2. Analyze and resolve functions │ │ - Rename functions to meaningful names │ │ - Fix function signatures │ ├─────────────────────────────────────────────────────────────────┤ │ 3. Find the values/instructions to patch │ │ - Look in the assembly listing │ │ - Right-click → Patch Instruction │ ├─────────────────────────────────────────────────────────────────┤ │ 4. Export the patched binary │ │ - File → Export Program │ │ - Format: Raw Bytes │ ├─────────────────────────────────────────────────────────────────┤ │ 5. Convert to UF2 │ │ - python uf2conv.py file.bin --base 0x10000000 │ │ --family 0xe48bff59 --output hacked.uf2 │ ├─────────────────────────────────────────────────────────────────┤ │ 6. Flash and verify │ │ - Hold BOOTSEL, plug in, drag UF2 │ │ - Check serial output and LED behavior │ └─────────────────────────────────────────────────────────────────┘ ``` ### Key Memory Sections | Section | Location | Contains | Writable? | | --------- | -------- | ------------------------------ | --------- | | `.text` | Flash | Code | No | | `.rodata` | Flash | Constants, strings | No | | `.data` | RAM | Initialized globals | Yes | | `.bss` | RAM | Uninitialized globals (zeroed) | Yes | ### Important Ghidra Commands | Action | How To Do It | | ----------------- | ------------------------------------- | | Rename function | Right-click → Edit Function Signature | | Patch instruction | Right-click → Patch Instruction | | Export binary | File → Export Program → Raw Bytes | | Go to address | Press 'G' and enter address | --- ## ✅ Practice Exercises ### Exercise 1: Change the Delay The LED blinks every 500ms. Find the `sleep_ms(500)` calls in the binary and change them to `sleep_ms(100)` for faster blinking. **Hint:** Look for the value `0x1F4` (500 in hex) being loaded into a register. ### Exercise 2: Reverse the LED Instead of GPIO 16 → ON → OFF, make it GPIO 16 → OFF → ON (start with LED on). **Hint:** Find and swap the two `gpio_put` calls (the ones with values 0 and 1). ### Exercise 3: Add a Second LED Patch the binary so that BOTH GPIO 16 and GPIO 17 blink together. **Hint:** You'll need to find space for additional instructions or modify existing ones cleverly. ### Exercise 4: Change the Format String The program prints "age: %d\r\n". Can you find this string in Ghidra and figure out where it's stored? **Hint:** Look in the `.rodata` section around address `0x10001xxx`. --- ## 🎓 Key Takeaways 1. **Variables are just memory locations** - The compiler assigns them addresses in SRAM. 2. **Compilers optimize aggressively** - Unused code and values may be removed entirely. 3. **Uninitialized doesn't mean random** - Modern compilers zero out the `.bss` section. 4. **Ghidra works without symbols** - You can analyze any binary, even stripped ones. 5. **Binary patching is powerful** - You can change behavior without source code. 6. **UF2 conversion is required** - The Pico 2 needs UF2 format, not raw binaries. 7. **GPIO is just memory-mapped I/O** - Writing to specific addresses controls hardware. --- ## 📖 Glossary | Term | Definition | | ------------------ | --------------------------------------------------------------------- | | **BSS** | Block Started by Symbol - section for uninitialized global variables | | **Declaration** | Telling the compiler a variable's name and type | | **Definition** | Allocating memory for a variable | | **GPIO** | General Purpose Input/Output - controllable pins on a microcontroller | | **Initialization** | Assigning an initial value to a variable | | **Linker** | Tool that combines compiled code and assigns memory addresses | | **Optimization** | Compiler removing or simplifying code for efficiency | | **Patching** | Modifying bytes directly in a binary file | | **rodata** | Read-only data section for constants and string literals | | **SIO** | Single-cycle I/O - fast GPIO control block in RP2350 | | **UF2** | USB Flashing Format - file format for Pico 2 firmware | | **Variable** | A named storage location in memory | --- ## 🔗 Additional Resources ### GPIO Coprocessor Reference The RP2350 GPIO coprocessor instructions: | Instruction | Description | | -------------------------- | ---------------------------- | | `mcrr p0, #4, Rt, Rt2, c0` | Set/clear GPIO output | | `mcrr p0, #4, Rt, Rt2, c4` | Set/clear GPIO output enable | ### RP2350 Memory Map Quick Reference | Address | Description | | ------------ | ------------------------ | | `0x10000000` | XIP Flash (code) | | `0x20000000` | SRAM (data) | | `0x40028000` | IO_BANK0 (GPIO control) | | `0x40038000` | PADS_BANK0 (pad control) | | `0xd0000000` | SIO (single-cycle I/O) | --- **Remember:** Every binary you encounter in the real world can be analyzed and understood using these same techniques. Practice makes perfect! Happy hacking! 🔧