diff options
Diffstat (limited to 'Userland/DevTools/UserspaceEmulator/Emulator.cpp')
-rw-r--r-- | Userland/DevTools/UserspaceEmulator/Emulator.cpp | 1816 |
1 files changed, 1816 insertions, 0 deletions
diff --git a/Userland/DevTools/UserspaceEmulator/Emulator.cpp b/Userland/DevTools/UserspaceEmulator/Emulator.cpp new file mode 100644 index 0000000000..c463d24618 --- /dev/null +++ b/Userland/DevTools/UserspaceEmulator/Emulator.cpp @@ -0,0 +1,1816 @@ +/* + * Copyright (c) 2020, Andreas Kling <kling@serenityos.org> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "Emulator.h" +#include "MmapRegion.h" +#include "SharedBufferRegion.h" +#include "SimpleRegion.h" +#include "SoftCPU.h" +#include <AK/Format.h> +#include <AK/LexicalPath.h> +#include <AK/MappedFile.h> +#include <Kernel/API/Syscall.h> +#include <LibELF/AuxiliaryVector.h> +#include <LibELF/Image.h> +#include <LibELF/Validation.h> +#include <LibPthread/pthread.h> +#include <LibX86/ELFSymbolProvider.h> +#include <fcntl.h> +#include <net/if.h> +#include <net/route.h> +#include <sched.h> +#include <serenity.h> +#include <stdio.h> +#include <string.h> +#include <strings.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/stat.h> +#include <sys/time.h> +#include <sys/uio.h> +#include <termios.h> +#include <unistd.h> + +#if defined(__GNUC__) && !defined(__clang__) +# pragma GCC optimize("O3") +#endif + +// #define DEBUG_SPAM + +namespace UserspaceEmulator { + +static constexpr u32 stack_location = 0x10000000; +static constexpr size_t stack_size = 64 * KiB; + +static Emulator* s_the; + +Emulator& Emulator::the() +{ + ASSERT(s_the); + return *s_the; +} + +Emulator::Emulator(const String& executable_path, const Vector<String>& arguments, const Vector<String>& environment) + : m_executable_path(executable_path) + , m_arguments(arguments) + , m_environment(environment) + , m_mmu(*this) + , m_cpu(*this) +{ + m_malloc_tracer = make<MallocTracer>(*this); + ASSERT(!s_the); + s_the = this; + // setup_stack(arguments, environment); + register_signal_handlers(); + setup_signal_trampoline(); +} + +Vector<ELF::AuxiliaryValue> Emulator::generate_auxiliary_vector(FlatPtr load_base, FlatPtr entry_eip, String executable_path, int executable_fd) const +{ + // FIXME: This is not fully compatible with the auxiliary vector the kernel generates, this is just the bare + // minimum to get the loader going. + Vector<ELF::AuxiliaryValue> auxv; + // PHDR/EXECFD + // PH* + auxv.append({ ELF::AuxiliaryValue::PageSize, PAGE_SIZE }); + auxv.append({ ELF::AuxiliaryValue::BaseAddress, (void*)load_base }); + + auxv.append({ ELF::AuxiliaryValue::Entry, (void*)entry_eip }); + + // FIXME: Don't hard code this? We might support other platforms later.. (e.g. x86_64) + auxv.append({ ELF::AuxiliaryValue::Platform, "i386" }); + + auxv.append({ ELF::AuxiliaryValue::ExecFilename, executable_path }); + + auxv.append({ ELF::AuxiliaryValue::ExecFileDescriptor, executable_fd }); + + auxv.append({ ELF::AuxiliaryValue::Null, 0L }); + return auxv; +} + +void Emulator::setup_stack(Vector<ELF::AuxiliaryValue> aux_vector) +{ + auto stack_region = make<SimpleRegion>(stack_location, stack_size); + stack_region->set_stack(true); + m_mmu.add_region(move(stack_region)); + m_cpu.set_esp(shadow_wrap_as_initialized<u32>(stack_location + stack_size)); + + Vector<u32> argv_entries; + + for (auto& argument : m_arguments) { + m_cpu.push_string(argument.characters()); + argv_entries.append(m_cpu.esp().value()); + } + + Vector<u32> env_entries; + + for (auto& variable : m_environment) { + m_cpu.push_string(variable.characters()); + env_entries.append(m_cpu.esp().value()); + } + + for (auto& auxv : aux_vector) { + if (!auxv.optional_string.is_empty()) { + m_cpu.push_string(auxv.optional_string.characters()); + auxv.auxv.a_un.a_ptr = (void*)m_cpu.esp().value(); + } + } + + for (ssize_t i = aux_vector.size() - 1; i >= 0; --i) { + auto& value = aux_vector[i].auxv; + m_cpu.push_buffer((const u8*)&value, sizeof(value)); + } + + m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // char** envp = { envv_entries..., nullptr } + for (ssize_t i = env_entries.size() - 1; i >= 0; --i) + m_cpu.push32(shadow_wrap_as_initialized(env_entries[i])); + u32 envp = m_cpu.esp().value(); + + m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // char** argv = { argv_entries..., nullptr } + for (ssize_t i = argv_entries.size() - 1; i >= 0; --i) + m_cpu.push32(shadow_wrap_as_initialized(argv_entries[i])); + u32 argv = m_cpu.esp().value(); + + m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // (alignment) + + u32 argc = argv_entries.size(); + m_cpu.push32(shadow_wrap_as_initialized(envp)); + m_cpu.push32(shadow_wrap_as_initialized(argv)); + m_cpu.push32(shadow_wrap_as_initialized(argc)); + m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // (alignment) +} + +bool Emulator::load_elf() +{ + auto file_or_error = MappedFile::map(m_executable_path); + if (file_or_error.is_error()) { + reportln("Unable to map {}: {}", m_executable_path, file_or_error.error()); + return false; + } + + auto elf_image_data = file_or_error.value()->bytes(); + ELF::Image executable_elf(elf_image_data); + + if (!executable_elf.is_dynamic()) { + // FIXME: Support static objects + ASSERT_NOT_REACHED(); + } + + String interpreter_path; + if (!ELF::validate_program_headers(*(const Elf32_Ehdr*)elf_image_data.data(), elf_image_data.size(), (const u8*)elf_image_data.data(), elf_image_data.size(), &interpreter_path)) { + reportln("failed to validate ELF file"); + return false; + } + + ASSERT(!interpreter_path.is_null()); + dbgln("interpreter: {}", interpreter_path); + + auto interpreter_file_or_error = MappedFile::map(interpreter_path); + ASSERT(!interpreter_file_or_error.is_error()); + auto interpreter_image_data = interpreter_file_or_error.value()->bytes(); + ELF::Image interpreter_image(interpreter_image_data); + + constexpr FlatPtr interpreter_load_offset = 0x08000000; + interpreter_image.for_each_program_header([&](const ELF::Image::ProgramHeader& program_header) { + // Loader is not allowed to have its own TLS regions + ASSERT(program_header.type() != PT_TLS); + + if (program_header.type() == PT_LOAD) { + auto region = make<SimpleRegion>(program_header.vaddr().offset(interpreter_load_offset).get(), program_header.size_in_memory()); + if (program_header.is_executable() && !program_header.is_writable()) + region->set_text(true); + memcpy(region->data(), program_header.raw_data(), program_header.size_in_image()); + memset(region->shadow_data(), 0x01, program_header.size_in_memory()); + if (program_header.is_executable()) { + m_loader_text_base = region->base(); + m_loader_text_size = region->size(); + } + mmu().add_region(move(region)); + return IterationDecision::Continue; + } + + return IterationDecision::Continue; + }); + + auto entry_point = interpreter_image.entry().offset(interpreter_load_offset).get(); + m_cpu.set_eip(entry_point); + + // executable_fd will be used by the loader + int executable_fd = open(m_executable_path.characters(), O_RDONLY); + if (executable_fd < 0) + return false; + + auto aux_vector = generate_auxiliary_vector(interpreter_load_offset, entry_point, m_executable_path, executable_fd); + setup_stack(move(aux_vector)); + + return true; +} + +int Emulator::exec() +{ + // X86::ELFSymbolProvider symbol_provider(*m_elf); + X86::ELFSymbolProvider* symbol_provider = nullptr; + + bool trace = false; + + while (!m_shutdown) { + m_cpu.save_base_eip(); + + auto insn = X86::Instruction::from_stream(m_cpu, true, true); + + if (trace) + outln("{:p} \033[33;1m{}\033[0m", m_cpu.base_eip(), insn.to_string(m_cpu.base_eip(), symbol_provider)); + + (m_cpu.*insn.handler())(insn); + + if (trace) + m_cpu.dump(); + + if (m_pending_signals) + dispatch_one_pending_signal(); + } + + if (auto* tracer = malloc_tracer()) + tracer->dump_leak_report(); + + return m_exit_status; +} + +Vector<FlatPtr> Emulator::raw_backtrace() +{ + Vector<FlatPtr, 128> backtrace; + backtrace.append(m_cpu.base_eip()); + + // FIXME: Maybe do something if the backtrace has uninitialized data in the frame chain. + + u32 frame_ptr = m_cpu.ebp().value(); + while (frame_ptr) { + u32 ret_ptr = m_mmu.read32({ 0x23, frame_ptr + 4 }).value(); + if (!ret_ptr) + break; + backtrace.append(ret_ptr); + frame_ptr = m_mmu.read32({ 0x23, frame_ptr }).value(); + } + return backtrace; +} + +const MmapRegion* Emulator::find_text_region(FlatPtr address) +{ + const MmapRegion* matching_region = nullptr; + mmu().for_each_region([&](auto& region) { + if (!is<MmapRegion>(region)) + return IterationDecision::Continue; + const auto& mmap_region = static_cast<const MmapRegion&>(region); + if (!(mmap_region.is_executable() && address >= mmap_region.base() && address < mmap_region.base() + mmap_region.size())) + return IterationDecision::Continue; + matching_region = &mmap_region; + return IterationDecision::Break; + }); + return matching_region; +} + +String Emulator::create_backtrace_line(FlatPtr address) +{ + String minimal = String::format("=={%d}== %p", getpid(), (void*)address); + const auto* region = find_text_region(address); + if (!region) + return minimal; + auto separator_index = region->name().index_of(":"); + if (!separator_index.has_value()) + return minimal; + + String lib_name = region->name().substring(0, separator_index.value()); + String lib_path = lib_name; + if (region->name().contains(".so")) + lib_path = String::formatted("/usr/lib/{}", lib_path); + + if (!m_dynamic_library_cache.contains(lib_path)) { + auto file_or_error = MappedFile::map(lib_path); + if (file_or_error.is_error()) + return minimal; + + auto debug_info = make<Debug::DebugInfo>(make<ELF::Image>(file_or_error.value()->bytes())); + m_dynamic_library_cache.set(lib_path, CachedELF { file_or_error.release_value(), move(debug_info) }); + } + + auto it = m_dynamic_library_cache.find(lib_path); + auto& elf = it->value.debug_info->elf(); + String symbol = elf.symbolicate(address - region->base()); + + auto line_without_source_info = String::format("=={%d}== %p [%s]: %s", getpid(), (void*)address, lib_name.characters(), symbol.characters()); + + auto source_position = it->value.debug_info->get_source_position(address - region->base()); + if (source_position.has_value()) + return String::format("=={%d}== %p [%s]: %s (\033[34;1m%s\033[0m:%zu)", getpid(), (void*)address, lib_name.characters(), symbol.characters(), LexicalPath(source_position.value().file_path).basename().characters(), source_position.value().line_number); + + return line_without_source_info; +} + +void Emulator::dump_backtrace(const Vector<FlatPtr>& backtrace) +{ + for (auto& address : backtrace) { + reportln("{}", create_backtrace_line(address)); + } +} + +void Emulator::dump_backtrace() +{ + dump_backtrace(raw_backtrace()); +} + +u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3) +{ +#ifdef DEBUG_SPAM + reportln("Syscall: {} ({:x})", Syscall::to_string((Syscall::Function)function), function); +#endif + switch (function) { + case SC_chdir: + return virt$chdir(arg1, arg2); + case SC_dup2: + return virt$dup2(arg1, arg2); + case SC_get_stack_bounds: + return virt$get_stack_bounds(arg1, arg2); + case SC_access: + return virt$access(arg1, arg2, arg3); + case SC_waitid: + return virt$waitid(arg1); + case SC_getcwd: + return virt$getcwd(arg1, arg2); + case SC_ttyname: + return virt$ttyname(arg1, arg2, arg3); + case SC_getpgrp: + return virt$getpgrp(); + case SC_getpgid: + return virt$getpgid(arg1); + case SC_setpgid: + return virt$setpgid(arg1, arg2); + case SC_execve: + return virt$execve(arg1); + case SC_sigaction: + return virt$sigaction(arg1, arg2, arg3); + case SC_sigreturn: + return virt$sigreturn(); + case SC_stat: + return virt$stat(arg1); + case SC_realpath: + return virt$realpath(arg1); + case SC_gethostname: + return virt$gethostname(arg1, arg2); + case SC_ioctl: + return virt$ioctl(arg1, arg2, arg3); + case SC_get_dir_entries: + return virt$get_dir_entries(arg1, arg2, arg3); + case SC_shbuf_create: + return virt$shbuf_create(arg1, arg2); + case SC_shbuf_allow_pid: + return virt$shbuf_allow_pid(arg1, arg2); + case SC_shbuf_allow_all: + return virt$shbuf_allow_all(arg1); + case SC_shbuf_get: + return virt$shbuf_get(arg1, arg2); + case SC_shbuf_release: + return virt$shbuf_release(arg1); + case SC_shbuf_seal: + return virt$shbuf_seal(arg1); + case SC_shbuf_set_volatile: + return virt$shbuf_set_volatile(arg1, arg2); + case SC_profiling_enable: + return virt$profiling_enable(arg1); + case SC_profiling_disable: + return virt$profiling_disable(arg1); + case SC_disown: + return virt$disown(arg1); + case SC_purge: + return virt$purge(arg1); + case SC_mmap: + return virt$mmap(arg1); + case SC_mount: + return virt$mount(arg1); + case SC_munmap: + return virt$munmap(arg1, arg2); + case SC_mremap: + return virt$mremap(arg1); + case SC_gettid: + return virt$gettid(); + case SC_getpid: + return virt$getpid(); + case SC_getsid: + return virt$getsid(arg1); + case SC_pledge: + return virt$pledge(arg1); + case SC_unveil: + return virt$unveil(arg1); + case SC_getuid: + return virt$getuid(); + case SC_geteuid: + return virt$geteuid(); + case SC_getgid: + return virt$getgid(); + case SC_getegid: + return virt$getegid(); + case SC_setuid: + return virt$setuid(arg1); + case SC_setgid: + return virt$setgid(arg2); + case SC_close: + return virt$close(arg1); + case SC_fstat: + return virt$fstat(arg1, arg2); + case SC_mkdir: + return virt$mkdir(arg1, arg2, arg3); + case SC_unlink: + return virt$unlink(arg1, arg2); + case SC_write: + return virt$write(arg1, arg2, arg3); + case SC_read: + return virt$read(arg1, arg2, arg3); + case SC_mprotect: + return virt$mprotect(arg1, arg2, arg3); + case SC_madvise: + return virt$madvise(arg1, arg2, arg3); + case SC_open: + return virt$open(arg1); + case SC_pipe: + return virt$pipe(arg1, arg2); + case SC_fcntl: + return virt$fcntl(arg1, arg2, arg3); + case SC_getgroups: + return virt$getgroups(arg1, arg2); + case SC_setgroups: + return virt$setgroups(arg1, arg2); + case SC_lseek: + return virt$lseek(arg1, arg2, arg3); + case SC_socket: + return virt$socket(arg1, arg2, arg3); + case SC_getsockopt: + return virt$getsockopt(arg1); + case SC_get_process_name: + return virt$get_process_name(arg1, arg2); + case SC_dbgputstr: + return virt$dbgputstr(arg1, arg2); + case SC_dbgputch: + return virt$dbgputch(arg1); + case SC_chmod: + return virt$chmod(arg1, arg2, arg3); + case SC_fchmod: + return virt$fchmod(arg1, arg2); + case SC_fchown: + return virt$fchown(arg1, arg2, arg3); + case SC_accept: + return virt$accept(arg1, arg2, arg3); + case SC_setsockopt: + return virt$setsockopt(arg1); + case SC_bind: + return virt$bind(arg1, arg2, arg3); + case SC_connect: + return virt$connect(arg1, arg2, arg3); + case SC_listen: + return virt$listen(arg1, arg2); + case SC_select: + return virt$select(arg1); + case SC_recvmsg: + return virt$recvmsg(arg1, arg2, arg3); + case SC_sendmsg: + return virt$sendmsg(arg1, arg2, arg3); + case SC_kill: + return virt$kill(arg1, arg2); + case SC_set_mmap_name: + return virt$set_mmap_name(arg1); + case SC_exit: + virt$exit((int)arg1); + return 0; + case SC_gettimeofday: + return virt$gettimeofday(arg1); + case SC_clock_gettime: + return virt$clock_gettime(arg1, arg2); + case SC_clock_settime: + return virt$clock_settime(arg1, arg2); + case SC_getrandom: + return virt$getrandom(arg1, arg2, arg3); + case SC_fork: + return virt$fork(); + case SC_sched_getparam: + return virt$sched_getparam(arg1, arg2); + case SC_sched_setparam: + return virt$sched_setparam(arg1, arg2); + case SC_set_thread_name: + return virt$set_thread_name(arg1, arg2, arg3); + case SC_setsid: + return virt$setsid(); + case SC_watch_file: + return virt$watch_file(arg1, arg2); + case SC_clock_nanosleep: + return virt$clock_nanosleep(arg1); + case SC_readlink: + return virt$readlink(arg1); + case SC_ptsname: + return virt$ptsname(arg1, arg2, arg3); + case SC_allocate_tls: + return virt$allocate_tls(arg1); + case SC_beep: + return virt$beep(); + case SC_ftruncate: + return virt$ftruncate(arg1, arg2); + case SC_umask: + return virt$umask(arg1); + default: + reportln("\n=={}== \033[31;1mUnimplemented syscall: {}\033[0m, {:p}", getpid(), Syscall::to_string((Syscall::Function)function), function); + dump_backtrace(); + TODO(); + } +} + +int Emulator::virt$shbuf_create(int size, FlatPtr buffer) +{ + u8* host_data = nullptr; + int shbuf_id = syscall(SC_shbuf_create, size, &host_data); + if (shbuf_id < 0) + return shbuf_id; + FlatPtr address = allocate_vm(size, PAGE_SIZE); + auto region = SharedBufferRegion::create_with_shbuf_id(address, size, shbuf_id, host_data); + m_mmu.add_region(move(region)); + m_mmu.copy_to_vm(buffer, &address, sizeof(address)); + return shbuf_id; +} + +FlatPtr Emulator::virt$shbuf_get(int shbuf_id, FlatPtr size_ptr) +{ + size_t host_size = 0; + void* host_data = (void*)syscall(SC_shbuf_get, shbuf_id, &host_size); + if (host_data == (void*)-1) + return (FlatPtr)host_data; + FlatPtr address = allocate_vm(host_size, PAGE_SIZE); + auto region = SharedBufferRegion::create_with_shbuf_id(address, host_size, shbuf_id, (u8*)host_data); + m_mmu.add_region(move(region)); + m_mmu.copy_to_vm(size_ptr, &host_size, sizeof(host_size)); + return address; +} + +int Emulator::virt$shbuf_allow_pid(int shbuf_id, pid_t peer_pid) +{ + auto* region = m_mmu.shbuf_region(shbuf_id); + ASSERT(region); + return region->allow_pid(peer_pid); +} + +int Emulator::virt$shbuf_allow_all(int shbuf_id) +{ + auto* region = m_mmu.shbuf_region(shbuf_id); + ASSERT(region); + return region->allow_all(); +} + +int Emulator::virt$shbuf_release(int shbuf_id) +{ + auto* region = m_mmu.shbuf_region(shbuf_id); + ASSERT(region); + auto rc = region->release(); + m_mmu.remove_region(*region); + return rc; +} + +int Emulator::virt$shbuf_seal(int shbuf_id) +{ + auto* region = m_mmu.shbuf_region(shbuf_id); + ASSERT(region); + return region->seal(); +} + +int Emulator::virt$shbuf_set_volatile(int shbuf_id, bool is_volatile) +{ + auto* region = m_mmu.shbuf_region(shbuf_id); + ASSERT(region); + return region->set_volatile(is_volatile); +} + +int Emulator::virt$profiling_enable(pid_t pid) +{ + return syscall(SC_profiling_enable, pid); +} + +int Emulator::virt$profiling_disable(pid_t pid) +{ + return syscall(SC_profiling_disable, pid); +} + +int Emulator::virt$disown(pid_t pid) +{ + return syscall(SC_disown, pid); +} + +int Emulator::virt$purge(int mode) +{ + return syscall(SC_purge, mode); +} + +int Emulator::virt$fstat(int fd, FlatPtr statbuf) +{ + struct stat local_statbuf; + int rc = syscall(SC_fstat, fd, &local_statbuf); + if (rc < 0) + return rc; + mmu().copy_to_vm(statbuf, &local_statbuf, sizeof(local_statbuf)); + return rc; +} + +int Emulator::virt$close(int fd) +{ + return syscall(SC_close, fd); +} + +int Emulator::virt$mkdir(FlatPtr path, size_t path_length, mode_t mode) +{ + auto buffer = mmu().copy_buffer_from_vm(path, path_length); + return syscall(SC_mkdir, buffer.data(), buffer.size(), mode); +} + +int Emulator::virt$unlink(FlatPtr path, size_t path_length) +{ + auto buffer = mmu().copy_buffer_from_vm(path, path_length); + return syscall(SC_unlink, buffer.data(), buffer.size()); +} + +int Emulator::virt$dbgputstr(FlatPtr characters, int length) +{ + auto buffer = mmu().copy_buffer_from_vm(characters, length); + dbgputstr((const char*)buffer.data(), buffer.size()); + return 0; +} + +int Emulator::virt$chmod(FlatPtr path_addr, size_t path_length, mode_t mode) +{ + auto path = mmu().copy_buffer_from_vm(path_addr, path_length); + return syscall(SC_chmod, path.data(), path.size(), mode); +} + +int Emulator::virt$fchmod(int fd, mode_t mode) +{ + return syscall(SC_fchmod, fd, mode); +} + +int Emulator::virt$fchown(int fd, uid_t uid, gid_t gid) +{ + return syscall(SC_fchown, fd, uid, gid); +} + +int Emulator::virt$setsockopt(FlatPtr params_addr) +{ + Syscall::SC_setsockopt_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + if (params.option == SO_RCVTIMEO || params.option == SO_TIMESTAMP) { + auto host_value_buffer = ByteBuffer::create_zeroed(params.value_size); + mmu().copy_from_vm(host_value_buffer.data(), (FlatPtr)params.value, params.value_size); + int rc = setsockopt(params.sockfd, params.level, params.option, host_value_buffer.data(), host_value_buffer.size()); + if (rc < 0) + return -errno; + return rc; + } + + if (params.option == SO_BINDTODEVICE) { + auto ifname = mmu().copy_buffer_from_vm((FlatPtr)params.value, params.value_size); + params.value = ifname.data(); + params.value_size = ifname.size(); + return syscall(SC_setsockopt, ¶ms); + } + + TODO(); +} + +int Emulator::virt$get_stack_bounds(FlatPtr base, FlatPtr size) +{ + auto* region = mmu().find_region({ m_cpu.ss(), m_cpu.esp().value() }); + FlatPtr b = region->base(); + size_t s = region->size(); + mmu().copy_to_vm(base, &b, sizeof(b)); + mmu().copy_to_vm(size, &s, sizeof(s)); + return 0; +} + +int Emulator::virt$ftruncate(int fd, off_t length) +{ + return syscall(SC_ftruncate, fd, length); +} + +mode_t Emulator::virt$umask(mode_t mask) +{ + return syscall(SC_umask, mask); +} + +int Emulator::virt$accept(int sockfd, FlatPtr address, FlatPtr address_length) +{ + socklen_t host_address_length = 0; + mmu().copy_from_vm(&host_address_length, address_length, sizeof(host_address_length)); + auto host_buffer = ByteBuffer::create_zeroed(host_address_length); + int rc = syscall(SC_accept, sockfd, host_buffer.data(), &host_address_length); + if (rc < 0) + return rc; + mmu().copy_to_vm(address, host_buffer.data(), min((socklen_t)host_buffer.size(), host_address_length)); + mmu().copy_to_vm(address_length, &host_address_length, sizeof(host_address_length)); + return rc; +} + +int Emulator::virt$bind(int sockfd, FlatPtr address, socklen_t address_length) +{ + auto buffer = mmu().copy_buffer_from_vm(address, address_length); + return syscall(SC_bind, sockfd, buffer.data(), buffer.size()); +} + +int Emulator::virt$connect(int sockfd, FlatPtr address, socklen_t address_size) +{ + auto buffer = mmu().copy_buffer_from_vm(address, address_size); + return syscall(SC_connect, sockfd, buffer.data(), buffer.size()); +} + +int Emulator::virt$dbgputch(char ch) +{ + dbgputch(ch); + return 0; +} + +int Emulator::virt$listen(int fd, int backlog) +{ + return syscall(SC_listen, fd, backlog); +} + +int Emulator::virt$kill(pid_t pid, int signal) +{ + return syscall(SC_kill, pid, signal); +} + +int Emulator::virt$gettimeofday(FlatPtr timeval) +{ + struct timeval host_timeval; + int rc = syscall(SC_gettimeofday, &host_timeval); + if (rc < 0) + return rc; + mmu().copy_to_vm(timeval, &host_timeval, sizeof(host_timeval)); + return rc; +} + +int Emulator::virt$clock_gettime(int clockid, FlatPtr timespec) +{ + struct timespec host_timespec; + int rc = syscall(SC_clock_gettime, clockid, &host_timespec); + if (rc < 0) + return rc; + mmu().copy_to_vm(timespec, &host_timespec, sizeof(host_timespec)); + return rc; +} + +int Emulator::virt$clock_settime(uint32_t clock_id, FlatPtr user_ts) +{ + struct timespec user_timespec; + mmu().copy_from_vm(&user_timespec, user_ts, sizeof(user_timespec)); + int rc = syscall(SC_clock_settime, clock_id, &user_timespec); + return rc; +} + +int Emulator::virt$set_mmap_name(FlatPtr) +{ + // FIXME: Implement. + return 0; +} + +int Emulator::virt$get_process_name(FlatPtr buffer, int size) +{ + if (size < 0) + return -EINVAL; + auto host_buffer = ByteBuffer::create_zeroed((size_t)size); + int rc = syscall(SC_get_process_name, host_buffer.data(), host_buffer.size()); + mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size()); + return rc; +} + +int Emulator::virt$lseek(int fd, off_t offset, int whence) +{ + return syscall(SC_lseek, fd, offset, whence); +} + +int Emulator::virt$socket(int domain, int type, int protocol) +{ + return syscall(SC_socket, domain, type, protocol); +} + +int Emulator::virt$recvmsg(int sockfd, FlatPtr msg_addr, int flags) +{ + msghdr mmu_msg; + mmu().copy_from_vm(&mmu_msg, msg_addr, sizeof(mmu_msg)); + + Vector<iovec, 1> mmu_iovs; + mmu_iovs.resize(mmu_msg.msg_iovlen); + mmu().copy_from_vm(mmu_iovs.data(), (FlatPtr)mmu_msg.msg_iov, mmu_msg.msg_iovlen * sizeof(iovec)); + Vector<ByteBuffer, 1> buffers; + Vector<iovec, 1> iovs; + for (const auto& iov : mmu_iovs) { + buffers.append(ByteBuffer::create_uninitialized(iov.iov_len)); + iovs.append({ buffers.last().data(), buffers.last().size() }); + } + + ByteBuffer control_buffer; + if (mmu_msg.msg_control) + control_buffer = ByteBuffer::create_uninitialized(mmu_msg.msg_controllen); + + sockaddr_storage addr; + msghdr msg = { &addr, sizeof(addr), iovs.data(), (int)iovs.size(), mmu_msg.msg_control ? control_buffer.data() : nullptr, mmu_msg.msg_controllen, mmu_msg.msg_flags }; + int rc = recvmsg(sockfd, &msg, flags); + if (rc < 0) + return -errno; + + for (size_t i = 0; i < buffers.size(); ++i) + mmu().copy_to_vm((FlatPtr)mmu_iovs[i].iov_base, buffers[i].data(), mmu_iovs[i].iov_len); + + if (mmu_msg.msg_name) + mmu().copy_to_vm((FlatPtr)mmu_msg.msg_name, &addr, min(sizeof(addr), (size_t)mmu_msg.msg_namelen)); + if (mmu_msg.msg_control) + mmu().copy_to_vm((FlatPtr)mmu_msg.msg_control, control_buffer.data(), min(mmu_msg.msg_controllen, msg.msg_controllen)); + mmu_msg.msg_namelen = msg.msg_namelen; + mmu_msg.msg_controllen = msg.msg_controllen; + mmu_msg.msg_flags = msg.msg_flags; + mmu().copy_to_vm(msg_addr, &mmu_msg, sizeof(mmu_msg)); + return rc; +} + +int Emulator::virt$sendmsg(int sockfd, FlatPtr msg_addr, int flags) +{ + msghdr mmu_msg; + mmu().copy_from_vm(&mmu_msg, msg_addr, sizeof(mmu_msg)); + + Vector<iovec, 1> iovs; + iovs.resize(mmu_msg.msg_iovlen); + mmu().copy_from_vm(iovs.data(), (FlatPtr)mmu_msg.msg_iov, mmu_msg.msg_iovlen * sizeof(iovec)); + Vector<ByteBuffer, 1> buffers; + for (auto& iov : iovs) { + buffers.append(mmu().copy_buffer_from_vm((FlatPtr)iov.iov_base, iov.iov_len)); + iov = { buffers.last().data(), buffers.last().size() }; + } + + ByteBuffer control_buffer; + if (mmu_msg.msg_control) + control_buffer = ByteBuffer::create_uninitialized(mmu_msg.msg_controllen); + + sockaddr_storage address; + socklen_t address_length = 0; + if (mmu_msg.msg_name) { + address_length = min(sizeof(address), (size_t)mmu_msg.msg_namelen); + mmu().copy_from_vm(&address, (FlatPtr)mmu_msg.msg_name, address_length); + } + + msghdr msg = { mmu_msg.msg_name ? &address : nullptr, address_length, iovs.data(), (int)iovs.size(), mmu_msg.msg_control ? control_buffer.data() : nullptr, mmu_msg.msg_controllen, mmu_msg.msg_flags }; + return sendmsg(sockfd, &msg, flags); +} + +int Emulator::virt$select(FlatPtr params_addr) +{ + Syscall::SC_select_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + fd_set readfds {}; + fd_set writefds {}; + fd_set exceptfds {}; + struct timespec timeout; + u32 sigmask; + + if (params.readfds) + mmu().copy_from_vm(&readfds, (FlatPtr)params.readfds, sizeof(readfds)); + if (params.writefds) + mmu().copy_from_vm(&writefds, (FlatPtr)params.writefds, sizeof(writefds)); + if (params.exceptfds) + mmu().copy_from_vm(&exceptfds, (FlatPtr)params.exceptfds, sizeof(exceptfds)); + if (params.timeout) + mmu().copy_from_vm(&timeout, (FlatPtr)params.timeout, sizeof(timeout)); + if (params.sigmask) + mmu().copy_from_vm(&sigmask, (FlatPtr)params.sigmask, sizeof(sigmask)); + + int rc = pselect(params.nfds, &readfds, &writefds, &exceptfds, params.timeout ? &timeout : nullptr, params.sigmask ? &sigmask : nullptr); + if (rc < 0) + return -errno; + + if (params.readfds) + mmu().copy_to_vm((FlatPtr)params.readfds, &readfds, sizeof(readfds)); + if (params.writefds) + mmu().copy_to_vm((FlatPtr)params.writefds, &writefds, sizeof(writefds)); + if (params.exceptfds) + mmu().copy_to_vm((FlatPtr)params.exceptfds, &exceptfds, sizeof(exceptfds)); + if (params.timeout) + mmu().copy_to_vm((FlatPtr)params.timeout, &timeout, sizeof(timeout)); + + return rc; +} + +int Emulator::virt$getsockopt(FlatPtr params_addr) +{ + Syscall::SC_getsockopt_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + if (params.option == SO_PEERCRED) { + struct ucred creds = {}; + socklen_t creds_size = sizeof(creds); + int rc = getsockopt(params.sockfd, params.level, SO_PEERCRED, &creds, &creds_size); + if (rc < 0) + return -errno; + // FIXME: Check params.value_size + mmu().copy_to_vm((FlatPtr)params.value, &creds, sizeof(creds)); + return rc; + } + + TODO(); +} + +int Emulator::virt$getgroups(ssize_t count, FlatPtr groups) +{ + if (!count) + return syscall(SC_getgroups, 0, nullptr); + + auto buffer = ByteBuffer::create_uninitialized(count * sizeof(gid_t)); + int rc = syscall(SC_getgroups, count, buffer.data()); + if (rc < 0) + return rc; + mmu().copy_to_vm(groups, buffer.data(), buffer.size()); + return 0; +} + +int Emulator::virt$setgroups(ssize_t count, FlatPtr groups) +{ + if (!count) + return syscall(SC_setgroups, 0, nullptr); + + auto buffer = mmu().copy_buffer_from_vm(groups, count * sizeof(gid_t)); + return syscall(SC_setgroups, count, buffer.data()); +} + +u32 Emulator::virt$fcntl(int fd, int cmd, u32 arg) +{ + switch (cmd) { + case F_DUPFD: + case F_GETFD: + case F_SETFD: + case F_GETFL: + case F_SETFL: + case F_ISTTY: + break; + default: + TODO(); + } + + return syscall(SC_fcntl, fd, cmd, arg); +} + +u32 Emulator::virt$open(u32 params_addr) +{ + Syscall::SC_open_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + auto path = mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length); + + int fd = openat_with_path_length(params.dirfd, (const char*)path.data(), path.size(), params.options, params.mode); + if (fd < 0) + return -errno; + return fd; +} + +int Emulator::virt$pipe(FlatPtr vm_pipefd, int flags) +{ + int pipefd[2]; + int rc = syscall(SC_pipe, pipefd, flags); + if (rc < 0) + return rc; + mmu().copy_to_vm(vm_pipefd, pipefd, sizeof(pipefd)); + return rc; +} + +u32 Emulator::virt$munmap(FlatPtr address, u32 size) +{ + auto* region = mmu().find_region({ 0x23, address }); + ASSERT(region); + if (region->size() != round_up_to_power_of_two(size, PAGE_SIZE)) + TODO(); + mmu().remove_region(*region); + return 0; +} + +FlatPtr Emulator::allocate_vm(size_t size, size_t alignment) +{ + // FIXME: Write a proper VM allocator + static FlatPtr next_address = 0x30000000; + + FlatPtr final_address; + + if (!alignment) + alignment = PAGE_SIZE; + + // FIXME: What if alignment is not a power of 2? + final_address = round_up_to_power_of_two(next_address, alignment); + next_address = round_up_to_power_of_two(final_address + size, PAGE_SIZE); + return final_address; +} + +u32 Emulator::virt$mmap(u32 params_addr) +{ + Syscall::SC_mmap_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + u32 final_size = round_up_to_power_of_two(params.size, PAGE_SIZE); + u32 final_address = allocate_vm(final_size, params.alignment); + if (params.addr != 0) { + // NOTE: We currently do not support allocating VM at a requeted address in the emulator. + // The loader needs this functionality to load .data just after .text. + // Luckily, since the loader calls mmap for .data right after it calls mmap for .text, + // the emulator will allocate a chunk of memory that is just after what we allocated for .text + // because of the way we currently allocate VM. + ASSERT(params.addr == final_address); + } + + if (params.flags & MAP_ANONYMOUS) + mmu().add_region(MmapRegion::create_anonymous(final_address, final_size, params.prot)); + else { + String name_str; + if (params.name.characters) { + auto name = ByteBuffer::create_uninitialized(params.name.length); + mmu().copy_from_vm(name.data(), (FlatPtr)params.name.characters, params.name.length); + name_str = { name.data(), name.size() }; + } + auto region = MmapRegion::create_file_backed(final_address, final_size, params.prot, params.flags, params.fd, params.offset, name_str); + if (region->name() == "libc.so: .text (Emulated)") { + bool rc = find_malloc_symbols(*region); + ASSERT(rc); + } + mmu().add_region(move(region)); + } + + return final_address; +} + +FlatPtr Emulator::virt$mremap(FlatPtr params_addr) +{ + Syscall::SC_mremap_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + if (auto* region = mmu().find_region({ m_cpu.ds(), params.old_address })) { + if (!is<MmapRegion>(*region)) + return -EINVAL; + ASSERT(region->size() == params.old_size); + auto& mmap_region = *(MmapRegion*)region; + auto* ptr = mremap(mmap_region.data(), mmap_region.size(), mmap_region.size(), params.flags); + if (ptr == MAP_FAILED) + return -errno; + return (FlatPtr)ptr; + } + return -EINVAL; +} + +u32 Emulator::virt$mount(u32 params_addr) +{ + Syscall::SC_mount_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + auto target = mmu().copy_buffer_from_vm((FlatPtr)params.target.characters, params.target.length); + auto fs_path = mmu().copy_buffer_from_vm((FlatPtr)params.fs_type.characters, params.fs_type.length); + params.fs_type.characters = (char*)fs_path.data(); + params.fs_type.length = fs_path.size(); + params.target.characters = (char*)target.data(); + params.target.length = target.size(); + + return syscall(SC_mount, ¶ms); +} + +u32 Emulator::virt$gettid() +{ + return gettid(); +} + +u32 Emulator::virt$getpid() +{ + return getpid(); +} + +u32 Emulator::virt$pledge(u32) +{ + return 0; +} + +u32 Emulator::virt$unveil(u32) +{ + return 0; +} + +u32 Emulator::virt$mprotect(FlatPtr base, size_t size, int prot) +{ + if (auto* region = mmu().find_region({ m_cpu.ds(), base })) { + if (!is<MmapRegion>(*region)) + return -EINVAL; + ASSERT(region->size() == size); + auto& mmap_region = *(MmapRegion*)region; + mmap_region.set_prot(prot); + return 0; + } + return -EINVAL; +} + +u32 Emulator::virt$madvise(FlatPtr, size_t, int) +{ + return 0; +} + +uid_t Emulator::virt$getuid() +{ + return getuid(); +} + +uid_t Emulator::virt$geteuid() +{ + return geteuid(); +} + +gid_t Emulator::virt$getgid() +{ + return getgid(); +} + +gid_t Emulator::virt$getegid() +{ + return getegid(); +} + +int Emulator::virt$setuid(uid_t uid) +{ + return syscall(SC_setuid, uid); +} + +int Emulator::virt$setgid(gid_t gid) +{ + return syscall(SC_setgid, gid); +} + +u32 Emulator::virt$write(int fd, FlatPtr data, ssize_t size) +{ + if (size < 0) + return -EINVAL; + auto buffer = mmu().copy_buffer_from_vm(data, size); + return syscall(SC_write, fd, buffer.data(), buffer.size()); +} + +u32 Emulator::virt$read(int fd, FlatPtr buffer, ssize_t size) +{ + if (size < 0) + return -EINVAL; + auto local_buffer = ByteBuffer::create_uninitialized(size); + int nread = syscall(SC_read, fd, local_buffer.data(), local_buffer.size()); + if (nread < 0) { + if (nread == -EPERM) { + dump_backtrace(); + TODO(); + } + return nread; + } + mmu().copy_to_vm(buffer, local_buffer.data(), local_buffer.size()); + return nread; +} + +void Emulator::virt$exit(int status) +{ + reportln("\n=={}== \033[33;1mSyscall: exit({})\033[0m, shutting down!", getpid(), status); + m_exit_status = status; + m_shutdown = true; +} + +ssize_t Emulator::virt$getrandom(FlatPtr buffer, size_t buffer_size, unsigned int flags) +{ + auto host_buffer = ByteBuffer::create_uninitialized(buffer_size); + int rc = syscall(SC_getrandom, host_buffer.data(), host_buffer.size(), flags); + if (rc < 0) + return rc; + mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size()); + return rc; +} + +int Emulator::virt$get_dir_entries(int fd, FlatPtr buffer, ssize_t size) +{ + auto host_buffer = ByteBuffer::create_uninitialized(size); + int rc = syscall(SC_get_dir_entries, fd, host_buffer.data(), host_buffer.size()); + if (rc < 0) + return rc; + mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size()); + return rc; +} + +int Emulator::virt$ioctl([[maybe_unused]] int fd, unsigned request, [[maybe_unused]] FlatPtr arg) +{ + if (request == TIOCGWINSZ) { + struct winsize ws; + int rc = syscall(SC_ioctl, fd, TIOCGWINSZ, &ws); + if (rc < 0) + return rc; + mmu().copy_to_vm(arg, &ws, sizeof(winsize)); + return 0; + } + if (request == TIOCSPGRP) { + return syscall(SC_ioctl, fd, request, arg); + } + if (request == TCGETS) { + struct termios termios; + int rc = syscall(SC_ioctl, fd, request, &termios); + if (rc < 0) + return rc; + mmu().copy_to_vm(arg, &termios, sizeof(termios)); + return rc; + } + if (request == TCSETS) { + struct termios termios; + mmu().copy_from_vm(&termios, arg, sizeof(termios)); + return syscall(SC_ioctl, fd, request, &termios); + } + if (request == TIOCNOTTY || request == TIOCSCTTY) { + return syscall(SC_ioctl, fd, request, 0); + } + if (request == FB_IOCTL_GET_SIZE_IN_BYTES) { + size_t size = 0; + auto rc = syscall(SC_ioctl, fd, request, &size); + mmu().copy_to_vm(arg, &size, sizeof(size)); + return rc; + } + if (request == FB_IOCTL_SET_RESOLUTION) { + FBResolution user_resolution; + mmu().copy_from_vm(&user_resolution, arg, sizeof(user_resolution)); + auto rc = syscall(SC_ioctl, fd, request, &user_resolution); + mmu().copy_to_vm(arg, &user_resolution, sizeof(user_resolution)); + return rc; + } + if (request == FB_IOCTL_SET_BUFFER) { + return syscall(SC_ioctl, fd, request, arg); + } + reportln("Unsupported ioctl: {}", request); + dump_backtrace(); + TODO(); +} + +int Emulator::virt$fork() +{ + int rc = fork(); + if (rc < 0) + return -errno; + return rc; +} + +int Emulator::virt$execve(FlatPtr params_addr) +{ + Syscall::SC_execve_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + auto path = String::copy(mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length)); + Vector<String> arguments; + Vector<String> environment; + + auto copy_string_list = [this](auto& output_vector, auto& string_list) { + for (size_t i = 0; i < string_list.length; ++i) { + Syscall::StringArgument string; + mmu().copy_from_vm(&string, (FlatPtr)&string_list.strings[i], sizeof(string)); + output_vector.append(String::copy(mmu().copy_buffer_from_vm((FlatPtr)string.characters, string.length))); + } + }; + + copy_string_list(arguments, params.arguments); + copy_string_list(environment, params.environment); + + reportln("\n=={}== \033[33;1mSyscall:\033[0m execve", getpid()); + reportln("=={}== @ {}", getpid(), path); + for (auto& argument : arguments) + reportln("=={}== - {}", getpid(), argument); + + Vector<char*> argv; + Vector<char*> envp; + + argv.append(const_cast<char*>("/bin/UserspaceEmulator")); + argv.append(const_cast<char*>(path.characters())); + if (g_report_to_debug) + argv.append(const_cast<char*>("--report-to-debug")); + argv.append(const_cast<char*>("--")); + + auto create_string_vector = [](auto& output_vector, auto& input_vector) { + for (auto& string : input_vector) + output_vector.append(const_cast<char*>(string.characters())); + output_vector.append(nullptr); + }; + + create_string_vector(argv, arguments); + create_string_vector(envp, environment); + + // Yoink duplicated program name. + argv.remove(3 + (g_report_to_debug ? 1 : 0)); + + return execve(argv[0], (char* const*)argv.data(), (char* const*)envp.data()); +} + +int Emulator::virt$stat(FlatPtr params_addr) +{ + Syscall::SC_stat_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + auto path = String::copy(mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length)); + struct stat host_statbuf; + int rc; + if (params.follow_symlinks) + rc = stat(path.characters(), &host_statbuf); + else + rc = lstat(path.characters(), &host_statbuf); + if (rc < 0) + return -errno; + mmu().copy_to_vm((FlatPtr)params.statbuf, &host_statbuf, sizeof(host_statbuf)); + return rc; +} + +int Emulator::virt$realpath(FlatPtr params_addr) +{ + Syscall::SC_realpath_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + if (params.path.length > PATH_MAX) { + return -ENAMETOOLONG; + } + auto path = mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length); + char host_buffer[PATH_MAX] = {}; + size_t host_buffer_size = min(sizeof(host_buffer), params.buffer.size); + + Syscall::SC_realpath_params host_params; + host_params.path = { (const char*)path.data(), path.size() }; + host_params.buffer = { host_buffer, host_buffer_size }; + int rc = syscall(SC_realpath, &host_params); + if (rc < 0) + return rc; + mmu().copy_to_vm((FlatPtr)params.buffer.data, host_buffer, host_buffer_size); + return rc; +} + +int Emulator::virt$gethostname(FlatPtr buffer, ssize_t buffer_size) +{ + if (buffer_size < 0) + return -EINVAL; + auto host_buffer = ByteBuffer::create_zeroed(buffer_size); + int rc = syscall(SC_gethostname, host_buffer.data(), host_buffer.size()); + if (rc < 0) + return rc; + mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size()); + 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) { + reportln("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$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, +}; + +static 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: + return DefaultSignalAction::Terminate; + case SIGCHLD: + case SIGURG: + case SIGWINCH: + case SIGINFO: + 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; + reportln("\n=={}== Got signal {} ({}), no handler registered", getpid(), signum, strsignal(signum)); + m_shutdown = true; + return; + } + + if (handler.handler == 1) { + // SIG_IGN + return; + } + + reportln("\n=={}== Got signal {} ({}), handler at {:p}", 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); +} + +// Make sure the compiler doesn't "optimize away" this function: +extern void signal_trampoline_dummy(); +void signal_trampoline_dummy() +{ + // 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 + // necessary 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)); +} + +int Emulator::virt$getpgrp() +{ + return syscall(SC_getpgrp); +} + +int Emulator::virt$getpgid(pid_t pid) +{ + return syscall(SC_getpgid, pid); +} + +int Emulator::virt$setpgid(pid_t pid, pid_t pgid) +{ + return syscall(SC_setpgid, pid, pgid); +} + +int Emulator::virt$ttyname(int fd, FlatPtr buffer, size_t buffer_size) +{ + auto host_buffer = ByteBuffer::create_zeroed(buffer_size); + int rc = syscall(SC_ttyname, fd, host_buffer.data(), host_buffer.size()); + if (rc < 0) + return rc; + mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size()); + return rc; +} + +int Emulator::virt$getcwd(FlatPtr buffer, size_t buffer_size) +{ + auto host_buffer = ByteBuffer::create_zeroed(buffer_size); + int rc = syscall(SC_getcwd, host_buffer.data(), host_buffer.size()); + if (rc < 0) + return rc; + mmu().copy_to_vm(buffer, host_buffer.data(), host_buffer.size()); + return rc; +} + +int Emulator::virt$getsid(pid_t pid) +{ + return syscall(SC_getsid, pid); +} + +int Emulator::virt$access(FlatPtr path, size_t path_length, int type) +{ + auto host_path = mmu().copy_buffer_from_vm(path, path_length); + return syscall(SC_access, host_path.data(), host_path.size(), type); +} + +int Emulator::virt$waitid(FlatPtr params_addr) +{ + Syscall::SC_waitid_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + Syscall::SC_waitid_params host_params = params; + siginfo info; + host_params.infop = &info; + + int rc = syscall(SC_waitid, &host_params); + if (rc < 0) + return rc; + + if (info.si_addr) { + // FIXME: Translate this somehow. + TODO(); + } + + if (params.infop) + mmu().copy_to_vm((FlatPtr)params.infop, &info, sizeof(info)); + + return rc; +} + +int Emulator::virt$chdir(FlatPtr path, size_t path_length) +{ + auto host_path = mmu().copy_buffer_from_vm(path, path_length); + return syscall(SC_chdir, host_path.data(), host_path.size()); +} + +int Emulator::virt$dup2(int old_fd, int new_fd) +{ + return syscall(SC_dup2, old_fd, new_fd); +} + +int Emulator::virt$sched_getparam(pid_t pid, FlatPtr user_addr) +{ + sched_param user_param; + mmu().copy_from_vm(&user_param, user_addr, sizeof(user_param)); + auto rc = syscall(SC_sched_getparam, pid, &user_param); + mmu().copy_to_vm(user_addr, &user_param, sizeof(user_param)); + return rc; +} + +int Emulator::virt$sched_setparam(int pid, FlatPtr user_addr) +{ + sched_param user_param; + mmu().copy_from_vm(&user_param, user_addr, sizeof(user_param)); + return syscall(SC_sched_setparam, pid, &user_param); +} + +int Emulator::virt$set_thread_name(pid_t pid, FlatPtr name_addr, size_t name_length) +{ + auto user_name = mmu().copy_buffer_from_vm(name_addr, name_length); + auto name = String::formatted("(UE) {}", StringView { user_name.data(), user_name.size() }); + return syscall(SC_set_thread_name, pid, name.characters(), name.length()); +} + +pid_t Emulator::virt$setsid() +{ + return syscall(SC_setsid); +} + +int Emulator::virt$watch_file(FlatPtr user_path_addr, size_t path_length) +{ + auto user_path = mmu().copy_buffer_from_vm(user_path_addr, path_length); + return syscall(SC_watch_file, user_path.data(), user_path.size()); +} + +int Emulator::virt$clock_nanosleep(FlatPtr params_addr) +{ + Syscall::SC_clock_nanosleep_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + timespec requested_sleep; + mmu().copy_from_vm(&requested_sleep, (FlatPtr)params.requested_sleep, sizeof(timespec)); + params.requested_sleep = &requested_sleep; + + auto remaining_vm_addr = params.remaining_sleep; + auto remaining = ByteBuffer::create_zeroed(sizeof(timespec)); + params.remaining_sleep = (timespec*)remaining.data(); + + int rc = syscall(SC_clock_nanosleep, ¶ms); + if (remaining_vm_addr) + mmu().copy_to_vm((FlatPtr)remaining_vm_addr, remaining.data(), sizeof(timespec)); + + return rc; +} + +int Emulator::virt$readlink(FlatPtr params_addr) +{ + Syscall::SC_readlink_params params; + mmu().copy_from_vm(¶ms, params_addr, sizeof(params)); + + if (params.path.length > PATH_MAX) { + return -ENAMETOOLONG; + } + auto path = mmu().copy_buffer_from_vm((FlatPtr)params.path.characters, params.path.length); + char host_buffer[PATH_MAX] = {}; + size_t host_buffer_size = min(sizeof(host_buffer), params.buffer.size); + + Syscall::SC_readlink_params host_params; + host_params.path = { (const char*)path.data(), path.size() }; + host_params.buffer = { host_buffer, host_buffer_size }; + int rc = syscall(SC_readlink, &host_params); + if (rc < 0) + return rc; + mmu().copy_to_vm((FlatPtr)params.buffer.data, host_buffer, host_buffer_size); + return rc; +} + +u32 Emulator::virt$allocate_tls(size_t size) +{ + // TODO: Why is this needed? without this, the loader overflows the bounds of the TLS region. + constexpr size_t TLS_SIZE_HACK = 8; + auto tcb_region = make<SimpleRegion>(0x20000000, size + TLS_SIZE_HACK); + bzero(tcb_region->data(), size); + memset(tcb_region->shadow_data(), 0x01, size); + + auto tls_region = make<SimpleRegion>(0, 4); + tls_region->write32(0, shadow_wrap_as_initialized(tcb_region->base() + (u32)size)); + memset(tls_region->shadow_data(), 0x01, 4); + + u32 tls_base = tcb_region->base(); + mmu().add_region(move(tcb_region)); + mmu().set_tls_region(move(tls_region)); + return tls_base; +} + +int Emulator::virt$ptsname(int fd, FlatPtr buffer, size_t buffer_size) +{ + auto pts = mmu().copy_buffer_from_vm(buffer, buffer_size); + return syscall(SC_ptsname, fd, pts.data(), pts.size()); +} + +int Emulator::virt$beep() +{ + return syscall(SC_beep); +} + +bool Emulator::find_malloc_symbols(const MmapRegion& libc_text) +{ + auto file_or_error = MappedFile::map("/usr/lib/libc.so"); + if (file_or_error.is_error()) + return false; + + ELF::Image image(file_or_error.value()->bytes()); + auto malloc_symbol = image.find_demangled_function("malloc"); + auto free_symbol = image.find_demangled_function("free"); + auto realloc_symbol = image.find_demangled_function("realloc"); + auto malloc_size_symbol = image.find_demangled_function("malloc_size"); + if (!malloc_symbol.has_value() || !free_symbol.has_value() || !realloc_symbol.has_value() || !malloc_size_symbol.has_value()) + return false; + + m_malloc_symbol_start = malloc_symbol.value().value() + libc_text.base(); + m_malloc_symbol_end = m_malloc_symbol_start + malloc_symbol.value().size(); + m_free_symbol_start = free_symbol.value().value() + libc_text.base(); + m_free_symbol_end = m_free_symbol_start + free_symbol.value().size(); + m_realloc_symbol_start = realloc_symbol.value().value() + libc_text.base(); + m_realloc_symbol_end = m_realloc_symbol_start + realloc_symbol.value().size(); + m_malloc_size_symbol_start = malloc_size_symbol.value().value() + libc_text.base(); + m_malloc_size_symbol_end = m_malloc_size_symbol_start + malloc_size_symbol.value().size(); + return true; +} +} |