万万没想到,一个可执行文件原来包含了这么多信息!
來源:公眾號【編程珠璣】
作者:守望先生
ID:shouwangxiansheng
?
拿到一個編譯好的可執行文件,你能獲取到哪些信息?文件大小,修改時間?文件類型?除此之外呢?實際上它包含了很多信息,這些你都知道嗎?
示例程序
//main.c #include<stdio.h> void?testFun() {printf("公眾號:編程珠璣\n"); } int?main(void) {testFun();return?0; }編譯得到可執行文件main:
$?gcc?-o?main?main.cELF頭信息
只需要一條簡單的命令,就可以獲取很多信息
$?readelf?-h?main ELF?Header:Magic:???7f?45?4c?46?02?01?01?00?00?00?00?00?00?00?00?00?Class:?????????????????????????????ELF64Data:??????????????????????????????2's?complement,?little?endianVersion:???????????????????????????1?(current)OS/ABI:????????????????????????????UNIX?-?System?VABI?Version:???????????????????????0Type:??????????????????????????????EXEC?(Executable?file)Machine:???????????????????????????Advanced?Micro?Devices?X86-64Version:???????????????????????????0x1Entry?point?address:???????????????0x400430Start?of?program?headers:??????????64?(bytes?into?file)Start?of?section?headers:??????????6648?(bytes?into?file)Flags:?????????????????????????????0x0Size?of?this?header:???????????????64?(bytes)Size?of?program?headers:???????????56?(bytes)Number?of?program?headers:?????????9Size?of?section?headers:???????????64?(bytes)Number?of?section?headers:?????????31Section?header?string?table?index:?28程序位數
Class:?????ELF64Class展示了該程序的位數,如這里顯示的是ELF64,如果你將它放到一個32位系統中運行,運行得起來就怪了。換句話說,64位系統上能運行32位和64位的程序,但是32位系統上,無法運行64位的程序。
大小端
??Data:???2's?complement,?little?endian還記得那個到處可見的面試題嗎?如何判斷當前CPU是大端還是小端?除了各種秀代碼的方式,你想到這個方式了嗎?
找一個該平臺上的正運行的可執行文件或系統庫,然后使用readelf -h看一下,是不是很快就看出來了?多么明顯的little endian。
關于大小端,更多內容可參考《談談字節序的問題》。
運行平臺
Machine:???Advanced?Micro?Devices?X86-64做嵌入式相關的可能經常需要做交叉編譯,而編譯出來的程序到底對不對呢?比如你在86平臺編譯arm的程序,最終生成的可執行文件到底能不能運行在arm平臺呢?通過Machine字段就可以很容易確定,從這里可以看到,它是運行在x86平臺的。
同樣的,當你在交叉編譯的時候,發現總有一個庫鏈接不上,但是庫又存在,不妨看看這個庫和你要編譯的平臺是否匹配。
鏈接了哪些動態庫?
編好的程序依賴了哪些動態庫呢?可不要放到另外一個平臺就起不來啊。瞅瞅:
$?ldd?mainlinux-vdso.so.1?=>??(0x00007ffe750e7000)libc.so.6?=>?/lib/x86_64-linux-gnu/libc.so.6?(0x00007f749920a000)/lib64/ld-linux-x86-64.so.2?(0x00007f74995d4000)原來鏈接了這些庫,所以當你在網上下載一些程序,運行的時候提示你某些so找不到,不妨看看它鏈接的動態庫在什么位置,你的機器上到底有沒有吧。
新增的函數和全局變量包含了嗎?
新增了一個全局變量或者函數,但是編譯完之后,不確定有沒有?
$?nm?main?|grep?testFun 0000000000400526?T?testFunnm看下就知道了。當然了,如果你看到某個庫的函數前面的標志不是T,而是U,說明該函數未在該庫中定義。
nm主要用于查看elf文件的符號表信息。
有符號表嗎
我們都知道,沒有符號表的程序,在core之后是沒有太多有效信息可看的,也是無法使用gdb正常調試的,這個在《GDB調試入門,看這篇就夠了》中已經有提到了,那么怎么看有沒有符號表呢?
$?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咦?你看這里是不是也可以看到程序位數,適用平臺等信息?
如果使用file命令看到最后是not stripped,那么則含有符號表,一般線上的程序可能會選擇去掉符號表信息,因為可以大大減少可執行文件的空間占用。
$?strip?main這個時候再看看:
$?nm?main no?main?symbols程序占用空間太大?
為什么程序的占用空間這么大?不妨看看是不是使用了過多的靜態變量或全局變量:
$?size?maintext???????data?????bss?????dec?????hex?filename1261????????552???????8????1821?????71d?main看到data部分的大小了嗎?看起來并沒有多少,如果這里占用空間過大,那可能是你程序中用到了太多的全局變量和靜態變量或常量。當然了,如果你的全局變量都是初始化為0的,那么data這里是不會有明顯的變化的(為什么?)。
在開頭分別加下面這一行,其影響可執行文件的效果不一樣奧。
char?str[1000]?=?{0}; char?str[1000]?=?{1};包含某個字符串嗎
這個程序里面包含什么特殊的字符串嗎?可以搜索一下:
$?strings?main?|grep?hello hello,嗯?這樣一想,好像還可以把版本號信息寫進去呢。
C還是C++?
如果將前面的程序按照C++編譯:
$?g++?-o?main?main.c $?nm?main?|grep?test 0000000000400526?T?_Z7testFunv你會發現使用g++編譯出來的test函數符號前帶頭,后帶尾,這也是C++中有重載和C中沒有重載的原因之一。
函數的匯編代碼是?
反匯編所有代碼:
$?objdump?-d?main那如果要反匯編特定函數(如main函數)呢?先按照地址順序輸出符號表信息:
$?nm?-n?main?|grep?main?-A?1 0000000000400537?T?main 0000000000400550?T?__libc_csu_init我們得到main的開始地址為0x400537,結束地址為0x400550。
反匯編:
看看只讀數據區有哪些內容?
當我們嘗試修改常量字符串的時候,編譯器會提示我們,它們是只讀的,真的如此嗎?
$?readelf?main?-x?.rodata Hex?dump?of?section?'.rodata':0x004005d0?01000200?00000000?68656c6c?6f2ce585?........hello,..0x004005e0?ace4bc97?e58fb7ef?bc9ae7bc?96e7a88b?................0x004005f0?e78fa0e7?8e9100?????????????????????.......看到了嗎?我們的hello,字符串放在了這里。
總結
本文僅列出了一些比較常見的可執行文中能讀到的信息,歡迎補充。
思考
對于a和b,它們的內存存儲區域是一樣的嗎?為什么?
char?*a?=?"hello,world"; char?a[]?=?"hello,world";sizeof計算a和b的大小一樣嗎?又為什么?
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的万万没想到,一个可执行文件原来包含了这么多信息!的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 肝一波 ~ 手写一个简易版的Mybati
- 下一篇: 这 10 行比较字符串相等的代码给我整懵