我正在尝试阅读 Linux 源代码(2.6.11)
在异常处理程序中,在 entry.s,error_code:
movl $(__USER_DS), %ecx
movl %ecx, %ds
movl %ecx, %es
我不知道为什么在这里加载用户数据段。由于它应该进入在内核模式下运行的异常处理程序代码,因此选择器应该是__KERNEL_DS。
我检查了其他版本的代码,他们在这个地方也专门做同样的事情。
我正在尝试阅读 Linux 源代码(2.6.11)
在异常处理程序中,在 entry.s,error_code:
movl $(__USER_DS), %ecx
movl %ecx, %ds
movl %ecx, %es
我不知道为什么在这里加载用户数据段。由于它应该进入在内核模式下运行的异常处理程序代码,因此选择器应该是__KERNEL_DS。
我检查了其他版本的代码,他们在这个地方也专门做同样的事情。
如果输入异常处理程序ds
并es
已设置为数据段,则除了可能延迟一微秒之外,它没有任何区别。异常处理程序通常不需要很快。
但是什么可能导致进入异常处理程序?可能是因为错误的值被加载到段寄存器然后被引用?在这种情况下,代码建立一个安全的环境很重要。 cs
由异常调用设置。为了防弹,ss
也esp
应该设置。
跟进:
查看 i386 的 2-6.22.18 内核,我看不到这一点:
error_code: /* the function address is in %fs's slot on the stack */
pushl %es
... pushes %ds, %eax, %ebp, %edi, %esi, %edx, %ecx, %ebx, %fs
... along with pseudo-ops to manage stack frame layout
movl $(__KERNEL_PERCPU), %ecx
movl %ecx, %fs
popl %ecx // retrieves saved %fs
... sets up registers for the exception function
该符号是为非 SMP 机器和SMP__KERNEL_PERCPU
定义的宏(在 中include/asm-i386/segment.h
)。8 代表 GDT 条目大小(我认为),它与 per-CPU GDT 中的条目有关。它的值是注释指示的“默认用户 DS”,因此实际上是相同的。0
(GDT_ENTRY_PERCPU * 8)
GDT_ENTRY_PERCPU
<base> + 15
内核数据段通过fs
和访问ss
。许多内核数据访问都在堆栈上。通过保持通过 访问用户模式描述符ds
,只需要很少的段寄存器加载。
在 entry.s 中:
#define RESTORE_ALL
RESTORE_REGS
addl $4, %esp;
1: iret;
.section .fixup,"ax";
2: sti;
movl $(__USER_DS), %edx;
movl %edx, %ds;
movl %edx, %es;
movl $11,%eax;
call do_exit;
.previous;
.section __ex_table,"a";
.align 4;
.long 1b,2b;
.previous
该宏将在异常/中断/系统调用结束时调用。修复代码将 ds&es 设置为 USER_DS,这表明一旦 ds&es 的 DPL 不是 3(用户权限),iret 本身就会引发异常。
所以linux在异常/中断/系统调用一开始就将ds&es设置为USER_DS来避免这个异常。