ARM64平台下如何提取C语言当前函数的调用方参数?
ARM64平台下栈回溯函数的参数提取实现方案
核心思路
基于DWARF调试信息与ARM64调用帧规则,通过解析函数参数的位置描述,结合当前寄存器与栈帧上下文,读取参数值。该方案完全适配-g编译的不同优化级别(O0/O2)。
具体实现步骤
1. 关联栈帧与DWARF函数调试信息
每个栈帧对应一个函数的执行上下文,首先通过栈帧的PC值定位到对应的DWARF编译单元(CU)和函数调试信息条目(DIE):
- 遍历
.debug_info段中的所有CU,找到包含当前PC地址范围的CU; - 在该CU中查找标记为
DW_TAG_subprogram的DIE,匹配PC对应的函数。
2. 提取函数参数的DWARF元信息
从函数DIE的子条目里筛选出DW_TAG_formal_parameter类型的DIE,每个条目对应一个函数参数:
- 读取
DW_AT_name属性,获取参数名称; - 读取
DW_AT_location属性,获取参数的存储位置描述(可能是直接的DW_OP指令序列,或者指向.debug_loc段的偏移); - 读取
DW_AT_type属性,获取参数的类型DIE,用于后续值的解析(比如区分int、double、指针等)。
3. 计算调用帧基址(CFA)
参数的栈位置通常基于调用帧地址(CFA)计算,需要从.eh_frame或.debug_frame段的调用帧信息(CFI)中获取计算规则:
- 根据当前PC找到对应的CFI条目;
- 解析CFI中的
DW_CFA_def_cfa指令,ARM64下常见规则为CFA = x29 + offset(x29为帧指针FP); - 结合当前栈帧的x29、SP寄存器值,计算出CFA的实际地址。
4. 解析参数位置并读取值
根据DW_AT_location的内容,分情况处理:
- 寄存器存储:如果位置是
DW_OP_regx指令(比如DW_OP_reg0对应x0寄存器),直接读取对应寄存器的值; - 栈存储:如果是
DW_OP_fbreg指令,提取偏移值,用CFA加上偏移得到参数的内存地址,读取对应长度的数据; - 位置列表:如果
DW_AT_location指向.debug_loc段,需根据当前PC找到列表中对应的位置条目,再按上述方式读取值。
5. 适配不同编译优化级别
- O0级别:参数通常会同时存在寄存器和栈帧的参数备份区,DWARF会记录栈上的位置,直接读取即可;
- O2级别:参数可能保留在寄存器中,或被优化复用,但
-g编译会保留足够的DWARF信息追踪参数的实际存储位置,严格按DWARF的位置描述读取即可。
关键代码示例
// 简化的参数信息结构体 typedef struct { const char* name; // DWARF位置指令序列的起始地址 const uint8_t* loc_ops; // 参数类型对应的DIE Dwarf_Die type_die; } ParamInfo; // 从函数DIE提取参数列表 void collect_function_params(Dwarf_Die func_die, ParamInfo* params, size_t* count) { *count = 0; Dwarf_Die child_die; if (dwarf_child(func_die, &child_die) != 0) return; do { if (dwarf_tag(&child_die) == DW_TAG_formal_parameter) { params[*count].name = dwarf_attr_string(&child_die, DW_AT_name); params[*count].loc_ops = dwarf_attr_location(&child_die, DW_AT_location); params[*count].type_die = *dwarf_attr_die(&child_die, DW_AT_type); (*count)++; } } while (dwarf_siblingof(&child_die, &child_die) == 0); } // 解析位置并读取参数值(以64位整数/指针为例) uint64_t read_param_value(const uint8_t* loc_ops, uint64_t cfa, uint64_t regs[31]) { uint8_t op = loc_ops[0]; switch (op) { case DW_OP_reg0 ... DW_OP_reg30: { uint32_t reg_idx = op - DW_OP_reg0; return regs[reg_idx]; } case DW_OP_fbreg: { int32_t offset = *(int32_t*)(loc_ops + 1); return *(uint64_t*)(cfa + offset); } // 扩展处理其他DW_OP指令(如DW_OP_plus_uconst等) default: return 0; } }
可行性说明
该方案完全可行,GDB等调试工具的info args功能正是基于DWARF调试信息与CFI调用帧规则实现的。只要确保正确解析.debug_info、.debug_loc、.eh_frame等段的内容,结合ARM64的寄存器与栈帧上下文,就能在不同编译优化级别下准确提取函数参数值。
内容的提问来源于stack exchange,提问作者Jordan
相关产品推荐
相关产品推荐

