首页 > 其他分享 >【读书笔记-《30天自制操作系统》-21】Day22

【读书笔记-《30天自制操作系统》-21】Day22

时间:2024-09-19 23:24:10浏览次数:11  
标签:cons struct 读书笔记 int 30 MOV Day22 char api

本篇内容首先介绍了CPU对于操作系统的保护功能,然后在上一篇API的基础上实现C语言编写的显示字符串与窗口画面的应用程序。
在这里插入图片描述

1. CPU对操作系统的保护

本篇首先通过对几种破坏操作系统的尝试,来介绍CPU对操作系统的保护功能。

1.1 防护破坏

尝试通过篡改定时器来拖慢任务切换。

[INSTRSET "i486p"]
[BITS 32]
		MOV		AL,0x34
		OUT		0x43,AL
		MOV		AL,0xff
		OUT		0x40,AL
		MOV		AL,0xff
		OUT		0x40,AL

; 	↑上述代码的功能与以下代码相当
;	io_out8(PIT_CTRL, 0x34);
;	io_out8(PIT_CNT0, 0xff);
;	io_out8(PIT_CNT0, 0xff);

		MOV		EDX,4
		INT		0x40

运行之后产生了异常。

在这里插入图片描述
这是因为在以应用程序模式运行时,执行IN指令和OUT指令都会产生一般保护异常。修改CPU设置可以允许应用程序使用IN和OUT指令,但这样也可能留下bug。

同样地,以下的操作在应用程序模式运行时也会产生错误:

  • 执行CLI,STI和HLT指令;(任务休眠可以通过调用任务休眠API来实现,而不能由应用程序自己来执行HLT)
  • 应用程序执行CALL设置好的地址范围以外的地址;(当前就只能通过INT 0x40来调用操作系统)

当然,如果在API中有漏洞,或者被篡改,CPU就无能为力了。这也是操作系统开发人员需要避免的。

1.2 发现bug

CPU的异常处理功能可以帮助我们发现程序中的一些bug。如下面的代码:

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();
}

明显a[102]与a[123]产生了数组越界。不过在QEMU上运行不会出现问题,而在真机上运行会导致重启。发生了重启是因为产生了没有设置过的异常。这里产生的是栈异常,我们需要一个函数来进行处理。栈中断的中断号为0x0c。(0x00-0x1f都是异常使用的中断)

		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
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);	/* 强制结束程序 */
}

然后将中断函数注册到IDT中:

set_gatedesc(idt + 0x0c, (int) asm_inthandler0c, 2 * 8, AR_INTGATE32);

这样在真机环境下运行,就可以成功产生异常。但是是在显示出“AB”之后才产生异常,也就是说a[102]没有被判断为异常。

这是因为a[102]虽然超出了数组的边界,但是没有超过为应用程序分配的数据段的边界。因此CPU发现bug的能力是有限的。不过CPU的异常功能本身就是为了保护操作系统,而不是为了发现bug。而像这样的错误其实可以由编译器发现。

此外我们还可以将引发异常的指令的地址显示出来,方便调试。

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]);
	cons_putstr0(cons, s);
	return &(task->tss.esp0);
}

这里我们将esp[11]的值显示出来。esp[11]即EIP。

在真机环境下运行,显示EIP = 00000042。查看相应的map文件:

0x00000024:	_HariMain
0x00000052:	_api_putchar

可以看出异常的地址位于HariMain内。需要更进一步查看lst文件:

11 00000000			_HariMain:

在lst文件中,HariMain的地址被当作0(相对地址),这样lst中的0x1e地址对应的就是map中的0x42地址:

22	0000001E		C6 45 0B 43		MOV BYTE [ESP+11], 67

可以看出这条指令正对应"a[123] = C"的指令。

1.3 按键结束应用程序

Linux系统中有通过Ctrl + 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();
				}

这里使用了Shift + F1来强制终止程序。当按下按键时,改写命令行窗口的寄存器值,跳转到asm_end_app,就实现强制终止程序了。

使用bug3程序来进行验证。bug3用于不断显示字符"a"。

void HariMain(void)
{
	for (;;) {
		api_putchar('a');
	}
}

运行之后按下结束键,程序被强制终止了。
在这里插入图片描述

2. C语言显示字符串

接下来通过C语言调用显示字符串的API,来实现显示字符串的应用程序。

显示字符串的API:

_api_putstr0:	; void api_putstr0(char *s);
		PUSH	EBX
		MOV		EDX,2
		MOV		EBX,[ESP+8]		; s
		INT		0x40
		POP		EBX
		RET

编写一个应用程序hello4.hrb:

void api_putstr0(char *s);
void api_end(void);

void HariMain(void)
{
	api_putstr0("hello, world\n");
	api_end();
}

运行一下:
在这里插入图片描述
什么都没有显示出来。是什么原因呢?我们先把EBX显示出来看看。

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
	int cs_base = *((int *) 0xfe8);
	struct TASK *task = task_now();
	struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
	
	char s[12]; //增加显示EBX
	
	if (edx == 1) {
		cons_putchar(cons, eax & 0xff, 1);
	} else if (edx == 2) {
	//暂时屏蔽
		/*cons_putstr0(cons, (char *) ebx + cs_base);*/
		sprintf(s, "%08X\n", ebx);
		cons_putstr0(cons, s);	//显示出EBX的值
	} else if (edx == 3) {
		cons_putstr1(cons, (char *) ebx + cs_base, ecx);
	} else if (edx == 4) {
		return &(task->tss.esp0);
	}
	return 0;
}

运行以上修改过的程序,显示出00000400。实际上hello4.hrb的文件大小只有114字节,并没有00000400的地址。这怎么能显示出来呢?

.hrb文件其实是由代码部分和数据部分组成的。由于之前没有使用字符串和外部变量,就会生成不包含数据部分的.hrb文件,运行起来也没有问题。而.hrb文件的数据部分会在应用程序启动时传送到应用程序使用的数据段中。在.hrb文件中,数据部分的位置存放在代码部分的开头一块区域中。

本书中生成的.hrb文件的前36个字节存放了如下信息:

  • 0x0000(DWORD): 请求操作系统为应用程序准备的数据段的大小
  • 0x0004(DWORD): Hari(.hrb文件的标记)
  • 0x0008(DWORD): 数据段内预备空间的大小
  • 0x000c(DWORD): ESP初始值&数据部分传送目的地址
  • 0x0010(DWORD): hrb文件内数据部分的大小
  • 0x0014(DWORD): hrb文件内数据部分从哪里开始
  • 0x0018(DWORD): 0xe9000000
  • 0x001c(DWORD): 应用程序运行入口地址 0x20
  • 0x0020(DWORD): malloc空间的起始地址

应用程序根据需要,在0x0000中存放了需要的数据段的大小。而"Hari"则是给操作系统用来判断是否为应用程序。0x0008中存放的数据段内预备空间的大小暂未使用,设置为0;0x000c中存放应用程序启动时ESP寄存器的初始值,从这个地址之前的部分会被作为栈使用,用于存放字符串等数据。在hello4.hrb中,这个值为0x400,这是因为在Makefile中分配了1KB的栈空间:

hello4.bim : hello4.obj a_nask.obj Makefile
	$(OBJ2BIM) @$(RULEFILE) out:hello4.bim stack:1k map:hello4.map \
		hello4.obj a_nask.obj

0x0018中存放的0xe9000000在内存中存放为00 00 00 E9,其实是JMP指令的机器语言编码,和后面4个字节组成跳转到应用程序入口的指令。

这样我们对cmd_app函数进行修改:

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
	struct MEMMAN *memman = (struct MEMMAN *) MEMMAN_ADDR;
	struct FILEINFO *finfo;
	struct SEGMENT_DESCRIPTOR *gdt = (struct SEGMENT_DESCRIPTOR *) ADR_GDT;
	char name[18], *p, *q;
	struct TASK *task = task_now();
	int i;

	for (i = 0; i < 13; i++) {
		if (cmdline[i] <= ' ') {
			break;
		}
		name[i] = cmdline[i];
	}
	name[i] = 0; /

	finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
	if (finfo == 0 && name[i - 1] != '.') {
		name[i    ] = '.';
		name[i + 1] = 'H';
		name[i + 2] = 'R';
		name[i + 3] = 'B';
		name[i + 4] = 0;
		finfo = file_search(name, (struct FILEINFO *) (ADR_DISKIMG + 0x002600), 224);
	}

	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));
			// 根据应用程序前36个字节中的信息分配数据段内存
			q = (char *) memman_alloc_4k(memman, segsiz);
			// 数据段内存的起始地址放在0xfe8地址中
			*((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;
	}
	return 0;
}

修改点包括:

  • 找不到"Hari"标志则报错
  • 数据段的大小根据.hrb文件中指定的值进行分配
  • 将.hrb文件中的数据部分复制到数据段后再启动程序

这样程序就可以正常运行了。

在这里插入图片描述
3. C语言显示窗口与画面

通过应用程序显示窗口,同样需要一个显示窗口的API,设计如下:

  • EDX = 5
  • EBX = 窗口缓冲区
  • ESI = 窗口在x方向的大小(窗口宽度)
  • EDI = 窗口在y方向的大小(窗口高度)
  • EAX = 透明色
  • ECX = 窗口名称

调用后,返回值为:

  • EAX = 用于操作窗口的句柄(用于刷新窗口等操作)

此外还需要考虑调用API之后将值存入寄存器并返回给应用程序。

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);
		sheet_updown(sht, 3);	/* 背景层高度3,位于task_a之上 */
		reg[7] = (int) sht;
	}
	return 0;
}

通过改写reg,可以实现向应用程序传递返回值。
而在主程序中,我们将shtctl的值放在0x0fed地址中,API再从这个地址获取:

*((int *) 0x0fe4) = (int) shtctl;

API程序内容如下:

_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

调用API的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();
}

运行之后,显示出一个窗口:

在这里插入图片描述
窗口显示出来了,再加上方块和字符也不难了。

在窗口上显示字符的API:

  • EDX = 6
  • EBX = 窗口句柄
  • ESI = 显示位置的x坐标
  • EDI = 显示位置的y坐标
  • EAX = 色号
  • ECX = 字符串长度
  • EBP = 字符串

描绘方块的API:

  • EDX = 7
  • EBX = 窗口句柄
  • ESI = x0
  • EDI = y0
  • EAX = x1
  • ECX = y1
  • EBP = 色号

根据以上的设计编写API:

int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int 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);	
		reg[7] = (int) sht;
	} 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);
	}
	return 0;
}
_api_putstrwin:	; void api_putstrwin(int win, int x, int y, int col, int len, char *str);
		PUSH	EDI
		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

最后在编写调用API的应用程序:

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();
}

运行之后效果如下:
在这里插入图片描述
本篇内容进一步加深了对于操作系统和应用程序之间关系的理解。下一篇内容是关于图形处理的,敬请期待。

标签:cons,struct,读书笔记,int,30,MOV,Day22,char,api
From: https://blog.csdn.net/Ocean1994/article/details/142331091

相关文章

  • 【读书笔记-《30天自制操作系统》-22】Day23
    本篇内容比较简单,集中于显示问题。首先编写了应用程序使用的api_malloc,然后实现了在窗口中画点与画线的API与应用程序。有了窗口显示,还要实现关闭窗口的功能,于是在键盘输入API的基础上实现了按下按键关闭窗口。最后发现用上文的强制结束按键结束应用程序,程序的窗口还没有关......
  • 51c视觉~合集30
    #SaRA修改一行代码就能实现高效微调!上海交大&腾讯开源:兼顾原始生成和下游任务仅修改一行训练代码即可实现微调过程。文章链接:https://arxiv.org/pdf/2409.06633项目链接:https://sjtuplayer.github.io/projects/SaRA/1.引言SaRA是一种针对预训练扩散模型的高效微调方法。通过微调预......
  • AirBus A330-700L Beluga XL 超级大白鲸运输机 All In One
    AirBusA330-700LBelugaXL超级大白鲸运输机AllInOne空中客车A330-700LBelugaXL“超级大白鲸”(制造数量6架)空中客车A300-600STBeluga“大白鲸”(制造数量5架现役)newsWheels-upforthefinalBelugaXL!为最后的BelugaXL做好准备https://www.airbu......
  • 辽宁2024下半年软考报名8月22日8点30后开始
    一、辽宁2024下半年软考报名时间8月22日8:30-8月27日16:30二、辽宁2024下半年软考报名入口1.1.考生登录辽宁省信息技术教育中心网站(http://www.lnitec.com/)进入考生报名入口,用有效手机号或邮箱注册,按网上报名系统提示信息进行网上报考。报考信息提交后,经本地考试管理机构审核(审核时......
  • 大连2024下半年软考报名8月20日9点30后开始
    一、大连2024下半年软考报名时间8月20日9:30-9月13日16:00二、大连2024下半年软考报名入口1.考生登录大连电子信息应用教育中心官网(http://www.dlrkb.com/rkb),进入右侧考生报名入口,按网上报名系统提示信息进行网上报名,考区选择大连(请勿选择辽宁省),报名信息提交后,经本地考试管理机构审......
  • kube-vip搭建k8s1.30.5高可用集群
    实验环境机器五台系统:ubuntu24.04cat/etc/hosts192.168.0.11jichao11k8s-master01192.168.0.12jichao12k8s-master02192.168.0.13jichao13k8s-master03192.168.0.14jichao14k8s-worker01192.168.0.15jichao15k8s-worker02192.168.0.200lb.kub......
  • 山西2024下半年软考报名9月2日2点30后开始
    一、山西2024下半年软考报名时间9月2日下午14:30至9月9日上午11:30二、山西2024下半年软考报名入口考生登录中国计算机技术职业资格网网上报名系统(https://www.ruankao.org.cn/),点击对应报名入口,按要求注册、填报报考信息、上传近期免冠照片,保存提交,通过照片审核后完成缴费即算报名......
  • 已经30岁了,想转行从头开始现实吗?什么样的工作算好工作?
        我是29岁那年,完成从转行+裸辞+副业的职业转型。如果你把职业生涯看成是从现在开始30岁,到你退休那年,中间这么漫长的30年,那么30岁转行完全来得及;如果你觉得必须在什么年纪,什么时间内必须完成赚到几十万上百万的目标,或者成为某个领域的大神,或者实现买车买房的愿望,对不......
  • 【洛谷 P5730】【深基5.例10】显示屏 题解(数组+循环)
    【深基5.例10】显示屏题目描述液晶屏上,每个阿拉伯数字都是可以显示成的点阵的(其中X表示亮点,.表示暗点)。现在给出数字位数(不超过)和一串数字,要求输出这些数字在显示屏上的效果。数字的显示方式如同样例输出,注意每个数字之间都有一列间隔。输入格式第一行输入一个正整数,表示数......
  • Maximum execution time of 30 seconds exceeded
    遇到 Maximumexecutiontimeof30secondsexceeded 这个错误,通常是因为PHP脚本执行时间超过了设定的最大执行时间限制。这可能是由于脚本执行了耗时的操作,例如长时间的数据库查询或其他资源密集型任务。以下是一些解决步骤:1.增加最大执行时间限制可以在PHP配置文件(ph......