searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

linux下强大的文件分析工具 -- nm

2023-11-28 03:27:40
99
0
linux下强大的文件分析工具 -- nm
什么是nm
nm命令是linux下自带的特定文件分析工具,一般用来检查分析二进制文件、库文件(静态库和动态库)、可执行文件中的符号表,返回二进制文件中各段的信息。
 
nm是names的简称,通过该指令可以列举文件中的符号(一般是库文件),因为很多时候我们并不首要关注库逻辑实现,只需要知道某些函数或变量的命名冲突等问题,这个时候就可以使用nm命令,当然,类似的命令还有其他的,这里我只讲nm。
 
目标文件、库文件、可执行文件
首先,提到这三种文件,我们不得不提的就是gcc的编译流程:预编译,编译,汇编,链接。
  • 目标文件 :常说的目标文件是我们的程序文件(.c/.cpp,.h)经过预编译,编译,汇编过程生成的二进制文件,不经过链接过程,编译生成指令为:
gcc(g++) -c file.c(file.cpp)
将生成对应的file.o文件,file.o即为二进制文件
  • 库文件: 分为静态库和动态库,这里不做过多介绍,库文件是由多个二进制文件打包而成,生成的.a文件,示例:
ar -rsc liba.a test1.o test2.o test3.o
将test1.o test2.o test3.o三个文件打包成liba.a库文件
  • 可执行文件:可执行文件是由多个二进制文件或者库文件(由上所得,库文件其实是二进制文件的集合)经过链接过程生成的一个可执行文件,对应windows下的.exe文件,可执行文件中有且仅有一个main()函数(用户程序入口,一般由bootloader指定,当然也可以改),一般情况下,二进制文件和库文件中是不包含main()函数的,但是在linux下用户有绝对的自由,做一个包含main函数的库文件也是可以使用的,但这不属于常规操作,不作讨论。
上述三种文件的格式都是二进制文件。
 
为什么要用到nm
在上述提到的三种文件中,用编辑器是无法查看其内容的(乱码),所以当我们有这个需求(例如debug,查看内存分布的时候)去查看一个二进制文件里包含了哪些内容时,这时候就将用到一些特殊工具,linux下的nm命令就可以完全胜任(同时还有objdump和readelf工具,这里暂不作讨论)。
 
怎么使用nm
如果你对linux下的各种概念还算了解的话,就该知道一般linux下的命令都会自带一些命令参数来满足各种应用需求,了解这些参数的使用是使用命令的开始。
man
那么,如何去了解一个命令呢,最好的方法就是linux下的man命令,linux是一个宝库,而man指令就相当于这个宝库的说明书。
用法:
man nm
 
Usage: nm [option(s)] [file(s)]
 List symbols in [file(s)] (a.out by default).
 The options are:
  -a, --debug-syms       只显示调试信息
  -A, --print-file-name   在每行符号信息前,打印查询的文件名
  -C, --demangle[=STYLE] 将低级别的符号名称转换为用户级别的名称,意思是转换为用户较容易理解的形式
  -D, --dynamic          显示动态符号信息 动态库
  -f, --format=FORMAT    设置信息输出格式. 它可以是`bsd',`sysv' 或者 `posix'. 默认为 `bsd'
  -g, --extern-only      只显示外部符号;封装成动态库时,需要被外部调用的函数必须是外部符号,否则无法使用
  -l, --line-numbers     显示每个符号的文件名和行号
  -n, --numeric-sort     将符号按地址排序
  -r, --reverse-sort     反向排序
  -S, --print-size       显示定义符号的大小
  -u, --undefined-only   仅显示未定义符号
  -X 32_64               (ignored)
 
显示每个内核符合所在的文件和行号:
nm -l ./debug/usr/lib/modules/4.18.0-305.3.1.el8.x86_64/vmlinux
 
显示定义符号的大小
[root@localhost compressed]# nm vmlinux -S | more 0000000000798130 000000000000003b T add_identity_map 0000000000792843 t adjust_got 0000000000797b70 0000000000000023 t alloc_pgt_page 000000000079d75c 000000000000000c r apple 0000000000795fe0 000000000000002a T atou 000000000079d4c0 0000000000000008 r bases.11147 0000000000795f20 000000000000000c T bcmp 000000000079f680 b boot_heap 00000000007b6e50 0000000000000008 B boot_params 00000000007af680 b boot_stack 00000000007b3680 b boot_stack_end 000000000079f680 B _bss 000000000079d6a0 0000000000000080 r build_str 0000000000797300 0000000000000863 T choose_random_location 000000000079f4e0 0000000000000008 d __chunk_size 0000000000798570 000000000000018f T cleanup_trampoline 00000000007962f0 0000000000000117 T __cmdline_find_option 0000000000796500 000000000000002d T cmdline_find_option 0000000000796410 00000000000000cc T __cmdline_find_option_bool
 
 
A :符号的值是绝对值,不会被更改 B或b :未被初始化的全局数据,放在.bss段 D或d :已经初始化的全局数据 G或g :指被初始化的数据,特指small objects I :另一个符号的间接参考 N :debugging 符号 p :位于堆栈展开部分 R或r :属于只读存储区 S或s :指为初始化的全局数据,特指small objects T或t :代码段的数据,.test段 U :符号未定义 W或w :符号为弱符号,当系统有定义符号时,使用定义符号,当系统未定义符号且定义了弱符号时,使用弱符号。 ? :unknown符号
 
注意: nm 不能看到局部变量
 
3. 示例
寻找特殊标识
有时会碰到一个编译了但没有链接的代码,那是因为它缺失了标识符;这种情况,可以用nm和objdump、readelf命令来查看程序的符号表;所有这些命令做的工作基本一样;
比如连接器报错有未定义的标识符;大多数情况下,会发生在库的缺失或企图链接一个错误版本的库的时候;浏览目标代码来寻找一个特殊标识符的引用:
nm -uCA *.o | grep foo
-u选项限制了每个目标文件中未定义标识符的输出。-A选项用于显示每个标识符的文件名信息;对于C++代码,常用的还有-C选项,它也为解码这些标识符;
注解
objdump、readld命令可以完成同样的任务。等效命令为: objdump−tobjdump−treadelf -s
列出 a.out 对象文件的静态和外部符:
$nm -e a.out
以十六进制显示符号大小和值并且按值排序符号:
$nm -xv a.out
显示 libc.a 中所有 64 位对象符号,忽略所有 32 位对象:
$nm -X64 /usr/lib/libc.a

0条评论
作者已关闭评论
c****h
1文章数
0粉丝数
c****h
1 文章 | 0 粉丝
c****h
1文章数
0粉丝数
c****h
1 文章 | 0 粉丝
原创

linux下强大的文件分析工具 -- nm

2023-11-28 03:27:40
99
0
linux下强大的文件分析工具 -- nm
什么是nm
nm命令是linux下自带的特定文件分析工具,一般用来检查分析二进制文件、库文件(静态库和动态库)、可执行文件中的符号表,返回二进制文件中各段的信息。
 
nm是names的简称,通过该指令可以列举文件中的符号(一般是库文件),因为很多时候我们并不首要关注库逻辑实现,只需要知道某些函数或变量的命名冲突等问题,这个时候就可以使用nm命令,当然,类似的命令还有其他的,这里我只讲nm。
 
目标文件、库文件、可执行文件
首先,提到这三种文件,我们不得不提的就是gcc的编译流程:预编译,编译,汇编,链接。
  • 目标文件 :常说的目标文件是我们的程序文件(.c/.cpp,.h)经过预编译,编译,汇编过程生成的二进制文件,不经过链接过程,编译生成指令为:
gcc(g++) -c file.c(file.cpp)
将生成对应的file.o文件,file.o即为二进制文件
  • 库文件: 分为静态库和动态库,这里不做过多介绍,库文件是由多个二进制文件打包而成,生成的.a文件,示例:
ar -rsc liba.a test1.o test2.o test3.o
将test1.o test2.o test3.o三个文件打包成liba.a库文件
  • 可执行文件:可执行文件是由多个二进制文件或者库文件(由上所得,库文件其实是二进制文件的集合)经过链接过程生成的一个可执行文件,对应windows下的.exe文件,可执行文件中有且仅有一个main()函数(用户程序入口,一般由bootloader指定,当然也可以改),一般情况下,二进制文件和库文件中是不包含main()函数的,但是在linux下用户有绝对的自由,做一个包含main函数的库文件也是可以使用的,但这不属于常规操作,不作讨论。
上述三种文件的格式都是二进制文件。
 
为什么要用到nm
在上述提到的三种文件中,用编辑器是无法查看其内容的(乱码),所以当我们有这个需求(例如debug,查看内存分布的时候)去查看一个二进制文件里包含了哪些内容时,这时候就将用到一些特殊工具,linux下的nm命令就可以完全胜任(同时还有objdump和readelf工具,这里暂不作讨论)。
 
怎么使用nm
如果你对linux下的各种概念还算了解的话,就该知道一般linux下的命令都会自带一些命令参数来满足各种应用需求,了解这些参数的使用是使用命令的开始。
man
那么,如何去了解一个命令呢,最好的方法就是linux下的man命令,linux是一个宝库,而man指令就相当于这个宝库的说明书。
用法:
man nm
 
Usage: nm [option(s)] [file(s)]
 List symbols in [file(s)] (a.out by default).
 The options are:
  -a, --debug-syms       只显示调试信息
  -A, --print-file-name   在每行符号信息前,打印查询的文件名
  -C, --demangle[=STYLE] 将低级别的符号名称转换为用户级别的名称,意思是转换为用户较容易理解的形式
  -D, --dynamic          显示动态符号信息 动态库
  -f, --format=FORMAT    设置信息输出格式. 它可以是`bsd',`sysv' 或者 `posix'. 默认为 `bsd'
  -g, --extern-only      只显示外部符号;封装成动态库时,需要被外部调用的函数必须是外部符号,否则无法使用
  -l, --line-numbers     显示每个符号的文件名和行号
  -n, --numeric-sort     将符号按地址排序
  -r, --reverse-sort     反向排序
  -S, --print-size       显示定义符号的大小
  -u, --undefined-only   仅显示未定义符号
  -X 32_64               (ignored)
 
显示每个内核符合所在的文件和行号:
nm -l ./debug/usr/lib/modules/4.18.0-305.3.1.el8.x86_64/vmlinux
 
显示定义符号的大小
[root@localhost compressed]# nm vmlinux -S | more 0000000000798130 000000000000003b T add_identity_map 0000000000792843 t adjust_got 0000000000797b70 0000000000000023 t alloc_pgt_page 000000000079d75c 000000000000000c r apple 0000000000795fe0 000000000000002a T atou 000000000079d4c0 0000000000000008 r bases.11147 0000000000795f20 000000000000000c T bcmp 000000000079f680 b boot_heap 00000000007b6e50 0000000000000008 B boot_params 00000000007af680 b boot_stack 00000000007b3680 b boot_stack_end 000000000079f680 B _bss 000000000079d6a0 0000000000000080 r build_str 0000000000797300 0000000000000863 T choose_random_location 000000000079f4e0 0000000000000008 d __chunk_size 0000000000798570 000000000000018f T cleanup_trampoline 00000000007962f0 0000000000000117 T __cmdline_find_option 0000000000796500 000000000000002d T cmdline_find_option 0000000000796410 00000000000000cc T __cmdline_find_option_bool
 
 
A :符号的值是绝对值,不会被更改 B或b :未被初始化的全局数据,放在.bss段 D或d :已经初始化的全局数据 G或g :指被初始化的数据,特指small objects I :另一个符号的间接参考 N :debugging 符号 p :位于堆栈展开部分 R或r :属于只读存储区 S或s :指为初始化的全局数据,特指small objects T或t :代码段的数据,.test段 U :符号未定义 W或w :符号为弱符号,当系统有定义符号时,使用定义符号,当系统未定义符号且定义了弱符号时,使用弱符号。 ? :unknown符号
 
注意: nm 不能看到局部变量
 
3. 示例
寻找特殊标识
有时会碰到一个编译了但没有链接的代码,那是因为它缺失了标识符;这种情况,可以用nm和objdump、readelf命令来查看程序的符号表;所有这些命令做的工作基本一样;
比如连接器报错有未定义的标识符;大多数情况下,会发生在库的缺失或企图链接一个错误版本的库的时候;浏览目标代码来寻找一个特殊标识符的引用:
nm -uCA *.o | grep foo
-u选项限制了每个目标文件中未定义标识符的输出。-A选项用于显示每个标识符的文件名信息;对于C++代码,常用的还有-C选项,它也为解码这些标识符;
注解
objdump、readld命令可以完成同样的任务。等效命令为: objdump−tobjdump−treadelf -s
列出 a.out 对象文件的静态和外部符:
$nm -e a.out
以十六进制显示符号大小和值并且按值排序符号:
$nm -xv a.out
显示 libc.a 中所有 64 位对象符号,忽略所有 32 位对象:
$nm -X64 /usr/lib/libc.a

文章来自个人专栏
silence
1 文章 | 1 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0