1. 程式人生 > 實用技巧 >自制作業系統(6)C語言繪製介面

自制作業系統(6)C語言繪製介面

利用C語言繪製圖像介面

首先說明一個BIOS呼叫:VGA顯示卡色彩功能

mov   al,0x13h
mov   ah,0x00
int   0x10

al值的解釋:

0x03表示16色字元模式

0x12表示VGA圖形模式,640 * 480 * 4位彩色模式

0x13表示VGA圖形模式,320 * 200 * 8位彩色模式,調色盤模式

0x6a表示擴充套件VGA圖形模式,800 * 600 * 4彩色模式

我們使用的是0x13,最後的8表示色彩值是8位,也就是256種

系統視訊記憶體的地址是0x000a0000,往視訊記憶體裡面寫入資料螢幕就會發生變化

修改核心程式碼:

kernel.asm

%include "pm.inc"

org   0x9000

VRAM_ADDRESS  equ  0x000a0000 ;定義視訊記憶體開始地址

jmp   LABEL_BEGIN

[SECTION .gdt]
 ;                                  段基址          段界限                屬性
LABEL_GDT:          Descriptor        0,            0,                   0  
LABEL_DESC_CODE32:  Descriptor        0,      SegCode32Len - 1,       DA_C + DA_32
LABEL_DESC_VIDEO:   Descriptor        0B8000h,         0ffffh,            DA_DRW
LABEL_DESC_VRAM:    Descriptor        0,         0ffffffffh,            DA_DRW ;4G記憶體變成可讀寫的
LABEL_DESC_STACK:   Descriptor        0,             TopOfStack,        DA_DRWA+DA_32 ;為C語言設定的棧

GdtLen     equ    $ - LABEL_GDT
GdtPtr     dw     GdtLen - 1
           dd     0

SelectorCode32    equ   LABEL_DESC_CODE32 -  LABEL_GDT
SelectorVideo     equ   LABEL_DESC_VIDEO  -  LABEL_GDT
SelectorStack     equ   LABEL_DESC_STACK  -  LABEL_GDT
SelectorVram      equ   LABEL_DESC_VRAM   -  LABEL_GDT


[SECTION  .s16]
[BITS  16]
LABEL_BEGIN:
     mov   ax, cs
     mov   ds, ax
     mov   es, ax
     mov   ss, ax
     mov   sp, 0100h

     mov   al, 0x13 ;VGA顯示卡呼叫
     mov   ah, 0
     int   0x10

     xor   eax, eax
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_SEG_CODE32
     mov   word [LABEL_DESC_CODE32 + 2], ax
     shr   eax, 16
     mov   byte [LABEL_DESC_CODE32 + 4], al
     mov   byte [LABEL_DESC_CODE32 + 7], ah

     xor   eax, eax ;為C語言設定堆疊
     mov   ax,  cs
     shl   eax, 4
     add   eax, LABEL_STACK
     mov   word [LABEL_DESC_STACK + 2], ax
     shr   eax, 16
     mov   byte [LABEL_DESC_STACK + 4], al
     mov   byte [LABEL_DESC_STACK + 7], ah

     xor   eax, eax
     mov   ax, ds
     shl   eax, 4
     add   eax,  LABEL_GDT
     mov   dword  [GdtPtr + 2], eax

     lgdt  [GdtPtr]

     cli

     in    al,  92h
     or    al,  00000010b
     out   92h, al

     mov   eax, cr0
     or    eax , 1
     mov   cr0, eax

     jmp   dword  SelectorCode32: 0

     [SECTION .s32]
     [BITS  32]
     LABEL_SEG_CODE32: ;調到這一行就進入了保護模式
     mov  ax, SelectorStack ;初始化堆疊
     mov  ss, ax
     mov  esp, TopOfStack

     mov  ax, SelectorVram
     mov  ds,  ax

C_CODE_ENTRY:
     %include "write_vga.asm" ;引入C語言的反彙編程式碼


    io_hlt:  ;進入休眠
      HLT
      RET

SegCode32Len   equ  $ - LABEL_SEG_CODE32

[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK:
times 512  db 0 ;初始化512位元組都為0
TopOfStack  equ  $ - LABEL_STACK


相應地修改pm.inc檔案:

pm.inc

%macro Descriptor 3
    dw    %2  &  0FFFFh
    dw    %1  &  0FFFFh
    db   (%1>>16) & 0FFh
    dw   ((%2 >> 8) & 0F00h) | (%3 & 0F0FFh)
    db   (%1 >> 24) & 0FFh
%endmacro


DA_32		EQU	4000h ;32位段
DA_C		EQU	98h ;存在的只執行程式碼段屬性值
DA_DRW		EQU	92h ;存在的可讀寫資料段屬性值
DA_DRWA		EQU	93h ;存在的已訪問可讀寫資料段型別值

由於上面初始化的堆疊是512位元組,所以C語言的區域性變數最大是512位元組

這種寫法就會出錯:

void fun(){
   char buf[513]
}

這種溢位會被黑客劫持利用

看一下C語言程式碼:

write_vga.c

void CMain(void) {
    int i;
    char*p = 0;
    // 視訊記憶體的起始地址到結束地址
    for (i = 0xa0000; i <= 0xaffff; i++) {
        p = i;
        // 隨意構造一個數值進行寫入
        *p = i & 0x0f;  
    }
    for(;;) {
        // 調用匯編裡面的程式碼進入死迴圈
       io_hlt();
    }
}

反編譯C語言得到write_vga.asm

gcc -m32 -fno-asynchronous-unwind-tables -s -c -o write_vga.o write_vga.c

./objconv -fnasm ../write_vga.o -o ../write_vga.asm

最終的write_vga.asm需要修改一點:

;global CMain: function

;extern io_hlt                                           ; near


;SECTION .text   align=1 execute                         ; section number 1, code

CMain:  ; Function begin
        push    ebp                                     ; 0000 _ 55
        mov     ebp, esp                                ; 0001 _ 89. E5
        sub     esp, 24                                 ; 0003 _ 83. EC, 18
        mov     dword [ebp-10H], 0                      ; 0006 _ C7. 45, F0, 00000000
        mov     dword [ebp-0CH], 655360                 ; 000D _ C7. 45, F4, 000A0000
        jmp     ?_002                                   ; 0014 _ EB, 17

?_001:  mov     eax, dword [ebp-0CH]                    ; 0016 _ 8B. 45, F4
        mov     dword [ebp-10H], eax                    ; 0019 _ 89. 45, F0
        mov     eax, dword [ebp-0CH]                    ; 001C _ 8B. 45, F4
        and     eax, 0FH                                ; 001F _ 83. E0, 0F
        mov     edx, eax                                ; 0022 _ 89. C2
        mov     eax, dword [ebp-10H]                    ; 0024 _ 8B. 45, F0
        mov     byte [eax], dl                          ; 0027 _ 88. 10
        add     dword [ebp-0CH], 1                      ; 0029 _ 83. 45, F4, 01
?_002:  cmp     dword [ebp-0CH], 720895                 ; 002D _ 81. 7D, F4, 000AFFFF
        jle     ?_001                                   ; 0034 _ 7E, E0
?_003:  call    io_hlt                                  ; 0036 _ E8, FFFFFFFC(rel)
        jmp     ?_003                                   ; 003B _ EB, F9
; CMain End of function


;SECTION .data   align=1 noexecute                       ; section number 2, data


;SECTION .bss    align=1 noexecute                       ; section number 3, bss

然後編譯核心程式碼kernel.asm

nasm kernel.asm -o kernel.bat

nasm boot.asm -o boot.bat

Java工具類的OperatingSystem.java需要修改一部分,為了顯示出使用了幾個扇區:

    		while (in.read(buf) > 0) {
    			floppyDisk.writeFloppy(Floppy.MAGNETIC_HEAD.MAGNETIC_HEAD_0, cylinder, beginSec, buf);
    			System.out.println("Load file " + fileName + " to floppy with cylinder: " + cylinder + " and sector:" + beginSec);
    			beginSec++;
    			if (beginSec > MAX_SECTOR_NUM) {
    				beginSec = 1;
    				cylinder++;
    			}
    		}

使用Java工具類生成system.img檔案,發現列印如下:

Load file boot.bat to floppy with cylinder: 0 and sector:1
Load file kernel.bat to floppy with cylinder: 1 and sector:2
Load file kernel.bat to floppy with cylinder: 1 and sector:3

說明kernel.bat檔案寫了柱面1的兩個扇區

於是修改boot.asm的部分程式碼:

boot.asm

org  0x7c00

LOAD_ADDR  EQU  0X9000 ;這裡修改下

entry:
    mov  ax, 0
    mov  ss, ax
    mov  ds, ax
    mov  es, ax
    mov  si, ax

readFloppy:
    mov          CH, 1
    mov          DH, 0
    mov          CL, 2
    mov          BX, LOAD_ADDR
    mov          AH, 0x02
    mov          AL,  2 ;al=2表示讀取兩個扇區
    mov          DL, 0
    INT          0x13
    JC           fin
    jmp          LOAD_ADDR

fin:
    HLT
    jmp  fin

再次執行命令並生成system.img

VirtualBox成功啟動並且顯示了圖形介面