萬(wàn)萬(wàn)沒(méi)想到,一個(gè)可執(zhí)行文件原來(lái)包含了這么多信息!
拿到一個(gè)編譯好的可執(zhí)行文件,你能獲取到哪些信息?文件大小,修改時(shí)間?文件類型?除此之外呢?實(shí)際上它包含了很多信息,這些你都知道嗎?
示例程序
- //main.c
- #include<stdio.h>
- void testFun()
- {
- printf("公眾號(hào):編程珠璣\n");
- }
- int main(void)
- {
- testFun();
- return 0;
- }
編譯得到可執(zhí)行文件main:
- $ gcc -o main main.c
ELF頭信息
只需要一條簡(jiǎn)單的命令,就可以獲取很多信息
- $ readelf -h main
- ELF Header:
- Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
- Class: ELF64
- Data: 2's complement, little endian
- Version: 1 (current)
- OS/ABI: UNIX - System V
- ABI Version: 0
- Type: EXEC (Executable file)
- Machine: Advanced Micro Devices X86-64
- Version: 0x1
- Entry point address: 0x400430
- Start of program headers: 64 (bytes into file)
- Start of section headers: 6648 (bytes into file)
- Flags: 0x0
- Size of this header: 64 (bytes)
- Size of program headers: 56 (bytes)
- Number of program headers: 9
- Size of section headers: 64 (bytes)
- Number of section headers: 31
- Section header string table index: 28
程序位數(shù)
- Class: ELF64
Class展示了該程序的位數(shù),如這里顯示的是ELF64,如果你將它放到一個(gè)32位系統(tǒng)中運(yùn)行,運(yùn)行得起來(lái)就怪了。換句話說(shuō),64位系統(tǒng)上能運(yùn)行32位和64位的程序,但是32位系統(tǒng)上,無(wú)法運(yùn)行64位的程序。
大小端
- Data: 2's complement, little endian
還記得那個(gè)到處可見(jiàn)的面試題嗎?如何判斷當(dāng)前CPU是大端還是小端?除了各種秀代碼的方式,你想到這個(gè)方式了嗎?
找一個(gè)該平臺(tái)上的正運(yùn)行的可執(zhí)行文件或系統(tǒng)庫(kù),然后使用readelf -h看一下,是不是很快就看出來(lái)了?多么明顯的little endian。
運(yùn)行平臺(tái)
- Machine: Advanced Micro Devices X86-64
做嵌入式相關(guān)的可能經(jīng)常需要做交叉編譯,而編譯出來(lái)的程序到底對(duì)不對(duì)呢?比如你在86平臺(tái)編譯arm的程序,最終生成的可執(zhí)行文件到底能不能運(yùn)行在arm平臺(tái)呢?通過(guò)Machine字段就可以很容易確定,從這里可以看到,它是運(yùn)行在x86平臺(tái)的。
同樣的,當(dāng)你在交叉編譯的時(shí)候,發(fā)現(xiàn)總有一個(gè)庫(kù)鏈接不上,但是庫(kù)又存在,不妨看看這個(gè)庫(kù)和你要編譯的平臺(tái)是否匹配。
鏈接了哪些動(dòng)態(tài)庫(kù)?
編好的程序依賴了哪些動(dòng)態(tài)庫(kù)呢?可不要放到另外一個(gè)平臺(tái)就起不來(lái)啊。瞅瞅:
- $ ldd main
- linux-vdso.so.1 => (0x00007ffe750e7000)
- libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f749920a000)
- /lib64/ld-linux-x86-64.so.2 (0x00007f74995d4000)
原來(lái)鏈接了這些庫(kù),所以當(dāng)你在網(wǎng)上下載一些程序,運(yùn)行的時(shí)候提示你某些so找不到,不妨看看它鏈接的動(dòng)態(tài)庫(kù)在什么位置,你的機(jī)器上到底有沒(méi)有吧。
新增的函數(shù)和全局變量包含了嗎?
新增了一個(gè)全局變量或者函數(shù),但是編譯完之后,不確定有沒(méi)有?
- $ nm main |grep testFun
- 0000000000400526 T testFun
nm看下就知道了。當(dāng)然了,如果你看到某個(gè)庫(kù)的函數(shù)前面的標(biāo)志不是T,而是U,說(shuō)明該函數(shù)未在該庫(kù)中定義。
nm主要用于查看elf文件的符號(hào)表信息。
有符號(hào)表嗎
我們都知道,沒(méi)有符號(hào)表的程序,在core之后是沒(méi)有太多有效信息可看的,也是無(wú)法使用gdb正常調(diào)試的,那么怎么看有沒(méi)有符號(hào)表呢?
- $ file main
- 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
咦?你看這里是不是也可以看到程序位數(shù),適用平臺(tái)等信息?
如果使用file命令看到最后是not stripped,那么則含有符號(hào)表,一般線上的程序可能會(huì)選擇去掉符號(hào)表信息,因?yàn)榭梢源蟠鬁p少可執(zhí)行文件的空間占用。
- $ strip main
這個(gè)時(shí)候再看看:
- $ nm main
- no main symbols
程序占用空間太大?
為什么程序的占用空間這么大?不妨看看是不是使用了過(guò)多的靜態(tài)變量或全局變量:
- $ size main
- text data bss dec hex filename
- 1261 552 8 1821 71d main
看到data部分的大小了嗎?看起來(lái)并沒(méi)有多少,如果這里占用空間過(guò)大,那可能是你程序中用到了太多的全局變量和靜態(tài)變量或常量。當(dāng)然了,如果你的全局變量都是初始化為0的,那么data這里是不會(huì)有明顯的變化的(為什么?)。
在開(kāi)頭分別加下面這一行,其影響可執(zhí)行文件的效果不一樣奧。
- char str[1000] = {0};
- char str[1000] = {1};
包含某個(gè)字符串嗎
這個(gè)程序里面包含什么特殊的字符串嗎?可以搜索一下:
- $ strings main |grep hello
- hello,
嗯?這樣一想,好像還可以把版本號(hào)信息寫(xiě)進(jìn)去呢。
C還是C++?
如果將前面的程序按照C++編譯:
- $ g++ -o main main.c
- $ nm main |grep test
- 0000000000400526 T _Z7testFunv
你會(huì)發(fā)現(xiàn)使用g++編譯出來(lái)的test函數(shù)符號(hào)前帶頭,后帶尾,這也是C++中有重載和C中沒(méi)有重載的原因之一。
函數(shù)的匯編代碼是?
反匯編所有代碼:
- $ objdump -d main
那如果要反匯編特定函數(shù)(如main函數(shù))呢?先按照地址順序輸出符號(hào)表信息:
- $ nm -n main |grep main -A 1
- 0000000000400537 T main
- 0000000000400550 T __libc_csu_init
我們得到main的開(kāi)始地址為0x400537,結(jié)束地址為0x400550。
反匯編:
- $ objdump -d main --start-address=0x400537 --stop-address=0x400550
- 0000000000400537 <main>:
- 400537: 55 push %rbp
- 400538: 48 89 e5 mov %rsp,%rbp
- 40053b: b8 00 00 00 00 mov $0x0,%eax
- 400540: e8 e1 ff ff ff callq 400526 <testFun>
- 400545: b8 00 00 00 00 mov $0x0,%eax
- 40054a: 5d pop %rbp
- 40054b: c3 retq
- 40054c: 0f 1f 40 00 nopl 0x0(%rax)
看看只讀數(shù)據(jù)區(qū)有哪些內(nèi)容? 當(dāng)我們嘗試修改常量字符串的時(shí)候,編譯器會(huì)提示我們,它們是只讀的,真的如此嗎? 看到了嗎?我們的hello,字符串放在了這里。 總結(jié) 本文僅列出了一些比較常見(jiàn)的可執(zhí)行文中能讀到的信息,歡迎補(bǔ)充。 思考 對(duì)于a和b,它們的內(nèi)存存儲(chǔ)區(qū)域是一樣的嗎?為什么? sizeof計(jì)算a和b的大小一樣嗎?又為什么?