九色国产,午夜在线视频,新黄色网址,九九色综合,天天做夜夜做久久做狠狠,天天躁夜夜躁狠狠躁2021a,久久不卡一区二区三区

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
匯編調(diào)用C函數(shù)

來自: 圖靈社區(qū)

內(nèi)容選自圖靈社區(qū)電子書《一個64位操作系統(tǒng)的實現(xiàn)》

作者:田宇 

鏈接:http://www.ituring.com.cn/article/192591(點擊尾部閱讀原文前往)


比如:從系統(tǒng)引導(dǎo)過程中的匯編程序跳轉(zhuǎn)到系統(tǒng)主函數(shù)中,或者在中斷處理的匯編代碼中跳轉(zhuǎn)到中斷處理函數(shù)(傳說中的中斷上部), 這些過程都是從匯編程序跳轉(zhuǎn)到C程序的,其中不可缺少的有:調(diào)用約定,參數(shù)傳遞方式,函數(shù)調(diào)用方式等。因為這些過程都是在系統(tǒng)內(nèi)核中,所以,我們講解的是GNU C語言和AT&T匯編語言。話不多說,下面讓我們逐一介紹。


函數(shù)的調(diào)用方式


函數(shù)的調(diào)用方式其實沒那么復(fù)雜,基本上就是jmp、call、ret或者他們的變種而已。讓我們先看下面的程序。


int test()

{

    int i = 0;

    i =  1 + 2;

    return i;

}


int main()

{

    test();

    return 0;

}  


這段程序基本上沒有什么難點,很簡單,對吧?唯一要注意的地方是main函數(shù)的返回值,這里個人建議大家要使用int類型作為主函數(shù)的返回值,而不要使用void,或者其他類型。雖然,在主函數(shù)執(zhí)行到return 0之后就跟我們沒有什么關(guān)系了。但是,有的編譯器要求主函數(shù)要有個返回值,或者,在某些場合里,系統(tǒng)環(huán)境會用到主函數(shù)的返回值??紤]到上述原因,要使用int類型作為主函數(shù)的返回值,如果處于某個特殊的或者可預(yù)測的環(huán)境下,那就無所謂了。


說了這么多,反匯編一下這段代碼,看看匯編語言是怎么調(diào)用test函數(shù)的。工具objdump,用于反匯編二進制程序,它有很多參數(shù),可以反匯編出各類想要的信息。


objdump工具命令:


objdump -d test


下面是反匯編后的部分代碼,把相關(guān)的系統(tǒng)運行庫等一些與上面C程序不相關(guān)的代碼忽略掉。經(jīng)過刪減后的反匯編代碼如下:


0000000000400474 :

  400474:    55                       push   %rbp

  400475:    48 89 e5                 mov    %rsp,%rbp

  400478:    c7 45 fc 00 00 00 00     movl   $0x0,-0x4(%rbp)

  40047f:    c7 45 fc 03 00 00 00     movl   $0x3,-0x4(%rbp)

  400486:    8b 45 fc                 mov    -0x4(%rbp),%eax

  400489:    c9                       leaveq 

  40048a:    c3                       retq   


000000000040048b

:

  40048b:    55                       push   %rbp

  40048c:    48 89 e5                 mov    %rsp,%rbp

  40048f:    b8 00 00 00 00           mov    $0x0,%eax

  400494:    e8 db ff ff ff           callq  400474

  400499:    b8 00 00 00 00           mov    $0x0,%eax

  40049e:    c9                       leaveq 

  40049f:    c3                       retq   


大家先看000000000040048b :這一行,這里就是主函數(shù),前面的000000000040048b其實是函數(shù)main的地址。一共16個數(shù),16 * 4 = 64,對!這就是64位地址寬度啦。


乍一看,有好多個“%”符號,還記得2.2.1節(jié)里講的AT&T匯編語法嗎?這就是那里面說——引用寄存器的時候要在前面加“%”符號。


還有一些匯編指令的后綴,如:“l(fā)”、“q”。“l(fā)”的意思是雙字(long型),“q”的意思是四字(64位寄存器的后綴就是這個)。


如果您仔細觀察,是不是會發(fā)現(xiàn)有些寄存器rbp,rsp等,感覺會跟ebp和esp有關(guān)系呢?答對了,esp寄存器是32位寄存器,而rsp寄存器是64位寄存器。這是Intel對寄存器的一種向下繼承性,從最開始一字節(jié)的al,ah,到兩字節(jié)的ax(16位),四字節(jié)的eax(32位),再到八字節(jié)的rax(64位),寄存器的長度在不斷的擴展,對于相關(guān)指令的使用,也從“b”、“l(fā)”,“q”,也是不斷的向下繼承或擴展。


這里有一條指令leaveq,它等效于 movq %rbp, %rsp; popq %rbp;


callq 400474 這句的意思就是跳轉(zhuǎn)到test函數(shù)里執(zhí)行。其實匯編調(diào)用C函數(shù)就這么簡單,如果把這條callq指令改成jmpq指令也是可以的。這要從call和jmp的區(qū)別上說起,call會把在其之后的那條指令的地址壓入棧,在上面反匯編后的代碼中,就是0000000000400499,然后再跳轉(zhuǎn)到test函數(shù)里執(zhí)行。而jmpq就不會把地址0000000000400499壓入棧中。當(dāng)函數(shù)執(zhí)行完畢,調(diào)用retq指令返回的時候,會把棧中的返回地址彈出到rip寄存器中,這樣就返回到main函數(shù)中繼續(xù)執(zhí)行了。


實現(xiàn)jmpq代替callq的偽代碼如下所示:


pushq    $0x0000000000400499  

jmpq     400474  


對于callq 400474 這條指令也可以使用retq來實現(xiàn)。它的實現(xiàn)原理是:指令retq會將棧中的返回地址彈出,并放入到rip寄存器中,然后處理器從rip寄存器所指的地址內(nèi)取指令后繼續(xù)執(zhí)行。根據(jù)這個原理,可以先將返回地址0000000000400499壓入棧中。然后再將test函數(shù)的入口地址0000000000400474壓入棧中,接著使用retq指令,以調(diào)用返回的形式,從main函數(shù)“返回”到test函數(shù)中。


實現(xiàn)retq代替callq的偽代碼如下所示:


pushq $0x0000000000400499

pushq $0x0000000000400474

retq  


這些看起來是不是沒有想象的那么難?其實把匯編的原理掌握清楚了,這些都是可以靈活運用的,希望這段內(nèi)容能啟發(fā)讀者的靈感~!


調(diào)用約定


對于不同的公司,不同的語言以及不同的需求,都是用各自不同的調(diào)用約定,而且他們往往差異很大。在IBM兼容機對市場進行洗牌后,微軟操作系統(tǒng)和編程工具占據(jù)了統(tǒng)治地位,除了微軟之外,還有零星的一些公司,以及開源項目GCC,都各自維護著自己的標準。下面是比較流行的幾款調(diào)用標準,咱們寫的大多數(shù)程序都出自這個標準之一。


  • stdcall


1、在進行函數(shù)調(diào)用的時候,函數(shù)的參數(shù)是從右向左依次放入棧中的。


如:


int function(int first,int second)  


這個函數(shù)的參數(shù)入棧順序,首先是參數(shù)second,然后是參數(shù)first。


2、函數(shù)的棧平衡操作是由被調(diào)用函數(shù)執(zhí)行的,使用的指令是 retn X,X表示參數(shù)占用的字節(jié)數(shù),CPU在ret之后自動彈出X個字節(jié)的堆??臻g。例如上面的function函數(shù),當(dāng)我們把function的函數(shù)參數(shù)壓入棧中后,當(dāng)function函數(shù)執(zhí)行完畢后,由function函數(shù)負責(zé)將傳遞給它的參數(shù)first和second從棧中彈出來。


3、在函數(shù)名的前面用下劃線修飾,在函數(shù)名的后面由@來修飾,并加上棧需要的字節(jié)數(shù)。如上面的function函數(shù),會被編譯器轉(zhuǎn)換為_function@8。


  • cdecl


1、在進行函數(shù)調(diào)用的時候,和stdcall一樣,函數(shù)的參數(shù)是從右向左依次放入棧中的。


2、函數(shù)的棧平衡操作是由調(diào)用函數(shù)執(zhí)行的,這點是與stdcall不同之處。stdcall使用retn X平衡棧,cdecl則使用leave、pop、增加棧指針寄存器的數(shù)據(jù)等方法平衡棧。


3、每一個調(diào)用它的函數(shù)都包含有清空棧的代碼,所以編譯產(chǎn)生的可執(zhí)行文件會比調(diào)用stdcall約定產(chǎn)生的文件大。


cdecl是GCC的默認調(diào)用約定。但是,GCC在x64位系統(tǒng)環(huán)境下,使用寄存器作為函數(shù)調(diào)用的參數(shù)。按照從左向右的順序,頭六個整型參數(shù)放在寄存器RDI, RSI, RDX, RCX, R8和R9上,同時XMM0到XMM7用來放置浮點變元,返回值保存在RAX中,并且由調(diào)用者負責(zé)平衡棧。


  • fastcall


1.函數(shù)調(diào)用約定規(guī)定,函數(shù)的參數(shù)在可能的情況下使用寄存器傳遞參數(shù),通常是前兩個 DWORD類型的參數(shù)或較小的參數(shù)使用ECX和EDX寄存器傳遞,其余參數(shù)按照從右向左的順序入棧。


2、函數(shù)的棧平衡操作是由被調(diào)用函數(shù)在返回之前負責(zé)清除棧中的參數(shù)。


還有很多調(diào)用規(guī)則,如:thiscall、naked call、pascal等,有興趣的讀者可以自己去研究一下。


參數(shù)傳遞方式


函數(shù)參數(shù)的傳遞方式無外乎兩種,一種是通過寄存器傳遞,另一種是通過內(nèi)存?zhèn)鬟f。這兩種傳遞方式在我們平時的開發(fā)中并不會被關(guān)注,因為不在特殊情況下,這兩種傳遞方式,都可以滿足要求。但是,我們要寫的是操作系統(tǒng),在操作系統(tǒng)里面有很多苛刻的環(huán)境要求,這使得我們不得不了解這些參數(shù)傳遞方式,來解決這些問題。


  • 寄存器傳遞


寄存器傳遞就是將函數(shù)的參數(shù)放到寄存器里傳遞,而不是放到棧里傳遞。這樣的好處主要是執(zhí)行速度快,編譯后生成的代碼量少。但只有少部分調(diào)用規(guī)定默認是通過寄存器傳遞參數(shù),大部分編譯器是需要特殊指定使用寄存器傳遞參數(shù)的。


在X86體系結(jié)構(gòu)下,系統(tǒng)調(diào)用一般會使用寄存器傳遞,由于作者看過的內(nèi)核種類有限,也不能確定所有的內(nèi)核都是這么處理的,但是Linux內(nèi)核肯定是這么做的。因為應(yīng)用程序的執(zhí)行空間和系統(tǒng)內(nèi)核的執(zhí)行空間是不一樣的,如果想從應(yīng)用層把參數(shù)傳遞到內(nèi)核層的話,最方便快捷的方法是通過寄存器傳遞參數(shù),否則需要使用很大的周折才能把數(shù)據(jù)傳遞過去,原因會在以后的章節(jié)中詳細講述。


  • 內(nèi)存?zhèn)鬟f


內(nèi)存?zhèn)鬟f參數(shù)很好理解,在大多數(shù)情況下參數(shù)傳遞都是通過內(nèi)存入棧的形式實現(xiàn)的。


在X86體系結(jié)構(gòu)下的Linux內(nèi)核中,中斷或異常的處理會使用內(nèi)存?zhèn)鬟f參數(shù)。因為,在中斷產(chǎn)生后,到中斷處理的上半部,中間的過渡代碼是用匯編實現(xiàn)的。匯編跳轉(zhuǎn)到C語言的過程中,C語言是用堆棧保存參數(shù)的,為了無縫銜接,匯編就需要把參數(shù)壓入棧中,然后再跳轉(zhuǎn)到C語言實現(xiàn)的中斷處理程序中。


以上這些都是在X86體系結(jié)構(gòu)下的參數(shù)傳遞方式,在X64體系結(jié)構(gòu)下,大部分編譯器都使用的是寄存器傳遞參數(shù)。因此,內(nèi)存?zhèn)鬟f和寄存器傳遞的區(qū)別就不太重要了。



●本文編號58,以后想閱讀這篇文章直接輸入58即可。

●輸入m可以獲取到文章目錄


推薦15個技術(shù)類公眾微信

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
c++函數(shù)調(diào)用過程分析(匯編語言編碼是ATT模式 可以手動修改為intel風(fēng)格依個人喜好我個人更偏向intel風(fēng)格)
讀懂操作系統(tǒng)(x64)之堆棧幀(過程調(diào)用)
x86_64架構(gòu)下的函數(shù)調(diào)用及棧幀原理
通過一段匯編,加深對寄存器ESP和EBP的理解
自己實現(xiàn)IDispatch::Invoke方法
【新提醒】[教程]逆向反匯編第二課
更多類似文章 >>
生活服務(wù)
熱點新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服