在早期的 2.6 内核的 arch/i386/boot/ 目录下还可以看到 bootsect.S 这个文件,该文件可以生成傀儡引导扇区,现在之所以称之为”傀儡引导扇区“ 是因为该引导扇区不再具有引导功能。在还用它进行引导时,该扇区占据了内核镜像 bzImage 的前 512 字节,现在这段代码仅实现了向 BIOS 中断向屏幕输出以下提示信息的功能:
![]()
正常情况下,系统不会执行该文件所生成的代码,也就是该模块不会被 BIOS 加载到段地址 0x7c00 。系统只能借助其它引导程序进行引导,如 LILO, GRUB;这些引导程序首先会将 bootsect 装载到地址 0x9000:0000 中,然后向已经复制到内存中的 bootsect 区域设置相关的参数,最终由内核识别和利用这些参数。
下面,将 bootsect.S 进行编译链接,然后把生成的二进制文件装载在一个软盘镜像文件里,并用 bochs 来调试观察其执行过程。
1. 将 bootsect.S 和 include/asm-i386/boot.h 复制到某个目录下,然后修改 bootsect.S 的 #include <asm/boot.h> 这行代码为 #include "boot.h"
2. 编写一个链接脚本文件,内容如下:
[Plain Text] 纯文本查看 复制代码 OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(_start)
SECTIONS
{
. = 0;
.boottext : { *(.boottext) }
.bootdata : { *(.bootdata) }
}
3. 编译链接[beyes@beyes bootsect]$ gcc -nostdinc -c -o bootsect.o bootsect.S
[beyes@beyes bootsect]$ ld -m elf_i386 -T boot.ld bootsect.o -o bootsect.elf
[beyes@beyes bootsect]$ objcopy -O binary bootsect.elf bootsect.bin //使用 objcopy 提取 bootsect.elf 文件中的二进制代码,并保存到 bootsect.bin 中
4. 制作软盘镜像文件:[beyes@beyes bootsect]$ dd if=/dev/zero of=floppy.img bs=1024 count=1440
1440+0 records in
1440+0 records out
1474560 bytes (1.5 MB) copied, 0.00818535 s, 180 MB/s
[root@beyes bootsect]# losetup /dev/loop1 floppy.img
[root@beyes bootsect]# cat bootsect.bin > /dev/loop1
[root@beyes bootsect]# losetup -d /dev/loop1 简单一点,也可以直接这么制作:[root@beyes bootsect]# dd if=bootsect.bin of=floppy2.img bs=1024 count=1440
0+1 records in
0+1 records out
512 bytes (512 B) copied, 3.5257e-05 s, 14.5 MB/s
5. 配置适用于 bochs 的 bxrc 配置文件(此步骤略)。
6. 使用 bochs 加载软盘镜像并调试:
在 0x7c00 处下断点,然后按下 c 并回车来到 bootsect.S 的 jmpl $BOOTSEC, $start2 这里,接下来便可以用 s 或 n 等命令对代码进行跟踪调试。
下面对 bootsect.S 代码进行注释:
[Plain Text] 纯文本查看 复制代码 .code16
.text
code16 是伪指令,告诉 gas 要生成 16位 目标代码;text 也是伪指令,告诉 gas 下面生成的代码放到指令段中,也就是上面的 ld 链接文件中的 boottext 。
[Plain Text] 纯文本查看 复制代码 .global _start
_start:
_start 是引导扇区入口地址,在这里被 .global 声明为全局变量,连接时可以被外部模块引用。
[Plain Text] 纯文本查看 复制代码 jmpl $BOOTSEG, $start2
jmpl 是长跳转指令,它不但改变指令指针寄存器 IP,还改变 CS 段寄存器。在上面的指令中,CS 会被赋值为 $BOOTSEG 所代表的地址,IP 为 start2 。
[Plain Text] 纯文本查看 复制代码 start2:
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movw $0x7c00, %sp
sti
cld
%cs=%ds=%es=%es=0x7c0
sti 指令使能响应中断
cld 指令使能 %si, %di 寄存器的自动增 1 模式(如下面使用的 lodsb 指令加载字符串时,%si 和 %di 指令能自动指向下一个字符)。
movw $bugger_off_msg, %si 将 bugger_off_msg 标号的地址放到 %si 中。
[Plain Text] 纯文本查看 复制代码 msg_loop:
lodsb
andb %al, %al
jz die
movb $0xe, %ah
movw $7, %bx
int $0x10
jmp msg_loop
这段代码不断的从 bugger_off_msg 这里读取字符,然后利用 0x10 号中断将这些字符逐个输出。其中,在使用 0x10 号中断时,ah 中存入 0xe 表示使用 ”电传打字机“ 模式,该模式在打印一个字符后,光标会自动移到下一个输出位置。
andb %al, %al 语句测试是不是读到了字符串的末位,如果是的话,程序就跳到 die 那里。
在 die 标号下看到几行语句:
[Plain Text] 纯文本查看 复制代码 xorw %ax, %ax
int $0x16
int $0x19
ljmp $0xf000,$0xfff0
其中第 0x16 号中断,在使用该中断之前,清零 %ax ,表示读取键盘输入,一旦键盘有输入,程序才能继续往下走,否则停留在 int $0x16 这里。中断 0x19 号用来重启系统。
[Plain Text] 纯文本查看 复制代码 .org 497
setup_sects: .byte SETUPSECTS
root_flags: .word ROOT_RDONLY
syssize: .word SYSSIZE
swap_dev: .word SWAP_DEV
ram_size: .word RAMDISK
vid_mode: .word SVGA_MODE
root_dev: .word ROOT_DEV
boot_flag: .word 0xAA55
.org 伪指令告诉编译器从偏移到 497 这里用 0 填充。最后的 0xAA55 是可引导扇区标记。 |