调试 NuttX/Vela 这类嵌入式系统时,光会 bt 和 print 远远不够。真正能让你在陌生代码里快速定位、看清结构、批量布点的,是 GDB 的符号检视命令。本文整理三件最常用的:
ptype------ 看类型长什么样info variables------ 找全局/静态变量在哪info functions------ 找函数在哪
它们都不打印运行时值,而是从符号表里捞元数据,速度快、不依赖程序运行状态,特别适合 coredump 离线分析。
一、ptype ------ 查看类型定义
ptype(print type)只打印类型的结构,不打印变量的值。
常见用法
| 命令 | 作用 |
|---|---|
ptype <var> |
查看变量的类型定义 |
ptype <type> |
直接查看某个类型(struct/union/enum/typedef) |
ptype/o <struct> |
显示 offset 和 size(调试内存布局神器) |
ptype/m <class> |
只显示成员,不展开继承(C++) |
ptype <expr> |
查看任意表达式的类型,如 ptype foo->bar[2].baz |
它能干什么
-
看 struct 字段布局
(gdb) ptype/o struct task_struct /* offset | size */ type = struct task_struct { /* 0 | 8 */ long state; /* 8 | 8 */ void *stack; ...排查内存对齐、padding、字段偏移问题时,比
offsetof直观得多。 -
typedef 穿透 :
ptype size_t告诉你它实际是unsigned long,避免猜类型。 -
enum 取值 :
ptype enum state_e一次性列出所有枚举值。 -
函数签名 :
ptype my_func显示返回值和参数类型,特别适合调试函数指针。 -
C++ 类层次 :
ptype完整展开(含虚函数表、继承),ptype/m只看自身成员。 -
判断指针 vs 数组 :源码里
int a[10]和int *a看着像,ptype一眼分清。
与近邻命令对比
print x/p x→ 看值ptype x→ 看类型whatis x→ 类型简版(不展开 struct 成员,一行输出)info types <regex>→ 在符号表里搜类型名
排查 crash 看 coredump 时,ptype/o 配合 p *(struct foo *)0xaddr 是定位「字段偏移踩错 / 结构体版本不一致」的常用组合。
二、info variables ------ 查找全局/静态变量
info variables 从符号表 里捞全局和静态变量,不打印值,只告诉你叫什么、在哪、什么类型。
基本用法
| 命令 | 作用 |
|---|---|
info variables |
列出所有全局和静态变量(通常很长) |
info variables <regex> |
用正则过滤,最常用 |
info variables -n |
只看非调试符号(minimal symbols) |
典型场景
-
找变量定义在哪
(gdb) info variables ^g_task_list$ File kernel/sched/task.c: 42: struct list_head g_task_list;直接告诉你文件:行号 + 类型,比翻代码快。
-
模糊搜索 :忘了变量全名,
info variables uart.*config一搜就出。 -
看驱动注册了哪些静态结构 :
info variables _ops$找所有以_ops结尾的(file_operations、uart_ops之类)。 -
核对链接产物 :某个
static变量是否真的进了最终镜像。
与近邻命令对比
| 命令 | 看什么 |
|---|---|
info variables <re> |
全局/静态变量符号 |
info functions <re> |
函数符号 |
info types <re> |
类型名(struct/typedef/enum) |
info address <sym> |
单个符号的地址 |
info symbol <addr> |
反查:这个地址属于哪个符号 |
print &var |
拿到变量地址(要先知道名字) |
ptype var |
看类型定义 |
⚠️ 局部变量不会 出现在 info variables 里 ------ 那是栈上的,要用 info locals / info args 看当前帧。
三、info functions ------ 查找函数符号
info functions 与 info variables 是一对,专门搜函数。
基本用法
| 命令 | 作用 |
|---|---|
info functions |
列出所有函数(通常爆屏,慎用) |
info functions <regex> |
正则过滤,最常用 |
info functions -n |
只看非调试符号 |
info functions -q <re> |
quiet 模式,不打印文件分组头 |
info functions -t <type> <re> |
按返回类型过滤(较新 GDB) |
典型场景
-
找函数定义在哪
(gdb) info functions ^uart_open$ File drivers/serial/uart.c: 128: int uart_open(struct file *filep);直接给文件:行号 + 完整签名。
-
模糊搜索 :
info functions .*_init$列出所有_init结尾的初始化函数。 -
看某模块对外接口 :
info functions ^uart_列出 uart 子系统所有函数。 -
C++ 重载/模板:所有重载版本一并列出,签名各异,方便挑你要打断点的那个。
-
静态函数也能找到(只要带调试符号),不像 grep 还要管作用域。
调试组合拳
(gdb) info functions probe # 模糊找入口
(gdb) b *uart_probe # 在函数入口下断点
(gdb) info address uart_probe # 拿函数地址(用于函数指针比对)
(gdb) disas uart_probe # 反汇编
(gdb) ptype uart_probe # 看签名
(gdb) rbreak ^uart_ # 给所有 uart_ 开头函数下断点(批量)
与近邻命令对比
| 命令 | 用途 |
|---|---|
info functions <re> |
列出匹配的函数符号 |
rbreak <re> |
给匹配的函数批量下断点(破坏性,会真下断) |
disas <func> |
反汇编整个函数体 |
info line <func> |
看函数起始行号 → 地址映射 |
ptype <func> |
看函数类型签名(返回值/参数) |
四、三件套联动:crash 离线分析的标准流程
假设 coredump 里看到一个陌生符号 uart_priv 出现在栈上:
(gdb) info variables ^uart_priv$ # 1. 在哪定义?
File drivers/serial/uart.c:
89: static struct uart_priv_s uart_priv;
(gdb) ptype uart_priv # 2. 类型长啥样?
type = struct uart_priv_s {
int state;
struct circbuf rx;
...
}
(gdb) ptype/o struct uart_priv_s # 3. 字段偏移确认
/* offset | size */ ...
(gdb) p uart_priv # 4. 当前值是什么
$1 = {state = 2, rx = {...}, ...}
(gdb) info address uart_priv # 5. 物理地址(对比 dump)
Symbol "uart_priv" is static at address 0x20001234.
再配合 info functions ^uart_ 把整套接口列出来,整个驱动的轮廓 5 分钟内就能摸清。
五、要点回顾
| 你想知道... | 用什么 |
|---|---|
| 这个类型长什么样?字段偏移多少? | ptype / ptype/o |
| 这个全局变量定义在哪个文件? | info variables <regex> |
| 这个函数定义在哪?签名是什么? | info functions <regex> |
| 这个地址是哪个符号? | info symbol <addr> |
| 这个符号的地址是多少? | info address <sym> |
三件套的共性:只读符号表,不依赖运行时。哪怕程序根本没跑起来,只要 ELF 带调试信息,就能用。这是它们在 coredump 分析里特别趁手的原因。
References
- GDB 官方手册 · Symbol Tables
- GDB 官方手册 · Examining Data ---
ptype