Skip to content

Commit d063784

Browse files
committed
Refactor elf_get_module_image_base, setup an elf class in preparation for more elf parsing
1 parent 6689d14 commit d063784

File tree

3 files changed

+77
-48
lines changed

3 files changed

+77
-48
lines changed

src/binary/elf.cpp

Lines changed: 52 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -12,54 +12,17 @@
1212

1313
namespace cpptrace {
1414
namespace detail {
15-
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
16-
T elf_byteswap_if_needed(T value, bool elf_is_little) {
17-
if(is_little_endian() == elf_is_little) {
18-
return value;
19-
} else {
20-
return byteswap(value);
21-
}
22-
}
23-
24-
template<std::size_t Bits>
25-
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
15+
elf::elf(
16+
file_wrapper file,
2617
const std::string& object_path,
27-
std::FILE* file,
28-
bool is_little_endian
29-
) {
30-
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
31-
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
32-
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
33-
auto loaded_header = load_bytes<Header>(file, 0);
34-
if(loaded_header.is_error()) {
35-
return std::move(loaded_header).unwrap_error();
36-
}
37-
const Header& file_header = loaded_header.unwrap_value();
38-
if(file_header.e_ehsize != sizeof(Header)) {
39-
return internal_error("ELF file header size mismatch" + object_path);
40-
}
41-
// PT_PHDR will occur at most once
42-
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
43-
// It should occur at the beginning but may as well loop just in case
44-
for(int i = 0; i < file_header.e_phnum; i++) {
45-
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
46-
if(loaded_ph.is_error()) {
47-
return std::move(loaded_ph).unwrap_error();
48-
}
49-
const PHeader& program_header = loaded_ph.unwrap_value();
50-
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
51-
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
52-
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
53-
}
54-
}
55-
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
56-
return 0;
57-
}
18+
bool is_little_endian,
19+
bool is_64
20+
) : file(std::move(file)), object_path(object_path), is_little_endian(is_little_endian), is_64(is_64) {}
5821

59-
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
22+
Result<elf, internal_error> elf::open_elf(const std::string& object_path) {
6023
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
6124
if(file == nullptr) {
62-
return internal_error("Unable to read object file " + object_path);
25+
return internal_error("Unable to read object file {}", object_path);
6326
}
6427
// Initial checks/metadata
6528
auto magic = load_bytes<std::array<char, 4>>(file, 0);
@@ -86,14 +49,57 @@ namespace detail {
8649
if(ei_version.unwrap_value() != 1) {
8750
return internal_error("Unexpected ELF version " + object_path);
8851
}
52+
return elf(std::move(file), object_path, is_little_endian, is_64);
53+
}
54+
55+
Result<std::uintptr_t, internal_error> elf::get_module_image_base() {
8956
// get image base
9057
if(is_64) {
91-
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
58+
return get_module_image_base_from_program_table<64>();
9259
} else {
93-
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
60+
return get_module_image_base_from_program_table<32>();
9461
}
9562
}
9663

64+
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type>
65+
T elf::byteswap_if_needed(T value, bool elf_is_little) {
66+
if(cpptrace::detail::is_little_endian() == elf_is_little) {
67+
return value;
68+
} else {
69+
return byteswap(value);
70+
}
71+
}
72+
73+
template<std::size_t Bits>
74+
Result<std::uintptr_t, internal_error> elf::get_module_image_base_from_program_table() {
75+
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
76+
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
77+
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
78+
auto loaded_header = load_bytes<Header>(file, 0);
79+
if(loaded_header.is_error()) {
80+
return std::move(loaded_header).unwrap_error();
81+
}
82+
const Header& file_header = loaded_header.unwrap_value();
83+
if(file_header.e_ehsize != sizeof(Header)) {
84+
return internal_error("ELF file header size mismatch" + object_path);
85+
}
86+
// PT_PHDR will occur at most once
87+
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
88+
// It should occur at the beginning but may as well loop just in case
89+
for(int i = 0; i < file_header.e_phnum; i++) {
90+
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
91+
if(loaded_ph.is_error()) {
92+
return std::move(loaded_ph).unwrap_error();
93+
}
94+
const PHeader& program_header = loaded_ph.unwrap_value();
95+
if(byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
96+
return byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
97+
byteswap_if_needed(program_header.p_offset, is_little_endian);
98+
}
99+
}
100+
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
101+
return 0;
102+
}
97103
}
98104
}
99105

src/binary/elf.hpp

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,26 @@
1111

1212
namespace cpptrace {
1313
namespace detail {
14-
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
14+
class elf {
15+
file_wrapper file;
16+
std::string object_path;
17+
bool is_little_endian;
18+
bool is_64;
19+
20+
elf(file_wrapper file, const std::string& object_path, bool is_little_endian, bool is_64);
21+
22+
public:
23+
static NODISCARD Result<elf, internal_error> open_elf(const std::string& object_path);
24+
25+
Result<std::uintptr_t, internal_error> get_module_image_base();
26+
27+
private:
28+
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
29+
T byteswap_if_needed(T value, bool elf_is_little);
30+
31+
template<std::size_t Bits>
32+
Result<std::uintptr_t, internal_error> get_module_image_base_from_program_table();
33+
};
1534
}
1635
}
1736

src/binary/module_base.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,12 @@ namespace detail {
3030
if(it == cache.end()) {
3131
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
3232
// have two threads try to do the same computation
33-
auto base = elf_get_module_image_base(object_path);
33+
auto obj = elf::open_elf(object_path);
3434
// TODO: Cache the error
35+
if(!obj) {
36+
return obj.unwrap_error();
37+
}
38+
auto base = obj.unwrap_value().get_module_image_base();
3539
if(base.is_error()) {
3640
return std::move(base).unwrap_error();
3741
}

0 commit comments

Comments
 (0)