Merge pull request #4546 from codingreapermaps/feat/libc-call-trace-runtime
feat(libc): record traced import calls on Windows
This commit is contained in:
commit
c496fd530f
|
|
@ -4,6 +4,7 @@ target_sources(libc PRIVATE
|
|||
${CMAKE_CURRENT_SOURCE_DIR}/src/CreationMask.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/gcc/StackGuard.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/windows/CrashReport.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/windows/CallTrace.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/linux/DlIteratePhdr.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/specifics/macos/GuestImages.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Strings.cpp
|
||||
|
|
@ -411,6 +412,18 @@ if(BUILD_TESTING)
|
|||
set_tests_properties(guest_errors PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_test_executable(guest_call_trace_tests ${LIBS_INCLUDE_DIR}/tests/GuestCallTrace.cpp)
|
||||
target_include_directories(guest_call_trace_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_call_trace_tests PRIVATE libc)
|
||||
configure_windows_unwind(guest_call_trace_tests)
|
||||
add_test(NAME guest_call_trace COMMAND guest_call_trace_tests)
|
||||
add_test(NAME guest_call_trace_abort COMMAND guest_call_trace_tests abort)
|
||||
set_tests_properties(guest_call_trace guest_call_trace_abort PROPERTIES ENVIRONMENT_MODIFICATION
|
||||
"PATH=path_list_prepend:$<TARGET_FILE_DIR:libc>")
|
||||
set_tests_properties(guest_call_trace_abort PROPERTIES TIMEOUT 20 PASS_REGULAR_EXPRESSION
|
||||
"last traced system library calls[^~]*#1 thread [0-9]+ libTraced.prx Integers from [^~]*guest_call_trace_tests.exe[^~]*#2 thread [0-9]+ libOther.prx Blocks from[^~]*most called traced imports \\(2 calls in total\\):[^~]* 1 libTraced.prx Integers[^~]* 1 libOther.prx Blocks")
|
||||
endif()
|
||||
add_test_executable(guest_syslog_tests ${LIBS_INCLUDE_DIR}/tests/GuestSyslog.cpp)
|
||||
target_include_directories(guest_syslog_tests PRIVATE ${LIBS_INCLUDE_DIR})
|
||||
target_link_libraries(guest_syslog_tests PRIVATE libc)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef CORE_LIBS_PRX_LIBC_INCLUDE_CALLTRACE_HPP
|
||||
#define CORE_LIBS_PRX_LIBC_INCLUDE_CALLTRACE_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
struct CallTraceEntry {
|
||||
std::uint64_t sequence;
|
||||
std::uint64_t thread;
|
||||
std::uint64_t returnAddress;
|
||||
const char* name;
|
||||
const char* library;
|
||||
};
|
||||
|
||||
struct CallTraceCount {
|
||||
std::uint64_t calls;
|
||||
const char* name;
|
||||
const char* library;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
void* TraceImport_nid_no_patch(void* target, const char* name, const char* library);
|
||||
std::size_t CopyCallTrace_nid_no_patch(CallTraceEntry* entries, std::size_t capacity);
|
||||
std::size_t CopyCallCounts_nid_no_patch(CallTraceCount* entries, std::size_t capacity, std::uint64_t* total);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,202 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include "prx/libc/include/CallTrace.hpp"
|
||||
#include "prx/libc/include/general/AsmMacros.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
|
||||
extern "C" void CallTraceEnter_nid_no_patch();
|
||||
|
||||
namespace {
|
||||
|
||||
struct ImportRecord {
|
||||
void* target;
|
||||
std::string name;
|
||||
std::string library;
|
||||
mutable std::atomic<std::uint64_t> calls{0};
|
||||
};
|
||||
|
||||
struct RingSlot {
|
||||
std::atomic<std::uint64_t> sequence{0};
|
||||
std::atomic<std::uint64_t> thread{0};
|
||||
std::atomic<std::uint64_t> returnAddress{0};
|
||||
std::atomic<const ImportRecord*> record{nullptr};
|
||||
};
|
||||
|
||||
constexpr std::size_t RingCapacity = 65536;
|
||||
constexpr std::size_t ThunkBytes = 24;
|
||||
constexpr std::size_t ThunkAreaBytes = 1024 * 1024;
|
||||
|
||||
std::mutex g_mutex;
|
||||
std::deque<ImportRecord> g_records;
|
||||
std::unordered_map<void*, void*> g_thunkByTarget;
|
||||
std::unique_ptr<RingSlot[]> g_ring;
|
||||
std::atomic<std::uint64_t> g_sequence{0};
|
||||
std::uint8_t* g_writable = nullptr;
|
||||
std::uint8_t* g_executable = nullptr;
|
||||
std::size_t g_thunks = 0;
|
||||
|
||||
bool Selected(std::string_view library) {
|
||||
const char* value = std::getenv("APS5_TRACE_CALLS");
|
||||
if (value == nullptr || *value == '\0') return false;
|
||||
std::string_view rest(value);
|
||||
if (library.ends_with(".prx")) library.remove_suffix(4);
|
||||
while (!rest.empty()) {
|
||||
const auto comma = rest.find(',');
|
||||
auto token = rest.substr(0, comma);
|
||||
rest = comma == std::string_view::npos ? std::string_view{} : rest.substr(comma + 1);
|
||||
if (token.ends_with(".prx")) token.remove_suffix(4);
|
||||
if (token == "all" || token == library) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MapThunkArea() {
|
||||
const HANDLE section = CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_EXECUTE_READWRITE, 0, static_cast<DWORD>(ThunkAreaBytes), nullptr);
|
||||
if (section == nullptr) throw std::system_error(static_cast<int>(GetLastError()), std::system_category(), "call trace: CreateFileMappingW");
|
||||
g_writable = static_cast<std::uint8_t*>(MapViewOfFile(section, FILE_MAP_WRITE, 0, 0, ThunkAreaBytes));
|
||||
g_executable = static_cast<std::uint8_t*>(MapViewOfFile(section, FILE_MAP_READ | FILE_MAP_EXECUTE, 0, 0, ThunkAreaBytes));
|
||||
const DWORD code = GetLastError();
|
||||
CloseHandle(section);
|
||||
if (g_writable == nullptr || g_executable == nullptr) throw std::system_error(static_cast<int>(code), std::system_category(), "call trace: MapViewOfFile");
|
||||
}
|
||||
|
||||
void* WriteThunk(const ImportRecord& record) {
|
||||
if ((g_thunks + 1) * ThunkBytes > ThunkAreaBytes) throw std::runtime_error("call trace: thunk area exhausted");
|
||||
const std::size_t offset = g_thunks * ThunkBytes;
|
||||
const std::uint8_t prefix[] = {0x49, 0xbb};
|
||||
const std::uint8_t jump[] = {0xff, 0x25, 0, 0, 0, 0};
|
||||
const auto recordAddress = reinterpret_cast<std::uint64_t>(&record);
|
||||
const auto handler = reinterpret_cast<std::uint64_t>(&CallTraceEnter_nid_no_patch);
|
||||
std::uint8_t* thunk = g_writable + offset;
|
||||
std::memcpy(thunk, prefix, sizeof(prefix));
|
||||
std::memcpy(thunk + 2, &recordAddress, sizeof(recordAddress));
|
||||
std::memcpy(thunk + 10, jump, sizeof(jump));
|
||||
std::memcpy(thunk + 16, &handler, sizeof(handler));
|
||||
FlushInstructionCache(GetCurrentProcess(), g_executable + offset, ThunkBytes);
|
||||
++g_thunks;
|
||||
return g_executable + offset;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static_assert(offsetof(ImportRecord, target) == 0);
|
||||
|
||||
extern "C" {
|
||||
|
||||
void APS5_VABI CallTraceRecord_nid_no_patch(const void* record, std::uint64_t returnAddress) {
|
||||
static_cast<const ImportRecord*>(record)->calls.fetch_add(1, std::memory_order_relaxed);
|
||||
const std::uint64_t sequence = g_sequence.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
RingSlot& slot = g_ring[sequence % RingCapacity];
|
||||
slot.sequence.store(0, std::memory_order_release);
|
||||
slot.thread.store(GetCurrentThreadId(), std::memory_order_relaxed);
|
||||
slot.returnAddress.store(returnAddress, std::memory_order_relaxed);
|
||||
slot.record.store(static_cast<const ImportRecord*>(record), std::memory_order_relaxed);
|
||||
slot.sequence.store(sequence, std::memory_order_release);
|
||||
}
|
||||
|
||||
void* TraceImport_nid_no_patch(void* target, const char* name, const char* library) {
|
||||
if (target == nullptr || name == nullptr || library == nullptr || !Selected(library)) return target;
|
||||
std::lock_guard lock(g_mutex);
|
||||
if (g_ring == nullptr) {
|
||||
MapThunkArea();
|
||||
g_ring = std::make_unique<RingSlot[]>(RingCapacity);
|
||||
}
|
||||
if (const auto existing = g_thunkByTarget.find(target); existing != g_thunkByTarget.end()) return existing->second;
|
||||
g_records.emplace_back(target, name, library);
|
||||
void* thunk = WriteThunk(g_records.back());
|
||||
g_thunkByTarget.emplace(target, thunk);
|
||||
return thunk;
|
||||
}
|
||||
|
||||
std::size_t CopyCallTrace_nid_no_patch(CallTraceEntry* entries, std::size_t capacity) {
|
||||
if (entries == nullptr || capacity == 0 || g_ring == nullptr) return 0;
|
||||
const std::uint64_t last = g_sequence.load(std::memory_order_acquire);
|
||||
const std::uint64_t span = std::min<std::uint64_t>({last, capacity, RingCapacity});
|
||||
std::size_t count = 0;
|
||||
for (std::uint64_t sequence = last - span + 1; sequence <= last; ++sequence) {
|
||||
const RingSlot& slot = g_ring[sequence % RingCapacity];
|
||||
if (slot.sequence.load(std::memory_order_acquire) != sequence) continue;
|
||||
const auto* record = slot.record.load(std::memory_order_relaxed);
|
||||
const CallTraceEntry entry{sequence, slot.thread.load(std::memory_order_relaxed), slot.returnAddress.load(std::memory_order_relaxed), record->name.c_str(), record->library.c_str()};
|
||||
if (slot.sequence.load(std::memory_order_acquire) != sequence) continue;
|
||||
entries[count++] = entry;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
std::size_t CopyCallCounts_nid_no_patch(CallTraceCount* entries, std::size_t capacity, std::uint64_t* total) {
|
||||
if (total != nullptr) *total = g_sequence.load(std::memory_order_acquire);
|
||||
if (entries == nullptr || capacity == 0) return 0;
|
||||
std::unique_lock lock(g_mutex, std::try_to_lock);
|
||||
if (!lock.owns_lock()) return 0;
|
||||
std::size_t count = 0;
|
||||
for (const auto& record : g_records) {
|
||||
const std::uint64_t calls = record.calls.load(std::memory_order_relaxed);
|
||||
if (calls == 0 || (count == capacity && calls <= entries[count - 1].calls)) continue;
|
||||
std::size_t position = count < capacity ? count++ : count - 1;
|
||||
while (position > 0 && entries[position - 1].calls < calls) {
|
||||
entries[position] = entries[position - 1];
|
||||
--position;
|
||||
}
|
||||
entries[position] = {calls, record.name.c_str(), record.library.c_str()};
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
asm(".text\n"
|
||||
APS5_ASM_FUNCTION("CallTraceEnter_nid_no_patch")
|
||||
" push %rdi\n"
|
||||
" push %rsi\n"
|
||||
" push %rdx\n"
|
||||
" push %rcx\n"
|
||||
" push %r8\n"
|
||||
" push %r9\n"
|
||||
" push %rax\n"
|
||||
" push %r10\n"
|
||||
" push %r11\n"
|
||||
" sub $128, %rsp\n"
|
||||
" movdqu %xmm0, 0(%rsp)\n"
|
||||
" movdqu %xmm1, 16(%rsp)\n"
|
||||
" movdqu %xmm2, 32(%rsp)\n"
|
||||
" movdqu %xmm3, 48(%rsp)\n"
|
||||
" movdqu %xmm4, 64(%rsp)\n"
|
||||
" movdqu %xmm5, 80(%rsp)\n"
|
||||
" movdqu %xmm6, 96(%rsp)\n"
|
||||
" movdqu %xmm7, 112(%rsp)\n"
|
||||
" mov %r11, %rdi\n"
|
||||
" mov 200(%rsp), %rsi\n"
|
||||
" call CallTraceRecord_nid_no_patch\n"
|
||||
" movdqu 0(%rsp), %xmm0\n"
|
||||
" movdqu 16(%rsp), %xmm1\n"
|
||||
" movdqu 32(%rsp), %xmm2\n"
|
||||
" movdqu 48(%rsp), %xmm3\n"
|
||||
" movdqu 64(%rsp), %xmm4\n"
|
||||
" movdqu 80(%rsp), %xmm5\n"
|
||||
" movdqu 96(%rsp), %xmm6\n"
|
||||
" movdqu 112(%rsp), %xmm7\n"
|
||||
" add $128, %rsp\n"
|
||||
" pop %r11\n"
|
||||
" pop %r10\n"
|
||||
" pop %rax\n"
|
||||
" pop %r9\n"
|
||||
" pop %r8\n"
|
||||
" pop %rcx\n"
|
||||
" pop %rdx\n"
|
||||
" pop %rsi\n"
|
||||
" pop %rdi\n"
|
||||
" jmp *(%r11)\n");
|
||||
#endif
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <tlhelp32.h>
|
||||
#include "prx/libc/include/CallTrace.hpp"
|
||||
#include "prx/libc/include/GuestArena.hpp"
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
|
@ -10,6 +11,7 @@
|
|||
#include <cstdarg>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
#include <mutex>
|
||||
#include "Sse4aEmulation.hpp"
|
||||
|
|
@ -32,6 +34,7 @@ void Report(const char* format, ...) {
|
|||
|
||||
void ReportAllThreads();
|
||||
void ReportStackCandidates(std::uint64_t rsp, int limit, std::uint64_t span);
|
||||
void ReportCallTrace();
|
||||
|
||||
void DescribeAddress(std::uint64_t address, char* buffer, std::size_t size) {
|
||||
HMODULE module = nullptr;
|
||||
|
|
@ -325,12 +328,34 @@ LONG WINAPI ReportCrash(EXCEPTION_POINTERS* info) {
|
|||
}
|
||||
}
|
||||
}
|
||||
ReportCallTrace();
|
||||
std::fflush(stderr);
|
||||
ReportAllThreads();
|
||||
std::fflush(stderr);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
void ReportCallTrace() {
|
||||
static CallTraceEntry entries[64];
|
||||
const std::size_t count = CopyCallTrace_nid_no_patch(entries, std::size(entries));
|
||||
if (count == 0) return;
|
||||
char line[MAX_PATH + 64];
|
||||
Report(" last traced system library calls (APS5_TRACE_CALLS):\n");
|
||||
for (std::size_t index = 0; index < count; ++index) {
|
||||
DescribeAddress(entries[index].returnAddress, line, sizeof(line));
|
||||
Report(" #%llu thread %llu %s %s from %s\n", static_cast<unsigned long long>(entries[index].sequence),
|
||||
static_cast<unsigned long long>(entries[index].thread), entries[index].library, entries[index].name, line);
|
||||
}
|
||||
static CallTraceCount counts[32];
|
||||
std::uint64_t total = 0;
|
||||
const std::size_t counted = CopyCallCounts_nid_no_patch(counts, std::size(counts), &total);
|
||||
if (counted == 0) return;
|
||||
Report(" most called traced imports (%llu calls in total):\n", static_cast<unsigned long long>(total));
|
||||
for (std::size_t index = 0; index < counted; ++index) {
|
||||
Report(" %12llu %s %s\n", static_cast<unsigned long long>(counts[index].calls), counts[index].library, counts[index].name);
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t CaptureFrames(const CONTEXT* context, std::uint64_t* frames, std::size_t capacity) {
|
||||
std::size_t count = 0;
|
||||
std::uint64_t frame = context->Rbp;
|
||||
|
|
@ -451,6 +476,7 @@ void ReportBacktrace(const char* what, bool reportThreads) {
|
|||
Report(" #%u %s\n", static_cast<unsigned>(i), line);
|
||||
}
|
||||
ReportStackCandidates(reinterpret_cast<std::uint64_t>(__builtin_frame_address(0)), 32, 0x4000);
|
||||
ReportCallTrace();
|
||||
if (reportThreads) ReportAllThreads();
|
||||
std::fflush(stderr);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,129 @@
|
|||
#include "prx/libc/include/CallTrace.hpp"
|
||||
#include "prx/libc/include/general/VabiMacros.hpp"
|
||||
#include <windows.h>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <source_location>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
struct Block {
|
||||
std::uint64_t words[4];
|
||||
};
|
||||
|
||||
static void Require(bool condition, std::source_location location = std::source_location::current()) {
|
||||
if (!condition) {
|
||||
std::fprintf(stderr, "Call trace check failed at line %u\n", location.line());
|
||||
std::abort();
|
||||
}
|
||||
}
|
||||
|
||||
static std::uint64_t APS5_VABI Integers(std::uint64_t a, std::uint64_t b, std::uint64_t c, std::uint64_t d, std::uint64_t e, std::uint64_t f, std::uint64_t g, std::uint64_t h) {
|
||||
return a + 2 * b + 3 * c + 4 * d + 5 * e + 6 * f + 7 * g + 8 * h;
|
||||
}
|
||||
|
||||
static double APS5_VABI Doubles(double a, double b, double c, double d, double e, double f, double g, double h, double i, double j) {
|
||||
return a + 2 * b + 3 * c + 4 * d + 5 * e + 6 * f + 7 * g + 8 * h + 9 * i + 10 * j;
|
||||
}
|
||||
|
||||
static double APS5_VABI Mixed(int a, double b, const char* c, float d, std::uint64_t e) {
|
||||
return a + b + static_cast<double>(std::strlen(c)) + d + static_cast<double>(e);
|
||||
}
|
||||
|
||||
static double APS5_VABI Variadic(int count, ...) {
|
||||
__builtin_sysv_va_list arguments;
|
||||
__builtin_sysv_va_start(arguments, count);
|
||||
double sum = 0;
|
||||
for (int index = 0; index < count; ++index) {
|
||||
sum += static_cast<double>(__builtin_va_arg(arguments, std::uint64_t)) * (index + 1);
|
||||
sum += __builtin_va_arg(arguments, double) * (index + 1);
|
||||
}
|
||||
__builtin_sysv_va_end(arguments);
|
||||
return sum;
|
||||
}
|
||||
|
||||
static Block APS5_VABI Blocks(std::uint64_t seed) {
|
||||
return {{seed, seed + 1, seed + 2, seed + 3}};
|
||||
}
|
||||
|
||||
template <typename TFunction>
|
||||
static TFunction* Traced(TFunction* function, const char* name, const char* library) {
|
||||
return reinterpret_cast<TFunction*>(TraceImport_nid_no_patch(reinterpret_cast<void*>(function), name, library));
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
Require(_putenv_s("APS5_TRACE_CALLS", "libTraced,libOther.prx") == 0);
|
||||
if (argc > 1 && std::strcmp(argv[1], "abort") == 0) {
|
||||
const auto first = Traced(&Integers, "Integers", "libTraced.prx");
|
||||
const auto second = Traced(&Blocks, "Blocks", "libOther.prx");
|
||||
Require(first(1, 2, 3, 4, 5, 6, 7, 8) == 204 && second(7).words[3] == 10);
|
||||
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
||||
std::abort();
|
||||
}
|
||||
CallTraceEntry entries[16];
|
||||
CallTraceCount counts[8];
|
||||
std::uint64_t total = 1;
|
||||
Require(CopyCallTrace_nid_no_patch(entries, 16) == 0);
|
||||
Require(CopyCallCounts_nid_no_patch(counts, 8, &total) == 0 && total == 0);
|
||||
Require(Traced(&Integers, "Integers", "libUnselected.prx") == &Integers);
|
||||
Require(TraceImport_nid_no_patch(nullptr, "Integers", "libTraced.prx") == nullptr);
|
||||
Require(CopyCallTrace_nid_no_patch(entries, 16) == 0);
|
||||
|
||||
const auto integers = Traced(&Integers, "Integers", "libTraced.prx");
|
||||
const auto doubles = Traced(&Doubles, "Doubles", "libTraced");
|
||||
const auto mixed = Traced(&Mixed, "Mixed", "libOther.prx");
|
||||
const auto variadic = Traced(&Variadic, "Variadic", "libOther");
|
||||
const auto blocks = Traced(&Blocks, "Blocks", "libTraced.prx");
|
||||
Require(integers != &Integers && doubles != &Doubles && mixed != &Mixed && variadic != &Variadic && blocks != &Blocks);
|
||||
Require(Traced(&Integers, "Alias", "libOther.prx") == integers);
|
||||
|
||||
Require(integers(1, 2, 3, 4, 5, 6, 7, 8) == Integers(1, 2, 3, 4, 5, 6, 7, 8));
|
||||
Require(doubles(1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5) == Doubles(1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5, 10.5));
|
||||
Require(mixed(-3, 0.25, "trace", 1.5f, 1ull << 40) == Mixed(-3, 0.25, "trace", 1.5f, 1ull << 40));
|
||||
const double expected = Variadic(8, 1ull, 0.5, 2ull, 1.5, 3ull, 2.5, 4ull, 3.5, 5ull, 4.5, 6ull, 5.5, 7ull, 6.5, 8ull, 7.5);
|
||||
Require(variadic(8, 1ull, 0.5, 2ull, 1.5, 3ull, 2.5, 4ull, 3.5, 5ull, 4.5, 6ull, 5.5, 7ull, 6.5, 8ull, 7.5) == expected);
|
||||
const Block block = blocks(40);
|
||||
Require(block.words[0] == 40 && block.words[1] == 41 && block.words[2] == 42 && block.words[3] == 43);
|
||||
|
||||
const char* const names[] = {"Integers", "Doubles", "Mixed", "Variadic", "Blocks"};
|
||||
const char* const libraries[] = {"libTraced.prx", "libTraced", "libOther.prx", "libOther", "libTraced.prx"};
|
||||
MEMORY_BASIC_INFORMATION image{};
|
||||
Require(VirtualQuery(reinterpret_cast<const void*>(&main), &image, sizeof(image)) == sizeof(image));
|
||||
Require(CopyCallTrace_nid_no_patch(entries, 16) == 5);
|
||||
for (std::uint64_t index = 0; index < 5; ++index) {
|
||||
Require(entries[index].sequence == index + 1 && entries[index].thread == GetCurrentThreadId());
|
||||
Require(std::strcmp(entries[index].name, names[index]) == 0 && std::strcmp(entries[index].library, libraries[index]) == 0);
|
||||
MEMORY_BASIC_INFORMATION caller{};
|
||||
Require(VirtualQuery(reinterpret_cast<const void*>(entries[index].returnAddress), &caller, sizeof(caller)) == sizeof(caller));
|
||||
Require(caller.AllocationBase == image.AllocationBase);
|
||||
}
|
||||
Require(CopyCallTrace_nid_no_patch(entries, 2) == 2 && entries[0].sequence == 4 && entries[1].sequence == 5);
|
||||
Require(CopyCallCounts_nid_no_patch(counts, 8, &total) == 5 && total == 5);
|
||||
for (std::size_t index = 0; index < 5; ++index) {
|
||||
Require(counts[index].calls == 1 && std::strcmp(counts[index].name, names[index]) == 0 && std::strcmp(counts[index].library, libraries[index]) == 0);
|
||||
}
|
||||
Require(doubles(1, 1, 1, 1, 1, 1, 1, 1, 1, 1) == 55 && blocks(1).words[0] == 1 && blocks(2).words[0] == 2);
|
||||
Require(CopyCallCounts_nid_no_patch(counts, 2, nullptr) == 2);
|
||||
Require(counts[0].calls == 3 && std::strcmp(counts[0].name, "Blocks") == 0 && counts[1].calls == 2 && std::strcmp(counts[1].name, "Doubles") == 0);
|
||||
Require(CopyCallCounts_nid_no_patch(nullptr, 8, &total) == 0 && total == 8);
|
||||
|
||||
constexpr std::uint64_t Workers = 4;
|
||||
constexpr std::uint64_t Calls = 20000;
|
||||
std::vector<std::thread> workers;
|
||||
for (std::uint64_t worker = 0; worker < Workers; ++worker) {
|
||||
workers.emplace_back([integers, worker] {
|
||||
for (std::uint64_t call = 0; call < Calls; ++call) Require(integers(worker, call, 1, 2, 3, 4, 5, 6) == Integers(worker, call, 1, 2, 3, 4, 5, 6));
|
||||
});
|
||||
}
|
||||
for (auto& worker : workers) worker.join();
|
||||
constexpr std::size_t Window = 65536;
|
||||
const auto recent = std::make_unique<CallTraceEntry[]>(Window);
|
||||
Require(CopyCallTrace_nid_no_patch(recent.get(), Window) == Window);
|
||||
Require(recent[Window - 1].sequence == 8 + Workers * Calls && recent[0].sequence == 8 + Workers * Calls - Window + 1);
|
||||
for (std::size_t index = 0; index < Window; ++index) Require(std::strcmp(recent[index].name, "Integers") == 0);
|
||||
Require(CopyCallCounts_nid_no_patch(counts, 1, &total) == 1 && total == 8 + Workers * Calls);
|
||||
Require(counts[0].calls == 1 + Workers * Calls && std::strcmp(counts[0].name, "Integers") == 0);
|
||||
}
|
||||
|
|
@ -60,6 +60,7 @@
|
|||
- [wcsftime](../../../core/libs/prx/libc/src/Time.cpp) (libc) follows FreeBSD 11.4's [wcsftime.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/locale/wcsftime.c) over this library's `wcsrtombs`, `strftime` and `mbsrtowcs` in the single-byte C locale of [none.c](https://github.com/freebsd/freebsd-src/blob/8c639945a44588f6cfdddfb057bb5aad88735da0/lib/libc/locale/none.c), with the guest's 16-bit `wchar_t`, without console verification. It has the `strftime` gaps above. A format unit above 255 returns 0 with `EILSEQ`; an empty result, a result that does not fit and a null buffer return 0 with `errno` unchanged, discarding the `ERANGE` or `EINVAL` that the Windows CRT's `strftime` sets. A null format or `tm` throws, where FreeBSD dereferences it (a null `tm` is read there only when the format has a conversion that needs it)
|
||||
- [syslog](../../../core/libs/prx/libc/src/Syslog.cpp) (libc) writes each message to `stderr` as `[syslog:<priority>] <message>`, with a newline added when missing, instead of sending it to the console's system log. Priority and facility handling follow FreeBSD's `vsyslog`: `%m` expands to the guest errno's message, unknown bits are reported (as priority 0x23, since FreeBSD passes `LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID` as the priority) and masked, a missing facility becomes `LOG_USER` or the facility set by `openlog`, and errno is preserved. `openlog` applies its identity and `LOG_PID`; a null identity retains the previous one, and a valid nonzero facility replaces the default. `closelog` clears the identity but retains the options and facility, as in FreeBSD 11; when no identity is set, FreeBSD falls back to the program name, which this backend does not emit. `LOG_PERROR` has no extra effect because stderr is the emulated log sink; `LOG_NDELAY`/`LOG_ODELAY`, `LOG_NOWAIT` and `LOG_CONS` have no daemon, child or console backend to control. `setlogmask` follows [FreeBSD 11](https://github.com/freebsd/freebsd-src/blob/8d2708de5d6112a492744d367cfdfb8fd219227a/lib/libc/gen/syslog.c): the process-wide mask defaults to 0xff, zero queries it, and nonzero values replace it; masked messages are skipped before formatting, including the unknown-priority diagnostic. The console's mask behaviour is unverified
|
||||
- [daemon](../../../core/libs/prx/libc/src/Process.cpp) (libc) throws: forking into the background is not supported. PPSA85300 imports it and fails to load without it. VLC 3.0 calls it only when its `daemon` option is set ([libvlc.c](https://github.com/videolan/vlc/blob/6de05adcbaf2e8b85fe86aad4169393098628119/src/libvlc.c#L190-L197)), and the title starts libVLC with `--ignore-config` and without `--daemon` ([player.cc](https://github.com/ZiZc3/VLC/blob/f3253020a64368153233ede29183014c0b6c8979/src/player.cc#L960-L981))
|
||||
- [Call tracing](../../../core/libs/prx/libc/src/specifics/windows/CallTrace.cpp) (`APS5_TRACE_CALLS`) exists on Windows only and records the entry of a call, not its arguments or its result. Nothing passes imports through `TraceImport_nid_no_patch` yet, so no title is traced. One process-wide ring keeps the last 65536 calls, so a thread that calls often pushes the others out. Calls are also counted per traced target since start-up; an import bound to a target already traced under another name is counted under the first name. The thunk saves the integer argument registers and `xmm0` to `xmm7`; arguments in wider vector registers pass only as long as the recording code stays free of AVX instructions, which is not tested
|
||||
- [_WStoul](../../../core/libs/prx/libc/src/Strings.cpp), [_Iswctype](../../../core/libs/prx/libc/src/RuneLocale.cpp) and [swscanf_s](../../../core/libs/prx/libc/src/ScanningWide.cpp) (libc) - `_WStoul` is `wcstoul` and throws for a base other than 0 or 2 to 36. `_Iswctype` takes the class numbers of Dinkumware's [xwcc.h](https://github.com/superna9999/ti-cgt6x/blob/cgt6x_7_3_4/include/xwcc.h) (1 `alnum` to 11 `xdigit`, 12 `blank`) and throws for any other value, including the FreeBSD masks `wctype` returns; the numbering is not confirmed on the console's `libc.prx`. `swscanf_s` follows C11 7.29.2.2 and K.3.9.1.5, but reads the size argument as `unsigned int` like `sscanf_s` where K.3.9.1.5 specifies `rsize_t`; the console's width is unverified. A runtime-constraint violation (null buffer, format or receiving pointer) only returns `EOF`, and a too small array gets a null first element as in `sscanf_s`. A literal mismatch is a matching failure and a field that is only a prefix, such as `0x` or `1e`, does not convert, as C11 requires; FreeBSD's `vfwscanf` reports an input failure for the first and converts the second. Integers out of range are truncated from `strtoll`/`strtoull`. It throws for `%p`, `%S`, `%C`, unknown length modifiers, scanset ranges, `%n` with suppression or width, and narrow `%s`, `%c` or `%[` input that the C-locale `wcrtomb` cannot convert
|
||||
- [flock](../../../core/libs/prx/libc/src/FileLock.cpp) (libkernel) on Windows shares one lock state between duplicated descriptors, following FreeBSD 11.4's [flock(2)](https://github.com/freebsd/freebsd-src/blob/releng/11.4/lib/libc/sys/flock.2). Independent opens have separate states; closing a descriptor or reopening a stream drops only its reference. Lock transitions on one open file description are serialized. The host's [LockFileEx](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex) locks also restrict ordinary reads and writes through other handles, unlike FreeBSD's advisory locks, and do not restrict mapped views. Console behavior, signal interruption and concurrent closure of a descriptor while its lock call is pending are unverified.
|
||||
- [std::_Throw_Cpp_error](../../../core/libs/prx/libc/src/exception/StandardExceptions.cpp) (libSceAmpr) follows Dinkumware's `thread0.cpp` as shipped with [Visual C++ 2013](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/thread0.cpp) and in [microsoft/STL](https://github.com/microsoft/STL/blob/da52dc0fcadbd83690cfd1ed32408d2847439493/stl/src/thread0.cpp), without console verification. `_Cpp_error` codes 0 to 6 throw a guest `std::system_error` whose `error_code` holds the FreeBSD errno of the `errc` value (EBUSY 16, EINVAL 22, ESRCH 3, ENOMEM 12, EPERM 1, EDEADLK 11, EAGAIN 35) and the generic category, and whose `what()` is the table message, `": "` and the generic category's message for that errno, selected by the first matching errno entry in VC12's [`syserror.cpp`](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/syserror.cpp); another code throws `std::out_of_range` on the host instead of reading past the table. The guest `system_error` is taken to be [VC12's](https://github.com/ganboing/crt.vc12/blob/f1d47126bf13d1c9dccaa6c53b72217fd0474968/src/system_error): the 16-byte `runtime_error` followed by `error_code {int, const error_category*}`, 32 bytes, so its copy constructor and assignment copy the code too. The generic category is a static libc object with VC12's `error_category` virtual order (destructors, `name`, `message`, `default_error_condition`, the two `equivalent`), not the console's `_Error_objects<int>::_Generic_object`, and the new `generic_category()` export returns that object; a title's inline category object in a bundled libc can still have a different identity. Its `message` and direct message export construct the existing [GuestLocale::String](../../../core/libs/prx/libc/include/GuestLocale.hpp) model: a reserved eight-byte word, 16-byte inline buffer, length and capacity, with guest `operator new` storage above 15 bytes. This 40-byte string layout and virtual return ABI have not been verified against a console library. The deleting destructor still throws. `_Syserror_map` covers the 72 distinct Dinkumware `errc` values present in FreeBSD 11.4 [errno.h](https://github.com/freebsd/freebsd-src/blob/releng/11.4/sys/sys/errno.h), returning null for other values; category messages use `unknown error` for those. First-match aliases are `EAGAIN`/`EWOULDBLOCK` -> `operation would block` and `ENOTSUP`/`EOPNOTSUPP` -> `not supported`. STREAMS-only errno assignments absent from that header (`ENODATA`, `ENOSR`, `ENOSTR`, `ETIME`) are not inferred
|
||||
|
|
|
|||
Loading…
Reference in New Issue