1
0
Fork 0
mirror of https://github.com/LadybirdBrowser/ladybird.git synced 2025-06-10 18:10:56 +09:00

UserspaceEmulator: Add support for UNIX signals :^)

The emulator will now register signal handlers for all possible signals
and act as a translation layer between the kernel and the emulated
process.

To get an accurate simulation of signal handling, we duplicate the same
trampoline mechanism used by the kernel's signal delivery system, and
also use the "sigreturn" syscall to return from a signal handler.

Signal masking is not fully implemented yet, but this is pretty cool!
This commit is contained in:
Andreas Kling 2020-08-05 19:36:24 +02:00
parent ce95628b7f
commit 8dea25d974
Notes: sideshowbarker 2024-07-19 04:15:26 +09:00
3 changed files with 269 additions and 0 deletions

View file

@ -71,6 +71,8 @@ Emulator::Emulator(const Vector<String>& arguments, const Vector<String>& enviro
ASSERT(!s_the); ASSERT(!s_the);
s_the = this; s_the = this;
setup_stack(arguments, environment); setup_stack(arguments, environment);
register_signal_handlers();
setup_signal_trampoline();
} }
void Emulator::setup_stack(const Vector<String>& arguments, const Vector<String>& environment) void Emulator::setup_stack(const Vector<String>& arguments, const Vector<String>& environment)
@ -188,6 +190,9 @@ int Emulator::exec()
if (trace) if (trace)
m_cpu.dump(); m_cpu.dump();
if (m_pending_signals)
dispatch_one_pending_signal();
} }
if (auto* tracer = malloc_tracer()) if (auto* tracer = malloc_tracer())
@ -247,6 +252,12 @@ u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3)
switch (function) { switch (function) {
case SC_execve: case SC_execve:
return virt$execve(arg1); return virt$execve(arg1);
case SC_sleep:
return virt$sleep(arg1);
case SC_sigaction:
return virt$sigaction(arg1, arg2, arg3);
case SC_sigreturn:
return virt$sigreturn();
case SC_stat: case SC_stat:
return virt$stat(arg1); return virt$stat(arg1);
case SC_realpath: case SC_realpath:
@ -986,6 +997,193 @@ int Emulator::virt$gethostname(FlatPtr buffer, ssize_t buffer_size)
return rc; return rc;
} }
static void emulator_signal_handler(int signum)
{
Emulator::the().did_receive_signal(signum);
}
void Emulator::register_signal_handlers()
{
for (int signum = 0; signum < NSIG; ++signum)
signal(signum, emulator_signal_handler);
}
int Emulator::virt$sigaction(int signum, FlatPtr act, FlatPtr oldact)
{
if (signum == SIGKILL) {
dbg() << "Attempted to sigaction() with SIGKILL";
return -EINVAL;
}
if (signum <= 0 || signum >= NSIG)
return -EINVAL;
struct sigaction host_act;
mmu().copy_from_vm(&host_act, act, sizeof(host_act));
auto& handler = m_signal_handler[signum];
handler.handler = (FlatPtr)host_act.sa_handler;
handler.mask = host_act.sa_mask;
handler.flags = host_act.sa_flags;
if (oldact) {
struct sigaction host_oldact;
auto& old_handler = m_signal_handler[signum];
host_oldact.sa_handler = (void (*)(int))(old_handler.handler);
host_oldact.sa_mask = old_handler.mask;
host_oldact.sa_flags = old_handler.flags;
mmu().copy_to_vm(oldact, &host_oldact, sizeof(host_oldact));
}
return 0;
}
int Emulator::virt$sleep(unsigned seconds)
{
return syscall(SC_sleep, seconds);
}
int Emulator::virt$sigreturn()
{
u32 stack_ptr = m_cpu.esp().value();
auto local_pop = [&]() -> ValueWithShadow<u32> {
auto value = m_cpu.read_memory32({ m_cpu.ss(), stack_ptr });
stack_ptr += sizeof(u32);
return value;
};
auto smuggled_eax = local_pop();
stack_ptr += 4 * sizeof(u32);
m_signal_mask = local_pop().value();
m_cpu.set_edi(local_pop());
m_cpu.set_esi(local_pop());
m_cpu.set_ebp(local_pop());
m_cpu.set_esp(local_pop());
m_cpu.set_ebx(local_pop());
m_cpu.set_edx(local_pop());
m_cpu.set_ecx(local_pop());
m_cpu.set_eax(local_pop());
m_cpu.set_eip(local_pop().value());
m_cpu.set_eflags(local_pop());
// FIXME: We're losing shadow bits here.
return smuggled_eax.value();
}
enum class DefaultSignalAction {
Terminate,
Ignore,
DumpCore,
Stop,
Continue,
};
DefaultSignalAction default_signal_action(int signal)
{
ASSERT(signal && signal < NSIG);
switch (signal) {
case SIGHUP:
case SIGINT:
case SIGKILL:
case SIGPIPE:
case SIGALRM:
case SIGUSR1:
case SIGUSR2:
case SIGVTALRM:
case SIGSTKFLT:
case SIGIO:
case SIGPROF:
case SIGTERM:
case SIGPWR:
return DefaultSignalAction::Terminate;
case SIGCHLD:
case SIGURG:
case SIGWINCH:
return DefaultSignalAction::Ignore;
case SIGQUIT:
case SIGILL:
case SIGTRAP:
case SIGABRT:
case SIGBUS:
case SIGFPE:
case SIGSEGV:
case SIGXCPU:
case SIGXFSZ:
case SIGSYS:
return DefaultSignalAction::DumpCore;
case SIGCONT:
return DefaultSignalAction::Continue;
case SIGSTOP:
case SIGTSTP:
case SIGTTIN:
case SIGTTOU:
return DefaultSignalAction::Stop;
}
ASSERT_NOT_REACHED();
}
void Emulator::dispatch_one_pending_signal()
{
int signum = -1;
for (signum = 1; signum < NSIG; ++signum) {
int mask = 1 << signum;
if (m_pending_signals & mask)
break;
}
ASSERT(signum != -1);
m_pending_signals &= ~(1 << signum);
auto& handler = m_signal_handler[signum];
if (handler.handler == 0) {
// SIG_DFL
auto action = default_signal_action(signum);
if (action == DefaultSignalAction::Ignore)
return;
report("\n==%d== Got signal %d (%s), no handler registered\n", getpid(), signum, strsignal(signum));
m_shutdown = true;
return;
}
if (handler.handler == 1) {
// SIG_IGN
return;
}
report("\n==%d== Got signal %d (%s), handler at %p\n", getpid(), signum, strsignal(signum), handler.handler);
auto old_esp = m_cpu.esp();
u32 stack_alignment = (m_cpu.esp().value() - 56) % 16;
m_cpu.set_esp(shadow_wrap_as_initialized(m_cpu.esp().value() - stack_alignment));
m_cpu.push32(shadow_wrap_as_initialized(m_cpu.eflags()));
m_cpu.push32(shadow_wrap_as_initialized(m_cpu.eip()));
m_cpu.push32(m_cpu.eax());
m_cpu.push32(m_cpu.ecx());
m_cpu.push32(m_cpu.edx());
m_cpu.push32(m_cpu.ebx());
m_cpu.push32(old_esp);
m_cpu.push32(m_cpu.ebp());
m_cpu.push32(m_cpu.esi());
m_cpu.push32(m_cpu.edi());
// FIXME: Push old signal mask here.
m_cpu.push32(shadow_wrap_as_initialized(0u));
m_cpu.push32(shadow_wrap_as_initialized((u32)signum));
m_cpu.push32(shadow_wrap_as_initialized(handler.handler));
m_cpu.push32(shadow_wrap_as_initialized(0u));
ASSERT((m_cpu.esp().value() % 16) == 0);
m_cpu.set_eip(m_signal_trampoline);
}
void report(const char* format, ...) void report(const char* format, ...)
{ {
va_list ap; va_list ap;
@ -994,4 +1192,46 @@ void report(const char* format, ...)
va_end(ap); va_end(ap);
} }
void signal_trampoline_dummy(void)
{
// The trampoline preserves the current eax, pushes the signal code and
// then calls the signal handler. We do this because, when interrupting a
// blocking syscall, that syscall may return some special error code in eax;
// This error code would likely be overwritten by the signal handler, so it's
// neccessary to preserve it here.
asm(
".intel_syntax noprefix\n"
"asm_signal_trampoline:\n"
"push ebp\n"
"mov ebp, esp\n"
"push eax\n" // we have to store eax 'cause it might be the return value from a syscall
"sub esp, 4\n" // align the stack to 16 bytes
"mov eax, [ebp+12]\n" // push the signal code
"push eax\n"
"call [ebp+8]\n" // call the signal handler
"add esp, 8\n"
"mov eax, %P0\n"
"int 0x82\n" // sigreturn syscall
"asm_signal_trampoline_end:\n"
".att_syntax" ::"i"(Syscall::SC_sigreturn));
}
extern "C" void asm_signal_trampoline(void);
extern "C" void asm_signal_trampoline_end(void);
void Emulator::setup_signal_trampoline()
{
auto trampoline_region = make<SimpleRegion>(0xb0000000, 4096);
u8* trampoline = (u8*)asm_signal_trampoline;
u8* trampoline_end = (u8*)asm_signal_trampoline_end;
size_t trampoline_size = trampoline_end - trampoline;
u8* code_ptr = trampoline_region->data();
memcpy(code_ptr, trampoline, trampoline_size);
m_signal_trampoline = trampoline_region->base();
mmu().add_region(move(trampoline_region));
}
} }

View file

@ -33,6 +33,7 @@
#include <LibDebug/DebugInfo.h> #include <LibDebug/DebugInfo.h>
#include <LibELF/Loader.h> #include <LibELF/Loader.h>
#include <LibX86/Instruction.h> #include <LibX86/Instruction.h>
#include <signal.h>
#include <sys/types.h> #include <sys/types.h>
namespace UserspaceEmulator { namespace UserspaceEmulator {
@ -59,6 +60,8 @@ public:
bool is_in_malloc_or_free() const; bool is_in_malloc_or_free() const;
void did_receive_signal(int signum) { m_pending_signals |= (1 << signum); }
private: private:
NonnullRefPtr<ELF::Loader> m_elf; NonnullRefPtr<ELF::Loader> m_elf;
OwnPtr<DebugInfo> m_debug_info; OwnPtr<DebugInfo> m_debug_info;
@ -69,9 +72,13 @@ private:
OwnPtr<MallocTracer> m_malloc_tracer; OwnPtr<MallocTracer> m_malloc_tracer;
void setup_stack(const Vector<String>& arguments, const Vector<String>& environment); void setup_stack(const Vector<String>& arguments, const Vector<String>& environment);
void register_signal_handlers();
void setup_signal_trampoline();
int virt$fork(); int virt$fork();
int virt$execve(FlatPtr); int virt$execve(FlatPtr);
int virt$sigaction(int, FlatPtr, FlatPtr);
int virt$sigreturn();
int virt$get_dir_entries(int fd, FlatPtr buffer, ssize_t); int virt$get_dir_entries(int fd, FlatPtr buffer, ssize_t);
int virt$ioctl(int fd, unsigned, FlatPtr); int virt$ioctl(int fd, unsigned, FlatPtr);
int virt$stat(FlatPtr); int virt$stat(FlatPtr);
@ -128,9 +135,12 @@ private:
int virt$connect(int sockfd, FlatPtr address, socklen_t address_size); int virt$connect(int sockfd, FlatPtr address, socklen_t address_size);
void virt$exit(int); void virt$exit(int);
ssize_t virt$getrandom(FlatPtr buffer, size_t buffer_size, unsigned int flags); ssize_t virt$getrandom(FlatPtr buffer, size_t buffer_size, unsigned int flags);
int virt$sleep(unsigned);
FlatPtr allocate_vm(size_t size, size_t alignment); FlatPtr allocate_vm(size_t size, size_t alignment);
void dispatch_one_pending_signal();
bool m_shutdown { false }; bool m_shutdown { false };
int m_exit_status { 0 }; int m_exit_status { 0 };
@ -138,6 +148,18 @@ private:
FlatPtr m_malloc_symbol_end { 0 }; FlatPtr m_malloc_symbol_end { 0 };
FlatPtr m_free_symbol_start { 0 }; FlatPtr m_free_symbol_start { 0 };
FlatPtr m_free_symbol_end { 0 }; FlatPtr m_free_symbol_end { 0 };
sigset_t m_pending_signals { 0 };
sigset_t m_signal_mask { 0 };
struct SignalHandlerInfo {
FlatPtr handler { 0 };
sigset_t mask { 0 };
int flags { 0 };
};
SignalHandlerInfo m_signal_handler[NSIG];
FlatPtr m_signal_trampoline { 0 };
}; };
void report(const char*, ...); void report(const char*, ...);

View file

@ -241,6 +241,13 @@ public:
} }
} }
u32 eflags() const { return m_eflags; }
void set_eflags(ValueWithShadow<u32> eflags)
{
m_eflags = eflags.value();
m_flags_tainted = eflags.is_uninitialized();
}
ValueWithShadow<u32> eax() const { return const_gpr32(X86::RegisterEAX); } ValueWithShadow<u32> eax() const { return const_gpr32(X86::RegisterEAX); }
ValueWithShadow<u32> ebx() const { return const_gpr32(X86::RegisterEBX); } ValueWithShadow<u32> ebx() const { return const_gpr32(X86::RegisterEBX); }
ValueWithShadow<u32> ecx() const { return const_gpr32(X86::RegisterECX); } ValueWithShadow<u32> ecx() const { return const_gpr32(X86::RegisterECX); }