1/*
2   This file is part of Valgrind, a dynamic binary instrumentation
3   framework.
4
5   Copyright (C) 2014-2017 Philippe Waroquiers
6
7   This program is free software; you can redistribute it and/or
8   modify it under the terms of the GNU General Public License as
9   published by the Free Software Foundation; either version 2 of the
10   License, or (at your option) any later version.
11
12   This program is distributed in the hope that it will be useful, but
13   WITHOUT ANY WARRANTY; without even the implied warranty of
14   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   General Public License for more details.
16
17   You should have received a copy of the GNU General Public License
18   along with this program; if not, write to the Free Software
19   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20   02111-1307, USA.
21
22   The GNU General Public License is contained in the file COPYING.
23*/
24
25/* This file is used to generate target executable(s) getoff-<platform>
26   In a bi-arch setup, this is used to build 2 executables
27   (for the primary and secondary platforms).
28
29   This program uses user space libraries to retrieve some platform
30   dependent offsets needed for Valgrind core, but that cannot (easily)
31   be retrieved by Valgrind core.
32
33   This is currently used only for handling the gdbsrv QGetTlsAddr query :
34   it only computes and outputs lm_modid_offset in struct link_map
35   of the dynamic linker. In theory, we should also compute the offset needed
36   to get the dtv from the thread register/pointer/...
37   Currently, the various valgrind-low-xxxxxx.c files are hardcoding this
38   offset as it is deemed (?) stable, and there is no clear way how to
39   compute this dtv offset.
40
41   The getoff-<platform> executable will be launched automatically by
42   Valgrind gdbserver when the first QGetTlsAddr query is retrieved.
43
44   On plaforms that do not support __thread and/or that do not provide
45   dlinfo RTLD_DI_TLS_MODID, this executable produces no output. */
46
47#ifndef _GNU_SOURCE
48#define _GNU_SOURCE
49#endif
50#include <config.h>
51
52#include <assert.h>
53#include <errno.h>
54#include <stdlib.h>
55#include <stdio.h>
56#include <string.h>
57
58#ifdef HAVE_DLINFO_RTLD_DI_TLS_MODID
59#include <link.h>
60#include <dlfcn.h>
61#endif
62
63/* true if arg matches the provided option */
64static
65int is_opt(char* arg, const char *option)
66{
67   int option_len = strlen(option);
68   if (option[option_len-1] == '=')
69      return (0 == strncmp(option, arg, option_len));
70   else
71      return (0 == strcmp(option, arg));
72}
73
74static int verbose = 0;
75
76static
77void usage (char* progname)
78{
79   fprintf(stderr,
80"Usage: %s [--help] [-h] [-v] [-o <outputfile>]\n"
81"Outputs various user space offsets\n"
82"By default, outputs on stdout.\n"
83"Use -o to output to <outputfile>\n"
84"-v : be more verbose\n",
85progname);
86
87}
88
89int main (int argc, char** argv)
90{
91   int i;
92   FILE *outputfile;
93   int nr_errors = 0;
94
95   outputfile = stdout;
96
97   i = 1;
98   while (i < argc) {
99      if (is_opt(argv[i], "--help") || is_opt(argv[i], "-h")) {
100         usage(argv[0]);
101         exit(0);
102      } else if (is_opt(argv[i], "-v")) {
103         verbose++;
104      } else if (is_opt(argv[i], "-o")) {
105         if (i+1 == argc) {
106            fprintf(stderr,
107                    "missing output file for -o option\n"
108                    "Use --help for more information.\n");
109            exit (1);
110         }
111         i++;
112         outputfile = fopen(argv[i], "w");
113         if (outputfile == NULL) {
114            fprintf(stderr, "Could not fopen %s in write mode\n", argv[i]);
115            perror ("fopen output file failed");
116            exit (1);
117         }
118      } else {
119         fprintf (stderr,
120                  "unknown or invalid argument %s\n"
121                  "Use --help for more information.\n",
122                  argv[i]);
123         exit(1);
124      }
125      i++;
126   }
127
128#ifdef HAVE_DLINFO_RTLD_DI_TLS_MODID
129   /* Compute offset of lm_modid in struct link_map.
130      This is needed to support QGetTlsAddr gdbsrv query.
131      Computation is done using an ugly hack, but less ugly than
132      hardcoding the offset depending on the glibc version and
133      platform.
134      The below works, based the assumption that RTLD_DI_TLS_MODID
135      just access and returns directly the field in the dummy
136      link_map structure we have prepared.
137
138      If glibc debug info is installed on your system, you can
139      also find this offset by doing in GDB:
140          p &((struct link_map*)0x0)->l_tls_modid
141      (see also coregrind/m_gdbserver/valgrind_low.h target_get_dtv
142       comments).
143   */
144   {
145      #define MAX_LINKMAP_WORDS 10000
146      size_t dummy_link_map[MAX_LINKMAP_WORDS];
147      size_t off;
148      size_t modid_offset;
149      for (off = 0; off < MAX_LINKMAP_WORDS; off++)
150         dummy_link_map[off] = off;
151      if (dlinfo ((void*)dummy_link_map, RTLD_DI_TLS_MODID,
152                  &modid_offset) == 0) {
153         assert(modid_offset >= 0 && modid_offset < MAX_LINKMAP_WORDS);
154         fprintf(outputfile,
155                 "lm_modid_offset 0x%zx\n", modid_offset*sizeof(size_t));
156      } else {
157         fprintf(stderr,
158                 "Error computing lm_modid_offset.\n"
159                 "dlinfo error %s\n", dlerror());
160         nr_errors++;
161      }
162      #undef MAX_LINKMAP_WORDS
163   }
164
165   if (outputfile != stdout)
166      if (fclose (outputfile) != 0) {
167         perror ("fclose output file failed\n");
168         nr_errors++;
169      }
170#else
171   if (verbose)
172      fprintf(stderr,
173              "cannot compute lm_modid_offset.\n"
174              "configure did not define HAVE_DLINFO_RTLD_DI_TLS_MODID.\n");
175#endif
176
177   if (nr_errors == 0)
178      exit(0);
179   else
180      exit(1);
181}
182