万万没想到,一个可执行文件原来包含了这么多信息!

开发 前端
拿到一个编译好的可执行文件,你能获取到哪些信息?文件大小,修改时间?文件类型?除此之外呢?实际上它包含了很多信息,这些你都知道吗?

拿到一个编译好的可执行文件,你能获取到哪些信息?文件大小,修改时间?文件类型?除此之外呢?实际上它包含了很多信息,这些你都知道吗?

示例程序

  1. //main.c 
  2. #include<stdio.h> 
  3. void testFun() 
  4.     printf("公众号:编程珠玑\n"); 
  5. int main(void) 
  6.     testFun(); 
  7.     return 0; 

编译得到可执行文件main:

  1. $ gcc -o main main.c 

ELF头信息

只需要一条简单的命令,就可以获取很多信息

  1. $ readelf -h main 
  2. ELF Header: 
  3.   Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00  
  4.   Class:                             ELF64 
  5.   Data:                              2's complement, little endian 
  6.   Version:                           1 (current) 
  7.   OS/ABI:                            UNIX - System V 
  8.   ABI Version:                       0 
  9.   Type:                              EXEC (Executable file) 
  10.   Machine:                           Advanced Micro Devices X86-64 
  11.   Version:                           0x1 
  12.   Entry point address:               0x400430 
  13.   Start of program headers:          64 (bytes into file) 
  14.   Start of section headers:          6648 (bytes into file) 
  15.   Flags:                             0x0 
  16.   Size of this header:               64 (bytes) 
  17.   Size of program headers:           56 (bytes) 
  18.   Number of program headers:         9 
  19.   Size of section headers:           64 (bytes) 
  20.   Number of section headers:         31 
  21.   Section header string table index: 28 

程序位数

  1. Class:     ELF64 

Class展示了该程序的位数,如这里显示的是ELF64,如果你将它放到一个32位系统中运行,运行得起来就怪了。换句话说,64位系统上能运行32位和64位的程序,但是32位系统上,无法运行64位的程序。

大小端

  1. Data:   2's complement, little endian 

还记得那个到处可见的面试题吗?如何判断当前CPU是大端还是小端?除了各种秀代码的方式,你想到这个方式了吗?

找一个该平台上的正运行的可执行文件或系统库,然后使用readelf -h看一下,是不是很快就看出来了?多么明显的little endian。

运行平台

  1. Machine:   Advanced Micro Devices X86-64 

做嵌入式相关的可能经常需要做交叉编译,而编译出来的程序到底对不对呢?比如你在86平台编译arm的程序,最终生成的可执行文件到底能不能运行在arm平台呢?通过Machine字段就可以很容易确定,从这里可以看到,它是运行在x86平台的。

同样的,当你在交叉编译的时候,发现总有一个库链接不上,但是库又存在,不妨看看这个库和你要编译的平台是否匹配。

链接了哪些动态库?

编好的程序依赖了哪些动态库呢?可不要放到另外一个平台就起不来啊。瞅瞅:

  1. $ ldd main 
  2.     linux-vdso.so.1 =>  (0x00007ffe750e7000) 
  3.     libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f749920a000) 
  4.     /lib64/ld-linux-x86-64.so.2 (0x00007f74995d4000) 

原来链接了这些库,所以当你在网上下载一些程序,运行的时候提示你某些so找不到,不妨看看它链接的动态库在什么位置,你的机器上到底有没有吧。

新增的函数和全局变量包含了吗?

新增了一个全局变量或者函数,但是编译完之后,不确定有没有?

  1. $ nm main |grep testFun 
  2. 0000000000400526 T testFun 

nm看下就知道了。当然了,如果你看到某个库的函数前面的标志不是T,而是U,说明该函数未在该库中定义。

nm主要用于查看elf文件的符号表信息。

有符号表吗

我们都知道,没有符号表的程序,在core之后是没有太多有效信息可看的,也是无法使用gdb正常调试的,那么怎么看有没有符号表呢?

  1. $ file main 
  2. main: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0d9a7eb860459b585d2b33ae28d7c67d5ba12669, not stripped 

咦?你看这里是不是也可以看到程序位数,适用平台等信息?

如果使用file命令看到最后是not stripped,那么则含有符号表,一般线上的程序可能会选择去掉符号表信息,因为可以大大减少可执行文件的空间占用。

  1. $ strip main 

这个时候再看看:

  1. $ nm main 
  2. no main symbols 

程序占用空间太大?

为什么程序的占用空间这么大?不妨看看是不是使用了过多的静态变量或全局变量:

  1. $ size main 
  2.    text       data     bss     dec     hex filename 
  3.    1261        552       8    1821     71d main 

看到data部分的大小了吗?看起来并没有多少,如果这里占用空间过大,那可能是你程序中用到了太多的全局变量和静态变量或常量。当然了,如果你的全局变量都是初始化为0的,那么data这里是不会有明显的变化的(为什么?)。

在开头分别加下面这一行,其影响可执行文件的效果不一样奥。

  1. char str[1000] = {0}; 
  2. char str[1000] = {1}; 

包含某个字符串吗

这个程序里面包含什么特殊的字符串吗?可以搜索一下:

  1. $ strings main |grep hello 
  2. hello, 

嗯?这样一想,好像还可以把版本号信息写进去呢。

C还是C++?

如果将前面的程序按照C++编译:

  1. $ g++ -o main main.c 
  2. $ nm main |grep test 
  3. 0000000000400526 T _Z7testFunv 

你会发现使用g++编译出来的test函数符号前带头,后带尾,这也是C++中有重载和C中没有重载的原因之一。

函数的汇编代码是?

反汇编所有代码:

  1. $ objdump -d main 

那如果要反汇编特定函数(如main函数)呢?先按照地址顺序输出符号表信息:

  1. $ nm -n main |grep main -A 1 
  2. 0000000000400537 T main 
  3. 0000000000400550 T __libc_csu_init 

我们得到main的开始地址为0x400537,结束地址为0x400550。

反汇编:

  1. $ objdump -d main --start-address=0x400537 --stop-address=0x400550 
  2. 0000000000400537 <main>
  3.   400537:    55                      push   %rbp 
  4.   400538:    48 89 e5                mov    %rsp,%rbp 
  5.   40053b:    b8 00 00 00 00          mov    $0x0,%eax 
  6.   400540:    e8 e1 ff ff ff          callq  400526 <testFun> 
  7.   400545:    b8 00 00 00 00          mov    $0x0,%eax 
  8.   40054a:    5d                      pop    %rbp 
  9.   40054b:    c3                      retq    
  10.   40054c:    0f 1f 40 00             nopl   0x0(%rax) 

 

 

看看只读数据区有哪些内容?

当我们尝试修改常量字符串的时候,编译器会提示我们,它们是只读的,真的如此吗?

  1. $ readelf main -x .rodata 
  2. Hex dump of section '.rodata': 
  3.   0x004005d0 01000200 00000000 68656c6c 6f2ce585 ........hello,.. 
  4.   0x004005e0 ace4bc97 e58fb7ef bc9ae7bc 96e7a88b ................ 
  5.   0x004005f0 e78fa0e7 8e9100                     ....... 

看到了吗?我们的hello,字符串放在了这里。

总结

本文仅列出了一些比较常见的可执行文中能读到的信息,欢迎补充。

思考

对于a和b,它们的内存存储区域是一样的吗?为什么?

  1. char *a = "hello,world"
  2. char a[] = "hello,world"; 

sizeof计算a和b的大小一样吗?又为什么?

 

 

 

责任编辑:赵宁宁 来源: 编程珠玑
相关推荐

2021-11-29 05:37:24

Windows Def操作系统微软

2021-08-31 09:35:01

TCPIP漏洞

2021-08-12 06:52:02

谷歌面试ArrayList

2015-07-15 13:00:31

英特尔开源

2019-12-09 10:13:20

HashMap选择容量

2021-03-18 09:06:17

函数MainJava

2016-09-01 13:54:23

Google太空电梯悬滑板

2021-01-27 18:13:35

日志nginx信息

2023-10-31 12:29:25

模型训练

2022-11-29 09:12:12

硬件技术拼图

2015-02-02 11:03:12

2010-02-22 18:04:27

CentOS mpla

2023-12-26 15:10:00

处理二进制文件

2012-01-05 10:37:40

Java

2020-08-14 08:19:25

Shell命令行数据

2017-12-12 11:09:39

显卡散热CPU

2018-06-27 14:23:38

机器学习人工智能入门方法

2019-10-12 08:53:26

Redis多线程版本

2011-08-09 10:24:19

可执行文件病毒病毒

2021-01-12 10:10:41

shell脚本Linux命令
点赞
收藏

51CTO技术栈公众号