21.1 攻克难题——字符串显示API
显示单个字符时,用
[CS:ECX]
的方式特意指定了
CS(代码段寄存器),因此可以成功读取
msg的内容。但在显示字符串时,由于无法指定段地址,程序误以为是
DS而从完全错误的内存地址中读取了内容。hrb_api并不知道代码段的起始位置位于内存的哪个地址,但cmd_app应该知道,因为当初设置这个代码段的正是cmd_app。
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline){
(中略)
if (finfo != 0) {
/* 找到了与字符串相同的文件 */
p = (char *) memman_alloc_4k(memman, finfo->size);
/*这里*/ *((int *) 0xfe8) = (int) p;
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;
}
//根据输入参数打印数据 eax为当前字符 ebx为字符串 ecx长度
void 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);
if(edx == 1)
cons_putchar(cons, eax&0xff, 1);
else if(edx == 2)
/*这里*/cons_putstr0(cons, (char*)ebx+cs_base);
else if(edx == 3)
/*这里*/cons_putstr1(cons, (char*)ebx+cs_base, ecx);
return;
}
21.2 用C语言编写应用程序
按照之前章节的理解,都是从汇编程序中输入了提前设定的字符才能进行打印,若将其整合成一个函数,当调用该函数时,便可打印岂不是更方便?函数api_putchar就是将输入参数c写入寄存器,并调用INT 0x40,该中断执行文件console.c中的hrb_api函数进行字符打印(详情看20.6节)。
按照文件分工,hello3.c文件调用了a_nask.nas文件中的函数,需要在Makefile文件中添加如下代码(文件格式请参考30天自制操作系统(第1-3天)中的2.6节):
hello3.bim : hello3.obj a_nask.obj Makefile
$(OBJ2BIM) @$(RULEFILE) out:hello3.bim map:hello3.map hello3.obj a_nask.obj
hello3.hrb : hello3.bim Makefile
$(BIM2HRB) hello3.bim hello3.hrb 0
/* hello3.c */
void api_putchar(int c);
void HariMain(void)
{
api_putchar('h');
api_putchar('e');
api_putchar('l');
api_putchar('l');
api_putchar('o');
return;
}
/* a_nask.nas */
_api_putchar: ; void api_putchar(int c);
MOV EDX,1
MOV AL,[ESP+4] ; c
INT 0x40
RET
21.3 保护操作系统(1)
需要为应用程序提供专用的内存空间,并且告诉它们
“
别的地方不许碰哦
”。要做到这一点,可以创建应用程序专用的数据段,并在应用程序运行期间,将
DS
和
SS
指向该段地址。
操作系统用代码段
……2 * 8
操作系统用数据段
……1 * 8
应用程序用代码段
……1003 * 8
应用程序用数据段
……1004 * 8
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline){
(中略)
char name[18], *p, *q;
(中略)
if (finfo != 0) {
/* 找到了与字符串相同的文件 */
p = (char *) memman_alloc_4k(memman, finfo->size);
/*这里*/q = (char *) memman_alloc_4k(memman, 64*1024);
*((int *) 0xfe8) = (int) p;
file_loadfile(finfo->clustno, finfo->size, p, fat, (char *) (ADR_DISKIMG + 0x003e00));
set_segmdesc(gdt + 1003, finfo->size - 1, (int) p, AR_CODE32_ER);
/*这里*/set_segmdesc(gdt + 1004, 64*1024 - 1, (int) q, AR_DATA32_RW);
(中略)
/*这里*/start_app(0, 1003 * 8, 64 * 1024, 1004 * 8);
memman_free_4k(memman, (int) p, finfo->size);
/*这里*/memman_free_4k(memman, (int) q, 64*1024);
cons_newline(cons);
return 1;
}
return 0;
}
;void start_app(int eip, int cs, int esp, int ds);
;操作系统栈的ESP保存在0xfe4这个地址,以便从应用程序返回操作系统时使用
_start_app:
PUSHAD ;将8个32位寄存器压入栈,即8*(32/8)=32字节
MOV EAX,[ESP+36] ; 应用程序用EIP
MOV ECX,[ESP+40] ; 应用程序用CS
MOV EDX,[ESP+44] ; 应用程序用ESP
MOV EBX,[ESP+48] ; 应用程序用DS/SS
MOV [0xfe4],ESP ; 操作系统用ESP
CLI ; 在切换过程中禁止中断请求
MOV ES,BX
MOV SS,BX
MOV DS,BX
MOV FS,BX
MOV GS,BX
MOV ESP,EDX ; ESP为应用程序
STI ; 切换完成后恢复中断请求
PUSH ECX ; 用于far-CALL的PUSH(cs=1003*8)
PUSH EAX ; 用于far-CALL的PUSH(eip=0)
CALL FAR [ESP] ; 调用应用程序
; 应用程序结束后返回此处
MOV EAX,1*8 ; 操作系统用DS/SS
CLI ; 再次进行切换,禁止中断请求
MOV ES,AX
MOV SS,AX
MOV DS,AX
MOV FS,AX
MOV GS,AX
MOV ESP,[0xfe4] ; 切换成操作系统ESP
STI ; 切换完成后恢复中断请求
POPAD ; 恢复之前保存的寄存器值
RET
21.4 对异常的支持
要想强制结束程序,只要在中断号
0x0d
中注册一个函数即可,这是因为在x86架构规范中,当应用程序试图破坏操作系统,或者试图违背操作系统的设置时,就会自动产生
0x0d
中断,因此该中断也被称为
“
异常”。写一个与
_asm_inthandler20函数大同小异的_asm_inthandler0d函数,与_asm_inthandler20的主要区别在于增加了STI/CLI这样控制中断请求禁止、恢复的指令和根据inthandler0d的结果来执行强制结束应用程序的操作
。
_asm_inthandler0d:
STI
PUSH ES
PUSH DS
PUSHAD
MOV AX,SS
CMP AX,1*8
JNE .from_app
; 当操作系统活动时产生中断的情况和之前差不多
MOV EAX,ESP
PUSH SS ; 保存中断时的SS
PUSH EAX ; 保存中断时的ESP
MOV AX,SS
MOV DS,AX
MOV ES,AX
CALL _inthandler0d
ADD ESP,8
POPAD
POP DS
POP ES
ADD ESP,4 ; 在INT 0x0d中需要这句
IRETD
.from_app:
; 当应用程序活动时产生中断
CLI
MOV EAX,1*8
MOV DS,AX ; 先仅将DS设定为操作系统用
MOV ECX,[0xfe4] ; 操作系统的ESP
ADD ECX,-8
MOV [ECX+4],SS ; 保存产生中断时的SS
MOV [ECX ],ESP ; 保存产生中断时的ESP
MOV SS,AX
MOV ES,AX
MOV ESP,ECX
STI
CALL _inthandler0d
CLI
CMP EAX,0
JNE .kill
POP ECX
POP EAX
MOV SS,AX ; 将SS恢复为应用程序用
MOV ESP,ECX ; 将ESP恢复为应用程序用
POPAD
POP DS
POP ES
ADD ESP,4 ; INT 0x0d需要这句
IRETD
.kill:
; 将应用程序强制结束
MOV EAX,1*8 ; 操作系统用的DS/SS
MOV ES,AX
MOV SS,AX
MOV DS,AX
MOV FS,AX
MOV GS,AX
MOV ESP,[0xfe4] ; 强制返回到start_app时的ESP
STI ; 切换完成后恢复中断请求
POPAD ; 恢复事先保存的寄存器值
RET