存储器的保护(三)——《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        ; 表示2的6次方
        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    ;基地址为0x00007c00。512字节
        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),  表示x行y列
;      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),  表示x行y列
;      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),  表示x行y列
;      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        ; 表示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,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),  表示x行y列
;      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),  表示x行y列
;      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),  表示x行y列
;      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】

时间: 2024-08-26 11:10:40

存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20的相关文章

《X86汇编语言 从实模式到保护模式》bochs 配置教程(详细)

本文是写给<X86汇编语言 从实模式到保护模式>读者的一份Bochs配置指南. 我们要做的有: 1.下载并安装bochs 2.配置bochs 3.通过bochs调试虚拟硬盘 bochs的官方网址:http://bochs.sourceforge.net/ bochs的下载地址:http://sourceforge.net/projects/bochs/files/bochs/ 本书附带文件下载地址:https://files-cdn.cnblogs.com/files/leec/booktoo

Java 线程第三版 第五章 极简同步技巧 读书笔记

一.能避免同步吗? 取得锁会因为以下原因导致成本很高: 取得由竞争的锁需要在虚拟机的层面上运行更多的程序代码. 要取得有竞争锁的线程总是必须等到锁被释放后. 1. 寄存器的效应 计算机有一定数量的主寄存器用来存储与程序有关的数据. 从逻辑上的观点来看,每个Thread都有自己的一组寄存器.当操作系统将某个Thread分配给CPU时,它会把该Thread特有的信息加载到CPU的寄存器中.在分配不同的Thread给CPU之前,它会将寄存器的信息存下来.所以Thread间绝不会共享保存在寄存器的数据.

ASM:《X86汇编语言-从实模式到保护模式》第12章:存储器的保护

12章其实是11章的拓展,代码基本不变,就是在保护模式下展开讨论. ★PART1:存储器的保护机制 1. 修改段寄存器的保护 当执行把段选择子传到段寄存器的选择器部分的时候,处理器固件在完成传送之前,要检查和确认选择子是正确的,并且该选择子选择的描述符也是正确的.假如索引号是正确的,也就是说明索引号8+7要小于等于边界.如果超过边界,那么处理器就会终止处理,产生异常中断13,同时段寄存器的原值保持不变. 同时处理器还要对描述符的类别进行检查,如果描述符的类别进行确认,举个例子来说,如果描述符的类

ASM:《X86汇编语言-从实模式到保护模式》5-7章相关知识

第5-7章感觉是这一本书中比较奇怪的章节,可能是作者考虑到读者人群水平的差异,故意由浅入深地讲如何在屏幕上显示字符和使用mov,jmp指令等等,但是这样讲的东西有点重复,而且看了第六,第七章以后,感觉第5章的做法真是太笨了. ★PART1:显卡与显存 1. 显卡与显存              a. 显卡控制显示器的最小单位是像素,一个像素对应着屏幕的一个点,屏幕上通常有数十万乃至更多的像素.而控制这些像素就要用到显存自己内置的一个东西,这个东西叫做显存(Video RAM,VRAM) .显存和

《CLR.via.C#第三版》第二部分第12章节 泛型 读书笔记(六)

终于讲到泛型了.当初看到这个书名,最想看的就是作者对泛型,委托,反射这些概念的理解.很多人对泛型的理解停留在泛型集合上,刚开始我也是,随着项目越做越多,对待泛型的认识也越来越深刻. 泛型的概念:泛型是一种特殊的类型,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行. 泛型的优势:源代码保护.类型安全.更加清晰的代码.更佳的性能. 原理:(关键字:开放类型,封闭类型)所有带泛型参数的类型都是一个开放式类型,它不能被实例化(类似接口),在具体使用时生成封闭类型(实际数据类型). 泛型

《CLR.via.C#第三版》第二部分第13章节 接口 读书笔记(七)

这章的书写感觉很普通,是些基础的认知知识. 其中一点的重要认知,泛型接口的好处(其实也是使用泛型的好处之一):编译时类型安全&处理值类型时减少装箱. 再说点书上没有的.本来这些知识我打算另外分类在C#基础里讲的,这里先单独表述下. 接口回调 其实我想不通为什么这本书不讲一下接口回调这个概念,我可不会相信接口回调只在java中用到. 到现在为止,我突然发现,很多编程的基础概念,于面向对象编程的语言来说,都是通用的,但是抱歉,在C#里(相关书籍)居然没有!但我相信你翻阅Java书籍就有很大几率看到这

实模式与保护模式下的分段分页机制

1. 实模式 在实模式下,CPU不会为任务提供任务的保护机制,代码任意运行.8086处理器是学习实模式的常用例子.它内部大致有以下寄存器: 8个16位的通用寄存器: AX (可以拆分成两个AH/AL的8位寄存器) BX (BH,BL) CX (CH,CL) DX (DH,DL) SI (source index, 源索引寄存器) DI (destination index, 目的索引寄存器) BP (base pointer, 基数指针寄存器) SP (stack pointer, 堆栈指针寄存

存储器的保护(一)——《x86汇编语言:从实模式到保护模式》读书笔记18

本文是原书第12章的学习笔记. 说句题外话,这篇博文是补写的,因为让我误删了,可恶的是CSDN的回收站里找不到! 好吧,那就再写一遍,我有坚强的意志.司马迁曰:“文王拘而演<周易>:仲尼厄而作<春秋>:屈原放逐,乃赋<离骚>:左丘失明,厥有<国语>:孙子膑脚,<兵法>修列:不韦迁蜀,世传<吕览>……”好了,不煽情了,进入正题. 第12章的代码如下. 1 ;代码清单12-1 2 ;文件名:c12_mbr.asm 3 ;文件说明:硬盘主引

程序的加载和执行(三)——《x86汇编语言:从实模式到保护模式》读书笔记23

程序的加载和执行(三)--读书笔记23 接着上次的内容说. 关于过程load_relocate_program的讲解还没有完,还差创建栈段描述符和重定位符号表. 分配栈空间与创建栈段描述符 462 ;建立程序堆栈段描述符 463 mov ecx,[edi+0x0c] ;4KB的倍率 464 mov ebx,0x000fffff 465 sub ebx,ecx ;得到段界限 466 mov eax,4096 467 mul dword [edi+0x0c] 468 mov ecx,eax ;准备为