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-<platform> the first time an
__thread variable is printed.
git-svn-id: svn://svn.valgrind.org/valgrind/trunk@14283 a5019735-40e9-0310-863c-91ae7b9d1cf9
diff --git a/auxprogs/getoff.c b/auxprogs/getoff.c
new file mode 100644
index 0000000..36f5751
--- /dev/null
+++ b/auxprogs/getoff.c
@@ -0,0 +1,139 @@
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <config.h>
+
+#include <assert.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <link.h>
+#include <dlfcn.h>
+
+/* 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 <outputfile>]\n"
+"Outputs various user space offsets\n"
+"By default, outputs on stdout.\n"
+"Use -o to output to <outputfile>\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);
+}