System Learning Daily 1

Boot Sector

引导扇区是硬盘的第一个扇区,通常大小是512Byte,里面放着的是MBR(Master Boot Record)

MBR 的主要内容

  1. 一段8086实模式代码。
  2. 四个磁盘分区条目,每个条目是16Byte,紧挨着Magic Number。
  3. 在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

/* Style one */
.type _terminal_init ,@function

/* Style two */
.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: /*(u16 row)*/
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: /*(u8 row, u8 column), return ax as pos*/
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
/*
al is row
ah is column
*/
_terminal_show: /* (u8 *str, u8 row, u8 column) the pointer is 16Bit */

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
/*
ax , index of sector to read, only 7 bit work
bx , buffer to store data
cx , times of word(2-byte) to read
*/

_disk_read:

push ax
mov al, 1
mov dx, 0x1F2
out dx, al
pop ax

mov dx, 0x1F3
out dx, al

inc dx /*0x1F4*/
mov al, 0x00
out dx, al

inc dx /*0x1F5*/
out dx, al

inc dx /*0x1F6*/
mov al, 0xE0
out dx, al

inc dx /*0x1F7*/
mov al, 0x20
out dx, al
dr_wait:
in al, dx
and al, 0x88
cmp al, 0x08
jnz dr_wait

/*cx is buffer size*/
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

/*
read the rest dummy data
if we do not do so, next time when we read,
the data would be the rest of last time.
*/

dr_read_zero:
in ax, dx
loop dr_read_zero

ret

这个是用来读磁盘的。写这个的时候就是全部采用寄存器传递参数了,参数怎么传递的也是相当的清楚。指定一提的是,每次读取数据的时候都要把整个扇区的数据全部读出来,不然没办法读别的数据,所以dr_read_zero就是空读,不写入到缓冲区里面。


总结

总结一下疑问在哪里

  1. 如何进行x86下面的过程调用?调用过程的时候如何传递数据,栈里面的数据如何安排?
  2. 分区表条目具体是怎么样的?
  3. 真实的MBR代码都做了些什么?

Reference

  1. Wikipedia, “Master boot record
  2. Website of MIT, “AT&T Syntax versus Intel Syntax”
  3. GUN AS Manual, “7.98 .type”

Update

2019-6-20
fix warnings in markdownlint.