From 75d6dc8434631c2a07d2d19103d1be1b0f3ff0f1 Mon Sep 17 00:00:00 2001 From: Jeremy Fitzhardinge Date: Wed, 21 Jan 2004 01:27:27 +0000 Subject: [PATCH] This change implements the TLS extension to the x86 ABI. This allows threads to have thread-private data which is quickly accessible via a segment in the GDT, stored in %gs. The patch implements the relevent syscalls (setthreadarea), and also manages switching the VCPU's segment information at thread context-switch time. Mostly Tom Hughes' work. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@2215 --- coregrind/Makefile.am | 2 +- coregrind/stage2.c | 10 -- coregrind/vg_execontext.c | 15 +++ coregrind/vg_include.h | 18 +++ coregrind/vg_ldt.c | 144 ++++++++++++++++++++---- coregrind/vg_libpthread.c | 226 ++++++++++++++++++++++++++++++++------ coregrind/vg_main.c | 7 +- coregrind/vg_proxylwp.c | 9 ++ coregrind/vg_scheduler.c | 32 +++++- coregrind/vg_syscalls.c | 78 +++++++++++-- glibc-2.3.supp | 23 ++++ include/vg_kerneliface.h | 13 +++ none/tests/Makefile.am | 15 ++- none/tests/seg_override.c | 4 +- none/tests/tls.c | 101 +++++++++++++++++ none/tests/tls.stderr.exp | 2 + none/tests/tls.stdout.exp | 19 ++++ none/tests/tls.vgtest | 1 + none/tests/tls2.c | 1 + none/tests/tls2_so.c | 1 + none/tests/tls_so.c | 20 ++++ 21 files changed, 655 insertions(+), 86 deletions(-) create mode 100644 none/tests/tls.c create mode 100644 none/tests/tls.stderr.exp create mode 100644 none/tests/tls.stdout.exp create mode 100644 none/tests/tls.vgtest create mode 100644 none/tests/tls2.c create mode 100644 none/tests/tls2_so.c create mode 100644 none/tests/tls_so.c diff --git a/coregrind/Makefile.am b/coregrind/Makefile.am index 473297fed..94587ff91 100644 --- a/coregrind/Makefile.am +++ b/coregrind/Makefile.am @@ -108,7 +108,7 @@ libpthread_so_SOURCES = \ vg_syscall.S libpthread_so_DEPENDENCIES = $(srcdir)/vg_libpthread.vs libpthread_so_LDFLAGS = -Werror -fno-omit-frame-pointer -UVG_LIBDIR \ - -shared -fpic \ + -shared -fpic -ldl \ -Wl,-version-script $(srcdir)/vg_libpthread.vs \ -Wl,-z,nodelete \ -Wl,--soname=libpthread.so.0 diff --git a/coregrind/stage2.c b/coregrind/stage2.c index 3f26fbbc8..831582455 100644 --- a/coregrind/stage2.c +++ b/coregrind/stage2.c @@ -472,7 +472,6 @@ static void list_tools(void) environment, except: 1. LD_LIBRARY_PATH=$VALGRINDLIB:$LD_LIBRARY_PATH 2. LD_PRELOAD=$VALGRINDLIB/vg_inject.so:($VALGRINDLIB/vgpreload_TOOL.so:)?$LD_PRELOAD - 3. LD_ASSUME_KERNEL=2.4.1 If any of these is missing, then it is added. @@ -488,13 +487,10 @@ static char **fix_environment(char **origenv, const char *preload) static const int ld_library_path_len = sizeof(ld_library_path)-1; static const char ld_preload[] = "LD_PRELOAD="; static const int ld_preload_len = sizeof(ld_preload)-1; - static const char ld_assume_kernel[] = "LD_ASSUME_KERNEL="; - static const int ld_assume_kernel_len = sizeof(ld_assume_kernel)-1; static const char valgrind_clo[] = VALGRINDCLO "="; static const char valgrind_clo_len = sizeof(valgrind_clo)-1; int ld_preload_done = 0; int ld_library_path_done = 0; - int ld_assume_kernel_done = 0; char *inject_path; int inject_path_len; int vgliblen = strlen(valgrind_lib); @@ -570,9 +566,6 @@ static char **fix_environment(char **origenv, const char *preload) *cpp = cp; ld_preload_done = 1; - } else if (memcmp(*cpp, ld_assume_kernel, ld_assume_kernel_len) == 0) { - *cpp = "LD_ASSUME_KERNEL=2.4.1"; - ld_assume_kernel_done = 1; } else if (memcmp(*cpp, valgrind_clo, valgrind_clo_len) == 0) { *cpp = ""; } @@ -599,9 +592,6 @@ static char **fix_environment(char **origenv, const char *preload) ret[envc++] = cp; } - - if (!ld_assume_kernel_done) - ret[envc++] = "LD_ASSUME_KERNEL=2.4.1"; ret[envc] = NULL; diff --git a/coregrind/vg_execontext.c b/coregrind/vg_execontext.c index 5c5c7945d..f2e9752e2 100644 --- a/coregrind/vg_execontext.c +++ b/coregrind/vg_execontext.c @@ -319,6 +319,21 @@ void get_needed_regs(ThreadId tid, Addr* eip, Addr* ebp, Addr* esp, *esp = tst->m_esp; *stack_highest_word = tst->stack_highest_word; } + + /* Nasty little hack to deal with sysinfo syscalls - if libc is + using the sysinfo page for syscalls (the TLS version does), then + eip will always appear to be in that page when doing a syscall, + not the actual libc function doing the syscall. This check sees + if EIP is within the syscall code, and pops the return address + off the stack so that eip is placed within the library function + calling the syscall. This makes stack backtraces much more + useful. */ + if (*eip >= VG_(client_trampoline_code)+VG_(tramp_syscall_offset) && + *eip < VG_(client_trampoline_code)+VG_(trampoline_code_length) && + VG_(is_addressable)(*esp, sizeof(Addr))) { + *eip = *(Addr *)*esp; + *esp += sizeof(Addr); + } } ExeContext* VG_(get_ExeContext) ( ThreadId tid ) diff --git a/coregrind/vg_include.h b/coregrind/vg_include.h index 8681485ba..8cc1c8b6d 100644 --- a/coregrind/vg_include.h +++ b/coregrind/vg_include.h @@ -624,11 +624,19 @@ extern VgLdtEntry* VG_(allocate_LDT_for_thread) ( VgLdtEntry* parent_ldt ); extern void VG_(deallocate_LDT_for_thread) ( VgLdtEntry* ldt ); +extern void + VG_(clear_TLS_for_thread) ( VgLdtEntry* tls ); /* Simulate the modify_ldt syscall. */ extern Int VG_(sys_modify_ldt) ( ThreadId tid, Int func, void* ptr, UInt bytecount ); +/* Simulate the {get,set}_thread_area syscalls. */ +extern Int VG_(sys_set_thread_area) ( ThreadId tid, + struct vki_modify_ldt_ldt_s* info ); +extern Int VG_(sys_get_thread_area) ( ThreadId tid, + struct vki_modify_ldt_ldt_s* info ); + /* Called from generated code. Given a segment selector and a virtual address, return a linear address, and do limit checks too. */ extern Addr VG_(do_useseg) ( UInt seg_selector, Addr virtual_addr ); @@ -803,6 +811,10 @@ typedef deallocate this at thread exit. */ VgLdtEntry* ldt; + /* TLS table. This consists of a small number (currently 3) of + entries from the Global Descriptor Table. */ + VgLdtEntry tls[VKI_GDT_TLS_ENTRIES]; + /* Saved machine context. Note the FPU state, %EIP and segment registers are not shadowed. @@ -1622,6 +1634,9 @@ __attribute__ ((__noreturn__)) extern void VG_(proxy_handlesig)( const vki_ksiginfo_t *siginfo, const struct vki_sigcontext *sigcontext ); +/* Get the PID/TID of the ProxyLWP. */ +extern Int VG_(proxy_id)(ThreadId tid); + /* --------------------------------------------------------------------- Exports of vg_syscalls.c @@ -1830,6 +1845,9 @@ extern Int VGOFF_(sh_eflags); /* This thread's LDT pointer. */ extern Int VGOFF_(ldt); +/* This thread's TLS pointer. */ +extern Int VGOFF_(tls); + /* Nb: Most helper offsets are in include/vg_skin.h, for use by skins */ extern Int VGOFF_(helper_undefined_instruction); diff --git a/coregrind/vg_ldt.c b/coregrind/vg_ldt.c index ca5390da5..5edb82170 100644 --- a/coregrind/vg_ldt.c +++ b/coregrind/vg_ldt.c @@ -121,6 +121,24 @@ void VG_(deallocate_LDT_for_thread) ( VgLdtEntry* ldt ) +/* Clear a TLS array. */ +void VG_(clear_TLS_for_thread) ( VgLdtEntry* tls ) +{ + VgLdtEntry* tlsp; + + if (0) + VG_(printf)("clear_TLS_for_thread\n" ); + + for (tlsp = tls; tlsp < tls + VKI_GDT_TLS_ENTRIES; tlsp++) { + tlsp->LdtEnt.Words.word1 = 0; + tlsp->LdtEnt.Words.word2 = 0; + } + + return; +} + + + /* Fish the base field out of an VgLdtEntry. This is the only part we are particularly interested in. */ @@ -151,9 +169,9 @@ unsigned int wine_ldt_get_limit( const VgLdtEntry *ent ) */ Addr VG_(do_useseg) ( UInt seg_selector, Addr virtual_addr ) { - Addr base; - UInt limit; - VgLdtEntry* the_ldt; + UInt table; + Addr base; + UInt limit; if (0) VG_(printf)("do_useseg: seg_selector = %p, vaddr = %p\n", @@ -161,31 +179,48 @@ Addr VG_(do_useseg) ( UInt seg_selector, Addr virtual_addr ) seg_selector &= 0x0000FFFF; - /* Sanity check the segment selector. Ensure that TI=1 (LDT) and - that RPL=11b (least privilege). These form the bottom 3 bits - of the selector. */ - vg_assert((seg_selector & 7) == 7); + /* Sanity check the segment selector. Ensure that RPL=11b (least + privilege). This forms the bottom 2 bits of the selector. */ + vg_assert((seg_selector & 3) == 3); - /* convert it onto a table index */ + /* Extract the table number */ + table = (seg_selector & 4) >> 2; + + /* Convert the segment selector onto a table index */ seg_selector >>= 3; - vg_assert(seg_selector >= 0 && seg_selector < 8192); - /* Come up with a suitable LDT entry. We look at the thread's LDT, - which is pointed to by a VG_(baseBlock) entry. If null, we will - use an implied zero-entry -- although this usually implies the - program is in deep trouble, since it is using LDT entries which - it probably hasn't set up. */ - the_ldt = (VgLdtEntry*)VG_(baseBlock)[VGOFF_(ldt)]; - if (the_ldt == NULL) { - base = 0; - limit = 0; - VG_(message)( - Vg_UserMsg, - "Warning: segment-override prefix encountered, but thread has no LDT" - ); + if (table == 0) { + VgLdtEntry* the_tls; + + vg_assert(seg_selector >= VKI_GDT_TLS_MIN && seg_selector < VKI_GDT_TLS_MAX); + + /* Come up with a suitable GDT entry. We look at the thread's TLS + array, which is pointed to by a VG_(baseBlock) entry. */ + the_tls = (VgLdtEntry*)VG_(baseBlock)[VGOFF_(tls)]; + base = (Addr)wine_ldt_get_base ( &the_tls[seg_selector-VKI_GDT_TLS_MIN] ); + limit = (UInt)wine_ldt_get_limit ( &the_tls[seg_selector-VKI_GDT_TLS_MIN] ); } else { - base = (Addr)wine_ldt_get_base ( &the_ldt[seg_selector] ); - limit = (UInt)wine_ldt_get_limit ( &the_ldt[seg_selector] ); + VgLdtEntry* the_ldt; + + vg_assert(seg_selector >= 0 && seg_selector < 8192); + + /* Come up with a suitable LDT entry. We look at the thread's LDT, + which is pointed to by a VG_(baseBlock) entry. If null, we will + use an implied zero-entry -- although this usually implies the + program is in deep trouble, since it is using LDT entries which + it probably hasn't set up. */ + the_ldt = (VgLdtEntry*)VG_(baseBlock)[VGOFF_(ldt)]; + if (the_ldt == NULL) { + base = 0; + limit = 0; + VG_(message)( + Vg_UserMsg, + "Warning: segment-override prefix encountered, but thread has no LDT" + ); + } else { + base = (Addr)wine_ldt_get_base ( &the_ldt[seg_selector] ); + limit = (UInt)wine_ldt_get_limit ( &the_ldt[seg_selector] ); + } } /* Note, this check is just slightly too slack. Really it should @@ -199,6 +234,10 @@ Addr VG_(do_useseg) ( UInt seg_selector, Addr virtual_addr ) ); } + if (0) + VG_(printf)("do_useseg: base = %p, addr = %p\n", + base, base + virtual_addr); + return base + virtual_addr; } @@ -367,6 +406,63 @@ Int VG_(sys_modify_ldt) ( ThreadId tid, } +Int VG_(sys_set_thread_area) ( ThreadId tid, + struct vki_modify_ldt_ldt_s* info ) +{ + Int idx = info->entry_number; + + if (idx == -1) { + for (idx = 0; idx < VKI_GDT_TLS_ENTRIES; idx++) { + VgLdtEntry* tls = VG_(threads)[tid].tls + idx; + + if (tls->LdtEnt.Words.word1 == 0 && tls->LdtEnt.Words.word2 == 0) + break; + } + + if (idx == VKI_GDT_TLS_ENTRIES) + return -VKI_ESRCH; + } else if (idx < VKI_GDT_TLS_MIN || idx > VKI_GDT_TLS_MAX) { + return -VKI_EINVAL; + } else { + idx = info->entry_number - VKI_GDT_TLS_MIN; + } + + translate_to_hw_format(info, VG_(threads)[tid].tls + idx, 0); + + info->entry_number = idx + VKI_GDT_TLS_MIN; + + return 0; +} + + +Int VG_(sys_get_thread_area) ( ThreadId tid, + struct vki_modify_ldt_ldt_s* info ) +{ + Int idx = info->entry_number; + VgLdtEntry* tls; + + if (idx < VKI_GDT_TLS_MIN || idx > VKI_GDT_TLS_MAX) + return -VKI_EINVAL; + + tls = VG_(threads)[tid].tls + idx - VKI_GDT_TLS_MIN; + + info->base_addr = ( tls->LdtEnt.Bits.BaseHi << 24 ) | + ( tls->LdtEnt.Bits.BaseMid << 16 ) | + tls->LdtEnt.Bits.BaseLow; + info->limit = ( tls->LdtEnt.Bits.LimitHi << 16 ) | + tls->LdtEnt.Bits.LimitLow; + info->seg_32bit = tls->LdtEnt.Bits.Default_Big; + info->contents = ( tls->LdtEnt.Bits.Type >> 2 ) & 0x3; + info->read_exec_only = ( tls->LdtEnt.Bits.Type & 0x1 ) ^ 0x1; + info->limit_in_pages = tls->LdtEnt.Bits.Granularity; + info->seg_not_present = tls->LdtEnt.Bits.Pres ^ 0x1; + info->useable = tls->LdtEnt.Bits.Sys; + info->reserved = 0; + + return 0; +} + + /*--------------------------------------------------------------------*/ /*--- end vg_ldt.c ---*/ /*--------------------------------------------------------------------*/ diff --git a/coregrind/vg_libpthread.c b/coregrind/vg_libpthread.c index 11e7ecb18..02d1a61ca 100644 --- a/coregrind/vg_libpthread.c +++ b/coregrind/vg_libpthread.c @@ -60,6 +60,10 @@ #include #undef __USE_UNIX98 +#define __USE_GNU +#include +#undef __USE_GNU + #include #include #include @@ -106,6 +110,9 @@ int is_kerror ( int res ) extern __locale_t __uselocale ( __locale_t ); #endif +static +void init_thread_specific_state ( void ); + static void init_libc_tsd_keys ( void ); @@ -523,6 +530,38 @@ int pthread_getconcurrency(void) and for clearing up afterwards. ------------------------------------------------ */ +typedef void *(*__attribute__ ((regparm (3), stdcall)) allocate_tls_t) (void *result); +typedef void (*__attribute__ ((regparm (3), stdcall)) deallocate_tls_t) (void *tcb, int dealloc_tcb); + +static allocate_tls_t allocate_tls = NULL; +static deallocate_tls_t deallocate_tls = NULL; + +static +int get_gs() +{ + int gs; + + asm volatile ("movw %%gs, %w0" : "=q" (gs)); + + return gs & 0xffff; +} + +static +void set_gs( int gs ) +{ + asm volatile ("movw %w0, %%gs" :: "q" (gs)); +} + +static +void *get_tcb() +{ + void *tcb; + + asm volatile ("movl %%gs:0, %0" : "=r" (tcb)); + + return tcb; +} + /* All exiting threads eventually pass through here, bearing the return value, or PTHREAD_CANCELED, in ret_val. */ static @@ -569,7 +608,13 @@ void thread_exit_wrapper ( void* ret_val ) my_assert(specifics_ptr != (void**)1); /* 1 means invalid thread */ if (specifics_ptr != NULL) my_free(specifics_ptr); - + + /* Free up any TLS data */ + if ((get_gs() & 7) == 3 && pthread_self() > 1) { + my_assert(deallocate_tls != NULL); + deallocate_tls(get_tcb(), 1); + } + /* Decide on my final disposition. */ VALGRIND_MAGIC_SEQUENCE(detached, (-1) /* default */, VG_USERREQ__SET_OR_GET_DETACH, @@ -600,12 +645,25 @@ void thread_exit_wrapper ( void* ret_val ) objects which might be on the parent's stack. */ typedef struct { - int attr__detachstate; - void* (*root_fn) ( void* ); - void* arg; + int attr__detachstate; + void* tls_data; + int tls_segment; + unsigned long sysinfo; + void* (*root_fn) ( void* ); + void* arg; } NewThreadInfo; +/* Struct used to describe a TDB header, copied from glibc. */ +typedef + struct { + void *tcb; + void *dtv; + void *self; + int multiple_threads; + unsigned long sysinfo; + } + tcbhead_t; /* This is passed to the VG_USERREQ__APPLY_IN_NEW_THREAD and so must not return. Note that this runs in the new thread, not the @@ -614,15 +672,50 @@ static __attribute__((noreturn)) void thread_wrapper ( NewThreadInfo* info ) { - int attr__detachstate; - void* (*root_fn) ( void* ); - void* arg; - void* ret_val; + int attr__detachstate; + void* tls_data; + int tls_segment; + unsigned long sysinfo; + void* (*root_fn) ( void* ); + void* arg; + void* ret_val; attr__detachstate = info->attr__detachstate; + tls_data = info->tls_data; + tls_segment = info->tls_segment; + sysinfo = info->sysinfo; root_fn = info->root_fn; arg = info->arg; + if (tls_data) { + tcbhead_t *tcb = tls_data; + struct vki_modify_ldt_ldt_s ldt_info; + + /* Fill in the TCB header */ + tcb->tcb = tcb; + tcb->self = tcb; + tcb->multiple_threads = 1; + tcb->sysinfo = sysinfo; + + /* Fill in an LDT descriptor */ + ldt_info.entry_number = tls_segment; + ldt_info.base_addr = (unsigned long)tls_data; + ldt_info.limit = 0xfffff; + ldt_info.seg_32bit = 1; + ldt_info.contents = 0; + ldt_info.read_exec_only = 0; + ldt_info.limit_in_pages = 1; + ldt_info.seg_not_present = 0; + ldt_info.useable = 1; + ldt_info.reserved = 0; + + /* Install the thread area */ + VG_(do_syscall)(__NR_set_thread_area, &ldt_info); + + /* Setup the GS segment register */ + set_gs(ldt_info.entry_number * 8 + 3); + } + /* Free up the arg block that pthread_create malloced. */ my_free(info); @@ -633,6 +726,9 @@ void thread_wrapper ( NewThreadInfo* info ) if (attr__detachstate == PTHREAD_CREATE_DETACHED) pthread_detach(pthread_self()); + /* Initialise thread specific state */ + init_thread_specific_state(); + # ifdef GLIBC_2_3 /* Set this thread's locale to the global (default) locale. A hack in support of glibc-2.3. This does the biz for the all new @@ -688,6 +784,7 @@ pthread_create (pthread_t *__restrict __thredd, { int tid_child; NewThreadInfo* info; + int gs; ensure_valgrind("pthread_create"); @@ -705,6 +802,29 @@ pthread_create (pthread_t *__restrict __thredd, else info->attr__detachstate = PTHREAD_CREATE_JOINABLE; + gs = get_gs(); + + if ((gs & 7) == 3) { + tcbhead_t *tcb = get_tcb(); + + if (allocate_tls == NULL || deallocate_tls == NULL) { + allocate_tls = (allocate_tls_t)dlsym(RTLD_DEFAULT, "_dl_allocate_tls"); + deallocate_tls = (deallocate_tls_t)dlsym(RTLD_DEFAULT, "_dl_deallocate_tls"); + } + + my_assert(allocate_tls != NULL); + + info->tls_data = allocate_tls(NULL); + info->tls_segment = gs >> 3; + info->sysinfo = tcb->sysinfo; + + tcb->multiple_threads = 1; + } else { + info->tls_data = NULL; + info->tls_segment = -1; + info->sysinfo = 0; + } + info->root_fn = __start_routine; info->arg = __arg; VALGRIND_MAGIC_SEQUENCE(tid_child, VG_INVALID_THREADID /* default */, @@ -1619,17 +1739,33 @@ void __pthread_initialize ( void ) ------------------------------------------------ */ #include -static int thread_specific_errno[VG_N_THREADS]; -static int thread_specific_h_errno[VG_N_THREADS]; -static struct __res_state - thread_specific_res_state[VG_N_THREADS]; -#undef errno -extern int errno; +typedef + struct { + int *errno_ptr; + int *h_errno_ptr; + struct __res_state *res_state_ptr; + int errno_data; + int h_errno_data; + struct __res_state res_state_data; + } + ThreadSpecificState; + +static ThreadSpecificState thread_specific_state[VG_N_THREADS]; + +static +void init_thread_specific_state ( void ) +{ + int tid = pthread_self(); + + thread_specific_state[tid].errno_ptr = NULL; + thread_specific_state[tid].h_errno_ptr = NULL; + thread_specific_state[tid].res_state_ptr = NULL; +} + int* __errno_location ( void ) { int tid; - int *ret; ensure_valgrind("__errno_location"); VALGRIND_MAGIC_SEQUENCE(tid, 1 /* default */, @@ -1638,16 +1774,17 @@ int* __errno_location ( void ) /* 'cos I'm paranoid ... */ if (tid < 1 || tid >= VG_N_THREADS) barf("__errno_location: invalid ThreadId"); - if (tid == 1) - ret = &errno; - else - ret = &thread_specific_errno[tid]; - - return ret; + if (thread_specific_state[tid].errno_ptr == NULL) { + if ((get_gs() & 7) == 3) + thread_specific_state[tid].errno_ptr = dlsym(RTLD_DEFAULT, "errno"); + else if (tid == 1) + thread_specific_state[tid].errno_ptr = dlvsym(RTLD_DEFAULT, "errno", "GLIBC_2.0"); + else + thread_specific_state[tid].errno_ptr = &thread_specific_state[tid].errno_data; + } + return thread_specific_state[tid].errno_ptr; } -#undef h_errno -extern int h_errno; int* __h_errno_location ( void ) { int tid; @@ -1658,14 +1795,17 @@ int* __h_errno_location ( void ) /* 'cos I'm paranoid ... */ if (tid < 1 || tid >= VG_N_THREADS) barf("__h_errno_location: invalid ThreadId"); - if (tid == 1) - return &h_errno; - return & thread_specific_h_errno[tid]; + if (thread_specific_state[tid].h_errno_ptr == NULL) { + if ((get_gs() & 7) == 3) + thread_specific_state[tid].h_errno_ptr = dlsym(RTLD_DEFAULT, "h_errno"); + else if (tid == 1) + thread_specific_state[tid].h_errno_ptr = dlvsym(RTLD_DEFAULT, "h_errno", "GLIBC_2.0"); + else + thread_specific_state[tid].h_errno_ptr = &thread_specific_state[tid].h_errno_data; + } + return thread_specific_state[tid].h_errno_ptr; } - -#undef _res -extern struct __res_state _res; struct __res_state* __res_state ( void ) { int tid; @@ -1676,9 +1816,18 @@ struct __res_state* __res_state ( void ) /* 'cos I'm paranoid ... */ if (tid < 1 || tid >= VG_N_THREADS) barf("__res_state: invalid ThreadId"); - if (tid == 1) - return & _res; - return & thread_specific_res_state[tid]; + if (thread_specific_state[tid].res_state_ptr == NULL) { + if ((get_gs() & 7) == 3) { + struct __res_state **resp = dlsym(RTLD_DEFAULT, "__resp"); + + thread_specific_state[tid].res_state_ptr = *resp; + } else if (tid == 1) { + thread_specific_state[tid].res_state_ptr = dlvsym(RTLD_DEFAULT, "_res", "GLIBC_2.0"); + } else { + thread_specific_state[tid].res_state_ptr = &thread_specific_state[tid].res_state_data; + } + } + return thread_specific_state[tid].res_state_ptr; } @@ -2331,7 +2480,6 @@ int sem_init(sem_t *sem, int pshared, unsigned int value) return 0; } - int sem_wait ( sem_t* sem ) { int res; @@ -2918,15 +3066,20 @@ weak_alias(pthread_rwlock_tryrdlock, __pthread_rwlock_tryrdlock) weak_alias(pthread_rwlock_trywrlock, __pthread_rwlock_trywrlock) -/* I've no idea what these are, but they get called quite a lot. - Anybody know? */ - #ifndef __UCLIBC__ +/* These are called as part of stdio to lock the FILE structure for MT + programs. Unfortunately, the lock is not always a pthreads lock - + the NPTL version uses a lighter-weight lock which uses futex + directly (and uses a structure which is smaller than + pthread_mutex). So basically, this is completely broken on recent + glibcs. */ + #undef _IO_flockfile void _IO_flockfile ( _IO_FILE * file ) { pthread_mutex_lock(file->_lock); } +strong_alias(_IO_flockfile, __flockfile); weak_alias(_IO_flockfile, flockfile); #undef _IO_funlockfile @@ -2934,6 +3087,7 @@ void _IO_funlockfile ( _IO_FILE * file ) { pthread_mutex_unlock(file->_lock); } +strong_alias(_IO_funlockfile, __funlockfile); weak_alias(_IO_funlockfile, funlockfile); #endif diff --git a/coregrind/vg_main.c b/coregrind/vg_main.c index b1a74a4b0..5e87e6296 100644 --- a/coregrind/vg_main.c +++ b/coregrind/vg_main.c @@ -58,6 +58,7 @@ Int VGOFF_(m_eflags) = INVALID_OFFSET; Int VGOFF_(m_dflag) = INVALID_OFFSET; Int VGOFF_(m_ssestate) = INVALID_OFFSET; Int VGOFF_(ldt) = INVALID_OFFSET; +Int VGOFF_(tls) = INVALID_OFFSET; Int VGOFF_(m_cs) = INVALID_OFFSET; Int VGOFF_(m_ss) = INVALID_OFFSET; Int VGOFF_(m_ds) = INVALID_OFFSET; @@ -327,8 +328,9 @@ static void vg_init_baseBlock ( void ) == 0 ); - /* This thread's LDT pointer, and segment registers. */ + /* This thread's LDT and TLS pointers, and segment registers. */ VGOFF_(ldt) = alloc_BaB(1); + VGOFF_(tls) = alloc_BaB(1); VGOFF_(m_cs) = alloc_BaB(1); VGOFF_(m_ss) = alloc_BaB(1); VGOFF_(m_ds) = alloc_BaB(1); @@ -448,6 +450,9 @@ static void vg_init_baseBlock ( void ) /* Pretend the root thread has a completely empty LDT to start with. */ VG_(baseBlock)[VGOFF_(ldt)] = (UInt)NULL; + /* Pretend the root thread has no TLS array for now. */ + VG_(baseBlock)[VGOFF_(tls)] = (UInt)NULL; + /* Initialise shadow regs */ if (VG_(needs).shadow_regs) { VG_(baseBlock)[VGOFF_(sh_esp)] = diff --git a/coregrind/vg_proxylwp.c b/coregrind/vg_proxylwp.c index cd76a1235..55cd12a41 100644 --- a/coregrind/vg_proxylwp.c +++ b/coregrind/vg_proxylwp.c @@ -1373,6 +1373,15 @@ void VG_(proxy_sanity)(void) vg_assert(sane); } +/* Get the PID/TID of the ProxyLWP. */ +Int VG_(proxy_id)(ThreadId tid) +{ + ThreadState *tst = VG_(get_ThreadState)(tid); + ProxyLWP *proxy = tst->proxy; + + return proxy->lwp; +} + /*--------------------------------------------------------------------*/ /*--- Proxy LWP machinery. vg_proxylwp.c ---*/ /*--------------------------------------------------------------------*/ diff --git a/coregrind/vg_scheduler.c b/coregrind/vg_scheduler.c index 3a8d7ce1e..850f66854 100644 --- a/coregrind/vg_scheduler.c +++ b/coregrind/vg_scheduler.c @@ -339,6 +339,7 @@ void VG_(load_thread_state) ( ThreadId tid ) vg_assert(vg_tid_currently_in_baseBlock == VG_INVALID_THREADID); VG_(baseBlock)[VGOFF_(ldt)] = (UInt)VG_(threads)[tid].ldt; + VG_(baseBlock)[VGOFF_(tls)] = (UInt)VG_(threads)[tid].tls; VG_(baseBlock)[VGOFF_(m_cs)] = VG_(threads)[tid].m_cs; VG_(baseBlock)[VGOFF_(m_ss)] = VG_(threads)[tid].m_ss; VG_(baseBlock)[VGOFF_(m_ds)] = VG_(threads)[tid].m_ds; @@ -420,6 +421,19 @@ void VG_(save_thread_state) ( ThreadId tid ) vg_assert((void*)VG_(threads)[tid].ldt == (void*)VG_(baseBlock)[VGOFF_(ldt)]); + /* We don't copy out the TLS entry, because it can never be changed + by the normal actions of the thread, only by the set_thread_area + syscall, in which case we will correctly be updating + VG_(threads)[tid].tls. This printf happens iff the following + assertion fails. */ + if ((void*)VG_(threads)[tid].tls != (void*)VG_(baseBlock)[VGOFF_(tls)]) + VG_(printf)("VG_(threads)[%d].tls=%p VG_(baseBlock)[VGOFF_(tls)]=%p\n", + tid, (void*)VG_(threads)[tid].tls, + (void*)VG_(baseBlock)[VGOFF_(tls)]); + + vg_assert((void*)VG_(threads)[tid].tls + == (void*)VG_(baseBlock)[VGOFF_(tls)]); + VG_(threads)[tid].m_cs = VG_(baseBlock)[VGOFF_(m_cs)]; VG_(threads)[tid].m_ss = VG_(baseBlock)[VGOFF_(m_ss)]; VG_(threads)[tid].m_ds = VG_(baseBlock)[VGOFF_(m_ds)]; @@ -469,6 +483,7 @@ void VG_(save_thread_state) ( ThreadId tid ) /* Fill it up with junk. */ VG_(baseBlock)[VGOFF_(ldt)] = junk; + VG_(baseBlock)[VGOFF_(tls)] = junk; VG_(baseBlock)[VGOFF_(m_cs)] = junk; VG_(baseBlock)[VGOFF_(m_ss)] = junk; VG_(baseBlock)[VGOFF_(m_ds)] = junk; @@ -538,6 +553,7 @@ void mostly_clear_thread_record ( ThreadId tid ) { vg_assert(tid >= 0 && tid < VG_N_THREADS); VG_(threads)[tid].ldt = NULL; + VG_(clear_TLS_for_thread)(VG_(threads)[tid].tls); VG_(threads)[tid].tid = tid; VG_(threads)[tid].status = VgTs_Empty; VG_(threads)[tid].associated_mx = NULL; @@ -596,6 +612,7 @@ void VG_(scheduler_init) ( void ) /* Copy VG_(baseBlock) state to tid_main's slot. */ vg_tid_currently_in_baseBlock = tid_main; vg_tid_last_in_baseBlock = tid_main; + VG_(baseBlock)[VGOFF_(tls)] = (UInt)VG_(threads)[tid_main].tls; VG_(save_thread_state) ( tid_main ); VG_(threads)[tid_main].stack_highest_word @@ -1372,6 +1389,9 @@ void cleanup_after_thread_exited ( ThreadId tid, Bool forcekill ) VG_(deallocate_LDT_for_thread)( VG_(threads)[tid].ldt ); VG_(threads)[tid].ldt = NULL; + /* Clear its TLS array. */ + VG_(clear_TLS_for_thread)( VG_(threads)[tid].tls ); + /* Not interested in the timeout anymore */ VG_(threads)[tid].awaken_at = 0xFFFFFFFF; @@ -1858,6 +1878,10 @@ void do__apply_in_new_thread ( ThreadId parent_tid, VG_(baseBlock)[VGOFF_(ldt)] = (UInt)VG_(threads)[tid].ldt; } + /* Initialise the thread's TLS array */ + VG_(clear_TLS_for_thread)( VG_(threads)[tid].tls ); + VG_(baseBlock)[VGOFF_(tls)] = (UInt)VG_(threads)[tid].tls; + VG_(save_thread_state)(tid); vg_tid_last_in_baseBlock = tid; @@ -2075,8 +2099,12 @@ void do_pthread_mutex_lock( ThreadId tid, } if (mutex->__m_count > 0) { - - vg_assert(VG_(is_valid_tid)((ThreadId)mutex->__m_owner)); + if (!VG_(is_valid_tid)((ThreadId)mutex->__m_owner)) { + VG_(record_pthread_error)( tid, + "pthread_mutex_lock/trylock: mutex has invalid owner"); + SET_PTHREQ_RETVAL(tid, VKI_EINVAL); + return; + } /* Someone has it already. */ if ((ThreadId)mutex->__m_owner == tid) { diff --git a/coregrind/vg_syscalls.c b/coregrind/vg_syscalls.c index ee5917329..b36245a92 100644 --- a/coregrind/vg_syscalls.c +++ b/coregrind/vg_syscalls.c @@ -1046,15 +1046,6 @@ PRE(exit) VG_(core_panic)("syscall exit() not caught by the scheduler?!"); } -PRE(clone) -{ - VG_(unimplemented) - ("clone(): not supported by Valgrind.\n " - "We do now support programs linked against\n " - "libpthread.so, though. Re-run with -v and ensure that\n " - "you are picking up Valgrind's implementation of libpthread.so."); -} - PRE(ptrace) { /* long ptrace (enum __ptrace_request request, pid_t pid, @@ -1184,6 +1175,33 @@ PRE(modify_ldt) } } +PRE(set_thread_area) +{ + MAYBE_PRINTF("set_thread_area ( %p )\n", arg1); + + SYSCALL_TRACK( pre_mem_read, tid, + "set_thread_area(ptr)", arg1, + sizeof(struct vki_modify_ldt_ldt_s) ); + + /* "do" the syscall ourselves; the kernel never sees it */ + res = VG_(sys_set_thread_area)( tid, (void *)arg1 ); +} + +PRE(get_thread_area) +{ + MAYBE_PRINTF("get_thread_area ( %p )\n", arg1); + SYSCALL_TRACK( pre_mem_write, tid, + "get_thread_area(ptr)", arg1, + sizeof(struct vki_modify_ldt_ldt_s) ); + + /* "do" the syscall ourselves; the kernel never sees it */ + res = VG_(sys_get_thread_area)( tid, (void *)arg1 ); + + if (!VG_(is_kerror)(res)) { + VG_TRACK( post_mem_write, arg1, sizeof(struct vki_modify_ldt_ldt_s) ); + } +} + PRE(setresgid) { /* int setresgid(gid_t rgid, gid_t egid, gid_t sgid); */ @@ -2141,6 +2159,24 @@ POST(fork) } } +PRE(clone) +{ + MAYBE_PRINTF("clone ( %d, %p, %p, %p, %p )\n",arg1,arg2,arg3,arg4,arg5); + + if (arg2 == 0 && + arg1 == (VKI_CLONE_CHILD_CLEARTID|VKI_CLONE_CHILD_SETTID|VKI_SIGCHLD)) { + before_fork(tid, tst); + res = VG_(do_syscall)(SYSNO, arg1, arg2, arg3, arg4, arg5); + after_fork(tid, tst); + } else { + VG_(unimplemented) + ("clone(): not supported by Valgrind.\n " + "We do now support programs linked against\n " + "libpthread.so, though. Re-run with -v and ensure that\n " + "you are picking up Valgrind's implementation of libpthread.so."); + } +} + PRE(fsync) { /* int fsync(int fd); */ @@ -4727,6 +4763,27 @@ PRE(utimes) sizeof(struct timeval) ); } +PRE(futex) +{ + /* int futex(void *futex, int op, int val, const struct timespec *timeout); */ + MAYBE_PRINTF("futex ( %p, %d, %d, %p, %p )\n", arg1,arg2,arg3,arg4,arg5); + SYSCALL_TRACK( pre_mem_read, tid, "futex(futex)", arg1, sizeof(int) ); + if (arg2 == VKI_FUTEX_WAIT && arg4 != (UInt)NULL) + SYSCALL_TRACK( pre_mem_read, tid, "futex(timeout)", arg4, + sizeof(struct timespec) ); + if (arg2 == VKI_FUTEX_REQUEUE) + SYSCALL_TRACK( pre_mem_read, tid, "futex(futex2)", arg4, sizeof(int) ); +} + +POST(futex) +{ + if (!VG_(is_kerror)(res)) { + VG_TRACK( post_mem_write, arg1, sizeof(int) ); + if (arg2 == VKI_FUTEX_FD && VG_(clo_track_fds)) + record_fd_open(tid, res, NULL); + } +} + #define SIGNAL_SIMULATION 1 PRE(pause) @@ -4927,6 +4984,8 @@ static const struct sys_info special_sys[] = { SYSB_(clone, False), SYSB_(modify_ldt, False), + SYSB_(set_thread_area, False), + SYSB_(get_thread_area, False), SYSB_(execve, False), SYSB_(brk, False), @@ -5126,6 +5185,7 @@ static const struct sys_info sys_info[] = { SYSBA(adjtimex, False), SYSBA(mmap2, False), SYSBA(clock_gettime, False), + SYSBA(futex, True), /* new signal handling makes these normal blocking syscalls */ SYSB_(pause, True), diff --git a/glibc-2.3.supp b/glibc-2.3.supp index c0ba8bf93..22827b43c 100644 --- a/glibc-2.3.supp +++ b/glibc-2.3.supp @@ -129,6 +129,11 @@ fun:fixup fun:_dl_runtime_resolve } +{ + _dl_fini + Helgrind:Eraser + fun:_dl_fini +} #-------- Threading bugs? # glibc 'knows' that destroying a locked mutex will unlock it @@ -191,3 +196,21 @@ Memcheck:Cond obj:/lib/ld-2.3.2.so } + +##----------------------------------------------------------------------## +## SuSE 9 after FV changes (post 2.1.0) + +{ + strlen/_dl_init_paths/dl_main/_dl_sysdep_start(Cond) + Memcheck:Cond + fun:strlen + fun:_dl_init_paths + fun:dl_main + fun:_dl_sysdep_start +} + +{ + Ugly strchr error in /lib/ld-2.3.2.so + Memcheck:Cond + obj:/lib/ld-2.3.2.so +} diff --git a/include/vg_kerneliface.h b/include/vg_kerneliface.h index 1767e32c3..2df0a93d2 100644 --- a/include/vg_kerneliface.h +++ b/include/vg_kerneliface.h @@ -631,12 +631,16 @@ typedef struct vki_modify_ldt_ldt_s { unsigned int limit_in_pages:1; unsigned int seg_not_present:1; unsigned int useable:1; + unsigned int reserved:25; } vki_modify_ldt_t; #define VKI_MODIFY_LDT_CONTENTS_DATA 0 #define VKI_MODIFY_LDT_CONTENTS_STACK 1 #define VKI_MODIFY_LDT_CONTENTS_CODE 2 +#define VKI_GDT_TLS_ENTRIES 3 +#define VKI_GDT_TLS_MIN 6 +#define VKI_GDT_TLS_MAX (VKI_GDT_TLS_MIN + VKI_GDT_TLS_ENTRIES) /* Flags for clone() */ /* linux/sched.h */ @@ -726,6 +730,15 @@ struct statfs64 { unsigned int f_spare[5]; }; +/* + * linux/futex.h + */ + +#define VKI_FUTEX_WAIT 0 +#define VKI_FUTEX_WAKE 1 +#define VKI_FUTEX_FD 2 +#define VKI_FUTEX_REQUEUE 3 + #endif /* __VG_KERNELIFACE_H */ diff --git a/none/tests/Makefile.am b/none/tests/Makefile.am index c6e23d08c..1e503c131 100644 --- a/none/tests/Makefile.am +++ b/none/tests/Makefile.am @@ -42,6 +42,7 @@ EXTRA_DIST = $(noinst_SCRIPTS) \ sha1_test.stderr.exp sha1_test.vgtest \ shortpush.stderr.exp shortpush.vgtest \ shorts.stderr.exp shorts.vgtest \ + tls.stderr.exp tls.stdout.exp tls.vgtest \ smc1.stderr.exp smc1.stdout.exp smc1.vgtest \ syscall-restart1.vgtest syscall-restart1.stdout.exp syscall-restart1.stderr.exp \ syscall-restart2.vgtest syscall-restart2.stdout.exp syscall-restart2.stderr.exp \ @@ -53,7 +54,7 @@ check_PROGRAMS = \ fucomip insn_mmx insn_sse insn_sse2 \ munmap_exe map_unmap mremap rcl_assert \ rcrl readline1 resolv seg_override sha1_test shortpush shorts smc1 \ - pth_blockedsig \ + tls.so tls2.so tls pth_blockedsig \ syscall-restart1 syscall-restart2 \ coolo_sigaction gxx304 yield @@ -95,9 +96,21 @@ shortpush_SOURCES = shortpush.c shorts_SOURCES = shorts.c syscall_restart1_SOURCES = syscall-restart1.c syscall_restart2_SOURCES = syscall-restart2.c +tls_SOURCES = tls.c tls2.c +tls_DEPENDENCIES = tls.so +tls_LDFLAGS = -Wl,-rpath,$(srcdir) +tls_LDADD = tls.so -lpthread +tls_so_SOURCES = tls_so.c +tls_so_LDADD = tls2.so +tls_so_DEPENDENCIES = tls2.so +tls_so_LDFLAGS = -Wl,-rpath,$(srcdir) -shared +tls2_so_SOURCES = tls2_so.c +tls2_so_LDFLAGS = -shared yield_SOURCES = yield.c yield_LDADD = -lpthread +tls_so.o tls2_so.o: CFLAGS += -fpic + # pthread C ones pth_blockedsig_SOURCES = pth_blockedsig.c pth_blockedsig_LDADD = -lpthread diff --git a/none/tests/seg_override.c b/none/tests/seg_override.c index 805bfda37..0f8cb2d8c 100644 --- a/none/tests/seg_override.c +++ b/none/tests/seg_override.c @@ -110,8 +110,8 @@ void ldt_seg_write ( int ldt_entno, unsigned offset, unsigned val ) { asm volatile("movl %2, %%eax\n\t" "movl %1, %%edx\n\t" - "movl %0, %%gs\n\t" - "movl %%eax, %%gs:(%%edx)\t" + "movl %0, %%fs\n\t" + "movl %%eax, %%fs:(%%edx)\t" : : "r" (7 /* LDT(TI), least privilege */ + (ldt_entno << 3)), "r" (offset), "r" (val) diff --git a/none/tests/tls.c b/none/tests/tls.c new file mode 100644 index 000000000..78993c2d5 --- /dev/null +++ b/none/tests/tls.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include + +#define COUNT 10 + +static int race; +static __thread int local; +__thread int global; +extern __thread int static_extern; +extern __thread int so_extern; + +/* deliberate failure */ +static int *test_race(void) +{ + return ∽̱ +} + +static int *test_local(void) +{ + return &local; +} + +static int *test_global(void) +{ + return &global; +} + +static int *test_static_extern(void) +{ + return &static_extern; +} + +static int *test_so_extern(void) +{ + return &so_extern; +} + +static const struct timespec awhile = { 0, 100000000 }; + +typedef int *(*func_t)(void); +struct testcase { + const char *name; + func_t func; +}; + +static void *tls_ptr(void *p) +{ + struct testcase *test = (struct testcase *)p; + int *ip = (*test->func)(); + int here = 0; + int i; + + for(i = 0; i < COUNT; i++) { + int a = (*ip)++; + int b = here++; + if (a != b) + printf("tls_ptr: case \"%s\" has mismatch: *ip=%d here=%d\n", + test->name, a, b); + nanosleep(&awhile, 0); + } + + return 0; +} + +int *test_so_extern(void); +int *test_so_local(void); +int *test_so_global(void); + +static const struct testcase tests[] = { +#define T(t) { #t, test_##t } + T(race), + T(local), + T(global), + T(static_extern), + T(so_extern), + T(so_local), + T(so_global), +#undef T +}; + +#define NTESTS (sizeof(tests)/sizeof(*tests)) + +int main() +{ + pthread_t threads[NTESTS*2]; + int curthread = 0; + static + int i; + + for(i = 0; i < NTESTS; i++) { + pthread_create(&threads[curthread++], NULL, tls_ptr, (void *)&tests[i]); + pthread_create(&threads[curthread++], NULL, tls_ptr, (void *)&tests[i]); + } + + for(i = 0; i < curthread; i++) + pthread_join(threads[i], NULL); + + return 0; +} diff --git a/none/tests/tls.stderr.exp b/none/tests/tls.stderr.exp new file mode 100644 index 000000000..139597f9c --- /dev/null +++ b/none/tests/tls.stderr.exp @@ -0,0 +1,2 @@ + + diff --git a/none/tests/tls.stdout.exp b/none/tests/tls.stdout.exp new file mode 100644 index 000000000..dbb4134d9 --- /dev/null +++ b/none/tests/tls.stdout.exp @@ -0,0 +1,19 @@ +tls_ptr: case "race" has mismatch: *ip=1 here=0 +tls_ptr: case "race" has mismatch: *ip=2 here=1 +tls_ptr: case "race" has mismatch: *ip=3 here=1 +tls_ptr: case "race" has mismatch: *ip=4 here=2 +tls_ptr: case "race" has mismatch: *ip=5 here=2 +tls_ptr: case "race" has mismatch: *ip=6 here=3 +tls_ptr: case "race" has mismatch: *ip=7 here=3 +tls_ptr: case "race" has mismatch: *ip=8 here=4 +tls_ptr: case "race" has mismatch: *ip=9 here=4 +tls_ptr: case "race" has mismatch: *ip=10 here=5 +tls_ptr: case "race" has mismatch: *ip=11 here=5 +tls_ptr: case "race" has mismatch: *ip=12 here=6 +tls_ptr: case "race" has mismatch: *ip=13 here=6 +tls_ptr: case "race" has mismatch: *ip=14 here=7 +tls_ptr: case "race" has mismatch: *ip=15 here=7 +tls_ptr: case "race" has mismatch: *ip=16 here=8 +tls_ptr: case "race" has mismatch: *ip=17 here=8 +tls_ptr: case "race" has mismatch: *ip=18 here=9 +tls_ptr: case "race" has mismatch: *ip=19 here=9 diff --git a/none/tests/tls.vgtest b/none/tests/tls.vgtest new file mode 100644 index 000000000..c27bf7fe4 --- /dev/null +++ b/none/tests/tls.vgtest @@ -0,0 +1 @@ +prog: tls diff --git a/none/tests/tls2.c b/none/tests/tls2.c new file mode 100644 index 000000000..68838828e --- /dev/null +++ b/none/tests/tls2.c @@ -0,0 +1 @@ +__thread int static_extern; diff --git a/none/tests/tls2_so.c b/none/tests/tls2_so.c new file mode 100644 index 000000000..d0a9a2655 --- /dev/null +++ b/none/tests/tls2_so.c @@ -0,0 +1 @@ +__thread int so_extern; diff --git a/none/tests/tls_so.c b/none/tests/tls_so.c new file mode 100644 index 000000000..492507eb2 --- /dev/null +++ b/none/tests/tls_so.c @@ -0,0 +1,20 @@ +#include + +extern __thread int so_extern; +static __thread int so_local; +extern __thread int global; + +int *test_so_extern(void) +{ + return &so_extern; +} + +int *test_so_local(void) +{ + return &so_local; +} + +int *test_so_global(void) +{ + return &global; +}