为什么我无法从此 C 代码访问在程序集中声明的 Tss 变量?
Why I can't access to Tss variable declared in assembly from this C code?
为什么我无法访问此 C 代码中的 Tss 变量 Qnd 如何解决此问题?为什么在尝试从 C 代码写入 Tss 变量时出现页面错误异常?
在保护模式下,我在 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 ,%rax
mov %al,4(%rbx)
shr ,%rax
mov %al,7(%rbx)
shr ,%rax
mov %eax,8(%rbx)
mov [=11=]x20,%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
但后来在长模式和 C 代码中 proc.c:
extern struct TSS Tss;
static void set_tss(struct Process *proc)
{
Tss.rsp0 = proc->stack + STACK_SIZE;
}
当写入 Tss.rsp0 时,我在这一行出现 Page Fault (14)exception。
在 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
link 到 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) 代码是 运行 在虚拟地址的上半部分 space ("kernel space") 并试图访问虚拟地址下半部分的东西地址 space ("user space") 不存在。这可能意味着链接器正在为物理内存生成地址(在内核设置分页之前使用),这在设置分页后就没有意义了。
b) 数据应该位于比代码(0xFFFF800000209AC8)更低的地址(0xFFF800000020103E)。这是不寻常的(通常 .data
部分的地址高于代码/.text
部分)。我们可以从您的源代码中看到,您在创建 TSS 数据时没有更改部分,因此很可能(除非是“剪切和粘贴遗漏”)您实际上已经在代码中获得了数据/.text
部分。这是相对糟糕的,因为 CPUs 是如何工作的(例如,如果您在缓存行的一部分中有代码而在同一缓存行的另一部分中有数据,那么写入数据看起来像是自修改代码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 仍然标识映射到虚拟地址 space 时,因此这应该是“意外工作”(直到稍后 CPU 尝试使用已消失的 TSS);然后在 call SetTss
完成后不久物理内存映射被删除(通过 movq [=26=]x0, p4_table
和 invlpg 0
)。这意味着在其他地方还有额外的“使用错误 section/s”bug/s。
为什么我无法访问此 C 代码中的 Tss 变量 Qnd 如何解决此问题?为什么在尝试从 C 代码写入 Tss 变量时出现页面错误异常? 在保护模式下,我在 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 ,%rax
mov %al,4(%rbx)
shr ,%rax
mov %al,7(%rbx)
shr ,%rax
mov %eax,8(%rbx)
mov [=11=]x20,%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
但后来在长模式和 C 代码中 proc.c:
extern struct TSS Tss;
static void set_tss(struct Process *proc)
{
Tss.rsp0 = proc->stack + STACK_SIZE;
}
当写入 Tss.rsp0 时,我在这一行出现 Page Fault (14)exception。
在 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
link 到 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) 代码是 运行 在虚拟地址的上半部分 space ("kernel space") 并试图访问虚拟地址下半部分的东西地址 space ("user space") 不存在。这可能意味着链接器正在为物理内存生成地址(在内核设置分页之前使用),这在设置分页后就没有意义了。
b) 数据应该位于比代码(0xFFFF800000209AC8)更低的地址(0xFFF800000020103E)。这是不寻常的(通常 .data
部分的地址高于代码/.text
部分)。我们可以从您的源代码中看到,您在创建 TSS 数据时没有更改部分,因此很可能(除非是“剪切和粘贴遗漏”)您实际上已经在代码中获得了数据/.text
部分。这是相对糟糕的,因为 CPUs 是如何工作的(例如,如果您在缓存行的一部分中有代码而在同一缓存行的另一部分中有数据,那么写入数据看起来像是自修改代码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 仍然标识映射到虚拟地址 space 时,因此这应该是“意外工作”(直到稍后 CPU 尝试使用已消失的 TSS);然后在 call SetTss
完成后不久物理内存映射被删除(通过 movq [=26=]x0, p4_table
和 invlpg 0
)。这意味着在其他地方还有额外的“使用错误 section/s”bug/s。