第6章 编写主引导扇区代码

第6章 编写主引导扇区代码

该章节的程序就是在屏幕上输出:Label offset: number标号的十进制格式,可以在最后章节查看相关输出,这样就能比较清楚前面章节的内容到底要干啥了。

本章代码清单

作者提供了源码,可以查看配书源码和工具的c06文件夹。

image

欢迎来到主引导扇区

该小节主要说明了计算机启动需要读取主引导扇区的内容,主引导扇区最后2个字节是0x550xAA

下图是编译后的二进制文件:

image

主引导扇区结束标记

主引导扇区的地址就是 0 ~ 0x1FF,刚好512个字节。

那么为什么是是0x55和0xAA呢?书中没有介绍,我去查了下资料。关于0xAA和0x55-CSDN博客 这篇文章从硬件层面说明了这个问题,感觉上会更合理一些。总结来看就是:

AA展开为10101010, 55展开为01010101,变成串行电平的话就是一个占空比为50%的方波, 这种方波在电路中最容易被分辨是否受干扰或者畸变。

注释

汇编语言的注释以 ; 开头。

; 这里就是注释

在屏幕上显示文字

显卡和显存

该小节主要介绍了显卡显存的概念,以及显卡的两种基本工作模式:图形模式和黑白模式。

显卡(Graphics processing unit,GPU):为显示器提供内容,并控制显示器的显示模式和状态,有集成显卡和独立显卡。

显存(Video RAM,VRAM):显示存储器,控制屏幕上显示的像素明暗和颜色。

显卡的两种基本工作模式

1.图形模式(Graphic Mode):现在的手机上,电脑看到的界面都是图形模式。

图形模式又分为两种:

  • 黑白模式:将显存上的像素一一对应到屏幕上。0表示不亮,1表示亮。

image

黑白模式显示原理
  • 真彩色模式:用3字节对应一个像素,所以可以显示 2^24=16777216 种颜色,称为真彩色。真彩色其实就是RGB(Red-Green-Blue)模式。

2.文本模式(Text Mode):专门用于显示字符的工作方式称为文本模式。例如:Linux命令行版本。

就是将显存的内容按一定规则映射到显示器上。

image

字符在屏幕上的显示原理

文本模式在加电自检后会初始化为80*25的文本模式:

image

文本模式:80*25=2000个字符

为什么是80*25?这个历史原因,我搜了一下,stackexchange 这篇文章说的比较详细了。

  • 640像素是自然且易于实现的屏幕宽度,每8位1个字节,所以宽度就是80。
  • 为什么25行?
  • 4:3是CRT显示器的比例,高度为480;
  • 等宽字体的比例是0.43,高度就是8/0.43=18.6像素,约为19;
  • 19*25=475 最接近480;

说到等宽字体,我编程和画图就是用等宽字体:Fira Code,感觉很不错。

显存映射到处理器的地址空间里:主要是为了加快访问速度。地址为:0xB8000 ~ 0xBFFFF

image

处理器的地址空间映射到显存

初始化段寄存器

该小节介绍了如何设置 es段寄存器 的段地址。

访问内存使用逻辑地址,就是 段地址:偏移地址 的形式。要访问文本模式的显示缓冲区,就可以将段寄存器设置为 0xb800。

mov ax,0xb800 ;指向文本模式的显示缓冲区
mov es,ax

设置段地址后,后面访问对应的地址就可以直接使用es前缀:

image

设置段寄存器的通用语法格式:

mov 段寄存器,通用寄存器
mov 段等存器,内存单元

设置段寄存器不能直接用立即数,需要通用寄存器中转。

显存的访问和ASCII代码

ASCII码表:就是美国信息交换标准代码(American Standard Code for Information Interchange),就是将0~127总共128个二进制数字表示字符。

image

ASCII码表

显存的访问:屏幕上的每个字符对应显存中连续的2字节。第一个字符时ASCII代码,后面一个是字符的显示属性(类似web前端的样式)。

image

字符代码和字符属性

其中:

  • H 字母的显示属性是0x07=0000_0111,前景色RGB都为1,所以是白色。
  • e 字母的显示属性是0x04=0000_0100,前景色R为1,所以是红色。
  • 第一个 l 字母的显示属性是0x04=0000_0010,前景色R为1,所以是绿色。
  • 第二个 l 字母的显示属性是0x04=0000_0001,前景色B为1,所以是蓝色。

显示字符

image

其中:

  • byte表示传送1个字节。
  • 传送的内容可以用ASCII码,也可以直接用字符代替。

类似byte,word表示1个字(2个字节),dword表示双字(4个字节)。

mov指令的格式

该小节介绍了mov指令的用法。

mov指令用于数据传送。

1.传送到寄存器:

mov 寄存器,寄存器
mov 寄存器,内存
mov 寄存器,立即数

其中:要表示内存位置的值,要用中刮号 [] 包裹起来。

示例:

mov ax,bx     ;把寄存器bx的值传送到寄存器ax
mov ax,[0x02] ;内存0x02位置开始的2个字节传送到ax
              ;低端字节序:
              ;  0x02位置的值存储到al
              ;  0x03位置的值存储到ah
mov ax,0x02   ;把0x02这个值传送到ax

2.传送到内存:

mov 内存,寄存器
mov [byte|word|dword] 内存,立即数

其中:寄存器可以直接看出字节数,所以不用加 byte、word、dword修饰。

示例:

mov [0x02],ax               ;把ax中的值传送到内存0x02开始的2个字节
                            ;低端字节序:
                            ;  al存储到0x02位置
                            ;  ah存储到0x03位置

mov byte [0x02],0x03        ;把0x03这个值传送到内存0x02位置

mov word [0x02],0x1234      ;把0x1234这个值传送到内存0x02开始的2个字节
                            ;低端字节序:
                            ;  0x02位置存储0x34
                            ;  0x03位置存储0x12

mov dword [0x02],0x12345678 ;把0x1234这个值传送到内存0x02开始的4个字节
                            ;低端字节序:
                            ;  0x02位置存储0x78
                            ;  0x03位置存储0x56
                            ;  0x04位置存储0x34
                            ;  0x05位置存储0x12

本节习题

1.填空题

  • 文本模式下显示缓冲区其实地址为:0xB8000,段地址需要除以0x10,即0xB800
  • 屏幕一共可以显示2000个字符,每个字符2个字节,偏移量从0开始,所以要从偏移量3998的位置开始,连续写入:H字符0010_0111

2.判断哪些不正确。

  • A不正确,因为立即数是16个字节,al是8个字节。
  • C不正确,因为ds和al寄存器长度不一致。
  • D不正确,因为传输到内存单元,没有指定size。
  • I不正确,因为ax和bl寄存器长度不一致。
  • K不正确,因为内存之间不能互相传递。

显示标号的汇编地址

标号

该小节介绍了汇编地址(Assembly Postion)和标号(Label)。

汇编地址(Assembly Position):编译器会把代码整体上作为一个独立的段来处理,并从开始计算每条指令的地址,即汇编地址。
在编译源文件的时候可以通过 -l 参数可以生成 .lst 文件,.lst 文件可以查看到汇编地址。

例如编译一个打印hello world的汇编程序:

; 代码文件:demo.asm
; 功能:在屏幕上显示Hello world!
jmp near start

message db 'Hello world!'

start:
    mov ax,0x7c0            ; 设置代码起始地址
    mov ds,ax

    mov ax,0xB800           ; 设置显示数据段
    mov es,ax
    
    mov cx,start-message    ; 循环次数就是start标号地址和message标号地址的差    
    mov si,message          ; 起始地址设置为message标号地址
    mov di,0
show:
    mov bl,[ds:si]          ; 下面4行用来显示一个字节
    mov byte [es:di],bl
    inc di
    mov byte [es:di],0x07

    inc si
    inc di
    loop show      

jmp $
times 510-($-$$) db 0
db 0x55,0xaa

编译后输出 .lst 文件:

nasm -f bin .\demo.asm -o .\demo.bin -l .\demo.lst

查看 .lst 文件:

image

nasm编译生成.lst文件查看汇编地址

每行汇编代码都有自己的长度,当前行汇编地址 加上 当前行汇编代码长度就是下一行的汇编开始地址。

汇编地址和内存偏移地址的关系:加载到内存后,独立一个段存在,按照汇编地址顺序写入对应的二进制指令,汇编地址和内存内偏移地址是一致的。

例如上例加载到内存段 0x6000 处。

image

程序加载到内存中

标号(Label):在NASM汇编语言里,每条指令的前面都可以拥有一个标号,以代表和指示该指令的汇编地址。

例如上例的 start 和 show 就是两个标号。

image

标号指向后续指令的汇编地址

书中用了自带的例子讲解标号,原理都是一样的。

如何显示十进制数字

该小节介绍了标号在程序中的应用十进制的显示原理

标号的应用:通过标号可以定位到标号定义的位置。

image

汇编语言中标号编译后直接变成汇编地址

书中使用 number 标号作为例子,原理是一样的。书中的例子number标号的汇编地址是:0000012E

十进制的显示原理:就是一直除以10,得到余数,然后反向进行显示即可。

书中的例子就是将 number 标号作为十进制进行输出,大概的流程如下图:

image

显示十进制数

书中是把余数存储到 number 标号定义的5个字节的内存空间中。这块的知识点下个章节就有介绍。

在程序中声明并初始化数据

该小节介绍了如何在程序中声明并初始化数据伪指令(Pseudo Instruction)的概念。

声明并初始化数据:一般通过db、dw、dd、dq伪指令(Pseudo Instruction):

  • db(Declare Byte):声明的单个内容空间为字节。
  • dw(Declare Word):声明的单个内容空间为字(2字节)。
  • dd(Declare Double Word):声明的单个内容空间为双字(4字节)。
  • dq(Declare Quad Word):声明的单个内容空间为四字(8字节)。
db 0,0,0,0,0 ;声明5个字节,初始化为0
dw 0,0,0,0,0 ;声明5个字,初始化为0
dd 0,0,0,0,0 ;声明5个双字,初始化为0
dq 0,0,0,0,0 ;声明5个四字,初始化为0

上面例子中虽然初始都为0,但是占用的内存空间是不一样的。

声明的数据一般都要使用,所以需要提供一个标号进行指向,可以理解为C语言的指针。

number1 db 0,0,0,0,0 ;声明5个字节,初始化为0
number2 dw 0,0,0,0,0 ;声明5个字,初始化为0
number3 dd 0,0,0,0,0 ;声明5个双字,初始化为0
number4 dq 0,0,0,0,0 ;声明5个四字,初始化为0

伪指令(Pseudo Instruction):不是处理器指令,只是编译器提供的汇编指令,在编译成机器代码后就消失了。

上面的dd、dw、dd、dq都是伪指令,mov、jmp就是机器指令。

本节习题

找出代码片段的错误:

data1 db 0x55,0xf000,0x0f
data2 dw 0x38,0x20,0x55aa

data1 这样声明了一个 0xf000 2个字节的数字,编译会报错。

分解数的各个数位

该小节详细介绍了处理器是如何做除法的、0x7c00的问题和异或操作。

除法:包括16位二进制除8位二进制32位二进制除16位二进制两种类型,总结来看就是这样:

image

汇编语言除法

0x7c00:为什么要加上0x7c00,因为程序是加载到内存的 0x0000:0x7c00 处。

image

偏移量计算

异或(eXclusion OR):两个数从低到高按位比较,相同则结果为0,不同则结果为1。汇编中异或指令为 xor

mov ax,0000_0000B ;B表示该数为二进制
xor ax,1111_0000B ;异或结果为:1111_0000B

书中使用了一个代码技巧,用 xor 的方式将1个寄存器的值置为0:

xor dx,dx ;将dx中的值置为0

和 mov dx,0 相比较:

  • mov dx,0 的机器码是 BA 00 00;
  • xor dx,dx 的机器码则是31 D2,更短;
  • xor dx,dx 两个操作数都是通用寄存器,所以执行速度最快。

显示分解出来的各个数位

该小节主要介绍了字符的显示加法指令。

字符显示:当计算完数字的个十百千万位,并存储在 number标号 开始的5个字节,此时内存映像:

image

显示10进制数值内存映像

因为存储时从低到高存储个、十、百、千、万位,所以显示要倒过来,这也是书中程序从0x04开始到0x00结束的原因了。

另外数字数字字符时在计算机中存储的是不同的值,根据ASCII码表,可以知道数字和数字字符相差0x30,所以数字加上0x30就是数字字符了。

image

数字和数字字符相差0x30

加法:非常简单的一个指令,用于两个操作数相加。

add dx,0x100 ;dx寄存器的值加上0x100。

使程序进入无限循环状态

该小节主要介绍了jmp指令和jmp使用near的操作数计算,通过jmp到自身实现无限循环。

jmp:用于跳转指定的地方执行。

例如:

jmp 0x0000:0x5000 ;跳转到 0x0000:0x5000 的地方执行

infi:
    ...
jmp near infi ;跳转到标号infi开始的地方执行

如果 infi 标号跟着就是 jmp 指令,那么就是无限循环:

infi:jmp near infi ;跳转到标号infi开始的地方执行

jmp使用near的操作数计算:这块感觉会比较难理解,分成两个部分理解会清晰了:①编译器计算跳转地址方式;②处理器运行时计算方式。编译器是根据处理器的方式计算的。

1.编译器计算跳转地址方式:

  • 用标号处的汇编地址减去当前指令的下一条指令的汇编地址。
  • 因为是near方式,计算结果只保留右边16位。

例如:

mov ax,0x01
start:
    mov ax,0x01
    mov bx,0x02
    mov cx,0x03
jmp near start     ;跳转到start

查看汇编地址:

image

F4FF的计算方式就是:

start的汇编地址 - (jmp指令的下一条地址)  
 = 0003 - (000C+0003) 
 = 0003 - 000F 
 = FFF4

采用低端字节序就是:F4FF。

2.处理器运行时计算方式。

  • 用IP内的值加jmp指令操作数,IP值是指向下一条指令。

参考上例:

IP的值 + jmp指令操作数
 = (000C+0003) + FFF4
 = 000F + FFF4
 = 0003

查看计算公式可以发现,编译器和处理器的方式刚好是反过来的。

完成并编译主引导扇区代码

主引导扇区有效标志

该小节主要介绍了主引导扇区的有效标志times指令。

主引导扇区的有效标志:一个有效的主引导扇区,其最后2字节的数据必须是0x55和0xAA。

db 0x55,0xaa

如果采用dw版本,因为 INTEL处理器 采用低端字节序,所以应该这样写:

dw 0xaa55

times: 指令可以重复若干次。

times 203 db 0 ;分配1个字节,重复203次。相当于分配203个字节。

书中例子主要是为了占坑,确保 0x55 和 0xaa 写在主引导扇区的最后两个字节。

代码的保存和编译

进入代码目录:

$ nasm -f bin .\c06_mbr.asm -o .\c06_mbr.bin -l .\c06_mbr.lst

加载和运行主引导扇区代码

把编译后的指令写入主引导扇区

该小节介绍了如何写入主引导扇区。
image

启动虚拟机观察运行结果

image

程序的调试技术

开源的Bochs虚拟机软件

该小节介绍了Bochs虚拟机软件的下载和安装。

下载地址:http://sourceforge.net/projects/bochs/files/bochs/

Bochs下的程序调试入门

该小节初步介绍了Bochs的程序调试方法。

image

Bochs开始运行

一开始通过jmp跳转到ROM-BIOS执行。

jmp far f000:e05b

为什么该条指令的地址是 0x00000000FFFFFFF0? 书中给出了说明,总结来看就是希望将该指令放在内存的最高位。

Bochs调试功能

1.s: step 单步执行
2.b: b 0x7c00 设置中断
3.c: continue 一直执行
4.r: 查看寄存器内容
image

  • 这8个32位寄存器分别是EAX、EBX、ECX、EDX、ESI、EDI、EBP和ES
  • 同时,指令指针寄存器IP也做了扩展,达到32位,即EIP。
  • 在64位处理器上,这些寄存器再次被扩展,达到了64位,即RAX、RBX、RCX、RDX、RSI、RDI、RBP、RSP和RIP
  • 64位的x86处理器还新增了8个64位的通用寄存器R8、R9、R10、R11、R12、R13、R14和R15。

5.sreg: Segment Register,查看段寄存器
image

在32位和64位处理器中,除了段寄存器CS、SS、DS和ES,还新增了两个段寄存器FS和GS

在32位和64位处理器中,以上6个段寄存器都依然是16位的,但都额外增加了一个不可访问的部分,叫作段描述符高速缓存器。

6.xp: eXamine memory at Physical address,查看物理内存内容。

查看0xb8000开始的2个双字(8字节)的内容:

xp/2 0xb8000

image

7.q: quit,退出的意思。

不用quit退出,下次再启动会报一个占用,再次重启就可以了。

参考资料

  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值