From 6d913c4521221c404110d93a97fc9f4714ed43a6 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Mon, 31 Aug 2026 12:39:10 -0700 Subject: [PATCH] libbacktrace: report decl_lineno with MOREDATA Based on patch by Andi Kleen. * backtrace.h (struct backtrace_moredata): Add backtrace_decl_lineno field. * internal.h (BACKTRACE_MOREDATA_VERSION): Change to 4. * dwarf.c (struct function): Add decl_lineno field. (call_callback): Add decl_lineno parameter. Change all callers. (read_referenced_name_from_attr): Likewise. (read_referenced_name): Likewise. (report_inlined_functions): Likewise. (read_function_entry): Set decl_lineno field. (dwarf_lookup_pc): Handle decl_lineno across inlines. * mdtest.c (struct mdinfo): Define. (struct mddata): Define. (md_callback_one): Adjust for mddata. (md_callback_three): Likewise. (test1, f2): Reformat for simpler line number testing. (f3): Test that decl_lineno is correct. (f13): Adjust for mddata. --- backtrace.h | 5 ++- dwarf.c | 73 +++++++++++++++++++++---------- internal.h | 2 +- mdtest.c | 124 +++++++++++++++++++++++++++++++++++++--------------- 4 files changed, 142 insertions(+), 62 deletions(-) diff --git a/backtrace.h b/backtrace.h index 6b5a695..b65ae57 100644 --- a/backtrace.h +++ b/backtrace.h @@ -228,7 +228,7 @@ struct backtrace_moredata possible that future versions of libbacktrace will add new fields and increment the version number accordingly. There is no plan to remove fields from this struct. Thus the current value of the - version field will be 3. */ + version field will be 4. */ int backtrace_version; /* The DATA value passed to whatever function is calling the callback (backtrace_full, backtrace_pcinfo, or backtrace_syminfo). */ @@ -236,6 +236,9 @@ struct backtrace_moredata /* The DWARF discriminator. This is zero if there is none. See https://wiki.dwarfstd.org/Path_Discriminators.md. */ unsigned int backtrace_discriminator; + /* The line number of the start of the function declaration or + definition. This is 0 if not available. */ + int backtrace_decl_lineno; }; #ifdef __cplusplus diff --git a/dwarf.c b/dwarf.c index 85a4554..7cde3db 100644 --- a/dwarf.c +++ b/dwarf.c @@ -602,6 +602,8 @@ struct function /* If this is an inlined function, the discriminator of the call site. */ unsigned int caller_disc; + /* The line number of the declaration. */ + int decl_lineno; /* Map PC ranges to inlined functions. */ struct function_addrs *function_addrs; size_t function_addrs_count; @@ -1071,7 +1073,7 @@ read_initial_length (struct dwarf_buf *buf, int *is_dwarf64) static int call_callback (struct backtrace_state *state, backtrace_full_callback callback, void *data, uintptr_t pc, const char *filename, int lineno, - const char *function, unsigned int disc) + const char *function, unsigned int disc, int decl_lineno) { if (!state->moredata) return callback (data, pc, filename, lineno, function); @@ -1083,6 +1085,7 @@ call_callback (struct backtrace_state *state, backtrace_full_callback callback, md.backtrace_version = BACKTRACE_MOREDATA_VERSION; md.backtrace_data = data; md.backtrace_discriminator = disc; + md.backtrace_decl_lineno = decl_lineno; return callback ((void *) &md, pc, filename, lineno, function); } } @@ -3579,15 +3582,17 @@ read_line_info (struct backtrace_state *state, struct dwarf_data *ddata, static const char *read_referenced_name (struct dwarf_data *, struct unit *, uint64_t, backtrace_error_callback, - void *); + void *, int *); -/* Read the name of a function from a DIE referenced by ATTR with VAL. */ +/* Read the name of a function from a DIE referenced by ATTR with VAL. + This sets *DECL_LINENO to the decl_line attribute of the DIE if + available. */ static const char * read_referenced_name_from_attr (struct dwarf_data *ddata, struct unit *u, struct attr *attr, struct attr_val *val, backtrace_error_callback error_callback, - void *data) + void *data, int *decl_lineno) { switch (attr->name) { @@ -3610,12 +3615,14 @@ read_referenced_name_from_attr (struct dwarf_data *ddata, struct unit *u, return NULL; uint64_t offset = val->u.uint - unit->low_offset; - return read_referenced_name (ddata, unit, offset, error_callback, data); + return read_referenced_name (ddata, unit, offset, error_callback, data, + decl_lineno); } if (val->encoding == ATTR_VAL_UINT || val->encoding == ATTR_VAL_REF_UNIT) - return read_referenced_name (ddata, u, val->u.uint, error_callback, data); + return read_referenced_name (ddata, u, val->u.uint, error_callback, data, + decl_lineno); if (val->encoding == ATTR_VAL_REF_ALT_INFO) { @@ -3627,7 +3634,7 @@ read_referenced_name_from_attr (struct dwarf_data *ddata, struct unit *u, uint64_t offset = val->u.uint - alt_unit->low_offset; return read_referenced_name (ddata->altlink, alt_unit, offset, - error_callback, data); + error_callback, data, decl_lineno); } return NULL; @@ -3635,12 +3642,13 @@ read_referenced_name_from_attr (struct dwarf_data *ddata, struct unit *u, /* Read the name of a function from a DIE referenced by a DW_AT_abstract_origin or DW_AT_specification tag. OFFSET is within - the same compilation unit. */ + the same compilation unit. This sets *DECL_LINENO to the function's + decl_line attribute if available. */ static const char * read_referenced_name (struct dwarf_data *ddata, struct unit *u, uint64_t offset, backtrace_error_callback error_callback, - void *data) + void *data, int *decl_lineno) { struct dwarf_buf unit_buf; uint64_t code; @@ -3732,12 +3740,18 @@ read_referenced_name (struct dwarf_data *ddata, struct unit *u, const char *name; name = read_referenced_name_from_attr (ddata, u, &abbrev->attrs[i], - &val, error_callback, data); + &val, error_callback, data, + decl_lineno); if (name != NULL) ret = name; } break; + case DW_AT_decl_line: + if (val.encoding == ATTR_VAL_UINT) + *decl_lineno = (int) val.u.uint; + break; + default: break; } @@ -3890,6 +3904,11 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, function->caller_disc = val.u.uint; break; + case DW_AT_decl_line: + if (val.encoding == ATTR_VAL_UINT) + function->decl_lineno = (int) val.u.uint; + break; + case DW_AT_call_line: if (val.encoding == ATTR_VAL_UINT) function->caller_lineno = val.u.uint; @@ -3907,7 +3926,8 @@ read_function_entry (struct backtrace_state *state, struct dwarf_data *ddata, name = read_referenced_name_from_attr (ddata, u, &abbrev->attrs[i], &val, - error_callback, data); + error_callback, data, + &function->decl_lineno); if (name != NULL) function->name = name; } @@ -4130,15 +4150,17 @@ read_function_info (struct backtrace_state *state, struct dwarf_data *ddata, } /* See if PC is inlined in FUNCTION. If it is, print out the inlined - information, and update FILENAME, LINENO, and DISC for the caller. - Returns whatever CALLBACK returns, or 0 to keep going. */ + information, and update FILENAME, LINENO, DISC, and DECL_LINENO for + the caller. Returns whatever CALLBACK returns, or 0 to keep + going. */ static int report_inlined_functions (struct backtrace_state *state, uintptr_t pc, struct function *function, backtrace_full_callback callback, void *data, const char **filename, int *lineno, - unsigned int *disc) + unsigned int *disc, + int *decl_lineno) { struct function_addrs *p; struct function_addrs *match; @@ -4191,13 +4213,13 @@ report_inlined_functions (struct backtrace_state *state, uintptr_t pc, /* Report any calls inlined into this one. */ ret = report_inlined_functions (state, pc, inlined, callback, data, - filename, lineno, disc); + filename, lineno, disc, decl_lineno); if (ret != 0) return ret; /* Report this inlined call. */ ret = call_callback (state, callback, data, pc, *filename, *lineno, - inlined->name, *disc); + inlined->name, *disc, *decl_lineno); if (ret != 0) return ret; @@ -4206,6 +4228,7 @@ report_inlined_functions (struct backtrace_state *state, uintptr_t pc, *filename = inlined->caller_filename; *lineno = inlined->caller_lineno; *disc = inlined->caller_disc; + *decl_lineno = inlined->decl_lineno; return 0; } @@ -4233,6 +4256,7 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, const char *filename; int lineno; unsigned int disc; + int decl_lineno; int ret; *found = 1; @@ -4374,7 +4398,7 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, if (new_data) return dwarf_lookup_pc (state, ddata, pc, callback, error_callback, data, found); - return call_callback (state, callback, data, pc, NULL, 0, NULL, 0); + return call_callback (state, callback, data, pc, NULL, 0, NULL, 0, 0); } /* Search for PC within this unit. */ @@ -4422,14 +4446,14 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, } return call_callback (state, callback, data, pc, entry->u->abs_filename, - 0, NULL, 0); + 0, NULL, 0, 0); } /* Search for function name within this unit. */ if (entry->u->function_addrs_count == 0) return call_callback (state, callback, data, pc, ln->filename, ln->lineno, - NULL, ln->disc); + NULL, ln->disc, 0); p = ((struct function_addrs *) bsearch (&pc, entry->u->function_addrs, @@ -4438,7 +4462,7 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, function_addrs_search)); if (p == NULL) return call_callback (state, callback, data, pc, ln->filename, ln->lineno, - NULL, ln->disc); + NULL, ln->disc, 0); /* Here pc >= p->low && pc < (p + 1)->low. The function_addrs are sorted by low, so if pc > p->low we are at the end of a range of @@ -4463,21 +4487,22 @@ dwarf_lookup_pc (struct backtrace_state *state, struct dwarf_data *ddata, } if (fmatch == NULL) return call_callback (state, callback, data, pc, ln->filename, ln->lineno, - NULL, ln->disc); + NULL, ln->disc, 0); function = fmatch->function; filename = ln->filename; lineno = ln->lineno; disc = ln->disc; + decl_lineno = function->decl_lineno; ret = report_inlined_functions (state, pc, function, callback, data, - &filename, &lineno, &disc); + &filename, &lineno, &disc, &decl_lineno); if (ret != 0) return ret; return call_callback (state, callback, data, pc, filename, lineno, - function->name, disc); + function->name, disc, decl_lineno); } @@ -4527,7 +4552,7 @@ dwarf_fileline (struct backtrace_state *state, uintptr_t pc, /* FIXME: See if any libraries have been dlopen'ed. */ - return call_callback (state, callback, data, pc, NULL, 0, NULL, 0); + return call_callback (state, callback, data, pc, NULL, 0, NULL, 0, 0); } /* Initialize our data structures from the DWARF debug info for a diff --git a/internal.h b/internal.h index 355d2a3..26b41fb 100644 --- a/internal.h +++ b/internal.h @@ -428,6 +428,6 @@ extern int backtrace_uncompress_lzma (struct backtrace_state *, size_t *uncompressed_size); /* Current expected backtrace_moredata version. */ -#define BACKTRACE_MOREDATA_VERSION (3) +#define BACKTRACE_MOREDATA_VERSION (4) #endif diff --git a/mdtest.c b/mdtest.c index 5f6665f..c7b3b36 100644 --- a/mdtest.c +++ b/mdtest.c @@ -48,6 +48,22 @@ POSSIBILITY OF SUCH DAMAGE. */ #include "testlib.h" +/* Used to collect moredata info from backtraces. */ + +struct mdinfo +{ + int decl_lineno; +}; + +/* We pass a pointer to this as the data field to the backtrace + functions. */ + +struct mddata +{ + struct bdata bdata; + struct mdinfo *mdall; +}; + /* backtrace_full_callback with moredata flag. */ static int @@ -55,15 +71,21 @@ md_callback_one (void *vdata, uintptr_t pc, const char *filename, int lineno, const char *function) { struct backtrace_moredata *md = (struct backtrace_moredata *) vdata; - struct bdata *data = (struct bdata *) md->backtrace_data; + struct mddata *mddata = (struct mddata *) md->backtrace_data; + struct bdata *data = &mddata->bdata; if (md->backtrace_version != BACKTRACE_MOREDATA_VERSION) { - fprintf (stderr, "md_callback_one: wrong moredata version: got %u, want %u\n", md->backtrace_version, BACKTRACE_MOREDATA_VERSION); + fprintf (stderr, + "md_callback_one: wrong moredata version: got %u, want %u\n", + md->backtrace_version, BACKTRACE_MOREDATA_VERSION); data->failed = 1; return 1; } + if (data->index < data->max) + mddata->mdall[data->index].decl_lineno = md->backtrace_decl_lineno; + return callback_one ((void *) data, pc, filename, lineno, function); } @@ -78,7 +100,19 @@ md_callback_three (void *vdata, uintptr_t pc, const char *symname, if (md->backtrace_version != BACKTRACE_MOREDATA_VERSION) { - fprintf (stderr, "md_callback_one: wrong moredata version: got %u, want %u\n", md->backtrace_version, BACKTRACE_MOREDATA_VERSION); + fprintf (stderr, + "md_callback_three: wrong moredata version: got %u, want %u\n", + md->backtrace_version, BACKTRACE_MOREDATA_VERSION); + data->failed = 1; + return; + } + + if (md->backtrace_decl_lineno != 0) + { + fprintf (stderr, + "md_callback_three: unexpected non-zero decl_lineno %d\n", + md->backtrace_decl_lineno); + data->failed = 1; return; } @@ -91,17 +125,11 @@ static int test1 (void) __attribute__ ((noinline, noclone, optnone, unused)); static int f2 (int) __attribute__ ((noinline, noclone)); static int f3 (int, int) __attribute__ ((noinline, noclone)); -static int -test1 (void) -{ - /* Returning a value here and elsewhere avoids a tailcall which - would mess up the backtrace. */ +static int test1 (void) { return f2 (__LINE__) + 1; } -static int -f2 (int f1line) -{ +static int f2 (int f1line) { return f3 (f1line, __LINE__) + 2; } @@ -109,14 +137,16 @@ static int f3 (int f1line, int f2line) { struct info all[20]; - struct bdata data; + struct mdinfo mdall[20]; + struct mddata data; int f3line; int i; - data.all = &all[0]; - data.index = 0; - data.max = 20; - data.failed = 0; + data.bdata.all = &all[0]; + data.bdata.index = 0; + data.bdata.max = 20; + data.bdata.failed = 0; + data.mdall = &mdall[0]; f3line = __LINE__ + 1; i = backtrace_full (state, 0, md_callback_one, error_callback_one, &data); @@ -124,24 +154,44 @@ f3 (int f1line, int f2line) if (i != 0) { fprintf (stderr, "test1: unexpected return value %d\n", i); - data.failed = 1; + data.bdata.failed = 1; } - if (data.index < 3) + if (data.bdata.index < 3) { fprintf (stderr, "test1: not enough frames; got %zu, expected at least 3\n", - data.index); - data.failed = 1; + data.bdata.index); + data.bdata.failed = 1; } - check ("test1", 0, all, f3line, "f3", "mdtest.c", &data.failed); - check ("test1", 1, all, f2line, "f2", "mdtest.c", &data.failed); - check ("test1", 2, all, f1line, "test1", "mdtest.c", &data.failed); + check ("test1", 0, all, f3line, "f3", "mdtest.c", &data.bdata.failed); + check ("test1", 1, all, f2line, "f2", "mdtest.c", &data.bdata.failed); + check ("test1", 2, all, f1line, "test1", "mdtest.c", &data.bdata.failed); - printf ("%s: backtrace_full noinline\n", data.failed ? "FAIL" : "PASS"); + if (!data.bdata.failed) + { + /* This assumes a particular pattern for the definitions of f1 and + f2, above. */ + if (data.mdall[1].decl_lineno != f2line - 1) + { + fprintf (stderr, + "test1: incorrect f2 decl_lineno; got %d, expected %d\n", + data.mdall[1].decl_lineno, f2line - 1); + data.bdata.failed = 1; + } + if (data.mdall[2].decl_lineno != f1line - 1) + { + fprintf (stderr, + "test1: incorrect f1 decl_lineno; got %d, expected %d\n", + data.mdall[2].decl_lineno, f1line - 1); + data.bdata.failed = 1; + } + } - if (data.failed) + printf ("%s: backtrace_full noinline\n", data.bdata.failed ? "FAIL" : "PASS"); + + if (data.bdata.failed) ++failures; return failures; @@ -169,14 +219,16 @@ static inline int f13 (int f1line, int f2line) { struct info all[20]; - struct bdata data; + struct mdinfo mdall[20]; + struct mddata data; int f3line; int i; - data.all = &all[0]; - data.index = 0; - data.max = 20; - data.failed = 0; + data.bdata.all = &all[0]; + data.bdata.index = 0; + data.bdata.max = 20; + data.bdata.failed = 0; + data.mdall = &mdall[0]; f3line = __LINE__ + 1; i = backtrace_full (state, 0, md_callback_one, error_callback_one, &data); @@ -184,16 +236,16 @@ f13 (int f1line, int f2line) if (i != 0) { fprintf (stderr, "test2: unexpected return value %d\n", i); - data.failed = 1; + data.bdata.failed = 1; } - check ("test2", 0, all, f3line, "f13", "mdtest.c", &data.failed); - check ("test2", 1, all, f2line, "f12", "mdtest.c", &data.failed); - check ("test2", 2, all, f1line, "test2", "mdtest.c", &data.failed); + check ("test2", 0, all, f3line, "f13", "mdtest.c", &data.bdata.failed); + check ("test2", 1, all, f2line, "f12", "mdtest.c", &data.bdata.failed); + check ("test2", 2, all, f1line, "test2", "mdtest.c", &data.bdata.failed); - printf ("%s: backtrace_full inline\n", data.failed ? "FAIL" : "PASS"); + printf ("%s: backtrace_full inline\n", data.bdata.failed ? "FAIL" : "PASS"); - if (data.failed) + if (data.bdata.failed) ++failures; return failures;