c-x86-GDT-GCC能处理不同的段(用于代码和数据)吗



我有一个关于GDTGCC的通用问题。为了学习,我已经开始编写操作系统内核了。目前,我使用GCC来编译代码。在设置GDT的几个教程中,代码和数据段使用相同的基地址(0(和限制(0xFFFFF(。作为一名x86新手,我的第一个想法是,如果我使用不同的基址和限制,这可能是一种额外的保护。

以下是我尝试过的(简称(:

链接器脚本:

ENTRY(_start)
SECTIONS {
. = 1M;
_kern_start = .;
.text  ALIGN(4096) :  {
_kern_text_start = .;
*(.multiboot)
*(.text)
_kern_text_end = .;
}
.rodata ALIGN(4096) : {
_kern_rodata_start = .;
*(.rodata)
_kern_rodata_end = .;
}


.data  ALIGN(4096):  {
_kern_data_start = .;
*(.data)
_kern_data_end = .;
}

.bss ALIGN(4096) :  {
_kern_bss_start = .;
*(.bss)
_kern_bss_end = .;
}


.stack ALIGN(4096) :  {
_kern_stack_start = .;
*(.stack)
_kern_stack_end = .;
}

.heap  ALIGN(4096) :  {
_kern_heap_start = .;
*(.heap)
_kern_heap_end = .;
}
_kern_end = .;
}

我为每个部分添加了符号,然后我编写了简单的Assembler函数,以获得我在C中调用的每个部分的起始地址和大小:

汇编程序函数(例如(:

FUNCTION(_kern_text_get_addr)
pushl %ebp
movl %esp, %ebp
movl $_kern_text_start, %eax
leave
ret
FUNCTION(_kern_text_get_size)
pushl %ebp
movl %esp, %ebp
movl $_kern_text_start, %ebx
movl $_kern_text_end, %eax
sub %ebx, %eax
leave
ret

我使用不同的部分来设置GDT:中的代码和数据(未显示在以下代码片段中(段

uint32_t base;
uint32_t limit;
base = _kern_text_get_addr();
limit = _kern_text_get_size() / 4096;
/* Kernel Code */
gdt_set_entry(&gdt[GDT_KERN_CODE], base, limit, GDT_ACCESS_EXEC | 
GDT_ACCESS_SEGMENT | 
GDT_ACCESS_RING0 | 
GDT_ACCESS_PRESENT, 
GDT_FLAG_SIZE | 
GDT_FLAG_GRAN);

使用汇编程序指令lgdt进行加载。但是,当我用长跳转刷新段寄存器时,我出现了"常规保护"(#GP(故障。所以我检查了生成的机器代码。问题是,当我使用GCC和默认选项编译它时,跳远指令的跳转地址是不正确的。它需要某种地址转换才能跳到正确的位置。此外,数据段使用了错误的地址。好吧,我可以用分页代替,但即使这个问题听起来很愚蠢:

是否可以使用GCC对代码和数据使用不同的段,或者换句话说,GCC是否可以处理不同的段?我知道GCC中有PIC参数,但还没有尝试过。

除了一个小的例外,GCC可以毫无问题地处理单独的代码和数据段。我唯一知道的中断是当获取嵌套函数的地址时创建的蹦床。这些蹦床是在堆栈上创建的,但作为代码执行,因此如果代码需要位于与数据不同的段中,则无法工作。由于嵌套函数是一种很少使用的GCC扩展,因此在实践中不会引起问题。您仍然可以通过在运行时提供一种在代码段中动态分配和初始化内存的方法来让它发挥作用。

然而,这样做没有任何好处。你必须将4G 32位线性地址空间划分为两个独立的不重叠的代码和数据段,才能获得任何安全优势。然而,您可以通过使用不执行页面保护位来获得相同的安全优势,而不必分割线性地址空间。这就是为什么所有当前的操作系统都使用平面分割模型,代码和数据段的基数为0,32位代码的限制为4G。出于同样的原因,64位x86 CPU让您别无选择,只能在64位模式下使用平面模型。

独立于位置的代码不受分段的影响,无论你在跳远(远跳(指令中遇到什么问题,都无法通过使用单独的代码和数据段来解决。

最新更新