Linux下簡單C語言小程序的反匯編分析


韓洋
原創作品轉載請注明出處
《Linux內核分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000

寫在開始,本文為因為參加MOOC相關課程而寫的作業,如有疏漏,還請指出。

選了一門Linux內核分析課程,因為閱讀內核代碼中或多或少要涉及到At&T匯編代碼的閱讀,所以這里寫下一個對一個簡單C命令行程序的反匯編分析過程,一方面完成作業,另一方面當作練手。下面開始:

 

1、編寫我們的C語言小程序

這里我們使用簡單的例子,代碼如下:

 1 #include <stdio.h>
 2 
 3 int exG(int x)
 4 {
 5     return x + 5;
 6 }
 7 
 8 int exF(int x)
 9 {
10     return exG(x);
11 }
12 
13 int main(void)
14 {
15     return exF(10) + 2;
16 }

使用vim等編輯器寫入上述代碼,保存到main.c,然后使用下面命令生成匯編源文件:
x86系統:
$gcc -S -o main.s main.c
x64系統:
$gcc -m32 -S -o main.s main.c
因為我們這里以32位平台為例子,所以在x64機器上要加上-m32來使GCC生成32位的匯編源文件。

2、處理源文件

執行完上述命令后,當前目錄下就會有一個main.s的文件,使用vim打開,不需要的鏈接信息[以"."開頭的行],得到如下匯編代碼:

 1 exG:
 2     pushl    %ebp
 3     movl    %esp, %ebp
 4     movl    8(%ebp), %eax
 5     addl    $5, %eax
 6     popl    %ebp
 7     ret
 8 exF:
 9     pushl    %ebp
10     movl    %esp, %ebp
11     pushl    8(%ebp)
12     call    exG
13     addl    $4, %esp
14     leave
15     ret
16 main:
17     pushl    %ebp
18     movl    %esp, %ebp
19     pushl    $10
20     call    exF
21     addl    $4, %esp
22     addl    $2, %eax
23     leave
24     ret

可以看到這個文件里是GCC幫我們生成的匯編代碼,這里需要說明下AT&T格式和intel格式,這兩種格式GCC是都可以生成的,如果要生成intel格式的匯編代碼,只需要加上 -masm=intel選項即可,但是Linux下默認是使用AT&T格式來書寫匯編代碼,Linux Kernel代碼中也是AT&T格式,我們要慢慢習慣使用AT&T格式書寫匯編代碼。這里最需要注意的AT&T和intel匯編格式不同點是:

AT&T格式的匯編指令是“源操作數在前,目的操作數在后”,而intel格式是反過來的,即如下:
AT&T格式:movl %eax, %edx
Intel格式:mov edx, eax
表示同一個意思,即把eax寄存器的內容放入edx寄存器。這里需要注意的是AT&T格式的movl里的l表示指令的操作數都是32位,類似的還是有movb,movw,movq,分別表示8位,16位和64位的操作數。更具體的AT&T匯編語法請執行Google或者查閱相關書籍。

3、匯編代碼分析

下面開始分析匯編代碼,運行程序后,C Runtime會在進行一系列准備工作后把我們讓eip指向我們的main函數開始執行,所以這里從main開始分析:

首先進入gdb調試環境:
在我們的機器上輸入如下命令生成帶有調試信息的elf文件,然后進入gdb進行調試:
$gcc -m32 -g -o main main.c
$gdb main -tui -q
進入gdb后,輸入layout asm切換到反匯編視圖,同時在main函數處下斷點:
(gdb)layout asm
(gdb)b main

然后我們使用
(gdb)si
來逐條指令執行並觀察寄存器變化情況,如圖:

對於main函數:

逐條指令執行
(gdb)si

pushl    %ebp
movl    %esp, %ebp
...

這兩條是Prolog,其作用包含保存當前的棧環境,以確保函數能正確返回和為當前函數開辟新的棧空間。這兩句的執行效果是把當前的ebp值入棧,再把ebp入棧后的esp中的值放入ebp。此時,esp和ebp都指向同一個內存地址。
這里需要說明的是入棧和出棧操作,在intel的x86架構上,棧是從高地址向低地址增長,所以:

入棧等價於:1、esp先下移留出對應的空間;2、把相應數值放入剛剛留出的空間完成入棧

出棧等價於:1、從當前esp指向內存取出數值;2、esp向上移動,釋放相應空間

此時棧中的情況如下如所示:[從這里開始,下圖中每個空格皆表示4字節內存空間]

圖1

繼續逐條指令執行

1 pushl    $10
2 call    exF
3 addl    $4, %esp
4 ....

pushl $10,當前esp先減4,然后把寬度為4直接的數值10放入esp當前指向的內存中。

call  exF ,函數調用指令,首先把當前eip的值[當前eip指向第三條指令,即addl $4, %esp]入棧,然后跳轉到exF函數的第一條指令開始執行。
此時棧中的情況如下如所示:
圖2

對於exF函數:

逐條指令執行
(gdb)si

1 pushl    %ebp
2 movl    %esp, %ebp
3 pushl    8(%ebp)
4 call    exG
5 addl $4, %esp
6 ....

這里前條指令和main函數的頭兩條指令作用相同,保存當前棧環境,為exF函數開辟新的棧空間 

pushl 8(%ebp),該指令把當前ebp中的數值加8后作為內存地址,並把該內存地址指向的內存空間內的數值"10"放入棧中。[參考圖2可以發現其實就是把調用函數是傳入的參數入棧]
call exG,函數調用指令,當前eip入棧后,跳轉到exG函數的第一條指令執行。

此時棧中的情況如下如所示:

圖3

對於exG函數:

逐條指令執行
(gdb)si

1 pushl    %ebp
2 movl    %esp, %ebp
3 movl    8(%ebp), %eax
4 addl    $5, %eax
5 popl    %ebp
6 ret

首先依然是函數前言(Prolog),保存棧環境,開辟新的棧空間

此時棧中的情況如下如所示:

圖4

此時GDB里使用bt 查看運行棧情況如下圖:

movl 8(%ebp),%eax 該指令把當前ebp中的數值加8后作為內存地址,並把該內存地址指向的內存空間內的數值“10”放入eax寄存器中。[參照圖4可以發現就是把調用函數是傳入的參數放入eax寄存器]
addl $5, %eax AT&T匯編語言中$符號后面跟上數字表示一個立即數,這里即為把eax中的值加上5,再放回eax,此時eax的值為15.
popl %ebp,從棧中獲取舊的esp值,並放入ebp寄存器。[這里之所以沒有再加上一條movl %ebp, %esp是因為函數中esp的值並沒有改變,依然指向存放舊esp值的內存空間]
ret 等價於pop eip,從當前棧頂,即esp所指內存處獲取值,作為eip,然后跳轉到eip中存放的地址繼續執行。
此時棧中情況如圖:
圖5

到這里,函數exG已經返回,其返回值存儲在eax寄存器中,即返回值為15

 

返回到函數exF中

1 ...
2 addl    $4, %esp
3 leave
4 ret

程序從上述指令開始繼續執行,
addl $4, %esp 回收棧空間,棧空間收縮4個字節,
leave,等價於 如下兩條指令
    movl %ebp, %esp
    pop %ebp
即函數結語[EpiLog],釋放exF函數使用的棧空間,此時棧中情況如圖:
圖6

再接着是ret指令,該指令執行后,函數exF返回,程序回到main函數繼續執行,此時棧中情況如圖:

圖7

此時eax中存放的是函數exF的返回值,即15

 

回到main函數繼續執行

1 ...
2 addl    $4, %esp
3 addl    $2, %eax
4 leave
5 ret

addl $4, %esp 棧收縮4個字節,回收棧空間
addl $2, %eax 此時eax中的值是main函數調用函數exF的得到的返回值,即15,本條指令將eax中的值加2后放回eax,執行后eax中的值為17
leave 函數結語,本條指令執行后,ebp的值為圖7中黑色Old EBP表示的值,esp指向圖7中黑色Old ebp所在內存空間的上一個內存空間,該處存放的是指向CRT調用main函數后緊接的指令的所在的內存地址
ret main函數返回

 

4、總結

計算機工作的過程實際上就是“取指令,執行指令”的循環,程序在執行時被裝入內存,計算機從內存中某個位置開始讀取指令按照一定邏輯順序執行,直到程序結束。在執行過程中根據需要為程序中各個模塊在內存中開辟一定的空間[如棧,堆],運行棧對應函數調用十分重要,函數參數和自動變量都存儲於運行棧中。計算機從內存的什么地方開始執行指令完全由cpu中指令指針寄存器[EIP]中的值決定,並不會區分內存中什么地方是代碼段,什么地方是數據段。


免責聲明!

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



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