mirror of
https://github.com/boykopovar/AnyPS5.git
synced 2026-10-22 08:33:12 -05:00
The crash and abort reports printed the last 64 traced calls of the whole process, so a thread that loops on a traced import (a mutex lock/unlock pair, a profiler marker) pushes the calls of the faulting thread out of the report. The ring already keeps the last 65536 calls with their thread. CopyThreadCallTrace_nid_no_patch returns the most recent calls of one thread from that ring, oldest first, and both reports (Windows crash and abort, Linux abort) list up to 32 of them for the reporting thread after the process-wide list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
9.7 KiB
C++
203 lines
9.7 KiB
C++
#include "prx/libc/include/CallTrace.hpp"
|
|
#include "prx/libc/include/general/VabiMacros.hpp"
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#else
|
|
#include <dlfcn.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
#include <atomic>
|
|
#include <csignal>
|
|
#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}};
|
|
}
|
|
|
|
static std::uint64_t CurrentThread() {
|
|
#ifdef _WIN32
|
|
return GetCurrentThreadId();
|
|
#else
|
|
return static_cast<std::uint64_t>(gettid());
|
|
#endif
|
|
}
|
|
|
|
static const void* ImageOf(const void* address) {
|
|
#ifdef _WIN32
|
|
MEMORY_BASIC_INFORMATION memory{};
|
|
if (VirtualQuery(address, &memory, sizeof(memory)) != sizeof(memory)) return nullptr;
|
|
return memory.AllocationBase;
|
|
#else
|
|
Dl_info info{};
|
|
if (dladdr(address, &info) == 0) return nullptr;
|
|
return info.dli_fbase;
|
|
#endif
|
|
}
|
|
|
|
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) {
|
|
#ifdef _WIN32
|
|
Require(_putenv_s("APS5_TRACE_CALLS", "libTraced,libOther.prx") == 0);
|
|
#else
|
|
Require(setenv("APS5_TRACE_CALLS", "libTraced,libOther.prx", 1) == 0);
|
|
#endif
|
|
if (argc > 1 && (std::strcmp(argv[1], "abort") == 0 || std::strcmp(argv[1], "abort-race") == 0)) {
|
|
#ifndef _WIN32
|
|
Require(std::signal(SIGABRT, [](int) { _exit(3); }) != SIG_ERR);
|
|
#endif
|
|
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);
|
|
#ifdef _WIN32
|
|
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
|
|
#else
|
|
std::atomic<bool> locked{false};
|
|
std::thread([&locked] {
|
|
flockfile(stderr);
|
|
locked.store(true);
|
|
for (;;) pause();
|
|
}).detach();
|
|
while (!locked.load()) std::this_thread::yield();
|
|
if (std::strcmp(argv[1], "abort-race") == 0) {
|
|
for (int call = 0; call < 100; ++call) Require(first(1, 2, 3, 4, 5, 6, 7, 8) == 204);
|
|
std::atomic<bool> go{false};
|
|
std::thread([&go] {
|
|
while (!go.load()) {
|
|
}
|
|
std::abort();
|
|
}).detach();
|
|
go.store(true);
|
|
}
|
|
#endif
|
|
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"};
|
|
const void* image = ImageOf(reinterpret_cast<const void*>(&main));
|
|
Require(image != nullptr);
|
|
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 == CurrentThread());
|
|
Require(std::strcmp(entries[index].name, names[index]) == 0 && std::strcmp(entries[index].library, libraries[index]) == 0);
|
|
Require(ImageOf(reinterpret_cast<const void*>(entries[index].returnAddress)) == image);
|
|
}
|
|
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);
|
|
Require(CopyThreadCallTrace_nid_no_patch(CurrentThread(), entries, 16) == 8);
|
|
for (std::uint64_t index = 0; index < 8; ++index) Require(entries[index].sequence == index + 1 && entries[index].thread == CurrentThread());
|
|
Require(CopyThreadCallTrace_nid_no_patch(CurrentThread(), entries, 2) == 2 && entries[0].sequence == 7 && entries[1].sequence == 8);
|
|
Require(std::strcmp(entries[1].name, "Blocks") == 0);
|
|
Require(CopyThreadCallTrace_nid_no_patch(CurrentThread() + 1, entries, 16) == 0);
|
|
Require(CopyThreadCallTrace_nid_no_patch(CurrentThread(), nullptr, 16) == 0 && CopyThreadCallTrace_nid_no_patch(CurrentThread(), entries, 0) == 0);
|
|
|
|
constexpr std::uint64_t Workers = 4;
|
|
constexpr std::uint64_t Calls = 20000;
|
|
std::vector<std::thread> workers;
|
|
std::uint64_t workerThreads[Workers]{};
|
|
for (std::uint64_t worker = 0; worker < Workers; ++worker) {
|
|
workers.emplace_back([integers, worker, &workerThreads] {
|
|
workerThreads[worker] = CurrentThread();
|
|
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(CopyThreadCallTrace_nid_no_patch(CurrentThread(), entries, 16) == 0);
|
|
const auto own = std::make_unique<CallTraceEntry[]>(Window);
|
|
std::size_t owned = 0;
|
|
for (const std::uint64_t thread : workerThreads) {
|
|
std::size_t expected = 0;
|
|
for (std::size_t index = 0; index < Window; ++index) expected += recent[index].thread == thread;
|
|
const std::size_t count = CopyThreadCallTrace_nid_no_patch(thread, own.get(), Window);
|
|
Require(count == expected);
|
|
for (std::size_t index = 0; index < count; ++index) Require(own[index].thread == thread && (index == 0 || own[index - 1].sequence < own[index].sequence));
|
|
if (count != 0) Require(CopyThreadCallTrace_nid_no_patch(thread, entries, 1) == 1 && entries[0].sequence == own[count - 1].sequence);
|
|
owned += count;
|
|
}
|
|
Require(owned == Window);
|
|
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);
|
|
}
|