第27天 LDT與庫

第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發送的“關閉窗口”請求數據時所進行的處理,主要是釋放指定的圖層。
  • 修改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圖層好像出現了問題!
  • 這次的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現在未使用。
  • 修改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中原封不動拆出來的,這裏不再羅列。
  • 由於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)
      {
          ……
      }
      
    • 其他應用程序的修改和上述修改大同小異,這裏不再羅列。
  • 關於庫的知識

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.com
    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的核心代碼,因此能夠使用的命令只有makemake cleanmake src_only
      • 主要使用make來生成OS。其餘二者基本上沒用。
    • 注意,make只能生成haribote.sys,不能生成磁盤映像文件
  • 然後,關於庫的部分。在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.com
    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目錄中,能夠使用的命令也只有makemake cleanmake src_only
    • 顯然,對於庫來講,不會有命令make run
    • 注意,此文件夾是已經默認make了的。
  • 接下來,關於應用程序部分。應用程序的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.com
      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可以生成磁盤映像文件。
      • 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 runmake 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.com
    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 fullmake run
      • make install_full:make fullmake 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,這樣將清除所有的臨時文件和最終生成文件。執行這個命令後,makemake run就無法運行了(因爲默認apilib是被make了的,即文件apilib.lib是存在的)。可以使用make full代替。
      • make refresh:make fullmake clean_full。從執行過make src_only_full的狀態執行這個命令的話,就會恢復到可以直接makemake run的狀態。
  • 經過上述劃分以及修改後,make的速度將會大大提升。

    • 在這個過程中,順便將某些應用程序修改了一下。比如將winhelo.hrb這個應用程序,添加了等待鍵盤輸入的代碼。
    • 修改的應用程序有winhelo、winhelo2、star1、stars和stars2。
  • 在harib24g下make full並用VMware運行:

    • 全家桶(笑)

8. 寫在23:20

  • 現在是2020.5.7 23:20。
  • 已經到了既定的日期,可是還沒有完成任務,真是羞赧不堪啊。
  • 預計這月10號可以完成。
  • 昨日,爸媽去種花生。早上因爲起牀氣沒及時去幫家裏種花生,讓老媽生氣了,現在想想自己真是混蛋,後悔極了。
  • 從昨天早上七點半,到傍晚六點半,一直在種花生,可是還是沒有種完。
  • 這次應該是我自初三以來第一次幫家裏種花生。高中學業繁忙,大學回不了家。
  • 農民階級是最受累的階級。那種面朝黃土背朝天的滋味真的不好受。
  • 出去中了一天的花生,回來後我的腰背、手臂和腿腳便痠痛得厲害。更甚的是,回來後後頸的皮膚一直如針扎般疼痛,原來是曬傷了!
  • 我儘量將這一切說的雲淡風輕,可是,農活是真的不輕鬆:翻地、施肥、打嶺、栽種、噴藥、貼地膜、蓋土、修地邊。
  • 我的手掌原本是肉色的,施肥以後就變成了黑色的,撒種以後就變成了紅色的,蓋完土以後又變成了黃色。
  • 今天完成了第27天的工作,文檔行數也過了1000行,明天繼續。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章