gpt4 book ai didi

c - 为什么我不能从这个 C 代码访问在汇编中声明的 Tss 变量?

转载 作者:行者123 更新时间:2023-12-04 14:55:06 26 4
gpt4 key购买 nike

为什么我无法访问此 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,%rax
mov %al,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 部分位于比 code/.text 部分更高的地址)。我们可以从您的源代码中看到,您在创建 TSS 数据时没有更改部分,因此很可能(除非它是“剪切和粘贴遗漏”)您实际上已经在 code/.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, %raxlea TssDesc, %rbx 正在使用物理地址(因为这些标签是在错误的部分创建的 - 即在 .boottext 部分而不是 .data 部分),导致下一个指令( mov %ax, 2(%rbx) )写入错误的地址(物理地址,而不是虚拟地址)。
然而;检查您的代码(主要在 kernel/boot64.S 中)表明我的假设在某处是不正确的。 call SetTss 在物理内存的前 1 GiB 仍然身份映射到虚拟地址空间时发生,因此这应该“偶然工作”(直到稍后 CPU 尝试使用已经消失的 TSS 时);然后在 movq $0x0, p4_table 完成后不久(通过 invlpg 0call SetTss )删除物理内存映射。这意味着在其他地方还有额外的“使用错误的部分”错误。

关于c - 为什么我不能从这个 C 代码访问在汇编中声明的 Tss 变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68212845/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com