中文字幕理论片,69视频免费在线观看,亚洲成人app,国产1级毛片,刘涛最大尺度戏视频,欧美亚洲美女视频,2021韩国美女仙女屋vip视频

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項超值服

開通VIP
程序的機(jī)器級表示

x86-64 CPU有16個寄存器,每個寄存器都能存儲一個64位(即8個bytes)的值,每個寄存器的名字都以%r開頭,并且不同的寄存器有約定上的不同的用途。下圖以%rax為例,這個寄存器專門用于存放返回值,其中的“子部分”%rax、%ax、%al也能作為指令的operand(操作數(shù)),但是他們分別能表示的數(shù)據(jù)長度各不相同(比如%al代表%rax中低位的8個bits),而且必須和mov指令的最后一個字母匹配,當(dāng)指令的目標(biāo)操作數(shù)的bytes不滿8個bytes的時候,約定是:若目標(biāo)操作數(shù)為1個byte或者2個bytes(如下圖中的%ax和%al)則就保留剩余bytes不變;而如果是4個byte(如下圖中的?x,并且對應(yīng)的指令的suffix為l,比如movl、xorl)就把高位的四個bytes置為零。寄存器也可以存放包括浮點(diǎn)數(shù)的任何數(shù)據(jù)類型。

匯編指令的操作數(shù)有幾種格式:

  • immediate(立即數(shù)),代表了一個常量值,ATT格式中以$開頭,比如$-577,$0x1F。
  • register(寄存器),代表一個寄存器中存放的內(nèi)容,下圖中用R[ra]表示(將所有寄存器看作一個以寄存器標(biāo)識為索引的數(shù)組R)。
  • memory(內(nèi)存),有幾種不同的addressing mode(尋址模式),其中最為一般化的就是M[Imm R[rb] R[ri]*s]這個形式(下表中最下面一行),其他的都是這種形式的特殊情況。M[Addr]表示內(nèi)存中地址從Addr開始的跨越一個或多個bytes長度的一個值(將內(nèi)存看作一個很大的元素為byte的數(shù)組M,以地址為索引)。在x86-64中,即使操作數(shù)是1個、2個或4個bytes,memory reference總是以quad word register(就是%rax什么的)表示,比如movw %dx, (%rax)。

MOV類指令就是把一個src(源)里的數(shù)值復(fù)制到一個指定的dst(目標(biāo)位置),寄存器或者某個內(nèi)存地址(根據(jù)寄存器的名稱或者mov指令的最后一個字母來判斷dst(目標(biāo))中的多少個bytes會被覆蓋)。x86-64有一些規(guī)定,比如:src和dst不能同時是內(nèi)存地址;movq的src只能是一個能代表32bit two‘s complement的立即數(shù),然后被sign extend(就是用符號位(最高位)填充滿所有高位)到64位后再復(fù)制到dst;而movabsq的src可以是任意的64位立即數(shù),而dst只能是寄存器。

MOVZ和MOVS都是把“小的src”復(fù)制到“大的dst”,MOVZ是zero-extend(高位補(bǔ)零),而MOVS是sign extend,其中也有一些特殊規(guī)定。這兩個指令可以很好地實(shí)現(xiàn)高級語言中的各種基本類型的cast(強(qiáng)制轉(zhuǎn)換)。

【旁注】由于歷史原因,Intel使用“word”來指16位的數(shù)據(jù)類型。像moveb,movew,movel和moveq最后一個字母表示operand(操作數(shù))的大小,b就是一個字節(jié),w是一個word,l是兩個word(l是long的縮寫,表示“l(fā)ong word”),q是四個word(quad word)。在浮點(diǎn)數(shù)的情況下,single precision是moves,double precision是movel(雖然和整型的movel名字相同,但是不會產(chǎn)生歧義,因為浮點(diǎn)數(shù)代碼的上下文完全不一樣)。

根據(jù)約定,我們把stack向下畫,x86-64中最低的地址算stack的頂部。%rsp寄存器中存放的是stack指針,指向棧頂元素。push和pop指令如下,push需要指定src,pop需要指定dst。

算數(shù)和邏輯指令例如:INC D、ADD S,DAND S,D、SAL k,D(左移)等等。

另外還有一個特殊的指令:leaq S,D,其中l(wèi)eaq的lea是load effective address(但壓根不會去引用內(nèi)存,常常用來做一些算術(shù)運(yùn)算),它的第一個操作數(shù)就像MOV指令中的“memory reference”,但并不會進(jìn)行“dereference”(解引用),而是就是指這個寄存器中的內(nèi)容,比如leaq (%rdx), %rax只不過是把%rdx中的內(nèi)容復(fù)制到%rax罷了,所以經(jīng)常被用來做一些算術(shù)運(yùn)算(no memory access occurs),比如leaq 7(%rdx,%rdx,4), %rax就是把%rax設(shè)為5倍的%rdx 7。移位運(yùn)算的shift ammount(第一個操作數(shù))要么是立即數(shù),要么只能是%cl這個單字節(jié)的寄存器。

x86-64還提供特殊的指令,就是能“不溢出”地計算兩個數(shù)的乘積(結(jié)果以兩個寄存器表示,%rdx中存放高位64位,%rax中存放低位64位),以及除法,如imulq S、divq等等,這些指令都只有一個操作數(shù),另一個參數(shù)必須事先存放在%rax中。這里的乘法雖然也叫imulq(還有一個算術(shù)指令IMUL S,D),但只有一個operand,所以不會造成混淆。

除了常規(guī)寄存器,還有一些寄存器,它們保存的都是些1位的condition code(條件碼),這些condition code代表了最近的算術(shù)或邏輯運(yùn)算造成的某些結(jié)果。最常用的condition code包括:

  • CF: Carry Flag(進(jìn)位標(biāo)志)。 最近的操作使高位產(chǎn)生了進(jìn)位,可用來檢查無符號數(shù)操作的溢出。
  • ZF: Zero Flag(零標(biāo)志)。最近的操作得出的結(jié)果為0。
  • SF: Sign Flag(符號標(biāo)志)。最近的操作得到的結(jié)果為負(fù)數(shù)。
  • OF: Overflow Flag(溢出標(biāo)志)。最近的操作造成了有符號數(shù)的溢出。

所有的算術(shù)和邏輯指令都會影響condition code。另外,CMP指令和SUB類似,但是不會改變dst中的值,只會改變上面這些condition code。同理,TEST和AND類似,而且經(jīng)常用在“和自己與”,因為和自己與還是自己,所以可以知道某個數(shù)是負(fù)數(shù)還是零。

SET指令是根據(jù)condition code的某種組合,然后把某個byte置為0或1(必定存在一種組合可以確定兩個數(shù)的大小關(guān)系,所以SET指令可以理解為高級語言中的比較符號(大于、小于號什么的)),比如sete(e代表equal,也可以叫setz)指令。SET指令經(jīng)常跟在CMP后面使用,從而確定兩個數(shù)的大小關(guān)系。

這里重要的啟示是,機(jī)器代碼并不知道某個值是個signed還是unsigned,還得靠人來通過不同的指令來“告訴”計算機(jī),比如setl是“signed <”(告訴計算機(jī)這里是個有符號數(shù)),而setb是“unsigned <”(告訴計算機(jī)這里是個無符號數(shù))

跳轉(zhuǎn)指令和SET指令類似,也是根據(jù)某些condition code的組合來決定是否跳轉(zhuǎn),比如je Label就是如果ZF等于1就跳轉(zhuǎn)到Label,否則繼續(xù)順序執(zhí)行。jmp是無條件跳轉(zhuǎn),除了jmp到某個label,還可以用jmp *%rax表示以%rax中的內(nèi)容為jump target(跳轉(zhuǎn)目標(biāo),即目標(biāo)指令的地址),也可以jpm *(%rax),表示jump target為這個寄存器中的地址的內(nèi)存位置中的內(nèi)容(星號后面跟的是一個內(nèi)存位置)。

在匯編代碼中,跳轉(zhuǎn)目標(biāo)都是用L1或者L2這樣的標(biāo)簽來表示。 assembler和linker都會以某種編碼生成合適的代碼來替換這些標(biāo)簽。有幾種不同的編碼,但最常用的叫做PC relative,也就是算出當(dāng)前指令(跳轉(zhuǎn)指令的下一條指令)的地址和跳轉(zhuǎn)目標(biāo)的差,作為跳轉(zhuǎn)指令的操作數(shù),然后等到CPU要執(zhí)行的時候就會根據(jù)當(dāng)前地址(program counter)和這個差算出jump target,這樣就相當(dāng)于是指令間的地址都是相對的(有正有負(fù)),不管動態(tài)加載到內(nèi)存時候的具體地址是多少,都不需要改變跳轉(zhuǎn)指令的jump target。

conditional move指令就是根據(jù)某個條件判斷要不要“move”的mov指令,有時候可以用它來優(yōu)化if分支,因為CPU會對指令進(jìn)行pipelining(流水線處理),即使有jump,CPU也會猜一下然后繼續(xù)不斷裝載指令,但萬一猜錯了if分支,那么已經(jīng)裝載的指令都要全部扔掉然后去裝載另一個分支的指令,而用了conditional move指令優(yōu)化過的代碼則不會有這種風(fēng)險,因為它會事先把兩種可能的結(jié)果都算出來,然后根據(jù)條件,只取其中一個結(jié)果就是了。很明顯,這種優(yōu)化是有風(fēng)險的,比如當(dāng)算出任何一種結(jié)果有副作用或是很昂貴的時候。編譯器必須自己權(quán)衡并作出決定。

while、for和switch都是用條件跳轉(zhuǎn)指令來實(shí)現(xiàn)的。值得注意的是,switch的匯編實(shí)現(xiàn)可以利用一種叫jump table的數(shù)據(jù)結(jié)構(gòu)來優(yōu)化,這個jump table就是一個數(shù)組,里面的元素對應(yīng)每個“case代碼塊”的起始地址,這種優(yōu)化方法有點(diǎn)類似于計數(shù)排序,條件是“各個case的數(shù)”分布必須在一定范圍內(nèi)。所以只要根據(jù)“switch數(shù)”算出在jump table中對應(yīng)的索引,然后就能直接得到j(luò)ump target了(所以也就不必一次一次比較了)。

【旁注】匯編代碼有兩種格式: ATT格式(以AT&T公司命名)和Intel格式。以上的內(nèi)容都是ATT格式。相比ATT格式,Intel格式的不同在于:

  • 省略了size designation suffix(指令名中用于指定數(shù)據(jù)長度的后綴),比如用mov代替movq。
  • 省略了寄存器名字前面的%符號,比如用rbx代替%rbx。
  • 用不同的格式來描述內(nèi)存中的位置,比如用QWORD PTR [rbx],而不是(%rbx)。
  • 和ATT format有著相反的順序,即:move a b表示”把b move 到a“。

Procedures(過程)

call指令類似jmp指令,可以是:call Label,也可以是:call *Operand,這條指令會把下一條指令的地址push到棧中,然后把PC設(shè)為call指令的operand代表的地址。ret指令從棧中pop出一個地址然后將PC設(shè)為此地址。call和ret指令完成了過程的調(diào)用和返回。

下圖是P過程調(diào)用Q過程的示意圖:

每一個過程在棧中都有一塊屬于自己的區(qū)域,叫stack frame(棧幀),注意棧是“向下”畫的。圖中每個stack frame的各個區(qū)域不是必須的,而是只有當(dāng)需要時才會分配,當(dāng)一個過程中的所有的局部變量用寄存器保存就足夠了,并且不會再調(diào)用其他函數(shù)時,那么其實(shí)它壓根就不需要stack frame。x86-64中,傳遞參數(shù)一般通過寄存器就足夠,但如果參數(shù)大于6個,就只能依賴于棧,上圖中P中的argument 7至argument n(以及Q中的Argument build area)就是用于分配第7至第n個參數(shù)的地方,Q可以通過stack棧頂指針加上一定的偏移量來訪問這些參數(shù)。Local variables的分配也同理,但是Q只能訪問P的argument build area和自己的local variables區(qū)域。在一個過程開始的時候,先讓棧頂指針向棧頂移動一定長度,即分配第7至n個參數(shù)以及l(fā)ocal variables,但是在return之前,為了回收這些分配的空間,還必須讓棧頂指針向相反的方向移動同樣的長度,這樣以后再執(zhí)行ret就可以保證pop出來的是正確的返回地址。所以,過程調(diào)用的匯編代碼常常是將這些局部變量需要在stack上分配和回收的長度“寫死”在代碼中。

另外,關(guān)于寄存器還有一些約定。某些寄存器不能被callee(被調(diào)用者)改變,這些寄存器叫作callee-saved registers(由被調(diào)用者“保證”它們的值不變)。也就是說,當(dāng)P調(diào)用Q時,可以放心地把某些變量存到callee-saved registers中,而不用擔(dān)心Q會改變這些寄存器中的內(nèi)容。當(dāng)然,Q可以先把這些寄存器中的內(nèi)容push到stack上,然后隨便用這些寄存器,只要在返回之前,從stack上把原來的值pop回相應(yīng)的callee-saved register中就行。P自己本身就很可能也是一個callee,所以在使用callee-saved registers存放變量前,也會先在stack上保存其中原來的值。另一類寄存器叫作caller-saved registers,它們可以被任何函數(shù)改變,所以當(dāng)P調(diào)用Q之前,必須把用到的caller-saved registers中的內(nèi)容先保存到stack(圖中的saved registers區(qū)域)或callee-saved registers中,然后才能放心地去調(diào)用Q。

這樣的利用stack和寄存器約定的過程調(diào)用機(jī)制,也能很自然地支持函數(shù)的遞歸調(diào)用,和調(diào)用其他函數(shù)并沒有什么區(qū)別。

數(shù)組的分配和訪問

數(shù)組可以理解為內(nèi)存中的一塊L*N字節(jié)的連續(xù)區(qū)域,這里的L為數(shù)組元素類型的大小,N為數(shù)組長度。而數(shù)組A(假設(shè)數(shù)組叫A)就代表指向數(shù)組第一個元素的指針,所以,數(shù)組中索引為i的個元素(在C中用A[i]表示)就存放在地址為A L*i的地方。在C中,為了方便,p i這個表達(dá)式的實(shí)際代表的值是xp L*i,p是一個T類型的指針,i是一個整數(shù),xp是p的值,L是T類型的大小。

假設(shè)有一個多維數(shù)組int A[5][3];,這里的A是一個長度為5的數(shù)組,數(shù)組的元素類型為“長度為3的整型數(shù)組”,它在內(nèi)存中的存儲順序是這樣的:

另外,關(guān)于多維數(shù)組中元素的訪問,編譯器可以做出一些優(yōu)化,以簡化對數(shù)組元素的地址的計算。

Structure和Union

假設(shè)有一個struct聲明:

struct rec {    int i;    int j;    int a[2];    int *p;};

那么這樣的一個struct對象在內(nèi)存中就是這樣的:

類似數(shù)組,如果想訪問struct對象中的某個字段,只要給這個“struct指針”(指向此struct開頭)加上對應(yīng)的offset(偏移量)即可,比如:假設(shè)變量r的類型為struct rec *,它的值為pr ,那么r->j的地址就為pr 4,如果寫成匯編就是:movl 4(%rdi), ?x(r在%rdi中,指令將r->j放在?x中),所以說,這個偏移量完全是在編譯時就已經(jīng)決定的,然后“寫死”在匯編代碼中,而機(jī)器代碼對于字段聲明或是它們的類型一無所知。

union是C的另一個特性,主要用于某個對象有一些“互斥”的字段,然后可以節(jié)省空間,所以一個union的總的占用空間是其所有字段中所占空間最大的字段所占的空間。

數(shù)據(jù)對齊

為了簡化硬件上的設(shè)計,通常有這樣一個限制:CPU每一次的操作總是從內(nèi)存中的一個地址為k的倍數(shù)的位置取得k個bytes。所以,如果我們能保證所有的原始類型(比如char,int這些)的數(shù)據(jù)的地址都是k的倍數(shù),那么每一次只需要一次CPU操作就能得到這個數(shù)據(jù)的全部。所以,Intel推薦我們對數(shù)據(jù)進(jìn)行對齊,從而提高性能(雖然在大多數(shù)情況下,即使不做對齊也能正常工作)。這個對齊的規(guī)定是:任何k個bytes的原始類型的數(shù)據(jù)的地址都必須是k的倍數(shù),比如int類型的數(shù)據(jù)的地址就應(yīng)是4的倍數(shù)(另外,某些Intel和AMD處理器也規(guī)定大多數(shù)的函數(shù)的stack frame上的數(shù)據(jù)的地址需要是16的倍數(shù) )。為了達(dá)到這個規(guī)定,通常會有一些空間上的浪費(fèi),比如這樣一個struct:

struct S1 {    int i;    char c;    int j;};

為了達(dá)到上述規(guī)定,可以在第二個字段c的后面補(bǔ)齊3個bytes(從而使j的地址滿足要求),看起來就像這樣:

指令.align 8就表示:接下來的數(shù)據(jù)的保證會從一個8的倍數(shù)的地址開始。

Buffer Overflow攻擊

由于C不會對數(shù)組的索引做檢查,所以完全可以使用超過數(shù)組長度的索引值來訪問那些不屬于數(shù)組的內(nèi)存空間,比如修改saved registers,或修改函數(shù)的返回地址等等。Buffer overflow攻擊簡單來說就是:某個函數(shù)接受一個用戶輸入的字符串,放進(jìn)一個預(yù)先分配好的char數(shù)組中,但沒有對用戶輸入的長度做任何檢查,所以一旦超過了預(yù)先分配的長度,就會造成stack狀態(tài)被破壞(比如覆蓋返回地址,讓程序跳轉(zhuǎn)到一段惡意代碼)。

通過編譯器防御buffer overflow攻擊的手段包括:

  • 即使是同一個程序,每次運(yùn)行它的時候,都先在stack上隨機(jī)分配一定的空間(不能太小,否則很容易被暴力破解),這段空間沒什么用,只是為了讓每次的stack pointer都不那么一樣,所以攻擊者沒那么容易猜到惡意代碼實(shí)際所在的地址。
  • 在local buffer分配之前,先插入一段“guard value”(一個隨機(jī)值),在恢復(fù)saved registers和跳轉(zhuǎn)到返回地址之前,先比較這個guard value是否被改變了,如果被改變了就報錯。
  • 可以指定內(nèi)存中的哪些部分是可執(zhí)行的代碼,從而禁止程序運(yùn)行某些非編譯器生成的代碼。

但最好的習(xí)慣還是應(yīng)該在代碼中對任何用戶輸入進(jìn)行校驗。

浮點(diǎn)數(shù)代碼

到現(xiàn)在為止所介紹的指令其實(shí)都是用于整數(shù)的,有一套專門用于浮點(diǎn)數(shù)的操作和運(yùn)算的指令和寄存器,類似已經(jīng)介紹過的那些用于整數(shù)的指令,包括傳送指令(類似MOV)、用于類型轉(zhuǎn)換的指令、用于算術(shù)運(yùn)算或位運(yùn)算(通??梢杂糜趯?shí)現(xiàn)“絕對值”、“相反數(shù)”這些)的指令、用于比較的指令。由于立即數(shù)只能是整數(shù),所以代碼中的“浮點(diǎn)數(shù)常量”在匯編代碼中都會被轉(zhuǎn)化為內(nèi)存中的值。

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
《深入理解計算機(jī)系統(tǒng)》讀書筆記 —— 第三章 程序的機(jī)器級表示
memset 的實(shí)現(xiàn)分析
【十分鐘教會你匯編】MIPS編程入門(媽媽說標(biāo)題要高大上,才會有人看>
C語言嵌入式系統(tǒng)編程修煉之內(nèi)存操作篇
數(shù)據(jù)結(jié)構(gòu)是如何裝入 CPU 寄存器的?
[翻譯]]ARM匯編簡介(五)載入/存儲多個值 && 入棧和出棧
更多類似文章 >>
生活服務(wù)
熱點(diǎn)新聞
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服