以下是读本书第三章的收获。

  如何知道一个源程序的各符号(指令和变量)地址?简单来说,地址就是该符号偏移文件开头的距离,符号的地址是按顺序编排的,所以两个相邻的符号,其地址也是相邻的。对于指令来说,指令的地址=上一个指令的地址+上一个指令的大小,最初的符号地址为0,可以根据此公式推算出所有符号的地址。

section称为节,它是提供给程序员编排程序用的,我们可以将一段读取字符串的代码放在section A下,将读取硬盘的代码放进section B下,可以给A,B换成一个更具体的名字,来提高可读性。        例如,下图这段代码,将整个程序分成section code和section data两节,顾名思义,就是存放代码和数据的两个section,这样我们就很清楚地知道每部分代码是做什么用的。另一个值得注意的细节是section并不会对符号的编址用什么影响,去掉section和不去掉其实符号的地址都是一样的。

vstart用于告诉编译器,之后的符号都以某个地址为初始地址来编址。如下图,像$$的地址替换成以0x7c00为初始地址的地址,符号var1和var2的地址被替换成以0x900的地址。

当然,我们还可以通过section.节名称.start来获得在文件中真正的地址。如section.code.start值为0x0,即section code偏移文件的距离为0。那么什么时候用vstart来修饰section呢,其实只要我们保证将改源码加载到内存后,符号的地址能够指向正确的位置即可。

mov ax, message
message db "1MBR"

  假设我们要将这段源代码加载到0x7c00后,我们会发现message是以地址0为初始地址编址的符号,即以文件开头编址的符号,这时候我们ax得到的不是真正的message的位置。此时我们需要增加一个vstart来修饰这段代码,message才能真正地以0x7c00地址为初始地址编址,即:

SECTION MBR vstart=0x7c00
mov ax, message
message db "1MBR"

访问外设

  CPU要管理或者访问外设也不是一件容易的事情,毕竟外设多种多样,并没有一个统一地功能设计,要CPU去兼容所有外设的功能,和实现不一样的IO操作指令其实是不太现实的。所以设计者在CPU和外设之间加多了一层,即IO接口。IO接口是帮助CPU去管理外设的,不同的外设会有不一样的IO接口。IO接口可以增加缓存来缓解CPU与外设访问速度上的不匹配,还可以做数据格式转换,电平转换等等,总之,是个帮助CPU访问外设的助手。

那么CPU是如何通过IO接口来访问外设的呢?答案就是通过IO接口上的寄存器,也称为端口。通过对不同的端口赋值,我们就可以获取外设的各种信息,如硬件是否加载好,硬盘的数据是否已经准备好等等。那么我们又是如何访问端口的呢,有两种方式,第一种是通过内存映射,将端口映射到相应的内存地址上,这样我们访问这地址就相当于访问了这个端口,像之前的ROM也是这样做的。第二种是对端口进行编码得到一套独立的端口号编址,通过in,out指令我们就可以对端口进行输入和输出,从而得到外设的信息。

   下面我们就通过这两种方式来对硬件进行IO操作。

对显存操作

  由上图可以看到,0xB8000-0xBFFFF这段内存区域是是映射到显存里面的,即采用内存映射方式来对显存进行操作;只要我们向内存的这块区域写进文本,显存相应位置的值就会更新,屏幕上也就会显示相应的文本了。

  下面代码将段基址寄存器gs初始化为0xB8000,定位到显存这块区域。然后下面加粗的代码直接将相应的文本数据填进显存里面。这样,屏幕上就会显示‘1 MBR’了。

SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax mov ax,0x600
mov bx,0x700
mov cx,
mov dx,0x184f
int 0x10 mov byte [gs:0x00], ''
mov byte [gs:0x01], 0xA4
mov byte [gs:0x02], ' '
mov byte [gs:0x03], 0xA4
mov byte [gs:0x04], 'M'
mov byte [gs:0x05], 0xA4
mov byte [gs:0x06], 'B'
mov byte [gs:0x07], 0xA4
mov byte [gs:0x08], 'R'
mov byte [gs:0x09], 0xA4
jmp $ times -($-$$) db
db 0x55, 0xaa

  编写好代码之后,编译:

nasm -o mbr.bin mbr.S

  再写进虚拟硬盘里面:

dd if=mbr.bin of=hd60M.img bs= count= conv=notrunc  

  最后再运行Bochs,结果显示如下图:

对硬盘操作

 端口用途

  接下来,我们用指令in,out来对硬盘进行操作,首先,我们得知道硬盘有哪一些端口号,下图将硬盘控制器相关的端口号列出来了,并且读操作和写操作时,端口的用途是不一样的。

  接下来简单地描述各个寄存器具体的含义:

  ①Data,用于读写数据的。这个寄存器就相当于缓存,读操作时需要从这个缓存里面拿数据,前提是数据已经准备好了;写操作时,需要向缓存里面存放数据,最终才能存到硬盘里面。

  ②Error,顾名思义,就是保存读操作时出现的错误信息。

  ③Features,有些指令需要用到额外的参数,这些参数就保存到这个寄存器里面。

  ④Sector count,指定读取或写入多少个扇区。

  ⑤LBA,是用来描述一个扇区的地址,在这里一个扇区的地址是28位的,LBA low,mid,high 分别保存着LBA的0-7位,8-15位,16-23位。那么另外的4位去哪呢?保存在device里面了,所以device其实也是个杂项,即保存着多个不同的信息。

  ⑥Device,0-3位保存着LBA的24-27位,第4位指定通道上的主盘或从盘,0为主盘,1为从盘,第5位固定是1,第6位指定是否启用LBA模式,第7位固定是1。

  ⑦Status,保存硬盘的状态信息。读硬盘时,第0位是ERR,如果ERR位为1,代表命令出错,错误原因看Error;第3位是data request位,如果此位为1,表示硬盘已经准备好数据了;第6位位DRDY,表示硬盘就绪,对硬盘诊断用的;第7位位BSY位,表示硬盘是否繁忙,其它位暂不用到。

  ⑧Command,写硬盘,需要在这个寄存器指定功能。我们主要用到了三个功能。存入0xEC时,代表进行硬盘识别;存入0x20,代表读扇区;存入0x30时代表进行写扇区。

  需要再强调一次的是,有的寄存器在读和写的时候作用是不一样的,但都是共用一个寄存器,如Status和Command是共用同一寄存器的。

 读写硬盘步骤

  读写硬盘需要用到多个寄存器,我们就需要考虑读写寄存器的先后顺序了,为了统一一些,我们采取以下步骤:

  (1)先选择通道,往通道sector count寄存器写入待操作的扇区数。

  (2)往通道的三个LBA寄存器写入扇区起始地址的低24位。

  (3)往device寄存器中写入LBA地址的24-27位,将第6位置为1,使其为LBA模式, 设置第4位,选择操作的硬盘(master硬盘或slave硬盘)。

  (4)往该通道上的command寄存器写入操作命令。

  (5)读取该通道的status寄存器,判断硬盘工作是否完成。

  (6)如果以上步骤是读硬盘,进入下一个步骤,否则,完工。

  (7)将硬盘数据读出。

  下面开始编写代码:

%include "boot.inc"
SECTION MBR vstart=0x7c00
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov fs,ax
mov sp,0x7c00
mov ax,0xb800
mov gs,ax mov ax,0x600
mov bx,0x700
mov cx,
mov dx,0x184f
int 0x10 mov byte [gs:0x00], ''
mov byte [gs:0x01], 0xA4
mov byte [gs:0x02], ' '
mov byte [gs:0x03], 0xA4
mov byte [gs:0x04], 'M'
mov byte [gs:0x05], 0xA4
mov byte [gs:0x06], 'B'
mov byte [gs:0x07], 0xA4
mov byte [gs:0x08], 'R'
mov byte [gs:0x09], 0xA4 mov eax,LOADER_START_SECTOR
mov bx,LOADER_BASE_ADDR
mov cx,
call rd_disk_m16 jmp LOADER_BASE_ADDR
rd_disk_m16:
;步骤1,写入读写扇区数量,cx指定数量
mov esi,eax
mov di,cx
mov dx,0x1f2
mov al,cl
out dx,al
;步骤2,写入低24位LBA地址
mov eax, esi
mov dx,0x1f3
out dx,al
mov cl,
shr eax,cl
mov dx,0x1f4
out dx,al shr eax,cl
mov dx,0x1f5
out dx,al
;步骤3,写入高4为LBA地址,并设置第4-7位
shr eax,cl
and al,0x0f
or al,0xe0
mov dx,0x1f6
out dx,al
;步骤4,写入读操作
mov dx,0x1f7
mov al,0x20
out dx,al
;步骤5,判断硬盘是否已经准备好数据
.not_ready:
nop
in al,dx
and al,0x88
cmp al,0x08
jnz .not_ready mov ax,di
mov dx,
mul dx
mov cx, ax
mov dx,0x1f0
.go_on_read:
;从端口读取数据
in ax,dx
mov [bx],ax
add bx,
loop .go_on_read
ret times -($-$$) db
db 0x55,0xaa

  boot.inc如下:

LOADER_BASE_ADDR equ 0x900
LOADER_START_SECTOR equ 0x2

    编写好代码之后,编译:

nasm -o mbr.bin mbr.S

  再写进虚拟硬盘里面:

dd if=mbr.bin of=hd60M.img bs=512 count=1 conv=notrunc  

  不过,我们的工作还没完成,既然要读硬盘,那硬盘里面除了MBR外还要放点什么才行,因此,我们编写一个简单的内核加载器,从硬盘加载到内存后,再让MBR程序调到这个内核加载器即可,loader.S代码如下:

%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR
mov byte [gs:0x00], ''
mov byte [gs:0x01], 0xA4
mov byte [gs:0x02], ' '
mov byte [gs:0x03], 0xA4
mov byte [gs:0x04], 'L'
mov byte [gs:0x05], 0xA4
mov byte [gs:0x06], 'O'
mov byte [gs:0x07], 0xA4
mov byte [gs:0x08], 'A'
mov byte [gs:0x09], 0xA4
mov byte [gs:0x0a], 'D'
mov byte [gs:0x0b], 0xA4
mov byte [gs:0x0c], 'E'
mov byte [gs:0x0d], 0xA4
mov byte [gs:0x0e], 'R'
mov byte [gs:0x0f], 0xA4
jmp $

  编译代码:

nasm -o loader.bin loader.S

  写进硬盘里面,seek=2代表写进扇区2里面,和mbr程序隔开存储:

dd if=loader.bin of=hd60M.img bs= count= seek= conv=notrunc

   最后,我们再运行bochs,然后屏幕就会显示'2 LOADER',离进入保护模式又近了一大步,下一节将讲述保护模式!!!!

《操作系统真象还原》MBR的更多相关文章

  1. 《操作系统真象还原》ELF文件

    下面是第五章部分内容的收获. 用C语言编写内核 一直以来我们都是用汇编语言编写程序的,但接下来我们或许很少用汇编语言编写代码了,大多数都是使用C语言.为什么要这样呢?书上的解释我看的不是很懂,只能结合 ...

  2. 《操作系统真象还原》BIOS

    以下是读本书第二章的收获. 记得我大学学习操作系统的时候会遇到一些奇奇怪怪的问题,因为觉得问题太奇怪了,所以羞于问老师.诸如ROM到底是个什么东西:如果用内存映射的方式访问外部设备,是不是内存条里专门 ...

  3. 《操作系统真象还原》bochs安装

    在安装bochs之前,我们先需要安装虚拟机和linux发行版,也可以安装双系统,总之有个linux操作系统就好. 我是在ubuntu14.04系统下安装bochs的. 安装Bochs 以下为安装步骤 ...

  4. Linux 系统下使用dd命令备份还原MBR主引导记录

    https://en.wikipedia.org/wiki/Master_boot_recordhttps://www.cyberciti.biz/faq/howto-copy-mbr/https:/ ...

  5. Kernel pwn 基础教程之 ret2usr 与 bypass_smep

    一.前言 在我们的pwn学习过程中,能够很明显的感觉到开发人员们为了阻止某些利用手段而增加的保护机制,往往这些保护机制又会引发出新的bypass技巧,像是我们非常熟悉的Shellcode与NX,NX与 ...

  6. MBR分区表的备份与还原

    MBR分区表的备份与还原 MBR分区的存储 从下图可以看出,MBR分区前446字节是boot loader:接下来64字节是分区表:再然后就是三个主分区加一个拓展分区. 一.备份分区表,要跳过前446 ...

  7. BIOS+MBR操作系统引导方式

    1. 主引导记录(Master Boot Record,缩写:MBR) 主引导记录又叫做主引导扇区,是计算机开机后启动操作系统时所必须要读取的硬盘首个扇区,它在硬盘上的三维地址为(柱面,磁头,扇区)= ...

  8. 磁盘,fdisk分区,MBR,dd命令

    光盘和磁盘.u盘.软盘.硬盘有什么区别 ①光盘: cdrom/dvdrom:光驱(光盘驱动器)    rom:只读    ram:可以擦写    cd:700M    dvd:4G ②软盘:flopp ...

  9. 【自制操作系统06】终于开始用 C 语言了,第一行内核代码!

    一.整理下到目前为止的流程图 写到这,终于才把一些苦力活都干完了,也终于到了我们的内核代码部分,也终于开始第一次用 c 语言写代码了!为了这个阶段性的胜利,以及更好地进入内核部分,下图贴一张到目前为止 ...

随机推荐

  1. Go语言实现:【剑指offer】重建二叉树

    该题目来源于牛客网<剑指offer>专题. 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4 ...

  2. error C2662

    原因:关于const的问题 具体错误:函数的参数列表中参数签名为const,但是却调用了该参数的非const的成员函数 例子: 即使我们知道NoConst()并不会改变类的data成员,编译器依旧会报 ...

  3. 安装symfony3.4的坑,也是PHP7.3的经典坑之解决办法

    对于刚入手symfony3.4的同学,肯定会发现,安装symfony后部署后看到的往往不是hello world,也不是symfony的欢迎页面,而是给你一个下马威,唉,给你来个bug开开胃. 当然这 ...

  4. Unity酱~ 卡通渲染技术分析(一)

    前面的话 unitychan是日本unity官方团队提供的一个Demo,里面有很好的卡通渲染效果,值得参考学习 上图是我整理出来的shader结构,可以看到Unity娘被拆分成了很多个小的部件,我想主 ...

  5. one-hot编码(pytorch实现)

    n = 5 #类别数 indices = torch.randint(0, n, size=(15,15)) #生成数组元素0~5的二维数组(15*15) one_hot = torch.nn.fun ...

  6. pytorch之 RNN regression

    关于RNN模型参数的解释,可以参看RNN参数解释 1 import torch from torch import nn import numpy as np import matplotlib.py ...

  7. mongo操作备忘

    #查看collection内 某个字段条目数 db.dictionary_system.find({"name":"xxx"}).count() #清空某个co ...

  8. k8s系列---存储卷pv/pvc。configMap/secert

    因为pod是有生命周期的,pod一重启,里面的数据就没了.所以我们需要数据持久化存储. 在k8s中,存储卷不属于容器,而是属于pod.也就是说同一个pod中的容器可以共享一个存储卷. 存储卷可以是宿主 ...

  9. 死磕Lambda表达式(一):初识Lambda

    弱小和无知不是生存的障碍,傲慢才是.--<三体> 什么是Lambda表达式 Lambda表达式是表示可传递匿名函数的一种简洁方式,Lambda表达式没有名称,但是有参数列表.函数主体.返回 ...

  10. 来简单说说var,let,const,function,import,class

    一.var和let var已经在JavaScript中存在很长一段时间了,但是它存在了一些不足的地方,接下来我们就来看看吧 首先var存在变量提升,这是怎么一回事呢,我们看下面代码 为什么是它呢,是因 ...