summaryrefslogtreecommitdiffstats
path: root/tests/backtrace-data.c
blob: 60d556e0870c340dbcf51a66728b4eba5ed60a6b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
/* Test custom provided Dwfl_Thread_Callbacks vector.
   Copyright (C) 2013 Red Hat, Inc.
   This file is part of elfutils.

   This file is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 3 of the License, or
   (at your option) any later version.

   elfutils 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.  */

/* Test custom provided Dwfl_Thread_Callbacks vector.  Test mimics what
   a ptrace based vector would do.  */

#include <config.h>
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdio_ext.h>
#include <locale.h>
#include <dirent.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <dwarf.h>
#if defined(__x86_64__) && defined(__linux__)
#include <sys/resource.h>
#include <sys/ptrace.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/user.h>
#include <fcntl.h>
#include <string.h>
#include ELFUTILS_HEADER(dwfl)
#endif
#include "system.h"

#if !defined(__x86_64__) || !defined(__linux__)

int
main (int argc __attribute__ ((unused)), char **argv)
{
  fprintf (stderr, "%s: Unwinding not supported for this architecture\n",
          argv[0]);
  return 77;
}

#else /* __x86_64__ && __linux__ */

/* The only arch specific code is set_initial_registers.  */

static int
find_elf (Dwfl_Module *mod __attribute__ ((unused)),
	  void **userdata __attribute__ ((unused)),
	  const char *modname __attribute__ ((unused)),
	  Dwarf_Addr base __attribute__ ((unused)),
	  char **file_name __attribute__ ((unused)),
	  Elf **elfp __attribute__ ((unused)))
{
  /* Not used as modules are reported explicitly.  */
  assert (0);
}

static bool
memory_read (Dwfl *dwfl, Dwarf_Addr addr, Dwarf_Word *result,
	     void *dwfl_arg __attribute__ ((unused)))
{
  pid_t child = dwfl_pid (dwfl);

  errno = 0;
  long l = ptrace (PTRACE_PEEKDATA, child, (void *) (uintptr_t) addr, NULL);

  // The unwinder can ask for an invalid address.
  // Don't assert on that but just politely refuse.
  if (errno != 0) {
      errno = 0;
      return false;
  }
  *result = l;

  return true;
}

/* Return filename and VMA address *BASEP where its mapping starts which
   contains ADDR.  */

static char *
maps_lookup (pid_t pid, Dwarf_Addr addr, GElf_Addr *basep)
{
  char *fname;
  int i = asprintf (&fname, "/proc/%ld/maps", (long) pid);
  assert (errno == 0);
  assert (i > 0);
  FILE *f = fopen (fname, "rb");
  assert (errno == 0);
  assert (f);
  free (fname);
  for (;;)
    {
      // 37e3c22000-37e3c23000 rw-p 00022000 00:11 49532 /lib64/ld-2.14.90.so */
      unsigned long start, end, offset;
      i = fscanf (f, "%lx-%lx %*s %lx %*x:%*x %*x", &start, &end, &offset);
      assert (errno == 0);
      if (i != 3)
          break;
      char *filename = strdup ("");
      assert (filename);
      size_t filename_len = 0;
      for (;;)
	{
	  int c = fgetc (f);
	  assert (c != EOF);
	  if (c == '\n')
	    break;
	  if (c == ' ' && *filename == '\0')
	    continue;
	  filename = realloc (filename, filename_len + 2);
	  assert (filename);
	  filename[filename_len++] = c;
	  filename[filename_len] = '\0';
	}
      if (start <= addr && addr < end)
	{
	  i = fclose (f);
	  assert (errno == 0);
	  assert (i == 0);

	  *basep = start - offset;
	  return filename;
	}
      free (filename);
    }
  *basep = 0;
  return NULL;
}

/* Add module containing ADDR to the DWFL address space.

   dwfl_report_elf call here violates Dwfl manipulation as one should call
   dwfl_report only between dwfl_report_begin_add and dwfl_report_end.
   Current elfutils implementation does not mind as dwfl_report_begin_add is
   empty.  */

static Dwfl_Module *
report_module (Dwfl *dwfl, pid_t child, Dwarf_Addr addr)
{
  GElf_Addr base;
  char *long_name = maps_lookup (child, addr, &base);
  if (!long_name)
      return NULL; // not found
  Dwfl_Module *mod = dwfl_report_elf (dwfl, long_name, long_name, -1,
				      base, false /* add_p_vaddr */);
  assert (mod);
  free (long_name);
  assert (dwfl_addrmodule (dwfl, addr) == mod);
  return mod;
}

static pid_t
next_thread (Dwfl *dwfl, void *dwfl_arg __attribute__ ((unused)),
	     void **thread_argp)
{
  if (*thread_argp != NULL)
    return 0;
  /* Put arbitrary non-NULL value into *THREAD_ARGP as a marker so that this
     function returns non-zero PID only once.  */
  *thread_argp = thread_argp;
  return dwfl_pid (dwfl);
}

static bool
set_initial_registers (Dwfl_Thread *thread,
		       void *thread_arg __attribute__ ((unused)))
{
  pid_t child = dwfl_pid (dwfl_thread_dwfl (thread));

  struct user_regs_struct user_regs;
  long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs);
  assert (errno == 0);
  assert (l == 0);

  Dwarf_Word dwarf_regs[17];
  dwarf_regs[0] = user_regs.rax;
  dwarf_regs[1] = user_regs.rdx;
  dwarf_regs[2] = user_regs.rcx;
  dwarf_regs[3] = user_regs.rbx;
  dwarf_regs[4] = user_regs.rsi;
  dwarf_regs[5] = user_regs.rdi;
  dwarf_regs[6] = user_regs.rbp;
  dwarf_regs[7] = user_regs.rsp;
  dwarf_regs[8] = user_regs.r8;
  dwarf_regs[9] = user_regs.r9;
  dwarf_regs[10] = user_regs.r10;
  dwarf_regs[11] = user_regs.r11;
  dwarf_regs[12] = user_regs.r12;
  dwarf_regs[13] = user_regs.r13;
  dwarf_regs[14] = user_regs.r14;
  dwarf_regs[15] = user_regs.r15;
  dwarf_regs[16] = user_regs.rip;
  bool ok = dwfl_thread_state_registers (thread, 0, 17, dwarf_regs);
  assert (ok);

  /* x86_64 has PC contained in its CFI subset of DWARF register set so
     elfutils will figure out the real PC value from REGS.
     So no need to explicitly call dwfl_thread_state_register_pc.  */

  return true;
}

static const Dwfl_Thread_Callbacks callbacks =
{
  next_thread,
  NULL, /* get_thread */
  memory_read,
  set_initial_registers,
  NULL, /* detach */
  NULL, /* thread_detach */
};

static int
frame_callback (Dwfl_Frame *state, void *arg)
{
  unsigned *framenop = arg;
  Dwarf_Addr pc;
  bool isactivation;
  if (! dwfl_frame_pc (state, &pc, &isactivation))
    {
      error (1, 0, "%s", dwfl_errmsg (-1));
      return 1;
    }
  Dwarf_Addr pc_adjusted = pc - (isactivation ? 0 : 1);

  /* Get PC->SYMNAME.  */
  Dwfl *dwfl = dwfl_thread_dwfl (dwfl_frame_thread (state));
  Dwfl_Module *mod = dwfl_addrmodule (dwfl, pc_adjusted);
  if (mod == NULL)
    mod = report_module (dwfl, dwfl_pid (dwfl), pc_adjusted);
  const char *symname = NULL;
  symname = dwfl_module_addrname (mod, pc_adjusted);

  printf ("#%2u %#" PRIx64 "%4s\t%s\n", (*framenop)++, (uint64_t) pc,
	  ! isactivation ? "- 1" : "", symname);
  return DWARF_CB_OK;
}

static int
thread_callback (Dwfl_Thread *thread, void *thread_arg __attribute__ ((unused)))
{
  unsigned frameno = 0;
  switch (dwfl_thread_getframes (thread, frame_callback, &frameno))
    {
    case 0:
      break;
    case -1:
      error (1, 0, "dwfl_thread_getframes: %s", dwfl_errmsg (-1));
      break;
    default:
      abort ();
    }
  return DWARF_CB_OK;
}

int
main (int argc __attribute__ ((unused)), char **argv __attribute__ ((unused)))
{
  /* We use no threads here which can interfere with handling a stream.  */
  __fsetlocking (stdin, FSETLOCKING_BYCALLER);
  __fsetlocking (stdout, FSETLOCKING_BYCALLER);
  __fsetlocking (stderr, FSETLOCKING_BYCALLER);

  /* Set locale.  */
  (void) setlocale (LC_ALL, "");

  elf_version (EV_CURRENT);

  pid_t child = fork ();
  switch (child)
  {
    case -1:
      assert (errno == 0);
      assert (0);
    case 0:;
      long l = ptrace (PTRACE_TRACEME, 0, NULL, NULL);
      assert (errno == 0);
      assert (l == 0);
      raise (SIGUSR1);
      return 0;
    default:
      break;
  }

  int status;
  pid_t pid = waitpid (child, &status, 0);
  assert (errno == 0);
  assert (pid == child);
  assert (WIFSTOPPED (status));
  assert (WSTOPSIG (status) == SIGUSR1);

  static char *debuginfo_path;
  static const Dwfl_Callbacks offline_callbacks =
    {
      .find_debuginfo = dwfl_standard_find_debuginfo,
      .debuginfo_path = &debuginfo_path,
      .section_address = dwfl_offline_section_address,
      .find_elf = find_elf,
    };
  Dwfl *dwfl = dwfl_begin (&offline_callbacks);
  assert (dwfl);

  struct user_regs_struct user_regs;
  long l = ptrace (PTRACE_GETREGS, child, NULL, &user_regs);
  assert (errno == 0);
  assert (l == 0);
  report_module (dwfl, child, user_regs.rip);

  bool ok = dwfl_attach_state (dwfl, EM_NONE, child, &callbacks, NULL);
  assert (ok);

  /* Multiple threads are not handled here.  */
  int err = dwfl_getthreads (dwfl, thread_callback, NULL);
  assert (! err);

  dwfl_end (dwfl);
  kill (child, SIGKILL);
  pid = waitpid (child, &status, 0);
  assert (errno == 0);
  assert (pid == child);
  assert (WIFSIGNALED (status));
  assert (WTERMSIG (status) == SIGKILL);

  return EXIT_SUCCESS;
}

#endif /* x86_64 */