From: Philippe Waroquiers Date: Fri, 15 Aug 2014 10:27:52 +0000 (+0000) Subject: fix 338160: Implement QGetTlsAddr query so that GDB+V gdbsrv can print __thread varia... X-Git-Tag: svn/VALGRIND_3_10_0~183 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=bc353dfe974c5c6a8400bd8a1f95b42e56c36038;p=thirdparty%2Fvalgrind.git fix 338160: Implement QGetTlsAddr query so that GDB+V gdbsrv can print __thread variables. To implement QGetTlsAddr, gdbsrv has to know how to get the glibc dtv address and the module id from the link_map. These 2 things are dependent on the internals of glibc. The dependency is mostly isolated in a few lines of arch dependent code or in an external utility that used a hack + -ldl lib to find the offset of the modid in the link_map structure. Tested on x86/amd64/ppc64/s390x. Somewhat tested on ppc32 and arm64. Untested/a few #ifdef-ed lines not compiled on arm/mips32/mips64 and darwin. For more background info about thread local storage handling, see 'ELF Handling For Thread-Local Storage' http://www.akkadia.org/drepper/tls.pdf Changes: * auxprogs/getoff.c new auxilliary program to get platform specific offsets (currently only the offset for the module id in struct link_map). * configure.ac : check for dlinfo(RTLD_DI_TLS_MODID) needed for getoff.c * new gdbserver_tests/hgtls, testing various types of __thread variables * various m_gdbserver files: - implement decoding of the QGetTlsAddr query - for each platform: platform specific code to get the dtv - call to external program getoff- the first time an __thread variable is printed. git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14283 --- diff --git a/NEWS b/NEWS index a6f4b6aaa5..60692f821b 100644 --- a/NEWS +++ b/NEWS @@ -55,6 +55,8 @@ Release 3.10.0 (?? ?????? 201?) * New and modified GDB server monitor features: + - thread local variables/storage (__thread) can now be displayed. + - The GDB server monitor command 'v.info location
' outputs information about an address. The information produced depends on the tool and on the options given to valgrind. @@ -204,6 +206,7 @@ where XXXXXX is the bug number as listed below. 337528 leak check heuristic for block prefixed by length as 64bit number 338024 inlined functions are not shown if DW_AT_ranges is used 338115 DRD: computed conflict set differs from actual after fork +338160 implement display of thread local storage in gdbsrv n-i-bz Fix KVM_CREATE_IRQCHIP ioctl handling n-i-bz s390x: Fix memory corruption for multithreaded applications n-i-bz vex arm->IR: allow PC as basereg in some LDRD cases diff --git a/auxprogs/Makefile.am b/auxprogs/Makefile.am index 7cb0308efc..2195338bae 100644 --- a/auxprogs/Makefile.am +++ b/auxprogs/Makefile.am @@ -51,3 +51,44 @@ endif if VGCONF_PLATFORMS_INCLUDE_X86_DARWIN valgrind_di_server_LDFLAGS += -Wl,-read_only_relocs -Wl,suppress endif + +#---------------------------------------------------------------------------- +# getoff- +# Used to retrieve user space various offsets, using user space libraries. +#---------------------------------------------------------------------------- +noinst_PROGRAMS = getoff-@VGCONF_ARCH_PRI@-@VGCONF_OS@ +if VGCONF_HAVE_PLATFORM_SEC +noinst_PROGRAMS += getoff-@VGCONF_ARCH_SEC@-@VGCONF_OS@ +endif + +getoff_@VGCONF_ARCH_PRI@_@VGCONF_OS@_SOURCES = getoff.c +getoff_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CPPFLAGS = $(AM_CPPFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) +getoff_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CFLAGS = $(AM_CFLAGS_@VGCONF_PLATFORM_PRI_CAPS@) +getoff_@VGCONF_ARCH_PRI@_@VGCONF_OS@_CCASFLAGS = $(AM_CCASFLAGS_PRI) +getoff_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDFLAGS = $(AM_CFLAGS_PRI) +if HAVE_DLINFO_RTLD_DI_TLS_MODID +getoff_@VGCONF_ARCH_PRI@_@VGCONF_OS@_LDADD = $(LDADD) -ldl +endif + +if VGCONF_HAVE_PLATFORM_SEC +getoff_@VGCONF_ARCH_SEC@_@VGCONF_OS@_SOURCES = getoff.c +getoff_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CPPFLAGS = $(AM_CPPFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) +getoff_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CFLAGS = $(AM_CFLAGS_@VGCONF_PLATFORM_SEC_CAPS@) +getoff_@VGCONF_ARCH_SEC@_@VGCONF_OS@_CCASFLAGS = $(AM_CCASFLAGS_SEC) +getoff_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDFLAGS = $(AM_CFLAGS_SEC) +if HAVE_DLINFO_RTLD_DI_TLS_MODID +getoff_@VGCONF_ARCH_SEC@_@VGCONF_OS@_LDADD = $(LDADD) -ldl +endif +endif + +#---------------------------------------------------------------------------- +# General stuff +#---------------------------------------------------------------------------- + +all-local: inplace-noinst_PROGRAMS inplace-noinst_DSYMS + +clean-local: clean-noinst_DSYMS + +install-exec-local: install-noinst_PROGRAMS install-noinst_DSYMS + +uninstall-local: uninstall-noinst_PROGRAMS uninstall-noinst_DSYMS diff --git a/auxprogs/getoff.c b/auxprogs/getoff.c new file mode 100644 index 0000000000..36f57517f5 --- /dev/null +++ b/auxprogs/getoff.c @@ -0,0 +1,139 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include + +#include +#include +#include +#include +#include + +#include +#include + +/* true if arg matches the provided option */ +static +int is_opt(char* arg, const char *option) +{ + int option_len = strlen(option); + if (option[option_len-1] == '=') + return (0 == strncmp(option, arg, option_len)); + else + return (0 == strcmp(option, arg)); +} + +static int verbose = 0; + +static +void usage (char* progname) +{ + fprintf(stderr, +"Usage: %s [--help] [-h] [-v] [-o ]\n" +"Outputs various user space offsets\n" +"By default, outputs on stdout.\n" +"Use -o to output to \n" +"-v : be more verbose\n", +progname); + +} +/* Currently, only computes and output lm_modid_offset in struct link_map + of the dynamic linker. In theory, we should also compute the offset needed + to get the dtv from the thread register/pointer/... + Currently, the various valgrind-low-xxxxxx.c files are hardcoding this + offset as it is deemed (?) stable, and there is no clear way how to + compute this dtv offset. +*/ +int main (int argc, char** argv) +{ + int i; + FILE *outputfile; + int nr_errors = 0; + + outputfile = stdout; + + i = 1; + while (i < argc) { + if (is_opt(argv[i], "--help") || is_opt(argv[i], "-h")) { + usage(argv[0]); + exit(0); + } else if (is_opt(argv[i], "-v")) { + verbose++; + } else if (is_opt(argv[i], "-o")) { + if (i+1 == argc) { + fprintf(stderr, + "missing output file for -o option\n" + "Use --help for more information.\n"); + exit (1); + } + i++; + outputfile = fopen(argv[i], "w"); + if (outputfile == NULL) { + fprintf(stderr, "Could not fopen %s in write mode\n", argv[i]); + perror ("fopen output file failed"); + exit (1); + } + } else { + fprintf (stderr, + "unknown or invalid argument %s\n" + "Use --help for more information.\n", + argv[i]); + exit(1); + } + i++; + } + +#ifdef HAVE_DLINFO_RTLD_DI_TLS_MODID + /* Compute offset of lm_modid in struct link_map. + This is needed to support QGetTlsAddr gdbsrv query. + Computation is done using an ugly hack, but less ugly than + hardcoding the offset depending on the glibc version and + platform. + The below works, based the assumption that RTLD_DI_TLS_MODID + just access and returns directly the field in the dummy + link_map structure we have prepared. + + If glibc debug info is installed on your system, you can + also find this offset by doing in GDB: + p &((struct link_map*)0x0)->l_tls_modid + (see also coregrind/m_gdbserver/valgrind_low.h target_get_dtv + comments). + */ + { + #define MAX_LINKMAP_WORDS 10000 + size_t dummy_link_map[MAX_LINKMAP_WORDS]; + size_t off; + size_t modid_offset; + for (off = 0; off < MAX_LINKMAP_WORDS; off++) + dummy_link_map[off] = off; + if (dlinfo ((void*)dummy_link_map, RTLD_DI_TLS_MODID, + &modid_offset) == 0) { + assert(modid_offset >= 0 && modid_offset < MAX_LINKMAP_WORDS); + fprintf(outputfile, + "lm_modid_offset 0x%x\n", modid_offset*sizeof(size_t)); + } else { + fprintf(stderr, + "Error computing lm_modid_offset.\n" + "dlinfo error %s\n", dlerror()); + nr_errors++; + } + #undef MAX_LINKMAP_WORDS + } + + if (outputfile != stdout) + if (fclose (outputfile) != 0) { + perror ("fclose output file failed\n"); + nr_errors++; + } +#else + if (verbose) + fprintf(stderr, + "cannot compute lm_modid_offset.\n" + "configure did not define HAVE_DLINFO_RTLD_DI_TLS_MODID.\n"); +#endif + + if (nr_errors == 0) + exit(0); + else + exit(1); +} diff --git a/configure.ac b/configure.ac index 949a989c6c..21b7782fed 100644 --- a/configure.ac +++ b/configure.ac @@ -1449,6 +1449,37 @@ AM_CONDITIONAL(HAVE_PTHREAD_CREATE_GLIBC_2_0, test x$ac_have_pthread_create_glibc_2_0 = xyes) +# Check for dlinfo RTLD_DI_TLS_MODID +AC_MSG_CHECKING([for dlinfo RTLD_DI_TLS_MODID]) + +safe_LIBS="$LIBS" +LIBS="-ldl" +AC_LINK_IFELSE([AC_LANG_PROGRAM([[ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#include +#include +]], [[ + size_t sizes[10000]; + size_t modid_offset; + (void) dlinfo ((void*)sizes, RTLD_DI_TLS_MODID, &modid_offset); + return 0; +]])], [ +ac_have_dlinfo_rtld_di_tls_modid=yes +AC_MSG_RESULT([yes]) +AC_DEFINE([HAVE_DLINFO_RTLD_DI_TLS_MODID], 1, + [Define to 1 if you have a dlinfo that can do RTLD_DI_TLS_MODID.]) +], [ +ac_have_dlinfo_rtld_di_tls_modid=no +AC_MSG_RESULT([no]) +]) +LIBS=$safe_LIBS + +AM_CONDITIONAL(HAVE_DLINFO_RTLD_DI_TLS_MODID, + test x$ac_have_dlinfo_rtld_di_tls_modid = xyes) + + # Check for eventfd_t, eventfd() and eventfd_read() AC_MSG_CHECKING([for eventfd()]) diff --git a/coregrind/m_gdbserver/server.c b/coregrind/m_gdbserver/server.c index 5346401037..d8d3a19f29 100644 --- a/coregrind/m_gdbserver/server.c +++ b/coregrind/m_gdbserver/server.c @@ -649,6 +649,45 @@ void handle_query (char *arg_own_buf, int *new_packet_len_p) { static struct inferior_list_entry *thread_ptr; + /* thread local storage query */ + if (strncmp ("qGetTLSAddr:", arg_own_buf, 12) == 0) { + char *from, *to; + char *end = arg_own_buf + strlen(arg_own_buf); + unsigned long gdb_id; + CORE_ADDR lm; + CORE_ADDR offset; + struct thread_info *ti; + + from = arg_own_buf + 12; + to = strchr(from, ','); + *to = 0; + gdb_id = strtoul (from, NULL, 16); + from = to + 1; + to = strchr(from, ','); + decode_address (&offset, from, to - from); + from = to + 1; + to = end; + decode_address (&lm, from, to - from); + dlog(2, "qGetTLSAddr thread %lu offset %p lm %p\n", + gdb_id, (void*)offset, (void*)lm); + + ti = gdb_id_to_thread (gdb_id); + if (ti != NULL) { + ThreadState *tst; + Addr tls_addr; + + tst = (ThreadState *) inferior_target_data (ti); + if (valgrind_get_tls_addr(tst, offset, lm, &tls_addr)) { + VG_(sprintf) (arg_own_buf, "%lx", tls_addr); + return; + } + // else we will report we do not support qGetTLSAddr + } else { + write_enn (arg_own_buf); + return; + } + } + /* qRcmd, monitor command handling. */ if (strncmp ("qRcmd,", arg_own_buf, 6) == 0) { char *p = arg_own_buf + 6; @@ -706,7 +745,7 @@ void handle_query (char *arg_own_buf, int *new_packet_len_p) return; } } - + if (strcmp ("qAttached", arg_own_buf) == 0) { /* tell gdb to always detach, never kill the process */ arg_own_buf[0] = '1'; diff --git a/coregrind/m_gdbserver/target.c b/coregrind/m_gdbserver/target.c index 2fde954b16..73dcf78b48 100644 --- a/coregrind/m_gdbserver/target.c +++ b/coregrind/m_gdbserver/target.c @@ -547,6 +547,144 @@ int valgrind_remove_watchpoint (char type, CORE_ADDR addr, int len) return valgrind_point (/* insert*/ False, type, addr, len); } +/* Returns the (platform specific) offset of lm_modid field in the link map + struct. + Stores the offset in *result and returns True if offset can be determined. + Returns False otherwise. *result is not to be used then. */ +static Bool getplatformoffset (SizeT *result) +{ + static Bool getplatformoffset_called = False; + + static Bool lm_modid_offset_found = False; + static SizeT lm_modid_offset = 1<<31; // Rubbish initial value. + // lm_modid_offset is a magic offset, retrieved using an external program. + + if (!getplatformoffset_called) { + const HChar *platform = VG_PLATFORM; + const HChar *cmdformat = "%s/%s-%s -o %s"; + const HChar *getoff = "getoff"; + HChar outfile[VG_(mkstemp_fullname_bufsz) (VG_(strlen)(getoff))]; + Int fd = VG_(mkstemp) (getoff, outfile); + HChar cmd[ VG_(strlen)(cmdformat) + + VG_(strlen)(VG_(libdir)) - 2 + + VG_(strlen)(getoff) - 2 + + VG_(strlen)(platform) - 2 + + VG_(strlen)(outfile) - 2 + + 1]; + UInt cmdlen; + struct vg_stat stat_buf; + Int ret; + + cmdlen = VG_(snprintf)(cmd, sizeof(cmd), + cmdformat, + VG_(libdir), getoff, platform, outfile); + vg_assert (cmdlen == sizeof(cmd) - 1); + ret = VG_(system) (cmd); + if (ret != 0 || VG_(debugLog_getLevel)() >= 1) + VG_(dmsg) ("command %s exit code %d\n", cmd, ret); + ret = VG_(fstat)( fd, &stat_buf ); + if (ret != 0) + VG_(dmsg) ("error VG_(fstat) %d %s\n", fd, outfile); + else { + HChar *w; + HChar *ssaveptr; + HChar *os; + HChar *str; + HChar *endptr; + + os = malloc (stat_buf.size+1); + vg_assert (os); + ret = VG_(read)(fd, os, stat_buf.size); + vg_assert(ret == stat_buf.size); + os[ret] = '\0'; + str = os; + while ((w = VG_(strtok_r)(str, " \n", &ssaveptr)) != NULL) { + if (VG_(strcmp) (w, "lm_modid_offset") == 0) { + w = VG_(strtok_r)(NULL, " \n", &ssaveptr); + lm_modid_offset = (SizeT) VG_(strtoull16) ( w, &endptr ); + if (endptr == w) + VG_(dmsg) ("%s lm_modid_offset unexpected hex value %s\n", + cmd, w); + else + lm_modid_offset_found = True; + } else { + VG_(dmsg) ("%s produced unexpected %s\n", cmd, w); + } + str = NULL; // ensure next VG_(strtok_r) continues the parsing. + } + VG_(free) (os); + } + + VG_(close)(fd); + ret = VG_(unlink)( outfile ); + if (ret != 0) + VG_(umsg) ("error: could not unlink %s\n", outfile); + getplatformoffset_called = True; + } + + *result = lm_modid_offset; + return lm_modid_offset_found; +} + +Bool valgrind_get_tls_addr (ThreadState *tst, + CORE_ADDR offset, + CORE_ADDR lm, + CORE_ADDR *tls_addr) +{ + CORE_ADDR **dtv_loc; + CORE_ADDR *dtv; + SizeT lm_modid_offset; + unsigned long int modid; + +#define CHECK_DEREF(addr, len, name) \ + if (!VG_(am_is_valid_for_client) ((Addr)(addr), (len), VKI_PROT_READ)) { \ + dlog(0, "get_tls_addr: %s at %p len %lu not addressable\n", \ + name, (void*)(addr), (unsigned long)(len)); \ + return False; \ + } + + *tls_addr = 0; + + if (the_low_target.target_get_dtv == NULL) { + dlog(1, "low level dtv support not available\n"); + return False; + } + + if (!getplatformoffset (&lm_modid_offset)) { + dlog(0, "link_map modid field offset not available\n"); + return False; + } + dlog (2, "link_map modid offset %p\n", (void*)lm_modid_offset); + vg_assert (lm_modid_offset < 0x10000); // let's say + + dtv_loc = (*the_low_target.target_get_dtv)(tst); + if (dtv_loc == NULL) { + dlog(0, "low level dtv support returned NULL\n"); + return False; + } + + CHECK_DEREF(dtv_loc, sizeof(CORE_ADDR), "dtv_loc"); + dtv = *dtv_loc; + + // Check we can read at least 2 address at the beginning of dtv. + CHECK_DEREF(dtv, 2*sizeof(CORE_ADDR), "dtv 2 first entries"); + dlog (2, "tid %d dtv %p\n", tst->tid, (void*)dtv); + + // Check we can read the modid + CHECK_DEREF(lm+lm_modid_offset, sizeof(unsigned long int), "link_map modid"); + modid = *(unsigned long int *)(lm+lm_modid_offset); + + // Check we can access the dtv entry for modid + CHECK_DEREF(dtv + 2 * modid, sizeof(CORE_ADDR), "dtv[2*modid]"); + + // And finally compute the address of the tls variable. + *tls_addr = *(dtv + 2 * modid) + offset; + + return True; + +#undef CHECK_DEREF +} + /* returns a pointer to the architecture state corresponding to the provided register set: 0 => normal guest registers, 1 => shadow1 diff --git a/coregrind/m_gdbserver/target.h b/coregrind/m_gdbserver/target.h index c98b976be5..1cacc1eaf1 100644 --- a/coregrind/m_gdbserver/target.h +++ b/coregrind/m_gdbserver/target.h @@ -200,11 +200,29 @@ extern int valgrind_write_memory (CORE_ADDR memaddr, extern int valgrind_insert_watchpoint (char type, CORE_ADDR addr, int len); extern int valgrind_remove_watchpoint (char type, CORE_ADDR addr, int len); +/* Get the address of a thread local variable. + 'tst' is the thread for which thread local address is searched for. + 'offset' is the offset of the variable in the tls data of the load + module identified by 'lm'. + 'lm' is the link_map address of the loaded module : it is the address + of the data structure used by the dynamic linker to maintain various + information about a loaded object. + + Returns True if the address of the variable could be found. + *tls_addr is then set to this address. + Returns False if tls support is not available for this arch, or + if an error occured. *tls_addr is set to NULL. */ +extern Bool valgrind_get_tls_addr (ThreadState *tst, + CORE_ADDR offset, + CORE_ADDR lm, + CORE_ADDR *tls_addr); + /* -------------------------------------------------------------------------- */ /* ----------- Utils functions for low level arch specific files ------------ */ /* -------------------------------------------------------------------------- */ + /* returns a pointer to the architecture state corresponding to the provided register set: 0 => normal guest registers, 1 => shadow1 diff --git a/coregrind/m_gdbserver/valgrind-low-amd64.c b/coregrind/m_gdbserver/valgrind-low-amd64.c index 82f85f0527..c48afc2382 100644 --- a/coregrind/m_gdbserver/valgrind-low-amd64.c +++ b/coregrind/m_gdbserver/valgrind-low-amd64.c @@ -315,6 +315,7 @@ Bool have_avx(void) VG_(machine_get_VexArchInfo) (&va, &vai); return (vai.hwcaps & VEX_HWCAPS_AMD64_AVX ? True : False); } + static const char* target_xml (Bool shadow_mode) { @@ -345,6 +346,15 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_amd64) + return (CORE_ADDR**)(tst->arch.vex.guest_FS_ZERO + 0x8); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { -1, // Must be computed at init time. regs, @@ -353,7 +363,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "amd64", - target_xml + target_xml, + target_get_dtv }; void amd64_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-arm.c b/coregrind/m_gdbserver/valgrind-low-arm.c index 255fe79881..9516d005c8 100644 --- a/coregrind/m_gdbserver/valgrind-low-arm.c +++ b/coregrind/m_gdbserver/valgrind-low-arm.c @@ -286,6 +286,16 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_arm) + // arm dtv is pointed to by TPIDRURO + return (CORE_ADDR**)(tst->arch.vex.guest_TPIDRURO); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -294,7 +304,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "arm", - target_xml + target_xml, + target_get_dtv }; void arm_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-arm64.c b/coregrind/m_gdbserver/valgrind-low-arm64.c index 32f19e1746..56f4ba6a47 100644 --- a/coregrind/m_gdbserver/valgrind-low-arm64.c +++ b/coregrind/m_gdbserver/valgrind-low-arm64.c @@ -261,6 +261,16 @@ const char* target_xml (Bool shadow_mode) #endif } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_arm64) + // arm64 dtv is pointed to by TPIDR_EL0. + return (CORE_ADDR**)(tst->arch.vex.guest_TPIDR_EL0); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -269,7 +279,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "arm64", - target_xml + target_xml, + target_get_dtv }; void arm64_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-mips32.c b/coregrind/m_gdbserver/valgrind-low-mips32.c index 47adf4edd0..1490867a74 100644 --- a/coregrind/m_gdbserver/valgrind-low-mips32.c +++ b/coregrind/m_gdbserver/valgrind-low-mips32.c @@ -354,6 +354,16 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_mips32) + // mips32 dtv location similar to ppc64 + return (CORE_ADDR**)(tst->arch.vex.guest_ULR - 0x7000 - sizeof(CORE_ADDR)); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -362,7 +372,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "mips", - target_xml + target_xml, + target_get_dtv }; void mips32_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-mips64.c b/coregrind/m_gdbserver/valgrind-low-mips64.c index b1f3bbd460..d35df419f5 100644 --- a/coregrind/m_gdbserver/valgrind-low-mips64.c +++ b/coregrind/m_gdbserver/valgrind-low-mips64.c @@ -355,6 +355,17 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_mips64) + // mips64 dtv location similar to ppc64 + return (CORE_ADDR**)(tst->arch.vex.guest_ULR - 0x7000 - sizeof(CORE_ADDR)); + return NULL; +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -363,7 +374,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "mips64", - target_xml + target_xml, + target_get_dtv }; void mips64_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-ppc32.c b/coregrind/m_gdbserver/valgrind-low-ppc32.c index 592e25b75e..6d2d44e046 100644 --- a/coregrind/m_gdbserver/valgrind-low-ppc32.c +++ b/coregrind/m_gdbserver/valgrind-low-ppc32.c @@ -332,6 +332,17 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_ppc32) + // ppc32 dtv is located just before the tcb, which is 0x7000 before + // the thread id (r2) + return (CORE_ADDR**)(tst->arch.vex.guest_GPR2 - 0x7000 - sizeof(CORE_ADDR)); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -340,7 +351,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "ppc32", - target_xml + target_xml, + target_get_dtv }; void ppc32_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-ppc64.c b/coregrind/m_gdbserver/valgrind-low-ppc64.c index fa146c15f9..490322d5cf 100644 --- a/coregrind/m_gdbserver/valgrind-low-ppc64.c +++ b/coregrind/m_gdbserver/valgrind-low-ppc64.c @@ -329,6 +329,17 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_ppc64be) || defined(VGA_ppc64le) + // ppc64 dtv is located just before the tcb, which is 0x7000 before + // the thread id (r13) + return (CORE_ADDR**)(tst->arch.vex.guest_GPR13 - 0x7000 - sizeof(CORE_ADDR)); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -337,7 +348,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "ppc64", - target_xml + target_xml, + target_get_dtv }; void ppc64_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-s390x.c b/coregrind/m_gdbserver/valgrind-low-s390x.c index b06b920280..8ba5180b8b 100644 --- a/coregrind/m_gdbserver/valgrind-low-s390x.c +++ b/coregrind/m_gdbserver/valgrind-low-s390x.c @@ -197,6 +197,18 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_s390x) + // Thread pointer is in a0 (high 32 bits) and a1. Dtv is the second word. + return (CORE_ADDR**)(((CORE_ADDR)tst->arch.vex.guest_a0 << 32 + | (CORE_ADDR)tst->arch.vex.guest_a1) + + sizeof(CORE_ADDR)); +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -205,7 +217,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "s390x", - target_xml + target_xml, + target_get_dtv }; void s390x_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind-low-x86.c b/coregrind/m_gdbserver/valgrind-low-x86.c index f2715a0edc..43ac1eb9b6 100644 --- a/coregrind/m_gdbserver/valgrind-low-x86.c +++ b/coregrind/m_gdbserver/valgrind-low-x86.c @@ -257,6 +257,29 @@ const char* target_xml (Bool shadow_mode) } } +static CORE_ADDR** target_get_dtv (ThreadState *tst) +{ +#if defined(VGA_x86) + // FIXME: should make the below formally visible from VEX. + extern ULong x86g_use_seg_selector ( HWord ldt, HWord gdt, + UInt seg_selector, UInt virtual_addr ); + + ULong dtv_loc_g = x86g_use_seg_selector (tst->arch.vex.guest_LDT, + tst->arch.vex.guest_GDT, + tst->arch.vex.guest_GS, + 0x4); + if (dtv_loc_g == 1ULL << 32) { + dlog(0, "Error getting x86 dtv\n"); + return NULL; + } else { + CORE_ADDR dtv_loc = dtv_loc_g; + return (CORE_ADDR**)dtv_loc; + } +#else + vg_assert(0); +#endif +} + static struct valgrind_target_ops low_target = { num_regs, regs, @@ -265,7 +288,8 @@ static struct valgrind_target_ops low_target = { get_pc, set_pc, "i386", - target_xml + target_xml, + target_get_dtv }; void x86_init_architecture (struct valgrind_target_ops *target) diff --git a/coregrind/m_gdbserver/valgrind_low.h b/coregrind/m_gdbserver/valgrind_low.h index e0def7590b..7b87856cf4 100644 --- a/coregrind/m_gdbserver/valgrind_low.h +++ b/coregrind/m_gdbserver/valgrind_low.h @@ -68,6 +68,34 @@ struct valgrind_target_ops Returns NULL if there is no target xml file*/ const char* (*target_xml) (Bool shadow_mode); + /* Returns the address in the thread control block where dtv is found. + Return NULL if an error occurs or no support for tls/dtv is available. + Note that the addressability of the returned result has not been + verified. In other words, target_get_dtv just adds some magic + offset to the arch specific thread register or thread pointer or ... + + The implementation of this is of course depending on the arch + but also depends on the way pthread lib arranges its data. + For background info about tls handling, read + 'ELF Handling For Thread-Local Storage' + http://www.akkadia.org/drepper/tls.pdf + (slightly obsolete e.g. the size of a dtv entry is 2 words now). + The reference is the glibc source, in particular the arch specific + file tls.h. + + For platforms where the dtv is located in the tcb, the magic offset + to add to the thread pointer/register/... can be found by doing: + cd none/tests + gdb ./tls + set debug-file-directory /usr/lib/debug # or equivalent + start + p &((struct pthread*)0x0)->header.dtv + Currently the dtv offset is hardcoded, based on the assumption + that this is relatively stable. If that would be false, then + getoff- should be modified to output this offset e.g. + depending on the glibc version. */ + CORE_ADDR** (*target_get_dtv)(ThreadState *tst); + }; extern void x86_init_architecture (struct valgrind_target_ops *target); diff --git a/gdbserver_tests/Makefile.am b/gdbserver_tests/Makefile.am index f2c7a0d693..d5a0306595 100644 --- a/gdbserver_tests/Makefile.am +++ b/gdbserver_tests/Makefile.am @@ -14,6 +14,11 @@ EXTRA_DIST = \ hginfo.stdinB.gdb \ hginfo.stdoutB.exp \ hginfo.vgtest \ + hgtls.stderrB.exp \ + hgtls.stderr.exp \ + hgtls.stdinB.gdb \ + hgtls.stdoutB.exp \ + hgtls.vgtest \ mcblocklistsearch.stderr.exp \ mcblocklistsearch.stdinB.gdb \ mcblocklistsearch.vgtest \ diff --git a/gdbserver_tests/hgtls.stderr.exp b/gdbserver_tests/hgtls.stderr.exp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gdbserver_tests/hgtls.stderrB.exp b/gdbserver_tests/hgtls.stderrB.exp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gdbserver_tests/hgtls.stdinB.gdb b/gdbserver_tests/hgtls.stdinB.gdb new file mode 100644 index 0000000000..2541e9ea4a --- /dev/null +++ b/gdbserver_tests/hgtls.stdinB.gdb @@ -0,0 +1,37 @@ +# connect gdb to Valgrind gdbserver: +target remote | ./vgdb --wait=60 --vgdb-prefix=./vgdb-prefix-hgtls +echo vgdb launched process attached\n +monitor v.set vgdb-error 999999 +# +# +# insert break: +break tls.c:55 +command +set $tls_ip = main +if test == &tests[0] + set $tls_ip = &race +end +if test == &tests[1] + set $tls_ip = &local +end +if test == &tests[2] + set $tls_ip = &global +end +if test == &tests[3] + set $tls_ip = &static_extern +end +if test == &tests[4] + set $tls_ip = &so_extern +end +if test == &tests[5] + set $tls_ip = &so_local +end +if test == &tests[6] + set $tls_ip = &global +end +printf "test %s tls_ip %p ip %p equal %d\n", test->name, $tls_ip, ip, $tls_ip == ip +continue +end +# continue till the end +continue +quit diff --git a/gdbserver_tests/hgtls.stdoutB.exp b/gdbserver_tests/hgtls.stdoutB.exp new file mode 100644 index 0000000000..c87dbc1b32 --- /dev/null +++ b/gdbserver_tests/hgtls.stdoutB.exp @@ -0,0 +1,59 @@ +Breakpoint 1 at 0x........: file tls.c, line 55. +Continuing. +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test race tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test race tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test local tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test local tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test global tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test global tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test static_extern tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test static_extern tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test so_extern tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test so_extern tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test so_local tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test so_local tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test so_global tls_ip 0x........ ip 0x........ equal 1 +[New Thread ....] +Breakpoint 1, tls_ptr (p=0x........ ) at tls.c:55 +55 int here = 0; +test so_global tls_ip 0x........ ip 0x........ equal 1 +Program exited normally. diff --git a/gdbserver_tests/hgtls.vgtest b/gdbserver_tests/hgtls.vgtest new file mode 100644 index 0000000000..60a73b9a90 --- /dev/null +++ b/gdbserver_tests/hgtls.vgtest @@ -0,0 +1,12 @@ +# test tls addresses +prog: ../none/tests/tls +vgopts: --tool=helgrind --vgdb=yes --vgdb-error=0 --vgdb-prefix=./vgdb-prefix-hgtls -q +prereq: test -e ../none/tests/tls +stdout_filter: filter_make_empty +stderr_filter: filter_make_empty +progB: gdb +argsB: --quiet -l 60 --nx ../none/tests/tls +stdinB: hgtls.stdinB.gdb +stdoutB_filter: filter_gdb +stderrB_filter: filter_make_empty +# stderrB_filter_args: hg01_all_ok.c diff --git a/none/tests/Makefile.am b/none/tests/Makefile.am index d11004a962..031f99afa6 100644 --- a/none/tests/Makefile.am +++ b/none/tests/Makefile.am @@ -277,7 +277,7 @@ threaded_fork_LDADD = -lpthread threadederrno_LDADD = -lpthread tls_SOURCES = tls.c tls2.c tls_DEPENDENCIES = tls.so tls2.so -tls_LDFLAGS = -Wl,-rpath,$(top_builddir)/none/tests +tls_LDFLAGS = -Wl,-rpath,$(abs_top_builddir)/none/tests tls_LDADD = tls.so tls2.so -lpthread tls_so_SOURCES = tls_so.c tls_so_DEPENDENCIES = tls2.so @@ -285,7 +285,7 @@ if VGCONF_OS_IS_DARWIN tls_so_LDFLAGS = -dynamic -dynamiclib -all_load -fpic tls_so_LDADD = `pwd`/tls2.so else - tls_so_LDFLAGS = -Wl,-rpath,$(top_builddir)/none/tests -shared -fPIC + tls_so_LDFLAGS = -Wl,-rpath,$(abs_top_builddir)/none/tests -shared -fPIC tls_so_LDADD = tls2.so endif tls_so_CFLAGS = $(AM_CFLAGS) -fPIC diff --git a/none/tests/tls.c b/none/tests/tls.c index 88722dcd9b..1810790774 100644 --- a/none/tests/tls.c +++ b/none/tests/tls.c @@ -46,8 +46,8 @@ typedef int *(*func_t)(void); struct testcase { const char *name; func_t func; + char pad[2 * (8 - sizeof(void*))]; }; - static void *tls_ptr(void *p) { struct testcase *test = (struct testcase *)p;