Split a bunch of internal headers into .cpp/.hpp
This commit is contained in:
parent
9275f62fc5
commit
da0aa4d5c0
@ -203,25 +203,31 @@ target_sources(
|
||||
target_sources(
|
||||
${target_name} PRIVATE
|
||||
# src
|
||||
src/binary/elf.cpp
|
||||
src/binary/mach-o.cpp
|
||||
src/binary/module_base.cpp
|
||||
src/binary/object.cpp
|
||||
src/binary/pe.cpp
|
||||
src/binary/safe_dl.cpp
|
||||
src/cpptrace.cpp
|
||||
src/ctrace.cpp
|
||||
src/demangle/demangle_with_cxxabi.cpp
|
||||
src/demangle/demangle_with_winapi.cpp
|
||||
src/demangle/demangle_with_nothing.cpp
|
||||
src/demangle/demangle_with_winapi.cpp
|
||||
src/snippets/snippet.cpp
|
||||
src/symbols/symbols_core.cpp
|
||||
src/symbols/symbols_with_addr2line.cpp
|
||||
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/symbols/symbols_core.cpp
|
||||
src/unwind/unwind_with_dbghelp.cpp
|
||||
src/unwind/unwind_with_execinfo.cpp
|
||||
src/unwind/unwind_with_libunwind.cpp
|
||||
src/unwind/unwind_with_nothing.cpp
|
||||
src/unwind/unwind_with_unwind.cpp
|
||||
src/unwind/unwind_with_winapi.cpp
|
||||
src/unwind/unwind_with_dbghelp.cpp
|
||||
src/snippets/snippet.cpp
|
||||
)
|
||||
|
||||
target_include_directories(
|
||||
|
||||
100
src/binary/elf.cpp
Normal file
100
src/binary/elf.cpp
Normal file
@ -0,0 +1,100 @@
|
||||
#include "elf.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#include <elf.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T elf_byteswap_if_needed(T value, bool elf_is_little) {
|
||||
if(is_little_endian() == elf_is_little) {
|
||||
return value;
|
||||
} else {
|
||||
return byteswap(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
|
||||
const std::string& object_path,
|
||||
std::FILE* file,
|
||||
bool is_little_endian
|
||||
) {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
|
||||
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
|
||||
auto loaded_header = load_bytes<Header>(file, 0);
|
||||
if(loaded_header.is_error()) {
|
||||
return std::move(loaded_header).unwrap_error();
|
||||
}
|
||||
const Header& file_header = loaded_header.unwrap_value();
|
||||
if(file_header.e_ehsize != sizeof(Header)) {
|
||||
return internal_error("ELF file header size mismatch" + object_path);
|
||||
}
|
||||
// PT_PHDR will occur at most once
|
||||
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
|
||||
// It should occur at the beginning but may as well loop just in case
|
||||
for(int i = 0; i < file_header.e_phnum; i++) {
|
||||
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
|
||||
if(loaded_ph.is_error()) {
|
||||
return std::move(loaded_ph).unwrap_error();
|
||||
}
|
||||
const PHeader& program_header = loaded_ph.unwrap_value();
|
||||
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
|
||||
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
|
||||
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
|
||||
}
|
||||
}
|
||||
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file " + object_path);
|
||||
}
|
||||
// Initial checks/metadata
|
||||
auto magic = load_bytes<std::array<char, 4>>(file, 0);
|
||||
if(magic.is_error()) {
|
||||
return std::move(magic).unwrap_error();
|
||||
}
|
||||
if(magic.unwrap_value() != (std::array<char, 4>{0x7F, 'E', 'L', 'F'})) {
|
||||
return internal_error("File is not ELF " + object_path);
|
||||
}
|
||||
auto ei_class = load_bytes<std::uint8_t>(file, 4);
|
||||
if(ei_class.is_error()) {
|
||||
return std::move(ei_class).unwrap_error();
|
||||
}
|
||||
bool is_64 = ei_class.unwrap_value() == 2;
|
||||
auto ei_data = load_bytes<std::uint8_t>(file, 5);
|
||||
if(ei_data.is_error()) {
|
||||
return std::move(ei_data).unwrap_error();
|
||||
}
|
||||
bool is_little_endian = ei_data.unwrap_value() == 1;
|
||||
auto ei_version = load_bytes<std::uint8_t>(file, 6);
|
||||
if(ei_version.is_error()) {
|
||||
return std::move(ei_version).unwrap_error();
|
||||
}
|
||||
if(ei_version.unwrap_value() != 1) {
|
||||
return internal_error("Unexpected ELF version " + object_path);
|
||||
}
|
||||
// get image base
|
||||
if(is_64) {
|
||||
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
|
||||
} else {
|
||||
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -5,97 +5,13 @@
|
||||
#include "../utils/utils.hpp"
|
||||
|
||||
#if IS_LINUX
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#include <elf.h>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T elf_byteswap_if_needed(T value, bool elf_is_little) {
|
||||
if(is_little_endian() == elf_is_little) {
|
||||
return value;
|
||||
} else {
|
||||
return byteswap(value);
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
|
||||
const std::string& object_path,
|
||||
std::FILE* file,
|
||||
bool is_little_endian
|
||||
) {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
|
||||
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
|
||||
auto loaded_header = load_bytes<Header>(file, 0);
|
||||
if(loaded_header.is_error()) {
|
||||
return std::move(loaded_header).unwrap_error();
|
||||
}
|
||||
const Header& file_header = loaded_header.unwrap_value();
|
||||
if(file_header.e_ehsize != sizeof(Header)) {
|
||||
return internal_error("ELF file header size mismatch" + object_path);
|
||||
}
|
||||
// PT_PHDR will occur at most once
|
||||
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
|
||||
// It should occur at the beginning but may as well loop just in case
|
||||
for(int i = 0; i < file_header.e_phnum; i++) {
|
||||
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
|
||||
if(loaded_ph.is_error()) {
|
||||
return std::move(loaded_ph).unwrap_error();
|
||||
}
|
||||
const PHeader& program_header = loaded_ph.unwrap_value();
|
||||
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
|
||||
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
|
||||
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
|
||||
}
|
||||
}
|
||||
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
|
||||
return 0;
|
||||
}
|
||||
|
||||
static Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file " + object_path);
|
||||
}
|
||||
// Initial checks/metadata
|
||||
auto magic = load_bytes<std::array<char, 4>>(file, 0);
|
||||
if(magic.is_error()) {
|
||||
return std::move(magic).unwrap_error();
|
||||
}
|
||||
if(magic.unwrap_value() != (std::array<char, 4>{0x7F, 'E', 'L', 'F'})) {
|
||||
return internal_error("File is not ELF " + object_path);
|
||||
}
|
||||
auto ei_class = load_bytes<std::uint8_t>(file, 4);
|
||||
if(ei_class.is_error()) {
|
||||
return std::move(ei_class).unwrap_error();
|
||||
}
|
||||
bool is_64 = ei_class.unwrap_value() == 2;
|
||||
auto ei_data = load_bytes<std::uint8_t>(file, 5);
|
||||
if(ei_data.is_error()) {
|
||||
return std::move(ei_data).unwrap_error();
|
||||
}
|
||||
bool is_little_endian = ei_data.unwrap_value() == 1;
|
||||
auto ei_version = load_bytes<std::uint8_t>(file, 6);
|
||||
if(ei_version.is_error()) {
|
||||
return std::move(ei_version).unwrap_error();
|
||||
}
|
||||
if(ei_version.unwrap_value() != 1) {
|
||||
return internal_error("Unexpected ELF version " + object_path);
|
||||
}
|
||||
// get image base
|
||||
if(is_64) {
|
||||
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
|
||||
} else {
|
||||
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
|
||||
}
|
||||
}
|
||||
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
641
src/binary/mach-o.cpp
Normal file
641
src/binary/mach-o.cpp
Normal file
@ -0,0 +1,641 @@
|
||||
#include "mach-o.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
|
||||
#if IS_APPLE
|
||||
|
||||
// A number of mach-o functions are deprecated as of macos 13
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <crt_externs.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/stab.h>
|
||||
#include <mach-o/arch.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
bool is_mach_o(std::uint32_t magic) {
|
||||
switch(magic) {
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool file_is_mach_o(const std::string& object_path) noexcept {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(magic) {
|
||||
return is_mach_o(magic.unwrap_value());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_fat_magic(std::uint32_t magic) {
|
||||
return magic == FAT_MAGIC || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
|
||||
// and https://lowlevelbits.org/parsing-mach-o-files/
|
||||
bool is_magic_64(std::uint32_t magic) {
|
||||
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
|
||||
}
|
||||
|
||||
bool should_swap_bytes(std::uint32_t magic) {
|
||||
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
void swap_mach_header(mach_header_64& header) {
|
||||
swap_mach_header_64(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_mach_header(mach_header& header) {
|
||||
swap_mach_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_segment_command(segment_command_64& segment) {
|
||||
swap_segment_command_64(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_segment_command(segment_command& segment) {
|
||||
swap_segment_command(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_nlist(struct nlist& entry) {
|
||||
swap_nlist(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
void swap_nlist(struct nlist_64& entry) {
|
||||
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LP(x) x##_64
|
||||
#else
|
||||
#define LP(x) x
|
||||
#endif
|
||||
|
||||
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
|
||||
if(stringtab && index < symtab.strsize) {
|
||||
return stringtab.get() + index;
|
||||
} else {
|
||||
return internal_error("can't retrieve symbol from symtab");
|
||||
}
|
||||
}
|
||||
|
||||
Result<monostate, internal_error> mach_o::load() {
|
||||
if(magic == FAT_MAGIC || magic == FAT_CIGAM) {
|
||||
return load_fat_mach();
|
||||
} else {
|
||||
fat_index = 0;
|
||||
if(is_magic_64(magic)) {
|
||||
return load_mach<64>();
|
||||
} else {
|
||||
return load_mach<32>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Result<mach_o, internal_error> mach_o::open_mach_o(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
}
|
||||
if(!is_mach_o(magic.unwrap_value())) {
|
||||
return internal_error("File is not mach-o {}", object_path);
|
||||
}
|
||||
mach_o obj(std::move(file), object_path, magic.unwrap_value());
|
||||
auto result = obj.load();
|
||||
if(result.is_error()) {
|
||||
return result.unwrap_error();
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> mach_o::get_text_vmaddr() {
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
if(segment.is_error()) {
|
||||
return std::move(segment).unwrap_error();
|
||||
}
|
||||
if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) {
|
||||
return segment.unwrap_value().vmaddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// somehow no __TEXT section was found...
|
||||
return internal_error("Couldn't find __TEXT section while parsing Mach-O object");
|
||||
}
|
||||
|
||||
std::size_t mach_o::get_fat_index() const {
|
||||
VERIFY(fat_index != std::numeric_limits<std::size_t>::max());
|
||||
return fat_index;
|
||||
}
|
||||
|
||||
void mach_o::print_segments() const {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment_load = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
if(segment_load.is_error()) {
|
||||
fprintf(stderr, " error\n");
|
||||
segment_load.drop_error();
|
||||
continue;
|
||||
}
|
||||
auto& segment = segment_load.unwrap_value();
|
||||
fprintf(stderr, " cmd %u\n", segment.cmd);
|
||||
fprintf(stderr, " cmdsize %u\n", segment.cmdsize);
|
||||
fprintf(stderr, " segname %s\n", segment.segname);
|
||||
fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr);
|
||||
fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize);
|
||||
fprintf(stderr, " off 0x%llx\n", segment.fileoff);
|
||||
fprintf(stderr, " filesize %llu\n", segment.filesize);
|
||||
fprintf(stderr, " nsects %u\n", segment.nsects);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::reference_wrapper<optional<mach_o::symtab_info_data>>, internal_error> mach_o::get_symtab_info() {
|
||||
if(!symtab_info.has_value() && !tried_to_load_symtab) {
|
||||
// don't try to load the symtab again if for some reason loading here fails
|
||||
tried_to_load_symtab = true;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
symtab_info_data info;
|
||||
auto symtab = load_symbol_table_command(command.file_offset);
|
||||
if(!symtab) {
|
||||
return std::move(symtab).unwrap_error();
|
||||
}
|
||||
info.symtab = symtab.unwrap_value();
|
||||
auto string = load_string_table(info.symtab.stroff, info.symtab.strsize);
|
||||
if(!string) {
|
||||
return std::move(string).unwrap_error();
|
||||
}
|
||||
info.stringtab = std::move(string).unwrap_value();
|
||||
symtab_info = std::move(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::reference_wrapper<optional<symtab_info_data>>{symtab_info};
|
||||
}
|
||||
|
||||
void mach_o::print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const std::unique_ptr<char[]>& stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const {
|
||||
const char* type = "";
|
||||
if(entry.n_type & N_STAB) {
|
||||
switch(entry.n_type) {
|
||||
case N_SO: type = "N_SO"; break;
|
||||
case N_OSO: type = "N_OSO"; break;
|
||||
case N_BNSYM: type = "N_BNSYM"; break;
|
||||
case N_ENSYM: type = "N_ENSYM"; break;
|
||||
case N_FUN: type = "N_FUN"; break;
|
||||
}
|
||||
} else if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
type = "N_SECT";
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n",
|
||||
to_ull(j),
|
||||
to_ull(entry.n_un.n_strx),
|
||||
to_ull(entry.n_type),
|
||||
type,
|
||||
to_ull(entry.n_sect),
|
||||
to_ull(entry.n_desc),
|
||||
to_ull(entry.n_value),
|
||||
stringtab == nullptr
|
||||
? "Stringtab error"
|
||||
: entry.n_un.n_strx < stringsize
|
||||
? stringtab.get() + entry.n_un.n_strx
|
||||
: "String index out of bounds"
|
||||
);
|
||||
}
|
||||
|
||||
void mach_o::print_symbol_table() {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
auto symtab_load = load_symbol_table_command(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
if(symtab_load.is_error()) {
|
||||
fprintf(stderr, " error\n");
|
||||
symtab_load.drop_error();
|
||||
continue;
|
||||
}
|
||||
auto& symtab = symtab_load.unwrap_value();
|
||||
fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd));
|
||||
fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize));
|
||||
fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff));
|
||||
fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms));
|
||||
fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff));
|
||||
fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize));
|
||||
auto stringtab = load_string_table(symtab.stroff, symtab.strsize);
|
||||
if(!stringtab) {
|
||||
stringtab.drop_error();
|
||||
}
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!entry) {
|
||||
fprintf(stderr, "error loading symtab entry\n");
|
||||
entry.drop_error();
|
||||
continue;
|
||||
}
|
||||
print_symbol_table_entry(
|
||||
entry.unwrap_value(),
|
||||
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
|
||||
symtab.strsize,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
Result<mach_o::debug_map, internal_error> mach_o::get_debug_map() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
debug_map debug_map;
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
}
|
||||
if(!symtab_info_res.unwrap_value().get()) {
|
||||
return internal_error("No symtab info");
|
||||
}
|
||||
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
std::string current_module;
|
||||
optional<debug_map_entry> current_function;
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto load_entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!load_entry) {
|
||||
return std::move(load_entry).unwrap_error();
|
||||
}
|
||||
auto& entry = load_entry.unwrap_value();
|
||||
// entry.n_type & N_STAB indicates symbolic debug info
|
||||
if(!(entry.n_type & N_STAB)) {
|
||||
continue;
|
||||
}
|
||||
switch(entry.n_type) {
|
||||
case N_SO:
|
||||
// pass - these encode path and filename for the module, if applicable
|
||||
break;
|
||||
case N_OSO:
|
||||
{
|
||||
// sets the module
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
current_module = str.unwrap_value();
|
||||
}
|
||||
break;
|
||||
case N_BNSYM: break; // pass
|
||||
case N_ENSYM: break; // pass
|
||||
case N_FUN:
|
||||
{
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
if(str.unwrap_value()[0] == 0) {
|
||||
// end of function scope
|
||||
if(!current_function) { /**/ }
|
||||
current_function.unwrap().size = entry.n_value;
|
||||
debug_map[current_module].push_back(std::move(current_function).unwrap());
|
||||
} else {
|
||||
current_function = debug_map_entry{};
|
||||
current_function.unwrap().source_address = entry.n_value;
|
||||
current_function.unwrap().name = str.unwrap_value();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return debug_map;
|
||||
}
|
||||
|
||||
Result<std::vector<mach_o::symbol_entry>, internal_error> mach_o::symbol_table() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
std::vector<symbol_entry> symbols;
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
}
|
||||
if(!symtab_info_res.unwrap_value().get()) {
|
||||
return internal_error("No symtab info");
|
||||
}
|
||||
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto load_entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!load_entry) {
|
||||
return std::move(load_entry).unwrap_error();
|
||||
}
|
||||
auto& entry = load_entry.unwrap_value();
|
||||
if(entry.n_type & N_STAB) {
|
||||
continue;
|
||||
}
|
||||
if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
symbols.push_back({
|
||||
entry.n_value,
|
||||
str.unwrap_value()
|
||||
});
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
void mach_o::print_debug_map(const debug_map& debug_map) {
|
||||
for(const auto& entry : debug_map) {
|
||||
std::cout<<entry.first<<": "<< '\n';
|
||||
for(const auto& symbol : entry.second) {
|
||||
std::cerr
|
||||
<< " "
|
||||
<< symbol.name
|
||||
<< " "
|
||||
<< std::hex
|
||||
<< symbol.source_address
|
||||
<< " "
|
||||
<< symbol.size
|
||||
<< std::dec
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<monostate, internal_error> mach_o::load_mach() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
bits = Bits;
|
||||
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
|
||||
std::size_t header_size = sizeof(Mach_Header);
|
||||
auto load_header = load_bytes<Mach_Header>(file, load_base);
|
||||
if(!load_header) {
|
||||
return load_header.unwrap_error();
|
||||
}
|
||||
Mach_Header& header = load_header.unwrap_value();
|
||||
magic = header.magic;
|
||||
if(should_swap()) {
|
||||
swap_mach_header(header);
|
||||
}
|
||||
cputype = header.cputype;
|
||||
cpusubtype = header.cpusubtype;
|
||||
filetype = header.filetype;
|
||||
n_load_commands = header.ncmds;
|
||||
sizeof_load_commands = header.sizeofcmds;
|
||||
flags = header.flags;
|
||||
// handle load commands
|
||||
std::uint32_t ncmds = header.ncmds;
|
||||
std::uint32_t load_commands_offset = load_base + header_size;
|
||||
// iterate load commands
|
||||
std::uint32_t actual_offset = load_commands_offset;
|
||||
for(std::uint32_t i = 0; i < ncmds; i++) {
|
||||
auto load_cmd = load_bytes<load_command>(file, actual_offset);
|
||||
if(!load_cmd) {
|
||||
return load_cmd.unwrap_error();
|
||||
}
|
||||
load_command& cmd = load_cmd.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_load_command(&cmd, NX_UnknownByteOrder);
|
||||
}
|
||||
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
|
||||
actual_offset += cmd.cmdsize;
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
|
||||
Result<monostate, internal_error> mach_o::load_fat_mach() {
|
||||
std::size_t header_size = sizeof(fat_header);
|
||||
std::size_t arch_size = sizeof(fat_arch);
|
||||
auto load_header = load_bytes<fat_header>(file, 0);
|
||||
if(!load_header) {
|
||||
return load_header.unwrap_error();
|
||||
}
|
||||
fat_header& header = load_header.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
// off_t arch_offset = (off_t)header_size;
|
||||
// for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
// fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
// if(should_swap()) {
|
||||
// swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
// }
|
||||
// off_t mach_header_offset = (off_t)arch.offset;
|
||||
// arch_offset += arch_size;
|
||||
// std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
// std::cerr<<"xxx: "<<arch.cputype<<" : "<<mhp->cputype<<std::endl;
|
||||
// std::cerr<<" "<<arch.cpusubtype<<" : "<<static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<<std::endl;
|
||||
// if(
|
||||
// arch.cputype == mhp->cputype &&
|
||||
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
|
||||
// ) {
|
||||
// load_base = mach_header_offset;
|
||||
// fat_index = i;
|
||||
// if(is_magic_64(magic)) {
|
||||
// load_mach<64>(true);
|
||||
// } else {
|
||||
// load_mach<32>(true);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
std::vector<fat_arch> fat_arches;
|
||||
fat_arches.reserve(header.nfat_arch);
|
||||
off_t arch_offset = (off_t)header_size;
|
||||
for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
auto load_arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
if(!load_arch) {
|
||||
return load_arch.unwrap_error();
|
||||
}
|
||||
fat_arch& arch = load_arch.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
fat_arches.push_back(arch);
|
||||
arch_offset += arch_size;
|
||||
}
|
||||
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
fat_arch* best = NXFindBestFatArch(
|
||||
mhp->cputype,
|
||||
mhp->cpusubtype,
|
||||
fat_arches.data(),
|
||||
header.nfat_arch
|
||||
);
|
||||
if(best) {
|
||||
off_t mach_header_offset = (off_t)best->offset;
|
||||
auto magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
}
|
||||
load_base = mach_header_offset;
|
||||
fat_index = best - fat_arches.data();
|
||||
if(is_magic_64(magic.unwrap_value())) {
|
||||
load_mach<64>();
|
||||
} else {
|
||||
load_mach<32>();
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
// If this is reached... something went wrong. The cpu we're on wasn't found.
|
||||
return internal_error("Couldn't find appropriate architecture in fat Mach-O");
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<segment_command_64, internal_error> mach_o::load_segment_command(std::uint32_t offset) const {
|
||||
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
|
||||
auto load_segment = load_bytes<Segment_Command>(file, offset);
|
||||
if(!load_segment) {
|
||||
return load_segment.unwrap_error();
|
||||
}
|
||||
Segment_Command& segment = load_segment.unwrap_value();
|
||||
ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT);
|
||||
if(should_swap()) {
|
||||
swap_segment_command(segment);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
segment_command_64 common;
|
||||
common.cmd = segment.cmd;
|
||||
common.cmdsize = segment.cmdsize;
|
||||
static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx");
|
||||
memcpy(common.segname, segment.segname, 16);
|
||||
common.vmaddr = segment.vmaddr;
|
||||
common.vmsize = segment.vmsize;
|
||||
common.fileoff = segment.fileoff;
|
||||
common.filesize = segment.filesize;
|
||||
common.maxprot = segment.maxprot;
|
||||
common.initprot = segment.initprot;
|
||||
common.nsects = segment.nsects;
|
||||
common.flags = segment.flags;
|
||||
return common;
|
||||
}
|
||||
|
||||
Result<symtab_command, internal_error> mach_o::load_symbol_table_command(std::uint32_t offset) const {
|
||||
auto load_symtab = load_bytes<symtab_command>(file, offset);
|
||||
if(!load_symtab) {
|
||||
return load_symtab.unwrap_error();
|
||||
}
|
||||
symtab_command& symtab = load_symtab.unwrap_value();
|
||||
ASSERT(symtab.cmd == LC_SYMTAB);
|
||||
if(should_swap()) {
|
||||
swap_symtab_command(&symtab, NX_UnknownByteOrder);
|
||||
}
|
||||
return symtab;
|
||||
}
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<nlist_64, internal_error> mach_o::load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const {
|
||||
using Nlist = typename std::conditional<Bits == 32, struct nlist, struct nlist_64>::type;
|
||||
uint32_t offset = load_base + symbol_base + index * sizeof(Nlist);
|
||||
auto load_entry = load_bytes<Nlist>(file, offset);
|
||||
if(!load_entry) {
|
||||
return load_entry.unwrap_error();
|
||||
}
|
||||
Nlist& entry = load_entry.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_nlist(entry);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
nlist_64 common;
|
||||
common.n_un.n_strx = entry.n_un.n_strx;
|
||||
common.n_type = entry.n_type;
|
||||
common.n_sect = entry.n_sect;
|
||||
common.n_desc = entry.n_desc;
|
||||
common.n_value = entry.n_value;
|
||||
return common;
|
||||
}
|
||||
|
||||
Result<std::unique_ptr<char[]>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
|
||||
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error while loading mach-o symbol table");
|
||||
}
|
||||
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
|
||||
return internal_error("fread error while loading mach-o symbol table");
|
||||
}
|
||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool mach_o::should_swap() const {
|
||||
return should_swap_bytes(magic);
|
||||
}
|
||||
|
||||
Result<bool, internal_error> macho_is_fat(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
} else {
|
||||
return is_fat_magic(magic.unwrap_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif
|
||||
@ -6,100 +6,20 @@
|
||||
|
||||
#if IS_APPLE
|
||||
|
||||
// A number of mach-o functions are deprecated as of macos 13
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <crt_externs.h>
|
||||
#include <mach-o/nlist.h>
|
||||
#include <mach-o/stab.h>
|
||||
#include <mach-o/arch.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/nlist.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
inline bool is_mach_o(std::uint32_t magic) {
|
||||
switch(magic) {
|
||||
case FAT_MAGIC:
|
||||
case FAT_CIGAM:
|
||||
case MH_MAGIC:
|
||||
case MH_CIGAM:
|
||||
case MH_MAGIC_64:
|
||||
case MH_CIGAM_64:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool file_is_mach_o(const std::string& object_path) noexcept {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(magic) {
|
||||
return is_mach_o(magic.unwrap_value());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool is_fat_magic(std::uint32_t magic) {
|
||||
return magic == FAT_MAGIC || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
|
||||
// and https://lowlevelbits.org/parsing-mach-o-files/
|
||||
inline bool is_magic_64(std::uint32_t magic) {
|
||||
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
|
||||
}
|
||||
|
||||
inline bool should_swap_bytes(std::uint32_t magic) {
|
||||
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
|
||||
}
|
||||
|
||||
inline void swap_mach_header(mach_header_64& header) {
|
||||
swap_mach_header_64(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_mach_header(mach_header& header) {
|
||||
swap_mach_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_segment_command(segment_command_64& segment) {
|
||||
swap_segment_command_64(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_segment_command(segment_command& segment) {
|
||||
swap_segment_command(&segment, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_nlist(struct nlist& entry) {
|
||||
swap_nlist(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
inline void swap_nlist(struct nlist_64& entry) {
|
||||
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
|
||||
#ifdef __LP64__
|
||||
#define LP(x) x##_64
|
||||
#else
|
||||
#define LP(x) x
|
||||
#endif
|
||||
bool file_is_mach_o(const std::string& object_path) noexcept;
|
||||
|
||||
struct load_command_entry {
|
||||
std::uint32_t file_offset;
|
||||
@ -127,13 +47,7 @@ namespace detail {
|
||||
struct symtab_info_data {
|
||||
symtab_command symtab;
|
||||
std::unique_ptr<char[]> stringtab;
|
||||
Result<const char*, internal_error> get_string(std::size_t index) const {
|
||||
if(stringtab && index < symtab.strsize) {
|
||||
return stringtab.get() + index;
|
||||
} else {
|
||||
return internal_error("can't retrieve symbol from symtab");
|
||||
}
|
||||
}
|
||||
Result<const char*, internal_error> get_string(std::size_t index) const;
|
||||
};
|
||||
|
||||
bool tried_to_load_symtab = false;
|
||||
@ -148,197 +62,30 @@ namespace detail {
|
||||
object_path(object_path),
|
||||
magic(magic) {}
|
||||
|
||||
Result<monostate, internal_error> load() {
|
||||
if(magic == FAT_MAGIC || magic == FAT_CIGAM) {
|
||||
return load_fat_mach();
|
||||
} else {
|
||||
fat_index = 0;
|
||||
if(is_magic_64(magic)) {
|
||||
return load_mach<64>();
|
||||
} else {
|
||||
return load_mach<32>();
|
||||
}
|
||||
}
|
||||
}
|
||||
Result<monostate, internal_error> load();
|
||||
|
||||
public:
|
||||
static inline NODISCARD Result<mach_o, internal_error> open_mach_o(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
}
|
||||
if(!is_mach_o(magic.unwrap_value())) {
|
||||
return internal_error("File is not mach-o {}", object_path);
|
||||
}
|
||||
mach_o obj(std::move(file), object_path, magic.unwrap_value());
|
||||
auto result = obj.load();
|
||||
if(result.is_error()) {
|
||||
return result.unwrap_error();
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
static NODISCARD Result<mach_o, internal_error> open_mach_o(const std::string& object_path);
|
||||
|
||||
mach_o(mach_o&&) = default;
|
||||
~mach_o() = default;
|
||||
|
||||
Result<std::uintptr_t, internal_error> get_text_vmaddr() {
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
if(segment.is_error()) {
|
||||
return std::move(segment).unwrap_error();
|
||||
}
|
||||
if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) {
|
||||
return segment.unwrap_value().vmaddr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// somehow no __TEXT section was found...
|
||||
return internal_error("Couldn't find __TEXT section while parsing Mach-O object");
|
||||
}
|
||||
Result<std::uintptr_t, internal_error> get_text_vmaddr();
|
||||
|
||||
std::size_t get_fat_index() const {
|
||||
VERIFY(fat_index != std::numeric_limits<std::size_t>::max());
|
||||
return fat_index;
|
||||
}
|
||||
std::size_t get_fat_index() const;
|
||||
|
||||
void print_segments() const {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
|
||||
auto segment_load = command.cmd == LC_SEGMENT_64
|
||||
? load_segment_command<64>(command.file_offset)
|
||||
: load_segment_command<32>(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
if(segment_load.is_error()) {
|
||||
fprintf(stderr, " error\n");
|
||||
segment_load.drop_error();
|
||||
continue;
|
||||
}
|
||||
auto& segment = segment_load.unwrap_value();
|
||||
fprintf(stderr, " cmd %u\n", segment.cmd);
|
||||
fprintf(stderr, " cmdsize %u\n", segment.cmdsize);
|
||||
fprintf(stderr, " segname %s\n", segment.segname);
|
||||
fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr);
|
||||
fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize);
|
||||
fprintf(stderr, " off 0x%llx\n", segment.fileoff);
|
||||
fprintf(stderr, " filesize %llu\n", segment.filesize);
|
||||
fprintf(stderr, " nsects %u\n", segment.nsects);
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
void print_segments() const;
|
||||
|
||||
Result<std::reference_wrapper<optional<symtab_info_data>>, internal_error> get_symtab_info() {
|
||||
if(!symtab_info.has_value() && !tried_to_load_symtab) {
|
||||
// don't try to load the symtab again if for some reason loading here fails
|
||||
tried_to_load_symtab = true;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
symtab_info_data info;
|
||||
auto symtab = load_symbol_table_command(command.file_offset);
|
||||
if(!symtab) {
|
||||
return std::move(symtab).unwrap_error();
|
||||
}
|
||||
info.symtab = symtab.unwrap_value();
|
||||
auto string = load_string_table(info.symtab.stroff, info.symtab.strsize);
|
||||
if(!string) {
|
||||
return std::move(string).unwrap_error();
|
||||
}
|
||||
info.stringtab = std::move(string).unwrap_value();
|
||||
symtab_info = std::move(info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return std::reference_wrapper<optional<symtab_info_data>>{symtab_info};
|
||||
}
|
||||
Result<std::reference_wrapper<optional<symtab_info_data>>, internal_error> get_symtab_info();
|
||||
|
||||
void print_symbol_table_entry(
|
||||
const nlist_64& entry,
|
||||
const std::unique_ptr<char[]>& stringtab,
|
||||
std::size_t stringsize,
|
||||
std::size_t j
|
||||
) const {
|
||||
const char* type = "";
|
||||
if(entry.n_type & N_STAB) {
|
||||
switch(entry.n_type) {
|
||||
case N_SO: type = "N_SO"; break;
|
||||
case N_OSO: type = "N_OSO"; break;
|
||||
case N_BNSYM: type = "N_BNSYM"; break;
|
||||
case N_ENSYM: type = "N_ENSYM"; break;
|
||||
case N_FUN: type = "N_FUN"; break;
|
||||
}
|
||||
} else if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
type = "N_SECT";
|
||||
}
|
||||
fprintf(
|
||||
stderr,
|
||||
"%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n",
|
||||
to_ull(j),
|
||||
to_ull(entry.n_un.n_strx),
|
||||
to_ull(entry.n_type),
|
||||
type,
|
||||
to_ull(entry.n_sect),
|
||||
to_ull(entry.n_desc),
|
||||
to_ull(entry.n_value),
|
||||
stringtab == nullptr
|
||||
? "Stringtab error"
|
||||
: entry.n_un.n_strx < stringsize
|
||||
? stringtab.get() + entry.n_un.n_strx
|
||||
: "String index out of bounds"
|
||||
);
|
||||
}
|
||||
) const;
|
||||
|
||||
void print_symbol_table() {
|
||||
int i = 0;
|
||||
for(const auto& command : load_commands) {
|
||||
if(command.cmd == LC_SYMTAB) {
|
||||
auto symtab_load = load_symbol_table_command(command.file_offset);
|
||||
fprintf(stderr, "Load command %d\n", i);
|
||||
if(symtab_load.is_error()) {
|
||||
fprintf(stderr, " error\n");
|
||||
symtab_load.drop_error();
|
||||
continue;
|
||||
}
|
||||
auto& symtab = symtab_load.unwrap_value();
|
||||
fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd));
|
||||
fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize));
|
||||
fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff));
|
||||
fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms));
|
||||
fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff));
|
||||
fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize));
|
||||
auto stringtab = load_string_table(symtab.stroff, symtab.strsize);
|
||||
if(!stringtab) {
|
||||
stringtab.drop_error();
|
||||
}
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!entry) {
|
||||
fprintf(stderr, "error loading symtab entry\n");
|
||||
entry.drop_error();
|
||||
continue;
|
||||
}
|
||||
print_symbol_table_entry(
|
||||
entry.unwrap_value(),
|
||||
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
|
||||
symtab.strsize,
|
||||
j
|
||||
);
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
void print_symbol_table();
|
||||
|
||||
struct debug_map_entry {
|
||||
uint64_t source_address;
|
||||
@ -355,349 +102,36 @@ namespace detail {
|
||||
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
Result<debug_map, internal_error> get_debug_map() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
debug_map debug_map;
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
}
|
||||
if(!symtab_info_res.unwrap_value().get()) {
|
||||
return internal_error("No symtab info");
|
||||
}
|
||||
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
std::string current_module;
|
||||
optional<debug_map_entry> current_function;
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto load_entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!load_entry) {
|
||||
return std::move(load_entry).unwrap_error();
|
||||
}
|
||||
auto& entry = load_entry.unwrap_value();
|
||||
// entry.n_type & N_STAB indicates symbolic debug info
|
||||
if(!(entry.n_type & N_STAB)) {
|
||||
continue;
|
||||
}
|
||||
switch(entry.n_type) {
|
||||
case N_SO:
|
||||
// pass - these encode path and filename for the module, if applicable
|
||||
break;
|
||||
case N_OSO:
|
||||
{
|
||||
// sets the module
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
current_module = str.unwrap_value();
|
||||
}
|
||||
break;
|
||||
case N_BNSYM: break; // pass
|
||||
case N_ENSYM: break; // pass
|
||||
case N_FUN:
|
||||
{
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
if(str.unwrap_value()[0] == 0) {
|
||||
// end of function scope
|
||||
if(!current_function) { /**/ }
|
||||
current_function.unwrap().size = entry.n_value;
|
||||
debug_map[current_module].push_back(std::move(current_function).unwrap());
|
||||
} else {
|
||||
current_function = debug_map_entry{};
|
||||
current_function.unwrap().source_address = entry.n_value;
|
||||
current_function.unwrap().name = str.unwrap_value();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return debug_map;
|
||||
}
|
||||
Result<debug_map, internal_error> get_debug_map();
|
||||
|
||||
Result<std::vector<symbol_entry>, internal_error> symbol_table() {
|
||||
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
|
||||
// first collect symbols and the objects they come from
|
||||
std::vector<symbol_entry> symbols;
|
||||
auto symtab_info_res = get_symtab_info();
|
||||
if(!symtab_info_res) {
|
||||
return std::move(symtab_info_res).unwrap_error();
|
||||
}
|
||||
if(!symtab_info_res.unwrap_value().get()) {
|
||||
return internal_error("No symtab info");
|
||||
}
|
||||
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
|
||||
const auto& symtab = symtab_info.symtab;
|
||||
// TODO: Take timestamp into account?
|
||||
for(std::size_t j = 0; j < symtab.nsyms; j++) {
|
||||
auto load_entry = bits == 32
|
||||
? load_symtab_entry<32>(symtab.symoff, j)
|
||||
: load_symtab_entry<64>(symtab.symoff, j);
|
||||
if(!load_entry) {
|
||||
return std::move(load_entry).unwrap_error();
|
||||
}
|
||||
auto& entry = load_entry.unwrap_value();
|
||||
if(entry.n_type & N_STAB) {
|
||||
continue;
|
||||
}
|
||||
if((entry.n_type & N_TYPE) == N_SECT) {
|
||||
auto str = symtab_info.get_string(entry.n_un.n_strx);
|
||||
if(!str) {
|
||||
return std::move(str).unwrap_error();
|
||||
}
|
||||
symbols.push_back({
|
||||
entry.n_value,
|
||||
str.unwrap_value()
|
||||
});
|
||||
}
|
||||
}
|
||||
return symbols;
|
||||
}
|
||||
Result<std::vector<symbol_entry>, internal_error> symbol_table();
|
||||
|
||||
// produce information similar to dsymutil -dump-debug-map
|
||||
static void print_debug_map(const debug_map& debug_map) {
|
||||
for(const auto& entry : debug_map) {
|
||||
std::cout<<entry.first<<": "<< '\n';
|
||||
for(const auto& symbol : entry.second) {
|
||||
std::cerr
|
||||
<< " "
|
||||
<< symbol.name
|
||||
<< " "
|
||||
<< std::hex
|
||||
<< symbol.source_address
|
||||
<< " "
|
||||
<< symbol.size
|
||||
<< std::dec
|
||||
<< '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
static void print_debug_map(const debug_map& debug_map);
|
||||
|
||||
private:
|
||||
template<std::size_t Bits>
|
||||
Result<monostate, internal_error> load_mach() {
|
||||
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
|
||||
bits = Bits;
|
||||
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
|
||||
std::size_t header_size = sizeof(Mach_Header);
|
||||
auto load_header = load_bytes<Mach_Header>(file, load_base);
|
||||
if(!load_header) {
|
||||
return load_header.unwrap_error();
|
||||
}
|
||||
Mach_Header& header = load_header.unwrap_value();
|
||||
magic = header.magic;
|
||||
if(should_swap()) {
|
||||
swap_mach_header(header);
|
||||
}
|
||||
cputype = header.cputype;
|
||||
cpusubtype = header.cpusubtype;
|
||||
filetype = header.filetype;
|
||||
n_load_commands = header.ncmds;
|
||||
sizeof_load_commands = header.sizeofcmds;
|
||||
flags = header.flags;
|
||||
// handle load commands
|
||||
std::uint32_t ncmds = header.ncmds;
|
||||
std::uint32_t load_commands_offset = load_base + header_size;
|
||||
// iterate load commands
|
||||
std::uint32_t actual_offset = load_commands_offset;
|
||||
for(std::uint32_t i = 0; i < ncmds; i++) {
|
||||
auto load_cmd = load_bytes<load_command>(file, actual_offset);
|
||||
if(!load_cmd) {
|
||||
return load_cmd.unwrap_error();
|
||||
}
|
||||
load_command& cmd = load_cmd.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_load_command(&cmd, NX_UnknownByteOrder);
|
||||
}
|
||||
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
|
||||
actual_offset += cmd.cmdsize;
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
Result<monostate, internal_error> load_mach();
|
||||
|
||||
Result<monostate, internal_error> load_fat_mach() {
|
||||
std::size_t header_size = sizeof(fat_header);
|
||||
std::size_t arch_size = sizeof(fat_arch);
|
||||
auto load_header = load_bytes<fat_header>(file, 0);
|
||||
if(!load_header) {
|
||||
return load_header.unwrap_error();
|
||||
}
|
||||
fat_header& header = load_header.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_header(&header, NX_UnknownByteOrder);
|
||||
}
|
||||
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
// off_t arch_offset = (off_t)header_size;
|
||||
// for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
// fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
// if(should_swap()) {
|
||||
// swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
// }
|
||||
// off_t mach_header_offset = (off_t)arch.offset;
|
||||
// arch_offset += arch_size;
|
||||
// std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
// std::cerr<<"xxx: "<<arch.cputype<<" : "<<mhp->cputype<<std::endl;
|
||||
// std::cerr<<" "<<arch.cpusubtype<<" : "<<static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<<std::endl;
|
||||
// if(
|
||||
// arch.cputype == mhp->cputype &&
|
||||
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
|
||||
// ) {
|
||||
// load_base = mach_header_offset;
|
||||
// fat_index = i;
|
||||
// if(is_magic_64(magic)) {
|
||||
// load_mach<64>(true);
|
||||
// } else {
|
||||
// load_mach<32>(true);
|
||||
// }
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
std::vector<fat_arch> fat_arches;
|
||||
fat_arches.reserve(header.nfat_arch);
|
||||
off_t arch_offset = (off_t)header_size;
|
||||
for(std::size_t i = 0; i < header.nfat_arch; i++) {
|
||||
auto load_arch = load_bytes<fat_arch>(file, arch_offset);
|
||||
if(!load_arch) {
|
||||
return load_arch.unwrap_error();
|
||||
}
|
||||
fat_arch& arch = load_arch.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
|
||||
}
|
||||
fat_arches.push_back(arch);
|
||||
arch_offset += arch_size;
|
||||
}
|
||||
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
|
||||
fat_arch* best = NXFindBestFatArch(
|
||||
mhp->cputype,
|
||||
mhp->cpusubtype,
|
||||
fat_arches.data(),
|
||||
header.nfat_arch
|
||||
);
|
||||
if(best) {
|
||||
off_t mach_header_offset = (off_t)best->offset;
|
||||
auto magic = load_bytes<std::uint32_t>(file, mach_header_offset);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
}
|
||||
load_base = mach_header_offset;
|
||||
fat_index = best - fat_arches.data();
|
||||
if(is_magic_64(magic.unwrap_value())) {
|
||||
load_mach<64>();
|
||||
} else {
|
||||
load_mach<32>();
|
||||
}
|
||||
return monostate{};
|
||||
}
|
||||
// If this is reached... something went wrong. The cpu we're on wasn't found.
|
||||
return internal_error("Couldn't find appropriate architecture in fat Mach-O");
|
||||
}
|
||||
Result<monostate, internal_error> load_fat_mach();
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<segment_command_64, internal_error> load_segment_command(std::uint32_t offset) const {
|
||||
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
|
||||
auto load_segment = load_bytes<Segment_Command>(file, offset);
|
||||
if(!load_segment) {
|
||||
return load_segment.unwrap_error();
|
||||
}
|
||||
Segment_Command& segment = load_segment.unwrap_value();
|
||||
ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT);
|
||||
if(should_swap()) {
|
||||
swap_segment_command(segment);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
segment_command_64 common;
|
||||
common.cmd = segment.cmd;
|
||||
common.cmdsize = segment.cmdsize;
|
||||
static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx");
|
||||
memcpy(common.segname, segment.segname, 16);
|
||||
common.vmaddr = segment.vmaddr;
|
||||
common.vmsize = segment.vmsize;
|
||||
common.fileoff = segment.fileoff;
|
||||
common.filesize = segment.filesize;
|
||||
common.maxprot = segment.maxprot;
|
||||
common.initprot = segment.initprot;
|
||||
common.nsects = segment.nsects;
|
||||
common.flags = segment.flags;
|
||||
return common;
|
||||
}
|
||||
Result<segment_command_64, internal_error> load_segment_command(std::uint32_t offset) const;
|
||||
|
||||
Result<symtab_command, internal_error> load_symbol_table_command(std::uint32_t offset) const {
|
||||
auto load_symtab = load_bytes<symtab_command>(file, offset);
|
||||
if(!load_symtab) {
|
||||
return load_symtab.unwrap_error();
|
||||
}
|
||||
symtab_command& symtab = load_symtab.unwrap_value();
|
||||
ASSERT(symtab.cmd == LC_SYMTAB);
|
||||
if(should_swap()) {
|
||||
swap_symtab_command(&symtab, NX_UnknownByteOrder);
|
||||
}
|
||||
return symtab;
|
||||
}
|
||||
Result<symtab_command, internal_error> load_symbol_table_command(std::uint32_t offset) const;
|
||||
|
||||
template<std::size_t Bits>
|
||||
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const {
|
||||
using Nlist = typename std::conditional<Bits == 32, struct nlist, struct nlist_64>::type;
|
||||
uint32_t offset = load_base + symbol_base + index * sizeof(Nlist);
|
||||
auto load_entry = load_bytes<Nlist>(file, offset);
|
||||
if(!load_entry) {
|
||||
return load_entry.unwrap_error();
|
||||
}
|
||||
Nlist& entry = load_entry.unwrap_value();
|
||||
if(should_swap()) {
|
||||
swap_nlist(entry);
|
||||
}
|
||||
// fields match just u64 instead of u32
|
||||
nlist_64 common;
|
||||
common.n_un.n_strx = entry.n_un.n_strx;
|
||||
common.n_type = entry.n_type;
|
||||
common.n_sect = entry.n_sect;
|
||||
common.n_desc = entry.n_desc;
|
||||
common.n_value = entry.n_value;
|
||||
return common;
|
||||
}
|
||||
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
|
||||
|
||||
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
|
||||
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
|
||||
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
|
||||
return internal_error("fseek error while loading mach-o symbol table");
|
||||
}
|
||||
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
|
||||
return internal_error("fread error while loading mach-o symbol table");
|
||||
}
|
||||
buffer[byte_count] = 0; // just out of an abundance of caution
|
||||
return buffer;
|
||||
}
|
||||
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
|
||||
|
||||
bool should_swap() const {
|
||||
return should_swap_bytes(magic);
|
||||
}
|
||||
bool should_swap() const;
|
||||
};
|
||||
|
||||
inline Result<bool, internal_error> macho_is_fat(const std::string& object_path) {
|
||||
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
|
||||
if(file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::uint32_t>(file, 0);
|
||||
if(!magic) {
|
||||
return magic.unwrap_error();
|
||||
} else {
|
||||
return is_fat_magic(magic.unwrap_value());
|
||||
}
|
||||
}
|
||||
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
95
src/binary/module_base.cpp
Normal file
95
src/binary/module_base.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#include "module_base.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
|
||||
#elif IS_WINDOWS
|
||||
#include <windows.h>
|
||||
#include "pe.hpp"
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
#if IS_LINUX
|
||||
Result<std::uintptr_t, internal_error> 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);
|
||||
// TODO: Cache the error
|
||||
if(base.is_error()) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#elif IS_APPLE
|
||||
Result<std::uintptr_t, internal_error> 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 obj = mach_o::open_mach_o(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!obj) {
|
||||
return obj.unwrap_error();
|
||||
}
|
||||
auto base = obj.unwrap_value().get_text_vmaddr();
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#else // Windows
|
||||
Result<std::uintptr_t, internal_error> 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);
|
||||
// TODO: Cache the error
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@ -4,94 +4,12 @@
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#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
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
#if IS_LINUX
|
||||
inline Result<std::uintptr_t, internal_error> 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);
|
||||
// TODO: Cache the error
|
||||
if(base.is_error()) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#elif IS_APPLE
|
||||
inline Result<std::uintptr_t, internal_error> 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 obj = mach_o::open_mach_o(object_path);
|
||||
// TODO: Cache the error
|
||||
if(!obj) {
|
||||
return obj.unwrap_error();
|
||||
}
|
||||
auto base = obj.unwrap_value().get_text_vmaddr();
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#else // Windows
|
||||
inline Result<std::uintptr_t, internal_error> 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);
|
||||
// TODO: Cache the error
|
||||
if(!base) {
|
||||
return std::move(base).unwrap_error();
|
||||
}
|
||||
cache.insert(it, {object_path, base.unwrap_value()});
|
||||
return base;
|
||||
} else {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
172
src/binary/object.cpp
Normal file
172
src/binary/object.cpp
Normal file
@ -0,0 +1,172 @@
|
||||
#include "object.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "module_base.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#if IS_LINUX
|
||||
#include <link.h> // needed for dladdr1's link_map info
|
||||
#endif
|
||||
#elif IS_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
#if IS_LINUX
|
||||
std::string resolve_l_name(const char* l_name) {
|
||||
if(l_name != nullptr && l_name[0] != 0) {
|
||||
return 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) {
|
||||
return ""; // TODO
|
||||
} else {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
// Use _dl_find_object when we can, it's orders of magnitude faster
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
dl_find_object result;
|
||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread safe
|
||||
frame.object_path = resolve_l_name(result.dlfo_link_map->l_name);
|
||||
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#else
|
||||
// dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
||||
// https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26
|
||||
Dl_info info;
|
||||
link_map* link_map_info;
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
if(
|
||||
// thread safe
|
||||
dladdr1(reinterpret_cast<void*>(address), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP)
|
||||
) {
|
||||
frame.object_path = resolve_l_name(link_map_info->l_name);
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
#elif IS_APPLE
|
||||
// macos doesn't have dladdr1 but it seems its dli_fname behaves more sensibly?
|
||||
// dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
||||
Dl_info info;
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
if(dladdr(reinterpret_cast<void*>(address), &info)) { // thread safe
|
||||
frame.object_path = info.dli_fname;
|
||||
auto base = get_module_image_base(info.dli_fname);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#else
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
object_frame get_frame_object_info(frame_ptr address) {
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
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*>(address),
|
||||
&handle
|
||||
)) {
|
||||
frame.object_path = get_module_name(handle);
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(handle)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
base.drop_error();
|
||||
}
|
||||
} else {
|
||||
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses) {
|
||||
std::vector<object_frame> frames;
|
||||
frames.reserve(addresses.size());
|
||||
for(const frame_ptr address : addresses) {
|
||||
frames.push_back(get_frame_object_info(address));
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.is_error()) {
|
||||
throw base.unwrap_error(); // This throw is intentional
|
||||
}
|
||||
return {
|
||||
frame.raw_address,
|
||||
frame.address_relative_to_object_start + base.unwrap_value(),
|
||||
frame.object_path
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,168 +7,14 @@
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#if IS_LINUX
|
||||
#include <link.h> // needed for dladdr1's link_map info
|
||||
#endif
|
||||
#elif IS_WINDOWS
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
#if IS_LINUX
|
||||
inline std::string resolve_l_name(const char* l_name) {
|
||||
if(l_name != nullptr && l_name[0] != 0) {
|
||||
return 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) {
|
||||
return ""; // TODO
|
||||
} else {
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple
|
||||
inline object_frame get_frame_object_info(frame_ptr address) {
|
||||
// Use _dl_find_object when we can, it's orders of magnitude faster
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
dl_find_object result;
|
||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread safe
|
||||
frame.object_path = resolve_l_name(result.dlfo_link_map->l_name);
|
||||
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#else
|
||||
// dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
|
||||
inline object_frame get_frame_object_info(frame_ptr address) {
|
||||
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
||||
// https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26
|
||||
Dl_info info;
|
||||
link_map* link_map_info;
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
if(
|
||||
// thread safe
|
||||
dladdr1(reinterpret_cast<void*>(address), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP)
|
||||
) {
|
||||
frame.object_path = resolve_l_name(link_map_info->l_name);
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
#elif IS_APPLE
|
||||
// macos doesn't have dladdr1 but it seems its dli_fname behaves more sensibly?
|
||||
// dladdr queries are needed to get pre-ASLR addresses and targets to run addr2line on
|
||||
inline object_frame get_frame_object_info(frame_ptr address) {
|
||||
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
|
||||
Dl_info info;
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
frame.object_address = 0;
|
||||
if(dladdr(reinterpret_cast<void*>(address), &info)) { // thread safe
|
||||
frame.object_path = info.dli_fname;
|
||||
auto base = get_module_image_base(info.dli_fname);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
base.drop_error();
|
||||
}
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#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;
|
||||
}
|
||||
}
|
||||
object_frame get_frame_object_info(frame_ptr address);
|
||||
|
||||
inline object_frame get_frame_object_info(frame_ptr address) {
|
||||
object_frame frame;
|
||||
frame.raw_address = address;
|
||||
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*>(address),
|
||||
&handle
|
||||
)) {
|
||||
frame.object_path = get_module_name(handle);
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.has_value()) {
|
||||
frame.object_address = address
|
||||
- reinterpret_cast<std::uintptr_t>(handle)
|
||||
+ base.unwrap_value();
|
||||
} else {
|
||||
base.drop_error();
|
||||
}
|
||||
} else {
|
||||
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
|
||||
}
|
||||
return frame;
|
||||
}
|
||||
#endif
|
||||
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses);
|
||||
|
||||
inline std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses) {
|
||||
std::vector<object_frame> frames;
|
||||
frames.reserve(addresses.size());
|
||||
for(const frame_ptr address : addresses) {
|
||||
frames.push_back(get_frame_object_info(address));
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
inline object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
|
||||
auto base = get_module_image_base(frame.object_path);
|
||||
if(base.is_error()) {
|
||||
throw base.unwrap_error(); // This throw is intentional
|
||||
}
|
||||
return {
|
||||
frame.raw_address,
|
||||
frame.address_relative_to_object_start + base.unwrap_value(),
|
||||
frame.object_path
|
||||
};
|
||||
}
|
||||
object_frame resolve_safe_object_frame(const safe_object_frame& frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
95
src/binary/pe.cpp
Normal file
95
src/binary/pe.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
#include "pe.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T pe_byteswap_if_needed(T value) {
|
||||
// PE header values are little endian, I think dos e_lfanew should be too
|
||||
if(!is_little_endian()) {
|
||||
return byteswap(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path) {
|
||||
// https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ
|
||||
// https://0xrick.github.io/win-internals/pe3/
|
||||
// Endianness should always be little for dos and pe headers
|
||||
std::FILE* file_ptr;
|
||||
errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb");
|
||||
auto file = raii_wrap(std::move(file_ptr), file_deleter);
|
||||
if(ret != 0 || file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::array<char, 2>>(file, 0);
|
||||
if(!magic) {
|
||||
return std::move(magic).unwrap_error();
|
||||
}
|
||||
if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) {
|
||||
return internal_error("File is not a PE file {}", object_path);
|
||||
}
|
||||
auto e_lfanew = load_bytes<DWORD>(file, 0x3c); // dos header + 0x3c
|
||||
if(!e_lfanew) {
|
||||
return std::move(e_lfanew).unwrap_error();
|
||||
}
|
||||
DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value());
|
||||
auto signature = load_bytes<std::array<char, 4>>(file, nt_header_offset); // nt header + 0
|
||||
if(!signature) {
|
||||
return std::move(signature).unwrap_error();
|
||||
}
|
||||
if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) {
|
||||
return internal_error("File is not a PE file {}", object_path);
|
||||
}
|
||||
auto size_of_optional_header_raw = load_bytes<WORD>(file, nt_header_offset + 4 + 0x10); // file header + 0x10
|
||||
if(!size_of_optional_header_raw) {
|
||||
return std::move(size_of_optional_header_raw).unwrap_error();
|
||||
}
|
||||
WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value());
|
||||
if(size_of_optional_header == 0) {
|
||||
return internal_error("Unexpected optional header size for PE file");
|
||||
}
|
||||
auto optional_header_magic_raw = load_bytes<WORD>(file, nt_header_offset + 0x18); // optional header + 0x0
|
||||
if(!optional_header_magic_raw) {
|
||||
return std::move(optional_header_magic_raw).unwrap_error();
|
||||
}
|
||||
WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value());
|
||||
VERIFY(
|
||||
optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC,
|
||||
"PE file does not match expected bit-mode " + object_path
|
||||
);
|
||||
// finally get image base
|
||||
if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||||
// 32 bit
|
||||
auto bytes = load_bytes<DWORD>(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c
|
||||
if(!bytes) {
|
||||
return std::move(bytes).unwrap_error();
|
||||
}
|
||||
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
|
||||
} else {
|
||||
// 64 bit
|
||||
// I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD
|
||||
auto bytes = load_bytes<unsigned __int64>(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18
|
||||
if(!bytes) {
|
||||
return std::move(bytes).unwrap_error();
|
||||
}
|
||||
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@ -2,94 +2,15 @@
|
||||
#define PE_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/error.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
|
||||
#if IS_WINDOWS
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
|
||||
T pe_byteswap_if_needed(T value) {
|
||||
// PE header values are little endian, I think dos e_lfanew should be too
|
||||
if(!is_little_endian()) {
|
||||
return byteswap(value);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
inline Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path) {
|
||||
// https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ
|
||||
// https://0xrick.github.io/win-internals/pe3/
|
||||
// Endianness should always be little for dos and pe headers
|
||||
std::FILE* file_ptr;
|
||||
errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb");
|
||||
auto file = raii_wrap(std::move(file_ptr), file_deleter);
|
||||
if(ret != 0 || file == nullptr) {
|
||||
return internal_error("Unable to read object file {}", object_path);
|
||||
}
|
||||
auto magic = load_bytes<std::array<char, 2>>(file, 0);
|
||||
if(!magic) {
|
||||
return std::move(magic).unwrap_error();
|
||||
}
|
||||
if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) {
|
||||
return internal_error("File is not a PE file {}", object_path);
|
||||
}
|
||||
auto e_lfanew = load_bytes<DWORD>(file, 0x3c); // dos header + 0x3c
|
||||
if(!e_lfanew) {
|
||||
return std::move(e_lfanew).unwrap_error();
|
||||
}
|
||||
DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value());
|
||||
auto signature = load_bytes<std::array<char, 4>>(file, nt_header_offset); // nt header + 0
|
||||
if(!signature) {
|
||||
return std::move(signature).unwrap_error();
|
||||
}
|
||||
if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) {
|
||||
return internal_error("File is not a PE file {}", object_path);
|
||||
}
|
||||
auto size_of_optional_header_raw = load_bytes<WORD>(file, nt_header_offset + 4 + 0x10); // file header + 0x10
|
||||
if(!size_of_optional_header_raw) {
|
||||
return std::move(size_of_optional_header_raw).unwrap_error();
|
||||
}
|
||||
WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value());
|
||||
if(size_of_optional_header == 0) {
|
||||
return internal_error("Unexpected optional header size for PE file");
|
||||
}
|
||||
auto optional_header_magic_raw = load_bytes<WORD>(file, nt_header_offset + 0x18); // optional header + 0x0
|
||||
if(!optional_header_magic_raw) {
|
||||
return std::move(optional_header_magic_raw).unwrap_error();
|
||||
}
|
||||
WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value());
|
||||
VERIFY(
|
||||
optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC,
|
||||
"PE file does not match expected bit-mode " + object_path
|
||||
);
|
||||
// finally get image base
|
||||
if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
|
||||
// 32 bit
|
||||
auto bytes = load_bytes<DWORD>(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c
|
||||
if(!bytes) {
|
||||
return std::move(bytes).unwrap_error();
|
||||
}
|
||||
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
|
||||
} else {
|
||||
// 64 bit
|
||||
// I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD
|
||||
auto bytes = load_bytes<unsigned __int64>(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18
|
||||
if(!bytes) {
|
||||
return std::move(bytes).unwrap_error();
|
||||
}
|
||||
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
|
||||
}
|
||||
}
|
||||
Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
67
src/binary/safe_dl.cpp
Normal file
67
src/binary/safe_dl.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "safe_dl.hpp"
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "../utils/program_name.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <link.h>
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
out->raw_address = address;
|
||||
dl_find_object result;
|
||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) {
|
||||
out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr);
|
||||
if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) {
|
||||
std::size_t path_length = std::strlen(result.dlfo_link_map->l_name);
|
||||
std::memcpy(
|
||||
out->object_path,
|
||||
result.dlfo_link_map->l_name,
|
||||
std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1))
|
||||
);
|
||||
} else {
|
||||
// empty l_name, this means it's the currently running executable
|
||||
memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1);
|
||||
auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX);
|
||||
if(res == -1) {
|
||||
// error handling?
|
||||
}
|
||||
// TODO: Special handling for /proc/pid/exe unlink edge case
|
||||
}
|
||||
} else {
|
||||
// std::cout<<"error"<<std::endl;
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
// TODO: Handle this part of the documentation?
|
||||
// The address can be a code address or data address. On architectures using function descriptors, no attempt is
|
||||
// made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object
|
||||
// may return the object that defines the function descriptor (and not the object that contains the code
|
||||
// implementing the function), or fail to find any object at all.
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
out->raw_address = address;
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@ -2,69 +2,11 @@
|
||||
#define SAFE_DL_HPP
|
||||
|
||||
#include "../utils/common.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "../utils/program_name.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <mutex>
|
||||
#include <unordered_map>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
|
||||
#if IS_LINUX || IS_APPLE
|
||||
#include <unistd.h>
|
||||
#include <dlfcn.h>
|
||||
#include <link.h>
|
||||
#endif
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
inline void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
out->raw_address = address;
|
||||
dl_find_object result;
|
||||
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) {
|
||||
out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr);
|
||||
if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) {
|
||||
std::size_t path_length = std::strlen(result.dlfo_link_map->l_name);
|
||||
std::memcpy(
|
||||
out->object_path,
|
||||
result.dlfo_link_map->l_name,
|
||||
std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1))
|
||||
);
|
||||
} else {
|
||||
// empty l_name, this means it's the currently running executable
|
||||
memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1);
|
||||
auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX);
|
||||
if(res == -1) {
|
||||
// error handling?
|
||||
}
|
||||
// TODO: Special handling for /proc/pid/exe unlink edge case
|
||||
}
|
||||
} else {
|
||||
// std::cout<<"error"<<std::endl;
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
// TODO: Handle this part of the documentation?
|
||||
// The address can be a code address or data address. On architectures using function descriptors, no attempt is
|
||||
// made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object
|
||||
// may return the object that defines the function descriptor (and not the object that contains the code
|
||||
// implementing the function), or fail to find any object at all.
|
||||
}
|
||||
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
|
||||
}
|
||||
}
|
||||
#else
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
inline void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
|
||||
out->raw_address = address;
|
||||
out->address_relative_to_object_start = 0;
|
||||
out->object_path[0] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "../binary/object.hpp"
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include "../utils/dbghelp_syminit_manager.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include "../utils/dwarf.hpp" // has dwarf #includes
|
||||
#include "../utils/error.hpp"
|
||||
#include "../binary/object.hpp"
|
||||
#include "../binary/mach-o.hpp"
|
||||
#include "../utils/utils.hpp"
|
||||
#include "../utils/program_name.hpp" // For CPPTRACE_MAX_PATH
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user