2012年8月10日 星期五

作業系統之前的程式 (1) - c++ 篇

這是作業系統之前的程式 (0) - hello XYZ的 c++ 版本。 作業系統之前的程式 (0) - hello XYZ這是在 mosut 分享的一個主題。後來補充了 c++ 版本, 示範了預設參數的 function c++ 語言特性。

不過 c++ 主要的 class 特性若不能使用, 會使得 c++ 的能力大大失色 (和 c 相差不遠), 這次的分享就是來使用 c++ 的 class。

fig 1 在真實機器上測試

一直以來都很喜歡 c++ 這個語言, simple os 很想用 c++ 來完成, 無奈實在不知道如何實作 c++ runtime, 目前透過 ref 1 可以完成到這樣的階段 (我找到這資料時, 你可以想像到我有多興奮)。c++ 有很多豐富的特性, 有些需要 c++ runtime 來實作, 目前這版本只能做到這樣, 千萬別帥氣的把 rtti, exception 這樣的語法帶入, 肯定會失敗的, 也無法使用 global/static object。global object 還好, 很多書籍都告知要少用 global object, static object 嘛 ... 那就真的沒辦法了。

c++ 似乎帶給某些程式員效率低下的錯覺, 沒錯, 要使用某些語言特性, 像是 rtti, exception 是會有額外負擔的, 「要馬兒好又要馬兒到對方陣營偷糧草」這樣的事情是不存在的。

不過以這個例子來說, 是和 c 一樣快的。ctor/dtor 並沒有帶來效率上的額外負擔, 不過由於在 ctor 需要使用 exception handle 來處理錯誤, 所以正常的 ctor, 應該會有額外的負擔。這負擔有多大呢?我不清楚, 我還不會處理 exception handle runtime, 距離這篇文章四年之後 (真的是好久), 我寫了以下幾篇關於 exception handle 的文章:
但程式是自己寫的, 可以用某些方法迴避使用 exception handle。

-fno-exceptions 一定需要, 要不然會需要這個 symbol __gxx_personality_v0 需要 link -lstdc++(你應該不會預期可以 link stdc++ 吧?), 在 os 之前的 c++ 要使用 exception, 得自己提供這段程式碼。

喜歡語言評論的朋友可以參考:
我喜歡其中的一句話: 「由於 c++ 和 c 真的很像, 有人把 c 的坑也算到 c++頭上了」XD

和之前一樣, 列出所有檔案以其 linker script, 從程式來看就和一般 os 下的 c++ 程式一樣, 有變化的一樣是編譯流程。

io.h
 1 __asm__(".code16gcc\n");
 2 #ifndef IO_H
 3 #define IO_H
 4 
 5 class Io
 6 {
 7   public:
 8     Io();
 9     ~Io();
10     void print(const char   *s);
11   private:
12 };
13 
14 
15 #endif

io.cpp
 1 #include "io.h"
 2 Io::Io()
 3 {
 4   print("ctor\r\n");
 5 }
 6 Io::~Io()
 7 {
 8   print("dtor\r\n");
 9 }
10 
11 void Io::print(const char   *s)
12 {
13   while(*s)
14   {
15     __asm__ __volatile__ ("int  $0x10" : : "a"(0x0E00 | *s), "b"(7));
16     s++;
17   }
18 }

cppb.cpp
 1 __asm__(".code16gcc\n");
 2 #include "io.h"
 3 
 4 /*
 5  * c bootloader
 6  */
 7 
 8 //#define POINTER_TEST
 9 
10 
11 int bbb=0; // test bss section
12 
13 extern "C" void WinMain(void)
14 {
15   __asm__ ("mov  %cs, %ax\n");
16   __asm__ ("mov  %ax, %ds\n");
17   __asm__ ("mov  %ax, %ss\n");
18   __asm__ ("mov  $0xfff0, %sp\n");
19   {
20   Io io;
21   io.print("hello cpp class\r\n");
22   }
23   #if 0
24   unsigned char *vb = (unsigned char *)0xb8000;
25   *vb = 'A';
26   *(unsigned char *)0xb8001 = 0xc;
27   *(unsigned char *)0xb8002 = 'B';
28   *(unsigned char *)0xb8003 = 0x9;
29   *(unsigned char *)0xb8004 = '@';
30   *(unsigned char *)0xb8005 = 0xc;
31   #endif
32   while(1);
33 }

cpp.ld
 1 /* for cb.c */
 2 ENTRY(WinMain);
 3 SECTIONS
 4 {
 5 
 6     . = 0x7C00;
 7     .text : AT(0x7C00)
 8     {
 9         *(.text)
10         *(.gnu.linkonce.t*)
11     }
12 
13     .rodata :
14     {
15         start_ctors = .;
16         *(SORT(.ctors.*))  /* Note the "SORT" */
17         end_ctors = .;
18 
19         start_dtors = .;
20         *(SORT(.dtors.*))
21         end_dtors = .;
22 
23         *(.rodata*)
24         *(.gnu.linkonce.r*)
25     }
26 
27     .data :
28     {
29         *(.data)
30         *(.gnu.linkonce.d*)
31     }
32 
33     .bss :
34     {
35         sbss = .;
36         *(COMMON)
37         *(.bss)
38         *(.gnu.linkonce.b*)
39         ebss = .;
40     }
41 
42     .sig : AT(0x7DFE)
43     {
44         SHORT(0xaa55);
45     }
46     /DISCARD/ :
47     {
48         *(.comment)
49         *(.eh_frame) /* discard this, unless you are implementing time support for C++ exceptions. */
52     }
53 }

g++  -m32  -g -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -c -o cppb.o cppb.cpp
g++  -m32  -g -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector  -c -o io.o io.cpp
ld -m elf_i386 -static -Tcpp.ld -nostdlib -M -o cppb.elf cppb.o io.o > cb.elf.map
objcopy -R .pdr -R .comment -R.note -S -O binary cppb.elf cppb.bin

dd if=/dev/zero of=floppy.img skip=1 seek=1 bs=512 count=2879
dd if=cppb.bin of=floppy.img bs=1 count=512 conv=notrunc
qemu -fda floppy.img

除了使用 qemu 來測試, 大多數也會用實機來測試, 之前實在吃過太多苦頭, 沒有實機測試過, 真的不放心。

取得本文的程式碼:

git clone git@github.com:descent/simple_os.git
cd simple_os
git checkout origin/cpp_bootloader -b cpp_bootloader


以下是把 io object 宣告為 global 和 static 的錯誤訊息 (早就說過不行了, 還要試, A ... 理論是理論, 總是要驗證一下才是)。

global object error message
ld -m elf_i386 -static -Tcpp.ld -nostdlib -M -o cppb.elf cppb.o io.o > cb.elf.map
ld: section .sig loaded at [0000000000007dfe,0000000000007dff] overlaps section .rodata loaded at [0000000000007de4,0000000000007e11]
cppb.o: In function `__static_initialization_and_destruction_0':
/home/descent/test/simple_os.test/c-bootloader/cpp/cppb.cpp:12: undefined reference to `__dso_handle'
/home/descent/test/simple_os.test/c-bootloader/cpp/cppb.cpp:12: undefined reference to `__cxa_atexit'

static object error message
g++ -m32 -g -Wall -Wextra -Werror -nostdlib -fno-builtin -nostartfiles -nodefaultlibs -fno-exceptions -fno-rtti -fno-stack-protector -c -o cppb.o cppb.cpp
ld -m elf_i386 -static -Tcpp.ld -nostdlib -M -o cppb.elf cppb.o io.o > cb.elf.map
ld: section .sig loaded at [0000000000007dfe,0000000000007dff] overlaps section .rodata loaded at [0000000000007dd4,0000000000007e01]
cppb.o: In function `WinMain':
/home/descent/test/simple_os.test/c-bootloader/cpp/cppb.cpp:20: undefined reference to `__cxa_guard_acquire'
/home/descent/test/simple_os.test/c-bootloader/cpp/cppb.cpp:20: undefined reference to `__cxa_guard_release'
/home/descent/test/simple_os.test/c-bootloader/cpp/cppb.cpp:20: undefined reference to `__dso_handle'
/home/descent/test/simple_os.test/c-bootloader/cpp/cppb.cpp:20: undefined reference to `__cxa_atexit'
make: *** [cppb.elf] Error 1

不好搞, 沒錯吧?
ref: https://www.google.com/search?q=__cxa_atexit+osdev+c%2B%2B&ie=utf-8&oe=utf-8&client=ubuntu&channel=fs


23: 00007ca4    83 FUNC    GLOBAL DEFAULT    1 _ZN2IoC1Ev
    15: 00007c50    83 FUNC    GLOBAL DEFAULT    1 _ZN2IoC2Ev
    27: 00007d20    39 FUNC    GLOBAL DEFAULT    1 _ZN2IoD1Ev
    17: 00007cf8    39 FUNC    GLOBAL DEFAULT    1 _ZN2IoD2Ev
    26: 00007d48    68 FUNC    GLOBAL DEFAULT    1 _ZN2Io5printEPKc

紅色的 c++ mangling 意思請參考 (這也是 c++ ABI 不相容的原因之一): http://mentorembedded.github.com/cxx-abi/abi-mangling.html

對一下應該勉強看得懂。
C1, C2 是 constructor
D1, D2 是 destructor

雖然執行速度和 c 一樣, 不過大小好像比 c 大了點。ctor/dtor 竟然要兩份。不懂阿 ...

ref:
  1. http://wiki.osdev.org/C%2B%2B_Bare_Bones
  2. http://wiki.osdev.org/C%2B%2B
  3. C++ Bare Bones
  4. C++ 写内核需要注意的一些事情 (原创)

沒有留言:

張貼留言

使用 google 的 reCAPTCHA 驗證碼, 總算可以輕鬆留言了。

我實在受不了 spam 了, 又不想讓大家的眼睛花掉, 只好放棄匿名留言。這是沒辦法中的辦法了。留言的朋友需要有 google 帳號。