2011年10月11日 星期二

build c runtime environment in x86 real mode by gas and gcc

test environment:
  • x86 real mode
  • gas
  • gcc

far jump and near jump.

gcc 產生的組合語言 call 指令是 far jump, 在 stack 會放入 4 byte 的返回指令,
near jump 則會在 stack 放入 2 byte 返回指令。這會造成在取 C 語言的參數時計算的 byte 數會相差 2 byte。

far jump
calll func_a
call func_a

near jump
callw func_a

我不知道原因, 這是我透過 DispAX 將 %esp 印出來觀察到的結果。之前一直 hang 在位址的計算上, 成功將 %esp 印出來, 總算找到問題。

可是很奇怪, 在 C 程式我加入了
asm(".code16gcc\n");
從 objdump -D 反組譯出來的 code 也是 callw 的指令, 可是卻要使用 4 byte 的返回指令長度來做加減, 不懂 ...

32 bit 版本只有移除 asm(".code16gcc\n");


16 bit 版本294: 66 55 push %bp
296: 66 89 e5 mov %sp,%bp
299: 66 83 ec 18 sub $0x18,%sp
29d: 67 66 c7 44 24 04 4e movw $0x4e04,0x24(%si)
2a4: 00 00 add %al,(%eax)
2a6: 00 67 66 add %ah,0x66(%edi)
2a9: c7 04 24 40 01 00 00 movl $0x140,(%esp)
2b0: 66 e8 76 ff callw 22a
2b4: ff (bad)
2b5: ff 66 c9 jmp *-0x37(%esi)
2b8: 66 c3 retw



32 bit 版本294: 55 push %ebp
295: 89 e5 mov %esp,%ebp
297: 83 ec 18 sub $0x18,%esp
29a: c7 44 24 04 4e 00 00 movl $0x4e,0x4(%esp)
2a1: 00
2a2: c7 04 24 40 01 00 00 movl $0x140,(%esp)
2a9: e8 7e ff ff ff call 22c
2ae: c9 leave
2af: c3 ret


16 bit 版本多了 0x66 這個 byte。

這個測試很簡單, 程式從組合語言開始, 設定好堆疊 (%ss, %esp) 後, 呼叫 C 語言的 funcion kmain, 再由 kmain 呼叫組合語言 function write_mem8。

write_mem8 會以 0xb8000 + offset 將字元顯示到螢幕上。
ex:

write_mem8(0, 'A') // 顯示 A 在第 0 行第 0 列
write_mem8(2, 'B') // 顯示 B 在第 0 行第 1 列

感覺很容易, 但我遇到以下難以理解的問題。

write_mem8 會從 %esp 抓出傳入的參數, 但是一旦我使用 C 語言的參數慣例, 使用 %epb 來抓取傳入的參數:

pushl %ebp
mov %esp, %ebx
程式總是會在
pushl %ebp
這裡 hang 住。

透過 kmain call write_mem8 就會有這問題, 直接在組合語言裡頭 call write_mem8 就沒事。

在花了幾小時之後, 我終於找到問題了, 因為我沒 popl %ebp, 造成 ret 將 return address 取出時, 取到錯誤的 return address。

boot_mix_c.s
1 /* chapter2/1/boot.S
2
3 Author: Wenbo Yang <solrex@gmail.com> <http://solrex.cn>
4
5 This file is part of the source code of book "Write Your Own OS with Free
6 and Open Source Software"
. Homepage @ <http://share.solrex.cn/WriteOS/>.
7
8 This file is licensed under the GNU General Public License; either
9 version 3 of the License, or (at your option) any later version. */
10
11 .code16
12 #.code16gcc
13 .text
14 LABEL_STACK:
15 #.align 8
16 .space 512, 0x12
17 .set TopOfStack, (. - LABEL_STACK - 1)
18 jmp LABEL_BEGIN /* jump over the .data section. */
19
20 LABEL_BEGIN:
21 mov %cs,%ax
22 mov %ax,%ds
23 mov %ax,%ss
24 # mov $0x0,%ax
25 mov %ax,%es
26 mov $0x0, %ebp # if don't assign %ebp to 0, in write_mem8 pushl %ebp will hang.
27 mov $(TopOfStack), %esp
28
29 mov $0xb800,%ax
30 mov %ax,%gs
31 mov $0x0,%edi
32
33 # mov (%esp), %eax
34 # call DispAX
35 # mov %esp, %eax
36 # call DispAX
37
38 # mov %cs,%ax
39 # call DispAX
40
41
42 #pushl %ebp
43 #mov %esp, %ebp
44 #mov %esp,%eax
45 #mov %cs,%eax
46
47 #call DispAX
48
49 #pushl $4 # 1 arg, address
50
51 # call DispStr
52 #call kmain
53 # movl $66, 4(%esp)
54 # movl $0, (%esp)
55
56 # mov %esp,%eax
57 # call DispAX
58 # mov %esp,%eax
59 # callw DispAX
60
61 # pushl $0x4d # 2 arg, char
62 # pushl $320 # 1 arg, address
63 # movl $66, 4(%esp)
64 # movl $2, (%esp)
65 # calll write_mem8
66 # movl $0xab, %eax
67 # mov %esp, %eax
68 # calll DispAX
69 calll kmain
70 # calll ckmain
71 # calll DispAX
72 #calll write_mem8
73 #calll write_mem8
74
75 # popl %ecx # 1 arg, addr
76 # popl %eax # 2 arg, char
77 # mov 4(%esp), %eax # 2 arg, char
78 # movl 2(%esp), %ecx # 1 arg, address
79 # mov $0xb800,%bx
80 # mov %bx,%es
81 # movb %al, %es:(%ecx)
82
83 mov $0x4c00, %ax
84 int $0x21 # 回到 DOS
85
86 #jmp .
87 write_mem16:
88 nop
89 nop
90 .globl write_mem8
91 write_mem8:
92 pushl %ebp
93 mov %esp, %ebp
94 # pushl %ecx
95 # pushl %eax
96
97 # mov (%esp),%eax
98 # callw DispAX
99
100 # push %ecx
101 # push %ebp
102 # mov %esp, %ebx
103 # mov %eax, %ebp
104
105 # mov %esp,%eax
106 # callw DispAX
107
108 # mov %esp,%eax
109 # callw DispAX
110 # subl $4, %esp
111 # mov %esp,%eax
112 # callw DispAX
113 #mov %ebp,(%esp)
114 # mov (%esp), %eax
115 # callw DispAX
116 # mov %ebp,%eax
117 # callw DispAX
118
119 # mov $0xb800,%ax
120 # mov %ax,%es
121
122 # mov 4(%esp), %ecx; # 1st arg, address
123 # movb 8(%esp), %al; # 2nd arg, char
124
125 # mov 8(%esp), %ecx; # 1st arg, address
126 # movb 12(%esp), %al; # 2nd arg, char
127
128 # movw %esp, %eax; # 2nd arg, char
129 # movb 8(%esp), %al; # 2nd arg, char
130 # call DispAX
131
132 mov 8(%ebp), %ecx; # 1nd arg, address
133 movb 12(%ebp), %al; # 2nd arg, char
134 # mov 4(%esp), %eax; # 1nd arg, address
135 # call DispAX
136
137 # mov 8(%esp), %eax; # 2nd arg
138 # call DispAX
139
140 # mov $0x2, %ecx;
141 #mov %ss:6(%esp), %eax;
142 # movb $67, %al;
143 movb %al, %gs:(%ecx)
144 # popl %ecx
145 # popl %eax
146 popl %ebp
147 ret
148
149 .globl DispAL
150 DispAL:
151 pushl %ecx
152 pushl %edx
153 pushl %eax
154
155 movb $0x0c, %ah # 0000: 黑底 1100: 紅字
156 movb %al, %dl
157 shr $4, %al
158 movl $2, %ecx
159 .begin:
160 andb $0x0f, %al
161 cmp $9, %al
162 ja .3 # cf=0, zf=0, above 9 (>9)
163 #addb $'0', %al
164 addb $0x30, %al
165 jmp .4
166 .3:
167 sub $0x0A, %al
168 #add $'A', %al
169 add $0x41, %al
170 .4:
171 #mov [gs:edi], ax
172 # mov %ax, %gs:(%edi)
173
174 movb %al, %gs:(%edi)
175
176 add $2, %edi
177
178
179
180 mov %dl, %al
181 loop .begin
182 # add $2, %edi
183
184 popl %eax
185 popl %edx
186 popl %ecx
187
188 ret
189 # DispAL 結束-------------------------------------------------------------
190 .globl DispAX
191 DispAX:
192 pushl %ebx
193 pushl %ecx
194 pushl %edx
195
196 movw %ax, %dx
197 shr $8, %ax # ah -> al
198 movl $2, %ecx
199 .b:
200 call DispAL
201 movw %dx, %ax
202 # addl $2, %edi
203 # call DispAL_m
204 # andw 0xff, %ax
205 loop .b
206
207 popl %edx
208 popl %ecx
209 popl %ebx
210
211 ret
212 # end DispAX
213 #.globl kmain
214 # .type kmain, @function
215
216 #DispStr:
217 # mov $BootMessage, %ax
218 # mov %ax,%bp
219 # mov $16,%cx
220 # mov $0x1301,%ax
221 # mov $0x00c,%bx
222 # mov $0,%dl
223 # int $0x10
224 # ret
225 .globl BootMessage
226 BootMessage:.ascii "Hello, OS wo"
227 nop
228 nop
229
230 .globl ckmain
231 # .type ckmain, @function
232 ckmain:
233 pushl %ebp
234 movl %esp, %ebp
235 # subl $24, %esp
236 movl $0x42, 4(%esp)
237 # movl %esp, %eax; # 2nd arg, char
238 # calll DispAX
239 movl $166, (%esp)
240 calll write_mem8
241 leave
242 ret
243 #.org 510
244 #.word 0xaa55
245




k.c
1 typedef char byte;
2 typedef unsigned char u8;
3 typedef unsigned short int u16;
4 typedef unsigned int u32;
5
6 asm(".code16gcc\n");
7
8 void write_mem8(u32 addr, u8 data); // assembly function.
9 void DispAL();
10 void DispAX();
11
12 //void kmain( void* mbd, unsigned int magic )
13 void kmain(void)
14 {
15 #if 1
16 int i=0;
17 u8 ch='A';
18
19 for (i=0 ; i < 160 ; i+=2)
20 {
21 write_mem8(i, ch++);
22 if (ch > 'Z')
23 ch='A';
24 }
25 #else
26 write_mem8(160*2, 'N');
27 #endif
28 // u8 b[10];
29
30 // b[0]='Z';
31 //extern u8 *BootMessage;
32
33 //asm("mov %ss, %ax");
34 //DispAX();
35 //asm("mov %esp, %eax");
36 //DispAX();
37 //asm("mov %esp, %eax");
38 //DispAX();
39
40 //*BootMessage='A';
41 #if 0
42 write_mem8(160*2, 'N');
43 write_mem8(160*3, 'Z');
44 #endif
45 }

這是指到 c function 傳入的參數,
132 mov 8(%ebp), %ecx; # 1nd arg, address
133 movb 12(%ebp), %al; # 2nd arg, char

若是 near call (callw func), 則要改成
132 mov 6(%ebp), %ecx; # 1nd arg, address
133 movb 10(%ebp), %al; # 2nd arg, char

組合語言可以使用 callw func, calll func 來決定哪一種版本, 但是 C 語言只會產生 calll 版本, 雖然 c 語言是產生 call func, 但轉成 opcode 時, 是 calll func 的意思(far call)。

x86 保護模式 c runtime 版本: http://descent-incoming.blogspot.com/2011/09/c-run-time-environment.html

x86 保護模式 c runtime 版本提到的 static array 在 real mode 上沒什麼問題, 可直接使用, 畢竟保護模式的 gdt 和 real mode 定址方式並不一樣。

沒有留言:

張貼留言

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

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