![]() |
#2
chengstone2010-02-09 08:03
|
使用retf指令报错信息:
pm32.c(123) : error C2065: 'retf' : undeclared identifier --209
百度了一下,有人解释说ret与retf的区别如下。pm32.c(123) : error C2065: 'retf' : undeclared identifier --209
ret -> pop ip
retf -> pop ip
pop cs
我试着用 pop ip 与 pop cs 代替retf,编译器却对这两个指令报错。retf -> pop ip
pop cs
报错信息:
pm32.c(123) : error C2065: 'ip' : undeclared identifier --205
pm32.c(124) : error C4606: impossible combination of opcode and oprands
因为对汇编不熟,只有通过调试跟堆栈踪寄存器变化尝试了许多方法都没有用。无奈之下,只好使用最原始的方法——编译完程序后直接修改二进制机器指令码。pm32.c(123) : error C2065: 'ip' : undeclared identifier --205
pm32.c(124) : error C4606: impossible combination of opcode and oprands
首先在需要用到retf指令的地方使用以下三个指令代替:(后跟两个nop指令主要是防止误修改)
ret
nop
nop
nop
nop
然后再run.c中添加一个修改二进制机器指令码的函数:(在使用编译函数YC_CompileCpp的CompileFile函数里调用)

//因为yc09编译器不支持长返回指令 retf,所以使用这个函数作为临时解决方法。
//在汇编代码中需要用到 retf指令 时,使用 ret nop nop 这个三个连续指令代替,
//在编译代码后,调用此函数将ret指令的机器指令改为retf的机器指令。
//imgBuffer:编译后的二进制文件 size:文件的字节大小
//汇编与机器指令对照:ret == 0xc3 retf == 0xcb nop == 0x90
void ret_To_retf(byte *imgBuffer,int size)
{
if(size<4) return;
size-=2;
while(--size)
if(imgBuffer[size] == 0xc3)
if(imgBuffer[size+1] == 0x90 && imgBuffer[size+2] == 0x90)
{
imgBuffer[size] = 0xcb;
printf("将一个ret改为了retf.\n");
}
}
//在汇编代码中需要用到 retf指令 时,使用 ret nop nop 这个三个连续指令代替,
//在编译代码后,调用此函数将ret指令的机器指令改为retf的机器指令。
//imgBuffer:编译后的二进制文件 size:文件的字节大小
//汇编与机器指令对照:ret == 0xc3 retf == 0xcb nop == 0x90
void ret_To_retf(byte *imgBuffer,int size)
{
if(size<4) return;
size-=2;
while(--size)
if(imgBuffer[size] == 0xc3)
if(imgBuffer[size+1] == 0x90 && imgBuffer[size+2] == 0x90)
{
imgBuffer[size] = 0xcb;
printf("将一个ret改为了retf.\n");
}
}
但这个方法并不完美,于是就先发到CSDN上,有幸经高手指点,原来使用一个宏指令定义一个retf指令就行了

#define retf db 0xcb
同过这个宏定义,就可以直接使用retf指令了。在此非常感谢高手的指点,同时也为自己对基础知识的不牢固感到羞愧

会到此次实验内容:在保护模式下,根据在GDT注册好的调用门,无特权转换的跳转到门任务下,打印一条信息后返回。
使用门任务步骤:
1.编写一个为测试调用门而是用的测试函数,功能为显示一个字符串。(编写好后最好测试一下知否能直接调用)
2.在GDT上注册好调用门:需要设置 选择子(指向测试用的函数)、偏移量、参数个数、属性 这4个参数。(因为调用门与GDT的数据结构不相同,所以需要在pm.h里新定义一个宏函数。)
3.定义一个选择子指向GDT中注册的调用门。
4.在保护模式下,使用call通过门任务选择子调用调用门,然后使用retf返回。
我们使用调用门选择子时,实际上就是进入了在GDT中调用门选择子所指向的测试函数。
下面是此次实验的代码:
code:run.c

//文件:run.c
//功能:编译操作系统的实验代码并创建img,生成bochs配置文件,运行bochs。
//说明:实验代码由16位部分引导程序与32位部分引导程序组成。
// 16位部分引导程序放在引导扇区的前半部分,0~79字节
// 32位部分引导程序放在引导扇区的后半部分,80~509字节
// 510、511字节放引导程序结束标记:0x55、0xaa
//运行:请使用yc00编译器编译运行。
//作者:miao
//时间:2010-1-13
#define FDISK_SIZE 1474560 //镜像大小:1.4MB
//虚拟机设置
char *pmSrc =
"megs: 32 \n"
"romimage: file=BIOS-bochs-latest, address=0xf0000 \n"
"vgaromimage: VGABIOS-elpin-2.40 \n"
"floppya: 1_44=pm.img, status=inserted \n"
"boot: a \n"
"log: pm.out \n"
"mouse: enabled=0 \n"
"keyboard_mapping: enabled=1, map=x11-pc-us.map \n";
//编译指定代码文件并放入镜像指定位置
//filename:要编译的文件名 imgBuffer:保存到的镜像缓冲区
//startIndex:指定起始位置 limitSize:编译后程序限定大小
int CompileFile(char *fileName, byte *imgBuffer, int startIndex, int limitSize)
{
char *tempBuffer; //保存部分引导程序的临时缓冲区
//编译此部分引导程序,结果放到tempBuffer中
int length = YC_CompileCpp(&tempBuffer, fileName, 0, 0);
if(length <= 0 || length >= limitSize)
{
printf("文件: %s 中存在一些错误或文件过大(超过%d字节):%d字节\n", fileName,limitSize,length);
return 1;
}
printf("文件: %s 编译成功,大小为:%d字节。\n", fileName, length);
//将1此部分引导程序放到镜像引导扇区缓冲区指定起始位置
memcpy(imgBuffer + startIndex, tempBuffer, length);
free(tempBuffer);
return 0;
}
int main(int argc, char **argv)
{
char * filePath = argv[0]; //当前文件夹路径
char fileName[MAX_PATH]; //用于缓存各个文件名
//将可执行文件的完整路径去掉文件名,保留文件夹路径
for( int i = strlen(filePath);filePath[i] != '\\';i--)
filePath[i] = '\0';
byte *imgBuffer = new byte[FDISK_SIZE];//镜像缓冲区
_start:
//编译16位部分引导程序并放在引导扇区的前半部分,0~79字节
if(CompileFile("pm16.c", imgBuffer, 0, 80))
goto _restart;
//编译32位部分引导程序并放在引导扇区的后半部分,80~509字节
if(CompileFile("pm32.c", imgBuffer, 80, 512-80-2))
goto _restart;
//0000H-01FFH 为FAT引导扇区[第0扇区] 以55 AA标志结束 长度为200H(512)字节
imgBuffer[510] = 0x55;
imgBuffer[511] = 0xaa;//标记软盘引导结尾
//创建操作系统镜像pm.img
if(YC_writefile("pm.img", imgBuffer, FDISK_SIZE) != FDISK_SIZE)
{
printf("写: %s 文件过程中出现错误。\r\n", fileName);
goto _restart;
}
printf("\n%s 创建成功。\n", fileName);
//生成操作系统虚拟机配置文件pm.src
YC_writefile("pm.src", pmSrc, strlen(pmSrc));
//运行虚拟机
YC_WinExec(strcat(strcpy(fileName, filePath), "bochs.exe"), "-q -f pm.src");
_restart:
printf("\n点击回车重新编译运行!\n\n\n");
while(getchar() != '\n');
goto _start;
return 0;
}
//功能:编译操作系统的实验代码并创建img,生成bochs配置文件,运行bochs。
//说明:实验代码由16位部分引导程序与32位部分引导程序组成。
// 16位部分引导程序放在引导扇区的前半部分,0~79字节
// 32位部分引导程序放在引导扇区的后半部分,80~509字节
// 510、511字节放引导程序结束标记:0x55、0xaa
//运行:请使用yc00编译器编译运行。
//作者:miao
//时间:2010-1-13
#define FDISK_SIZE 1474560 //镜像大小:1.4MB
//虚拟机设置
char *pmSrc =
"megs: 32 \n"
"romimage: file=BIOS-bochs-latest, address=0xf0000 \n"
"vgaromimage: VGABIOS-elpin-2.40 \n"
"floppya: 1_44=pm.img, status=inserted \n"
"boot: a \n"
"log: pm.out \n"
"mouse: enabled=0 \n"
"keyboard_mapping: enabled=1, map=x11-pc-us.map \n";
//编译指定代码文件并放入镜像指定位置
//filename:要编译的文件名 imgBuffer:保存到的镜像缓冲区
//startIndex:指定起始位置 limitSize:编译后程序限定大小
int CompileFile(char *fileName, byte *imgBuffer, int startIndex, int limitSize)
{
char *tempBuffer; //保存部分引导程序的临时缓冲区
//编译此部分引导程序,结果放到tempBuffer中
int length = YC_CompileCpp(&tempBuffer, fileName, 0, 0);
if(length <= 0 || length >= limitSize)
{
printf("文件: %s 中存在一些错误或文件过大(超过%d字节):%d字节\n", fileName,limitSize,length);
return 1;
}
printf("文件: %s 编译成功,大小为:%d字节。\n", fileName, length);
//将1此部分引导程序放到镜像引导扇区缓冲区指定起始位置
memcpy(imgBuffer + startIndex, tempBuffer, length);
free(tempBuffer);
return 0;
}
int main(int argc, char **argv)
{
char * filePath = argv[0]; //当前文件夹路径
char fileName[MAX_PATH]; //用于缓存各个文件名
//将可执行文件的完整路径去掉文件名,保留文件夹路径
for( int i = strlen(filePath);filePath[i] != '\\';i--)
filePath[i] = '\0';
byte *imgBuffer = new byte[FDISK_SIZE];//镜像缓冲区
_start:
//编译16位部分引导程序并放在引导扇区的前半部分,0~79字节
if(CompileFile("pm16.c", imgBuffer, 0, 80))
goto _restart;
//编译32位部分引导程序并放在引导扇区的后半部分,80~509字节
if(CompileFile("pm32.c", imgBuffer, 80, 512-80-2))
goto _restart;
//0000H-01FFH 为FAT引导扇区[第0扇区] 以55 AA标志结束 长度为200H(512)字节
imgBuffer[510] = 0x55;
imgBuffer[511] = 0xaa;//标记软盘引导结尾
//创建操作系统镜像pm.img
if(YC_writefile("pm.img", imgBuffer, FDISK_SIZE) != FDISK_SIZE)
{
printf("写: %s 文件过程中出现错误。\r\n", fileName);
goto _restart;
}
printf("\n%s 创建成功。\n", fileName);
//生成操作系统虚拟机配置文件pm.src
YC_writefile("pm.src", pmSrc, strlen(pmSrc));
//运行虚拟机
YC_WinExec(strcat(strcpy(fileName, filePath), "bochs.exe"), "-q -f pm.src");
_restart:
printf("\n点击回车重新编译运行!\n\n\n");
while(getchar() != '\n');
goto _start;
return 0;
}
code:pm.h

//文件:pm.h
//功能:pm16.c与pm32.c的公共头文件
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:请先用yc90编译run.c文件,生成run.exe程序
// 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行
//作者:miao
//时间:2010-1-30
//定义GDT(全局描述符)属性
#define DA_32 0x4000 //32位段
#define DA_DRW 0x92 //存在的可读写数据段属性值
#define DA_DRWA 0x93 //存在的已访问可读写数据段类型值
#define DA_CR 0x9A //存在的可执行可读代码段属性值
#define DA_C 0x98 //存在的可执行可读代码段属性值
//定义LDT(局部描述符)属性
#define DA_LDT 0x82 //局部描述符表类型值
//定义门属性
#define DA_386CGate 0x8c //386调用门类型
//描述符特权等级(0~3:从高到低)
#define DA_DPL0 0x00 //描述符特权等级为3
//选择子
#define SA_TIL 0x4 //将TI位置1,表示是LDT选择子
typedef unsigned int t_32; //4字节
typedef unsigned short t_16; //2字节
typedef unsigned char t_8; //1字节
typedef int t_bool;//4字节
typedef unsigned int t_port;//4字节
//存储段描述符/系统段描述符
struct DESCRIPTOR //共 8 个字节
{
t_16 limit_low; //Limit 2字节
t_16 base_low; //Base 2字节
t_8 base_mid; //Base 1字节
t_8 attr1; //P(1) DPL(2) DT(1) TYPE(4) 1字节
t_8 limit_high_attr2; //G(1) D(1) 0(1) AVL(1) LimitHigh(4) 1字节
t_8 base_high; //Base 1字节
};
#define Descriptor(bas,len,attr) { \
(len) & 0xffff, \
(bas) & 0xffff, \
((bas)>>16)&0xff, \
(attr) & 0xff, \
(((attr)>>8) &0xf0) + (((len)>>16) & 0x0f), \
((bas) >> 24) & 0xff } \
#define Gate(slector,offset,dCount,attr) { \
(offset) & 0xffff, \
slector, \
(dCount)&0x1f , \
attr, \
((offset)>>16) &0xff, \
((offset) >> 24) & 0xff } \
//功能:pm16.c与pm32.c的公共头文件
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:请先用yc90编译run.c文件,生成run.exe程序
// 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行
//作者:miao
//时间:2010-1-30
//定义GDT(全局描述符)属性
#define DA_32 0x4000 //32位段
#define DA_DRW 0x92 //存在的可读写数据段属性值
#define DA_DRWA 0x93 //存在的已访问可读写数据段类型值
#define DA_CR 0x9A //存在的可执行可读代码段属性值
#define DA_C 0x98 //存在的可执行可读代码段属性值
//定义LDT(局部描述符)属性
#define DA_LDT 0x82 //局部描述符表类型值
//定义门属性
#define DA_386CGate 0x8c //386调用门类型
//描述符特权等级(0~3:从高到低)
#define DA_DPL0 0x00 //描述符特权等级为3
//选择子
#define SA_TIL 0x4 //将TI位置1,表示是LDT选择子
typedef unsigned int t_32; //4字节
typedef unsigned short t_16; //2字节
typedef unsigned char t_8; //1字节
typedef int t_bool;//4字节
typedef unsigned int t_port;//4字节
//存储段描述符/系统段描述符
struct DESCRIPTOR //共 8 个字节
{
t_16 limit_low; //Limit 2字节
t_16 base_low; //Base 2字节
t_8 base_mid; //Base 1字节
t_8 attr1; //P(1) DPL(2) DT(1) TYPE(4) 1字节
t_8 limit_high_attr2; //G(1) D(1) 0(1) AVL(1) LimitHigh(4) 1字节
t_8 base_high; //Base 1字节
};
#define Descriptor(bas,len,attr) { \
(len) & 0xffff, \
(bas) & 0xffff, \
((bas)>>16)&0xff, \
(attr) & 0xff, \
(((attr)>>8) &0xf0) + (((len)>>16) & 0x0f), \
((bas) >> 24) & 0xff } \
#define Gate(slector,offset,dCount,attr) { \
(offset) & 0xffff, \
slector, \
(dCount)&0x1f , \
attr, \
((offset)>>16) &0xff, \
((offset) >> 24) & 0xff } \
code:pm16.c

//文件:pm16.c
//功能:切换到保护模式,跳转到32位代码段
//说明:我试图仅在引导扇区编写保护模式的相关实验,因此将这个程序精简了很多。
// 它只负责跳转到保护模式,其他的工作都在pm32.c下完成。
// pm16.c只占引导扇区的前半部分0~79字节。
// pm32.c部分会加载到内存0x7c50处。
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:请先用yc90编译run.c文件,生成run.exe程序
// 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行
//作者:miao
//时间:2010-1-30
#define YCBIT 16 //告诉编译器,以16位格式编译程序
#define YCORG 0x7c00 //告诉编译器,在7c00处加载程序
#include "pm.h"
//GDT界限,只负责跳转到保护模式,到时会加载新的GDT
DESCRIPTOR label_gdt[] =
{
// 段基址 段界限 属性
Descriptor(0, 0, 0),
Descriptor(0x7c50, 0xfffff, DA_CR | DA_32),//32位代码段(pm32.c),可执行可读
};
//GDT 选择子,根据GDT界限设置偏移量值
#define SelectorCode32 8*1 //指向32位段处
#pragma pack(1)
struct GDT_PTR
{
unsigned short size;
void *addr;
};
#pragma pack()
GDT_PTR GdtPtr = {sizeof(label_gdt), (char*)label_gdt}; //段界限,基地址
asm void main()
{
mov ax, cs
mov ds, ax
mov es, ax
//清屏
mov ah, 06h //屏幕初始化或上卷
mov aL, 00h //AH = 6, AL = 0h
mov bx, 1110h //蓝色底色
mov cx, 0 //左上角: (0, 0)
mov dl, 4fh //第0列
mov dh, 1fh //第0行
int 10h //显示中断
lgdt GdtPtr //加载 GDTR
cli //关中断
//打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
//准备切换到保护模式,置cr0的PE位为1
mov eax, cr0
or eax, 1
mov cr0, eax
//真正进入保护模式
jmp dword SelectorCode32:0x0
}
//功能:切换到保护模式,跳转到32位代码段
//说明:我试图仅在引导扇区编写保护模式的相关实验,因此将这个程序精简了很多。
// 它只负责跳转到保护模式,其他的工作都在pm32.c下完成。
// pm16.c只占引导扇区的前半部分0~79字节。
// pm32.c部分会加载到内存0x7c50处。
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:请先用yc90编译run.c文件,生成run.exe程序
// 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行
//作者:miao
//时间:2010-1-30
#define YCBIT 16 //告诉编译器,以16位格式编译程序
#define YCORG 0x7c00 //告诉编译器,在7c00处加载程序
#include "pm.h"
//GDT界限,只负责跳转到保护模式,到时会加载新的GDT
DESCRIPTOR label_gdt[] =
{
// 段基址 段界限 属性
Descriptor(0, 0, 0),
Descriptor(0x7c50, 0xfffff, DA_CR | DA_32),//32位代码段(pm32.c),可执行可读
};
//GDT 选择子,根据GDT界限设置偏移量值
#define SelectorCode32 8*1 //指向32位段处
#pragma pack(1)
struct GDT_PTR
{
unsigned short size;
void *addr;
};
#pragma pack()
GDT_PTR GdtPtr = {sizeof(label_gdt), (char*)label_gdt}; //段界限,基地址
asm void main()
{
mov ax, cs
mov ds, ax
mov es, ax
//清屏
mov ah, 06h //屏幕初始化或上卷
mov aL, 00h //AH = 6, AL = 0h
mov bx, 1110h //蓝色底色
mov cx, 0 //左上角: (0, 0)
mov dl, 4fh //第0列
mov dh, 1fh //第0行
int 10h //显示中断
lgdt GdtPtr //加载 GDTR
cli //关中断
//打开地址线A20
in al, 92h
or al, 00000010b
out 92h, al
//准备切换到保护模式,置cr0的PE位为1
mov eax, cr0
or eax, 1
mov cr0, eax
//真正进入保护模式
jmp dword SelectorCode32:0x0
}
code:pm32.c

//文件:pm32.c
//功能:保护模式下32位代码段,功能为加载新的GDT,调用门任务,返回后初始化LDT然后跳入LDT局部任务
//说明:32位部分引导程序放在镜像引导扇区的后半部分,80~509字节中,程序大小不能超过这个限制
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:因为yc90无法识别retf指令,所以用ref指令代替,并且在后面加上两个nop指令。
// 运行run.exe时会自动将ret的机器指令码改为retf的机器指令码。
// 请先用yc90编译run.c文件,生成run.exe程序
// 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果 ,点击回车再次编译运行
//作者:miao
//时间:2010-2-3
#define YCBIT 32 //告诉编译器,以32位格式编译程序
#define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0
#include "pm.h"
#define ProtecAddr 0x7c50 //进入保护模式后的程序基址
#define retf db 0xcb //因为yc09编译器不识别指令retf,使用宏直接定义指令retf
asm void LDTCode();//局部代码段, 由32 位代码段跳入
asm void CodeDest();//门任务测试函数
asm void DispStr();//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置
//LDT选择子,根据pm32.c中的LDT界限设置偏移量值
#define SelectorLDTCodeA 8*0+SA_TIL //指向32位段局部任务处
//LDT界限
DESCRIPTOR label_ldt[] =
{
// 段基址 段界限 属性
Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读
};
//GDT 选择子,根据pm32.c中的GDT界限设置偏移量值
#define SelectorCode32 8*1 //指向32位段处代码段,可执行可读
#define SelectorVideo 8*2 //指向显存首地址
#define SelectorData32 8*3 //指向32位段处,这样,在程序中的变量就可以读写了
#define SelectorLDT 8*4 //指向LDT,通过这个跳转到局部任务
//门选择子
#define SelectorCallGateTest 8*5 //指向门任务
//GDT界限,注意,这个与pm16.c中的GDT不同,从pm16.c跳转过来后会立即载入这个新的GDT
DESCRIPTOR label_gdt[] =
{
// 段基址 段界限 属性
Descriptor(0, 0, 0),
Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读
Descriptor(0xb8000, 0xffff, DA_DRW), //显存地址段,可读可写
Descriptor(ProtecAddr, 0xfffff, DA_DRW | DA_32), //令32位代码段(pm32.c)的变量可以读写
Descriptor(0, 0xfffff, DA_LDT),//局部描述符,段基址和32位代码段相同,调用时需要加上偏移量
// 选择子 偏移量 参数个数 属性
Gate(SelectorCode32, (t_32)&CodeDest, 0, DA_386CGate | DA_DPL0),
//使用SelectorCode32选择子,所以内存地址:基址(ProtecAddr) + 函数地址偏移量
};
#pragma pack(1)
struct GDT_PTR
{
t_16 size;
void *addr;
}GdtPtr = {sizeof(label_gdt), (char*)&label_gdt + ProtecAddr}; //段界限,基地址
#pragma pack()
char Msg1[] = "This is protect model.";
char Msg2[] = "This is gate task.";
char Msg3[] = "This is local model.";
//32 位代码段. 由实模式跳入
asm void main()
{
lgdt cs:GdtPtr //加载新的GDTR
mov eax, SelectorVideo
mov gs, ax //视频段选择子(目的)
mov eax, SelectorData32 //令32位代码段的变量(printPlace)可以读写
mov ds, ax
//下面显示一个字符串(显示已经到达保护模式信息)
mov esi, &Msg1 //源数据偏移
mov edi, ((80 * 0 + 0) * 2) //目的数据偏移。屏幕第0行, 第0列。
call DispStr
//call SelectorCode32:&CodeDest //先测试一下,直接跳转到测试函数 CodeDest()
call SelectorCallGateTest:0 //测试通过,直接通过门跳转到测试函数 CodeDest()
//在GDT的上设置好LDT的基地址,然后加载局部描述符(LDT)
xor eax, eax
mov eax, &label_ldt + ProtecAddr
mov word label_gdt+SelectorLDT+2, ax
shr eax, 16
mov byte label_gdt+SelectorLDT+4, al
mov byte label_gdt+SelectorLDT+7, ah
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeA:&LDTCode //跳转到局部任务
}
//局部代码段, 由32 位代码段跳入
asm void LDTCode()
{
//下面显示一个字符串(显示已经到达保护模式信息)
mov esi, &Msg3 //源数据偏移
mov edi, ((80 * 2 + 0) * 2) //目的数据偏移。屏幕第1行, 第0列。
call DispStr
_dead:
jmp _dead
}
//门测试函数
asm void CodeDest()
{
//下面显示一个字符串(显示已经到达门任务信息)
mov esi, &Msg2 //源数据偏移
mov edi, ((80 * 1 + 0) * 2) //目的数据偏移。屏幕第2行, 第0列。
call DispStr
retf
}
//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置
asm void DispStr()
{
mov ah, 14h //蓝底红字(ah = 14h)
//循环逐个将字符串输出
_DispStr:
mov al, ds:[esi]//因为可读,才能用cs指向当前段的Msg1字符串
inc esi
cmp al, '\0' //判断是否字符串结束
jz _stop
mov gs:[edi], ax
add edi, 2
jmp _DispStr
_stop: //显示完毕
ret
}
//功能:保护模式下32位代码段,功能为加载新的GDT,调用门任务,返回后初始化LDT然后跳入LDT局部任务
//说明:32位部分引导程序放在镜像引导扇区的后半部分,80~509字节中,程序大小不能超过这个限制
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:因为yc90无法识别retf指令,所以用ref指令代替,并且在后面加上两个nop指令。
// 运行run.exe时会自动将ret的机器指令码改为retf的机器指令码。
// 请先用yc90编译run.c文件,生成run.exe程序
// 之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果 ,点击回车再次编译运行
//作者:miao
//时间:2010-2-3
#define YCBIT 32 //告诉编译器,以32位格式编译程序
#define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0
#include "pm.h"
#define ProtecAddr 0x7c50 //进入保护模式后的程序基址
#define retf db 0xcb //因为yc09编译器不识别指令retf,使用宏直接定义指令retf
asm void LDTCode();//局部代码段, 由32 位代码段跳入
asm void CodeDest();//门任务测试函数
asm void DispStr();//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置
//LDT选择子,根据pm32.c中的LDT界限设置偏移量值
#define SelectorLDTCodeA 8*0+SA_TIL //指向32位段局部任务处
//LDT界限
DESCRIPTOR label_ldt[] =
{
// 段基址 段界限 属性
Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读
};
//GDT 选择子,根据pm32.c中的GDT界限设置偏移量值
#define SelectorCode32 8*1 //指向32位段处代码段,可执行可读
#define SelectorVideo 8*2 //指向显存首地址
#define SelectorData32 8*3 //指向32位段处,这样,在程序中的变量就可以读写了
#define SelectorLDT 8*4 //指向LDT,通过这个跳转到局部任务
//门选择子
#define SelectorCallGateTest 8*5 //指向门任务
//GDT界限,注意,这个与pm16.c中的GDT不同,从pm16.c跳转过来后会立即载入这个新的GDT
DESCRIPTOR label_gdt[] =
{
// 段基址 段界限 属性
Descriptor(0, 0, 0),
Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32), //32位代码段(pm32.c),可执行可读
Descriptor(0xb8000, 0xffff, DA_DRW), //显存地址段,可读可写
Descriptor(ProtecAddr, 0xfffff, DA_DRW | DA_32), //令32位代码段(pm32.c)的变量可以读写
Descriptor(0, 0xfffff, DA_LDT),//局部描述符,段基址和32位代码段相同,调用时需要加上偏移量
// 选择子 偏移量 参数个数 属性
Gate(SelectorCode32, (t_32)&CodeDest, 0, DA_386CGate | DA_DPL0),
//使用SelectorCode32选择子,所以内存地址:基址(ProtecAddr) + 函数地址偏移量
};
#pragma pack(1)
struct GDT_PTR
{
t_16 size;
void *addr;
}GdtPtr = {sizeof(label_gdt), (char*)&label_gdt + ProtecAddr}; //段界限,基地址
#pragma pack()
char Msg1[] = "This is protect model.";
char Msg2[] = "This is gate task.";
char Msg3[] = "This is local model.";
//32 位代码段. 由实模式跳入
asm void main()
{
lgdt cs:GdtPtr //加载新的GDTR
mov eax, SelectorVideo
mov gs, ax //视频段选择子(目的)
mov eax, SelectorData32 //令32位代码段的变量(printPlace)可以读写
mov ds, ax
//下面显示一个字符串(显示已经到达保护模式信息)
mov esi, &Msg1 //源数据偏移
mov edi, ((80 * 0 + 0) * 2) //目的数据偏移。屏幕第0行, 第0列。
call DispStr
//call SelectorCode32:&CodeDest //先测试一下,直接跳转到测试函数 CodeDest()
call SelectorCallGateTest:0 //测试通过,直接通过门跳转到测试函数 CodeDest()
//在GDT的上设置好LDT的基地址,然后加载局部描述符(LDT)
xor eax, eax
mov eax, &label_ldt + ProtecAddr
mov word label_gdt+SelectorLDT+2, ax
shr eax, 16
mov byte label_gdt+SelectorLDT+4, al
mov byte label_gdt+SelectorLDT+7, ah
mov ax, SelectorLDT
lldt ax
jmp SelectorLDTCodeA:&LDTCode //跳转到局部任务
}
//局部代码段, 由32 位代码段跳入
asm void LDTCode()
{
//下面显示一个字符串(显示已经到达保护模式信息)
mov esi, &Msg3 //源数据偏移
mov edi, ((80 * 2 + 0) * 2) //目的数据偏移。屏幕第1行, 第0列。
call DispStr
_dead:
jmp _dead
}
//门测试函数
asm void CodeDest()
{
//下面显示一个字符串(显示已经到达门任务信息)
mov esi, &Msg2 //源数据偏移
mov edi, ((80 * 1 + 0) * 2) //目的数据偏移。屏幕第2行, 第0列。
call DispStr
retf
}
//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置
asm void DispStr()
{
mov ah, 14h //蓝底红字(ah = 14h)
//循环逐个将字符串输出
_DispStr:
mov al, ds:[esi]//因为可读,才能用cs指向当前段的Msg1字符串
inc esi
cmp al, '\0' //判断是否字符串结束
jz _stop
mov gs:[edi], ax
add edi, 2
jmp _DispStr
_stop: //显示完毕
ret
}
此次试验,以及上一次实验都是非常简单的对局部任务和调用门接触了一下。但是局部任务和调用门等并非是这么简单的东西,在下一个实验中将会做一个比较综合性的实验。