如何解决为什么我不能从这个 C 代码访问在汇编中声明的 Tss 变量?
为什么我不能在这个 C 代码中访问 Tss 变量 Qnd 如何解决这个问题?以及为什么在尝试从 C 代码写入 Tss 变量时出现 Page Fault 异常? 在保护模式下,我在 boot32.S 中声明了 gdt64 和 Tss:
.align 16
gdt64:
.quad 0x0000000000000000 // 0x00 NULL
.quad 0x0020980000000000 // 0x08 KCODE64
.quad 0x0020f80000000000
.quad 0x0000f20000000000
TssDesc:
.word TssLen-1
.word 0
.byte 0
.byte 0x89
.byte 0
.byte 0
.quad 0
gdt64_end:
.align 16
.global init_gdt64_ptr_baseaddr
.global init_gdt64_ptr
init_gdt64_ptr:
.word gdt64_end - gdt64 - 1
init_gdt64_ptr_baseaddr:
.quad gdt64 # Change to QUAD from LONG
.global Tss
.global TssDesc
Tss:
.long 0
.quad 0xffff800000190000
.fill 88
.long TssLen
.equ TssLen,. - Tss
boot64.S:
.extern Tss
.extern TssDesc
...
SetTss:
lea Tss,%rax
lea TssDesc,%rbx
mov %ax,2(%rbx)
shr $16,%rax
mov %al,4(%rbx)
shr $8,7(%rbx)
shr $8,%rax
mov %eax,8(%rbx)
mov $0x20,%ax
ltr %ax
ret
...
.global _start64h
_start64h:
mov $KERNEL_VMA,%rax
add %rax,init_gdt64_ptr_baseaddr
lgdt init_gdt64_ptr(%rax)
add %rax,%rsp
call SetTss
但后来在长模式和 proc.c 中的 C 代码中:
extern struct TSS Tss;
static void set_tss(struct Process *proc)
{
Tss.rsp0 = proc->stack + STACK_SIZE;
}
当写入 Tss.rsp0 时,我在这一行得到页面错误 (14) 异常。
在 gdb 中,如果我尝试通过“p/x Tss”读取 Tss 得到
这个:
Cannot access memory at address 0x20103a
Qemu 输出: 异常处理程序中的这行代码:
printk("[Error %d at ring %d] %d:%x %x",tf->trapno,(tf->cs & 3),tf->errorcode,read_cr2(),tf->rip);
输出:
[Error 14 at ring 0] 2:20103EH FFFF800000209AC8H
这个项目在 github 上的链接:https://github.com/JustVic/kernel_multitasking
内核开发是如此艰难的过程:(...
解决方法
Qemu 的 printk("[Error %d at ring %d] %d:%x %x",tf->trapno,(tf->cs & 3),tf->errorcode,read_cr2(),tf->rip);
显示的信息表明地址 FFFF800000209AC8H 处的指令试图写入地址 20103EH 处的“不存在”页面。
这导致了 3 个可能的观察结果:
a) 代码在虚拟地址空间的上半部分(“内核空间”)中运行,并试图访问虚拟地址空间(“用户空间”)下半部分不存在的内容。这可能意味着链接器正在为物理内存(在内核设置分页之前使用)生成地址,这在设置分页后没有意义。
b) 数据应该位于比代码更低的地址(0xFFF800000020103E)(位于 0xFFFF800000209AC8)。这是不寻常的(通常 .data
部分位于比代码/.text
部分更高的地址)。我们可以从您的源代码中看到,您在创建 TSS 数据时没有更改部分,因此很可能(除非它是“剪切和粘贴遗漏”)您实际上在代码中获得了数据/.text
部分。这是相对糟糕的,因为 CPU 的工作方式(例如,如果您在高速缓存线的一部分中有代码而在同一高速缓存线的另一部分中有数据,则写入数据看起来像 CPU 的自修改代码,而现代可以同时“运行”数百条指令的 CPU 真的不喜欢自我修改代码)。
c) 内核代码就在“非规范漏洞”之上。这也是不寻常的。原因是“64 位”80x86 大多不支持指令中的 64 位立即数操作数(mov
的一种特殊变体,如 mov rax,0x0123456789ABCDEF
,可以处理 64 位立即数和没有其他的)。这意味着当“编译时已知的地址”可以压缩为 32 位时,代码会更有效,并且可以使用零扩展或符号扩展到 64 位。扩展到 64 位的(负)32 位数字符号最终在 0xFFFFFFFF80000000 到 0xFFFFFFFFFFFFFFFF 范围内,因此该地址范围比从 0xFFFF800000000000 开始的范围(对于“编译时已知的地址”)更有效(可以'不能用 32 位表示)。
查看源代码中的“kernel/kernel.ld”,您似乎已经创建了特殊部分来解决第一个问题(例如,在设置分页之前运行的代码的 .boottext
部分和.bootdata
部分用于设置分页之前使用的数据)。但是,第二个问题(“代码段中的数据”)暗示您没有正确使用段,因此可以合理推断第一个问题也是由于未正确使用段引起的。
换句话说,可以合理地假设(在您的 SetTss
例程中)lea Tss,%rax
和 lea TssDesc,%rbx
正在使用物理地址(因为这些标签是在错误的部分创建的 - 即,在 .boottext
部分而不是 .data
部分),导致下一条指令(mov %ax,2(%rbx)
)写入错误的地址(物理地址,而不是虚拟地址)。
然而;检查您的代码(主要在 kernel/boot64.S
中)表明我的假设在某处不正确。 call SetTss
发生在物理内存的前 1 GiB 仍然身份映射到虚拟地址空间时,因此这应该“偶然工作”(直到稍后 CPU 尝试使用已消失的 TSS);然后在 movq $0x0,p4_table
完成后不久移除物理内存映射(通过 invlpg 0
和 call SetTss
)。这意味着在其他地方还有额外的“使用错误的部分”错误。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。