Reference:

  1. linux中的nm命令简介
  2. Linux的nm查看动态和静态库中的符号

1. nm功能

列出 .o, .a, .so 中的符号信息(不是直接作用于 main.c、test.h、test.c 等文件),包括诸如符号的值、符号类型以及符号名称等。所谓符号,通常指定义出的函数、全局变量等等。

2. 使用

nm [option(s)] [file(s)]

有用的options

  • -A 在每个符号信息的前面打印所在对象文件名称;
  • -C 输出demangle过了的符号名称;
  • -D 打印动态符号;
  • -l 使用对象文件中的调试信息打印出所在源文件及行号;
  • -n 按照地址/符号值来排序;
  • -u 打印出那些未定义的符号。

常见的符号类型:

  • A 该符号的值在今后的链接中将不再改变;
  • B 该符号放在BSS段中,通常是那些未初始化的全局变量;
  • D 该符号放在普通的数据段中,通常是那些已经初始化的全局变量;
  • T 该符号放在代码段中,通常是那些全局非静态函数;
  • U 该符号未定义过,需要自其他对象文件中链接进来;
  • W 未明确指定的弱链接符号;同链接的其他对象文件中有它的定义就用上,否则就用一个系统特别指定的默认值。

3. 举例

如:

nm -u hello.o
显示 hello.o 中的未定义符号,需要和其他对象文件进行链接。

nm -A /usr/lib/* 2>/dev/null | grep “T memset”
在 /usr/lib/ 目录下找出哪个库文件定义了 memset 函数。

3.1 测试案例

test.h 为:

void print();

test.c 为:

 #include <stdio.h>
 #include "test.h"
  
 void print()
 {
 	printf("rainy days\n");
 }

main.c 为:

 #include "test.h"
  
 int main()
 {
 	print();
 	return 0;
 }

现在看看nm命令的作用效果, 如下:

[taoge@localhost learn_nm]$ nm *
nm: main.c: File format not recognized
nm: test.c: File format not recognized
nm: test.h: File format not recognized
[taoge@localhost learn_nm]$ 

如上文所说,nm对这类文件无用。继续看nm能否读取目标文件和可执行文件:

[taoge@localhost learn_nm]$ ls
main.c  test.c  test.h
[taoge@localhost learn_nm]$ gcc -c test.c main.c 
[taoge@localhost learn_nm]$ gcc test.o main.o
[taoge@localhost learn_nm]$ ./a.out 
rainy days
[taoge@localhost learn_nm]$ nm *

a.out:
08049564 d _DYNAMIC
08049630 d _GLOBAL_OFFSET_TABLE_
0804849c R _IO_stdin_used
         w _Jv_RegisterClasses
08049554 d __CTOR_END__
08049550 d __CTOR_LIST__
0804955c D __DTOR_END__
08049558 d __DTOR_LIST__
0804854c r __FRAME_END__
08049560 d __JCR_END__
08049560 d __JCR_LIST__
0804964c A __bss_start
08049648 D __data_start
08048450 t __do_global_ctors_aux
08048330 t __do_global_dtors_aux
080484a0 R __dso_handle
         w __gmon_start__
0804844a T __i686.get_pc_thunk.bx
08049550 d __init_array_end
08049550 d __init_array_start
080483e0 T __libc_csu_fini
080483f0 T __libc_csu_init
         U __libc_start_main@@GLIBC_2.0
0804964c A _edata
08049654 A _end
0804847c T _fini
08048498 R _fp_hw
08048290 T _init
08048300 T _start
0804964c b completed.5963
08049648 W data_start
08049650 b dtor_idx.5965
08048390 t frame_dummy
080483c8 T main
080483b4 T print
         U puts@@GLIBC_2.0
nm: main.c: File format not recognized

main.o:
00000000 T main
         U print
nm: test.c: File format not recognized
nm: test.h: File format not recognized

test.o:
00000000 T print
         U puts

我们继续看静态库和动态库, 如下:

[taoge@localhost learn_nm]$ ls
main.c  test.c  test.h
[taoge@localhost learn_nm]$ gcc -c test.c
[taoge@localhost learn_nm]$ ar rcs libtest.a test.o
[taoge@localhost learn_nm]$ gcc -shared -fPIC -o libtest.so test.o
[taoge@localhost learn_nm]$ ls
libtest.a  libtest.so  main.c  test.c  test.h  test.o
[taoge@localhost learn_nm]$ nm lib*

libtest.a:

test.o:
00000000 T print
         U puts

libtest.so:
000014bc a _DYNAMIC
00001590 a _GLOBAL_OFFSET_TABLE_
         w _Jv_RegisterClasses
000014a8 d __CTOR_END__
000014a4 d __CTOR_LIST__
000014b0 d __DTOR_END__
000014ac d __DTOR_LIST__
000004a0 r __FRAME_END__
000014b4 d __JCR_END__
000014b4 d __JCR_LIST__
000015a4 A __bss_start
         w __cxa_finalize@@GLIBC_2.1.3
00000440 t __do_global_ctors_aux
00000350 t __do_global_dtors_aux
000014b8 d __dso_handle
         w __gmon_start__
00000419 t __i686.get_pc_thunk.bx
000015a4 A _edata
000015ac A _end
00000478 T _fini
000002ec T _init
000015a4 b completed.5963
000015a8 b dtor_idx.5965
000003e0 t frame_dummy
00000420 T print
         U puts@@GLIBC_2.0

可以看到, 我们可以从静态库和动态库中获取到函数名称, 如print()。

我们再来看看全局变量的情形, 我们把main.c改为:

#include <stdio.h>

int add(int x, int y)
{
   return x + y;
}

int aaa;
int bbb = 1;
char szTest[] = "good";

int main()
{
   int ccc = 2;
   return 0;
}

然后用nm分析a.out(注意, 如果只有nm命令, 则默认a.out为其要处理的文件):

[taoge@localhost learn_nm]$ ls
main.c
[taoge@localhost learn_nm]$ gcc main.c 
[taoge@localhost learn_nm]$ ./a.out 
[taoge@localhost learn_nm]$ nm a.out 
08049538 d _DYNAMIC
08049604 d _GLOBAL_OFFSET_TABLE_
0804847c R _IO_stdin_used
         w _Jv_RegisterClasses
08049528 d __CTOR_END__
08049524 d __CTOR_LIST__
08049530 D __DTOR_END__
0804952c d __DTOR_LIST__
08048520 r __FRAME_END__
08049534 d __JCR_END__
08049534 d __JCR_LIST__
08049628 A __bss_start
08049618 D __data_start
08048430 t __do_global_ctors_aux
08048310 t __do_global_dtors_aux
08048480 R __dso_handle
         w __gmon_start__
0804842a T __i686.get_pc_thunk.bx
08049524 d __init_array_end
08049524 d __init_array_start
080483c0 T __libc_csu_fini
080483d0 T __libc_csu_init
         U __libc_start_main@@GLIBC_2.0
08049628 A _edata
08049634 A _end
0804845c T _fini
08048478 R _fp_hw
08048274 T _init
080482e0 T _start
08049630 B aaa
08048394 T add
0804961c D bbb
08049628 b completed.5963
08049618 W data_start
0804962c b dtor_idx.5965
08048370 t frame_dummy
080483a2 T main
08049620 D szTest

可以看到, 不仅有add函数, 还有全局变量aaa, bbb和szTest, 要注意, aaa是未初始化的, 所以在Bss段, 而bbb、szTest是初始化了的, 所以在Data段。 值得注意的是, 并没有ccc, 因为ccc是局部变量, nm看不到的。

我们还应该注意到, 在上面看不到"good",为啥呢? 因为nm是用来看szTest而非"good"的。但是strings命令可干这事, 如下:

[taoge@localhost learn_nm]$ ls
a.out  main.c
[taoge@localhost learn_nm]$ strings a.out 
/lib/ld-linux.so.2
__gmon_start__
libc.so.6
_IO_stdin_used
__libc_start_main
GLIBC_2.0
PTRh
[^_]
good
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐