1/*
2 * Backtrace debugging
3 * Copyright (c) 2009, Jouni Malinen <j@w1.fi>
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#ifdef WPA_TRACE_BFD
10#define _GNU_SOURCE
11#include <link.h>
12#endif /* WPA_TRACE_BCD */
13#include "includes.h"
14
15#include "common.h"
16#include "trace.h"
17
18#ifdef WPA_TRACE
19
20static struct dl_list active_references =
21{ &active_references, &active_references };
22
23#ifdef WPA_TRACE_BFD
24#include <bfd.h>
25
26#define DMGL_PARAMS      (1 << 0)
27#define DMGL_ANSI        (1 << 1)
28
29static char *prg_fname = NULL;
30static bfd *cached_abfd = NULL;
31static asymbol **syms = NULL;
32static unsigned long start_offset;
33static int start_offset_looked_up;
34
35
36static int callback(struct dl_phdr_info *info, size_t size, void *data)
37{
38	/*
39	 * dl_iterate_phdr(3):
40	 * "The first object visited by callback is the main program."
41	 */
42	start_offset = info->dlpi_addr;
43
44	/*
45	 * dl_iterate_phdr(3):
46	 * "The dl_iterate_phdr() function walks through the list of an
47	 *  application's shared objects and calls the function callback
48	 *  once for each object, until either all shared objects have
49	 *  been processed or callback returns a nonzero value."
50	 */
51	return 1;
52}
53
54
55static void get_prg_fname(void)
56{
57	char exe[50], fname[512];
58	int len;
59	os_snprintf(exe, sizeof(exe) - 1, "/proc/%u/exe", getpid());
60	len = readlink(exe, fname, sizeof(fname) - 1);
61	if (len < 0 || len >= (int) sizeof(fname)) {
62		wpa_printf(MSG_ERROR, "readlink: %s", strerror(errno));
63		return;
64	}
65	fname[len] = '\0';
66	prg_fname = strdup(fname);
67}
68
69
70static bfd * open_bfd(const char *fname)
71{
72	bfd *abfd;
73	char **matching;
74
75	abfd = bfd_openr(prg_fname, NULL);
76	if (abfd == NULL) {
77		wpa_printf(MSG_INFO, "bfd_openr failed");
78		return NULL;
79	}
80
81	if (bfd_check_format(abfd, bfd_archive)) {
82		wpa_printf(MSG_INFO, "bfd_check_format failed");
83		bfd_close(abfd);
84		return NULL;
85	}
86
87	if (!bfd_check_format_matches(abfd, bfd_object, &matching)) {
88		wpa_printf(MSG_INFO, "bfd_check_format_matches failed");
89		free(matching);
90		bfd_close(abfd);
91		return NULL;
92	}
93
94	return abfd;
95}
96
97
98static void read_syms(bfd *abfd)
99{
100	long storage, symcount;
101	bfd_boolean dynamic = FALSE;
102
103	if (syms)
104		return;
105
106	if (!(bfd_get_file_flags(abfd) & HAS_SYMS)) {
107		wpa_printf(MSG_INFO, "No symbols");
108		return;
109	}
110
111	storage = bfd_get_symtab_upper_bound(abfd);
112	if (storage == 0) {
113		storage = bfd_get_dynamic_symtab_upper_bound(abfd);
114		dynamic = TRUE;
115	}
116	if (storage < 0) {
117		wpa_printf(MSG_INFO, "Unknown symtab upper bound");
118		return;
119	}
120
121	syms = malloc(storage);
122	if (syms == NULL) {
123		wpa_printf(MSG_INFO, "Failed to allocate memory for symtab "
124			   "(%ld bytes)", storage);
125		return;
126	}
127	if (dynamic)
128		symcount = bfd_canonicalize_dynamic_symtab(abfd, syms);
129	else
130		symcount = bfd_canonicalize_symtab(abfd, syms);
131	if (symcount < 0) {
132		wpa_printf(MSG_INFO, "Failed to canonicalize %ssymtab",
133			   dynamic ? "dynamic " : "");
134		free(syms);
135		syms = NULL;
136		return;
137	}
138}
139
140
141struct bfd_data {
142	bfd_vma pc;
143	bfd_boolean found;
144	const char *filename;
145	const char *function;
146	unsigned int line;
147};
148
149/*
150 * binutils removed the bfd parameter and renamed things but
151 * those were macros so we can detect their absence.
152 * Cf. https://sourceware.org/git/gitweb.cgi?p=binutils-gdb.git;a=commitdiff;h=fd3619828e94a24a92cddec42cbc0ab33352eeb4;hp=5dfda3562a69686c43aad4fb0269cc9d5ec010d5
153 */
154#ifndef bfd_get_section_vma
155#define bfd_get_section_vma(bfd, section) bfd_section_vma(section)
156#endif
157#ifndef bfd_get_section_size
158#define bfd_get_section_size bfd_section_size
159#endif
160
161static void find_addr_sect(bfd *abfd, asection *section, void *obj)
162{
163	struct bfd_data *data = obj;
164	bfd_vma vma;
165	bfd_size_type size;
166
167	if (data->found)
168		return;
169
170	if (!(bfd_get_section_vma(abfd, section)))
171		return;
172
173	vma = bfd_get_section_vma(abfd, section);
174	if (data->pc < vma)
175		return;
176
177	size = bfd_get_section_size(section);
178	if (data->pc >= vma + size)
179		return;
180
181	data->found = bfd_find_nearest_line(abfd, section, syms,
182					    data->pc - vma,
183					    &data->filename,
184					    &data->function,
185					    &data->line);
186}
187
188
189static void wpa_trace_bfd_addr(void *pc)
190{
191	bfd *abfd = cached_abfd;
192	struct bfd_data data;
193	const char *name;
194	char *aname = NULL;
195	const char *filename;
196
197	if (abfd == NULL)
198		return;
199
200	data.pc = (bfd_hostptr_t) ((u8 *) pc - start_offset);
201	data.found = FALSE;
202	bfd_map_over_sections(abfd, find_addr_sect, &data);
203
204	if (!data.found)
205		return;
206
207	do {
208		if (data.function)
209			aname = bfd_demangle(abfd, data.function,
210					     DMGL_ANSI | DMGL_PARAMS);
211		name = aname ? aname : data.function;
212		filename = data.filename;
213		if (filename) {
214			char *end = os_strrchr(filename, '/');
215			int i = 0;
216			while (*filename && *filename == prg_fname[i] &&
217			       filename <= end) {
218				filename++;
219				i++;
220			}
221		}
222		wpa_printf(MSG_INFO, "     %s() %s:%u",
223			   name, filename, data.line);
224		free(aname);
225		aname = NULL;
226
227		data.found = bfd_find_inliner_info(abfd, &data.filename,
228						   &data.function, &data.line);
229	} while (data.found);
230}
231
232
233static const char * wpa_trace_bfd_addr2func(void *pc)
234{
235	bfd *abfd = cached_abfd;
236	struct bfd_data data;
237
238	if (abfd == NULL)
239		return NULL;
240
241	data.pc = (bfd_hostptr_t) ((u8 *) pc - start_offset);
242	data.found = FALSE;
243	bfd_map_over_sections(abfd, find_addr_sect, &data);
244
245	if (!data.found)
246		return NULL;
247
248	return data.function;
249}
250
251
252static void wpa_trace_bfd_init(void)
253{
254	if (!prg_fname) {
255		get_prg_fname();
256		if (!prg_fname)
257			return;
258	}
259
260	if (!cached_abfd) {
261		cached_abfd = open_bfd(prg_fname);
262		if (!cached_abfd) {
263			wpa_printf(MSG_INFO, "Failed to open bfd");
264			return;
265		}
266	}
267
268	read_syms(cached_abfd);
269	if (!syms) {
270		wpa_printf(MSG_INFO, "Failed to read symbols");
271		return;
272	}
273
274	if (!start_offset_looked_up) {
275		dl_iterate_phdr(callback, NULL);
276		start_offset_looked_up = 1;
277	}
278}
279
280
281void wpa_trace_dump_funcname(const char *title, void *pc)
282{
283	wpa_printf(MSG_INFO, "WPA_TRACE: %s: %p", title, pc);
284	wpa_trace_bfd_init();
285	wpa_trace_bfd_addr(pc);
286}
287
288
289size_t wpa_trace_calling_func(const char *buf[], size_t len)
290{
291	bfd *abfd;
292	void *btrace_res[WPA_TRACE_LEN];
293	int i, btrace_num;
294	size_t pos = 0;
295
296	if (len == 0)
297		return 0;
298	if (len > WPA_TRACE_LEN)
299		len = WPA_TRACE_LEN;
300
301	wpa_trace_bfd_init();
302	abfd = cached_abfd;
303	if (!abfd)
304		return 0;
305
306	btrace_num = backtrace(btrace_res, len);
307	if (btrace_num < 1)
308		return 0;
309
310	for (i = 0; i < btrace_num; i++) {
311		struct bfd_data data;
312
313		data.pc = (bfd_hostptr_t) ((u8 *) btrace_res[i] - start_offset);
314		data.found = FALSE;
315		bfd_map_over_sections(abfd, find_addr_sect, &data);
316
317		while (data.found) {
318			if (data.function &&
319			    (pos > 0 ||
320			     os_strcmp(data.function, __func__) != 0)) {
321				buf[pos++] = data.function;
322				if (pos == len)
323					return pos;
324			}
325
326			data.found = bfd_find_inliner_info(abfd, &data.filename,
327							   &data.function,
328							   &data.line);
329		}
330	}
331
332	return pos;
333}
334
335#else /* WPA_TRACE_BFD */
336
337#define wpa_trace_bfd_init() do { } while (0)
338#define wpa_trace_bfd_addr(pc) do { } while (0)
339#define wpa_trace_bfd_addr2func(pc) NULL
340
341#endif /* WPA_TRACE_BFD */
342
343void wpa_trace_dump_func(const char *title, void **btrace, int btrace_num)
344{
345	char **sym;
346	int i;
347	enum { TRACE_HEAD, TRACE_RELEVANT, TRACE_TAIL } state;
348
349	wpa_trace_bfd_init();
350	wpa_printf(MSG_INFO, "WPA_TRACE: %s - START", title);
351	sym = backtrace_symbols(btrace, btrace_num);
352	state = TRACE_HEAD;
353	for (i = 0; i < btrace_num; i++) {
354		const char *func = wpa_trace_bfd_addr2func(btrace[i]);
355		if (state == TRACE_HEAD && func &&
356		    (os_strcmp(func, "wpa_trace_add_ref_func") == 0 ||
357		     os_strcmp(func, "wpa_trace_check_ref") == 0 ||
358		     os_strcmp(func, "wpa_trace_show") == 0))
359			continue;
360		if (state == TRACE_TAIL && sym && sym[i] &&
361		    os_strstr(sym[i], "__libc_start_main"))
362			break;
363		if (state == TRACE_HEAD)
364			state = TRACE_RELEVANT;
365		if (sym)
366			wpa_printf(MSG_INFO, "[%d]: %s", i, sym[i]);
367		else
368			wpa_printf(MSG_INFO, "[%d]: ?? [%p]", i, btrace[i]);
369		wpa_trace_bfd_addr(btrace[i]);
370		if (state == TRACE_RELEVANT && func &&
371		    os_strcmp(func, "main") == 0)
372			state = TRACE_TAIL;
373	}
374	free(sym);
375	wpa_printf(MSG_INFO, "WPA_TRACE: %s - END", title);
376}
377
378
379void wpa_trace_show(const char *title)
380{
381	struct info {
382		WPA_TRACE_INFO
383	} info;
384	wpa_trace_record(&info);
385	wpa_trace_dump(title, &info);
386}
387
388
389void wpa_trace_add_ref_func(struct wpa_trace_ref *ref, const void *addr)
390{
391	if (addr == NULL)
392		return;
393	ref->addr = addr;
394	wpa_trace_record(ref);
395	dl_list_add(&active_references, &ref->list);
396}
397
398
399void wpa_trace_check_ref(const void *addr)
400{
401	struct wpa_trace_ref *ref;
402	dl_list_for_each(ref, &active_references, struct wpa_trace_ref, list) {
403		if (addr != ref->addr)
404			continue;
405		wpa_trace_show("Freeing referenced memory");
406		wpa_trace_dump("Reference registration", ref);
407		abort();
408	}
409}
410
411
412void wpa_trace_deinit(void)
413{
414#ifdef WPA_TRACE_BFD
415	free(syms);
416	syms = NULL;
417#endif /* WPA_TRACE_BFD */
418}
419
420#endif /* WPA_TRACE */
421