Day22
一些感想
都会在什么情况下触发异常中断
昨天有:修改不属于自己段的内存、修改段寄存器
今天有:执行IO的IN、OUT指令,执行中断CLI、STI、HLT指令、CALL应用程序之外的地址
看起来应用程序只能限制在调用API了。
其它异常
- 栈异常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)
- 除零异常0x00
- 非法指令异常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();
}
看起来没什么问题。。今天先到这里