準備你的行囊----建立環(huán)境
為了讓大家更為輕松,除非迫不得已,我們盡量使用系統上已經(jīng)安裝的工具,在這一章里,下面兩個(gè)外部工具是必須的
nasm:作為匯編環(huán)境,官方網(wǎng)站
http://www.nasm.us/UltraEdit:作為16進(jìn)制文本編輯器
同時(shí),讀者應該稍微具備的匯編知識,不用太多,知道下面這些指令的意義和用法即可
MOV 數據傳送指令
ADD 加法指令
PUSH,POP 堆棧指令
CMP 比較指令
LEA 取地址指令
XOR 異或指令
所有的轉移指令:JMP,JZ,JE
如果你還想進(jìn)一步了解機器碼的規范,可以下載
http://download.csdn.net/source/1103630,里面有Intel的文檔,以及本文用到的操作碼查詢(xún)表
用0和1寫(xiě)程序
曾經(jīng)有人發(fā)給我一張圖片,說(shuō)世界上'最牛程序員'的鍵盤(pán),鍵盤(pán)上一共兩三個(gè)鍵,01,當時(shí)年少無(wú)知,崇拜到抓狂,今天就讓我們當回'頂尖高手',用01直接寫(xiě)程序
請打開(kāi)一個(gè)十六進(jìn)制編輯器比如UltraEdit
把下面的二進(jìn)制代碼化為16進(jìn)制輸入進(jìn)去(主要無(wú)法直接輸入二進(jìn)制代碼)
1011 1000 0000 0001 0000 0000 0000 0101 0000 0001 0000 0000
十六進(jìn)制為B8 01 00 05 01 00
將文件保存為test.com文件,恭喜你,你剛剛完成了一個(gè)偉大'壯舉',你 成功的讓CPU計算出了1+1等于幾,如果你興匆匆的運行它,什么結果都看不到,那是因為為了保證代碼簡(jiǎn)單,還沒(méi)有告訴CPU輸出結果的緣故,你愿意的話(huà),可以運行cmd,切換到保存test.com的目錄,通過(guò)執行debug test.com,來(lái)看看我們到底輸入了什么
1011 1000 代表 MOV ax
0000 0001 0000 0000 代表1
0000 0101 代表 ADD ax
0000 0001 0000 0000 代表 0001h
全文加起來(lái)表示
MOV ax,01h
ADD ax,01h
可以看出,我們的代碼對應了兩條機器指令,每個(gè)指令分成兩個(gè)部分,比如MOV ax,1的二進(jìn)制代碼,1011 1000 代表 MOV ax他指定了本條指令的操作,叫做指令操作碼(Opcode),0000 0001 0000 0000 代表1,指定了操作的操作數,可以看出機器碼是有自己固定的格式,只要掌握了這個(gè)格式,查詢(xún)對應的操作碼,應該就可以掌握機器語(yǔ)言了
當然,事情也有復雜的一面,同一條匯編指令其操作碼可能根據尋址方式或寄存器或操作數的位數的變化發(fā)生變化,比如同樣是MOV指令,MOV al,1 和MOV ax,1中Mov的操作碼分別為B0(1011 0000)和B8(1011 1000),而MOV ax,[ES:100]操作碼會(huì )變成26 A1(前面26是段超越前綴,現在不用仔細追究),Intel8086規定的MOV指令就有12種之多,而且操作碼的長(cháng)度還有可能不同,這些操作碼都可以在表
x86操作碼查詢(xún)表>中對應的查到,不需要記憶,下面我們就來(lái)了解機器語(yǔ)言指令的格式
自己設計機器語(yǔ)言指令格式
在閱讀Intel公司的實(shí)現前,為了不讓您陷入一堆的解釋和說(shuō)明中迷惘無(wú)助,我們先來(lái)熱熱身,做點(diǎn)有趣的事情---思考一下如果讓你自己來(lái)設計機器語(yǔ)言指令的格式,那么你會(huì )做出怎樣的設計,下面是我的設計思路
首先匯編代碼和機器代碼是對應的,所以讓我們來(lái)看看一條典型x86匯編指令:
MOV ax,1
這條指令由三個(gè)部分組成:指令,目的操作數,源操作數
指令為Mov,目的操作數ax,源操作數1,
ADD bx,2
指令為Add,目的操作數bx,源操作數2
相對應的我們可以考慮把機器指令格式也分成三個(gè)部分:指令碼,目的操作數,原操作數
由于寄存器的數目是有限的,我們可以列個(gè)寄存器機器碼指令表,這樣代碼中的寄存器就可以被替換為如下的機器代碼,比如
000 AX
001 CX
010 DX
011 BX
100 SP
101 BP
110 SI
111 DI
然后我們再列一個(gè)指令碼表,比如
MOV=00000000
ADD=00000001
AND=00000010
.
.
.
則MOV ax,1就可以變成 00000000 00000000 00000001(ax是000)
但是這樣簡(jiǎn)單清晰的三個(gè)部分會(huì )出現一些問(wèn)題mov bx,0,和mov bx,ax就有可能混淆了,因為ax的代碼是000,和立即數0相同
所以我們需要一個(gè)標志位來(lái)確定是那種操作數,操作數有下面5種可能
目的操作數和原操作數的大小就比較難了,因為操作數可能是
1)一個(gè)立即數 比如1
2)一個(gè)寄存器 ax,bx,cx,dx
3)一個(gè)內存地址 [StringLable]
4)一個(gè)由一個(gè)或多個(gè)寄存器組成的內存地址
[ebx],[ebx+esi],[es:ebx+esi]
5)一個(gè)由一個(gè)或多個(gè)寄存器再加上一個(gè)偏移量組成的內存地址
[ebx+esi]
顯然我們需要兩個(gè)標志字段,每個(gè)5個(gè)值,(每個(gè)操作數一個(gè))來(lái)標定自己是哪種操作數,每個(gè)標志字段只要3位就夠了,我把這兩個(gè)標志字段放到一個(gè)字節里,放在兩個(gè)操作數前面
格式一:
指令碼 保留2位|標志1|標志2| 操作數1 操作數2
Mov ax,1 00000000 00|001|000 00000000 00000001
標志的意義
000:立即數
001:寄存器
010:內存地址
011:多個(gè)寄存器
100:多個(gè)寄存器加偏移量
問(wèn)題又出來(lái)了,當標志位為100,這時(shí),操作數應該是多個(gè)寄存器+偏移量,假設每個(gè)寄存器占3位,兩個(gè)就是6位,留給我們的偏移量的空間只有兩位,也就是說(shuō)偏移量最大只有3,這顯然是不夠的,所以我們必須加上一個(gè)字節表示偏移量,而當不需要偏移量的時(shí)候,這兩個(gè)字段可以不存在,也就是說(shuō)表格變成了
格式二:
指令碼 00|標志1|標志2 操作數1
偏移量 00|操作數2
bbb|iii 偏移量
Mov ax,[bp+si+5] 00000000 00|001|100 00000000 00|101|110 00000110
怎么樣,有點(diǎn)像樣子了吧,固定長(cháng)度8位的指令碼可能有256種指令,我想最基本的操作,AND,OR,XOR,ADD,SHR,SHL等等不會(huì )太多,而其他的操作都可以由這些操作組合而成,比如減法是補碼的加法,乘法是重復相加等
似乎大部分問(wèn)題都已經(jīng)解決了,但是稍微熟悉x86匯編的朋友就會(huì )知道,不可能有任何指令的兩個(gè)操作數都是內存,也就是永遠不會(huì )出現
MOV [dx+di],[ex+si]這樣的語(yǔ)句,要想實(shí)現這樣的移動(dòng)我們必須要把源操作數移動(dòng)到一個(gè)寄存器里,然后再從寄存器里移動(dòng)到目的地
反應在我們的設計上,我們就會(huì )發(fā)現兩個(gè)偏移量是多余的,任何情況下最多會(huì )有一個(gè)被使用到,所以表格可以修改成這樣
格式三:
指令碼 00|標志1|標志2 偏移量 操作數1
操作數2
00|bbb|iii
MOV ax,[bp+si+5] 00000000 00|001|100 00000110 00000000 00|101|110
MOV ax,bx 00000000 00|001|001 無(wú) 00000000 00000011
其實(shí)看看上表的第二條語(yǔ)句,我們就會(huì )發(fā)現一個(gè)很重大的問(wèn)題,那就是空間浪費,第二行中所有黑體的部分都是被浪費掉的空間,浪費了12位,總共才32位的代碼,居然就浪費了12位,心疼啊,而且看看標志字段,占了三位,總共可以表示8個(gè)標志,確只用了5個(gè),我們能不能想辦法把這些空間利用起來(lái)呢?
我們重新仔細考慮第二個(gè)字節,也就是標志字節,把最高位的兩位利用起來(lái),稱(chēng)作寄存器標志,他的值如下表
00:操作數中沒(méi)有寄存器
01:操作數的后一個(gè)為寄存器
10:操作數的前一個(gè)為寄存器
11:兩個(gè)操作數都是寄存器
如果此位指明某操作數為寄存器,則后面的標志位直接為寄存器值,如果為00,則后面的操作數只可能為 (內存,立即數) 形式,這樣MOV ax,bx的機器碼就變成了下面的樣子
格式四:
指令碼 寄存器標志|標志1|標志2 偏移量 操作數1
操作數2
00|bbb|iii
MOV ax,bx 00000000 11|000|011 無(wú) 無(wú) 無(wú)
好了,指令系統的雛形已經(jīng)出來(lái)了,雖然和Intel的實(shí)現有很多不同,并且本身還有各種問(wèn)題,比如依然有浪費空間的情況,功能也不太健全,不過(guò)基本體現了指令格式的特點(diǎn):
分成幾個(gè)字段表示不同意義
盡量短小精干
不能浪費任何一位
下面讓我們來(lái)看看Intel公司的實(shí)現方法
讓書(shū)寫(xiě)機器碼像填表一樣簡(jiǎn)單
從上面的敘述,我們已經(jīng)大概能看出點(diǎn)門(mén)道,每條指令分為幾個(gè)部分,表示不同的含義.Intel規定,機器指令都可以被表示成六個(gè)部分,Prefix,Opcode,ModR/M,SIB,Displacement,Immediate,除了Opcode部分是必須的外,其他部分都有可能不存在
好像有點(diǎn)復雜不是?不要著(zhù)急,我們稍作解釋就可以把書(shū)寫(xiě)機器指令變得像填寫(xiě)表格一樣簡(jiǎn)單
下面我們把幾條命令按照六個(gè)部分進(jìn)行分割,填寫(xiě)到這張表里,后面會(huì )解釋六個(gè)部分的含義
Prefix
前綴
0-4個(gè)前綴,每個(gè)1字節
可選 Opcode
操作碼
1-2字節
一定存在 ModR/M
尋址與寄存器
1個(gè)字節
可選 SIB
內存尋址模式
一個(gè)字節
可選 Displayment
偏移量
1,2或4個(gè)字節
可選 Immeidate
立即數
1,2或4個(gè)字節
可選
oo|rrr|mmm cc|iii|bbb
MOV ax,1 無(wú) 1011 1000 無(wú) 無(wú) 無(wú) 0001 0000
ADD ax,1 無(wú) 0000 0101 無(wú) 無(wú) 無(wú) 0001 0000
MOV ax,[ES:0100h] 0010 0110(26h代表es的段超越前綴) 1010 0001 無(wú) 無(wú) 無(wú) 0000 0000
0001 0000
mov ax,[ebx+esi*2+1] 0110 0111
(67h,代表使用了32位 1000 1011 01 000 100 01 110 011 0000 0001 無(wú)
mov [ebx+esi*2+1],01h 67 1100 0111 01 000 100 01 110 011 0000 0001 0000 00001
只要會(huì )填這個(gè)表,我們就可以寫(xiě)出所有的機器代碼.
可以看到,Intel的格式中并沒(méi)有明確的標出兩個(gè)操作數,而是把偏移量和立即數單獨拿了出來(lái),而且同一條指令的操作碼會(huì )根據尋址方式的不同而變化,不像我們的設計,MOV就是MOV,所有的MOV指令都對應同樣的操作碼,Prefix部分也是我們的設計所沒(méi)有的
下面簡(jiǎn)單的解釋下這六個(gè)部分,每個(gè)部分的具體含義和使用,后面的例子里會(huì )逐步闡述
prefix:
指令前綴,為了一些特殊的定義或者操作而存在,只有10個(gè)可能的值,可以在下表里面查到,我們大致了解下就是了
· 鎖(Lock)和重復前綴:
鎖前綴用于多CPU環(huán)境中對共享存儲的排他訪(fǎng)問(wèn)。重復前綴用于字符串的重復操作,他可以獲得比軟件循環(huán)方法更快的速度。
— F0H—LOCK 前綴.
— F2H—REPNE/REPNZ 前綴.
— F3H—REP 前綴
— F3H—REPE/REPZ prefix (used only with string instructions).
· Segment override:
根據指令的定義和程序的上下文,一條指令所使用的段寄存器名稱(chēng)可以不出現在指令格式中,這稱(chēng)為段缺省規則。當要求一條指令不按缺省規則使用某個(gè)段寄存器時(shí),必須以段取代前綴明確指明此段寄存器。
— 2EH—CS 段前綴
— 36H—SS 段前綴.
— 3EH—DS 段前綴.
— 26H—ES 段前綴.
— 64H—FS 段前綴.
— 65H—GS 段前綴.
· 操作大小前綴 66H 和 地址長(cháng)度前綴 67H
Opcode:
操作碼,這個(gè)操作碼指定了具體的操作,他的值可以在下表查到,注意查表時(shí)候要根據操作類(lèi)型,操作數類(lèi)型和尋址方式來(lái)查詢(xún),比如Mov指令有12種操作操作碼,我們需要根據操作數的類(lèi)型,比如Mov bx,1,的兩個(gè)操作數一個(gè)是寄存器,一個(gè)是立即數,即Reg,Imm,查下表,應為1011wrrr
MemOfs,Acc 1010001w
Acc,MemOfs 1010000w
Reg,Imm 1011wrrr
Mem,Imm 1100011woo000mmm
Reg,Reg 1000101woorrrmmm
Reg,Mem 1000101woorrrmmm
Mem,Reg 1000100woorrrmmm
Reg16,Seg 10001100oosssmmm
Seg,Reg16 10001110oosssmmm
Mem16,Seg 10001100oosssmmm
Seg,Mem16 10001110oosssmmm
Reg32,CRn 000011110010000011sssrrr
CRn,Reg32 000011110010001011sssrrr
Reg32,DRn 000011110010000111sssrrr
DRn,Reg32 000011110010001111sssrrr
Reg32,TRn 000011110010010011sssrrr
TRn,Reg32 000011110010011011sssrrr
表中rrr,w,mmm,oo都可以看做幾個(gè)變量,會(huì )根據寄存器,和尋址方式的變化而變化,如果使用4位寄存器,比如al,ah,bl,bh等,則其值為0,否則為1,表
x86操作碼查詢(xún)表>可以查到,注意所查的結果中已經(jīng)包含了后面的ModR/M字節
ModR/M和SIB:
這兩個(gè)字節共同決定了尋址方式,ModR/M包含三個(gè)部分oo|rrr|mmm:這三個(gè)部分聯(lián)合表示了尋址方式,oo指示了尋址模式,rrr:指明所用寄存器,注意使用
x86操作碼查詢(xún)表>查詢(xún)得到的結果里已經(jīng)包含ModR/M字節,而SIB是輔助的尋址方式確定位,也包含三個(gè)部分
ss:放大倍數
iii:變址寄存器
bbb:基址寄存器
比如如果用到這樣的地址[ebp+5*esi],則ebp為基址寄存器,esi為變址寄存器,5為放大倍數
Displayment偏移量位:尋址方式中的偏移量,如[ebp+5]中的5
Immediate:立即數,操作數中的立即數
一起練練手:人肉翻譯匯編代碼
一) mov bx,cx
查詢(xún)其操作碼為1000 100w,由于使用16位寄存器,則w=1 得到100010001即16進(jìn)制的89H
ModR/M包含三個(gè)部分oo|rrr|mmm:這三個(gè)部分聯(lián)合表示了尋址方式,這里由于沒(méi)有內存尋址,查表得,oo=11,rrr和mmm各表示一個(gè)寄存器,那么問(wèn)題來(lái)了:哪個(gè)表示目的寄存器bx,哪個(gè)表示源寄存器cx呢?翻文檔太累了,不如用nasm匯編一下這條指令瞧瞧.得到的ModR/M字節為對應寄存器代碼可以看出來(lái),rrr表示的是源寄存器bx,則這一個(gè)字節為:11 001 011,即16進(jìn)制CBH
由于這條語(yǔ)句沒(méi)有內存尋址,SIB列為空,也沒(méi)有偏移量列Displayment,這條語(yǔ)句也沒(méi)有立即數作為操作數,所以Immediate列為空
至于Prefix列,我們稍微看下Prefix的說(shuō)明和他的值表就能知道,Prefix列只有少數的幾種情況才能出現,比如段超越啊,16位/32位切換啊,鎖定啊,像mov bx,cx這樣普通的語(yǔ)句自然也沒(méi)有Prefix列
所以我們可以得到mov bx,cx的最終代碼為
Prefix
Opcode
ModR/M
oo|rrr|mmm SIB
ss|iii|bbb Displayment
Immeidate
mov bx,cx 100010001 11 001 011
mov cx,bx
mov cl,bl
既然已經(jīng)掌握了mov bx,cx,那么mov cx,bx呢? mov cl,bl呢?大家自己想想
如果覺(jué)得上面例子還是太簡(jiǎn)單了,畢竟6列只用了2列,那么我們就來(lái)挑戰一個(gè)有點(diǎn)難度的怎么樣
二) mov [ebx+esi+1],dword 00h
word是nasm的關(guān)鍵字,表明存入內存的操作數是一個(gè)雙字,在內存中占32位,即4個(gè)字節
查詢(xún)Opcode,得1100011w,w=1,即C7
現在來(lái)看ModR/M,這里會(huì )有些變化了,我們要仔細分析我們的內存尋址方式ebx+esi+1,有一個(gè)8位的偏移量1,所以oo=01,后面的rrr和mmm該指明用于尋址的兩個(gè)寄存器,ebp和esi,查詢(xún)r(jià)rr表,應該分別是011,110,則rrr=011,mmm=110,但是我偏偏不這樣作,我設置rrr為000(EAX),mmm為100(ESP),于是代碼變?yōu)榱?1000100,44h
奇怪?明明是ebx+esi,怎么偏偏讓你給變成了eax+esp了?
其實(shí)在查詢(xún)mmm的時(shí)候,我們不應該查詢(xún)r(jià)rr表,應該查詢(xún)iii表,iii表是專(zhuān)門(mén)查詢(xún)變址寄存器號碼的,rrr表和iii表基本上完全相同,只是rrr表中100代表ESP,而iii表中呢.....no index....,這不是表示沒(méi)有變址寄存器,而是表示設置兩個(gè)寄存器的工作交給后面的SIB來(lái)做,44h可以看做是個(gè)特殊的數字,這個(gè)數字就表明尋址方式所用的寄存器會(huì )讓SIB位來(lái)完成.
上面的做法不是我別出心裁,其實(shí)如果你用nasm編譯這句話(huà),也會(huì )得到這個(gè)結果,讓SIB來(lái)設置內存尋址,我想至少有兩個(gè)好處,
一是可以更加靈活一些,畢竟人家SIB有整整一個(gè)字節專(zhuān)門(mén)來(lái)作這件事情,比如如果尋址模式位改為ebx+esi*2+1,SIB里專(zhuān)門(mén)有兩位ss,表示這個(gè)倍數,而ModR/M里呢,對不起,沒(méi)地方放了
二是可以讓匯編編譯器簡(jiǎn)單一些:統一成一種格式方便處理
ok,那么如果我們嚴格按照寄存器查表的結果(ebx=011,esi=110)能不能運行呢,大家自己去試試吧
SIB
ss:沒(méi)有倍數,ss=00
iii:剛才查過(guò)了esi=110
bbb:ebx=011
合起來(lái)是00110011即33
后面是8位的偏移量,01h,最后是立即數00h,注意這里是個(gè)雙字,所以占4個(gè)字節
填在表里
Prefix
Opcode
ModR/M
oo|rrr|mmm SIB
ss|iii|bbb Displayment
Immeidate
mov [ebx+esi+1],dword 00h 67,66 C7 44 33 01 0000
你可能用nasm匯編了一下這條語(yǔ)句,發(fā)現前面多了個(gè)67,66,恭喜你,67和66正是Prefix,由于你是在16位環(huán)境下匯編的,所以如果某條指令使用到32位的數據和地址,指令前面就會(huì )出現前綴,67表示使用了32位地址,66表示使用了32位數據.消除的方法是在文件頭上加上[BITS 32]
推薦一個(gè)好的機器碼入門(mén)
http://www.luocong.com/learningopcode.htm>,x86 OPCODE規范下載
讓人迷惑的倒置 -LittleEndian
參見(jiàn)上面的代碼,MOV到ax的操作數為16位二進(jìn)制的一,即0001h(h表示16進(jìn)制)可是從這里看上去,是0100h,這是為什么呢?
其實(shí)這是著(zhù)名的Little Endian存儲格式搗的鬼,Little Endian的意思是高位在高地址,低位在低地址,比如0100 0011 0010 0001這個(gè)二進(jìn)制數(十六進(jìn)制為4321h),在內存里類(lèi)似
位置 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
值 1 0 0 0 0 1 0 0 1 1 0 0 0 0 1 0
顯示的時(shí)候,顯示程序一般都以一個(gè)字節為整體顯示這個(gè)數,即先解析處0-7位,為數字21h,顯示在前面,然后解析8-16位,為數據43h,顯示在后面,則變?yōu)榱?1h 43h,如果顯示程序能按照字為整體解析并顯示,就能沒(méi)有這個(gè)倒裝了,但是顯示是不會(huì )知道你到底需要怎么顯示的,比如你可以定義一個(gè)32位數據,也可能定義64位數據,即使是按照16位,也仍然會(huì )有倒裝發(fā)生,所以現在一般顯示程序都簡(jiǎn)單按照字節顯示
除了LittleEndian反過(guò)來(lái)當然也有BigEndian,這種存儲格式就和咱平時(shí)的數字理解習慣沒(méi)有沖突了
LittleEndian是Intel x86(8086/8088,80286,80x86,PentiumX)系列CPU所采用的格式,而B(niǎo)igEndian是Motorola的PowerPC系列CPU所采用的標準,網(wǎng)絡(luò )傳輸也采用BigEndian,二者各有優(yōu)缺點(diǎn),有興趣的讀者可以參考1980年的著(zhù)名論文
On Holy Wars and a Plea for Peace>
別看LittleEndian這個(gè)是個(gè)細節,卻絆倒了不少初學(xué)者的腿,比如你剛打開(kāi)Windbg,想嘗試利用調試工具修改某個(gè)游戲角色的體力值,從157110修改為100000000,157110的16進(jìn)制為265B6,而你在內存里怎么都找不到02 65 B6這個(gè)序列,那就是LittleEndian搞的鬼
據Jargon File記載,endian這個(gè)詞來(lái)源于Jonathan Swift在1726年寫(xiě)的諷刺小說(shuō) 'Gulliver's Travels'(《格利佛游記》)。該小說(shuō)在描述Gulliver暢游小人國時(shí)碰到了如下的一個(gè)場(chǎng)景。在小人國里的小人因為非常?。ㄉ砀?英寸)所以總是碰到一些意想不到的問(wèn)題。有一次因為對水煮蛋該從大的一端(Big-End)剝開(kāi)還是小的一端(Little-End)剝開(kāi)的爭論而引發(fā)了一場(chǎng)戰爭,并形成了兩支截然對立的隊伍:支持從Big- End剝開(kāi)的人Swift就稱(chēng)作Big-Endians而支持從Little-End剝開(kāi)的人就稱(chēng)作Little-Endians……(后綴ian表明的就是支持某種觀(guān)點(diǎn)的人:-)。Endian這個(gè)詞由此而來(lái)。
1980年,Danny Cohen在其著(zhù)名的論文'On Holy Wars and a Plea for Peace'中為了平息一場(chǎng)關(guān)于在消息中字節該以什么樣的順序進(jìn)行傳送的爭論而引用了該詞。該文中,Cohen非常形象貼切地把支持從一個(gè)消息序列的 MSB開(kāi)始傳送的那伙人叫做Big-Endians,支持從LSB開(kāi)始傳送的相對應地叫做Little-Endians。此后Endian這個(gè)詞便隨著(zhù)這篇論文而被廣為采用。
思考:指令的起止
既然每條指令都可能不一樣常,我們的CPU怎么知道每條指令從哪里開(kāi)始,到哪里結束?
要知道變長(cháng)指令的起止,系統就必須自己知道各個(gè)指令的長(cháng)度,可以說(shuō)系統內部有個(gè)登記簿,登記了每個(gè)指令的長(cháng)度.
程序執行的時(shí)候,系統會(huì )把eip指向的指令加載到cpu,cpu會(huì )嘗試翻譯指令,這樣系統會(huì )知道這條指令的長(cháng)度,比如長(cháng)度為6,則將eip增加6,指向下一條語(yǔ)句.如何正確計算指令長(cháng)度本身是采用CISC(復雜指令集)計算機特有的問(wèn)題,因為使用RISC(精簡(jiǎn)指令集)的cpu,他的指令長(cháng)度是固定的,讓指令變長(cháng)的優(yōu)勢在于可以節省空間,也方便以后的擴展,缺點(diǎn)是cpu實(shí)現會(huì )比較復雜
輸出結果
也許你覺(jué)得雖然cpu已經(jīng)執行了我們的工作,但是由于看不到結果,不能滿(mǎn)足我們小小的虛榮心,那么下面我們就告訴系統,讓他把結果展示在屏幕上
打開(kāi)剛才建立的test.com,在剛才的程序后面附加上下面這段
04 30 88 C2 B4 02 CD 21 E9 FD FF
程序變?yōu)?
B8 01 00 05 01 00 04 30 88 C2 B4 02 CD 21 E9 FD FF
保存運行一下看看是不是輸出了結果
感覺(jué)好多了吧,至少看見(jiàn)了自己勞動(dòng)的結晶,后面附加的那段機器碼是調用了Dos的int 21中斷輸出了一個(gè)字符,我們直接給出他對應的匯編代碼
mov ax,1
add ax,1
add al,'0' ;數字到ascii的粗糙轉換
mov dl,al ;-----|
mov ah,02h;-----|--調用中斷
int 21h ;-----|
jmp $ ;保證程序不會(huì )立即退出,好讓我們看到結果
從上面的圖上我們可以清晰的看到機器碼和匯編指令的對應關(guān)系,不再贅述
add al,'0',是把結果轉化成ascii,'0'的值為30h,2+30h=32h,是'2'這個(gè)字符的ascii值,當然這是個(gè)非常粗糙的轉換,一旦數字大過(guò)9,就會(huì )輸出奇怪的結果,這樣作是為了機器碼盡量簡(jiǎn)單,方便大家輸入
通過(guò)上面的二進(jìn)制編碼與匯編代碼的對比,我們大概能知道匯編和機器指令是一一對應的,但是由于機器指令實(shí)在是太不方便人類(lèi)記憶,寫(xiě)起來(lái)也非常繁瑣,所以需要匯編語(yǔ)言,也就是說(shuō)匯編語(yǔ)言實(shí)際上是機器語(yǔ)言的助記符號
總結
我們會(huì )算1+1了