作者descent (「雄辩是银,沉默是金」)
看板C_and_CPP
标题[心得] bss
时间Wed Dec 14 20:16:28 2016
之前在《bss section (0) (
https://goo.gl/jlDiYt )》留下来的问题要在这篇解答。
先来看看 b.c, 这程式很简单, b.c L6 int i; 会放在 bss, p() 会把 c=i+1 用
itoa() 转成 string 印出。预期得到 1, 因为在 C 中, 教科书都告诉我们 int i; 虽然
没有初值, 但 c 会初始为 0, 这不是凭空得来的, 里头有魔法, 让我们来恶搞一下 bss
吧!
这是 bare-metal 程式, 和一般程式的编译以及执行方式有些不同。
b.c
1 asm(".code16gcc\n");
2
3 typedef unsigned char u8;
4 typedef unsigned int u32;
5
6 int i;
7 char* itoa(int n, char* str);
8
9 void print(const char *s)
10 {
11 while(*s)
12 {
13 __asm__ __volatile__ ("int $0x10" : : "a"(0x0E00 | *s), "b"(7));
14 s++;
15 }
16 }
17
18 int p()
19 {
20 int c=i+1;
21 char arr[6]="a";
22 char *s = arr;
23
24 //const char *str="test";
25 //volatile u8 *video_addr = (u8*)0xB8000;
26 //asm("movw $0xb000 %gs");
27 //*video_addr = 'a';
28 s = itoa(c, s);
29 print(s);
30 return c;
31 }
32
33 #if 1
34 char* itoa(int n, char* str)
35 {
36 char digit[]="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
37 char* p=str;
38 char* head=str;
39 int radix = 10;
40
41 // if(!p || radix < 2 || radix > 36)
42 // return p;
43 if (n==0)
44 {
45 *p++='0';
46 *p=0;
47 return str;
48 }
50 if (radix == 10 && n < 0)
51 {
52 *p++='-';
53 n= -n;
54 }
56 while(n)
57 {
58 *p++=digit[n%radix];
59 n/=radix;
60 }
61 *p=0;
63 for (--p; head < p ; ++head, --p)
64 {
65 char temp=*head;
66 *head=*p;
67 *p=temp;
68 }
70 return str;
71 }
72 #endif
73
c_init.s
1 .code16
2 .extern __bss_start__
3 .extern __bss_end__
4
5 .text
6 .global _start
7 _start:
8 mov %cs, %ax
9 mov %ax, %ds
10 mov %ax, %es
11
12 # setup stack
13 mov %ax, %ss
14 mov $0xffff, %sp
15
16 # call disp_str
17 call init_bss_asm
18 call p
19 # call disp_str2
20 jmp .
41
42
43 # init bss
44 init_bss_asm:
45 movw $__bss_end__, %di /* Destination */
46 movw $__bss_start__, %si /* Source */
47 movw $0x0, %ax
48 movw %ax, %gs
49 jmp 2f
50 1:
51 movw %si, %ax
52 movb $0x1, %gs:(%eax)
53 add $1, %si
54
55 2:
56 cmpw %di, %si
57 jne 1b
58 ret
59
编译/执行方式
as --32 -o c_init.o c_init.s
gcc -std=c99 -fno-stack-protector -m32 -ffreestanding -fno-builtin -g
-Iinclude -I../include -fomit-frame-pointer -fno-exceptions
-fno-asynchronous-unwind-tables -fno-unwind-tables -Os -c b.c
ld -m elf_i386 -nostdlib -g -o c_init.elf -Tbss.lds c_init.o b.o
objcopy -R .pdr -R .comment -R.note -S -O binary c_init.elf c_init.bin
c_init.bin 为要执行的 bin 档
dd if=c_init.bin of=boot.img bs=512 count=1
dd if=/dev/zero of=boot.img skip=1 seek=1 bs=512 count=2879
透过 qemu 执行
qemu-system-i386 -fda boot.img
由 c_init.s _start 开始执行, 执行 init_bss_asm 呼叫 p(), p() 把 i+1 印出来。i
位於 bss, 所以应该看到 1 被印出来 (fig 1), 不过执行结果是 16843010, 怎麽不是
1, 我又搞错了吗? 非也, 这是刻意营造的结果, 我把 bss 初始为 0x01010101 , 加上
0x1 後为 0x01010102 = 十进位 16843010。 因为受限於开机磁区的 512 byte 限制, 所
以编译这段程式码需要加上 -Os, 让它可以小於 512 byte。
我加入以下选项, 让 gcc 不要产生 .eh_frame section, .eh_frame section 对
bare-metal 用处不大, 还占了 0x8c 的空间。
-fomit-frame-pointer -fno-exceptions -fno-asynchronous-unwind-tables
-fno-unwind-tables
.eh_frame, .eh_frame_hdr -
ref:how to remove that trash (
https://goo.gl/MLj0ql )
之前用 gcc 4.x 好像不会产生 .eh_frame section, 这次用 gcc 5.4 就需要这些选项来
避免产生 .eh_frame section。
(
https://goo.gl/pxndrD )
fig 1
初始化 bss, c_init.s L17 call init_bss_asm 把 0x1 copy 到这个 bss 区域
(c_init.s L52), bss 开头和结束可由 Linker Scripts 得知, bss.lds L18, L23 可以
看到 __bss_start__, __bss_end__, 这就是 c_init.s 用到的那两个变数, 神奇吧!「
还可以这样用的哦!」gnu toolchain 实在太 ... 厉害了。当然在 Linker Scripts 直
接指定一个位址也可以, 不过这样用比较帅 (帅阿! 老皮), 也比较方便。
bss 区的变数不会存放在执行档里, 需要靠 c runtime library 来初始化, 我们能痛快
的使用这些变数, 便是这些初始化程式码的功劳。
没有初始值或是初始值为 0 的 static, global 变数, 就是放在 bss。
bss.lds
1 ENTRY(_start)
2
3 SECTIONS
4 {
5 . = 0x7c00;
6 .text :
7 {
8 *(.text)
9 }
10 .= ALIGN(32);
11
12 .data :
13 {
14 *(.data)
15 }
16
17 .= ALIGN(32);
18 __bss_start__ =.;
19 .bss :
20 {
21 *(.bss)
22 }
23 __bss_end__ = .;
24
25 .sig : AT(0x7DFE)
26 {
27 SHORT(0xaa55);
28 }
29 /*
30 .asig : AT(0x7e50)
31 {
32 SHORT(0xefab);
33 }
34 .bsig : AT(0x7f50)
35 {
36 SHORT(0xefab);
37 }
38 */
39 /DISCARD/ :
40 {
41 *(.note*);
42 *(.iplt*);
43 *(.igot*);
44 *(.rel*);
45 *(.comment);
46 /* add any unwanted sections spewed out by your version of gcc and flags
here */
47 }
48
49 }
list 5 L88 i 为於 0x7d60, 而 list 5 L27 指出的 .bss 从 0x7d60 开始, 总共 4
byte, 刚好就是 i 的位址。
0x7d60 位於 .bss 区, 长度4 byte, 自然就是 int i 占据的大小。若你不相信, 把
bochs run 一次, 反组译後就可得证。
list 5. readelf -a c_init.elf
1 ELF Header:
2 Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
3 Class: ELF32
4 Data: 2's complement, little endian
5 Version: 1 (current)
6 OS/ABI: UNIX - System V
7 ABI Version: 0
8 Type: EXEC (Executable file)
9 Machine: Intel 80386
10 Version: 0x1
11 Entry point address: 0x7c00
12 Start of program headers: 52 (bytes into file)
13 Start of section headers: 5692 (bytes into file)
14 Flags: 0x0
15 Size of this header: 52 (bytes)
16 Size of program headers: 32 (bytes)
17 Number of program headers: 3
18 Size of section headers: 40 (bytes)
19 Number of section headers: 16
20 Section header string table index: 13
21
22 Section Headers:
23 [Nr] Name Type Addr Off Size ES Flg Lk Inf Al
24 [ 0] NULL 00000000 000000 000000 00 0 0 0
25 [ 1] .text PROGBITS 00007c00 000c00 000138 00 AX 0 0 1
26 [ 2] .rodata.str1.1 PROGBITS 00007d38 000d38 000025 01 AMS 0 0 1
27 [ 3] .bss NOBITS 00007d60 000d5d 000004 00 WA 0 0 4
28 [ 4] .sig PROGBITS 00007d64 000d64 000002 00 WA 0 0 1
29 [ 5] .debug_info PROGBITS 00000000 000d66 00017e 00 0 0 1
30 [ 6] .debug_abbrev PROGBITS 00000000 000ee4 000128 00 0 0 1
31 [ 7] .debug_loc PROGBITS 00000000 00100c 000162 00 0 0 1
32 [ 8] .debug_aranges PROGBITS 00000000 00116e 000020 00 0 0 1
33 [ 9] .debug_ranges PROGBITS 00000000 00118e 000018 00 0 0 1
34 [10] .debug_line PROGBITS 00000000 0011a6 00007c 00 0 0 1
35 [11] .debug_str PROGBITS 00000000 001222 000131 01 MS 0 0 1
36 [12] .debug_frame PROGBITS 00000000 001354 00008c 00 0 0 4
37 [13] .shstrtab STRTAB 00000000 00159b 0000a0 00 0 0 1
38 [14] .symtab SYMTAB 00000000 0013e0 000170 10 15 16 4
39 [15] .strtab STRTAB 00000000 001550 00004b 00 0 0 1
40 Key to Flags:
41 W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
42 L (link order), O (extra OS processing required), G (group), T (TLS),
43 C (compressed), x (unknown), o (OS specific), E (exclude),
44 p (processor specific)
45
46 There are no section groups in this file.
47
48 Program Headers:
49 Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
50 LOAD 0x000000 0x00007000 0x00007000 0x00d5d 0x00d64 RWE 0x1000
51 LOAD 0x000d64 0x00007d64 0x00007dfe 0x00002 0x00002 RW 0x1000
52 GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
53
54 Section to Segment mapping:
55 Segment Sections...
56 00 .text .rodata.str1.1 .bss
57 01 .sig
58 02
59
60 There is no dynamic section in this file.
61
62 There are no relocations in this file.
63
64 The decoding of unwind sections for machine type Intel 80386 is not
currently supported.
65
66 Symbol table '.symtab' contains 23 entries:
67 Num: Value Size Type Bind Vis Ndx Name
68 0: 00000000 0 NOTYPE LOCAL DEFAULT UND
69 1: 00007c00 0 SECTION LOCAL DEFAULT 1
70 2: 00007d38 0 SECTION LOCAL DEFAULT 2
71 3: 00007d60 0 SECTION LOCAL DEFAULT 3
72 4: 00007d64 0 SECTION LOCAL DEFAULT 4
73 5: 00000000 0 SECTION LOCAL DEFAULT 5
74 6: 00000000 0 SECTION LOCAL DEFAULT 6
75 7: 00000000 0 SECTION LOCAL DEFAULT 7
76 8: 00000000 0 SECTION LOCAL DEFAULT 8
77 9: 00000000 0 SECTION LOCAL DEFAULT 9
78 10: 00000000 0 SECTION LOCAL DEFAULT 10
79 11: 00000000 0 SECTION LOCAL DEFAULT 11
80 12: 00000000 0 SECTION LOCAL DEFAULT 12
81 13: 00000000 0 FILE LOCAL DEFAULT ABS c_init.o
82 14: 00007c13 0 NOTYPE LOCAL DEFAULT 1 init_bss_asm
83 15: 00000000 0 FILE LOCAL DEFAULT ABS b.c
84 16: 00007c2f 36 FUNC GLOBAL DEFAULT 1 print
85 17: 00007cf5 67 FUNC GLOBAL DEFAULT 1 p
86 18: 00007d60 0 NOTYPE GLOBAL DEFAULT 3 __bss_start__
87 19: 00007c53 162 FUNC GLOBAL DEFAULT 1 itoa
88 20: 00007d60 4 OBJECT GLOBAL DEFAULT 3 i
89 21: 00007d64 0 NOTYPE GLOBAL DEFAULT 3 __bss_end__
90 22: 00007c00 0 NOTYPE GLOBAL DEFAULT 1 _start
91
92 No version information found in this file.
以上为 x86 真实模式下的 bss 环境。那麽在 x86 保护模式下呢? 原理是一样的, 就算
是 arm 也是一样, 只是切入保护模式後 segment register 要改用 selector, 确认使用
的 selector:offset 是 bss 区域即可。这说来简单, 我可花了不少时间才搞清楚:
selector:offset
程式整个载入位址
整个程式的定址空间
用组合语言来初始化 bss 不太好, 我後来写了 c 语言的版本, 可携性较高。在 OS 环境
的庇护下写程式, 真是很幸福, 不需要知道很多事情; 也很不幸, 因为有很多事情被遮蔽
起来, 让我们无法了解细节。
ref:
linux c 编程 - 一站式学习 (
https://goo.gl/idfRnI ) (p 237)
一步步写嵌入式操作系统 - arm 编程的方法与实践 (
https://goo.gl/SGSRnd ) (p 50)
// 本文使用 Blog2BBS 自动将Blog文章转成缩址的BBS纯文字
http://goo.gl/TZ4E17 //
blog 原文
https://goo.gl/Yr0rdc
--
纸上得来终觉浅,绝知此事要躬行。
--
※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 61.218.53.138
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/C_and_CPP/M.1481717799.A.5B7.html
※ 编辑: descent (61.218.53.138), 12/14/2016 20:16:49
1F:推 wtchen: 请问一下,上次看了你用sd开机的code,请问你SDRAM 12/14 20:20
2F:→ wtchen: initialization的部份放在哪?不然要怎麽从sd载入到SDRAM 12/14 20:21
3F:→ descent: stm32用sram,不需要初始化就可以使用 12/14 21:42
4F:→ wtchen: 感谢 12/14 22:01
5F:推 art1: 程式没有眼睛,请问要怎麽看见全域变数或看不见区域变数? 12/14 22:30
不好意思, 不太懂你的问题?
※ 编辑: descent (180.204.64.97), 01/17/2017 21:36:52