first edit: 20120506
这是在传统的 BIOS 的环境, UEFI 就不是这样了。
相关文章:
作业系统之前的程式 (1) - c++ 篇 ( http://goo.gl/HJy4jn )
作业系统之前的程式 (2) - c++ global object ctor/dtor can not be invoked (
http://goo.gl/S248QM )
这是我在 mosut ( http://goo.gl/YEfGEz ) 分享的主题。介绍一下在作业系统之前的程
式 (仅仅印出个字串)。写一个作业系统之前的程式就和在作业系统下很类似, 不过有几
点要注意。
bootloader hello world 被加载的位址是 0x7c00, 也就是从这里开始执行。
510, 511 byte 必须是 0xaa, 0x55 结尾。
不能大于 512 byte, 其实扣掉 0x11, 0x55 2 byte, 只能有 510 byte。
没有 printf 可以用, 得使用 bios call 或是写入 video ram 来完成萤幕的输出。
可以使用组合语言, 还有大家很熟悉的 c/c++ 语言来写这样的练习程式, 其他可用的语
言我就不知道了, 我只实作过这几种语言。不过使用的 toolchain 和在作业系统下有点
不同。也就是说, 程式的写法是一样的, 但是使用 compiler 产生执行档的方式是不一样
的。而使用 C 语言只是展示用, 实际上一个 bootloader 要完成的事情可能无法使用C
语言来完成, 因为很容易就超过 512 byte。
bh.asm
1 ( http://goo.gl/l2TEyL ) org 07c00h ; 告诉编译器程序加载到 7c00 处
2 ( http://goo.gl/l2TEyL ) mov ax, cs
3 ( http://goo.gl/l2TEyL ) mov ds, ax
4 ( http://goo.gl/l2TEyL ) mov es, ax
5 ( http://goo.gl/l2TEyL ) call DispStr ; 使用显示字符串例程
6 ( http://goo.gl/l2TEyL ) jmp $ ; 无限循环
7 ( http://goo.gl/l2TEyL ) DispStr:
8 ( http://goo.gl/l2TEyL ) mov ax, BootMessage
9 ( http://goo.gl/l2TEyL ) mov bp, ax ; ES:BP = 串位址
10 ( http://goo.gl/l2TEyL ) mov cx, 16 ; CX = 串长度
11 ( http://goo.gl/l2TEyL ) mov ax, 01301h ; AH = 13, AL = 01h
12 ( http://goo.gl/l2TEyL ) mov bx, 000ch ; 页号为 0(BH = 0) 黑底红字 (BL =
0Ch, 高亮)
13 ( http://goo.gl/l2TEyL ) mov dl, 0
14 ( http://goo.gl/l2TEyL ) int 10h ; 10h 号中断
15 ( http://goo.gl/l2TEyL ) ret
16 ( http://goo.gl/l2TEyL ) BootMessage: db "Hello, NASM!"
17 ( http://goo.gl/l2TEyL ) times 510-($-$$) db 0 ; 填充剩下的空间,使生成的
二进制程式码恰好为 512 字节
18 ( http://goo.gl/l2TEyL ) dw 0xaa55 ; 结束标志
bh.asm 是使用 nasm 来完成的 bootloader 程式 (from Orange's 一个作业系统的实现
( http://goo.gl/fFD7QT ) p 1-3), 范例的组合语言的是 intel 语法。使用 nasm 产生
bootloader 的可执行档很简单。
nasm bh.asm -o bh.asm.bin
bh.asm.bin 为 512 byte 的执行档, 写入软碟第 1 个磁区即可。把软碟放入开机后即可
看到 Hello, NASM!
( http://goo.gl/W1OxZx )
bh.s
1 ( http://goo.gl/l2TEyL ) .code16
2 ( http://goo.gl/l2TEyL ) .text
3 ( http://goo.gl/l2TEyL ) .global begin
4 ( http://goo.gl/l2TEyL ) begin:
5 ( http://goo.gl/l2TEyL ) mov %cs,%ax
6 ( http://goo.gl/l2TEyL ) mov %ax,%ds
7 ( http://goo.gl/l2TEyL ) mov %ax,%es
8 ( http://goo.gl/l2TEyL ) movw $0xb800, %ax
9 ( http://goo.gl/l2TEyL ) movw %ax, %gs
10 ( http://goo.gl/l2TEyL )
11 ( http://goo.gl/l2TEyL )
12 ( http://goo.gl/l2TEyL ) mov $0, %edi /* Destination */
13 ( http://goo.gl/l2TEyL ) mov $msg, %esi /* Source */
14 ( http://goo.gl/l2TEyL )
15 ( http://goo.gl/l2TEyL ) 1:
16 ( http://goo.gl/l2TEyL ) #cmp $0, %ecx
17 ( http://goo.gl/l2TEyL ) cmpb $0, (%esi)
18 ( http://goo.gl/l2TEyL ) jz 2f
19 ( http://goo.gl/l2TEyL ) movb %ds:(%esi), %al
20 ( http://goo.gl/l2TEyL ) inc %esi
21 ( http://goo.gl/l2TEyL ) movb %al, %gs:(%edi)
22 ( http://goo.gl/l2TEyL ) inc %edi
23 ( http://goo.gl/l2TEyL ) movb $0xc, %gs:(%edi)
24 ( http://goo.gl/l2TEyL ) inc %edi
25 ( http://goo.gl/l2TEyL ) dec %ecx
26 ( http://goo.gl/l2TEyL ) jmp 1b
27 ( http://goo.gl/l2TEyL ) 2:
28 ( http://goo.gl/l2TEyL ) movb $'E', %gs:(160)
29 ( http://goo.gl/l2TEyL ) jmp .
30 ( http://goo.gl/l2TEyL ) #msg:.ascii "Hello GAS"
31 ( http://goo.gl/l2TEyL ) msg:
32 ( http://goo.gl/l2TEyL ) .asciz "Hello GAS"
33 ( http://goo.gl/l2TEyL ) #.asciz "Hello World"
34 ( http://goo.gl/l2TEyL ) .org 510
35 ( http://goo.gl/l2TEyL ) .word 0xaa55
36 ( http://goo.gl/l2TEyL )
bh.s 是使用 gas 来完成的 bootloader 程式,范例的组合语言是at & t 语法, 使用
gas 产生 bootloader 的可执行档有点复杂, 除了程式码本身, 还要一个 linker
script, 在连结的时候使用。
as.ld
1 ( http://goo.gl/l2TEyL ) ENTRY(begin);
2 ( http://goo.gl/l2TEyL ) SECTIONS
3 ( http://goo.gl/l2TEyL ) {
4 ( http://goo.gl/l2TEyL ) . = 0x7C00;
5 ( http://goo.gl/l2TEyL ) .text : AT(0x7C00)
6 ( http://goo.gl/l2TEyL ) {
7 ( http://goo.gl/l2TEyL ) _text = .;
8 ( http://goo.gl/l2TEyL ) *(.text);
9 ( http://goo.gl/l2TEyL ) _text_end = .;
10 ( http://goo.gl/l2TEyL ) }
11 ( http://goo.gl/l2TEyL ) .data :
12 ( http://goo.gl/l2TEyL ) {
13 ( http://goo.gl/l2TEyL ) _data = .;
14 ( http://goo.gl/l2TEyL ) *(.bss);
15 ( http://goo.gl/l2TEyL ) *(.bss*);
16 ( http://goo.gl/l2TEyL ) *(.data);
17 ( http://goo.gl/l2TEyL ) *(.rodata*);
18 ( http://goo.gl/l2TEyL ) *(COMMON)
19 ( http://goo.gl/l2TEyL ) _data_end = .;
20 ( http://goo.gl/l2TEyL ) }
21 ( http://goo.gl/l2TEyL ) /*
22 ( http://goo.gl/l2TEyL ) .sig : AT(0x7DFE)
23 ( http://goo.gl/l2TEyL ) {
24 ( http://goo.gl/l2TEyL ) SHORT(0xaa55);
25 ( http://goo.gl/l2TEyL ) }
26 ( http://goo.gl/l2TEyL ) */
27 ( http://goo.gl/l2TEyL ) /DISCARD/ :
28 ( http://goo.gl/l2TEyL ) {
29 ( http://goo.gl/l2TEyL ) *(.note*);
30 ( http://goo.gl/l2TEyL ) *(.iplt*);
31 ( http://goo.gl/l2TEyL ) *(.igot*);
32 ( http://goo.gl/l2TEyL ) *(.rel*);
33 ( http://goo.gl/l2TEyL ) *(.comment);
34 ( http://goo.gl/l2TEyL ) /* add any unwanted sections spewed out by your
version of gcc and flags here */
35 ( http://goo.gl/l2TEyL ) }
36 ( http://goo.gl/l2TEyL ) }
产生 bootloader 执行档的步骤为:
as -o bh.s.o bh.s
ld -Tas.ld -o bh.s.elf bh.s.o
objcopy -O binary bh.s.elf bh.s.bin
( http://goo.gl/T9QT13 )
bh.s.bin 就是 bootloader 执行档, 不像 nasm 那么干脆是吧! 你一定不喜欢, 不过这
样的学习负担是有额外的收获的。下面的 C 语言版本就需要这样的作法。
cb.c
1 ( http://goo.gl/l2TEyL ) __asm__(".code16gcc\n");
2 ( http://goo.gl/l2TEyL ) /*
3 ( http://goo.gl/l2TEyL ) * c bootloader
4 ( http://goo.gl/l2TEyL ) */
5 ( http://goo.gl/l2TEyL )
6 ( http://goo.gl/l2TEyL )
7 ( http://goo.gl/l2TEyL )
8 ( http://goo.gl/l2TEyL ) void main(const char *s);
9 ( http://goo.gl/l2TEyL )
10 ( http://goo.gl/l2TEyL ) int bbb=0; // test bss section
11 ( http://goo.gl/l2TEyL )
12 ( http://goo.gl/l2TEyL ) void WinMain(void)
13 ( http://goo.gl/l2TEyL ) {
14 ( http://goo.gl/l2TEyL ) main("hello world");
15 ( http://goo.gl/l2TEyL ) while(1);
16 ( http://goo.gl/l2TEyL ) }
17 ( http://goo.gl/l2TEyL )
18 ( http://goo.gl/l2TEyL ) void main(const char *s)
19 ( http://goo.gl/l2TEyL ) {
20 ( http://goo.gl/l2TEyL ) while(*s)
21 ( http://goo.gl/l2TEyL ) {
22 ( http://goo.gl/l2TEyL ) __asm__ __volatile__ ("int $0x10" : :
"a"(0x0E00 | *s), "b"(7));
23 ( http://goo.gl/l2TEyL ) s++;
24 ( http://goo.gl/l2TEyL ) }
25 ( http://goo.gl/l2TEyL ) }
很像一般的 C 程式吧!不过我们有着 windows 才有的 WinMain 和一般 C 的 main, 哪
个才是程式开始的地方呢?仍然需要搭配的 linker script, 从以下的 linker script
可以看出, 我们的程式由 WinMain 开始。
l.ld
1 ( http://goo.gl/l2TEyL ) /* for cb.c */
2 ( http://goo.gl/l2TEyL ) ENTRY(WinMain);
3 ( http://goo.gl/l2TEyL ) SECTIONS
4 ( http://goo.gl/l2TEyL ) {
5 ( http://goo.gl/l2TEyL ) . = 0x7C00;
6 ( http://goo.gl/l2TEyL ) .text : AT(0x7C00)
7 ( http://goo.gl/l2TEyL ) {
8 ( http://goo.gl/l2TEyL ) _text = .;
9 ( http://goo.gl/l2TEyL ) *(.text);
10 ( http://goo.gl/l2TEyL ) _text_end = .;
11 ( http://goo.gl/l2TEyL ) }
12 ( http://goo.gl/l2TEyL ) .data :
13 ( http://goo.gl/l2TEyL ) {
14 ( http://goo.gl/l2TEyL ) _data = .;
15 ( http://goo.gl/l2TEyL ) *(.bss);
16 ( http://goo.gl/l2TEyL ) *(.bss*);
17 ( http://goo.gl/l2TEyL ) *(.data);
18 ( http://goo.gl/l2TEyL ) *(.rodata*);
19 ( http://goo.gl/l2TEyL ) *(COMMON)
20 ( http://goo.gl/l2TEyL ) _data_end = .;
21 ( http://goo.gl/l2TEyL ) }
22 ( http://goo.gl/l2TEyL ) .sig : AT(0x7DFE)
23 ( http://goo.gl/l2TEyL ) {
24 ( http://goo.gl/l2TEyL ) SHORT(0xaa55);
25 ( http://goo.gl/l2TEyL ) }
26 ( http://goo.gl/l2TEyL ) /DISCARD/ :
27 ( http://goo.gl/l2TEyL ) {
28 ( http://goo.gl/l2TEyL ) *(.note*);
29 ( http://goo.gl/l2TEyL ) *(.iplt*);
30 ( http://goo.gl/l2TEyL ) *(.igot*);
31 ( http://goo.gl/l2TEyL ) *(.rel*);
32 ( http://goo.gl/l2TEyL ) *(.comment);
33 ( http://goo.gl/l2TEyL ) /* add any unwanted sections spewed out by your
version of gcc and flags here */
34 ( http://goo.gl/l2TEyL ) }
35 ( http://goo.gl/l2TEyL ) }
使用以下的步骤来产生 bootloader 执行档:
gcc -fno-stack-protector -std=c99 -march=i686 -ffreestanding -Wall -c cb.c
ld -Tl.ld -nostdlib -o cb.elf cb.o
objcopy -R .pdr -R .comment -R.note -S -O binary cb.elf cb.bin
最前面提到的第一点: 加载的位址是 0x7c00, 这表示, 在程式里变量的位址要从
0x7c00 来当作计算标准。nasm 使用 org 07c00h 来达成目的。而 gas 使用 linker
script 来完成。C 版本也是一样。
as.ld L4 . = 0x7C00; 就是告诉 ld 用 0x7c00 来计算位址, 而不是从 0 开始算起。
至于第二点: nasm 使用 bh.asm L17, 18 的方式来产生 0xaa55; gas 使用 bh.s L34,
35 的方法来产生 0xaa55; C 版本则使用 linker script 来产生 0xaa55, l.ld L21 ~
24
20120611 补充 C 语言篇:
模拟器毕竟是模拟器, 在真实的机器上测试果然就有问题了, 需要加入 L14 - L17, 将
%ds, %ss, %sp 设定好才行。
cb.c
1 ( http://goo.gl/3d1tcH ) __asm__(".code16gcc\n");
2 ( http://goo.gl/3d1tcH ) /*
3 ( http://goo.gl/3d1tcH ) * c bootloader
4 ( http://goo.gl/3d1tcH ) */
5 ( http://goo.gl/3d1tcH )
6 ( http://goo.gl/3d1tcH ) //#define POINTER_TEST
7 ( http://goo.gl/3d1tcH )
8 ( http://goo.gl/3d1tcH ) void main(const char *s);
9 ( http://goo.gl/3d1tcH )
10 ( http://goo.gl/3d1tcH ) int bbb=0; // test bss section
11 ( http://goo.gl/3d1tcH )
12 ( http://goo.gl/3d1tcH ) void WinMain(void)
13 ( http://goo.gl/3d1tcH ) {
14 ( http://goo.gl/3d1tcH ) __asm__ ("mov %cs, %ax\n");
15 ( http://goo.gl/3d1tcH ) __asm__ ("mov %ax, %ds\n");
16 ( http://goo.gl/3d1tcH ) __asm__ ("mov %ax, %ss\n");
17 ( http://goo.gl/3d1tcH ) __asm__ ("mov $0xff00, %sp\n");
...
35 ( http://goo.gl/3d1tcH ) }
36 ( http://goo.gl/3d1tcH )
37 ( http://goo.gl/3d1tcH ) #ifndef POINTER_TEST
38 ( http://goo.gl/3d1tcH ) void main(const char *s)
39 ( http://goo.gl/3d1tcH ) {
40 ( http://goo.gl/3d1tcH ) while(*s)
41 ( http://goo.gl/3d1tcH ) {
42 ( http://goo.gl/3d1tcH ) __asm__ __volatile__ ("int $0x10" : : "a"(0x0E00
| *s), "b"(7));
43 ( http://goo.gl/3d1tcH ) s++;
44 ( http://goo.gl/3d1tcH ) }
45 ( http://goo.gl/3d1tcH ) }
46 ( http://goo.gl/3d1tcH ) #endif
执行画面像这样:
( http://goo.gl/8BtA2Q )
20120726 补充:
身为 c++ 爱好者, 怎么可以没有 c++ 版本, 要不是因为 c++ runtime 实在太过复杂,
只好先屈就 c runtime, 我实际上很想用 c++ 来写 os。
先来看看 c++ 版本, 不知道为什么 g++ 一定会检查 main() 的 prototype (加上
-ffreestanding 即可, 这样 g++ 就不会检查 main() prototype), 所以无法任意宣告 (
一定要 int main(int argc, char** argv)), 我只好将 main 改成 print, 我不知道有
无 option 可以关掉这种检查。其他和 c 版本差不多。
12 ( http://goo.gl/3d1tcH ) extern "C" void WinMain(void)
c++ Mangling ( http://goo.gl/blDB4P ) 的特性会把 function 重新改名, 使用 L:12
来避免这样的情形, 因为 linker script 指定 WinMain 当进入点, 要不然自己改
linker script。
用了一个 c++ 的特性, function 的参数可以有默认值, 要不然就和 c 版本没什么差别
了。
8 ( http://goo.gl/3d1tcH ) void print(const char *s, const char
*msg="\r\ng++ test");
cppb.cpp
1 ( http://goo.gl/3d1tcH ) __asm__(".code16gcc\n");
5 ( http://goo.gl/3d1tcH )
7 ( http://goo.gl/3d1tcH )
8 ( http://goo.gl/3d1tcH ) void print(const char *s, const char
*msg="\r\ng++ test");
9 ( http://goo.gl/3d1tcH )
11 ( http://goo.gl/3d1tcH )
12 ( http://goo.gl/3d1tcH ) extern "C" void WinMain(void)
13 ( http://goo.gl/3d1tcH ) {
14 ( http://goo.gl/3d1tcH ) __asm__ ("mov %cs, %ax\n");
15 ( http://goo.gl/3d1tcH ) __asm__ ("mov %ax, %ds\n");
16 ( http://goo.gl/3d1tcH ) __asm__ ("mov %ax, %ss\n");
17 ( http://goo.gl/3d1tcH ) __asm__ ("mov $0xfff0, %sp\n");
25 ( http://goo.gl/3d1tcH ) print("hello cpp");
36 ( http://goo.gl/3d1tcH ) while(1);
37 ( http://goo.gl/3d1tcH ) }
38 ( http://goo.gl/3d1tcH )
40 ( http://goo.gl/3d1tcH ) void print(const char *s, const char *msg)
41 ( http://goo.gl/3d1tcH ) {
42 ( http://goo.gl/3d1tcH ) #if 1
43 ( http://goo.gl/3d1tcH ) while(*s)
44 ( http://goo.gl/3d1tcH ) {
45 ( http://goo.gl/3d1tcH ) __asm__ __volatile__ ("int $0x10" : :
"a"(0x0E00 | *s), "b"(7));
46 ( http://goo.gl/3d1tcH ) s++;
47 ( http://goo.gl/3d1tcH ) }
48 ( http://goo.gl/3d1tcH ) #endif
49 ( http://goo.gl/3d1tcH ) #if 1
50 ( http://goo.gl/3d1tcH ) while(*msg)
51 ( http://goo.gl/3d1tcH ) {
52 ( http://goo.gl/3d1tcH ) __asm__ __volatile__ ("int $0x10" : :
"a"(0x0E00 | *msg), "b"(7));
53 ( http://goo.gl/3d1tcH ) msg++;
54 ( http://goo.gl/3d1tcH ) }
55 ( http://goo.gl/3d1tcH ) #endif
56 ( http://goo.gl/3d1tcH ) }
使用以下的步骤来产生 bootloader 执行档 (linker script 同 c 版本那个):
g++ -m32 -g -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles
-nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector -c cppb.cpp
ld -m elf_i386 -static -Tl.ld -nostdlib -M -o cppb.elf cppb.o > cb.elf.map
objcopy -R .pdr -R .comment -R.note -S -O binary cppb.elf cppb.bin
ref:
http://wiki.osdev.org/C%2b%2b_Bare_Bones ( http://goo.gl/EeqHT )
真实机器的执行画面。
( http://goo.gl/DqyqjX )
以下方式可以取得本文的程式码:
git clone git@github.com:descent/simple_os.git
cd simple_os
git checkout origin/cpp_bootloader -b cpp_bootloader
// 本文使用 Blog2BBS 自动将Blog文章转成缩址的BBS纯文字 http://goo.gl/TZ4E17 //
blog 原文:
http://descent-incoming.blogspot.tw/2012/05/0-hello-xyz.html