<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      03-程序的機(jī)器級表示

      在編譯結(jié)束、匯編開始之前,會生成.s程序,這個程序中存放的是代碼到匯編的匯編指令。然后再將.s文件通過匯編器生成.o二進(jìn)制文件。我們來做個實驗看看一個代碼編程匯編是什么樣子,然后二進(jìn)制.o文件通過objdump反匯編后是什么樣子(這里需要說明一下,objdump是一個反匯編工具。匯編器將匯編代碼翻譯成二進(jìn)制的機(jī)器代碼,機(jī)器代碼無法被查看,那么反匯編器就是將機(jī)器代碼翻譯成匯編代碼)

      long mult2(long, long);
      
      void mulstore(long x, long y, long *dest) {
          long t = mult2(x, y);
          *dest = t;
      }
      

      如上實例代碼通過gcc指令編譯生成.s匯編代碼:

      gcc -Og -S mstore.c
      

      生成匯編.s代碼如下:
      -w952

      其中以.開頭的都是匯編鏈接相關(guān)的偽指令,我們將其忽略,剩余的代碼即為邏輯相關(guān):
      -w995

      pushq指令執(zhí)行的操作是,將寄存器rbx的值壓棧,并且棧頂指針由高地址向低地址偏移:
      -w912

      如上圖,pushq %rbx等價于:

      subq $8, %rsp
      movq %rbx, (%rsp)
      

      首先給棧指針減去直接數(shù)8,即讓地址發(fā)生一個字偏移,然后將需要存放的值由rbx寄存器移動至rsp棧指針指向的區(qū)域。程序之所以將rbx寄存器的值壓棧,主要是進(jìn)行保存操作,以便于匯編代碼執(zhí)行完畢后恢復(fù)rbx的值,如上述代碼中的popq %rbx,將棧頂元素彈出。

      關(guān)于匯編,需要補(bǔ)充說明一些內(nèi)容,在Inter x86-64的處理器中包含了16個通用目的的寄存器,這些寄存器用來存放整數(shù)數(shù)據(jù)和指針。分別如下:
      -w663

      可以看到16個寄存器名字均以%r開頭,在詳細(xì)介紹寄存器的功能之前,我們首先需搞清楚兩個概念:調(diào)用者保存寄存器被調(diào)用者保存寄存器

      程序中經(jīng)常會有某個函數(shù)調(diào)用另一個函數(shù),那么發(fā)起調(diào)用的就是調(diào)用者,被調(diào)用的函數(shù)就是被調(diào)用者。
      -w1304

      上圖理論上提到了有兩種保存方式,調(diào)用者保存和被調(diào)用者保存,通常由于寄存器的數(shù)量是有限的,我們在對某個函數(shù)進(jìn)行調(diào)用的時候,被調(diào)用的函數(shù)可能也會使用寄存器,因此寄存器在源調(diào)函數(shù)中的值可能會被覆蓋修改,所以我們需要保存相關(guān)值,保存方法如下:
      -w1263

      對于具體使用調(diào)用者保存寄存器還是被調(diào)用者保存寄存器的方式,不同的寄存器有不同的措施,以下是使用調(diào)用者保存寄存器和被調(diào)用者保存寄存器策略的劃分:

      -w1096

      實際上rbx寄存器只能使用被調(diào)用者保存寄存器的策略,回到剛才的代碼可以看到:
      -w970

      上面的代碼中,相對于main函數(shù)而言,mulstore函數(shù)是被調(diào)用者,當(dāng)進(jìn)入被調(diào)用者時,后續(xù)的一系列操作都將可能對rbx寄存器進(jìn)行修改,因此我們會首先將rbx寄存器的值入棧,然后再程序返回前通過popq %rbx將棧頂元素彈出至rbx寄存器中,達(dá)到恢復(fù)的目的。我們可以看到第二句執(zhí)行了movq指令,將寄存器rdx中的值存放至rbx中,那有同學(xué)就要問了,我們怎么知道我們要去操作rdx寄存器呢?為什么不能是rsi寄存器或者rax寄存器?如果有這樣的問題,說明對每個寄存器特定功能不了解,接下來我們先去了解幾個主要寄存器的功能:
      -w826
      -w1110

      可以看到rax一般用于存放要返回的值,rdi、rsirdx、rcx、r8r9分別用于存放當(dāng)前函數(shù)的六個參數(shù)值,這七個寄存器均采用調(diào)用者保存寄存器策略,rsp寄存器在上面提到過,是棧頂指針寄存器。

      指令通常都有后綴,如pushq中的q,代表著8字節(jié),更多匯編碼后綴如下圖所示:
      -w1220

      繼續(xù)分析我們的代碼,根據(jù)上面不同寄存器具有不同功能得知,mulstore函數(shù)的三個參數(shù)分別保存至rdirsi、rdx中:
      -w1259

      那么call則是調(diào)用mult2函數(shù),可以看到rbx寄存器外加了括號,這類似C語言中指針解引用,說明rbx存放的是地址,我們通過地址尋找到具體地址中存放的值,即代碼中dest指針指向的值,然后將rax寄存器的值移動至rbx中,rax存放著mult2函數(shù)的返回值。最終將rbx寄存器通過popq恢復(fù)。

      一開始提到objdump反匯編操作,首先通過gcc -Og -c mstore.c生成mstore.o二進(jìn)制機(jī)器代碼文件,然后通過objdump -d mstore.o將二進(jìn)制機(jī)器代碼反匯編至匯編代碼,具體結(jié)果如圖,在我的CentOS 7.3中貼圖如下:
      -w1319

      -w1255

      寄存器指令,大多數(shù)指令包含兩部分:操作碼操作數(shù)。大多數(shù)指令具有一個或多個操作數(shù),ret返回指令則沒有操作數(shù)。在AT&T格式匯編中,立即數(shù)以$符號開頭,后跟一個C語言定義的整數(shù)。操作數(shù)是寄存器的情況,即使在64位的處理器上,不僅64位的寄存器可以作為操作數(shù),32位、16位甚至8位的寄存器都可以作為操作數(shù)。3寄存器帶小括號表示內(nèi)存引用。我們通常將內(nèi)存抽象成一個字節(jié)數(shù)組,當(dāng)需要從內(nèi)存中存取數(shù)據(jù)時,需要獲得目的數(shù)據(jù)的起始地址addr,以及數(shù)據(jù)長度b。為了簡便,通常會省略下標(biāo)b。
      -w1209

      有效地址是通過立即數(shù)與基址寄存器的值相加,再加上變址寄存器與比例因子的乘積。
      -w1285

      需要注意,比例因子s取值必須是1、2、4、8。實際上比例因子的取值是與源代碼中定義的數(shù)組類型的是相關(guān)的,編譯器會根據(jù)數(shù)組的類型來確定比例因子的數(shù)值,例如:定義char類型的數(shù)組,比例因子就是1,int類型,比例因子就是4,至于double類型比例因子就是8。

      尋址方式

      -w1034

      上圖中,RB基址寄存器RI變址寄存器。

      mov指令

      mov含有兩個操作數(shù),一個是源操作數(shù),另一個是目的操作數(shù)。源操作數(shù)可以是立即數(shù)、寄存器、內(nèi)存引用等。目的操作數(shù)是用來存放源操作數(shù)內(nèi)容的,因此目的操作數(shù)可為寄存器或內(nèi)存引用等,目的操作數(shù)不能是立即數(shù)。我們在mov指令后經(jīng)常看到movq、movw、movb等的形式,代表移動的位數(shù),指令mov的后綴一定要和寄存器的大小進(jìn)行匹配,如果是32位寄存器,就需要movl;如果是64位寄存器,就需要movq
      -w1027

      -w942

      需要注意,x86_64處理器有一條限制,mov指令的源操作數(shù)和目的操作數(shù)不能都是內(nèi)存的地址,當(dāng)需要將一個數(shù)從內(nèi)存的一個位置復(fù)制到另一個位 置時,需要兩條mov指令來完成;第一條指令將內(nèi)存源位置的數(shù)值加載到寄存器;第二條指令再將該寄存器的值寫入內(nèi)存的目的位置。
      -w1007

      mov指令源操作數(shù)是立即數(shù)Imm時,立即數(shù)只能32位補(bǔ)碼,對該32位源操作數(shù)進(jìn)行符號位擴(kuò)展,傳送至64位目的位置(可以是寄存器也可以是內(nèi)存)。
      -w1169

      如果要將64位補(bǔ)碼移動至寄存器(而不能是內(nèi)存)中,可以使用movabsq指令,該指令只能將64位補(bǔ)碼移動至寄存器中而非內(nèi)存,這里需要注意。

      mov指令如何修改目的寄存器的內(nèi)容?

      movabsq $0x0011223344556677, %rax,指的是將立即數(shù)存放至寄存器中,如下圖所示:
      -w1234
      現(xiàn)在要將一個只有8位的立即數(shù)-1復(fù)制到寄存器al當(dāng)中,那么首先什么是al寄存器?其實在寄存器發(fā)展當(dāng)中,隨著寄存器位數(shù)的增加,很多寄存器的低位依然保留原來的名字,高位衍生出新的名字具體如下:
      -w815

      所以al寄存器實際上就是rax寄存器的低8位,那么回到剛才,將8位立即數(shù)-1復(fù)制到寄存器al中,使用movb指令:movb $-1, %al,寄存器低8位發(fā)生改變,那么為什么是全F呢,因為-1的補(bǔ)碼是全F,寄存器存放的是立即數(shù)的補(bǔ)碼:
      -w1178

      那如果要將低16位的立即數(shù)-1復(fù)制到寄存器中,首先得復(fù)制ax寄存器,ax寄存器是rax寄存器的低16位,其次要用到命令movw,即movw $-1, %ax
      -w1163

      我們上面說到如果64位寄存器rax中,移入32位的立即數(shù),那么要進(jìn)行符號位擴(kuò)展,但是目前我們是將32位立即數(shù)移動至32位寄存器eax當(dāng)中,那么寄存器高4字節(jié)應(yīng)當(dāng)置0,這是x86_64處理器的規(guī)定,比如現(xiàn)在要將32位立即數(shù)-1復(fù)制到32位寄存器eax當(dāng)中,使用movl指令,movl $-1, %eax
      -w1206

      總結(jié)如下:
      -w1151

      以上介紹了mov指令的位擴(kuò)展等操作,但是都基于一個前提那就是源操作數(shù)與目的操作數(shù)的數(shù)位相同。

      當(dāng)源操作數(shù)位小于目的操作數(shù)的數(shù)位時,需要對目的操作數(shù)剩余字節(jié)進(jìn)行零擴(kuò)展或符號位擴(kuò)展,具體是哪種擴(kuò)展,與指令相關(guān)。零擴(kuò)展數(shù)據(jù)傳送指令有5條,指令后zzero的縮寫;符號位擴(kuò)展傳送指令有6條,指令后ssign的縮寫。接下來的第一個字母是源操作數(shù)大小,第二個字母表示目的操作數(shù)的大小,指令如圖所示:
      -w1065

      可以看到符號擴(kuò)展比零擴(kuò)展多一條4字節(jié)到8字節(jié)的擴(kuò)展指令movslq,為何零擴(kuò)展無movzlq?是因為movl指令可實現(xiàn)該擴(kuò)展,即我們上面提到的,如movl $-1, %eax,當(dāng)?shù)?code>32位使用F填充后,高32位必須置0,即movl實現(xiàn)了類似于movzlq的功能,所以無需指令movzlq。同時需要說明符號位擴(kuò)展中的cltq指令,該指令的源操作數(shù)總是寄存器eax,目的操作數(shù)總是寄存器rax,cltq的效果等價于執(zhí)行了movslq %eax, %rax,即將eax32位用符號位擴(kuò)展。

      數(shù)據(jù)傳送

      對一個執(zhí)行的程序而言,若要計算加法\(c = a+b\),那么需要將數(shù)據(jù)通過內(nèi)存總線和系統(tǒng)總線從內(nèi)存中寫入寄存器中,然后通過CPU內(nèi)部的邏輯運算單元ALU來計算ab的加法,將返回值寫給rax、eax、ax、al等相關(guān)位數(shù)寄存器(具體使用哪個和數(shù)據(jù)類型字寬有關(guān)),需要再次說明,之所以ALU的結(jié)果放到rax中,因為rax這個特定寄存器的功能就是用來存放返回值的。

      -w1214

      -w1251

      舉個例子,我們來看下如下代碼,分析其匯編執(zhí)行流程:

      -w1195

      我們使用gcc -Og -S exchange.c單獨對exchange函數(shù)進(jìn)行匯編,生成匯編代碼主要指令如下:

      exchange:
          movq    (%rdi), %rax
          movq    %rsi, (%rdi)
          ret
      

      根據(jù)之前的學(xué)習(xí),我們知道第一個參數(shù)存放的位置在rdi中,第二個參數(shù)存放的位置在rsi中(均為long四字類型)。所以xp指針指向的值的地址保存在rdi中,y的值存放在rsi中,函數(shù)exchange主要有三條指令實現(xiàn),包括兩條數(shù)據(jù)傳送指令和一條返回指令。

      -w1248

      -w1257

      此外,還有兩個數(shù)據(jù)傳送指令需要借助程序棧,程序棧本質(zhì)上是內(nèi)存中的一個區(qū)域。棧的增長方向是從高地址向低地址,因此,棧頂?shù)脑厥撬袟V性氐刂分凶畹偷?/strong>。根據(jù)慣例,棧是倒過來畫的,棧頂在圖的底部,棧底在頂部,rsp是棧頂寄存器。
      -w1418

      例如現(xiàn)在我們需要保存寄存器rax內(nèi)存儲的數(shù)據(jù)0x123,可以使用pushq指令把數(shù)據(jù)壓入棧內(nèi)。若要將數(shù)據(jù)彈出,則使用popq指令,這些指令只有一個操作數(shù)(壓入的數(shù)據(jù)源和彈出的數(shù)據(jù)目的)。
      -w1048

      我們首先來看下一個入棧的操作過程:

      • 首先指向棧頂?shù)募拇嫫鞯?code>rsp進(jìn)行一個減法操作,例如壓棧之前,棧頂指針rsp指向棧頂?shù)奈恢?,此處的?nèi)存地址0x108;壓棧的第一步就是寄存器rsp的值減8,此時指向的內(nèi)存地址是0x100。

      -w1237

      • 然后將需要保存的數(shù)據(jù)復(fù)制到新的棧頂?shù)刂?,此時,內(nèi)存地址0x100處將保存寄存器rax內(nèi)存儲的數(shù)據(jù)0x123。實際上pushq的指令等效于subqmovq這兩條指令。它們之間的區(qū)別是在于pushq這一條指令只需要一個字節(jié),而subqmovq這兩條指令需要8個字節(jié)。所以執(zhí)行subq %rax意味著執(zhí)行了兩個操作,首先是將棧頂?shù)刂窚p8,然后再將rax寄存器存放的值存放至棧頂指針rsp指向的位置。

      -w1195

      說到底,push指令的本質(zhì)還是將數(shù)據(jù)寫入到內(nèi)存中,那么與之對應(yīng)的pop指令就是從內(nèi)存中讀取數(shù)據(jù),并且修改棧頂指針。例如圖中這條popq指令就是將棧頂保存的數(shù)據(jù)復(fù)制到寄存器rbx中。

      那么pop操作也可分解為兩部分:

      • 首先從棧頂?shù)奈恢米x出數(shù)據(jù),復(fù)制到寄存器rbx(被調(diào)用者保存寄存器)。此時,棧頂指針rsp指向的內(nèi)存地址是0x100。

      -w1217

      • 然后將棧頂指針加8,pop后棧頂指針rsp指向的內(nèi)存地址是0x108。

      -w1463
      因此pop操作也可以等效movqaddq這兩條指令。實際上pop指令是通過修改棧頂指針?biāo)赶虻膬?nèi)存地址來實現(xiàn)數(shù)據(jù)刪除的,此時,內(nèi)存地址0x100內(nèi)所保存的數(shù)據(jù)0x123仍然存在,直到下次push操作,此處保存的數(shù)值才會被覆蓋。

      -w926

      leaq指令

      加載有效地址(load effective address)指令leaq實際上是movq指令的變形,它的指令形式是從內(nèi)存讀數(shù)據(jù)到寄存器當(dāng)中,但實際根本未引用內(nèi)存,它的第一個操作數(shù)看上去是內(nèi)存引用,但該指令并不是從指定位置讀入數(shù)據(jù),而是將有效地址寫入到目的操作數(shù)。
      -w1403
      -w1355

      以上為leaq指令將有效地址值寫入寄存器中的過程,即加載有效地址。leaq不僅可以加載有效地址,還可表示加法和有限乘法運算,對下述代碼進(jìn)行編譯:
      -w1297

      通過gcc -O1 -S a.c,得到的匯編代碼指令如下:

      scale:
              leaq    (%rdi,%rsi,4), %rax
              leaq    (%rdx,%rdx,2), %rdx
              leaq    (%rax,%rdx,4), %rax
              ret
      

      x存放在rdi寄存器中,y存放在rsi寄存器中,z存放在rdx寄存器中,那么第一條指令將rdi+4*rsi放入rax中,即表示x+4y;第二條指令表示將3z寫入rdx寄存器;第三條指令則是將rax存放的x+4y4rdx相加結(jié)果寫入rax,此時rax存放的是x+4y+12z的結(jié)果,最終rax寄存器作為返回值返回即可。
      -w1131

      leaq指令能執(zhí)行加法和有限的乘法,在編譯如上簡單的算術(shù)表達(dá)式時,是很有用處的。
      -w1351

      一元和二元操作、移位操作、特殊算術(shù)操作

      -w1195

      -w1195

      移位操作:
      -w1161
      -w1122

      我們根據(jù)具體代碼舉例說明移位操作,示例如下:
      -w1159

      我們分析一下這個代碼第3行,對應(yīng)的匯編指令:
      -w1132

      上圖rdx的值為z,rax通過第一條指令,存放3z的值,然后第二條指令左移4位等同于乘以\(2^4 = 16\),所以這兩條指令最終計算的是48z的值,存放至rax寄存器中。為什么編譯器不直接使用乘法指令來實現(xiàn)這個運算呢?主要是因為乘法指令的執(zhí)行需要更長的時間,因此編譯器在生成匯編指令時,會優(yōu)先考慮更高效的方式。

      一元、二元操作、移位操作指令總結(jié)
      -w983

      特殊算數(shù)操作指令
      -w1059

      條件碼

      關(guān)于條件碼寄存器的各個字段,之前博客也有介紹過(Link),主要以8086寄存器為例說明。

      條件碼寄存器其實也稱為標(biāo)志寄存器,其具有三種作用:

      • 用來存儲相關(guān)指令的某些執(zhí)行結(jié)果;
      • 用來為CPU執(zhí)行相關(guān)指令提供行為依據(jù);
      • 用來控制CPU的相關(guān)工作方式。

      -w1193
      -w1352

      以下是8086相關(guān)標(biāo)志位:
      -w1172

      -w1367

      條件碼寄存器(狀態(tài)寄存器)的值是由ALU在執(zhí)行算術(shù)和運算指令時寫入的,下圖中的這些算術(shù)和邏輯運算指令都會改變條件碼寄存器的內(nèi)容:
      -w1322

      對于不同的指令也定義了相應(yīng)的規(guī)則來設(shè)置條件碼寄存器。例如:

      • 邏輯操作指令xor進(jìn)位標(biāo)志(CF)溢出標(biāo)志(OF)會置0;
      • 對于inc加一指令和dec減一指令會設(shè)置溢出標(biāo)志(OF)零標(biāo)志(ZF),但不會改變進(jìn)位標(biāo)志(CF)。
        -w1384
        -w541

      關(guān)于條件碼使用,我們舉例看下如下代碼以及其匯編指令:
      -w1333

      gcc -Og -S a.c得到匯編指令為:

      comp:
              cmpq    %rsi, %rdi
              sete    %al
              movzbl  %al, %eax
              ret
      

      上面已經(jīng)介紹了cmp指令,cmp指令是根據(jù)兩個操作數(shù)的差來設(shè)置條件碼寄存器。cmp指令和減法指令sub類似,也是根據(jù)兩個操作是的差來設(shè)置條件碼,二者不同的是cmp指令只是設(shè)置條件碼寄存器,并不會更新目的寄存器的值。

      在這個例子中,指令sete根據(jù)需標(biāo)志(ZF)的值對寄存器al進(jìn)行賦值,后綴eequal的縮寫。如果零標(biāo)志等于1,指令sete將寄存器al置為1;如果零標(biāo)志等于0,指令sete將寄存器al置為0

      -w1221

      然后mov指令對寄存器al進(jìn)行零擴(kuò)展,最后返回判斷結(jié)果,存放至rax寄存器中。

      下面看一個復(fù)雜例子,代碼如下:
      -w1250

      轉(zhuǎn)成匯編指令后:
      -w1014

      -w1211
      -w1213

      判斷a<b是否為真,需要首先判斷a-b的值,a-b<0設(shè)置SF=1,反之SF=0,然后判斷是否正溢出或負(fù)溢出,溢出置OF=1,反之OF=0;計算SF^OF,若結(jié)果為1,則a<btrue;反之a<bfalse。所以,綜上可發(fā)現(xiàn),根據(jù)符號標(biāo)志(SF)和溢出標(biāo)志(OF)的異或結(jié)果,可以對a小于b是否為真做出判斷。更多相關(guān)set指令如下:
      -w1068

      跳轉(zhuǎn)指令

      接下來看下跳轉(zhuǎn)指令相關(guān)代碼及匯編指令:
      -w855

      通過cmp指令首先設(shè)置x<y對應(yīng)的標(biāo)志寄存器的符號標(biāo)志(SF)和溢出標(biāo)志(OF),然后跳轉(zhuǎn)指令進(jìn)行相應(yīng)的位運算來判斷其布爾值的真假,以此來判斷是否發(fā)生跳轉(zhuǎn)至.L2處,位運算計算方法如下圖:
      -w982

      只不過,相比于代碼中的x < y,匯編指令通過jge判斷x是否大于等于ygegreater >equal =的縮寫。對于代碼中的if-else語句,當(dāng)滿足條件時,程序洽著一條執(zhí)行路徑執(zhí)行,當(dāng)不滿足條件時,就走另外一條路徑。這種機(jī)制比較簡單和通用,但是在現(xiàn)代處理器上,它的執(zhí)行效率可能會比較低。針對這種情況,有一種替代的策略,就是使用數(shù)據(jù)的條件轉(zhuǎn)移來代替控制的條件轉(zhuǎn)移。還是針對兩個數(shù)差的絕對值問題,給出了另外一種實現(xiàn)方式,我們既要計算y-x的值,也要計算x-y的值,分別用兩個變量來記錄結(jié)果,然后再判斷xy的大小,根據(jù)測試情況來判斷是否更新返回值。這兩種寫法看上去差別不大,但第二種效率更高。具體如下所示:
      -w979

      上圖c前面這幾條指令都是普通的數(shù)據(jù)傳送和減法操作。cmovge是根據(jù)條件碼的某種組合來進(jìn)行有條件的傳送數(shù)據(jù),當(dāng)滿足規(guī)定的條件時,將寄存器rdx內(nèi)的數(shù)據(jù)復(fù)制到寄存器rax內(nèi)。在這個例子中,只有當(dāng)x大于等于y時,才會執(zhí)行這一條指令。
      -w1025
      更多傳送指令如下所示:
      -w962

      為什么基于條件傳送的代碼會比基于跳轉(zhuǎn)指令的代碼效率高呢?這里涉及到現(xiàn)代處理器通過流水線來獲得高性能。當(dāng)遇到條件跳轉(zhuǎn)時,處理器會根據(jù)分支預(yù)測器來猜測每條跳轉(zhuǎn)指令是否執(zhí)行,當(dāng)發(fā)生錯誤預(yù)測時,會浪費大量的時間,導(dǎo)致程序性能嚴(yán)重下降。

      循環(huán)

      do-while
      -w716
      -w947

      while
      -w1069

      對比一下forwhile的匯編代碼:
      -w966
      可以發(fā)現(xiàn)除了這一句跳轉(zhuǎn)指令不同,其他部分都是一致的。這兩個匯編代碼是采用-Og選項產(chǎn)生的。綜上所述,三種形式的循環(huán)語句都是通過條件測試和跳轉(zhuǎn)指令來實現(xiàn)。以上則是三種循環(huán)的示例說明。

      swich語句

      -w991
      對于上面的代碼,匯編代碼如下:
      -w1008

      cmpq指令設(shè)置狀態(tài)寄存器,ja指令判斷是否超過6,超過的話跳轉(zhuǎn)至default對應(yīng)的L8程序段,case0、case6可通過跳轉(zhuǎn)表訪問不同分支。代碼跳轉(zhuǎn)表聲明為一個長度為7的數(shù)組,每個元素都是一個指向代碼位置的指針,具體關(guān)系如下圖所示:
      -w958

      -w982

      -w955

      在這個例子中,程序使用跳轉(zhuǎn)表來處理多重分支,甚至當(dāng)switch有上百種情況時,雖然跳轉(zhuǎn)表的長度會增加,但是程序的執(zhí)行只需要一次跳轉(zhuǎn)也能處理復(fù)雜分支的情況,與使用一組很長的if-else相比,使用跳轉(zhuǎn)表的優(yōu)點是執(zhí)行switch語句的時間與case的數(shù)量是無關(guān)的。因此在處理多重分支的時,與一組很長的if-else相比,switch的執(zhí)行效率要高。

      程序調(diào)用過程相關(guān)知識

      為了方便討論,以C語言代碼函數(shù)調(diào)用為例,假設(shè)函數(shù)P調(diào)用函數(shù)Q,函數(shù)Q執(zhí)行完畢后返回函數(shù)P,這一系列操作包括圖中一個或多個機(jī)制:
      -w1037

      C語言過程調(diào)用機(jī)制的關(guān)鍵特性在于使用棧數(shù)據(jù)結(jié)構(gòu)提供FIFO內(nèi)存管理原則,在過程P調(diào)用過程Q的例子中,可以看到當(dāng)Q在執(zhí)行時,P以及所有在向上追溯到P的調(diào)用鏈中的過程都被暫時掛起。當(dāng)Q運行時,它只需要為局部變量分配新的存儲空間,或設(shè)置到另一個過程的調(diào)用。另一方面,當(dāng)Q返回時,任何它所分配的局部存儲空間都可被釋放。因此,程序可以用棧來管理它的過程所需要的存儲空間,棧和程序寄存器存放著傳遞控制和數(shù)據(jù)、分配內(nèi)存所需要的信息。當(dāng)P調(diào)用Q時,控制和數(shù)據(jù)信息添加到棧尾。當(dāng)P返回時,這些信息會被釋放掉。
      -w1006

      函數(shù)P調(diào)用函數(shù)Q時,會把返回地址壓入棧中,該地址指明了當(dāng)函數(shù)Q執(zhí)行結(jié)束 返回時要從函數(shù)P的哪個位置繼續(xù)執(zhí)行。這個返回地址的壓棧操作并不是由指令push來執(zhí)行的,而是由函數(shù)調(diào)用call來實現(xiàn)的。具體以multstore代碼為例我們可以查看返回地址細(xì)節(jié):
      -w1013

      -w1010

      編譯并使用命令objdump進(jìn)行反匯編,查看其具體調(diào)用情況:

      gcc -Og -o prog main.c multstore.c
      objdump -d prog
      

      查看部分反匯編代碼:

      上圖可知中4005a9: e8 26 00 00 00 callq 4005d4 <multstore>這一行,指令call不僅要將函數(shù)multstore的第一條指令的地址寫入到程序指令寄存器rip中,以此實現(xiàn)函數(shù)調(diào)用,同時還要將返回地址壓入棧中。
      -w1066
      當(dāng)函數(shù)multstore調(diào)用完畢后,指令ret從棧中返回地址彈出,寫入程序指令寄存器rip中:
      -w890
      函數(shù)返回,繼續(xù)執(zhí)行上面反匯編代碼中main函數(shù)第7行相關(guān)的操作。以上整個過程就是函數(shù)調(diào)用與返回所涉及的操作。那么函數(shù)調(diào)用的參數(shù)傳遞是如何實現(xiàn)的呢?在一開始我們知道,函數(shù)傳遞參數(shù)分別通過6個寄存器可以實現(xiàn),但是如果傳遞的參數(shù)大于6個呢?超出的參數(shù)就會通過壓棧來實現(xiàn)存儲。
      -w1012
      -w1061

      以下面代碼為例,探討參數(shù)傳遞過程:
      -w1065

      代碼中函數(shù)有8個參數(shù),包括字節(jié)數(shù)不同的整數(shù)以及不同類型的指針,參數(shù)1到參數(shù)6是通過寄存器來傳遞,參數(shù)7和參數(shù)8是通過棧來傳遞。
      -w1086
      這里補(bǔ)充說明:
      通過棧來傳遞參數(shù)時,所有數(shù)據(jù)的大小都是向8的倍數(shù)對齊,雖然變量a4只占一個字節(jié),但是仍然為其分配了8個字節(jié)的存儲空間。由于返回地址占用了棧頂?shù)奈恢?,所以這兩個參數(shù)距離棧頂指針的距離分別為816
      -w1034
      -w1038

      棧局部存儲
      當(dāng)代碼中對一個局部變量使用地址運算符時,我們需要在棧上為這個局部變量開辟 相應(yīng)的存儲空間,接下來我們看一個與地址運算符相關(guān)的例子。
      -w1057

      -w1055

      函數(shù)caller定義了兩個局部變量arg1arg2,函數(shù)swap的功能是交換這兩個變量的值,最后返回二者之和。我們通過分析函數(shù)caller的匯編代碼來看一下地址運算符的處理方式:
      -w947

      subq $16, %rsp第一條減法指令將棧頂指針減去16,它表示的含義是在棧上分配16個字節(jié)的空間。

      我們再來看一個較為復(fù)雜的棧上存放局部變量的例子,代碼如下:
      -w1142

      根據(jù)上面的C代碼,我們來畫一下這個函數(shù)的棧幀。根據(jù)變量的類型可知x18個字節(jié),x24個字節(jié),x3占兩個字節(jié),x4占一個字節(jié),因此,這四個變量在棧幀中的空間分配如圖所示。
      -w1108
      由于上面call_proc代碼中第6行調(diào)用的函數(shù)proc需要8個參數(shù),因此參數(shù)7和參數(shù)8需要通過棧幀來傳遞。注意,傳遞的參數(shù)需要8個字節(jié)對齊,而局部變量是不需要對齊的。
      -w1090

      關(guān)于寄存器中的局部存儲空間,其實之前已經(jīng)提到過,在說明調(diào)用者保存寄存器被調(diào)用者保存寄存器時已經(jīng)舉例,這里不再贅述。
      -w1122

      遞歸程序調(diào)用過程
      以斐波那契數(shù)列遞歸調(diào)用代碼為例:
      -w926

      數(shù)組、指針內(nèi)存訪問

      數(shù)組在內(nèi)存中是一段連續(xù)的空間,至于其每個單元的地址間距,與其單個元素類型字長有關(guān)。指針在內(nèi)存中的加一跳轉(zhuǎn)與指針的類型有關(guān),若指針是char類型,每次加一僅跳轉(zhuǎn)一個地址單元,若指針是int類型,則每次加一跳轉(zhuǎn)四個地址單元,如下圖:
      -w1124

      二維數(shù)組的存放如下圖所示:
      -w1076
      所以可以看到,數(shù)組行號不變,按行遍歷效率要高于列號不變按列去遍歷。

      結(jié)構(gòu)體對齊的計算不再說明,總結(jié)就是,結(jié)構(gòu)體元素類型的排列順序會影響其最終的結(jié)果,結(jié)構(gòu)體內(nèi)存對齊的設(shè)計也是為了提升尋址效率;相比于結(jié)構(gòu)體的內(nèi)存對齊,聯(lián)合體的設(shè)計更加巧妙,并且節(jié)約空間,聯(lián)合體中多個元素共享同一塊地址空間。關(guān)于內(nèi)存對齊的細(xì)節(jié),可此參考文章:[Link]

      避免棧緩沖區(qū)溢出攻擊的方法措施

      緩沖區(qū)溢出攻擊的普遍發(fā)生給計算機(jī)系統(tǒng)造成了很多麻煩,現(xiàn)代編譯器和操作系統(tǒng)實現(xiàn)了很多機(jī)制來盡量避免這種攻擊,限制入侵者通過緩沖區(qū)溢出攻擊獲得系統(tǒng)控制的方式,這里有一種避免緩沖區(qū)溢出的方法:棧隨機(jī)化。以下是書中對棧隨機(jī)化的介紹,避免安全單一化,每次程序執(zhí)行前,在棧上提前分配若干字節(jié)空間,后續(xù)程序棧地址就會發(fā)生改變,以此來達(dá)到隨機(jī)化的目的,避免入侵者確定??臻g具體位置。
      -w987

      第二種方法則是棧破壞檢測,若棧發(fā)生"下溢",則可以在將函數(shù)的返回地址壓棧的時候,加上一個隨機(jī)產(chǎn)生的整數(shù),如果出現(xiàn)了數(shù)組越界,那么這個整數(shù)將被修改,這樣在函數(shù)返回的時候,就可以通過檢測這個整數(shù)是否被修改,來判斷是否有"下溢"發(fā)生。這個隨機(jī)的整數(shù)被稱為"canary",它的原意是金絲雀,這種鳥對危險氣體的敏感度超過人類,所以過去煤礦工人往往會帶著金絲雀下井,如果金絲雀死了,礦工便知道井下有危險氣體,需要撤離。

      那怎么加上這個canary呢,只需要在gcc編譯的時候,加入"-fstack-protector"選項即可。一個函數(shù)對應(yīng)一個stack frame,每個stack frame都需要一個canary,這會消耗掉一部分的??臻g。此外,由于每次函數(shù)返回時都需要檢測canary,代碼的整執(zhí)行時間也勢必會增加。
      -w988

      那么以上則是對csapp中程序機(jī)器級表示的相關(guān)總結(jié)。

      posted @ 2023-07-13 19:42  miseryjerry  閱讀(261)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 国产精品久久久久影院亚瑟| 老熟妇老熟女老女人天堂| 亚洲婷婷综合色高清在线| 国产成人无码AV片在线观看不卡 | 人妻少妇久久久久久97人妻| 果冻传媒一区二区天美传媒| 日本一二三区视频在线| 激情亚洲内射一区二区三区| 国产乱码1卡二卡3卡四卡5| 午夜男女爽爽影院在线| 国产乱子伦视频在线播放| 国语自产拍精品香蕉在线播放| 国产国产午夜福利视频| 国产成人精品三级在线影院| 国产成人高清精品免费软件| 国产精品免费看久久久无码 | 国产精品久久久久久爽爽爽| 亚洲精品色哟哟一区二区| 99精品国产中文字幕| 白丝乳交内射一二三区| 成人午夜视频一区二区无码| 最新国产精品拍自在线观看| 天天看片视频免费观看| 亚洲精品成人片在线播放| 91久久亚洲综合精品成人| 东方四虎在线观看av| 国产在线不卡精品网站| 成人动漫在线观看| 国产亚洲999精品AA片在线爽| 任我爽精品视频在线播放| 当阳市| 久久综合老鸭窝色综合久久| 中文字幕日韩精品有码| 波多野结衣久久一区二区| 国产av一区二区久久蜜臀| 国产精品福利自产拍在线观看| 成人无码视频| 国内精品无码一区二区三区| 久久精品国产中文字幕| 亚洲精品国产无套在线观| 久久国内精品自在自线91|