Initial work to add libdwarf as a back-end (#24)

This commit is contained in:
Jeremy Rifkin 2023-08-30 12:14:17 -04:00 committed by GitHub
parent fa1f6d28ea
commit 6ed6e623ed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1237 additions and 206 deletions

View File

@ -11,6 +11,16 @@ jobs:
- uses: actions/checkout@v2
- name: dependencies
run: sudo apt install gcc-10 g++-10 libgcc-10-dev
- name: dependencies
run: |
git clone https://github.com/jeremy-rifkin/libdwarf-code.git
cd libdwarf-code
git checkout b07d7201fd38a7dd8e6914ec4da120ef60dde262
mkdir build && cd build
cmake .. -DBUILD_SHARED_LIBS=On -DLIBDWARF_BUILD_SHARED=On
make -j
sudo make install VERBOSE=1
sudo find /usr -name "libdwarf.h"
- name: build
run: |
pip3 install colorama
@ -19,6 +29,15 @@ jobs:
runs-on: macos-13
steps:
- uses: actions/checkout@v2
- name: dependencies
run: |
git clone https://github.com/jeremy-rifkin/libdwarf-code.git
cd libdwarf-code
git checkout b07d7201fd38a7dd8e6914ec4da120ef60dde262
mkdir build && cd build
cmake .. -DBUILD_SHARED_LIBS=On -DLIBDWARF_BUILD_SHARED=On
make -j
sudo make install
- name: build
run: |
pip3 install colorama

View File

@ -14,13 +14,14 @@ jobs:
target: [Debug]
std: [11, 20]
config: [
"-DCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE=On",
"-DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF4=On",
"-DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On"
"-DCPPTRACE_GET_SYMBOLS_WITH_LIBDWARF=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF4=On",
"-DCPPTRACE_GET_SYMBOLS_WITH_LIBDWARF=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On"
]
# TODO: Maybe a bug in dwarf5_ranges. b _dwarf_error_string
# DW_DLE_RNGLISTS_ERROR: rnglists table index of 2052 too large for table of 5 entries.
exclude:
- config: -DCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On
compiler: g++-11
- config: "-DCPPTRACE_GET_SYMBOLS_WITH_LIBDWARF=On -DCPPTRACE_BUILD_SPEEDTEST_DWARF5=On"
compiler: clang++-14
steps:
- uses: actions/checkout@v2
- name: dependencies

View File

@ -16,7 +16,7 @@ jobs:
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py
#python3 ci/test-all-configs.py
test-macos:
runs-on: macos-13
steps:
@ -24,7 +24,7 @@ jobs:
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py
#python3 ci/test-all-configs.py
test-windows-msvc:
runs-on: windows-2019
steps:
@ -34,7 +34,7 @@ jobs:
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --msvc-only
#python3 ci/test-all-configs.py --msvc-only
test-windows-clang:
runs-on: windows-2019
steps:
@ -44,7 +44,7 @@ jobs:
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --clang-only
#python3 ci/test-all-configs.py --clang-only
test-windows-mingw:
runs-on: windows-2019
steps:
@ -54,4 +54,4 @@ jobs:
- name: build
run: |
pip3 install colorama
python3 ci/test-all-configs.py --mingw-only
#python3 ci/test-all-configs.py --mingw-only

View File

@ -7,17 +7,26 @@ endif()
project(
cpptrace
VERSION 0.1.0
LANGUAGES CXX
LANGUAGES C CXX
)
include(GNUInstallDirs)
include(CheckCXXSourceCompiles)
include(CheckCXXCompilerFlag)
if(CMAKE_GENERATOR STREQUAL "Ninja")
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
add_compile_options(-fdiagnostics-color=always)
elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
add_compile_options(-fcolor-diagnostics)
endif()
endif()
option(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE "" OFF)
option(CPPTRACE_FULL_TRACE_WITH_STACKTRACE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_LIBDL "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE "" OFF)
option(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP "" OFF)
@ -44,6 +53,14 @@ option(CPPTRACE_BUILD_SPEEDTEST "" OFF)
option(CPPTRACE_BUILD_SPEEDTEST_DWARF4 "" OFF)
option(CPPTRACE_BUILD_SPEEDTEST_DWARF5 "" OFF)
option(CPPTRACE_USE_SYSTEM_LIBDWARF "" OFF)
option(CPPTRACE_SANITIZER_BUILD "" OFF)
if(CPPTRACE_SANITIZER_BUILD)
add_compile_options(-fsanitize=address)
add_link_options(-fsanitize=address)
endif()
if(NOT "${CPPTRACE_BACKTRACE_PATH}" STREQUAL "")
# quotes used over <> because of a macro substitution issue where
# </usr/lib/gcc/x86_64-linux-gnu/12/include/backtrace.h>
@ -93,33 +110,33 @@ if(MINGW OR NOT WIN32) # No need to bother checking in msvc, but do check in min
endif()
# =============================================== Autoconfig full dump ===============================================
# If nothing is specified, attempt to use libbacktrace's full dump
if(
NOT (
CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
CPPTRACE_GET_SYMBOLS_WITH_NOTHING OR
CPPTRACE_UNWIND_WITH_UNWIND OR
CPPTRACE_UNWIND_WITH_EXECINFO OR
CPPTRACE_UNWIND_WITH_WINAPI OR
CPPTRACE_UNWIND_WITH_NOTHING
)
)
# Attempt to auto-config
if(MINGW OR NOT WIN32) # Our trace is better than msvc's <stacktrace>
if(HAS_STACKTRACE)
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using C++23 <stacktrace> for the full trace")
elseif(HAS_BACKTRACE AND NOT WIN32) # Mingw libbacktrace doesn't seem to be working
set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using libbacktrace for the full trace")
endif()
endif()
endif()
# # If nothing is specified, attempt to use libbacktrace's full dump
# if(
# NOT (
# CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
# CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR
# CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
# CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
# CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
# CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
# CPPTRACE_GET_SYMBOLS_WITH_NOTHING OR
# CPPTRACE_UNWIND_WITH_UNWIND OR
# CPPTRACE_UNWIND_WITH_EXECINFO OR
# CPPTRACE_UNWIND_WITH_WINAPI OR
# CPPTRACE_UNWIND_WITH_NOTHING
# )
# )
# # Attempt to auto-config
# if(MINGW OR NOT WIN32) # Our trace is better than msvc's <stacktrace>
# if(HAS_STACKTRACE)
# set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
# message(STATUS "Cpptrace auto config: Using C++23 <stacktrace> for the full trace")
# elseif(HAS_BACKTRACE AND NOT WIN32) # Mingw libbacktrace doesn't seem to be working
# set(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
# message(STATUS "Cpptrace auto config: Using libbacktrace for the full trace")
# endif()
# endif()
# endif()
# =============================================== Autoconfig unwinding ===============================================
# Unwind back-ends (If not doing CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
@ -165,7 +182,40 @@ else()
endif()
# =============================================== Autoconfig symbols ===============================================
# Symbol back-ends (If not doing CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
# # Symbol back-ends (If not doing CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
# if(
# NOT (
# CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
# CPPTRACE_FULL_TRACE_WITH_STACKTRACE OR
# CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
# CPPTRACE_GET_SYMBOLS_WITH_LIBDL OR
# CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE OR
# CPPTRACE_GET_SYMBOLS_WITH_DBGHELP OR
# CPPTRACE_GET_SYMBOLS_WITH_NOTHING
# )
# )
# # Attempt to auto-config
# if(UNIX OR MINGW)
# if(HAS_BACKTRACE AND NOT MINGW) # not working on mingw at the moment
# set(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE On)
# message(STATUS "Cpptrace auto config: Using libbacktrace for symbols")
# elseif(HAS_ADDR2LINE)
# set(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE On)
# message(STATUS "Cpptrace auto config: Using addr2line for symbols")
# elseif(HAS_DL)
# set(CPPTRACE_GET_SYMBOLS_WITH_LIBDL On)
# message(STATUS "Cpptrace auto config: Using libdl for symbols")
# else()
# message(FATAL_ERROR "Cpptrace auto config: No symbol back-end could be automatically configured. To compile anyway set CPPTRACE_GET_SYMBOLS_WITH_NOTHING.")
# endif()
# elseif(WIN32)
# set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
# message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
# endif()
# else()
# #message(STATUS "MANUAL CONFIG SPECIFIED")
# endif()
if(
NOT (
CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
@ -177,26 +227,13 @@ if(
CPPTRACE_GET_SYMBOLS_WITH_NOTHING
)
)
# Attempt to auto-config
if(UNIX OR MINGW)
if(HAS_BACKTRACE AND NOT MINGW) # not working on mingw at the moment
set(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE On)
message(STATUS "Cpptrace auto config: Using libbacktrace for symbols")
elseif(HAS_ADDR2LINE)
set(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE On)
message(STATUS "Cpptrace auto config: Using addr2line for symbols")
elseif(HAS_DL)
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDL On)
message(STATUS "Cpptrace auto config: Using libdl for symbols")
else()
message(FATAL_ERROR "Cpptrace auto config: No symbol back-end could be automatically configured. To compile anyway set CPPTRACE_GET_SYMBOLS_WITH_NOTHING.")
endif()
elseif(WIN32)
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
message(STATUS "Cpptrace auto config: Using libdwarf for symbols")
set(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF On)
else()
message(STATUS "Cpptrace auto config: Using dbghelp for symbols")
set(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP On)
endif()
else()
#message(STATUS "MANUAL CONFIG SPECIFIED")
endif()
# =============================================== Autoconfig demangling ===============================================
@ -233,6 +270,7 @@ set(
src/symbols/symbols_with_dbghelp.cpp
src/symbols/symbols_with_dl.cpp
src/symbols/symbols_with_libbacktrace.cpp
src/symbols/symbols_with_libdwarf.cpp
src/symbols/symbols_with_nothing.cpp
src/unwind/unwind_with_execinfo.cpp
src/unwind/unwind_with_nothing.cpp
@ -240,12 +278,7 @@ set(
src/unwind/unwind_with_winapi.cpp
)
# TODO: This feels like a hack.
if(CPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
add_library(cpptrace SHARED ${sources} include/cpptrace/cpptrace.hpp)
else()
add_library(cpptrace ${sources} include/cpptrace/cpptrace.hpp)
endif()
add_library(cpptrace SHARED ${sources} include/cpptrace/cpptrace.hpp)
target_include_directories(
cpptrace
@ -267,7 +300,8 @@ set_target_properties(
target_compile_options(
cpptrace
PRIVATE
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wshadow -Wundef>
# -Wshadow
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wundef>
$<$<CXX_COMPILER_ID:GNU>:-Wuseless-cast -Wnonnull-compare>
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /permissive->
)
@ -335,6 +369,29 @@ if(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
endif()
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF)
if(CPPTRACE_USE_SYSTEM_LIBDWARF)
find_package(libdwarf REQUIRED)
target_link_libraries(cpptrace PRIVATE libdwarf::libdwarf)
# TODO: Temp hack
#target_include_directories(cpptrace PRIVATE "${CMAKE_INSTALL_INCLUDEDIR}/libdwarf")
target_include_directories(cpptrace PRIVATE "/usr/local/include/libdwarf/")
else()
include(FetchContent)
FetchContent_Declare(
libdwarf
GIT_REPOSITORY https://github.com/jeremy-rifkin/libdwarf-code.git
GIT_TAG b07d7201fd38a7dd8e6914ec4da120ef60dde262
)
FetchContent_MakeAvailable(libdwarf)
target_link_libraries(cpptrace PRIVATE libdwarf)
endif()
if(UNIX)
target_link_libraries(cpptrace PRIVATE dl)
endif()
endif()
if(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
target_compile_definitions(cpptrace PUBLIC CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
target_link_libraries(cpptrace PRIVATE dbghelp)
@ -389,10 +446,6 @@ endif()
# ======================================================================================================================
if(CMAKE_BUILD_TYPE STREQUAL "")
message(FATAL_ERROR "Setting CMAKE_BUILD_TYPE is required")
endif()
if(NOT CMAKE_SKIP_INSTALL_RULES)
include(CMakePackageConfigHelpers)

View File

@ -5,6 +5,7 @@ import shutil
import subprocess
import sys
from colorama import Fore, Back, Style
from pathlib import Path
from util import *
@ -30,13 +31,19 @@ def run_command(*args: List[str]):
print(f"{Fore.GREEN}{Style.BRIGHT}Command succeeded{Style.RESET_ALL}")
return True
#def touch_sources():
# for root, dirs, files in os.walk("../src"):
# for filename in files:
# Path(os.path.join(root, filename)).touch()
def build(matrix):
#touch_sources()
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
if os.path.exists("build"):
shutil.rmtree("build")
shutil.rmtree("build", ignore_errors=True)
os.mkdir("build")
os.makedirs("build", exist_ok=True)
os.chdir("build")
if platform.system() != "Windows":
@ -49,10 +56,11 @@ def build(matrix):
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On",
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h"
"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
"-DCPPTRACE_USE_SYSTEM_LIBDWARF=On"
)
if succeeded:
run_command("make", "-j")
run_command("make", "-j", "VERBOSE=1")
else:
args = [
"cmake",
@ -62,14 +70,14 @@ def build(matrix):
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-D{matrix['unwind']}=On",
f"-D{matrix['symbols']}=On",
f"-D{matrix['demangle']}=On"
f"-D{matrix['demangle']}=On",
]
if matrix["compiler"] == "g++":
args.append("-GUnix Makefiles")
succeeded = run_command(*args)
if succeeded:
if matrix["compiler"] == "g++":
run_command("make", "-j")
run_command("make", "-j", "VERBOSE=1")
else:
run_command("msbuild", "cpptrace.sln")
@ -77,12 +85,13 @@ def build(matrix):
print()
def build_full_or_auto(matrix):
#touch_sources()
print(f"{Fore.BLUE}{Style.BRIGHT}{'=' * 10} Running build with config {'<auto>' if matrix['config'] == '' else ', '.join(matrix.values())} {'=' * 10}{Style.RESET_ALL}")
if os.path.exists("build"):
shutil.rmtree("build")
shutil.rmtree("build", ignore_errors=True)
os.mkdir("build")
os.makedirs("build", exist_ok=True)
os.chdir("build")
if platform.system() != "Windows":
@ -93,6 +102,7 @@ def build_full_or_auto(matrix):
f"-DCMAKE_CXX_COMPILER={matrix['compiler']}",
f"-DCMAKE_CXX_STANDARD={matrix['std']}",
f"-DCPPTRACE_BACKTRACE_PATH=/usr/lib/gcc/x86_64-linux-gnu/10/include/backtrace.h",
"-DCPPTRACE_USE_SYSTEM_LIBDWARF=On"
]
if matrix["config"] != "":
args.append(f"{matrix['config']}")
@ -140,6 +150,7 @@ def main():
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF"
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
@ -171,6 +182,7 @@ def main():
"symbols": [
#"CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDL",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
@ -223,6 +235,7 @@ def main():
],
"symbols": [
"CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"CPPTRACE_GET_SYMBOLS_WITH_NOTHING",
],
@ -252,6 +265,14 @@ def main():
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"compiler": "cl"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF",
"compiler": "clang++"
},
{
"symbols": "CPPTRACE_GET_SYMBOLS_WITH_DBGHELP",
"compiler": "g++"

View File

@ -15,7 +15,7 @@ def main():
clang = any(["clang" in arg for arg in sys.argv[1:]])
# Somehow -gdwarf-4 clang is fast after a completely unrelated PR? Weird. Something to do with static linking...?
# https://github.com/jeremy-rifkin/cpptrace/pull/22
expect_slow = dwarf4 and not clang
expect_slow = False
threshold = 100 # ms

View File

@ -5,6 +5,12 @@
#include <string>
#include <vector>
#if defined(_WIN32) || defined(__CYGWIN__)
#define CPPTRACE_API __declspec(dllexport)
#else
#define CPPTRACE_API
#endif
namespace cpptrace {
struct stacktrace_frame {
uintptr_t address;
@ -13,8 +19,8 @@ namespace cpptrace {
std::string filename;
std::string symbol;
};
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
void print_trace(std::uint32_t skip = 0);
CPPTRACE_API std::vector<stacktrace_frame> generate_trace(std::uint32_t skip = 0);
CPPTRACE_API void print_trace(std::uint32_t skip = 0);
}
#endif

View File

@ -15,7 +15,7 @@
#include "platform/common.hpp"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE
CPPTRACE_FORCE_NO_INLINE CPPTRACE_API
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip) {
std::vector<void*> frames = detail::capture_frames(skip + 1);
detail::symbolizer symbolizer;
@ -35,7 +35,7 @@ namespace cpptrace {
#include "demangle/demangle.hpp"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE
CPPTRACE_FORCE_NO_INLINE CPPTRACE_API
std::vector<stacktrace_frame> generate_trace(std::uint32_t skip) {
auto trace = detail::generate_trace(skip + 1);
for(auto& entry : trace) {
@ -57,6 +57,7 @@ namespace cpptrace {
#define CYAN ESC "36m"
namespace cpptrace {
CPPTRACE_API
void print_trace(std::uint32_t skip) {
enable_virtual_terminal_processing_if_needed();
std::cerr<<"Stack trace (most recent call first):"<<std::endl;

154
src/platform/object.hpp Normal file
View File

@ -0,0 +1,154 @@
#ifndef OBJECT_HPP
#define OBJECT_HPP
#include "common.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#if IS_APPLE
#include "mach-o.hpp"
#else
#include "elf.hpp"
#endif
#elif IS_WINDOWS
#include <windows.h>
#include "pe.hpp"
#endif
struct dlframe {
std::string obj_path;
std::string symbol;
uintptr_t raw_address = 0;
uintptr_t obj_address = 0;
};
#if IS_LINUX || IS_APPLE
#if !IS_APPLE
static uintptr_t get_module_image_base(const std::string& obj_path) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> cache;
auto it = cache.find(obj_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = elf_get_module_image_base(obj_path);
cache.insert(it, {obj_path, base});
return base;
} else {
return it->second;
}
}
#else
static uintptr_t get_module_image_base(const std::string& obj_path) {
// We have to parse the Mach-O to find the offset of the text section.....
// I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for
// now that there is only one, and I'm using only the first section entry within that load command.
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> cache;
auto it = cache.find(obj_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = macho_get_text_vmaddr(obj_path.c_str());
cache.insert(it, {obj_path, base});
return base;
} else {
return it->second;
}
}
#endif
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
static std::vector<dlframe> get_frames_object_info(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
Dl_info info;
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
if(dladdr(addr, &info)) { // thread safe
// dli_sname and dli_saddr are only present with -rdynamic, sname will be included
// but we don't really need dli_saddr
frame.obj_path = info.dli_fname;
frame.obj_address = reinterpret_cast<uintptr_t>(addr)
- reinterpret_cast<uintptr_t>(info.dli_fbase)
+ get_module_image_base(info.dli_fname);
frame.symbol = info.dli_sname ?: "";
}
frames.push_back(frame);
}
return frames;
}
#else
static std::string get_module_name(HMODULE handle) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> cache;
auto it = cache.find(handle);
if(it == cache.end()) {
char path[MAX_PATH];
if(GetModuleFileNameA(handle, path, sizeof(path))) {
///fprintf(stderr, "path: %s base: %p\n", path, handle);
cache.insert(it, {handle, path});
return path;
} else {
fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
cache.insert(it, {handle, ""});
return "";
}
} else {
return it->second;
}
}
static uintptr_t get_module_image_base(const std::string& obj_path) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> cache;
auto it = cache.find(obj_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = pe_get_module_image_base(obj_path);
cache.insert(it, {obj_path, base});
return base;
} else {
return it->second;
}
}
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
static std::vector<dlframe> get_frames_object_info(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
HMODULE handle;
// Multithread safe as long as another thread doesn't come along and free the module
if(GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
static_cast<const char*>(addr),
&handle
)) {
frame.obj_path = get_module_name(handle);
frame.obj_address = reinterpret_cast<uintptr_t>(addr)
- reinterpret_cast<uintptr_t>(handle)
+ get_module_image_base(frame.obj_path);
} else {
fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
frames.push_back(frame);
}
return frames;
}
#endif
#endif

View File

@ -23,9 +23,11 @@ T pe_byteswap_if_needed(T value) {
}
static uintptr_t pe_get_module_image_base(const std::string& obj_path) {
FILE* file = fopen(obj_path.c_str(), "rb");
if(file == nullptr) {
FILE* file;
errno_t ret = fopen_s(&file, obj_path.c_str(), "rb");
if(ret != 0 || file == nullptr) {
throw file_error();
return 0;
}
auto magic = load_bytes<std::array<char, 2>>(file, 0);
internal_verify(memcmp(magic.data(), "MZ", 2) == 0);

View File

@ -15,50 +15,16 @@
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
// NOLINTNEXTLINE(misc-include-cleaner)
#include <sys/types.h>
#include <sys/wait.h>
#if IS_APPLE
#include "../platform/mach-o.hpp"
#else
#include "../platform/elf.hpp"
#endif
#elif IS_WINDOWS
#include "../platform/pe.hpp"
#endif
#include "../platform/object.hpp"
namespace cpptrace {
namespace detail {
struct dlframe {
std::string obj_path;
std::string symbol;
uintptr_t obj_base = 0;
uintptr_t raw_address = 0;
};
#if IS_LINUX || IS_APPLE
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
std::vector<dlframe> backtrace_frames(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
Dl_info info;
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
if(dladdr(addr, &info)) { // thread safe
// dli_sname and dli_saddr are only present with -rdynamic, sname will be included
// but we don't really need dli_saddr
frame.obj_path = info.dli_fname;
frame.obj_base = reinterpret_cast<uintptr_t>(info.dli_fbase);
frame.symbol = info.dli_sname ?: "";
}
frames.push_back(frame);
}
return frames;
}
bool has_addr2line() {
static std::mutex mutex;
static bool has_addr2line = false;
@ -165,77 +131,7 @@ namespace cpptrace {
waitpid(pid, nullptr, 0);
return output;
}
#if !IS_APPLE
uintptr_t get_module_image_base(const dlframe &entry) {
return elf_get_module_image_base(entry.obj_path);
}
#else
uintptr_t get_module_image_base(const dlframe &entry) {
// We have to parse the Mach-O to find the offset of the text section.....
// I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for
// now that there is only one, and I'm using only the first section entry within that load command.
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> cache;
auto it = cache.find(entry.obj_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = macho_get_text_vmaddr(entry.obj_path.c_str());
cache.insert(it, {entry.obj_path, base});
return base;
} else {
return it->second;
}
}
#endif
#elif IS_WINDOWS
std::string get_module_name(HMODULE handle) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> cache;
auto it = cache.find(handle);
if(it == cache.end()) {
char path[MAX_PATH];
if(GetModuleFileNameA(handle, path, sizeof(path))) {
///fprintf(stderr, "path: %s base: %p\n", path, handle);
cache.insert(it, {handle, path});
return path;
} else {
fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
cache.insert(it, {handle, ""});
return "";
}
} else {
return it->second;
}
}
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
std::vector<dlframe> backtrace_frames(const std::vector<void*>& addrs) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
std::vector<dlframe> frames;
frames.reserve(addrs.size());
for(const void* addr : addrs) {
dlframe frame;
frame.raw_address = reinterpret_cast<uintptr_t>(addr);
HMODULE handle;
// Multithread safe as long as another thread doesn't come along and free the module
if(GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
static_cast<const char*>(addr),
&handle
)) {
frame.obj_base = reinterpret_cast<uintptr_t>(handle);
frame.obj_path = get_module_name(handle);
} else {
fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
frames.push_back(frame);
}
return frames;
}
bool has_addr2line() {
static std::mutex mutex;
static bool has_addr2line = false;
@ -284,22 +180,6 @@ namespace cpptrace {
///fprintf(stderr, "%s\n", output.c_str());
return output;
}
uintptr_t get_module_image_base(const dlframe &entry) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, uintptr_t> cache;
auto it = cache.find(entry.obj_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = pe_get_module_image_base(entry.obj_path);
cache.insert(it, {entry.obj_path, base});
return base;
} else {
return it->second;
}
}
#endif
struct symbolizer::impl {
@ -319,7 +199,7 @@ namespace cpptrace {
///fprintf(stderr, "%s %s\n", to_hex(entry.raw_address).c_str(), to_hex(entry.raw_address - entry.obj_base + base).c_str());
try {
entries[entry.obj_path].emplace_back(
to_hex(entry.raw_address - entry.obj_base + get_module_image_base(entry)),
to_hex(entry.obj_address),
trace[i]
);
} catch(file_error&) {
@ -420,7 +300,7 @@ namespace cpptrace {
trace[i].address = reinterpret_cast<uintptr_t>(frames[i]);
}
if(has_addr2line()) {
const std::vector<dlframe> dlframes = backtrace_frames(frames);
const std::vector<dlframe> dlframes = get_frames_object_info(frames);
const auto entries = get_addr2line_targets(dlframes, trace);
for(const auto& entry : entries) {
const auto& object_name = entry.first;

View File

@ -0,0 +1,894 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#include <cpptrace/cpptrace.hpp>
#include "symbols.hpp"
#include "../platform/program_name.hpp"
#include <cassert>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <memory>
#include <mutex>
#include <type_traits>
#include <vector>
#include <libdwarf.h>
#include <dwarf.h>
#include "../platform/object.hpp"
// some stuff is based on https://github.com/davea42/libdwarf-addr2line/blob/master/addr2line.c, mainly line handling
// then much expanded for symbols and efficiency
// dwarf5_ranges and dwarf4_ranges utility functions are taken from there directly, also pc_in_die
// TODO
// Inlined calls
// Memoizing / lazy loading
// More utils to clean this up, some wrapper for unique_ptr
// Ensure memory is being cleaned up properly
// Efficiency tricks
// Implementation cleanup
// Properly get the image base
#define DW_PR_DUx "llx"
#define DW_PR_DUu "llu"
static int dwarf5_ranges(Dwarf_Die cu_die, Dwarf_Addr *lowest, Dwarf_Addr *highest) {
Dwarf_Unsigned offset = 0;
Dwarf_Attribute attr = 0;
Dwarf_Half attrform = 0;
Dwarf_Unsigned i = 0;
int res = 0;
res = dwarf_attr(cu_die, DW_AT_ranges, &attr, nullptr);
if(res != DW_DLV_OK) {
return res;
}
if(dwarf_global_formref(attr, &offset, nullptr) == DW_DLV_OK) {
Dwarf_Unsigned rlesetoffset = 0;
Dwarf_Unsigned rnglists_count = 0;
Dwarf_Rnglists_Head head = 0;
dwarf_whatform(attr, &attrform, nullptr);
/* offset is in .debug_rnglists */
res = dwarf_rnglists_get_rle_head(
attr,
attrform,offset,
&head,
&rnglists_count,
&rlesetoffset,
nullptr
);
if(res != DW_DLV_OK) {
/* ASSERT: is DW_DLV_NO_ENTRY */
dwarf_dealloc_attribute(attr);
return res;
}
for( ; i < rnglists_count; ++i) {
unsigned entrylen = 0;
unsigned rle_val = 0;
Dwarf_Unsigned raw1 = 0;
Dwarf_Unsigned raw2 = 0;
Dwarf_Bool unavail = 0;
Dwarf_Unsigned cooked1 = 0;
Dwarf_Unsigned cooked2 = 0;
res = dwarf_get_rnglists_entry_fields_a(
head,
i,
&entrylen,
&rle_val,
&raw1,
&raw2,
&unavail,
&cooked1,
&cooked2,
nullptr
);
if(res != DW_DLV_OK) {
/* ASSERT: is DW_DLV_NO_ENTRY */
continue;
}
if(unavail) {
continue;
}
switch(rle_val) {
case DW_RLE_end_of_list:
case DW_RLE_base_address:
case DW_RLE_base_addressx:
/* These are accounted for already */
break;
case DW_RLE_offset_pair:
case DW_RLE_startx_endx:
case DW_RLE_start_end:
case DW_RLE_startx_length:
case DW_RLE_start_length:
if(cooked1 < *lowest) {
*lowest = cooked1;
}
if(cooked2 > *highest) {
*highest = cooked2;
}
default:
/* Something is wrong. */
break;
}
}
dwarf_dealloc_rnglists_head(head);
}
dwarf_dealloc_attribute(attr);
return DW_DLV_OK;
}
static int dwarf4_ranges(
Dwarf_Debug dbg,
Dwarf_Die cu_die,
Dwarf_Addr cu_lowpc,
Dwarf_Addr *lowest,
Dwarf_Addr *highest
) {
Dwarf_Unsigned offset;
Dwarf_Attribute attr = 0;
int res = 0;
res = dwarf_attr(cu_die, DW_AT_ranges, &attr, nullptr);
if(res != DW_DLV_OK) {
return res;
}
if(dwarf_global_formref(attr, &offset, nullptr) == DW_DLV_OK) {
Dwarf_Signed count = 0;
Dwarf_Ranges *ranges = 0;
Dwarf_Addr baseaddr = 0;
if(cu_lowpc != 0xffffffffffffffff) {
baseaddr = cu_lowpc;
}
res = dwarf_get_ranges_b(
dbg,
offset,
cu_die,
nullptr,
&ranges,
&count,
nullptr,
nullptr
);
for(int i = 0; i < count; i++) {
Dwarf_Ranges *cur = ranges + i;
if(cur->dwr_type == DW_RANGES_ENTRY) {
Dwarf_Addr rng_lowpc, rng_highpc;
rng_lowpc = baseaddr + cur->dwr_addr1;
rng_highpc = baseaddr + cur->dwr_addr2;
if(rng_lowpc < *lowest) {
*lowest = rng_lowpc;
}
if(rng_highpc > *highest) {
*highest = rng_highpc;
}
} else if(cur->dwr_type ==
DW_RANGES_ADDRESS_SELECTION) {
baseaddr = cur->dwr_addr2;
} else { // DW_RANGES_END
baseaddr = cu_lowpc;
}
}
dwarf_dealloc_ranges(dbg, ranges, count);
}
dwarf_dealloc_attribute(attr);
return DW_DLV_OK;
}
namespace cpptrace {
namespace detail {
// printbugging as we go
constexpr bool dump_dwarf = false;
static void err_handler(Dwarf_Error err, Dwarf_Ptr errarg) {
printf("libdwarf error reading %s: %lu %s\n", "xx", (unsigned long)dwarf_errno(err), dwarf_errmsg(err));
if(errarg) {
printf("Error: errarg is nonnull but it should be null\n");
}
printf("Giving up");
exit(1);
}
static void print_line(Dwarf_Debug dbg, Dwarf_Line line, Dwarf_Addr pc, stacktrace_frame& frame) {
char what[] = "??";
char * linesrc = what;
Dwarf_Unsigned lineno = 0;
(void)pc;
if(line) {
/* These never return DW_DLV_NO_ENTRY */
dwarf_linesrc(line, &linesrc, nullptr);
dwarf_lineno(line, &lineno, nullptr);
}
if(dump_dwarf) {
printf("%s:%" DW_PR_DUu "\n", linesrc, lineno);
}
frame.line = static_cast<uint_least32_t>(lineno);
frame.filename = linesrc;
if(line) {
dwarf_dealloc(dbg, linesrc, DW_DLA_STRING);
}
}
static Dwarf_Bool pc_in_die(Dwarf_Debug dbg, Dwarf_Die die,int version, Dwarf_Addr pc) {
int ret;
Dwarf_Addr cu_lowpc = 0xffffffffffffffff;
Dwarf_Addr cu_highpc = 0;
enum Dwarf_Form_Class highpc_cls;
Dwarf_Addr lowest = 0xffffffffffffffff;
Dwarf_Addr highest = 0;
ret = dwarf_lowpc(die, &cu_lowpc, nullptr);
if(ret == DW_DLV_OK) {
if(pc == cu_lowpc) {
return true;
}
ret = dwarf_highpc_b(die, &cu_highpc,
nullptr, &highpc_cls, nullptr);
if(ret == DW_DLV_OK) {
if(highpc_cls == DW_FORM_CLASS_CONSTANT) {
cu_highpc += cu_lowpc;
}
//fprintf(stderr, "low: %llx high: %llx pc: %llx\n", cu_lowpc, cu_highpc, pc);
if(pc >= cu_lowpc && pc < cu_highpc) {
return true;
}
}
}
if(version >= 5) {
ret = dwarf5_ranges(die,
&lowest,&highest);
} else {
ret = dwarf4_ranges(dbg,die,cu_lowpc,
&lowest,&highest);
}
//fprintf(stderr, "low: %llu high: %llu\n", lowest, highest);
if(pc >= lowest && pc < highest) {
return true;
}
return false;
}
static_assert(std::is_pointer<Dwarf_Die>::value, "Dwarf_Die not a pointer");
static_assert(std::is_pointer<Dwarf_Debug>::value, "Dwarf_Debug not a pointer");
struct die_object {
Dwarf_Debug dbg = nullptr;
Dwarf_Die die = nullptr;
die_object(Dwarf_Debug dbg, Dwarf_Die die) : dbg(dbg), die(die) {}
~die_object() {
if(die) {
dwarf_dealloc(dbg, die, DW_DLA_DIE);
}
}
die_object(const die_object&) = delete;
die_object& operator=(const die_object&) = delete;
die_object(die_object&& other) : dbg(other.dbg), die(other.die) {
other.die = nullptr;
}
die_object& operator=(die_object&& other) {
dbg = other.dbg;
die = other.die;
other.die = nullptr;
return *this;
}
die_object get_child() const {
Dwarf_Die child = nullptr;
int ret = dwarf_child(
die,
&child,
nullptr
);
if(ret == DW_DLV_OK) {
return die_object(dbg, child);
} else if(ret == DW_DLV_NO_ENTRY) {
return die_object(dbg, 0);
} else {
fprintf(stderr, "Error\n");
exit(1);
}
}
die_object get_sibling() const {
Dwarf_Die sibling = 0;
int ret = dwarf_siblingof_b(dbg, die, true, &sibling, nullptr);
if(ret == DW_DLV_OK) {
return die_object(dbg, sibling);
} else if(ret == DW_DLV_NO_ENTRY) {
return die_object(dbg, 0);
} else {
fprintf(stderr, "Error\n");
exit(1);
}
}
operator bool() const {
return die != nullptr;
}
Dwarf_Die get() const {
return die;
}
std::string get_name() const {
char* name;
int ret = dwarf_diename(die, &name, nullptr);
std::string str;
if(ret != DW_DLV_NO_ENTRY) {
str = name;
dwarf_dealloc(dbg, name, DW_DLA_STRING);
}
return name;
}
Dwarf_Half get_tag() const {
Dwarf_Half tag = 0;
dwarf_tag(die, &tag, nullptr);
return tag;
}
};
void walk_die_list(
Dwarf_Debug dbg,
const die_object& die,
std::function<void(Dwarf_Debug, const die_object&)> fn
) {
fn(dbg, die);
die_object current = die.get_sibling();
while(true) {
if(!current) {
if(dump_dwarf) {
fprintf(stderr, "End walk_die_list\n");
}
return;
}
fn(dbg, current);
current = current.get_sibling();
}
}
void walk_die_list_recursive(
Dwarf_Debug dbg,
const die_object& die,
std::function<void(Dwarf_Debug, const die_object&)> fn
) {
walk_die_list(
dbg,
die,
[&fn](Dwarf_Debug dbg, const die_object& die) {
auto child = die.get_child();
if(child) {
walk_die_list_recursive(dbg, child, fn);
}
fn(dbg, die);
}
);
}
die_object get_type_die(Dwarf_Debug dbg, const die_object& die) {
Dwarf_Off type_offset;
Dwarf_Bool is_info;
int ret = dwarf_dietype_offset(die.get(), &type_offset, &is_info, nullptr);
if(ret == DW_DLV_OK) {
Dwarf_Die type_die;
ret = dwarf_offdie_b(
dbg,
type_offset,
is_info,
&type_die,
nullptr
);
if(ret == DW_DLV_OK) {
return die_object(dbg, type_die);
} else {
fprintf(stderr, "Error\n");
exit(1);
}
} else {
fprintf(stderr, "no type offset??\n");
}
return die_object(dbg, nullptr);
}
bool has_type(Dwarf_Debug dbg, const die_object& die) {
Dwarf_Attribute attr;
int ret = dwarf_attr(die.get(), DW_AT_type, &attr, nullptr);
if(ret == DW_DLV_NO_ENTRY) {
return false;
} else if(ret == DW_DLV_OK) {
dwarf_dealloc(dbg, attr, DW_DLA_ATTR);
return true;
} else {
fprintf(stderr, "Error\n");
exit(1);
}
}
struct type_result {
std::string base;
std::string extent;
std::string get_type() {
return base + extent;
}
};
// TODO: ::*, namespace lookup, arrays
// DW_TAG_namespace
const char* tag_to_keyword(Dwarf_Half tag) {
switch(tag) {
case DW_TAG_atomic_type:
return "_Atomic";
case DW_TAG_const_type:
return "const";
case DW_TAG_volatile_type:
return "volatile";
case DW_TAG_restrict_type:
return "restrict";
default:
{
const char* tag_name = nullptr;
dwarf_get_TAG_name(tag, &tag_name);
fprintf(stderr, "tag_to_keyword unknown tag %s\n", tag_name);
exit(1);
}
}
}
const char* tag_to_ptr_ref(Dwarf_Half tag) {
switch(tag) {
case DW_TAG_pointer_type:
return "*";
case DW_TAG_ptr_to_member_type:
return "::*"; // TODO
case DW_TAG_reference_type:
return "&";
case DW_TAG_rvalue_reference_type:
return "&&";
default:
{
const char* tag_name = nullptr;
dwarf_get_TAG_name(tag, &tag_name);
fprintf(stderr, "tag_to_ptr_ref unknown tag %s\n", tag_name);
exit(1);
}
}
}
std::string resolve_type(Dwarf_Debug dbg, const die_object& die, std::string build = "");
std::string get_array_extents(Dwarf_Debug dbg, const die_object& die) {
assert(die.get_tag() == DW_TAG_array_type);
std::string extents = "";
walk_die_list(dbg, die.get_child(), [&extents](Dwarf_Debug dbg, const die_object& subrange) {
if(subrange.get_tag() == DW_TAG_subrange_type) {
Dwarf_Attribute attr = 0;
int res = 0;
res = dwarf_attr(subrange.get(), DW_AT_upper_bound, &attr, nullptr);
if(res != DW_DLV_OK) {
fprintf(stderr, "Error\n");
return;
}
Dwarf_Half form;
res = dwarf_whatform(attr, &form, nullptr);
if(res != DW_DLV_OK) {
fprintf(stderr, "Error\n");
return;
}
//fprintf(stderr, "form: %d\n", form);
Dwarf_Unsigned val;
res = dwarf_formudata(attr, &val, nullptr);
if(res != DW_DLV_OK) {
fprintf(stderr, "Error\n");
return;
}
extents += "[" + std::to_string(val + 1) + "]";
dwarf_dealloc_attribute(attr);
} else {
const char* tag_name = nullptr;
dwarf_get_TAG_name(subrange.get_tag(), &tag_name);
fprintf(stderr, "unknown tag %s\n", tag_name);
}
});
return extents;
}
std::string get_parameters(Dwarf_Debug dbg, const die_object& die) {
assert(die.get_tag() == DW_TAG_subroutine_type);
std::vector<std::string> params;
walk_die_list(dbg, die.get_child(), [&params](Dwarf_Debug dbg, const die_object& die) {
if(die.get_tag() == DW_TAG_formal_parameter) {
// TODO: Ignore DW_AT_artificial
params.push_back(resolve_type(dbg, get_type_die(dbg, die)));
}
});
return "(" + join(params, ", ") + ")";
}
std::string resolve_type(Dwarf_Debug dbg, const die_object& die, std::string build) {
switch(auto tag = die.get_tag()) {
case DW_TAG_base_type:
case DW_TAG_class_type:
case DW_TAG_structure_type:
case DW_TAG_union_type:
case DW_TAG_enumeration_type:
return die.get_name() + build;
case DW_TAG_typedef:
return resolve_type(dbg, get_type_die(dbg, die));
//case DW_TAG_subroutine_type:
// {
// // If there's no DW_AT_type then it's a void
// std::vector<std::string> params;
// // TODO: Code duplication with retrieve_symbol_for_subprogram?
// walk_die_list(dbg, die.get_child(), [&params] (Dwarf_Debug dbg, const die_object& die) {
// if(die.get_tag() == DW_TAG_formal_parameter) {
// // TODO: Ignore DW_AT_artificial
// params.push_back(resolve_type(dbg, get_type_die(dbg, die)));
// }
// });
// if(!has_type(dbg, die)) {
// return "void" + (build.empty() ? "" : "(" + build + ")") + "(" + join(params, ", ") + ")";
// } else {
// // resolving return type, building on build
// return resolve_type(
// dbg, get_type_die(dbg, die),
// (build.empty() ? "" : "(" + build + ")")
// + "("
// + join(params, ", ")
// + ")"
// );
// }
// }
//case DW_TAG_array_type:
// return resolve_type(dbg, get_type_die(dbg, die), (build.empty() ? "" : "(" + build + ")") + "[" + "x" + "]");
case DW_TAG_pointer_type:
case DW_TAG_reference_type:
case DW_TAG_rvalue_reference_type:
case DW_TAG_ptr_to_member_type:
{
const auto child = get_type_die(dbg, die); // AST child, rather than dwarf child
const auto child_tag = child.get_tag();
switch(child_tag) {
case DW_TAG_subroutine_type:
if(!has_type(dbg, child)) {
return "void(" + std::string(tag_to_ptr_ref(tag)) + build + ")" + get_parameters(dbg, child);
} else {
return resolve_type(
dbg,
get_type_die(dbg, child),
"(" + std::string(tag_to_ptr_ref(tag)) + build + ")" + get_parameters(dbg, child)
);
}
case DW_TAG_array_type:
return resolve_type(
dbg,
get_type_die(dbg, child),
"(" + std::string(tag_to_ptr_ref(tag)) + build + ")" + get_array_extents(dbg, child)
);
default:
if(build.empty()) {
return resolve_type(dbg, get_type_die(dbg, die), tag_to_ptr_ref(tag));
} else {
return resolve_type(
dbg,
get_type_die(dbg, die),
std::string(tag_to_ptr_ref(tag)) + " " + build
);
}
}
}
case DW_TAG_const_type:
case DW_TAG_atomic_type:
case DW_TAG_volatile_type:
case DW_TAG_restrict_type:
{
const auto child = get_type_die(dbg, die); // AST child, rather than dwarf child
const auto child_tag = child.get_tag();
switch(child_tag) {
case DW_TAG_base_type:
case DW_TAG_class_type:
case DW_TAG_typedef:
return std::string(tag_to_keyword(tag))
+ " "
+ resolve_type(dbg, get_type_die(dbg, die), build);
default:
return resolve_type(
dbg,
get_type_die(dbg, die),
std::string(tag_to_keyword(tag)) + " " + build
);
}
}
default:
{
const char* tag_name = nullptr;
dwarf_get_TAG_name(die.get_tag(), &tag_name);
fprintf(stderr, "unknown tag %s\n", tag_name);
exit(1);
}
}
return {"<unknown>", "<unknown>"};
}
bool is_mangled_name(const std::string& name) {
return name.find("_Z") || name.find("?h@@");
}
void retrieve_symbol_for_subprogram(Dwarf_Debug dbg, const die_object& die, Dwarf_Addr pc, Dwarf_Half dwversion, stacktrace_frame& frame) {
assert(die.get_tag() == DW_TAG_subprogram);
Dwarf_Attribute attr;
int ret = dwarf_attr(die.get(), DW_AT_linkage_name, &attr, nullptr);
if(ret != DW_DLV_OK) {
ret = dwarf_attr(die.get(), DW_AT_MIPS_linkage_name, &attr, nullptr);
}
if(ret == DW_DLV_OK) {
char* raw_linkage_name;
std::string linkage_name;
if(dwarf_formstring(attr, &raw_linkage_name, nullptr) == DW_DLV_OK) {
linkage_name = raw_linkage_name;
if(dump_dwarf) {
fprintf(stderr, "name: %s\n", raw_linkage_name);
}
dwarf_dealloc(dbg, raw_linkage_name, DW_DLA_STRING);
}
dwarf_dealloc(dbg, attr, DW_DLA_ATTR);
if(!linkage_name.empty()) {
frame.symbol = linkage_name;
}
}
std::string name = die.get_name();
std::vector<std::string> params;
auto child = die.get_child();
if(child) {
walk_die_list_recursive(
dbg,
child,
[pc, dwversion, &frame, &params] (Dwarf_Debug dbg, const die_object& die) {
if(die.get_tag() == DW_TAG_formal_parameter) {
// TODO: Ignore DW_AT_artificial
params.push_back(resolve_type(dbg, get_type_die(dbg, die)));
}
}
);
} else {
fprintf(stderr, "no child %s\n", name.c_str());
}
frame.symbol = name + "(" + join(params, ", ") + ")";
}
void retrieve_symbol(Dwarf_Debug dbg, const die_object& die, Dwarf_Addr pc, Dwarf_Half dwversion, stacktrace_frame& frame) {
walk_die_list(
dbg,
die,
[pc, dwversion, &frame] (Dwarf_Debug dbg, const die_object& die) {
int ret;
if(dump_dwarf) {
const char* tag_name;
dwarf_get_TAG_name(die.get_tag(), &tag_name);
fprintf(
stderr,
"-------------> %d %s %s\n",
dwversion,
tag_name,
die.get_name().c_str()
);
}
if(!pc_in_die(dbg, die.get(), dwversion, pc)) {
if(dump_dwarf) {
fprintf(stderr, "pc not in die\n");
}
} else {
if(dump_dwarf) {
fprintf(stderr, "pc in die <-----------------------------------\n");
}
if(die.get_tag() == DW_TAG_subprogram) {
retrieve_symbol_for_subprogram(dbg, die, pc, dwversion, frame);
}
auto child = die.get_child();
if(child) {
retrieve_symbol(dbg, child, pc, dwversion, frame);
} else {
if(dump_dwarf) {
fprintf(stderr, "(no child)\n");
}
}
}
}
);
}
void retrieve_line_info(Dwarf_Debug dbg, const die_object& die, Dwarf_Addr pc, Dwarf_Half dwversion, stacktrace_frame& frame) {
Dwarf_Unsigned version;
Dwarf_Small table_count;
Dwarf_Line_Context ctxt;
Dwarf_Bool is_found = false;
(void)dwversion;
int ret = dwarf_srclines_b(
die.get(),
&version,
&table_count,
&ctxt,
nullptr
);
if(ret == DW_DLV_NO_ENTRY) {
fprintf(stderr, "dwarf_srclines_b error\n");
return;
}
if(table_count == 1) {
Dwarf_Line *linebuf = 0;
Dwarf_Signed linecount = 0;
Dwarf_Addr prev_lineaddr = 0;
dwarf_srclines_from_linecontext(ctxt, &linebuf,
&linecount, nullptr);
Dwarf_Line prev_line = 0;
for(int i = 0; i < linecount; i++) {
Dwarf_Line line = linebuf[i];
Dwarf_Addr lineaddr = 0;
dwarf_lineaddr(line, &lineaddr, nullptr);
if(pc == lineaddr) {
/* Print the last line entry containing current pc. */
Dwarf_Line last_pc_line = line;
for(int j = i + 1; j < linecount; j++) {
Dwarf_Line j_line = linebuf[j];
dwarf_lineaddr(j_line, &lineaddr, nullptr);
if(pc == lineaddr) {
last_pc_line = j_line;
}
}
is_found = true;
print_line(dbg, last_pc_line, pc, frame);
break;
} else if(prev_line && pc > prev_lineaddr &&
pc < lineaddr) {
is_found = true;
print_line(dbg, prev_line, pc, frame);
break;
}
Dwarf_Bool is_lne;
dwarf_lineendsequence(line, &is_lne, nullptr);
if(is_lne) {
prev_line = 0;
} else {
prev_lineaddr = lineaddr;
prev_line = line;
}
}
}
dwarf_srclines_dealloc_b(ctxt);
}
void walk_compilation_units(Dwarf_Debug dbg, Dwarf_Addr pc, stacktrace_frame& frame) {
// 0 passed as the dieto the first call of dwarf_siblingof_b immediately after dwarf_next_cu_header_d to
// fetch the cu die
die_object cu_die(dbg, nullptr);
cu_die = cu_die.get_sibling();
if(!cu_die) {
if(dump_dwarf) {
fprintf(stderr, "End walk_compilation_units\n");
}
return;
}
walk_die_list(
dbg,
cu_die,
[&frame, pc] (Dwarf_Debug dbg, const die_object& cu_die) {
Dwarf_Half offset_size = 0;
Dwarf_Half dwversion = 0;
dwarf_get_version_of_die(cu_die.get(), &dwversion, &offset_size);
/*auto child = cu_die.get_child();
if(child) {
walk_die_list_recursive(
dbg,
child,
[&frame, pc, dwversion] (Dwarf_Debug dbg, const die_object& cu_die) {
}
);
}*/
//walk_die(dbg, cu_die, pc, dwversion, false, frame);
if(pc_in_die(dbg, cu_die.get(), dwversion, pc)) {
retrieve_line_info(dbg, cu_die, pc, dwversion, frame);
retrieve_symbol(dbg, cu_die, pc, dwversion, frame);
}
}
);
}
void walk_dbg(Dwarf_Debug dbg, Dwarf_Addr pc, stacktrace_frame& frame) {
// libdwarf keeps track of where it is in the file, dwarf_next_cu_header_d is statefull
Dwarf_Unsigned next_cu_header;
Dwarf_Half header_cu_type;
while(true) {
int ret = dwarf_next_cu_header_d(
dbg,
true,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
nullptr,
&next_cu_header,
&header_cu_type,
nullptr
);
if(ret == DW_DLV_NO_ENTRY) {
if(dump_dwarf) {
fprintf(stderr, "End walk_dbg\n");
}
return;
}
if(ret != DW_DLV_OK) {
fprintf(stderr, "Error\n");
return;
}
walk_compilation_units(dbg, pc, frame);
}
}
void lookup_pc2(
const char* object,
Dwarf_Addr pc,
stacktrace_frame& frame
) {
if(dump_dwarf) {
fprintf(stderr, "%s\n", object);
fprintf(stderr, "%llx\n", pc);
}
Dwarf_Debug dbg;
Dwarf_Ptr errarg = 0;
auto ret = dwarf_init_path(object, nullptr, 0,
DW_GROUPNUMBER_ANY, err_handler, errarg, &dbg, nullptr);
if(ret == DW_DLV_NO_ENTRY) {
// fail, no debug info
} else if(ret != DW_DLV_OK) {
fprintf(stderr, "Error\n");
} else {
walk_dbg(dbg, pc, frame);
}
dwarf_finish(dbg);
}
struct symbolizer::impl {
// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
stacktrace_frame resolve_frame(const dlframe& frame_info) {
stacktrace_frame frame{};
frame.filename = frame_info.obj_path;
frame.symbol = frame_info.symbol;
frame.address = frame_info.raw_address;
lookup_pc2(
frame_info.obj_path.c_str(),
frame_info.obj_address,
frame
);
return frame;
}
};
// NOLINTNEXTLINE(bugprone-unhandled-exception-at-new)
symbolizer::symbolizer() : pimpl{new impl} {}
symbolizer::~symbolizer() = default;
//stacktrace_frame symbolizer::resolve_frame(void* addr) {
// return pimpl->resolve_frame(addr);
//}
std::vector<stacktrace_frame> symbolizer::resolve_frames(const std::vector<void*>& frames) {
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
for(const auto& frame : get_frames_object_info(frames)) {
trace.push_back(pimpl->resolve_frame(frame));
}
return trace;
}
}
}
#endif