保护模式
预备知识
线性地址:在保护模式下,寻址方式会产生变化。程序给出的32位地址不再直接解释为物理地址,而是相对于某一个段的偏移量(offset)。真正的物理地址由下式给出:
<center>physical address = linear address = base address + offset</center>
其中的base address是一个32位的地址,对应某个段的基地址;而offset则是程序给出的32位段内偏移量。
48位的逻辑地址(或称虚拟地址):
段表:其中存储好每个段的首地址(base address)、段的长度(limit)等相关信息。
段描述符:段表由一系列连续的段表项构成,其中每个段表项都是一个64位的数据结构称为段描述符。
在这里插入图片描述
段选择符:高13位是一个index,用于指出所访问段的段描述符是段表中的第几项(数组下标);TI用于指出选择哪一个段描述符表,TI为0时表示选择全局描述符表(GDT),TI为1时表示选择局部描述符表(LDT)。
Alt
汇编指令LGDT
Opcode | Instruction | Description |
---|---|---|
OF 01/ 2 | LGDT m16&32 | Load m into GDTR |
IF OperandSize = 16
THEN GDTR.Limit:Base ← m16:24 (* 24 bits of base loaded *)
ELSE GDTR.Limit:Base ← m16:32;
我觉得是指从当前内存连续取6个字节的数据,其中2个字节的数据存到gdtr的limit里,另外4个字节的数据存到gdtr的base里
以上是刚开始如此想的,但是emmm......结果可想而知
正确理解应该是:从当前内存地址cpu.eip处取32位数,这个是一个地址,然后从这个地址开始取48位数,这6个字节的数据,才是将要储存到gdtr寄存器中的数据。
比如,在nemu/src/cpu/instr文件夹中加入lgdt.c文件,内容如下
make_instr_func(lgdt)
{
OPERAND rel;
rel.type = OPR_IMM;
rel.sreg = SREG_CS;
rel.data_size = 32;
rel.addr = eip + 2;
operand_read(&rel);
uint8_t val[6];
memcpy(&val, hw_mem + rel.val, 6);
cpu.gdtr.limit = val[0] + (val[1] << 8);
if(data_size == 16)
cpu.gdtr.base = val[2] + (val[3] << 8) + (val[4] << 16);
else
cpu.gdtr.base = val[2] + (val[3] << 8) + (val[4] << 16) + (val[5] << 24);
return 1 + 1 + 4;
}
汇编指令LJMP
IF operand type = ptr16:16 or ptr16:32
THEN
IF OperandSize = 16
THEN
CS:IP ← ptr16:16;
EIP ← EIP AND 0000FFFFH; (* clear upper 16 bits )
ELSE ( OperandSize = 32 *)
CS:EIP ← ptr16:32;
FI;
FI;
当开启保护模式后,NEMU中运行的程序访问内存时给出的就不简单是32位的物理地址,而是一个16位的段选择符加上32位的段内偏移量(有效地址)所构成的48位的逻辑地址(或称虚拟地址)。
所以指令ljmp后会跟着6个字节的数据,前2个字节的数据是段选择符(前面已经介绍过了),根据这2个字节的数据,计算出index,然后根据index去寻找段表数组,index是该数组的下标,然后从这个段表项中得到段基地址,另外4个字节的数据是段内偏移量,所以把两个数相加,就是要跳转到的位置。
实现load_sreg()函数
void load_sreg(uint8_t sreg)
{
SegDesc segDesc;
memcpy(&segDesc, hw_mem + cpu.gdtr.base + 8 * (cpu.segReg[sreg].index), 8);
uint32_t base = (segDesc.base_32_24 << 24) + (segDesc.base_23_16 << 16) + segDesc.base_15_0;
uint32_t limit = (segDesc.limit_19_16 << 16) + segDesc.limit_15_0;
assert(base == 0);
assert(limit == 0xfffff);
assert(segDesc.granularity == 1);
cpu.segReg[sreg].base = base;
cpu.segReg[sreg].limit = limit;
}
实验总结
历时许久终于做完了,我太难了
- 刚开始对LGDT指令和LJMP指令以及MOV(mov sw, ew与mov (control regsiter) to register)指令的不熟悉,上网上搜了好久没有找到,或者说找到相关的,但是理解不了,所以导致进度一直停滞不前。
在一个月黑风高的晚上,闭上眼睛苦思冥想,终于想到了一个,需要花很多时间,但是一定可以做出来的方法,所以从这个想法开始执行到结束,花了不到一天时间,没有执行这个想法之前,停滞了一个多星期没有进展 - 注意细节,每一个细节都有可能导致Segment fault,要了解所有可能出现Segment fault的地方,并且加上输出(
愚蠢,输出是最愚蠢的办法,但是,实力如此,没办法),然后根据输出去寻找最可能出现错误的地方。
转载自https://blog.csdn.net/qq_43168521/article/details/102579269
网友评论