当前位置: 首页 > news >正文

Day20 API

文章目录

      • 1. 显示单个字符的API(harib17c)
      • 2. 结束应用程序(harib17d)
      • 3. 不随操作系统改变的API(harib17e)
      • 4. 为应用程序自由命名(harib17g)
      • 5. 用API显示字符串(harib17h)

1. 显示单个字符的API(harib17c)

重新整理console.c文件的代码之后,产出了比较内聚的函数,这些函数接口都属于操作系统内部所使用的函数,并不对外提供。

// 文件 bootpack.h
void cons_putchar(struct CONSOLE *cons, int chr, char move);	// console打印一般字符
void cons_newline(struct CONSOLE *cons);	// 换行
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal);	// 运行指令
void cmd_mem(struct CONSOLE *cons, unsigned int memtotal);	// 打印内存信息
void cmd_cls(struct CONSOLE *cons);	// 清屏
void cmd_dir(struct CONSOLE *cons);	// 显示文件信息
void cmd_type(struct CONSOLE *cons, int *fat, char *cmdline);	// 打印文件内容
void cmd_hlt(struct CONSOLE *cons, int *fat);	// 休眠

如果期望在应用程序(以Day19章最后一节的hlt.hrb为例)中打印单个字符,就需要使用相关cons_putchar函数的系统调用,即API。
由于cons_putchar本身包含3个参数,因此需要在naskfunc.nas文件中创建一个_asm_cons_putchar函数。

_asm_cons_putchar:PUSH	1			# move参数赋值AND		EAX,0xff	# 低8bits于0xff进行按位与运算,保留EAX的低8bits(输入的字符)PUSH	EAX			# chr参数赋值PUSH	DWORD [0x0fec]	# cons参数赋值CALL	_cons_putchar	# 调用cons_putcharADD		ESP,12		# 将栈中的数据丢弃(升高ESP,认为已push的3个参数可以被覆盖)RETF	# far-RET

其中0x0fec是在操作系统的console_task中将cons的地址放在了0x0fec位置。

void console_task(struct SHEET *sheet, unsigned int memtotal)
{/* 省略 */struct CONSOLE cons;/* 省略 */	*((int *) 0x0fec) = (int) &cons;	// 取出cons地址赋值给一个本行有效的临时的全局int变量,该全局int变量的地址为0x0fec/* 省略 */
}

如此,只是操作系统的代码完成了,并且封装了一个名为_asm_cons_putchar的API。需要先make编译完操作系统后,查看map文件中_asm_cons_putchar的地址,然后在应用程序中调用这个地址。

那么为啥不直接在CALL指令后跟_asm_cons_putchar函数名呢?
因为操作系统和应用程序并不在一起编译。使用汇编器对应用程序汇编时,并不包含操作系统本身的代码,因此汇编器无法得知要调用的函数地址,汇编时就会出错。

# 文件 hlt.nas
[BITS 32]MOV		AL,'A'CALL    2*8:0xbe3		# far-CALL
fin:HLTJMP		fin

在C语言中,goto语句和函数调用的处理方式完全不同,但是在汇编语言中CALL指令和JMP指令本质上差不多,只是在执行CALL时,会自动将函数执行结束后的返回地址PUSH到栈中。

2. 结束应用程序(harib17d)

此时的hlt.hrb被farjmp之后,console窗口就卡在那里了。如果期望调用完可以返回,则需要修改应用程序hlt.nas中的HLT为RETF,并且修改操作系统中farjmp为farcall。

# 文件 naskfunc.nas
_farcall:		# void farcall(int eip, int cs);CALL	FAR	[ESP+4]				; eip, csRET
# 文件hlt.nas, 执行后会打印hello
[BITS 32]MOV		AL,'h'CALL    2*8:0xbe8MOV		AL,'e'CALL    2*8:0xbe8MOV		AL,'l'CALL    2*8:0xbe8MOV		AL,'l'CALL    2*8:0xbe8MOV		AL,'o'CALL    2*8:0xbe8RETF

在这里插入图片描述

3. 不随操作系统改变的API(harib17e)

当前的缺点很明显,就是如果修改系统代码,就可能导致_asm_cons_putchar函数的地址发生改变,进而导致应用软件需要重新编译。
其中一个解决该问题的办法就是将函数符号注册给IDT,类似于中断服务函数的注册。IDT中最多可以256个函数,从其中选择一个空闲的拿来用就好。

// dsctbl.c文件
void init_gdtidt(void)
{struct GATE_DESCRIPTOR    *idt = (struct GATE_DESCRIPTOR    *) ADR_IDT;/* 省略 *//* idt的设置 */set_gatedesc(idt + 0x2c, (int) asm_inthandler2c, 2 * 8, AR_INTGATE32);set_gatedesc(idt + 0x40, (int) asm_cons_putchar, 2 * 8, AR_INTGATE32);	// 注册asm_cons_putchar函数到idt的0x40/* 省略 */
}

还需要修改应用程序hlt.nas的CALL为INT,MS-DOS的API据说也是这么做的。

# hlt.nas 文件
[BITS 32]MOV		AL,'h'INT		0x40	# CALL 修改为 INTMOV		AL,'e'INT		0x40MOV		AL,'l'INT		0x40MOV		AL,'l'INT		0x40MOV		AL,'o'INT		0x40RETF

使用INT指令调用函数的时候,会被视作中断来处理,因此在被调用的函数内部,无法使用RET进行返回,需要替换为IRETD指令。另外,当INT调用的时候,CPU会自动执行CLI指令来禁止中断请求,为了防止这种情况,在应用函数_asm_cons_putchar的开头首先执行STI用来取消禁止中断。

# naskfunc.nas文件
_asm_cons_putchar:STI		# 取消禁止中断PUSH	1AND		EAX,0xffPUSH	EAXPUSH	DWORD [0x0fec]CALL	_cons_putcharADD		ESP,12IRETD	# 替换为IRETD

4. 为应用程序自由命名(harib17g)

为了代码的扩展性,将cmd_hlt修改为cmd_app,它会根据命令行的内容判断文件名,并运行相应的应用程序。如果找到文件则返回1,未找到则返回0。如果返回0,则直接做错误处理。

// console.c文件
void cons_runcmd(char *cmdline, struct CONSOLE *cons, int *fat, unsigned int memtotal)
{if (strcmp(cmdline, "mem") == 0) {cmd_mem(cons, memtotal);} else if (strcmp(cmdline, "cls") == 0) {cmd_cls(cons);} else if (strcmp(cmdline, "dir") == 0) {cmd_dir(cons);} else if (strncmp(cmdline, "type ", 5) == 0) {cmd_type(cons, fat, cmdline);} else if (cmdline[0] != 0) {if (cmd_app(cons, fat, cmdline) == 0) {/* 不是命令,不是应用程序,也不是空字符 */putfonts8_asc_sht(cons->sht, 8, cons->cur_y, COL8_FFFFFF, COL8_000000, "Bad command.", 12);cons_newline(cons);cons_newline(cons);}}return;
}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; /* 暂且将文件名后面一个元素空间清空 *//* 寻找文件 */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;
}

此时,可修改hlt.nas文件名为hello.nas,顺便加上循环。汇编之后会生成hello.hrb。

# hello.nas 文件
[INSTRSET "i486p"]
[BITS 32]MOV		ECX,msg
putloop:MOV		AL,[CS:ECX]CMP		AL,0JE		finINT		0x40ADD		ECX,1JMP		putloop
fin:RETF
msg:DB	"hello",0

5. 用API显示字符串(harib17h)

常用的显示字符串策略有两种:显示字符,直到遇到0;预先指定显示的长度。

// console.c 文件
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,可以分配两个空闲的INT号给IDT。也可以借鉴BIOS的调用方式,在寄存器中存入功能号,使得只用一个INT就可以调用不同的函数。当前使用EDX存放功能号。
暂时这样划分功能号:

  • 1 -> 显示单个字符(AL=字符编码)
  • 2 -> 显示字符串func0(EBX=字符串地址)
  • 3 -> 显示字符串func1(EBX=字符串地址,ECX=字符串长度)
// console.c 文件
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);if (edx == 1) {cons_putchar(cons, eax & 0xff, 1);} else if (edx == 2) {cons_putstr0(cons, (char *) ebx);} else if (edx == 3) {cons_putstr1(cons, (char *) ebx, ecx);}return;
}
# naskfunc.nas文件
_asm_hrb_api:STIPUSHAD		# 用于保存寄存器值的PUSHPUSHAD		# 用于向hrp_api传值的PUSHCALL	_hrb_apiADD		ESP,32POPADIRETD

PUSHAD: 一次性将所有 32 位通用寄存器的值压入栈(push),用于在函数调用或中断处理前保存现场(保存寄存器状态)。
作用就是下面这个顺序的集合:
PUSH EAX
PUSH ECX
PUSH EDX
PUSH EBX
PUSH ESP ; 注意:压入的是当前 ESP(在 PUSH EBX 之后的值)
PUSH EBP
PUSH ESI
PUSH EDI

此外需要修改init_gdtidt函数中对idt的初始化,将idt + 0x40配置为hrb_api

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

重新创建一个调用显示字符串的函数:

# hello2.nas 文件
[INSTRSET "i486p"]
[BITS 32]MOV		EDX,2MOV		EBX,msg		# 把msg地址赋值给EBX, 其实此处只是一个偏移INT		0x40RETF
msg:DB	"hello",0

在hello.nas中,单字符打印时会用[CS:ECX]的方式指定CS代码段基地址,才可以成功读取到字符。
在这里插入图片描述
但是在IDT触发调用hello2应用程序时,由于无法指定段地址,所以hrb_api并不知道“hello”这个字符串的具体在哪个地方。可以在调用cmd_app的时候将申请的内存地址(表示应用程序代码段的基地址)通过全局变量传递出去,使hrb_api可以访问到该地址。

// console.c 文件
int cmd_app(struct CONSOLE *cons, int *fat, char *cmdline)
{char *p = NULL;/* 省略 */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;}/* 省略 */
}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);		// msg偏移 + 代码段地址} else if (edx == 3) {cons_putstr1(cons, (char *) ebx + cs_base, ecx);}return;
}
http://www.dtcms.com/a/361896.html

相关文章:

  • 什么是最大熵强化学习?
  • Go项目中关于优雅关闭的那些事
  • 动态配置最佳实践:Spring Boot 十种落地方式与回滚审计指南(含实操与避坑)
  • 如何将mysql数据导入人大金仓数据库
  • 漏洞挖掘 渗透测试思路图总结
  • 期货交易策略自动化实现
  • 数组基础及原理
  • 秋招冲刺计划(Day12)
  • Qwen-Image-Edit完全指南:实战20B参数模型的文字与语义-外观双重编辑
  • 如何使用VMware创建一台Ubuntu机器
  • Linux内核内存管理系列博客教程学习规划
  • KVM虚拟机快速安装与配置指南
  • leetcode算法day24
  • 安科瑞能源管理系统支撑低碳园区节能降碳发展
  • 【前端:Html】--4.进阶:媒体
  • K8S 知识框架和命令操作
  • 刷题之链表oj题目
  • 学习JavaScript的第一个简单程序:Hello World
  • Vue3响应式陷阱:如何避免ref解构导致的响应式丢失
  • ansible知识点总结1
  • Rviz-Gazebo联动
  • C++ 类型系统浅析:值类别与引用类型
  • 工业飞拍技术:高速生产线的 “动态抓拍神器”,到底牛在哪?
  • Java面试宝典:Redis高并发高可用(主从复制、哨兵)
  • oracle默认事务隔离级别
  • ArcGIS 4.x 绘图
  • 开源 C++ QT Widget 开发(十)IPC进程间通信--共享内存
  • 164.在 Vue3 中使用 OpenLayers 加载 Esri 地图(多种形式)
  • Python核心技术开发指南(033)——函数的嵌套
  • matlab扫雷小游戏