File Index
Files and directories
Sign in
FILE

addr2line.c

File preview with syntax highlighting.

linux-6.19.14/tools/perf/util/addr2line.c C
  1// SPDX-License-Identifier: GPL-2.0
  2#include "addr2line.h"
  3#include "debug.h"
  4#include "dso.h"
  5#include "string2.h"
  6#include "srcline.h"
  7#include "symbol.h"
  8#include "symbol_conf.h"
  9
 10#include <api/io.h>
 11#include <linux/zalloc.h>
 12#include <subcmd/run-command.h>
 13
 14#include <inttypes.h>
 15#include <signal.h>
 16#include <stdlib.h>
 17#include <string.h>
 18
 19#define MAX_INLINE_NEST 1024
 20
 21/* If addr2line doesn't return data for 5 seconds then timeout. */
 22int addr2line_timeout_ms = 5 * 1000;
 23
 24static int filename_split(char *filename, unsigned int *line_nr)
 25{
 26	char *sep;
 27
 28	sep = strchr(filename, '\n');
 29	if (sep)
 30		*sep = '\0';
 31
 32	if (!strcmp(filename, "??:0"))
 33		return 0;
 34
 35	sep = strchr(filename, ':');
 36	if (sep) {
 37		*sep++ = '\0';
 38		*line_nr = strtoul(sep, NULL, 0);
 39		return 1;
 40	}
 41	pr_debug("addr2line missing ':' in filename split\n");
 42	return 0;
 43}
 44
 45static void addr2line_subprocess_cleanup(struct child_process *a2l)
 46{
 47	if (a2l->pid != -1) {
 48		kill(a2l->pid, SIGKILL);
 49		finish_command(a2l); /* ignore result, we don't care */
 50		a2l->pid = -1;
 51		close(a2l->in);
 52		close(a2l->out);
 53	}
 54
 55	free(a2l);
 56}
 57
 58static struct child_process *addr2line_subprocess_init(const char *addr2line_path,
 59							const char *binary_path)
 60{
 61	const char *argv[] = {
 62		addr2line_path ?: "addr2line",
 63		"-e", binary_path,
 64		"-a", "-i", "-f", NULL
 65	};
 66	struct child_process *a2l = zalloc(sizeof(*a2l));
 67	int start_command_status = 0;
 68
 69	if (a2l == NULL) {
 70		pr_err("Failed to allocate memory for addr2line");
 71		return NULL;
 72	}
 73
 74	a2l->pid = -1;
 75	a2l->in = -1;
 76	a2l->out = -1;
 77	a2l->no_stderr = 1;
 78
 79	a2l->argv = argv;
 80	start_command_status = start_command(a2l);
 81	a2l->argv = NULL; /* it's not used after start_command; avoid dangling pointers */
 82
 83	if (start_command_status != 0) {
 84		pr_warning("could not start addr2line (%s) for %s: start_command return code %d\n",
 85			addr2line_path, binary_path, start_command_status);
 86		addr2line_subprocess_cleanup(a2l);
 87		return NULL;
 88	}
 89
 90	return a2l;
 91}
 92
 93enum a2l_style {
 94	BROKEN,
 95	GNU_BINUTILS,
 96	LLVM,
 97};
 98
 99static enum a2l_style addr2line_configure(struct child_process *a2l, const char *dso_name)
100{
101	static bool cached;
102	static enum a2l_style style;
103
104	if (!cached) {
105		char buf[128];
106		struct io io;
107		int ch;
108		int lines;
109
110		if (write(a2l->in, ",\n", 2) != 2)
111			return BROKEN;
112
113		io__init(&io, a2l->out, buf, sizeof(buf));
114		ch = io__get_char(&io);
115		if (ch == ',') {
116			style = LLVM;
117			cached = true;
118			lines = 1;
119			pr_debug3("Detected LLVM addr2line style\n");
120		} else if (ch == '0') {
121			style = GNU_BINUTILS;
122			cached = true;
123			lines = 3;
124			pr_debug3("Detected binutils addr2line style\n");
125		} else {
126			if (!symbol_conf.disable_add2line_warn) {
127				char *output = NULL;
128				size_t output_len;
129
130				io__getline(&io, &output, &output_len);
131				pr_warning("%s %s: addr2line configuration failed\n",
132					   __func__, dso_name);
133				pr_warning("\t%c%s", ch, output);
134			}
135			pr_debug("Unknown/broken addr2line style\n");
136			return BROKEN;
137		}
138		while (lines) {
139			ch = io__get_char(&io);
140			if (ch <= 0)
141				break;
142			if (ch == '\n')
143				lines--;
144		}
145		/* Ignore SIGPIPE in the event addr2line exits. */
146		signal(SIGPIPE, SIG_IGN);
147	}
148	return style;
149}
150
151static int read_addr2line_record(struct io *io,
152				 enum a2l_style style,
153				 const char *dso_name,
154				 u64 addr,
155				 bool first,
156				 char **function,
157				 char **filename,
158				 unsigned int *line_nr)
159{
160	/*
161	 * Returns:
162	 * -1 ==> error
163	 * 0 ==> sentinel (or other ill-formed) record read
164	 * 1 ==> a genuine record read
165	 */
166	char *line = NULL;
167	size_t line_len = 0;
168	unsigned int dummy_line_nr = 0;
169	int ret = -1;
170
171	if (function != NULL)
172		zfree(function);
173
174	if (filename != NULL)
175		zfree(filename);
176
177	if (line_nr != NULL)
178		*line_nr = 0;
179
180	/*
181	 * Read the first line. Without an error this will be:
182	 * - for the first line an address like 0x1234,
183	 * - the binutils sentinel 0x0000000000000000,
184	 * - the llvm-addr2line the sentinel ',' character,
185	 * - the function name line for an inlined function.
186	 */
187	if (io__getline(io, &line, &line_len) < 0 || !line_len)
188		goto error;
189
190	pr_debug3("%s %s: addr2line read address for sentinel: %s", __func__, dso_name, line);
191	if (style == LLVM && line_len == 2 && line[0] == ',') {
192		/* Found the llvm-addr2line sentinel character. */
193		zfree(&line);
194		return 0;
195	} else if (style == GNU_BINUTILS && (!first || addr != 0)) {
196		int zero_count = 0, non_zero_count = 0;
197		/*
198		 * Check for binutils sentinel ignoring it for the case the
199		 * requested address is 0.
200		 */
201
202		/* A given address should always start 0x. */
203		if (line_len >= 2 || line[0] != '0' || line[1] != 'x') {
204			for (size_t i = 2; i < line_len; i++) {
205				if (line[i] == '0')
206					zero_count++;
207				else if (line[i] != '\n')
208					non_zero_count++;
209			}
210			if (!non_zero_count) {
211				int ch;
212
213				if (first && !zero_count) {
214					/* Line was erroneous just '0x'. */
215					goto error;
216				}
217				/*
218				 * Line was 0x0..0, the sentinel for binutils. Remove
219				 * the function and filename lines.
220				 */
221				zfree(&line);
222				do {
223					ch = io__get_char(io);
224				} while (ch > 0 && ch != '\n');
225				do {
226					ch = io__get_char(io);
227				} while (ch > 0 && ch != '\n');
228				return 0;
229			}
230		}
231	}
232	/* Read the second function name line (if inline data then this is the first line). */
233	if (first && (io__getline(io, &line, &line_len) < 0 || !line_len))
234		goto error;
235
236	pr_debug3("%s %s: addr2line read line: %s", __func__, dso_name, line);
237	if (function != NULL)
238		*function = strdup(strim(line));
239
240	zfree(&line);
241	line_len = 0;
242
243	/* Read the third filename and line number line. */
244	if (io__getline(io, &line, &line_len) < 0 || !line_len)
245		goto error;
246
247	pr_debug3("%s %s: addr2line filename:number : %s", __func__, dso_name, line);
248	if (filename_split(line, line_nr == NULL ? &dummy_line_nr : line_nr) == 0 &&
249	    style == GNU_BINUTILS) {
250		ret = 0;
251		goto error;
252	}
253
254	if (filename != NULL)
255		*filename = strdup(line);
256
257	zfree(&line);
258	line_len = 0;
259
260	return 1;
261
262error:
263	free(line);
264	if (function != NULL)
265		zfree(function);
266	if (filename != NULL)
267		zfree(filename);
268	return ret;
269}
270
271static int inline_list__append_record(struct dso *dso,
272				      struct inline_node *node,
273				      struct symbol *sym,
274				      const char *function,
275				      const char *filename,
276				      unsigned int line_nr)
277{
278	struct symbol *inline_sym = new_inline_sym(dso, sym, function);
279
280	return inline_list__append(inline_sym, srcline_from_fileline(filename, line_nr), node);
281}
282
283int cmd__addr2line(const char *dso_name, u64 addr,
284		   char **file, unsigned int *line_nr,
285		   struct dso *dso,
286		   bool unwind_inlines,
287		   struct inline_node *node,
288		   struct symbol *sym __maybe_unused)
289{
290	struct child_process *a2l = dso__a2l(dso);
291	char *record_function = NULL;
292	char *record_filename = NULL;
293	unsigned int record_line_nr = 0;
294	int record_status = -1;
295	int ret = 0;
296	size_t inline_count = 0;
297	int len;
298	char buf[128];
299	ssize_t written;
300	struct io io = { .eof = false };
301	enum a2l_style a2l_style;
302
303	if (!a2l) {
304		if (!filename__has_section(dso_name, ".debug_line"))
305			goto out;
306
307		dso__set_a2l(dso,
308			     addr2line_subprocess_init(symbol_conf.addr2line_path, dso_name));
309		a2l = dso__a2l(dso);
310	}
311
312	if (a2l == NULL) {
313		if (!symbol_conf.disable_add2line_warn)
314			pr_warning("%s %s: addr2line_subprocess_init failed\n", __func__, dso_name);
315		goto out;
316	}
317	a2l_style = addr2line_configure(a2l, dso_name);
318	if (a2l_style == BROKEN)
319		goto out;
320
321	/*
322	 * Send our request and then *deliberately* send something that can't be
323	 * interpreted as a valid address to ask addr2line about (namely,
324	 * ","). This causes addr2line to first write out the answer to our
325	 * request, in an unbounded/unknown number of records, and then to write
326	 * out the lines "0x0...0", "??" and "??:0", for GNU binutils, or ","
327	 * for llvm-addr2line, so that we can detect when it has finished giving
328	 * us anything useful.
329	 */
330	len = snprintf(buf, sizeof(buf), "%016"PRIx64"\n,\n", addr);
331	written = len > 0 ? write(a2l->in, buf, len) : -1;
332	if (written != len) {
333		if (!symbol_conf.disable_add2line_warn)
334			pr_warning("%s %s: could not send request\n", __func__, dso_name);
335		goto out;
336	}
337	io__init(&io, a2l->out, buf, sizeof(buf));
338	io.timeout_ms = addr2line_timeout_ms;
339	switch (read_addr2line_record(&io, a2l_style, dso_name, addr, /*first=*/true,
340				      &record_function, &record_filename, &record_line_nr)) {
341	case -1:
342		if (!symbol_conf.disable_add2line_warn)
343			pr_warning("%s %s: could not read first record\n", __func__, dso_name);
344		goto out;
345	case 0:
346		/*
347		 * The first record was invalid, so return failure, but first
348		 * read another record, since we sent a sentinel ',' for the
349		 * sake of detected the last inlined function. Treat this as the
350		 * first of a record as the ',' generates a new start with GNU
351		 * binutils, also force a non-zero address as we're no longer
352		 * reading that record.
353		 */
354		switch (read_addr2line_record(&io, a2l_style, dso_name,
355					      /*addr=*/1, /*first=*/true,
356					      NULL, NULL, NULL)) {
357		case -1:
358			if (!symbol_conf.disable_add2line_warn)
359				pr_warning("%s %s: could not read sentinel record\n",
360					   __func__, dso_name);
361			break;
362		case 0:
363			/* The sentinel as expected. */
364			break;
365		default:
366			if (!symbol_conf.disable_add2line_warn)
367				pr_warning("%s %s: unexpected record instead of sentinel",
368					   __func__, dso_name);
369			break;
370		}
371		goto out;
372	default:
373		/* First record as expected. */
374		break;
375	}
376
377	if (file) {
378		*file = strdup(record_filename);
379		ret = 1;
380	}
381	if (line_nr)
382		*line_nr = record_line_nr;
383
384	if (unwind_inlines) {
385		if (node && inline_list__append_record(dso, node, sym,
386						       record_function,
387						       record_filename,
388						       record_line_nr)) {
389			ret = 0;
390			goto out;
391		}
392	}
393
394	/*
395	 * We have to read the records even if we don't care about the inline
396	 * info. This isn't the first record and force the address to non-zero
397	 * as we're reading records beyond the first.
398	 */
399	while ((record_status = read_addr2line_record(&io,
400						      a2l_style,
401						      dso_name,
402						      /*addr=*/1,
403						      /*first=*/false,
404						      &record_function,
405						      &record_filename,
406						      &record_line_nr)) == 1) {
407		if (unwind_inlines && node && inline_count++ < MAX_INLINE_NEST) {
408			if (inline_list__append_record(dso, node, sym,
409						       record_function,
410						       record_filename,
411						       record_line_nr)) {
412				ret = 0;
413				goto out;
414			}
415			ret = 1; /* found at least one inline frame */
416		}
417	}
418
419out:
420	free(record_function);
421	free(record_filename);
422	if (io.eof) {
423		dso__set_a2l(dso, NULL);
424		addr2line_subprocess_cleanup(a2l);
425	}
426	return ret;
427}
428
429void dso__free_a2l(struct dso *dso)
430{
431	struct child_process *a2l = dso__a2l(dso);
432
433	if (!a2l)
434		return;
435
436	addr2line_subprocess_cleanup(a2l);
437
438	dso__set_a2l(dso, NULL);
439}
~/ Read-only file preview.