mirror of
https://github.com/kamb205/KB-RPCS3.git
synced 2026-10-17 15:00:50 +02:00
369 lines
11 KiB
C++
369 lines
11 KiB
C++
/*
|
|
* TLS probe title (PPSA99391 E/F, PPSA99392 N).
|
|
*
|
|
* Built with the same SDK, compiler wrappers and link recipe as the RPCS3 title. Writes its findings to
|
|
* /app0/tlsprobe-<variant>.txt and ends with sceSystemServiceLoadExec("exit").
|
|
*
|
|
* PROBE_EF: emulated TLS, stock __emutls_get_address (E) + ps5_fast_emutls_get_address (F).
|
|
* PROBE_N : native TLS (initial-exec/local-exec), PT_TLS linker-script change.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
#include "emutls_control.h"
|
|
|
|
#include <atomic>
|
|
#include <cinttypes>
|
|
#include <cstdarg>
|
|
#include <cstdint>
|
|
#include <cstdio>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <pthread.h>
|
|
#include <signal.h>
|
|
#include <thread>
|
|
#include <unistd.h>
|
|
#include <vector>
|
|
|
|
extern "C" {
|
|
int sceKernelDebugOutText(int channel, const char* text);
|
|
int sceSystemServiceLoadExec(const char* path, const char* const* argv);
|
|
int sceKernelUsleep(unsigned int usec);
|
|
void ps5_pthread_exit(void* value) __attribute__((noreturn));
|
|
struct ps5_heap_stats { uint64_t unknown[8]; };
|
|
void ps5_heap_stats(struct ps5_heap_stats* stats);
|
|
void ps5_fp_ieee(void);
|
|
unsigned ps5_fast_emutls_cache_live(void);
|
|
}
|
|
|
|
#ifndef PROBE_TAG
|
|
#define PROBE_TAG "EF"
|
|
#endif
|
|
#ifndef SPEED_ITERS
|
|
#define SPEED_ITERS 100000000ull
|
|
#endif
|
|
static constexpr unsigned long long kMagic = 0x5EC0DE00A11CE5EDull;
|
|
|
|
// ---- thread_local test variables (extern "C" so the __emutls_v.* control names are predictable) ----
|
|
extern "C" {
|
|
thread_local int g_a = 42;
|
|
thread_local unsigned long long g_magic = kMagic;
|
|
thread_local unsigned long long g_z; /* zero-initialised */
|
|
}
|
|
|
|
static std::atomic<int> g_ctor_total{0};
|
|
static std::atomic<int> g_dtor_total{0};
|
|
struct Tracked
|
|
{
|
|
unsigned char pad[24];
|
|
Tracked() { g_ctor_total.fetch_add(1, std::memory_order_relaxed); }
|
|
~Tracked() { g_dtor_total.fetch_add(1, std::memory_order_relaxed); }
|
|
};
|
|
extern "C" { thread_local Tracked g_tracked; }
|
|
|
|
#ifdef PROBE_EF
|
|
// F is tested on controls of its own, never shared with the stock implementation: E and F keep independent
|
|
// index allocators, so pointing both at one control (the old invalid design) is not done. A manual control
|
|
// has exactly the layout the compiler emits for a thread_local.
|
|
static int g_e_init = 5;
|
|
static int g_f_init = 7;
|
|
static __emutls_control g_e_ctl = { sizeof(int), alignof(int), {0}, &g_e_init };
|
|
static __emutls_control g_f_ctl = { sizeof(int), alignof(int), {0}, &g_f_init };
|
|
#endif
|
|
|
|
// ---- logging ----
|
|
static FILE* g_log;
|
|
|
|
static void logf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
|
|
static void logf(const char* fmt, ...)
|
|
{
|
|
char line[512];
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vsnprintf(line, sizeof line, fmt, ap);
|
|
va_end(ap);
|
|
if (g_log)
|
|
{
|
|
fputs(line, g_log);
|
|
fputc('\n', g_log);
|
|
fflush(g_log);
|
|
}
|
|
char out[600];
|
|
snprintf(out, sizeof out, "[tlsprobe-%s] %s\n", PROBE_TAG, line);
|
|
sceKernelDebugOutText(0, out);
|
|
}
|
|
|
|
// ---- per-thread checks ----
|
|
static std::atomic<int> g_threads_done{0};
|
|
static std::atomic<int> g_threads_bad{0};
|
|
static pthread_mutex_t g_addr_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
static std::vector<uintptr_t> g_addr_a;
|
|
static std::vector<uintptr_t> g_addr_magic;
|
|
|
|
struct ThreadResult { bool init_ok; int a_after; uintptr_t addr_a, addr_magic; };
|
|
|
|
static ThreadResult check_thread(bool hammer)
|
|
{
|
|
ThreadResult r{};
|
|
r.init_ok = (g_a == 42) && (g_magic == kMagic) && (g_z == 0);
|
|
r.addr_a = (uintptr_t)&g_a;
|
|
r.addr_magic = (uintptr_t)&g_magic;
|
|
if (hammer)
|
|
for (int i = 0; i < 1000000; i++)
|
|
g_a++;
|
|
r.a_after = g_a;
|
|
g_tracked.pad[0] = (unsigned char)g_a; /* force g_tracked to be instantiated per thread */
|
|
return r;
|
|
}
|
|
|
|
static void record(ThreadResult r)
|
|
{
|
|
if (!r.init_ok || r.a_after != 42 + 1000000)
|
|
g_threads_bad.fetch_add(1, std::memory_order_relaxed);
|
|
pthread_mutex_lock(&g_addr_mutex);
|
|
g_addr_a.push_back(r.addr_a);
|
|
g_addr_magic.push_back(r.addr_magic);
|
|
pthread_mutex_unlock(&g_addr_mutex);
|
|
g_threads_done.fetch_add(1, std::memory_order_relaxed);
|
|
}
|
|
|
|
static void* pthread_worker(void*)
|
|
{
|
|
record(check_thread(true));
|
|
return nullptr;
|
|
}
|
|
|
|
static std::atomic<int> g_reuse_bad{0};
|
|
static void* pthread_fresh_worker(void*)
|
|
{
|
|
if (!(g_a == 42 && g_magic == kMagic && g_z == 0))
|
|
g_reuse_bad.fetch_add(1, std::memory_order_relaxed);
|
|
g_tracked.pad[0] = 1;
|
|
return nullptr;
|
|
}
|
|
|
|
static void* pthread_exit_worker(void*)
|
|
{
|
|
check_thread(true);
|
|
g_threads_done.fetch_add(1, std::memory_order_relaxed);
|
|
pthread_exit(nullptr);
|
|
}
|
|
|
|
static void* ps5_exit_worker(void*)
|
|
{
|
|
check_thread(true);
|
|
g_threads_done.fetch_add(1, std::memory_order_relaxed);
|
|
ps5_pthread_exit(nullptr);
|
|
}
|
|
|
|
static std::atomic<int> g_signal_ok{0};
|
|
static void sig_handler(int, siginfo_t*, void*)
|
|
{
|
|
/* read and write a thread_local from a signal handler on this thread */
|
|
if (g_a >= 42 && g_magic == kMagic)
|
|
{
|
|
const int before = g_a;
|
|
g_a = before + 1;
|
|
if (g_a == before + 1)
|
|
g_signal_ok.fetch_add(1, std::memory_order_relaxed);
|
|
g_a = before;
|
|
}
|
|
}
|
|
|
|
static void run_signal_test()
|
|
{
|
|
struct sigaction sa{};
|
|
sa.sa_sigaction = sig_handler;
|
|
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
|
sigaction(SIGUSR1, &sa, nullptr);
|
|
g_signal_ok = 0;
|
|
/* signal to self from the main thread */
|
|
raise(SIGUSR1);
|
|
std::thread t([] { raise(SIGUSR1); });
|
|
t.join();
|
|
logf("signal: %d/2 handlers saw the thread's own thread_local values", g_signal_ok.load());
|
|
}
|
|
|
|
static void run_common()
|
|
{
|
|
logf("variant %s, %s", PROBE_TAG,
|
|
#ifdef PROBE_N
|
|
"native TLS"
|
|
#else
|
|
"emulated TLS"
|
|
#endif
|
|
);
|
|
logf("main: a=%d magic=0x%llx z=%llu", g_a, g_magic, g_z);
|
|
|
|
// 8 std::thread
|
|
std::vector<std::thread> ts;
|
|
for (int i = 0; i < 8; i++)
|
|
ts.emplace_back([] { record(check_thread(true)); });
|
|
for (auto& t : ts) t.join();
|
|
// 8 raw pthread_create
|
|
pthread_t pt[8];
|
|
for (int i = 0; i < 8; i++) pthread_create(&pt[i], nullptr, pthread_worker, nullptr);
|
|
for (int i = 0; i < 8; i++) pthread_join(pt[i], nullptr);
|
|
|
|
logf("threads: %d done, %d bad (initial values/isolation)", g_threads_done.load(), g_threads_bad.load());
|
|
logf("main unchanged: a=%d magic=0x%llx z=%llu", g_a, g_magic, g_z);
|
|
|
|
// distinct addresses
|
|
bool distinct = true;
|
|
for (size_t i = 1; i < g_addr_a.size(); i++)
|
|
if (g_addr_a[i] == g_addr_a[0] || g_addr_magic[i] == g_addr_magic[0]) distinct = false;
|
|
logf("addresses: %zu threads, distinct=%s (first a=0x%" PRIxPTR " magic=0x%" PRIxPTR ")",
|
|
g_addr_a.size(), distinct ? "yes" : "NO", g_addr_a.empty() ? 0 : g_addr_a[0],
|
|
g_addr_magic.empty() ? 0 : g_addr_magic[0]);
|
|
|
|
// reuse: 2000 sequential create/join; every new thread must see fresh initial values
|
|
for (int i = 0; i < 2000; i++)
|
|
{
|
|
pthread_t t;
|
|
pthread_create(&t, nullptr, pthread_fresh_worker, nullptr);
|
|
pthread_join(t, nullptr);
|
|
}
|
|
logf("reuse: 2000 create/join done, %d threads saw stale initial values", g_reuse_bad.load());
|
|
|
|
run_signal_test();
|
|
}
|
|
|
|
static void run_destructors()
|
|
{
|
|
g_ctor_total = 0;
|
|
g_dtor_total = 0;
|
|
{ std::thread t([] { volatile auto* p = &g_tracked; (void)p; }); t.join(); }
|
|
const int after_std = g_dtor_total.load();
|
|
{ pthread_t t; pthread_create(&t, nullptr, [](void*) -> void* { volatile auto* p = &g_tracked; (void)p; return nullptr; }, nullptr); pthread_join(t, nullptr); }
|
|
const int after_ret = g_dtor_total.load();
|
|
{ pthread_t t; pthread_create(&t, nullptr, pthread_exit_worker, nullptr); pthread_join(t, nullptr); }
|
|
const int after_pexit = g_dtor_total.load();
|
|
{ pthread_t t; pthread_create(&t, nullptr, ps5_exit_worker, nullptr); pthread_join(t, nullptr); }
|
|
const int after_ps5 = g_dtor_total.load();
|
|
logf("destructors: ctors=%d | dtor totals: std::thread=%d, return=%d, pthread_exit=%d, ps5_pthread_exit=%d",
|
|
g_ctor_total.load(), after_std, after_ret, after_pexit, after_ps5);
|
|
}
|
|
|
|
static volatile unsigned long long g_sink;
|
|
static volatile int g_plain; // non-TLS global
|
|
|
|
static unsigned long long now_ns()
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (unsigned long long)ts.tv_sec * 1000000000ull + (unsigned long long)ts.tv_nsec;
|
|
}
|
|
|
|
#ifdef PROBE_EF
|
|
static void run_f_test()
|
|
{
|
|
// F on its own control: initial value, per-thread isolation, and cache lifetime across threads.
|
|
int* p = (int*)ps5_fast_emutls_get_address(&g_f_ctl);
|
|
logf("F: main object value=%d (want 7) at %p; cache live entries=%u",
|
|
*p, (void*)p, ps5_fast_emutls_cache_live());
|
|
std::atomic<int> bad{0};
|
|
std::vector<std::thread> ts;
|
|
for (int i = 0; i < 8; i++)
|
|
ts.emplace_back([&] {
|
|
int* q = (int*)ps5_fast_emutls_get_address(&g_f_ctl);
|
|
if (*q != 7 || q == p) bad.fetch_add(1, std::memory_order_relaxed);
|
|
for (int k = 0; k < 100000; k++) (*q)++;
|
|
});
|
|
for (auto& t : ts) t.join();
|
|
// The 8 exited threads' entries must be gone; only the main thread's remains.
|
|
logf("F: 8 threads initial value/distinct: %d bad; cache live entries=%u (want 1: main only)",
|
|
bad.load(), ps5_fast_emutls_cache_live());
|
|
}
|
|
|
|
static void speed_ef()
|
|
{
|
|
int p = 0;
|
|
unsigned long long t0 = now_ns();
|
|
for (unsigned long long i = 0; i < SPEED_ITERS; i++) { g_plain = (int)i; p += g_plain; }
|
|
unsigned long long t1 = now_ns();
|
|
logf("speed plain global: %llu ns/access", (t1 - t0) / SPEED_ITERS);
|
|
|
|
t0 = now_ns();
|
|
for (unsigned long long i = 0; i < SPEED_ITERS; i++) { volatile int* q = (volatile int*)__emutls_get_address(&g_e_ctl); p += *q; }
|
|
t1 = now_ns();
|
|
logf("speed E (stock __emutls_get_address): %llu ns/access", (t1 - t0) / SPEED_ITERS);
|
|
|
|
t0 = now_ns();
|
|
for (unsigned long long i = 0; i < SPEED_ITERS; i++) { volatile int* q = (volatile int*)ps5_fast_emutls_get_address(&g_f_ctl); p += *q; }
|
|
t1 = now_ns();
|
|
logf("speed F (ps5_fast_emutls_get_address): %llu ns/access", (t1 - t0) / SPEED_ITERS);
|
|
g_sink += (unsigned long long)p;
|
|
}
|
|
#endif
|
|
|
|
#ifdef PROBE_N
|
|
static void speed_n()
|
|
{
|
|
int p = 0;
|
|
const unsigned long long t0 = now_ns();
|
|
for (unsigned long long i = 0; i < SPEED_ITERS; i++) { g_a = (int)i; p += g_a; }
|
|
const unsigned long long t1 = now_ns();
|
|
logf("speed N (native thread_local): %llu ns/access", (t1 - t0) / SPEED_ITERS);
|
|
g_sink += (unsigned long long)p;
|
|
}
|
|
#endif
|
|
|
|
#ifdef PROBE_N
|
|
static void run_n_layout()
|
|
{
|
|
uintptr_t fs0;
|
|
__asm__ volatile("movq %%fs:0, %0" : "=r"(fs0));
|
|
logf("N: fs0=0x%" PRIxPTR " &magic=0x%" PRIxPTR " (compiler offset %lld)", fs0, (uintptr_t)&g_magic,
|
|
(long long)((intptr_t)&g_magic - (intptr_t)fs0));
|
|
// scan [fs0 - 128 KiB, fs0) for the magic value
|
|
const uintptr_t lo = fs0 > 0x20000 ? fs0 - 0x20000 : 0;
|
|
int found = 0;
|
|
for (uintptr_t p = lo; p + sizeof(uint64_t) <= fs0; p += 8)
|
|
{
|
|
uint64_t v;
|
|
memcpy(&v, (const void*)p, sizeof v);
|
|
if (v == kMagic)
|
|
{
|
|
logf("N: magic found at fs0%+lld (0x%" PRIxPTR ")", (long long)((intptr_t)p - (intptr_t)fs0), p);
|
|
found++;
|
|
if (found >= 4) break;
|
|
}
|
|
}
|
|
if (!found)
|
|
logf("N: magic NOT found in [fs0-128KiB, fs0) (image not copied, or block elsewhere)");
|
|
logf("N: value at &magic = 0x%llx", g_magic);
|
|
}
|
|
#endif
|
|
|
|
int main(int, char**)
|
|
{
|
|
char path[64];
|
|
snprintf(path, sizeof path, "/app0/tlsprobe-%s.txt", PROBE_TAG);
|
|
g_log = fopen(path, "w");
|
|
logf("=== TLS probe %s ===", PROBE_TAG);
|
|
|
|
run_common();
|
|
run_destructors();
|
|
#ifdef PROBE_EF
|
|
run_f_test();
|
|
speed_ef();
|
|
#endif
|
|
#ifdef PROBE_N
|
|
run_n_layout();
|
|
speed_n();
|
|
#endif
|
|
|
|
logf("=== done ===");
|
|
if (g_log) fclose(g_log);
|
|
return 0;
|
|
}
|
|
|
|
extern "C" void catchReturnFromMain(int status)
|
|
{
|
|
(void)status;
|
|
if (g_log) fclose(g_log);
|
|
sceSystemServiceLoadExec("exit", nullptr);
|
|
for (;;)
|
|
sceKernelUsleep(100000);
|
|
}
|