• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆 - 47, 文章 - 10, 評(píng)論 - 8, 引用 - 0
            數(shù)據(jù)加載中……

            簡(jiǎn)明x86匯編語(yǔ)言教程(五)

            原創(chuàng):司徒彥南

            3.4 串操作

            我們前面已經(jīng)提到,內(nèi)存可以和寄存器交換數(shù)據(jù),也可以被賦予立即數(shù)。問(wèn)題是,如果我們需要把內(nèi)存的某部分內(nèi)容復(fù)制到另一個(gè)地址,又怎么做呢?

            設(shè)想將DS:SI處的連續(xù)512字節(jié)內(nèi)容復(fù)制到ES:DI(先不考慮可能的重疊)。也許會(huì)有人寫(xiě)出這樣的代碼:


            NextByte:
            mov cx,512
            mov al,ds:[si]
            mov es:[di],al
            inc si
            inc di
            loop NextByte
            ; 循環(huán)次數(shù)

            我不喜歡上面的代碼。它的確能達(dá)到作用,但是,效率不好。如果你是在做優(yōu)化,那么寫(xiě)出這樣的代碼意味著賠了夫人又折兵。

            Intel的CPU的強(qiáng)項(xiàng)是串操作。所謂串操作就是由CPU去完成某一數(shù)量的、重復(fù)的內(nèi)存操作。需要說(shuō)明的是,我們常用的KMP算法(用于匹配字符串中的模式)的改進(jìn)——Boyer算法,由于沒(méi)有利用串操作,因此在Intel的CPU上的效率并非最優(yōu)。好的編譯器往往可以利用Intel CPU的這一特性?xún)?yōu)化代碼,然而,并非所有的時(shí)候它都能產(chǎn)生最好的代碼。

            某些指令可以加上REP前綴(repeat, 反復(fù)之意),這些指令通常被叫做串操作指令。

            舉例來(lái)說(shuō),STOSD指令將EAX的內(nèi)容保存到ES:DI,同時(shí)在DI上加或減四。類(lèi)似的,STOSB和STOSW分別作1字節(jié)或1字的上述操作,在DI上加或減的數(shù)是1或2。

            計(jì)算機(jī)語(yǔ)言通常是不允許二義性的。為什么我要說(shuō)“加或減”呢?沒(méi)錯(cuò),孤立地看STOS?指令,并不能知道到底是加還是減,因?yàn)檫@取決于“方向”標(biāo)志(DF, Direction Flag)。如果DF被復(fù)位,則加;反之則減。

            置位、復(fù)位的指令分別是STD和CLD。

            當(dāng)然,REP只是幾種可用前綴之一。常用的還包括REPNE,這個(gè)前綴通常被用來(lái)比較兩個(gè)串,或搜索某個(gè)特定字符(字、雙字)。REPZ、REPE、REPNZ也是非常常用的指令前綴,分別代表ZF(Zero Flag)在不同狀態(tài)時(shí)重復(fù)執(zhí)行。

            下面說(shuō)三個(gè)可以復(fù)制數(shù)據(jù)的指令:

            助記符意義
            movsb將DS:SI的一字節(jié)復(fù)制到ES:DI,之后SI++、DI++
            movsw將DS:SI的一字節(jié)復(fù)制到ES:DI,之后SI+=2、DI+=2
            movsd將DS:SI的一字節(jié)復(fù)制到ES:DI,之后SI+=4、DI+=4

            于是上面的程序改寫(xiě)為

            cld
            mov cx, 128
            rep movsd
            ; 復(fù)位DF
            ; 512/4 = 128,共128個(gè)雙字
            ; 行動(dòng)!

            第一句cld很多時(shí)候是多余的,因?yàn)閷?shí)際寫(xiě)程序時(shí),很少會(huì)出現(xiàn)置DF的情況。不過(guò)在正式?jīng)Q定刪掉它之前,建議你仔細(xì)地調(diào)試自己的程序,并確認(rèn)每一個(gè)能夠走到這里的路徑中都不會(huì)將DF置位。

            錯(cuò)誤(非預(yù)期的)的DF是危險(xiǎn)的。它很可能斷送掉你的程序,因?yàn)檫@直接造成緩沖區(qū)溢出問(wèn)題。

            什么是緩沖區(qū)溢出呢?緩沖區(qū)溢出分為兩類(lèi),一類(lèi)是寫(xiě)入緩沖區(qū)以外的內(nèi)容,一類(lèi)是讀取緩沖區(qū)以外的內(nèi)容。后一種往往更隱蔽,但隨便哪一個(gè)都有可能斷送掉你的程序。

            緩沖區(qū)溢出對(duì)于一個(gè)網(wǎng)絡(luò)服務(wù)來(lái)說(shuō)很可能更加危險(xiǎn)。懷有惡意的用戶(hù)能夠利用它執(zhí)行自己希望的指令。服務(wù)通常擁有更高的特權(quán),而這很可能會(huì)造成特權(quán)提升;即使不能提升攻擊者擁有的特權(quán),他也可以利用這種問(wèn)題使服務(wù)崩潰,從而形成一次成功的DoS(拒絕服務(wù))攻擊。每年CERT的安全公告中,都有6成左右的問(wèn)題是由于緩沖區(qū)溢出造成的。

            在使用匯編語(yǔ)言,或C語(yǔ)言編寫(xiě)程序時(shí),很容易在無(wú)意中引入緩沖區(qū)溢出。然而并不是所有的語(yǔ)言都會(huì)引入緩沖區(qū)溢出問(wèn)題,Java和C#,由于沒(méi)有指針,并且緩沖區(qū)采取動(dòng)態(tài)分配的方式,有效地消除了造成緩沖區(qū)溢出的土壤。

            匯編語(yǔ)言中,由于REP*前綴都用CX作為計(jì)數(shù)器,因此情況會(huì)好一些(當(dāng)然,有時(shí)也會(huì)更糟糕,因?yàn)橛捎贑X的限制,很可能使原本可能改變程序行為的緩沖區(qū)溢出的范圍縮小,從而更為隱蔽)。避免緩沖區(qū)溢出的一個(gè)主要方法就是仔細(xì)檢查,這包括兩方面:設(shè)置合理的緩沖區(qū)大小,和根據(jù)大小編寫(xiě)程序。除此之外,非常重要的一點(diǎn)就是,在匯編語(yǔ)言這個(gè)級(jí)別寫(xiě)程序,你肯定希望去掉所有的無(wú)用指令,然而再去掉之前,一定要進(jìn)行嚴(yán)格的測(cè)試;更進(jìn)一步,如果能加上注釋?zhuān)⑼ㄟ^(guò)善用宏來(lái)做調(diào)試模式檢查,往往能夠達(dá)到更好的效果。

            3.5 關(guān)于保護(hù)模式中內(nèi)存操作的一點(diǎn)說(shuō)明

            正如3.2節(jié)提到到的那樣,保護(hù)模式中,你可以使用32位的線(xiàn)性地址,這意味著直接訪(fǎng)問(wèn)4GB的內(nèi)存。由于這個(gè)原因,選擇器不用像實(shí)模式中段寄存器那樣頻繁地修改。順便提一句,這份教程中所說(shuō)的保護(hù)模式指的是386以上的保護(hù)模式,或者,Microsoft通常稱(chēng)為“增強(qiáng)模式”的那種。

            在為選擇器裝入數(shù)值的時(shí)候一定要非常小心。錯(cuò)誤的數(shù)值往往會(huì)導(dǎo)致無(wú)效頁(yè)面錯(cuò)誤(在Windows中經(jīng)常出現(xiàn):)。同時(shí),也不要忘記你的地址是32位的,這也是保護(hù)模式的主要優(yōu)勢(shì)之一。

            現(xiàn)在假設(shè)存在一個(gè)描述符描述從物理的0:0開(kāi)始的全部?jī)?nèi)存,并已經(jīng)加載進(jìn)DS(數(shù)據(jù)選擇器),則我們可以通過(guò)下面的程序來(lái)操作VGA的VRAM:

            mov edi,0a0000h
            mov byte ptr [edi],0fh
            ; VGA顯存的偏移量
            ; 將第一字節(jié)改為0fh

            很明顯,這比實(shí)模式下的程序

            mov ax,0a000h
            mov ds,ax
            mov di,0
            mov [di],0fh
            ; AX -> VGA段地址
            ; 將AX值載入DS
            ; DI清零
            ; 修改第一字節(jié)

            看上去要舒服一些。

            3.6 堆棧

            到目前為止,您已經(jīng)了解了基本的寄存器以及內(nèi)存的操作知識(shí)。事實(shí)上,您現(xiàn)在已經(jīng)可以寫(xiě)出很多的底層數(shù)據(jù)處理程序了。

            下面我來(lái)說(shuō)說(shuō)堆棧。堆棧實(shí)在不是一個(gè)讓人陌生的數(shù)據(jù)結(jié)構(gòu),它是一個(gè)先進(jìn)后出(FILO)先進(jìn)后出(FILO)是這樣一個(gè)概念:最后放進(jìn)表中的數(shù)據(jù)在取出時(shí)最先出來(lái)。先進(jìn)后出(FILO)和先進(jìn)先出(FIFO, 和先進(jìn)后出的規(guī)則相反),以及隨機(jī)存取是最主要的三種存儲(chǔ)器訪(fǎng)問(wèn)方式。對(duì)于堆棧而言,最后放入的數(shù)據(jù)在取出時(shí)最先出現(xiàn)。對(duì)于子程序調(diào)用,特別是遞歸調(diào)用來(lái)說(shuō),這是一個(gè)非常有用的特性。)的線(xiàn)性表,能夠幫助你完成很多很好的工作。

            一個(gè)鐵桿的匯編語(yǔ)言程序員有時(shí)會(huì)發(fā)現(xiàn)系統(tǒng)提供的寄存器不夠。很顯然,你可以使用普通的內(nèi)存操作來(lái)完成這個(gè)工作,就像C/C++中所做的那樣。

            沒(méi)錯(cuò),沒(méi)錯(cuò),可是,如果數(shù)據(jù)段(數(shù)據(jù)選擇器)以及偏移量發(fā)生變化怎么辦?更進(jìn)一步,如果希望保存某些在這種操作中可能受到影響的寄存器的時(shí)候怎么辦?確實(shí),你可以把他們也存到自己的那片內(nèi)存中,自己實(shí)現(xiàn)堆棧。

            太麻煩了……

            既然系統(tǒng)提供了堆棧,并且性能比自己寫(xiě)一份更好,那么為什么不直接加以利用呢?

            系統(tǒng)堆棧不僅僅是一段內(nèi)存。由于CPU對(duì)它實(shí)施管理,因此你不需要考慮堆棧指針的修正問(wèn)題。可以把寄存器內(nèi)容,甚至一個(gè)立即數(shù)直接放到堆棧里,并在需要的時(shí)候?qū)⑵淙〕觥M瑫r(shí),系統(tǒng)并不要求取出的數(shù)據(jù)仍然回到原來(lái)的位置。

            除了顯式地操作堆棧(使用PUSH和POP指令)之外,很多指令也需要使用堆棧,如INT、CALL、LEAVE、RET、RETF、IRET等等。配對(duì)使用上述指令并不會(huì)造成什么問(wèn)題,然而,如果你打算使用LEAVE、RET、RETF、IRET這樣的指令實(shí)現(xiàn)跳轉(zhuǎn)(比JMP更為麻煩,然而有時(shí),例如在加密軟件中,或者需要修改調(diào)用者狀態(tài)時(shí),這是必要的)的話(huà),那么我的建議是,先搞清楚它們做的到底是什么,并且,精確地了解自己要做什么。

            正如前面所說(shuō)的,有兩個(gè)顯式地操作堆棧的指令:

            助記符

            功能

            PUSH將操作數(shù)存入堆棧,同時(shí)修正堆棧指針
            POP將棧頂內(nèi)容取出并存到目的操作數(shù)中,同時(shí)修正堆棧指針

            我們現(xiàn)在來(lái)看看堆棧的操作。

            執(zhí)行之前

            o_5_1.gif

            執(zhí)行代碼

            mov ax,1234h
            mov bx,10
            push ax
            push bx

            之后,堆棧的狀態(tài)為

            o_5_2.gif

            之后,再執(zhí)行

            pop dx
            pop cx

            堆棧的狀態(tài)成為

            o_5_3.gif

            當(dāng)然,dx、cx中的內(nèi)容將分別是000ah和1234h。

            注意,最后這張圖中,我沒(méi)有抹去1234h和000ah,因?yàn)镻OP指令并不從內(nèi)存中抹去數(shù)值。不過(guò)盡管如此,我個(gè)人仍然非常反對(duì)繼續(xù)使用這兩個(gè)數(shù)(你可以通過(guò)修改SP來(lái)再次POP它們),然而這很容易導(dǎo)致錯(cuò)誤。

            一定要保證堆棧段有足夠的空間來(lái)執(zhí)行中斷,以及其他一些隱式的堆棧操作。僅僅統(tǒng)計(jì)PUSH的數(shù)量并據(jù)此計(jì)算堆棧所需的大小很可能造成問(wèn)題。

            CALL指令將返回地址放到堆棧中。絕大多數(shù)C/C++編譯器提供了“堆棧檢查”這個(gè)編譯選項(xiàng),其作用在于保證C程序段中沒(méi)有忘記對(duì)堆棧中多余的數(shù)據(jù)進(jìn)行清理,從而保證返回地址有效。

            本章小結(jié)

            本章中介紹了內(nèi)存的操作的一些入門(mén)知識(shí)。限于篇幅,我不打算展開(kāi)細(xì)講指令,如cmps*,lods*,stos*,等等。這些指令的用法和前面介紹的movs*基本一樣,只是有不同的作用而已。

            posted on 2006-11-06 10:33 編程之道 閱讀(367) 評(píng)論(0)  編輯 收藏 引用 所屬分類(lèi): 開(kāi)發(fā)相關(guān)ASM

            亚洲色欲久久久久综合网| 国产精品视频久久| 国产69精品久久久久久人妻精品| 久久久久一级精品亚洲国产成人综合AV区 | 伊人色综合九久久天天蜜桃| 久久香综合精品久久伊人| 久久精品天天中文字幕人妻| 国产精品激情综合久久| 性色欲网站人妻丰满中文久久不卡| 99久久国产热无码精品免费| 深夜久久AAAAA级毛片免费看| 久久精品国产亚洲av麻豆色欲| 精品人妻伦九区久久AAA片69| 亚洲va久久久噜噜噜久久天堂| 国产99久久久国产精品~~牛| 五月丁香综合激情六月久久 | 久久青青草原亚洲av无码app | 久久亚洲精精品中文字幕| 久久九九久精品国产| 久久亚洲中文字幕精品有坂深雪| 青春久久| 久久久久人妻精品一区三寸蜜桃| 久久99精品久久久久久动态图| 狠狠色丁香婷婷久久综合| 久久精品无码av| AAA级久久久精品无码区| 国产精品久久久久久福利漫画 | 99久久精品免费看国产| 成人妇女免费播放久久久| 日产精品久久久一区二区| 久久免费看黄a级毛片| 婷婷久久综合九色综合绿巨人| 久久成人18免费网站| 韩国三级中文字幕hd久久精品 | 久久精品嫩草影院| 久久综合综合久久狠狠狠97色88| 97久久超碰国产精品2021| 精品国产乱码久久久久久人妻 | 久久综合亚洲色HEZYO社区| 一本色道久久88综合日韩精品 | 久久精品国产亚洲AV忘忧草18|