问题
The 32-bit program below calls set_thread_area(2) to create an entry in GDT, which is meant to be used for TLS. Typically the resulting selector is put into FS
or GS
and successfully used. But if it is put into DS
or ES
, running on a 64-bit kernel, eventually (after context switch I guess) this selector zeroes out.
But if I instead use modify_ldt(2) and put selector of the resulting LDT entry into these segment registers, they appear to hold their values!
Also, if I put e.g. selector of 64-bit code segment (0x33
) or 32-bit code segment (0x23
), both of which refer to GDT, into DS
or ES
, they don't get zeroed out.
Here's the source (compiles with fasm), demonstrating the strange behavior:
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
This seems to be somehow related to the fact that 64-bit processes have NULL selector in these two segment registers by default.
This happens on Linux 3.16.0 and earlier, but doesn't on 4.2.0 and newer.
What's going on here? Why do ES
and DS
zero out when having TLS selectors in them, but do not with any other selector?
来源:https://stackoverflow.com/questions/32937701/why-do-es-and-ds-zero-out-eventually-on-64-bit-kernel-when-set-to-tls-selectors