Linux下程序的機器級表示學習心得


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得到匯編代碼。(此時的匯編代碼有以.開頭的代碼,刪除它們之后就是正常書中給出的匯編代碼。

  • 得到的匯編代碼

  • 使用gdbbt/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返maincall調用的位置

  • 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匯編中通過兩條指令完成,即:leaveret。這兩條指令更直白點就相當於: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語言文件編譯成可調試匯編的可執行文件。

  • 通過調試過程中的stepiprint /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就是eiprbp就是ebprsp就是。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM