第12课 – 实模式到保护模式(下)

不一般的jmp(s16->s32)
    在16位代码中,所有的立即数默认为16位
    从16位代码段跳转到32位代码段时,必须做强制转换
      

深入保护模式:定义显存段

    为了显示数据,必须存在两大硬件:显卡 + 显示器
        显卡
            为显示器提供需要显示的数据
            控制显示器的模式和状态
        显示器
            将目标数据以可见的方式呈现在屏幕上

显存的概念和意义

    显卡拥有自己内部的数据存储器,简称显存

显卡的工作模式:文本模式&图形模式

    在不同的模式下,显卡对显存内容的解释是不同的
    可以使用专属指令或int 0x10中断改变显卡工作模式
    在文本模式下:
        显存的地址范围映射为:[0xB8000, 0xBFFFF]    // 第一课有介绍
        一屏幕可以显示25行,每行80个字符

显卡的文本模式原理

    

文本模式下显示字符

    

小目标

    在保护模式下,打印指定内存中的字符串
        定义全局段(.gs),用于保护模式下的函数调用
        定义全局数据段(.dat),用于定义只读数据(D.T.OS!)
        利用对显存段的操作定义字符串打印函数(PrintString)

打印函数(PrintString)的设计

  

汇编小贴士

    32位保护模式下的乘法操作(mul)
        被乘数放到AX寄存器
        乘数放到通用寄存器或内存单元(16位)
        相乘的结果放到EAX寄存器中
    再论$和$$
        $表示当前行相对于代码起始位置的偏移量
        $$表示当前代码节(section)的起始位置
        小结

    实模式下可以使用32位寄存器和32位地址
    显存是显卡内部的存储单元,本质上与普通内存无差别
    显卡有两种工作模式:文本模式&图形模式
    文本模式下操作显存单元中的数据能够立即反映到显示器

代码

复制代码
  1 // loader.asm
  2 // 打印字符'P'
  3 [section .gdt] ; 全局描述符表
  4 ; GDT definition
  5 ; 段基址 段界限 段属性
  6 GDT_ENTRY    : Descriptor 0,          0,                   0
  7 CODE32_DESC  : Descriptor 0,          Code32SegLen - 1,    DA_C + DA_32
  8 VIDEO_DESC   : Descriptor 0xB8000,    0x07FFF,             DA_DRWA + DA_32
  9 ; FDT end
 10 
 11 ; GDT Selector
 12 Code32Selector equ (0x0001 << 3) + SA_TIG + SA_RPL0 ; 0x0001==第二个选择子
 13 VideoSelector  equ (0x0002 << 3) + SA_TIG + SA_RPL0
 14 ; end of [section .gdt]
 15 
 16 [section .s32] ; 32位代码段
 17 [bits 32] ; 使用32位编译
 18 CODE32_SEGMENT: ; 32位代码段数据
 19 mov eax, 0
 20 mov ax, VideoSelector            ; 把视频段放到gs寄存器
 21 mov gs, ax
 22 mov edi, (80 * 12 + 37) * 2      ; 每行80个字符,每个字符占2字节(字符+属性);当前要放到12行37列
 23 mov ah, 0x0c                     ; 输出字符为红色
 24 mov al, 'P'                      ; 输出字符P
 25 mov [gs:edi], ax                 ; 往gs寄存器指定位置写入显示信息
 26 jmp $
 27 
 28 // loader.asm
 29 // 打印字符串
 30 %include "inc.asm"
 31 
 32 org 0x9000
 33 
 34 jmp CODE16_SEGMENT
 35 
 36 [section .gdt]    ; 全局描述符表,部分段基址暂未知地址需使用时再调节
 37 ; GDT definition
 38 ;                                    段基址        段界限                    段属性
 39 GDT_ENTRY         :        Descriptor    0,            0,                        0
 40 CODE32_DESC       :        Descriptor    0,            Code32SegLen - 1,        DA_C    + DA_32
 41 VIDEO_DESC        :        Descriptor    0xB8000,      0x07FFF,                 DA_DRWA + DA_32
 42 DATA32_DESC       :        Descriptor    0,            Data32SegLen - 1,        DA_DR   + DA_32
 43 STACK_DESC        :        Descriptor    0,            TopOfStackInit,          DA_DRW  + DA_32
 44 ; GDT end
 45 
 46 GdtLen    equ    $ - GDT_ENTRY
 47 
 48 GdtPtr:                    ; 全局描述符表指针
 49         dw    GdtLen - 1   ; 偏移,记录描述符数量
 50         dd    0            ; 全局描述符起始地址,先初始化为0
 51 
 52 
 53 ; GDT Selector
 54 ;                        TI:描述符作用域1bit    RPL:请求权限级别2bit
 55 Code32Selector   equ (0x0001 << 3) + SA_TIG + SA_RPL0    ; 0x0001==第二个选择子
 56 VideoSelector    equ (0x0002 << 3) + SA_TIG + SA_RPL0
 57 Data32Selector   equ (0x0003 << 3) + SA_TIG + SA_RPL0
 58 StackSelector    equ (0x0004 << 3) + SA_TIG + SA_RPL0
 59 
 60 ; end of [section .gdt]
 61 
 62 TopOfStackInit equ 0x7c00
 63 
 64 [section .dat]                        ; 32位数据段
 65 [bits 32]
 66 DATA32_SEGMENT:
 67     DTOS           db    "D.T.OS!", 0            ; 注意添加字符串结束标记0
 68     DTOS_OFFSET          equ    DTOS - $$
 69     HELLO_WORLD    db    "Hello    World!", 0
 70     HELLO_WORLD_OFFSET   equ    HELLO_WORLD - $$
 71 
 72 Data32SegLen equ $ - DATA32_SEGMENT
 73 
 74 [section .s16]    ; 实模式代码段(16bit)
 75 [bits 16]         ; 使用16位编译
 76 CODE16_SEGMENT:
 77     mov ax, cs    ; 初始化相关寄存器
 78     mov ds, ax
 79     mov es, ax
 80     mov ss, ax
 81     mov sp, TopOfStackInit
 82 
 83     ; initialize GDT for 32 bits code segment
 84     mov esi, CODE32_SEGMENT
 85     mov edi, CODE32_DESC
 86 
 87     call InitDescItem
 88         
 89     mov esi, DATA32_SEGMENT
 90     mov edi, DATA32_DESC
 91 
 92     call InitDescItem
 93 
 94     ; initialize GDT pointer struct
 95     mov eax, 0                         ; 代码段地址左移4位
 96     mov ax, ds
 97     shl eax, 4
 98     add eax, GDT_ENTRY                 ; 代码段偏移地址==> 左移过后的代码段+全局描述符表入口地址偏移量
 99     mov dword [GdtPtr + 2], eax        ; 写入全局描述符表指针
100 
101     ; 1. load GDT
102     lgdt [GdtPtr]                      ; 加载全局描述符表
103 
104     ; 2. close interrupt
105     cli                                ; 关闭中断
106 
107     ; 3. open A20
108     in al, 0x92                        ; 通过0x92端口开启A20地址线开关
109     or al, 00000010b
110     out 0x92, al
111 
112     ; 4. enter protect mode
113     mov eax, cr0                       ; 设置cr0寄存器,进入保护模式
114     or eax, 0x01
115     mov cr0, eax
116 
117     ; 5. jump to 32 bits code
118     jmp dword Code32Selector : 0       ; 使用jmp跳转到32位代码段选择子的0偏移处
119 
120 
121 ; esi    --> code segment label
122 ; edi    --> descriptor label
123 InitDescItem:                         ; 初始化描述符项目
124     push eax
125     
126     mov eax, 0                        ; 代码段地址左移4位
127     mov ax, cs
128     shl eax, 4                        ; 实地址=段寄存器地址左移4位+偏移地址
129     add eax, esi
130     mov word [edi + 2], ax            ; 将段基址写入描述符2个字节(16位寄存器),低32位的16-31bit(偏移2字节)
131     shr eax, 16                       ; 移除eax实地址中已经写入段基址的2字节数据
132     mov byte [edi + 4], al            ; 将段基址写入描述符1个字节(8位寄存器),高32位的0-7bit(偏移4+0=4字节)
133     mov byte [edi + 7], ah            ; 将段基址写入描述符1个字节(8位寄存器),高32位的24-31bit(偏移4+3=7字节)
134 
135     pop eax
136     
137     ret
138 
139 
140 [section .s32]    ; 32位代码段
141 [bits 32]        ; 使用32位编译
142 CODE32_SEGMENT:    ; 32位代码段数据
143     mov ax, VideoSelector             ; 把视频段选择子放到gs全局段寄存器
144     mov gs, ax
145 
146     mov ax, StackSelector             ; 32位保护模式栈段
147     mov ss, ax
148 
149     mov ax, Data32Selector
150     mov ds, ax
151 
152     mov ebp, DTOS_OFFSET              ; 32位模式下用段内偏移地址
153     mov bx, 0x0C                      ; 黑底淡红字
154     mov dh, 12                        ; 指定行地址,注意行列都是从0开始
155     mov dl, 33                        ; 指定列地址
156 
157     call PrintString
158 
159     mov ebp, HELLO_WORLD_OFFSET       ; 32位保护模式下字符串偏移地址
160     mov bx, 0x0C
161     mov dh, 13
162     mov dl, 31
163 
164     call PrintString                  ; 32位保护模式打印字符串
165 
166 
167     jmp $
168 
169 ; ds:ebp    --> string address
170 ; bx        --> attribute
171 ; dx        --> dh : row, dl : col
172 PrintString:                          ; 打印字符串函数
173     push ebp
174     push eax
175     push edi
176     push cx
177     push dx
178 print:
179     mov cl, [ds:ebp]                  ; cl记录要打印的字符
180     cmp cl, 0                         ; 对比字符串结束符号
181     je end
182     mov eax, 80                       ; 每行字符数
183     mul dh                            ; 乘以行数
184     add al, dl                        ; 加上列数,最终计算出要显示的位置
185     shl eax, 1                        ; 左移乘以2,计算字节偏移
186     mov edi, eax                      ; 写入显示的偏移地址到edi
187     mov ah, bl                        ; 字符属性写入高位
188     mov al, cl                        ; 字符写入低位
189     mov [gs:edi], ax                  ; 写入显存对应地址
190     inc ebp                           ; 指向下一个字符
191     inc dl                            ; 指向屏幕的下一列
192     jmp print
193 
194 end:
195     pop dx
196     pop cx
197     pop edi
198     pop eax
199     pop ebp
200     ret
201 
202 Code32SegLen    equ    $ - CODE32_SEGMENT
复制代码

输出

  在显示器的字符模式下12行37列打印字符P为淡红色(注意行列都从0开始)
       

    保护模式输出字符串
    

Nim 游戏 -(拿石头游戏)

Nim游戏的规则是:

1. 有N个石头(肯定大于0否则就没法玩了)

2. 有两个玩家(因为就你们两个好基友)

3. 自己先下手(够腹黑的,嘿嘿)

4. 每次必须拿1-3个石头(不能太贪婪)

5. 最后拿完的算赢(全部都归最后拿完的那个人,如果石头是钻石的话……,这结果会不会导致基友感情破裂?)

 

通过规则发现了一些诀窍:

1. 如果轮到自己的时候余数为1-3,也就是自己能拿完的时候就赢了;

2. 如果留给对方的是4个,无论对方怎么拿最终留给自己的总是能一次拿完;相反,你要是得到4个,那你就输了;

3. 作为腹黑的你肯定要想方设法让对方留给你1-3个,或者尽可能的留给对方4个;

4. 只要留给自己的不是4的整数倍,自己就能赢(拿完者赢),因为只要自己不是4的整数倍,自己就能让对方一直变成4的整数倍

 

所以方案就是:

自己要尽可能的让对方拿石子之前变成4的整数倍,而自己决不能出现这种情况;

本着双方都是有着爱因斯坦般聪明无比的原则,显然轮到自己时剩余量为4的整数倍自己就输了;

 

代码

1 return ((n % 4) != 0);      // 如果除以4求余结果不是0说明不是4的整数倍
2 return ((n & 0b11)!=0);      // 0b11是二进制3的意思,这里是位与,相当于保留最后两个比特,如果结果不是0说明不是4的整数倍
3 return ((n >> 2 << 2) != n);   // n右移两位再左移两位如果不能和原来的数相同,说明右移丢失的2个比特不是0,这也证明了不是4的整数倍

第11课 – 实模式到保护模式(中)

80286的光荣退场
    历史意义
        引入了保护模式,为现代操作系统和应用程序奠定了基础
    奇葩设计
        段寄存器为24位,通用寄存器为16位(不伦不类)
            理论上,段寄存器中的数值可以直接作为段基址
            16位通用寄存器最多访问64K的内存
            为了访问16M内存,必须不停切换段基址

80386的登场(计算机新时期的标志)

     
    32位地址总线(可支持4G的内存空间)
    段寄存器和通用寄存器都是32位
        任何一个寄存器都能访问到内存的任意角落
            开启了平坦内存模式的新时代
            段基址为0,使用通用寄存器访问4G空间

新时期的内存使用方式

    实模式
        兼容8086的内存使用方式
    分段模式
        通过[段地址:偏移地址]的方式将内存从功能上分段(数据段,代码段)
    平坦模式
        所有内存就是一个段[0:32位偏移地址]

段属性定义

    段属性定义了段所属权限等
    

选择子属性定义

    

保护模式中的段定义

    

汇编小贴士

    section关键字用于“逻辑的”定义一段代码集合
    section定义的代码段不同于[段地址:偏移地址]的代码段
        section定义的代码段仅限于源码中的代码段(代码节)
        [段地址:偏移地址]的代码段指的是内存中的代码段
        下面这幅图两个段使用了同一个名字,所以它们属于一个段,两段数据组合为0x01020000,由于采用4字节对齐,所以后面要补零
        
    [bits 16]    用于指示编译器将代码按照16位方式进行编译
    [bits 32]    用于指示编译器将代码按照32位方式进行编译

注意事项

    段描述表中的第0个描述符不使用(仅用于占位)
    代码中必须显示的指明16位代码段和32位代码段
    必须使用jmp指令从16位代码段跳转到32位代码段

问题

    为什么不直接使用标签定义描述符中的段基地址?
        NASM将汇编文件当成一个独立的代码段编译
        汇编代码中的标签(Label)代表的是段内偏移地址
        实模式下需要配合段寄存器的值计算标签的物理地址
    为什么16位代码段到32位代码段必须无条件跳转?
        流水线技术
            处理器为了提高效率将当前指令和后续指令预取到流水线
            可能同时预取指令中含有16位和32位代码
            为了避免将混合执行不同位数的代码
            无条件跳转jmp能强制刷新流水线

小结

    80386处理器是计算机发展史上的里程碑
    32位的寄存器和地址总线能够直接访问4G内存
    需要在16位实模式中对GDT中的数据进行初始化
    代码中需要为GDT定义一个标识数据结构(GdtPtr
    需要使用jmp指令从16位代码跳转到32位代码

代码

复制代码
  1 // inc.asm
  2 
  3 ; Segment Attribute    ; 一致性是指高特权不能访问低特权,但是低特权能访问高特权,不过特权级别不变
  4 DA_32  equ    0x4000    ; 保护模式32位段
  5 DA_DR    equ    0x90    ; 只读数据段
  6 DA_DRW    equ 0x92    ; 可读写数据段
  7 DA_DRWA    equ 0x93    ; 已访问可读写数据段
  8 DA_C    equ    0x98    ; 只执行代码段
  9 DA_CR    equ    0x9A    ; 可执行可读代码段
 10 DA_CCO    equ    0x9C    ; 只执行一致代码段
 11 DA_CCOR    equ    0x9E    ; 可执行可读一致代码段
 12 
 13 ; Selector Attribute
 14 SA_RPL0    equ    0        ; 高特权,内核级
 15 SA_RPL1    equ    1        ; 中高特权,服务级
 16 SA_RPL2    equ    2        ; 中低特权,服务级
 17 SA_RPL3    equ    3        ; 低特权,用户级
 18 
 19 SA_TIG    equ    0        ; GDT
 20 SA_TIL    equ    4        ; LDT
 21 
 22 ; 描述符
 23 ; usage: Descriptor Base, Limit, Attr
 24 ;         Base: dd
 25 ;         Limit:dd (low 20 bits available)
 26 ;         Attr: dw (lower 4 bits of higher byte are always 0)
 27 %macro Descriptor 3                            ; 段基址,段界限,段属性
 28     dw    %2 & 0xFFFF                            ; 段界限1
 29     dw    %1 & 0xFFFF                            ; 段基址1
 30     db    (%1 >> 16) & 0xFF                    ; 段基址2
 31     dw    ((%2 >> 8) & 0xF00) | (%3 & 0xF0FF)    ; 属性1 + 段界限2 + 属性2
 32     db    (%1 >> 24) & 0xFF                    ; 段基址3
 33 %endmacro                                    ; 共8字节
 34 
 35 
 36 
 37 
 38 // loader.asm
 39 %include "inc.asm"
 40 
 41 org 0x9000
 42 
 43 jmp CODE16_SEGMENT
 44 
 45 [section .gdt]    ; 全局描述符表
 46 ; GDT definition
 47 ;                                    段基址    段界限                    段属性
 48 GDT_ENTRY        :        Descriptor    0,        0,                        0
 49 CODE32_DESC        :        Descriptor    0,        Code32SegLen - 1,        DA_C + DA_32
 50 ; FDT end
 51 
 52 GdtLen    equ    $ - GDT_ENTRY
 53 
 54 GdtPtr:                    ; 全局描述符表指针
 55         dw    GdtLen - 1    ; 偏移,记录描述符数量
 56         dd    0            ; 全局描述符起始地址,先初始化为0
 57 
 58 
 59 ; GDT Selector
 60 
 61 Code32Selector    equ    (0x0001 << 3) + SA_TIG + SA_RPL0    ; 0x0001==第二个选择子
 62 
 63 ; end of [section .gdt]
 64 
 65 
 66 [section .s16]    ; 实模式代码段(16bit)
 67 [bits 16]        ; 使用16位编译
 68 CODE16_SEGMENT:
 69     mov ax, cs    ; 初始化相关寄存器
 70     mov ds, ax
 71     mov es, ax
 72     mov ss, ax
 73     mov sp, 0x7c00
 74 
 75     ; initialize GDT for 32 bits code segment
 76     mov eax, 0                        ; eax清0
 77     mov ax, cs                        ; cs寄存器放入ax寄存器
 78     shl eax, 4                        ; 左移4位
 79     add eax, CODE32_SEGMENT            ; 32位段基址 ==> cs代码段左移4位加上32位段的偏移
 80     mov word [CODE32_DESC + 2], ax    ; 将ax寄存器的32位段基址写入描述符的低32位的16-31bit(2字节处)
 81     shr eax, 16                        ; 将eax右移2字节(上面已经写入了2字节)
 82     mov byte [CODE32_DESC + 4], al    ; 将al寄存器的32位段基址写入描述符的高32位的0-7bit(4字节处)
 83     mov byte [CODE32_DESC + 7], ah    ; 将ah寄存器的32位段基址写入描述符的高32位的24-31bit(7字节处)
 84 
 85     ; initialize GDT pointer struct
 86     mov eax, 0                        ; 代码段地址左移4位
 87     mov ax, ds
 88     shl eax, 4
 89     add eax, GDT_ENTRY                ; 代码段偏移地址==> 左移过后的代码段+全局描述符表入口地址偏移量
 90     mov dword [GdtPtr + 2], eax        ; 写入全局描述符表指针
 91 
 92     ; 1. load GDT
 93     lgdt [GdtPtr]                    ; 加载全局描述符表
 94 
 95     ; 2. close interrupt
 96     cli                                ; 关闭中断
 97 
 98     ; 3. open A20
 99     in al, 0x92                        ; 通过0x92端口开启A20地址线开关
100     or al, 00000010b
101     out 0x92, al
102 
103     ; 4. enter protect mode
104     mov eax, cr0                    ; 设置cr0寄存器,进入保护模式
105     or eax, 0x01
106     mov cr0, eax
107 
108     ; 5. jump to 32 bits code
109     jmp dword Code32Selector : 0    ; 使用jmp跳转到32位代码段选择子的0偏移处
110 
111 [section .s32]    ; 32位代码段
112 [bits 32]        ; 使用32位编译
113 CODE32_SEGMENT:    ; 32位代码段数据
114     mov eax, 0
115     jmp CODE32_SEGMENT
116 
117 Code32SegLen    equ    $ - CODE32_SEGMENT
118 
119 
120 
121 // makefile
122 
123 .PHONY : all clean rebuild
124 
125 BOOT_SRC := boot.asm
126 BOOT_OUT := boot.bin
127 
128 LOADER_SRC  := loader.asm
129 INCLUDE_SRC := inc.asm
130 LOADER_OUT  := loader
131 
132 IMG := data.img
133 IMG_PATH := /mnt/hgfs
134 
135 RM := rm -rf
136 
137 all : $(IMG) $(BOOT_OUT) $(LOADER_OUT)
138     @echo "Build Success ==> D.T.OS!"
139 
140 $(IMG) :
141     # bximage $@ -q -fd -size=1.44
142     bximage -mode=create -q -fd=1.44M $@
143 
144 $(BOOT_OUT) : $(BOOT_SRC)
145     nasm $^ -o $@
146     dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
147 
148 $(LOADER_OUT) : $(LOADER_SRC) $(INCLUDE_SRC)
149     nasm $< -o $@
150     sudo mount -o loop $(IMG) $(IMG_PATH)
151     sudo cp $@ $(IMG_PATH)/$@
152     sudo umount $(IMG_PATH)
153 
154 clean :
155     $(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
156 
157 rebuild :
158     @$(MAKE) clean
159     @$(MAKE) all
复制代码

第10课 – 实模式到保护模式(上)

第10课 – 实模式到保护模式(上)

从计算机的历史谈起

    以前的程序开发是直接操作物理内存的
    CPU指令的操作数直接使用实地址(实际内存地址)
    任何一个程序员都拥有全部内存操作权力

拥有全部内存操作权力带来的问题

    难以重定位
        程序必须固定在内存的某个位置,因为加载地址必须明确指定
    给多个程序设计带来问题
        只要两个程序碰到内存重叠,程序就不能运行

CPU历史的里程碑-8086

      

    地址线宽度为20位,可访问1M内存空间
    引入[段地址偏移地址]的内存访问形式
        8086的段寄存器和通用寄存器为16位
        单个寄存器寻址最多访问64k的内存空间
        需要两个寄存器配合来完成所有内存空间的访问

深入解析[段地址:偏移地址]

    硬件所做的工作
        段地址左移4位,构成20位的基地址(起始地址)
        基地址+偏移地址=实地址
    对于开发者的意义
        更有效的划分内存的功能(数据段,代码段,等)
        当出现程序地址冲突时,通过修改段地址解决冲突

示例

    mov ax, [0x1234]        ; 实地址 ==> (ds << 4) + 0x1234
    mov ax, [es:0x1234]    ; 实地址 ==> (es << 4) + 0x1234

有趣的问题

    [段地址:偏移地址]能访问的最大地址为0xFFFF:0xFFFF,
    即:0x10FFEF;超过了1MB(0xFFFFF)的空间,CPU如何处理?

8086中的高端地址区(High Memory Area)

    0xFFFF:0xFFFF
        0xFFFF0+0xFFFF
            0xFFFF0+(0xF+0xFFF0)
                (0xFFFF0+0xF)+0xFFF0
                    0xFFFFF+0xFFF0(HMA)

8086的处理方式

    由于8086只有20位地址线,因此最高位被丢弃(溢出),对内存地址的访问相当于取模1MB
    0xFFFF:0xFFFF
        100001111111111101111(0x10FFEF)
            回卷0xFFEF(舍弃最前面的1)

再谈8086历史

    8086在当时是非常成功的一款产品
    因此拥有一大批的开发者和应用程序
    各种基于8086程序设计的技术得到了发展
    不幸的是,各种奇技淫巧也应运而生

8086时期应用程序中的问题

    1M内存完全不够用
    开发者在程序中大量使用内存回卷技术
    应用程序之间没有界限,相互之间随意干扰
        A程序可以随意访问B程序的数据
        C程序可以修改系统调度程序的指令

80286的登场

    
    8086已经有那么多应用程序了,所以新CPU必须向下兼容
    加大内存容量,增加地址线数量(24位,16M ==> 0xFFFFFF)
    [段地址:偏移地址]的方式可以强化一下
        为每个段提供更多属性(如:范围,特权级,等)
        为每个段的定义提供固定方式

80286的兼容模式

    默认情况下完全兼容8086的运行模式(实模式)
        默认可直接访问1MB的内存空间
        通过特殊的方式访问1MB+的内存空间

80286之后的工作模式

    

初识保护模式

    每一段内存都拥有一个属性定义(描述符Descriptor)
    所有段的属性定义构成一张表(描述符表Descriptor Table)
    段寄存器保存的是属性定义在表中的索引(选择子Selector)

描述符(Descriptor)的内存结构

    
    因为一些历史原因,段基址被分成了几段

    GDT每个位置的意义如下:(引用于:https://www.cnblogs.com/Philip-Tell-Truth/p/5211248.html

  G位(Granularity,粒度)
    这个位用于解释界限的含义,当G=0,段界限是以字节为单位的,这个时候,段的拓展范围是1B-1MB(段描述符的段界限是20位的);如果G=1,则段界限是以4KB为单位的,范围4KB-4GB。
  D/B位(Default Operation Size,默认的操作数大小)
    对于代码段,当D=0表示指令的偏移地址或者操作数是16位的;D=1表示偏移地址或者操作数是32位的.
    对于栈段,当D=0,表示使用SP寄存器,栈段上界是0xFFFF;当D=1,表示使用ESP寄存器,栈段上界是0xFFFFFFFF。
  L位(64-bit Code Segement)
    这个位用于保留给64位处理器使用,当L=0,表示是32位处理器,当L=1,表示是64位处理器。
  AVL位(Available)
    通常由操作系统用,处理器并不会使用它。
  P位(Segement Present,存在位)
    P位用于描述描述符对应的段是否存在,一般来说,描述符所指示的段都是存在于内存中的,但是,当内存空间紧张的时候,有可能只是建立了描述符,对应的内存空间并不存在,这个时候就应该把P位清零。表示段并不 存在,另外,同样是在内从空间紧张的情况下,会把很少用到的段换出到硬盘中,腾出空间给当前急需内存的程序使用(当前正在执行的),这时,同样要把段的描述符P位清零,当再次轮到它执行时,P=1.
    P位是处理器负责检查的,每当通过描述符访问内存的段中时,如果P位是0,则处理器会产生一个异常中断,通常,这个中断处理过程是操作系统提供的。该处理过程的任务是负责将该段从硬盘中换回内存,并将P=1。在多用户,多任务的系统中,这是一种常用的虚拟内存调度策略。
  DPL位(Descriptor Privilege Level,DPL特权级)
    32位处理器的DPL位有四种,分别是0,1,2,3(就是特权级0123),不同特权级的程序是相互隔离的,其访问是严格限制的,而且有些处理器指令(特权指令)只能由0特权级的程序来执行。在这里,DPL指的是访问该段所必须拥有的最低特权级,如果这里的数值是2,那么特权级0,1,2可以访问这个段,特权级3访问这个段会被处理器阻止。
  S位(Descriptor Type,描述符类型)
    当S=0,说明这是一个系统段,当S=1,说明这是一个代码段或者是数据段(栈段是特殊的数据段)。
  TYPE位(描述符类别)
        
    X表示是否可执行(eXecutable)。数据段总是不可执行的,X=0,代码段可执行,X=1。
    E是对数据段而言,指示段的扩展方向,E=0表示向上拓展,E=1表示向下拓展。
    W段指示读写属性,W=0指示不可写入,否则会引发处理器异常中断,W=1表示允许写入。
    对代码段而言,C表示是否特权级依从(Conforming),C=0表示非依从的代码段,这样的代码段可以和他特权级相同的代码段调用。或者通过们调用,C=1表示允许从低特权级的程序转移到该段执行。代码段总是可以    执行,但是总是不允许被修改(如果要修改代码段,可以指定一个可以读写的数据段指向这个代码段)。至于能不能读出,由R位决定,R=0表示不能读出,R=1表示可以读出。(相当于一个ROM)(R位不是指示处理器能否读取指令的,而是限制程序和指令的行为,比如使用超越前缀CS:访问代码段的内容)。
    A位是已访问位(Assessed),指示这个段最近有没有被访问过,在描述符创建后,这个位置应该被置零。之后,每当这个段被访问的时候,处理器都会将这个位变成1,对这个位清零是操作系统做的,通过定期监视该段  的位置,可以统计出该段的使用频率,当内存空间紧张的时候,可以不经常用的段退到硬盘上,从而实现虚拟内存管理。

描述符表(Descriptor Table)

    

选择子(Selector)的结构

    

进入保护模式的方法

    定义描述符表
    打开A20地址线
    加载描述符表
    通知CPU进入保护模式

小结

    [段地址:偏移地址]的寻址方式解决了早期程序重定位的难题
    8086实模式下的程序无法保证安全性
    80286提出了保护模式的概念,加强了内存段的安全性
    处于兼容性的考虑,80286之后的处理器都有2中保护模式
    处理器需要特定的设置步骤才能进入保护模式,默认为实模式

第9课 – 主引导程序控制权的转移

BootLoader内存布局
    

通过FAT表加载文件内容

     

实验步骤

    在虚拟软盘中创建体积较大的文本文件(Loader)
    将Loader的内容加载到BaseOfLoader地址处
    打印Loader中的文本(判断加载是否完全)

第一个Loader程序

    起始地址0x9000(org 0x9000)
    通过int 0x10在屏幕上打印字符串
     

汇编小提示:标志寄存器

    
    jxx代表了一个指令族,功能是根据标志位进行调整
        jo当OF为1则跳转
        jc当CF为1则跳转
        jns当SF不为1则跳转
        jz当ZF为1则跳转
        je比较结果相等则跳转(即:jz)

小结

    Boot需要进行重构保证在512字节内完成功能
    在汇编程序中尽量确保函数调用前后通用寄存器的状态不变
    Boot成功加载Loader后将控制权转移
    Loader程序没有代码体积上的限制

代码

复制代码
 1 // makefile
 2 
 3 .PHONY : all clean rebuild
 4 
 5 BOOT_SRC := boot.asm
 6 BOOT_OUT := boot.bin
 7 
 8 LOADER_SRC := loader.asm
 9 LOADER_OUT := loader
10 
11 IMG := data.img
12 IMG_PATH := /mnt/hgfs
13 
14 RM := rm -rf
15 
16 all : $(IMG) $(BOOT_OUT) $(LOADER_OUT)
17     @echo "Build Success ==> D.T.OS!"
18 
19 $(IMG) :
20     # bximage $@ -q -fd -size=1.44
21     bximage -mode=create -q -fd=1.44M $@
22 
23 $(BOOT_OUT) : $(BOOT_SRC)
24     nasm $^ -o $@
25     dd if=$@ of=$(IMG) bs=512 count=1 conv=notrunc
26 
27 $(LOADER_OUT) : $(LOADER_SRC)
28     nasm $^ -o $@
29     sudo mount -o loop $(IMG) $(IMG_PATH)
30     sudo cp $@ $(IMG_PATH)/$@
31     sudo umount $(IMG_PATH)
32 
33 clean :
34     $(RM) $(IMG) $(BOOT_OUT) $(LOADER_OUT)
35 
36 rebuild :
37     @$(MAKE) clean
38     @$(MAKE) all
复制代码
复制代码
  1 // boot.asm
  2 org 0x7c00                                ; 从0x7c00处开始存储代码
  3 
  4 jmp short start                            ; BS_JmpBoot,短跳指令(3字节);jmp指令占用1字节,地址占用1字节;下面的nop空指令占用1字节;共3字节
  5 nop
  6 
  7 define:
  8     BaseOfStack     equ 0x7c00            ; 定义栈的起始地址,注意栈是从高到低增长的,先加后入栈,先出后减
  9     BaseOfLoader    equ 0x9000            ; 内存加载地址
 10     RootEntryOffset equ 19                ; 目录文件项从19逻辑扇区开始
 11     RootEntryLength equ 14                ; 目录文件项共14扇区
 12     EntryItemLength    equ 32                ; 根目录区每个目录项大小
 13     FatEntryOffset    equ 1                ; FAT表起始扇区(FAT表记录了文件簇对应的实际地址)
 14     FatEntryLength    equ 9                ; FAT表长度(扇区)
 15 
 16 
 17 ; ##############################################################################
 18 header:                                    ; MBR
 19     BS_OEMName        db "D.T.Soft"        ; OEM字符,8个,不足以空格填充
 20     BPB_BytsPerSec    dw 512                ; 每扇区字节数
 21     BPB_SecPerClus    db 1                ; 每簇占用扇区数
 22     BPB_RsvdSecCnt    dw 1                ; Boot占用的扇区数
 23     BPB_NumFATs        db 2                ; FAT表的记录数
 24     BPB_RootEntCnt    dw 224                ; 最大根目录文件数
 25     BPB_TotSec16    dw 2880                ; 逻辑扇区总数
 26     BPB_Media        db 0xF0                ; 媒体描述符
 27     BPB_FATSz16        dw 9                ; 每个FAT占用扇区数
 28     BPB_SecPerTrk    dw 18                ; 每个磁道扇区数
 29     BPB_NumHeads    dw 2                ; 磁头数
 30     BPB_HiddSec        dd 0                ; 隐藏扇区数
 31     BPB_TotSec32    dd 0                ; 如果BPB_TotSec16是0,则在这里记录扇区总数
 32     BS_DrvNum        db 0                ; 中断13的驱动器号
 33     BS_Reserved1    db 0                ; 未使用
 34     BS_BootSig        db 0x29                ; 扩展引导标志
 35     BS_VolID        dd 0                ; 卷序列号
 36     BS_VolLab        db "D.T.OS-0.01"    ; 卷标,必须11个字符,不足以空格填充
 37     BS_FileSysType    db "FAT12   "        ; 文件系统类型,必须使8个字符,不足填充空格
 38 
 39 
 40 ; ##############################################################################
 41 start:                                    ; 汇编起始标号,类似于main()函数,之前用jmp跳转到此
 42     mov ax, cs                            ; 设置相关的段寄存器
 43     mov ss, ax
 44     mov ds, ax
 45     mov es, ax
 46     mov sp, BaseOfStack                    ; 设置函数调用栈地址
 47 
 48     mov ax, RootEntryOffset                ; 记录目录文件项起始扇区
 49     mov cx, RootEntryLength                ; 记录目录文件项占用扇区数
 50     mov bx, Buf                            ; 读取的目录文件项存放处
 51 
 52     call ReadSector                        ;     读取指定扇区数据到Buf缓存
 53     mov si, Target                        ; 要查找的文件名
 54     mov cx, TarLen                        ; 文件名长度
 55     mov dx, 0                            ; 设置默认返回值为查找失败
 56 
 57     call FindEntry                        ;     查找文件,dx作为返回值
 58 
 59     cmp dx, 0                            ; 如果没有找到文件就输出提示,否則继续
 60     jz output
 61 
 62     mov si, bx                            ; 将读取的数据bx放到si,这里是将目录文件项在Buf的地址作为源
 63     mov di, EntryItem                    ; 存储根目录区的内存空间di
 64     mov cx, EntryItemLength                ; 根目录区的大小
 65 
 66     call MemCpy                            ;     调用内存拷贝,把目录文件项的第一个目录项从Buf拷贝到EntryItem
 67 
 68     mov ax, FatEntryLength                ; Fat表占用扇区数
 69     mov cx, [BPB_BytsPerSec]            ; 每扇区字节数
 70     mul cx                                ; mul乘法,al*cx,结果放到AX系列寄存器,此时ax等于Fat表占用字节数
 71     mov bx, BaseOfLoader                ; Fat表内存暂存区地址
 72     sub bx, ax                            ; 减法,将读取的数据放到Fat暂存区的前面,注意上面的乘法结果不能溢出,这里的AX没有溢出(9*512=4608,ax=16bit=65535)
 73 
 74     mov ax, FatEntryOffset                ; Fat1起始扇区
 75     mov cx, FatEntryLength                ; Fat表的长度
 76 
 77     call ReadSector                        ;     读取Fat表,放到bx指向的地址
 78 
 79     mov dx, [EntryItem + 0x1A]            ; FAT表内存地址加0x1A写入dx寄存器,即文件开始的簇号(DIR_FstClus)
 80 
 81     ;call FatVec                            ;    获取簇对应的地址
 82 
 83     ;jmp last
 84     mov si, BaseOfLoader                ; 数据加载区起始地址
 85 
 86 loading:
 87     mov ax, dx                            ; 将文件簇号放入ax,注意一个簇占用1扇区,否则需要转换成扇区数
 88     add ax, 31                            ; 31=33-2;将簇号加上数据区域的地址;数据起始于33扇区,且从2开始,前2个表项不使用
 89     mov cx, 1                            ; 读一个扇区
 90     push dx
 91     push bx
 92     mov bx, si                            ; 将数据存放地址放入bx
 93     call ReadSector                        ; 从ax读取cx大小到bx,读一个扇区的文件区数据到BaseOfLoader的前面
 94     pop bx
 95     pop cx                                ; pop cx ==> (pop dx;mov cx, dx;)
 96     call FatVec                            ; 返回下一个簇的编号;bx=FAT1表的内存地址,cx=文件开始簇号,dx=下一个簇号
 97     cmp dx, 0xFF7                        ; 是否为坏簇
 98     jnb BaseOfLoader                    ; 簇号如果不小于坏簇编号就跳转;cmp的第一个操作数不小于第二个操作数就跳转
 99     ;jnb output
100     add si, 512                            ; 指向下一个簇(当前一个簇占一个扇区,一个扇区512字节)
101     jmp loading                            ; 循环加载
102 
103 output:
104     mov bp, MsgStr
105     mov cx, MsgLen
106     ;mov bp, BaseOfLoader
107     ;mov cx, [EntryItem + 0x1C]            ; 输出长度为当前文件的大小,EntryItem=匹配的文件的文件项,0x1C=Dir_FileSize
108 
109     call Print                            ; 调用字符串打印函数
110 
111 last:                                    ; 死循环
112     hlt
113     jmp last
114 
115 
116 ; ##############################################################################
117 ; cx --> index
118 ; bx --> fat table address
119 ;
120 ; return:
121 ;    dx --> fat[index]
122 FatVec:                                    ; 获取簇对应的地址,bx=fat表的地址,cx文件起始簇号
123     mov ax, cx                            ; 下面是乘以1.5的相关操作(/2*3),因为2个簇占3字节,通过簇号的奇偶校验决定该簇在FAT表的偏移
124     mov cl, 2
125     div cl                                ; 文件簇编号除以2,商放al,余数放ah;余数为0时簇号为偶数,相对FAT表的偏移量采用低12bit(低1.5字节),否则采用高12bit(高1.5字节)
126 
127     push ax                                ; ah用于决定截取方式,al保留了除以2的整数值
128 
129     mov ah, 0                            ; ax的高位置0,相当于舍去余数保留商
130     mov cx, 3
131     mul cx                                ; ax乘以3
132     mov cx, ax                            ; cx=文件簇在FAT表的位置
133 
134     pop ax
135 
136     cmp ah, 0                            ; 对簇地址判断奇偶决定比特组合方式(前12比特还是后12比特)
137     jz even                                ; 如果余数是0就跳转到even截取前1.5字节
138     jmp odd                                ; 否则跳转到odd截取后1.5字节
139 
140 even:                                    ; 偶数:截取前1.5字节;fatVec[j] = ( (Fat[i+1] & 0x0f) << 8 ) | Fat[i];
141     mov dx, cx                            ; 文件簇偏移放入dx
142     add dx, 1                            ; 偏移到第二个字节,需要截取第二个字节
143     add dx, bx                            ; 文件簇索引+Fat表的地址(得到第二个字节的实际地址)
144     mov bp, dx                            ; 借助bp寄存器读取当前簇的第二个字节
145     mov dl, byte [bp]
146     and dl, 0x0F                        ; 保留低4位
147     shl dx, 8                            ; 左移8位使第二字节的低4bit放到dx的高位
148     add cx, bx                            ; 指向第一个字节
149     mov bp, cx
150     or  dl, byte [bp]                    ; 读取当前簇的第一个字节放到dx的低位,将2个字节组合起来
151     jmp return
152 
153 odd:                                    ; 奇数:截取后1.5字节;FatVec[j+1] = (Fat[i+2] << 4) | ( (Fat[i+1] >> 4) & 0x0F );
154     mov dx, cx
155     add dx, 2                            ; 偏移到第三个字节(最后一个字节)
156     add dx, bx                            ; 文件簇偏移量+Fat表的地址(得到第三个字节的实际地址)
157     mov bp, dx
158     mov dl, byte [bp]                    ; dl保存第3个字节的数据
159     mov dh, 0                            ; 将dx的高位置0
160     shl dx, 4                            ; 将读取的最后一个字节左移4位,留出低4位给第二个字节的高4位(注意dx是16bit的,左移4位后dl的高4位移到dh的低4位)
161     add cx, 1                            ; 偏移指向第二个字节
162     add cx, bx                            ; 文件簇偏移量+Fat表的地址(得到第二个字节的实际地址)
163     mov bp, cx                            ; 读取第二个字节的值
164     mov cl, byte [bp]
165     shr cl, 4                            ; 将读取的第二个字节右移4位,高位移向低位
166     and cl, 0x0F                        ; 保留右移过后的低位4bit
167     mov ch, 0                            ; 将cx的高位置0
168     or  dx, cx                            ; 将2个字节组合起来,最后一字节(存储在2字节的寄存器)左移4位 | 第二字节右移4bit
169 
170 return:
171     ret
172 
173 
174 ; ##############################################################################
175 ; ds:si --> source
176 ; es:di --> destination
177 ; cx    --> length
178 MemCpy:                                    ; 内存拷贝
179     ;push si
180     ;push di
181     ;push cx
182     ;push ax
183 
184     cmp si, di                            ; 比较内存地址大小,因为两个内存地址空间如果有重复拷贝方式将不同
185 
186     ja btoe                                ; jump if above,如果源地址大于目的地址就从前向后拷贝,否则从后向前拷贝;避免两个内存空间有重复地址时覆盖未拷贝的源数据。
187     
188     add si, cx                            ; 从后向前拷贝需要源和目标地址都挪到最后,
189     add di, cx
190     dec si                                ; 上面的add使指针指向了最后一个字节的后面,所以需要回退一个字节
191     dec di
192 
193     jmp etob
194 
195 btoe:
196     cmp cx, 0                            ; 如果长度为0就停止
197     jz done
198     mov al, [si]                        ; 借助al寄存器将源地址的一个字节(al=8bit)拷贝到目的地址
199     mov byte [di], al
200     inc si                                ; inc递增指令,拷贝完一个字节后源和目标地址指向下一个
201     inc di
202     dec cx                                ; 长度减1
203     jmp btoe                            ; 循环拷贝
204 
205 etob:                                    ; end to begin
206     cmp cx, 0                            ; 如果拷贝完毕就结束
207     jz done
208     mov al, [si]
209     mov byte [di], al
210     dec si                                ; 源和目标地址递减,注意是从后向前拷贝的
211     dec di
212     dec cx
213     jmp etob                            ; 循环拷贝
214 
215 done:                                    ; 内存拷贝完毕还原寄存器数据
216     ;pop ax
217     ;pop cx
218     ;pop di
219     ;pop si
220 
221     ret
222 
223 
224 ; ##############################################################################
225 ; es:bx --> root entry offset address
226 ; ds:si --> target string
227 ; cx    --> target length
228 ;
229 ; return:
230 ;        (dx != 0) ? exist : noexist
231 ;            exist --> bx is the target entry
232 FindEntry:                                ; 查找根目录文件
233     ;push di
234     ;push bp
235     push cx
236 
237     mov dx, [BPB_RootEntCnt]            ; 最大根目录文件数
238     mov bp, sp                            ; 栈地址,不能直接将sp栈顶指针赋值给通用寄存器
239 
240 find:
241     cmp dx, 0                            ; 如果没有文件就跳转到noexist结束
242     jz noexist
243     mov di, bx                            ; bx==Buf缓存;ReadSector已经读取数据到Buf缓存
244     mov cx, [bp]                        ; 借助中间寄存器获取栈顶指针,此时的bp栈顶指针指向最后入栈的cx寄存器
245     push si
246     call MemCmp                            ; 内存匹配查找(文件查找),返回cx表示匹配的字符数(为0表示没有文件或匹配成功)
247     pop si
248     cmp cx, 0                            ; 返回0表示匹配成功或根目录没有文件,否则继续查找
249     jz exist
250     add bx, EntryItemLength                ; Buf缓存地址加32,每个目录项占用32字节
251     dec dx                                ; dx-1,文件数减1
252     jmp find
253 
254 exist:
255 noexist:
256     pop cx
257     ;pop bp
258     ;pop di
259 
260     ret
261 
262 
263 ; ##############################################################################
264 ; ds:si --> source
265 ; es:di --> destination
266 ; cx    --> length
267 ;
268 ; return:
269 ;        (cx == 0) ? equal : noequeal
270 MemCmp:                                    ; 内存数据对比
271     ;push si
272     ;push di
273     ;push ax
274 
275 compare:
276     cmp cx, 0                            ; 到末尾(文件名结束符)就跳转到equal,目录项的第一段是文件名
277     jz equal
278     mov al, [si]                        ; si源(要查找的文件名)
279     cmp al, byte [di]                    ; di目标(根目录区的文件名)
280     jz goon                                ; 匹配一个字节就跳转到goon继续循环判断
281     jmp noequal                            ; 不匹配就跳转到noequal,函数返回以便查找下一个文件
282 goon:
283     inc si                                ; 源和目标+1递增
284     inc di
285     dec cx                                ; 剩余字符串长度递减
286     jmp compare                            ; 跳转到compare继续循环判断
287 
288 equal:
289 noequal:
290     ;pop ax
291     ;pop di
292     ;pop si
293 
294     ret
295 
296 
297 ; ##############################################################################
298 ; es:bp --> string address
299 ; cx    --> string length
300 Print:                                    ; 字符串打印函数
301     mov dx, 0                            ; dx打印的起始行列
302     mov ax, 0x1301                        ; ah=0x13,在Teletype电传打字机模式下输出;al=0x01,字符串只含字符,启用BL属性,光标跟随移动
303     mov bx, 0x0007                        ; bh页码,bl前景色;bl=07,黑底白字
304     int 0x10                            ; 打印中断
305     ret                                    ; 函数返回
306 
307 
308 ; ##############################################################################
309 ; no parameter
310 ResetFloppy:                            ; 重置软盘
311     ;push ax
312     ;push dx
313 
314     mov ah, 0x00                        ; 磁盘系统复位
315     mov dl, [BS_DrvNum]                    ; 驱动器号(0x00~0x7F软盘,0x80~0x0FF硬盘)
316     int 0x13                            ; 读取磁盘的中断
317     
318     ;pop dx
319     ;pop ax
320 
321     ret
322 
323 ; ax    --> logic sector number
324 ; cx    --> number of sector
325 ; es:bx    --> target address
326 ReadSector:                                ; 读扇区(函数)
327     ;push bx
328     ;push cx
329     ;push dx
330     ;push ax
331     
332     call ResetFloppy                    ; 重置软盘
333     
334     push bx
335     push cx
336 
337     mov bl, [BPB_SecPerTrk]                ; 每柱面(磁道)扇区数;本段代码用于计算柱面、磁头和扇区号以及设置驱动器号
338     div bl                                ; 除法,用于计算要读取的数据的起始柱面和扇区偏移;被除数在AX(或者DX高位+AX地位),商在AL,余数在AH
339     mov cl, ah                            ; FAT的扇区从0开始,软盘的扇区从1开始
340     add cl, 1                            ; cl记录要读取的数据相对柱面的扇区偏移,注意FAT扇区和软盘扇区的起始编号不同
341     mov ch, al                            ; 读取的数据起始柱面号
342     shr ch, 1                            ; 计算数据所在柱面偏移量;右移一位表示除以2(当前软盘只有上下2个磁头);shr移位大于1时需要借助cl寄存器;
343     mov dh, al                            ; 商,dh记录起始磁头
344     and dh, 1                            ; 如果逻辑柱面(磁道)号是偶数就在0磁头,否则就是1磁头(目前只有2个磁头)
345     mov dl, [BS_DrvNum]                    ; 设置驱动器号
346 
347     pop ax                                ; 还原要读取的扇区数,相当于原来的cx,因为cx是最后入栈的,这里是最先出栈
348     pop bx                                ; bx已设置为指向Buf
349 
350     mov ah, 0x02                        ; 0x02读扇区,磁头、驱动器号、柱面(磁道)、扇区-->dh、dl、ch、cl;长度、Buf-->ax、bx
351 
352 read:
353     int 0x13                            ; 读取磁盘的中断
354     jc read                                ; 若进位位(CF)被置位,表示调用失败,需要重新读取
355 
356     ;pop ax
357     ;pop dx
358     ;pop cx
359     ;pop bx
360 
361     ret
362 
363 
364 ; ##############################################################################
365 MsgStr db "No LOADER ..."                ; 定义字符串
366 MsgLen equ ($-MsgStr)                    ; 定义上面字符串长度标号
367 
368 Target db "LOADER     "                    ; 要查栈的文件名
369 TarLen equ ($-Target)                    ; 要查找的文件名长度
370 
371 EntryItem times EntryItemLength db 0x00    ; 目录项空间,且用0填充根目录区,32字节
372 
373 Buf:                                    ; Buf空间,数据的读取和写入空间;下面两行代码是为mbr准备的
374     times 510-($-$$) db 0x00            ; 用0填充mbr中的剩余部分(注意留出0x55aa两字节空间)
375     db 0x55, 0xaa                        ; mbr的最后两个字节用0x55AA结尾
复制代码
复制代码
 1 // loader.asm
 2 org 0x9000
 3 
 4 begin:
 5     mov si, msg
 6 
 7 print:
 8     mov al, [si]
 9     add si, 1
10     cmp al, 0x00
11     je end
12     mov ah, 0x0E
13     mov bx, 0x0F
14     int 0x10
15     jmp print
16 
17 end:
18     hlt
19     jmp end
20 
21 msg:
22     db 0x0a, 0x0a
23     db "Hello, D.T.OS!"
24     db 0x0a, 0x0a
25     db 0x00
复制代码

输出

    找到文件(loader)正常输出(Hello,D.T.OS!)
    
    找不到文件(LOADER    A)的错误输出(No LOADER …)
     

    找到文件(LOADER     )后的输出(输出文件内容,由于文件我写了中文注释所以有乱码)
    

第8课 – 突破512字节的限制(下)

整体思路
    

最后的冲刺

    备份目标文件的目录信息(MemCpy)
    加载Fat表,并完成Fat表项的查找与读取(FatVec)

备份目标文件的目录信息(内存拷贝)

    

MemCpy实现要点:拷贝方向

    

汇编小贴士

    
  

Fat表项的读取

    

Fat表项的“动态组装”

    

FatVec[j]的“动态组装”

    

小结

    内存拷贝时需要考虑拷贝的方向
        当si>di时,从前向后拷贝
        当si<=di时,从后向前拷贝
    Fat表加载到内存中之后,需要“动态组装”表项
        Fat表中使用3个字节表示2个表项
        其实字节=表项下标/2*3(运算结果取整)

代码

复制代码
  1 ; boot.asm
  2 org 0x7c00                                ; 从0x7c00处开始存储代码
  3 
  4 jmp short start                            ; BS_JmpBoot,短跳指令(3字节);jmp指令占用1字节,地址占用1字节;下面的nop空指令占用1字节;共3字节
  5 nop
  6 
  7 define:
  8     BaseOfStack     equ 0x7c00            ; 定义栈的起始地址,注意栈是从高到低增长的,先加后入栈,先出后减
  9     BaseOfLoader    equ 0x9000            ; 内存加载地址
 10     RootEntryOffset equ 19                ; 目录文件项从19逻辑扇区开始
 11     RootEntryLength equ 14                ; 目录文件项共14扇区
 12     EntryItemLength    equ 32                ; 根目录区每个目录项大小
 13     FatEntryOffset    equ 1                ; FAT表起始扇区(FAT表记录了文件簇对应的实际地址)
 14     FatEntryLength    equ 9                ; FAT表长度(扇区)
 15 
 16 
 17 ; ##############################################################################
 18 header:                                    ; MBR
 19     BS_OEMName        db "D.T.Soft"        ; OEM字符,8个,不足以空格填充
 20     BPB_BytsPerSec    dw 512                ; 每扇区字节数
 21     BPB_SecPerClus    db 1                ; 每簇占用扇区数
 22     BPB_RsvdSecCnt    dw 1                ; Boot占用的扇区数
 23     BPB_NumFATs        db 2                ; FAT表的记录数
 24     BPB_RootEntCnt    dw 224                ; 最大根目录文件数
 25     BPB_TotSec16    dw 2880                ; 逻辑扇区总数
 26     BPB_Media        db 0xF0                ; 媒体描述符
 27     BPB_FATSz16        dw 9                ; 每个FAT占用扇区数
 28     BPB_SecPerTrk    dw 18                ; 每个磁道扇区数
 29     BPB_NumHeads    dw 2                ; 磁头数
 30     BPB_HiddSec        dd 0                ; 隐藏扇区数
 31     BPB_TotSec32    dd 0                ; 如果BPB_TotSec16是0,则在这里记录扇区总数
 32     BS_DrvNum        db 0                ; 中断13的驱动器号
 33     BS_Reserved1    db 0                ; 未使用
 34     BS_BootSig        db 0x29                ; 扩展引导标志
 35     BS_VolID        dd 0                ; 卷序列号
 36     BS_VolLab        db "D.T.OS-0.01"    ; 卷标,必须11个字符,不足以空格填充
 37     BS_FileSysType    db "FAT12   "        ; 文件系统类型,必须使8个字符,不足填充空格
 38 
 39 
 40 ; ##############################################################################
 41 start:                                    ; 汇编起始标号,类似于main()函数,之前用jmp跳转到此
 42     mov ax, cs                            ; 设置相关的段寄存器
 43     mov ss, ax
 44     mov ds, ax
 45     mov es, ax
 46     mov sp, BaseOfStack                    ; 设置函数调用栈地址
 47 
 48     mov ax, RootEntryOffset                ; 记录目录文件项起始扇区
 49     mov cx, RootEntryLength                ; 记录目录文件项占用扇区数
 50     mov bx, Buf                            ; 读取的目录文件项存放处
 51 
 52     call ReadSector                        ;     读取指定扇区数据到Buf缓存
 53     mov si, Target                        ; 要查找的文件名
 54     mov cx, TarLen                        ; 文件名长度
 55     mov dx, 0                            ; 设置默认返回值为查找失败
 56 
 57     call FindEntry                        ;     查找文件,dx作为返回值
 58 
 59     cmp dx, 0                            ; 如果没有找到文件就输出提示,否則继续
 60     jz output
 61 
 62     mov si, bx                            ; 将读取的数据bx放到si,这里是将目录文件项在Buf的地址作为源
 63     mov di, EntryItem                    ; 存储根目录区的内存空间di
 64     mov cx, EntryItemLength                ; 根目录区的大小
 65 
 66     call MemCpy                            ;     调用内存拷贝,把目录文件项的第一个目录项从Buf拷贝到EntryItem
 67 
 68     mov ax, FatEntryLength                ; Fat表占用扇区数
 69     mov cx, [BPB_BytsPerSec]            ; 每扇区字节数
 70     mul cx                                ; mul乘法,al*cx,结果放到AX系列寄存器,此时ax等于Fat表占用字节数
 71     mov bx, BaseOfLoader                ; Fat表内存暂存区地址
 72     sub bx, ax                            ; 减法,将读取的数据放到Fat暂存区的前面,注意上面的乘法结果不能溢出,这里的AX没有溢出(9*512=4608,ax=16bit=65535)
 73 
 74     mov ax, FatEntryOffset                ; Fat1起始扇区
 75     mov cx, FatEntryLength                ; Fat表的长度
 76 
 77     call ReadSector                        ;     读取Fat表,放到bx指向的地址
 78 
 79     mov cx, [EntryItem + 0x1A]            ; FAT表内存地址加0x1A写入cx寄存器,即文件开始的簇号(DIR_FstClus)
 80 
 81     call FatVec                            ;    获取簇对应的地址
 82 
 83     jmp last
 84 
 85 output:
 86     mov bp, MsgStr
 87     mov cx, MsgLen
 88 
 89     call Print                            ; 调用字符串打印函数
 90 
 91 last:                                    ; 死循环
 92     hlt
 93     jmp last
 94 
 95 
 96 ; ##############################################################################
 97 ; cx --> index
 98 ; bx --> fat table address
 99 ;
100 ; return:
101 ;    dx --> fat[index]
102 FatVec:                                    ; 获取簇对应的地址,bx=fat表的地址,cx文件起始簇号
103     mov ax, cx                            ; 下面是乘以1.5的相关操作(/2*3),因为2个簇占3字节,通过簇号的奇偶校验决定该簇在FAT表的偏移
104     mov cl, 2
105     div cl                                ; 文件簇编号除以2,商放al,余数放ah;余数为0时簇号为偶数,相对FAT表的偏移量采用低12bit(低1.5字节),否则采用高12bit(高1.5字节)
106 
107     push ax                                ; ah用于决定截取方式,al保留了除以2的整数值
108 
109     mov ah, 0                            ; ax的高位置0,相当于舍去余数保留商
110     mov cx, 3
111     mul cx                                ; ax乘以3
112     mov cx, ax                            ; cx=文件簇在FAT表的位置
113 
114     pop ax
115 
116     cmp ah, 0                            ; 对簇地址判断奇偶决定比特组合方式(前12比特还是后12比特)
117     jz even                                ; 如果余数是0就跳转到even截取前1.5字节
118     jmp odd                                ; 否则跳转到odd截取后1.5字节
119 
120 even:                                    ; 偶数:截取前1.5字节;fatVec[j] = ( (Fat[i+1] & 0x0f) << 8 ) | Fat[i];
121     mov dx, cx                            ; 文件簇偏移放入dx
122     add dx, 1                            ; 偏移到第二个字节,需要截取第二个字节
123     add dx, bx                            ; 文件簇索引+Fat表的地址(得到第二个字节的实际地址)
124     mov bp, dx                            ; 借助bp寄存器读取当前簇的第二个字节
125     mov dl, byte [bp]
126     and dl, 0x0F                        ; 保留低4位
127     shl dx, 8                            ; 左移8位使第二字节的低4bit放到dx的高位
128     add cx, bx                            ; 指向第一个字节
129     mov bp, cx
130     or  dl, byte [bp]                    ; 读取当前簇的第一个字节放到dx的低位,将2个字节组合起来
131     jmp return
132 
133 odd:                                    ; 奇数:截取后1.5字节;FatVec[j+1] = (Fat[i+2] << 4) | ( (Fat[i+1] >> 4) & 0x0F );
134     mov dx, cx
135     add dx, 2                            ; 偏移到第三个字节(最后一个字节)
136     add dx, bx                            ; 文件簇偏移量+Fat表的地址(得到第三个字节的实际地址)
137     mov bp, dx
138     mov dl, byte [bp]                    ; dl保存第3个字节的数据
139     mov dh, 0                            ; 将dx的高位置0
140     shl dx, 4                            ; 将读取的最后一个字节左移4位,留出低4位给第二个字节的高4位(注意dx是16bit的,左移4位后dl的高4位移到dh的低4位)
141     add cx, 1                            ; 偏移指向第二个字节
142     add cx, bx                            ; 文件簇偏移量+Fat表的地址(得到第二个字节的实际地址)
143     mov bp, cx                            ; 读取第二个字节的值
144     mov cl, byte [bp]
145     shr cl, 4                            ; 将读取的第二个字节右移4位,高位移向低位
146     and cl, 0x0F                        ; 保留右移过后的低位4bit
147     mov ch, 0                            ; 将cx的高位置0
148     or  dx, cx                            ; 将2个字节组合起来,最后一字节(存储在2字节的寄存器)左移4位 | 第二字节右移4bit
149 
150 return:
151     ret
152 
153 
154 ; ##############################################################################
155 ; ds:si --> source
156 ; es:di --> destination
157 ; cx    --> length
158 MemCpy:                                    ; 内存拷贝
159     push si
160     push di
161     push cx
162     push ax
163 
164     cmp si, di                            ; 比较内存地址大小,因为两个内存地址空间如果有重复拷贝方式将不同
165 
166     ja btoe                                ; jump if above,如果源地址大于目的地址就从前向后拷贝,否则从后向前拷贝;避免两个内存空间有重复地址时覆盖未拷贝的源数据。
167     
168     add si, cx                            ; 从后向前拷贝需要源和目标地址都挪到最后,
169     add di, cx
170     dec si                                ; 上面的add使指针指向了最后一个字节的后面,所以需要回退一个字节
171     dec di
172 
173     jmp etob
174 
175 btoe:
176     cmp cx, 0                            ; 如果长度为0就停止
177     jz done
178     mov al, [si]                        ; 借助al寄存器将源地址的一个字节(al=8bit)拷贝到目的地址
179     mov byte [di], al
180     inc si                                ; inc递增指令,拷贝完一个字节后源和目标地址指向下一个
181     inc di
182     dec cx                                ; 长度减1
183     jmp btoe                            ; 循环拷贝
184 
185 etob:                                    ; end to begin
186     cmp cx, 0                            ; 如果拷贝完毕就结束
187     jz done
188     mov al, [si]
189     mov byte [di], al
190     dec si                                ; 源和目标地址递减,注意是从后向前拷贝的
191     dec di
192     dec cx
193     jmp etob                            ; 循环拷贝
194 
195 done:                                    ; 内存拷贝完毕还原寄存器数据
196     pop ax
197     pop cx
198     pop di
199     pop si
200 
201     ret
202 
203 
204 ; ##############################################################################
205 ; es:bx --> root entry offset address
206 ; ds:si --> target string
207 ; cx    --> target length
208 ;
209 ; return:
210 ;        (dx != 0) ? exist : noexist
211 ;            exist --> bx is the target entry
212 FindEntry:                                ; 查找根目录文件
213     push di
214     push bp
215     push cx
216 
217     mov dx, [BPB_RootEntCnt]            ; 最大根目录文件数
218     mov bp, sp                            ; 栈地址,不能直接将sp栈顶指针赋值给通用寄存器
219 
220 find:
221     cmp dx, 0                            ; 如果没有文件就跳转到noexist结束
222     jz noexist
223     mov di, bx                            ; bx==Buf缓存;ReadSector()已经读取数据到Buf缓存
224     mov cx, [bp]                        ; 借助中间寄存器获取栈顶指针,此时的bp栈顶指针指向最后入栈的cx寄存器
225     call MemCmp                            ; 内存匹配查找(文件查找),返回cx表示匹配的字符数(为0表示没有文件或匹配成功)
226     cmp cx, 0                            ; 返回0表示匹配成功或根目录没有文件,否则继续查找
227     jz exist
228     add bx, EntryItemLength                ; Buf缓存地址加32,每个目录项占用32字节
229     dec dx                                ; dx-1,文件数减1
230     jmp find
231 
232 exist:
233 noexist:
234     pop cx
235     pop bp
236     pop di
237 
238     ret
239 
240 
241 ; ##############################################################################
242 ; ds:si --> source
243 ; es:di --> destination
244 ; cx    --> length
245 ;
246 ; return:
247 ;        (cx == 0) ? equal : noequeal
248 MemCmp:                                    ; 内存数据对比
249     push si
250     push di
251     push ax
252 
253 compare:
254     cmp cx, 0                            ; 到末尾(文件名结束符)就跳转到equal,目录项的第一段是文件名
255     jz equal
256     mov al, [si]                        ; si源(要查找的文件名)
257     cmp al, byte [di]                    ; di目标(根目录区的文件名)
258     jz goon                                ; 匹配一个字节就跳转到goon继续循环判断
259     jmp noequal                            ; 不匹配就跳转到noequal,函数返回以便查找下一个文件
260 goon:
261     inc si                                ; 源和目标+1递增
262     inc di
263     dec cx                                ; 剩余字符串长度递减
264     jmp compare                            ; 跳转到compare继续循环判断
265 
266 equal:
267 noequal:
268     pop ax
269     pop di
270     pop si
271 
272     ret
273 
274 
275 ; ##############################################################################
276 ; es:bp --> string address
277 ; cx    --> string length
278 Print:                                    ; 字符串打印函数
279     mov dx, 0                            ; dx打印的起始行列
280     mov ax, 0x1301                        ; ah=0x13,在Teletype电传打字机模式下输出;al=0x01,字符串只含字符,启用BL属性,光标跟随移动
281     mov bx, 0x0007                        ; bh页码,bl前景色;bl=07,黑底白字
282     int 0x10                            ; 打印中断
283     ret                                    ; 函数返回
284 
285 
286 ; ##############################################################################
287 ; no parameter
288 ResetFloppy:                            ; 重置软盘
289     push ax
290     push dx
291 
292     mov ah, 0x00                        ; 磁盘系统复位
293     mov dl, [BS_DrvNum]                    ; 驱动器号(0x00~0x7F软盘,0x80~0x0FF硬盘)
294     int 0x13                            ; 读取磁盘的中断
295     
296     pop dx
297     pop ax
298 
299     ret
300 
301 ; ax    --> logic sector number
302 ; cx    --> number of sector
303 ; es:bx    --> target address
304 ReadSector:                                ; 读扇区(函数)
305     push bx                                ; 保存相关寄存器
306     push cx
307     push dx
308     push ax
309     
310     call ResetFloppy                    ; 重置软盘
311     
312     push bx
313     push cx
314 
315     mov bl, [BPB_SecPerTrk]                ; 每柱面(磁道)扇区数;本段代码用于计算柱面、磁头和扇区号以及设置驱动器号
316     div bl                                ; 除法,用于计算要读取的数据的起始柱面和扇区偏移;被除数在AX(或者DX高位+AX地位),商在AL,余数在AH
317     mov cl, ah                            ; FAT的扇区从0开始,软盘的扇区从1开始
318     add cl, 1                            ; cl记录要读取的数据相对柱面的扇区偏移,注意FAT扇区和软盘扇区的起始编号不同
319     mov ch, al                            ; 读取的数据起始柱面号
320     shr ch, 1                            ; 计算数据所在柱面偏移量;右移一位表示除以2(当前软盘只有上下2个磁头);shr移位大于1时需要借助cl寄存器;
321     mov dh, al                            ; 商,dh记录起始磁头
322     and dh, 1                            ; 如果逻辑柱面(磁道)号是偶数就在0磁头,否则就是1磁头(目前只有2个磁头)
323     mov dl, [BS_DrvNum]                    ; 设置驱动器号
324 
325     pop ax                                ; 还原要读取的扇区数,相当于原来的cx,因为cx是最后入栈的,这里是最先出栈
326     pop bx                                ; bx已设置为指向Buf
327 
328     mov ah, 0x02                        ; 0x02读扇区,磁头、驱动器号、柱面(磁道)、扇区-->dh、dl、ch、cl;长度、Buf-->ax、bx
329 
330 read:
331     int 0x13                            ; 读取磁盘的中断
332     jc read                                ; 若进位位(CF)被置位,表示调用失败,需要重新读取
333 
334     pop ax
335     pop dx
336     pop cx
337     pop bx
338 
339     ret
340 
341 
342 ; ##############################################################################
343 MsgStr db "No LOADER ..."                ; 定义字符串
344 MsgLen equ ($-MsgStr)                    ; 定义上面字符串长度标号
345 
346 Target db "LOADER"                        ; 要查栈的文件名
347 TarLen equ ($-Target)                    ; 要查找的文件名长度
348 
349 EntryItem times EntryItemLength db 0x00    ; 目录项空间,且用0填充根目录区,32字节
350 
351 Buf:                                    ; Buf空间,数据的读取和写入空间;下面两行代码是为mbr准备的
352     times 510-($-$$) db 0x00            ; 用0填充mbr中的剩余部分(注意留出0x55aa两字节空间)
353     db 0x55, 0xaa                        ; mbr的最后两个字节用0x55AA结尾
复制代码

第7课 – 突破512字节的限制(中)

突破限制的预备工作

    

整体思路

    

如何在根目录区中查找目标文件?

通过根目录项的前11个字节进行判断

    

内存比较

    指定源起始地址(DS:SI)
    指定目标起始地址(ES:DI)
    判断在期望长度(CX)内每一个字节是否相等
    

汇编小贴士

    汇编中的比较与跳转
        比较:
            cmp cx, 0    ;比较cx的值是否为0,并把结果记录到标志寄存器
        跳转:
            jz equal          ; 如果比较为真(根据标志寄存器的值)就跳转到equal
      
    访问栈空间中的栈顶数据
        不能使用sp直接访问栈顶数据
        通过其它通用寄存器间接访问栈顶数据
  

查找根目录区是否存在目标文件

  

加载根目录区

    

小结

    可通过查找根目录区判断是否存在目标文件
        加载根目录区至内存中(ReadSector
        遍历根目录区中的每一项(FindEntry
        通过每一项的前11个字节进行判断(MemCmp
        当目标不存在时,打印错误信息(print

代码

复制代码
  1 org 0x7c00                            ; 从0x7c00处开始存储代码
  2  
  3 jmp short start                        ; BS_JmpBoot;短跳指令,jmp指令占用1字节,地址占用1字节;下面的nop空指令占用1字节;共3字节
  4 nop
  5  
  6 define:
  7     BaseOfStack     equ 0x7c00        ; 定义栈的起始地址,注意栈是从高到低增长的,先加后入栈,先出后减
  8     RootEntryOffset equ 19            ; 目录文件项从19逻辑扇区开始
  9     RootEntryLength equ 14            ; 目录文件项共14扇区
 10  
 11 ; ##############################################################################
 12 header:
 13     BS_OEMName        db "D.T.Soft"    ; OEM字符,8个,不足以空格填充
 14     BPB_BytsPerSec    dw 512            ; 每扇区字节数
 15     BPB_SecPerClus    db 1            ; 每簇占用扇区数
 16     BPB_RsvdSecCnt    dw 1            ; Boot占用的扇区数
 17     BPB_NumFATs        db 2            ; FAT表的记录数
 18     BPB_RootEntCnt    dw 224            ; 最大根目录文件数
 19     BPB_TotSec16    dw 2880            ; 逻辑扇区总数
 20     BPB_Media        db 0xF0            ; 媒体描述符
 21     BPB_FATSz16        dw 9            ; 每个FAT占用扇区数
 22     BPB_SecPerTrk    dw 18            ; 每个磁道扇区数
 23     BPB_NumHeads    dw 2            ; 磁头数
 24     BPB_HiddSec        dd 0            ; 隐藏扇区数
 25     BPB_TotSec32    dd 0            ; 如果BPB_TotSec16是0,则在这里记录扇区总数
 26     BS_DrvNum        db 0            ; 中断13的驱动器号
 27     BS_Reserved1    db 0            ; 未使用
 28     BS_BootSig        db 0x29            ; 扩展引导标志
 29     BS_VolID        dd 0            ; 卷序列号
 30     BS_VolLab        db "D.T.OS-0.01"; 卷标,必须11个字符,不足以空格填充
 31     BS_FileSysType    db "FAT12   "    ; 文件系统类型,必须使8个字符,不足填充空格
 32  
 33 ; ##############################################################################
 34 start:                                ; 汇编起始标号,类似于main()函数
 35     mov ax, cs                        ; 设置相关的段寄存器
 36     mov ss, ax
 37     mov ds, ax
 38     mov es, ax
 39     mov sp, BaseOfStack                ; 设置函数调用栈
 40  
 41     ;mov ax, 59                        ; 文件数据起始于59扇区
 42     ;mov cx, 1                        ; 读取1个扇区
 43     ;mov bx, Buf                    ; 数据写入的地址
 44  
 45     ;call ReadSector                ; 调用读扇区函数(数据保存到ES:BX)
 46     
 47     ;mov si, MsgStr
 48     ;mov di, DEST
 49     ;mov cx, MsgLen
 50     mov ax, RootEntryOffset            ; 记录数据起始扇区
 51     mov cx, RootEntryLength            ; 记录数据占用扇区数
 52     mov bx, Buf                        ; 读取的数据存放处
 53  
 54     call ReadSector                    ; 读取指定扇区数据到Buf缓存
 55     mov si, Target                    ; 要查找的文件名
 56     mov cx, TarLen                    ; 文件名长度
 57     mov dx, 0        ; 设置默认返回值
 58  
 59     call FindEntry                    ; 查找文件
 60  
 61     cmp dx, 0                        ; 如果没有找到文件就输出提示,否則跳到last死循環
 62     jz output
 63     jmp last
 64  
 65     ;call MemCmp
 66  
 67     ;cmp cx, 0
 68     ;jz output
 69     ;jmp last
 70  
 71     ;mov bp, Buf                    ; 字符串段的偏移地址,相对于ES; mov bp, MsgStr    ; 输出Hello, DTOS!
 72     ;mov cx, 29                        ; 字符串长度    ; mov cx, MsgLen    ; Test file for virtual floppy.
 73 output:
 74     mov bp, MsgStr
 75     mov cx, MsgLen
 76     call Print                        ; 调用字符串打印函数
 77  
 78 last:                                ; 死循环
 79     hlt
 80     jmp last
 81  
 82 ; ##############################################################################
 83 ; es:bx --> root entry offset address
 84 ; ds:si --> target string
 85 ; cx    --> target length
 86 ;
 87 ; return:
 88 ;        (dx != 0) ? exist : noexist
 89 ;            exist --> bx is the target entry
 90 FindEntry:                            ; 查找根目录文件
 91     push di
 92     push bp
 93     push cx
 94  
 95     mov dx, [BPB_RootEntCnt]        ; 最大根目录文件数
 96     mov bp, sp                        ; 栈地址,不能直接将sp栈顶指针赋值给通用寄存器
 97  
 98 find:
 99     cmp dx, 0                        ; 如果没有文件就跳转到noexist结束
100     jz noexist
101     mov di, bx                        ; bx==Buf缓存;ReadSector()已经读取数据到Buf缓存
102     mov cx, [bp]                    ; 借助中间寄存器获取栈顶指针,此时的bp栈顶指针指向最后入栈的cx寄存器
103     call MemCmp                        ; 内存匹配查找(文件查找)
104     cmp cx, 0                        ; 如果还有文件就继续,否则匹配失败跳转到exist结束
105     jz exist
106     add bx, 32                        ; Buf缓存地址加32,每个目录项占用32字节
107     dec dx                            ; dx-1,文件数减1
108     jmp find
109  
110 exist:
111 noexist:
112     pop cx
113     pop bp
114     pop di
115  
116     ret
117  
118 ; ##############################################################################
119 ; ds:si --> source
120 ; es:di --> destination
121 ; cx    --> length
122 ;
123 ; return:
124 ;        (cx == 0) ? equal : noequeal
125 MemCmp:                                ; 内存数据对比
126     push si
127     push di
128     push ax
129  
130 compare:
131     cmp cx, 0                        ; 到末尾(文件名结束符)就跳转到equal
132     jz equal
133     mov al, [si]                    ; si源(要查找的文件名)
134     cmp al, byte [di]                ; di目标(根目录区的文件名)
135     jz goon                            ; 匹配一个字节就跳转到goon继续循环判断
136     jmp noequal                        ; 不匹配就跳转到noequal,函数返回以便查找下一个文件
137 goon:
138     inc si                            ; 源和目标+1递增
139     inc di
140     dec cx                            ; 剩余次数-1递减
141     jmp compare                        ; 跳转到compare继续循环
142  
143 equal:
144 noequal:
145     pop ax
146     pop di
147     pop si
148  
149     ret
150  
151 ; ##############################################################################
152 ; es:bp --> string address
153 ; cx    --> string length
154 Print:                                ; 字符串打印函数
155     mov ax, 0x1301                    ; ah=13,在Teletype电传打字机模式下输出;al=01,字符串只含字符,启用BL属性,改变光标
156     mov bx, 0x0007                    ; bh页码,bl前景色;bl=07,80×25字符的2色文本
157     int 0x10                        ; 打印中断
158     ret                                ; 函数返回
159  
160 ; ##############################################################################
161 ; no parameter
162 ResetFloppy:                        ; 重置软盘段
163     push ax
164     push dx
165  
166     mov ah, 0x00                    ; 磁盘系统复位
167     mov dl, [BS_DrvNum]                ; 驱动器号(0x00~0x7F软盘,0x80~0x0FF硬盘)
168     int 0x13                        ; 读取磁盘的中断
169     
170     pop dx
171     pop ax
172  
173     ret
174  
175 ; ax    --> logic sector number
176 ; cx    --> number of sector
177 ; es:bx    --> target address
178 ReadSector:                            ; 读扇区段(函数)
179     push bx                            ; 保存相关寄存器
180     push cx
181     push dx
182     push ax
183     
184     call ResetFloppy                ; 重置软盘
185     
186     push bx
187     push cx
188  
189     mov bl, [BPB_SecPerTrk]            ; 每柱面(磁道)扇区数;本段代码用于计算柱面、磁头和扇区号以及设置驱动器号
190     div bl                            ; 除法,被除数在AX(或者DX高位+AX地位),商在AL,余数在AH
191     mov cl, ah                        ; 余数,cl记录起始扇区;FAT的扇区从0开始,软盘的扇区从1开始,所以下面+1
192     add cl, 1
193     mov ch, al                        ; 商(逻辑柱面或磁道数),ch记录起始柱面(磁道)
194     shr ch, 1                        ; 右移一位表示除以2(当前软盘只有2个磁头),因为扇区编号是由两面的柱面(磁道)组合的,然后再从外往内增大;shr移位大于1时需要借助cl寄存器;
195     mov dh, al                        ; 商,dh记录起始磁头
196     and dh, 1                        ; 如果逻辑柱面(磁道)号是偶数就在0磁头,否则就是1磁头(目前只有2个磁头)
197     mov dl, [BS_DrvNum]                ; 设置驱动器号
198  
199     pop ax                            ; 还原要读取的扇区数,相当于原来的cx,因为cx是最后入栈的,这里是最先出栈
200     pop bx                            ; bx已设置为指向Buf
201  
202     mov ah, 0x02                    ; 0x02读扇区
203  
204 read:
205     int 0x13                        ; 读取磁盘的中断
206     jc read                            ; 若进位位(CF)被置位,表示调用失败,需要重新读取
207  
208     pop ax
209     pop dx
210     pop cx
211     pop bx
212  
213     ret
214  
215 ; ##############################################################################
216 ;MsgStr db "Hello, DTOS!"            ; 定义字符串,上面43行注释使用
217 MsgStr db "No LOADER  "                ; 定义字符串,上面43行注释使用
218 MsgLen equ ($-MsgStr)                ; 定义上面字符串长度标号
219 ;DEST db "Hello, DTOS!"                ; 測試目標文件
220 Target db "LOADER"                    ; 要查栈的文件名
221 TarLen equ ($-Target)                ; 要查找的文件名长度
222 Buf:                                ; Buf缓存,数据的读取和写入空间;下面两行代码是为mbr准备的,其它读写无用无害
223     times 510-($-$$) db 0x00
224     db 0x55, 0xaa
复制代码

输出

    找到了文件(LOADER)
       

    没有找到文件(LiOADER)
      

第6课 – 突破512字节的限制(上)

BIOS中的字符串打印
    指定打印参数(AX=0x1301,BX=0x0007)
    指定字符串的内存地址(ES:BP=串地址)
    指定字符串的长度(CX=串长度)
    中断调用(int 0x10)

字符串打印示例

    

汇编小贴士

    汇编中可以定义函数(函数名使用标签定义
        call function
        函数体的最后一条指令为ret
    如果代码中定义了函数,就需要定义栈空间
        用于保存关键寄存器的值
        栈顶地址通过sp寄存器保存
    汇编中的16位除法操作(div
        被除数放到AX寄存器
        除数放到通用寄存器或内存单元(8位)
        结果:商位于AL,余数位于AH

汇编的“常量”(equ)

    用法:Const equ 0x7c00    ; define Const 0x7c00
    与dx(db、dw、dd)的区别
    dx定义占用相应的内存空间
    equ定义不占用任何内存空间

软盘(硬盘)的构造

    

3.5寸软盘的数据特性

    每个盘面一共80个柱面(编号0-79
    每个柱面有18个扇区(编号1-18
    存储大小:2×80×18×512=1474560Bytes = 1440KB
    软盘数据存储方式:扇区先从最外圈的柱面(磁道)开始,然后往中心增大;这里要注意的是,扇区在柱面(磁道)编排完后会到另一面编排,然后才增大柱面(磁道)号;
                                  例如:0磁头0柱面(磁道)存储1~18扇区,1磁头0柱面(磁道)存储19~36扇区;0磁头1柱面(磁道)存储37~54扇区。。。

软盘数据的读取

    软盘数据以扇区(512字节)为单位进行读取
    指定数据所在位置的磁头号,柱面号,扇区号
    计算公式:
                    

BIOS中的软盘数据读取(int 0x13)

    

软盘数据读取流程

    

反汇编代码调试

    ndisasm -o 0x7c00 boot.bin > boot.txt
    启动bochs虚拟机调试
    设置断点为怀疑的地址:break 0x7c78
    查看断点信息:info break
    继续执行到断点处:c
    设置断点:break 0x7c81
    查看断点:info break
    继续执行到断点处:c
    下一步:step
    下一步:s
    下一步:s
    下一步:s
    下一步:s
    下一步:s
    下一步:s
    下一步:s
    输出寄存器值:reg
    继续:c

代码

复制代码
  1 // makefile
  2 .PHONY : all clean rebuild
  3  
  4 SRC := boot.asm
  5 OUT := boot.bin
  6 IMG := data.img
  7  
  8 RM := rm -rf
  9  
 10 all : $(OUT) $(IMG)
 11     dd if=$(OUT) of=$(IMG) bs=512 count=1 conv=notrunc
 12     @echo "Success!"
 13  
 14 $(IMG) :
 15     bximage -mode=create -q -fd=1.44M $@
 16  
 17 $(OUT) : $(SRC)
 18     nasm $^ -o $@
 19  
 20 clean :
 21     $(RM) $(IMG) $(OUT)
 22  
 23 rebuild :
 24     @$(MAKE) clean
 25     @$(MAKE) all
 26  
 27 // boot.asm
 28 org 0x7c00                             ; 从0x7c00处开始存储代码
 29  
 30 jmp short start                        ; BS_JmpBoot;短跳指令(8bit),跳到start处,jmp指令占用1字节,地址占用1字节;下面的nop空指令占用1字节;共3字节
 31 nop                                    ; jmp near ptr近跳转(16bit);jmp for ptr远跳转(段转移,每段16bit);jmp word ptr(段内转移);jmp dword ptr(段间转移)
 32  
 33 define:
 34     BaseOfStack equ 0x7c00             ; equ用于定义别称,不参与编译;定义栈地址,注意栈地址是从高到低的,与代码存储相反;入栈先变址后入栈,出栈先出栈后变址;
 35  
 36 header:                                ; MBR扇区部分数据,db(define byte,8bit)、dw(define word, 16bit)、dd(double word, 32bit)
 37     BS_OEMName        db "D.T.Soft"    ; OEM字符,8个,不足以空格填充
 38     BPB_BytsPerSec    dw 512           ; 每扇区字节数
 39     BPB_SecPerClus    db 1             ; 每簇占用扇区数
 40     BPB_RsvdSecCnt    dw 1             ; Boot占用的扇区数
 41     BPB_NumFATs        db 2            ; FAT表的记录数
 42     BPB_RootEntCnt    dw 224           ; 最大根目录文件数
 43     BPB_TotSec16    dw 2880            ; 逻辑扇区总数
 44     BPB_Media        db 0xF0           ; 媒体描述符
 45     BPB_FATSz16        dw 9            ; 每个FAT占用扇区数
 46     BPB_SecPerTrk    dw 18             ; 每个磁道扇区数
 47     BPB_NumHeads    dw 2               ; 磁头数
 48     BPB_HiddSec        dd 0            ; 隐藏扇区数
 49     BPB_TotSec32    dd 0               ; 如果BPB_TotSec16是0,则在这里记录扇区总数
 50     BS_DrvNum        db 0              ; int 13的驱动器号,0表示A盘
 51     BS_Reserved1    db 0               ; 未使用
 52     BS_BootSig        db 0x29          ; 扩展引导标志
 53     BS_VolID        dd 0               ; 卷序列号
 54     BS_VolLab        db "D.T.OS-0.01"  ; 卷标,必须11个字符,不足以空格填充
 55     BS_FileSysType    db "FAT12   "    ; 文件系统类型,必须是8个字符,不足填充空格
 56  
 57 start:
 58     mov ax, cs                         ; 设置相关的寄存器
 59     mov ss, ax
 60     mov ds, ax
 61     mov es, ax
 62     mov sp, BaseOfStack                ; 设置栈地址,因为要调用函数
 63  
 64     mov ax, 59                         ; 文件数据起始于59扇区(经查找,我想要读取的数据在第59扇区,文章尾部有图片)
 65     mov cx, 1                          ; 读取1个扇区
 66     mov bx, Buf                        ; 数据写入的地址
 67  
 68     call ReadSector                    ; 调用读扇区函数(数据保存到ES:BX)
 69  
 70     mov bp, Buf                        ; 字符串段的偏移地址,相对于ES  ; mov bp, MsgStr    ; 输出Hello, DTOS!
 71     mov cx, 29                         ; 字符串长度  ; mov cx, MsgLen
 72  
 73     call Print                         ; 调用字符串打印函数
 74  
 75 last:                                  ; 死循环
 76     hlt
 77     jmp last
 78  
 79 ; es:bp --> string address
 80 ; cx    --> string length
 81 Print:                                ; 字符串打印函数
 82     mov ax, 0x1301                    ; ah=13,在Teletype电传打字机模式下输出;al=01,字符串只含字符,启用BL属性,改变光标
 83     mov bx, 0x0007                    ; bh页码,bl前景色;bl=07,80×25字符的2色文本
 84     int 0x10                          ; 打印中断
 85     ret                               ; 函数返回
 86  
 87 ; no parameter
 88 ResetFloppy:                          ; 重置软盘段
 89     push ax
 90     push dx
 91  
 92     mov ah, 0x00                      ; 磁盘系统复位
 93     mov dl, [BS_DrvNum]               ; 驱动器号(0x00~0x7F软盘,0x80~0x0FF硬盘)
 94     int 0x13                          ; 读取磁盘的中断
 95     
 96     pop dx
 97     pop ax
 98  
 99     ret
100  
101 ; ax    --> logic sector number
102 ; cx    --> number of sector
103 ; es:bx    --> target address
104 ReadSector:                           ; 读扇区段(函数)
105     push bx                           ; 保存相关寄存器
106     push cx
107     push dx
108     push ax
109     
110     call ResetFloppy                  ; 重置软盘
111     
112     push bx
113     push cx
114  
115     mov bl, [BPB_SecPerTrk]           ; 每柱面(磁道)扇区数;本段代码用于计算柱面、磁头和扇区号以及设置驱动器号
116     div bl                            ; 除法,被除数在AX(或者DX高位+AX地位),商在AL,余数在AH
117     mov cl, ah                        ; 余数,cl记录起始扇区
118     add cl, 1                         ; FAT的扇区编号从0开始,而磁盘的扇区从1开始所以加1
119     mov ch, al                        ; 商,ch记录起始柱面(磁道)
120     shr ch, 1                         ; 右移一位表示除以2(当前软盘只有2个磁头),因为扇区编号是由两面的柱面(磁道)组合的,然后再从外往内增大;shr移位大于1时需要借助cl寄存器;
121     mov dh, al                        ; 商,dh记录起始磁头
122     and dh, 1                         ; 如果逻辑柱面(磁道)号是偶数就在0磁头,否则就是1磁头(目前只有2个磁头)
123     mov dl, [BS_DrvNum]               ; 设置驱动器号
124  
125     pop ax                            ; 还原要读取的扇区数,相当于原来的cx,因为cx是最后入栈的,这里是最先出栈
126     pop bx                            ; Buf
127  
128     mov ah, 0x02                      ; 0x02读扇区
129  
130 read:
131     int 0x13                          ; 读取磁盘的中断
132     jc read                           ; 若进位位(CF)被置位,表示调用失败,需要重新读取
133  
134     pop ax
135     pop dx
136     pop cx
137     pop bx
138  
139     ret
140  
141 MsgStr db "Hello, DTOS!"
142 MsgLen equ ($-MsgStr)
143 Buf:
144     times 510-($-$$) db 0x00
145     db 0x55, 0xaa
复制代码

 

下面的输出没有清屏,所以在最上面
通过查看boot.bin的2进制文件(以16进制查看),我们可以搜索到自己写入软盘的文本文件信息(这里是从0x75EF开始的,注意地址从0开始;我们从0x7600/0x200每扇区=59扇区处开始读)

 

第4课 – 主引导程序的扩展(上)

突破主引导程序512字节的限制
主引导程序的大小不能超过512字节(扇区一般是128×2^n字节,而n一般取2,所以一个扇区一般是512字节,扇区是硬盘的最小存储单位
主引导程序因为大小的限制只能完成最基本的必要的硬件初始化工作,然后从存储介质中加载另一个程序来完成剩余的初始化,最终启动并将控制权交给操作系统。
一般操作系统存储在文件系统格式化的分区里,所以主引导程序需要包含对应的读取文件系统的程序代码

文件系统

FAT12DOS时代的早期文件系统格式,且一直沿用于软盘
FAT12的基本组织单位有:字节、扇区、簇;扇区是磁盘的最小数据单元,而簇是由1个或多个扇区组成的最小文件存储单元
FAT12有两个表项,互为镜像(备份)
 

解决方案

使用FAT12对软盘(data.img)进行格式化
编写可执行程序(Loader)并拷贝到软盘中
主引导程序(Boot)在文件系统中查找Loader
将Loader复制到内存中并跳转到入口处执行

FAT12的主引导区

主引导区存储的比较重要的信息是文件系统的类型,文件系统逻辑扇区总数,每簇包含的扇区数,等。主引导区最后以0x55AA两个字节结尾,总共占用一个扇区
主引导区的数据构成:
 

实验结论

FreeDos中的format程序在格式化软盘的时候自动在第0扇区生成了一个主引导程序,该引导程序只打印一个字符串
文件格式(.txt)和文件系统(FAT12)都是用于定义数据如何存放的规则,只要遵循这个规则就能够成功读写目标数据
freedos镜像(这只是安装文件,freeDOS是一种早期的DOS命令行操作系统):http://www.freedos.org/download/
 
在linux里面使用bximage命令创建一张虚拟软盘
  
准备格式化软盘
    设置好bochs虚拟机的配置文件
  
使用安装好的freeDos格式化软盘
 
挂载并查看软盘,我这里因为权限问题切换到了root用户
 

代码

使用下面的代码读取主引导区的数据(使用的就是上面的软盘,把它拷贝到windows)
复制代码
// Fat12Test.cpp;Little Endian Mode(X86【INTEL、AMD】)
// 这是使用QtCreator4.5.1(Qt5.10.1)在windows8.1中的代码
#include <QtCore/QCoreApplication>
#include <QFile>
#include <QDataStream>
#include <QDebug>
 
#pragma pack(push)
#pragma pack(1)               // 注意下面的结构体字节对齐为最小字节,避免填充
 
struct Fat12Header                 // 主引导区(MBR)数据分布(第0扇区)
{
    uchar BS_JmpBoot[3];           // 跳转指令
    char BS_OEMName[8];            // OEM字符串,必须8个字符,不足填充空格
    ushort BPB_BytsPerSec;         // 每扇区字节数
    uchar BPB_SecPerClus;          // 每簇占用的扇区数
    ushort BPB_RsvdSecCnt;         // Boot占用的扇区数
    uchar BPB_NumFATs;             // FAT表的记录数
    ushort BPB_RootEntCnt;         // 最大根目录条目数
    ushort BPB_TotSec16;           // 逻辑扇区数
    uchar BPB_Media;               // 媒体描述符
    ushort BPB_FATSz16;            // 每个FAT占用扇区数
    ushort BPB_SecPerTrk;          // 每磁道扇区数
    ushort BPB_NumHeads;           // 磁头数
    uint BPB_HiddSec;              // 隐藏扇区数
    uint BPB_TotSec32;             // 如果BPB_TotSec16是0,则在这里记录扇区总数
    uchar BS_DrvNum;               // 中断13的驱动器号
    uchar BS_Reserved1;            // 未使用
    uchar BS_BootSig;              // 扩展引导标志
    uint BS_VolID;                 // 卷序列号
    char BS_VolLab[11];            // 卷标,必须11字符,不足填充空格
    char BS_FileSysType[8];        // 文件系统类型,必须8字符,不足填充空格
    // char BOOT_Code[448];        // 引导代码,由偏移0字节处的短跳而来
    // ushort END;                 // 系统引导标识(0x55AA)
};
 
#pragma pack(pop)
 
void PrintHeader(Fat12Header& rf, QString p)
{
    QFile file(p);
 
    if( file.open(QIODevice::ReadOnly) )
    {
        QDataStream in(&file);
 
        //file.seek(JMPBOOT);    // 如果Fat12Header没有BS_JmpBoot[3]
 
        in.readRawData(reinterpret_cast<char*>(&rf), sizeof(rf));
 
        rf.BS_OEMName[7] = 0;   // 通过字符串打印的要添加结束标记避免读取越界
        rf.BS_VolLab[10] = 0x0;
        rf.BS_FileSysType[7] = '\0';
 
       // 拷贝粘贴后制表符被破坏了,所以看起来不整洁,输出图是有制表符处理的
        qDebug() << "BS_JmpBoot: "     << hex << ((rf.BS_JmpBoot[0]<<16) + (rf.BS_JmpBoot[1]<<8) + (rf.BS_JmpBoot[2]));
        qDebug() << "BS_OEMName: "            << rf.BS_OEMName;
        qDebug() << "BPB_BytsPerSec: " << hex << rf.BPB_BytsPerSec;
        qDebug() << "BPB_SecPerClus: " << hex << rf.BPB_SecPerClus;
        qDebug() << "BPB_RsvdSecCnt: " << hex << rf.BPB_RsvdSecCnt;
        qDebug() << "BPB_NumFATs: "    << hex << rf.BPB_NumFATs;
        qDebug() << "BPB_RootEntCnt: " << hex << rf.BPB_RootEntCnt;
        qDebug() << "BPB_TotSec16: "   << hex << rf.BPB_TotSec16;
        qDebug() << "BPB_Media: "      << hex << rf.BPB_Media;
        qDebug() << "BPB_FATSz16: "    << hex << rf.BPB_FATSz16;
        qDebug() << "BPB_SecPerTrk: "  << hex << rf.BPB_SecPerTrk;
        qDebug() << "BPB_NumHeads: "   << hex << rf.BPB_NumHeads;
        qDebug() << "BPB_HiddSec: "    << hex << rf.BPB_HiddSec;
        qDebug() << "BPB_TotSec32: "   << hex << rf.BPB_TotSec32;
        qDebug() << "BS_DrvNum: "     << hex << rf.BS_DrvNum;
        qDebug() << "BS_Reserved1: "  << hex << rf.BS_Reserved1;
        qDebug() << "BS_BootSig: "    << hex << rf.BS_BootSig;
        qDebug() << "BS_VolID: "       << hex << rf.BS_VolID;
        qDebug() << "BS_VolLab: "             << rf.BS_VolLab;
        qDebug() << "BS_FileSysType: "        << rf.BS_FileSysType;
 
        file.seek(510);
 
        uchar b510 = 0;
        uchar b511 = 0;
 
        in.readRawData(reinterpret_cast<char*>(&b510), sizeof(b510));
        in.readRawData(reinterpret_cast<char*>(&b511), sizeof(b511));
 
        qDebug() << "Byte 510: " << hex << b510;
        qDebug() << "Byte 511: " << hex << b511;
    }
 
    file.close();
}
 
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
 
    Fat12Header f12;
 
    PrintHeader(f12, "C:\\Users\\Admin\\Desktop\\data.img");    // 也可以使用C:/Users/Admin/Desktop/data.img
 
    return a.exec();
}
复制代码
代码输出:
 
这是在linux里面使用bochs配置data.img启动的效果,最后两行文字就是软盘格式化过程添加进去的:
 
cat data.img
 
软盘主引导区数据的16进制表示,可见最后的55aa(用vim以16进制打开方式:vim data.img -b然后命令模式下输入:%!xxd即可)
 

第3课 – 调试环境的搭建

为了调试系统,我们需要一个调试环境,这里我们选择Bochs虚拟机,它支持对操作系统的调试(包括MBR等);我采用的是2.6.9版在linux上运行

Bochs

     

     

    bochs是一款专业模拟x86架构的虚拟机
    开源且高度可移植
    由C++编写
    支持操作系统开发过程中的断点调试
    通过简单配置就能运行大多数主流操作系统
    相对而然Bochs大多采用仿真方式,所以速度比较慢;virtual PC仿真量不多不少,速度剧中;vmware很少采用仿真,速度很快;
    Bochs比其它两个虚拟机更适合像操作系统这样的底层调试
    bochs网站:http://bochs.sourceforge.net/
    下载好源码后解压tar -zxf bochs-2.x.x.tar.gz -C 解压目录(不用-C参数会解压到当前目录)
    进入解压后的目录cd bochs-2.x.x
    生成makefile文件./configure –enable-debugger –enable-disasm(注意不要忘记参数,否则无法调试及反汇编,可以使用./configure –help查看,其它参数自行查找)
    编译make(好像花了一两分钟,如果提示找不到bochsdbg,就把bochs拷贝一份cp bochs bochsdbg;有些人可能没有gtk2.0包,请自行安装;可能还会有某些版本undefined reference to symbol pthread_create@GLIBC的问题,请自行百度)
    安装make install(如果你的权限不够sudo make install,或者切换到超级用户)
    安装vgabios:emerge –ask vgabios(我的是gentoo系统;对于unbuntu而然是apt-get install vgabios;RedHat是yum install vgabios;其它的自行百度)
    查询bochs安装路径:which bochs
    查询bgabios安装路径:whereis vgabios
  下面是刚解压后没有执行任何命令的文件
  

配置bochs

    bochs解压后的源码目录有一个名叫.bochsrc的隐藏的默认配置文件;截止到此,你可以删除bochs的解压文件了
        正确编写配置文件是正确运行软件的关键
        显示指定配置文件:bochs -f bochsrc_file
        隐式指定配置文件:bochs(默认会查找当前目录下的.bochsrcbochsrcbochsrc.txt
    我的配置文件名为/data/bochsrc(以后我会在/data/目录下使用隐式启动方式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
###############################################################
# Configuration file for Bochs
###############################################################
 
# how much memory the emulated machine will have
megs: 32
 
# filename of ROM images# 注意下面2个路径,他们通过上面的which、whereis查找而来
romimage: file=/usr/local/share/bochs/BIOS-bochs-latest
vgaromimage: file=/usr/share/vgabios/vgabios.bin
 
# what disk images will be used# 注意下面的路径,他是你的img软盘镜像所在
floppya: 1_44=/data/DTOS.img, status=inserted
 
# choose the boot disk.
boot: floppy
 
# where do we send log messages?
# log: bochsout.txt
 
# disable the mouse
mouse: enabled=0
 
# enable key mapping, using US layout as default.
# 注意有些电脑需要使用下面注释掉的内容,把下面第一行注释掉,第二行取消注释即可
keyboard:keymap=/usr/local/share/bochs/keymaps/x11-pc-us.map
# keyboard_mapping:enabled=1,map=/usr/local/share/bochs/keymaps/x11-pc-us.map

启动bochs

    使用命令行终端启动虚拟机
    
        如果你进入了上述界面,恭喜你,安装并配置成功。
    接下来输入6或者回车进入调试模式(如果你安装正确的话应该有调试功能)
     
 
  这时候bochs空空如也,啥也没有,因为我们开启了调试所以没有让它继续,这时候输入continue或者c即可此时输入焦点已经切换到bochs虚拟机了,需要点击命令行窗口再输入
    
    这时候虚拟机启动完毕,输出了我们的字符串”\n\nHello,DTOS!\n\n“(注意前后是有两个换行的,由于没有安装声卡,所以没有声音,你可以到vmware或者实体机上测试)
    
    这时候需要通过虚拟机右上角的关机按钮进行关机,关机后终端才会回到正常可输入状态(当然你也可以暴力点,直接在终端ctrl C等等)

Bochs常用调试命令

    
    Bochs的调试命令和GDB类似