Based on patch from Andi Kleen.
* backtrace.h (backtrace_create_state): Rewrite comment to change
threaded parameter to flags.
(backtrace_full_callback, backtrace_syminfo_callback): Document
behavior change if moredata flag is set.
(struct backtrace_moredata): Define.
* backtrace-supported.h.in (BACKTRACE_SUPPORTS_MOREDATA): Define.
* internal.h (struct backtrace_state): Add moredata field.
(BACKTRACE_MOREDATA_VERSION): Define.
* state.c (backtrace_create_state): Change threaded parameter to
flags. Set state moredata field based on flags.
* dwarf.c (struct line): Add disc field.
(struct function): Add caller_disc field.
(call_callback): New static function.
(add_line): Add disc parameter. Change all callers. Store disc
in new line value.
(read_line_program): Track discriminator value.
(read_line_info): Initialize disc field in final line entry.
(read_function_entry): Handle DW_AT_GNU_discriminator.
(report_inlined_functions): Add disc parameter. Change all
callers. Call call_callback rather than calling callback
directly.
(dwarf_lookup_pc): Handle inlined discriminators. Call
call_callback rather than calling callback directly.
(dwarf_fileline): Call call_callback rather than calling callback
directly.
* elf.c (elf_syminfo): Pass moredata if requested.
* macho.c (macho_syminfo): Likewise.
* pecoff.c (coff_syminfo): Likewise.
* xcoff.c (xcoff_syminfo): Likewise.
(xcoff_lookup_pc): Likewise.
* backtrace.c: #include <string.h>.
(unwind): Pass moredata if requested.
* unknown.c: #include <string.h>.
(unknown_fileline): Pass moredata if requested.
* fileline.c (backtrace_syminfo_to_full_callback): Add comment
about moredata.
* mdtest.c: New test file.
* Makefile.am (mdtest_SOURCES): Define.
(mdtest_CFLAGS, mdtest_LDFLAGS, mdtest_LDADD): Define.
(BUILDTESTS): Add mdtest.
* Makefile.in: Regenerate.
endif HAVE_DWZ
+mdtest_SOURCES = mdtest.c testlib.c
+mdtest_CFLAGS = $(libbacktrace_TEST_CFLAGS) -O
+mdtest_LDFLAGS = $(libbacktrace_testing_ldflags)
+mdtest_LDADD = libbacktrace.la
+
+BUILDTESTS += mdtest
+
stest_SOURCES = stest.c
stest_CFLAGS = $(libbacktrace_TEST_CFLAGS)
stest_LDFLAGS = $(libbacktrace_testing_ldflags)
@HAVE_BUILDID_TRUE@@HAVE_DWZ_TRUE@@HAVE_ELF_TRUE@@HAVE_OBJCOPY_DEBUGLINK_TRUE@@NATIVE_TRUE@am__append_8 = b3test
@HAVE_BUILDID_TRUE@@HAVE_DWZ_TRUE@@HAVE_ELF_TRUE@@HAVE_OBJCOPY_DEBUGLINK_TRUE@@NATIVE_TRUE@am__append_9 = b3test_dwz_buildid b3test_dwz_buildidfull
@HAVE_ELF_TRUE@@NATIVE_TRUE@am__append_10 = btest_lto
-@NATIVE_TRUE@am__append_11 = btest_alloc stest stest_alloc
+@NATIVE_TRUE@am__append_11 = btest_alloc mdtest stest stest_alloc
@HAVE_DWZ_TRUE@@NATIVE_TRUE@am__append_12 = btest_dwz
@HAVE_DWZ_TRUE@@HAVE_OBJCOPY_DEBUGLINK_TRUE@@NATIVE_TRUE@am__append_13 = btest_dwz_gnudebuglink
@HAVE_ELF_TRUE@@HAVE_ZLIB_TRUE@@NATIVE_TRUE@am__append_14 = -lz
@NATIVE_TRUE@ test_unknown$(EXEEXT) unittest$(EXEEXT) \
@NATIVE_TRUE@ unittest_alloc$(EXEEXT) btest$(EXEEXT)
@HAVE_ELF_TRUE@@NATIVE_TRUE@am__EXEEXT_6 = btest_lto$(EXEEXT)
-@NATIVE_TRUE@am__EXEEXT_7 = btest_alloc$(EXEEXT) stest$(EXEEXT) \
-@NATIVE_TRUE@ stest_alloc$(EXEEXT)
+@NATIVE_TRUE@am__EXEEXT_7 = btest_alloc$(EXEEXT) mdtest$(EXEEXT) \
+@NATIVE_TRUE@ stest$(EXEEXT) stest_alloc$(EXEEXT)
@HAVE_ELF_TRUE@@NATIVE_TRUE@am__EXEEXT_8 = ztest$(EXEEXT) \
@HAVE_ELF_TRUE@@NATIVE_TRUE@ ztest_alloc$(EXEEXT) \
@HAVE_ELF_TRUE@@NATIVE_TRUE@ zstdtest$(EXEEXT) \
m2test_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(m2test_CFLAGS) $(CFLAGS) \
$(m2test_LDFLAGS) $(LDFLAGS) -o $@
+@NATIVE_TRUE@am_mdtest_OBJECTS = mdtest-mdtest.$(OBJEXT) \
+@NATIVE_TRUE@ mdtest-testlib.$(OBJEXT)
+mdtest_OBJECTS = $(am_mdtest_OBJECTS)
+@NATIVE_TRUE@mdtest_DEPENDENCIES = libbacktrace.la
+mdtest_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(mdtest_CFLAGS) $(CFLAGS) \
+ $(mdtest_LDFLAGS) $(LDFLAGS) -o $@
@NATIVE_TRUE@am_mtest_OBJECTS = mtest-mtest.$(OBJEXT) \
@NATIVE_TRUE@ mtest-testlib.$(OBJEXT)
mtest_OBJECTS = $(am_mtest_OBJECTS)
$(ctestg_alloc_SOURCES) $(ctestzstd_SOURCES) \
$(ctestzstd_alloc_SOURCES) $(dwarf5_SOURCES) \
$(dwarf5_alloc_SOURCES) $(edtest_SOURCES) \
- $(edtest_alloc_SOURCES) $(m2test_SOURCES) $(mtest_SOURCES) \
- $(stest_SOURCES) $(stest_alloc_SOURCES) $(test_elf_32_SOURCES) \
- $(test_elf_64_SOURCES) $(test_macho_SOURCES) \
- $(test_pecoff_SOURCES) $(test_unknown_SOURCES) \
- $(test_xcoff_32_SOURCES) $(test_xcoff_64_SOURCES) \
- $(ttest_SOURCES) $(ttest_alloc_SOURCES) $(unittest_SOURCES) \
+ $(edtest_alloc_SOURCES) $(m2test_SOURCES) $(mdtest_SOURCES) \
+ $(mtest_SOURCES) $(stest_SOURCES) $(stest_alloc_SOURCES) \
+ $(test_elf_32_SOURCES) $(test_elf_64_SOURCES) \
+ $(test_macho_SOURCES) $(test_pecoff_SOURCES) \
+ $(test_unknown_SOURCES) $(test_xcoff_32_SOURCES) \
+ $(test_xcoff_64_SOURCES) $(ttest_SOURCES) \
+ $(ttest_alloc_SOURCES) $(unittest_SOURCES) \
$(unittest_alloc_SOURCES) $(xztest_SOURCES) \
$(xztest_alloc_SOURCES) $(zstdtest_SOURCES) \
$(zstdtest_alloc_SOURCES) $(ztest_SOURCES) \
@NATIVE_TRUE@btest_alloc_CFLAGS = $(libbacktrace_TEST_CFLAGS)
@NATIVE_TRUE@btest_alloc_LDFLAGS = $(libbacktrace_testing_ldflags)
@NATIVE_TRUE@btest_alloc_LDADD = libbacktrace_alloc.la
+@NATIVE_TRUE@mdtest_SOURCES = mdtest.c testlib.c
+@NATIVE_TRUE@mdtest_CFLAGS = $(libbacktrace_TEST_CFLAGS) -O
+@NATIVE_TRUE@mdtest_LDFLAGS = $(libbacktrace_testing_ldflags)
+@NATIVE_TRUE@mdtest_LDADD = libbacktrace.la
@NATIVE_TRUE@stest_SOURCES = stest.c
@NATIVE_TRUE@stest_CFLAGS = $(libbacktrace_TEST_CFLAGS)
@NATIVE_TRUE@stest_LDFLAGS = $(libbacktrace_testing_ldflags)
@rm -f m2test$(EXEEXT)
$(AM_V_CCLD)$(m2test_LINK) $(m2test_OBJECTS) $(m2test_LDADD) $(LIBS)
+mdtest$(EXEEXT): $(mdtest_OBJECTS) $(mdtest_DEPENDENCIES) $(EXTRA_mdtest_DEPENDENCIES)
+ @rm -f mdtest$(EXEEXT)
+ $(AM_V_CCLD)$(mdtest_LINK) $(mdtest_OBJECTS) $(mdtest_LDADD) $(LIBS)
+
mtest$(EXEEXT): $(mtest_OBJECTS) $(mtest_DEPENDENCIES) $(EXTRA_mtest_DEPENDENCIES)
@rm -f mtest$(EXEEXT)
$(AM_V_CCLD)$(mtest_LINK) $(mtest_OBJECTS) $(mtest_LDADD) $(LIBS)
m2test-testlib.obj: testlib.c
$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(m2test_CFLAGS) $(CFLAGS) -c -o m2test-testlib.obj `if test -f 'testlib.c'; then $(CYGPATH_W) 'testlib.c'; else $(CYGPATH_W) '$(srcdir)/testlib.c'; fi`
+mdtest-mdtest.o: mdtest.c
+ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mdtest_CFLAGS) $(CFLAGS) -c -o mdtest-mdtest.o `test -f 'mdtest.c' || echo '$(srcdir)/'`mdtest.c
+
+mdtest-mdtest.obj: mdtest.c
+ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mdtest_CFLAGS) $(CFLAGS) -c -o mdtest-mdtest.obj `if test -f 'mdtest.c'; then $(CYGPATH_W) 'mdtest.c'; else $(CYGPATH_W) '$(srcdir)/mdtest.c'; fi`
+
+mdtest-testlib.o: testlib.c
+ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mdtest_CFLAGS) $(CFLAGS) -c -o mdtest-testlib.o `test -f 'testlib.c' || echo '$(srcdir)/'`testlib.c
+
+mdtest-testlib.obj: testlib.c
+ $(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mdtest_CFLAGS) $(CFLAGS) -c -o mdtest-testlib.obj `if test -f 'testlib.c'; then $(CYGPATH_W) 'testlib.c'; else $(CYGPATH_W) '$(srcdir)/testlib.c'; fi`
+
mtest-mtest.o: mtest.c
$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(mtest_CFLAGS) $(CFLAGS) -c -o mtest-mtest.o `test -f 'mtest.c' || echo '$(srcdir)/'`mtest.c
--log-file $$b.log --trs-file $$b.trs \
$(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
"$$tst" $(AM_TESTS_FD_REDIRECT)
+mdtest.log: mdtest$(EXEEXT)
+ @p='mdtest$(EXEEXT)'; \
+ b='mdtest'; \
+ $(am__check_pre) $(LOG_DRIVER) --test-name "$$f" \
+ --log-file $$b.log --trs-file $$b.trs \
+ $(am__common_driver_flags) $(AM_LOG_DRIVER_FLAGS) $(LOG_DRIVER_FLAGS) -- $(LOG_COMPILE) \
+ "$$tst" $(AM_TESTS_FD_REDIRECT)
stest.log: stest$(EXEEXT)
@p='stest$(EXEEXT)'; \
b='stest'; \
be #include'd to see whether the backtrace library will be able to
get a backtrace and produce symbolic information. */
-
/* BACKTRACE_SUPPORTED will be #define'd as 1 if the backtrace library
should work, 0 if it will not. Libraries may #include this to make
other arrangements. */
#define BACKTRACE_SUPPORTS_THREADS @BACKTRACE_SUPPORTS_THREADS@
-/* BACKTRACE_SUPPORTS_DATA will be #defined'd as 1 if the backtrace_syminfo
+/* BACKTRACE_SUPPORTS_DATA will be #define'd as 1 if the backtrace_syminfo
will work for variables. It will always work for functions. */
#define BACKTRACE_SUPPORTS_DATA @BACKTRACE_SUPPORTS_DATA@
+
+/* BACKTRACE_SUPPORTS_MOREDATA will be #define'd as 1 if
+ backtrace_create_state supports setting the MOREDATA flag. */
+
+#define BACKTRACE_SUPPORTS_MOREDATA 1
#include "config.h"
+#include <string.h>
#include <sys/types.h>
#include "unwind.h"
if (!ip_before_insn)
--pc;
- if (!bdata->can_alloc)
- bdata->ret = bdata->callback (bdata->data, pc, NULL, 0, NULL);
- else
+ if (bdata->can_alloc)
bdata->ret = backtrace_pcinfo (bdata->state, pc, bdata->callback,
bdata->error_callback, bdata->data);
+ else
+ {
+ if (!bdata->state->moredata)
+ bdata->ret = bdata->callback (bdata->data, pc, NULL, 0, NULL);
+ else
+ {
+ struct backtrace_moredata md;
+
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = BACKTRACE_MOREDATA_VERSION;
+ md.backtrace_data = bdata->data;
+ bdata->ret = bdata->callback (&md, pc, NULL, 0, NULL);
+ }
+ }
+
if (bdata->ret != 0)
return _URC_END_OF_STACK;
/* Create state information for the backtrace routines. This must be
called before any of the other routines, and its return value must
- be passed to all of the other routines. FILENAME is the path name
- of the executable file; if it is NULL the library will try
- system-specific path names. If not NULL, FILENAME must point to a
- permanent buffer. If THREADED is non-zero the state may be
- accessed by multiple threads simultaneously, and the library will
- use appropriate atomic operations. If THREADED is zero the state
- may only be accessed by one thread at a time. This returns a state
- pointer on success, NULL on error. If an error occurs, this will
- call the ERROR_CALLBACK routine.
+ be passed to all of the other routines.
+
+ FILENAME is the path name of the executable file; if it is NULL the
+ library will try system-specific path names. If not NULL, FILENAME
+ must point to a permanent buffer.
+
+ FLAGS passes flags as bits in an int value:
+ 1: THREADED
+ 2: MOREDATA
+
+ If (FLAGS & 1) != 0 the THREADED flag is set. If this flag is set,
+ the state may be accessed by multiple threads simultaneously, and the
+ library will use appropriate atomic operations. If THREADED is not
+ set the state may only be accessed by one thread at a time.
+
+ If (FLAGS & 2) != 0 the MOREDATA flag is set. If this flag is set,
+ then backtrace_full_callback and backtrace_symbol_callback will not
+ pass the DATA argument as the user-specified DATA value, but will
+ instead pass it as a pointer to a backtrace_moredata struct. This is
+ a backward compatible approach to getting more data from the various
+ backtrace functions.
+
+ Historical note: in previous versions (before July, 2026) the FLAGS
+ argument was named THREADED, and passing non-zero for THREADED was
+ documented as doing what setting the THREADED flag does today. In
+ practice all callers passed either 0 or 1, so the new semantics of
+ the flag do not affect users of old versions of the library.
+ However, code that passes the MOREDATA flag must ensure that it is
+ using a new version of the library. The backtrace-supported.h file
+ will #define BACKTRACE_SUPPORTS_MOREDATA as 1 for versions of
+ libbacktrace that support the MOREDATA flag.
+
+ The backtrace_create_state function returns a state pointer on
+ success, NULL on error. If an error occurs, it will call the
+ ERROR_CALLBACK routine before returning.
Calling this function allocates resources that cannot be freed.
There is no backtrace_free_state function. The state is used to
backtrace_error_callback error_callback, void *data);
/* The type of the callback argument to the backtrace_full function.
- DATA is the argument passed to backtrace_full. PC is the program
- counter. FILENAME is the name of the file containing PC, or NULL
- if not available. LINENO is the line number in FILENAME containing
- PC, or 0 if not available. FUNCTION is the name of the function
- containing PC, or NULL if not available. This should return 0 to
- continuing tracing. The FILENAME and FUNCTION buffers may become
- invalid after this function returns. */
+ DATA is either the argument passed to backtrace_full (if the MOREDATA
+ flag was not set when calling backtrace_create_state) or a pointer to
+ a backtrace_moredata struct. PC is the program counter. FILENAME is
+ the name of the file containing PC, or NULL if not available. LINENO
+ is the line number in FILENAME containing PC, or 0 if not available.
+ FUNCTION is the name of the function containing PC, or NULL if not
+ available. This should return 0 to continuing tracing. The FILENAME
+ and FUNCTION buffers may become invalid after this function
+ returns. */
typedef int (*backtrace_full_callback) (void *data, uintptr_t pc,
const char *filename, int lineno,
backtrace_error_callback error_callback,
void *data);
-/* The type of the callback argument to backtrace_syminfo. DATA and
- PC are the arguments passed to backtrace_syminfo. SYMNAME is the
- name of the symbol for the corresponding code. SYMVAL is the
- value and SYMSIZE is the size of the symbol. SYMNAME will be NULL
- if no error occurred but the symbol could not be found. */
+/* The type of the callback argument to backtrace_syminfo. DATA is
+ either the argument passed to backtrace_full (if the MOREDATA flag
+ was not set when calling backtrace_create_state) or a pointer to a
+ backtrace_moredata struct. PC is the the argument passed to
+ backtrace_syminfo. SYMNAME is the name of the symbol for the
+ corresponding code. SYMVAL is the value and SYMSIZE is the size of
+ the symbol. SYMNAME will be NULL if no error occurred but the symbol
+ could not be found. */
typedef void (*backtrace_syminfo_callback) (void *data, uintptr_t pc,
const char *symname,
backtrace_error_callback error_callback,
void *data);
+/* The type of the value that the DATA argument passed to
+ backtrace_full_callback or backtrace_syminfo_callback points to if
+ the MOREDATA flag is set in the call to backtrace_create_state.
+
+ The backtrace_moredata value will only be valid for the lifetime of
+ the callback; the callback may copy the data out but must not save
+ the pointer it receives. */
+
+struct backtrace_moredata
+{
+ /* The version of this struct. The current expectation is that the
+ version number will be the number of fields in the struct. It's
+ 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. */
+ int backtrace_version;
+ /* The DATA value passed to whatever function is calling the callback
+ (backtrace_full, backtrace_pcinfo, or backtrace_syminfo). */
+ void *backtrace_data;
+ /* The DWARF discriminator. This is zero if there is none. See
+ https://wiki.dwarfstd.org/Path_Discriminators.md. */
+ unsigned int backtrace_discriminator;
+};
+
#ifdef __cplusplus
} /* End extern "C". */
#endif
const char *filename;
/* Line number. */
int lineno;
+ /* Discriminator. */
+ int disc;
/* Index of the object in the original array read from the DWARF
section, before it has been sorted. The index makes it possible
to use Quicksort and maintain stability. */
/* If this is an inlined function, the line number of the call
site. */
int caller_lineno;
+ /* If this is an inlined function, the discriminator of the call
+ site. */
+ unsigned int caller_disc;
/* Map PC ranges to inlined functions. */
struct function_addrs *function_addrs;
size_t function_addrs_count;
return len;
}
+/* Call the callback function, which differs based on the moredata state
+ flag. */
+
+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)
+{
+ if (!state->moredata)
+ return callback (data, pc, filename, lineno, function);
+ else
+ {
+ struct backtrace_moredata md;
+
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = BACKTRACE_MOREDATA_VERSION;
+ md.backtrace_data = data;
+ md.backtrace_discriminator = disc;
+ return callback ((void *) &md, pc, filename, lineno, function);
+ }
+}
+
/* Free an abbreviations structure. */
static void
static int
add_line (struct backtrace_state *state, struct dwarf_data *ddata,
- uintptr_t pc, const char *filename, int lineno,
+ uintptr_t pc, const char *filename, int lineno, int disc,
backtrace_error_callback error_callback, void *data,
struct line_vector *vec)
{
{
ln = (struct line *) vec->vec.base + (vec->count - 1);
if (pc == ln->pc && filename == ln->filename && lineno == ln->lineno)
- return 1;
+ {
+ /* We only care about the discriminator if moredata is true. */
+ if (!state->moredata)
+ return 1;
+ if (disc == ln->disc)
+ return 1;
+ }
}
ln = ((struct line *)
ln->filename = filename;
ln->lineno = lineno;
+ ln->disc = disc;
ln->idx = vec->count;
++vec->count;
const char *reset_filename;
const char *filename;
int lineno;
+ unsigned int disc;
address = 0;
op_index = 0;
reset_filename = "";
filename = reset_filename;
lineno = 1;
+ disc = 0;
while (line_buf->left > 0)
{
unsigned int op;
/ hdr->max_ops_per_insn);
op_index = (op_index + advance) % hdr->max_ops_per_insn;
lineno += hdr->line_base + (int) (op % hdr->line_range);
- add_line (state, ddata, address, filename, lineno,
+ add_line (state, ddata, address, filename, lineno, disc,
line_buf->error_callback, line_buf->data, vec);
+ disc = 0;
}
else if (op == DW_LNS_extended_op)
{
op_index = 0;
filename = reset_filename;
lineno = 1;
+ disc = 0;
break;
case DW_LNE_set_address:
address = read_address (line_buf, hdr->addrsize);
}
break;
case DW_LNE_set_discriminator:
- /* We don't care about discriminators. */
- read_uleb128 (line_buf);
+ disc = read_uleb128 (line_buf);
break;
default:
if (!advance (line_buf, len - 1))
switch (op)
{
case DW_LNS_copy:
- add_line (state, ddata, address, filename, lineno,
+ add_line (state, ddata, address, filename, lineno, disc,
line_buf->error_callback, line_buf->data, vec);
+ disc = 0;
break;
case DW_LNS_advance_pc:
{
ln->pc = (uintptr_t) -1;
ln->filename = NULL;
ln->lineno = 0;
+ ln->disc = 0;
ln->idx = 0;
if (!backtrace_vector_release (state, &vec.vec, error_callback, data))
}
break;
+ case DW_AT_GNU_discriminator:
+ if (val.encoding == ATTR_VAL_UINT)
+ function->caller_disc = val.u.uint;
+ break;
+
case DW_AT_call_line:
if (val.encoding == ATTR_VAL_UINT)
function->caller_lineno = val.u.uint;
}
/* See if PC is inlined in FUNCTION. If it is, print out the inlined
- information, and update FILENAME and LINENO for the caller.
+ information, and update FILENAME, LINENO, and DISC for the caller.
Returns whatever CALLBACK returns, or 0 to keep going. */
static int
-report_inlined_functions (uintptr_t pc, struct function *function,
+report_inlined_functions (struct backtrace_state *state, uintptr_t pc,
+ struct function *function,
backtrace_full_callback callback, void *data,
- const char **filename, int *lineno)
+ const char **filename, int *lineno,
+ unsigned int *disc)
{
struct function_addrs *p;
struct function_addrs *match;
inlined = match->function;
/* Report any calls inlined into this one. */
- ret = report_inlined_functions (pc, inlined, callback, data,
- filename, lineno);
+ ret = report_inlined_functions (state, pc, inlined, callback, data,
+ filename, lineno, disc);
if (ret != 0)
return ret;
/* Report this inlined call. */
- ret = callback (data, pc, *filename, *lineno, inlined->name);
+ ret = call_callback (state, callback, data, pc, *filename, *lineno,
+ inlined->name, *disc);
if (ret != 0)
return ret;
/* Our caller will report the caller of the inlined function; tell
- it the appropriate filename and line number. */
+ it the appropriate filename, line number, and discriminator. */
*filename = inlined->caller_filename;
*lineno = inlined->caller_lineno;
+ *disc = inlined->caller_disc;
return 0;
}
struct function *function;
const char *filename;
int lineno;
+ unsigned int disc;
int ret;
*found = 1;
if (new_data)
return dwarf_lookup_pc (state, ddata, pc, callback, error_callback,
data, found);
- return callback (data, pc, NULL, 0, NULL);
+ return call_callback (state, callback, data, pc, NULL, 0, NULL, 0);
}
/* Search for PC within this unit. */
entry->u->abs_filename = filename;
}
- return callback (data, pc, entry->u->abs_filename, 0, NULL);
+ return call_callback (state, callback, data, pc, entry->u->abs_filename,
+ 0, NULL, 0);
}
/* Search for function name within this unit. */
if (entry->u->function_addrs_count == 0)
- return callback (data, pc, ln->filename, ln->lineno, NULL);
+ return call_callback (state, callback, data, pc, ln->filename, ln->lineno,
+ NULL, ln->disc);
p = ((struct function_addrs *)
bsearch (&pc, entry->u->function_addrs,
sizeof (struct function_addrs),
function_addrs_search));
if (p == NULL)
- return callback (data, pc, ln->filename, ln->lineno, NULL);
+ return call_callback (state, callback, data, pc, ln->filename, ln->lineno,
+ NULL, ln->disc);
/* 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
--p;
}
if (fmatch == NULL)
- return callback (data, pc, ln->filename, ln->lineno, NULL);
+ return call_callback (state, callback, data, pc, ln->filename, ln->lineno,
+ NULL, ln->disc);
function = fmatch->function;
filename = ln->filename;
lineno = ln->lineno;
+ disc = ln->disc;
- ret = report_inlined_functions (pc, function, callback, data,
- &filename, &lineno);
+ ret = report_inlined_functions (state, pc, function, callback, data,
+ &filename, &lineno, &disc);
if (ret != 0)
return ret;
- return callback (data, pc, filename, lineno, function->name);
+ return call_callback (state, callback, data, pc, filename, lineno,
+ function->name, disc);
}
/* FIXME: See if any libraries have been dlopen'ed. */
- return callback (data, pc, NULL, 0, NULL);
+ return call_callback (state, callback, data, pc, NULL, 0, NULL, 0);
}
/* Initialize our data structures from the DWARF debug info for a
{
struct elf_syminfo_data *edata;
struct elf_symbol *sym = NULL;
+ void *mdata;
+ struct backtrace_moredata md;
if (!state->threaded)
{
}
}
+ if (!state->moredata)
+ mdata = data;
+ else
+ {
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = BACKTRACE_MOREDATA_VERSION;
+ md.backtrace_data = data;
+ mdata = (void *) &md;
+ }
+
if (sym == NULL)
- callback (data, addr, NULL, 0, 0);
+ callback (mdata, addr, NULL, 0, 0);
else
- callback (data, addr, sym->name, sym->address, sym->size);
+ callback (mdata, addr, sym->name, sym->address, sym->size);
}
/* Return whether FILENAME is a symlink. */
{
struct backtrace_call_full *bdata = (struct backtrace_call_full *) data;
+ /* If STATE->MOREDATA is set, then data will point to a
+ backtrace_moredata struct, which is what full_callback expects. */
+
bdata->ret = bdata->full_callback (bdata->full_data, pc, NULL, 0, symname);
}
const char *filename;
/* Non-zero if threaded. */
int threaded;
+ /* Non-zero if passing additional data. */
+ int moredata;
/* The master lock for fileline_fn, fileline_data, syminfo_fn,
syminfo_data, fileline_initialization_failed and everything the
data pointers point to. */
unsigned char **uncompressed,
size_t *uncompressed_size);
+/* Current expected backtrace_moredata version. */
+#define BACKTRACE_MOREDATA_VERSION (3)
+
#endif
{
struct macho_syminfo_data *sdata;
struct macho_symbol *sym;
+ void *mdata;
+ struct backtrace_moredata md;
sym = NULL;
if (!state->threaded)
}
}
+ if (!state->moredata)
+ mdata = data;
+ else
+ {
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = BACKTRACE_MOREDATA_VERSION;
+ md.backtrace_data = data;
+ mdata = (void *) &md;
+ }
+
if (sym == NULL)
- callback (data, addr, NULL, 0, 0);
+ callback (mdata, addr, NULL, 0, 0);
else
- callback (data, addr, sym->name, sym->address, 0);
+ callback (mdata, addr, sym->name, sym->address, 0);
}
/* Look through a fat file to find the relevant executable. Returns 1
--- /dev/null
+/* mdtest.c -- Test for libbacktrace library with moredata flag
+ Copyright (C) 2026 Free Software Foundation, Inc.
+ Written by Ian Lance Taylor.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ (1) Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ (2) Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+
+ (3) The name of the author may not be used to
+ endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE. */
+
+/* This program tests the externally visible interfaces of the
+ libbacktrace library. */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
+#include "filenames.h"
+
+#include "backtrace.h"
+#include "backtrace-supported.h"
+#include "internal.h"
+
+#include "testlib.h"
+
+/* backtrace_full_callback with moredata flag. */
+
+static int
+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;
+
+ 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);
+ data->failed = 1;
+ return 1;
+ }
+
+ return callback_one ((void *) data, pc, filename, lineno, function);
+}
+
+/* backtrace_syminfo_callback with moredata flag. */
+
+static void
+md_callback_three (void *vdata, uintptr_t pc, const char *symname,
+ uintptr_t symval, uintptr_t symsize)
+{
+ struct backtrace_moredata *md = (struct backtrace_moredata *) vdata;
+ struct symdata *data = (struct symdata *) md->backtrace_data;
+
+ 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);
+ return;
+ }
+
+ callback_three ((void *) data, pc, symname, symval, symsize);
+}
+
+/* Test the backtrace function with non-inlined functions. */
+
+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. */
+ return f2 (__LINE__) + 1;
+}
+
+static int
+f2 (int f1line)
+{
+ return f3 (f1line, __LINE__) + 2;
+}
+
+static int
+f3 (int f1line, int f2line)
+{
+ struct info all[20];
+ struct bdata data;
+ int f3line;
+ int i;
+
+ data.all = &all[0];
+ data.index = 0;
+ data.max = 20;
+ data.failed = 0;
+
+ f3line = __LINE__ + 1;
+ i = backtrace_full (state, 0, md_callback_one, error_callback_one, &data);
+
+ if (i != 0)
+ {
+ fprintf (stderr, "test1: unexpected return value %d\n", i);
+ data.failed = 1;
+ }
+
+ if (data.index < 3)
+ {
+ fprintf (stderr,
+ "test1: not enough frames; got %zu, expected at least 3\n",
+ data.index);
+ data.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);
+
+ printf ("%s: backtrace_full noinline\n", data.failed ? "FAIL" : "PASS");
+
+ if (data.failed)
+ ++failures;
+
+ return failures;
+}
+
+/* Test the backtrace function with inlined functions. */
+
+static inline int test2 (void) __attribute__ ((always_inline, unused));
+static inline int f12 (int) __attribute__ ((always_inline));
+static inline int f13 (int, int) __attribute__ ((always_inline));
+
+static inline int
+test2 (void)
+{
+ return f12 (__LINE__) + 1;
+}
+
+static inline int
+f12 (int f1line)
+{
+ return f13 (f1line, __LINE__) + 2;
+}
+
+static inline int
+f13 (int f1line, int f2line)
+{
+ struct info all[20];
+ struct bdata data;
+ int f3line;
+ int i;
+
+ data.all = &all[0];
+ data.index = 0;
+ data.max = 20;
+ data.failed = 0;
+
+ f3line = __LINE__ + 1;
+ i = backtrace_full (state, 0, md_callback_one, error_callback_one, &data);
+
+ if (i != 0)
+ {
+ fprintf (stderr, "test2: unexpected return value %d\n", i);
+ data.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);
+
+ printf ("%s: backtrace_full inline\n", data.failed ? "FAIL" : "PASS");
+
+ if (data.failed)
+ ++failures;
+
+ return failures;
+}
+
+static int test5 (void) __attribute__ ((unused));
+
+int global = 1;
+
+static int
+test5 (void)
+{
+ struct symdata symdata;
+ int i;
+ uintptr_t addr = (uintptr_t) &global;
+
+ if (sizeof (global) > 1)
+ addr += 1;
+
+ symdata.name = NULL;
+ symdata.val = 0;
+ symdata.size = 0;
+ symdata.failed = 0;
+
+ i = backtrace_syminfo (state, addr, md_callback_three,
+ error_callback_three, &symdata);
+ if (i == 0)
+ {
+ fprintf (stderr,
+ "test5: unexpected return value from backtrace_syminfo %d\n",
+ i);
+ symdata.failed = 1;
+ }
+
+ if (!symdata.failed)
+ {
+ if (symdata.name == NULL)
+ {
+ fprintf (stderr, "test5: NULL syminfo name\n");
+ symdata.failed = 1;
+ }
+ else if (!(strncmp (symdata.name, "global", 6) == 0
+ && (symdata.name[6] == '\0'|| symdata.name[6] == '.')))
+ {
+ fprintf (stderr,
+ "test5: unexpected syminfo name got %s expected %s\n",
+ symdata.name, "global");
+ symdata.failed = 1;
+ }
+ else if (symdata.val != (uintptr_t) &global)
+ {
+ fprintf (stderr,
+ "test5: unexpected syminfo value got %lx expected %lx\n",
+ (unsigned long) symdata.val,
+ (unsigned long) (uintptr_t) &global);
+ symdata.failed = 1;
+ }
+ else if (symdata.size != sizeof (global) && symdata.size != 0)
+ {
+ fprintf (stderr,
+ "test5: unexpected syminfo size got %lx expected %lx\n",
+ (unsigned long) symdata.size,
+ (unsigned long) sizeof (global));
+ symdata.failed = 1;
+ }
+ }
+
+ printf ("%s: backtrace_syminfo variable\n",
+ symdata.failed ? "FAIL" : "PASS");
+
+ if (symdata.failed)
+ ++failures;
+
+ return failures;
+}
+
+/* Run all the tests. */
+
+int
+main (int argc ATTRIBUTE_UNUSED, char **argv)
+{
+ state = backtrace_create_state (argv[0], BACKTRACE_SUPPORTS_THREADS | 2,
+ error_callback_create, NULL);
+
+#if BACKTRACE_SUPPORTED
+ test1 ();
+ test2 ();
+#if BACKTRACE_SUPPORTS_DATA
+ test5 ();
+#endif
+#endif
+
+ exit (failures ? EXIT_FAILURE : EXIT_SUCCESS);
+}
{
struct coff_syminfo_data *sdata;
struct coff_symbol *sym = NULL;
+ void *mdata;
+ struct backtrace_moredata md;
if (!state->threaded)
{
}
}
+ if (!state->moredata)
+ mdata = data;
+ else
+ {
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = 3;
+ md.backtrace_data = data;
+ mdata = (void *) &md;
+ }
+
if (sym == NULL)
- callback (data, addr, NULL, 0, 0);
+ callback (mdata, addr, NULL, 0, 0);
else
- callback (data, addr, sym->name, sym->address, 0);
+ callback (mdata, addr, sym->name, sym->address, 0);
}
/* Add the backtrace data for one PE/COFF file. Returns 1 on success,
other routines. */
struct backtrace_state *
-backtrace_create_state (const char *filename, int threaded,
+backtrace_create_state (const char *filename, int flags,
backtrace_error_callback error_callback,
void *data)
{
+ int threaded;
+ int moredata;
struct backtrace_state init_state;
struct backtrace_state *state;
+ threaded = (flags & 1) != 0;
+ moredata = (flags & 2) != 0;
+ if ((flags & ~3) != 0)
+ {
+ error_callback (data, "backtrace_create_state: unsupported flag", 0);
+ return NULL;
+ }
+
#ifndef HAVE_SYNC_FUNCTIONS
if (threaded)
{
memset (&init_state, 0, sizeof init_state);
init_state.filename = filename;
init_state.threaded = threaded;
+ init_state.moredata = moredata;
state = ((struct backtrace_state *)
backtrace_alloc (&init_state, sizeof *state, error_callback, data));
#include "config.h"
+#include <string.h>
#include <sys/types.h>
#include "backtrace.h"
/* A trivial routine that always fails to find fileline data. */
static int
-unknown_fileline (struct backtrace_state *state ATTRIBUTE_UNUSED,
- uintptr_t pc, backtrace_full_callback callback,
+unknown_fileline (struct backtrace_state *state, uintptr_t pc,
+ backtrace_full_callback callback,
backtrace_error_callback error_callback ATTRIBUTE_UNUSED,
void *data)
{
- return callback (data, pc, NULL, 0, NULL);
+ if (!state->moredata)
+ return callback (data, pc, NULL, 0, NULL);
+ else
+ {
+ struct backtrace_moredata md;
+
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = 3;
+ md.backtrace_data = data;
+ return callback ((void *) &md, pc, NULL, 0, NULL);
+ }
}
/* Initialize the backtrace data when we don't know how to read the
struct xcoff_syminfo_data *edata;
struct xcoff_symbol *sym = NULL;
const char *name;
+ void *mdata;
+ struct backtrace_moredata md;
if (!state->threaded)
{
}
}
+ if (!state->moredata)
+ mdata = data;
+ else
+ {
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = 3;
+ md.backtrace_data = data;
+ mdata = (void *) &md;
+ }
+
if (sym == NULL)
- callback (data, addr, NULL, 0, 0);
+ callback (mdata, addr, NULL, 0, 0);
else
{
name = sym->name;
/* AIX prepends a '.' to function entry points, remove it. */
if (name && *name == '.')
++name;
- callback (data, addr, name, sym->address, sym->size);
+ callback (mdata, addr, name, sym->address, sym->size);
}
}
0 if not. */
static int
-xcoff_lookup_pc (struct backtrace_state *state ATTRIBUTE_UNUSED,
+xcoff_lookup_pc (struct backtrace_state *state,
struct xcoff_fileline_data *fdata, uintptr_t pc,
backtrace_full_callback callback,
backtrace_error_callback error_callback ATTRIBUTE_UNUSED,
/* AIX prepends a '.' to function entry points, remove it. */
if (function != NULL && *function == '.')
++function;
- return callback (data, pc, filename, lnno, function);
+
+ if (!state->moredata)
+ return callback (data, pc, filename, lnno, function);
+ else
+ {
+ struct backtrace_moredata md;
+
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = 3;
+ md.backtrace_data = data;
+ return callback ((void *) &md, pc, filename, lnno, function);
+ }
}
/* Return the file/line information for a PC using the XCOFF lineno
/* FIXME: See if any libraries have been dlopen'ed. */
- return callback (data, pc, NULL, 0, NULL);
+ if (!state->moredata)
+ return callback (data, pc, NULL, 0, NULL);
+ else
+ {
+ struct backtrace_moredata md;
+
+ memset (&md, 0, sizeof md);
+ md.backtrace_version = 3;
+ md.backtrace_data = data;
+ return callback ((void *) data, pc, NULL, 0, NULL);
+ }
}
/* Initialize the function vector info for xcoff_fileline. */