The additive event_log extraction tracer needs a current-thread lookup that returns nullptr instead of asserting when called from boot code before any XThread exists (GetCurrentThread asserts on non-guest threads). New read-only static accessor over the thread-local; no behaviour change. Fixes the native Linux build of the phase-a args/file.read tracer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
579 lines
21 KiB
C++
579 lines
21 KiB
C++
/**
|
|
******************************************************************************
|
|
* Xenia : Xbox 360 Emulator Research Project *
|
|
******************************************************************************
|
|
* Copyright 2020 Ben Vanik. All rights reserved. *
|
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
|
******************************************************************************
|
|
*/
|
|
|
|
#ifndef XENIA_KERNEL_XTHREAD_H_
|
|
#define XENIA_KERNEL_XTHREAD_H_
|
|
|
|
#include <atomic>
|
|
#include <string>
|
|
|
|
#include "xenia/base/mutex.h"
|
|
#if !XE_PLATFORM_WIN32
|
|
#include <condition_variable>
|
|
#include <csignal>
|
|
#include <mutex>
|
|
#endif
|
|
#if XE_PLATFORM_WIN32
|
|
#include <csetjmp>
|
|
#endif
|
|
#include "xenia/base/threading.h"
|
|
#include "xenia/cpu/thread.h"
|
|
#include "xenia/cpu/thread_state.h"
|
|
#include "xenia/kernel/util/native_list.h"
|
|
#include "xenia/kernel/util/xfiletime.h"
|
|
#include "xenia/kernel/xmutant.h"
|
|
#include "xenia/kernel/xobject.h"
|
|
#include "xenia/xbox.h"
|
|
|
|
namespace xe {
|
|
namespace kernel {
|
|
|
|
constexpr fourcc_t kThreadSaveSignature = make_fourcc("THRD");
|
|
|
|
class XEvent;
|
|
|
|
enum IRQL_FLAGS : uint8_t {
|
|
IRQL_PASSIVE = 0,
|
|
IRQL_APC = 1,
|
|
IRQL_DISPATCH = 2,
|
|
IRQL_DPC = 3,
|
|
IRQL_AUDIO = 68, // used a few times in the audio driver
|
|
IRQL_CLOCK = 116, // irql used by the clock interrupt
|
|
IRQL_HIGHEST = 124
|
|
};
|
|
|
|
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/ke/kthread_state.htm
|
|
enum X_KTHREAD_STATE_FLAGS : uint8_t {
|
|
KTHREAD_STATE_INITIALIZED = 0,
|
|
KTHREAD_STATE_READY = 1,
|
|
KTHREAD_STATE_RUNNING = 2,
|
|
KTHREAD_STATE_STANDBY = 3,
|
|
KTHREAD_STATE_TERMINATED = 4,
|
|
KTHREAD_STATE_WAITING = 5,
|
|
KTHREAD_STATE_UNKNOWN = 6, //"Transition" except that makes no sense here, so
|
|
// 6 likely has a different meaning on xboxkrnl
|
|
};
|
|
|
|
constexpr uint32_t X_CREATE_SUSPENDED = 0x00000001;
|
|
|
|
constexpr uint32_t X_TLS_OUT_OF_INDEXES = UINT32_MAX;
|
|
struct XDPC {
|
|
xe::be<uint16_t> type;
|
|
uint8_t selected_cpu_number;
|
|
uint8_t desired_cpu_number;
|
|
X_LIST_ENTRY list_entry;
|
|
xe::be<uint32_t> routine;
|
|
xe::be<uint32_t> context;
|
|
xe::be<uint32_t> arg1;
|
|
xe::be<uint32_t> arg2;
|
|
|
|
void Initialize(uint32_t guest_func, uint32_t guest_context) {
|
|
type = 19;
|
|
selected_cpu_number = 0;
|
|
desired_cpu_number = 0;
|
|
routine = guest_func;
|
|
context = guest_context;
|
|
}
|
|
};
|
|
|
|
struct XAPC {
|
|
static constexpr uint32_t kSize = 40;
|
|
static constexpr uint32_t kDummyKernelRoutine = 0xF00DFF00;
|
|
static constexpr uint32_t kDummyRundownRoutine = 0xF00DFF01;
|
|
|
|
// KAPC is 0x28(40) bytes? (what's passed to ExAllocatePoolWithTag)
|
|
// This is 4b shorter than NT - looks like the reserved dword at +4 is gone.
|
|
// NOTE: stored in guest memory.
|
|
uint16_t type; // +0
|
|
uint8_t apc_mode; // +2
|
|
uint8_t enqueued; // +3
|
|
xe::be<uint32_t> thread_ptr; // +4
|
|
X_LIST_ENTRY list_entry; // +8
|
|
xe::be<uint32_t> kernel_routine; // +16
|
|
xe::be<uint32_t> rundown_routine; // +20
|
|
xe::be<uint32_t> normal_routine; // +24
|
|
xe::be<uint32_t> normal_context; // +28
|
|
xe::be<uint32_t> arg1; // +32
|
|
xe::be<uint32_t> arg2; // +36
|
|
};
|
|
|
|
struct X_KSEMAPHORE {
|
|
X_DISPATCH_HEADER header;
|
|
xe::be<uint32_t> limit;
|
|
};
|
|
static_assert_size(X_KSEMAPHORE, 0x14);
|
|
|
|
struct X_KTHREAD;
|
|
struct X_KPROCESS;
|
|
struct X_KPRCB {
|
|
TypedGuestPointer<X_KTHREAD> current_thread; // 0x0
|
|
TypedGuestPointer<X_KTHREAD> next_thread; // 0x4
|
|
TypedGuestPointer<X_KTHREAD> idle_thread; // 0x8
|
|
uint8_t current_cpu; // 0xC
|
|
uint8_t unk_D[3]; // 0xD
|
|
// should only have 1 bit set, used for ipis
|
|
xe::be<uint32_t> processor_mask; // 0x10
|
|
// incremented in clock interrupt
|
|
xe::be<uint32_t> dpc_clock; // 0x14
|
|
xe::be<uint32_t> interrupt_clock; // 0x18
|
|
xe::be<uint32_t> unk_1C; // 0x1C
|
|
xe::be<uint32_t> unk_20; // 0x20
|
|
// various fields used by KeIpiGenericCall
|
|
xe::be<uint32_t> ipi_args[3]; // 0x24
|
|
// looks like the target cpus clear their corresponding bit
|
|
// in this mask to signal completion to the initiator
|
|
xe::be<uint32_t> targeted_ipi_cpus_mask; // 0x30
|
|
xe::be<uint32_t> ipi_function; // 0x34
|
|
// used to synchronize?
|
|
TypedGuestPointer<X_KPRCB> ipi_initiator_prcb; // 0x38
|
|
xe::be<uint32_t> unk_3C; // 0x3C
|
|
xe::be<uint32_t> dpc_related_40; // 0x40
|
|
// must be held to modify any dpc-related fields in the kprcb
|
|
xe::be<uint32_t> dpc_lock; // 0x44
|
|
X_LIST_ENTRY queued_dpcs_list_head; // 0x48
|
|
xe::be<uint32_t> dpc_active; // 0x50
|
|
X_KSPINLOCK spin_lock; // 0x54
|
|
TypedGuestPointer<X_KTHREAD> running_idle_thread; // 0x58
|
|
// definitely scheduler related
|
|
X_SINGLE_LIST_ENTRY enqueued_threads_list; // 0x5C
|
|
xe::be<uint32_t> has_ready_thread_by_priority; // 0x60
|
|
// i think the following mask has something to do with the array that comes
|
|
// after
|
|
xe::be<uint32_t> unk_mask_64; // 0x64
|
|
|
|
X_LIST_ENTRY unk_68[32]; // 0x68
|
|
// ExTerminateThread tail calls a function that does KeInsertQueueDpc of this
|
|
// dpc
|
|
XDPC thread_exit_dpc; // 0x168
|
|
// thread_exit_dpc's routine drains this list and frees each threads threadid,
|
|
// kernel stack and dereferences the thread
|
|
X_LIST_ENTRY terminating_threads_list; // 0x184
|
|
XDPC switch_thread_processor_dpc; // 0x18C
|
|
};
|
|
// Processor Control Region
|
|
struct X_KPCR {
|
|
xe::be<uint32_t> tls_ptr; // 0x0
|
|
xe::be<uint32_t> msr_mask; // 0x4
|
|
union {
|
|
xe::be<uint16_t> software_interrupt_state; // 0x8
|
|
struct {
|
|
uint8_t generic_software_interrupt; // 0x8
|
|
uint8_t apc_software_interrupt_state; // 0x9
|
|
};
|
|
};
|
|
xe::be<uint16_t> unk_0A; // 0xA
|
|
uint8_t processtype_value_in_dpc; // 0xC
|
|
uint8_t timeslice_ended; // 0xD
|
|
uint8_t timer_pending; // 0xE
|
|
uint8_t unk_0F; // 0xF
|
|
// used in KeSaveFloatingPointState / its vmx counterpart
|
|
xe::be<uint32_t> thread_fpu_related; // 0x10
|
|
xe::be<uint32_t> thread_vmx_related; // 0x14
|
|
uint8_t current_irql; // 0x18
|
|
uint8_t background_scheduling_active; // 0x19
|
|
uint8_t background_scheduling_1A; // 0x1A
|
|
uint8_t background_scheduling_1B; // 0x1B
|
|
xe::be<uint32_t> timer_related; // 0x1C
|
|
uint8_t unk_20[0x10]; // 0x20
|
|
xe::be<uint64_t> pcr_ptr; // 0x30
|
|
|
|
// this seems to be just garbage data? we can stash a pointer to context here
|
|
// as a hack for now
|
|
union {
|
|
uint8_t unk_38[8]; // 0x38
|
|
uint64_t host_stash; // 0x38
|
|
};
|
|
uint8_t unk_40[28]; // 0x40
|
|
xe::be<uint32_t> unk_stack_5c; // 0x5C
|
|
uint8_t unk_60[12]; // 0x60
|
|
xe::be<uint32_t> use_alternative_stack; // 0x6C
|
|
xe::be<uint32_t> stack_base_ptr; // 0x70 Stack base address (high addr)
|
|
xe::be<uint32_t> stack_end_ptr; // 0x74 Stack end (low addr)
|
|
|
|
// maybe these are the stacks used in apcs?
|
|
// i know they're stacks, RtlGetStackLimits returns them if another var here
|
|
// is set
|
|
|
|
xe::be<uint32_t> alt_stack_base_ptr; // 0x78
|
|
xe::be<uint32_t> alt_stack_end_ptr; // 0x7C
|
|
// if bit 1 is set in a handler pointer, it actually points to a KINTERRUPT
|
|
// otherwise, it points to a function to execute
|
|
xe::be<uint32_t> interrupt_handlers[32]; // 0x80
|
|
X_KPRCB prcb_data; // 0x100
|
|
// pointer to KPCRB?
|
|
TypedGuestPointer<X_KPRCB> prcb; // 0x2A8
|
|
uint8_t unk_2AC[0x2C]; // 0x2AC
|
|
};
|
|
|
|
struct X_KMUTANT {
|
|
X_DISPATCH_HEADER header; // 0x0
|
|
X_LIST_ENTRY unk_list; // 0x10
|
|
TypedGuestPointer<X_KTHREAD> owner; // 0x18
|
|
bool abandoned; // 0x1C
|
|
// these might just be padding
|
|
uint8_t unk_1D; // 0x1D
|
|
uint8_t unk_1E; // 0x1E
|
|
uint8_t unk_1F; // 0x1F
|
|
};
|
|
static_assert_size(X_KMUTANT, 0x20);
|
|
|
|
enum X_KWAIT_REASON : uint16_t {
|
|
WaitAll = 0,
|
|
WaitAny = 1,
|
|
WaitUnk3 = 3,
|
|
};
|
|
|
|
// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/ntos/ke_x/kwait_block.htm
|
|
// pretty much the vista KWAIT_BLOCK verbatim, except that sparebyte is gone
|
|
// and WaitType is 2 bytes instead of 1
|
|
struct X_KWAIT_BLOCK {
|
|
X_LIST_ENTRY wait_list_entry; // 0x0
|
|
TypedGuestPointer<X_KTHREAD> thread;
|
|
TypedGuestPointer<X_DISPATCH_HEADER> object;
|
|
TypedGuestPointer<X_KWAIT_BLOCK> next_wait_block;
|
|
// this isnt the official vista name, but i think its better.
|
|
// this value is what will be returned to the waiter if this particular wait
|
|
// is satisfied
|
|
xe::be<uint16_t> wait_result_xstatus;
|
|
// WAIT_ALL or WAIT_ANY
|
|
xe::be<X_KWAIT_REASON> wait_type;
|
|
};
|
|
|
|
static_assert_size(X_KWAIT_BLOCK, 0x18);
|
|
|
|
struct X_KTIMER {
|
|
X_DISPATCH_HEADER header; // 0x0
|
|
xe::be<uint64_t> due_time; // 0x10
|
|
X_LIST_ENTRY table_bucket_entry; // 0x18
|
|
TypedGuestPointer<XDPC> dpc; // 0x20
|
|
xe::be<uint32_t> period; // 0x24
|
|
};
|
|
static_assert_size(X_KTIMER, 0x28);
|
|
|
|
struct X_KTHREAD {
|
|
X_DISPATCH_HEADER header; // 0x0
|
|
util::X_TYPED_LIST<X_KMUTANT, offsetof(X_KMUTANT, unk_list)>
|
|
mutants_list; // 0x10
|
|
X_KTIMER wait_timeout_timer; // 0x18
|
|
X_KWAIT_BLOCK wait_timeout_block; // 0x40
|
|
uint8_t unk_58[0x4]; // 0x58
|
|
xe::be<uint32_t> stack_base; // 0x5C
|
|
xe::be<uint32_t> stack_limit; // 0x60
|
|
xe::be<uint32_t> stack_kernel; // 0x64
|
|
xe::be<uint32_t> tls_address; // 0x68
|
|
// state = is thread running, suspended, etc
|
|
uint8_t thread_state; // 0x6C
|
|
// 0x70 = priority?
|
|
uint8_t alerted[2]; // 0x6D
|
|
uint8_t alertable; // 0x6F
|
|
uint8_t priority; // 0x70
|
|
uint8_t fpu_exceptions_on; // 0x71
|
|
// these two process types both get set to the same thing, process_type is
|
|
// referenced most frequently, however process_type_dup gets referenced a few
|
|
// times while the process is being created
|
|
uint8_t process_type_dup;
|
|
uint8_t process_type;
|
|
// apc_mode determines which list an apc goes into
|
|
util::X_TYPED_LIST<XAPC, offsetof(XAPC, list_entry)> apc_lists[2];
|
|
TypedGuestPointer<X_KPROCESS> process; // 0x84
|
|
uint8_t executing_kernel_apc; // 0x88
|
|
// when context switch happens, this is copied into
|
|
// apc_software_interrupt_state for kpcr
|
|
uint8_t deferred_apc_software_interrupt_state; // 0x89
|
|
uint8_t user_apc_pending; // 0x8A
|
|
uint8_t may_queue_apcs; // 0x8B
|
|
X_KSPINLOCK apc_lock; // 0x8C
|
|
xe::be<uint32_t> num_context_switches_to; // 0x90
|
|
X_LIST_ENTRY ready_prcb_entry; // 0x94
|
|
xe::be<uint32_t> msr_mask; // 0x9C
|
|
xe::be<X_STATUS> wait_result; // 0xA0
|
|
uint8_t wait_irql; // 0xA4
|
|
uint8_t processor_mode; // 0xA5
|
|
uint8_t wait_next; // 0xA6
|
|
uint8_t wait_reason; // 0xA7
|
|
TypedGuestPointer<X_KWAIT_BLOCK> wait_blocks; // 0xA8
|
|
uint8_t unk_AC[4]; // 0xAC
|
|
int32_t apc_disable_count; // 0xB0
|
|
xe::be<int32_t> quantum; // 0xB4
|
|
uint8_t saturation_increment; // 0xB8
|
|
uint8_t base_priority; // 0xB9
|
|
uint8_t priority_decrement; // 0xBA
|
|
uint8_t boost_disabled; // 0xBB
|
|
uint8_t suspend_count; // 0xBC
|
|
uint8_t was_preempted; // 0xBD
|
|
uint8_t terminated; // 0xBE
|
|
uint8_t current_cpu; // 0xBF
|
|
// these two pointers point to KPRCBs, but seem to be rarely referenced, if at
|
|
// all
|
|
TypedGuestPointer<X_KPRCB> a_prcb_ptr; // 0xC0
|
|
TypedGuestPointer<X_KPRCB> another_prcb_ptr; // 0xC4
|
|
uint8_t process_priority_class; // 0xC8
|
|
uint8_t base_priority_copy; // 0xC9
|
|
uint8_t max_dynamic_priority; // 0xCA
|
|
uint8_t unk_CB; // 0xCB
|
|
X_KSPINLOCK timer_list_lock; // 0xCC
|
|
xe::be<uint32_t> stack_alloc_base; // 0xD0
|
|
XAPC on_suspend; // 0xD4
|
|
X_KSEMAPHORE suspend_sema; // 0xFC
|
|
// this is an entry in
|
|
X_LIST_ENTRY process_threads; // 0x110
|
|
xe::be<uint32_t> unk_118; // 0x118
|
|
X_LIST_ENTRY queue_related; // 0x11C
|
|
xe::be<uint32_t> unk_124; // 0x124
|
|
xe::be<uint32_t> unk_128; // 0x128
|
|
xe::be<uint32_t> unk_12C; // 0x12C
|
|
xe::be<uint64_t> create_time; // 0x130
|
|
xe::be<uint64_t> exit_time; // 0x138
|
|
xe::be<uint32_t> exit_status; // 0x140
|
|
// tracks all pending timers that have apcs which target this thread
|
|
X_LIST_ENTRY timer_list; // 0x144
|
|
xe::be<uint32_t> thread_id; // 0x14C
|
|
xe::be<uint32_t> start_address; // 0x150
|
|
X_LIST_ENTRY unk_154; // 0x154
|
|
uint8_t unk_15C[0x4]; // 0x15C
|
|
xe::be<uint32_t> last_error; // 0x160
|
|
xe::be<uint32_t> fiber_ptr; // 0x164
|
|
uint8_t unk_168[0x4]; // 0x168
|
|
xe::be<uint32_t> creation_flags; // 0x16C
|
|
|
|
// we handle context differently from a native kernel, so we can stash extra
|
|
// data here! the first 8 bytes of vscr are unused anyway
|
|
union {
|
|
vec128_t vscr; // 0x170
|
|
struct {
|
|
void* host_xthread_stash;
|
|
uintptr_t vscr_remainder;
|
|
};
|
|
};
|
|
|
|
union {
|
|
// 2048 bytes
|
|
vec128_t vmx_context[128]; // 0x180
|
|
struct {
|
|
// 1536 bytes
|
|
X_KWAIT_BLOCK scratch_waitblock_memory[65];
|
|
// space for some more data!
|
|
uint32_t kernel_aux_stack_base_;
|
|
uint32_t kernel_aux_stack_current_;
|
|
uint32_t kernel_aux_stack_limit_;
|
|
};
|
|
};
|
|
xe::be<double> fpscr; // 0x980
|
|
xe::be<double> fpu_context[32]; // 0x988
|
|
|
|
XAPC unk_A88; // 0xA88
|
|
};
|
|
static_assert_size(X_KTHREAD, 0xAB0);
|
|
|
|
#if !XE_PLATFORM_WIN32
|
|
// Exception thrown by XThread::Reenter() to unwind through JIT frames.
|
|
// C++ exception unwinding uses DWARF .eh_frame info registered for JIT code,
|
|
// ensuring destructors and RAII guards in host C++ frames are properly called.
|
|
struct FiberReentryException {
|
|
uint32_t address;
|
|
};
|
|
#endif
|
|
|
|
class XThread : public XObject, public cpu::Thread {
|
|
public:
|
|
static const XObject::Type kObjectType = XObject::Type::Thread;
|
|
|
|
static constexpr uint32_t kStackAddressRangeBegin = 0x70000000;
|
|
static constexpr uint32_t kStackAddressRangeEnd = 0x7F000000;
|
|
|
|
static constexpr uint32_t kThreadKernelStackSize = 0xF0;
|
|
|
|
struct CreationParams {
|
|
uint32_t stack_size;
|
|
uint32_t xapi_thread_startup;
|
|
uint32_t start_address;
|
|
uint32_t start_context;
|
|
uint32_t creation_flags;
|
|
uint32_t guest_process;
|
|
};
|
|
|
|
XThread(KernelState* kernel_state);
|
|
XThread(KernelState* kernel_state, uint32_t stack_size,
|
|
uint32_t xapi_thread_startup, uint32_t start_address,
|
|
uint32_t start_context, uint32_t creation_flags, bool guest_thread,
|
|
bool main_thread = false, uint32_t guest_process = 0);
|
|
~XThread() override;
|
|
|
|
static bool IsInThread(XThread* other);
|
|
static bool IsInThread();
|
|
static XThread* GetCurrentThread();
|
|
// Null-safe variant of GetCurrentThread: returns nullptr (instead of
|
|
// asserting) when the calling OS thread is not a guest XThread. Used by
|
|
// additive instrumentation that can fire from boot code before any XThread
|
|
// exists. Read-only accessor; no behaviour change.
|
|
static XThread* TryGetCurrentThread();
|
|
static uint32_t GetCurrentThreadHandle();
|
|
static uint32_t GetCurrentThreadId();
|
|
|
|
static uint32_t GetLastError();
|
|
static void SetLastError(uint32_t error_code);
|
|
|
|
const CreationParams* creation_params() const { return &creation_params_; }
|
|
uint32_t tls_ptr() const { return tls_static_address_; }
|
|
uint32_t pcr_ptr() const { return pcr_address_; }
|
|
uint32_t stack_base() const { return stack_base_; }
|
|
uint32_t stack_limit() const { return stack_limit_; }
|
|
// True if the thread is created by the guest app.
|
|
bool is_guest_thread() const { return guest_thread_; }
|
|
bool main_thread() const { return main_thread_; }
|
|
bool is_running() const { return running_; }
|
|
|
|
uint32_t thread_id() const { return thread_id_; }
|
|
uint32_t last_error();
|
|
void set_last_error(uint32_t error_code);
|
|
void set_name(const std::string_view name);
|
|
|
|
X_STATUS Create();
|
|
X_STATUS Exit(int exit_code);
|
|
X_STATUS Terminate(int exit_code);
|
|
|
|
virtual void Execute();
|
|
|
|
virtual void Reenter(uint32_t address);
|
|
|
|
void EnterCriticalRegion();
|
|
void LeaveCriticalRegion();
|
|
|
|
void EnqueueApc(uint32_t normal_routine, uint32_t normal_context,
|
|
uint32_t arg1, uint32_t arg2);
|
|
|
|
int32_t priority() const { return priority_; }
|
|
int32_t QueryPriority();
|
|
void SetPriority(int32_t increment);
|
|
|
|
// Called periodically (~20ms) by KernelState's timestamp timer to simulate
|
|
// the Xenon scheduler's quantum-based priority decay for non-real-time
|
|
// threads (base_priority < 18). Threads that run for longer than one
|
|
// quantum (~20ms) have their effective priority decayed toward the base,
|
|
// which causes them to drop into lower host priority buckets and prevents
|
|
// starvation. On the first decay step the accumulated priority boost is
|
|
// also drained.
|
|
void CheckQuantumAndDecay();
|
|
// Called when a thread wakes from a kernel wait. Applies a priority
|
|
// boost of |increment| above base_priority (matching the Xenon kernel's
|
|
// unwait-boost behavior) and restarts the quantum timer. The boost is
|
|
// drained on the next quantum expiry via CheckQuantumAndDecay().
|
|
// If increment is 0 or the thread has boost disabled, the priority is
|
|
// simply restored to base_priority.
|
|
void BoostOnWake(int32_t increment);
|
|
|
|
// Xbox thread IDs:
|
|
// 0 - core 0, thread 0 - user
|
|
// 1 - core 0, thread 1 - user
|
|
// 2 - core 1, thread 0 - sometimes xcontent
|
|
// 3 - core 1, thread 1 - user
|
|
// 4 - core 2, thread 0 - xaudio
|
|
// 5 - core 2, thread 1 - user
|
|
void SetAffinity(uint32_t affinity);
|
|
uint8_t active_cpu() const;
|
|
void SetActiveCpu(uint8_t cpu_index);
|
|
|
|
bool GetTLSValue(uint32_t slot, uint32_t* value_out);
|
|
bool SetTLSValue(uint32_t slot, uint32_t value);
|
|
|
|
uint32_t suspend_count();
|
|
X_FILETIME creation_time();
|
|
uint32_t start_address();
|
|
|
|
X_STATUS Resume(uint32_t* out_suspend_count = nullptr);
|
|
X_STATUS Suspend(uint32_t* out_suspend_count = nullptr);
|
|
X_STATUS Delay(uint32_t processor_mode, uint32_t alertable,
|
|
uint64_t interval);
|
|
|
|
#if !XE_PLATFORM_WIN32
|
|
// Performs self-suspension: increments suspend_count and blocks until
|
|
// another thread calls Resume() and suspend_count reaches 0.
|
|
// Returns the previous suspend_count value.
|
|
uint32_t SelfSuspend();
|
|
#endif
|
|
|
|
xe::threading::Thread* thread() { return thread_.get(); }
|
|
|
|
virtual bool Save(ByteStream* stream) override;
|
|
static object_ref<XThread> Restore(KernelState* kernel_state,
|
|
ByteStream* stream);
|
|
|
|
// Internal - do not use.
|
|
void AcquireMutantOnStartup(object_ref<XMutant> mutant) {
|
|
pending_mutant_acquires_.push_back(mutant);
|
|
}
|
|
void SetCurrentThread();
|
|
|
|
protected:
|
|
bool AllocateStack(uint32_t size);
|
|
void FreeStack();
|
|
void InitializeGuestObject();
|
|
|
|
void DeliverAPCs();
|
|
void RundownAPCs();
|
|
|
|
xe::threading::WaitHandle* GetWaitHandle() override { return thread_.get(); }
|
|
|
|
CreationParams creation_params_ = {0};
|
|
|
|
std::vector<object_ref<XMutant>> pending_mutant_acquires_;
|
|
|
|
uint32_t thread_id_ = 0;
|
|
uint32_t tls_static_address_ = 0;
|
|
uint32_t tls_dynamic_address_ = 0;
|
|
uint32_t tls_total_size_ = 0;
|
|
uint32_t pcr_address_ = 0;
|
|
uint32_t stack_alloc_base_ = 0; // Stack alloc base
|
|
uint32_t stack_alloc_size_ = 0; // Stack alloc size
|
|
uint32_t stack_base_ = 0; // High address
|
|
uint32_t stack_limit_ = 0; // Low address
|
|
bool guest_thread_ = false;
|
|
bool main_thread_ = false; // Entry-point thread
|
|
bool running_ = false;
|
|
|
|
int32_t priority_ = 0; // current effective priority (may be decayed)
|
|
int32_t base_priority_ = 0; // priority floor — decay never goes below this
|
|
int32_t boost_amount_ = 0; // accumulated priority boost above base
|
|
uint64_t quantum_start_ms_ = 0; // host uptime (ms) when quantum last reset
|
|
|
|
#if !XE_PLATFORM_WIN32
|
|
// Condition variable for thread self-suspension.
|
|
std::mutex suspend_mutex_;
|
|
std::condition_variable suspend_cv_;
|
|
#endif
|
|
|
|
// Reentry mechanism for fiber-based stack switching.
|
|
// On Linux, C++ exceptions are used instead of setjmp/longjmp so that
|
|
// destructors and RAII guards in host C++ frames are properly unwound.
|
|
// JIT code has DWARF .eh_frame unwind info registered via __register_frame.
|
|
#if XE_PLATFORM_WIN32
|
|
std::jmp_buf reentry_jmp_buf_;
|
|
uint32_t reentry_address_ = 0;
|
|
#endif
|
|
|
|
std::mutex thread_lock_;
|
|
};
|
|
|
|
class XHostThread : public XThread {
|
|
public:
|
|
XHostThread(KernelState* kernel_state, uint32_t stack_size,
|
|
uint32_t creation_flags, std::function<int()> host_fn,
|
|
uint32_t guest_process = 0);
|
|
|
|
virtual void Execute();
|
|
|
|
private:
|
|
std::function<int()> host_fn_;
|
|
};
|
|
|
|
} // namespace kernel
|
|
} // namespace xe
|
|
|
|
#endif // XENIA_KERNEL_XTHREAD_H_
|