orbis: implement initial guest signals support

This commit is contained in:
DH 2025-09-21 08:46:28 +03:00
parent 36b9e969c2
commit 70fa577a7b
12 changed files with 239 additions and 51 deletions

View file

@ -73,6 +73,7 @@ struct ProcessOps {
SysResult (*thr_kill2)(Thread *thread, pid_t pid, slong id, sint sig);
SysResult (*thr_suspend)(Thread *thread, ptr<const timespec> timeout);
SysResult (*thr_wake)(Thread *thread, slong id);
SysResult (*sigreturn)(Thread *thread, ptr<UContext> context);
SysResult (*thr_set_name)(Thread *thread, slong id, ptr<const char> name);
SysResult (*unmount)(Thread *thread, ptr<char> path, sint flags);

View file

@ -41,6 +41,8 @@ struct Thread {
kvector<SigInfo> queuedSignals;
shared_atomic32 suspendFlags{0};
utils::shared_atomic32 interruptedMtx{ 0 };
std::int64_t hostTid = -1;
lwpid_t tid = -1;
unsigned unblocked = 0;
@ -64,13 +66,14 @@ struct Thread {
// Print backtrace
void where();
void unblock();
void block();
bool unblock();
bool block();
void suspend();
void resume();
void sendSignal(int signo);
void notifyUnblockedSignal(int signo);
void setSigMask(SigSet newSigMask);
// FIXME: implement thread destruction
void incRef() {}

View file

@ -162,4 +162,10 @@ struct SigInfo {
} spare;
} reason;
};
struct SigFrame {
uint64_t handler;
UContext context;
SigInfo info;
};
} // namespace orbis

View file

@ -23,7 +23,7 @@ static constexpr auto kRelaxSpinCount = 12;
static constexpr auto kSpinCount = 16;
inline namespace utils {
inline thread_local void (*g_scopedUnblock)(bool) = nullptr;
inline thread_local bool (*g_scopedUnblock)(bool) = nullptr;
bool try_spin_wait(auto &&pred) {
for (std::size_t i = 0; i < kSpinCount; ++i) {

View file

@ -11,6 +11,7 @@
#include <sys/mman.h>
#include <thread>
#include <unistd.h>
#include <utility>
static const std::uint64_t g_allocProtWord = 0xDEADBEAFBADCAFE1;
static constexpr std::uintptr_t kHeapBaseAddress = 0x00000600'0000'0000;
@ -318,9 +319,31 @@ void Thread::suspend() { sendSignal(-1); }
void Thread::resume() { sendSignal(-2); }
void Thread::sendSignal(int signo) {
if (signo >= 0) {
if (!sigMask.test(signo)) {
return;
}
}
if (pthread_sigqueue(getNativeHandle(), SIGUSR1, {.sival_int = signo})) {
perror("pthread_sigqueue");
}
// TODO: suspend uses another delivery confirmation
if (signo != -1) {
interruptedMtx.store(1, std::memory_order::release);
while (interruptedMtx.wait(1, std::chrono::microseconds(1000)) !=
std::errc{}) {
if (interruptedMtx.load() == 0) {
return;
}
if (pthread_sigqueue(getNativeHandle(), SIGUSR1, {.sival_int = -2})) {
perror("pthread_sigqueue");
}
}
}
}
void Thread::notifyUnblockedSignal(int signo) {
@ -335,19 +358,55 @@ void Thread::notifyUnblockedSignal(int signo) {
}
}
void Thread::setSigMask(SigSet newSigMask) {
newSigMask.clear(kSigKill);
newSigMask.clear(kSigStop);
auto oldSigMask = std::exchange(sigMask, newSigMask);
for (std::size_t word = 0; word < std::size(newSigMask.bits); ++word) {
auto unblockedBits = ~oldSigMask.bits[word] & newSigMask.bits[word];
std::uint32_t offset = word * 32 + 1;
for (std::uint32_t i = std::countr_zero(unblockedBits); i < 32;
i += std::countr_zero(unblockedBits >> (i + 1)) + 1) {
notifyUnblockedSignal(offset + i);
}
}
}
void Thread::where() { tproc->ops->where(this); }
void Thread::unblock() { tproc->ops->unblock(this); }
void Thread::block() { tproc->ops->block(this); }
bool Thread::unblock() {
if (interruptedMtx.load(std::memory_order::relaxed) != 0) {
return false;
}
tproc->ops->unblock(this);
return true;
}
bool Thread::block() {
tproc->ops->block(this);
std::uint32_t prev = interruptedMtx.exchange(0, std::memory_order::relaxed);
if (prev != 0) {
interruptedMtx.notify_one();
}
return prev == 0;
}
scoped_unblock::scoped_unblock() {
if (g_currentThread && g_currentThread->context) {
g_scopedUnblock = [](bool unblock) {
if (unblock) {
g_currentThread->unblock();
} else {
g_currentThread->block();
return g_currentThread->unblock();
}
g_currentThread->block();
return true;
};
}
}

View file

@ -572,6 +572,7 @@ orbis::SysResult orbis::sys_osem_wait(Thread *thread, sint id, sint need,
std::lock_guard lock(sem->mtx);
bool timedout = false;
ErrorCode result{};
while (true) {
if (sem->isDeleted)
return ErrorCode::ACCES;
@ -591,7 +592,12 @@ orbis::SysResult orbis::sys_osem_wait(Thread *thread, sint id, sint need,
}
orbis::scoped_unblock unblock;
sem->cond.wait(sem->mtx, ut);
auto waitResult = sem->cond.wait(sem->mtx, ut);
if (waitResult == std::errc::interrupted) {
result = ErrorCode::INTR;
break;
}
}
if (pTimeout) {
@ -613,7 +619,7 @@ orbis::SysResult orbis::sys_osem_wait(Thread *thread, sint id, sint need,
if (timedout) {
return SysResult::notAnError(ErrorCode::TIMEDOUT);
}
return {};
return result;
}
orbis::SysResult orbis::sys_osem_trywait(Thread *thread, sint id, sint need) {
ORBIS_LOG_TRACE(__FUNCTION__, thread, id, need);

View file

@ -2,6 +2,7 @@
#include "sys/sysproto.hpp"
#include "thread/Process.hpp"
#include "thread/Thread.hpp"
#include "thread/ProcessOps.hpp"
#include "ucontext.hpp"
#include "utils/Logs.hpp"
#include <csignal>
@ -120,11 +121,11 @@ orbis::SysResult orbis::sys_kill(Thread *thread, sint pid, sint signum) {
hostPid = process->hostPid;
}
// TODO: wrap signal
// int result = ::kill(hostPid, signum);
// if (result < 0) {
// return static_cast<ErrorCode>(errno);
// }
// FIXME: invoke subscriber thread
int result = ::sigqueue(hostPid, SIGUSR1, {.sival_int = signum});
if (result < 0) {
return static_cast<ErrorCode>(errno);
}
return {};
}
@ -139,13 +140,11 @@ orbis::SysResult orbis::sys_sigqueue(Thread *thread, pid_t pid, sint signum,
orbis::SysResult orbis::sys_sigreturn(Thread *thread, ptr<UContext> sigcntxp) {
ORBIS_LOG_WARNING(__FUNCTION__, sigcntxp);
// auto sigRet = thread->sigReturns.front();
// thread->sigReturns.erase(thread->sigReturns.begin(),
// thread->sigReturns.begin() + 1); writeRegister(thread->context,
// RegisterId::rip, sigRet.rip); writeRegister(thread->context,
// RegisterId::rsp, sigRet.rsp); ORBIS_LOG_ERROR(__FUNCTION__, sigRet.rip,
// sigRet.rsp);
return {};
if (auto sigreturn = thread->tproc->ops->sigreturn) {
return sigreturn(thread, sigcntxp);
}
return ErrorCode::NOSYS;
}
orbis::SysResult orbis::nosys(Thread *thread) {

View file

@ -63,10 +63,6 @@ uint UmtxChain::notify_all(const UmtxKey &key) {
}
} // namespace orbis
static bool isSpuriousWakeup(orbis::ErrorCode errc) {
return errc == orbis::ErrorCode::AGAIN || errc == orbis::ErrorCode::INTR;
}
orbis::ErrorCode orbis::umtx_lock_umtx(Thread *thread, ptr<umtx> umtx, ulong id,
std::uint64_t ut) {
ORBIS_LOG_TODO(__FUNCTION__, thread->tid, umtx, id, ut);
@ -97,8 +93,7 @@ orbis::ErrorCode orbis::umtx_wait(Thread *thread, ptr<void> addr, ulong id,
while (true) {
orbis::scoped_unblock unblock;
result = orbis::toErrorCode(node->second.cv.wait(chain.mtx));
if ((result != ErrorCode{} && !isSpuriousWakeup(result)) ||
node->second.thr != thread)
if ((result != ErrorCode{}) || node->second.thr != thread)
break;
}
} else {
@ -117,7 +112,7 @@ orbis::ErrorCode orbis::umtx_wait(Thread *thread, ptr<void> addr, ulong id,
result = ErrorCode::TIMEDOUT;
break;
}
if (result != ErrorCode{} && !isSpuriousWakeup(result)) {
if (result != ErrorCode{}) {
break;
}
}
@ -193,7 +188,7 @@ static ErrorCode do_lock_normal(Thread *thread, ptr<umutex> m, uint flags,
if (mode == umutex_lock_mode::try_)
return ErrorCode::BUSY;
if (error != ErrorCode{} && !isSpuriousWakeup(error))
if (error != ErrorCode{})
return error;
auto node = chain.enqueue(key, thread);
@ -202,8 +197,8 @@ static ErrorCode do_lock_normal(Thread *thread, ptr<umutex> m, uint flags,
orbis::scoped_unblock unblock;
error = orbis::toErrorCode(node->second.cv.wait(chain.mtx, ut));
}
if (error == ErrorCode{} && !isSpuriousWakeup(error) &&
node->second.thr == thread && m->owner.load() != 0) {
if (error == ErrorCode{} && node->second.thr == thread &&
m->owner.load() != 0) {
error = ErrorCode::TIMEDOUT;
}
}
@ -358,12 +353,12 @@ orbis::ErrorCode orbis::umtx_cv_wait(Thread *thread, ptr<ucond> cv,
ErrorCode result = umtx_unlock_umutex(thread, m);
if (result == ErrorCode{}) {
orbis::scoped_unblock unblock;
if (ut + 1 == 0) {
while (true) {
orbis::scoped_unblock unblock;
result = orbis::toErrorCode(node->second.cv.wait(chain.mtx, ut));
if ((result != ErrorCode{} && !isSpuriousWakeup(result)) ||
node->second.thr != thread) {
if (result != ErrorCode{} || node->second.thr != thread) {
break;
}
}
@ -371,7 +366,6 @@ orbis::ErrorCode orbis::umtx_cv_wait(Thread *thread, ptr<ucond> cv,
auto start = std::chrono::steady_clock::now();
std::uint64_t udiff = 0;
while (true) {
orbis::scoped_unblock unblock;
result =
orbis::toErrorCode(node->second.cv.wait(chain.mtx, ut - udiff));
if (node->second.thr != thread) {
@ -385,7 +379,7 @@ orbis::ErrorCode orbis::umtx_cv_wait(Thread *thread, ptr<ucond> cv,
break;
}
if (result != ErrorCode{} && !isSpuriousWakeup(result)) {
if (result != ErrorCode{}) {
break;
}
}
@ -470,8 +464,7 @@ orbis::ErrorCode orbis::umtx_rw_rdlock(Thread *thread, ptr<urwlock> rwlock,
while (true) {
orbis::scoped_unblock unblock;
result = orbis::toErrorCode(node->second.cv.wait(chain.mtx, ut));
if ((result != ErrorCode{} && !isSpuriousWakeup(result)) ||
node->second.thr != thread) {
if (result != ErrorCode{} || node->second.thr != thread) {
break;
}
}
@ -492,7 +485,7 @@ orbis::ErrorCode orbis::umtx_rw_rdlock(Thread *thread, ptr<urwlock> rwlock,
break;
}
if (result != ErrorCode{} && !isSpuriousWakeup(result)) {
if (result != ErrorCode{}) {
break;
}
}
@ -574,8 +567,7 @@ orbis::ErrorCode orbis::umtx_rw_wrlock(Thread *thread, ptr<urwlock> rwlock,
while (true) {
orbis::scoped_unblock unblock;
error = orbis::toErrorCode(node->second.cv.wait(chain.mtx, ut));
if ((error != ErrorCode{} && !isSpuriousWakeup(error)) ||
node->second.thr != thread) {
if ((error != ErrorCode{}) || node->second.thr != thread) {
break;
}
}
@ -595,7 +587,7 @@ orbis::ErrorCode orbis::umtx_rw_wrlock(Thread *thread, ptr<urwlock> rwlock,
error = ErrorCode::TIMEDOUT;
break;
}
if (error != ErrorCode{} && !isSpuriousWakeup(error)) {
if (error != ErrorCode{}) {
break;
}
}
@ -757,8 +749,7 @@ orbis::ErrorCode orbis::umtx_sem_wait(Thread *thread, ptr<usem> sem,
while (true) {
orbis::scoped_unblock unblock;
result = orbis::toErrorCode(node->second.cv.wait(chain.mtx, ut));
if ((result != ErrorCode{} && !isSpuriousWakeup(result)) ||
node->second.thr != thread)
if ((result != ErrorCode{}) || node->second.thr != thread)
break;
}
} else {
@ -777,7 +768,7 @@ orbis::ErrorCode orbis::umtx_sem_wait(Thread *thread, ptr<usem> sem,
result = ErrorCode::TIMEDOUT;
break;
}
if (result != ErrorCode{} && !isSpuriousWakeup(result)) {
if (result != ErrorCode{}) {
break;
}
}

View file

@ -19,18 +19,28 @@ std::errc shared_atomic32::wait_impl(std::uint32_t oldValue,
g_scopedUnblock != nullptr;
if (unblock) {
g_scopedUnblock(true);
if (!g_scopedUnblock(true)) {
return std::errc::interrupted;
}
}
int result = syscall(SYS_futex, this, FUTEX_WAIT, oldValue,
useTimeout ? &timeout : nullptr);
auto errorCode = result < 0 ? static_cast<std::errc>(errno) : std::errc{};
if (unblock) {
g_scopedUnblock(false);
if (!g_scopedUnblock(false)) {
if (result < 0) {
return std::errc::interrupted;
}
return {};
}
}
if (result < 0) {
return static_cast<std::errc>(errno);
return errorCode;
}
return {};

View file

@ -650,6 +650,11 @@ SysResult thr_wake(orbis::Thread *thread, orbis::slong id) {
ORBIS_LOG_FATAL(__FUNCTION__, id);
return ErrorCode::NOTSUP;
}
SysResult sigreturn(orbis::Thread *thread,
orbis::ptr<orbis::UContext> context) {
rx::thread::setContext(thread, *context);
return{};
}
SysResult thr_set_name(orbis::Thread *thread, orbis::slong id,
orbis::ptr<const char> name) {
ORBIS_LOG_WARNING(__FUNCTION__, name, id, thread->tid);
@ -1023,6 +1028,7 @@ ProcessOps rx::procOpsTable = {
.thr_kill2 = thr_kill2,
.thr_suspend = thr_suspend,
.thr_wake = thr_wake,
.sigreturn = sigreturn,
.thr_set_name = thr_set_name,
.unmount = unmount,
.nmount = nmount,

View file

@ -4,6 +4,7 @@
#include "orbis/thread/Process.hpp"
#include "orbis/thread/Thread.hpp"
#include "orbis/utils/Logs.hpp"
#include "rx/debug.hpp"
#include "rx/mem.hpp"
#include <asm/prctl.h>
#include <csignal>
@ -125,6 +126,53 @@ handleSigUser(int sig, siginfo_t *info, void *ucontext) {
}
// ORBIS_LOG_ERROR("thread wake", thread->tid);
} else if (guestSignal >= 0) {
ORBIS_LOG_WARNING(__FUNCTION__, "handled signal", guestSignal, inGuestCode,
::getpid(), thread->tid);
auto it = thread->tproc->sigActions.find(guestSignal);
if (it != thread->tproc->sigActions.end()) {
auto guestContext = reinterpret_cast<ucontext_t *>(
inGuestCode ? context : thread->context);
auto sigact = it->second;
thread->setSigMask(sigact.mask);
auto handlerPtr = reinterpret_cast<std::uintptr_t>(sigact.handler);
auto rsp = guestContext->uc_mcontext.gregs[REG_RSP];
ORBIS_LOG_WARNING(__FUNCTION__, "invoking signal handler", guestSignal,
rsp, thread->stackStart, thread->stackEnd);
// FIXME: alt stack?
rsp -= 128; // redzone
rsp -= sizeof(orbis::SigFrame);
// FIXME handle flags
auto &sigFrame = *std::bit_cast<orbis::SigFrame *>(rsp);
sigFrame = {};
rx::thread::copyContext(thread, sigFrame.context, *guestContext);
sigFrame.info.signo = guestSignal;
sigFrame.handler = handlerPtr;
guestContext->uc_mcontext.gregs[REG_RDI] = guestSignal; // arg1, signo
guestContext->uc_mcontext.gregs[REG_RSI] =
std::bit_cast<std::uintptr_t>(&sigFrame.info); // arg2, siginfo
guestContext->uc_mcontext.gregs[REG_RDX] =
std::bit_cast<std::uintptr_t>(&sigFrame.context); // arg3, ucontext
guestContext->uc_mcontext.gregs[REG_RCX] = 0; // arg4, si_addr
guestContext->uc_mcontext.gregs[REG_RIP] = handlerPtr;
guestContext->uc_mcontext.gregs[REG_RSP] = rx::alignDown(rsp, 16);
}
}
if (inGuestCode && guestSignal != -1) {
std::uint32_t prevValue = 1;
if (thread->interruptedMtx.compare_exchange_strong(prevValue, 0)) {
thread->interruptedMtx.notify_one();
}
}
if (inGuestCode) {
@ -187,11 +235,69 @@ void rx::thread::copyContext(orbis::Thread *thread, orbis::UContext &dst,
dst = {};
dst.stack.sp = thread->stackStart;
dst.stack.size = (char *)thread->stackEnd - (char *)thread->stackStart;
dst.stack.align = 0x10000;
dst.stack.align = 16;
dst.sigmask = thread->sigMask;
copyContext(dst.mcontext, src.uc_mcontext);
}
void rx::thread::setContext(orbis::Thread *thread, const orbis::UContext &src) {
auto &context = *std::bit_cast<ucontext_t *>(thread->context);
thread->stackStart = src.stack.sp;
thread->stackEnd = (char *)thread->stackStart + src.stack.size;
thread->setSigMask(src.sigmask);
// dst.onstack = src.gregs[REG_ONSTACK];
context.uc_mcontext.gregs[REG_RDI] = src.mcontext.rdi;
context.uc_mcontext.gregs[REG_RSI] = src.mcontext.rsi;
context.uc_mcontext.gregs[REG_RDX] = src.mcontext.rdx;
context.uc_mcontext.gregs[REG_RCX] = src.mcontext.rcx;
context.uc_mcontext.gregs[REG_R8] = src.mcontext.r8;
context.uc_mcontext.gregs[REG_R9] = src.mcontext.r9;
context.uc_mcontext.gregs[REG_RAX] = src.mcontext.rax;
context.uc_mcontext.gregs[REG_RBX] = src.mcontext.rbx;
context.uc_mcontext.gregs[REG_RBP] = src.mcontext.rbp;
context.uc_mcontext.gregs[REG_R10] = src.mcontext.r10;
context.uc_mcontext.gregs[REG_R11] = src.mcontext.r11;
context.uc_mcontext.gregs[REG_R12] = src.mcontext.r12;
context.uc_mcontext.gregs[REG_R13] = src.mcontext.r13;
context.uc_mcontext.gregs[REG_R14] = src.mcontext.r14;
context.uc_mcontext.gregs[REG_R15] = src.mcontext.r15;
context.uc_mcontext.gregs[REG_TRAPNO] = src.mcontext.trapno;
// in perfect world:
// std::uint64_t csgsfs = 0;
// csgsfs |= src.mcontext.fs;
// csgsfs |= static_cast<std::uint64_t>(src.mcontext.gs) << 16;
// csgsfs |= static_cast<std::uint64_t>(src.mcontext.cs) << 32;
// context.uc_mcontext.gregs[REG_CSGSFS] = csgsfs;
context.uc_mcontext.gregs[REG_CSGSFS] &= ~0xff'ffull;
context.uc_mcontext.gregs[REG_CSGSFS] |= src.mcontext.fs;
// dst.addr = src.gregs[REG_ADDR];
// dst.flags = src.gregs[REG_FLAGS];
// dst.es = src.gregs[REG_ES];
// dst.ds = src.gregs[REG_DS];
context.uc_mcontext.gregs[REG_ERR] = src.mcontext.err;
context.uc_mcontext.gregs[REG_RIP] = src.mcontext.rip;
context.uc_mcontext.gregs[REG_EFL] = src.mcontext.rflags;
context.uc_mcontext.gregs[REG_RSP] = src.mcontext.rsp;
// dst.ss = src.gregs[REG_SS];
// dst.len = sizeof(orbis::MContext);
// dst.fpformat = src.gregs[REG_FPFORMAT];
// dst.ownedfp = src.gregs[REG_OWNEDFP];
// dst.lbrfrom = src.gregs[REG_LBRFROM];
// dst.lbrto = src.gregs[REG_LBRTO];
// dst.aux1 = src.gregs[REG_AUX1];
// dst.aux2 = src.gregs[REG_AUX2];
// dst.fpstate = src.gregs[REG_FPSTATE];
// dst.fsbase = src.gregs[REG_FSBASE];
// dst.gsbase = src.gregs[REG_GSBASE];
// dst.xfpustate = src.gregs[REG_XFPUSTATE];
// dst.xfpustate_len = src.gregs[REG_XFPUSTATE_LEN];
}
void rx::thread::initialize() {
struct sigaction act{};
act.sa_sigaction = handleSigSys;
@ -271,6 +377,6 @@ void rx::thread::invoke(orbis::Thread *thread) {
_writefsbase_u64(thread->fsBase);
auto context = reinterpret_cast<ucontext_t *>(thread->context);
setContext(context->uc_mcontext);
::setContext(context->uc_mcontext);
_writefsbase_u64(hostFs);
}

View file

@ -13,5 +13,6 @@ void setupThisThread();
void copyContext(orbis::MContext &dst, const mcontext_t &src);
void copyContext(orbis::Thread *thread, orbis::UContext &dst,
const ucontext_t &src);
void setContext(orbis::Thread *thread, const orbis::UContext &src);
void invoke(orbis::Thread *thread);
} // namespace rx::thread