奶头挺立呻吟高潮av全片,成人试看120秒体验区,性欧美极品v,A片高潮抽搐揉捏奶头视频

C語(yǔ)言

調(diào)用C函數(shù)

時(shí)間:2025-02-02 10:11:10 C語(yǔ)言 我要投稿

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

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

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

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

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

  int test()

  {

  int i = 0;

  i = 1 + 2;

  return i;

  }

  int main()

  {

  test();

  return 0;

  }

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

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

  objdump工具命令:

  objdump -d test

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

  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í)是函數(shù)main的地址。一共16個(gè)數(shù),16 * 4 = 64,對(duì)!這就是64位地址寬度啦。

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

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

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

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

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

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

  pushq $0x0000000000400499

  jmpq 400474

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

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

  pushq $0x0000000000400499

  pushq $0x0000000000400474

  retq

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

  調(diào)用約定

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

  stdcall

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

  如:

  int function(int first,int second)

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

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

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

  cdecl

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

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

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

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

  fastcall

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

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

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

  參數(shù)傳遞方式

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

  寄存器傳遞

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

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

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

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

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

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

【調(diào)用C函數(shù)】相關(guān)文章:

C/C++函數(shù)調(diào)用的方式07-29

C++調(diào)用C函數(shù)的方法05-21

java調(diào)用c函數(shù)的實(shí)例09-16

C語(yǔ)言函數(shù)的遞歸調(diào)用08-26

C語(yǔ)言函數(shù)的運(yùn)用及調(diào)用10-09

C函數(shù)的調(diào)用過(guò)程07-15

C++如何調(diào)用matlab函數(shù)06-29

C語(yǔ)言函數(shù)的遞歸和調(diào)用08-22

C語(yǔ)言函數(shù)調(diào)用與參數(shù)傳遞08-05

主站蜘蛛池模板: 临桂县| 灵丘县| 柘城县| 黄冈市| 芜湖市| 精河县| 吉安市| 宜宾县| 顺平县| 虎林市| 廉江市| 长顺县| 庆阳市| 广宗县| 陵川县| 太仆寺旗| 清新县| 融水| 民乐县| 新竹县| 陇西县| 色达县| 岳阳市| 滁州市| 天祝| 靖边县| 个旧市| 彭山县| 和静县| 营口市| 灵武市| 鹤壁市| 和顺县| 桂东县| 寿阳县| 钟祥市| 通渭县| 佳木斯市| 德格县| 溧水县| 临武县|