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