2011年10月31日 星期一

c run time environment in x86 protected mode (1)

寫作業系統無可避免一定會接觸到組合語言, 但若是能將 C 語言執行環境準備好, 應該不會有人不想用 C 來繼續完成 OS。這個程式測試在 x86 保護模式中, 建立 c runtime 環境。

一開始的我很單純, 以為設定好 stack, 就可以快樂的使用 c 語言了, 而事實上, 的確也可以使用 c 語言, 但執行的結果是不是正確, 那可就不一定了。

ckmain, cckmain 是我用來模擬 C 語言轉成組合語言的部份 (gcc -S k.c 即可得到) 。和之前錯誤的版本最大不同之處是 (當然一樣要將 stack 設定好):

30
#LABEL_DESC_CODE32: Descriptor 0, (SegCode32Len - 1), (DA_C + DA_32)
31 LABEL_DESC_CODE32: Descriptor 0, 0xfffff, (DA_C + DA_32)


由於 LABEL_DESC_CODE32 這個 gdt entry 沒有包含 C function kmain 的大小, 30 那行只有設定到組合語言程式碼的大小, 沒有包含到 k.c 的 kmain function, 透過 objdump -D 5mc.elf 可以得知 kmain 會接在組合語言的後面, 自然 kmain 的部份就超過 (SegCode32Len - 1) 的長度, 造成某例外的發生, 我猜是 #GP 吧!

程式首先進入 x86 保護模式, 呼叫 c function kmain, kmain 呼叫組合語言 write_mem8 在螢幕上印出一個字元。

5M_mem_rw_mix_c.S
1 /*
2 ref: Orange'S:一个操作系统的实现
3 do the 5M memory r/w
4 link with C code, but not yet ok.
5 */
6 /* chapter3/1/loader.S
7
8 Author: Wenbo Yang <solrex@gmail.com> <http://solrex.cn>
9
10 This file is part of the source code of book "Write Your Own OS with Free
11 and Open Source Software"
. Homepage @ <http://share.solrex.cn/WriteOS/>.
12
13 This file is licensed under the GNU General Public License; either
14 version 3 of the License, or (at your option) any later version. */
15
16 #include "pm.h"
17
18 .code16
19 .text
20 jmp LABEL_BEGIN /* jump over the .data section. */
21
22 /* NOTE! Wenbo-20080512: Actually here we put the normal .data section into
23 the .code section. For application SW, it is not allowed. However, we are
24 writing an OS. That is OK. Because there is no OS to complain about
25 that behavior. :) */
26
27 /* Global Descriptor Table */
28 LABEL_GDT: Descriptor 0, 0, 0
29 LABEL_DESC_NORMAL: Descriptor 0, 0xffff, DA_DRW # Normal descriptor is for back to real mode.
30 #LABEL_DESC_CODE32: Descriptor 0, (SegCode32Len - 1), (DA_C + DA_32)
31 LABEL_DESC_CODE32: Descriptor 0, 0xfffff, (DA_C + DA_32)
32 LABEL_DESC_CODE16: Descriptor 0, 0xffff, DA_C # 非一致程式碼段, 16
33 LABEL_DESC_DATA: Descriptor 0, DataLen-1, DA_DRW # Data
34 LABEL_DESC_STACK: Descriptor 0, TopOfStack, DA_DRWA+DA_32 # Stack, 32 位
35 LABEL_DESC_TEST: Descriptor 0x500000, 0xffff, DA_DRW
36 LABEL_DESC_VIDEO: Descriptor 0xB8000, 0xffff, DA_DRW
37 LABEL_DESC_TEST_DATA: Descriptor 0, 0xffffffff, DA_DRW # Data
38
39
40 .set GdtLen, (. - LABEL_GDT) /* GDT Length */
41
42 GdtPtr: .2byte (GdtLen - 1) /* GDT Limit */
43 .4byte 0 /* GDT Base */
44
45 /* GDT Selector */
46 .set SelectorNormal, (LABEL_DESC_NORMAL - LABEL_GDT)
47 .set SelectorCode32, (LABEL_DESC_CODE32 - LABEL_GDT)
48 .set SelectorCode16, (LABEL_DESC_CODE16 - LABEL_GDT)
49 .set SelectorData, (LABEL_DESC_DATA - LABEL_GDT)
50 .set SelectorStack, (LABEL_DESC_STACK - LABEL_GDT)
51 .set SelectorTest, (LABEL_DESC_TEST - LABEL_GDT)
52 .set SelectorVideo, (LABEL_DESC_VIDEO - LABEL_GDT)
53 .set SelectorTestData, (LABEL_DESC_TEST_DATA - LABEL_GDT)
54
55 #[SECTION .data1] ; 資料段
56 #ALIGN 32
57 #[BITS 32]
58 LABEL_DATA:
59 SPValueInRealMode: .2byte 0x1234
60 # string
61 PMMessage: .ascii "In Protect Mode now. ^-^\0" # 在保護模式中顯示
62 .set OffsetPMMessage, (PMMessage - LABEL_DATA)
63 #StrTest: .ascii "B\0"
64 StrTest: .ascii "ABCDEFGHIJKLMNOPQRSTUVWXYZ\0"
65 #OffsetStrTest equ StrTest - $$
66 .set OffsetStrTest , (StrTest - LABEL_DATA)
67 #DataLen equ $ - LABEL_DATA
68 #.section .rodata
69 #.align 4
70 #.type chor_regs, @object
71 #.size chor_regs, 8
72 chor_regs:
73 .long 320
74 .long 0x5a # Z
75 var1:
76 .int 40
77 var2:
78 .int 20
79 var3:
80 .int 30
81
82 .set DataLen, . - LABEL_DATA
83 /* 32-bit global stack segment. */
84 LABEL_STACK:
85 .space 512, 0x12
86 .set TopOfStack, (. - LABEL_STACK - 1)
87
88 # END of [SECTION .data1]
89
90 /* Program starts here. */
91 LABEL_BEGIN:
92 mov %cs, %ax /* Move code segment address(CS) to data segment */
93 mov %ax, %ds /* register(DS), ES and SS. Because we have */
94 mov %ax, %es /* embedded .data section into .code section in */
95 mov %ax, %ss /* the start(mentioned in the NOTE above). */
96
97 movw $0x100, %sp
98 nop
99 movw %ax, (LABEL_GO_BACK_TO_REAL+3) # modify segment value, indexed memory mode, ref professional aeesmbly language p 102.
100
101 /* Initialize 16-bits code segment descriptor. */
102 xor %eax, %eax
103 mov %cs, %ax
104 shl $4, %eax
105 addl $(LABEL_SEG_CODE16), %eax
106 movw %ax, (LABEL_DESC_CODE16 + 2)
107 shr $16, %eax
108 movb %al, (LABEL_DESC_CODE16 + 4)
109 movb %ah, (LABEL_DESC_CODE16 + 7)
110
111 /* Initialize 32-bits code segment descriptor. */
112 xor %eax, %eax
113 mov %cs, %ax
114 shl $4, %eax
115 addl $(LABEL_SEG_CODE32), %eax
116 movw %ax, (LABEL_DESC_CODE32 + 2)
117 shr $16, %eax
118 movb %al, (LABEL_DESC_CODE32 + 4)
119 movb %ah, (LABEL_DESC_CODE32 + 7)
120
121 # initialize data segment descriptor
122 xor %eax, %eax
123 mov %ds, %ax
124 shl $4, %eax
125 addl $(LABEL_DATA), %eax
126 movw %ax, (LABEL_DESC_DATA + 2)
127 shr $16, %eax
128 movb %al, (LABEL_DESC_DATA + 4)
129 movb %ah, (LABEL_DESC_DATA + 7)
130
131 # initialize stack segment descriptor
132 xor %eax, %eax
133 mov %ds, %ax
134 shl $4, %eax
135 addl $(LABEL_STACK), %eax
136 movw %ax, (LABEL_DESC_STACK + 2)
137 shr $16, %eax
138 movb %al, (LABEL_DESC_STACK + 4)
139 movb %ah, (LABEL_DESC_STACK + 7)
140
141 /* Prepared for loading GDTR */
142 xor %eax, %eax
143 mov %ds, %ax
144 shl $4, %eax
145 add $(LABEL_GDT), %eax /* eax <- gdt base*/
146 movl %eax, (GdtPtr + 2)
147
148 /* Load GDTR(Global Descriptor Table Register) */
149 lgdtw GdtPtr
150
151 /* Clear Interrupt Flags */
152 cli
153
154 /* Open A20 line. */
155 inb $0x92, %al
156 orb $0b00000010, %al
157 outb %al, $0x92
158
159 /* Enable protect mode, PE bit of CR0. */
160 movl %cr0, %eax
161 orl $1, %eax
162 movl %eax, %cr0
163
164 /* Mixed-Size Jump. */
165 ljmp $SelectorCode32, $0 /* Thanks to earthengine@gmail, I got */
166 /* this mixed-size jump insn of gas. */
167 /* this calls far jump (ptr 16:32) in intel manual) */
168
169 LABEL_REAL_ENTRY: # 從保護模式跳回到實模式就到了這裡
170 mov %cx, %ax
171 mov %ax, %ds
172 mov %ax, %es
173 mov %ax, %ss
174
175 # mov sp, [SPValueInRealMode]
176
177 in $0x92, %al
178 and $0b11111101, %al # close A20 line
179 out %al, $0x92
180
181 sti # 開中斷
182
183 mov $0x4c00, %ax
184 int $0x21 # 回到 DOS
185 # END of .code16
186
187 LABEL_SEG_CODE32:
188 .code32
189
190 mov $(SelectorData), %ax
191 mov %ax, %ds # 資料段選擇子
192 mov $(SelectorTest), %ax
193 mov %ax, %es # 測試段選擇子
194
195
196
197
198 mov $(SelectorVideo), %ax
199 mov %ax, %gs /* Video segment selector(dest) */
200
201 mov $(SelectorStack), %ax
202 mov %ax, %ss # 堆疊段選擇子
203
204 mov $(TopOfStack), %esp
205
206 /*
207 movl $((80 * 10 + 0) * 2), %edi
208 movb $0xC, %ah # 0000: Black Back 1100: Red Front
209 movb $'P', %al
210
211 mov %ax, %gs:(%edi)
212 */
213
214 # print string "In Protect Mode now. ^-^"
215 movb $0x0c, %ah # 0000: 黑底 1100: 紅字
216 xor %esi, %esi
217 xor %edi, %edi
218 mov $(OffsetPMMessage), %esi # data string offset
219 movl $((80 * 10 + 0) * 2), %edi # 目的資料偏移。螢幕第 10 行, 第 0 列。
220 cld # Clear Direction Flag, ref: http://www.fermi.mn.it/linux/quarta/x86/cld.htm
221 # After CLD is executed, string operations will increment the index
222 # (SI and/or DI) that they use.
223 .1:
224 lodsb # For legacy mode, Load byte at address DS:(E)SI into AL.
225 # For 64-bit mode load byte at address (R)SI into AL.
226 # ref: http://siyobik.info/main/reference/instruction/LODS%2FLODSB%2FLODSW%2FLODSD%2FLODSQ
227 test %al, %al # result is 0, zf sets to 1.
228 jz .2 # zf = 1 jump
229 # mov [gs:edi], ax
230 mov %ax, %gs:(%edi)
231 add $2, %edi
232 jmp .1
233 .2: # 顯示完畢
234
235 #push %eax # Multiboot magic number
236 # push %ebx # Multiboot data structure
237
238 # movb $0xef, %al
239 # movl $66, 4(%esp)
240 # movl $2, (%esp)
241
242 call kmain
243 # call DispAL
244 # call ckmain
245 call cckmain
246 #movzbl chor_regs, %eax
247
248 # mov $(SelectorTestData), %ax
249 # mov %ax, %es
250
251 # mov $(var1), %ebx
252 # movw %es:(LABEL_DATA+100), %eax
253 #movw %ds:(SPValueInRealMode), %eax
254 #movl $(SPValueInRealMode), %eax
255 # movl (var1-LABEL_DATA), %eax
256 # call DispAL
257
258 # pushl $66
259 # pushl $2
260 # calll write_mem8
261 #call init_graph_vga
262 # movb $0xa9, %al
263 # call DispAL
264 /*
265 call DispReturn
266 #movb $0xa9, %al
267 #call DispAL
268
269 call TestRead
270 call TestWrite
271 call TestRead
272 */
273
274 ljmpl $SelectorCode16,$0
275 # jmpl $SelectorCode16,$0 # it works
276
277 # ------------------------------------------------------------------------
278 TestRead:
279 xor %esi, %esi
280 mov $8, %ecx
281 .loop:
282 mov %es:(%esi), %al
283 call DispAL
284 inc %esi
285 loop .loop
286 call DispReturn
287
288 ret
289 # TestRead 結束-----------------------------------------------------------
290
291
292 # ------------------------------------------------------------------------
293 TestWrite:
294 pushl %esi
295 pushl %edi
296 xor %esi, %esi
297 xor %edi, %edi
298 mov $(OffsetStrTest), %esi # data offset
299 cld # Clear Direction Flag, ref: http://www.fermi.mn.it/linux/quarta/x86/cld.htm
300 # After CLD is executed, string operations will increment the index
301 # (SI and/or DI) that they use.
302 .6:
303 lodsb # For legacy mode, Load byte at address DS:(E)SI into AL.
304 # For 64-bit mode load byte at address (R)SI into AL.
305 # ref: http://siyobik.info/main/reference/instruction/LODS%2FLODSB%2FLODSW%2FLODSD%2FLODSQ
306
307 test %al, %al
308 jz .5 # zf = 1 jump
309 # mov [es:edi], al
310 mov %al, %es:(%edi)
311 inc %edi
312 jmp .6
313 .5:
314
315 popl %edi
316 popl %esi
317 ret
318 # TestWrite 結束----------------------------------------------------------
319
320 # ref 30 天打造OS, page 80
321 .globl write_mem8
322 write_mem8:
323 mov 4(%esp), %ecx;
324 movb 8(%esp), %al;
325 movb %al, %gs:(%ecx)
326 ret
327
328
329 # ------------------------------------------------------------------------
330 # 顯示 AL 中的數字
331 # 默認地:
332 # 數字已經存在 AL 中
333 # edi 始終指向要顯示的下一個字元的位置
334 # 被改變的暫存器:
335 # ax, edi
336 # ------------------------------------------------------------------------
337 .globl DispAL
338 DispAL:
339 pushl %ecx
340 pushl %edx
341
342 movb $0x0c, %ah # 0000: 黑底 1100: 紅字
343 movb %al, %dl
344 shr $4, %al
345 movl $2, %ecx
346 .begin:
347 andb $0x0f, %al
348 cmp $9, %al
349 ja .3 # cf=0, zf=0, above 9 (>9)
350 #addb $'0', %al
351 addb $0x30, %al
352 jmp .4
353 .3:
354 sub $0x0A, %al
355 #add $'A', %al
356 add $0x41, %al
357 .4:
358 #mov [gs:edi], ax
359 mov %ax, %gs:(%edi)
360 add $2, %edi
361
362 mov %dl, %al
363 loop .begin
364 add $2, %edi
365
366 popl %edx
367 popl %ecx
368
369 ret
370 # DispAL 結束-------------------------------------------------------------
371
372
373 # ------------------------------------------------------------------------
374 DispReturn:
375 pushl %eax
376 pushl %ebx
377 mov %edi, %eax
378 movb $160, %bl
379 divb %bl # %eax/160, 商 al, 餘數 ah.
380 and $0x0FF, %eax
381 inc %eax # ++ %eax
382 mov $160, %bl
383 mul %bl
384 mov %eax, %edi
385 popl %ebx
386 popl %eax
387 ret
388 # DispReturn 結束---------------------------------------------------------
389
390 .globl ckmain
391 .type ckmain, @function
392 ckmain:
393 pushl %ebp
394 movl %esp, %ebp
395 subl $24, %esp
396 movl $78, 4(%esp)
397 movl $320, (%esp)
398 call write_mem8
399 leave
400 ret
401
402 .globl cckmain
403 .type cckmain, @function
404 cckmain:
405 pushl %ebp
406 movl %esp, %ebp
407 subl $24, %esp
408 movl (chor_regs+4-LABEL_DATA), %eax
409 movzbl %al, %edx
410 movl (chor_regs-LABEL_DATA), %eax
411 movl %edx, 4(%esp)
412 movl %eax, (%esp)
413 call write_mem8
414 leave
415 ret
416
417
418 /* Get the length of 32-bit segment code. */
419 .set SegCode32Len, . - LABEL_SEG_CODE32
420
421
422
423
424 LABEL_SEG_CODE16:
425 .code16
426 #jmp .
427 # back to real mode
428 mov $SelectorNormal, %ax
429 mov %ax, %ds
430 mov %ax, %es
431 mov %ax, %fs
432 mov %ax, %gs
433 mov %ax, %ss
434
435 mov %cr0, %eax
436 and $0b11111110, %al
437 mov %eax, %cr0
438
439
440 LABEL_GO_BACK_TO_REAL:
441 #.2byte 0xea66
442 #.4byte 0x00000000
443 #.2byte LABEL_REAL_ENTRY
444 jmp $0, $LABEL_REAL_ENTRY # 段位址會在程序開始處被設置成正確的值
445
446
447 .set Code16Len, . - LABEL_SEG_CODE16
448
449
450


k.c 會呼叫 write_mem8 填入 address, 以 0xb8000 為 base, address 是 offset, 在螢幕上秀出一個字元。

k.c
1 typedef char byte;
2 typedef unsigned char u8;
3 typedef unsigned short int u16;
4 typedef unsigned int u32;
5
6 void write_mem8(u32 addr, u8 data); // assembly function.
7
8
9 //void kmain( void* mbd, unsigned int magic )
10 void kmain(void)
11 {
12 #if 0
13 void DispAL(void);
14 mbd=0;
15 magic=0;
16 #if 0
17 if ( magic != 0x2BADB002 )
18 {
19 /* Something went not according to specs. Print an error */
20 /* message and halt, but do *not* rely on the multiboot */
21 /* data structure. */
22 }
23 #endif
24 /* You could either use multiboot.h */
25 /* (http://www.gnu.org/software/grub/manual/multiboot/multiboot.html#multiboot_002eh) */
26 /* or do your offsets yourself. The following is merely an example. */
27 //char * boot_loader_name =(char*) ((long*)mbd)[16];
28
29 /* Print a letter to screen to see everything is working: */
30 unsigned char *videoram = (unsigned char *) 0xb8000;
31 videoram[160] = 65; /* character 'A' */
32 videoram[161] = 0x07; /* light grey (7) on black (0). */
33 asm("movb $0xab, %al");
34 //DispAL();
35 #endif
36 //asm("movb $0xab, %al");
37 write_mem8(160*2+2, 'N');
38 //asm("movb $0xab, %al");
39
40 }

接下來的 c runtime 環境:

static array - static const u32 hor_regs[] = { 320, 'A'};
以上的 static array 會翻成以下的組合語言:

68 .section .rodata
69 .align 4
70 .type chor_regs, @object
71 .size chor_regs, 8
72 chor_regs:
73 .long 320
74 .long 0x5a # Z



37 write_mem8(160*2+2, 'N');
翻成:

402 .globl cckmain
403 .type cckmain, @function
404 cckmain:
405 pushl %ebp
406 movl %esp, %ebp
407 subl $24, %esp
408 movl (chor_regs+4), %eax
409 movzbl %al, %edx
410 movl (chor_regs), %eax
411 movl %edx, 4(%esp)
412 movl %eax, (%esp)
413 call write_mem8
414 leave
415 ret


但是我目前沒辦法處理 408, 410, 需要改成以下的寫法才可以正確處理 chor_regs 位址。

408  movl (chor_regs+4-LABEL_DATA), %eax
409 movzbl %al, %edx
410 movl (chor_regs-LABEL_DATA), %eax

所以目前在 C 語言只能使用存在 stack 的變數。

在經過幾天的測試後, 目前已經可以使用 static array。

c.env
1
2 # initialize data segment descriptor
3 # for c runtime environment, base changes from %cs:LABEL_DATA to %cs:0
4 xor %eax, %eax
5 mov %ds, %ax
6 shl $4, %eax
6.5 # addl $(LABEL_DATA), %eax
7 addl $0, %eax
8 movw %ax, (LABEL_DESC_DATA + 2)
9 shr $16, %eax
10 movb %al, (LABEL_DESC_DATA + 4)

將這個 gdt entry 的 base 設定從 %cs:LABEL_DATA (真實模式的定址法) 改成 %cs:0 (真實模式的定址法), 這樣就不用在減掉 LABEL_DATA, 用以下的程式碼就可以 access chor_regs 的 data。其他細節請參考完整 source code。

408  movl (chor_regs+4), %eax
409 movzbl %al, %edx
410 movl (chor_regs), %eax

我終於理解 30 天打造 OS 裡頭寫的:「 c 語言從來沒有直接讀寫記憶體指令」的意思。

code 11u8 *vaddr=(u8 *)(0xb8000+160);

*vaddr='E';


以上的 c code 會被 gcc 翻成
.globl cckmain
.type cckmain, @function
cckmain:
pushl %ebp
movl %esp, %ebp
subl $16, %esp
movl $753824, -4(%ebp)
movl -4(%ebp), %eax
movb $68, (%eax)
leave
ret

在 x86 保護模式的分段記憶體管理 (還沒使用分頁管理), $753824 (0xb8000+160) 並不一定就是 0xb8000+160 這個 video ram address, movb $68, (%eax) 其實可以看成 movb $68, %ds:(%eax), 而這位址其實是 base + 0xb8000+160, 若是 %ds 這個 selector 指的 gdt entry base 不是 0, 那就不會 access 0xb8000+160 這個位址。

不過和上面的 static array 好像有點衝突, 我無法讓 %ds 的 gdt entry base 指在 %cs:0 (真實模式的定址法) 又同時指在 0 (絕對位址), 所以目前 c runtime 環境無法支援 code 11 的寫法。

x86 真實模式 c runtime 版本: http://descent-incoming.blogspot.com/2011/10/build-c-runtime-environment-in-x86-real.html

c function 參數
不傳入參數
在 c function 使用自動變數
傳入參數
在 c function 使用自動變數

傳回參數

越深入越搞不定 c runtime 執行環境。

目前的困難:
同一個 c function - a , b call a ok, b call c, c 再 call a 就 fail。
array 變數傳入 function 內似乎也是錯的。

沒有留言:

張貼留言

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

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