zoukankan      html  css  js  c++  java
  • 存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20

    存储器的保护(三)

    改动本章代码清单,使之能够检測1MB以上的内存空间(从地址0x0010_0000開始,不考虑快速缓存的影响)。要求:对内存的读写按双字的长度进行。并在检測的同一时候显示已检測的内存数量。建议对每一个双字单元用两个花码0x55AA55AA和0xAA55AA55进行检測。

    上面的文字选自原书第12章的习题1.
    这篇博文就讨论一下这道题。由于是初学,我不正确自己做太高的要求。仅仅要实现功能就可以。

    代码清单

            ;文件说明:第12章习题-1
            ;创建日期:2016-3-7
    
    
            ;--------- equ some colors
    
            GREEN         equ 0x02
            RED           equ 0x04
            BLUE_LIGHT    equ 0x09
            YELLOW        equ 0x0e
    
            MEMORY_START  equ 0x100000
            MEMORY_END    equ 0x800000
            MEMORY_SIZE   equ (MEMORY_END-MEMORY_START)/4  ;以双字为单位
    
            LENGTH_OF_BAR equ 6        ; 表示26次方
            BAR_POSITION  equ 10*80+4  ;进度条的位置
    
            ;设置堆栈段和栈指针 
            mov eax,cs      
            mov ss,eax
            mov sp,0x7c00
    
            mov ah,0x00; 清屏
            mov al,0x03
            int 0x10
    
            ;计算GDT所在的逻辑段地址
            mov eax,[cs:pgdt+0x7c00+0x02]      ;GDT的32位线性基地址 
            xor edx,edx
            mov ebx,16
            div ebx                            ;分解成16位逻辑地址 
    
            mov ds,eax                         ;令DS指向该段以进行操作
            mov ebx,edx                        ;段内起始偏移地址 
    
            ;跳过0#描写叙述符
    
    
            ;创建1#描写叙述符,这是一个数据段,相应0~4GB的线性地址空间
            mov dword [ebx+0x08],0x0000ffff    ;基地址为0,段界限为0xfffff
            mov dword [ebx+0x0c],0x00cf9200    ;粒度为4KB,存储器段描写叙述符 
    
            ;创建保护模式下初始代码段描写叙述符,代码段可读
            mov dword [ebx+0x10],0x7c0001ff    ;基地址为0x00007c00512字节 
            mov dword [ebx+0x14],0x00409a00    ;粒度为1个字节。代码段描写叙述符 
    
            ;创建栈段描写叙述符
            mov dword [ebx+0x18],0x7c00fffe
            mov dword [ebx+0x1c],0x00cf9600
    
            ;初始化描写叙述符表寄存器GDTR
            mov word [cs: pgdt+0x7c00],31      ;描写叙述符表的界限   
    
            lgdt [cs: pgdt+0x7c00]
    
            in al,0x92                         ;南桥芯片内的port 
            or al,0000_0010B
            out 0x92,al                        ;打开A20
    
            cli                                ;中断机制尚未工作
    
            mov eax,cr0
            or eax,1
            mov 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,eax
            mov fs,eax
            mov gs,eax
    
            mov eax,0x0018                   ;载入栈段选择子
            mov ss,eax
            xor esp,esp                        ;ESP <- 0    
    
            ; 绘制白色条
            push (1<<LENGTH_OF_BAR) ;number of blocks
            push BAR_POSITION
            push 0x7720 ; white block
            call put_char
    
            push 21*80+25
            push BLUE_LIGHT
            push MEMORY_SIZE
            call show_hex_dword ;显示总共要检測的数量(以双字为单位)
    
            ; 显示 '/'
            push 1
            push 21*80+23
            push 0x092f ; 蓝色的'/'
            call put_char
    
            xor ecx,ecx          ;计数器清零,记录检測了多少个双字
            mov ebx,MEMORY_START ;检測的起始地址 
    
    ;-----------------------------------------------------  
    exam:   ;显示正在检測的地址
            push 21*80+6
            push YELLOW
            push ebx
            call show_hex_dword
    
            mov dword [es:ebx],0x55aa55aa
            cmp dword [es:ebx],0x55aa55aa
            jnz err
    
            mov dword [es:ebx],0xaa55aa55
            cmp dword [es:ebx],0xaa55aa55
            jnz err
    
            add ebx,4    ;地址添加4个字节
            inc ecx
    
            push 21*80+15
            push BLUE_LIGHT
            push ecx
            call show_hex_dword ;显示已经检測的数量(以双字为单位)
    
            push BAR_POSITION  ;绘制进度条
            push ecx
            push MEMORY_SIZE
            call draw_progress_bar
    
            cmp ebx,MEMORY_END  
            jnz exam
    
    err:          
            hlt 
    
    ;--------------------------------------     
    ;功能:在指定位置显示N个字符
    ;输入: push 显示的个数
    ;      push (x*80+y),  表示xy列
    ;      push 属性和字符  
    ;返回:无
    
    put_char:
            pushad
            mov ebp,esp
            mov ecx,[ebp+11*4]  ; 取得个数
            mov ebx,[ebp+10*4]  ; 取得位置
            mov ax,[ebp+9*4]    ;取得属性和字
    
    put:
            mov [es:0xb8000+ebx*2],ax
            inc ebx
            loop put
    
            popad
            ret 3*4
    
    ;-----------------------------------------      
    ;功能:依据比例在指定位置绘制进度条
    ;输入: 
    ;      push (x*80+y),  表示xy列
    ;      push 分子
    ;      push 分母       
    ;返回:无   
    
    draw_progress_bar:
            pushad
            mov ebp,esp
            mov esi,[ebp+11*4]  ; 取得位置
            mov eax,[ebp+10*4]  ; 取得分子
            mov ebx,[ebp+9*4]    ;取得分母
    
            shr ebx,LENGTH_OF_BAR
            xor edx,edx
            div ebx
            cmp eax,1
            jb out
    
            push eax
            push esi
            push 0x2020; 绿色背景。空格
            call put_char
    
    out:
            popad
            ret 3*4         
    
    ;-----------------------------------
    ;功能:在指定位置显示16进制的数字
    ;输入: 
    ;      push (x*80+y),  表示xy列
    ;      push 属性
    ;      push 要显示的值       
    ;返回:无   
    
    show_hex_dword:
            pushad
    
            mov ebp,esp
            mov esi,[ebp+11*4]  ;取得@1:(x,y) 
            mov eax,[ebp+9*4]   ;取得@3:value
    
            mov ebx,16
            xor ecx,ecx
    
    remainder:  
            xor edx,edx
            div ebx
    
            inc ecx
            push edx
            cmp eax,0
            jnz remainder
    
            mov ah,[ebp+10*4]   ;取得属性
    print:  
            pop ebx
            mov al,[cs:string_hex+ebx]      
            mov [es:0xb8000+esi*2],ax
            inc esi
            loop print
    
            popad
            ret 3*4         
    ;-------------------------------------------------------------------------------
            pgdt     dw 0
                     dd 0x00007e00      ;GDT的物理地址
    
            string_hex: db'0123456789ABCDEF'
    ;-------------------------------------------------------------------------------                             
            times 510-($-$$) db 0
            db 0x55,0xaa
    

    代码分析

    设计思路

    1. 这个程序实现的主要功能是:检測1MB以上的内存空间。比方检測物理地址为1M~8M的单元。
    2. 检測方法是向每一个双字单元写入0x55aa55aa。并读出来和0x55aa55aa做比較,假设相等。则再写入0xaa55aa55,并读出来和0xaa55aa55作比較,假设相等。那么这个双字单元是OK的。把物理地址加上4,继续检測。假设读出的和写入的不相等,那么检測出错,程序停止。

    3. 检測的时候,显示正在检測的内存地址
    4. 显示一个进度条
    5. 显示“已经检測的内存数 / 总共须要检測的内存数”

    下面我们分析详细的实现。不打算逐行讲述所有代码,仅选择重点部分解说。

    定义一些常量

            GREEN         equ 0x02 ; 黑底绿字
            RED           equ 0x04 ; 黑底红字
            BLUE_LIGHT    equ 0x09 ; 黑底蓝色字
            YELLOW        equ 0x0e ; 黑底黄字
    
            MEMORY_START  equ 0x100000
            MEMORY_END    equ 0x800000
            MEMORY_SIZE   equ (MEMORY_END-MEMORY_START)/4  ;以双字为单位
    
            LENGTH_OF_BAR equ 6        ; 表示26次方
            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,0x03
            int 0x10

    这三行代码是为了清屏。

    详细原理能够參见我的博文《BIOS功能调用之滚屏与清屏》

    http://blog.csdn.net/longintchar/article/details/50806752

    创建GDT

            ;跳过0#描写叙述符
    
    
            ;创建1#描写叙述符。这是一个数据段,相应0~4GB的线性地址空间
            mov dword [ebx+0x08],0x0000ffff    ;基地址为0,段界限为0xfffff
            mov dword [ebx+0x0c],0x00cf9200    ;粒度为4KB。存储器段描写叙述符 
    
            ;创建保护模式下初始代码段描写叙述符,代码段可读
            mov dword [ebx+0x10],0x7c0001ff    ;基地址为0x00007c00,512字节 
            mov dword [ebx+0x14],0x00409a00    ;粒度为1个字节,代码段描写叙述符 
    
            ;创建栈段描写叙述符
            mov dword [ebx+0x18],0x7c00fffe
            mov 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 blocks
            push BAR_POSITION
            push 0x7720 ; white block
            call put_char

    这里调用了过程 put_char

    ;--------------------------------------     
    ;功能:在指定位置显示N个字符
    ;输入: push 显示的个数
    ;      push (x*80+y),  表示xy列
    ;      push 属性和字符   
    ;返回:无
    
    put_char:
            pushad
            mov ebp,esp
            mov ecx,[ebp+11*4]  ; 取得个数
            mov ebx,[ebp+10*4]  ; 取得位置
            mov ax,[ebp+9*4]    ;取得属性和字符
    
    put:
            mov [es:0xb8000+ebx*2],ax
            inc ebx
            loop put
    
            popad
            ret 3*4

    曾经我们都是用寄存器传递參数,这次我们用栈传递參数。在调用过程之前。先依照要求把參数压入栈中。当进入过程,执行完pushad这条指令后,栈的情况例如以下图:


    这里用到了pushad和popad指令。假设你不懂的话,能够參考我的另一篇博文:

    《PUSHA/PUSHAD POPA/POPAD 指令详细解释》

    http://blog.csdn.net/longintchar/article/details/50866801

    所以下面四行就能够取得栈中的參数。

            mov ebp,esp
            mov ecx,[ebp+11*4]  ; 取得个数
            mov ebx,[ebp+10*4]  ; 取得位置
            mov ax,[ebp+9*4]    ;取得属性和字符

    另一点须要说明,
    ret 3*4 这句话使用了带操作数的过程返回指令。这样的使用方法在原书P278页解说了。
    假设希望在过程返回的同一时候,顺便弹出调用者压入的參数(使栈平衡)。那么能够用带操作数的过程返回指令。指令格式是:

        ret imm16
        retf imm16

    这两条指令都同意用16位的马上数作为參数,不同之处仅在于前者是近返回,后者是远返回。马上数一般总是偶数,原因是栈操作总是以字或者双字进行。马上数的值表示在过程返回时应当从栈中弹出多少字节的数据。
    对于我们的put_char过程,由于调用的时候压入了3个參数(3*4=12字节),所以ret后面的參数是12.

    push 0x7720 这句表示压入白底的空格符,显示出来就是白色的小方块了。

    显示总共要检測的内存数量(以双字为单位)

            push 21*80+25
            push BLUE_LIGHT
            push MEMORY_SIZE
            call show_hex_dword ;显示总共要检測的数量(以双字为单位)

    依旧用栈来传递參数,调用了过程show_hex_dword

    ;-----------------------------------
    ;功能:在指定位置显示16进制的数字
    ;输入: 
    ;      push (x*80+y),  表示xy列
    ;      push 属性
    ;      push 要显示的值       
    ;返回:无   
    
    show_hex_dword:
            pushad
    
            mov ebp,esp
            mov esi,[ebp+11*4]  ;取得@1:(x,y) 
            mov eax,[ebp+9*4]   ;取得@3:value
    
            mov ebx,16
            xor ecx,ecx
    
    remainder:  
            xor edx,edx
            div ebx
    
            inc ecx
            push edx
            cmp eax,0
            jnz remainder
    
            mov ah,[ebp+10*4]   ;取得属性
    print:  
            pop ebx
            mov al,[cs:string_hex+ebx]      
            mov [es:0xb8000+esi*2],ax
            inc esi
            loop print
    
            popad
            ret 3*4         

    这段代码的功能就是在指定的位置(压入第一个參数,比方3行4列就写 push 3*80+4),显示指定属性(压入第二个參数,仅低字节有效,比方绿色0x02)的16进制数字(压入第三个參数,比方想在屏幕上显示16进制的8b9c,那么就push 0x8b9c).
    这段代码的设计思路就是把要显示的数不断除以16(由于是以16进制显示),而且把余数压栈。直到商等于0.之后再从栈依次弹出余数,把余数作为索引值查表。将相应的字符写到屏幕上。

    查表的关键语句是:

    mov al,[cs:string_hex+ebx]      

    表格定义在源文件的倒数第三行

            string_hex: db'0123456789ABCDEF'

    由于查表须要对代码段进行訪问。所以在创建代码段描写叙述符的时候,一定要让代码段可读。

    開始内存检測

            xor ecx,ecx          ;计数器清零。记录检測了多少个双字
            mov ebx,MEMORY_START ;检測的起始地址 

    在检測之前,计数器清零,检測的起始地址传送到EBX寄存器。

    exam:   ;显示正在检測的地址
            push 21*80+6
            push YELLOW
            push ebx
            call show_hex_dword
    
            mov dword [es:ebx],0x55aa55aa
            cmp dword [es:ebx],0x55aa55aa
            jnz err
    
            mov dword [es:ebx],0xaa55aa55
            cmp dword [es:ebx],0xaa55aa55
            jnz err
    
            add ebx,4    ;地址添加4个字节
            inc ecx
    
            push 21*80+15
            push BLUE_LIGHT
            push ecx
            call show_hex_dword ;显示已经检測的数量(以双字为单位)
    
            push BAR_POSITION  ;绘制进度条
            push ecx
            push MEMORY_SIZE
            call draw_progress_bar
    
            cmp ebx,MEMORY_END  
            jnz exam
    
    err:          
            hlt 

    上面的代码就是内存检測的主体部分了。
    首先显示正在检測的地址(要检測的地址在ebx中)。

    然后向这个地址写入花码,并读出比較,假设不相等。就跳转到

    err:          
            hlt 

    假设相等,则ebx加上4,ecx加上1,而且显示ecx的值,绘制进度条,然后继续检測。

    绘制进度条

    ;-----------------------------------------      
    ;功能:依据比例在指定位置绘制进度条
    ;输入: 
    ;      push (x*80+y),  表示xy列
    ;      push 分子
    ;      push 分母       
    ;返回:无   
    
    draw_progress_bar:
            pushad
            mov ebp,esp
            mov esi,[ebp+11*4]  ; 取得位置
            mov eax,[ebp+10*4]  ; 取得分子
            mov ebx,[ebp+9*4]    ;取得分母
    
            shr ebx,LENGTH_OF_BAR
            xor edx,edx
            div ebx
            cmp eax,1
            jb out
    
            push eax
            push esi
            push 0x2020; 绿色背景,空格
            call put_char
    
    out:
            popad
            ret 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】

  • 相关阅读:
    [LeetCode] Power of Three 判断3的次方数
    [LeetCode] 322. Coin Change 硬币找零
    [LeetCode] 321. Create Maximum Number 创建最大数
    ITK 3.20.1 VS2010 Configuration 配置
    VTK 5.10.1 VS2010 Configuration 配置
    FLTK 1.3.3 MinGW 4.9.1 Configuration 配置
    FLTK 1.1.10 VS2010 Configuration 配置
    Inheritance, Association, Aggregation, and Composition 类的继承,关联,聚合和组合的区别
    [LeetCode] Bulb Switcher 灯泡开关
    [LeetCode] Maximum Product of Word Lengths 单词长度的最大积
  • 原文地址:https://www.cnblogs.com/cxchanpin/p/7116033.html
Copyright © 2011-2022 走看看