今天比較搞笑,在看《Inside vc.net》的com的時候,講到了c++中的vftable,這個虛擬表還真不是很清楚,所以就在網(wǎng)上查了一些關(guān)于vftable的資料,地址為:
http://blog.csdn.net/gzjh1/archive/2006/05/26/756024.aspx,轉(zhuǎn)貼一下:)
在面向?qū)ο蟮腃++語言中,虛函數(shù)(virtual?function)是一個非常重要的概念。因為它充分體現(xiàn)了面向?qū)ο笏枷胫械睦^承和多態(tài)性這兩大特性,在C++語言里應(yīng)用極廣。比如在微軟的MFC類庫中,你會發(fā)現(xiàn)很多函數(shù)都有virtual關(guān)鍵字,也就是說,它們都是虛函數(shù)。難怪有人甚至稱虛函數(shù)是C++語言的精髓。?
????????那么,什么是虛函數(shù)呢,我們先來看看微軟的解釋:
????????虛函數(shù)是指一個類中你希望重載的成員函數(shù),當(dāng)你用一個基類指針或引用指向一個繼承類對象的時候,你調(diào)用一個虛函數(shù),實際調(diào)用的是繼承類的版本。
???????????????????????????????????????????????????????????????——摘自MSDN
????????這個定義說得不是很明白。MSDN中還給出了一個例子,但是它的例子也并不能很好的說明問題。我們自己編寫這樣一個例子:
#include?"stdio.h"
#include?"conio.h"
?
class?Parent


{
public:
?char?data[20];
?void?Function1();
?virtual?void?Function2();??//?這里聲明Function2是虛函數(shù)
}parent;
void?Parent::Function1()


{
?printf("This?is?parent,function1\n");
}
void?Parent::Function2()


{
?printf("This?is?parent,function2\n");
}
?
class?Child:public?Parent


{
?void?Function1();
?void?Function2();
}?child;
void?Child::Function1()


{
?printf("This?is?child,function1\n");
}
void?Child::Function2()


{
?printf("This?is?child,function2\n");
}
?
int?main(int?argc,?char*?argv[])


{
?Parent?*p;?//?定義一個基類指針
?if(_getch()=='c')?????//?如果輸入一個小寫字母c
???????p=&child;?????????//?指向繼承類對象
?else
???????p=&parent;??????//?否則指向基類對象
p->Function1();???//?這里在編譯時會直接給出Parent::Function1()的
???????????????????????????????????入口地址。
?p->Function2();????//?注意這里,執(zhí)行的是哪一個Function2?
?return?0;
}
????????用任意版本的Visual?C++或Borland?C++編譯并運行,輸入一個小寫字母c,得到下面的結(jié)果:
This?is?parent,function1
This?is?child,function2
??????為什么會有第一行的結(jié)果呢?因為我們是用一個Parent類的指針調(diào)用函數(shù)Fuction1(),雖然實際上這個指針指向的是Child類的對象,但編譯器無法知道這一事實(直到運行的時候,程序才可以根據(jù)用戶的輸入判斷出指針指向的對象),它只能按照調(diào)用Parent類的函數(shù)來理解并編譯,所以我們看到了第一行的結(jié)果。
????????那么第二行的結(jié)果又是怎么回事呢?我們注意到,F(xiàn)unction2()函數(shù)在基類中被virtual關(guān)鍵字修飾,也就是說,它是一個虛函數(shù)。虛函數(shù)最關(guān)鍵的特點是“動態(tài)聯(lián)編”,它可以在運行時判斷指針指向的對象,并自動調(diào)用相應(yīng)的函數(shù)。如果我們在運行上面的程序時任意輸入一個非c的字符,結(jié)果如下:
This?is?parent,function1
This?is?parent,function2
????????請注意看第二行,它的結(jié)果出現(xiàn)了變化。程序中僅僅調(diào)用了一個Function2()函數(shù),卻可以根據(jù)用戶的輸入自動決定到底調(diào)用基類中的Function2還是繼承類中的Function2,這就是虛函數(shù)的作用。我們知道,在MFC中,很多類都是需要你繼承的,它們的成員函數(shù)很多都要重載,比如編寫MFC應(yīng)用程序最常用的CView::OnDraw(CDC*)函數(shù),就必須重載使用。把它定義為虛函數(shù)(實際上,在MFC中OnDraw不僅是虛函數(shù),還是純虛函數(shù)),可以保證時刻調(diào)用的是用戶自己編寫的OnDraw。虛函數(shù)的重要用途在這里可見一斑。
??????在了解虛函數(shù)的基礎(chǔ)之上,我們考慮這樣的問題:一個基類指針必須知道它所指向的對象是基類還是繼承類的示例,才能在調(diào)用虛函數(shù)時“自動”決定應(yīng)該調(diào)用哪個版本,它是如何知道的?有些講C++的書上提到,這種“動態(tài)聯(lián)編”的機制是通過一個“vtable”實現(xiàn)的,vtable是什么?微軟在關(guān)于COM的文檔里這樣描述:
????????vtable是指一張函數(shù)指針表,如同C++中類的實現(xiàn)一樣,vtable中的指針指向一個對象支持的接口成員函數(shù)。????????
???????????????????????????????????????????????????????????????——摘自MSDN
????????很遺憾,微軟這次還是沒有把問題說清楚,當(dāng)然,上面的文檔本來就是關(guān)于COM的,與我們關(guān)心的問題不同。
????????那么vtable是什么?我們先來看看下面的實驗:
????????在前面的示例程序中加一句printf(“%d”,sizeof(Child));運行,然后去掉Function2()前的virtual關(guān)鍵字,再運行,得到這樣的結(jié)果:當(dāng)Function2定義成虛函數(shù)的時候,結(jié)果是24,否則結(jié)果是20。也就是說,如果Function2不是虛函數(shù),一個Child類的示例所占空間的大小僅僅是它的成員變量data數(shù)組的大小,如果Function2是虛函數(shù),結(jié)果多了4個字節(jié)。我們使用的是32位的Visual?C++?6.0,4個字節(jié)恰好是一個指針,或者是一個整數(shù)所占的空間。
????????那么這多出來的四個字節(jié)究竟起到了什么作用?
???????用Visual?C++打開前面的示例程序,在main函數(shù)中p->Function1();?一句前面按F9設(shè)斷點,按F5開始調(diào)試,輸入一個小寫c,程序停到了我們設(shè)的斷點上。找到Debug工具條,按Disassembly按鈕,如圖所示:
(圖呢?CSDN上的這個應(yīng)該是作者的原創(chuàng)了吧?可是圖竟然不見了>_<!!)
算了,不管圖了,反正也不是很重要,go?on
????????我們看到了反匯編后的代碼。由上圖可見,對Function1和Function2的調(diào)用反匯編后生成的代碼截然不同。Function1不是虛函數(shù),因此對它的調(diào)用僅僅被編譯成為一條call指令,轉(zhuǎn)向Parent::Function1子程序;而Function2是虛函數(shù),它的代碼要復(fù)雜一些,我們來仔細(xì)分析:
45:???????p->Function2();????
004012CA???mov??????eax,dword?ptr?[ebp-4]
//?eax就是我們的p指針
004012CD???mov??????edx,dword?ptr?[eax]
//?edx取child對象頭部四個字節(jié)
004012CF???mov???????esi,esp
004012D1???mov???????ecx,dword?ptr?[ebp-4]
//?可能要檢查棧,不管它
004012D4???call????????dword?ptr?[edx]
//?注意這里,調(diào)用了child對象頭部的一個函數(shù)指針
004012D6???cmp???????esi,esp
004012D8???call?????????__chkesp?(004013b0)
這里最關(guān)鍵的一句是call?dword?ptr[edx],edx是child對象頭部,前面我們分析過了,child對象共有24字節(jié),其中成員變量占用20字節(jié),還有4個字節(jié)作用未知?,F(xiàn)在從這段匯編代碼上看,那4個字節(jié)很可能就是child對象開頭的這個函數(shù)指針,因為編譯器并不知道我們的成員變量data是做什么用的,更不可能把data的任何一部分當(dāng)成一個函數(shù)指針來處理。
那么這個函數(shù)指針會跳轉(zhuǎn)到那里去呢?我們按F10單步運行到這個call指令,然后按F11跟進去:
00401032???jmp?????????Parent::Function2?(0040bfe0)
00401037???jmp?????????Parent::Parent?(004010d0)
????→?0040103C???jmp?????????Child::Function2?(00401250)
00401041???jmp?????????Child::Child?(004011c0)
光標(biāo)停在了第三行,40103C的地方,執(zhí)行這里的jmp指令后,又跳轉(zhuǎn)到Child::Function2的位置,從而得到我們上面所看到的結(jié)果。
這并不是最終的結(jié)論,我們看看40103C周圍的幾行代碼,連續(xù)幾行全都是jmp指令,這是什么程序結(jié)構(gòu)?有匯編語言編程經(jīng)驗的朋友可能會想起來了,這是一張入口表,分別存放著到幾個重要函數(shù)的跳轉(zhuǎn)指令!我們再回去看看微軟對于vtable的描述:vtable是指一張函數(shù)指針表,(如同C++中類的實現(xiàn)一樣,)vtable中的指針指向(一個對象支持的接口)成員函數(shù)。打括號的字不要看,這句話的主干就是:vtable是一張函數(shù)指針表,指向成員函數(shù)。種種事實證明,上面的四行代碼就是我們要找的這個vtable!
現(xiàn)在我們應(yīng)該對虛函數(shù)的原理有一個認(rèn)識了。每個虛函數(shù)都在vtable中占了一個表項,保存著一條跳轉(zhuǎn)到它的入口地址的指令(實際上就是保存了它的入口地址)。當(dāng)一個包含虛函數(shù)的對象(注意,不是對象的指針)被創(chuàng)建的時候,它在頭部附加一個指針,指向vtable中相應(yīng)位置。調(diào)用虛函數(shù)的時候,不管你是用什么指針調(diào)用的,它先根據(jù)vtable找到入口地址再執(zhí)行,從而實現(xiàn)了“動態(tài)聯(lián)編”。而不像普通函數(shù)那樣簡單地跳轉(zhuǎn)到一個固定地址。
以上結(jié)論僅僅是針對Visual?C++?6.0編譯器而言的,對于其他編譯器,具體實現(xiàn)并不完全相同,但都大同小異。著名的“綠色兵團”雜志上撰文介紹,Linux平臺上的GNU?C++編譯器就把指向vtable的指針放在對象尾部而不是頭部,而且vtable中僅僅存放虛函數(shù)的入口地址,而不是跳轉(zhuǎn)到虛函數(shù)的指令。具體的一些細(xì)節(jié),篇幅所限,我們這里不再討論,希望有興趣的朋友能繼續(xù)研究。
PS:綜上所述,只有對指針和引用,虛函數(shù)的魔力才有作用!這篇文章又講到了匯編的知識,以前看過一些匯編的資料,但是一直沒有弄明白,主要還是因為沒有自己去寫代碼,把上面的代碼在vc.net2005中運行了一下,打開Disassemble、Register窗口,里面的東西還真是稀里糊涂的??袢巳沼浿兄v的Assemble還不錯,
http://krrj.blogbus.com/s42550/(主要是講注冊機的內(nèi)容,截取其中Assemble的部分)轉(zhuǎn)貼如下,也可以看看
http://www.xker.com/Html/bcyy/hbyy/20051118784.htm的《
簡明x86匯編語言教程》
稍微有點兒計算機知識的朋友一定知道,計算機是只識別0和1的,最初那會兒,要寫程序,就要用0和1來寫,呵呵,Cool吧!所以曾經(jīng)有過的對程序員的崇拜,可能就源自那個時候吧??后來,人們發(fā)現(xiàn)用0和1來寫程序,太不爽了,不但寫起來不上手,而且回過頭來看的話,應(yīng)該很難再看明白了,總之出于這些原因,就有了匯編語言。?


匯編語言用一些助記符來代替0和1的多種組合,也就是各個指令,這樣的話,從一定程度上來說,方便了許多(一頭老牛:方便太多了)(一只菜鳥:一點兒也不方便,完全看不懂)。但是,匯編也同樣不方便,同樣寫起來不爽,而且后期維護同樣不方便,再加上人們慢慢地需要寫一些更大的程序,在這樣的情況下,高級語言就被人發(fā)明了出來,就是我們今天用的Basic、pascal、C、C++等等等等,這些語言的出現(xiàn),一下了使程序的開發(fā)難度大大減低了(一頭老牛:減低太多了,我膝蓋就能寫程序了)(一只菜鳥:還不是一樣難),以前用匯編要很長時間才能開發(fā)出來的程序,現(xiàn)在只需要很短的時間且很輕松的就可以搞定了,特別是最近幾年,可視化編程的大肆普及,使程序員的神秘感一下子摔了下來,Coder這樣的詞現(xiàn)在都滿天飛了。最慘的就是匯編,一夜之間變成了低級語言、下流的語言、吃完大蒜不刷牙的民工、開車加完油不給錢的地痞、在公共汽車上吐口水的冰島人等等等等??


(匯編:嗚嗚嗚…我不活了)。?


但是匯編還是有它先天的優(yōu)勢的,因為其與CPU內(nèi)部的指令一一對應(yīng),所以在一些特殊的場合,必須由匯編來實現(xiàn),比如訪問硬件的端口、寫病毒….?


而且生成的可執(zhí)行文件效率巨高,且生成的可執(zhí)行文件賊小,寫小程序是很爽的,呵呵,而且用匯編寫注冊機,是件很輕松的事,你不用再為怎樣還原為你所熟悉的語言而為難。說了這么多,還是切入主題吧(昏倒觀眾若干):?


既然計算機只識別0和1,那么,所有存儲在計算機上的文件,也都是以二進制的形式存放的,當(dāng)然也包括可執(zhí)行文件了。?


所以,你只要找一個十六進制編輯器比如Ultra?Edit什么的,就可直接打開并查看可執(zhí)行文件了,呵呵,如果你能看懂的話??你會發(fā)現(xiàn),此時看到的,全是些十六進制數(shù)值(每4位二進制數(shù)可轉(zhuǎn)換為一位十六進制數(shù)),這就是可執(zhí)行文件的具體內(nèi)容,當(dāng)然,其中就包括可執(zhí)行文件的代碼了。(一頭老牛:好親切?。ㄒ恢徊锁B:笨牛,你給我閉嘴,我眼都花了)。?


呵呵,此時,你是不是覺得看這些東西,有些那個??


這些東西看起來就像有字天書,沒人能*這玩意兒來進行分析,于是乎。就有了相應(yīng)的軟件,可以將這些十六進制數(shù)值轉(zhuǎn)換為相應(yīng)的匯編代碼,這樣的話,我們就可以對別人的軟件進行分析了。這就是所謂的逆向分析了。?


呵呵,聰明的你現(xiàn)在一定在想,如果找到軟件計算注冊碼的部分,并對其進行分析,弄懂它的計算方法,那么你不就不用通過¥的方式來進行軟件注冊了嗎?當(dāng)然,你也可以將此計算過程還原為任意一個你所熟悉的編程語言,那么,編譯后的這個程序,就叫做注冊機,它的功能就是計算某一特定軟件的注冊碼。(呵呵,是不是經(jīng)常在軟件中看到此類說明?"禁止制作和提供該軟件的注冊機及破解程序;禁止對本軟件進行反向工程,如反匯編、反編譯等")?


作者這樣做,心情我們是可以理解的,畢竟人家花了那么多心思在自己的軟件上,所以,我不希望你僅僅是因為交不起注冊費的原因來學(xué)習(xí)破解。?


總的說來,上邊兒的介紹有點兒太理想化了,上面提到的分析方法,就是所謂的靜態(tài)分析,此類分析常用的工具有W32DASM、IDA和HIEW等。靜態(tài)分析,顧名思義,就是只通過查看軟件的反匯編代碼來對軟件進行分析。一般如果只是想暴破軟件,只進行靜態(tài)分析就夠了。但要想真正的弄清注冊算法,一般還是要進行動態(tài)分析的,即能過調(diào)試器來一邊執(zhí)行程序一邊進行分析。具體內(nèi)容,我會在《破解原理》和《調(diào)試器入門》中詳細(xì)說明,呵呵,畢竟現(xiàn)在都以經(jīng)有點兒跑題了。?


我廢話說了這么多,其實就是想告訴你匯編的重要性,我不要求你精通,但最少你也得能看懂吧,要不,還談什么分析?雖然有哥們兒一點兒匯編都不懂就上路了,甚至還破掉了幾個軟件,但是,這樣是不是慘了點兒?難不成你想暴破軟件暴破一輩子??


其實你完全不用懼怕匯編的,看上去怪嚇人的,其實跟你平時背那些控件的屬性方法差不多,MFC那么多你都搞的定,匯編命令才有多少?而且,匯編不光只是在Crack軟件時有用,在好多地方也都有用,且用處巨大,所以我覺得,把匯編拿下,是件義不容辭的事:?


你只要相信它并不難就好了。?


(以下為第二次修改時加入)?


先給你講一下CPU的組成吧:?


CPU的任務(wù)就是執(zhí)行存放在存儲器里的指令序列。為此,除要完成算術(shù)邏輯操作外,還需要擔(dān)負(fù)CPU和存儲器以及I/O之間的數(shù)據(jù)傳送任務(wù)。早期的CPU芯片只包括運算器和控制器兩大部分。到了近幾年,為了使存儲器速度能更好地與運算器的速度相匹配,又在芯片中引入了高速緩沖存儲器(知道為什么P4比P4賽揚貴那么多嗎?)。(當(dāng)!一個硬物飛了過來,話外音:你講這些做什么,我們又不要設(shè)計CPU)?


你急什么嘛,由于匯編比較“低級”??;;所以它是直接操作硬件的,你以為這是用VB呢,想什么時候用變量隨手就可以拿來用,你不掌握好CPU內(nèi)部的一些工作分配情況,到時怎么來看匯編代碼啊。(當(dāng)!又一聲,重要還不快點兒說)?


除了高速緩沖存儲器之外的組成,大體上可以分為3個部分:?


1.算術(shù)邏輯部件ALU(arithmetic?logic?unit)用來進行算術(shù)和邏輯運算。這部分與我們的關(guān)系不太大,我們沒必要管它。?


2.控制邏輯。同樣與我們的關(guān)系不大。?


3.這個才是最最重要的。工作寄存器,它在計算機中起著重要的作用,每一個寄存器相當(dāng)于運算器中的一個存儲單元,但它的存取速度卻賊快賊快,比存儲器要快很多了。它用來存放計算過程中所需要的或所得到的各種信息,包括操作數(shù)地址、操作數(shù)及運算的中間結(jié)果等。下面我們專門的介紹這些寄存器。?


在介紹之前,有必要說點兒基礎(chǔ)性的知識。知道什么是32位吧,就是說寄存器是32位的,暈~~等于沒說。在CPU中,一個二進制位被看作是一位,八位就是一個字節(jié),在內(nèi)存中,就是以字節(jié)為單位來在存儲信息的,每一個字節(jié)單元給以一唯一的存儲器地址,稱為物理地址,到時候訪問相應(yīng)的內(nèi)存,就是通過這個地址。八個二進制位都能表達(dá)些什么呢?可以表達(dá)所有的ASCII碼,也就是說一個內(nèi)存單元可以存儲一個英文字符或數(shù)字什么的,而中文要用Unicode碼來表示,也就是說兩個內(nèi)存單元,才能裝一個漢字。十六位就是兩個字節(jié)這不難理解吧,當(dāng)然啦,那有了十六位,就肯定有三十二位六十四位什么的,三十二位叫做雙字,六十四位就叫做四字。今天我們所使的CPU,相信全是32位的了,除非你用的是286或更早的話。自然而然,CPU中的寄存器,也就是32位的了,也就是說一個寄存器,可以裝下32個0或1(這其中不包括段寄存器)。?


大體上來說,你需要掌握的寄存器,有十六個,我一個一個給介紹給你:?


首先,介紹小翠兒(當(dāng)!,我自己打我自己一下得了,最近看周星馳看多了),重說,首先,介紹通用寄存器。?


一共八個,分別是EAX、EBX、ECX、EDX、ESP、EBP、EDI、ESI。?


其中,EAX—EDX這四個寄存器又可稱為數(shù)據(jù)寄存器,你除了直接訪問外,還可分別對其高十六位和低十六位(還計的我說它們是32位的嗎?)進行訪問。它們的低十六位就是把它們前邊兒的E去掉,即EAX的低十六位就是AX。而且它們的低十六位又可以分別進行八位訪問,也就是說,AX還可以再進行分解,即AX還可分為AH(高八位)AL(低八位)。其它三個寄存器請自行推斷。這樣的話,你就可以應(yīng)付各種情況,如果你想操作的是一個八位數(shù)據(jù),那么可以用?MOV?AL?(八位數(shù)據(jù))或MOV?AH?(八位數(shù)據(jù)),如果你要操作的是一個十六位數(shù)據(jù),可以用MOV?AX?(十六位數(shù)據(jù))三十二位的話,就用MOV?EAX?(三十二位數(shù)據(jù))也許我這樣說,你還是會不明白,沒關(guān)系,慢慢來,我給你大概畫張圖吧,雖然不怎么漂亮:?


───────────────────────?


│????????????????????│??????????│??????????│?


│????????????????????│??????????│??????????│?


│?????高十六位??????EAX????AH??AX????AL??????│?


│????????????????????│??????????│??????????│?


│????????????????????│??????????│??????????│?


───────────────────────?


(我倒啊
這個圖為啥老是不能正常顯示?我都重畫三遍了)??


明白了嗎?不明白沒有關(guān)系,你就按你自己的理解能力,能理解多少,就理解多少。?


這四個寄存器,主要就是用來暫時存放計算過程中所用的操作數(shù)、結(jié)果或其它信息。?


而ESP、EBP、EDI、ESI這四個呢,就只能用字來訪問,它們的主要用途就是在存儲器尋址時,提供偏移地址。因此,它們可以稱為指針或變址寄存器。話說回來,從386以后,所有的寄存器都可以用來存儲內(nèi)存地址。(這里給你講一個小知識,你在破解的時候是不是看到過[EBX]這樣的形式呢?這就是說此時EBX中裝的是一個內(nèi)存地址,而真正要訪問的,就是那那個內(nèi)存單元中所存儲的值)。?


在這幾個寄存器中,ESP稱為堆棧指針寄存。堆棧是一個很重要的概念,它是以“后進先出”方式工作的一個存儲區(qū),它必須存在于堆棧段中,因而其段地址存放于SS寄存器中。它只有一個出入口,所以只有一個堆棧指針寄存器。ESP的內(nèi)容在任何時候都指向當(dāng)前的棧頂。我這樣說你可能會覺的還是不明白,那我舉個例子吧,知道民工蓋房吧,假設(shè)有兩個民工,一個民工(以下簡稱民工A)要向地上鋪磚,另一個民工(以下簡稱民工B)給民工A遞磚,民工A趴在地上,手邊是民工B從遠(yuǎn)處搬來的板磚,他拿起來就用,民工B從遠(yuǎn)處搬來后,就還放在那一堆磚上,這樣,民工A拿著用后,民工B隨既就又補了上去,這就是后進先出。你在腦子里想象一下這個這程。有沒有想明白,民工A永遠(yuǎn)是從最上邊開始拿磚。堆棧就是這樣,它的基址開始于一個高地址,然后每當(dāng)有數(shù)據(jù)入棧,它就向低地址的方向進行存儲。相應(yīng)的入棧指令是PUSH。每當(dāng)有數(shù)據(jù)入棧,ESP就跟著改變,總之,它永遠(yuǎn)指向最后一個壓入棧的數(shù)據(jù)。之后,如果要用壓入堆棧的數(shù)據(jù),就用出棧指令將其取出。相應(yīng)的指令是POP,POP指令執(zhí)行后,ESP會加上相應(yīng)的數(shù)據(jù)位數(shù)。?


特別是現(xiàn)在到了Win32系統(tǒng)下面,堆棧的作用更是不可忽視,API所用的數(shù)據(jù),均是*堆棧來傳送的,即先將要傳送的數(shù)據(jù)壓入堆棧,然后CALL至API函數(shù),API函數(shù)會在函數(shù)體內(nèi)用出棧指令將相應(yīng)的數(shù)據(jù)出棧。然后進行操作。以后你就會知道這點的重要性了。許多明碼比較的軟件,一般都是在關(guān)鍵CALL前,將真假兩個注冊碼壓入棧。然后在CALL內(nèi)出棧后進行比較。所以,只要找到個關(guān)鍵CALL,就能在壓棧指令處,下d命令來查看真正的注冊碼。具體內(nèi)容會在后面詳細(xì)介紹,本章暫不予討論。?


另外還有EBP,它稱為基址指針寄存器,它們都可以與堆棧段寄存器SS聯(lián)用來確定堆棧中的某一存儲單元的地址,ESP用來指示段頂?shù)钠频刂罚鳨BP可作為堆棧區(qū)中的一個基地址以便訪問堆棧中的信息。ESI(源變址寄存器)和EDI(目的變址寄存器)一般與數(shù)據(jù)段寄存器DS聯(lián)用,用來確定數(shù)據(jù)段中某一存儲單元的地址。這兩個變址寄存器有自動增量和自動減量的功能,可以很方便地用于變址。在串處理指令中,ESI和EDI作為隱含的源變址和目的變址寄存器時,ESI和DS聯(lián)用,EDI和附加段ES聯(lián)用,分別達(dá)到在數(shù)據(jù)段和附加段中尋址的目的。目前暫時不明白不要緊。?


接下來,再介紹如花(當(dāng)當(dāng)當(dāng),我再打自己三下算了)接下來,介紹一下專用寄存器,呵呵,有沒有被這個名字嚇倒?看起來怪專業(yè)的。?


所謂的專用寄存器,有兩個,一個是EIP,一個是FLAGS。?


我們先來說這個EIP,可以說,EIP算是所有寄存器中最重要的一個了。它的意思就是指令指針寄存器,它用來存放代碼段中的偏移地址。在程序運行的過程中,它始終指向下一條指令的首地址。它與段寄存器CS聯(lián)用確定下一條指令的物理地址。當(dāng)這一地址送到存儲器后,控制器可以取得下一條要執(zhí)行的指令,而控制器一旦取得這條指令就馬上修改EIP的內(nèi)容,使它始終指向下一條指令的首地址??梢姡嬎銠C就是用EIP寄存器來控制指令序列的執(zhí)行流程的。?


那些跳轉(zhuǎn)指令,就是通過修改EIP的值來達(dá)到相應(yīng)的目的的。?


再接著我們說一下這個FLAGS,標(biāo)志寄存器,又稱PSW(program?status?word),即程序狀態(tài)寄存器。這一個是存放條件標(biāo)志碼、控制標(biāo)志和系統(tǒng)標(biāo)志的寄存器。?


其實我們根本不需要太多的去了解它,你目前只需知道它的工作原理就成了,我舉個例子吧:?


Cmp?EAX,EBX??;用EAX與EBX相減?


JNZ?00470395???;不相等的話,就跳到這里;?


這兩條指令很簡單,就是用EAX寄存器裝的數(shù)減去EBX寄存器中裝的數(shù)。來比較這兩個數(shù)是不是相等,當(dāng)Cmp指令執(zhí)行過后,就會在FLAGS的ZF(zero?flag)零標(biāo)志位上置相應(yīng)值,如果結(jié)果為0,也就是他們兩個相等的話,ZF置1,否則置0。其它還有OF(溢出標(biāo)志)SF(符號標(biāo)志)CF(進位標(biāo)志)AF(輔助進位標(biāo)志)PF(奇偶標(biāo)志)等。?


這些你目前沒必要了解那么清楚,會用相應(yīng)的轉(zhuǎn)移指令就行了。?


最后要介紹的就是段寄存器了(剛才是誰說的櫻紅?反正不是我)?


這部分寄存器一共六個,分別是CS代碼段,DS數(shù)據(jù)段,ES附加段,SS堆棧段,F(xiàn)S以及GS這兩個還是附加段。?


其實現(xiàn)在到了Win32環(huán)境下,段寄存器以經(jīng)不如DOS時代那樣重要了。?


所以,我們知道就行了。?


啰嗦了這么多,相信你對CPU以經(jīng)有了個大概的了解了吧。什么?還是什么也不明白?呵呵,那也不要灰心,請相信這是我的錯,是我沒有講清楚而已,你可以去參考一些書籍。我始終覺的,你案頭有一本講匯編的書是非常非常有必要的,我這邊兒是清華版的《80x86匯編語言程序設(shè)計》沈美明主編,46元。?


我們接下來就再講一講一些常用的匯編指令吧。(由于考慮到目前以經(jīng)有了相應(yīng)的帖子,所以,我只是從匯編指令中,挑出一些最常用,需要掌握的,更多內(nèi)容,還請參見書本。)?


CMP?A,B?比較A與B其中A與B可以是寄存器或內(nèi)存地址,也可同時是兩個寄存器,但不能同都是內(nèi)存地址。這個指令太長見了,許多明碼比較的軟件,就用這個指令。?


MOV?A,B?把B的值送給A其中,A與B可是寄存器或內(nèi)存地址,也可同時是兩個寄存器,但不能同都是內(nèi)存地址。?


Xor?a,a異或操作,主要是用來將a清空?


LEA裝入地址,例如LEA?DX,string?將字符的地址裝入DX寄存器?


PUSH?壓棧?


POP?出棧?


ADD?加法指令?格式:ADD?DST,SRC?執(zhí)行的操作:(DST)<-(SRC)+(DST)?


SUB?減法指令?格式:SUB?DST,SRC?執(zhí)行的操作:(DST)<-(DST)-(SRC)?


MUL?無符號乘法指令?格式:?MUL?SRC??執(zhí)行的操作:字節(jié)操作(AX)<-(AL)*(SRC);字操作(DX,AX)<-(AX)*(SRC);雙字操作:(EDX,EAX)<-(EAX)*(SRC)?


DIV?無符號除法指令?格式:DIV?SRC??執(zhí)行的操作:字節(jié)操作:16們被除數(shù)在AX中,8位除數(shù)為源操作數(shù),結(jié)果的8位商在AL中,8位余數(shù)在AH中。表示為:?


(AL)<-(AX)/(SRC)的商,(AH)<-(AX)/(SRC)的余數(shù)。字操作:32位被除數(shù)在DX,AX中。其中DX為高位字,16位除數(shù)為源操作數(shù),結(jié)果的16位商在AX中,16位余數(shù)在DX中。表示為:(AX)<-(DX,AX)/(SRC)的商,(DX)<-(DX,AX)/(SRC)的余數(shù)。?


雙字操作:64位的被除數(shù)在EDX,EAX中。其中EDX為高位雙字;32位除數(shù)為源操作數(shù),結(jié)果的32位商在EAX中,32位余數(shù)在EDX中。表示為:?


(EAX)<-(EDX,EAX)/(SRC)的商,(EDX)<-(EDX,EAX)/(SRC)的余數(shù)。?


NOP?無作用,可以用來抹去相應(yīng)的語句,這樣的話,嘿嘿嘿…?


CALL調(diào)用子程序,你可以把它當(dāng)作高級語言中的過程來理解。?


控制轉(zhuǎn)移指令:?


JE?或JZ?若相等則跳?


JNE或JNZ?若不相等則跳?


JMP?無條件跳?


JB?若小于則跳?


JA?若大于則跳?


JG?若大于則跳?


JGE?若大于等于則跳?


JL?若小于則跳?


JLE?若小于等于則跳?


總的來說,以上幾個,都是比較常見的,需要掌握,但需要掌握的絕不止這幾個,其它的指令希望你能在私下里再了解一下,可以找相應(yīng)的教程來看。?


剛才忘了,現(xiàn)在再把數(shù)制轉(zhuǎn)換也給貼上:?


首先說二進制轉(zhuǎn)換為十進制的問題:?


各位二進制數(shù)碼乘以與其對應(yīng)的權(quán)之和即為該二進制相對應(yīng)的十進制數(shù)。例如:?


10100=2的4次方+2的2次方,也就是十進制數(shù)20。?


11000=2的4次方+2的3次方,也就是十進制數(shù)24。?


接著說一下十進制數(shù)轉(zhuǎn)換為二進制數(shù)的方法:?


這樣的方法到底有多少,我也不清楚,我只講最簡單的一個-除法:?


把要轉(zhuǎn)換的十進制數(shù)的整數(shù)部分不斷除以2,并記下余數(shù),直到商為0為止。?


例:N=34D(說明一下,你可能在某些數(shù)字的后邊看到過加有一個字母,這個字母便是用來表示數(shù)制的,十進制數(shù)用D,二進制數(shù)用B,八進制數(shù)用O,十六進制數(shù)用H)?


??34/2=17?????(a0=0)?


??17/2=8??????(a1=1)?


??8/2=4???????(a2=0)?


??4/2=2???????(a3=0)?


??2/2=1???????(a4=0)?


??1/2=0???????(a5=1)?


所以N=34D=100010B。?


對于被轉(zhuǎn)換的十進制數(shù)的小數(shù)部分則應(yīng)不斷乘以2,并記下其整數(shù)部分,直到結(jié)果的小數(shù)部分為0為止。?


十六進制數(shù)與二進制數(shù)、十進制數(shù)之間的轉(zhuǎn)換:?


總的來說,十六進制數(shù)與二進數(shù)之間的轉(zhuǎn)換,應(yīng)該算是很簡單的了,你只需把與之相對應(yīng)的數(shù)值進行轉(zhuǎn)換就成了。?


十六進制數(shù)的基數(shù)是16,共有16個數(shù)碼,它們是0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F。其中A表示十進制中的10,其余類推。它們與二進制和十進制數(shù)的關(guān)系如下:?


0H=0D=0000B,1H=1D=0001B,2H=2D=0010B,3H=3D=0011B,4H=4D=0100B,5H=5D=0101B,6H=6D=0110B,7H=7D=0111B,8H=8D=1000B,9H=9D=1001B,AH=10D=1010B,BH=11D=1011B,CH=12D=1100B,DH=13D=1101B,EH=14D=1110B,FH=15D=1111B?


所以,二進制與十六進制之間要進行轉(zhuǎn)換的話,只要把它們由低到高每四位組成一級,直接用十六進制來表示就可以了:?


例:???1000??????1010??????0011???????0101?


???????8?????????A?????????3??????????5?


十六進制轉(zhuǎn)二進制則用只需將每一位用四位二進制數(shù)來表示就成了:?


例:?????A?????????B?????????1??????????0?


?????1010??????1011???????0001???????0000?


最后是十六進制數(shù)與十進制數(shù)之間的互相轉(zhuǎn)換?


十六進制數(shù)轉(zhuǎn)十進制數(shù)?


各位十六進制數(shù)與其對應(yīng)權(quán)值的乘積之和即為與此十六進制數(shù)相對應(yīng)的十進制數(shù)。?


例:N=BF3CH?


???=11*16的3次方+15*16的2次方+3*16的1次方+12*16的0次方?


???=11*4096+15*256+3*16+12*1?


???=48956D?


十進制轉(zhuǎn)十六進制?


我還是只講最簡單的除法:?


把要轉(zhuǎn)換的十進制數(shù)的整數(shù)值部分不斷除以16,并記下余數(shù),直到商為0為止。?


例N=48956D?


???48956/16=3059???????(a0=12)?


???3059/16=191?????????(a1=3)?


???191/16=11???????????(a2=15)?


???11/16=0?????????????(a3=11)?


所以N=48956D=BF3CH。?


通過以上的介紹,我不知道你到底看懂沒有,如果有的話,請你去看一下書本,把我沒講到的地方和講過了的地方都仔細(xì)地看幾遍。如果你根本就沒有看懂,那么你就更需要去看書了,不要因為這就喪失掉學(xué)習(xí)的信心。你認(rèn)真地把前邊兒的CPU介紹看完,弄清楚寄存器的概念,再把后邊匯編指令拿下,就可以上路了。你認(rèn)真學(xué),認(rèn)真背的話,會發(fā)現(xiàn)其實并沒你想像中的那么難。一星期的時間,就可大概掌握了,但只是掌握而已,最起碼可以看懂匯編代碼了。要真想學(xué)好的話,就連后邊兒的也一同看了吧,再寫一些小程序來練練手。當(dāng)然想精通匯編,那可不是一天兩天一月兩月的事,但你只要有恒心,有什么搞不定的?CPU也是人做的,指令只是其中的一部分而已,人家能做出CPU,你還怕連使用都學(xué)不會??



課后FAQ?


Q:我以前學(xué)過8086/8088,并且也在DOS下寫過程序,能行嗎??


A:絕對能行,相對8086/8088,現(xiàn)在的CPU在基本指令方面,也沒有添加多少新的指令。你只需了解一下各寄存器的變化以及補充一下Windows程序的知識就成了。而且,既然你用匯編在DOS下寫過程序,那么對Debug等調(diào)試器,肯定已經(jīng)很上手了,所以你有先天的優(yōu)勢。?


Q:匯編對我來說不成問題,可我為什么總是不上手呢??


A:呵呵,這樣的老鳥倒還有不少,他們把匯編用的相當(dāng)熟練,但是,只是因為經(jīng)驗的原因,所以才覺的不上手的,許多人當(dāng)初不也都這樣嗎?最起碼我就是,見了CALL就跟進,呵呵,倒跟了不少API,所以對于這部分高手,你只需多練練手以及掌握一些分析的技巧就成了。?


Q:我沒學(xué)過編程,能學(xué)匯編嗎??


A:總的來說,也行。不過希望匯編的學(xué)習(xí),不會使你丟掉學(xué)習(xí)其它高級語言的信心。:)?



答網(wǎng)友問?


Q:寄存器可以隨便用么,有沒有什么限制?寫個程序的時候那些變量什么的可以放在任意的寄存器么??


A:呵呵,我現(xiàn)在就來回答樓上朋友的問題。?


寄存器有它的使用機制,及各個寄存器都有著明確的分工。?


如小翠兒??如數(shù)據(jù)寄存器(EAX-EDX),它們都是通用寄存器,及在軟件中,任何數(shù)據(jù)都可存放于此。但是除此之外,它們又可以都可以用于各自的專用目的。?


例如:?


EAX可以作為累加器來使用,所以它是算術(shù)運算的主要寄存器。在乘除法等指令中指定用來存放操作數(shù)。比如在乘法中,你可以用AL或AX或EAX來裝被乘數(shù),而AX或DX:AX或EAX或EDX:EAX則用來裝最后的積。?


EBX一般在計算存儲器地址時,它經(jīng)常用作基址寄存器。?


ECX則常用來保存計數(shù)值,如在移位指令它用來裝位移量、循環(huán)和串處理指令中作隱含的計數(shù)器。?


最后就剩下四大天王中的黎明了,近一段時間來,他總是比較低調(diào)
(你別打我了,我去撞墻好了)最后就剩下EDX了,一般在作雙字長運算時把DX和AX組在一起存放一個雙字長數(shù)(你還記的什么是雙字長吧,舉個例子,比如說有一個數(shù)二進制數(shù)據(jù)01101000110101000100100111010001,你要把它寄存起來,就可以把0110100011010100(即高十六位)放在DX中,把0100100111010001(即低十六位)放在AX中,這個數(shù)表示為DX:AX)當(dāng)然完全可以用一個EDX就把這個數(shù)給裝下。所以,還可以用EDX:EAX來裝一個64位數(shù)據(jù),這個你會推斷出來吧。?


而ESP、EBP、EDI、ESI,我上邊兒以經(jīng)大概介紹的差不多了,所以這里不說它們了。?


當(dāng)然還有其它的一些限制,因為我們只是要看程序的匯編代碼(人家寫好了的,肯定不會犯錯誤吧),而不是要去寫,所以可以不必掌握。有性趣的話,去看相關(guān)書籍。?


另外再說一下你的最后一個問題“寫個程序的時候那些變量什么的可以放在任意的寄存器么??”這句話我不明白你要問的是什么。我想你可能是把一些關(guān)點給搞錯了,變量這詞通常都是出現(xiàn)在高級語言中的,而你用高級語言寫程序的話,完全不用理解那些寄存器什么的,這些都跟高級語言沒什么關(guān)系。但是最終,高級語言也還是把你寫的程序轉(zhuǎn)換為對寄存器、內(nèi)部存儲器的操作。?


<本章完>


?1
int?fun()
?2

{?
?3
????int?a=0;?
?4
????register?int?i;?
?5
????for?(i=0;?i<1000;?i++)
?6
????
{
?7
????????a+=i;?
?8
????}
?9
????return?a;?
10
}
11
12
int?main(int?argc,?char*?argv[])
13

{
14
????int?a;
15
????a?=?fun();
16
????printf("%d\n",a);
17
18
????return?0;
19
} 上面這段程序的Assembly為:
//ESP為堆棧指針,每push一個register,ESP減4始終指向棧頂
//每一個語句的前部分那些數(shù)字表示EIP

004125F0??push????????ebp????????//ebp?0012FFC0
004125F1??mov?????????ebp,esp????//esp?0012FF70->EBP?=?0012FF70
004125F3??sub?????????esp,44h????//ESP?=?0012FF2C
004125F6??push????????ebx????????//ESP?=?0012FF28
004125F7??push????????esi????????//ESP?=?0012FF24
004125F8??push????????edi????????//ESP?=?0012FF20
????int?a;
????a?=?fun();
004125F9??call????????fun?(4116D6h)

/**//*
004116D6??jmp?????????fun?(4125A0h)????//ESP?=?0012FF1C

int?fun()
{?????????????????????//ESP?=?0012FF1C
004125A0??push????????ebp??????????//EBP?=?0012FF70????ESP?=?0012FF18
004125A1??mov?????????ebp,esp?????????//EBP?=?0012FF18在fun函數(shù)中未變
004125A3??sub?????????esp,48h?????????//ESP?=?0012FED0
004125A6??push????????ebx??????????//ESP?=?0012FECC
004125A7??push????????esi??????????//ESP?=?0012FEC8
004125A8??push????????edi??????????//ESP?=?0012FEC4一直未變直到pop棧
????int?a=0;?
//a的值:0x6bc9b010(隨即數(shù))???????????//0012FF14?=?6BC9B010(Register窗口中)?//表示0012FF14處的值為6BC9B010
//dword表示雙字節(jié),ptr表示a為內(nèi)存中的變量?[a]表示a的值
004125A9??mov?????????dword?ptr?[a],0??????//0012FF14(a的地址?)之后連續(xù)四個字節(jié)的值為0
//i的值:0x0041b4e6????????????//0012FF10?=?0041B4E6
????register?int?i;?
????for?(i=0;?i<1000;?i++)
004125B0??mov?????????dword?ptr?[i],0?????//0012FF10之后連續(xù)四個字節(jié)的值為0
//fun函數(shù)的起始地址為0x004125a0
004125B7??jmp?????????fun+22h?(4125C2h)?//0x004125a0+22h==0x4125C2h

004125B9??mov?????????eax,dword?ptr?[i]?
004125BC??add?????????eax,1?
//0012FF10?=?00000001?
//第一次運行到這里可以在Memory窗口中看到0x0012FF10處的值為01?00?00?00要反過來看值
004125BF??mov?????????dword?ptr?[i],eax?
//0012FF10?=?00000000
004125C2??cmp?????????dword?ptr?[i],3E8h?
//如果i地址中的之[i]與3E8h(1000)相等,跳到EIP=0x4125D6處即跳出循環(huán)
004125C9??jge?????????fun+36h?(4125D6h)?
????{
????????a+=i;?
//將a的值放到寄存器Eax中
004125CB??mov?????????eax,dword?ptr?[a]????//EAX?=?00000000?a、i都為0
004125CE??add?????????eax,dword?ptr?[i]?//EAX?=?00000000
004125D1??mov?????????dword?ptr?[a],eax?
????}
//無條件跳到4125B9h處即i++
004125D4??jmp?????????fun+19h?(4125B9h)?
????return?a;?
004125D6??mov?????????eax,dword?ptr?[a]?
}
//之前ESP?=?0012FEC4?EBP?=?0012FF18
004125D9??pop?????????edi??//ESP?=?0012FEC8
004125DA??pop?????????esi??//ESP?=?0012FECC
004125DB??pop?????????ebx??//ESP?=?0012FED0
004125DC??mov?????????esp,ebp?//ESP?=?0012FF18
004125DE??pop?????????ebp??//ESP?=?0012FF1C?EBP?=?0012FF70與main函數(shù)調(diào)用fun時的值相等
004125DF??ret????
*/
//ESP?=?0012FF20?EBP?=?0012FF70
004125FE??mov?????????dword?ptr?[a],eax?
????printf("%d\n",a);
00412601??mov?????????eax,dword?ptr?[a]?
00412604??push????????eax??
00412605??push????????offset?string?"%d\n"?(42DB5Ch)?
0041260A??call????????@ILT+2495(_printf)?(4119C4h)?
0041260F??add?????????esp,8?

????return?0;
//將Eax中的值置為0
00412612??xor?????????eax,eax?
}其中那些進棧出棧的作用還不明白