《orang's 一个操作系统的实现》笔记一,引导扇区

时间:2022-08-28 14:25:56

在我们开机的时候,BIOS会将我们的启动设备的前512字节的代码复制到0x7c00处,这时便会执行我们的引导代码,本次的引导代码如下:

 1 .code16  #告诉编译器,这段代码将会在16位实模式下执行,如果在这里遇到对eax ebx等32位寄存器操作时,加上前缀0x66,因为16位和32位保护模式的译码方式不一样
 2 .text
 3 .globl _start #声明ld连接器入口_start
 4 _start:
 5     movw %cs,%ax #设置段寄存器
 6     movw %ax,%ds
 7     movw %ax,%es
 8     call DispStr
 9 1:
10     jmp 1b  
11     
12 DispStr:
13     movw $Strsize,%bx
14     movw (%bx),%ax     #字符串大小
15     movw %ax,%cx       
16     movw $BootMessage,%ax
17     movw %ax,%bp       #es:bp 显示字符串地址
18     movw $0x1301,%ax   #ah=0x13在teletype模式下显示字符串 al=1 显示属性在bl
19     movw $0x000c,%bx   #显示颜色,背景色
20     movw $0x0000,%dx   #dh:dl:21     int  $0x10
22     ret
23 
24 BootMessage:
25     .asciz "Hello,OS,World!" #以0结尾的字符串
26 
27 Strsize:
28     .word .-BootMessage #'.'表示当前这一行的地址
29 
30 .org 510
31 .word 0xaa55 #引导扇区标志

上面这段代码利用了BIOS提供的显示中断,这些中断是在BIOS检测阶段为我们建立的,中断向量分布在0-256*4字节处。

执行结果如下图所示:

《orang's 一个操作系统的实现》笔记一,引导扇区

下面主要说一下编译链接过程,编译和链接用了下面两条语句(编译环境centos7 64位):

as --32 -o bootsect.o bootsect.s
ld -m elf_i386 --oformat binary -Ttext=0x7c00 -o bootsect.bin bootsect.o

 

编译器用的是as,用来将汇编语言编译成带有符号可重定向的二进制文件,这种文件一般不能直接执行,下面详细的说明一下这个过程,拿我们上面的那个例子来说,编译器在编译movw $BootMessage,%ax(其中w表示传送一个字,$表示立即数,%ax表示ax寄存器)的时候,它如何知道$BootMessage这个立即数是多少呢?其实这个时候它是不知道的,那么它在编译的时候就会将这个位置记录下来,在链接器工作的时候将再将这个位置填上对应的地址,上面的ld在链接的时候有一个选项-Ttext=0x7c00,这就告诉了链接器这段程序里面的标号的偏移量要加一个0x7c00,比如说上面的程序,BootMessage相对于程序的第一句有一个偏移量,假设BootMessage前面的程序占用了0x30个字节,这个时候$BootMessage就会被$0x7c30所代替,所以如果这个地址指定错误(也可以在程序前面加ORG 0x7c00),那么程序将不会正常运行,因为程序被加载的地址就是0x7c00,这样我们的字符串地址才会刚好在0x7c30这个位置,我们可以做个实验来看看这个现象。

首先我们将链接地址指定为0x7c00,然后用反汇编查看代码,看看$BootMessage被编译成了多少,反汇编如下:

 1 [root@localhost chapter1]# objdump -D -b binary -m i8086 bootsect.bin 
 2 
 3 bootsect.bin:     文件格式 binary
 4 
 5 
 6 Disassembly of section .data:
 7 
 8 00000000 <.data>:
 9    0:    8c c8                    mov    %cs,%ax
10    2:    8e d8                    mov    %ax,%ds
11    4:    8e c0                    mov    %ax,%es
12    6:    e8 02 00                 call   0xb
13    9:    eb fe                    jmp    0x9
14    b:    bb 33 7c                 mov    $0x7c33,%bx
15    e:    8b 07                    mov    (%bx),%ax
16   10:    89 c1                    mov    %ax,%cx
17   12:    b8 23 7c                 mov    $0x7c23,%ax
18   15:    89 c5                    mov    %ax,%bp
19   17:    b8 01 13                 mov    $0x1301,%ax
20   1a:    bb 0c 00                 mov    $0xc,%bx
21   1d:    ba 00 00                 mov    $0x0,%dx
22   20:    cd 10                    int    $0x10
23   22:    c3                       ret    
24   23:    48                       dec    %ax
25   24:    65 6c                    gs insb (%dx),%es:(%di)
26   26:    6c                       insb   (%dx),%es:(%di)
27   27:    6f                       outsw  %ds:(%si),(%dx)
28   28:    2c 4f                    sub    $0x4f,%al
29   2a:    53                       push   %bx
30   2b:    2c 57                    sub    $0x57,%al
31   2d:    6f                       outsw  %ds:(%si),(%dx)
32   2e:    72 6c                    jb     0x9c
33   30:    64 21 00                 and    %ax,%fs:(%bx,%si)
34   33:    10 00                    adc    %al,(%bx,%si)
35     ...
36  1fd:    00 55 aa                 add    %dl,-0x56(%di)

 

 

 我们从上面可以看到,14行的BootMessage变成了0x7c23,我们看24行的数字0x48,正好是'H'的ASCII码,后面的就是字符串了,这段程序装到0x7c00地址处,那么字符串的地址就变成了0x7c23,显然,链接器根据我们提供的链接地址帮我们重新设置了这些标号的数值,但是如果我们修改了0x7c00,那么程序还能不能运行呢,对于mov %cs,%ax这类指令还是可以执行的,它们不包涵标号信息,还有就是相对调转指令等,这些指令在执行的时候不会直接将跳转的地址送入EIP,而是会根据信息计算一个相对于当前EIP的偏移量,这个时候这些指令就和它们所在的位置无关了(位置无关码),比如:

L:jmp L

如果我们在链接的时候指定链接地址为0x7c02的话会怎样?应该想到结果了吧,因为字符串大小也是一个标号,所以我们把程序改成下面这样:

 1 1 .code16  #告诉编译器,这段代码将会在16位实模式下执行,如果在这里遇到对eax ebx等32位寄存器操作时,加上前缀0x66,因为16位和32位保护模式的译码方式不一样
 2  2 .text
 3  3 .globl _start #声明ld连接器入口_start
 4  4 _start:
 5  5     movw %cs,%ax #设置段寄存器
 6  6     movw %ax,%ds
 7  7     movw %ax,%es
 8  8     call DispStr
 9  9 1:
10 10     jmp 1b  
11 11     
12 12 DispStr:
13 13     movw $Strsize,%bx
14 14     movw $15,%ax     #字符串大小
15 15     movw %ax,%cx       
16 16     movw $BootMessage,%ax
17 17     movw %ax,%bp       #es:bp 显示字符串地址
18 18     movw $0x1301,%ax   #ah=0x13在teletype模式下显示字符串 al=1 显示属性在bl
19 19     movw $0x000c,%bx   #显示颜色,背景色
20 20     movw $0x0000,%dx   #dh:dl:21 21     int  $0x10
22 22     ret
23 23 
24 24 BootMessage:
25 25     .asciz "Hello,OS,World!" #以0结尾的字符串
26 26 
27 27 Strsize:
28 28     .word .-BootMessage #'.'表示当前这一行的地址
29 29 30 30 .org 510 31 31 .word 0xaa55 #引导扇区标志

 

只是将movw (%bx),%ax 修改成了movw $15,%ax,编译运行如下图:

《orang's 一个操作系统的实现》笔记一,引导扇区

因为链接地址变成0x7c02,相应的BootMessage变成了0x7c25,这个地址正好是"llo,OS,World!",后面有两个奇怪的符号就是内存中未知的数据