幫助發現bug
CPU的例外處理功能,還能幫助我們發現bug,比如下面的例子:
bug1.c:
void api_putchar(int c);
void api_end(void);
void HariMain(void)
{
char a[100];
a[10] = 'A'; /* 這句沒有問題 */
api_putchar(a[10]);
a[102] = 'B'; /* 這句有問題 */
api_putchar(a[102]);
a[123] = 'C'; /* 這句也有問題 */
api_putchar(a[123]);
api_end();
}
由于陣列保存在堆疊中,陣列越界會產生堆疊例外,我們撰寫一個函式來處理堆疊例外,堆疊例外的中斷號為0x0c,CPU說明書中,從0x00到0x1都是例外所使用的中斷,IRQ的中斷號都是從0x20之后開始的,其中0x00號是除零例外,當試圖除以0時產生;0x06號是非法指令例外,當試圖執行CPU無法理解的機器語言指令時產生,
naskfunc.nas
_asm_inthandler0c:
STI
PUSH ES
PUSH DS
PUSHAD
MOV EAX,ESP
PUSH EAX
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler0c
CMP EAX,0
JNE end_app
POP EAX
POPAD
POP DS
POP ES
ADD ESP,4 ; 在INT 0x0c 中也需要這句
IRETD
發生例外時,應該能顯示引發例外的指令地址,我們在console.c程式中加上這個功能,
console.c節選:
int *inthandler0c(int *esp)
{
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct TASK *task = task_now();
char s[30];
cons_putstr0(cons, "\nINT 0C :\n Stack Exception.\n");
sprintf(s, "EIP = %08X\n", esp[11]);
cons_putstr0(cons, s);
return &(task->tss.esp0); /* 強制結束程式 */
}
int *inthandler0d(int *esp)
{
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct TASK *task = task_now();
char s[30];
cons_putstr0(cons, "\nINT 0D :\n General Protected Exception.\n");
sprintf(s, "EIP = %08X\n", esp[11]); /* 顯示esp(即堆疊)的11號元素EIP */
cons_putstr0(cons, s);
return &(task->tss.esp0); /* 強制結束程式 */
}
執行make run ,看一下效果:

強制結束應用程式
有這樣一類應用程式,雖然執行內容沒有錯誤,但是會一直執行,比如死回圈,這樣的應用程式會一直占用CPU資源,系統的整體速度就會變慢,
bug2.c:
void HariMain(void)
{
for (;;) { }
}
因此我們設定一個強制結束鍵,按下強制結束鍵就可以結束程式,這里設定為按下“Shift + F1”結束,
bootpack.c節選
if (i == 256 + 0x3b && key_shift != 0 && task_cons->tss.ss0 != 0) { /* 判斷按下的是否是 Shift+F1 */
cons = (struct CONSOLE *) *((int *) 0x0fec);
cons_putstr0(cons, "\nBreak(key) :\n");
io_cli(); /* 不能在改變暫存器值時切換到其他任務 */
task_cons->tss.eax = (int) &(task_cons->tss.esp0);
task_cons->tss.eip = (int) asm_end_app;
io_sti();
}
make run 一下,執行bug2命令執行死回圈,按下"Shift+F1"跳出回圈,

用C語言顯示字串
現在我們來實作用C語言顯示字串,先來寫一個顯示字串的API,
a_nask.nas節選:
_api_putstr0: ; void api_putstr0(char *s);
PUSH EBX
MOV EDX,2
MOV EBX,[ESP+8] ; s
INT 0x40
POP EBX
RET
我們再寫一個C語言程式呼叫上面的API:
void api_putstr0(char *s);
void api_end(void);
void HariMain(void)
{
api_putstr0("hello, mint's world\n");
api_end();
}
這里要顯示完整的字串,有個地方需要注意,bim2hrb 生成的 .hrb檔案 包括兩個部分,代碼部分和資料部分,
之前程式中沒有使用字串和外部變數,生成的.hrb檔案中不包含資料部分,我們在啟動程式時,需要先指定資料段的大小,將資料部分復制到資料段中,再啟動應用程式,

根據hello4.hrb檔案存放的內容,我們來修改console.c檔案,
console.c節選:
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
int i, segsiz, datsiz, esp, dathrb;
(略)
if (finfo != 0) {
/* 找到檔案的情況 */
p = (char *) memman_alloc_4k(memman, finfo->size);
file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
segsiz = *((int *) (p + 0x0000)); /* 0x0000地址存放為應用程式準備的資料段大小 */
esp = *((int *) (p + 0x000c)); /* 0x000c地址存放ESP初始值和資料部分傳送目的地址 */
datsiz = *((int *) (p + 0x0010)); /* 0x0010地址存放hrb檔案資料部分的大小 */
dathrb = *((int *) (p + 0x0014)); /* 0x0014地址存放hrb檔案資料的開始地址 */
q = (char *) memman_alloc_4k(memman, segsiz);
*((int *) 0xfe8) = (int) q;
set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
set_segmdesc(gdt + 1004, segsiz - 1, (int) q, AR_DATA32_RW + 0x60);
for (i = 0; i < datsiz; i++) { /* 將.hrb檔案復制到資料段后再啟動程式 */
q[esp + i] = p[dathrb + i];
}
start_app(0x1b, 1003 * 8, esp, 1004 * 8, &(task->tss.esp0));
memman_free_4k(memman, (int) q, segsiz);
} else { /* 找不到.hrb檔案就報錯 */
cons_putstr0(cons, ".hrb file format error.\n");
}
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
/* 沒有找到檔案的情況 */
return 0;
}
執行一下make run_——
顯示視窗
現在我們來讓應用程式顯示視窗吧,顯示視窗需要指定視窗名稱、視窗高和寬,視窗位置、視窗緩沖區等資訊,來看一下程式,
console.c節選
/*
edi = 視窗高度
esi = 視窗寬度
ebp =
esp =
ebx = 視窗緩沖區
ecx = 視窗名稱
eax = 透明色
*/
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
int ds_base = *((int *) 0xfe8);
struct TASK *task = task_now();
struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
struct SHTCTL *shtctl = (struct SHTCTL *) *((int *) 0x0fe4);
struct SHEET *sht;
int *reg = &eax + 1; /* eax后面的地址 */
/* 強行改寫通過PUSHAD保存的值 */
/* reg[0] : EDI, reg[1] : ESI, reg[2] : EBP, reg[3] : ESP */
/* reg[4] : EBX, reg[5] : EDX, reg[6] : ECX, reg[7] : EAX */
if (edx == 1) {
cons_putchar(cons, eax & 0xff, 1);
} else if (edx == 2) {
cons_putstr0(cons, (char *) ebx + ds_base);
} else if (edx == 3) {
cons_putstr1(cons, (char *) ebx + ds_base, ecx);
} else if (edx == 4) {
return &(task->tss.esp0);
} else if (edx == 5) {
sht = sheet_alloc(shtctl);
sheet_setbuf(sht, (char *) ebx + ds_base, esi, edi, eax);
make_window8((char *) ebx + ds_base, esi, edi, (char *) ecx + ds_base, 0);
sheet_slide(sht, 100, 50); /* 視窗顯示在(100,50)*/
sheet_updown(sht, 3); /* 背景層高度3位于task_a之上 */
reg[7] = (int) sht;
}
return 0;
}
這里shtctl的值從0x0fe4地址獲得,reg是向應用程式回傳值,視窗顯示在(100,50)這個位置,背景層高度為3,
make run——

在視窗中描繪字符和方塊
最后我們再嘗試在視窗顯示字符和方塊吧,這兩個功能之前實作過,把它們加在API中就可以了,
實作思路:
在視窗上顯示字符:
EDX = 6
EBX = 視窗句柄
ESI = 顯示位置的x坐標
EDI = 顯示位置的y坐標
EAX = 色號
ECX = 字串長度
EBP = 字串
描繪方塊:
EDX = 7
EBX = 視窗句柄
EAX = x0
ECX = y0
ESI = x1
EDI = y1
EBP = 色號
按照這個思路,在程式中實作,執行make run,看一下效果:
注:本文參照《30天自制作業系統》制作,感謝各位的持續關注,原著原始碼鏈接見第十九篇,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/302784.html
標籤:其他
