Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 74 additions & 4 deletions core/libs/prx/libc/src/specifics/x86_64/CallTrace.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
#if defined(_WIN32) || defined(__linux__)
#if defined(_WIN32) || defined(__linux__) || (defined(__APPLE__) && defined(__x86_64__))
#ifdef _WIN32
#include <windows.h>
#elif defined(__APPLE__)
#include <cerrno>
#include <csignal>
#include <ctime>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <pthread.h>
#include <sys/mman.h>
#include <unistd.h>
#else
#include <cerrno>
#include <csignal>
Expand Down Expand Up @@ -113,6 +124,10 @@ void StartCallCountWriter() {
std::uint64_t CurrentThread() {
#ifdef _WIN32
return GetCurrentThreadId();
#elif defined(__APPLE__)
std::uint64_t thread = 0;
pthread_threadid_np(nullptr, &thread);
return thread;
#else
return static_cast<std::uint64_t>(gettid());
#endif
Expand All @@ -128,6 +143,22 @@ void MapThunkArea() {
CloseHandle(section);
if (g_writable == nullptr || g_executable == nullptr) throw std::system_error(static_cast<int>(code), std::system_category(), "call trace: MapViewOfFile");
}
#elif defined(__APPLE__)
void MapThunkArea() {
void* writable = mmap(nullptr, ThunkAreaBytes, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (writable == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "call trace: mmap");
mach_vm_address_t executable = 0;
vm_prot_t current = VM_PROT_NONE;
vm_prot_t maximum = VM_PROT_NONE;
const kern_return_t remapped = mach_vm_remap(mach_task_self(), &executable, ThunkAreaBytes, 0, VM_FLAGS_ANYWHERE, mach_task_self(), reinterpret_cast<mach_vm_address_t>(writable), false, &current, &maximum, VM_INHERIT_NONE);
if (remapped != KERN_SUCCESS || mach_vm_protect(mach_task_self(), executable, ThunkAreaBytes, false, VM_PROT_READ | VM_PROT_EXECUTE) != KERN_SUCCESS) {
if (remapped == KERN_SUCCESS) mach_vm_deallocate(mach_task_self(), executable, ThunkAreaBytes);
munmap(writable, ThunkAreaBytes);
throw std::system_error(remapped == KERN_SUCCESS ? KERN_PROTECTION_FAILURE : remapped, std::system_category(), "call trace: mach_vm_remap");
}
g_writable = static_cast<std::uint8_t*>(writable);
g_executable = reinterpret_cast<std::uint8_t*>(executable);
}
#else
void MapThunkArea() {
const int file = memfd_create("aps5-call-trace", MFD_CLOEXEC);
Expand All @@ -147,7 +178,9 @@ void MapThunkArea() {
g_writable = static_cast<std::uint8_t*>(writable);
g_executable = static_cast<std::uint8_t*>(executable);
}
#endif

#ifndef _WIN32
struct ModuleSegment {
std::uint64_t start;
std::uint64_t end;
Expand All @@ -167,6 +200,42 @@ std::atomic<bool> g_reporting{false};
std::atomic<bool> g_reported{false};
unsigned long long g_moduleChanges = ~0ull;

#if defined(__APPLE__)
void SnapshotModules() {
const auto images = _dyld_image_count();
unsigned long long changes = images;
for (std::uint32_t index = 0; index < images; ++index)
changes = changes * 131u + reinterpret_cast<unsigned long long>(_dyld_get_image_header(index));
if (changes == g_moduleChanges) return;
g_moduleChanges = changes;
auto* map = new ModuleMap{};
for (std::uint32_t index = 0; index < images && map->count < std::size(map->segments); ++index) {
const auto* header = reinterpret_cast<const mach_header_64*>(_dyld_get_image_header(index));
if (header == nullptr || header->magic != MH_MAGIC_64) continue;
const char* path = _dyld_get_image_name(index);
if (path == nullptr || *path == '\0') path = getprogname();
const char* slash = std::strrchr(path, '/');
const char* name = slash == nullptr ? path : slash + 1;
const auto slide = static_cast<std::uint64_t>(_dyld_get_image_vmaddr_slide(index));
const auto base = reinterpret_cast<std::uint64_t>(header);
auto* command = reinterpret_cast<const load_command*>(header + 1);
for (std::uint32_t commandIndex = 0; commandIndex < header->ncmds && map->count < std::size(map->segments); ++commandIndex) {
if (command->cmd == LC_SEGMENT_64) {
const auto* segment = reinterpret_cast<const segment_command_64*>(command);
if (segment->vmsize != 0 && (segment->initprot & VM_PROT_EXECUTE) != 0) {
auto& recorded = map->segments[map->count++];
recorded.base = base;
recorded.start = segment->vmaddr + slide;
recorded.end = recorded.start + segment->vmsize;
std::strncpy(recorded.name, name, sizeof(recorded.name) - 1);
}
}
command = reinterpret_cast<const load_command*>(reinterpret_cast<const std::uint8_t*>(command) + command->cmdsize);
}
}
g_modules.store(map, std::memory_order_release);
}
#else
void SnapshotModules() {
unsigned long long changes = 0;
dl_iterate_phdr([](dl_phdr_info* info, std::size_t, void* data) {
Expand Down Expand Up @@ -198,6 +267,7 @@ void SnapshotModules() {
}, map);
g_modules.store(map, std::memory_order_release);
}
#endif

class ReportLine {
public:
Expand Down Expand Up @@ -370,12 +440,12 @@ void* TraceImport_nid_no_patch(void* target, const char* name, const char* libra
MapThunkArea();
g_records.store(new ImportRecord[RecordCapacity], std::memory_order_release);
g_ring.store(new RingSlot[RingCapacity], std::memory_order_release);
#ifdef __linux__
#if defined(__linux__) || defined(__APPLE__)
InstallAbortReport();
#endif
StartCallCountWriter();
}
#ifdef __linux__
#if defined(__linux__) || defined(__APPLE__)
SnapshotModules();
#endif
if (const auto existing = g_thunkByTarget.find(target); existing != g_thunkByTarget.end()) return existing->second;
Expand Down Expand Up @@ -475,7 +545,7 @@ asm(".text\n"
" movdqu %xmm7, 112(%rsp)\n"
" mov %r11, %rdi\n"
" mov 200(%rsp), %rsi\n"
" call CallTraceRecord_nid_no_patch\n"
" call " APS5_ASM_SYMBOL("CallTraceRecord_nid_no_patch") "\n"
" movdqu 0(%rsp), %xmm0\n"
" movdqu 16(%rsp), %xmm1\n"
" movdqu 32(%rsp), %xmm2\n"
Expand Down
2 changes: 2 additions & 0 deletions core/relinker/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,8 @@ if(BUILD_TESTING)
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_trace_imports.py $<TARGET_FILE:relinker>)
add_test(NAME linux_trace_imports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_linux_trace_imports.py $<TARGET_FILE:relinker>)
add_test(NAME macos_trace_imports COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_macos_trace_imports.py $<TARGET_FILE:relinker>)
add_test(NAME windows_address_space COMMAND ${Python3_EXECUTABLE}
${CMAKE_CURRENT_SOURCE_DIR}/relinker/tests/test_windows_address_space.py $<TARGET_FILE:relinker>)
add_test(NAME windows_tls_thread_pointer COMMAND ${Python3_EXECUTABLE}
Expand Down
2 changes: 0 additions & 2 deletions core/relinker/cli/src/CliArgs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,6 @@ Args ParseArgs(int argc, char* argv[]) {

if (args.windowsFixedBase && !args.toWindows)
throw std::runtime_error("--windows-fixed-base requires --windows");
if (args.traceImports && args.toMacos)
throw std::runtime_error("--trace-imports cannot be used with --macos");
if (args.traceImports && args.lazyBinding)
throw std::runtime_error("--trace-imports conflicts with --lazy-binding");
if (args.linuxFixedBase && (args.toWindows || args.toMacos))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ struct MacOsTlsExport {
std::uint64_t Flags = 0;
};

struct MacOsTraceImport {
std::uint32_t Rva;
std::string Name;
std::string Library;
};

struct MacOsImageInput {
bool Executable = true;
std::string InstallName;
Expand All @@ -61,6 +67,8 @@ struct MacOsImageInput {
std::vector<MacOsTlsImport> TlsImports;
std::vector<MacOsTlsExport> TlsExports;
const std::vector<Codegen::TrampolineSite>* Trampolines = nullptr;
bool TraceImports = false;
std::vector<MacOsTraceImport> Traced;
std::function<void(std::string_view)> Message;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ namespace Elfpatcher::MacOs {

class MacOsMachOPatcher final : public IElfPatcher {
public:
explicit MacOsMachOPatcher(std::optional<std::uint64_t> fixedBase = std::nullopt, std::function<void(std::string_view)> message = {});
explicit MacOsMachOPatcher(std::optional<std::uint64_t> fixedBase = std::nullopt, std::function<void(std::string_view)> message = {}, bool traceImports = false);

std::vector<std::uint8_t> Patch(
std::vector<std::uint8_t> sourceElf,
Expand All @@ -27,6 +27,7 @@ class MacOsMachOPatcher final : public IElfPatcher {
private:
std::optional<std::uint64_t> fixedBase;
std::function<void(std::string_view)> message;
bool traceImports;
};

}
Expand Down
102 changes: 97 additions & 5 deletions core/relinker/elfpatcher/src/macos/MacOsImage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <algorithm>
#include <bit>
#include <iomanip>
#include <initializer_list>
#include <limits>
#include <map>
#include <memory>
Expand Down Expand Up @@ -95,6 +96,40 @@ constexpr std::uint8_t EntryStub[] = {
constexpr std::size_t EntryStubEntryDisplacement = 0x69;
constexpr std::size_t EntryStubStartDisplacement = 0x84;

struct MacOsTracePrologue {
std::vector<std::uint8_t> Bytes;
std::size_t EntryDisplacement;
};

MacOsTracePrologue BuildTracePrologue(const std::uint32_t prologueRva, const std::uint32_t tableRva, const std::uint32_t tableEndRva, const std::uint32_t functionSlotRva) {
std::vector<std::uint8_t> code = {0x57, 0x56, 0x52, 0x51, 0x53, 0x41, 0x54, 0x48, 0x83, 0xec, 0x08};
const auto rip = [&](const std::initializer_list<std::uint8_t> opcode, const std::uint32_t target) {
code.insert(code.end(), opcode);
const auto displacement = static_cast<std::int64_t>(target) - static_cast<std::int64_t>(prologueRva + code.size() + 4);
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("Import trace stub exceeds rel32 range");
Io::AppendU32(code, static_cast<std::uint32_t>(displacement));
};
rip({0x48, 0x8d, 0x1d}, tableRva);
rip({0x4c, 0x8d, 0x25}, tableEndRva);
const auto loop = code.size();
code.insert(code.end(), {0x4c, 0x39, 0xe3, 0x73, 0x00});
const auto exit = code.size();
code.insert(code.end(), {0x48, 0x8b, 0x03, 0x48, 0x8b, 0x3c, 0x03, 0x48, 0x8b, 0x73, 0x08, 0x48, 0x01, 0xde, 0x48, 0x8b, 0x53, 0x10, 0x48, 0x01, 0xda});
rip({0xff, 0x15}, functionSlotRva);
code.insert(code.end(), {0x48, 0x8b, 0x0b, 0x48, 0x89, 0x04, 0x0b, 0x48, 0x83, 0xc3, 0x18, 0xeb});
const auto back = static_cast<std::int64_t>(loop) - static_cast<std::int64_t>(code.size() + 1);
if (back < -128 || back > 127)
throw Domain::RelinkerException("Import trace loop exceeds rel8 range");
code.push_back(static_cast<std::uint8_t>(static_cast<std::int8_t>(back)));
if (code.size() - exit > 127)
throw Domain::RelinkerException("Import trace loop exceeds rel8 range");
code[exit - 1] = static_cast<std::uint8_t>(code.size() - exit);
code.insert(code.end(), {0x48, 0x83, 0xc4, 0x08, 0x41, 0x5c, 0x5b, 0x59, 0x5a, 0x5e, 0x5f, 0xe9, 0x00, 0x00, 0x00, 0x00});
const auto entry = code.size() - 4;
return {std::move(code), entry};
}

struct Section {
std::string Name;
std::uint64_t Offset;
Expand Down Expand Up @@ -491,7 +526,8 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
const auto descriptor = metaRva + static_cast<std::uint32_t>(MetaTlsDescriptor);
const auto initPointer = AlignUp(metaSize, 8);
const auto startSlot = initPointer + 8;
const auto tlsExports = startSlot + 8;
const auto traceFunctionSlot = input.TraceImports ? startSlot + 8 : startSlot;
const auto tlsExports = traceFunctionSlot + 8;
std::map<std::string, std::uint64_t> tlsImportSlots;
for (const auto& import : input.TlsImports) tlsImportSlots.emplace(import.Symbol, 0);
auto metaEnd = tlsExports + input.TlsExports.size() * 16;
Expand Down Expand Up @@ -666,6 +702,44 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
}
if (input.Executable) {
input.Binds.push_back({"_Aps5StartGuest_nid_no_patch", metaRva + static_cast<std::uint32_t>(startSlot)});
if (input.TraceImports)
input.Binds.push_back({"_TraceImport_nid_no_patch", metaRva + static_cast<std::uint32_t>(traceFunctionSlot)});
}
std::size_t traceEntryPatch = 0;
std::uint32_t tracePrologueRva = 0;
if (input.Executable && input.TraceImports) {
const auto tableRva = code.GetRva();
const auto tableEnd = tableRva + static_cast<std::uint32_t>(input.Traced.size() * 24);
std::map<std::string, std::uint32_t> stringRva;
std::vector<std::string> stringOrder;
auto cursor = tableEnd;
const auto intern = [&](const std::string& text) {
const auto existing = stringRva.find(text);
if (existing != stringRva.end()) return existing->second;
stringRva.emplace(text, cursor);
stringOrder.push_back(text);
cursor += static_cast<std::uint32_t>(text.size() + 1);
return stringRva.at(text);
};
std::vector<std::uint8_t> table;
for (std::size_t index = 0; index < input.Traced.size(); ++index) {
const auto entry = tableRva + static_cast<std::uint32_t>(index * 24);
const auto relative = [entry](const std::uint32_t target) { return static_cast<std::uint64_t>(static_cast<std::int64_t>(target) - static_cast<std::int64_t>(entry)); };
Io::AppendU64(table, relative(input.Traced[index].Rva));
Io::AppendU64(table, relative(intern(input.Traced[index].Name)));
Io::AppendU64(table, relative(intern(input.Traced[index].Library)));
}
for (const auto byte : table) code.Emit({byte});
for (const auto& text : stringOrder) {
if (code.GetRva() != stringRva.at(text)) throw Domain::RelinkerException("Import trace string was placed at the wrong address");
for (const auto character : text) code.Emit({static_cast<std::uint8_t>(character)});
code.Emit({0});
}
if (code.GetRva() != cursor) throw Domain::RelinkerException("Import trace table size changed while it was written");
tracePrologueRva = cursor;
const auto prologue = BuildTracePrologue(cursor, tableRva, tableEnd, metaRva + static_cast<std::uint32_t>(traceFunctionSlot));
traceEntryPatch = static_cast<std::size_t>(code.GetRva() - codeRva) + prologue.EntryDisplacement;
for (const auto byte : prologue.Bytes) code.Emit({byte});
}
const auto metaBytes = meta.size();
segments.push_back({"__APS5DATA", metaRva, metaBytes, VmProtRead | VmProtWrite, std::move(meta), std::move(dataSections)});
Expand All @@ -680,6 +754,8 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
while (used != 0 && data[used - 1] == 0) --used;
data.resize(used);
}
if (segments[index].Name == "__APS5TEXT" && traceEntryPatch != 0 && data.size() < traceEntryPatch + 4)
data.resize(traceEntryPatch + 4, 0);
data.resize(AlignUp(data.size(), PageSize), 0);
}

Expand Down Expand Up @@ -817,16 +893,32 @@ std::vector<std::uint8_t> WriteMacOsImage(MacOsImageInput& input) {
std::vector<std::uint8_t> file;
constexpr std::size_t HeaderSize = 32;
const auto stubOffset = AlignUp(HeaderSize + commands.size(), 16);
if (stubOffset + (input.Executable ? sizeof(EntryStub) : 0) > LoadRva)
const auto traceJump = input.Executable && input.TraceImports;
const auto entryOffset = stubOffset + (traceJump ? 5 : 0);
if (entryOffset + (input.Executable ? sizeof(EntryStub) : 0) > LoadRva)
throw Domain::RelinkerException("Mach-O load commands do not fit before the guest image");
file.resize(LoadRva, 0);
if (input.Executable) {
std::copy(std::begin(EntryStub), std::end(EntryStub), file.begin() + static_cast<std::ptrdiff_t>(stubOffset));
if (traceJump) {
const auto text = std::find_if(segments.begin(), segments.end(), [](const Segment& segment) { return segment.Name == "__APS5TEXT"; });
if (text == segments.end() || traceEntryPatch + 4 > text->Data.size())
throw Domain::RelinkerException("Import trace prologue is missing from the Mach-O image");
const auto displacement = static_cast<std::int64_t>(entryOffset) - static_cast<std::int64_t>(codeRva + traceEntryPatch + 4);
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("macOS entry stub target is out of rel32 range");
Io::WriteU32(text->Data, traceEntryPatch, static_cast<std::uint32_t>(static_cast<std::int32_t>(displacement)));
const auto jump = static_cast<std::int64_t>(tracePrologueRva) - static_cast<std::int64_t>(stubOffset + 5);
if (jump < std::numeric_limits<std::int32_t>::min() || jump > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("Import trace prologue is out of rel32 range");
file[stubOffset] = 0xe9;
Io::WriteU32(file, stubOffset + 1, static_cast<std::uint32_t>(static_cast<std::int32_t>(jump)));
}
std::copy(std::begin(EntryStub), std::end(EntryStub), file.begin() + static_cast<std::ptrdiff_t>(entryOffset));
const auto writeDisplacement = [&](const std::size_t field, const std::uint64_t targetRva) {
const auto displacement = static_cast<std::int64_t>(targetRva) - static_cast<std::int64_t>(stubOffset + field + 4);
const auto displacement = static_cast<std::int64_t>(targetRva) - static_cast<std::int64_t>(entryOffset + field + 4);
if (displacement < std::numeric_limits<std::int32_t>::min() || displacement > std::numeric_limits<std::int32_t>::max())
throw Domain::RelinkerException("macOS entry stub target is out of rel32 range");
Io::WriteU32(file, stubOffset + field, static_cast<std::uint32_t>(static_cast<std::int32_t>(displacement)));
Io::WriteU32(file, entryOffset + field, static_cast<std::uint32_t>(static_cast<std::int32_t>(displacement)));
};
writeDisplacement(EntryStubEntryDisplacement, input.EntryRva);
writeDisplacement(EntryStubStartDisplacement, metaRva + startSlot);
Expand Down
Loading
Loading