Day22

今天继续完善保护操作系统,并且提供了各种错误的中断。

一些感想

都会在什么情况下触发异常中断

昨天有:修改不属于自己段的内存、修改段寄存器
今天有:执行IO的IN、OUT指令,执行中断CLI、STI、HLT指令、CALL应用程序之外的地址
看起来应用程序只能限制在调用API了。

其它异常

  1. 栈异常0x0c
    在栈空间上触发的异常。同时许久没讲的esp总算派上用场了,应该给出当前中断之后,栈顶指针地址
    esp[0] // EDI esp[0 ~ 7]为_asm_inthandler中PUSHAD的结果
    esp[1] // ESI
    esp[2] // EBP
    esp[4] // EBX
    esp[5] // EDX
    esp[6] // ECX
    esp[7] // EAX
    esp[8] // DS esp[8 ~ 9]为_asm_inthandler中PUSH的结果
    esp[9] // ES
    esp[10] // 错误编号(基本上是0,显示出来也没什么意思)
    esp[11] // EIP
    esp[12] // CS esp[10 ~ 15]为异常产生时CPU自动PUSH的结果
    esp[13] // EFLAGS
    esp[14] // ESP (应用程序用ESP)
    esp[15] // SS(应用程序用SS)
    
  2. 除零异常0x00
  3. 非法指令异常0x06

解读可执行程序开始存放的信息

这一部分是作者提供的思路,只对该系统适用

  • 0x0000 (DWORD)
    存放的数据段大小,需要预先在生成.hrb时指定
  • 0x0004 (DWORD)
    Hari 标记,只是用来判断是不是可执行文件
  • 0x0008 (DWORD)
    数据段内预备空间的大小,暂时用不上
  • 0x000c (DWORD)
    应用程序启动时ESP寄存器的初始值,即初始的栈空间大小
  • 0x0010 (DWORD)
    向数据段传送的部分的字节数
  • 0x0014 (DWORD)
    向数据段传送的部分在.hrb文件中的起始地址
  • 0x0018 (DWORD)
    0xe9000000 JMP到应用程序运行的入口地址
  • 0x001c (DWORD)
    存放应用程序运行入口地址减去0x20之后的值。这样做可以通过直接JMP 0x1b之后运行应用程序
  • 0x0020 (DWORD)
    malloc函数要使用的地址

对于包含数据的程序,需要先分配数据段,再启动程序

保护操作系统(5)

harib19a

应用程序尝试使用IO指令

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

尝试中断

[INSTRSET "i486p"]
[BITS 32]
        CLI
fin:
        HLT
        JMP        fin

尝试CALL别的地方

[INSTRSET "i486p"]
[BITS 32]
        CALL    2*8:0xac1
        MOV        EDX,4
        INT        0x40

均会触发异常中断。
但是注意不要在API里下毒。。

帮助发现bug

harib19b

做一个数组越界的程序

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

因为是栈中分配的数组,现在做一个栈异常

_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

接下来就写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);    // 强制结束程序
}

在IDT中登记略

通过对刚才bug程序的测试,发现是对赋值’C’时出了错。
PS:具体查看指令
0x24为HariMain起始地址 0x24 + 0x1e(偏移) = 0x42(EIP)
HariMain的0x1e为 MOV BYTE[11 + EBP],67 即刚才说的’C’

强制结束应用程序

harib19c

比如一个死循环程序,我们需要通过某些方法来结束它,这里定义为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); // 指向cmd的ESP
    task_cons->tss.eip = (int)asm_end_app;
    io_sti();
}

当按下强制结束键之后,改变寄存器值,EIP直接指到asm_end_app,结束程序
为了防止程序不在运行的时候结束程序,需要判断ss0不为0,(ss0不为0时活动)

_asm_end_app:
;    EAX为tss.esp0的地址
        MOV        ESP,[EAX]
        MOV        DWORD [EAX+4],0
        POPAD
        RET                    ; 返回cmd_app

在task_alloc初始化的时候,也需要先把ss0置为0
这样就可以强制停止应用程序了

用C语言显示字符串(1)

harib19d

做一个C语言显示字符串的API

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

用这个api来显示字符串。但什么都显示不出来。
但是作者奇怪地想到了之前的RETF没用了,需要修改(参考昨天第二节)
这样可以运行打印字符,但不能运行打印字符串

用C语言显示字符串(3)

harib19e

为了debug,在调用API的时候,看看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 CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
    struct TASK *task = task_now();
    char s[12];
    switch(edx)
    {
    case 1:
        cons_putchar(cons, eax & 0xff, 1);
        break;
    case 2:
        cons_putstr0(cons, (char *)ebx + cs_base);
        spritnf(s, "%08X\n", ebx);
        cons_putstr0(cons, s);
        break;
    case 3:
        cons_putstr1(cons, (char *)ebx + cs_base, ecx);
        break;
    case 4:
        return &(task->tss.esp0);
        break;
    default:
        break;
    }
    return 0;
}

EBX会显示为0x400,原因是认为”hello world”这个字符串存放在0x400中(其实并不是)
事实上,这个常量字符串应该存放在数据段中,但是我们并没指定。所以需要指定一下

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

这样就先分配好数据段,复制数据部分,就可以正确执行了。

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

        GLOBAL    _HariMain

[SECTION .text]

_HariMain:
        MOV        EDX,2
        MOV        EBX,msg
        INT        0x40
        MOV        EDX,4
        INT        0x40

[SECTION .data]

msg:
        DB    "hello, world", 0x0a, 0

SECTION 的意思是表示这部分东西,应该放到代码段还是数据段

显示窗口

harib19f

总算做好了各种异常、各种API,现在又跑到图形化了的地方了。开始着手可以让应用程序申请窗口。
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
    switch(edx)
    {
    case 1:
        cons_putchar(cons, eax & 0xff, 1);
        break;
    case 2:
        cons_putstr0(cons, (char *)ebx + cs_base);
        break;
    case 3:
        cons_putstr1(cons, (char *)ebx + cs_base, ecx);
        break;
    case 4:
        return &(task->tss.esp0);
        break;
    case 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之上
        break;
    default:
        break;
    }
    return 0;
}

汇编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

测试一下

char buf[150 * 50];

void HariMain(void)
{
    int win;
    win = api_openwin(buf, 150, 50, -1, "hello");
    api_end();
}

能出窗口了

在窗口中描绘字符和方块

harib19g

添加一些有关窗口的API
显示字符:
EDX = 6
EBX = 窗口句柄
ESI = 显示位置的x坐标
EDI = 显示位置的y坐标
EAX = 色号
ECX = 字符串长度
EBP = 字符串
描绘方块:
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)
{
    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
    switch(edx)
    {
    case 1:
        cons_putchar(cons, eax & 0xff, 1);
        break;
    case 2:
        cons_putstr0(cons, (char *)ebx + cs_base);
        break;
    case 3:
        cons_putstr1(cons, (char *)ebx + cs_base, ecx);
        break;
    case 4:
        return &(task->tss.esp0);
        break;
    case 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之上
        break;
    case 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);
        break;
    case 7:
        sht = (struct SHEET *) ebx;
        boxfill8(sht->buf, sht->bxsize, ebp, eax, ecx, esi, edi);
        sheet_refresh(sht, eax, ecx, esi + 1, edi + 1);
        break;
    default:
        break;
    }
    return 0;
}

汇编api:

_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就是把各种寄存器值存起来
来测试一下

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

看起来没什么问题。。今天先到这里