3大突破!Rhino.Inside.Revit如何解决BIM参数化设计痛点
这个命令用于查看ELF格式的可执行文件/库的符号表信息。
让我详细解释命令解析nm -S -n vela.elf各选项含义nm- GNU二进制文件工具用于显示目标文件的符号表-S- 显示符号大小size-n- 按地址/值排序数字顺序vela.elf- 目标ELF文件通常是嵌入式或Linux可执行文件输出示例0000000000401000 000000000000002a T _start 0000000000401030 0000000000000015 T main 0000000000401048 0000000000000032 T function1各列含义第一列符号地址16进制第二列符号大小字节-S选项显示第三列符号类型第四列符号名称常见符号类型类型含义T/t代码段中的函数/文本符号D/d已初始化数据段B/b未初始化数据段BSSR/r只读数据段U未定义符号需要链接A绝对地址符号实际应用场景固件分析- 查看嵌入式系统符号布局调试- 定位函数地址用于调试大小分析- 查看函数/变量占用空间逆向工程- 理解程序结构相关有用命令# 更详细的符号信息nm -S -n --size-sort vela.elf# 显示动态符号nm -D vela.elf# 显示所有符号包括调试符号nm -a vela.elf# 配合objdump查看反汇编objdump -d vela.elf|grepfunction_address这个命令在嵌入式开发、内核调试和逆向工程中非常有用。
nm显示的符号主要包含全局符号不包括局部变量和堆变量。
让我详细解释包含的符号会出现在nm输出中
全局函数// 会出现在nm输出voidglobal_function(){...}
全局变量// 会出现在nm输出intglobal_var10;staticintstatic_global_var20;
静态变量包括函数内的静态变量voidfunc(){staticintstatic_local_var30;// 会出现在nm输出// 注意static_local_var虽然作用域是函数内但生命周期是全局的}
弱符号__attribute__((weak))intweak_symbol5;// 会出现在nm输出不包含的符号不会出现在nm输出中
局部变量栈变量voidfunc(){intstack_var100;// 不会出现在nm输出 - 栈分配charbuffer[128];// 不会出现在nm输出 - 栈分配}
堆变量动态分配int*heap_varmalloc(
;// 不会出现在nm输出 - 运行时分配
寄存器变量registerintreg_var;// 不会出现在nm输出
函数参数voidfunc(intparam){...}// param不会出现在nm输出实际验证示例test.c:#includestdlib.hintglobal_data42;// 会显示 - 全局变量staticintstatic_data100;// 会显示 - 静态全局变量voidglobal_func(){// 会显示 - 全局函数staticintfunc_static0;// 会显示 - 函数内的静态变量intlocal5;// 不会显示 - 局部变量int*heapmalloc(
;// 不会显示 - 堆变量}intmain(){return0;}编译和查看gcc -c test.c -o test.o nm -S test.o可能的输出0000000000000000 0000000000000004 D global_data 0000000000000004 0000000000000004 d static_data 0000000000000000 0000000000000015 T global_func 0000000000000008 0000000000000004 d func_static.0 # 编译器重命名 0000000000000015 T main为什么nm不显示局部变量编译原理局部变量在编译时只有类型信息没有固定地址它们被编译成相对于栈指针(SP/FP)的偏移量符号表作用主要用于链接器需要知道哪些符号可以被其他文件引用局部变量不参与链接过程存储位置全局/静态变量在.data/.bss段固定地址局部变量在栈上运行时确定地址堆变量在堆上运行时动态分配如何查看局部变量信息
使用调试信息gcc -g test.c -otestnm -atest|grep-ilocal# 可能包含调试符号
使用objdump查看反汇编objdump -d test.o# 可以看到局部变量的栈操作# push rbp# mov rbp, rsp# sub rsp, 16 # 为局部变量分配栈空间
使用readelf查看节区readelf -S test.o# .text: 代码段函数# .data: 已初始化全局变量# .bss: 未初始化全局变量# 没有专门的栈节区或堆节区
总结对比表变量类型nm显示原因全局函数✓需要链接地址全局变量✓需要链接地址静态全局变量✓需要分配固定存储静态局部变量✓存储在.data/.bss段局部变量栈✗栈偏移无符号表项函数参数✗寄存器/栈传递堆变量✗运行时动态分配寄存器变量✗使用寄存器关键理解nm查看的是链接器需要知道的信息凡是需要链接器在链接时分配地址或解决引用的符号都会出现在符号表中。
局部变量和堆变量的生命周期和作用域决定了它们不需要也无法在链接时处理。
为什么需要重命名
作用域冲突C语言允许在不同函数中使用相同的静态变量名voidfunc1(){staticintcount0;// 需要唯一名称}voidfunc2(){staticintcount0;// 需要唯一名称但不能冲突}两个count变量虽然同名但作用域不同必须有不同的全局符号名。
静态局部变量的特殊性静态局部变量具有局部作用域只能在函数内访问全局生命周期存储在.data/.bss段需要唯一全局符号名用于链接器编译器重命名规则GCC/Clang的典型命名模式原始变量名.数字后缀 函数名.变量名.数字后缀 文件作用域前缀.变量名.数字后缀示例分析// test.cvoidfunc(){staticintvar0;// 编译为 var.0staticintcount0;// 编译为 count.1 (同函数内递增)}voidfunc2(){staticintvar0;// 编译为 var.2 (跨函数递增)}编译后$ nm test.o 0000000000000004 d var.0 0000000000000008 d count.1 000000000000000c d var.2为什么是.0而不是其他值数字后缀的生成规则同一函数内按声明顺序从0开始递增voidfunc(){staticinta0;// → a.0 (第一个静态变量)staticintb0;// → b.1 (第二个){staticintc0;// → c.2 (第三个即使在不同块内)}}不同函数间编译器可能方案A全局计数器整个文件方案B基于函数独立计数方案C基于编译单元独立计数实际编译器行为以GCC为例// gcc的行为示例staticintfile_static0;// → file_static (无后缀文件作用域已唯
voidfoo(){staticintx0;// → x.0staticinty0;// → y.1}voidbar(){staticintx0;// → x.2 (继续递增)}查看真实的重命名让我们实际验证一下test_rename.c:#includestdio.hvoidfunc1(){staticintcounter0;staticinttotal0;counter;totalcounter;}voidfunc2(){staticintcounter0;staticintresult0;counter--;resultcounter*2;}intmain(){return0;}编译和查看符号gcc -c test_rename.c -o test_rename.o nm test_rename.o可能的输出0000000000000000 d counter.0 # func1的第一个静态变量 0000000000000004 d total.1 # func1的第二个静态变量 0000000000000008 d counter.2 # func2的第一个静态变量 000000000000000c d result.3 # func2的第二个静态变量 0000000000000000 T func1 0000000000000015 T func2 000000000000002a T main重命名的技术实现
编译器的符号表处理// 简化的编译器逻辑classCompiler{intstatic_counter0;stringmangle_static_var(string orig_name){returnorig_name.to_string(static_counter);}};
不同编译器的差异编译器命名模式示例GCCname.数字var.0,var.1Clang类似GCCvar.0,var.1MSVC?name函数修饰?varfuncHAICC类似GCCvar.0_
C的更复杂重整C由于命名空间、类、模板等特性命名重整更复杂classTest{staticintclass_static;// → _ZN4Test11class_staticEvoidmethod(){staticintlocal;// → _ZZN4Test6methodEvE5local}};为什么不是其他命名方案可能的替代方案为什么不用使用函数名前缀func_var优点更易读缺点函数名可能很长产生冗余使用随机数var_0x1234优点唯一性好缺点不可预测调试困难使用哈希值var_abc123优点紧凑缺点可能冲突不直观实际选择.0的原因简单性实现简单可预测性便于调试和反汇编确定性同一代码每次编译结果相同兼容性与调试信息协调工作对调试的影响
GDB中的处理# GDB会自动处理名称重整(gdb)info variables All defined variables: File test.c: static int func()::counter;# GDB显示原始名称static int func()::total;
objdump查看objdump -t test.o|grepstatic# 显示重整后的名称但可以配合源代码理解特殊情况
内联函数中的静态变量inlinevoidfunc(){staticintcount0;// 每个编译单元独立实例}// 可能产生: count.0 (在第一个.o), count.0 (在第二个.o)
模板中的静态变量CtemplatetypenameTvoidfunc(){staticT counter0;// 每个模板实例化都有独立副本}// funcint::counter 和 funcfloat::counter 是不同变量
总结为什么是.0从0开始这是最自然的计数器起始值递增策略简单且能保证唯一性点号分隔.不是有效的C标识符字符避免与用户代码冲突历史惯例Unix工具链的传统做法这种命名方案平衡了唯一性避免符号冲突可读性便于人工识别实现简单性编译器容易生成确定性便于构建系统工作下次你看到var.2173这样的符号时就知道这是该编译单元中第2174个被处理的静态局部变量如果从0开始计数
B站免费看TVB大片观看-B站免费看TVB大片观看应用