Linux下程序的機器級表示學習心得
上周學習完Linux程序的機器級表示后,對於其中有些還是掌握的不太透徹。對於老師提出的關於本章一些細節的問題還是有不會,所以又重新溫習了一下上周的學習內容,以下為學習心得。
分析反匯編
操作過程
-
分析反匯編采用了書上的一個簡單案例。C語言代碼如下。
int a(int x) { returnx+1; } int b(int x) { return a(x); } int main (void) { return b(8)+14; }
-
使用
vim
編輯器編譯代碼main.c
。
-
使用命令
gcc -S main.c -o main.s
得到匯編代碼。(此時的匯編代碼有以.開頭的代碼,刪除它們之后就是正常書中給出的匯編代碼。 -
得到的匯編代碼
-
使用
gdb
的bt/frame/up/down
指令動態查看調用線幀的情況。
分析
-
從
main
:開始執行,保存幀指針%ebp
,並設置新的幀指針 -
pushl $8
分配4字節的棧空間,並且設置arg1=8
-
調用b:
call b
-
b同樣初始化幀指針,分配棧空間,和之前的main函數相同
-
pushl 8(%ebp)
將%esp
中的立即數8存入棧中 -
調用a:
call a
-
a被調用,初始化棧指針,分配棧空間
-
將
%eax
與立即數 1 相加 -
在a結束前彈棧
-
ret
返回b中call
的調用位置 -
b也結束,
return返
回main
中call
調用的位置 -
main
繼續%eax
加14的操作 -
leave
為返回准備棧,相當於%ebp
出棧,最后ret
結束
即:調用者P和被調用者Q,則Q的參數放在P的棧幀中,當P調用Q的時候,P中的返回地址被壓入棧中,形成P的棧幀末尾。返回地址就是當程序從Q返回時應繼續執行的地方,Q棧幀從保存的幀指針的值開始后是保存其他寄存器的值。
結合backtrace命令分析棧幀
-
首先
backtrace/bt
用來打印棧幀指針,也可以在該命令后加上要打印的棧幀的個數,查看程序執行到此時,是經過哪些函數呼叫的程序,程序“調用堆棧”是當前函數之前的所有已調用函數的列表(包括當前函數)。每個函數及其變量都分配了一個“幀”,最近調用的函數在0號幀中(“底部”幀) -
命令有
- `fame farme1 `用於打印指定棧幀 - ` info reg `查看寄存器使用情況 - ` info stack` 產看堆棧使用情況 - `up/down` 跳到上一層/下一層函數
-
綜述:
- 先將調用者(A)的堆棧的基址(`%ebp`)入棧,以保存之前任務的信息。 - 然后將調用者(A)的棧頂指針(`%esp`)的值賦給`%ebp`,作為新的基址(即被調用者B的棧底)。 - 然后在這個基址(被調用者B的棧底)上開辟(一般用sub指令)相應的空間用作被調用者B的棧空間。 - 函數B返回后,從當前棧幀的%ebp即恢復為調用者A的棧頂(`%esp)`,使棧頂恢復函數B被調用前的位置;然后調用者A再從恢復后的棧頂可彈出之前的%ebp值(可以這么做是因為這個值在函數調用前一步被壓入堆棧)。這樣,`%ebp`和`%esp`就都恢復了調用函數B前的位置,也就是棧恢復函數B調用前的狀態。這樣就解釋了棧幀的出現和消失
這個過程在AT&T匯編中通過兩條指令完成,即:
leave
、ret
。這兩條指令更直白點就相當於:mov %ebp , %esp
、pop %ebp
- 下面我們使用GDB調試
main.c
的代碼,使用剛才編譯好的main鏡像。
-gdb start
(啟動gdb)
-(gdb) file main
(加載鏡像文件)
-(gdb) break main
(把main()
設置為斷點,注意gdb並沒有把斷點設置在main的第一條指令,而是設置在了調整棧指針為局部變量保留空間之后)
-(gdb) run
(運行程序)
-(gdb) stepi
(單步執行,stepi
命令執行之后顯示出來的源代碼行或者指令地址,注意:都是即將執行的指令,而不是剛剛執行完的指令!對於更復雜的例子會有明顯的變化)
利用gdb對寄存器進行分析
通過gdb調試可執行文件查看
%eip
,%ebp
,%esp
等寄存器內容如何變化。
-
在linux中gdb調試匯編文件需要先用
gcc -g3 -o * *.c
的命令來將c語言文件編譯成可調試匯編的可執行文件。 -
通過調試過程中的
stepi
和print /x $***
可以查詢到相應寄存器的內容: -
根據之前的main函數逐步使用上面的代碼,可以獲得不同寄存器的變化。
| number | %eax寄存器變化| %esp寄存器變化|%ebp寄存器變化時間|
| -----------| :-----------:|:------------:|:---------------:|
| 1 | Ox4004fc | Oxffffde18 | Oxffffde20
| 2 | Ox4004fc | Oxffffde18 | Oxffffde20
| 3 | Ox8 | Oxffffde18 | Oxffffde20
| 4 | Ox8 | Oxffffde18 | Oxffffde20
| 5 | Ox8 | Oxffffde10 | Oxffffde20
| 6 | Ox8 | Oxffffde08 | Oxffffde20
| 7 | Ox8 | Oxffffde08 | Oxffffde08
| 8 | Ox8 | Oxffffde08 | Oxffffde08
| 9 | Ox8 | Oxffffde08 | Oxffffde08
| 10 | Ox9 | Oxffffde08 | Oxffffde08
| 11 | Ox9 | Oxffffde10 | Oxffffde20
| 12 | Ox9 | Oxffffde18 | Oxffffde20
| 13 | Ox9 | Oxffffde28 | Oxffffde30
| 14 | Ox9 | Oxffffde30 | Oxffffde30
| ... | ... | ... | ...
-
如果想觀察三個寄存器的每一步的變化配合
gdb stepi
可以重復上述步驟。 -
部分過程截圖。
-
由上圖可以看到三個寄存器的初始值
-
由上圖可以看到三個寄存器在執行完第一條指令之后的內容的變換
注意:在64位中
rip
就是eip
,rbp
就是ebp
,rsp
就是。