第22天
CPU中的IRQ中斷是從0x20號開始的,0x20也就是定時器中斷。0x0~0x1f都是CPU異常所使用的中斷。0x00是除零異常;0x06是非法指令異常;之前介紹的0x0d是一般保護異常;0x0c是棧異常。
我們一開始先講棧異常。所謂的棧異常,就是定義一個數(shù)組比如 a[100],但是卻在程序中訪問a[101],這就是棧異常。CPU不會因為應(yīng)用程序訪問超出定義的棧發(fā)出警告,但是如果應(yīng)用程序訪問超出操作系統(tǒng)定義的數(shù)據(jù)段或者代碼段就會產(chǎn)生0x0c號中斷。
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);
}
_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
IRETD
0x0c異常的處理程序?qū)懞?,再把asm_inthandler0c注冊進idt里就可以了。下面的匯編代碼中把esp的值push到棧里,當作參數(shù)傳進inthandler0c函數(shù)中。在這個函數(shù)中把傳進去的esp作為int數(shù)據(jù)類型的指針,并加上了11,現(xiàn)在看看加上11后表示什么值。esp[07]為_asm_inthandler0c中pushad壓進去的值;esp[89]為_asm_inthandler0c中push進去的ds和es。esp[10~15]為異常產(chǎn)生時CPU自動push的結(jié)果,其中esp[11]為eip。
我們的操用系統(tǒng)已經(jīng)能處理2種操作系統(tǒng)的異常了。為了完善操作系統(tǒng)我們再增加一種功能:用戶按下某個按鍵時候,強制執(zhí)行當前正在運行的應(yīng)用程序。
我們現(xiàn)在有2個任務(wù)同時在運行,1個是一開機就運行的task_a,還有一個就是命令行任務(wù)task_console。如果task_console啟一個應(yīng)用程序,這個應(yīng)用程序很復雜,要運行很久,但是過了一段時間之后,用戶后悔了,不想再運行了,然后按下ctrl+f1結(jié)束應(yīng)用程序。那么處理ctrl+f1就不能在task_console里執(zhí)行,因為應(yīng)用程序運行的時候,task_console就沒有辦法處理消息隊列里的數(shù)據(jù)了,所以只能在task_a里處理。當a_task任務(wù)接收到ctrl+f1的時候察看task_console里是不是運行應(yīng)用程序,如果在運行應(yīng)用程序那么就把寄存器強制轉(zhuǎn)換為task_console的寄存器就可以了。
我們已經(jīng)實現(xiàn)了用C語言調(diào)用顯示字符的api,接下來可以寫顯示字符串的api了。
_api_putstr0: ; void api_putstr0(char *s);
PUSH EBX
MOV EDX,2
MOV EBX,[ESP+8] ; s
INT 0x40
POP EBX
RET
但是編繹執(zhí)行的時候程序無法正常運行。這里牽涉到可執(zhí)行文件格式的問題,接下來講講編繹,鏈接之后可執(zhí)行文件的格式了。
- 0x0000:請求操作系統(tǒng)為應(yīng)用程序準備的數(shù)據(jù)段的大小
- 0x0004:“Hari”字符串
- 0x0008:數(shù)據(jù)段內(nèi)預(yù)備空間的大小
- 0x000c:esp初始值和數(shù)據(jù)部分傳送目的的地址
- 0x0010:hrb文件內(nèi)數(shù)據(jù)部分的大小
- 0x0014:hrb文件內(nèi)數(shù)據(jù)部分從哪里開始
- 0x0018:0xe9000000
- 0x001c:應(yīng)用程序入口地址 - 0x20
- 0x0020:malloc空間的起始地址
重點看0x000c中存放的esp的值,esp為棧地址,也就是說esp之前部分被認為是棧空間,要顯示的字符串也會被放到棧里。0x0018這個地址中的數(shù)據(jù)是按雙字存放的,以intel8086CPU存放數(shù)據(jù)的規(guī)定是高高低低,也就是說0x0018~0x0001b的內(nèi)存分別是00,00,00,e9。而e9就是jmp指令的機器碼。e9后面跟著的就是應(yīng)用程序入口地址-0x20的值。如果我們把這個文件讀入內(nèi)存然后從0x001b開始執(zhí)行的話,就可以正常jmp到程序的入口地址,然后就可以正常執(zhí)行了,在正常執(zhí)行之前把ss和sp,ds之類的寄存器設(shè)置好,使能正常指向程序的數(shù)據(jù)段就行了。下在是代碼段。
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));
esp = *((int *) (p + 0x000c));
datsiz = *((int *) (p + 0x0010));
dathrb = *((int *) (p + 0x0014));
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++) {
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 {
cons_putstr0(cons, ".hrb file format error.\n");
}
memman_free_4k(memman, (int) p, finfo->size);
cons_newline(cons);
return 1;
}
這段代碼先判斷文件中的0x0004開始的4個字節(jié)是不是"Hari”字符串,如果不就認為不是可執(zhí)行文件。一個可執(zhí)行文件至少有36個字節(jié),因為文件頭就是36字節(jié)了,如果文件小于36字節(jié)肯定不是可執(zhí)行文件。由于鏈接程序自動將數(shù)據(jù)段調(diào)整為4K的倍數(shù) ,所以文件第1個字節(jié)肯定是0x00,所以也判斷一下第1個字節(jié),如果不為0的話肯定不是可執(zhí)行文件。
接下來寫顯示窗口的應(yīng)用程序。
- edx = 5
- ebx = 窗口緩沖區(qū)
- esi = 窗口寬度
- edi = 窗口高度
- eax = 透明色
- ecx = 窗口名稱
返回值eax = 用于操作窗口的句柄。
我們先寫應(yīng)用程序
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_end(void);
char buf[150 * 50];
void HariMain(void)
{
int win;
win = api_openwin(buf, 150, 50, -1, "hello");
api_end();
}
這里api_openwin函數(shù)返回的是用于操作窗口的句柄,其實就是SHEET類型的指針。
再來寫api_openwin函數(shù)
_api_openwin: ; int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
PUSH EDI
PUSH ESI
PUSH EBX
MOV EDX,5
MOV EBX,[ESP+16] ; buf
MOV ESI,[ESP+20] ; xsiz
MOV EDI,[ESP+24] ; ysiz
MOV EAX,[ESP+28] ; col_inv
MOV ECX,[ESP+32] ; title
INT 0x40
POP EBX
POP ESI
POP EDI
RET
這程序很簡單,就是根據(jù)我們之前設(shè)計的寄存器的功能,賦值,然后調(diào)用0x40中斷
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);
sheet_updown(sht, 3);
reg[7] = (int) sht;
}
我們默認把窗口放到(100,50)的位置。reg[7]就是中斷返回時候eax的值。
設(shè)計在窗口上寫字符串和畫方塊的api
顯示字符串a(chǎn)pi
- edx = 6
- ebx = 窗口句柄
- esi = x座標
- edi = y座標
- eax = 色號
- ecx = 字符串長度
- ebp = 字符串
描繪方塊api
- edx = 7
- ebx = 窗口句柄
- eax = x0
- ecx = y0
- esi = x1
- edi = y1
- ebp = 色號
接下來跟之前顯示窗口的功能差不多。
寫好之后應(yīng)用程序就可以這么調(diào)用;
int api_openwin(char *buf, int xsiz, int ysiz, int col_inv, char *title);
void api_putstrwin(int win, int x, int y, int col, int len, char *str);
void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
void api_end(void);
char buf[150 * 50];
void HariMain(void)
{
int win;
win = api_openwin(buf, 150, 50, -1, "hello");
api_boxfilwin(win, 8, 36, 141, 43, 3 /*黃色 */);
api_putstrwin(win, 28, 28, 0 /* ?黑色 */, 12, "hello, world");
api_end();
}