#include
#include
#include
#include
#include
#include
#include
#include
#include
// free() and abort() functions
#include
// For registering SIGSEGV callbacks
#include
// The following C headers are needed for some specific C functionality (see
// the comments), which is not available in C++:
#ifdef HAVE_LFORTRAN_UNWIND
// For _Unwind_Backtrace() function
# include
#endif
#if defined(HAVE_LFORTRAN_DEMANGLE)
// For demangling function names
# include
#endif
#ifdef HAVE_LFORTRAN_LINK
// For dl_iterate_phdr() functionality
# include
#endif
#ifdef HAVE_LFORTRAN_MACHO
# include
#endif
#ifdef HAVE_LFORTRAN_BFD
// For bfd_* family of functions for loading debugging symbols from the binary
// This is the only nonstandard header file and the binary needs to be linked
// with "-lbfd".
// Note: on macOS, one must call `dsymutil` on any binary in order for BFD to
// be able to find line number information. Example:
//
// $ dsymutil ./test_stacktrace
//
// This is done automatically by our CMake build system for the `lfortran`
// binary.
//
// The bfd.h header file sometimes requires the PACKAGE define to be defined:
# define PACKAGE 1
# include
#endif
namespace LFortran {
std::string binary_executable_path = "/proc/self/exe";
#ifdef HAVE_LFORTRAN_UNWIND
static _Unwind_Reason_Code unwind_callback(struct _Unwind_Context *context,
void *vdata)
{
std::vector &d = *(std::vector *)vdata;
uintptr_t pc;
pc = _Unwind_GetIP(context);
if (pc != 0) {
pc--;
StacktraceItem i;
i.pc = pc;
d.push_back(i);
}
return _URC_NO_REASON;
}
#endif // HAVE_LFORTRAN_UNWIND
#ifdef HAVE_LFORTRAN_LINK
/* Tries to find the 'data.addr' in the current shared lib (as passed in
'info'). If it succeeds, returns (in the 'data') the full path to the shared
lib and the local address in the file.
*/
int shared_lib_callback(struct dl_phdr_info *info,
size_t /* size */, void *_data)
{
StacktraceItem &item = *(StacktraceItem *)_data;
for (int i=0; i < info->dlpi_phnum; i++) {
if (info->dlpi_phdr[i].p_type == PT_LOAD) {
ElfW(Addr) min_addr = info->dlpi_addr + info->dlpi_phdr[i].p_vaddr;
ElfW(Addr) max_addr = min_addr + info->dlpi_phdr[i].p_memsz;
if ((item.pc >= min_addr) && (item.pc < max_addr)) {
item.binary_filename = info->dlpi_name;
if (item.binary_filename == "") {
item.binary_filename = binary_executable_path;
}
item.local_pc = item.pc - info->dlpi_addr;
// We found a match, return a non-zero value
return 1;
}
}
}
// We didn't find a match, return a zero value
return 0;
}
#endif // HAVE_LFORTRAN_LINK
// Fills in `local_pc` and `binary_filename` of `item`
void get_local_address(StacktraceItem &item)
{
#ifdef HAVE_LFORTRAN_LINK
// Iterate over all loaded shared libraries (see dl_iterate_phdr(3) -
// Linux man page for more documentation)
if (dl_iterate_phdr(shared_lib_callback, &item) == 0) {
// `dl_iterate_phdr` returns the last value returned by our
// `shared_lib_callback`. It will only be 0 if no shared library
// (including the main program) contains the address `match.addr`. Given
// that the addresses are collected from a stacktrace, this should only
// happen if the stacktrace is somehow corrupted. In that case, we simply
// abort here.
// `dl_iterate_phdr` returns the last value returned by our
// `shared_lib_callback`. It will only be 0 if no shared library
// `dl_iterate_phdr` returns the last value returned by our
// `shared_lib_callback`. It will only be 0 if no shared library
// (including the main program) contains the address `match.addr`. Given
// that the addresses are collected from a stacktrace, this should only
// happen if the stacktrace is somehow corrupted. In that case, we simply
// abort here.
std::cout ncmds; j++) {
if (cmd->cmd == LC_SEGMENT) {
struct segment_command* seg = (struct segment_command*)cmd;
if (((intptr_t)item.pc >= (seg->vmaddr+offset)) &&
((intptr_t)item.pc < (seg->vmaddr+offset + seg->vmsize))) {
item.local_pc = item.pc - offset;
item.binary_filename = _dyld_get_image_name(i);
// Resolve symlinks to a real path:
char buffer[PATH_MAX];
char* resolved;
resolved = realpath(item.binary_filename.c_str(), buffer);
if (resolved) item.binary_filename = resolved;
return;
}
}
if (cmd->cmd == LC_SEGMENT_64) {
struct segment_command_64* seg = (struct segment_command_64*)cmd;
if ((item.pc >= (seg->vmaddr + offset)) &&
(item.pc < (seg->vmaddr + offset + seg->vmsize))) {
item.local_pc = item.pc - offset;
item.binary_filename = _dyld_get_image_name(i);
// Resolve symlinks to a real path:
char buffer[PATH_MAX];
char* resolved;
resolved = realpath(item.binary_filename.c_str(), buffer);
if (resolved) item.binary_filename = resolved;
return;
}
}
cmd = (struct load_command*)((char*)cmd + cmd->cmdsize);
}
}
std::cout line_found) {
// If we already found the line, exit
return;
}
if ((section->flags & SEC_ALLOC) == 0) {
return;
}
bfd_vma section_vma = section->vma;
if (data->addr < section_vma) {
// If the addr lies above the section, exit
return;
}
bfd_size_type section_size = section->size * 8;
if (data->addr >= section_vma + section_size) {
// If the addr lies below the section, exit
return;
}
// Calculate the correct offset of our line in the section
bfd_vma offset = data->addr - section_vma;
// Finds the line corresponding to the offset
const char *filename=NULL, *function_name=NULL;
data->line_found = bfd_find_nearest_line(abfd, section, data->symbol_table,
offset, &filename, &function_name, &data->line);
if (filename == NULL)
data->filename = "";
else
data->filename = filename;
if (function_name == NULL)
data->function_name = "";
else
data->function_name = function_name;
}
/* Loads the symbol table into 'data->symbol_table'. */
int load_symbol_table(bfd *abfd, line_data *data)
{
if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0)
// If we don't have any symbols, return
return 0;
void **symbol_table_ptr = reinterpret_cast(&data->symbol_table);
long n_symbols;
unsigned int symbol_size;
n_symbols = bfd_read_minisymbols(abfd, false, symbol_table_ptr, &symbol_size);
if (n_symbols == 0) {
// If the bfd_read_minisymbols() already allocated the table, we need
// to free it first:
if (data->symbol_table != NULL)
free(data->symbol_table);
// dynamic
n_symbols = bfd_read_minisymbols(abfd, true, symbol_table_ptr, &symbol_size);
}
if (n_symbols < 0) {
// bfd_read_minisymbols() failed
return 1;
}
return 0;
}
void get_symbol_info_bfd(std::string binary_filename, uintptr_t addr,
std::string &source_filename, std::string &function_name,
int &line_number)
{
line_data data;
data.addr = addr;
data.line_found = 0;
// Initialize 'abfd' and do some sanity checks
bfd *abfd;
abfd = bfd_openr(binary_filename.c_str(), NULL);
if (abfd == NULL) {
std::cout