Boot Sector 引导扇区是硬盘的第一个扇区,通常大小是512Byte,里面放着的是MBR(Master Boot Record)
MBR 的主要内容
一段8086实模式代码。
四个磁盘分区条目,每个条目是16Byte,紧挨着Magic Number。
在512Byte的末尾处(也就是在510Byte和511Byte的位置)有2个Byte作为Magic Number,分别是0x55、0xAA。
实际上现在的MBR有很多不同的标准,具体参考Wiki[1]。最简单也是最通用的就是上面这样的。
昨天主要写了一下16位实模式下面的引导程序代码。主要是把相关的字符模式下显卡的显示驱动给写好。至少能够在Qemu启动的时候,看到屏幕上面出现了Hello World。没有太多的了解磁盘分区条目。因为暂时没有用到,Magic Number怎么放入在上一篇文章里面已经贴出来LD脚本了,就不重复了。
Markdown似乎不支持汇编代码的高亮,看着白板总是觉得怪怪的,所以就随便用了C++的高亮。
具体代码如下:
相关汇编指令和常量定义 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 .intel_syntax noprefix .code16 .set MAGIC1, 0x55 .set MAGIC2, 0xAA .set DISRAM, 0xB800 .set FILLCHAR, ' ' .set DEFCOLOR, 0x07 .set HEIGHT, 25 .set WIDTH, 80 .set INITSEG, 0x7C0 .section .mbrcheck .byte MAGIC1 .byte MAGIC2 .section .data _buffer: .skip 0x20 , 0x00 .long 0xDEADBEFF _msg: .ascii "Hello World\0" .section .stack .skip 0x20 , 0x00
.intel_syntax noprefix是用来指定,下面的代码使用Intel Syntax,也有相关指令可以切换回AT&T Syntax。[2]
.code16是用来指定我们下面的代码是8086实模式的代码。
_terminal_init 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .global _terminal_init .type _terminal_init STT_FUNC _terminal_init: mov ax, DISRAM mov es, ax mov ax, 0 mov cx, HEIGHT ti_s0: push cx push ax call _terminal_clearline pop ax pop cx inc ax loop ti_s0 ret
写的是init,实际上这玩意是清除整个屏幕用的。
1 2 3 4 5 push cx push ax call _terminal_clearline pop ax pop cx
push cx是为了保存当前cx的数值,push ax是为了穿参数,ax指定了清除的行号(0开始)。_terminal_clearline写得有问题,传参数非得用栈不用寄存器。
1 2 3 4 5 6 7 .type _terminal_init ,@function .type _terminal_init STT_FUNC
这里其实第一种和第二种的写法都可以,不过第二种更好一点,这一点在GAS的手册里面有讲到[3]。
_terminal_clearline 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 .global _terminal_clearline .type _terminal_clearline STT_FUNC _terminal_clearline: mov bx, sp mov bl, ss:[bx + 2 ] mov al, WIDTH * 2 mul bl mov bx, ax mov cx, WIDTH mov al, FILLCHAR mov ah, DEFCOLOR tcl_s0: mov es:[bx], ax add bx, 2 loop tcl_s0 ret
这个没啥好说的,接受紧挨着IP下面的16位数据作为行号,其实应该寄存器传参的。
_terminal_setpos 1 2 3 4 5 6 7 8 9 10 11 12 .global _termianl_setpos .type _termianl_setpos STT_FUNC _termianl_setpos: mov bx, sp mov dh, 0 mov dl, ss:[bx + 3 ] add dl, dl mov bl, ss:[bx + 2 ] mov al, WIDTH * 2 mul bl add ax, dx ret
这个函数是用来设置输出字符的位置的,返回的ax的值就是合适位置的显存地址,可以直接mov bx, ax然后顺序写入。栈里面的的16位数据,低八位是行,高八位数据是列。这个地方注释也有问题,没有说清楚具体的参数位置。
_terminal_show 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 28 29 30 31 .global _terminal_show .type _terminal_show STT_FUNC _terminal_show: mov bx, sp mov di, ss:[bx + 2 ] mov ax, ss:[bx + 4 ] push ax call _termianl_setpos pop dx mov dx, ax mov bx, 0 mov ax, DISRAM mov es, ax mov ah, DEFCOLOR show_s0: cmp byte ptr ds:[bx + di], 0 je show_s1 mov al, ds:[bx + di] mov si, bx mov bp, bx add bp, dx mov es:[bp + si], ax inc bx jmp show_s0 show_s1: ret
读取数据显示出来。存在的问题也还是注释也不对。参数怎么传递也是没讲清楚。
_disk_read 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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 .global _disk_read .type _disk_read STT_FUNC _disk_read: push ax mov al, 1 mov dx, 0x1F2 out dx, al pop ax mov dx, 0x1F3 out dx, al inc dx mov al, 0x00 out dx, al inc dx out dx, al inc dx mov al, 0xE0 out dx, al inc dx mov al, 0x20 out dx, al dr_wait: in al, dx and al, 0x88 cmp al, 0x08 jnz dr_wait push cx mov dx, 0x1F0 dr_read: in ax, dx mov ds:[bx], ax add bx, 2 loop dr_read pop ax mov cx, 512 sub cx, ax dr_read_zero: in ax, dx loop dr_read_zero ret
这个是用来读磁盘的。写这个的时候就是全部采用寄存器传递参数了,参数怎么传递的也是相当的清楚。指定一提的是,每次读取数据的时候都要把整个扇区的数据全部读出来,不然没办法读别的数据,所以dr_read_zero就是空读,不写入到缓冲区里面。
总结 总结一下疑问在哪里
如何进行x86下面的过程调用?调用过程的时候如何传递数据,栈里面的数据如何安排?
分区表条目具体是怎么样的?
真实的MBR代码都做了些什么?
Reference
Wikipedia, “Master boot record
Website of MIT, “AT&T Syntax versus Intel Syntax”
GUN AS Manual, “7.98 .type”
Update
2019-6-20 fix warnings in markdownlint.