当前位置: 首页 > news >正文

查看ELF文件符号表信息

这个命令用于查看ELF格式的可执行文件/库的符号表信息。让我详细解释:

命令解析

nm -S -n vela.elf

各选项含义:

  1. nm- GNU二进制文件工具,用于显示目标文件的符号表
  2. -S- 显示符号大小(size)
  3. -n- 按地址/值排序(数字顺序)
  4. vela.elf- 目标ELF文件(通常是嵌入式或Linux可执行文件)

输出示例

0000000000401000 000000000000002a T _start 0000000000401030 0000000000000015 T main 0000000000401048 0000000000000032 T function1

各列含义:

  1. 第一列:符号地址(16进制)
  2. 第二列:符号大小(字节,-S选项显示)
  3. 第三列:符号类型
  4. 第四列:符号名称

常见符号类型:

类型含义
T/t代码段中的函数/文本符号
D/d已初始化数据段
B/b未初始化数据段(BSS)
R/r只读数据段
U未定义符号(需要链接)
A绝对地址符号

实际应用场景:

  1. 固件分析- 查看嵌入式系统符号布局
  2. 调试- 定位函数地址用于调试
  3. 大小分析- 查看函数/变量占用空间
  4. 逆向工程- 理解程序结构

相关有用命令:

# 更详细的符号信息nm -S -n --size-sort vela.elf# 显示动态符号nm -D vela.elf# 显示所有符号(包括调试符号)nm -a vela.elf# 配合objdump查看反汇编objdump -d vela.elf|grep<function_address>

这个命令在嵌入式开发、内核调试和逆向工程中非常有用。


nm显示的符号主要包含全局符号,不包括局部变量和堆变量。让我详细解释:

包含的符号(会出现在nm输出中):

1.全局函数

// 会出现在nm输出voidglobal_function(){...}

2.全局变量

// 会出现在nm输出intglobal_var=10;staticintstatic_global_var=20;

3.静态变量(包括函数内的静态变量)

voidfunc(){staticintstatic_local_var=30;// 会出现在nm输出!// 注意:static_local_var虽然作用域是函数内,但生命周期是全局的}

4.弱符号

__attribute__((weak))intweak_symbol=5;// 会出现在nm输出

不包含的符号(不会出现在nm输出中):

1.局部变量(栈变量)

voidfunc(){intstack_var=100;// 不会出现在nm输出 - 栈分配charbuffer[128];// 不会出现在nm输出 - 栈分配}

2.堆变量(动态分配)

int*heap_var=malloc(100);// 不会出现在nm输出 - 运行时分配

3.寄存器变量

registerintreg_var;// 不会出现在nm输出

4.函数参数

voidfunc(intparam){...}// param不会出现在nm输出

实际验证示例:

test.c:

#include<stdlib.h>intglobal_data=42;// 会显示 - 全局变量staticintstatic_data=100;// 会显示 - 静态全局变量voidglobal_func(){// 会显示 - 全局函数staticintfunc_static=0;// 会显示 - 函数内的静态变量intlocal=5;// 不会显示 - 局部变量int*heap=malloc(10);// 不会显示 - 堆变量}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不显示局部变量?

  1. 编译原理

    • 局部变量在编译时只有类型信息,没有固定地址
    • 它们被编译成相对于栈指针(SP/FP)的偏移量
  2. 符号表作用

    • 主要用于链接器(需要知道哪些符号可以被其他文件引用)
    • 局部变量不参与链接过程
  3. 存储位置

    • 全局/静态变量:在.data/.bss段(固定地址)
    • 局部变量:在栈上(运行时确定地址)
    • 堆变量:在堆上(运行时动态分配)

如何查看局部变量信息?

1.使用调试信息

gcc -g test.c -otestnm -atest|grep-ilocal# 可能包含调试符号

2.使用objdump查看反汇编

objdump -d test.o# 可以看到局部变量的栈操作:# push rbp# mov rbp, rsp# sub rsp, 16 # 为局部变量分配栈空间

3.使用readelf查看节区

readelf -S test.o# .text: 代码段(函数)# .data: 已初始化全局变量# .bss: 未初始化全局变量# 没有专门的"栈节区"或"堆节区"

总结对比表:

变量类型nm显示原因
全局函数需要链接地址
全局变量需要链接地址
静态全局变量需要分配固定存储
静态局部变量存储在.data/.bss段
局部变量(栈)栈偏移,无符号表项
函数参数寄存器/栈传递
堆变量运行时动态分配
寄存器变量使用寄存器

关键理解:nm查看的是链接器需要知道的信息,凡是需要链接器在链接时分配地址或解决引用的符号,都会出现在符号表中。局部变量和堆变量的生命周期和作用域决定了它们不需要(也无法)在链接时处理。


为什么需要重命名?

1.作用域冲突

C语言允许在不同函数中使用相同的静态变量名:

voidfunc1(){staticintcount=0;// 需要唯一名称}voidfunc2(){staticintcount=0;// 需要唯一名称,但不能冲突}

两个count变量虽然同名,但作用域不同,必须有不同的全局符号名。

2.静态局部变量的特殊性

静态局部变量具有:

  • 局部作用域(只能在函数内访问)
  • 全局生命周期(存储在.data/.bss段)
  • 需要唯一全局符号名(用于链接器)

编译器重命名规则

GCC/Clang的典型命名模式:

<原始变量名>.<数字后缀> <函数名>.<变量名>.<数字后缀> <文件作用域前缀>.<变量名>.<数字后缀>

示例分析:

// test.cvoidfunc(){staticintvar=0;// 编译为 var.0staticintcount=0;// 编译为 count.1 (同函数内递增)}voidfunc2(){staticintvar=0;// 编译为 var.2 (跨函数递增)}

编译后:

$ nm test.o 0000000000000004 d var.0 0000000000000008 d count.1 000000000000000c d var.2

为什么是.0而不是其他值?

数字后缀的生成规则:

  1. 同一函数内:按声明顺序从0开始递增

    voidfunc(){staticinta=0;// → a.0 (第一个静态变量)staticintb=0;// → b.1 (第二个){staticintc=0;// → c.2 (第三个,即使在不同块内)}}
  2. 不同函数间:编译器可能:

    • 方案A:全局计数器(整个文件)
    • 方案B:基于函数独立计数
    • 方案C:基于编译单元独立计数
  3. 实际编译器行为(以GCC为例):

    // gcc的行为示例staticintfile_static=0;// → file_static (无后缀,文件作用域已唯一)voidfoo(){staticintx=0;// → x.0staticinty=0;// → y.1}voidbar(){staticintx=0;// → x.2 (继续递增)}

查看真实的重命名

让我们实际验证一下:

test_rename.c:

#include<stdio.h>voidfunc1(){staticintcounter=0;staticinttotal=0;counter++;total+=counter;}voidfunc2(){staticintcounter=0;staticintresult=0;counter--;result=counter*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

重命名的技术实现

1.编译器的符号表处理

// 简化的编译器逻辑classCompiler{intstatic_counter=0;stringmangle_static_var(string orig_name){returnorig_name+"."+to_string(static_counter++);}};

2.不同编译器的差异

编译器命名模式示例
GCC<name>.<数字>var.0,var.1
Clang类似GCCvar.0,var.1
MSVC?<name>@<函数>@@<修饰>?var@func@@HA
ICC类似GCCvar.0_

3.C++的更复杂重整

C++由于命名空间、类、模板等特性,命名重整更复杂:

classTest{staticintclass_static;// → _ZN4Test11class_staticEvoidmethod(){staticintlocal;// → _ZZN4Test6methodEvE5local}};

为什么不是其他命名方案?

可能的替代方案(为什么不用):

  1. 使用函数名前缀func_var

    • 优点:更易读
    • 缺点:函数名可能很长,产生冗余
  2. 使用随机数var_0x1234

    • 优点:唯一性好
    • 缺点:不可预测,调试困难
  3. 使用哈希值var_abc123

    • 优点:紧凑
    • 缺点:可能冲突,不直观

实际选择.0的原因:

  1. 简单性:实现简单
  2. 可预测性:便于调试和反汇编
  3. 确定性:同一代码每次编译结果相同
  4. 兼容性:与调试信息协调工作

对调试的影响

1.GDB中的处理

# GDB会自动处理名称重整(gdb)info variables All defined variables: File test.c: static int func()::counter;# GDB显示原始名称static int func()::total;

2.objdump查看

objdump -t test.o|grepstatic# 显示重整后的名称,但可以配合源代码理解

特殊情况

1.内联函数中的静态变量

inlinevoidfunc(){staticintcount=0;// 每个编译单元独立实例}// 可能产生: count.0 (在第一个.o), count.0 (在第二个.o)

2.模板中的静态变量(C++)

template<typenameT>voidfunc(){staticT counter=0;// 每个模板实例化都有独立副本}// func<int>::counter 和 func<float>::counter 是不同变量

总结

为什么是.0

  1. 从0开始:这是最自然的计数器起始值
  2. 递增策略:简单且能保证唯一性
  3. 点号分隔.不是有效的C标识符字符,避免与用户代码冲突
  4. 历史惯例:Unix工具链的传统做法

这种命名方案平衡了:

  • 唯一性:避免符号冲突
  • 可读性:便于人工识别
  • 实现简单性:编译器容易生成
  • 确定性:便于构建系统工作

下次你看到var.2173这样的符号时,就知道这是该编译单元中第2174个被处理的静态局部变量(如果从0开始计数)!

http://www.cnnetsun.cn/news/849022.html

相关文章:

  • 适用于飞书表格信息抽取的伪代码元构建提示词
  • 亲测好用!9个AI论文平台测评:本科生毕业论文必备
  • msidcrl40.dll文件丢失找不到 免费下载方法分享
  • Claude开发入门 03,从需求到代码:用 Claude 完成简单接口开发的实操指南
  • 冠军代言的客户口碑提升数据
  • Vue 报错「v-for must be a valid iterable」?3 步教你把循环填齐,列表立刻转起来!
  • 一不留神,网络安全工程师的岗位需求,破237万了
  • 【Android毕设源码分享】基于Android的健身房预约小程序的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 什么是大模型外呼?
  • google网站流量怎么获取?老运营人的实战笔记
  • Python中的__slots__:减少内存占用的高级技巧
  • 收藏!2026年程序员必追:AI大模型才是未来5年最优技术赛道
  • Serverless架构下的大模型Agent代码沙箱实现,开发者必学,建议收藏!
  • 一文看懂ARM Cortex-M内核中断:嵌入式设备的“高效指挥官”
  • 好写作AI:别跟自己的构思“谈恋爱”!让AI当反派,和你抬出个新世界
  • 好写作AI:别把会议录音当“学术垃圾”!用AI一键炼出摘要和待办清单
  • 埋点数据与UI操作的自动化校验:软件测试的核心挑战与解决方案
  • 学长亲荐!10个AI论文网站测评:研究生开题报告神器推荐
  • 9 款 AI 写论文哪个好?实测后揭露真相:虎贲等考 AI 才是论文党的 “终极救星”
  • 别踩坑!虎贲等考AI双控术:一键搞定降重与去AIGC痕迹
  • 基于python的大学生社团管理系统[python]-计算机毕业设计源码+LW文档
  • DeepAgents 框架深度解析:从理论到实践的智能代理架构
  • 网络安全渗透测试的八个步骤(一)
  • Linux的超全,命令
  • 初识 TCP 协议:从“听说过”到“真正认识它”,新手也能看懂的入门笔记
  • 小尺寸PCB显影与蚀刻池塘效应补偿详解
  • 电气论文公式和电路图算查重吗?理工科降重的3个误区
  • 虎贲等考 AI:开题报告不用改五版!从 “被导师打回” 到 “一次通过” 的合规秘籍
  • 救命神器!8款AI论文工具测评:研究生写论文救星
  • 信奥赛C++提高组csp-s之数位DP详细讲解