您的位置:首页 > 其它

操作系统实验五:保护模式之初步认知门任务(求助:如何使用其他指令替代或模拟retf指令)

2010-02-03 21:15 387 查看
向汇编高手求助:

此次实验中需要用到retf指令在调用门任务后返回。可是yc09编译器不支持retf指令。而我使用ret指令无法正确跳转返回。

因为对汇编不熟,胡乱试了许多方法后依然无法解决,无奈之下,只好在编译好代码后直接修改机器指令码。希望有路过的高手、大牛等告说我在不换编译器的前提下如何解决这问题。



使用retf指令报错信息:

pm32.c(123) : error C2065: 'retf' : undeclared identifier --209



百度了一下,有人解释说ret与retf的区别如下。

ret -> pop ip
retf -> pop ip
pop cs



我试着用 pop ip 与 pop cs 代替retf,编译器却对这两个指令报错。

报错信息:

pm32.c(123) : error C2065: 'ip' : undeclared identifier --205
pm32.c(124) : error C4606: impossible combination of opcode and oprands



此次实验内容:在保护模式下,根据在GDT注册好的门任务,无特权转换的跳转到门任务下,打印一条信息后返回。



使用门任务步骤:

1.编写一个为测试门任务而是用的测试函数,功能为显示一个字符串。(编写好后最好测试一下知否能直接调用)

2.在GDT上注册好门任务:需要设置 选择子、偏移量、参数个数、属性 这4个参数。

(因为门任务与GDT的数据结构不相同,所以需要在pm.h里新定义一个宏函数。)

3.定义一个选择子指向GDT中注册的门任务。

4.在保护模式下,使用call通过门任务选择子调用门任务。



实验代码如下:



code:run.c(新增一个修改ret为retf机器指令的函数:void ret_To_retf(byte *imgBuffer,int size),并在CompileFile函数调用)



//文件:run.c
//功能:编译操作系统的实验代码并创建img,生成bochs配置文件,运行bochs。
//说明:实验代码由16位部分引导程序与32位部分引导程序组成。
//      16位部分引导程序放在引导扇区的前半部分,0~79字节
//      32位部分引导程序放在引导扇区的后半部分,80~509字节
//      510、511字节放引导程序结束标记:0x55、0xaa
//运行:请使用yc09编译器编译运行。
//作者:miao
//时间:2010-2-3

#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";

//因为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");
      }
}

//编译指定代码文件并放入镜像指定位置
//filename:要编译的文件名 imgBuffer:保存到的镜像缓冲区 
//startIndex:指定起始位置 limitSize:编译后程序限定大小
//isneed:若为true,就调用ret_To_retf函数检测代码并将ret替换为retf的机器码
int CompileFile(char *fileName, byte *imgBuffer, int startIndex, int limitSize, bool isneed)
{
  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);
 
   if(isneed)
    ret_To_retf(imgBuffer + startIndex,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,false))
    goto _restart;
  //编译32位部分引导程序并放在引导扇区的后半部分,80~509字节
  if(CompileFile("pm32.c", imgBuffer, 80, 512-80-2,true))
    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(新增了一个宏(Gate),在pm32.c中GDT用到)

//文件:pm.h
//功能:pm16.c与pm32.c的公共头文件
//运行:run.exe自动会编译pm16.c与pm32.c然后生成img并调用Bochs运行此程序
//提示:请先用yc09编译run.c文件,生成run.exe程序
//      之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果,点击回车再次编译运行
//作者:miao
//时间:2010-2-3

//定义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  SA_TIL        0x4    //将TI位置1,表示是LDT选择子
//定义门属性
#define  DA_386CGate   0x8c   //386调用门类型
#define  DA_DPL0       0x00   //DPL = 0

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) ***L(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运行此程序
//提示:请先用yc09编译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运行此程序
//提示:因为yc09无法识别retf指令,所以用ref指令代替,并且在后面加上两个nop指令。
//      运行run.exe时会自动将ret的机器指令码改为retf的机器指令码。
//      请先用yc09编译run.c文件,生成run.exe程序  
//      之后修改pm16.c与pm32.c中代码,可直接运行run.exe查看效果 ,点击回车再次编译运行
//作者:miao
//时间:2010-1-33

#define YCBIT 32  //告诉编译器,以32位格式编译程序
#define YCORG 0x0 //此值会对在编译时对变量函数等产生地址基址偏移量,简单起便,设置为0
#include "pm.h"

#define ProtecAddr 0x7c50 //进入保护模式后的程序基址

asm void LDTCode();//局部代码段, 由32 位代码段跳入
asm void CodeDest();//门任务
asm void DispStr();//显示一个字符串,需要先设置好esi指向字符串地址,edi指向字符串的起始位置

//LDT界限
DESCRIPTOR label_ldt[] = 
{
  //         段基址      段界限   属性
  Descriptor(ProtecAddr, 0xfffff, DA_CR | DA_32),  //32位代码段(pm32.c),可执行可读
};
//LDT 选择子,根据pm32.c中的LDT界限设置偏移量值
#define SelectorLDTCodeA 8*0+SA_TIL //指向32位段局部任务处

//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 //跳转到局部任务
}

//门测试函数
asm void CodeDest()
{
  //下面显示一个字符串(显示已经到达门任务信息)
  mov   esi, &Msg2  //源数据偏移
  mov   edi, ((80 * 1 + 0) * 2) //目的数据偏移。屏幕第2行, 第0列。
  call  DispStr

  ret //因为yc09不支持 retf指令,
  nop //所以用这3个指令代替,
  nop //在编译后检测并替换为 retf 的机器指令。
}

//局部代码段, 由32 位代码段跳入
asm void LDTCode()
{
  //下面显示一个字符串(显示已经到达保护模式信息)
  mov   esi, &Msg3  //源数据偏移
  mov   edi, ((80 * 2 + 0) * 2) //目的数据偏移。屏幕第1行, 第0列。
  call  DispStr
  
_dead:
  jmp   _dead
}

//显示一个字符串,需要先设置好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
}




想要实验以上代码的朋友请注意:







1.到杨晓兵大大的博客上下载安装yc09编译器,安装只需一分钟左右。

2.将此次试验中的run.c、pm.h、pm16.c、pm32.c代码拷贝到某个实验用的文件夹内。

3.在安装yc09的目录YC09/example文件夹内找到bochs.exe、BIOS-bochs-latest、VGABIOS-elpin-2.40、x11-pc-us.map四个文件拷贝到试验用的文件夹内。

4.使用yc09编译运行run.c
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: