Day20

今天讲到API,总算和课上说的系统调用、软中断什么的联系起来了。

一些感想

系统调用的过程

放上课上的笔记
API
在实现API的时候也有一些细节:

  1. 用EDX来存储功能号
    在系统调用运行软中断的时候,如果光靠中断号来对应不同函数,能提供的数量有限。因此在中断时还附加功能号,存放在EDX中,根据EDX的值来判断该执行什么中断子程序。
  2. 当心寄存器
    参考第7节,_asm_cons_putchar调用_cons_putchar的时候,改动了寄存器的值,导致不能正常显示。所以在调用前后,PUSHAD、POPAD一下比较方便。

具体来看API的实现吧。

程序整理

harib17a

略过对API的讲解,这一节把console中的各种指令都封装成了一个个函数,这样对于console_task来说无疑是简洁很多了。
整理过后进入正题。。

显示单个字符的API(1)

harib17b

这里还没用到中断,其实不太想写。还是稍微写一下吧。。
首先要很奇怪地把cons地址储存起来

*((int *)0x0fec) = (int)&cons;

当应用程序要打印字符的时候,会调用asm

_asm_cons_putchar:
        PUSH    1
        AND        EAX,0xff ; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
        PUSH    EAX
        PUSH    DWORD [0x0fec] ; 读取内存并PUSH该字符值
        CALL    _cons_putchar
        ADD        ESP,12 ; 将栈中数据丢弃
        RET

应用程序写成这样

[BITS 32]
        MOV        AL,'A'
        CALL    0xbe3
fin:
        HLT
        JMP        fin

但是目前这样写还是有错

显示单个字符API(2)

harib17c

因为应用程序和操作系统不在同一个段中,应用程序在调用(CALL)系统API的时候,应该同时指明段地址(即操作系统所在段),也就是far-CALL
同时记得把上面的asm后来的RET改成RETF

[BITS 32]
        MOV        AL,'A'
        CALL    2*8:0xbe3
fin:
        HLT
        JMP        fin

这个0xbe3是汇编好了之后,查找到的asm那个汇编函数所在的地址。。着实不方便

结束应用程序

harib17d

在汇编中添加一个_farcall的函数,用于应用程序调用之后返回。

_farcall: ; void farcall(int eip, int cs);
        CALL    FAR    [ESP+4] ; eip, cs
        RET

当需要执行应用程序的时候,先farcall到应用程序所在位置,应用程序最后RETF就行了

[BITS 32]
        MOV        AL,'A'
        CALL    2*8:0xbe8
        RETF

之前还是be3,现在变成be8了。。可见这样的API确实不好用。。

不随操作系统版本而改变的API

harib17e

讲到中断啦。。首先先在IDT里注册一下

set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);

这样需要的时候,直接

INT 0x40

这样就很方便了。
因为是中断,asm也需要改变一下

_asm_cons_putchar:
        STI        
        PUSH    1
        AND        EAX,0xff ; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
        PUSH    EAX
        PUSH    DWORD [0x0fec] ; 读取内存并PUSH该字符值
        CALL    _cons_putchar
        ADD        ESP,12 ; 将栈中数据丢弃
        IRETD

这样才有点像API嘛。。

为应用程序自由命名

harib17f

就像昨天预测的,执行应用程序直接找有没有对应名字就好了。。

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;
    int i;

    // 根据命令行生成文件名
    for(i = 0; i < 13; i++)
    {
        if(cmdline[i] <= ' ')
        {
            break;
        }
        name[i] = cmdline[i];
    }
    name[i] = 0; // 暂且将文件名的后面置为0

    // 寻找文件
    finfo = file_search(name, (struct FILEINFO *)(ADR_DISKIMG + 0x002600), 224);
    if(finfo == 0 && name[i - 1] != '.')
    {
        // 由于找不到文件,故在文件名后面加上“.hrb”后重新寻找
        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));
        set_segmdesc(gdt + 1003, finfo->size - 1, (int)p, AR_CODE32_ER);
        farcall(0, 1003 * 8);
        memman_free_4k(memman, (int)p, finfo->size);
        cons_newline(cons);
        return 1;
    }
    // 没有找到文件的情况
    return 0;
}

这样可以说是比较贴近windows用法的程序调用方式了。

当心寄存器

harib17g

为了防止寄存器被改之后出错,先保存一下各种寄存器,之后记得恢复吧

_asm_cons_putchar:
        STI        
        PUSHAD
        PUSH    1
        AND        EAX,0xff ; 将AH和EAX的高位置0,将EAX置为已存入字符编码的状态
        PUSH    EAX
        PUSH    DWORD [0x0fec] ; 读取内存并PUSH该字符值
        CALL    _cons_putchar
        ADD        ESP,12 ; 将栈中数据丢弃
        POPAD
        IRETD

用API显示字符串

harib17h

首先提供两种输入字符串的API

void cons_putstr0(struct CONSOLE *cons, char *s)
{
    for(; *s != 0; ++s)
    {
        cons_putchar(cons, *s, 1);
    }
    return;
}

void cons_putstr1(struct CONSOLE *cons, char *s, int l)
{
    int i;
    for(i = 0; i < l; ++i)
    {
        cons_putchar(cons, s[i], 1);
    }
    return;
}

因为之前没有这样的API,所以打印字符都显得比较丑),有了这两个之后,就可以更改之前写的各种命令了(略)。
在这个版本里添加了功能号,这才是中断服务程序啊。

_asm_hrb_api:
        STI
        PUSHAD    ; 用于保存寄存器值的PUSH
        PUSHAD    ; 用于向hrb_api传值的PUSH
        CALL    _hrb_api
        ADD        ESP,32
        POPAD
        IRETD

c语言部分我改成用switch来写,这样更易于维护吧)

void hrb_api(int edi, int esi, int ebp, int esp, int ebx, int edx, int ecx, int eax)
{
    struct CONSOLE *cons = (struct CONSOLE *) *((int *) 0x0fec);
    switch(edx)
    {
    case 1:
        cons_putchar(cons, eax & 0xff, 1);
        break;
    case 2:
        cons_putstr0(cons, (char *)ebx);
        break;
    case 3:
        cons_putstr1(cons, (char *)ebx, ecx);
        break;
    default:
        break;
    }
    return;
}

然后不要忘了注册IDT

set_gatedesc(idt + 0x40, (int)asm_hrb_api, 2 * 8, AR_INTGATE32);

以后提供更多的API,增加case就好了。完美
应用程序示例:

[INSTRSET "i486p"]
[BITS 32]
        MOV        ECX,msg
        MOV        EDX,1
putloop:
        MOV        AL,[CS:ECX]
        CMP        AL,0
        JE        fin
        INT        0x40
        ADD        ECX,1
        JMP        putloop
fin:
        RETF
msg:
        DB    "hello",0

这个程序是可以正常运行的。
但是另一个程序还是有问题,作者留了个坑到明天。。