news 2026/4/18 5:17:08

查看ELF文件符号表信息

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
查看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开始计数)!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 15:42:05

3大供应商协调管理技巧,助你搞定多方协作沟通与监督!

3大供应商协调管理技巧&#xff0c;助你搞定多方协作沟通与监督&#xff01; 在会议服务行业&#xff0c;涉及众多供应商的多方协作是常见且关键的环节。有效的供应商协调管理能够确保会议顺利进行&#xff0c;提升服务质量。北京天德腾润公关策划有限公司作为专业会务服务机构…

作者头像 李华
网站建设 2026/4/2 1:52:32

基于springboot的个人博客网站标签 vue可视化统计

目录 技术栈选择后端实现前端可视化数据交互优化扩展功能 开发技术源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 技术栈选择 Spring Boot 作为后端框架&#xff0c;提供 RESTful API 接口&#xff1b;Vue.js 作为前端框架&#xff0…

作者头像 李华
网站建设 2026/4/16 10:51:03

基于springboot的房屋销售房产交易系统vue

目录 系统概述技术架构核心功能模块扩展性设计部署方案 开发技术源码文档获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01; 系统概述 基于SpringBoot和Vue的房屋销售房产交易系统是一个前后端分离的现代化平台&#xff0c;旨在提供高效的房产信息…

作者头像 李华
网站建设 2026/4/18 2:25:33

电流传感器暗战:光伏大佬们不愿说的秘密武器

最近参加了一场光伏行业的闭门交流会&#xff0c;听到一个很有意思的现象&#xff1a;各大企业技术总监的PPT里&#xff0c;组件效率、硅片厚度这些常规参数都被放在次要位置&#xff0c;反而一个个都在显眼处标注着电流传感器的性能指标。这让我意识到&#xff0c;行业竞争的焦…

作者头像 李华
网站建设 2026/4/17 14:50:13

司替戊醇Stiripentol联合用药剂量指南与氯巴占联用的个体化剂量调整

司替戊醇作为一种重要的抗癫痫药物&#xff0c;在联合氯巴占治疗Dravet综合征等难治性癫痫时展现出显著疗效。然而&#xff0c;其剂量调整需基于患者个体特征、疗效反应及安全性进行精细化管理&#xff0c;以确保治疗获益最大化。联合用药的剂量基础与目标司替戊醇的推荐剂量为…

作者头像 李华