2016年12月14日 星期三

bss section (1)

the 1st edition: 20120229 (3)
the 2nd edition: 20161214 (3)

之前在《bss section (0)》留下來的問題要在這篇解答。

先來看看 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

之前用 gcc 4.x 好像不會產生 .eh_frame section, 這次用 gcc 5.4 就需要這些選項來避免產生 .eh_frame section。

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 區域即可。這說來簡單, 我可花了不少時間才搞清楚:
  1. selector:offset
  2. 程式整個載入位址
  3. 整個程式的定址空間
用組合語言來初始化 bss 不太好, 我後來寫了 c 語言的版本, 可攜性較高。在 OS 環境的庇護下寫程式, 真是很幸福, 不需要知道很多事情; 也很不幸, 因為有很多事情被遮蔽起來, 讓我們無法了解細節。

ref:

沒有留言:

張貼留言

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

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