第27天 LDT与库
2020.5.7
1. 先来修复bug(harib24a)
-
在harib23j中有一个bug,就是用ncst运行的应用程序,使用Shift+F1和点击“X”按钮都无法关闭窗口。
- 其实这个bug在很久之前就有了,只是一直没有发现而已。
-
修改HariMain:
void HariMain(void) { …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… 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); /* 为了确实执行结束处理,如果处于休眠状态则唤醒 */ } } …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { …… if ((mdec.btn & 0x01) != 0) { if (mmx < 0) { for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { …… if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { /* “X”按钮 */ 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 { …… } } break; } } } } else { …… } } else { …… } } } else if (768 <= i && i <= 1023) { /* 命令行窗口结束处理 */ close_console(shtctl->sheets0 + (i - 768)); } else if (1024 <= i && i <= 2023) { close_constask(taskctl->tasks0 + (i - 1024)); } } } }
- 添加的两行代码都是
task_run(task, -1, 0);
,-1和0代表不改变任务的level和priority。这句代码的功能是将休眠的任务唤醒。- 尽管在TSS中改写了EIP和EAX以便执行结束任务的处理,但是如果任务一直处于休眠状态那么结束任务的处理就永远得不到执行。因此,需要唤醒任务,使得结束处理确实能够被执行。
- 之前的代码之所以没有这两句代码,强制结束应用程序也没出什么问题的原因是:
- 命令行窗口会触发0.5s光标定时器中断(在命令行窗口中,不显示光标时也会每0.5s产生一次定时器中断),当产生定时器中断时,定时器就会向FIFO缓冲区写入数据,于是任务就被自动唤醒了。因此,即便看上去可以强制执行,但是其实距离应用程序真正结束还是会产生最大0.5s的延迟。因此,这次修改也会让Shift+F1和“X”按钮的速度得到提升。
- 但是ncst命令的光标定时器被删掉了,因此不会产生定时器中断,如果任务一直在休眠,那么不主动唤醒,就会一直处于休眠状态。
- 添加的两行代码都是
-
make
后用VMware运行:
- 上图先使用ncst运行color2.hrb应用程序,然后点击“X”按钮关闭了color2.hrb。
2. 应用程序运行时关闭命令行窗口(harib24b)
-
现在解决应用程序运行时无法关闭对应的命令行窗口的问题。
- 在之前(先不考虑ncst),在应用程序退出前面试法务关闭用来启动这个程序的命令行窗口的。
-
修改HariMain:
void HariMain(void) { …… struct SHEET *sht = 0, *key_win, *sht2; /*添加了sht2*/ …… for (;;) { …… if (fifo32_status(&fifo) == 0) { …… } else { …… if (256 <= i && i <= 511) { /* 键盘数据 */ …… } else if (512 <= i && i <= 767) { /* 鼠标数据 */ if (mouse_decode(&mdec, i - 512) != 0) { …… if ((mdec.btn & 0x01) != 0) { if (mmx < 0) { for (j = shtctl->top - 1; j > 0; j--) { …… if (0 <= x && x < sht->bxsize && 0 <= y && y < sht->bysize) { if (sht->buf[y * sht->bxsize + x] != sht->col_inv) { …… if (sht->bxsize - 21 <= x && x < sht->bxsize - 5 && 5 <= y && y < 19) { /* “X”按钮 */ if ((sht->flags & 0x10) != 0) { /* 是否是应用程序窗口 */ …… } else { /* 命令行窗口 */ task = sht->task; sheet_updown(sht, -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(); } } break; } } } } else { …… } } else { …… } } } else if (768 <= i && i <= 1023) { /* 命令行结束处理 */ close_console(shtctl->sheets0 + (i - 768)); } else if (1024 <= i && i <= 2023) { close_constask(taskctl->tasks0 + (i - 1024)); } else if (2024 <= i && i <= 2279) { /* 只关闭命令行窗口 */ sht2 = shtctl->sheets0 + (i - 2024); memman_free_4k(memman, (int) sht2->buf, 256 * 165); sheet_free(sht2); } } } }
- 只修改了bootpack的两处:
- 前一处:让OS在用户按下“X”按钮时暂时将命令行窗口隐藏起来。
- 这是因为关闭有的应用程序的命令行窗口需要一定的时间,如果点了按钮较长时间窗口还没有关闭,用户的体验感会极差,先隐藏给用户已经关闭窗口的信息,用户友好性极高。
- 后一处:当FIFO缓冲区接收到从console.c发送的“关闭窗口”请求数据时所进行的处理,主要是释放指定的图层。
- 前一处:让OS在用户按下“X”按钮时暂时将命令行窗口隐藏起来。
- 只修改了bootpack的两处:
-
修改console_task:
void console_task(struct SHEET *sheet, int memtotal) { …… if (cons.sht != 0) { /*将sheet改成了cons.sht*/ …… } …… for (;;) { io_cli(); if (fifo32_status(&task->fifo) == 0) { …… } else { …… if (i <= 1 && cons.sht != 0) { /* 光标定时器 */ …… } …… if (i == 3) { /* 光标OFF */ if (cons.sht != 0) { /*将sheet改成了cons.sht*/ boxfill8(cons.sht->buf, cons.sht->bxsize, COL8_000000, cons.cur_x, cons.cur_y, cons.cur_x + 7, cons.cur_y + 15); } cons.cur_c = -1; } …… if (256 <= i && i <= 511) { /* 键盘数据 */ if (i == 8 + 256) { …… } else if (i == 10 + 256) { …… if (cons.sht == 0) { /*将sheet改成了cons.sht*/ cmd_exit(&cons, fat); } …… } else { …… } } /* 重新显示光标 */ if (cons.sht != 0) { /*将sheet改成了cons.sht*/ if (cons.cur_c >= 0) { boxfill8(cons.sht->buf, cons.sht->bxsize, cons.cur_c, cons.cur_x, cons.cur_y, cons.cur_x + 7, cons.cur_y + 15); } sheet_refresh(cons.sht, cons.cur_x, cons.cur_y, cons.cur_x + 8, cons.cur_y + 16); } } } }
- 主要修改点是将参数sheet修改成了cons.sht。这两个变量基本上是一致的。但是cons.sht在命令行窗口关闭后会被置为0,而sheet则不变。
-
修改hrb_api:
int *hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax) { …… struct FIFO32 *sys_fifo = (struct FIFO32 *) *((int *) 0x0fec); …… } 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; /*置0*/ io_sti(); } …… } } else if (edx == 16) { …… }
- 内存地址0xfec中存放的是OS(task_a)用的FIFO缓冲区地址。
- 等待键盘输入期间,如果从从FIFO缓冲区接受到了4这个数据,则表示收到了关闭命令行窗口的信号,此时取消定时器,并发出清理图层的消息,然后将cons->sht置为0。
-
make
后用VMware运行:
- 上图是运行color.hrb并关闭了命令行窗口。
3. 保护应用程序(1)(harib24c)
-
编写恶意应用程序crack7.nas:
[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
-
make
后运行试试:- 运行应用程序lines.hrb后:
- 打开新的命令行窗口,运行crack7.hrb:
- lines图层好像出现了问题!
- 运行应用程序lines.hrb后:
-
这次的crack7.hrb成功地破坏了OS,准确地说,由于无法破坏OS本身,转而破坏运行中的应用程序。
- 运行中的应用程序存在被破坏的风险,如果不加以处理的话,用户可能就不敢运行多个应用程序了。
-
首先,crack7将1005*8装进自己运行的数据段寄存器DS,成功伪装成为GDT的1005号。然后从1005号段的第4字节读取数据,判断是否是“Hari”。
- 1005号段其实就是代表第一个打开的命令行窗口所运行的应用程序的代码段编号。
- 回顾一下GDT编号:
- 1:OS用的数据段
- 2:OS用的代码段
- 3~1002:多任务用的段
- 3:task_a
- 4:idle
- 5:一般就是第一个命令行窗口
- 6:一般就是第二个命令行窗口
- 1003~2002:应用程序用的代码段
- 1003:task_a用(没用应用程序,不使用)
- 1004:idle用(没有应用程序,不使用)
- 1005:第一个命令行窗口的应用程序代码段
- 1006:第二个命令行窗口的应用程序代码段
- 2003~3002:应用程序用的数据段
- 2003:task_a用(没用应用程序,不使用)
- 2004:idle用(没有应用程序,不使用)
- 2005:第一个命令行窗口的应用程序数据段
- 2006:第二个命令行窗口的应用程序数据段
-
如果读出的数据是“Hari”,那么说明应用程序正在运行的可能性很高。然后,读取数据段开头的四个字节,即应用程序数据段的大小。
-
再然后,crack7.hrb将2005*8装进自己运行的数据段寄存器,又成功伪装成为GDT的2005号。然后将GDT2005号的所有数据都改成
123
(当然其他的数字也可以)。- 显然,crack7伪装将自己伪装成了第一个命令行窗口的应用程序,然后访问了“自己”的代码段和数据段,然后修改了“自己”的数据段。
- crack7的目的只是覆盖应用程序数据段应有的内容,使其无法正常运行。
- 对于CPU来讲,应用程序访问“自己”用的段当然是理所应当的事情啦,所以不会产生异常。
- 让人不由地感叹crack7简直tql。
-
想要防御crack7,只要禁止应用程序随意访问其他任务所拥有的内存段就可以了。这样crack7就只能攻击自己了(笑)。
4. 保护应用程序(2)(harib24d)
-
想要防御crack7,有一个办法是:通过改写GDT的设置,只将正在运行的那个程序的段设置为应用程序用,其他的应用程序都暂时设置为操作系统用。现在每个任务只有两个应用程序段,这个方法貌似可行。
- 显然,这个方法需要修改的代码量太大。而且,需要在每次切换任务时都需要改写GDT设置。
-
其实CPU早就准备好了这个问题的解决方法(感谢英特尔的大叔们!),那就是LDT。
-
LDT讲解
- LDT,local (segment) descriptor table,局部段号记录表。GDT是全局段号记录表。
- GDT中的段设置是提供给所有任务通用的,而LDT中的段设置则只是对某个应用程序有效。
- 将应用程序段设置在LDT中,其他的任务由于无法使用该LDT,也就无法破坏了。
- 和GDT一样,LDT的容量也是64KB(可以容纳8192个段),不过在此OS中只需要设置两个段,所以只使用其中的16字节,将这16字节的信息放在结构体TASK中。
- 可以通过GDTR这个寄存器将GDT的起始内存地址告诉CPU,而LDT的起始内存地址则是通过在GDT中创建LDT段来告知CPU的。也就是说,GDT中可以设置很多个LDT(当然,不能同时使用两个以上的LDT),这和TSS非常相似。
- 挖坑:LDT的详细信息有时间再细看。由于书上这部分的篇幅不多且时间紧迫,因此这部分不再详解。
-
修改bootpack.h,添加设置LDT的段属性编号和修改TASK结构体:
/* dsctbl.c */ #define AR_LDT 0x0082 /* mtask.c */ 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; };
-
修改mtask.c以便设置LDT。将LDT编号写入tss.ldtr,这样在创建TSS时就顺便在GDT中设置了LDT,CPU也可以知道该任务应该使用哪个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; 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); 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; /* 傕偆慡晹巊梡拞 */ }
- 在task_init中:
- 现在,GDT的31002号仍然存放的是任务用的段。10032002存放的是应用程序用的内存段(数据段+代码段),task->ldt[0]是数据段,task->ldt[1]是代码段。2003~3002现在未使用。
- 在task_init中:
-
修改console.c,使应用程序段创建在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) { …… 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); …… start_app(0x1b, 0 * 8 + 4, esp, 1 * 8 + 4, &(task->tss.esp0)); …… } else { cons_putstr0(cons, ".hrb file format error.\n"); } …… } /* 未找到文件 */ return 0; }
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);
设置数据段。- 在start_app调用中,指定的段号是4(=0*8+4)和12(=1*8+4),这里的乘8和GDT是一样的,+4代表该段号不是GDT中的段号,而是LDT中的段号的意思。
- 在多个任务同时运行(同时运行多个应用程序)的时候,应用程序用的代码段号都是4,数据段号都是12,这和之前的bug一样?其实不然,因为这里使用的是+4,指定的段号是LDT中段号,每个任务(应用程序)都有自己的专用的LDT,这样写是完全没问题的。
-
make
后用VMware运行crack7.hrb试试:
- 产生了一般保护性中断。这是因为1005和2005号段已经不再是应用程序用的代码段和数据段了。
-
如果将crack7.nas中的段号从1005*8和2005*8改成4和12,也不会破坏其他应用程序的运行。这是因为,crack7.hrb本身也是应用程序,这样运行crack7只会修改crack7自己本身。
-
不过还有一个漏洞可以利用,那就是CPU中的LLDT指令,这个指令可以改变LDTR寄存器(用于保存当前正在运行的应用程序所使用的的LDT的寄存器)的值,这样的话就可以切换到其他应用程序的LDT,从而引发问题。其实,这是不需要担心的,因为这个指令是OS专用指令,位于应用程序段内的程序是无法执行的,即便强制执行这个指令,也会像执行CLI指令那样产生异常,进而该应用程序被强制结束。
- 终于,又一次保护了OS!Love & Peace!。
5. 优化应用程序的大小(harib24e)
-
hello3.hrb有520字节,在第21天的harib18b中hello3.hrb只有100字节。明明hello3.hrb也没有修改啊。
- 这是因为创建hello3.hrb时所引用的a_nask.nas变大了。也就是说,在hello3.hrb中,除了包含向api_putchar和api_end这样真正需要用到的函数外,还包含了api_openwin和api_linewin这些hello3.hrb根本用不到的函数。
-
将这些函数做成不同的.obj文件,将api_putchar等需要用到的函数和api_openwin等用不到的函数分开。
- 连接器(Linker,obj2bim)的功能只是决定是否将.obj文件连接上去,而不会在一个包含多个函数的obj文件中挑出需要的部分,并舍弃不需要的部分(这并不是obj2bim的功能不够强大,一般的连接器都是这样设计的)。
-
因此将a_nask.nas拆成很多文件:
- api001.nas:
[FORMAT "WCOFF"] [INSTRSET "i486p"] [BITS 32] [FILE "api001.nas"] GLOBAL _api_putchar [SECTION .text] _api_putchar: ; void api_putchar(int c); MOV EDX,1 MOV AL,[ESP+4] ; c INT 0x40 RET
- api002.nas:
[FORMAT "WCOFF"] [INSTRSET "i486p"] [BITS 32] [FILE "api002.nas"] GLOBAL _api_putstr0 [SECTION .text] _api_putstr0: ; void api_putstr0(char *s); PUSH EBX MOV EDX,2 MOV EBX,[ESP+8] ; s INT 0x40 POP EBX RET
- api003.nas~api020.nas和上述类似,全都是从a_nask.nas中原封不动拆出来的,这里不再罗列。
- api001.nas:
-
由于hello3.hrb需要的.obj文件只有api001.obj和api004.obj,因此修改一下Makefile:
hello3.bim : hello3.obj api001.obj api004.obj Makefile $(OBJ2BIM) @$(RULEFILE) out:hello3.bim map:hello3.map hello3.obj api001.obj api004.obj
make
以后,hello3.hrb只有112字节,减少了408字节。
-
虽然说是减少了字节数,如果一个一个应用程序地根据需要的API去修改Makefile文件,确实很麻烦。在此之前,只需要将a_nask.obj连接上去就行了。
-
obj2bim连接器有一个功能,如果指定的.obj文件中的函数没有被程序所使用的,那么这个.obj文件是不会被连接的。所以将用不到的.obj文件写进Makefile也没问题。其实,市面上大多数的连接器都没有这个功能,只要制定好的.obj文件就会连接进去,obj2bim比较特殊一点(笑)。
-
因此,修改Makefile文件:
OBJS_API = api001.obj api002.obj api003.obj api004.obj api005.obj api006.obj \ api007.obj api008.obj api009.obj api010.obj api011.obj api012.obj \ api013.obj api014.obj api015.obj api016.obj api017.obj api018.obj \ api019.obj api020.obj a.bim : a.obj $(OBJS_API) Makefile $(OBJ2BIM) @$(RULEFILE) out:a.bim map:a.map a.obj $(OBJS_API)
- 这样,如果今后添加了api021.obj,只需要修改OBJS_API即可。
- 其余的修改和a.bim的修改差不多,因此这里不再赘述。
-
make
后对比一下修改前和修改后应用程序的大小:
- 上述应用程序中,有几个是无法正常运行的。比如hello.hrb和hello2.hrb并不是用bim2obj生成的,因此运行时会报告hrb文件格式错误。此外,现在支持了LDT,crack7.hrb也就不能破坏OS了,因此将这三个文件在harib24f中删除。
6. 库(harib24f)
-
将函数拆分开来,并用连接器进行连接,需要创建很多的.obj文件;如果不拆分,使用一个大的.obj文件(a_nask.obj),应用程序就会被无端增大。
-
要解决这个问题,需要使用
库
。- 库,library,用途是将多个obj文件打包成一个文件。
-
要创建一个库,首先需要obj文件,除此之外,还需要一个叫做库管理器的程序。库管理器的英文是librarian。tolset中依旧有这个库管理器golib00.exe了。
-
创建库,在Makefile中添加代码:
GOLIB = $(TOOLPATH)golib00.exe apilib.lib : Makefile $(OBJS_API) $(GOLIB) $(OBJS_API) out:apilib.lib
- 用这三行代码便可以得到一个apilib.lib这样一个文件。
-
可以在ojb2bim中指定刚刚生成的这个apilib.lib文件来代替一串的obj文件。从Makefile角度上来看好像没有什么区别,不过用一个文件代替一群文件,这还是很不错的。继续修改Makefile:
a.bim : a.obj apilib.lib Makefile $(OBJ2BIM) @$(RULEFILE) out:a.bim map:a.map a.obj apilib.lib
-
再编写一个apilib.h文件:
void api_putchar(int c); void api_putstr0(char *s); void api_putstr1(char *s, int l); void api_end(void); 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_initmalloc(void); char *api_malloc(int size); void api_free(char *addr, int size); void api_point(int win, int x, int y, int col); void api_refreshwin(int win, int x0, int y0, int x1, int y1); void api_linewin(int win, int x0, int y0, int x1, int y1, int col); void api_closewin(int win); int api_getkey(int mode); int api_alloctimer(void); void api_inittimer(int timer, int data); void api_settimer(int timer, int time); void api_freetimer(int timer); void api_beep(int tone);
- 有了这个头文件,用一句代码就可以搞定应用程序开头的API函数声明了:
#include "apilib.h"
- 比如,beepdown.c就可以写成如下代码:
#include "apilib.h" void HariMain(void) { …… }
- 其他应用程序的修改和上述修改大同小异,这里不再罗列。
- 有了这个头文件,用一句代码就可以搞定应用程序开头的API函数声明了:
-
关于库的知识
7. 整理make环境(harib24g)
-
OS、应用程序和库的源文件都混在一起,看起来非常混乱,因此做一下分类:
-
首先,关于OS的部分。在harib24g中创建了一个名为“haribote”的目录,将OS的核心源代码以及Makefile移动到这里。
OBJS_BOOTPACK = bootpack.obj naskfunc.obj hankaku.obj graphic.obj dsctbl.obj \ int.obj fifo.obj keyboard.obj mouse.obj memory.obj sheet.obj timer.obj \ mtask.obj window.obj console.obj file.obj TOOLPATH = ../../z_tools/ INCPATH = ../../z_tools/haribote/ MAKE = $(TOOLPATH)make.exe -r NASK = $(TOOLPATH)nask.exe CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet GAS2NASK = $(TOOLPATH)gas2nask.exe -a OBJ2BIM = $(TOOLPATH)obj2bim.exe MAKEFONT = $(TOOLPATH)makefont.exe BIN2OBJ = $(TOOLPATH)bin2obj.exe BIM2HRB = $(TOOLPATH)bim2hrb.exe RULEFILE = ../haribote.rul EDIMG = $(TOOLPATH)edimg.exe IMGTOL = $(TOOLPATH)imgtol GOLIB = $(TOOLPATH)golib00.exe COPY = copy DEL = del # 默认动作 default : $(MAKE) ipl10.bin $(MAKE) haribote.sys # 文件生成规则 ipl10.bin : ipl10.nas Makefile $(NASK) ipl10.nas ipl10.bin ipl10.lst asmhead.bin : asmhead.nas Makefile $(NASK) asmhead.nas asmhead.bin asmhead.lst hankaku.bin : hankaku.txt Makefile $(MAKEFONT) hankaku.txt hankaku.bin hankaku.obj : hankaku.bin Makefile $(BIN2OBJ) hankaku.bin hankaku.obj _hankaku bootpack.bim : $(OBJS_BOOTPACK) Makefile $(OBJ2BIM) @$(RULEFILE) out:bootpack.bim stack:3136k map:bootpack.map \ $(OBJS_BOOTPACK) # 3MB+64KB=3136KB bootpack.hrb : bootpack.bim Makefile $(BIM2HRB) bootpack.bim bootpack.hrb 0 haribote.sys : asmhead.bin bootpack.hrb Makefile copy /B asmhead.bin+bootpack.hrb haribote.sys # 生成规则 %.gas : %.c bootpack.h Makefile $(CC1) -o $*.gas $*.c %.nas : %.gas Makefile $(GAS2NASK) $*.gas $*.nas %.obj : %.nas Makefile $(NASK) $*.nas $*.obj $*.lst # 命令 clean : -$(DEL) asmhead.bin -$(DEL) hankaku.bin -$(DEL) *.lst -$(DEL) *.obj -$(DEL) *.map -$(DEL) *.bim -$(DEL) *.hrb src_only : $(MAKE) clean -$(DEL) ipl10.bin -$(DEL) haribote.sys
- haribote目录只含有OS的核心代码,因此能够使用的命令只有
make
、make clean
、make src_only
。- 主要使用
make
来生成OS。其余二者基本上没用。
- 主要使用
- 注意,
make
只能生成haribote.sys,不能生成磁盘映像文件。
- haribote目录只含有OS的核心代码,因此能够使用的命令只有
-
然后,关于库的部分。在harib24g中创建了一个名为“apilib”的目录,将库相关的源代码以及Makefile移动进去。
OBJS_API = api001.obj api002.obj api003.obj api004.obj api005.obj api006.obj \ api007.obj api008.obj api009.obj api010.obj api011.obj api012.obj \ api013.obj api014.obj api015.obj api016.obj api017.obj api018.obj \ api019.obj api020.obj TOOLPATH = ../../z_tools/ INCPATH = ../../z_tools/haribote/ MAKE = $(TOOLPATH)make.exe -r NASK = $(TOOLPATH)nask.exe CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -Os -Wall -quiet GAS2NASK = $(TOOLPATH)gas2nask.exe -a OBJ2BIM = $(TOOLPATH)obj2bim.exe MAKEFONT = $(TOOLPATH)makefont.exe BIN2OBJ = $(TOOLPATH)bin2obj.exe BIM2HRB = $(TOOLPATH)bim2hrb.exe RULEFILE = ../haribote.rul EDIMG = $(TOOLPATH)edimg.exe IMGTOL = $(TOOLPATH)imgtol GOLIB = $(TOOLPATH)golib00.exe COPY = copy DEL = del # 默认 default : $(MAKE) apilib.lib # 文件生成规则 apilib.lib : Makefile $(OBJS_API) $(GOLIB) $(OBJS_API) out:apilib.lib # 通用规则 %.obj : %.nas Makefile $(NASK) $*.nas $*.obj $*.lst # 命令 clean : -$(DEL) *.lst -$(DEL) *.obj src_only : $(MAKE) clean -$(DEL) apilib.lib
- apilib目录中,能够使用的命令也只有
make
、make clean
、make src_only
。 - 显然,对于库来讲,不会有命令make run。
- 注意,此文件夹是已经默认
make
了的。
- apilib目录中,能够使用的命令也只有
-
接下来,关于应用程序部分。应用程序的Makefile比较有意思,每一个应用程序都有相应的Makefile文件。这里,以a.hrb的Makefile为例:
APP = a STACK = 1k MALLOC = 0k include ../app_make.txt
- Makefile文件只有5行,但是app_make.txt还是比较长的。
- 之所以用include,是因为所有的应用程序的Makefile都大同小异,如果将其中相同的部分改为include方式来引用就可以缩短Makefile,而且以后对Makefile修改时,也只需要修改app_make.txt文件即可应用到全部应用程序,方便省事,岂不美哉。
- app_make.txt:
TOOLPATH = ../../z_tools/ INCPATH = ../../z_tools/haribote/ APILIBPATH = ../apilib/ HARIBOTEPATH = ../haribote/ MAKE = $(TOOLPATH)make.exe -r NASK = $(TOOLPATH)nask.exe CC1 = $(TOOLPATH)cc1.exe -I$(INCPATH) -I../ -Os -Wall -quiet GAS2NASK = $(TOOLPATH)gas2nask.exe -a OBJ2BIM = $(TOOLPATH)obj2bim.exe MAKEFONT = $(TOOLPATH)makefont.exe BIN2OBJ = $(TOOLPATH)bin2obj.exe BIM2HRB = $(TOOLPATH)bim2hrb.exe RULEFILE = ../haribote.rul EDIMG = $(TOOLPATH)edimg.exe IMGTOL = $(TOOLPATH)imgtol GOLIB = $(TOOLPATH)golib00.exe COPY = copy DEL = del # 默认动作 default : $(MAKE) $(APP).hrb # 文件生成规则 $(APP).bim : $(APP).obj $(APILIBPATH)apilib.lib Makefile ../app_make.txt $(OBJ2BIM) @$(RULEFILE) out:$(APP).bim map:$(APP).map stack:$(STACK) \ $(APP).obj $(APILIBPATH)apilib.lib $(APP).hrb : $(APP).bim Makefile ../app_make.txt $(BIM2HRB) $(APP).bim $(APP).hrb $(MALLOC) haribote.img : ../haribote/ipl10.bin ../haribote/haribote.sys $(APP).hrb \ Makefile ../app_make.txt $(EDIMG) imgin:../../z_tools/fdimg0at.tek \ wbinimg src:../haribote/ipl10.bin len:512 from:0 to:0 \ copy from:../haribote/haribote.sys to:@: \ copy from:$(APP).hrb to:@: \ imgout:haribote.img # 一般规则 %.gas : %.c ../apilib.h Makefile ../app_make.txt $(CC1) -o $*.gas $*.c %.nas : %.gas Makefile ../app_make.txt $(GAS2NASK) $*.gas $*.nas %.obj : %.nas Makefile ../app_make.txt $(NASK) $*.nas $*.obj $*.lst # 命令 run : $(MAKE) haribote.img $(COPY) haribote.img ..\..\z_tools\qemu\fdimage0.bin $(MAKE) -C ../../z_tools/qemu full : $(MAKE) -C $(APILIBPATH) $(MAKE) $(APP).hrb run_full : $(MAKE) -C $(APILIBPATH) $(MAKE) -C ../haribote $(MAKE) run clean : -$(DEL) *.lst -$(DEL) *.obj -$(DEL) *.map -$(DEL) *.bim -$(DEL) haribote.img src_only : $(MAKE) clean -$(DEL) $(APP).hrb
- 注意,可以使用的命令增加了。
make
可以生成a.hrb。make run
可以生成一个磁盘文件只包含haribote.sys和a.hrb的精简版磁盘映像,然后使用QEMU运行。- 倘若只生成精简版磁盘映像,而不用QEMU运行,可以在app_make.txt添加代码:
run_vmware: $(MAKE) -C $(APILIBPATH) $(MAKE) haribote.img
- 这样,使用命令
make run_vmware
便可以实现。 make run_vmware
可以生成磁盘映像文件。
- 倘若只生成精简版磁盘映像,而不用QEMU运行,可以在app_make.txt添加代码:
make full
:在生成应用程序(a.hrb)时可能需要引用apilib.lib,但是可能出现在“make”a.hrb时apilib.lib还未生成的情况。因此,这个时候应该使用make full
。- 在
make full
中有一句代码$(MAKE) -C $(APILIBPATH)
,这句代码的作用是“先执行目录apilib中的make”,如果已经存在apilib.lib的话,这句语句将不会执行。 - 因此,如果不放心,一直使用
make full
来代替make
也是可以的。
- 在
make full_run
是make run的full版本。即将apilib和OS核心都make以后,再执行原本的make run。注意,make full不会生成磁盘印象。- 主要使用
make run
、make run_vmware
。 - 注意:已经执行过了apilib目录下的
make
了。
-
最后,是harib24g的Makefile:
TOOLPATH = ../z_tools/ INCPATH = ../z_tools/haribote/ MAKE = $(TOOLPATH)make.exe -r EDIMG = $(TOOLPATH)edimg.exe IMGTOL = $(TOOLPATH)imgtol COPY = copy DEL = del # 默认动作 default : $(MAKE) haribote.img # 文件生成规则 haribote.img : haribote/ipl10.bin haribote/haribote.sys Makefile \ a/a.hrb hello3/hello3.hrb hello4/hello4.hrb hello5/hello5.hrb \ winhelo/winhelo.hrb winhelo2/winhelo2.hrb winhelo3/winhelo3.hrb \ star1/star1.hrb stars/stars.hrb stars2/stars2.hrb \ lines/lines.hrb walk/walk.hrb noodle/noodle.hrb \ beepdown/beepdown.hrb color/color.hrb color2/color2.hrb $(EDIMG) imgin:../z_tools/fdimg0at.tek \ wbinimg src:haribote/ipl10.bin len:512 from:0 to:0 \ copy from:haribote/haribote.sys to:@: \ copy from:haribote/ipl10.nas to:@: \ copy from:make.bat to:@: \ copy from:a/a.hrb to:@: \ copy from:hello3/hello3.hrb to:@: \ copy from:hello4/hello4.hrb to:@: \ copy from:hello5/hello5.hrb to:@: \ copy from:winhelo/winhelo.hrb to:@: \ copy from:winhelo2/winhelo2.hrb to:@: \ copy from:winhelo3/winhelo3.hrb to:@: \ copy from:star1/star1.hrb to:@: \ copy from:stars/stars.hrb to:@: \ copy from:stars2/stars2.hrb to:@: \ copy from:lines/lines.hrb to:@: \ copy from:walk/walk.hrb to:@: \ copy from:noodle/noodle.hrb to:@: \ copy from:beepdown/beepdown.hrb to:@: \ copy from:color/color.hrb to:@: \ copy from:color2/color2.hrb to:@: \ imgout:haribote.img # 命令 run : $(MAKE) haribote.img $(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin $(MAKE) -C ../z_tools/qemu install : $(MAKE) haribote.img $(IMGTOL) w a: haribote.img full : $(MAKE) -C haribote $(MAKE) -C apilib $(MAKE) -C a $(MAKE) -C hello3 $(MAKE) -C hello4 $(MAKE) -C hello5 $(MAKE) -C winhelo $(MAKE) -C winhelo2 $(MAKE) -C winhelo3 $(MAKE) -C star1 $(MAKE) -C stars $(MAKE) -C stars2 $(MAKE) -C lines $(MAKE) -C walk $(MAKE) -C noodle $(MAKE) -C beepdown $(MAKE) -C color $(MAKE) -C color2 $(MAKE) haribote.img run_full : $(MAKE) full $(COPY) haribote.img ..\z_tools\qemu\fdimage0.bin $(MAKE) -C ../z_tools/qemu install_full : $(MAKE) full $(IMGTOL) w a: haribote.img run_os : $(MAKE) -C haribote $(MAKE) run clean : # 不执行任何操作 src_only : $(MAKE) clean -$(DEL) haribote.img clean_full : $(MAKE) -C haribote clean $(MAKE) -C apilib clean $(MAKE) -C a clean $(MAKE) -C hello3 clean $(MAKE) -C hello4 clean $(MAKE) -C hello5 clean $(MAKE) -C winhelo clean $(MAKE) -C winhelo2 clean $(MAKE) -C winhelo3 clean $(MAKE) -C star1 clean $(MAKE) -C stars clean $(MAKE) -C stars2 clean $(MAKE) -C lines clean $(MAKE) -C walk clean $(MAKE) -C noodle clean $(MAKE) -C beepdown clean $(MAKE) -C color clean $(MAKE) -C color2 clean src_only_full : $(MAKE) -C haribote src_only $(MAKE) -C apilib src_only $(MAKE) -C a src_only $(MAKE) -C hello3 src_only $(MAKE) -C hello4 src_only $(MAKE) -C hello5 src_only $(MAKE) -C winhelo src_only $(MAKE) -C winhelo2 src_only $(MAKE) -C winhelo3 src_only $(MAKE) -C star1 src_only $(MAKE) -C stars src_only $(MAKE) -C stars2 src_only $(MAKE) -C lines src_only $(MAKE) -C walk src_only $(MAKE) -C noodle src_only $(MAKE) -C beepdown src_only $(MAKE) -C color src_only $(MAKE) -C color2 src_only -$(DEL) haribote.img refresh : $(MAKE) full $(MAKE) clean_full -$(DEL) haribote.img
- 可以使用很多命令:
make
:生成一个包含OS内核以及全部应用程序的磁盘映像。make run
:make
之后使用QEMU启动。make install
:make
后将磁盘映像文件安装到软盘中。make full
:将OS内核、apilib和应用程序全部执行make后生成磁盘映像。make run_full
:make full
后make run
。make install_full
:make full
后make install
。make run_os
: 将OS核心make后make run
,当只对OS核心进行修改时可以使用这个命令。make clean
:clean命令原本是用于清除临时文件的,但由于这个Makefile不生成临时文件(.map之类的),因此这个命令不执行任何操作。make src_only
:将生成的磁盘映像文件删除。make clean_full
:对OS核心、apilib和全部应用程序执行make clean
,这样清除所有临时文件。make src_only_full
:对OS核心、apilib和全部应用程序执行make src_only
,这样将清除所有的临时文件和最终生成文件。执行这个命令后,make
和make run
就无法运行了(因为默认apilib是被make了的,即文件apilib.lib是存在的)。可以使用make full
代替。make refresh
:make full
后make clean_full
。从执行过make src_only_full
的状态执行这个命令的话,就会恢复到可以直接make
和make run
的状态。
- 可以使用很多命令:
-
经过上述划分以及修改后,make的速度将会大大提升。
- 在这个过程中,顺便将某些应用程序修改了一下。比如将winhelo.hrb这个应用程序,添加了等待键盘输入的代码。
- 修改的应用程序有winhelo、winhelo2、star1、stars和stars2。
-
在harib24g下
make full
并用VMware运行:
- 全家桶(笑)。
8. 写在23:20
- 现在是2020.5.7 23:20。
- 已经到了既定的日期,可是还没有完成任务,真是羞赧不堪啊。
- 预计这月10号可以完成。
- 昨日,爸妈去种花生。早上因为起床气没及时去帮家里种花生,让老妈生气了,现在想想自己真是混蛋,后悔极了。
- 从昨天早上七点半,到傍晚六点半,一直在种花生,可是还是没有种完。
- 这次应该是我自初三以来第一次帮家里种花生。高中学业繁忙,大学回不了家。
- 农民阶级是最受累的阶级。那种面朝黄土背朝天的滋味真的不好受。
- 出去中了一天的花生,回来后我的腰背、手臂和腿脚便酸痛得厉害。更甚的是,回来后后颈的皮肤一直如针扎般疼痛,原来是晒伤了!
- 我尽量将这一切说的云淡风轻,可是,农活是真的不轻松:翻地、施肥、打岭、栽种、喷药、贴地膜、盖土、修地边。
- 我的手掌原本是肉色的,施肥以后就变成了黑色的,撒种以后就变成了红色的,盖完土以后又变成了黄色。
- 今天完成了第27天的工作,文档行数也过了1000行,明天继续。