Week 4-BN: resolve functions with Y (Change Type); fully resolve name+type

- Step 16: Y is the primary resolution key (Change Type sets name+type); N is
  rename-only; explicit how-to and worked examples use G then Y
- Step 4: add exact DWARF signatures to both symbol tables
- Python shortcut now sets names AND types (bv.get_function_at(...).set_user_type)
- Step 26: worked examples use G then Y
This commit is contained in:
Kevin Thomas committed 2026-10-03 15:08:58 -04:00
1 parent e41c4581bd
commit 41e6d6c11f
2 files changed
+112 -75

No files matched your search

+112 -75
View File
@@ -171,37 +171,39 @@ Each line is `address type name`. The `T`/`t` type is a function. Here are the f
**Project 1 — `0x0005_intro-to-variables`:** **Project 1 — `0x0005_intro-to-variables`:**
| Address | ELF symbol | Role | | Address | ELF symbol | Signature | Role |
| ------- | ---------- | ---- | | ------- | ---------- | --------- | ---- |
| `0x1000015c` | `_reset_handler` | reset entry | | `0x1000015c` | `_reset_handler` | — | reset entry |
| `0x10000186` | `platform_entry` | calls `runtime_init`, `main`, `exit` | | `0x10000186` | `platform_entry` | `void platform_entry(void)` | calls `runtime_init`, `main`, `exit` |
| `0x1000019a` | `data_cpy` | copies `.data` from flash to SRAM | | `0x1000019a` | `data_cpy` | `void data_cpy(void*, void*, void*)` | copies `.data` from flash to SRAM |
| `0x100001e4` | `_init` | runs `.init_array` | | `0x100001e4` | `_init` | `void _init(void)` | runs `.init_array` |
| `0x10000210` | `frame_dummy` | C runtime boilerplate | | `0x10000210` | `frame_dummy` | `void frame_dummy(void)` | C runtime boilerplate |
| `0x10000234` | `main` | the lesson function | | `0x10000234` | `main` | `int main(void)` | the lesson function |
| `0x10000248` | `gpio_set_function` | SDK GPIO helper | | `0x10000248` | `gpio_set_function` | `void gpio_set_function(uint, gpio_function_t)` | SDK GPIO helper |
| `0x10002cfc` | `exit` | C runtime exit | | `0x10002cfc` | `exit` | `void exit(int)` | C runtime exit |
| `0x10002d04` | `runtime_init` | SDK runtime init | | `0x10002d04` | `runtime_init` | `void runtime_init(void)` | SDK runtime init |
| `0x10002f54` | `stdio_init_all` | SDK serial init | | `0x10002f54` | `stdio_init_all` | `bool stdio_init_all(void)` | SDK serial init |
| `0x100030e4` | `__wrap_printf` | the `printf` wrapper | | `0x100030e4` | `__wrap_printf` | `int __wrap_printf(const char*, ...)` | the `printf` wrapper |
These signatures come from the ELF's DWARF debug info, so they are exact. You apply them in Binary Ninja in Step 16 (`G` -> address, then `Y` -> Change Type).
**Project 2 — `0x0008_uninitialized-variables`:** **Project 2 — `0x0008_uninitialized-variables`:**
| Address | ELF symbol | Role | | Address | ELF symbol | Signature | Role |
| ------- | ---------- | ---- | | ------- | ---------- | --------- | ---- |
| `0x1000015c` | `_reset_handler` | reset entry | | `0x1000015c` | `_reset_handler` | — | reset entry |
| `0x10000186` | `platform_entry` | calls `runtime_init`, `main`, `exit` | | `0x10000186` | `platform_entry` | `void platform_entry(void)` | calls `runtime_init`, `main`, `exit` |
| `0x1000019a` | `data_cpy` | copies `.data` from flash to SRAM | | `0x1000019a` | `data_cpy` | `void data_cpy(void*, void*, void*)` | copies `.data` from flash to SRAM |
| `0x100001e4` | `_init` | runs `.init_array` | | `0x100001e4` | `_init` | `void _init(void)` | runs `.init_array` |
| `0x10000210` | `frame_dummy` | C runtime boilerplate | | `0x10000210` | `frame_dummy` | `void frame_dummy(void)` | C runtime boilerplate |
| `0x10000234` | `main` | the lesson function (`blink_and_print` inlined) | | `0x10000234` | `main` | `int main(void)` | the lesson function (`blink_and_print` inlined) |
| `0x10000278` | `gpio_set_function` | SDK GPIO helper | | `0x10000278` | `gpio_set_function` | `void gpio_set_function(uint, gpio_function_t)` | SDK GPIO helper |
| `0x100002b4` | `gpio_init` | SDK GPIO init | | `0x100002b4` | `gpio_init` | `void gpio_init(uint)` | SDK GPIO init |
| `0x10000d10` | `sleep_ms` | SDK delay | | `0x10000d10` | `sleep_ms` | `void sleep_ms(uint32_t)` | SDK delay |
| `0x10002e74` | `exit` | C runtime exit | | `0x10002e74` | `exit` | `void exit(int)` | C runtime exit |
| `0x10002e7c` | `runtime_init` | SDK runtime init | | `0x10002e7c` | `runtime_init` | `void runtime_init(void)` | SDK runtime init |
| `0x100030cc` | `stdio_init_all` | SDK serial init | | `0x100030cc` | `stdio_init_all` | `bool stdio_init_all(void)` | SDK serial init |
| `0x1000325c` | `__wrap_printf` | the `printf` wrapper | | `0x1000325c` | `__wrap_printf` | `int __wrap_printf(const char*, ...)` | the `printf` wrapper |
> **`main` is `0x10000234` in both projects.** In Project 2 the `static void blink_and_print` helper is inlined into `main` by the `Release` optimizer, so it does not appear as a separate symbol. That is why both projects put `main` at the same address. In a `Debug` build it stays separate and `main` moves — another reason to build `Release`. > **`main` is `0x10000234` in both projects.** In Project 2 the `static void blink_and_print` helper is inlined into `main` by the `Release` optimizer, so it does not appear as a separate symbol. That is why both projects put `main` at the same address. In a `Debug` build it stays separate and `main` moves — another reason to build `Release`.
@@ -536,42 +538,49 @@ Three keys do all the work:
| Key | Binary Ninja action | Use it for | | Key | Binary Ninja action | Use it for |
| --- | --- | --- | | --- | --- | --- |
| `G` | Go to address | Jump to a function's address | | `G` | Go to address | Jump to a function's address |
| `N` | Rename symbol | Give the function its real name | | `Y` | **Change Type** | Set the function's signature. The dialog shows the full prototype, so this sets the name *and* the type in one step. |
| `Y` | Set type | Give the function its signature | | `N` | Rename | Rename only, when you just want the name and not the type |
For each function below, the loop is the same: `G` to its address, `N` to rename it, `Y` to set its signature. For each function below: `G` to its address, then **`Y` (Change Type)** and type the prototype from the table.
#### How to resolve a function in Binary Ninja (`Y`)
`Y` is the **Change Type** key, and it is what actually resolves the function — it turns `void sub_10002f54()` into `bool stdio_init_all(void)`. The Change Type dialog shows the full declaration (name and type), so typing the prototype sets both:
1. `G` to the function's address. The cursor lands on the function.
2. Press **`Y`**. In the Change Type dialog, type the prototype from the table exactly — for example `bool stdio_init_all(void)` — and press Enter.
The decompiler header then shows the real prototype, and calls to the function read cleanly instead of `sub_<addr>()`. `N` is only for renaming without touching the type; `Y` alone sets both the name and the type.
If `Y` seems to do nothing, confirm the cursor is on the function, or right-click it and pick **Change Type...**. Binary Ninja parses what you type and silently keeps the old type if it does not parse, so glance at the header after each `Y`.
#### Worked example: `main` #### Worked example: `main`
1. Press `G`, type `0x10000234`, press Enter. The view jumps there; the cursor lands on `sub_10000234`. 1. Press `G`, type `0x10000234`, press Enter. The view jumps there; the cursor lands on `sub_10000234`.
2. Press `N`, type `main`, press Enter. 2. Press **`Y`** (Change Type), type `int main(void)`, press Enter. That sets the name to `main` and the type to `int(void)`.
3. Press `Y`, type `int main(void)`, press Enter.
> **Binary Ninja shows `int32_t` where Ghidra shows `int`.** After you set `int main(void)`, the decompiler header may read `int32_t main(void)`. That is the same type — on this platform `int` is 32 bits and Binary Ninja's parser normalises it to `int32_t`. Do not fight it; it is not an error. > **Binary Ninja shows `int32_t` where Ghidra shows `int`.** After you set `int main(void)`, the decompiler header may read `int32_t main(void)`. That is the same type — on this platform `int` is 32 bits and Binary Ninja's parser normalises it to `int32_t`. Do not fight it; it is not an error.
#### Worked example: `stdio_init_all` #### Worked example: `stdio_init_all`
1. `G` -> `0x10002f54`. 1. `G` -> `0x10002f54`.
2. `N` -> `stdio_init_all`. 2. `Y` -> `bool stdio_init_all(void)`.
3. `Y` -> `bool stdio_init_all(void)`.
It returns **`bool`**, not `void` — the ELF says `_Bool stdio_init_all(void)`. Our `main` ignores the return value, so the decompiler still reads cleanly. It returns **`bool`**, not `void` — the ELF says `_Bool stdio_init_all(void)`. Our `main` ignores the return value, so the decompiler still reads cleanly.
#### Worked example: `uart_init` #### Worked example: `uart_init`
1. `G` -> `0x10000e10`. 1. `G` -> `0x10000e10`.
2. `N` -> `uart_init`. 2. `Y` -> `uint uart_init(uart_inst_t *uart, uint baudrate)`.
3. `Y` -> `uint uart_init(uart_inst_t *uart, uint baudrate)`.
#### Worked example: `__wrap_printf` #### Worked example: `__wrap_printf`
1. `G` -> `0x100030e4`. 1. `G` -> `0x100030e4`.
2. `N` -> `__wrap_printf`. 2. `Y` -> `int __wrap_printf(const char *fmt, ...)`. Keep the `...` — `printf` is variadic.
3. `Y` -> `int __wrap_printf(const char *fmt, ...)`. Keep the `...` — `printf` is variadic.
> **`printf` in our source is `__wrap_printf` in the binary.** The SDK links our `printf` calls to its `__wrap_printf` wrapper. Rename it `printf` if you prefer the lesson's shorthand, but `__wrap_printf` is the real symbol. > **`printf` in our source is `__wrap_printf` in the binary.** The SDK links our `printf` calls to its `__wrap_printf` wrapper.
The rest of the chain is the same three keystrokes per function. This is **our code plus the library functions it actually calls** — not the whole SDK. `main` only calls `stdio_init_all` and `printf`, so we follow that chain down: `stdio_init_all` pulls in the stdio/UART setup, and `printf` lands in the SDK's `__wrap_printf`. The rest of the chain is the same two keystrokes per function (`G`, then `Y`). This is **our code plus the library functions it actually calls** — not the whole SDK. `main` only calls `stdio_init_all` and `printf`, so we follow that chain down: `stdio_init_all` pulls in the stdio/UART setup, and `printf` lands in the SDK's `__wrap_printf`.
The call chain for this project: The call chain for this project:
@@ -614,25 +623,41 @@ main
> >
> **`stdio_init_all` returns `bool`, not `void`** — `_Bool stdio_init_all(void)` in the ELF. The `main` source ignores the return value, so the decompiler still reads fine. > **`stdio_init_all` returns `bool`, not `void`** — `_Bool stdio_init_all(void)` in the ELF. The `main` source ignores the return value, so the decompiler still reads fine.
> **Shortcut:** instead of renaming by hand, paste this into Binary Ninja's Python console (`Plugins -> Python Console`). It applies the same symbol map programmatically: > **Shortcut — resolves name *and* type for every function.** Instead of doing `N` + `Y` by hand, paste this into Binary Ninja's Python console (`Plugins -> Python Console`). It sets each function's name and signature programmatically:
> >
> ```python > ```python
> from binaryninja import Symbol, SymbolType > from binaryninja import Symbol, SymbolType
> symbols = { > # address: (name, signature); None means "leave the type alone"
> 0x1000015c: "_reset_handler", 0x10000186: "platform_entry", > funcs = {
> 0x1000019a: "data_cpy", 0x100001e4: "_init", > 0x1000015c: ("_reset_handler", None),
> 0x10000210: "frame_dummy", 0x10000234: "main", > 0x10000186: ("platform_entry", "void platform_entry(void)"),
> 0x10002cfc: "exit", 0x10002d04: "runtime_init", > 0x1000019a: ("data_cpy", "void data_cpy(void*, void*, void*)"),
> 0x10002f54: "stdio_init_all", 0x100032a0: "stdio_uart_init", > 0x100001e4: ("_init", "void _init(void)"),
> 0x10002f2c: "stdio_set_driver_enabled", > 0x10000210: ("frame_dummy", "void frame_dummy(void)"),
> 0x10002d30: "stdio_out_chars_crlf", 0x10002e40: "stdio_put_string", > 0x10000234: ("main", "int main(void)"),
> 0x100030e4: "__wrap_printf", 0x10003020: "__wrap_vprintf", > 0x10002cfc: ("exit", "void exit(int)"),
> 0x10000248: "gpio_set_function", 0x10000e10: "uart_init", > 0x10002d04: ("runtime_init", "void runtime_init(void)"),
> 0x10000da0: "time_us_64", 0x100033e0: "strlen", > 0x10002f54: ("stdio_init_all", "bool stdio_init_all(void)"),
> 0x100032a0: ("stdio_uart_init", "void stdio_uart_init(void)"),
> 0x10002f2c: ("stdio_set_driver_enabled", "void stdio_set_driver_enabled(stdio_driver_t*, bool)"),
> 0x10002d30: ("stdio_out_chars_crlf", "void stdio_out_chars_crlf(stdio_driver_t*, const char*, int)"),
> 0x10002e40: ("stdio_put_string", "int stdio_put_string(const char*, int, bool, bool)"),
> 0x100030e4: ("__wrap_printf", "int __wrap_printf(const char*, ...)"),
> 0x10003020: ("__wrap_vprintf", "int __wrap_vprintf(const char*, va_list)"),
> 0x10000248: ("gpio_set_function", "void gpio_set_function(uint, gpio_function_t)"),
> 0x10000e10: ("uart_init", "uint uart_init(uart_inst_t*, uint)"),
> 0x10000da0: ("time_us_64", "uint64_t time_us_64(void)"),
> 0x100033e0: ("strlen", "size_t strlen(const char*)"),
> } > }
> for addr, name in symbols.items(): > for addr, (name, sig) in funcs.items():
> bv.define_user_symbol(Symbol(SymbolType.FunctionSymbol, addr, name)) > bv.define_user_symbol(Symbol(SymbolType.FunctionSymbol, addr, name))
> if sig:
> f = bv.get_function_at(addr)
> if f is not None:
> f.set_user_type(sig)
> ``` > ```
>
> SDK type names (`stdio_driver_t`, `gpio_function_t`, `uart_inst_t`) are not in the raw `.bin`, so Binary Ninja creates them as **undefined named types** — that is fine, they still read correctly in the decompiler. If any one signature fails to parse, set that row by hand with `Y`.
### Step 17: Read `main` in the decompiler ### Step 17: Read `main` in the decompiler
@@ -859,27 +884,24 @@ Same idea as Project 1, different addresses. Here the format string is at `0x100
### Step 26: Resolve the functions in the Binary Ninja GUI ### Step 26: Resolve the functions in the Binary Ninja GUI
Same three keys as Step 16 — `G` to the address, `N` to rename, `Y` to set the signature — using the Project 2 ELF symbol map from Step 4. Same two keys as Step 16 — `G` to the address, then `Y` (Change Type) to set the prototype — using the Project 2 ELF symbol map from Step 4.
The mechanics are identical to Step 16, so here are the worked examples for the functions that are specific to this project. The mechanics are identical to Step 16, so here are the worked examples for the functions that are specific to this project.
#### `main` #### `main`
1. `G` -> `0x10000234`. 1. `G` -> `0x10000234`.
2. `N` -> `main`. 2. `Y` -> `int main(void)` (Binary Ninja shows `int32_t main(void)` — the same 32-bit `int`).
3. `Y` -> `int main(void)` (Binary Ninja shows `int32_t main(void)` — the same 32-bit `int`).
#### `gpio_init` #### `gpio_init`
1. `G` -> `0x100002b4`. 1. `G` -> `0x100002b4`.
2. `N` -> `gpio_init`. 2. `Y` -> `void gpio_init(uint gpio)`.
3. `Y` -> `void gpio_init(uint gpio)`.
#### `sleep_ms` #### `sleep_ms`
1. `G` -> `0x10000d10`. 1. `G` -> `0x10000d10`.
2. `N` -> `sleep_ms`. 2. `Y` -> `void sleep_ms(uint32_t ms)`.
3. `Y` -> `void sleep_ms(uint32_t ms)`.
#### `stdio_init_all` and `__wrap_printf` #### `stdio_init_all` and `__wrap_printf`
@@ -933,25 +955,40 @@ Two things in this project have **no symbol of their own**, because the compiler
| `0x10000ef4` | `time_us_64` | `uint64_t time_us_64(void)` | | `0x10000ef4` | `time_us_64` | `uint64_t time_us_64(void)` |
| `0x10003558` | `strlen` | `size_t strlen(const char*)` | | `0x10003558` | `strlen` | `size_t strlen(const char*)` |
Python console shortcut: Python console shortcut (resolves name **and** type):
```python ```python
from binaryninja import Symbol, SymbolType from binaryninja import Symbol, SymbolType
symbols = { # address: (name, signature); None means "leave the type alone"
0x1000015c: "_reset_handler", 0x10000186: "platform_entry", funcs = {
0x1000019a: "data_cpy", 0x100001e4: "_init", 0x1000015c: ("_reset_handler", None),
0x10000210: "frame_dummy", 0x10000234: "main", 0x10000186: ("platform_entry", "void platform_entry(void)"),
0x100002b4: "gpio_init", 0x10000278: "gpio_set_function", 0x1000019a: ("data_cpy", "void data_cpy(void*, void*, void*)"),
0x10000d10: "sleep_ms", 0x10002e74: "exit", 0x100001e4: ("_init", "void _init(void)"),
0x10002e7c: "runtime_init", 0x100030cc: "stdio_init_all", 0x10000210: ("frame_dummy", "void frame_dummy(void)"),
0x10003418: "stdio_uart_init", 0x100030a4: "stdio_set_driver_enabled", 0x10000234: ("main", "int main(void)"),
0x10002ea8: "stdio_out_chars_crlf", 0x10002fb8: "stdio_put_string", 0x100002b4: ("gpio_init", "void gpio_init(uint)"),
0x1000325c: "__wrap_printf", 0x10003198: "__wrap_vprintf", 0x10000278: ("gpio_set_function", "void gpio_set_function(uint, gpio_function_t)"),
0x10000f88: "uart_init", 0x10000ef4: "time_us_64", 0x10000d10: ("sleep_ms", "void sleep_ms(uint32_t)"),
0x10003558: "strlen", 0x10002e74: ("exit", "void exit(int)"),
0x10002e7c: ("runtime_init", "void runtime_init(void)"),
0x100030cc: ("stdio_init_all", "bool stdio_init_all(void)"),
0x10003418: ("stdio_uart_init", "void stdio_uart_init(void)"),
0x100030a4: ("stdio_set_driver_enabled", "void stdio_set_driver_enabled(stdio_driver_t*, bool)"),
0x10002ea8: ("stdio_out_chars_crlf", "void stdio_out_chars_crlf(stdio_driver_t*, const char*, int)"),
0x10002fb8: ("stdio_put_string", "int stdio_put_string(const char*, int, bool, bool)"),
0x1000325c: ("__wrap_printf", "int __wrap_printf(const char*, ...)"),
0x10003198: ("__wrap_vprintf", "int __wrap_vprintf(const char*, va_list)"),
0x10000f88: ("uart_init", "uint uart_init(uart_inst_t*, uint)"),
0x10000ef4: ("time_us_64", "uint64_t time_us_64(void)"),
0x10003558: ("strlen", "size_t strlen(const char*)"),
} }
for addr, name in symbols.items(): for addr, (name, sig) in funcs.items():
bv.define_user_symbol(Symbol(SymbolType.FunctionSymbol, addr, name)) bv.define_user_symbol(Symbol(SymbolType.FunctionSymbol, addr, name))
if sig:
f = bv.get_function_at(addr)
if f is not None:
f.set_user_type(sig)
``` ```
The decompiler now shows `main` initializing GPIO 16 and looping. We make two changes: The decompiler now shows `main` initializing GPIO 16 and looping. We make two changes:
Binary file not shown.