下面的 32 位程序调用set_thread_area(2) http://linux.die.net/man/2/set_thread_area在 GDT 中创建一个条目,该条目旨在用于 TLS。通常将结果选择器放入FS
or GS
并成功使用。但如果将其放入DS
or ES
,在 64 位内核上运行,最终(我猜是在上下文切换之后)该选择器归零。
但如果我改用modify_ldt(2) http://man7.org/linux/man-pages/man2/modify_ldt.2.html并将生成的 LDT 条目的选择器放入这些段寄存器中,它们似乎保存了它们的值!
另外,如果我把例如64位代码段选择器(0x33
) 或 32 位代码段 (0x23
),两者都引用 GET,进入DS
or ES
,他们不会被归零。
这是源代码(编译为fasm http://flatassembler.net/),展示了奇怪的行为:
format ELF executable
segment readable executable
SYS_WRITE=4
STDERR=2
SYS_SET_THREAD_AREA=243
SYS_EXIT=1
entry $
start:
mov eax, SYS_SET_THREAD_AREA
mov ebx, user_desc_TLS1
int 0x80
mov ecx,[entry_number_TLS1]
lea ecx,[ecx*8+3]
mov ds,cx
; let's wait until DS spontaneously zeroes...
xor eax,eax
checkDsZero:
mov ax,ds
test eax,eax
jnz checkDsZero
mov eax, SYS_WRITE
mov ebx, STDERR
mov ecx, dsZeroedMsg
mov edx, dsZeroedMsgLen
int 0x80
xor ebx, ebx
mov eax, SYS_EXIT
int 0x80
segment readable writable
dsZeroedMsg:
db "DS zeroed. Exiting",0xa
dsZeroedMsgLen=$-dsZeroedMsg
SEGMENT_32BIT=1
CONTENTS_DATA=0*2
CONTENTS_STACK=1*2
CONTENTS_CODE=2*2
READ_EXEC_ONLY=0x8
LIMIT_IN_PAGES=0x10
NOT_PRESENT=0x20
USABLE_BIT=0x40
LONG_MODE=0x80
user_desc_TLS1:
entry_number_TLS1:
dd -1
base_addr_TLS1:
dd start+0x345
limit_TLS1:
dd 0x123
properties_TLS1:
dd SEGMENT_32BIT+CONTENTS_DATA
这似乎与 64 位进程默认在这两个段寄存器中具有 NULL 选择器这一事实有关。
这种情况发生在 Linux 3.16.0 及更早版本上,但不会发生在 4.2.0 及更高版本上。
这里发生了什么?为什么ES
and DS
当其中包含 TLS 选择器时清零,但不使用任何其他选择器?