从实模式到保护模式笔记01编写主引导扇区代码

从实模式到保护模式笔记01编写主引导扇区代码

本系列博客是李忠老师的《x86汇编:从实模式到保护模式》的读书笔记,因为之前有王爽的《汇编语言》的底子,博客在实模式时将比较简要,主要学习nasm与masm相比写法的不同,每章以代码+执行+讲解的形式展开。

0x00 代码

mov ax,0xb800
mov es,ax

mov byte [es:0x00],'L'
mov byte [es:0x01],0x02
mov byte [es:0x02],'a'
mov byte [es:0x03],0x02
mov byte [es:0x04],'b'
mov byte [es:0x05],0x02
mov byte [es:0x06],'e'
mov byte [es:0x07],0x02
mov byte [es:0x08],'l'
mov byte [es:0x09],0x07
mov byte [es:0x0a],','
mov byte [es:0x0b],0x07
mov byte [es:0x0c],'o'
mov byte [es:0x0d],0x07
mov byte [es:0x0e],'f'
mov byte [es:0x0f],0x07
mov byte [es:0x10],'f'
mov byte [es:0x11],0x07
mov byte [es:0x12],'s'
mov byte [es:0x13],0x07
mov byte [es:0x14],'e'
mov byte [es:0x15],0x07
mov byte [es:0x16],'t'
mov byte [es:0x17],0x07
mov byte [es:0x18],':'
mov byte [es:0x19],0x07

mov ax,number
mov bx,10

mov cx,cs
mov ds,cx

mov dx,0
div bx
mov [0x7c00+number+0x00],dl;保存个位上的数字

xor dx,dx
div bx
mov [0x7c00+number+0x01],dl;十位

xor dx,dx
div bx
mov [0x7c00+number+0x02],dl;百位

xor dx,dx
div bx
mov [0x7c00+number+0x03],dl;千位

xor dx,dx
div bx
mov [0x7c00+number+0x04],dl;万位

;以下用10进制显示标号的偏移地址的各个十进制位
mov al,[0x7c00+number+0x04]
add al,0x30
mov [es:0x1a],al
mov byte [es:0x1b],0x04

mov al,[0x7c00+number+0x03]
add al,0x30
mov [es:0x1c],al
mov byte [es:0x1d],0x04

mov al,[0x7c00+number+0x02]
add al,0x30
mov [es:0x1e],al
mov byte [es:0x1f],0x04

mov al,[0x7c00+number+0x01]
add al,0x30
mov [es:0x20],al
mov byte [es:0x21],0x04

mov al,[0x7c00+number+0x00]
add al,0x30
mov [es:0x22],al
mov byte [es:0x23],0x04

mov byte [es:0x24],'D'
mov byte [es:0x25],0x07

infi:jmp near infi

number db 0,0,0,0,0
times 203 db 0
db 0x55,0xaa

QQ截图20200628103546

上述代码运行正确

0x01 代码详解

​ 处理器加电后先去0xffff:0x0000处执行bios代码,bios执行完自身逻辑后,将MBR(主引导扇区0磁头0磁道1扇区)读入0x0000:0x7c00处,并jmp过去执行主引导扇区。而主引导扇区最后两个字节必须是 0x55 0xaa。

​ 而地址空间中0xb800:0x0000-0xbfff:0x0000是现存映射的,所以可以往里面写入ascii码来改变屏幕显示的文字。

在nasm中,直接mov byte [es:0x00],’a’ 这种形式,和masm不同

汇编地址:处理器访问内存时,采取短地址:偏移地址的形式访问,所以在源程序编译时,编译器会把源程序整体当作一个独立的段来处理,并从0开始计算和跟踪每一条指令的地址,这些地址在编译时确定成为 编译地址,编译地址都是相对于第一条指令的偏移量,且被执行时是一个立即数,因此可以用立即数表达式,如上面的[0x7c00+number+0x00]。

标号:在nasm里,每条指令前都可以有一个标号,以代表该指令的汇编地址,nasm中冒号写不写无所谓,而且标号可以单独占用一行:

infi:jmp near infi
infi jmp near infi
infi:
	jmp near infi;三种写法都是对的,习惯用第一种

​ 值得注意的是,如果程序第一条指令被装入后,在所在的段的偏移地址不是0,那么其余的汇编地址也不能对应真正的段内偏移地址。例如上面的代码中number标号表示db定义的数据的首字节 对应的汇编地址,但是由于被装入到0x0000:0x7c00,第一条指令的偏移地址都不是0,所以标号的地址也不对应偏移地址,需要+0x7c00使用。

​ 和masm一样 nasm中的jmp 到标号 也是有三种 jmp short 标号 jmp near 标号 jmp far 标号 前两者都是保存的标号相对于当前ip的相对距离 范围分别是从 -128-127 -32768-32767,因为是相对跳转,所以不用担心标号的汇编地址与段内偏移地址不对应的问题,所以上面的代码是 infi: jmp near infi 而不是 infi:jmp near 0x7c00+infi。而后者far则是保存的跳转的段内偏移地址,所以如果用标号,就得+0x7c00,也就是infi:jmp far infi+0x7c00

和masm不同,nasm使用times 203 db 0来重复,而不是db 203 dpu (0)

0x02 bochs调试

​ bochs安装目录有一个bochsdebug.exe,一样的使用方式,但是运行后会等待我们调试

QQ截图20200628112307

​ 如图 左边显示下一条执行的指令和对应地址,而闪烁的光标等待我们输入调试命令。注意到:第一条指令的地址并不是0xffff:0x0000,因为现代x86处理器并不是和8086一样了,现代处理器加电后cs为0xf000,ip被初始为0xfff0,并且其余部分的地址线都是高电平,由于bochs虚拟的地址线是32位,所以最终为0xfffffff0。但是,虽然现代x86处理起加电从这个地址开始,并且bios的rom被映射到这里,实际上,现代x86处理器也还是兼容在0x000ffff0处访问bios的rom的,也就是说 bios的rom被映射到了地址空间上的两个片段

命令 s 可以单步执行(step)

命令 b 物理地址 可以在对应物理地址下断点 如 b 0x7c00 会在0x00007c00 下断点(break)

命令 c 持续执行 直到遇到断点 ,所以可以和刚刚的b命令配合实现让处理器一直执行命令直到指定的物理地址(continue)

QQ截图20200628114411

​ 上图我们通过b 0x7c00 和 c命令让cpu一直执行到了0x7c00处

QQ截图20200628114537

​ 此时bochs的界面也确实执行完了bios即将到达我们在mbr里写的,已经被读入到0x7c00处的代码mov ax,0xb800

命令 r 显示通用寄存器的值(register)

QQ截图20200628114805

​ 因为是64位的bochs,所有的寄存器已经被扩展到64位了,但是仍然可以拆开成2个32位,低32位又可以拆成2个16位,低16位即是8086的通用寄存器。

​ 在32位和64位处理器中,增加了两个新的段寄存器FS、GS ,并且6个段寄存器依然都是16位的,但是额外增加了一个不可访问的部分,段描述符高速缓存器,这个由处理器内部使用,不能在程序中访问,里面存放了段的起始地址、段的扩展范围和各种属性(比如是数据段还是代码段,是否可写、是否被访问过等)。具体的用法将在保护模式展开。

命令 xp 物理地址 显示指定地址的内容,显示的长度默认是一个双字,4字节,可以用 xp/n 物理地址 来指定显示区域的字节数。(eXamine memory physical adress)

QQ截图20200628121419

0x03 章节题目

QQ截图20200628121822

​ (1)16位/8位 商2101在al 余数5在ah 但是超过范围了 所以要用32位/16位

mov ax,21015
mov dx,0
mov bx,10
div bx

​ (2)首先我们要知道,这次要显示的是在段内的偏移地址而不是汇编地址,本章的示例程序,也就是本文开头那个,显示的地址是汇编地址:

QQ截图20200628123014

QQ截图20200628123022

​ 可以看到 infi:jmp near infi 的偏移地址地址是0x7d2b,且这条指令是e9fdff占3字节(截图没截全)所以对下一条指令进行标号的number的偏移地址应该是0x7d2e, 也吻合0x7c00+302d(12eh)=0x7d2e。

​ 所以我们要显示infi标号对应的指令的段内偏移地址,只需要把mov ax,number改成 mov ax,0x7c00+infi,结果如下(32043d=7d2bh):

QQ截图20200628123846

​ (3)很简单,没有指令,也就无法编译成机器码,那我们就只能字节写机器码到代码中,也就是用db定义数据的方式一个字节一个字节的写入,然后当代码执行就ok了!