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

本篇内容不多,主要是一些优化的工作。首先优化了应用程序,然后引入对应用程序的保护功能,最后引入库的概念。
在这里插入图片描述

1. 应用程序优化

首先来解决上一篇中遗留的一个bug:使用ncst命令运行的应用程序,按下Shift+F1或者点击x按钮都无法关闭。

分析上一篇新增的代码,没有发现问题,因此这个问题应该是之前就已经存在了。解决的方法也很简单,只需要增加以下的代码:

按下Shift+F1按钮:

if (i == 256 + 0x3b && key_shift != 0 && key_win != 0) {	/* Shift+F1 */
	task = key_win->task;
	if (task != 0 && task->tss.ss0 != 0) {
		cons_putstr0(task->cons, "\nBreak(key) :\n");
		io_cli();	
		task->tss.eax = (int) &(task->tss.esp0);
		task->tss.eip = (int) asm_end_app;
		io_sti();
		task_run(task, -1, 0); // 增加此行代码
	}
}

鼠标点击:

if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) {
	/* 点击「×」按钮 */
	if ((sht->flags & 0x10) != 0) {		
		task = sht->task;
		cons_putstr0(task->cons, "\nBreak(mouse) :\n");
		io_cli();	
		task->tss.eax = (int) &(task->tss.esp0);
		task->tss.eip = (int) asm_end_app;
		io_sti();
		task_run(task, -1, 0);// 增加此行代码
	} else {	
		task = sht->task;
		io_cli();
		fifo32_put(&task->fifo, 4);
		io_sti();
	}
}

增加的语句是用来将休眠的任务唤醒。

虽然我们已经增加了结束任务的处理,但如果任务一直处于休眠状态,还是无法执行这些处理,导致无法结束任务。因此我们需要将任务唤醒。

为什么之前没有发现这个问题呢?因为在有命令行窗口的情况下,命令行窗口会触发控制光标闪烁的定时器中断。中断超时,会向FIFO写入数据,这样任务被自动唤醒了。因此之前虽然看起来可以强制结束,实际上还是有一定的延时的,最大是0.5s。

这样修改之后,就可以通过按键或者鼠标点击来关闭窗口了。

现在还有一个问题,在应用程序处于运行过程中时,无法关闭对应的命令行窗口。这里我们需要做一些优化。

首先是主程序:

……
if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) 
{
	/* 点击「×」按钮 */
	if ((sht->flags & 0x10) != 0) {		
		task = sht->task;
		cons_putstr0(task->cons, "\nBreak(mouse) :\n");
		io_cli();
		task->tss.eax = (int) &(task->tss.esp0);
		task->tss.eip = (int) asm_end_app;
		io_sti();
		task_run(task, -1, 0);
	} else {	
		task = sht->task;
		sheet_updown(sht, -1);//修改1:暂时隐藏图层
		keywin_off(key_win);
		key_win = shtctl->sheets[shtctl->top - 1];
		keywin_on(key_win);
		io_cli();
		fifo32_put(&task->fifo, 4);
		io_sti();
	}
}
……
else if (2024 <= i && i <= 2279) 
{	// 修改2:只关闭命令行窗口
	sht2 = shtctl->sheets0 + (i - 2024);
	memman_free_4k(memman, (int) sht2->buf, 256 * 165);
	sheet_free(sht2);
}

这里主要是两点优化。首先是点击x按钮时将命令行窗口图层隐藏的操作。因为有时关闭命令行窗口需要消耗一定的时间,这时先将图层隐藏能让用户觉得响应及时;然后就是在从FIFO中接收到console.c发送的关闭窗口请求数据后所进行的处理,这里主要是释放命令行窗口的图层。
API中键盘输入的部分需要做如下修改:

else if (edx == 15) 
{
	for (;;) 
	{
		……
		if (i == 4) {	// 修改点:只关闭命令行窗口
			timer_cancel(cons->timer);
			io_cli();
			fifo32_put(sys_fifo, cons->sht - shtctl->sheets0 + 2024);	/* 2024~2279 */
			cons->sht = 0;
			io_sti();
		}
		……
	}
} 

等待键盘输入期间,如果FIFO中接收到4,表示收到了关闭命令行窗口的信号。此时取消定时器,发出清理图层的消息,然后将cons->sht置为0。

2. 应用程序保护

接下来是增加一些对应用程序的保护措施。首先看下面的代码:

[FORMAT "WCOFF"]
[INSTRSET "i486p"]
[BITS 32]
[FILE "crack7.nas"]

		GLOBAL	_HariMain

[SECTION .text]

_HariMain:
		MOV		AX,1005*8
		MOV		DS,AX
		CMP		DWORD [DS:0x0004],'Hari'
		JNE		fin					; 不是应用程序,不执行任何操作

		MOV		ECX,[DS:0x0000]		; 读取应用程序数据段的大小
		MOV		AX,2005*8
		MOV		DS,AX

crackloop:							; 全部用123填充
		ADD		ECX,-1
		MOV		BYTE [DS:ECX],123
		CMP		ECX,0
		JNE		crackloop

fin:								; 结束
		MOV		EDX,4
		INT		0x40

打开命令行窗口,运行lines程序;然后打开一个新的命令行窗口,在新窗口中运行crack7,lines应用程序的运行就会产生异常,这是crack7程序攻击了lines程序。

crack7程序首先从1005号段中的第四个字节开始读取数据,判断是否为“Hari”。这里1005其实代表第一个打开的命令行窗口所对应的应用程序代码段编号。如果从中读出“Hari”,该应用程序正在运行的可能性很高,接下来就读取段开头的4个字节,即应用程序数据段的大小。随后切换到2005号段,将其中的内容全部用123覆盖,这样就造成了程序的异常。

那么如何阻止应用程序攻击其他的应用程序呢?CPU已经具备了解决方案,这就是LDT(Local Descriptor Table)。与GDT相比,LDT同样用于设置段,但LDT中段的设置只对某个应用程序有效。这样其他程序无法使用,也就无法搞破坏了。

LDT的内存地址是通过在GDT中创建LDT段来告知CPU的。在GDT中可以设置多个LDT,和TSS非常相似。

我们在头文件中添加用于设置LDT的段属性编号。

#define AR_LDT		0x0082
……
struct TASK {
	int sel, flags; 
	int level, priority;
	struct FIFO32 fifo;
	struct TSS32 tss;
	struct SEGMENT_DESCRIPTOR ldt[2];// 增加LDT
	struct CONSOLE *cons;
	int ds_base, cons_stack;
};

将LDT的编号写入tss.ldtr,则创建TSS时就在GDT中设置了LDT。

struct TASK *task_init(struct MEMMAN *memman)
{
	……
	for (i = 0; i < MAX_TASKS; i++) {
		taskctl->tasks0[i].flags = 0;
		taskctl->tasks0[i].sel = (TASK_GDT0 + i) * 8;
		// 设置ldtr
		taskctl->tasks0[i].tss.ldtr = (TASK_GDT0 + MAX_TASKS + i) * 8;
		set_segmdesc(gdt + TASK_GDT0 + i, 103, (int) &taskctl->tasks0[i].tss, AR_TSS32);
		// 设置GDT时设置LDT
		set_segmdesc(gdt + TASK_GDT0 + MAX_TASKS + i, 15, (int) taskctl->tasks0[i].ldt, AR_LDT);
	}
	……
}

struct TASK *task_alloc(void)
{
	……
	for (i = 0; i < MAX_TASKS; i++) {
		if (taskctl->tasks0[i].flags == 0) {
			……
			task->tss.fs = 0;
			task->tss.gs = 0;
			// 删除原来的task->tss.ldtr = 0;
			task->tss.iomap = 0x40000000;
			task->tss.ss0 = 0;
			return task;
		}
	}
	return 0;
}

最后再修改应用程序,使应用程序创建在LDT中。

int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{
	……
	if (finfo != 0) {
		/* 找到文件的情况 */
		……
		if (finfo->size >= 36 && strncmp(p + 4, "Hari", 4) == 0 && *p == 0x00) {
		……
		// 在LDT中设置程序的代码段与数据段
		set_segmdesc(task->ldt + 0, finfo->size - 1, (int) p, AR_CODE32_ER + 0x60);
		set_segmdesc(task->ldt + 1, segsiz - 1,      (int) q, AR_DATA32_RW + 0x60);
			for (i = 0; i < datsiz; i++) {
				q[esp + i] = p[dathrb + i];
			}
			start_app(0x1b, 0 * 8 + 4, esp, 1 * 8 + 4, &(task->tss.esp0));
			……
		} else {
			cons_putstr0(cons, ".hrb file format error.\n");
		}
		memman_free_4k(memman, (int) p, finfo->size);
		cons_newline(cons);
		return 1;
	}
	return 0;
}

在start_app中使用的段号为4(=0x8 + 4)和12(1x8 + 4),乘以8的部分和GDT一致,而加4的部分表示该段号不是GDT中的段号,而是LDT中的段号。

这样再重复上面的操作,会产生异常,应用程序得到了保护。

在这里插入图片描述

3. 库

本篇的最后,为了简化已经开发完成的程序,引入了库的概念。

将一个庞大的程序拆解成小的部分(函数),将各个部分再组成完整的程序,这种编程方式被称为“结构化编程”。这样编写好的程序还可以保存下来,应用于其他程序。

依据这种思想,将可以用于其他程序的部件组织起来,就构成了库。库的扩展名为.lib,可以将多个.obj文件打包成库。如下面的Makefile内容:

GOLIB    = $(TOOLPATH)golib00.exe 

apilib.lib : Makefile $(OBJS_API)
	$(GOLIB) $(OBJS_API) out:apilib.lib

最终可以得到apilib.lib这样一个文件。

我们不仅可以自己编写库,也可以使用别人开发好的库。把库分享给别人时,文件也是越少越好,因此.lib这种格式的库很常用。通过引入库,我们可以减少生成的目标文件。

下一篇内容是文件操作与文字显示,敬请期待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值