# lnxall_trace 使用说明

> 版本 **3.0.1**（`main.c:TRACE_VERSION`）
> 适用平台：`aarch64`(LP64) / `arm32`(ILP32, Thumb-2) / `x86_64`(LP64)
> 二进制：`tools/trace/lnxall_trace`

---

## 1. 工具定位

`lnxall_trace` 是一个**非阻塞**的进程调试/跟踪工具（基于 `ptrace`），用于在**目标板**上对**正在运行**的进程（如 `proto_forward`）做在线排障：

| 能力 | 说明 |
|---|---|
| 读数据 | 全局变量、结构体、嵌套成员/数组元素、任意内存 dump |
| 写数据 | 写变量/结构体成员（int/float/double 自动识别）、按模式填充内存 |
| 调用函数 | 在目标进程内**同步**注入函数调用并取回返回值（gdb `call` 语义） |
| 看类型 | 结构体布局（成员偏移/大小）、符号地址/大小/类型（需 DWARF 或 ELF 符号） |
| 动态库符号 | `loadso` 加载 `.so` 符号表（配合 `patch` / `call`） |
| 热补丁 | 把对 `old_func` 的调用重定向到 `new_func`（入口改写跳转），可一键还原 |
| 线程排障 | 查看线程注册表 vs `/proc/<pid>/task` 实际状态，手动放行卡住的线程 |

**非阻塞**含义：attach 使用 `PTRACE_SEIZE`，**不会暂停目标进程**；只有执行 `call`/`d`/`m` 等命令时会短暂停止相关线程，命令结束自动恢复（`call` 例外，见第 6.4 节）。

---

## 2. 编译与部署

### 2.1 编译

集成构建（推荐）：由 `src/Makefile` 检测到 `../tools/trace` 存在时自动委托子 make，并传入与 `proto_forward` **完全一致**的交叉编译器：

```make
# src/Makefile
TRACE_DIR := $(wildcard ../tools/trace)
$(MAKE) -C ../tools/trace CC=$(CC)
```

单独构建：

```bash
cd tools/trace
make CC=aarch64-linux-gnu-gcc          # 必须传与目标板一致的交叉编译器
```

> ⚠️ 不传 `CC` 时会回退到主机编译器（`cc`），产物**无法在目标板运行**。
> `make all` 是 `.PHONY`，每次都会重新编译（避免切换工具链后沿用旧产物）。

### 2.2 部署

`src/install.sh` 会把产物一起安装到应用目录：

```bash
cp ../tools/trace/lnxall_trace "$PF_INSTPFX/bin" -rf     # src/install.sh
```

即板上通常位于 `$PF_INSTPFX/bin/lnxall_trace`（安装前缀见 `PF_INSTPFX`，一般形如 `/opt/lnxall_app`）。

### 2.3 目标侧配合（可选但推荐）

`call` 命令需要一个"死代码槽位"来放置返回断点。目标程序若包含下面的桩函数，`call` 会**优先**使用它（最可靠）：

```c
/* src/system/trace/src/trace_debug.c */
//空函数，在text段，用于占位
int trace_dbg_nop_stub(void)
{
    __asm__ __volatile__ ("nop\n" ... :::"memory");
    return 0;
}
```

若目标没有该符号，`call` 会自动**回退扫描目标 `.text` 段的 NOP 对齐填充区**寻找槽位（见 `cmd_call.c` 顶部注释）。

### 2.4 调试信息（重要）

| 目标二进制情况 | 可用能力 |
|---|---|
| 带 ELF 符号（`.symtab`/`.dynsym`） | 变量/函数按名字访问、地址反查、内存读写、`call` |
| 带 DWARF（编译加 `-g`）且工具以 `-f` 启动 | 额外支持**结构体布局**、`ptype`、按真实类型显示值（区分 int/float/char/指针） |
| stripped（无符号） | 只能按**地址**操作（`d <addr>,<len>` / `m <addr>,<len>=...`） |

---

## 3. 启动与权限

### 3.1 启动方式

```bash
lnxall_trace                     # 交互模式，进入后手动 attach <PID>
lnxall_trace <PID>               # 启动即自动 attach
lnxall_trace <PID> -f -p -t 600  # 完整模式 + 完整输出 + 空闲 600s 退出
```

### 3.2 命令行参数

| 参数 | 含义 |
|---|---|
| `<PID>` | 首个非选项数字参数：启动后自动 attach（可选） |
| `-f` / `--full` / `--dwarf` | **完整模式**：解析 DWARF，支持结构体布局 / `ptype`（内存占用按目标实际用量，几十 MB 级） |
| `-l` / `--lite` / `--no-dwarf` | **精简模式（默认）**：不解析 DWARF，省内存、attach 快；仅 ELF 符号 + 内存 + `call` + 线程等功能 |
| `-p` / `--print-full` / `--detail` | 启动即**完整输出**（横幅/attach 汇总/诊断/地址与偏移等辅助信息）；运行期可用 `print full` 切换 |
| `-t <sec>` / `--timeout <sec>` | 空闲自动退出时限，`0` 禁用；默认 **300s** |

> 选项可放在 PID 前后；同时给 `-f` 与 `-l` 时**以最后出现的为准**。

### 3.3 权限要求

需要 **root** 或与目标同用户且有 ptrace 权限。若出现 `PTRACE_SEIZE: Operation not permitted`，按提示处理：

```bash
echo 0 > /proc/sys/kernel/yama/ptrace_scope     # 或直接以 root 运行
```

### 3.4 空闲自动退出与 Ctrl-C

| 行为 | 说明 |
|---|---|
| 空闲自动退出 | 默认 **300 秒**无输入即自动 detach 并退出；`-t <sec>` 或 REPL 命令 `timeout <sec>` 调整，`timeout off`/`0` 禁用 |
| `Ctrl-C`（REPL 空闲） | 取消当前输入行，重新显示提示符（不退出） |
| `Ctrl-C`（`call` 进行中） | 中断目标并把目标**停在被调函数帧内**（不回退 PC）；随后用 `c/continue` 放行时会提示"可能返回到已恢复的代码" |
| 退出方式 | `detach` / `quit` / `exit` / `q`，或 `Ctrl-D`(EOF)；退出前自动还原热补丁、释放出参副本 |

> 提示符：未 attach 为 `[ptrace] >`；已 attach 为 `[lnxall:<PID>] >`；若上次 `call` 后目标仍停着，会显示 `[lnxall:<PID>](stopped) >`。

---

## 4. 三种"档位"（互不影响，可组合）

| 档位 | 开关 | 默认 | 作用 |
|---|---|---|---|
| DWARF | 启动参数 `-f` / `-l` | **lite** | 是否解析类型信息（决定结构体布局/`ptype` 可用性）。运行期**不可切换**（`mode` 可查询） |
| 输出详细度 | `print brief|full`（或启动 `-p`） | **brief** | brief 只打结果；full 追加地址/大小/偏移/能力统计 |
| 调试日志 | `verbose on|off` | off | 打开后向 stderr 打印 `[DBG]` 诊断行 |

---

## 5. 命令速查表

| 命令 | 语法 | 用途 |
|---|---|---|
| attach | `attach <PID>` | 挂接目标进程（非阻塞） |
| detach | `detach` \| `quit` \| `exit` \| `q` | 断开并退出（自动还原补丁） |
| 读变量 | `d <var>` | 读全局变量/结构体/数组（支持 `a.b.c`、`arr[2].x`） |
| 读成员 | `d <struct>.<field>` | 读结构体成员 |
| dump 内存 | `d <addr>,<len>` | 以 16 字节/行打印内存（地址 + hex + ASCII） |
| 符号信息 | `d &<var>` | 显示符号地址/大小/类型 |
| 函数地址 | `d <func_name>` | 显示函数入口地址 |
| 按类型打印 | `d <struct_name> <addr>` | 按结构体布局打印 `addr` 处内存 |
| 地址反查 | `d <addr>` 或裸 `<addr>` | 反查覆盖该地址的符号（结构体则按类型打印） |
| 写变量 | `m <var>=<value>` | 写变量（int/float/double 自动识别） |
| 写成员 | `m <struct>.<field>=<value>` | 写结构体成员（支持嵌套/数组下标） |
| 写内存 | `m <addr>,<len>=<hex>` | hex 字节串**循环填充** `<len>` 字节 |
| 调用函数 | `call <func>(args)` 或 `<func>(args)` | 同步调用目标函数（关键字 `call` 可省） |
| 建出参 | `<type> <name> = <val>` / `struct <type> <name> = 0` | 创建出参 scratch 符号（配合 `call ...(&name)`） |
| 热补丁 | `patch <old> <new>` / `patch`（裸命令列出站点） | 调用重定向 |
| 还原补丁 | `unpatch` | 恢复全部原始指令 |
| 类型布局 | `ptype <struct>` / `ptype`（裸命令列全部） | 结构体成员偏移与大小 |
| 加载 so | `loadso <so-path>` / `unloadso <so-path>` | 加载/卸载共享库符号表 |
| 线程排障 | `threads` | 注册表 vs `/proc/<pid>/task` 实际状态 |
| 放行线程 | `resume <tid>` | 手动 CONT 卡住的线程 |
| 会话控制 | `print [brief\|full]`、`verbose on\|off`、`mode`、`timeout [<sec>]`、`help` | 档位/状态查询与设置 |

---

## 6. 命令详解

### 6.1 `attach` / `detach`

- `attach <PID>`：先 `kill(pid,0)` 校验存在性，再 `PTRACE_SEIZE`（**不停目标**），并开启 `PTRACE_O_TRACEFORK | TRACEVFORK | TRACECLONE | TRACEEXEC` —— 目标后续 fork/clone 的子线程/子进程也会被接管（自动登记，避免新线程卡死）。
 随后解析：可执行路径 → 加载基址（PIE/non-PIE）→ ELF 符号表 → （`-f` 时）DWARF 类型；按需做地址重定位。
- 已有 attach 时再 `attach` 会报 `Already attached`，须先 `detach`。
- 符号表加载失败（stripped / 老旧 32 位 ELF）只**告警**：跳过地址调整与 DWARF 解析，其余功能仍可用。
- `detach`：先还原所有热补丁，再停止/detach 子进程、兄弟线程与主目标，释放符号表、DWARF 解析结果与出参副本。

### 6.2 `d` —— 读取/显示

六种形态（`main.c` 分发 + `cmd_d.c` 实现）：

```text
d <var>                 读变量 / 结构体 / 数组（支持 'a.b.c'、'a[2].x'）
d <struct>.<field>      读结构体成员（同上，强调成员形式）
d <addr>,<len>          dump 内存：16 字节/行，含地址 + hex + ASCII（单次 ≤ 4096 字节）
d &<var>                显示符号地址 / 大小 / 类型
d <func_name>           显示函数入口地址
d <struct_name> <addr>  按结构体布局打印 addr 处内存（类型名可省略）
d <addr>                地址反查：找到覆盖该地址的符号并按类型打印
<addr>                  等价于 d <addr>（裸地址，支持 0x… 或十进制）
```

**值显示规则**（`cmd_d.c`）：有 DWARF 时按真实基础类型唯一解释（signed/unsigned/float/char/bool/pointer）；**精简模式无类型信息时按"整数 + 16 进制"显示**，不再猜测浮点。
数组/长结构体在 brief 模式下会**截断显示**并省略提示，`print full` 才补充完整信息。

示例：

```text
[lnxall:1234] > d microgrid_ctrl.choose_func
[lnxall:1234] > d microgrid_ctrl                 # 整个结构体（需 -f）
[lnxall:1234] > d &microgrid_ctrl                # 地址/大小/类型
[lnxall:1234] > d dev_get_dev_tag_int            # 函数地址
[lnxall:1234] > d 0x7f8a2c0010,64                # dump 64 字节
[lnxall:1234] > 0x7f8a2c0010                     # 反查该地址属于哪个符号
```

### 6.3 `m` —— 修改

```text
m <var>=<value>            写变量（int / float / double 自动识别，0x… 亦可）
m <struct>.<field>=<value> 写结构体成员（支持嵌套与数组下标）
m <addr>,<len>=<hex>       写内存：hex 字节串循环填充满 <len> 字节
```

- 例：`m microgrid_ctrl.choose_func=3`、`m 0x7f8a2c0010,64=0x00`（清零 64 字节）。
- 写入回显：brief 仅数值，`print full` 追加 16 进制。
- 单次长度上限 4096 字节（`MAX_MEM_OPS_SIZE`）。

### 6.4 `call` —— 同步函数调用（核心功能）

```text
call <func>(arg1, arg2, ...)      # 'call' 关键字可省略，直接写 func(...)
```

**执行流程**（gdb infcall 语义，`cmd_call.c`）：

1. 解析函数名与实参；
2. 停止目标全部线程（含兄弟线程）；
3. 在目标 `.text` 的**死代码槽位**写入返回断点：优先使用 `trace_dbg_nop_stub` 桩函数内槽位，否则扫描 `.text` NOP 对齐填充区；每轮 `call` 重新计算，不缓存；
4. 改写 PC/SP/LR 与参数寄存器后放行；
5. **同步等待**返回断点命中，收集返回值，恢复调用前寄存器快照；
6. 命令结束时**自动 resume** 目标（无需手动 `c`）。

**参数类型**（`arg_type_t`）：

| 形式 | 含义 | 示例 |
|---|---|---|
| 整型/浮点字面量 | 直接按数值传参，支持 `0x` | `call f(10, 0x40, 3.5)` |
| 字符/字符串 | 双引号字符串（未闭合引号会报错） | `call f("DEV_NO_EMS", "MAX_DEMAND")` |
| `&符号` | 传**符号地址**（配合出参使用） | `call f(&value)` |
| 裸符号名 | 传该符号的**当前值** | `call f(g_threshold)` |

**限制**：

| 项 | aarch64 | arm32 (Thumb-2) | x86_64 |
|---|---|---|---|
| 参数上限 `MAX_FUNC_ARGS` | **8**（x0–x7） | **8**（r0–r3 + 4 个栈参数） | **6**（rdi,rsi,rdx,rcx,r8,r9） |
| 返回值 | 整数返回寄存器（x0） | r0 | rax |

- 超限会**显式报错** `too many arguments (max N ...)`，不会静默丢弃。
- 返回值以整数形式打印：`> returned: <十进制> (0x<十六进制>)`；**浮点返回（如 d0）不在采集范围**，如需浮点结果请改用出参指针。
- **无内建超时**：调用会一直等到函数返回、目标崩溃/退出，或用户 `Ctrl-C`（此时目标停在被调函数帧内）。
- 失败提示包括：函数未找到/不是函数、断点写入失败、目标被信号杀死、目标退出、非预期 SIGTRAP、缺 NOP 槽位等。

**出参（out-param）构造**：创建一个调试器侧的 scratch 符号，`call` 时把它暂存到目标栈上，返回后再**回读**到调试器副本：

```text
[lnxall:1234] > double value = 0          # 基础类型 scratch
[lnxall:1234] > struct node_t node = 0    # 结构体 scratch（需 -f）
[lnxall:1234] > call some_func(&value, &node)
[lnxall:1234] > d value                   # 查看回填结果
```

上限 `MAX_LOCAL_SYMS = 256` 个；重复名字会报错；detach 时全部释放。

### 6.5 `patch` / `unpatch` —— 热补丁

```text
patch <old_func> <new_func>     # 之后对 old_func 的调用实际执行 new_func
patch                           # 裸命令：列出当前生效的补丁站点
unpatch                         # 还原全部原始指令
```

- 原理：改写 `old_func` **入口指令**为跳转到 `new_func`（`patch_enc_*` 为纯编码函数，可离线/交叉校验）。
- 各架构的跳转范围与回退策略：

| 架构 | 首选编码 | 超范围回退 |
|---|---|---|
| aarch64 | 4B `B`（±128MB） | 在 ±16MB 内找 4 字节 NOP 空洞做**两跳**（全程直接跳转，BTI 安全） |
| arm32 (ARM 模式) | 4B `B`（±32MB） | 8 字节字面量桩（保留 bit0 以便切到 Thumb 目标） |
| arm32 (Thumb) | 4B `B.W`（±16MB） | **报错**（Thumb 字面量桩受限） |
| x86_64 | 5B `jmp rel32` | — |

- **`.so` 内的函数需先 `loadso <so-path>`**，否则按名字找不到符号。
- `detach` 前会**自动还原**全部补丁，避免把"入口被改过"的进程留给下一个调试器。

### 6.6 `ptype` / `loadso` / `unloadso`

```text
ptype                    # 列出所有已知结构体（名字 / 大小 / 成员数）
ptype <struct_name>      # 显示成员布局：+偏移、成员名、大小
loadso <so-path>         # 加载共享库符号表（先在 /proc/<pid>/maps 中确认已加载并取基址）
unloadso <so-path>       # 卸载该 so 的全部符号（支持路径或 basename 匹配，如 libdev.so）
```

- `ptype` **需要 DWARF**：lite 模式下会提示需以 `-f` 启动。
- `loadso` 上限 `MAX_SO_LOADS = 32` 条记录；so 符号表容量与主程序相同（8192）。
- 若 `loadso` 报 `not found in /proc/<pid>/maps`，说明目标尚未加载该库。

### 6.7 `threads` / `resume <tid>` —— 线程排障

`threads` 打印两部分：

1. **调试器侧注册表**：`thread_list`（为 `call` 冻结的兄弟线程）、`new_threads`（调用期间新建的线程）、`children`（跟踪的子进程）；
2. **实际线程**：遍历 `/proc/<pid>/task`，逐条给出 `State | TracerPid | 分类`，分类为 `leader` / `thread_list(frozen)` / `new` / `untracked`。

若发现线程长期停留在 `State=t`（被 ptrace 停住未放行），用 `resume <tid>` 手动 `CONT` 兜底。

> 本工具已在 REPL 层做了双保险（SIGCHLD self-pipe 通知 + 200ms `wait` 队列轮询兜底），正常情况下不会出现线程滞留；`threads`/`resume` 用于极端场景排查。

### 6.8 会话控制

```text
print [brief|full]      # 裸命令显示当前档位；切换输出详细度
verbose [on|off]        # 裸命令显示当前状态；开启后 stderr 打印 [DBG] 行
mode                    # 显示当前 DWARF 模式（运行期不可切换）
timeout [<sec>|off]     # 裸命令显示当前时限；设置空闲自动退出（0/off 禁用）
help                    # 打印命令帮助
quit / exit / q / detach
```

> 用户输入的每条命令都会写入 syslog（`user command: <原文>`，标识 `ptrace-deubgger`），便于事后审计。

---

## 7. 典型使用流程

### 7.1 在线查看 EMS 微网策略状态

```bash
# 板上，root
lnxall_trace -f -p $(pidof proto_forward)

[lnxall:1234] > d microgrid_ctrl.choose_func          # 当前策略类型
[lnxall:1234] > d microgrid_ctrl                       # 整个控制结构体
[lnxall:1234] > d microgrid_data.SOC_floor             # 多柜 SOC 最小值
[lnxall:1234] > ptype cabinet_inside_t                 # 结构体布局（若名字存在）
[lnxall:1234] > d 0x7f8a2c0010,64                      # 按地址 dump 一段内存
```

### 7.2 在线改参数并观察

```text
[lnxall:1234] > m microgrid_ctrl.choose_func=3         # 切换策略
[lnxall:1234] > m microgrid_ctrl.EnPvPrectrl=1         # 打开某开关
[lnxall:1234] > m 0x7f8a2c0010,8=0x0000000000000000   # 按地址清零
```

### 7.3 调用框架自测接口（同步执行、自动恢复）

```text
[lnxall:1234] > call frm_test_comp(0, 10000, 64, 0)    # 时延测试：10k 次往返
  > returned: 0 (0x0)
```

### 7.4 带出参取结果

```text
[lnxall:1234] > int v = 0
[lnxall:1234] > call dev_get_dev_tag_int("DEV_NO_EMS", "MAX_DEMAND")
  > returned: 500 (0x1f4)
[lnxall:1234] > d v
```

### 7.5 临时替换函数行为（热补丁）

```text
[lnxall:1234] > loadso /opt/lnxall_app/lib/libdev.so   # 若是 .so 内函数
[lnxall:1234] > patch calc_old calc_new                # 调用重定向
[lnxall:1234] > patch                                  # 查看生效站点
[lnxall:1234] > unpatch                                # 恢复
```

---

## 8. 限制与注意事项

| 类别 | 限制 |
|---|---|
| DWARF | 默认**关闭**（lite）；结构体布局 / `ptype` / 精确类型显示需 `-f` 且目标带 `-g` |
| 结构体 | 最多 `MAX_STRUCT_FIELDS=128` 个成员；结构体表按需扩容，硬上限 65536 |
| `call` | 无超时；仅支持寄存器传参（aarch64=8 / arm32=4+4 / x86=6）；浮点返回不采集；需目标存在可用 NOP 槽位 |
| `call` 线程 | 调用期间目标全部线程被停止（会短暂影响实时性）；被调函数须线程安全、不可依赖定时/信号上下文 |
| 符号 | 主程序 8192 个符号；so 符号 8192；`loadso` 记录 32 条；出参 scratch 256 个 |
| 内存操作 | 单次 `d/m <addr>,<len>` ≤ **4096** 字节；dump 每行 16 字节 |
| 线程/子进程 | 跟踪表上限 256 线程、256 子进程 |
| 热补丁 | 受跳转距离限制；Thumb 超范围直接报错；`.so` 函数须先 `loadso` |
| 命令行 | 单行 ≤ 1024 字符；符号名 ≤ 256 字符；字符串实参 ≤ 512 字节 |
| 安全 | 用户命令写入 syslog；`patch`/`m` 属写操作，务必在可控环境下使用 |
| 退出 | 崩溃/被杀不会损坏目标（命令结束即恢复）；但 `Ctrl-C` 中断 `call` 后目标停在被调函数帧内，需 `c` 放行并注意其可能返回到已恢复代码 |

---

## 9. 常见问题（FAQ）

| 现象 | 原因 / 处理 |
|---|---|
| `PTRACE_SEIZE: Operation not permitted` | 权限不足：以 root 运行，或 `echo 0 > /proc/sys/kernel/yama/ptrace_scope` |
| `Warning: symbol table not loaded` | 目标被 strip 或 32 位 ELF 兼容问题；改用地址操作（`d <addr>,<len>`） |
| `ptype` 报"needs DWARF / disabled in lite mode" | 以 `-f` 重启工具；并确认目标二进制带 `-g` |
| `Error: Function 'xxx' not found` | 目标无该符号：确认非 static/未被 strip；`.so` 函数先 `loadso` |
| `Error: too many arguments` | 超过本架构参数上限（见 6.4 表格） |
| `call` 长时间无输出 | 该调用**无超时**，被调函数可能阻塞/死锁：按 `Ctrl-C` 中断，再用 `c` 放行 |
| 提示符出现 `(stopped)` | 上次 `call` 后目标仍停着：`c`/`continue` 放行 |
| 线程 `State=t` 长期滞留 | `threads` 查看，`resume <tid>` 手动放行（极端场景兜底） |
| 工具无输入自动退出 | 空闲超时（默认 300s）：启动加 `-t 0`，或 REPL 执行 `timeout off` |
| `attach` 后目标行为异常 | 确认未在 `call` 期间依赖线程实时性；长时间排障后及时 `detach` |

---

## 10. 源码结构（开发者参考）

| 文件 | 职责 |
|---|---|
| `main.c` | 入口、命令行解析、REPL 分发、通用工具函数、`TRACE_VERSION` |
| `cmd_attach.c` | `attach` / `detach`（PTRACE_SEIZE、符号与 DWARF 装载、资源释放） |
| `cmd_d.c` | `d` 读取/显示、结构体与数组格式化、字段路径解析 |
| `cmd_m.c` | `m` 写入（变量/成员/内存） |
| `cmd_call.c` | `call` 注入调用、返回断点槽位查找、出参暂存与回读 |
| `cmd_patch.c` | 热补丁（各架构指令编码与跳转范围回退） |
| `cmd_struct.c` | `ptype`、出参 scratch 符号创建 |
| `cmd_so.c` | `loadso` / `unloadso`（`/proc/<pid>/maps` 取基址） |
| `cmd_symbol.c` | 符号查询（`d &name`、按地址反查） |
| `thread_ops.c` | 线程停止/恢复、SIGCHLD self-pipe、事件处理、`threads`/`resume` |
| `mem_rw.c` | 进程内存读写：`PTRACE_PEEKDATA/POKEDATA` 按机器字对齐逐字操作；`read_any_mem` 对调试器侧出参堆副本透明直读 |
| `symbol_parse.c` | ELF 符号表/路径/加载基址解析（32/64 位通用） |
| `dwarf_parse.c` | DWARF4/5 类型解析（结构体、数组、typedef 链、基础类型编码） |
| `arch_port.c/.h` | 架构移植层：寄存器访问、调用约定、断点指令、跳转编码特征 |
| `Makefile` | 构建（`CC` 由调用方注入；`all` 无条件重编） |

**目标侧**：`src/system/trace/src/trace_debug.c` 提供 `trace_dbg_nop_stub()`（`call` 的返回断点槽位）与调试用标准输出重定向辅助接口。

**版本维护**：只需递增 `main.c` 中的 `TRACE_VERSION` 第三段（`3.0.<patch>`）。
