Skip to content

Commit fdeb4b9

Browse files
committed
Implement get_section_data and for_each_section on Linux/Android
1 parent b6e5662 commit fdeb4b9

4 files changed

Lines changed: 167 additions & 9 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,8 @@ Below is a summary of the current support for libhat's platform-dependent APIs:
114114
| `hp::is_writable` ||| ||
115115
| `hp::is_executable` ||| ||
116116
| `hp::module::get_module_data` ||| ||
117-
| `hp::module::get_section_data` || | | |
118-
| `hp::module::for_each_section` || | | |
117+
| `hp::module::get_section_data` || | | |
118+
| `hp::module::for_each_section` || | | |
119119
| `hp::module::for_each_segment` |||||
120120

121121
## Quick start

include/libhat/process.hpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,23 @@ LIBHAT_EXPORT namespace hat::process {
2525
/// To verify whether the region is safe to read, use hat::process::is_readable.
2626
[[nodiscard]] std::span<std::byte> get_module_data() const;
2727

28-
/// Returns the memory region for a named section
28+
/// Returns the memory region for a named section. On Linux-based platforms this is implemented via
29+
/// {@code for_each_section}, and subsequently requires file I/O and parsing (see below). Caching the return
30+
/// value should be considered if repeated calls are frequently made. If looking up multiple named sections is
31+
/// required, consider doing so with a single call to {@code for_each_section}.
2932
[[nodiscard]] std::span<std::byte> get_section_data(std::string_view name) const;
3033

3134
/// Invokes the callback for each named linker section defined by this module as long as it returns true. The
32-
/// returned byte range is not guaranteed to have page aligned begin and end addresses.
35+
/// returned byte range is not guaranteed to have page aligned begin and end addresses. The returned protections
36+
/// are yielded from the section headers, and may not reflect the current virtual protections for the relevant
37+
/// memory pages. On Linux-based platforms, section information is not mapped into memory by the loader, so the
38+
/// implementation of this function has to read the module's associated ELF and parse the respective headers.
3339
void for_each_section(const std::function<bool(std::string_view name, std::span<std::byte>, hat::protection)>& callback) const;
3440

3541
/// Invokes the callback for each memory segment defined by this module as long as it returns true. Depending on
3642
/// the platform, a segment may be represented by multiple linker sections. The returned byte range is not
37-
/// guaranteed to have page aligned begin and end addresses.
43+
/// guaranteed to have page aligned begin and end addresses. The returned protections are yielded from the
44+
/// segment headers, and may not reflect the current virtual protections for the relevant memory pages.
3845
void for_each_segment(const std::function<bool(std::span<std::byte>, hat::protection)>& callback) const;
3946

4047
[[nodiscard]] constexpr auto operator<=>(const module&) const noexcept = default;

src/os/linux/Process.cpp

Lines changed: 124 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,30 @@
66
#include <dlfcn.h>
77
#include <link.h>
88

9+
#include <string.h>
10+
11+
#include <fstream>
912
#include <memory>
1013

1114
#include "Common.hpp"
1215
#include "../../Utils.hpp"
1316

1417
namespace hat::process {
1518

19+
#ifdef LIBHAT_LP64
20+
using elf_ehdr_t = Elf64_Ehdr;
21+
using elf_shdr_t = Elf64_Shdr;
22+
static constexpr unsigned char elf_class = ELFCLASS64;
23+
#else
24+
using elf_ehdr_t = Elf32_Ehdr;
25+
using elf_shdr_t = Elf32_Shdr;
26+
static constexpr unsigned char elf_class = ELFCLASS32;
27+
#endif
28+
29+
using Handle = std::unique_ptr<void, decltype([](void* handle) {
30+
dlclose(handle);
31+
})>;
32+
1633
hat::process::module get_process_module() {
1734
const auto module = get_module({});
1835
if (!module) {
@@ -51,6 +68,113 @@ namespace hat::process {
5168
return {reinterpret_cast<std::byte*>(this->address()), max};
5269
}
5370

71+
std::span<std::byte> module::get_section_data(std::string_view name) const {
72+
std::span<std::byte> data{};
73+
this->for_each_section([&](auto section_name, auto section_data, auto) -> bool {
74+
if (section_name == name) {
75+
data = section_data;
76+
return false;
77+
}
78+
return true;
79+
});
80+
return data;
81+
}
82+
83+
void module::for_each_section(const std::function<bool(std::string_view, std::span<std::byte>, hat::protection)>& callback) const {
84+
auto phdrCallback = [&](const dl_phdr_info& info) {
85+
const auto addr = std::bit_cast<uintptr_t>(info.dlpi_addr);
86+
if (addr != this->address()) {
87+
return 0;
88+
}
89+
90+
const char* path = (info.dlpi_name && *info.dlpi_name != '\0')
91+
? info.dlpi_name : "/proc/self/exe";
92+
std::ifstream file{path, std::ios::binary};
93+
if (!file.is_open()) {
94+
return 0; // ?????
95+
}
96+
97+
elf_ehdr_t ehdr{};
98+
if (!file.read(reinterpret_cast<char*>(&ehdr), EI_NIDENT)) {
99+
return 0; // Invalid ELF
100+
}
101+
102+
const auto& e_ident = ehdr.e_ident;
103+
if (e_ident[EI_MAG0] != ELFMAG0 || e_ident[EI_MAG1] != ELFMAG1
104+
|| e_ident[EI_MAG2] != ELFMAG2 || e_ident[EI_MAG3] != ELFMAG3
105+
|| e_ident[EI_CLASS] != elf_class) {
106+
return 0; // Invalid ELF
107+
}
108+
109+
if (!file.read(reinterpret_cast<char*>(&ehdr) + EI_NIDENT, sizeof(ehdr) - EI_NIDENT)) {
110+
return 0; // Invalid ELF
111+
}
112+
113+
if (!ehdr.e_shnum || ehdr.e_shentsize < sizeof(elf_shdr_t)) {
114+
return 0; // No section header table, or entries are smaller than expected. Nothing to do
115+
}
116+
117+
std::vector<char> sections(ehdr.e_shnum * ehdr.e_shentsize);
118+
if (!file.seekg(static_cast<std::streamoff>(ehdr.e_shoff), std::ios::beg)) {
119+
return 0;
120+
}
121+
if (!file.read(sections.data(), static_cast<std::streamsize>(sections.size()))) {
122+
return 0;
123+
}
124+
125+
elf_shdr_t shstrtab{};
126+
if (ehdr.e_shstrndx >= ehdr.e_shnum) {
127+
return 0;
128+
}
129+
std::memcpy(&shstrtab, sections.data() + ehdr.e_shstrndx * ehdr.e_shentsize, sizeof(shstrtab));
130+
131+
std::vector<char> strings(shstrtab.sh_size);
132+
if (!file.seekg(static_cast<std::streamoff>(shstrtab.sh_offset), std::ios::beg)) {
133+
return 0;
134+
}
135+
if (!file.read(strings.data(), static_cast<std::streamsize>(strings.size()))) {
136+
return 0;
137+
}
138+
139+
for (auto it = sections.begin(); it != sections.end(); it += ehdr.e_shentsize) {
140+
elf_shdr_t header{};
141+
std::memcpy(&header, std::to_address(it), sizeof(header));
142+
if (header.sh_type == SHT_NULL) {
143+
continue;
144+
}
145+
146+
if (!header.sh_addr || header.sh_name > strings.size() || (header.sh_flags & SHF_ALLOC) == 0) {
147+
continue;
148+
}
149+
150+
const char* cstr = strings.data() + header.sh_name;
151+
const size_t size = strnlen(cstr, strings.size() - header.sh_name);
152+
const std::string_view name{cstr, size};
153+
154+
const std::span data{
155+
reinterpret_cast<std::byte*>(this->address()) + header.sh_addr,
156+
header.sh_size
157+
};
158+
159+
hat::protection prot = hat::protection::Read;
160+
if (header.sh_flags & SHF_WRITE) prot |= hat::protection::Write;
161+
if (header.sh_flags & SHF_EXECINSTR) prot |= hat::protection::Execute;
162+
163+
if (!callback(name, data, prot)) {
164+
break;
165+
}
166+
}
167+
168+
return 1;
169+
};
170+
171+
dl_iterate_phdr(
172+
[](dl_phdr_info* info, size_t, void* data) {
173+
return (*static_cast<decltype(phdrCallback)*>(data))(*info);
174+
},
175+
&phdrCallback);
176+
}
177+
54178
void module::for_each_segment(const std::function<bool(std::span<std::byte>, hat::protection)>& callback) const {
55179
auto phdrCallback = [&](const dl_phdr_info& info) {
56180
const auto addr = std::bit_cast<uintptr_t>(info.dlpi_addr);
@@ -90,10 +214,6 @@ namespace hat::process {
90214
}
91215

92216
std::optional<hat::process::module> get_module(const std::string_view name) {
93-
using Handle = std::unique_ptr<void, decltype([](void* handle) {
94-
dlclose(handle);
95-
})>;
96-
97217
const std::string buffer{name};
98218
const char* file = buffer.empty() ? nullptr : buffer.c_str();
99219

test/tests/Process.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,34 @@ TEST(ProcessTest, ProcessModuleHasDefaultSegments) {
2323
EXPECT_TRUE(r);
2424
EXPECT_TRUE(rw);
2525
}
26+
27+
#ifndef LIBHAT_MAC
28+
TEST(ProcessTest, ProcessModuleHasDefaultSections) {
29+
bool rx = false; // .text
30+
bool r = false; // .rdata
31+
bool rw = false; // .data
32+
hat::process::get_process_module().for_each_section([&](auto, auto, auto prot) {
33+
if (prot == (hat::protection::Read | hat::protection::Execute)) {
34+
rx = true;
35+
} else if (prot == (hat::protection::Read)) {
36+
r = true;
37+
} else if (prot == (hat::protection::Read | hat::protection::Write)) {
38+
rw = true;
39+
}
40+
return !rx || !r || !rw;
41+
});
42+
EXPECT_TRUE(rx);
43+
EXPECT_TRUE(r);
44+
EXPECT_TRUE(rw);
45+
}
46+
47+
TEST(ProcessTest, ModuleSectionsMatchLookup) {
48+
const auto mod = hat::process::get_process_module();
49+
mod.for_each_section([&](auto name, auto data, auto) {
50+
auto lookup = mod.get_section_data(name);
51+
EXPECT_EQ(std::to_address(lookup.begin()), std::to_address(data.begin()));
52+
EXPECT_EQ(std::to_address(lookup.end()), std::to_address(data.end()));
53+
return true;
54+
});
55+
}
56+
#endif

0 commit comments

Comments
 (0)