• <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>
            C++博客 聯系 聚合 管理  

            Blog Stats

            文章分類(17)

            收藏夾(2)

            文章檔案(18)

            相冊

            Blogs

            citywanderer

            今天比較搞笑,在看《Inside vc.net》的com的時候,講到了c++中的vftable,這個虛擬表還真不是很清楚,所以就在網上查了一些關于vftable的資料,地址為:http://blog.csdn.net/gzjh1/archive/2006/05/26/756024.aspx,轉貼一下:)
            在面向對象的C++語言中,虛函數(virtual?function)是一個非常重要的概念。因為它充分體現了面向對象思想中的繼承和多態性這兩大特性,在C++語言里應用極廣。比如在微軟的MFC類庫中,你會發現很多函數都有virtual關鍵字,也就是說,它們都是虛函數。難怪有人甚至稱虛函數是C++語言的精髓。?
            ????????那么,什么是虛函數呢,我們先來看看微軟的解釋:
            ????????虛函數是指一個類中你希望重載的成員函數,當你用一個基類指針或引用指向一個繼承類對象的時候,你調用一個虛函數,實際調用的是繼承類的版本。
            ???????????????????????????????????????????????????????????????——摘自MSDN
            ????????這個定義說得不是很明白。MSDN中還給出了一個例子,但是它的例子也并不能很好的說明問題。我們自己編寫這樣一個例子:
            #include?
            "stdio.h"
            #include?
            "conio.h"
            ?
            class?Parent
            {
            public:
            ?
            char?data[20];
            ?
            void?Function1();
            ?
            virtual?void?Function2();??//?這里聲明Function2是虛函數
            }
            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();????//?注意這里,執行的是哪一個Function2?
            ?return?0;
            }

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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


            2.控制邏輯。同樣與我們的關系不大。?


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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


            那些跳轉指令,就是通過修改EIP的值來達到相應的目的的。?


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


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


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


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


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


            這些你目前沒必要了解那么清楚,會用相應的轉移指令就行了。?


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


            這部分寄存器一共六個,分別是CS代碼段,DS數據段,ES附加段,SS堆棧段,FS以及GS這兩個還是附加段。?


            其實現在到了Win32環境下,段寄存器以經不如DOS時代那樣重要了。?


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


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


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


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


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


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


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


            PUSH?壓棧?


            POP?出棧?


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


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


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


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


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


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


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


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


            CALL調用子程序,你可以把它當作高級語言中的過程來理解。?


            控制轉移指令:?


            JE?或JZ?若相等則跳?


            JNE或JNZ?若不相等則跳?


            JMP?無條件跳?


            JB?若小于則跳?


            JA?若大于則跳?


            JG?若大于則跳?


            JGE?若大于等于則跳?


            JL?若小于則跳?


            JLE?若小于等于則跳?


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


            剛才忘了,現在再把數制轉換也給貼上:?


            首先說二進制轉換為十進制的問題:?


            各位二進制數碼乘以與其對應的權之和即為該二進制相對應的十進制數。例如:?


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


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


            接著說一下十進制數轉換為二進制數的方法:?


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


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


            例:N
            =34D(說明一下,你可能在某些數字的后邊看到過加有一個字母,這個字母便是用來表示數制的,十進制數用D,二進制數用B,八進制數用O,十六進制數用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。?


            對于被轉換的十進制數的小數部分則應不斷乘以2,并記下其整數部分,直到結果的小數部分為0為止。?


            十六進制數與二進制數、十進制數之間的轉換:?


            總的來說,十六進制數與二進數之間的轉換,應該算是很簡單的了,你只需把與之相對應的數值進行轉換就成了。?


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


            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?


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


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


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


            十六進制轉二進制則用只需將每一位用四位二進制數來表示就成了:?


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


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


            最后是十六進制數與十進制數之間的互相轉換?


            十六進制數轉十進制數?


            各位十六進制數與其對應權值的乘積之和即為與此十六進制數相對應的十進制數。?


            例:N
            =BF3CH?


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


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


            ???
            =48956D?


            十進制轉十六進制?


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


            把要轉換的十進制數的整數值部分不斷除以16,并記下余數,直到商為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。?


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



            課后FAQ?


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


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


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


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


            Q:我沒學過編程,能學匯編嗎??


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



            答網友問?


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


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


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


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


            例如:?


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


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


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


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


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


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


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


            <本章完>


            ?1int?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
            12int?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始終指向棧頂
            //每一個語句的前部分那些數字表示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函數中未變
            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(隨即數)???????????//0012FF14?=?6BC9B010(Register窗口中)?//表示0012FF14處的值為6BC9B010
            //dword表示雙字節,ptr表示a為內存中的變量?[a]表示a的值
            004125A9??mov?????????dword?ptr?[a],0??????//0012FF14(a的地址?)之后連續四個字節的值為0
            //i的值:0x0041b4e6????????????//0012FF10?=?0041B4E6
            ????register?int?i;?
            ????for?(i=0;?i<1000;?i++)
            004125B0??mov?????????dword?ptr?[i],0?????//0012FF10之后連續四個字節的值為0
            //fun函數的起始地址為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處即跳出循環
            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函數調用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?
            }
            其中那些進棧出棧的作用還不明白
            posted on 2006-07-02 16:46 citywanderer 閱讀(441) 評論(0)  編輯 收藏 引用 所屬分類: C++
            久久精品桃花综合| 久久综合色老色| 久久久久久国产精品免费免费| 要久久爱在线免费观看| 久久综合一区二区无码| 久久青青草原国产精品免费| 久久精品水蜜桃av综合天堂| 午夜精品久久久久久影视riav| segui久久国产精品| 亚洲精品tv久久久久久久久| 少妇熟女久久综合网色欲| 亚洲AV无码久久精品蜜桃| 久久人做人爽一区二区三区 | 久久久久久一区国产精品| 国产日韩欧美久久| 久久久精品国产亚洲成人满18免费网站| 久久国产乱子伦精品免费午夜| 久久国产三级无码一区二区| 亚洲中文字幕伊人久久无码 | 久久久久波多野结衣高潮| 日韩精品久久久肉伦网站| 99久久国产亚洲高清观看2024 | 国产综合久久久久久鬼色| 精品午夜久久福利大片| 久久久精品国产| 国产精品久久久久久一区二区三区| 久久AAAA片一区二区| 久久超乳爆乳中文字幕| 国产视频久久| 亚洲国产精品狼友中文久久久| 无码精品久久久天天影视| 国产福利电影一区二区三区久久久久成人精品综合 | 蜜臀久久99精品久久久久久| 一本久久免费视频| 久久99亚洲网美利坚合众国| 国产精品久久久久乳精品爆| 久久夜色精品国产亚洲| 99久久www免费人成精品| 麻豆一区二区99久久久久| 久久久精品人妻无码专区不卡| 蜜臀av性久久久久蜜臀aⅴ|