30天自制操作系统(第22天)

本文介绍了如何在C语言中处理栈异常,如使用asm_inthandler0c函数,以及通过中断机制处理键盘事件(如Shift+F1组合键)来结束应用程序。同时,还涉及了在窗口中显示字符和绘制方块的API调用。
摘要由CSDN通过智能技术生成

22.1 帮助发现bug 

按照上一章节对一般性保护异常现象的分析,如果出现对数组赋值时超出范围应该作何处理呢?下面将根据函数asm_inthandler0d编写函数asm_inthandler0c用于处理栈异常现象。 inthandler0c函数中的esp[11]是取esp栈中的第11号元素,如果想要得到产生异常时其他寄存器的值,可按照下面显示相应元素即可。
esp[ 0] : EDI
esp[ 1] : ESI esp[0 7] _asm_inthandler PUSHAD 的结果
esp[ 2] : EBP
esp[ 4] : EBX
esp[ 5] : EDX
esp[ 6] : ECX
esp[ 7] : EAX
esp[ 8 ] : DS esp[8 9] _asm_inthandler PUSH 的结果
esp[ 9] : ES
esp[10] : 错误编号(基本上是 0 ,显示出来也没什么意思)
esp[11] : EIP
esp[12] : CS esp[10 15] 为异常产生时 CPU 自动 PUSH 的结果
esp[13] : EFLAGS
esp[14] : ESP (应用程序用 ESP
esp[15] : SS (应用程序用 SS
/*                dsctbl.c                */
set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32);

/*                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 			
		IRETD

/*                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 0D :\n Stack Exception.\n");
	sprintf(s, "EIP = %08X\n", esp[11]);
	cons_putstr0(cons, s);
	return &(task->tss.esp0); /*强制结束程序*/
}

22.2 强制结束应用程序

如果遇到一个死循环的程序,应该如何应对呢?下面实现按下“shift+F1”组合键,结束应用程序。读键盘数据是在文件bootpack.c中实现的,所以将组合键的代码也放在该文件中。
void HariMain(void){
	(中略)
	struct CONSOLE *cons;
	(中略)
	for (;;) {
		(中略)
		if (fifo32_status(&fifo) == 0) {
			task_sleep(task_a);
			io_sti();
		} else {
			i = fifo32_get(&fifo);
			io_sti();
			if (256 <= i && i <= 511) {/* 键盘数据 */
				(中略)
				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();
				}
				(中略)
			} else if (512 <= i && i <= 767) {/* 鼠标数据 */
				(中略)
			} else if (i <= 1) {/* 光标用计时器 */
				(中略)
			} 
		}
	}
}

_asm_end_app:
    ; EAX为tss.esp0的地址
    MOV     ESP,[EAX]
    MOV     DWORD [EAX+4],0 ; 这里!
    POPAD
    RET                     ; 返回cmd_app

22.3 用C语言显示字符串(1

按照函数api_putchar能够调用中断打印字符,依葫芦画瓢可以写出函数api_putstr0能够调用中断打印字符串。
_api_putstr0: ; void api_putstr0(char *s);
	PUSH 	EBX         ;用于后续对EBX寄存器中的数据进行恢复
	MOV 	EDX,2		;EDX=2时cons_putstr0(cons, (char*)ebx+cs_base);
	MOV 	EBX,[ESP+8] ; s
	INT 	0x40
	POP 	EBX
	RET

22.4 用C语言显示字符串(2

.hrb 文件,开头的 36 个字节不是程序,而是存放了下列这些信息:
0x0000 (DWORD) …… 请求操作系统为应用程序准备的数据段的大小
0x0004 (DWORD) ……“ Hari .hrb 文件的标记)
0x0008 (DWORD) …… 数据段内预备空间的大小
0x000c (DWORD) …… ESP 初始值 & 数据部分传送目的地址
0x0010 (DWORD) …… hrb文件内数据部分的大小,向数据段传送的部分的字节数
0x0014 (DWORD) ....... 向数据段传送的部分在 .hrb 文件中的起始地址
0x0018 (DWORD) ……0xe9000000 E9是JMP指令的机器语言编码,
0x001c (DWORD) ……存放的是应用程序运行入口地址 - 0x20
0x0020 (DWORD) …… malloc 空间的起始地址
根据上述对hrb文件的分析,可以修改console.c函数
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
    int 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));
            esp = *((int *) (p + 0x000c));//数据传送目的地址
            datsiz = *((int *) (p + 0x0010));//数据段部分大小
            dathrb = *((int *) (p + 0x0014));//放在hrb文件中的起始位置,复制从该地址起的datsiz个数据
            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 {
            cons_putstr0(cons, ".hrb file format error.\n");
        }
        memman_free_4k(memman, (int) p, finfo->size);
        cons_newline(cons);
        return 1;
    }
    /*没有找到文件的情况*/
    return 0;
}

22.5 显示窗口

EDX = 5
EBX = 窗口缓冲区
ESI = 窗口在 x 轴方向上的大小(即窗口宽度)
EDI = 窗口在 y 轴方向上的大小(即窗口高度)
EAX = 透明色
ECX = 窗口名称
调用后,返回值如下:
EAX = 用于操作窗口的句柄(用于刷新窗口等操作)
asm_hrb_api 中执行了两次 PUSHAD,第一次是为了保存寄存器的值,第二次是为了向hrb_api传递值。因此如果查出被传递的变量的地址,在那个地址的后面应该正好存放着相同的寄存器的值。然后只要修改那个值,就可以由 POPAD 获取修改后的值,实现将值返回给应用程序的功能。
/*            a_nask.nas                */
_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 由于buf的大小为7500,2^12=4096<7500,所以得用2^16个字节
    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

/*                winhelo.c                */
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();
}

/*                console.c                */
//根据输入参数打印数据 eax为当前字符 ebx为字符串 ecx长度
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后面的地址*/
	/* 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);
		sheet_updown(sht, 3); /*背景层高度3位于task_a之上*/
		reg[7] = (int) sht; 
	}/*到这结束*/
	return 0;
}

22.6 在窗口中描绘字符和方块

 
在窗口上显示字符的API
描绘方块的 API
EDX = 6
EBX = 窗口句柄
ESI = 显示位置的 x 坐标
EDI = 显示位置的 y 坐标
EAX = 色号
ECX = 字符串长度
EBP = 字符串
EDX = 7
EBX = 窗口句柄
EAX = x0
ECX = y0
ESI = x1
EDI = y1
EBP = 色号
/*                a_nask.nas                */
_api_putstrwin: ; void api_putstrwin(int win, int x, int y, int col, int len, char *str);
	PUSH 	EDI			;保存4个寄存器的值
	PUSH 	ESI
	PUSH 	EBP
	PUSH 	EBX
	MOV 	EDX,6
	MOV 	EBX,[ESP+20] ; win
	MOV 	ESI,[ESP+24] ; x
	MOV 	EDI,[ESP+28] ; y
	MOV 	EAX,[ESP+32] ; col
	MOV 	ECX,[ESP+36] ; len
	MOV 	EBP,[ESP+40] ; str
	INT 	0x40
	POP 	EBX
	POP 	EBP
	POP 	ESI
	POP 	EDI
	RET

_api_boxfilwin: ; void api_boxfilwin(int win, int x0, int y0, int x1, int y1, int col);
	PUSH 	EDI
	PUSH 	ESI
	PUSH 	EBP
	PUSH 	EBX
	MOV 	EDX,7
	MOV 	EBX,[ESP+20] ; win
	MOV 	EAX,[ESP+24] ; x0
	MOV 	ECX,[ESP+28] ; y0
	MOV 	ESI,[ESP+32] ; x1
	MOV 	EDI,[ESP+36] ; y1
	MOV 	EBP,[ESP+40] ; col
	INT 	0x40
	POP 	EBX
	POP 	EBP
	POP 	ESI
	POP 	EDI
	RET

/*                console.c                */
	}else if(edx == 6){
		sht = (struct SHEET *) ebx;
		putfonts8_asc(sht->buf, sht->bxsize, esi, edi, eax, (char *) ebp + ds_base);
		sheet_refresh(sht, esi, edi, esi + ecx * 8, edi + 16);
	}else if(edx == 7){
		sht = (struct SHEET *) ebx;
		boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);
		sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
	}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值