Re: [分享] 打造 c++ 標準程式庫 for stm32f407

作者: descent (「雄辯是銀,沉默是金」)   2016-03-03 12:44:17
我移植到了 x86 16 bit 真實模式下, 大家懷念一下 turbo c 的 tiny mode,
huge mode XD
先把 cs, ds, es, ss 指到同一個值, 再把 sp 暫存器設定好完成 stack 設定就可以順
利完成 x86 16 bit mode 的 c runtime, 這裡都要用上組合語言, 而從這之後, 就可以
用 c 語言了, 開心吧, bss 初始化就用 c 來撰寫了, 當然也可以開始用 c++ 了。再把
read/write bios call 加入就擁有了 input/output 的能力, x86 自然是從鍵盤
input, output 到螢幕上。這樣就完成整個移植了, 說起來很簡單, 而過程就很刺激,
遇到不少問題, 紀錄如下:
x86/start.s bios_print_char
這是用組合語言寫的 function, 讓 c++ call, 用途是在螢幕上印出一個字元。return
時必須要用 retl 而不是 ret, 差個 l 差很多。我本來打算用 inline assembly 寫這個
function 的, 不過寫不出來, 只好用純組合語言寫, inline assembly 可參考《
Writing a 16-bit dummy kernel in C/C++ ( http://goo.gl/KiYdYX )》。
retl 會讓 sp + 4
ret 會讓 sp + 2
+4 才是我要的值, 這樣 stack 才不會亂掉。
在這個組合語言寫的 function 要保存 ebx/eax register, 否則以下程式碼
char cc='A';
cout << ": " << cc << endl;
會錯誤。
原因就是沒保存 ebx/eax, 我很辛苦的從組合語言去除錯, 找到問題後, 覺得自己還蠻厲
害的。這段程式碼有用到 ebx, 而印出到螢幕的程式碼也用到 ebx, 呼叫印出到螢幕的程
式碼後, ebx 就不是原來的值了, 所以造成錯誤。
縮小 heap 空間, 因為整個執行環境的記憶體只有 64 k (明明有 640k 記憶體可以用,
你有點疑惑吧! 參考: 深入 Turbo C 器 ( http://goo.gl/EqE1at ) ), 包含執行檔案的
大小, 若執行檔佔了 10k, 我只剩下 56 k 可用, 包含 bss, stack。這已經比
stm32f4discovyer 的 192k ram 還小了。
另外是 bss type 的問題, 紀錄在 linker script symbol type R_386_16, R_386_32 (
http://goo.gl/SOGmTy )
有兩個版本, 一個是透過 boot loader 載入, 一個是 dos 下的 .com 檔案。
完成後我把 simple scheme 也移植過來, 畢竟已經有了 dos 平台的標準程式庫, 照裡來
說應該要很容易, 不過只能使用 64k 的 ram, 實在太小, 我又很辛苦的縮到 64k, 才算
移植成功, 參考以下影片:
https://www.youtube.com/watch?v=T4Ng5y7nTmY
64k 實在大小, 該怎麼辦? 有一個方法是使用 big real mode, 另外一個是使用不同的節
區, 我不打算搞 big real mode 這樣的方法, 來試試看不同節區的方法吧!
把 es, ds, ss 指到另外的地方, 可以再增加 64k, 所以把 start.s (這是 x86 simple
c++ library 程式進入點) 改成以下的樣子:
mov $0x8000,%ax
mov %ax,%ds
mov %ax,%es
mov %ax, %ss
這樣就可以了嗎? 當然沒那麼簡單, 這樣是會出事的。還要把 data section 複製到
0x8000 這個 segment, ex:
0x9000:data_section_addr_begin ~ 0x9000:data_section_addr_end ->
0x8000:data_section_addr_begin ~ 0x8000:data_section_addr_end
init_data_asm 就是在做這樣的事情。為什麼我會知道呢? 因為我把反組譯的程式碼看過
後, 發現補上這樣的行為, 就可以符合 c 語言的正確行為, 否則在函式參數傳遞會有問
題, 可能會在傳遞指標時發生問題。
再來是 ctor 的位址我在 linker script 這邊沒處理好, x86_16.ld.error L37 ~ 39 會
抓到錯誤的 call_ctor address, x86_16.ld 的版本才是對的。
因為 align 的問題, ex:
00 01 02 00
本來應該要抓到 01 02, 結果抓到 00 01,
調整 linker script alignment, 就變成
01 02 00 00
就可以正確抓到 01 02。
你一定好奇我怎麼找到這問題, 使用 bochs 內建除錯器, 慢慢和組合語言搏鬥, dump 記
憶體位址來觀察, 很辛苦的。
x86_16.ld.error
1 ENTRY(begin)
2
3 SECTIONS
4 {
5 . = 0x100;
6
7 .text :
8 {
9 KEEP(*(booting))
10 KEEP(*(.isr_vector))
11 *(.text)
12 *(.text.*)
13 _etext = .;
14 _sidata = .;
15 }
16 .bss :
17 {
18 _bss = .;
19 _sbss = .;
20 *(.bss) /* Zero-filled run time allocate data memory */
21 _ebss = .;
22 }
23
24
25 .= ALIGN(32);
26
27 /* Initialized data will initially be loaded in FLASH at the end of the
.text section. */
28 /* .data : AT(0x5000) */
29 .data :
30 {
31 _data = .;
32
33 *(.ccm)
34 *(.rodata)
35 *(.rodata.*)
36 _sdata = .;
37 __start_global_ctor__ = .;
38 *(.init_array)
39 __end_global_ctor__ = .;
40
41 *(.data) /* Initialized data */
42 _edata = .;
43 }
44
45 .myheap (NOLOAD):
46 {
47 . = ALIGN(8);
48 *(.myheap)
49 . = ALIGN(8);
50 }
51
80 }
x86_16.ld
1 ENTRY(begin)
2
3 SECTIONS
4 {
5 . = 0x100;
6
7 .text :
8 {
9 KEEP(*(booting))
10 KEEP(*(.isr_vector))
11 *(.text)
12 *(.text.*)
13 _etext = .;
14 _sidata = .;
15 }
16 .bss :
17 {
18 _bss = .;
19 _sbss = .;
20 *(.bss) /* Zero-filled run time allocate data memory */
21 _ebss = .;
22 }
23
24
25 .= ALIGN(32);
26
27 /* Initialized data will initially be loaded in FLASH at the end of the
.text section. */
28 /* .data : AT(0x5000) */
29 .data :
30 {
31 _data = .;
32
33 _sdata = .;
34 __start_global_ctor__ = .;
35 *(.init_array)
36 __end_global_ctor__ = .;
37 *(.ccm)
38 *(.rodata)
39 *(.rodata.*)
40
41 *(.data) /* Initialized data */
42 _edata = .;
43 }
44
45 .myheap (NOLOAD):
46 {
47 . = ALIGN(8);
48 *(.myheap)
49 . = ALIGN(8);
50 }
51
80 }
那要怎麼像 turbo c 做到 huge mode 的記憶體模式呢? 這我就不知道了, 我不想再去研
究過時的東西了。
git url:
https://github.com/descent/simple_stdcpplib ( https://goo.gl/K5Khpe )
branch: x86_16_support_diff_segment
考古一下, 在 ms dos real mode 上, sizeof int 是 2byte, 有玩過 dos 程式設計的人
應該都知道, 不過一定都是這樣嗎?
以下 fig1, fig2 是在 dos real mode 測試 borland c++ 和 g++ sizeof int 的結果。
( https://goo.gl/hwxZGL )
fig 1 bc 31 sizeof int: 2
( https://goo.gl/cIMwkh )
fig 2 g++ sizeof int: 4
g++ 的 int 是 4 bytes, 而 borland c 是 2 bytes, 很不一樣吧?
borland c 是 dos 下開發的霸主, 很好用, gcc 比較少人使用, c 的 type 不只和 os
有關, 就算在同一個 os 下, 不同編譯器也有著不同的結果。
ref:
深入Turbo C器 ( http://goo.gl/EqE1at )
// 本文使用 Blog2BBS 自動將Blog文章轉成縮址的BBS純文字 http://goo.gl/TZ4E17 //
blog 版本:
descent-incoming.blogspot.tw/2016/02/porting-simple-c-library-x86-16bit.html

Links booklink

Contact Us: admin [ a t ] ucptt.com