Skip to content

Commit

Permalink
Linux: More safe stack cleanup for clone
Browse files Browse the repository at this point in the history
Previously: Would keep one clone thread's stack active for teardown
delaying.

With aggressive cloning and teardown, this was unsafe.
Only reap the stack when told it is safe to do so.
  • Loading branch information
Sonicadvance1 committed Feb 21, 2024
1 parent 66feea9 commit 9f9c684
Show file tree
Hide file tree
Showing 6 changed files with 83 additions and 29 deletions.
23 changes: 14 additions & 9 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -394,21 +394,26 @@ struct StackFramePlusRet {
};

[[noreturn]]
static void Clone3HandlerRet() {
StackFrameData *Data = (StackFrameData*)alloca(0);
static void CloneBody(StackFrameData *Data, bool NeedsDataFree) {
uint64_t Result = FEX::HLE::HandleNewClone(Data->Thread, Data->CTX, &Data->NewFrame, &Data->GuestArgs);
FEX::LinuxEmulation::Threads::DeallocateStackObject(Data->GuestArgs.NewStack);
// To behave like a real clone, we now just need to call exit here
exit(Result);
auto Stack = Data->GuestArgs.NewStack;
if (NeedsDataFree) {
FEXCore::Allocator::free(Data);
}

FEX::LinuxEmulation::Threads::DeallocateStackObjectAndExit(Stack, Result);
FEX_UNREACHABLE;
}

[[noreturn]]
static void Clone3HandlerRet() {
StackFrameData *Data = (StackFrameData*)alloca(0);
CloneBody(Data, false);
}

static int Clone2HandlerRet(void *arg) {
StackFrameData *Data = (StackFrameData*)arg;
uint64_t Result = FEX::HLE::HandleNewClone(Data->Thread, Data->CTX, &Data->NewFrame, &Data->GuestArgs);
FEX::LinuxEmulation::Threads::DeallocateStackObject(Data->GuestArgs.NewStack);
FEXCore::Allocator::free(arg);
return Result;
CloneBody(Data, true);
}

// Clone3 flags
Expand Down
4 changes: 3 additions & 1 deletion Source/Tools/LinuxEmulation/LinuxSyscalls/Syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ desc: Glue logic, STRACE magic
#define SYSCALL_ARCH_NAME Arm64
#endif

#include "LinuxSyscalls/x64/SyscallsEnum.h"

#define CONCAT_(a, b) a ## b
#define CONCAT(a, b) CONCAT_(a, b)
#define SYSCALL_DEF(name) ( SYSCALL_ARCH_NAME::CONCAT(CONCAT(SYSCALL_, SYSCALL_ARCH_NAME), _##name))
#define SYSCALL_DEF(name) ( HLE::SYSCALL_ARCH_NAME::CONCAT(CONCAT(SYSCALL_, SYSCALL_ARCH_NAME), _##name))

// #define DEBUG_STRACE

Expand Down
77 changes: 62 additions & 15 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/Utils/Threads.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
#include "LinuxSyscalls/Utils/Threads.h"
#include "LinuxSyscalls/Syscalls.h"

#include <FEXCore/Core/Context.h>
#include <FEXCore/Utils/Threads.h>
Expand All @@ -12,33 +13,53 @@ namespace FEX::LinuxEmulation::Threads {
void *Ptr;
size_t Size;
};

struct DeadStackPoolItem {
void *Ptr;
size_t Size;
bool ReadyToBeReaped;
};

std::mutex DeadStackPoolMutex{};
std::mutex LiveStackPoolMutex{};

static fextl::deque<StackPoolItem> DeadStackPool{};
static fextl::deque<DeadStackPoolItem> DeadStackPool{};
static fextl::deque<StackPoolItem> LiveStackPool{};

void *AllocateStackObject() {
std::lock_guard lk{DeadStackPoolMutex};
if (DeadStackPool.size() == 0) {
// Nothing in the pool, just allocate
return FEXCore::Allocator::mmap(nullptr, FEX::LinuxEmulation::Threads::STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}

// Keep the first item in the stack pool
auto Result = DeadStackPool.front().Ptr;
DeadStackPool.pop_front();
void *Ptr{};

for (auto it = DeadStackPool.begin(); it != DeadStackPool.end();) {
auto Ready = std::atomic_ref<bool>(it->ReadyToBeReaped);
bool ReadyToBeReaped = Ready.load();
if (Ptr == nullptr && ReadyToBeReaped) {
Ptr = it->Ptr;
it = DeadStackPool.erase(it);
continue;
}

// Erase the rest as a garbage collection step
for (auto &Item : DeadStackPool) {
FEXCore::Allocator::munmap(Item.Ptr, Item.Size);
if (ReadyToBeReaped) {
FEXCore::Allocator::munmap(it->Ptr, it->Size);
it = DeadStackPool.erase(it);
continue;
}

++it;
}

if (Ptr == nullptr) {
Ptr = FEXCore::Allocator::mmap(nullptr, FEX::LinuxEmulation::Threads::STACK_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
return Result;

return Ptr;
}

void AddStackToDeadPool(void *Ptr) {
bool *AddStackToDeadPool(void *Ptr) {
std::lock_guard lk{DeadStackPoolMutex};
DeadStackPool.emplace_back(StackPoolItem{Ptr, FEX::LinuxEmulation::Threads::STACK_SIZE});
auto &it = DeadStackPool.emplace_back(DeadStackPoolItem{Ptr, FEX::LinuxEmulation::Threads::STACK_SIZE, false});
return &it.ReadyToBeReaped;
}

void AddStackToLivePool(void *Ptr) {
Expand All @@ -61,6 +82,32 @@ namespace FEX::LinuxEmulation::Threads {
AddStackToDeadPool(Ptr);
}

[[noreturn]]
void DeallocateStackObjectAndExit(void *Ptr, int Status) {
RemoveStackFromLivePool(Ptr);
auto ReadyToBeReaped = AddStackToDeadPool(Ptr);
*ReadyToBeReaped = true;

// To behave like a real clone, we now just need to call exit here
#ifdef _M_ARM_64
__asm volatile(
"mov x8, %[SyscallNum];"
"mov w0, %w[Result];"
"svc #0;"
:: [SyscallNum] "i" (SYSCALL_DEF(exit))
, [Result] "r" (Status)
: "memory", "x0", "x8");
#else
__asm volatile(
"mov %[Result], %%edi;"
"syscall;"
:: "a" (SYSCALL_DEF(exit))
, [Result] "r" (Status)
: "memory", "rdi");
#endif
FEX_UNREACHABLE;
}

namespace PThreads {
void *InitializeThread(void *Ptr);

Expand Down Expand Up @@ -154,7 +201,7 @@ namespace FEX::LinuxEmulation::Threads {

auto ClearStackPool = [&](auto &StackPool) {
for (auto it = StackPool.begin(); it != StackPool.end(); ) {
StackPoolItem &Item = *it;
auto &Item = *it;
uintptr_t ItemStack = reinterpret_cast<uintptr_t>(Item.Ptr);
if (ItemStack <= StackLocation && (ItemStack + Item.Size) > StackLocation) {
// This is our stack item, skip it
Expand Down
3 changes: 2 additions & 1 deletion Source/Tools/LinuxEmulation/LinuxSyscalls/Utils/Threads.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ namespace FEX::LinuxEmulation::Threads {
*
* @param Ptr The stack base from `AllocateStackObject`
*/
void DeallocateStackObject(void *Ptr);
[[noreturn]]
void DeallocateStackObjectAndExit(void *Ptr, int Status);

/**
* @brief Registers thread creation handlers with FEXCore.
Expand Down
2 changes: 0 additions & 2 deletions Source/Tools/LinuxEmulation/LinuxSyscalls/x64/Syscalls.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ struct InternalThreadState;
}

namespace FEX::HLE::x64 {
#include "SyscallsEnum.h"

class x64SyscallHandler final : public FEX::HLE::SyscallHandler {
public:
x64SyscallHandler(FEXCore::Context::Context *ctx, FEX::HLE::SignalDelegator *_SignalDelegation);
Expand Down
3 changes: 2 additions & 1 deletion Source/Tools/LinuxEmulation/LinuxSyscalls/x64/SyscallsEnum.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tags: LinuxSyscalls|syscalls-x86-64
*/
#pragma once

namespace FEX::HLE::x64 {
///< Enum containing all x86-64 linux syscalls for the guest kernel version
enum Syscalls_x64 {
SYSCALL_x64_read = 0,
Expand Down Expand Up @@ -479,4 +480,4 @@ enum Syscalls_x64 {
SYSCALL_x64_futex_time64 = ~0,
SYSCALL_x64_sched_rr_get_interval_time64 = ~0,
};

}

0 comments on commit 9f9c684

Please sign in to comment.