/* * i386 signal handling routines * * Copyright 1999 Alexandre Julliard * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #if 0 #pragma makedep unix #endif #ifdef __i386__ #include "config.h" #include "wine/port.h" #include #include #include #include #include #include #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_SYS_PARAM_H # include #endif #ifdef HAVE_SYSCALL_H # include #else # ifdef HAVE_SYS_SYSCALL_H # include # endif #endif #ifdef HAVE_SYS_SIGNAL_H # include #endif #ifdef HAVE_SYS_UCONTEXT_H # include #endif #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" #include "wine/asm.h" #include "wine/exception.h" #include "unix_private.h" #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(seh); /* not defined for x86, so copy the x86_64 definition */ typedef struct DECLSPEC_ALIGN(16) _M128A { ULONGLONG Low; LONGLONG High; } M128A; typedef struct { WORD ControlWord; WORD StatusWord; BYTE TagWord; BYTE Reserved1; WORD ErrorOpcode; DWORD ErrorOffset; WORD ErrorSelector; WORD Reserved2; DWORD DataOffset; WORD DataSelector; WORD Reserved3; DWORD MxCsr; DWORD MxCsr_Mask; M128A FloatRegisters[8]; M128A XmmRegisters[16]; BYTE Reserved4[96]; } XMM_SAVE_AREA32; /*********************************************************************** * signal context platform-specific definitions */ #ifdef __linux__ struct modify_ldt_s { unsigned int entry_number; void *base_addr; unsigned int limit; unsigned int seg_32bit : 1; unsigned int contents : 2; unsigned int read_exec_only : 1; unsigned int limit_in_pages : 1; unsigned int seg_not_present : 1; unsigned int usable : 1; unsigned int garbage : 25; }; static inline int modify_ldt( int func, struct modify_ldt_s *ptr, unsigned long count ) { return syscall( 123 /* SYS_modify_ldt */, func, ptr, count ); } static inline int set_thread_area( struct modify_ldt_s *ptr ) { return syscall( 243 /* SYS_set_thread_area */, ptr ); } #elif defined (__BSDI__) #include #elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__) #include #include #include #elif defined (__OpenBSD__) #include #include #elif defined(__svr4__) || defined(_SCO_DS) || defined(__sun) #if defined(_SCO_DS) || defined(__sun) #include #endif #elif defined (__APPLE__) #include #elif defined(__NetBSD__) #include #include #elif defined(__GNU__) #include #include #else #error You must define the signal context functions for your platform #endif /* linux */ static const size_t teb_size = 4096; /* we reserve one page for the TEB */ static ULONG first_ldt_entry = 32; struct x86_thread_data { DWORD fs; /* 1d4 TEB selector */ DWORD gs; /* 1d8 libc selector; update winebuild if you move this! */ DWORD dr0; /* 1dc debug registers */ DWORD dr1; /* 1e0 */ DWORD dr2; /* 1e4 */ DWORD dr3; /* 1e8 */ DWORD dr6; /* 1ec */ DWORD dr7; /* 1f0 */ void *exit_frame; /* 1f4 exit frame pointer */ /* the ntdll_thread_data structure follows here */ }; C_ASSERT( offsetof( TEB, SystemReserved2 ) + offsetof( struct x86_thread_data, gs ) == 0x1d8 ); C_ASSERT( offsetof( TEB, SystemReserved2 ) + offsetof( struct x86_thread_data, exit_frame ) == 0x1f4 ); static inline struct x86_thread_data *x86_thread_data(void) { return (struct x86_thread_data *)NtCurrentTeb()->SystemReserved2; } static inline WORD get_cs(void) { WORD res; __asm__( "movw %%cs,%0" : "=r" (res) ); return res; } static inline WORD get_ds(void) { WORD res; __asm__( "movw %%ds,%0" : "=r" (res) ); return res; } static inline WORD get_fs(void) { WORD res; __asm__( "movw %%fs,%0" : "=r" (res) ); return res; } static inline WORD get_gs(void) { WORD res; __asm__( "movw %%gs,%0" : "=r" (res) ); return res; } static inline void set_fs( WORD val ) { __asm__( "mov %0,%%fs" :: "r" (val)); } /*********************************************************************** * is_gdt_sel */ static inline int is_gdt_sel( WORD sel ) { return !(sel & 4); } /*********************************************************************** * save_fpu * * Save the thread FPU context. */ static inline void save_fpu( CONTEXT *context ) { struct { DWORD ControlWord; DWORD StatusWord; DWORD TagWord; DWORD ErrorOffset; DWORD ErrorSelector; DWORD DataOffset; DWORD DataSelector; } float_status; context->ContextFlags |= CONTEXT_FLOATING_POINT; __asm__ __volatile__( "fnsave %0; fwait" : "=m" (context->FloatSave) ); /* Reset unmasked exceptions status to avoid firing an exception. */ memcpy(&float_status, &context->FloatSave, sizeof(float_status)); float_status.StatusWord &= float_status.ControlWord | 0xffffff80; __asm__ __volatile__( "fldenv %0" : : "m" (float_status) ); } /*********************************************************************** * save_fpux * * Save the thread FPU extended context. */ static inline void save_fpux( CONTEXT *context ) { /* we have to enforce alignment by hand */ char buffer[sizeof(XMM_SAVE_AREA32) + 16]; XMM_SAVE_AREA32 *state = (XMM_SAVE_AREA32 *)(((ULONG_PTR)buffer + 15) & ~15); context->ContextFlags |= CONTEXT_EXTENDED_REGISTERS; __asm__ __volatile__( "fxsave %0" : "=m" (*state) ); memcpy( context->ExtendedRegisters, state, sizeof(*state) ); } /*********************************************************************** * restore_fpu * * Restore the FPU context to a sigcontext. */ static inline void restore_fpu( const CONTEXT *context ) { FLOATING_SAVE_AREA float_status = context->FloatSave; /* reset the current interrupt status */ float_status.StatusWord &= float_status.ControlWord | 0xffffff80; __asm__ __volatile__( "frstor %0; fwait" : : "m" (float_status) ); } /*********************************************************************** * restore_fpux * * Restore the FPU extended context to a sigcontext. */ static inline void restore_fpux( const CONTEXT *context ) { /* we have to enforce alignment by hand */ char buffer[sizeof(XMM_SAVE_AREA32) + 16]; XMM_SAVE_AREA32 *state = (XMM_SAVE_AREA32 *)(((ULONG_PTR)buffer + 15) & ~15); memcpy( state, context->ExtendedRegisters, sizeof(*state) ); /* reset the current interrupt status */ state->StatusWord &= state->ControlWord | 0xff80; __asm__ __volatile__( "fxrstor %0" : : "m" (*state) ); } /*********************************************************************** * set_full_cpu_context * * Set the new CPU context. */ extern void set_full_cpu_context( const CONTEXT *context ); __ASM_GLOBAL_FUNC( set_full_cpu_context, "movl 4(%esp),%ecx\n\t" "movw 0x8c(%ecx),%gs\n\t" /* SegGs */ "movw 0x90(%ecx),%fs\n\t" /* SegFs */ "movw 0x94(%ecx),%es\n\t" /* SegEs */ "movl 0x9c(%ecx),%edi\n\t" /* Edi */ "movl 0xa0(%ecx),%esi\n\t" /* Esi */ "movl 0xa4(%ecx),%ebx\n\t" /* Ebx */ "movl 0xb4(%ecx),%ebp\n\t" /* Ebp */ "movw %ss,%ax\n\t" "cmpw 0xc8(%ecx),%ax\n\t" /* SegSs */ "jne 1f\n\t" /* As soon as we have switched stacks the context structure could * be invalid (when signal handlers are executed for example). Copy * values on the target stack before changing ESP. */ "movl 0xc4(%ecx),%eax\n\t" /* Esp */ "leal -4*4(%eax),%eax\n\t" "movl 0xc0(%ecx),%edx\n\t" /* EFlags */ "movl %edx,3*4(%eax)\n\t" "movl 0xbc(%ecx),%edx\n\t" /* SegCs */ "movl %edx,2*4(%eax)\n\t" "movl 0xb8(%ecx),%edx\n\t" /* Eip */ "movl %edx,1*4(%eax)\n\t" "movl 0xb0(%ecx),%edx\n\t" /* Eax */ "movl %edx,0*4(%eax)\n\t" "pushl 0x98(%ecx)\n\t" /* SegDs */ "movl 0xa8(%ecx),%edx\n\t" /* Edx */ "movl 0xac(%ecx),%ecx\n\t" /* Ecx */ "popl %ds\n\t" "movl %eax,%esp\n\t" "popl %eax\n\t" "iret\n" /* Restore the context when the stack segment changes. We can't use * the same code as above because we do not know if the stack segment * is 16 or 32 bit, and 'movl' will throw an exception when we try to * access memory above the limit. */ "1:\n\t" "movl 0xa8(%ecx),%edx\n\t" /* Edx */ "movl 0xb0(%ecx),%eax\n\t" /* Eax */ "movw 0xc8(%ecx),%ss\n\t" /* SegSs */ "movl 0xc4(%ecx),%esp\n\t" /* Esp */ "pushl 0xc0(%ecx)\n\t" /* EFlags */ "pushl 0xbc(%ecx)\n\t" /* SegCs */ "pushl 0xb8(%ecx)\n\t" /* Eip */ "pushl 0x98(%ecx)\n\t" /* SegDs */ "movl 0xac(%ecx),%ecx\n\t" /* Ecx */ "popl %ds\n\t" "iret" ) /*********************************************************************** * set_cpu_context * * Set the new CPU context. Used by NtSetContextThread. */ void DECLSPEC_HIDDEN set_cpu_context( const CONTEXT *context ) { DWORD flags = context->ContextFlags & ~CONTEXT_i386; if (flags & CONTEXT_EXTENDED_REGISTERS) restore_fpux( context ); else if (flags & CONTEXT_FLOATING_POINT) restore_fpu( context ); if (flags & CONTEXT_DEBUG_REGISTERS) { x86_thread_data()->dr0 = context->Dr0; x86_thread_data()->dr1 = context->Dr1; x86_thread_data()->dr2 = context->Dr2; x86_thread_data()->dr3 = context->Dr3; x86_thread_data()->dr6 = context->Dr6; x86_thread_data()->dr7 = context->Dr7; } if (flags & CONTEXT_FULL) { if (!(flags & CONTEXT_CONTROL)) FIXME( "setting partial context (%x) not supported\n", flags ); else if (flags & CONTEXT_SEGMENTS) set_full_cpu_context( context ); else { CONTEXT newcontext = *context; newcontext.SegDs = get_ds(); newcontext.SegEs = get_ds(); newcontext.SegFs = get_fs(); newcontext.SegGs = get_gs(); set_full_cpu_context( &newcontext ); } } } /*********************************************************************** * get_server_context_flags * * Convert CPU-specific flags to generic server flags */ static unsigned int get_server_context_flags( DWORD flags ) { unsigned int ret = 0; flags &= ~CONTEXT_i386; /* get rid of CPU id */ if (flags & CONTEXT_CONTROL) ret |= SERVER_CTX_CONTROL; if (flags & CONTEXT_INTEGER) ret |= SERVER_CTX_INTEGER; if (flags & CONTEXT_SEGMENTS) ret |= SERVER_CTX_SEGMENTS; if (flags & CONTEXT_FLOATING_POINT) ret |= SERVER_CTX_FLOATING_POINT; if (flags & CONTEXT_DEBUG_REGISTERS) ret |= SERVER_CTX_DEBUG_REGISTERS; if (flags & CONTEXT_EXTENDED_REGISTERS) ret |= SERVER_CTX_EXTENDED_REGISTERS; return ret; } /*********************************************************************** * context_to_server * * Convert a register context to the server format. */ NTSTATUS context_to_server( context_t *to, const CONTEXT *from ) { DWORD flags = from->ContextFlags & ~CONTEXT_i386; /* get rid of CPU id */ memset( to, 0, sizeof(*to) ); to->cpu = CPU_x86; if (flags & CONTEXT_CONTROL) { to->flags |= SERVER_CTX_CONTROL; to->ctl.i386_regs.ebp = from->Ebp; to->ctl.i386_regs.esp = from->Esp; to->ctl.i386_regs.eip = from->Eip; to->ctl.i386_regs.cs = from->SegCs; to->ctl.i386_regs.ss = from->SegSs; to->ctl.i386_regs.eflags = from->EFlags; } if (flags & CONTEXT_INTEGER) { to->flags |= SERVER_CTX_INTEGER; to->integer.i386_regs.eax = from->Eax; to->integer.i386_regs.ebx = from->Ebx; to->integer.i386_regs.ecx = from->Ecx; to->integer.i386_regs.edx = from->Edx; to->integer.i386_regs.esi = from->Esi; to->integer.i386_regs.edi = from->Edi; } if (flags & CONTEXT_SEGMENTS) { to->flags |= SERVER_CTX_SEGMENTS; to->seg.i386_regs.ds = from->SegDs; to->seg.i386_regs.es = from->SegEs; to->seg.i386_regs.fs = from->SegFs; to->seg.i386_regs.gs = from->SegGs; } if (flags & CONTEXT_FLOATING_POINT) { to->flags |= SERVER_CTX_FLOATING_POINT; to->fp.i386_regs.ctrl = from->FloatSave.ControlWord; to->fp.i386_regs.status = from->FloatSave.StatusWord; to->fp.i386_regs.tag = from->FloatSave.TagWord; to->fp.i386_regs.err_off = from->FloatSave.ErrorOffset; to->fp.i386_regs.err_sel = from->FloatSave.ErrorSelector; to->fp.i386_regs.data_off = from->FloatSave.DataOffset; to->fp.i386_regs.data_sel = from->FloatSave.DataSelector; to->fp.i386_regs.cr0npx = from->FloatSave.Cr0NpxState; memcpy( to->fp.i386_regs.regs, from->FloatSave.RegisterArea, sizeof(to->fp.i386_regs.regs) ); } if (flags & CONTEXT_DEBUG_REGISTERS) { to->flags |= SERVER_CTX_DEBUG_REGISTERS; to->debug.i386_regs.dr0 = from->Dr0; to->debug.i386_regs.dr1 = from->Dr1; to->debug.i386_regs.dr2 = from->Dr2; to->debug.i386_regs.dr3 = from->Dr3; to->debug.i386_regs.dr6 = from->Dr6; to->debug.i386_regs.dr7 = from->Dr7; } if (flags & CONTEXT_EXTENDED_REGISTERS) { to->flags |= SERVER_CTX_EXTENDED_REGISTERS; memcpy( to->ext.i386_regs, from->ExtendedRegisters, sizeof(to->ext.i386_regs) ); } return STATUS_SUCCESS; } /*********************************************************************** * context_from_server * * Convert a register context from the server format. */ NTSTATUS context_from_server( CONTEXT *to, const context_t *from ) { if (from->cpu != CPU_x86) return STATUS_INVALID_PARAMETER; to->ContextFlags = CONTEXT_i386; if (from->flags & SERVER_CTX_CONTROL) { to->ContextFlags |= CONTEXT_CONTROL; to->Ebp = from->ctl.i386_regs.ebp; to->Esp = from->ctl.i386_regs.esp; to->Eip = from->ctl.i386_regs.eip; to->SegCs = from->ctl.i386_regs.cs; to->SegSs = from->ctl.i386_regs.ss; to->EFlags = from->ctl.i386_regs.eflags; } if (from->flags & SERVER_CTX_INTEGER) { to->ContextFlags |= CONTEXT_INTEGER; to->Eax = from->integer.i386_regs.eax; to->Ebx = from->integer.i386_regs.ebx; to->Ecx = from->integer.i386_regs.ecx; to->Edx = from->integer.i386_regs.edx; to->Esi = from->integer.i386_regs.esi; to->Edi = from->integer.i386_regs.edi; } if (from->flags & SERVER_CTX_SEGMENTS) { to->ContextFlags |= CONTEXT_SEGMENTS; to->SegDs = from->seg.i386_regs.ds; to->SegEs = from->seg.i386_regs.es; to->SegFs = from->seg.i386_regs.fs; to->SegGs = from->seg.i386_regs.gs; } if (from->flags & SERVER_CTX_FLOATING_POINT) { to->ContextFlags |= CONTEXT_FLOATING_POINT; to->FloatSave.ControlWord = from->fp.i386_regs.ctrl; to->FloatSave.StatusWord = from->fp.i386_regs.status; to->FloatSave.TagWord = from->fp.i386_regs.tag; to->FloatSave.ErrorOffset = from->fp.i386_regs.err_off; to->FloatSave.ErrorSelector = from->fp.i386_regs.err_sel; to->FloatSave.DataOffset = from->fp.i386_regs.data_off; to->FloatSave.DataSelector = from->fp.i386_regs.data_sel; to->FloatSave.Cr0NpxState = from->fp.i386_regs.cr0npx; memcpy( to->FloatSave.RegisterArea, from->fp.i386_regs.regs, sizeof(to->FloatSave.RegisterArea) ); } if (from->flags & SERVER_CTX_DEBUG_REGISTERS) { to->ContextFlags |= CONTEXT_DEBUG_REGISTERS; to->Dr0 = from->debug.i386_regs.dr0; to->Dr1 = from->debug.i386_regs.dr1; to->Dr2 = from->debug.i386_regs.dr2; to->Dr3 = from->debug.i386_regs.dr3; to->Dr6 = from->debug.i386_regs.dr6; to->Dr7 = from->debug.i386_regs.dr7; } if (from->flags & SERVER_CTX_EXTENDED_REGISTERS) { to->ContextFlags |= CONTEXT_EXTENDED_REGISTERS; memcpy( to->ExtendedRegisters, from->ext.i386_regs, sizeof(to->ExtendedRegisters) ); } return STATUS_SUCCESS; } /*********************************************************************** * NtSetContextThread (NTDLL.@) * ZwSetContextThread (NTDLL.@) */ NTSTATUS WINAPI NtSetContextThread( HANDLE handle, const CONTEXT *context ) { NTSTATUS ret = STATUS_SUCCESS; BOOL self = (handle == GetCurrentThread()); /* debug registers require a server call */ if (self && (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386))) self = (x86_thread_data()->dr0 == context->Dr0 && x86_thread_data()->dr1 == context->Dr1 && x86_thread_data()->dr2 == context->Dr2 && x86_thread_data()->dr3 == context->Dr3 && x86_thread_data()->dr6 == context->Dr6 && x86_thread_data()->dr7 == context->Dr7); if (!self) { context_t server_context; context_to_server( &server_context, context ); ret = set_thread_context( handle, &server_context, &self ); } if (self && ret == STATUS_SUCCESS) set_cpu_context( context ); return ret; } /*********************************************************************** * NtGetContextThread (NTDLL.@) * ZwGetContextThread (NTDLL.@) * * Note: we use a small assembly wrapper to save the necessary registers * in case we are fetching the context of the current thread. */ NTSTATUS WINAPI NtGetContextThread( HANDLE handle, CONTEXT *context ) { NTSTATUS ret; DWORD needed_flags = context->ContextFlags & ~CONTEXT_i386; BOOL self = (handle == GetCurrentThread()); /* debug registers require a server call */ if (needed_flags & CONTEXT_DEBUG_REGISTERS) self = FALSE; if (!self) { context_t server_context; unsigned int server_flags = get_server_context_flags( context->ContextFlags ); if ((ret = get_thread_context( handle, &server_context, server_flags, &self ))) return ret; if ((ret = context_from_server( context, &server_context ))) return ret; needed_flags &= ~context->ContextFlags; } if (self) { if (needed_flags & CONTEXT_INTEGER) { context->Eax = 0; context->Ecx = 0; context->Edx = 0; /* other registers already set from asm wrapper */ context->ContextFlags |= CONTEXT_INTEGER; } if (needed_flags & CONTEXT_CONTROL) { context->SegCs = get_cs(); context->SegSs = get_ds(); /* other registers already set from asm wrapper */ context->ContextFlags |= CONTEXT_CONTROL; } if (needed_flags & CONTEXT_SEGMENTS) { context->SegDs = get_ds(); context->SegEs = get_ds(); context->SegFs = get_fs(); context->SegGs = get_gs(); context->ContextFlags |= CONTEXT_SEGMENTS; } if (needed_flags & CONTEXT_FLOATING_POINT) save_fpu( context ); if (needed_flags & CONTEXT_EXTENDED_REGISTERS) save_fpux( context ); /* FIXME: xstate */ /* update the cached version of the debug registers */ if (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386)) { x86_thread_data()->dr0 = context->Dr0; x86_thread_data()->dr1 = context->Dr1; x86_thread_data()->dr2 = context->Dr2; x86_thread_data()->dr3 = context->Dr3; x86_thread_data()->dr6 = context->Dr6; x86_thread_data()->dr7 = context->Dr7; } } if (context->ContextFlags & (CONTEXT_INTEGER & ~CONTEXT_i386)) TRACE( "%p: eax=%08x ebx=%08x ecx=%08x edx=%08x esi=%08x edi=%08x\n", handle, context->Eax, context->Ebx, context->Ecx, context->Edx, context->Esi, context->Edi ); if (context->ContextFlags & (CONTEXT_CONTROL & ~CONTEXT_i386)) TRACE( "%p: ebp=%08x esp=%08x eip=%08x cs=%04x ss=%04x flags=%08x\n", handle, context->Ebp, context->Esp, context->Eip, context->SegCs, context->SegSs, context->EFlags ); if (context->ContextFlags & (CONTEXT_SEGMENTS & ~CONTEXT_i386)) TRACE( "%p: ds=%04x es=%04x fs=%04x gs=%04x\n", handle, context->SegDs, context->SegEs, context->SegFs, context->SegGs ); if (context->ContextFlags & (CONTEXT_DEBUG_REGISTERS & ~CONTEXT_i386)) TRACE( "%p: dr0=%08x dr1=%08x dr2=%08x dr3=%08x dr6=%08x dr7=%08x\n", handle, context->Dr0, context->Dr1, context->Dr2, context->Dr3, context->Dr6, context->Dr7 ); return STATUS_SUCCESS; } /*********************************************************************** * LDT support */ #define LDT_SIZE 8192 #define LDT_FLAGS_DATA 0x13 /* Data segment */ #define LDT_FLAGS_CODE 0x1b /* Code segment */ #define LDT_FLAGS_32BIT 0x40 /* Segment is 32-bit (code or stack) */ #define LDT_FLAGS_ALLOCATED 0x80 /* Segment is allocated */ struct ldt_copy { void *base[LDT_SIZE]; unsigned int limit[LDT_SIZE]; unsigned char flags[LDT_SIZE]; } __wine_ldt_copy; static WORD gdt_fs_sel; static RTL_CRITICAL_SECTION ldt_section; static RTL_CRITICAL_SECTION_DEBUG critsect_debug = { 0, 0, &ldt_section, { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList }, 0, 0, { (DWORD_PTR)(__FILE__ ": ldt_section") } }; static RTL_CRITICAL_SECTION ldt_section = { &critsect_debug, -1, 0, 0, 0, 0 }; static const LDT_ENTRY null_entry; static inline void *ldt_get_base( LDT_ENTRY ent ) { return (void *)(ent.BaseLow | (ULONG_PTR)ent.HighWord.Bits.BaseMid << 16 | (ULONG_PTR)ent.HighWord.Bits.BaseHi << 24); } static inline unsigned int ldt_get_limit( LDT_ENTRY ent ) { unsigned int limit = ent.LimitLow | (ent.HighWord.Bits.LimitHi << 16); if (ent.HighWord.Bits.Granularity) limit = (limit << 12) | 0xfff; return limit; } static LDT_ENTRY ldt_make_entry( void *base, unsigned int limit, unsigned char flags ) { LDT_ENTRY entry; entry.BaseLow = (WORD)(ULONG_PTR)base; entry.HighWord.Bits.BaseMid = (BYTE)((ULONG_PTR)base >> 16); entry.HighWord.Bits.BaseHi = (BYTE)((ULONG_PTR)base >> 24); if ((entry.HighWord.Bits.Granularity = (limit >= 0x100000))) limit >>= 12; entry.LimitLow = (WORD)limit; entry.HighWord.Bits.LimitHi = limit >> 16; entry.HighWord.Bits.Dpl = 3; entry.HighWord.Bits.Pres = 1; entry.HighWord.Bits.Type = flags; entry.HighWord.Bits.Sys = 0; entry.HighWord.Bits.Reserved_0 = 0; entry.HighWord.Bits.Default_Big = (flags & LDT_FLAGS_32BIT) != 0; return entry; } static void ldt_set_entry( WORD sel, LDT_ENTRY entry ) { int index = sel >> 3; #ifdef linux struct modify_ldt_s ldt_info = { index }; ldt_info.base_addr = ldt_get_base( entry ); ldt_info.limit = entry.LimitLow | (entry.HighWord.Bits.LimitHi << 16); ldt_info.seg_32bit = entry.HighWord.Bits.Default_Big; ldt_info.contents = (entry.HighWord.Bits.Type >> 2) & 3; ldt_info.read_exec_only = !(entry.HighWord.Bits.Type & 2); ldt_info.limit_in_pages = entry.HighWord.Bits.Granularity; ldt_info.seg_not_present = !entry.HighWord.Bits.Pres; ldt_info.usable = entry.HighWord.Bits.Sys; if (modify_ldt( 0x11, &ldt_info, sizeof(ldt_info) ) < 0) perror( "modify_ldt" ); #elif defined(__NetBSD__) || defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__OpenBSD__) || defined(__DragonFly__) /* The kernel will only let us set LDTs with user priority level */ if (entry.HighWord.Bits.Pres && entry.HighWord.Bits.Dpl != 3) entry.HighWord.Bits.Dpl = 3; if (i386_set_ldt(index, (union descriptor *)&entry, 1) < 0) { perror("i386_set_ldt"); fprintf( stderr, "Did you reconfigure the kernel with \"options USER_LDT\"?\n" ); exit(1); } #elif defined(__svr4__) || defined(_SCO_DS) struct ssd ldt_mod; ldt_mod.sel = sel; ldt_mod.bo = (unsigned long)ldt_get_base( entry ); ldt_mod.ls = entry.LimitLow | (entry.HighWord.Bits.LimitHi << 16); ldt_mod.acc1 = entry.HighWord.Bytes.Flags1; ldt_mod.acc2 = entry.HighWord.Bytes.Flags2 >> 4; if (sysi86(SI86DSCR, &ldt_mod) == -1) perror("sysi86"); #elif defined(__APPLE__) if (i386_set_ldt(index, (union ldt_entry *)&entry, 1) < 0) perror("i386_set_ldt"); #elif defined(__GNU__) if (i386_set_ldt(mach_thread_self(), sel, (descriptor_list_t)&entry, 1) != KERN_SUCCESS) perror("i386_set_ldt"); #else fprintf( stderr, "No LDT support on this platform\n" ); exit(1); #endif __wine_ldt_copy.base[index] = ldt_get_base( entry ); __wine_ldt_copy.limit[index] = ldt_get_limit( entry ); __wine_ldt_copy.flags[index] = (entry.HighWord.Bits.Type | (entry.HighWord.Bits.Default_Big ? LDT_FLAGS_32BIT : 0) | LDT_FLAGS_ALLOCATED); } static void ldt_set_fs( WORD sel, TEB *teb ) { if (sel == gdt_fs_sel) { #ifdef __linux__ struct modify_ldt_s ldt_info = { sel >> 3 }; ldt_info.base_addr = teb; ldt_info.limit = teb_size - 1; ldt_info.seg_32bit = 1; if (set_thread_area( &ldt_info ) < 0) perror( "set_thread_area" ); #elif defined(__FreeBSD__) || defined (__FreeBSD_kernel__) || defined(__DragonFly__) i386_set_fsbase( teb ); #endif } set_fs( sel ); } /********************************************************************** * get_thread_ldt_entry */ NTSTATUS CDECL get_thread_ldt_entry( HANDLE handle, void *data, ULONG len, ULONG *ret_len ) { THREAD_DESCRIPTOR_INFORMATION *info = data; NTSTATUS status = STATUS_SUCCESS; if (len < sizeof(*info)) return STATUS_INFO_LENGTH_MISMATCH; if (info->Selector >> 16) return STATUS_UNSUCCESSFUL; if (is_gdt_sel( info->Selector )) { if (!(info->Selector & ~3)) info->Entry = null_entry; else if ((info->Selector | 3) == get_cs()) info->Entry = ldt_make_entry( 0, ~0u, LDT_FLAGS_CODE | LDT_FLAGS_32BIT ); else if ((info->Selector | 3) == get_ds()) info->Entry = ldt_make_entry( 0, ~0u, LDT_FLAGS_DATA | LDT_FLAGS_32BIT ); else if ((info->Selector | 3) == get_fs()) info->Entry = ldt_make_entry( NtCurrentTeb(), 0xfff, LDT_FLAGS_DATA | LDT_FLAGS_32BIT ); else return STATUS_UNSUCCESSFUL; } else { SERVER_START_REQ( get_selector_entry ) { req->handle = wine_server_obj_handle( handle ); req->entry = info->Selector >> 3; status = wine_server_call( req ); if (!status) { if (reply->flags) info->Entry = ldt_make_entry( (void *)reply->base, reply->limit, reply->flags ); else status = STATUS_UNSUCCESSFUL; } } SERVER_END_REQ; } if (status == STATUS_SUCCESS && ret_len) /* yes, that's a bit strange, but it's the way it is */ *ret_len = sizeof(info->Entry); return status; } /****************************************************************************** * NtSetLdtEntries (NTDLL.@) * ZwSetLdtEntries (NTDLL.@) */ NTSTATUS WINAPI NtSetLdtEntries( ULONG sel1, LDT_ENTRY entry1, ULONG sel2, LDT_ENTRY entry2 ) { sigset_t sigset; if (sel1 >> 16 || sel2 >> 16) return STATUS_INVALID_LDT_DESCRIPTOR; if (sel1 && (sel1 >> 3) < first_ldt_entry) return STATUS_INVALID_LDT_DESCRIPTOR; if (sel2 && (sel2 >> 3) < first_ldt_entry) return STATUS_INVALID_LDT_DESCRIPTOR; server_enter_uninterrupted_section( &ldt_section, &sigset ); if (sel1) ldt_set_entry( sel1, entry1 ); if (sel2) ldt_set_entry( sel2, entry2 ); server_leave_uninterrupted_section( &ldt_section, &sigset ); return STATUS_SUCCESS; } /********************************************************************** * signal_init_threading */ void signal_init_threading(void) { #ifdef __linux__ /* the preloader may have allocated it already */ gdt_fs_sel = get_fs(); if (!gdt_fs_sel || !is_gdt_sel( gdt_fs_sel )) { struct modify_ldt_s ldt_info = { -1 }; ldt_info.seg_32bit = 1; ldt_info.usable = 1; if (set_thread_area( &ldt_info ) >= 0) gdt_fs_sel = (ldt_info.entry_number << 3) | 3; else gdt_fs_sel = 0; } #elif defined(__FreeBSD__) || defined (__FreeBSD_kernel__) gdt_fs_sel = GSEL( GUFS_SEL, SEL_UPL ); #endif } /********************************************************************** * signal_alloc_thread */ NTSTATUS signal_alloc_thread( TEB *teb ) { struct x86_thread_data *thread_data = (struct x86_thread_data *)teb->SystemReserved2; if (!gdt_fs_sel) { static int first_thread = 1; sigset_t sigset; int idx; LDT_ENTRY entry = ldt_make_entry( teb, teb_size - 1, LDT_FLAGS_DATA | LDT_FLAGS_32BIT ); if (first_thread) /* no locking for first thread */ { /* leave some space if libc is using the LDT for %gs */ if (!is_gdt_sel( get_gs() )) first_ldt_entry = 512; idx = first_ldt_entry; ldt_set_entry( (idx << 3) | 7, entry ); first_thread = 0; } else { server_enter_uninterrupted_section( &ldt_section, &sigset ); for (idx = first_ldt_entry; idx < LDT_SIZE; idx++) { if (__wine_ldt_copy.flags[idx]) continue; ldt_set_entry( (idx << 3) | 7, entry ); break; } server_leave_uninterrupted_section( &ldt_section, &sigset ); if (idx == LDT_SIZE) return STATUS_TOO_MANY_THREADS; } thread_data->fs = (idx << 3) | 7; } else thread_data->fs = gdt_fs_sel; return STATUS_SUCCESS; } /********************************************************************** * signal_free_thread */ void signal_free_thread( TEB *teb ) { struct x86_thread_data *thread_data = (struct x86_thread_data *)teb->SystemReserved2; sigset_t sigset; if (gdt_fs_sel) return; server_enter_uninterrupted_section( &ldt_section, &sigset ); __wine_ldt_copy.flags[thread_data->fs >> 3] = 0; server_leave_uninterrupted_section( &ldt_section, &sigset ); } /********************************************************************** * signal_init_thread */ void signal_init_thread( TEB *teb ) { const WORD fpu_cw = 0x27f; struct x86_thread_data *thread_data = (struct x86_thread_data *)teb->SystemReserved2; stack_t ss; ss.ss_sp = (char *)teb + teb_size; ss.ss_size = signal_stack_size; ss.ss_flags = 0; if (sigaltstack(&ss, NULL) == -1) perror( "sigaltstack" ); ldt_set_fs( thread_data->fs, teb ); thread_data->gs = get_gs(); #ifdef __GNUC__ __asm__ volatile ("fninit; fldcw %0" : : "m" (fpu_cw)); #else FIXME("FPU setup not implemented for this platform.\n"); #endif } /*********************************************************************** * init_thread_context */ static void init_thread_context( CONTEXT *context, LPTHREAD_START_ROUTINE entry, void *arg, void *relay ) { context->SegCs = get_cs(); context->SegDs = get_ds(); context->SegEs = get_ds(); context->SegFs = get_fs(); context->SegGs = get_gs(); context->SegSs = get_ds(); context->EFlags = 0x202; context->Eax = (DWORD)entry; context->Ebx = (DWORD)arg; context->Esp = (DWORD)NtCurrentTeb()->Tib.StackBase - 16; context->Eip = (DWORD)relay; context->FloatSave.ControlWord = 0x27f; ((XMM_SAVE_AREA32 *)context->ExtendedRegisters)->ControlWord = 0x27f; ((XMM_SAVE_AREA32 *)context->ExtendedRegisters)->MxCsr = 0x1f80; } /*********************************************************************** * attach_thread */ PCONTEXT DECLSPEC_HIDDEN attach_thread( LPTHREAD_START_ROUTINE entry, void *arg, BOOL suspend, void *relay ) { CONTEXT *ctx; if (suspend) { CONTEXT context = { CONTEXT_ALL }; init_thread_context( &context, entry, arg, relay ); wait_suspend( &context ); ctx = (CONTEXT *)((ULONG_PTR)context.Esp & ~15) - 1; *ctx = context; } else { ctx = (CONTEXT *)((char *)NtCurrentTeb()->Tib.StackBase - 16) - 1; init_thread_context( ctx, entry, arg, relay ); } ctx->ContextFlags = CONTEXT_FULL | CONTEXT_FLOATING_POINT | CONTEXT_EXTENDED_REGISTERS; LdrInitializeThunk( ctx, (void **)&ctx->Eax, 0, 0 ); return ctx; } /*********************************************************************** * signal_start_thread */ __ASM_GLOBAL_FUNC( signal_start_thread, "pushl %ebp\n\t" __ASM_CFI(".cfi_adjust_cfa_offset 4\n\t") __ASM_CFI(".cfi_rel_offset %ebp,0\n\t") "movl %esp,%ebp\n\t" __ASM_CFI(".cfi_def_cfa_register %ebp\n\t") "pushl %ebx\n\t" __ASM_CFI(".cfi_rel_offset %ebx,-4\n\t") "pushl %esi\n\t" __ASM_CFI(".cfi_rel_offset %esi,-8\n\t") "pushl %edi\n\t" __ASM_CFI(".cfi_rel_offset %edi,-12\n\t") /* store exit frame */ "movl %ebp,%fs:0x1f4\n\t" /* x86_thread_data()->exit_frame */ /* switch to thread stack */ "movl %fs:4,%eax\n\t" /* NtCurrentTeb()->StackBase */ "leal -0x1000(%eax),%esp\n\t" /* attach dlls */ "pushl 20(%ebp)\n\t" /* relay */ "pushl 16(%ebp)\n\t" /* suspend */ "pushl 12(%ebp)\n\t" /* arg */ "pushl 8(%ebp)\n\t" /* entry */ "xorl %ebp,%ebp\n\t" "call " __ASM_NAME("attach_thread") "\n\t" "movl %eax,%esi\n\t" "leal -12(%eax),%esp\n\t" /* clear the stack */ "andl $~0xfff,%eax\n\t" /* round down to page size */ "movl %eax,(%esp)\n\t" "call " __ASM_NAME("virtual_clear_thread_stack") "\n\t" /* switch to the initial context */ "movl %esi,(%esp)\n\t" "call " __ASM_NAME("set_cpu_context") ) /*********************************************************************** * signal_exit_thread */ __ASM_GLOBAL_FUNC( signal_exit_thread, "movl 8(%esp),%ecx\n\t" /* fetch exit frame */ "movl %fs:0x1f4,%edx\n\t" /* x86_thread_data()->exit_frame */ "testl %edx,%edx\n\t" "jnz 1f\n\t" "jmp *%ecx\n\t" /* switch to exit frame stack */ "1:\tmovl 4(%esp),%eax\n\t" "movl $0,%fs:0x1f4\n\t" "movl %edx,%ebp\n\t" __ASM_CFI(".cfi_def_cfa %ebp,4\n\t") __ASM_CFI(".cfi_rel_offset %ebp,0\n\t") __ASM_CFI(".cfi_rel_offset %ebx,-4\n\t") __ASM_CFI(".cfi_rel_offset %esi,-8\n\t") __ASM_CFI(".cfi_rel_offset %edi,-12\n\t") "leal -20(%ebp),%esp\n\t" "pushl %eax\n\t" "call *%ecx" ) /********************************************************************** * NtCurrentTeb (NTDLL.@) */ __ASM_STDCALL_FUNC( NtCurrentTeb, 0, ".byte 0x64\n\tmovl 0x18,%eax\n\tret" ) #endif /* __i386__ */