当前位置:首页 » 《关注互联网》 » 正文

30天自制操作系统——第二十一天用C语言编写应用程序_mint1993的专栏

29 人参与  2022年01月13日 08:36  分类 : 《关注互联网》  评论

点击全文阅读


帮助发现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天自制操作系统》制作,感谢各位的持续关注,原著源码链接见第十九篇。


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/33140.html

窗口  显示  文件  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1