199 lines
7.8 KiB
C++
199 lines
7.8 KiB
C++
#ifndef OBJECT_HPP
|
|
#define OBJECT_HPP
|
|
|
|
#include "../utils/common.hpp"
|
|
#include "../utils/utils.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
|
|
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
|
|
#include <link.h>
|
|
#endif
|
|
#elif IS_WINDOWS
|
|
#include <windows.h>
|
|
#include "pe.hpp"
|
|
#endif
|
|
|
|
namespace cpptrace {
|
|
namespace detail {
|
|
#if IS_LINUX || IS_APPLE
|
|
#if !IS_APPLE
|
|
inline std::uintptr_t get_module_image_base(const std::string& object_path) {
|
|
static std::mutex mutex;
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
static std::unordered_map<std::string, std::uintptr_t> cache;
|
|
auto it = cache.find(object_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(object_path);
|
|
cache.insert(it, {object_path, base});
|
|
return base;
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
#else
|
|
inline std::uintptr_t get_module_image_base(const std::string& object_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, std::uintptr_t> cache;
|
|
auto it = cache.find(object_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(object_path);
|
|
cache.insert(it, {object_path, base});
|
|
return base;
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
#endif
|
|
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
|
|
inline std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addrs) {
|
|
// Use _dl_find_object when we can, it's orders of magnitude faster
|
|
std::vector<object_frame> frames;
|
|
frames.reserve(addrs.size());
|
|
for(const frame_ptr addr : addrs) {
|
|
object_frame frame;
|
|
frame.raw_address = addr;
|
|
frame.object_address = 0;
|
|
dl_find_object result;
|
|
if(_dl_find_object(reinterpret_cast<void*>(addr), &result) == 0) { // thread safe
|
|
if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) {
|
|
frame.object_path = result.dlfo_link_map->l_name;
|
|
} else {
|
|
// empty l_name, this means it's the currently running executable
|
|
// TODO: Caching and proper handling
|
|
char buffer[CPPTRACE_PATH_MAX + 1]{};
|
|
auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
|
|
if(res == -1) {
|
|
// error handling?
|
|
} else {
|
|
frame.object_path = buffer;
|
|
}
|
|
}
|
|
frame.object_address = addr
|
|
- to_frame_ptr(result.dlfo_link_map->l_addr)
|
|
+ get_module_image_base(frame.object_path);
|
|
}
|
|
frames.push_back(frame);
|
|
}
|
|
return frames;
|
|
}
|
|
#else
|
|
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
|
|
inline std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addrs) {
|
|
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
|
std::vector<object_frame> frames;
|
|
frames.reserve(addrs.size());
|
|
for(const frame_ptr addr : addrs) {
|
|
Dl_info info;
|
|
object_frame frame;
|
|
frame.raw_address = addr;
|
|
frame.object_address = 0;
|
|
if(dladdr(reinterpret_cast<void*>(addr), &info)) { // thread safe
|
|
frame.object_path = info.dli_fname;
|
|
frame.object_address = addr
|
|
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
|
+ get_module_image_base(info.dli_fname);
|
|
}
|
|
frames.push_back(frame);
|
|
}
|
|
return frames;
|
|
}
|
|
#endif
|
|
#else
|
|
inline 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))) {
|
|
///std::fprintf(stderr, "path: %s base: %p\n", path, handle);
|
|
cache.insert(it, {handle, path});
|
|
return path;
|
|
} else {
|
|
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
|
cache.insert(it, {handle, ""});
|
|
return "";
|
|
}
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
inline std::uintptr_t get_module_image_base(const std::string& object_path) {
|
|
static std::mutex mutex;
|
|
std::lock_guard<std::mutex> lock(mutex);
|
|
static std::unordered_map<std::string, std::uintptr_t> cache;
|
|
auto it = cache.find(object_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(object_path);
|
|
cache.insert(it, {object_path, base});
|
|
return base;
|
|
} else {
|
|
return it->second;
|
|
}
|
|
}
|
|
|
|
// aladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
|
|
inline std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addrs) {
|
|
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
|
std::vector<object_frame> frames;
|
|
frames.reserve(addrs.size());
|
|
for(const frame_ptr addr : addrs) {
|
|
object_frame frame;
|
|
frame.raw_address = addr;
|
|
frame.object_address = 0;
|
|
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,
|
|
reinterpret_cast<const char*>(addr),
|
|
&handle
|
|
)) {
|
|
frame.object_path = get_module_name(handle);
|
|
frame.object_address = addr
|
|
- reinterpret_cast<std::uintptr_t>(handle)
|
|
+ get_module_image_base(frame.object_path);
|
|
} else {
|
|
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
|
}
|
|
frames.push_back(frame);
|
|
}
|
|
return frames;
|
|
}
|
|
#endif
|
|
|
|
inline object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
|
|
return {
|
|
frame.raw_address,
|
|
frame.address_relative_to_object_start + get_module_image_base(frame.object_path),
|
|
frame.object_path
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|