news 2026/6/22 19:42:47

查看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/6/19 1:46:26

数智化战士们,马年六要六不要请收好

骏马奔腾启新程&#xff0c;数字转型正当时&#xff01; 值此马年新春&#xff0c; 湖北 CIO 圈为各位同仁整理六要六不要&#xff0c;愿大家在新的一年里&#xff0c;策马扬鞭&#xff0c;驰骋数字蓝海&#xff0c;再创转型佳绩&#xff01;六要 要锚定战略&#xff0c;以数智…

作者头像 李华
网站建设 2026/6/22 12:28:03

工业温湿度监测布线优化与远程管控的技术实现方案

在工业自动化、数据中心运维、冷链仓储等场景中&#xff0c;温湿度的精准采集与远程管控&#xff0c;是保障设备稳定运行、物料安全存储、工艺合规性的核心环节。当前&#xff0c;传统温湿度监测方案普遍存在布线复杂、供电适配性差、协议兼容困难、运维效率低等技术痛点&#…

作者头像 李华
网站建设 2026/6/19 3:03:08

Element Plus:Vue 3时代的现代化UI组件库解析

目录 引言 一、技术架构 1.1 组件注册与插件机制 1.2 响应式系统与性能优化 1.3 TypeScript深度集成 二、设计理念 2.1 原子化设计系统 2.2 响应式布局引擎 2.3 国际化与无障碍支持 三、核心功能 3.1 数据展示组件 3.2 表单验证系统 3.3 高级交互组件 四、生态扩展 4.1 主题…

作者头像 李华
网站建设 2026/6/9 20:03:58

OTA 会清空的情况有哪些?

persist.* 属性是怎么存的&#xff1f; 在 Android 里&#xff1a; persist.xxx 类型的属性 会被写入 /data/property/&#xff08;老版本在 /data/system/property/&#xff09; 属于 data 分区的数据 &#x1f449; OTA 升级&#xff08;不 wipe data&#xff09;时&…

作者头像 李华
网站建设 2026/6/16 9:28:46

MindSpore实战:昇腾NPU上的深度学习模型优化全记录

1 引言&#xff1a;为何选择MindSpore与昇腾生态 作为一名长期从事计算机视觉应用的开发者&#xff0c;我最近全面转向华为的MindSpore深度学习框架与昇腾NPU硬件平台。这一选择不仅源于对国产AI生态的支持&#xff0c;更是考虑到其在分布式训练和推理性能上的独特优势。 与主…

作者头像 李华
网站建设 2026/6/15 16:50:54

一款集成了智能客户关系管理思维的上门预约小程序系统

温馨提示&#xff1a;文末有资源获取方式在家政服务行业&#xff0c;获取新客户的成本往往高于维护老客户。传统模式中&#xff0c;客户与服务者之间缺乏有效连接&#xff0c;导致单次交易居多&#xff0c;客户流失率高。如何将一次性客户转化为长期稳定的忠实用户&#xff0c;…

作者头像 李华