0%

nm symbols 类型及程序组成

nm symbols 类型列表

man nm

  • A 该符号的值是绝对的,在以后的链接过程中,不允许进行改变。这样的符号值,常常出现在中断向量表中,例如用符号来表示各个中断向量函数在中断向量表中的位置。
  • B/b 该符号的值出现在非初始化数据段 (bss) 中。例如,在一个文件中定义全局 static int test。则该符号 test 的类型为 b,位于 bss section 中。其值表示该符号在 bss 段中的偏移。一般而言,bss 段分配于 RAM 中
  • C 该符号为 common。common symbol 是未初始化数据段。该符号没有包含于一个普通 section 中。只有在链接过程中才进行分配。符号的值表示该符号需要的字节数。例如在一个 c 文件中,定义 int test,并且该符号在别的地方会被引用,则该符号类型即为 C。否则其类型为 B。
  • D/d 该符号位于初始话数据段中。一般来说,分配到 data section 中。例如定义全局 int baud_table[5] = {9600, 19200, 38400, 57600, 115200},则会分配于初始化数据段中。
  • G/g 该符号也位于初始化数据段中。主要用于 small object 提高访问 small data object 的一种方式。
  • I 该符号是对另一个符号的间接引用。
  • N 该符号是一个 debugging 符号。
  • R/r 该符号位于只读数据区。例如定义全局 const int test[] = {123, 123}; 则 test 就是一个只读数据区的符号。注意在 cygwin 下如果使用 gcc 直接编译成 MZ 格式时,源文件中的 test 对应 _test,并且其符号类型为 D,即初始化数据段中。但是如果使用 m6812-elf-gcc 这样的交叉编译工具,源文件中的 test 对应目标文件的 test, 即没有添加下划线,并且其符号类型为 R。一般而言,位于 rodata section。值得注意的是,如果在一个函数中定义 const char *test = “abc”, const char test_int = 3。使用 nm 都不会得到符号信息,但是字符串“abc”分配于只读存储器中,test 在 rodata section 中,大小为 4。
  • S/s 符号位于非初始化数据区,用于 small object。
  • T/t 该符号位于代码区 text section。
  • U 该符号在当前文件中是未定义的,即该符号的定义在别的文件中。例如,当前文件调用另一个文件中定义的函数,在这个被调用的函数在当前就是未定义的;但是在定义它的文件中类型是 T。但是对于全局变量来说,在定义它的文件中,其符号类型为 C,在使用它的文件中,其类型为 U。
  • V/v 该符号是一个 weak object。
  • W/w The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
  • - 该符号是 a.out 格式文件中的 stabs symbol。
  • ? 该符号类型没有定义

程序分段

  1. text 段, 就是代码段,直观上看就是你码上去的东西,代码编译之后就交给 OS 运行,不能在 OS 上运行中修改它,因此这个段是只读的
  2. data 段,就是数据段,顾名思义它里面存的是数据,这个数据是初始化的全局变量,初始化的用 static 修饰的变量也放在这里,会占用程序文本空间,编译出来的可执行文件大小为 4.1M
int a[1024*1024] = {1};
void main (void)
{
    printf("hello world\n");
}
  1. bss 段,bss: block started by symbol,直译过来就是从符号开始的块,它里面存储的是未初始化的全局变量,包括初始化为 0 的全局变量,但不会占用程序文本空间,编译出来的可执行文件大小为 6.0K,去掉那个全局变量 a 编译出来的大小也是 6.0K
int a[1024*1024];
void main (void)
{
    printf("hello world\n");
}
  1. heap 堆,用 malloc 分配的内存就是它了,这个是在程序运行过程中动态分配的,所以不会占用程序文本空间;堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用 malloc 等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用 free 等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)

  2. stack 栈,这里面存放的是临时变量,也就是你在函数中 int a; a 就放在栈里,另外函数的形参也放在这里;是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括 static 声明的变量,static 意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存 / 恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。

  3. rodata 段,存放 C 中的字符串和#define 定义的常量

一般情况下,一个程序本质上都是由 bss 段、data 段、text 段三个组成的——本概念是当前的计算机程序设计中是很重要的一个基本概念。而且在嵌入式系统的设计中也非常重要,牵涉到嵌入式系统运行时的内存大小分配,存储单元占用空间大小的问题。在采用段式内存管理的架构中(比如 intel 的 80x86 系统),bss 段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时 bss 段部分将会清零(bss 段属于静态内存分配,即程序一开始就将其清零了)。比如,在 C 语言程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的全局变量保存在.bss 段中。text 和 data 段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而 bss 段不在可执行文件中,由系统初始化。未初始化的全局变量和静态变量放在 BSS 段,初始化的全局变量和静态变量放在数据段

常用分析命令

$ nm -S --size-sort -l output/out.elf
$ csky-elf-objdump -h -t output/out.elf