存储器的保护(三)——《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 ;南桥芯片内的端口
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
代码分析
设计思路
- 这个程序实现的主要功能是:检测1MB以上的内存空间,比如检测物理地址为1M~8M的单元。
- 检测方法是向每个双字单元写入0x55aa55aa,并读出来和0x55aa55aa做比较,如果相等,则再写入0xaa55aa55,并读出来和0xaa55aa55作比较,如果相等,那么这个双字单元是OK的,把物理地址加上4,继续检测。如果读出的和写入的不相等,那么检测出错,程序停止。
- 检测的时候,显示正在检测的内存地址
- 显示一个进度条
- 显示“已经检测的内存数 / 总共需要检测的内存数”
下面我们分析具体的实现。不打算逐行讲述所有代码,仅选择重点部分讲解。
定义一些常量
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】
存储器的保护(三)——《x86汇编语言:从实模式到保护模式》读书笔记20的更多相关文章
- ASM:《X86汇编语言-从实模式到保护模式》第10章:32位x86处理器的编程架构
★PART1:32位的x86处理器执行方式和架构 1. 寄存器的拓展(IA-32) 从80386开始,处理器内的寄存器从16位拓展到32位,命名其实就是在前面加上e(Extend)就好了,8个通用寄存 ...
- ASM:《X86汇编语言-从实模式到保护模式》第11章:进入保护模式
★PART1:进入保护模式 1. 全局描述符表(Global Descriptor Table,GDT) 32位保护模式下,如果要使用一个段,必须先登记,登记的信息包括段的起始地址,段的 ...
- ASM:《X86汇编语言-从实模式到保护模式》第8章:实模式下硬盘的访问,程序重定位和加载
第八章是一个非常重要的章节,讲述的是实模式下对硬件的访问(这一节主要讲的是硬盘),还有用户程序重定位的问题.现在整理出来刚好能和保护模式下的用户程序定位作一个对比. ★PART1:用户程序的重 ...
- ASM:《X86汇编语言-从实模式到保护模式》第16章:Intel处理器的分页机制和动态页面分配
第16章讲的是分页机制和动态页面分配的问题,说实话这个一开始接触是会把人绕晕的,但是这个的确太重要了,有了分页机制内存管理就变得很简单,而且能直接实现平坦模式. ★PART1:Intel X86基础分 ...
- ASM:《X86汇编语言-从实模式到保护模式》第12章:存储器的保护
12章其实是11章的拓展,代码基本不变,就是在保护模式下展开讨论. ★PART1:存储器的保护机制 1. 修改段寄存器的保护 当执行把段选择子传到段寄存器的选择器部分的时候,处理器固件在完成传送之前, ...
- ASM:《X86汇编语言-从实模式到保护模式》第15章:任务切换
15章其实应该是和14章相辅相成的(感觉应该是作者觉得14章内容太多了然后切出来了一点).任务切换和14章的某些概念是分不开的. ★PART1:任务门与任务切换的方法 1. 任务管理程序 14章的时候 ...
- ASM:《X86汇编语言-从实模式到保护模式》第9章:实模式下中断机制和实时时钟
中断是处理器一个非常重要的工作机制.第9章是讲中断在实模式下如何工作,第17章是讲中断在保护模式下如何工作. ★PART1:外部硬件中断 外部硬件中断是通过两个信号线引入处理器内部的,这两条线分别叫N ...
- ASM:《X86汇编语言-从实模式到保护模式》第14章:保护模式下的特权保护和任务概述
★PART1:32位保护模式下任务的隔离和特权级保护 这一章是全书的重点之一,这一张必须要理解特权级(包括CPL,RPL和DPL的含义)是什么,调用门的使用,还有LDT和TSS的工作原理(15章着重 ...
- ASM:《X86汇编语言-从实模式到保护模式》5-7章:汇编基础
第5-7章感觉是这一本书中比较奇怪的章节,可能是作者考虑到读者人群水平的差异,故意由浅入深地讲如何在屏幕上显示字符和使用mov,jmp指令等等,但是这样讲的东西有点重复,而且看了第六,第七章以后,感觉 ...
随机推荐
- 利用input event 实时监听input输入的内容
<div id="addNumber"> <p>How many people would you like to invite?</p> &l ...
- 关于C#/sqlserver生成32位数据库字段总结
一.C#中用Guid.NewGuid().ToString() Sql中用NEWID() 以上方法生成的是36位的GUID,如果需要转换成32位,则需要替换掉其中的'-'字符. Sql中的方法:r ...
- .Net Core内存回收模式及性能测试对比
.NET Core 两种GC模式: Server GC / Workstation GC Server GC : 主要应用于多处理器系统,并且作为ASP.NET Core宿主的默认配置.它会为每个处理 ...
- MicroPython (一)点亮我的Led
工具 : putty F429Discovery 开发板 Notepad++ 注意:不知道为什么 其他的终端工具有问题,推荐 putty 基本没有发现问题 putty 实时调试 使用putty 打开U ...
- Django Managers管理器
Managers class Manager 管理器是向Django模型提供数据库查询操作的接口.Django应用程序中每个模型至少有一个管理器. Manager names 默认情况下管理器的名字为 ...
- 7,CountDownLatch 与 CyclicBarrier 的 区别
CountDownLatch : 一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 用给定的计数 初始化 CountDownLatch.由于调用了 countDo ...
- centos 安装 Pip 的方法总结
转自https://blog.csdn.net/u014236259/article/details/75212659 在我们安装Python后,如果未安装包管理工具pip,此时需要自己手动安装: 方 ...
- 机器学习 - ML + 深度学习 - DL
机器学习 CNCC - 2016 | 机器学习(原文链接) Machine Learning - ML,机器学习起源于人工智能,是AI的一个分支. 机器学习的理论基础:计算学习理论 - Computa ...
- 浏览器性能接口performance.timing说明
原文来自于 https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html 下图描述了该接口的各个时间 ...
- python全栈开发_day3_数据类型,输入输出及运算符
一:常见数据类型 1)int整型 例:age=1 整型常用于年龄,账号等 2)float浮点型 例:salary=5.1 浮点型常用于薪水,身高,体重等 3)str字符串类型 例:name=“chen ...