5

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

 2 years ago
source link: https://blog.csdn.net/mint1993/article/details/120444454
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK