?? 匯編005.txt
字號:
| xor cx,cx |
| dec1: |
| xor edx,edx |
| div ebx |
| push dx |
| inc cx |
| or eax,eax |
| jnz dec1 |
| dec2: |
| pop ax |
| call toasc |
| call echo |
| loop dec2 |
| popad |
| ret |
|todec endp |
|-----------------------------------------------------------|
分析:將一個數用十進制數來表示,要它對它進行除以10的運算,得到商和余數。再將商除以10,如此循環直到商為0為止,在這個過程中得到的一系列的模(余數)就是十進制數系列。在主程序中,已經將f000:1234雙字單元的內容放到EAX寄存器中,由于后來要用十六進制數,二進制數顯示,所以EAX寄存器的內容不允許改變,因此在子過程的一開始,要將EAX的內容先入棧,所以子過程的一開始就用PUSHAD將8個32位通用寄存器的內容全部入棧。在標號dec1不斷地進行除以10運算,將所得到的余數全部入棧,同時用cx進行計數。在標號dec2中,逐個彈出在標號dec1中得到的余數,然后分別將它們顯示出來,這樣就可以將該存儲單元中的內容用十進數表示,下面解釋每一條指令的功能:
a1.pushad;將8個32位通用寄存器全部入棧
a2.xor cx,cx;cx清0
a3.mov ebx,10;10=>ebx
a4.xor edx,edx;edx清0
a5.div ebx;edx存放高32位,不過是0,EAX中存放低32位,即ffff:[1234]雙字的內容;除法得到的商放在EAX,余數放在EDX
a6.push dx;將edx的低16位dx入棧
a7.inc cx;cx+1=>cx
a8.or eax,eax;對eax進行或操作,主要是用來判斷eax是否為0,即判斷商是否為0,從而判斷是否應該結束標號為dec1的循環。
a9.jnz dec1
a10.pop ax;將放在堆棧中的余數逐個彈出到ax中
a11.call toasc;顯示ax的內容
a12.call echo
a13.loop dec2;將所有的余數顯示完畢
a14.popad;8個32位通用寄存器全部出棧
a15.ret
b.子過程tohex
PUSH BP
SP=>BP
SP<=SP-CNT1
|------------------------------------------------------------|
|tohex proc near |
| countb=8 |
| enter countb,0 |
| movzx ebp,bp |
| mov ecx,countb |
| mov edx,eax |
|hex1: |
| mov al,dl |
| and al,0fh |
| mov [ebp-countb+ecx-1],al |
| ror edx,4 |
| loop hex1 |
| mov cx,countb |
| xor ebx,ebx |
|hex2: |
| cmp byte ptr [ebp-countb+ebx],0 |
| jnz hex3 |
| inc ebx |
| loop hex2 |
| dec ebx |
| mov cx,1 |
|hex3: |
| mov al,[ebp-countb+ebx] |
| inc ebx |
| call toasc |
| call echo |
| loop hex3 |
| leave |
| ret |
|tohex endp |
|------------------------------------------------------------|
分析:該子過程的功能是將f000:1234雙字單元的內容以16進制數顯示出來,首先來考慮一下將一個數以16進制數表示出來的算法,事實上在匯編語言中操作數一直都是以十六進制表示的。因此,在這個子過程中不可以像上一個子過程一樣,通過不斷的除法取模得到結果。事實上,我們只需要將32位操作,以每半個字節(四位)的內容顯示出來就可以了,有了這一編程思想,就很容易看懂上面的子過程。當然你們會問,為什么要每次只顯示半個字節而不顯示一個字節呢?呵呵,十六進制的十六個數是從0000-1111,不就是半個字節了。所以要循環8次才可以顯示出32位的EAX,所以這里用ror指令來不斷循環移位,每次右移4位放到dl的低4位中。這8個半字節分別放在[ebp-1]至[ebp-8]的存儲單元中。不過,存儲的順序是由低位到高位,如果就這樣顯示結果肯定顯示反了。標號hex2,hex3的主要功能是用來判斷f000:1234雙字單元的內容是否為0,如果為0,只需要將最后結果顯示一個0即可,否則就顯示出8位內容。下面是每條指令的功能:
b1.countb=8;偽指令定義一局部變量countb,其值為8
b2.enter countb,0;建立堆棧框架指令
b3.movzx ebp,bp;對bp進行零擴展
b4.mov ecx,countb;8=>ecx
b5.mov edx,eax;將eax=>edx
b6.mov al,dl
b7.and al,0fh;取低4位
b8.mov [ebp-countb+ecx-1],al;將8個半字節的內容逐一送到[ebp-1]至[ebp-8]的內存單元中
b9.ror edx,4;對edx進行循環右移,每次移動4位
b10.loop hex1
b11.mov cx,countb
b12.xor ebx,ebx;ebx清0
b13.cmp byte ptr [ebp-countb+ebx],0;下面的語句主要用來判斷源操作數f000:1234的內容是否為0,如果是0,就在屏幕上只顯示一個0
b14.jnz hex3
b15.inc ebx
b16.loop hex2
b17.dec ebx
b18.mov cx,1
b19.mov al,[ebp-countb+ebx];逐一顯示[ebp-8]到[ebp-1]的內容。
b20.inc ebx
b21.call toasc
b22.call echo
b23.loop hex3
b24.leave;釋放堆棧框架
b25.ret
c.子過程tobin
|---------------------------------------|
|tobin proc near |
| push eax |
| push ecx |
| push edx |
| bsr edx,eax |
| jnz bin1 |
| xor dx,dx |
|bin1: |
| mov cl,31 |
| sub cl,dl |
| shl eax,cl |
| mov cx,dx |
| inc cx
| mov edx,eax |
|bin2:
| rol edx,1
| mov al,'0'
| adc al,0
| call echo
| loop bin2
| pop edx |
| pop ecx
| pop eax
| ret
|tobin endp
|---------------------------------------|
分析:將一個數用二進制數顯示出來,只需要用ROL指令就可以了。這里作者寫的程序就是這個思路,在標號bin1中主要判斷f000:1234單元的內容是否為0,如果為0,那么只需要在屏幕上顯示一個0就可以了。否則的話,就用ROL指令對源操作數移位32位,從最高位31位到最低位逐一顯示出來,程序設計思路很簡單,沒有什么復雜的算法,下面看每一條指令的含義:
c1.push eax;eax入棧
c2.push ecx;ecx入棧
c3.push edx;edx入棧
c4.bsr edx,eax;對eax進行掃描,并把第一個為1的位號送給edx
c5.jnz bin1;如果eax不為0,就跳到c7去執行
c6.xor dx,dx;如果eax為0,就將dx清0
c7.mov cl,31;從c7到c12主要用來設置計數器cx,如果eax=0,那么就設置cx=1,如果eax不等于0,那么就設置ecx=32
c8.sub cl,dl
c9.shl eax,cl
c10.mov cx,dx
c11.inc cx
c12.mov edx,eax
c13.rol edx,1;從c13到c15主要用來顯示二進制數據,順序是從最高位31位到最低位0位
c14.mov al,'0'
c15.adc al,0
c16.call echo
c17.loop bin2
c18.pop edx;edx出棧
c19.pop ecx;ecx出棧
c20.pop eax;eax出棧
c21.ret
在后續的篇幅里將主要介紹保護式下的段頁管理機制及及如何在保護模下編程。
雖然80386處理器要較以前的處理器的功能大大增強,但這些功能只能在保護模式下才能全部得到發揮。在實模式下最大尋址空間只有1M,但在保護模式最大尋址空間可達4G,可以訪問到所有的物理內存。同時由于引入虛擬內存的概念,在程序設計中可使用的地址空間為64TB。80386處理器采用了可擴充的分段管理和可選的分頁管理機制,這兩個存儲管理機制由MMU(Memory Management Unit)部件來實現。因此,如果在80386下進行實模式編程,這時的80386處理器相當于一功能更強大,運行速度更快的8086處理器。80386提供對虛擬存儲器的支持,虛擬存儲器的理論基礎就是:速度非常快的內存儲器和海量的外存儲器,所以它是一種軟硬件結合的技術,它能夠提供比物理內存大得多的存儲空間。
80386下的段具有三個屬性:段基址,段界限,段屬性,通常描述段的稱作段描述符(Segment Descriptor),而描述符通常放在一個線性表中,這種線性表又分為:GDT(Global Descriptor Table),LDT(Local Descriptor Table),IDT(Interrupt Descriptor Table),通常用一個叫做選擇子的東西去確定使用上述三個線性表中哪一個描述符。程序中使用的地址空間就是虛擬地址空間,上面已經說過80386下虛擬地址空間可達到64TB(后面將解釋為什么可以達到64TB),虛擬地址空間由一個選擇子和段內偏移組成,這是因為通過段的選擇子我們可以得到該段的描述符,而在描述符中又說明了段的基址,段的界限及段的屬性,再加上段的偏移就可以得到虛擬地址空間。不過請注意,這里并沒有將段基址乘以16再加上偏移地址,這是保護模式與實式模式的區別之一。很明顯,任何數據都必須裝入到物理內存才能夠被存儲器處理,所以二維的虛擬地址空間必須轉換成一維的物理地址。同時,由于每個任務都有自已的虛擬地址空間,為了防止多個并行任務將虛擬地址空間映射同一物理地址空間采用線性地址空間隔離虛擬地址和物理地址,線性地址空間由一維的線性地址構成,線性地址空間與物理地址空間對等,線性地址為32位,可尋址空間為4GB(物理地址空間最大也可以達到4GB,址址為32位,所以說線性地址空間與物理地址空間對等)。下面是80386虛擬地址空間與物理址空間的轉換示意圖:
|----------| |------------| |--------| |------------------| |--------|
| 虛擬地址 |------>|分段管理部件|------>|線性地址|---|--->|可選的分頁管理部件|---|-->|物理地址|
|----|-----| |------------| |--------| | |------------------| | |--------|
|------|-------| | |
| | |---------------------------|
|----------| |---------|
| 選擇子 | | 段內偏移|
|----------| |---------|
地址映射過程中,通過分段管理部件將虛擬地址空間轉換成線性地址,這一步是必然存在的。如果在程序中啟用了分頁管理機制,那么線性地址還要經過分頁管理部件的處理才得到最后的物理地址。如果沒有采用分頁管理機制,那么得到的線性地址就是物理地址。分頁管理部件的主要的工作機制在于將線性地址和物理地址劃分成大小相同的塊,通過在建立兩者之間的頁表來建立對應關系。分段管理機制使用大小可變的存儲塊,使用分段管理機制適合處理復雜系統的邏輯分段。分頁管理機制使用固定大小的塊,所以它適合管理物理存儲器,分頁管理機制能夠更有效地使用虛擬地址空間。
80386支持多任務,因此對各個任務進行保護是非常必要的,對任務的保護可分為:同一任務內的保護,不同任務之間的保護。
a.同一任務內的保護,在同一任務內定義有四種特權級別(Previlege Level),將這些特權級別分配給段中的代碼和數據,把最高的特權級別分配給最重要的數據和最可信任的代碼,將較低級別的特權分給一般的代碼和不重要的數據。特權級別用0~3來表示,用數字0表示最高特權級別,用數字3表示最低特權級別,在比較特權級別時不使用大于或小于,而是使用外層或里層來比較,很明顯特權級別為0表示最里層,特別級別為3表示最外層。任何一個存儲段(程序直接進行訪問的代碼段和數據段)都有一個特權級別,在一個程序試圖訪問這個存儲時,就會進行特權級別的比較,如果小于或等于(如果等于表明同級,小于則表明是內層)處該存儲段的特權級別就可以對該存儲段進行訪問。任務在特定時刻下的特權級別稱為CPL(Current Previlege Level),看一簡單的結構示意圖:
|---------|-------|
| CodeA | DataA | 特權級別為0
|---------|-------|
|---------|-------|
| CodeB | DataB | 特權級別為1
|---------|-------|
|---------|-------|
| CodeC | DataC | 特權級別為2
|---------|-------|
|---------|-------|
| CodeD | DataD | 特權級別為3
|---------|-------|
CodeA可以訪問DataA,CodeB,DataB,CodeC,DataC,CodeD,DataD
CodeB可以訪問Datab,CodeC,DataC,CodeD,DataD,但不可以訪問CodeA,DataA
CodeC可以訪問DataC,CodeD,DataD,但不可以訪問CodeA,DataA,CodeB,DataB
CodeD處在最外層,只能訪問同級的DataD,不可以訪問CodeA,DataA,CodeB,DataB,CodeC,DataC
通常應用程序放在最外層,但由于每個應用程序的虛擬地址空間不同,因此它們被隔離保護。這種特權級別的典型用法就是:將操作系統的核心放在0層,操作系統的其余部分放在1級,2級留給中間軟件使用,3級放應用程序,這樣的安排的好處在于:操作系統的核心因為放在0層,因此它可以訪問任務中所有的存儲段,而1級的部分操作系統可以訪問除0級以外的所有存儲段,應用程序只能訪問自身的存儲段。
b.不同任務間的保護,通過把每個任務放在不同的虛擬地址空間來實現隔離保護,虛擬地址到物理地址之間的映射由每個任務中的映射函數來決定,隨著任務切換,映射函數也跟著切換,這樣可以保證任務A映射到物理內存中的區域與任務B映射到內存中的區域是不同的,盡管有可能它們的虛擬地址空間相同,但它們最終在物理內存中的位置是不同的,從而起到了保護作用。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -