存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20
存儲器的保護(三)
修改本章代碼清單,使之可以檢測1MB以上的內存空間(從地址0x0010_0000開始,不考慮高速緩存的影響)。要求:對內存的讀寫按雙字的長度進行,并在檢測的同時顯示已檢測的內存數量。建議對每個雙字單元用兩個花碼0x55AA55AA和0xAA55AA55進行檢測。
上面的文字選自原書第12章的習題1.
這篇博文就討論一下這道題。由于是初學,我不對自己做太高的要求,只要實現功能即可。
代碼清單
;文件說明:第12章習題-1;創建日期:2016-3-7;--------- equ some colorsGREEN equ 0x02RED equ 0x04BLUE_LIGHT equ 0x09YELLOW equ 0x0eMEMORY_START equ 0x100000MEMORY_END equ 0x800000MEMORY_SIZE equ (MEMORY_END-MEMORY_START)/4 ;以雙字為單位LENGTH_OF_BAR equ 6 ; 表示2的6次方BAR_POSITION equ 10*80+4 ;進度條的位置;設置堆棧段和棧指針 mov eax,cs mov ss,eaxmov sp,0x7c00mov ah,0x00; 清屏mov al,0x03int 0x10;計算GDT所在的邏輯段地址mov eax,[cs:pgdt+0x7c00+0x02] ;GDT的32位線性基地址 xor edx,edxmov ebx,16div ebx ;分解成16位邏輯地址 mov ds,eax ;令DS指向該段以進行操作mov ebx,edx ;段內起始偏移地址 ;跳過0#描述符;創建1#描述符,這是一個數據段,對應0~4GB的線性地址空間mov dword [ebx+0x08],0x0000ffff ;基地址為0,段界限為0xfffffmov dword [ebx+0x0c],0x00cf9200 ;粒度為4KB,存儲器段描述符 ;創建保護模式下初始代碼段描述符,代碼段可讀mov dword [ebx+0x10],0x7c0001ff ;基地址為0x00007c00,512字節 mov dword [ebx+0x14],0x00409a00 ;粒度為1個字節,代碼段描述符 ;創建棧段描述符mov dword [ebx+0x18],0x7c00fffemov dword [ebx+0x1c],0x00cf9600;初始化描述符表寄存器GDTRmov word [cs: pgdt+0x7c00],31 ;描述符表的界限 lgdt [cs: pgdt+0x7c00]in al,0x92 ;南橋芯片內的端口 or al,0000_0010Bout 0x92,al ;打開A20cli ;中斷機制尚未工作mov eax,cr0or eax,1mov cr0,eax ;設置PE位;以下進入保護模式... ...jmp dword 0x0010:flush ;16位的描述符選擇子:32位偏移[bits 32] flush: mov eax,0x0008 ;加載數據段(0..4GB)選擇子; ds,es,fs,gs指向了(0..4G)mov ds,eax mov es,eaxmov fs,eaxmov gs,eaxmov eax,0x0018 ;加載棧段選擇子mov ss,eaxxor esp,esp ;ESP <- 0 ; 繪制白色條push (1<<LENGTH_OF_BAR) ;number of blockspush BAR_POSITIONpush 0x7720 ; white blockcall put_charpush 21*80+25push BLUE_LIGHTpush MEMORY_SIZEcall show_hex_dword ;顯示總共要檢測的數量(以雙字為單位); 顯示 '/'push 1push 21*80+23push 0x092f ; 藍色的'/'call put_charxor ecx,ecx ;計數器清零,記錄檢測了多少個雙字mov ebx,MEMORY_START ;檢測的起始地址 ;----------------------------------------------------- exam: ;顯示正在檢測的地址push 21*80+6push YELLOWpush ebxcall show_hex_dwordmov dword [es:ebx],0x55aa55aacmp dword [es:ebx],0x55aa55aajnz errmov dword [es:ebx],0xaa55aa55cmp dword [es:ebx],0xaa55aa55jnz erradd ebx,4 ;地址增加4個字節inc ecxpush 21*80+15push BLUE_LIGHTpush ecxcall show_hex_dword ;顯示已經檢測的數量(以雙字為單位)push BAR_POSITION ;繪制進度條push ecxpush MEMORY_SIZEcall draw_progress_barcmp ebx,MEMORY_END jnz examerr: hlt ;-------------------------------------- ;功能:在指定位置顯示N個字符 ;輸入: push 顯示的個數 ; push (x*80+y), 表示x行y列 ; push 屬性和字符 ;返回:無put_char:pushadmov ebp,espmov ecx,[ebp+11*4] ; 取得個數mov ebx,[ebp+10*4] ; 取得位置mov ax,[ebp+9*4] ;取得屬性和字put:mov [es:0xb8000+ebx*2],axinc ebxloop putpopadret 3*4;----------------------------------------- ;功能:根據比例在指定位置繪制進度條 ;輸入: ; push (x*80+y), 表示x行y列 ; push 分子 ; push 分母 ;返回:無 draw_progress_bar:pushadmov ebp,espmov esi,[ebp+11*4] ; 取得位置mov eax,[ebp+10*4] ; 取得分子mov ebx,[ebp+9*4] ;取得分母shr ebx,LENGTH_OF_BARxor edx,edxdiv ebxcmp eax,1jb outpush eaxpush esipush 0x2020; 綠色背景,空格call put_charout:popadret 3*4 ;----------------------------------- ;功能:在指定位置顯示16進制的數字 ;輸入: ; push (x*80+y), 表示x行y列 ; push 屬性 ; push 要顯示的值 ;返回:無 show_hex_dword:pushadmov ebp,espmov esi,[ebp+11*4] ;取得@1:(x,y) mov eax,[ebp+9*4] ;取得@3:valuemov ebx,16xor ecx,ecxremainder: xor edx,edxdiv ebxinc ecxpush edxcmp eax,0jnz remaindermov ah,[ebp+10*4] ;取得屬性 print: pop ebxmov al,[cs:string_hex+ebx] mov [es:0xb8000+esi*2],axinc esiloop printpopadret 3*4 ;-------------------------------------------------------------------------------pgdt dw 0dd 0x00007e00 ;GDT的物理地址string_hex: db'0123456789ABCDEF' ;------------------------------------------------------------------------------- times 510-($-$$) db 0db 0x55,0xaa代碼分析
設計思路
下面我們分析具體的實現。不打算逐行講述所有代碼,僅選擇重點部分講解。
定義一些常量
GREEN equ 0x02 ; 黑底綠字RED equ 0x04 ; 黑底紅字BLUE_LIGHT equ 0x09 ; 黑底藍色字YELLOW equ 0x0e ; 黑底黃字MEMORY_START equ 0x100000MEMORY_END equ 0x800000MEMORY_SIZE equ (MEMORY_END-MEMORY_START)/4 ;以雙字為單位LENGTH_OF_BAR equ 6 ; 表示2的6次方BAR_POSITION equ 10*80+4 ;進度條的位置前四行定義了字符屬性;
中間三行定義了要檢測的內存起始地址,結束地址(檢測不包含結束地址),還有檢測的內存大小(以雙字為單位)。之所以用equ定義是因為修改起來方便。
LENGTH_OF_BAR equ 6 ; 表示2的6次方
這句話表示進度條的總長度占64(2^6=64)個字符,當然可以根據需要修改。但應該是2的N次方(具體原因下文會說明)。
BAR_POSITION equ 10*80+4 ;進度條的位置
這行定義了進度條的位置,如果是x行y列,對應的表示就是(x*80+y);因為一行有80個字符。
清屏
mov ah,0x00; 清屏mov al,0x03int 0x10這三行代碼是為了清屏。具體原理可以參見我的博文《BIOS功能調用之滾屏與清屏》
http://blog.csdn.net/longintchar/article/details/50806752
創建GDT
;跳過0#描述符;創建1#描述符,這是一個數據段,對應0~4GB的線性地址空間mov dword [ebx+0x08],0x0000ffff ;基地址為0,段界限為0xfffffmov dword [ebx+0x0c],0x00cf9200 ;粒度為4KB,存儲器段描述符 ;創建保護模式下初始代碼段描述符,代碼段可讀mov dword [ebx+0x10],0x7c0001ff ;基地址為0x00007c00,512字節 mov dword [ebx+0x14],0x00409a00 ;粒度為1個字節,代碼段描述符 ;創建棧段描述符mov dword [ebx+0x18],0x7c00fffemov dword [ebx+0x1c],0x00cf9600以上代碼用于創建GDT。由于想在引導程序中實現全部功能,所以編譯后的文件不能超過512字節。為了節省筆墨,我跳過了0#描述符。
關于代碼段,必須是可讀的,因為過程“show_hex_dword”需要訪問代碼段中的一個表格:
string_hex: db'0123456789ABCDEF'
關于棧段描述符的定義,具體講解參見 存儲器的保護(一)——《x86匯編語言:從實模式到保護模式》讀書筆記18 http://blog.csdn.net/longintchar/article/details/50759826
繪制白色條
; 繪制白色條push (1<<LENGTH_OF_BAR) ;number of blockspush BAR_POSITIONpush 0x7720 ; white blockcall put_char這里調用了過程 put_char
;-------------------------------------- ;功能:在指定位置顯示N個字符 ;輸入: push 顯示的個數 ; push (x*80+y), 表示x行y列 ; push 屬性和字符 ;返回:無put_char:pushadmov ebp,espmov ecx,[ebp+11*4] ; 取得個數mov ebx,[ebp+10*4] ; 取得位置mov ax,[ebp+9*4] ;取得屬性和字符put:mov [es:0xb8000+ebx*2],axinc ebxloop putpopadret 3*4以前我們都是用寄存器傳遞參數,這次我們用棧傳遞參數。在調用過程之前,先按照要求把參數壓入棧中。當進入過程,執行完pushad這條指令后,棧的情況如下圖:
這里用到了pushad和popad指令,如果你不懂的話,可以參考我的另一篇博文:
《PUSHA/PUSHAD POPA/POPAD 指令詳解》
http://blog.csdn.net/longintchar/article/details/50866801
所以以下四行就可以取得棧中的參數。
mov ebp,espmov ecx,[ebp+11*4] ; 取得個數mov ebx,[ebp+10*4] ; 取得位置mov ax,[ebp+9*4] ;取得屬性和字符還有一點需要說明,
ret 3*4 這句話使用了帶操作數的過程返回指令。這種用法在原書P278頁講解了。
如果希望在過程返回的同時,順便彈出調用者壓入的參數(使棧平衡),那么可以用帶操作數的過程返回指令。指令格式是:
這兩條指令都允許用16位的立即數作為參數,不同之處僅在于前者是近返回,后者是遠返回。立即數一般總是偶數,原因是棧操作總是以字或者雙字進行。立即數的值表示在過程返回時應當從棧中彈出多少字節的數據。
對于我們的put_char過程,因為調用的時候壓入了3個參數(3*4=12字節),所以ret后面的參數是12.
push 0x7720 這句表示壓入白底的空格符,顯示出來就是白色的小方塊了。
顯示總共要檢測的內存數量(以雙字為單位)
push 21*80+25push BLUE_LIGHTpush MEMORY_SIZEcall show_hex_dword ;顯示總共要檢測的數量(以雙字為單位)依然用棧來傳遞參數,調用了過程show_hex_dword;
;----------------------------------- ;功能:在指定位置顯示16進制的數字 ;輸入: ; push (x*80+y), 表示x行y列 ; push 屬性 ; push 要顯示的值 ;返回:無 show_hex_dword:pushadmov ebp,espmov esi,[ebp+11*4] ;取得@1:(x,y) mov eax,[ebp+9*4] ;取得@3:valuemov ebx,16xor ecx,ecxremainder: xor edx,edxdiv ebxinc ecxpush edxcmp eax,0jnz remaindermov ah,[ebp+10*4] ;取得屬性 print: pop ebxmov al,[cs:string_hex+ebx] mov [es:0xb8000+esi*2],axinc esiloop printpopadret 3*4這段代碼的功能就是在指定的位置(壓入第一個參數,比如3行4列就寫 push 3*80+4),顯示指定屬性(壓入第二個參數,僅低字節有效,比如綠色0x02)的16進制數字(壓入第三個參數,比如想在屏幕上顯示16進制的8b9c,那么就push 0x8b9c).
這段代碼的設計思路就是把要顯示的數不斷除以16(因為是以16進制顯示),并且把余數壓棧,直到商等于0.之后再從棧依次彈出余數,把余數作為索引值查表,將對應的字符寫到屏幕上。查表的關鍵語句是:
表格定義在源文件的倒數第三行
string_hex: db'0123456789ABCDEF'因為查表需要對代碼段進行訪問,所以在創建代碼段描述符的時候,一定要讓代碼段可讀。
開始內存檢測
xor ecx,ecx ;計數器清零,記錄檢測了多少個雙字mov ebx,MEMORY_START ;檢測的起始地址在檢測之前,計數器清零,檢測的起始地址傳送到EBX寄存器。
exam: ;顯示正在檢測的地址push 21*80+6push YELLOWpush ebxcall show_hex_dwordmov dword [es:ebx],0x55aa55aacmp dword [es:ebx],0x55aa55aajnz errmov dword [es:ebx],0xaa55aa55cmp dword [es:ebx],0xaa55aa55jnz erradd ebx,4 ;地址增加4個字節inc ecxpush 21*80+15push BLUE_LIGHTpush ecxcall show_hex_dword ;顯示已經檢測的數量(以雙字為單位)push BAR_POSITION ;繪制進度條push ecxpush MEMORY_SIZEcall draw_progress_barcmp ebx,MEMORY_END jnz examerr: hlt上面的代碼就是內存檢測的主體部分了。
首先顯示正在檢測的地址(要檢測的地址在ebx中)。然后向這個地址寫入花碼,并讀出比較,如果不相等,就跳轉到
如果相等,則ebx加上4,ecx加上1,并且顯示ecx的值,繪制進度條,然后繼續檢測。
繪制進度條
;----------------------------------------- ;功能:根據比例在指定位置繪制進度條 ;輸入: ; push (x*80+y), 表示x行y列 ; push 分子 ; push 分母 ;返回:無 draw_progress_bar:pushadmov ebp,espmov esi,[ebp+11*4] ; 取得位置mov eax,[ebp+10*4] ; 取得分子mov ebx,[ebp+9*4] ;取得分母shr ebx,LENGTH_OF_BARxor edx,edxdiv ebxcmp eax,1jb outpush eaxpush esipush 0x2020; 綠色背景,空格call put_charout:popadret 3*4上面的這個過程是在指定的位置繪制進綠色的進度條,要求壓入三個參數。第一個是位置,第二個是分子,第三個是分母。
比如說要檢測160個雙字,當前已經檢測了10個了,那么第二個參數就是10,第三個參數就是160。如果之前的白色條的長度是64,那么就繪制64*(10/160)=4個綠色方塊。看上去的效果就是綠色條的長度是總長度的十六分之一。
在每次檢測4個字節后,我們就調用這個過程,這樣程序運行后就有一個動畫效果了。
這個過程實現的關鍵是計算出要繪制多少個綠色空格。
假設白色空格數可以表示成2的m次方。
計算公式推導如下圖:
根據公式,我們把ebx右移LENGTH_OF_BAR(=6)位,作為除數,被除數就在eax中,然后edx清零,再然后
edx:eax / ebx(移位運算后的值) = eax …edx
余數舍去,假如計算出來畫1.5個方塊,那么就繪制1個。
需要注意的是,計算后eax的值可能為0,如果為0就一定要跳出,一個綠色方塊也不繪制。
如果eax大于等于1,那么調用過程put_char繪制綠色方塊。
好了,整個代碼的分析就到這里了,我們趕緊看看結果吧。
檢測結束后:
【end】
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php 并发 100 压测,简单PHP把
- 下一篇: Arrays类中的binarysearc