標籤:馮諾依曼 彙編 編譯器
劉子健 原創作品轉載請註明出處《Linux核心分析》MOOC課程http://mooc.study.163.com/course/USTC-1000029000
對一下代碼進行反組譯碼分析:
int g(int x){ return x + 42;} int f(int x){ return g(x);} int main(void){ return f(42) + 42;}
我的主機是64位的Linux,所以使用的反組譯碼代碼也是64-bits的.
.file"2015_03_01.c".text.globlg.typeg, @functiong:.LFB0:.cfi_startprocpushq%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq%rsp, %rbp.cfi_def_cfa_register 6movl%edi, -4(%rbp)movl-4(%rbp), %eaxaddl$42, %eaxpopq%rbp.cfi_def_cfa 7, 8ret.cfi_endproc.LFE0:.sizeg, .-g.globlf.typef, @functionf:.LFB1:.cfi_startprocpushq%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq%rsp, %rbp.cfi_def_cfa_register 6subq$8, %rspmovl%edi, -4(%rbp)movl-4(%rbp), %eaxmovl%eax, %edicallgleave.cfi_def_cfa 7, 8ret.cfi_endproc.LFE1:.sizef, .-f.globlmain.typemain, @functionmain:.LFB2:.cfi_startprocpushq%rbp.cfi_def_cfa_offset 16.cfi_offset 6, -16movq%rsp, %rbp.cfi_def_cfa_register 6movl$42, %edicallfaddl$42, %eaxpopq%rbp.cfi_def_cfa 7, 8ret.cfi_endproc.LFE2:.sizemain, .-main.ident"GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
反組譯碼得到的代碼裡面有很多提示資訊,提示資訊以 . 開頭,程式執行時這些提示資訊不是指令,我們在這個反組譯碼範例裡面可以精簡代碼,把這些提示資訊刪除.有些資訊不能剔除,這些資訊是編譯器必須的,否則你過不了編譯連結.
下面是精簡後的反組譯碼代碼:以下代碼可以通過 gcc ./2015_03_01.s -o ./a.out
.text.globlg.typeg, @functiong:pushq%rbpmovq%rsp, %rbpmovl%edi, -4(%rbp)movl-4(%rbp), %eaxaddl$42, %eaxpopq%rbpret.sizeg, .-g.globlf.typef, @functionf:pushq%rbpmovq%rsp, %rbpsubq$8, %rspmovl%edi, -4(%rbp)movl-4(%rbp), %eaxmovl%eax, %edicallgleaveret.sizef, .-f.globlmain.typemain, @functionmain:pushq%rbpmovq%rsp, %rbpmovl$42, %edicallfaddl$42, %eaxpopq%rbpret.sizemain, .-main
關於基本彙編指令的分析,我之前有筆記,可以去看這裡:
http://blog.csdn.net/cinmyheart/article/details/25558911
我們這裡著重分析反組譯碼代碼:
g: , f:, main: 均用來指示函數的入口.
對於函數main.
首先壓棧,pushq 指令將rsp寄存器的值減去一個指標長度,在64-bits機器上即8byte,然後將 rbp寄存器的值寫入到rsp指向的地址處.
movq %rsp, %ebp指令則將rsp寄存器的值賦值給rbp寄存器.這樣一來,屬於main函數的棧地區便構建好了.
接著movl 把立即數42賦值給寄存器edi, 然後call指令調用函數f.函數f的返回值會儲存在eax寄存器中,等待f調用完之後,會把eax寄存器的值和立即數42相加,並儲存在eax寄存器中.最後把rbp寄存器處的值彈棧.然後ret指令返回.
---------------------------------------------------------------------------------
call f
指令就相當於
push %eip #把當前指令指標寄存器壓棧,然後跳轉到f處
jump f
---------------------------------------------------------------------------------
ret 指令就相當於
popl %eip #把當前esp寄存器指向地址處的值,賦值給eip
然後把esp寄存器的值減去一個指標長度,即8-byte
---------------------------------------------------------------------------------
看看函數f都幹了神馬.
還是和上面介紹main函數一樣的"老規矩",構建函數f的堆棧,
pushq %rbp
movq %rsp, %rbp
接著使用subq $8, %rsp把rsp寄存器的值減去8.
接著把edi寄存器的值賦值給rbp寄存器指向地址處減去4byte的地址處
緊接著,把這個地址處的值賦值給eax寄存器.
把eax寄存器的值又賦值給edi寄存器(其實我想說,這不是嚇折騰麼...這編譯器啊..這期間edi寄存器的值沒變)
然後調用函數g
一句話概括就是把edi寄存器的值加上42賦值給eax寄存器,然後返回.(不改變edi寄存器的值)
闡明自己對“電腦是如何工作的”理解:
對於正常化後的程式指令,逐一的對程式指令進行"解釋處理".不同的CPU,可能有不同的彙編指令集,比方說Intel -- X86 /X64平台,ARM平台,PowerPC等等,但是他們最基本的的思想都是近似的--馮諾依曼體繫結構.
數字電腦的數制採用二進位;電腦應該按照程式順序執行
-----------------------
通過反組譯碼一個簡單的C程式,分析彙編代碼理解電腦是如何工作的