Day20
今天讲到API,总算和课上说的系统调用、软中断什么的联系起来了。
一些感想
系统调用的过程
放上课上的笔记
在实现API的时候也有一些细节:
- 用EDX来存储功能号
在系统调用运行软中断的时候,如果光靠中断号来对应不同函数,能提供的数量有限。因此在中断时还附加功能号,存放在EDX中,根据EDX的值来判断该执行什么中断子程序。 - 当心寄存器
参考第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
这个程序是可以正常运行的。
但是另一个程序还是有问题,作者留了个坑到明天。。