mirror of
https://github.com/RGBCube/serenity
synced 2025-05-14 09:04:59 +00:00
DevTools: Remove UserspaceEmulator
It hasn't been built since we dropped i386 support. When we want to bring it back, we can get it back from source control. No behavior change.
This commit is contained in:
parent
4e383bdac1
commit
3f9d0c7789
28 changed files with 0 additions and 13189 deletions
|
@ -1,26 +0,0 @@
|
|||
serenity_component(
|
||||
UserspaceEmulator
|
||||
RECOMMENDED
|
||||
TARGETS UserspaceEmulator
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
Emulator.cpp
|
||||
Emulator_syscalls.cpp
|
||||
MallocTracer.cpp
|
||||
MmapRegion.cpp
|
||||
Range.cpp
|
||||
RangeAllocator.cpp
|
||||
Region.cpp
|
||||
SimpleRegion.cpp
|
||||
SoftCPU.cpp
|
||||
SoftFPU.cpp
|
||||
SoftMMU.cpp
|
||||
SoftVPU.cpp
|
||||
main.cpp
|
||||
)
|
||||
|
||||
add_compile_options(-mmmx -Wno-psabi -frounding-math)
|
||||
|
||||
serenity_bin(UserspaceEmulator)
|
||||
target_link_libraries(UserspaceEmulator PRIVATE LibX86 LibDebug LibCore LibFileSystem LibLine LibSystem)
|
|
@ -1,789 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Leon Albrecht <leon2002.l@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Emulator.h"
|
||||
#include "MmapRegion.h"
|
||||
#include "SimpleRegion.h"
|
||||
#include "SoftCPU.h"
|
||||
#include <AK/Format.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/StringUtils.h>
|
||||
#include <Kernel/API/MemoryLayout.h>
|
||||
#include <LibCore/MappedFile.h>
|
||||
#include <LibELF/AuxiliaryVector.h>
|
||||
#include <LibELF/Image.h>
|
||||
#include <LibELF/Validation.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <LibX86/ELFSymbolProvider.h>
|
||||
#include <fcntl.h>
|
||||
#include <syscall.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#if defined(AK_COMPILER_GCC)
|
||||
# pragma GCC optimize("O3")
|
||||
#endif
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
static constexpr u32 stack_location = 0x10000000;
|
||||
static constexpr size_t stack_size = 1 * MiB;
|
||||
|
||||
static constexpr u32 signal_trampoline_location = 0xb0000000;
|
||||
|
||||
static Emulator* s_the;
|
||||
|
||||
Emulator& Emulator::the()
|
||||
{
|
||||
VERIFY(s_the);
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
Emulator::Emulator(ByteString const& executable_path, Vector<StringView> const& arguments, Vector<ByteString> const& environment)
|
||||
: m_executable_path(executable_path)
|
||||
, m_arguments(arguments)
|
||||
, m_environment(environment)
|
||||
, m_mmu(*this)
|
||||
, m_cpu(make<SoftCPU>(*this))
|
||||
, m_editor(Line::Editor::construct())
|
||||
{
|
||||
m_malloc_tracer = make<MallocTracer>(*this);
|
||||
|
||||
static constexpr FlatPtr userspace_range_ceiling = 0xbe000000;
|
||||
#ifdef UE_ASLR
|
||||
static constexpr FlatPtr page_mask = 0xfffff000u;
|
||||
size_t random_offset = (get_random<u8>() % 32 * MiB) & page_mask;
|
||||
FlatPtr base = userspace_range_base + random_offset;
|
||||
#else
|
||||
FlatPtr base = userspace_range_base;
|
||||
#endif
|
||||
|
||||
m_range_allocator.initialize_with_range(VirtualAddress(base), userspace_range_ceiling - base);
|
||||
|
||||
VERIFY(!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, ByteString const& 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"sv });
|
||||
|
||||
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)
|
||||
{
|
||||
m_range_allocator.reserve_user_range(VirtualAddress(stack_location), stack_size);
|
||||
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 const& argument : m_arguments) {
|
||||
m_cpu->push_string(argument);
|
||||
argv_entries.append(m_cpu->esp().value());
|
||||
}
|
||||
|
||||
Vector<u32> env_entries;
|
||||
|
||||
for (auto const& variable : m_environment) {
|
||||
m_cpu->push_string(variable.view());
|
||||
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);
|
||||
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((u8 const*)&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();
|
||||
|
||||
while ((m_cpu->esp().value() + 4) % 16 != 0)
|
||||
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));
|
||||
|
||||
VERIFY(m_cpu->esp().value() % 16 == 0);
|
||||
}
|
||||
|
||||
bool Emulator::load_elf()
|
||||
{
|
||||
auto file_or_error = Core::MappedFile::map(m_executable_path);
|
||||
if (file_or_error.is_error()) {
|
||||
reportln("Unable to map {}: {}"sv, 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
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
StringBuilder interpreter_path_builder;
|
||||
auto result_or_error = ELF::validate_program_headers(*(Elf32_Ehdr const*)elf_image_data.data(), elf_image_data.size(), elf_image_data, &interpreter_path_builder);
|
||||
if (result_or_error.is_error() || !result_or_error.value()) {
|
||||
reportln("failed to validate ELF file"sv);
|
||||
return false;
|
||||
}
|
||||
auto interpreter_path = interpreter_path_builder.string_view();
|
||||
|
||||
VERIFY(!interpreter_path.is_null());
|
||||
dbgln("interpreter: {}", interpreter_path);
|
||||
|
||||
auto interpreter_file_or_error = Core::MappedFile::map(interpreter_path);
|
||||
VERIFY(!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([&](ELF::Image::ProgramHeader const& program_header) {
|
||||
// Loader is not allowed to have its own TLS regions
|
||||
VERIFY(program_header.type() != PT_TLS);
|
||||
|
||||
if (program_header.type() == PT_LOAD) {
|
||||
auto start_address = program_header.vaddr().offset(interpreter_load_offset);
|
||||
m_range_allocator.reserve_user_range(start_address, program_header.size_in_memory());
|
||||
auto region = make<SimpleRegion>(start_address.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;
|
||||
|
||||
constexpr bool trace = false;
|
||||
|
||||
size_t instructions_until_next_profile_dump = profile_instruction_interval();
|
||||
if (is_profiling() && m_loader_text_size.has_value())
|
||||
emit_profile_event(profile_stream(), "mmap"sv, ByteString::formatted(R"("ptr": {}, "size": {}, "name": "/usr/lib/Loader.so")", *m_loader_text_base, *m_loader_text_size));
|
||||
|
||||
while (!m_shutdown) {
|
||||
if (m_steps_til_pause) [[likely]] {
|
||||
m_cpu->save_base_eip();
|
||||
auto insn = X86::Instruction::from_stream(*m_cpu, X86::ProcessorMode::Protected);
|
||||
// Exec cycle
|
||||
if constexpr (trace) {
|
||||
outln("{:p} \033[33;1m{}\033[0m", m_cpu->base_eip(), insn.to_byte_string(m_cpu->base_eip(), symbol_provider));
|
||||
}
|
||||
|
||||
(m_cpu->*insn.handler())(insn);
|
||||
|
||||
if (is_profiling()) {
|
||||
if (instructions_until_next_profile_dump == 0) {
|
||||
instructions_until_next_profile_dump = profile_instruction_interval();
|
||||
emit_profile_sample(profile_stream());
|
||||
} else {
|
||||
--instructions_until_next_profile_dump;
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (trace) {
|
||||
m_cpu->dump();
|
||||
}
|
||||
|
||||
if (m_pending_signals) [[unlikely]] {
|
||||
dispatch_one_pending_signal();
|
||||
}
|
||||
if (m_steps_til_pause > 0)
|
||||
m_steps_til_pause--;
|
||||
|
||||
} else {
|
||||
handle_repl();
|
||||
}
|
||||
}
|
||||
|
||||
if (auto* tracer = malloc_tracer())
|
||||
tracer->dump_leak_report();
|
||||
|
||||
return m_exit_status;
|
||||
}
|
||||
|
||||
void Emulator::send_signal(int signal)
|
||||
{
|
||||
SignalInfo info {
|
||||
// FIXME: Fill this in somehow
|
||||
.signal_info = {
|
||||
.si_signo = signal,
|
||||
.si_code = SI_USER,
|
||||
.si_errno = 0,
|
||||
.si_pid = getpid(),
|
||||
.si_uid = geteuid(),
|
||||
.si_addr = 0,
|
||||
.si_status = 0,
|
||||
.si_band = 0,
|
||||
.si_value = {
|
||||
.sival_int = 0,
|
||||
},
|
||||
},
|
||||
.context = {},
|
||||
};
|
||||
did_receive_signal(signal, info, true);
|
||||
}
|
||||
|
||||
void Emulator::handle_repl()
|
||||
{
|
||||
// Console interface
|
||||
// FIXME: Previous Instruction**s**
|
||||
// FIXME: Function names (base, call, jump)
|
||||
auto saved_eip = m_cpu->eip();
|
||||
m_cpu->save_base_eip();
|
||||
auto insn = X86::Instruction::from_stream(*m_cpu, X86::ProcessorMode::Protected);
|
||||
// FIXME: This does not respect inlining
|
||||
// another way of getting the current function is at need
|
||||
if (auto symbol = symbol_at(m_cpu->base_eip()); symbol.has_value()) {
|
||||
outln("[{}]: {}", symbol->lib_name, symbol->symbol);
|
||||
}
|
||||
|
||||
outln("==> {}", create_instruction_line(m_cpu->base_eip(), insn));
|
||||
for (int i = 0; i < 7; ++i) {
|
||||
m_cpu->save_base_eip();
|
||||
insn = X86::Instruction::from_stream(*m_cpu, X86::ProcessorMode::Protected);
|
||||
outln(" {}", create_instruction_line(m_cpu->base_eip(), insn));
|
||||
}
|
||||
// We don't want to increase EIP here, we just want the instructions
|
||||
m_cpu->set_eip(saved_eip);
|
||||
|
||||
outln();
|
||||
m_cpu->dump();
|
||||
outln();
|
||||
|
||||
auto line_or_error = m_editor->get_line(">> ");
|
||||
if (line_or_error.is_error())
|
||||
return;
|
||||
|
||||
// FIXME: find a way to find a global symbol-address for run-until-call
|
||||
auto help = [] {
|
||||
outln("Available commands:");
|
||||
outln("continue, c: Continue the execution");
|
||||
outln("quit, q: Quit the execution (this will \"kill\" the program and run checks)");
|
||||
outln("ret, r: Run until function returns");
|
||||
outln("step, s [count]: Execute [count] instructions and then halt");
|
||||
outln("signal, sig [number:int], send signal to emulated program (default: sigint:2)");
|
||||
};
|
||||
auto line = line_or_error.release_value();
|
||||
if (line.is_empty()) {
|
||||
if (m_editor->history().is_empty()) {
|
||||
help();
|
||||
return;
|
||||
}
|
||||
line = m_editor->history().last().entry;
|
||||
}
|
||||
|
||||
auto parts = line.split_view(' ');
|
||||
m_editor->add_to_history(line);
|
||||
|
||||
if (parts[0].is_one_of("s"sv, "step"sv)) {
|
||||
if (parts.size() == 1) {
|
||||
m_steps_til_pause = 1;
|
||||
return;
|
||||
}
|
||||
auto number = AK::StringUtils::convert_to_int<i64>(parts[1]);
|
||||
if (!number.has_value()) {
|
||||
outln("usage \"step [count]\"\n\tcount can't be less than 1");
|
||||
return;
|
||||
}
|
||||
m_steps_til_pause = number.value();
|
||||
} else if (parts[0].is_one_of("c"sv, "continue"sv)) {
|
||||
m_steps_til_pause = -1;
|
||||
} else if (parts[0].is_one_of("r"sv, "ret"sv)) {
|
||||
m_run_til_return = true;
|
||||
// FIXME: This may be uninitialized
|
||||
m_watched_addr = m_mmu.read32({ 0x23, m_cpu->ebp().value() + 4 }).value();
|
||||
m_steps_til_pause = -1;
|
||||
} else if (parts[0].is_one_of("q"sv, "quit"sv)) {
|
||||
m_shutdown = true;
|
||||
} else if (parts[0].is_one_of("sig"sv, "signal"sv)) {
|
||||
if (parts.size() == 1) {
|
||||
send_signal(SIGINT);
|
||||
return;
|
||||
}
|
||||
if (parts.size() == 2) {
|
||||
auto number = AK::StringUtils::convert_to_int<i32>(parts[1]);
|
||||
if (number.has_value()) {
|
||||
send_signal(*number);
|
||||
return;
|
||||
}
|
||||
}
|
||||
outln("Usage: sig [signal:int], default: SINGINT:2");
|
||||
} else {
|
||||
help();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
MmapRegion const* Emulator::find_text_region(FlatPtr address)
|
||||
{
|
||||
MmapRegion const* matching_region = nullptr;
|
||||
mmu().for_each_region_of_type<MmapRegion>([&](auto& region) {
|
||||
if (!(region.is_executable() && address >= region.base() && address < region.base() + region.size()))
|
||||
return IterationDecision::Continue;
|
||||
matching_region = ®ion;
|
||||
return IterationDecision::Break;
|
||||
});
|
||||
return matching_region;
|
||||
}
|
||||
|
||||
// FIXME: This interface isn't the nicest
|
||||
MmapRegion const* Emulator::load_library_from_address(FlatPtr address)
|
||||
{
|
||||
auto const* region = find_text_region(address);
|
||||
if (!region)
|
||||
return {};
|
||||
|
||||
ByteString lib_name = region->lib_name();
|
||||
if (lib_name.is_null())
|
||||
return {};
|
||||
|
||||
ByteString lib_path = lib_name;
|
||||
if (FileSystem::looks_like_shared_library(lib_name))
|
||||
lib_path = ByteString::formatted("/usr/lib/{}", lib_path);
|
||||
|
||||
if (!m_dynamic_library_cache.contains(lib_path)) {
|
||||
auto file_or_error = Core::MappedFile::map(lib_path);
|
||||
if (file_or_error.is_error())
|
||||
return {};
|
||||
|
||||
auto image = make<ELF::Image>(file_or_error.value()->bytes());
|
||||
auto debug_info = make<Debug::DebugInfo>(*image);
|
||||
m_dynamic_library_cache.set(lib_path, CachedELF { file_or_error.release_value(), move(debug_info), move(image) });
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
MmapRegion const* Emulator::first_region_for_object(StringView name)
|
||||
{
|
||||
MmapRegion* ret = nullptr;
|
||||
mmu().for_each_region_of_type<MmapRegion>([&](auto& region) {
|
||||
if (region.lib_name() == name) {
|
||||
ret = ®ion;
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return ret;
|
||||
}
|
||||
|
||||
// FIXME: This disregards function inlining.
|
||||
Optional<Emulator::SymbolInfo> Emulator::symbol_at(FlatPtr address)
|
||||
{
|
||||
auto const* address_region = load_library_from_address(address);
|
||||
if (!address_region)
|
||||
return {};
|
||||
auto lib_name = address_region->lib_name();
|
||||
auto const* first_region = (lib_name.is_null() || lib_name.is_empty()) ? address_region : first_region_for_object(lib_name);
|
||||
VERIFY(first_region);
|
||||
auto lib_path = lib_name;
|
||||
if (FileSystem::looks_like_shared_library(lib_name)) {
|
||||
lib_path = ByteString::formatted("/usr/lib/{}", lib_name);
|
||||
}
|
||||
|
||||
auto it = m_dynamic_library_cache.find(lib_path);
|
||||
auto const& elf = it->value.debug_info->elf();
|
||||
auto symbol = elf.symbolicate(address - first_region->base());
|
||||
|
||||
auto source_position = it->value.debug_info->get_source_position(address - first_region->base());
|
||||
return { { lib_name, symbol, source_position } };
|
||||
}
|
||||
|
||||
ByteString Emulator::create_backtrace_line(FlatPtr address)
|
||||
{
|
||||
auto maybe_symbol = symbol_at(address);
|
||||
if (!maybe_symbol.has_value()) {
|
||||
return ByteString::formatted("=={}== {:p}", getpid(), address);
|
||||
}
|
||||
if (!maybe_symbol->source_position.has_value()) {
|
||||
return ByteString::formatted("=={}== {:p} [{}]: {}", getpid(), address, maybe_symbol->lib_name, maybe_symbol->symbol);
|
||||
}
|
||||
|
||||
auto const& source_position = maybe_symbol->source_position.value();
|
||||
return ByteString::formatted("=={}== {:p} [{}]: {} (\e[34;1m{}\e[0m:{})", getpid(), address, maybe_symbol->lib_name, maybe_symbol->symbol, LexicalPath::basename(source_position.file_path), source_position.line_number);
|
||||
}
|
||||
|
||||
void Emulator::dump_backtrace(Vector<FlatPtr> const& backtrace)
|
||||
{
|
||||
for (auto const& address : backtrace) {
|
||||
reportln("{}"sv, create_backtrace_line(address));
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::dump_backtrace()
|
||||
{
|
||||
dump_backtrace(raw_backtrace());
|
||||
}
|
||||
|
||||
void Emulator::emit_profile_sample(Stream& output)
|
||||
{
|
||||
if (!is_in_region_of_interest())
|
||||
return;
|
||||
StringBuilder builder;
|
||||
timeval tv {};
|
||||
gettimeofday(&tv, nullptr);
|
||||
builder.appendff(R"~(, {{"type": "sample", "pid": {}, "tid": {}, "timestamp": {}, "lost_samples": 0, "stack": [)~", getpid(), gettid(), tv.tv_sec * 1000 + tv.tv_usec / 1000);
|
||||
builder.join(',', raw_backtrace());
|
||||
builder.append("]}\n"sv);
|
||||
output.write_until_depleted(builder.string_view().bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
void Emulator::emit_profile_event(Stream& output, StringView event_name, ByteString const& contents)
|
||||
{
|
||||
StringBuilder builder;
|
||||
timeval tv {};
|
||||
gettimeofday(&tv, nullptr);
|
||||
builder.appendff(R"~(, {{"type": "{}", "pid": {}, "tid": {}, "timestamp": {}, "lost_samples": 0, "stack": [], {}}})~", event_name, getpid(), gettid(), tv.tv_sec * 1000 + tv.tv_usec / 1000, contents);
|
||||
builder.append('\n');
|
||||
output.write_until_depleted(builder.string_view().bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
ByteString Emulator::create_instruction_line(FlatPtr address, X86::Instruction const& insn)
|
||||
{
|
||||
auto symbol = symbol_at(address);
|
||||
if (!symbol.has_value() || !symbol->source_position.has_value())
|
||||
return ByteString::formatted("{:p}: {}", address, insn.to_byte_string(address));
|
||||
|
||||
return ByteString::formatted("{:p}: {} \e[34;1m{}\e[0m:{}", address, insn.to_byte_string(address), LexicalPath::basename(symbol->source_position->file_path), symbol->source_position.value().line_number);
|
||||
}
|
||||
|
||||
static void emulator_signal_handler(int signum, siginfo_t* signal_info, void* context)
|
||||
{
|
||||
Emulator::the().did_receive_signal(signum, { *signal_info, *reinterpret_cast<ucontext_t*>(context) });
|
||||
}
|
||||
|
||||
void Emulator::register_signal_handlers()
|
||||
{
|
||||
struct sigaction action {
|
||||
.sa_sigaction = emulator_signal_handler,
|
||||
.sa_mask = 0,
|
||||
.sa_flags = SA_SIGINFO,
|
||||
};
|
||||
sigemptyset(&action.sa_mask);
|
||||
|
||||
for (int signum = 0; signum < NSIG; ++signum)
|
||||
sigaction(signum, &action, nullptr);
|
||||
}
|
||||
|
||||
enum class DefaultSignalAction {
|
||||
Terminate,
|
||||
Ignore,
|
||||
DumpCore,
|
||||
Stop,
|
||||
Continue,
|
||||
};
|
||||
|
||||
static DefaultSignalAction default_signal_action(int signal)
|
||||
{
|
||||
VERIFY(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;
|
||||
}
|
||||
VERIFY_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;
|
||||
}
|
||||
VERIFY(signum != -1);
|
||||
m_pending_signals &= ~(1 << signum);
|
||||
|
||||
if (((1 << (signum - 1)) & m_signal_mask) != 0)
|
||||
return;
|
||||
|
||||
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"sv, getpid(), signum, strsignal(signum));
|
||||
dump_backtrace();
|
||||
m_shutdown = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (handler.handler == 1) {
|
||||
// SIG_IGN
|
||||
return;
|
||||
}
|
||||
|
||||
reportln("\n=={}== Got signal {} ({}), handler at {:p}"sv, getpid(), signum, strsignal(signum), handler.handler);
|
||||
|
||||
auto old_esp = m_cpu->esp().value();
|
||||
|
||||
auto signal_info = m_signal_data[signum];
|
||||
signal_info.context.uc_sigmask = m_signal_mask;
|
||||
signal_info.context.uc_stack = {
|
||||
.ss_sp = bit_cast<void*>(old_esp),
|
||||
.ss_flags = 0,
|
||||
.ss_size = 0,
|
||||
};
|
||||
signal_info.context.uc_mcontext = __mcontext {
|
||||
.eax = m_cpu->eax().value(),
|
||||
.ecx = m_cpu->ecx().value(),
|
||||
.edx = m_cpu->edx().value(),
|
||||
.ebx = m_cpu->ebx().value(),
|
||||
.esp = m_cpu->esp().value(),
|
||||
.ebp = m_cpu->ebp().value(),
|
||||
.esi = m_cpu->esi().value(),
|
||||
.edi = m_cpu->edi().value(),
|
||||
.eip = m_cpu->eip(),
|
||||
.eflags = m_cpu->eflags(),
|
||||
.cs = m_cpu->cs(),
|
||||
.ss = m_cpu->ss(),
|
||||
.ds = m_cpu->ds(),
|
||||
.es = m_cpu->es(),
|
||||
// ???
|
||||
.fs = 0,
|
||||
.gs = 0,
|
||||
};
|
||||
|
||||
// Align the stack to 16 bytes.
|
||||
// Note that we push some elements on to the stack before the return address,
|
||||
// so we need to account for this here.
|
||||
constexpr static FlatPtr elements_pushed_on_stack_before_handler_address = 1; // one slot for a saved register
|
||||
FlatPtr const extra_bytes_pushed_on_stack_before_handler_address = sizeof(ucontext_t) + sizeof(siginfo_t);
|
||||
FlatPtr stack_alignment = (old_esp - elements_pushed_on_stack_before_handler_address * sizeof(FlatPtr) + extra_bytes_pushed_on_stack_before_handler_address) % 16;
|
||||
// Also note that we have to skip the thread red-zone (if needed), so do that here.
|
||||
old_esp -= stack_alignment;
|
||||
|
||||
m_cpu->set_esp(shadow_wrap_with_taint_from(old_esp, m_cpu->esp()));
|
||||
|
||||
m_cpu->push32(shadow_wrap_as_initialized(0u)); // syscall return value slot
|
||||
|
||||
m_cpu->push_buffer(bit_cast<u8 const*>(&signal_info.context), sizeof(ucontext_t));
|
||||
auto pointer_to_ucontext = m_cpu->esp().value();
|
||||
|
||||
m_cpu->push_buffer(bit_cast<u8 const*>(&signal_info.signal_info), sizeof(siginfo_t));
|
||||
auto pointer_to_signal_info = m_cpu->esp().value();
|
||||
|
||||
// FPU state, leave a 512-byte gap. FIXME: Fill this in.
|
||||
m_cpu->set_esp({ m_cpu->esp().value() - 512, m_cpu->esp().shadow() });
|
||||
|
||||
// Leave one empty slot to align the stack for a handler call.
|
||||
m_cpu->push32(shadow_wrap_as_initialized(0u));
|
||||
m_cpu->push32(shadow_wrap_as_initialized(pointer_to_ucontext));
|
||||
m_cpu->push32(shadow_wrap_as_initialized(pointer_to_signal_info));
|
||||
m_cpu->push32(shadow_wrap_as_initialized(static_cast<u32>(signum)));
|
||||
|
||||
m_cpu->push32(shadow_wrap_as_initialized<u32>(handler.handler));
|
||||
|
||||
m_cpu->set_eip(m_signal_trampoline);
|
||||
}
|
||||
|
||||
// Make sure the compiler doesn't "optimize away" this function:
|
||||
static void signal_trampoline_dummy() __attribute__((used));
|
||||
NEVER_INLINE 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.
|
||||
constexpr static auto offset_to_first_register_slot = sizeof(__ucontext) + sizeof(siginfo) + 512 + 4 * sizeof(FlatPtr);
|
||||
asm(
|
||||
".intel_syntax noprefix\n"
|
||||
".globl asm_signal_trampoline\n"
|
||||
"asm_signal_trampoline:\n"
|
||||
// stack state: 0, ucontext, signal_info, (alignment = 16), fpu_state (alignment = 16), 0, ucontext*, siginfo*, signal, (alignment = 16), handler
|
||||
|
||||
// Pop the handler into ecx
|
||||
"pop ecx\n" // save handler
|
||||
// we have to save eax 'cause it might be the return value from a syscall
|
||||
"mov [esp+%P2], eax\n"
|
||||
// Note that the stack is currently aligned to 16 bytes as we popped the extra entries above.
|
||||
// and it's already setup to call the handler with the expected values on the stack.
|
||||
// call the signal handler
|
||||
"call ecx\n"
|
||||
// drop the 4 arguments
|
||||
"add esp, 16\n"
|
||||
// Current stack state is just saved_eax, ucontext, signal_info, fpu_state?.
|
||||
// syscall SC_sigreturn
|
||||
"mov eax, %P0\n"
|
||||
"int 0x82\n"
|
||||
".globl asm_signal_trampoline_end\n"
|
||||
"asm_signal_trampoline_end:\n"
|
||||
".att_syntax"
|
||||
:
|
||||
: "i"(Syscall::SC_sigreturn),
|
||||
"i"(offset_to_first_register_slot),
|
||||
"i"(offset_to_first_register_slot - sizeof(FlatPtr)));
|
||||
}
|
||||
|
||||
extern "C" void asm_signal_trampoline(void);
|
||||
extern "C" void asm_signal_trampoline_end(void);
|
||||
|
||||
void Emulator::setup_signal_trampoline()
|
||||
{
|
||||
m_range_allocator.reserve_user_range(VirtualAddress(signal_trampoline_location), 4096);
|
||||
auto trampoline_region = make<SimpleRegion>(signal_trampoline_location, 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));
|
||||
}
|
||||
|
||||
void Emulator::dump_regions() const
|
||||
{
|
||||
const_cast<SoftMMU&>(m_mmu).for_each_region([&](Region const& region) {
|
||||
reportln("{:p}-{:p} {:c}{:c}{:c} {} {}{}{} "sv,
|
||||
region.base(),
|
||||
region.end() - 1,
|
||||
region.is_readable() ? 'R' : '-',
|
||||
region.is_writable() ? 'W' : '-',
|
||||
region.is_executable() ? 'X' : '-',
|
||||
is<MmapRegion>(region) ? static_cast<MmapRegion const&>(region).name() : "",
|
||||
is<MmapRegion>(region) ? "(mmap) " : "",
|
||||
region.is_stack() ? "(stack) " : "",
|
||||
region.is_text() ? "(text) " : "");
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
bool Emulator::is_in_libsystem() const
|
||||
{
|
||||
return m_cpu->base_eip() >= m_libsystem_start && m_cpu->base_eip() < m_libsystem_end;
|
||||
}
|
||||
|
||||
bool Emulator::is_in_loader_code() const
|
||||
{
|
||||
if (!m_loader_text_base.has_value() || !m_loader_text_size.has_value())
|
||||
return false;
|
||||
return (m_cpu->base_eip() >= m_loader_text_base.value() && m_cpu->base_eip() < m_loader_text_base.value() + m_loader_text_size.value());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,307 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, sin-ack <sin-ack@protonmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MallocTracer.h"
|
||||
#include "RangeAllocator.h"
|
||||
#include "Report.h"
|
||||
#include "SoftMMU.h"
|
||||
#include <AK/Types.h>
|
||||
#include <LibCore/MappedFile.h>
|
||||
#include <LibDebug/DebugInfo.h>
|
||||
#include <LibELF/AuxiliaryVector.h>
|
||||
#include <LibELF/Image.h>
|
||||
#include <LibLine/Editor.h>
|
||||
#include <LibX86/Instruction.h>
|
||||
#include <signal.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class MallocTracer;
|
||||
class SoftCPU;
|
||||
|
||||
class Emulator {
|
||||
public:
|
||||
static Emulator& the();
|
||||
|
||||
Emulator(ByteString const& executable_path, Vector<StringView> const& arguments, Vector<ByteString> const& environment);
|
||||
|
||||
void set_profiling_details(bool should_dump_profile, size_t instruction_interval, Stream* profile_stream, Vector<NonnullOwnPtr<ByteString>>* profiler_strings, Vector<int>* profiler_string_id_map)
|
||||
{
|
||||
m_is_profiling = should_dump_profile;
|
||||
m_profile_instruction_interval = instruction_interval;
|
||||
m_profile_stream = profile_stream;
|
||||
m_profiler_strings = profiler_strings;
|
||||
m_profiler_string_id_map = profiler_string_id_map;
|
||||
}
|
||||
|
||||
void set_in_region_of_interest(bool value)
|
||||
{
|
||||
m_is_in_region_of_interest = value;
|
||||
}
|
||||
|
||||
Stream& profile_stream() { return *m_profile_stream; }
|
||||
Vector<NonnullOwnPtr<ByteString>>& profiler_strings() { return *m_profiler_strings; }
|
||||
Vector<int>& profiler_string_id_map() { return *m_profiler_string_id_map; }
|
||||
|
||||
bool is_profiling() const { return m_is_profiling; }
|
||||
bool is_in_region_of_interest() const { return m_is_in_region_of_interest; }
|
||||
size_t profile_instruction_interval() const { return m_profile_instruction_interval; }
|
||||
bool is_memory_auditing_suppressed() const { return m_is_memory_auditing_suppressed; }
|
||||
|
||||
bool load_elf();
|
||||
void dump_backtrace();
|
||||
void dump_backtrace(Vector<FlatPtr> const&);
|
||||
Vector<FlatPtr> raw_backtrace();
|
||||
|
||||
int exec();
|
||||
void handle_repl();
|
||||
u32 virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3);
|
||||
|
||||
SoftMMU& mmu() { return m_mmu; }
|
||||
|
||||
MallocTracer* malloc_tracer() { return m_malloc_tracer; }
|
||||
|
||||
bool is_in_loader_code() const;
|
||||
bool is_in_libsystem() const;
|
||||
|
||||
void pause()
|
||||
{
|
||||
m_steps_til_pause = 0;
|
||||
m_run_til_return = false;
|
||||
}
|
||||
ALWAYS_INLINE void return_callback(FlatPtr addr)
|
||||
{
|
||||
if (m_run_til_return) [[unlikely]] {
|
||||
if (addr == m_watched_addr)
|
||||
pause();
|
||||
}
|
||||
}
|
||||
ALWAYS_INLINE void call_callback(FlatPtr addr)
|
||||
{
|
||||
if (m_run_til_call) [[unlikely]] {
|
||||
if (addr == m_watched_addr)
|
||||
pause();
|
||||
}
|
||||
}
|
||||
|
||||
struct SignalInfo {
|
||||
siginfo_t signal_info;
|
||||
ucontext_t context;
|
||||
};
|
||||
void did_receive_signal(int signum, SignalInfo info, bool from_emulator = false)
|
||||
{
|
||||
if (!from_emulator && signum == SIGINT)
|
||||
return did_receive_sigint(signum);
|
||||
|
||||
m_pending_signals |= (1 << signum);
|
||||
m_signal_data[signum] = info;
|
||||
}
|
||||
|
||||
void did_receive_sigint(int)
|
||||
{
|
||||
if (m_steps_til_pause == 0)
|
||||
m_shutdown = true;
|
||||
else
|
||||
pause();
|
||||
}
|
||||
|
||||
struct SymbolInfo {
|
||||
ByteString lib_name;
|
||||
ByteString symbol;
|
||||
Optional<Debug::DebugInfo::SourcePosition> source_position;
|
||||
};
|
||||
|
||||
Optional<SymbolInfo> symbol_at(FlatPtr address);
|
||||
|
||||
void dump_regions() const;
|
||||
|
||||
private:
|
||||
const ByteString m_executable_path;
|
||||
Vector<StringView> const m_arguments;
|
||||
Vector<ByteString> const m_environment;
|
||||
|
||||
SoftMMU m_mmu;
|
||||
NonnullOwnPtr<SoftCPU> m_cpu;
|
||||
|
||||
OwnPtr<MallocTracer> m_malloc_tracer;
|
||||
|
||||
void setup_stack(Vector<ELF::AuxiliaryValue>);
|
||||
Vector<ELF::AuxiliaryValue> generate_auxiliary_vector(FlatPtr load_base, FlatPtr entry_eip, ByteString const& executable_path, int executable_fd) const;
|
||||
void register_signal_handlers();
|
||||
void setup_signal_trampoline();
|
||||
|
||||
void send_signal(int);
|
||||
|
||||
void emit_profile_sample(Stream&);
|
||||
void emit_profile_event(Stream&, StringView event_name, ByteString const& contents);
|
||||
|
||||
int virt$accept4(FlatPtr);
|
||||
u32 virt$allocate_tls(FlatPtr, size_t);
|
||||
int virt$anon_create(size_t, int);
|
||||
int virt$bind(int sockfd, FlatPtr address, socklen_t address_length);
|
||||
u32 virt$bindmount(u32 params_addr);
|
||||
int virt$chdir(FlatPtr, size_t);
|
||||
int virt$chmod(FlatPtr);
|
||||
int virt$chown(FlatPtr);
|
||||
int virt$clock_gettime(int, FlatPtr);
|
||||
int virt$clock_nanosleep(FlatPtr);
|
||||
int virt$clock_settime(uint32_t clock_id, FlatPtr user_ts);
|
||||
int virt$close(int);
|
||||
int virt$connect(int sockfd, FlatPtr address, socklen_t address_size);
|
||||
int virt$create_inode_watcher(unsigned);
|
||||
int virt$dbgputstr(FlatPtr characters, int length);
|
||||
int virt$disown(pid_t);
|
||||
int virt$dup2(int, int);
|
||||
int virt$emuctl(FlatPtr, FlatPtr, FlatPtr);
|
||||
int virt$execve(FlatPtr);
|
||||
void virt$exit(int);
|
||||
int virt$faccessat(FlatPtr);
|
||||
int virt$fchmod(int, mode_t);
|
||||
int virt$fchown(int, uid_t, gid_t);
|
||||
u32 virt$fcntl(int fd, int, u32);
|
||||
int virt$fork();
|
||||
u32 virt$fsopen(u32);
|
||||
u32 virt$fsmount(u32);
|
||||
int virt$fstat(int, FlatPtr);
|
||||
int virt$ftruncate(int fd, FlatPtr length_addr);
|
||||
int virt$futex(FlatPtr);
|
||||
int virt$get_dir_entries(int fd, FlatPtr buffer, ssize_t);
|
||||
int virt$get_stack_bounds(FlatPtr, FlatPtr);
|
||||
int virt$getcwd(FlatPtr buffer, size_t buffer_size);
|
||||
gid_t virt$getegid();
|
||||
uid_t virt$geteuid();
|
||||
gid_t virt$getgid();
|
||||
int virt$getgroups(ssize_t count, FlatPtr);
|
||||
int virt$gethostname(FlatPtr, ssize_t);
|
||||
int virt$getpeername(FlatPtr);
|
||||
int virt$getpgid(pid_t);
|
||||
int virt$getpgrp();
|
||||
u32 virt$getpid();
|
||||
pid_t virt$getppid();
|
||||
ssize_t virt$getrandom(FlatPtr buffer, size_t buffer_size, unsigned int flags);
|
||||
int virt$getsid(pid_t);
|
||||
int virt$getsockname(FlatPtr);
|
||||
int virt$getsockopt(FlatPtr);
|
||||
u32 virt$gettid();
|
||||
uid_t virt$getuid();
|
||||
int virt$inode_watcher_add_watch(FlatPtr);
|
||||
int virt$inode_watcher_remove_watch(int, int);
|
||||
int virt$ioctl(int fd, unsigned, FlatPtr);
|
||||
int virt$kill(pid_t, int);
|
||||
int virt$killpg(int pgrp, int sig);
|
||||
int virt$listen(int, int);
|
||||
int virt$lseek(int fd, FlatPtr offset_addr, int whence);
|
||||
u32 virt$madvise(FlatPtr, size_t, int);
|
||||
int virt$mkdir(FlatPtr path, size_t path_length, mode_t mode);
|
||||
u32 virt$mmap(u32);
|
||||
u32 virt$mprotect(FlatPtr, size_t, int);
|
||||
FlatPtr virt$mremap(FlatPtr);
|
||||
int virt$annotate_mapping(FlatPtr);
|
||||
u32 virt$munmap(FlatPtr address, size_t size);
|
||||
u32 virt$open(u32);
|
||||
FlatPtr virt$perf_event(int type, FlatPtr arg1, FlatPtr arg2);
|
||||
FlatPtr virt$perf_register_string(FlatPtr, size_t);
|
||||
int virt$pipe(FlatPtr pipefd, int flags);
|
||||
u32 virt$pledge(u32);
|
||||
int virt$poll(FlatPtr);
|
||||
int virt$profiling_disable(pid_t);
|
||||
int virt$profiling_enable(pid_t, u64);
|
||||
int virt$purge(int mode);
|
||||
u32 virt$read(int, FlatPtr, ssize_t);
|
||||
int virt$readlink(FlatPtr);
|
||||
int virt$realpath(FlatPtr);
|
||||
int virt$recvfd(int, int);
|
||||
int virt$recvmsg(int sockfd, FlatPtr msg_addr, int flags);
|
||||
int virt$rename(FlatPtr address);
|
||||
u32 virt$remount(u32);
|
||||
int virt$rmdir(FlatPtr path, size_t path_length);
|
||||
int virt$scheduler_get_parameters(FlatPtr);
|
||||
int virt$scheduler_set_parameters(FlatPtr);
|
||||
int virt$sendfd(int, int);
|
||||
int virt$sendmsg(int sockfd, FlatPtr msg_addr, int flags);
|
||||
int virt$set_mmap_name(FlatPtr);
|
||||
int virt$set_process_name(FlatPtr buffer, int size);
|
||||
int virt$setgid(gid_t);
|
||||
int virt$setgroups(ssize_t count, FlatPtr);
|
||||
int virt$setpgid(pid_t pid, pid_t pgid);
|
||||
pid_t virt$setsid();
|
||||
int virt$setsockopt(FlatPtr);
|
||||
int virt$setuid(uid_t);
|
||||
int virt$shutdown(int sockfd, int how);
|
||||
int virt$sigaction(int, FlatPtr, FlatPtr);
|
||||
int virt$sigprocmask(int how, FlatPtr set, FlatPtr old_set);
|
||||
int virt$sigreturn();
|
||||
int virt$socket(int, int, int);
|
||||
int virt$stat(FlatPtr);
|
||||
int virt$symlink(FlatPtr address);
|
||||
void virt$sync();
|
||||
u32 virt$sysconf(u32 name);
|
||||
mode_t virt$umask(mode_t);
|
||||
int virt$uname(FlatPtr params_addr);
|
||||
int virt$unlink(FlatPtr path, size_t path_length);
|
||||
u32 virt$unveil(u32);
|
||||
int virt$waitid(FlatPtr);
|
||||
u32 virt$write(int, FlatPtr, ssize_t);
|
||||
|
||||
void dispatch_one_pending_signal();
|
||||
MmapRegion const* find_text_region(FlatPtr address);
|
||||
MmapRegion const* load_library_from_address(FlatPtr address);
|
||||
MmapRegion const* first_region_for_object(StringView name);
|
||||
ByteString create_backtrace_line(FlatPtr address);
|
||||
ByteString create_instruction_line(FlatPtr address, X86::Instruction const& insn);
|
||||
|
||||
bool m_shutdown { false };
|
||||
int m_exit_status { 0 };
|
||||
|
||||
i64 m_steps_til_pause { -1 };
|
||||
bool m_run_til_return { false };
|
||||
bool m_run_til_call { false };
|
||||
FlatPtr m_watched_addr { 0 };
|
||||
RefPtr<Line::Editor> m_editor;
|
||||
|
||||
FlatPtr m_libsystem_start { 0 };
|
||||
FlatPtr m_libsystem_end { 0 };
|
||||
|
||||
sigset_t m_pending_signals { 0 };
|
||||
sigset_t m_signal_mask { 0 };
|
||||
Array<SignalInfo, NSIG> m_signal_data;
|
||||
|
||||
struct SignalHandlerInfo {
|
||||
FlatPtr handler { 0 };
|
||||
sigset_t mask { 0 };
|
||||
int flags { 0 };
|
||||
};
|
||||
SignalHandlerInfo m_signal_handler[NSIG];
|
||||
|
||||
FlatPtr m_signal_trampoline { 0 };
|
||||
Optional<FlatPtr> m_loader_text_base;
|
||||
Optional<size_t> m_loader_text_size;
|
||||
|
||||
struct CachedELF {
|
||||
NonnullOwnPtr<Core::MappedFile> mapped_file;
|
||||
NonnullOwnPtr<Debug::DebugInfo> debug_info;
|
||||
NonnullOwnPtr<ELF::Image> image;
|
||||
};
|
||||
|
||||
HashMap<ByteString, CachedELF> m_dynamic_library_cache;
|
||||
|
||||
RangeAllocator m_range_allocator;
|
||||
|
||||
Stream* m_profile_stream { nullptr };
|
||||
Vector<int>* m_profiler_string_id_map { nullptr };
|
||||
Vector<NonnullOwnPtr<ByteString>>* m_profiler_strings { nullptr };
|
||||
|
||||
bool m_is_profiling { false };
|
||||
size_t m_profile_instruction_interval { 0 };
|
||||
bool m_is_in_region_of_interest { false };
|
||||
bool m_is_memory_auditing_suppressed { false };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Ali Mohammad Pur <mpfard@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/StdLibExtras.h>
|
||||
#include <serenity.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
enum class Command {
|
||||
MarkROIStart = 5,
|
||||
MarkROIEnd = 6,
|
||||
};
|
||||
|
||||
inline void control(Command command)
|
||||
{
|
||||
emuctl(to_underlying(command), 0, 0);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,430 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "MallocTracer.h"
|
||||
#include "Emulator.h"
|
||||
#include "MmapRegion.h"
|
||||
#include <AK/Debug.h>
|
||||
#include <AK/TemporaryChange.h>
|
||||
#include <mallocdefs.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
MallocTracer::MallocTracer(Emulator& emulator)
|
||||
: m_emulator(emulator)
|
||||
{
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
inline void MallocTracer::for_each_mallocation(Callback callback) const
|
||||
{
|
||||
m_emulator.mmu().for_each_region([&](auto& region) {
|
||||
if (is<MmapRegion>(region) && static_cast<const MmapRegion&>(region).is_malloc_block()) {
|
||||
auto* malloc_data = static_cast<MmapRegion&>(region).malloc_metadata();
|
||||
for (auto& mallocation : malloc_data->mallocations) {
|
||||
if (mallocation.used && callback(mallocation) == IterationDecision::Break)
|
||||
return IterationDecision::Break;
|
||||
}
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
}
|
||||
|
||||
void MallocTracer::update_metadata(MmapRegion& mmap_region, size_t chunk_size)
|
||||
{
|
||||
mmap_region.set_malloc_metadata({},
|
||||
adopt_own(*new MallocRegionMetadata {
|
||||
.region = mmap_region,
|
||||
.address = mmap_region.base(),
|
||||
.chunk_size = chunk_size,
|
||||
.mallocations = {},
|
||||
}));
|
||||
auto& malloc_data = *mmap_region.malloc_metadata();
|
||||
|
||||
bool is_chunked_block = malloc_data.chunk_size <= size_classes[num_size_classes - 1];
|
||||
if (is_chunked_block)
|
||||
malloc_data.mallocations.resize((ChunkedBlock::block_size - sizeof(ChunkedBlock)) / malloc_data.chunk_size);
|
||||
else
|
||||
malloc_data.mallocations.resize(1);
|
||||
|
||||
// Mark the containing mmap region as a malloc block!
|
||||
mmap_region.set_malloc(true);
|
||||
}
|
||||
|
||||
void MallocTracer::target_did_malloc(Badge<Emulator>, FlatPtr address, size_t size)
|
||||
{
|
||||
if (m_emulator.is_in_loader_code())
|
||||
return;
|
||||
auto* region = m_emulator.mmu().find_region({ 0x23, address });
|
||||
VERIFY(region);
|
||||
auto& mmap_region = verify_cast<MmapRegion>(*region);
|
||||
|
||||
auto* shadow_bits = mmap_region.shadow_data() + address - mmap_region.base();
|
||||
memset(shadow_bits, 0, size);
|
||||
|
||||
if (auto* existing_mallocation = find_mallocation(address)) {
|
||||
VERIFY(existing_mallocation->freed);
|
||||
existing_mallocation->size = size;
|
||||
existing_mallocation->freed = false;
|
||||
existing_mallocation->malloc_backtrace = m_emulator.raw_backtrace();
|
||||
existing_mallocation->free_backtrace.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mmap_region.is_malloc_block()) {
|
||||
auto chunk_size = mmap_region.read32(offsetof(CommonHeader, m_size)).value();
|
||||
update_metadata(mmap_region, chunk_size);
|
||||
}
|
||||
auto* mallocation = mmap_region.malloc_metadata()->mallocation_for_address(address);
|
||||
VERIFY(mallocation);
|
||||
*mallocation = { address, size, true, false, m_emulator.raw_backtrace(), Vector<FlatPtr>() };
|
||||
}
|
||||
|
||||
void MallocTracer::target_did_change_chunk_size(Badge<Emulator>, FlatPtr block, size_t chunk_size)
|
||||
{
|
||||
if (m_emulator.is_in_loader_code())
|
||||
return;
|
||||
auto* region = m_emulator.mmu().find_region({ 0x23, block });
|
||||
VERIFY(region);
|
||||
auto& mmap_region = verify_cast<MmapRegion>(*region);
|
||||
update_metadata(mmap_region, chunk_size);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Mallocation* MallocRegionMetadata::mallocation_for_address(FlatPtr address) const
|
||||
{
|
||||
auto index = chunk_index_for_address(address);
|
||||
if (!index.has_value())
|
||||
return nullptr;
|
||||
return &const_cast<Mallocation&>(this->mallocations[index.value()]);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE Optional<size_t> MallocRegionMetadata::chunk_index_for_address(FlatPtr address) const
|
||||
{
|
||||
bool is_chunked_block = chunk_size <= size_classes[num_size_classes - 1];
|
||||
if (!is_chunked_block) {
|
||||
// This is a BigAllocationBlock
|
||||
return 0;
|
||||
}
|
||||
auto offset_into_block = address - this->address;
|
||||
if (offset_into_block < sizeof(ChunkedBlock))
|
||||
return 0;
|
||||
auto chunk_offset = offset_into_block - sizeof(ChunkedBlock);
|
||||
auto chunk_index = chunk_offset / this->chunk_size;
|
||||
if (chunk_index >= mallocations.size())
|
||||
return {};
|
||||
return chunk_index;
|
||||
}
|
||||
|
||||
void MallocTracer::target_did_free(Badge<Emulator>, FlatPtr address)
|
||||
{
|
||||
if (!address)
|
||||
return;
|
||||
if (m_emulator.is_in_loader_code())
|
||||
return;
|
||||
|
||||
if (auto* mallocation = find_mallocation(address)) {
|
||||
if (mallocation->freed) {
|
||||
reportln("\n=={}== \033[31;1mDouble free()\033[0m, {:p}"sv, getpid(), address);
|
||||
reportln("=={}== Address {} has already been passed to free()"sv, getpid(), address);
|
||||
m_emulator.dump_backtrace();
|
||||
} else {
|
||||
mallocation->freed = true;
|
||||
mallocation->free_backtrace = m_emulator.raw_backtrace();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
reportln("\n=={}== \033[31;1mInvalid free()\033[0m, {:p}"sv, getpid(), address);
|
||||
reportln("=={}== Address {} has never been returned by malloc()"sv, getpid(), address);
|
||||
m_emulator.dump_backtrace();
|
||||
}
|
||||
|
||||
void MallocTracer::target_did_realloc(Badge<Emulator>, FlatPtr address, size_t size)
|
||||
{
|
||||
if (m_emulator.is_in_loader_code())
|
||||
return;
|
||||
auto* region = m_emulator.mmu().find_region({ 0x23, address });
|
||||
VERIFY(region);
|
||||
auto& mmap_region = verify_cast<MmapRegion>(*region);
|
||||
|
||||
VERIFY(mmap_region.is_malloc_block());
|
||||
|
||||
auto* existing_mallocation = find_mallocation(address);
|
||||
VERIFY(existing_mallocation);
|
||||
VERIFY(!existing_mallocation->freed);
|
||||
|
||||
size_t old_size = existing_mallocation->size;
|
||||
|
||||
auto* shadow_bits = mmap_region.shadow_data() + address - mmap_region.base();
|
||||
|
||||
if (size > old_size) {
|
||||
memset(shadow_bits + old_size, 1, size - old_size);
|
||||
} else {
|
||||
memset(shadow_bits + size, 1, old_size - size);
|
||||
}
|
||||
|
||||
existing_mallocation->size = size;
|
||||
// FIXME: Should we track malloc/realloc backtrace separately perhaps?
|
||||
existing_mallocation->malloc_backtrace = m_emulator.raw_backtrace();
|
||||
}
|
||||
|
||||
Mallocation* MallocTracer::find_mallocation(FlatPtr address)
|
||||
{
|
||||
auto* region = m_emulator.mmu().find_region({ 0x23, address });
|
||||
if (!region)
|
||||
return nullptr;
|
||||
return find_mallocation(*region, address);
|
||||
}
|
||||
|
||||
Mallocation* MallocTracer::find_mallocation_before(FlatPtr address)
|
||||
{
|
||||
Mallocation* found_mallocation = nullptr;
|
||||
for_each_mallocation([&](auto& mallocation) {
|
||||
if (mallocation.address >= address)
|
||||
return IterationDecision::Continue;
|
||||
if (!found_mallocation || (mallocation.address > found_mallocation->address))
|
||||
found_mallocation = const_cast<Mallocation*>(&mallocation);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return found_mallocation;
|
||||
}
|
||||
|
||||
Mallocation* MallocTracer::find_mallocation_after(FlatPtr address)
|
||||
{
|
||||
Mallocation* found_mallocation = nullptr;
|
||||
for_each_mallocation([&](auto& mallocation) {
|
||||
if (mallocation.address <= address)
|
||||
return IterationDecision::Continue;
|
||||
if (!found_mallocation || (mallocation.address < found_mallocation->address))
|
||||
found_mallocation = const_cast<Mallocation*>(&mallocation);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
return found_mallocation;
|
||||
}
|
||||
|
||||
void MallocTracer::audit_read(Region const& region, FlatPtr address, size_t size)
|
||||
{
|
||||
if (!m_auditing_enabled)
|
||||
return;
|
||||
|
||||
if (m_emulator.is_memory_auditing_suppressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_emulator.is_in_libsystem()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_emulator.is_in_loader_code()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* mallocation = find_mallocation(region, address);
|
||||
|
||||
if (!mallocation) {
|
||||
reportln("\n=={}== \033[31;1mHeap buffer overflow\033[0m, invalid {}-byte read at address {:p}"sv, getpid(), size, address);
|
||||
m_emulator.dump_backtrace();
|
||||
auto* mallocation_before = find_mallocation_before(address);
|
||||
auto* mallocation_after = find_mallocation_after(address);
|
||||
size_t distance_to_mallocation_before = mallocation_before ? (address - mallocation_before->address - mallocation_before->size) : 0;
|
||||
size_t distance_to_mallocation_after = mallocation_after ? (mallocation_after->address - address) : 0;
|
||||
if (mallocation_before && (!mallocation_after || distance_to_mallocation_before < distance_to_mallocation_after)) {
|
||||
reportln("=={}== Address is {} byte(s) after block of size {}, identity {:p}, allocated at:"sv, getpid(), distance_to_mallocation_before, mallocation_before->size, mallocation_before->address);
|
||||
m_emulator.dump_backtrace(mallocation_before->malloc_backtrace);
|
||||
return;
|
||||
}
|
||||
if (mallocation_after && (!mallocation_before || distance_to_mallocation_after < distance_to_mallocation_before)) {
|
||||
reportln("=={}== Address is {} byte(s) before block of size {}, identity {:p}, allocated at:"sv, getpid(), distance_to_mallocation_after, mallocation_after->size, mallocation_after->address);
|
||||
m_emulator.dump_backtrace(mallocation_after->malloc_backtrace);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t offset_into_mallocation = address - mallocation->address;
|
||||
|
||||
if (mallocation->freed) {
|
||||
reportln("\n=={}== \033[31;1mUse-after-free\033[0m, invalid {}-byte read at address {:p}"sv, getpid(), size, address);
|
||||
m_emulator.dump_backtrace();
|
||||
reportln("=={}== Address is {} byte(s) into block of size {}, allocated at:"sv, getpid(), offset_into_mallocation, mallocation->size);
|
||||
m_emulator.dump_backtrace(mallocation->malloc_backtrace);
|
||||
reportln("=={}== Later freed at:"sv, getpid());
|
||||
m_emulator.dump_backtrace(mallocation->free_backtrace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MallocTracer::audit_write(Region const& region, FlatPtr address, size_t size)
|
||||
{
|
||||
if (!m_auditing_enabled)
|
||||
return;
|
||||
|
||||
if (m_emulator.is_memory_auditing_suppressed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_emulator.is_in_loader_code()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto* mallocation = find_mallocation(region, address);
|
||||
if (!mallocation) {
|
||||
reportln("\n=={}== \033[31;1mHeap buffer overflow\033[0m, invalid {}-byte write at address {:p}"sv, getpid(), size, address);
|
||||
m_emulator.dump_backtrace();
|
||||
auto* mallocation_before = find_mallocation_before(address);
|
||||
auto* mallocation_after = find_mallocation_after(address);
|
||||
size_t distance_to_mallocation_before = mallocation_before ? (address - mallocation_before->address - mallocation_before->size) : 0;
|
||||
size_t distance_to_mallocation_after = mallocation_after ? (mallocation_after->address - address) : 0;
|
||||
if (mallocation_before && (!mallocation_after || distance_to_mallocation_before < distance_to_mallocation_after)) {
|
||||
reportln("=={}== Address is {} byte(s) after block of size {}, identity {:p}, allocated at:"sv, getpid(), distance_to_mallocation_before, mallocation_before->size, mallocation_before->address);
|
||||
m_emulator.dump_backtrace(mallocation_before->malloc_backtrace);
|
||||
return;
|
||||
}
|
||||
if (mallocation_after && (!mallocation_before || distance_to_mallocation_after < distance_to_mallocation_before)) {
|
||||
reportln("=={}== Address is {} byte(s) before block of size {}, identity {:p}, allocated at:"sv, getpid(), distance_to_mallocation_after, mallocation_after->size, mallocation_after->address);
|
||||
m_emulator.dump_backtrace(mallocation_after->malloc_backtrace);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t offset_into_mallocation = address - mallocation->address;
|
||||
|
||||
if (mallocation->freed) {
|
||||
reportln("\n=={}== \033[31;1mUse-after-free\033[0m, invalid {}-byte write at address {:p}"sv, getpid(), size, address);
|
||||
m_emulator.dump_backtrace();
|
||||
reportln("=={}== Address is {} byte(s) into block of size {}, allocated at:"sv, getpid(), offset_into_mallocation, mallocation->size);
|
||||
m_emulator.dump_backtrace(mallocation->malloc_backtrace);
|
||||
reportln("=={}== Later freed at:"sv, getpid());
|
||||
m_emulator.dump_backtrace(mallocation->free_backtrace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MallocTracer::populate_memory_graph()
|
||||
{
|
||||
// Create Node for each live Mallocation
|
||||
for_each_mallocation([&](auto& mallocation) {
|
||||
if (mallocation.freed)
|
||||
return IterationDecision::Continue;
|
||||
m_memory_graph.set(mallocation.address, {});
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// Find pointers from each memory region to another
|
||||
for_each_mallocation([&](auto& mallocation) {
|
||||
if (mallocation.freed)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
size_t pointers_in_mallocation = mallocation.size / sizeof(u32);
|
||||
|
||||
auto& edges_from_mallocation = m_memory_graph.find(mallocation.address)->value;
|
||||
|
||||
for (size_t i = 0; i < pointers_in_mallocation; ++i) {
|
||||
auto value = m_emulator.mmu().read32({ 0x23, mallocation.address + i * sizeof(u32) });
|
||||
auto other_address = value.value();
|
||||
if (!value.is_uninitialized() && m_memory_graph.contains(value.value())) {
|
||||
if constexpr (REACHABLE_DEBUG)
|
||||
reportln("region/mallocation {:p} is reachable from other mallocation {:p}"sv, other_address, mallocation.address);
|
||||
edges_from_mallocation.edges_from_node.append(other_address);
|
||||
}
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// Find mallocations that are pointed to by other regions
|
||||
Vector<FlatPtr> reachable_mallocations = {};
|
||||
m_emulator.mmu().for_each_region([&](auto& region) {
|
||||
// Skip the stack
|
||||
if (region.is_stack())
|
||||
return IterationDecision::Continue;
|
||||
if (region.is_text())
|
||||
return IterationDecision::Continue;
|
||||
if (!region.is_readable())
|
||||
return IterationDecision::Continue;
|
||||
// Skip malloc blocks
|
||||
if (is<MmapRegion>(region) && static_cast<const MmapRegion&>(region).is_malloc_block())
|
||||
return IterationDecision::Continue;
|
||||
|
||||
size_t pointers_in_region = region.size() / sizeof(u32);
|
||||
|
||||
for (size_t i = 0; i < pointers_in_region; ++i) {
|
||||
auto value = region.read32(i * sizeof(u32));
|
||||
auto other_address = value.value();
|
||||
if (!value.is_uninitialized() && m_memory_graph.contains(value.value())) {
|
||||
if constexpr (REACHABLE_DEBUG)
|
||||
reportln("region/mallocation {:p} is reachable from region {:p}-{:p}"sv, other_address, region.base(), region.end() - 1);
|
||||
m_memory_graph.find(other_address)->value.is_reachable = true;
|
||||
reachable_mallocations.append(other_address);
|
||||
}
|
||||
}
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
// Propagate reachability
|
||||
// There are probably better ways to do that
|
||||
Vector<FlatPtr> visited = {};
|
||||
for (size_t i = 0; i < reachable_mallocations.size(); ++i) {
|
||||
auto reachable = reachable_mallocations.at(i);
|
||||
if (visited.contains_slow(reachable))
|
||||
continue;
|
||||
visited.append(reachable);
|
||||
auto& mallocation_node = m_memory_graph.find(reachable)->value;
|
||||
|
||||
if (!mallocation_node.is_reachable)
|
||||
mallocation_node.is_reachable = true;
|
||||
|
||||
for (auto& edge : mallocation_node.edges_from_node) {
|
||||
reachable_mallocations.append(edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MallocTracer::dump_memory_graph()
|
||||
{
|
||||
for (auto& key : m_memory_graph.keys()) {
|
||||
auto value = m_memory_graph.find(key)->value;
|
||||
dbgln("Block {:p} [{}reachable] ({} edges)", key, !value.is_reachable ? "not " : "", value.edges_from_node.size());
|
||||
for (auto& edge : value.edges_from_node) {
|
||||
dbgln(" -> {:p}", edge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MallocTracer::dump_leak_report()
|
||||
{
|
||||
TemporaryChange change(m_auditing_enabled, false);
|
||||
|
||||
size_t bytes_leaked = 0;
|
||||
size_t leaks_found = 0;
|
||||
|
||||
populate_memory_graph();
|
||||
|
||||
if constexpr (REACHABLE_DEBUG)
|
||||
dump_memory_graph();
|
||||
|
||||
for_each_mallocation([&](auto& mallocation) {
|
||||
if (mallocation.freed)
|
||||
return IterationDecision::Continue;
|
||||
|
||||
auto& value = m_memory_graph.find(mallocation.address)->value;
|
||||
|
||||
if (value.is_reachable)
|
||||
return IterationDecision::Continue;
|
||||
++leaks_found;
|
||||
bytes_leaked += mallocation.size;
|
||||
reportln("\n=={}== \033[31;1mLeak\033[0m, {}-byte allocation at address {:p}"sv, getpid(), mallocation.size, mallocation.address);
|
||||
m_emulator.dump_backtrace(mallocation.malloc_backtrace);
|
||||
return IterationDecision::Continue;
|
||||
});
|
||||
|
||||
if (!leaks_found)
|
||||
reportln("\n=={}== \033[32;1mNo leaks found!\033[0m"sv, getpid());
|
||||
else
|
||||
reportln("\n=={}== \033[31;1m{} leak(s) found: {} byte(s) leaked\033[0m"sv, getpid(), leaks_found, bytes_leaked);
|
||||
}
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MmapRegion.h"
|
||||
#include "SoftMMU.h"
|
||||
#include <AK/Badge.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class Emulator;
|
||||
class SoftCPU;
|
||||
|
||||
struct GraphNode {
|
||||
Vector<FlatPtr> edges_from_node {};
|
||||
|
||||
bool is_reachable { false };
|
||||
};
|
||||
|
||||
using MemoryGraph = HashMap<FlatPtr, GraphNode>;
|
||||
|
||||
struct Mallocation {
|
||||
bool contains(FlatPtr a) const
|
||||
{
|
||||
return a >= address && a < (address + size);
|
||||
}
|
||||
|
||||
FlatPtr address { 0 };
|
||||
size_t size { 0 };
|
||||
bool used { false };
|
||||
bool freed { false };
|
||||
|
||||
Vector<FlatPtr> malloc_backtrace;
|
||||
Vector<FlatPtr> free_backtrace;
|
||||
};
|
||||
|
||||
class MallocRegionMetadata {
|
||||
public:
|
||||
MmapRegion& region;
|
||||
FlatPtr address { 0 };
|
||||
size_t chunk_size { 0 };
|
||||
|
||||
Optional<size_t> chunk_index_for_address(FlatPtr) const;
|
||||
Mallocation* mallocation_for_address(FlatPtr) const;
|
||||
|
||||
Vector<Mallocation> mallocations;
|
||||
};
|
||||
|
||||
class MallocTracer {
|
||||
public:
|
||||
explicit MallocTracer(Emulator&);
|
||||
|
||||
void target_did_malloc(Badge<Emulator>, FlatPtr address, size_t);
|
||||
void target_did_free(Badge<Emulator>, FlatPtr address);
|
||||
void target_did_realloc(Badge<Emulator>, FlatPtr address, size_t);
|
||||
void target_did_change_chunk_size(Badge<Emulator>, FlatPtr, size_t);
|
||||
|
||||
void audit_read(Region const&, FlatPtr address, size_t);
|
||||
void audit_write(Region const&, FlatPtr address, size_t);
|
||||
|
||||
void dump_leak_report();
|
||||
|
||||
private:
|
||||
template<typename Callback>
|
||||
void for_each_mallocation(Callback callback) const;
|
||||
|
||||
Mallocation* find_mallocation(Region const&, FlatPtr);
|
||||
Mallocation* find_mallocation(FlatPtr);
|
||||
Mallocation* find_mallocation_before(FlatPtr);
|
||||
Mallocation* find_mallocation_after(FlatPtr);
|
||||
|
||||
void dump_memory_graph();
|
||||
void populate_memory_graph();
|
||||
|
||||
void update_metadata(MmapRegion& mmap_region, size_t chunk_size);
|
||||
|
||||
Emulator& m_emulator;
|
||||
|
||||
MemoryGraph m_memory_graph {};
|
||||
|
||||
bool m_auditing_enabled { true };
|
||||
};
|
||||
|
||||
ALWAYS_INLINE Mallocation* MallocTracer::find_mallocation(Region const& region, FlatPtr address)
|
||||
{
|
||||
if (!is<MmapRegion>(region))
|
||||
return nullptr;
|
||||
if (!static_cast<MmapRegion const&>(region).is_malloc_block())
|
||||
return nullptr;
|
||||
auto* malloc_data = static_cast<MmapRegion&>(const_cast<Region&>(region)).malloc_metadata();
|
||||
if (!malloc_data)
|
||||
return nullptr;
|
||||
auto* mallocation = malloc_data->mallocation_for_address(address);
|
||||
if (!mallocation)
|
||||
return nullptr;
|
||||
if (!mallocation->used)
|
||||
return nullptr;
|
||||
if (!mallocation->contains(address))
|
||||
return nullptr;
|
||||
return mallocation;
|
||||
}
|
||||
}
|
|
@ -1,327 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "MmapRegion.h"
|
||||
#include "Emulator.h"
|
||||
#include <AK/ByteReader.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
static void* mmap_initialized(size_t bytes, char initial_value, char const* name)
|
||||
{
|
||||
auto* ptr = mmap_with_name(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, 0, 0, name);
|
||||
VERIFY(ptr != MAP_FAILED);
|
||||
memset(ptr, initial_value, bytes);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void free_pages(void* ptr, size_t bytes)
|
||||
{
|
||||
int rc = munmap(ptr, bytes);
|
||||
VERIFY(rc == 0);
|
||||
}
|
||||
|
||||
NonnullOwnPtr<MmapRegion> MmapRegion::create_anonymous(u32 base, u32 size, u32 prot, ByteString name)
|
||||
{
|
||||
auto* data = (u8*)mmap_initialized(size, 0, ByteString::formatted("(UE) {}", name).characters());
|
||||
auto* shadow_data = (u8*)mmap_initialized(size, 1, "MmapRegion ShadowData");
|
||||
auto region = adopt_own(*new MmapRegion(base, size, prot, data, shadow_data));
|
||||
region->m_name = move(name);
|
||||
return region;
|
||||
}
|
||||
|
||||
NonnullOwnPtr<MmapRegion> MmapRegion::create_file_backed(u32 base, u32 size, u32 prot, int flags, int fd, off_t offset, ByteString name)
|
||||
{
|
||||
// Since we put the memory to an arbitrary location, do not pass MAP_FIXED and MAP_FIXED_NOREPLACE to the Kernel.
|
||||
auto real_flags = flags & ~(MAP_FIXED | MAP_FIXED_NOREPLACE);
|
||||
auto* data = (u8*)mmap_with_name(nullptr, size, prot, real_flags, fd, offset, name.is_empty() ? nullptr : ByteString::formatted("(UE) {}", name).characters());
|
||||
VERIFY(data != MAP_FAILED);
|
||||
auto* shadow_data = (u8*)mmap_initialized(size, 1, "MmapRegion ShadowData");
|
||||
auto region = adopt_own(*new MmapRegion(base, size, prot, data, shadow_data));
|
||||
region->m_file_backed = true;
|
||||
region->m_name = move(name);
|
||||
return region;
|
||||
}
|
||||
|
||||
MmapRegion::MmapRegion(u32 base, u32 size, int prot, u8* data, u8* shadow_data)
|
||||
: Region(base, size, true)
|
||||
, m_data(data)
|
||||
, m_shadow_data(shadow_data)
|
||||
{
|
||||
set_prot(prot);
|
||||
}
|
||||
|
||||
MmapRegion::~MmapRegion()
|
||||
{
|
||||
free_pages(m_data, size());
|
||||
free_pages(m_shadow_data, size());
|
||||
}
|
||||
|
||||
ValueWithShadow<u8> MmapRegion::read8(FlatPtr offset)
|
||||
{
|
||||
if (!is_readable()) {
|
||||
reportln("8-bit read from unreadable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_read(*this, base() + offset, 1);
|
||||
}
|
||||
|
||||
VERIFY(offset < size());
|
||||
return { m_data[offset], m_shadow_data[offset] };
|
||||
}
|
||||
|
||||
ValueWithShadow<u16> MmapRegion::read16(u32 offset)
|
||||
{
|
||||
if (!is_readable()) {
|
||||
reportln("16-bit read from unreadable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_read(*this, base() + offset, 2);
|
||||
}
|
||||
|
||||
VERIFY(offset + 1 < size());
|
||||
u16 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u32> MmapRegion::read32(u32 offset)
|
||||
{
|
||||
if (!is_readable()) {
|
||||
reportln("32-bit read from unreadable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_read(*this, base() + offset, 4);
|
||||
}
|
||||
|
||||
VERIFY(offset + 3 < size());
|
||||
u32 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u64> MmapRegion::read64(u32 offset)
|
||||
{
|
||||
if (!is_readable()) {
|
||||
reportln("64-bit read from unreadable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_read(*this, base() + offset, 8);
|
||||
}
|
||||
|
||||
VERIFY(offset + 7 < size());
|
||||
u64 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u128> MmapRegion::read128(u32 offset)
|
||||
{
|
||||
if (!is_readable()) {
|
||||
reportln("128-bit read from unreadable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_read(*this, base() + offset, 16);
|
||||
}
|
||||
|
||||
VERIFY(offset + 15 < size());
|
||||
u128 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u256> MmapRegion::read256(u32 offset)
|
||||
{
|
||||
if (!is_readable()) {
|
||||
reportln("256-bit read from unreadable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_read(*this, base() + offset, 32);
|
||||
}
|
||||
|
||||
VERIFY(offset + 31 < size());
|
||||
u256 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
void MmapRegion::write8(u32 offset, ValueWithShadow<u8> value)
|
||||
{
|
||||
if (!is_writable()) {
|
||||
reportln("8-bit write from unwritable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_write(*this, base() + offset, 1);
|
||||
}
|
||||
|
||||
VERIFY(offset < size());
|
||||
m_data[offset] = value.value();
|
||||
m_shadow_data[offset] = value.shadow()[0];
|
||||
}
|
||||
|
||||
void MmapRegion::write16(u32 offset, ValueWithShadow<u16> value)
|
||||
{
|
||||
if (!is_writable()) {
|
||||
reportln("16-bit write from unwritable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_write(*this, base() + offset, 2);
|
||||
}
|
||||
|
||||
VERIFY(offset + 1 < size());
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
void MmapRegion::write32(u32 offset, ValueWithShadow<u32> value)
|
||||
{
|
||||
if (!is_writable()) {
|
||||
reportln("32-bit write from unwritable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_write(*this, base() + offset, 4);
|
||||
}
|
||||
|
||||
VERIFY(offset + 3 < size());
|
||||
VERIFY(m_data != m_shadow_data);
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
void MmapRegion::write64(u32 offset, ValueWithShadow<u64> value)
|
||||
{
|
||||
if (!is_writable()) {
|
||||
reportln("64-bit write from unwritable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_write(*this, base() + offset, 8);
|
||||
}
|
||||
|
||||
VERIFY(offset + 7 < size());
|
||||
VERIFY(m_data != m_shadow_data);
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
void MmapRegion::write128(u32 offset, ValueWithShadow<u128> value)
|
||||
{
|
||||
if (!is_writable()) {
|
||||
reportln("128-bit write from unwritable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_write(*this, base() + offset, 16);
|
||||
}
|
||||
VERIFY(offset + 15 < size());
|
||||
VERIFY(m_data != m_shadow_data);
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
void MmapRegion::write256(u32 offset, ValueWithShadow<u256> value)
|
||||
{
|
||||
if (!is_writable()) {
|
||||
reportln("256-bit write from unwritable MmapRegion @ {:p}"sv, base() + offset);
|
||||
emulator().dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (is_malloc_block()) {
|
||||
if (auto* tracer = emulator().malloc_tracer())
|
||||
tracer->audit_write(*this, base() + offset, 32);
|
||||
}
|
||||
VERIFY(offset + 31 < size());
|
||||
VERIFY(m_data != m_shadow_data);
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
NonnullOwnPtr<MmapRegion> MmapRegion::split_at(VirtualAddress offset)
|
||||
{
|
||||
VERIFY(!m_malloc);
|
||||
VERIFY(!m_malloc_metadata);
|
||||
Range new_range = range();
|
||||
Range other_range = new_range.split_at(offset);
|
||||
auto other_region = adopt_own(*new MmapRegion(other_range.base().get(), other_range.size(), prot(), data() + new_range.size(), shadow_data() + new_range.size()));
|
||||
other_region->m_file_backed = m_file_backed;
|
||||
other_region->m_name = m_name;
|
||||
set_range(new_range);
|
||||
return other_region;
|
||||
}
|
||||
|
||||
void MmapRegion::set_prot(int prot)
|
||||
{
|
||||
set_readable(prot & PROT_READ);
|
||||
set_writable(prot & PROT_WRITE);
|
||||
set_executable(prot & PROT_EXEC);
|
||||
if (m_file_backed) {
|
||||
if (mprotect(m_data, size(), prot & ~PROT_EXEC) < 0) {
|
||||
perror("MmapRegion::set_prot: mprotect");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MmapRegion::set_name(ByteString name)
|
||||
{
|
||||
m_name = move(name);
|
||||
set_mmap_name(range().base().as_ptr(), range().size(), ByteString::formatted("(UE) {}", m_name).characters());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SoftMMU.h"
|
||||
#include <sys/mman.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class MallocRegionMetadata;
|
||||
class MallocTracer;
|
||||
|
||||
class MmapRegion final : public Region {
|
||||
public:
|
||||
static NonnullOwnPtr<MmapRegion> create_anonymous(u32 base, u32 size, u32 prot, ByteString name);
|
||||
static NonnullOwnPtr<MmapRegion> create_file_backed(u32 base, u32 size, u32 prot, int flags, int fd, off_t offset, ByteString name);
|
||||
virtual ~MmapRegion() override;
|
||||
|
||||
virtual ValueWithShadow<u8> read8(u32 offset) override;
|
||||
virtual ValueWithShadow<u16> read16(u32 offset) override;
|
||||
virtual ValueWithShadow<u32> read32(u32 offset) override;
|
||||
virtual ValueWithShadow<u64> read64(u32 offset) override;
|
||||
virtual ValueWithShadow<u128> read128(u32 offset) override;
|
||||
virtual ValueWithShadow<u256> read256(u32 offset) override;
|
||||
|
||||
virtual void write8(u32 offset, ValueWithShadow<u8>) override;
|
||||
virtual void write16(u32 offset, ValueWithShadow<u16>) override;
|
||||
virtual void write32(u32 offset, ValueWithShadow<u32>) override;
|
||||
virtual void write64(u32 offset, ValueWithShadow<u64>) override;
|
||||
virtual void write128(u32 offset, ValueWithShadow<u128>) override;
|
||||
virtual void write256(u32 offset, ValueWithShadow<u256>) override;
|
||||
|
||||
virtual u8* data() override { return m_data; }
|
||||
virtual u8* shadow_data() override { return m_shadow_data; }
|
||||
|
||||
bool is_malloc_block() const { return m_malloc; }
|
||||
void set_malloc(bool b) { m_malloc = b; }
|
||||
|
||||
NonnullOwnPtr<MmapRegion> split_at(VirtualAddress);
|
||||
|
||||
int prot() const
|
||||
{
|
||||
return (is_readable() ? PROT_READ : 0) | (is_writable() ? PROT_WRITE : 0) | (is_executable() ? PROT_EXEC : 0);
|
||||
}
|
||||
void set_prot(int prot);
|
||||
|
||||
MallocRegionMetadata* malloc_metadata() { return m_malloc_metadata; }
|
||||
void set_malloc_metadata(Badge<MallocTracer>, NonnullOwnPtr<MallocRegionMetadata> metadata) { m_malloc_metadata = move(metadata); }
|
||||
|
||||
ByteString const& name() const { return m_name; }
|
||||
ByteString lib_name() const
|
||||
{
|
||||
if (m_name.contains("Loader.so"sv))
|
||||
return "Loader.so";
|
||||
auto const maybe_separator = m_name.find(':');
|
||||
if (!maybe_separator.has_value())
|
||||
return {};
|
||||
return m_name.substring(0, *maybe_separator);
|
||||
}
|
||||
void set_name(ByteString name);
|
||||
|
||||
private:
|
||||
MmapRegion(u32 base, u32 size, int prot, u8* data, u8* shadow_data);
|
||||
|
||||
u8* m_data { nullptr };
|
||||
u8* m_shadow_data { nullptr };
|
||||
bool m_file_backed { false };
|
||||
bool m_malloc { false };
|
||||
|
||||
OwnPtr<MallocRegionMetadata> m_malloc_metadata;
|
||||
ByteString m_name;
|
||||
};
|
||||
|
||||
template<>
|
||||
inline bool Region::fast_is<MmapRegion>() const { return m_mmap; }
|
||||
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Range.h"
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
Vector<Range, 2> Range::carve(Range const& taken) const
|
||||
{
|
||||
VERIFY((taken.size() % PAGE_SIZE) == 0);
|
||||
Vector<Range, 2> parts;
|
||||
if (taken == *this)
|
||||
return {};
|
||||
if (taken.base() > base())
|
||||
parts.append({ base(), taken.base().get() - base().get() });
|
||||
if (taken.end() < end())
|
||||
parts.append({ taken.end(), end().get() - taken.end().get() });
|
||||
return parts;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Memory/VirtualAddress.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class Range {
|
||||
friend class RangeAllocator;
|
||||
|
||||
public:
|
||||
Range() = delete;
|
||||
Range(VirtualAddress base, size_t size)
|
||||
: m_base(base)
|
||||
, m_size(size)
|
||||
{
|
||||
}
|
||||
|
||||
VirtualAddress base() const { return m_base; }
|
||||
size_t size() const { return m_size; }
|
||||
bool is_valid() const { return !m_base.is_null(); }
|
||||
|
||||
bool contains(VirtualAddress vaddr) const { return vaddr >= base() && vaddr < end(); }
|
||||
|
||||
VirtualAddress end() const { return m_base.offset(m_size); }
|
||||
|
||||
bool operator==(Range const& other) const
|
||||
{
|
||||
return m_base == other.m_base && m_size == other.m_size;
|
||||
}
|
||||
|
||||
bool contains(VirtualAddress base, size_t size) const
|
||||
{
|
||||
if (base.offset(size) < base)
|
||||
return false;
|
||||
return base >= m_base && base.offset(size) <= end();
|
||||
}
|
||||
|
||||
bool contains(Range const& other) const
|
||||
{
|
||||
return contains(other.base(), other.size());
|
||||
}
|
||||
|
||||
Vector<Range, 2> carve(Range const&) const;
|
||||
|
||||
Range split_at(VirtualAddress address)
|
||||
{
|
||||
VERIFY(address.is_page_aligned());
|
||||
VERIFY(m_base < address);
|
||||
size_t new_size = (address - m_base).get();
|
||||
VERIFY(new_size < m_size);
|
||||
size_t other_size = m_size - new_size;
|
||||
m_size = new_size;
|
||||
return { address, other_size };
|
||||
}
|
||||
|
||||
private:
|
||||
VirtualAddress m_base;
|
||||
size_t m_size { 0 };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace AK {
|
||||
template<>
|
||||
struct Traits<UserspaceEmulator::Range> : public DefaultTraits<UserspaceEmulator::Range> {
|
||||
static constexpr bool is_trivial() { return true; }
|
||||
};
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "RangeAllocator.h"
|
||||
#include <AK/BinarySearch.h>
|
||||
#include <AK/Checked.h>
|
||||
#include <AK/Random.h>
|
||||
|
||||
#define VM_GUARD_PAGES
|
||||
#define PAGE_MASK ((FlatPtr)0xfffff000u)
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
RangeAllocator::RangeAllocator()
|
||||
: m_total_range({}, 0)
|
||||
{
|
||||
}
|
||||
|
||||
void RangeAllocator::initialize_with_range(VirtualAddress base, size_t size)
|
||||
{
|
||||
m_total_range = { base, size };
|
||||
m_available_ranges.append({ base, size });
|
||||
}
|
||||
|
||||
void RangeAllocator::dump() const
|
||||
{
|
||||
dbgln("RangeAllocator({})", this);
|
||||
for (auto const& range : m_available_ranges) {
|
||||
dbgln(" {:x} -> {:x}", range.base().get(), range.end().get() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
void RangeAllocator::carve_at_index(int index, Range const& range)
|
||||
{
|
||||
auto remaining_parts = m_available_ranges[index].carve(range);
|
||||
VERIFY(remaining_parts.size() >= 1);
|
||||
VERIFY(m_total_range.contains(remaining_parts[0]));
|
||||
m_available_ranges[index] = remaining_parts[0];
|
||||
if (remaining_parts.size() == 2) {
|
||||
VERIFY(m_total_range.contains(remaining_parts[1]));
|
||||
m_available_ranges.insert(index + 1, move(remaining_parts[1]));
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Range> RangeAllocator::allocate_randomized(size_t size, size_t alignment)
|
||||
{
|
||||
if (!size)
|
||||
return {};
|
||||
|
||||
VERIFY((size % PAGE_SIZE) == 0);
|
||||
VERIFY((alignment % PAGE_SIZE) == 0);
|
||||
|
||||
// FIXME: I'm sure there's a smarter way to do this.
|
||||
static constexpr size_t maximum_randomization_attempts = 1000;
|
||||
for (size_t i = 0; i < maximum_randomization_attempts; ++i) {
|
||||
VirtualAddress random_address { round_up_to_power_of_two(get_random<FlatPtr>(), alignment) };
|
||||
|
||||
if (!m_total_range.contains(random_address, size))
|
||||
continue;
|
||||
|
||||
auto range = allocate_specific(random_address, size);
|
||||
if (range.has_value())
|
||||
return range;
|
||||
}
|
||||
|
||||
return allocate_anywhere(size, alignment);
|
||||
}
|
||||
|
||||
Optional<Range> RangeAllocator::allocate_anywhere(size_t size, size_t alignment)
|
||||
{
|
||||
if (!size)
|
||||
return {};
|
||||
|
||||
VERIFY((size % PAGE_SIZE) == 0);
|
||||
VERIFY((alignment % PAGE_SIZE) == 0);
|
||||
|
||||
#ifdef VM_GUARD_PAGES
|
||||
// NOTE: We pad VM allocations with a guard page on each side.
|
||||
if (Checked<size_t>::addition_would_overflow(size, PAGE_SIZE * 2))
|
||||
return {};
|
||||
|
||||
size_t effective_size = size + PAGE_SIZE * 2;
|
||||
size_t offset_from_effective_base = PAGE_SIZE;
|
||||
#else
|
||||
size_t effective_size = size;
|
||||
size_t offset_from_effective_base = 0;
|
||||
#endif
|
||||
|
||||
if (Checked<size_t>::addition_would_overflow(effective_size, alignment))
|
||||
return {};
|
||||
|
||||
for (size_t i = 0; i < m_available_ranges.size(); ++i) {
|
||||
auto& available_range = m_available_ranges[i];
|
||||
// FIXME: This check is probably excluding some valid candidates when using a large alignment.
|
||||
if (available_range.size() < (effective_size + alignment))
|
||||
continue;
|
||||
|
||||
FlatPtr initial_base = available_range.base().offset(offset_from_effective_base).get();
|
||||
FlatPtr aligned_base = round_up_to_power_of_two(initial_base, alignment);
|
||||
|
||||
Range allocated_range(VirtualAddress(aligned_base), size);
|
||||
VERIFY(m_total_range.contains(allocated_range));
|
||||
|
||||
if (available_range == allocated_range) {
|
||||
m_available_ranges.remove(i);
|
||||
return allocated_range;
|
||||
}
|
||||
carve_at_index(i, allocated_range);
|
||||
return allocated_range;
|
||||
}
|
||||
dbgln("RangeAllocator: Failed to allocate anywhere: size={}, alignment={}", size, alignment);
|
||||
return {};
|
||||
}
|
||||
|
||||
Optional<Range> RangeAllocator::allocate_specific(VirtualAddress base, size_t size)
|
||||
{
|
||||
if (!size)
|
||||
return {};
|
||||
|
||||
VERIFY(base.is_page_aligned());
|
||||
VERIFY((size % PAGE_SIZE) == 0);
|
||||
|
||||
Range allocated_range(base, size);
|
||||
if (!m_total_range.contains(allocated_range)) {
|
||||
dbgln("Unallocatable mmap request?! {:p}+{:p}", base.get(), size);
|
||||
return {};
|
||||
}
|
||||
for (size_t i = 0; i < m_available_ranges.size(); ++i) {
|
||||
auto& available_range = m_available_ranges[i];
|
||||
if (!available_range.contains(base, size))
|
||||
continue;
|
||||
if (available_range == allocated_range) {
|
||||
m_available_ranges.remove(i);
|
||||
return allocated_range;
|
||||
}
|
||||
carve_at_index(i, allocated_range);
|
||||
return allocated_range;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void RangeAllocator::deallocate(Range const& range)
|
||||
{
|
||||
VERIFY(m_total_range.contains(range));
|
||||
VERIFY(range.size());
|
||||
VERIFY((range.size() % PAGE_SIZE) == 0);
|
||||
VERIFY(range.base() < range.end());
|
||||
VERIFY(!m_available_ranges.is_empty());
|
||||
|
||||
size_t nearby_index = 0;
|
||||
auto* existing_range = binary_search(
|
||||
m_available_ranges.span(),
|
||||
range,
|
||||
&nearby_index,
|
||||
[](auto& a, auto& b) { return a.base().get() - b.end().get(); });
|
||||
|
||||
size_t inserted_index = 0;
|
||||
if (existing_range) {
|
||||
existing_range->m_size += range.size();
|
||||
inserted_index = nearby_index;
|
||||
} else {
|
||||
m_available_ranges.insert_before_matching(
|
||||
Range(range), [&](auto& entry) {
|
||||
return entry.base() >= range.end();
|
||||
},
|
||||
nearby_index, &inserted_index);
|
||||
}
|
||||
|
||||
if (inserted_index < (m_available_ranges.size() - 1)) {
|
||||
// We already merged with previous. Try to merge with next.
|
||||
auto& inserted_range = m_available_ranges[inserted_index];
|
||||
auto& next_range = m_available_ranges[inserted_index + 1];
|
||||
if (inserted_range.end() == next_range.base()) {
|
||||
inserted_range.m_size += next_range.size();
|
||||
m_available_ranges.remove(inserted_index + 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RangeAllocator::reserve_user_range(VirtualAddress begin, size_t size)
|
||||
{
|
||||
auto end = round_up_to_power_of_two(begin.offset(size).get(), PAGE_SIZE);
|
||||
auto allocated_range = allocate_specific(begin.page_base(), end - begin.page_base().get());
|
||||
VERIFY(allocated_range.has_value());
|
||||
}
|
||||
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2018-2021, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
#include <AK/Vector.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class RangeAllocator {
|
||||
public:
|
||||
RangeAllocator();
|
||||
|
||||
void initialize_with_range(VirtualAddress, size_t);
|
||||
|
||||
Optional<Range> allocate_anywhere(size_t, size_t alignment = PAGE_SIZE);
|
||||
Optional<Range> allocate_specific(VirtualAddress, size_t);
|
||||
Optional<Range> allocate_randomized(size_t, size_t alignment);
|
||||
void deallocate(Range const&);
|
||||
|
||||
void reserve_user_range(VirtualAddress, size_t);
|
||||
|
||||
void dump() const;
|
||||
|
||||
bool contains(Range const& range) const { return m_total_range.contains(range); }
|
||||
|
||||
private:
|
||||
void carve_at_index(int, Range const&);
|
||||
|
||||
Vector<Range> m_available_ranges;
|
||||
Range m_total_range;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Region.h"
|
||||
#include "Emulator.h"
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
Region::Region(u32 base, u32 size, bool mmap)
|
||||
: m_emulator(Emulator::the())
|
||||
, m_range(Range { VirtualAddress { base }, size })
|
||||
, m_mmap(mmap)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
|
@ -1,88 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2022, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Range.h"
|
||||
#include "ValueWithShadow.h"
|
||||
#include <AK/TypeCasts.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/UFixedBigInt.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class Emulator;
|
||||
|
||||
class Region {
|
||||
public:
|
||||
virtual ~Region() = default;
|
||||
|
||||
Range const& range() const { return m_range; }
|
||||
|
||||
u32 base() const { return m_range.base().get(); }
|
||||
u32 size() const { return m_range.size(); }
|
||||
u32 end() const { return m_range.end().get(); }
|
||||
|
||||
bool contains(u32 address) const { return address >= base() && address < end(); }
|
||||
|
||||
virtual void write8(u32 offset, ValueWithShadow<u8>) = 0;
|
||||
virtual void write16(u32 offset, ValueWithShadow<u16>) = 0;
|
||||
virtual void write32(u32 offset, ValueWithShadow<u32>) = 0;
|
||||
virtual void write64(u32 offset, ValueWithShadow<u64>) = 0;
|
||||
virtual void write128(u32 offset, ValueWithShadow<u128>) = 0;
|
||||
virtual void write256(u32 offset, ValueWithShadow<u256>) = 0;
|
||||
|
||||
virtual ValueWithShadow<u8> read8(u32 offset) = 0;
|
||||
virtual ValueWithShadow<u16> read16(u32 offset) = 0;
|
||||
virtual ValueWithShadow<u32> read32(u32 offset) = 0;
|
||||
virtual ValueWithShadow<u64> read64(u32 offset) = 0;
|
||||
virtual ValueWithShadow<u128> read128(u32 offset) = 0;
|
||||
virtual ValueWithShadow<u256> read256(u32 offset) = 0;
|
||||
|
||||
virtual u8* cacheable_ptr([[maybe_unused]] u32 offset) { return nullptr; }
|
||||
|
||||
bool is_stack() const { return m_stack; }
|
||||
void set_stack(bool b) { m_stack = b; }
|
||||
|
||||
bool is_text() const { return m_text; }
|
||||
void set_text(bool b) { m_text = b; }
|
||||
|
||||
bool is_readable() const { return m_readable; }
|
||||
bool is_writable() const { return m_writable; }
|
||||
bool is_executable() const { return m_executable; }
|
||||
|
||||
void set_readable(bool b) { m_readable = b; }
|
||||
void set_writable(bool b) { m_writable = b; }
|
||||
void set_executable(bool b) { m_executable = b; }
|
||||
|
||||
virtual u8* data() = 0;
|
||||
virtual u8* shadow_data() = 0;
|
||||
|
||||
Emulator& emulator() { return m_emulator; }
|
||||
Emulator const& emulator() const { return m_emulator; }
|
||||
|
||||
template<typename T>
|
||||
bool fast_is() const = delete;
|
||||
|
||||
protected:
|
||||
Region(u32 base, u32 size, bool mmap = false);
|
||||
void set_range(Range r) { m_range = r; }
|
||||
|
||||
private:
|
||||
Emulator& m_emulator;
|
||||
|
||||
Range m_range;
|
||||
|
||||
bool m_mmap { false };
|
||||
bool m_stack { false };
|
||||
bool m_text { false };
|
||||
bool m_readable { true };
|
||||
bool m_writable { true };
|
||||
bool m_executable { true };
|
||||
};
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, the SerenityOS developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Format.h>
|
||||
|
||||
extern bool g_report_to_debug;
|
||||
|
||||
template<typename... Ts>
|
||||
void reportln(StringView format, Ts... args)
|
||||
{
|
||||
if (g_report_to_debug) {
|
||||
AK::VariadicFormatParams<AK::AllowDebugOnlyFormatters::Yes, Ts...> variadic_format_params { args... };
|
||||
AK::vdbgln(format, variadic_format_params);
|
||||
} else {
|
||||
warnln(format, args...);
|
||||
}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SimpleRegion.h"
|
||||
#include <AK/ByteReader.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
SimpleRegion::SimpleRegion(u32 base, u32 size)
|
||||
: Region(base, size)
|
||||
{
|
||||
m_data = (u8*)calloc(1, size);
|
||||
m_shadow_data = (u8*)malloc(size);
|
||||
memset(m_shadow_data, 1, size);
|
||||
}
|
||||
|
||||
SimpleRegion::~SimpleRegion()
|
||||
{
|
||||
free(m_shadow_data);
|
||||
free(m_data);
|
||||
}
|
||||
|
||||
ValueWithShadow<u8> SimpleRegion::read8(FlatPtr offset)
|
||||
{
|
||||
VERIFY(offset < size());
|
||||
return { m_data[offset], m_shadow_data[offset] };
|
||||
}
|
||||
|
||||
ValueWithShadow<u16> SimpleRegion::read16(u32 offset)
|
||||
{
|
||||
VERIFY(offset + 1 < size());
|
||||
|
||||
u16 value, shadow;
|
||||
ByteReader::load<u16>(m_data + offset, value);
|
||||
ByteReader::load<u16>(m_shadow_data + offset, shadow);
|
||||
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u32> SimpleRegion::read32(u32 offset)
|
||||
{
|
||||
VERIFY(offset + 3 < size());
|
||||
|
||||
u32 value, shadow;
|
||||
ByteReader::load<u32>(m_data + offset, value);
|
||||
ByteReader::load<u32>(m_shadow_data + offset, shadow);
|
||||
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u64> SimpleRegion::read64(u32 offset)
|
||||
{
|
||||
VERIFY(offset + 7 < size());
|
||||
|
||||
u64 value, shadow;
|
||||
ByteReader::load<u64>(m_data + offset, value);
|
||||
ByteReader::load<u64>(m_shadow_data + offset, shadow);
|
||||
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u128> SimpleRegion::read128(u32 offset)
|
||||
{
|
||||
VERIFY(offset + 15 < size());
|
||||
u128 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
ValueWithShadow<u256> SimpleRegion::read256(u32 offset)
|
||||
{
|
||||
VERIFY(offset + 31 < size());
|
||||
u256 value, shadow;
|
||||
ByteReader::load(m_data + offset, value);
|
||||
ByteReader::load(m_shadow_data + offset, shadow);
|
||||
return { value, shadow };
|
||||
}
|
||||
|
||||
void SimpleRegion::write8(u32 offset, ValueWithShadow<u8> value)
|
||||
{
|
||||
VERIFY(offset < size());
|
||||
m_data[offset] = value.value();
|
||||
m_shadow_data[offset] = value.shadow()[0];
|
||||
}
|
||||
|
||||
void SimpleRegion::write16(u32 offset, ValueWithShadow<u16> value)
|
||||
{
|
||||
VERIFY(offset + 1 < size());
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
void SimpleRegion::write32(u32 offset, ValueWithShadow<u32> value)
|
||||
{
|
||||
VERIFY(offset + 3 < size());
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
void SimpleRegion::write64(u32 offset, ValueWithShadow<u64> value)
|
||||
{
|
||||
VERIFY(offset + 7 < size());
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
void SimpleRegion::write128(u32 offset, ValueWithShadow<u128> value)
|
||||
{
|
||||
VERIFY(offset + 15 < size());
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
void SimpleRegion::write256(u32 offset, ValueWithShadow<u256> value)
|
||||
{
|
||||
VERIFY(offset + 31 < size());
|
||||
ByteReader::store(m_data + offset, value.value());
|
||||
ByteReader::store(m_shadow_data + offset, value.shadow());
|
||||
}
|
||||
|
||||
u8* SimpleRegion::cacheable_ptr(u32 offset)
|
||||
{
|
||||
return m_data + offset;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "SoftMMU.h"
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class SimpleRegion final : public Region {
|
||||
public:
|
||||
SimpleRegion(u32 base, u32 size);
|
||||
virtual ~SimpleRegion() override;
|
||||
|
||||
virtual ValueWithShadow<u8> read8(u32 offset) override;
|
||||
virtual ValueWithShadow<u16> read16(u32 offset) override;
|
||||
virtual ValueWithShadow<u32> read32(u32 offset) override;
|
||||
virtual ValueWithShadow<u64> read64(u32 offset) override;
|
||||
virtual ValueWithShadow<u128> read128(u32 offset) override;
|
||||
virtual ValueWithShadow<u256> read256(u32 offset) override;
|
||||
|
||||
virtual void write8(u32 offset, ValueWithShadow<u8>) override;
|
||||
virtual void write16(u32 offset, ValueWithShadow<u16>) override;
|
||||
virtual void write32(u32 offset, ValueWithShadow<u32>) override;
|
||||
virtual void write64(u32 offset, ValueWithShadow<u64>) override;
|
||||
virtual void write128(u32 offset, ValueWithShadow<u128>) override;
|
||||
virtual void write256(u32 offset, ValueWithShadow<u256>) override;
|
||||
|
||||
virtual u8* data() override { return m_data; }
|
||||
virtual u8* shadow_data() override { return m_shadow_data; }
|
||||
|
||||
virtual u8* cacheable_ptr(u32 offset) override;
|
||||
|
||||
private:
|
||||
u8* m_data { nullptr };
|
||||
u8* m_shadow_data { nullptr };
|
||||
};
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -1,587 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Leon Albrecht <leon2002.la@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Report.h"
|
||||
#include <AK/Concepts.h>
|
||||
#include <AK/FPControl.h>
|
||||
#include <AK/SIMD.h>
|
||||
#include <LibX86/Instruction.h>
|
||||
#include <LibX86/Interpreter.h>
|
||||
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
using namespace AK::SIMD;
|
||||
using AK::RoundingMode;
|
||||
|
||||
class Emulator;
|
||||
class SoftCPU;
|
||||
|
||||
union MMX {
|
||||
u64 raw;
|
||||
c8x8 v8;
|
||||
i16x4 v16;
|
||||
i32x2 v32;
|
||||
u16x4 v16u;
|
||||
u32x2 v32u;
|
||||
};
|
||||
static_assert(AssertSize<MMX, sizeof(u64)>());
|
||||
|
||||
class SoftFPU final {
|
||||
public:
|
||||
SoftFPU(Emulator& emulator, SoftCPU& cpu)
|
||||
: m_emulator(emulator)
|
||||
, m_cpu(cpu)
|
||||
, m_fpu_cw { 0x037F }
|
||||
{
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool c0() const { return m_fpu_c0; }
|
||||
ALWAYS_INLINE bool c1() const { return m_fpu_c1; }
|
||||
ALWAYS_INLINE bool c2() const { return m_fpu_c2; }
|
||||
ALWAYS_INLINE bool c3() const { return m_fpu_c3; }
|
||||
|
||||
ALWAYS_INLINE void set_c0(bool val) { m_fpu_c0 = val; }
|
||||
ALWAYS_INLINE void set_c1(bool val) { m_fpu_c1 = val; }
|
||||
ALWAYS_INLINE void set_c2(bool val) { m_fpu_c2 = val; }
|
||||
ALWAYS_INLINE void set_c3(bool val) { m_fpu_c3 = val; }
|
||||
|
||||
long double fpu_get(u8 index);
|
||||
|
||||
void fpu_push(long double value);
|
||||
long double fpu_pop();
|
||||
void fpu_set_absolute(u8 index, long double value);
|
||||
void fpu_set(u8 index, long double value);
|
||||
|
||||
MMX mmx_get(u8 index) const;
|
||||
void mmx_set(u8 index, MMX value);
|
||||
|
||||
private:
|
||||
friend class SoftCPU;
|
||||
|
||||
Emulator& m_emulator;
|
||||
SoftCPU& m_cpu;
|
||||
|
||||
enum class FPU_Exception : u8 {
|
||||
InvalidOperation,
|
||||
DenormalizedOperand,
|
||||
ZeroDivide,
|
||||
Overflow,
|
||||
Underflow,
|
||||
Precision,
|
||||
StackFault,
|
||||
};
|
||||
|
||||
enum class FPU_Tag : u8 {
|
||||
Valid = 0b00,
|
||||
Zero = 0b01,
|
||||
Special = 0b10,
|
||||
Empty = 0b11
|
||||
};
|
||||
|
||||
void fpu_dump_env()
|
||||
{
|
||||
reportln("Exceptions: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{} #SF:{} Summary:{}"sv,
|
||||
m_fpu_error_invalid,
|
||||
m_fpu_error_denorm,
|
||||
m_fpu_error_zero_div,
|
||||
m_fpu_error_overflow,
|
||||
m_fpu_error_underflow,
|
||||
m_fpu_error_precision,
|
||||
m_fpu_error_stackfault,
|
||||
m_fpu_error_summary);
|
||||
reportln("Masks: #I:{} #D:{} #Z:{} #O:{} #U:{} #P:{}"sv,
|
||||
m_fpu_cw.mask_invalid,
|
||||
m_fpu_cw.mask_denorm,
|
||||
m_fpu_cw.mask_zero_div,
|
||||
m_fpu_cw.mask_overflow,
|
||||
m_fpu_cw.mask_underflow,
|
||||
m_fpu_cw.mask_precision);
|
||||
reportln("C0:{} C1:{} C2:{} C3:{}"sv, c0(), c1(), c2(), c3());
|
||||
reportln("fpu-stacktop: {}"sv, m_fpu_stack_top);
|
||||
reportln("fpu-stack /w stacktop (real):"sv);
|
||||
for (u8 i = 0; i < 8; ++i) {
|
||||
reportln("\t{} ({}): fp {} ({}), mmx {:016x}"sv,
|
||||
i, (u8)((m_fpu_stack_top + i) % 8),
|
||||
m_storage[(m_fpu_stack_top + i) % 8].fp, fpu_is_set(i) ? "set" : "free",
|
||||
m_storage[(m_fpu_stack_top + i) % 8].mmx.raw);
|
||||
}
|
||||
}
|
||||
|
||||
ByteString fpu_exception_string(FPU_Exception ex)
|
||||
{
|
||||
switch (ex) {
|
||||
case FPU_Exception::StackFault:
|
||||
return "Stackfault";
|
||||
case FPU_Exception::InvalidOperation:
|
||||
return "Invalid Operation";
|
||||
case FPU_Exception::DenormalizedOperand:
|
||||
return "Denormalized Operand";
|
||||
case FPU_Exception::ZeroDivide:
|
||||
return "Divide by Zero";
|
||||
case FPU_Exception::Overflow:
|
||||
return "Overflow";
|
||||
case FPU_Exception::Underflow:
|
||||
return "Underflow";
|
||||
case FPU_Exception::Precision:
|
||||
return "Precision";
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
// FIXME: Technically we should check for exceptions after each insn, too,
|
||||
// this might be important for FLDENV, but otherwise it should
|
||||
// be fine this way
|
||||
void fpu_set_exception(FPU_Exception ex);
|
||||
|
||||
ALWAYS_INLINE void fpu_set_stack_overflow()
|
||||
{
|
||||
reportln("Stack Overflow"sv);
|
||||
set_c1(1);
|
||||
fpu_set_exception(FPU_Exception::StackFault);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void fpu_set_stack_underflow()
|
||||
{
|
||||
reportln("Stack Underflow"sv);
|
||||
set_c1(0);
|
||||
fpu_set_exception(FPU_Exception::StackFault);
|
||||
}
|
||||
|
||||
constexpr FPU_Tag fpu_get_tag_absolute(u8 index) const
|
||||
{
|
||||
switch (index) {
|
||||
case 0:
|
||||
return FPU_Tag(m_fpu_status_0);
|
||||
case 1:
|
||||
return FPU_Tag(m_fpu_status_1);
|
||||
case 2:
|
||||
return FPU_Tag(m_fpu_status_2);
|
||||
case 3:
|
||||
return FPU_Tag(m_fpu_status_3);
|
||||
case 4:
|
||||
return FPU_Tag(m_fpu_status_4);
|
||||
case 5:
|
||||
return FPU_Tag(m_fpu_status_5);
|
||||
case 6:
|
||||
return FPU_Tag(m_fpu_status_6);
|
||||
case 7:
|
||||
return FPU_Tag(m_fpu_status_7);
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
constexpr FPU_Tag fpu_get_tag(u8 index) const
|
||||
{
|
||||
VERIFY(index < 8);
|
||||
return fpu_get_tag_absolute((m_fpu_stack_top + index) % 8);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void fpu_set_tag_absolute(u8 index, FPU_Tag tag)
|
||||
{
|
||||
switch (index) {
|
||||
case 0:
|
||||
m_fpu_status_0 = (u8)tag;
|
||||
break;
|
||||
case 1:
|
||||
m_fpu_status_1 = (u8)tag;
|
||||
break;
|
||||
case 2:
|
||||
m_fpu_status_2 = (u8)tag;
|
||||
break;
|
||||
case 3:
|
||||
m_fpu_status_3 = (u8)tag;
|
||||
break;
|
||||
case 4:
|
||||
m_fpu_status_4 = (u8)tag;
|
||||
break;
|
||||
case 5:
|
||||
m_fpu_status_5 = (u8)tag;
|
||||
break;
|
||||
case 6:
|
||||
m_fpu_status_6 = (u8)tag;
|
||||
break;
|
||||
case 7:
|
||||
m_fpu_status_7 = (u8)tag;
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void fpu_set_tag(u8 index, FPU_Tag tag)
|
||||
{
|
||||
VERIFY(index < 8);
|
||||
fpu_set_tag_absolute((m_fpu_stack_top + index) % 8, tag);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void set_tag_from_value_absolute(u8 index, long double val)
|
||||
{
|
||||
switch (fpclassify(val)) {
|
||||
case FP_ZERO:
|
||||
fpu_set_tag_absolute(index, FPU_Tag::Zero);
|
||||
break;
|
||||
case FP_NAN:
|
||||
case FP_INFINITE:
|
||||
case FP_SUBNORMAL:
|
||||
fpu_set_tag_absolute(index, FPU_Tag::Special);
|
||||
break;
|
||||
case FP_NORMAL:
|
||||
fpu_set_tag_absolute(index, FPU_Tag::Valid);
|
||||
break;
|
||||
default:
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void set_tag_from_value(u8 index, long double val)
|
||||
{
|
||||
set_tag_from_value_absolute((m_fpu_stack_top + index) % 8, val);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool fpu_isnan(u8 index)
|
||||
{
|
||||
return isnan(fpu_get(index));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool fpu_is_set(u8 index) const
|
||||
{
|
||||
return fpu_get_tag_absolute((m_fpu_stack_top + index) % 8) != FPU_Tag::Empty;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE RoundingMode fpu_get_round_mode() const
|
||||
{
|
||||
return m_fpu_cw.rounding_control;
|
||||
}
|
||||
|
||||
template<Arithmetic T>
|
||||
T round_checked(long double);
|
||||
|
||||
template<FloatingPoint T>
|
||||
T convert_checked(long double);
|
||||
|
||||
ALWAYS_INLINE void fpu_set_unordered()
|
||||
{
|
||||
set_c0(1);
|
||||
set_c2(1);
|
||||
set_c3(1);
|
||||
}
|
||||
void warn_if_mmx_absolute(u8 index) const;
|
||||
void warn_if_fpu_absolute(u8 index) const;
|
||||
|
||||
void mmx_common() { m_fpu_tw = 0; }
|
||||
|
||||
bool m_reg_is_mmx[8] { false };
|
||||
|
||||
union {
|
||||
long double fp;
|
||||
struct {
|
||||
MMX mmx;
|
||||
u16 __high;
|
||||
};
|
||||
} m_storage[8];
|
||||
|
||||
AK::X87ControlWord m_fpu_cw;
|
||||
|
||||
union {
|
||||
u16 m_fpu_sw { 0 };
|
||||
struct {
|
||||
u16 m_fpu_error_invalid : 1; // pre | IE -> #I (#IS, #IA)
|
||||
u16 m_fpu_error_denorm : 1; // pre | DE -> #D
|
||||
u16 m_fpu_error_zero_div : 1; // pre | ZE -> #Z
|
||||
u16 m_fpu_error_overflow : 1; // post| OE -> #O
|
||||
u16 m_fpu_error_underflow : 1; // post| UE -> #U
|
||||
u16 m_fpu_error_precision : 1; // post| PE -> #P
|
||||
u16 m_fpu_error_stackfault : 1; // SF
|
||||
u16 m_fpu_error_summary : 1;
|
||||
u16 m_fpu_c0 : 1;
|
||||
u16 m_fpu_c1 : 1;
|
||||
u16 m_fpu_c2 : 1;
|
||||
u16 m_fpu_stack_top : 3;
|
||||
u16 m_fpu_c3 : 1;
|
||||
u16 m_fpu_busy : 1;
|
||||
};
|
||||
};
|
||||
|
||||
union {
|
||||
u16 m_fpu_tw { 0xFFFF };
|
||||
struct {
|
||||
u16 m_fpu_status_0 : 2;
|
||||
u16 m_fpu_status_1 : 2;
|
||||
u16 m_fpu_status_2 : 2;
|
||||
u16 m_fpu_status_3 : 2;
|
||||
u16 m_fpu_status_4 : 2;
|
||||
u16 m_fpu_status_5 : 2;
|
||||
u16 m_fpu_status_6 : 2;
|
||||
u16 m_fpu_status_7 : 2;
|
||||
};
|
||||
};
|
||||
|
||||
u32 m_fpu_ip { 0 };
|
||||
u16 m_fpu_cs { 0 };
|
||||
|
||||
u32 m_fpu_dp { 0 };
|
||||
u16 m_fpu_ds { 0 };
|
||||
|
||||
u16 m_fpu_iop { 0 };
|
||||
|
||||
// Instructions
|
||||
|
||||
// DATA TRANSFER
|
||||
void FLD_RM32(const X86::Instruction&);
|
||||
void FLD_RM64(const X86::Instruction&);
|
||||
void FLD_RM80(const X86::Instruction&);
|
||||
|
||||
void FST_RM32(const X86::Instruction&);
|
||||
void FST_RM64(const X86::Instruction&);
|
||||
void FSTP_RM32(const X86::Instruction&);
|
||||
void FSTP_RM64(const X86::Instruction&);
|
||||
void FSTP_RM80(const X86::Instruction&);
|
||||
|
||||
void FILD_RM32(const X86::Instruction&);
|
||||
void FILD_RM16(const X86::Instruction&);
|
||||
void FILD_RM64(const X86::Instruction&);
|
||||
|
||||
void FIST_RM16(const X86::Instruction&);
|
||||
void FIST_RM32(const X86::Instruction&);
|
||||
void FISTP_RM16(const X86::Instruction&);
|
||||
void FISTP_RM32(const X86::Instruction&);
|
||||
void FISTP_RM64(const X86::Instruction&);
|
||||
void FISTTP_RM16(const X86::Instruction&);
|
||||
void FISTTP_RM32(const X86::Instruction&);
|
||||
void FISTTP_RM64(const X86::Instruction&);
|
||||
|
||||
void FBLD_M80(const X86::Instruction&);
|
||||
void FBSTP_M80(const X86::Instruction&);
|
||||
|
||||
void FXCH(const X86::Instruction&);
|
||||
|
||||
void FCMOVE(const X86::Instruction&);
|
||||
void FCMOVNE(const X86::Instruction&);
|
||||
void FCMOVB(const X86::Instruction&);
|
||||
void FCMOVBE(const X86::Instruction&);
|
||||
void FCMOVNB(const X86::Instruction&);
|
||||
void FCMOVNBE(const X86::Instruction&);
|
||||
void FCMOVU(const X86::Instruction&);
|
||||
void FCMOVNU(const X86::Instruction&);
|
||||
|
||||
// BASIC ARITHMETIC
|
||||
void FADD_RM32(const X86::Instruction&);
|
||||
void FADD_RM64(const X86::Instruction&);
|
||||
void FADDP(const X86::Instruction&);
|
||||
|
||||
void FIADD_RM16(const X86::Instruction&);
|
||||
void FIADD_RM32(const X86::Instruction&);
|
||||
|
||||
void FSUB_RM32(const X86::Instruction&);
|
||||
void FSUB_RM64(const X86::Instruction&);
|
||||
void FSUBP(const X86::Instruction&);
|
||||
void FSUBR_RM32(const X86::Instruction&);
|
||||
void FSUBR_RM64(const X86::Instruction&);
|
||||
void FSUBRP(const X86::Instruction&);
|
||||
|
||||
void FISUB_RM16(const X86::Instruction&);
|
||||
void FISUB_RM32(const X86::Instruction&);
|
||||
void FISUBR_RM16(const X86::Instruction&);
|
||||
void FISUBR_RM32(const X86::Instruction&);
|
||||
|
||||
void FMUL_RM32(const X86::Instruction&);
|
||||
void FMUL_RM64(const X86::Instruction&);
|
||||
void FMULP(const X86::Instruction&);
|
||||
|
||||
void FIMUL_RM16(const X86::Instruction&);
|
||||
void FIMUL_RM32(const X86::Instruction&);
|
||||
|
||||
void FDIV_RM32(const X86::Instruction&);
|
||||
void FDIV_RM64(const X86::Instruction&);
|
||||
void FDIVP(const X86::Instruction&);
|
||||
void FDIVR_RM32(const X86::Instruction&);
|
||||
void FDIVR_RM64(const X86::Instruction&);
|
||||
void FDIVRP(const X86::Instruction&);
|
||||
|
||||
void FIDIV_RM16(const X86::Instruction&);
|
||||
void FIDIV_RM32(const X86::Instruction&);
|
||||
void FIDIVR_RM16(const X86::Instruction&);
|
||||
void FIDIVR_RM32(const X86::Instruction&);
|
||||
|
||||
void FPREM(const X86::Instruction&);
|
||||
void FPREM1(const X86::Instruction&);
|
||||
|
||||
void FABS(const X86::Instruction&);
|
||||
void FCHS(const X86::Instruction&);
|
||||
|
||||
void FRNDINT(const X86::Instruction&);
|
||||
|
||||
void FSCALE(const X86::Instruction&);
|
||||
|
||||
void FSQRT(const X86::Instruction&);
|
||||
|
||||
void FXTRACT(const X86::Instruction&);
|
||||
|
||||
// COMPARISON
|
||||
void FCOM_RM32(const X86::Instruction&);
|
||||
void FCOM_RM64(const X86::Instruction&);
|
||||
void FCOMP_RM32(const X86::Instruction&);
|
||||
void FCOMP_RM64(const X86::Instruction&);
|
||||
void FCOMPP(const X86::Instruction&);
|
||||
void FCOMI(const X86::Instruction&);
|
||||
void FCOMIP(const X86::Instruction&);
|
||||
|
||||
void FUCOM(const X86::Instruction&);
|
||||
void FUCOMP(const X86::Instruction&);
|
||||
void FUCOMPP(const X86::Instruction&);
|
||||
void FUCOMI(const X86::Instruction&);
|
||||
void FUCOMIP(const X86::Instruction&);
|
||||
|
||||
void FICOM_RM16(const X86::Instruction&);
|
||||
void FICOM_RM32(const X86::Instruction&);
|
||||
void FICOMP_RM16(const X86::Instruction&);
|
||||
void FICOMP_RM32(const X86::Instruction&);
|
||||
|
||||
void FTST(const X86::Instruction&);
|
||||
void FXAM(const X86::Instruction&);
|
||||
|
||||
// TRANSCENDENTAL
|
||||
void FSIN(const X86::Instruction&);
|
||||
void FCOS(const X86::Instruction&);
|
||||
void FSINCOS(const X86::Instruction&);
|
||||
void FPTAN(const X86::Instruction&);
|
||||
void FPATAN(const X86::Instruction&);
|
||||
|
||||
void F2XM1(const X86::Instruction&);
|
||||
void FYL2X(const X86::Instruction&);
|
||||
void FYL2XP1(const X86::Instruction&);
|
||||
|
||||
// CONSTANT LOAD
|
||||
void FLD1(const X86::Instruction&);
|
||||
void FLDZ(const X86::Instruction&);
|
||||
void FLDPI(const X86::Instruction&);
|
||||
void FLDL2E(const X86::Instruction&);
|
||||
void FLDLN2(const X86::Instruction&);
|
||||
void FLDL2T(const X86::Instruction&);
|
||||
void FLDLG2(const X86::Instruction&);
|
||||
|
||||
// CONTROL
|
||||
void FINCSTP(const X86::Instruction&);
|
||||
void FDECSTP(const X86::Instruction&);
|
||||
void FFREE(const X86::Instruction&);
|
||||
void FFREEP(const X86::Instruction&); // undocumented
|
||||
|
||||
// FIXME: Non N- versions?
|
||||
void FNINIT(const X86::Instruction&);
|
||||
void FNCLEX(const X86::Instruction&);
|
||||
|
||||
void FNSTCW(const X86::Instruction&);
|
||||
void FLDCW(const X86::Instruction&);
|
||||
|
||||
void FNSTENV(const X86::Instruction&);
|
||||
void FLDENV(const X86::Instruction&);
|
||||
|
||||
void FNSAVE(const X86::Instruction&);
|
||||
void FRSTOR(const X86::Instruction&);
|
||||
|
||||
void FNSTSW(const X86::Instruction&);
|
||||
void FNSTSW_AX(const X86::Instruction&);
|
||||
|
||||
// FIXME: WAIT && FWAIT
|
||||
void FNOP(const X86::Instruction&);
|
||||
|
||||
// FPU & SIMD MANAGEMENT
|
||||
// FIXME: FXSAVE && FXRSTOR
|
||||
|
||||
// DO NOTHING?
|
||||
// FIXME: FENI, FDISI, FSETPM
|
||||
void FNENI(const X86::Instruction&);
|
||||
void FNDISI(const X86::Instruction&);
|
||||
void FNSETPM(const X86::Instruction&);
|
||||
|
||||
// MMX
|
||||
// ARITHMETIC
|
||||
void PADDB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PADDW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PADDD_mm1_mm2m64(const X86::Instruction&);
|
||||
void PADDSB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PADDSW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PADDUSB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PADDUSW_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
void PSUBB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSUBW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSUBD_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSUBSB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSUBSW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSUBUSB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSUBUSW_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
void PMULHW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PMULLW_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
void PMADDWD_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
// COMPARISON
|
||||
void PCMPEQB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PCMPEQW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PCMPEQD_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
void PCMPGTB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PCMPGTW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PCMPGTD_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
// CONVERSION
|
||||
void PACKSSDW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PACKSSWB_mm1_mm2m64(const X86::Instruction&);
|
||||
void PACKUSWB_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
// UNPACK
|
||||
void PUNPCKHBW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PUNPCKHWD_mm1_mm2m64(const X86::Instruction&);
|
||||
void PUNPCKHDQ_mm1_mm2m64(const X86::Instruction&);
|
||||
void PUNPCKLBW_mm1_mm2m32(const X86::Instruction&);
|
||||
void PUNPCKLWD_mm1_mm2m32(const X86::Instruction&);
|
||||
void PUNPCKLDQ_mm1_mm2m32(const X86::Instruction&);
|
||||
|
||||
// LOGICAL
|
||||
void PAND_mm1_mm2m64(const X86::Instruction&);
|
||||
void PANDN_mm1_mm2m64(const X86::Instruction&);
|
||||
void POR_mm1_mm2m64(const X86::Instruction&);
|
||||
void PXOR_mm1_mm2m64(const X86::Instruction&);
|
||||
|
||||
// SHIFT
|
||||
void PSLLW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSLLW_mm1_imm8(const X86::Instruction&);
|
||||
void PSLLD_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSLLD_mm1_imm8(const X86::Instruction&);
|
||||
void PSLLQ_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSLLQ_mm1_imm8(const X86::Instruction&);
|
||||
void PSRAW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSRAW_mm1_imm8(const X86::Instruction&);
|
||||
void PSRAD_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSRAD_mm1_imm8(const X86::Instruction&);
|
||||
void PSRLW_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSRLW_mm1_imm8(const X86::Instruction&);
|
||||
void PSRLD_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSRLD_mm1_imm8(const X86::Instruction&);
|
||||
void PSRLQ_mm1_mm2m64(const X86::Instruction&);
|
||||
void PSRLQ_mm1_imm8(const X86::Instruction&);
|
||||
|
||||
// DATA TRANSFER
|
||||
void MOVD_mm1_rm32(const X86::Instruction&);
|
||||
void MOVD_rm32_mm2(const X86::Instruction&);
|
||||
|
||||
void MOVQ_mm1_mm2m64(const X86::Instruction&);
|
||||
void MOVQ_mm1m64_mm2(const X86::Instruction&);
|
||||
void MOVQ_mm1_rm64(const X86::Instruction&); // long mode
|
||||
void MOVQ_rm64_mm2(const X86::Instruction&); // long mode
|
||||
|
||||
// EMPTY MMX STATE
|
||||
void EMMS(const X86::Instruction&);
|
||||
};
|
||||
|
||||
}
|
|
@ -1,384 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SoftMMU.h"
|
||||
#include "Emulator.h"
|
||||
#include "MmapRegion.h"
|
||||
#include "Report.h"
|
||||
#include <AK/ByteBuffer.h>
|
||||
#include <AK/Memory.h>
|
||||
#include <AK/QuickSort.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
SoftMMU::SoftMMU(Emulator& emulator)
|
||||
: m_emulator(emulator)
|
||||
{
|
||||
}
|
||||
|
||||
void SoftMMU::add_region(NonnullOwnPtr<Region> region)
|
||||
{
|
||||
VERIFY(!find_region({ 0x23, region->base() }));
|
||||
|
||||
size_t first_page_in_region = region->base() / PAGE_SIZE;
|
||||
size_t last_page_in_region = (region->base() + region->size() - 1) / PAGE_SIZE;
|
||||
for (size_t page = first_page_in_region; page <= last_page_in_region; ++page) {
|
||||
m_page_to_region_map[page] = region.ptr();
|
||||
}
|
||||
|
||||
m_regions.append(move(region));
|
||||
quick_sort((Vector<OwnPtr<Region>>&)m_regions, [](auto& a, auto& b) { return a->base() < b->base(); });
|
||||
}
|
||||
|
||||
void SoftMMU::remove_region(Region& region)
|
||||
{
|
||||
size_t first_page_in_region = region.base() / PAGE_SIZE;
|
||||
for (size_t i = 0; i < ceil_div(region.size(), PAGE_SIZE); ++i) {
|
||||
m_page_to_region_map[first_page_in_region + i] = nullptr;
|
||||
}
|
||||
|
||||
m_regions.remove_first_matching([&](auto& entry) { return entry.ptr() == ®ion; });
|
||||
}
|
||||
|
||||
void SoftMMU::ensure_split_at(X86::LogicalAddress address)
|
||||
{
|
||||
// FIXME: If this fails, call Emulator::dump_backtrace
|
||||
VERIFY(address.selector() != 0x2b);
|
||||
|
||||
u32 offset = address.offset();
|
||||
VERIFY((offset & (PAGE_SIZE - 1)) == 0);
|
||||
size_t page_index = address.offset() / PAGE_SIZE;
|
||||
|
||||
if (!page_index)
|
||||
return;
|
||||
if (m_page_to_region_map[page_index - 1] != m_page_to_region_map[page_index])
|
||||
return;
|
||||
if (!m_page_to_region_map[page_index])
|
||||
return;
|
||||
|
||||
// If we get here, we know that the page exists and belongs to a region, that there is
|
||||
// a previous page, and that it belongs to the same region.
|
||||
auto* old_region = verify_cast<MmapRegion>(m_page_to_region_map[page_index]);
|
||||
|
||||
// dbgln("splitting at {:p}", address.offset());
|
||||
// dbgln(" old region: {:p}-{:p}", old_region->base(), old_region->end() - 1);
|
||||
|
||||
NonnullOwnPtr<MmapRegion> new_region = old_region->split_at(VirtualAddress(offset));
|
||||
// dbgln(" new region: {:p}-{:p}", new_region->base(), new_region->end() - 1);
|
||||
// dbgln(" up old region: {:p}-{:p}", old_region->base(), old_region->end() - 1);
|
||||
|
||||
size_t first_page_in_region = new_region->base() / PAGE_SIZE;
|
||||
size_t last_page_in_region = (new_region->base() + new_region->size() - 1) / PAGE_SIZE;
|
||||
|
||||
// dbgln(" @ remapping pages {} thru {}", first_page_in_region, last_page_in_region);
|
||||
|
||||
for (size_t page = first_page_in_region; page <= last_page_in_region; ++page) {
|
||||
VERIFY(m_page_to_region_map[page] == old_region);
|
||||
m_page_to_region_map[page] = new_region.ptr();
|
||||
}
|
||||
|
||||
m_regions.append(move(new_region));
|
||||
quick_sort((Vector<OwnPtr<Region>>&)m_regions, [](auto& a, auto& b) { return a->base() < b->base(); });
|
||||
}
|
||||
|
||||
void SoftMMU::set_tls_region(NonnullOwnPtr<Region> region)
|
||||
{
|
||||
VERIFY(!m_tls_region);
|
||||
m_tls_region = move(region);
|
||||
}
|
||||
|
||||
ValueWithShadow<u8> SoftMMU::read8(X86::LogicalAddress address)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read8: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read8: Non-readable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
return region->read8(address.offset() - region->base());
|
||||
}
|
||||
|
||||
ValueWithShadow<u16> SoftMMU::read16(X86::LogicalAddress address)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read16: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read16: Non-readable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
return region->read16(address.offset() - region->base());
|
||||
}
|
||||
|
||||
ValueWithShadow<u32> SoftMMU::read32(X86::LogicalAddress address)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read32: No region for @ {:04x}:{:p}"sv, address.selector(), address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read32: Non-readable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
return region->read32(address.offset() - region->base());
|
||||
}
|
||||
|
||||
ValueWithShadow<u64> SoftMMU::read64(X86::LogicalAddress address)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read64: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read64: Non-readable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
return region->read64(address.offset() - region->base());
|
||||
}
|
||||
|
||||
ValueWithShadow<u128> SoftMMU::read128(X86::LogicalAddress address)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read128: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read128: Non-readable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
return region->read128(address.offset() - region->base());
|
||||
}
|
||||
|
||||
ValueWithShadow<u256> SoftMMU::read256(X86::LogicalAddress address)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read256: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read256: Non-readable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
return region->read256(address.offset() - region->base());
|
||||
}
|
||||
|
||||
void SoftMMU::write8(X86::LogicalAddress address, ValueWithShadow<u8> value)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::write8: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_writable()) {
|
||||
reportln("SoftMMU::write8: Non-writable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
region->write8(address.offset() - region->base(), value);
|
||||
}
|
||||
|
||||
void SoftMMU::write16(X86::LogicalAddress address, ValueWithShadow<u16> value)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::write16: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_writable()) {
|
||||
reportln("SoftMMU::write16: Non-writable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
region->write16(address.offset() - region->base(), value);
|
||||
}
|
||||
|
||||
void SoftMMU::write32(X86::LogicalAddress address, ValueWithShadow<u32> value)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::write32: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_writable()) {
|
||||
reportln("SoftMMU::write32: Non-writable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
region->write32(address.offset() - region->base(), value);
|
||||
}
|
||||
|
||||
void SoftMMU::write64(X86::LogicalAddress address, ValueWithShadow<u64> value)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::write64: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_writable()) {
|
||||
reportln("SoftMMU::write64: Non-writable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
region->write64(address.offset() - region->base(), value);
|
||||
}
|
||||
|
||||
void SoftMMU::write128(X86::LogicalAddress address, ValueWithShadow<u128> value)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::write128: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_writable()) {
|
||||
reportln("SoftMMU::write128: Non-writable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
region->write128(address.offset() - region->base(), value);
|
||||
}
|
||||
|
||||
void SoftMMU::write256(X86::LogicalAddress address, ValueWithShadow<u256> value)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::write256: No region for @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_writable()) {
|
||||
reportln("SoftMMU::write256: Non-writable region @ {:p}"sv, address.offset());
|
||||
m_emulator.dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
region->write256(address.offset() - region->base(), value);
|
||||
}
|
||||
|
||||
void SoftMMU::copy_to_vm(FlatPtr destination, void const* source, size_t size)
|
||||
{
|
||||
// FIXME: We should have a way to preserve the shadow data here as well.
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
write8({ 0x23, destination + i }, shadow_wrap_as_initialized(((u8 const*)source)[i]));
|
||||
}
|
||||
|
||||
void SoftMMU::copy_from_vm(void* destination, const FlatPtr source, size_t size)
|
||||
{
|
||||
// FIXME: We should have a way to preserve the shadow data here as well.
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
((u8*)destination)[i] = read8({ 0x23, source + i }).value();
|
||||
}
|
||||
|
||||
ByteBuffer SoftMMU::copy_buffer_from_vm(const FlatPtr source, size_t size)
|
||||
{
|
||||
auto buffer = ByteBuffer::create_uninitialized(size).release_value_but_fixme_should_propagate_errors(); // FIXME: Handle possible OOM situation.
|
||||
copy_from_vm(buffer.data(), source, size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool SoftMMU::fast_fill_memory8(X86::LogicalAddress address, size_t size, ValueWithShadow<u8> value)
|
||||
{
|
||||
if (!size)
|
||||
return true;
|
||||
auto* region = find_region(address);
|
||||
if (!region)
|
||||
return false;
|
||||
if (!region->contains(address.offset() + size - 1))
|
||||
return false;
|
||||
|
||||
if (is<MmapRegion>(*region) && static_cast<MmapRegion const&>(*region).is_malloc_block()) {
|
||||
if (auto* tracer = m_emulator.malloc_tracer()) {
|
||||
// FIXME: Add a way to audit an entire range of memory instead of looping here!
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
tracer->audit_write(*region, address.offset() + (i * sizeof(u8)), sizeof(u8));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t offset_in_region = address.offset() - region->base();
|
||||
memset(region->data() + offset_in_region, value.value(), size);
|
||||
memset(region->shadow_data() + offset_in_region, value.shadow()[0], size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SoftMMU::fast_fill_memory32(X86::LogicalAddress address, size_t count, ValueWithShadow<u32> value)
|
||||
{
|
||||
if (!count)
|
||||
return true;
|
||||
auto* region = find_region(address);
|
||||
if (!region)
|
||||
return false;
|
||||
if (!region->contains(address.offset() + (count * sizeof(u32)) - 1))
|
||||
return false;
|
||||
|
||||
if (is<MmapRegion>(*region) && static_cast<MmapRegion const&>(*region).is_malloc_block()) {
|
||||
if (auto* tracer = m_emulator.malloc_tracer()) {
|
||||
// FIXME: Add a way to audit an entire range of memory instead of looping here!
|
||||
for (size_t i = 0; i < count; ++i) {
|
||||
tracer->audit_write(*region, address.offset() + (i * sizeof(u32)), sizeof(u32));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t offset_in_region = address.offset() - region->base();
|
||||
fast_u32_fill((u32*)(region->data() + offset_in_region), value.value(), count);
|
||||
fast_u32_fill((u32*)(region->shadow_data() + offset_in_region), value.shadow_as_value(), count);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SoftMMU::dump_backtrace()
|
||||
{
|
||||
m_emulator.dump_backtrace();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,149 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Region.h"
|
||||
#include "Report.h"
|
||||
#include "ValueWithShadow.h"
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibX86/Instruction.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
class Emulator;
|
||||
|
||||
class SoftMMU {
|
||||
public:
|
||||
explicit SoftMMU(Emulator&);
|
||||
|
||||
ValueWithShadow<u8> read8(X86::LogicalAddress);
|
||||
ValueWithShadow<u16> read16(X86::LogicalAddress);
|
||||
ValueWithShadow<u32> read32(X86::LogicalAddress);
|
||||
ValueWithShadow<u64> read64(X86::LogicalAddress);
|
||||
ValueWithShadow<u128> read128(X86::LogicalAddress);
|
||||
ValueWithShadow<u256> read256(X86::LogicalAddress);
|
||||
|
||||
void dump_backtrace();
|
||||
|
||||
template<typename T>
|
||||
ValueWithShadow<T> read(X86::LogicalAddress address)
|
||||
requires(IsTriviallyConstructible<T>)
|
||||
{
|
||||
auto* region = find_region(address);
|
||||
if (!region) {
|
||||
reportln("SoftMMU::read256: No region for @ {:p}"sv, address.offset());
|
||||
dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
if (!region->is_readable()) {
|
||||
reportln("SoftMMU::read256: Non-readable region @ {:p}"sv, address.offset());
|
||||
dump_backtrace();
|
||||
TODO();
|
||||
}
|
||||
|
||||
alignas(alignof(T)) u8 data[sizeof(T)];
|
||||
Array<u8, sizeof(T)> shadow;
|
||||
|
||||
for (auto i = 0u; i < sizeof(T); ++i) {
|
||||
auto result = region->read8(address.offset() - region->base() + i);
|
||||
data[i] = result.value();
|
||||
shadow[i] = result.shadow()[0];
|
||||
}
|
||||
|
||||
return {
|
||||
*bit_cast<T*>(&data[0]),
|
||||
shadow,
|
||||
};
|
||||
}
|
||||
|
||||
void write8(X86::LogicalAddress, ValueWithShadow<u8>);
|
||||
void write16(X86::LogicalAddress, ValueWithShadow<u16>);
|
||||
void write32(X86::LogicalAddress, ValueWithShadow<u32>);
|
||||
void write64(X86::LogicalAddress, ValueWithShadow<u64>);
|
||||
void write128(X86::LogicalAddress, ValueWithShadow<u128>);
|
||||
void write256(X86::LogicalAddress, ValueWithShadow<u256>);
|
||||
|
||||
ALWAYS_INLINE Region* find_region(X86::LogicalAddress address)
|
||||
{
|
||||
if (address.selector() == 0x2b)
|
||||
return m_tls_region.ptr();
|
||||
|
||||
size_t page_index = address.offset() / PAGE_SIZE;
|
||||
return m_page_to_region_map[page_index];
|
||||
}
|
||||
|
||||
void add_region(NonnullOwnPtr<Region>);
|
||||
void remove_region(Region&);
|
||||
void ensure_split_at(X86::LogicalAddress);
|
||||
|
||||
void set_tls_region(NonnullOwnPtr<Region>);
|
||||
|
||||
bool fast_fill_memory8(X86::LogicalAddress, size_t size, ValueWithShadow<u8>);
|
||||
bool fast_fill_memory32(X86::LogicalAddress, size_t size, ValueWithShadow<u32>);
|
||||
|
||||
void copy_to_vm(FlatPtr destination, void const* source, size_t);
|
||||
void copy_from_vm(void* destination, const FlatPtr source, size_t);
|
||||
ByteBuffer copy_buffer_from_vm(const FlatPtr source, size_t);
|
||||
|
||||
template<typename Callback>
|
||||
void for_each_region(Callback callback)
|
||||
{
|
||||
if (m_tls_region) {
|
||||
if (callback(*m_tls_region) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
for (auto& region : m_regions) {
|
||||
if (callback(region) == IterationDecision::Break)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename Type, typename Callback>
|
||||
void for_each_region_of_type(Callback callback)
|
||||
{
|
||||
return for_each_region([callback](auto& region) {
|
||||
if (!is<Type>(region))
|
||||
return IterationDecision::Continue;
|
||||
return callback(static_cast<Type&>(region));
|
||||
});
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void for_regions_in(X86::LogicalAddress address, size_t size, Callback callback)
|
||||
{
|
||||
VERIFY(size > 0);
|
||||
X86::LogicalAddress address_end = address;
|
||||
address_end.set_offset(address_end.offset() + size);
|
||||
ensure_split_at(address);
|
||||
ensure_split_at(address_end);
|
||||
|
||||
size_t first_page = address.offset() / PAGE_SIZE;
|
||||
size_t last_page = (address_end.offset() - 1) / PAGE_SIZE;
|
||||
Region* last_reported = nullptr;
|
||||
for (size_t page = first_page; page <= last_page; ++page) {
|
||||
Region* current_region = m_page_to_region_map[page];
|
||||
if (page != first_page && current_region == last_reported)
|
||||
continue;
|
||||
if (callback(current_region) == IterationDecision::Break)
|
||||
return;
|
||||
last_reported = current_region;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Emulator& m_emulator;
|
||||
|
||||
Region* m_page_to_region_map[786432] = { nullptr };
|
||||
|
||||
OwnPtr<Region> m_tls_region;
|
||||
Vector<NonnullOwnPtr<Region>> m_regions;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,799 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Leon Albrecht <leon.a@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "SoftVPU.h"
|
||||
#include "SoftCPU.h"
|
||||
#include <AK/SIMDMath.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
void SoftVPU::PREFETCHTNTA(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PREFETCHT0(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PREFETCHT1(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PREFETCHT2(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::LDMXCSR(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Shadows
|
||||
m_mxcsr.mxcsr = insn.modrm().read32(m_cpu, insn).value();
|
||||
|
||||
// #GP - General Protection Fault
|
||||
VERIFY((m_mxcsr.mxcsr & 0xFFFF'0000) == 0);
|
||||
|
||||
// Just let the host's SSE (or if not available x87) handle the rounding for us
|
||||
// We do not want to accidentally raise an FP-Exception on the host, so we
|
||||
// mask all exceptions
|
||||
#ifdef __SSE__
|
||||
AK::MXCSR temp = m_mxcsr;
|
||||
temp.invalid_operation_mask = 1;
|
||||
temp.denormal_operation_mask = 1;
|
||||
temp.divide_by_zero_mask = 1;
|
||||
temp.overflow_mask = 1;
|
||||
temp.underflow_mask = 1;
|
||||
temp.precision_mask = 1;
|
||||
AK::set_mxcsr(temp);
|
||||
#else
|
||||
// FIXME: This will mess with x87-land, because it uses the same trick, and
|
||||
// Does not know of us doing this
|
||||
AK::X87ControlWord cw { 0x037F };
|
||||
cw.rounding_control = m_mxcsr.rounding_control;
|
||||
AK::set_cw_x87(cw);
|
||||
#endif
|
||||
}
|
||||
void SoftVPU::STMXCSR(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Shadows
|
||||
insn.modrm().write32(m_cpu, insn, ValueWithShadow<u32>::create_initialized(m_mxcsr.mxcsr));
|
||||
}
|
||||
|
||||
void SoftVPU::MOVUPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
m_xmm[xmm1] = m_xmm[insn.modrm().rm()];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
m_xmm[xmm1].ps = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
m_xmm[xmm1].ps[0] = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
m_xmm[xmm1].ps[0] = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVUPS_xmm1m128_xmm2(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm2 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
m_xmm[insn.modrm().rm()] = m_xmm[xmm2];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
u128 temp = bit_cast<u128>(m_xmm[xmm2]);
|
||||
insn.modrm().write128(m_cpu, insn, ValueWithShadow<u128>::create_initialized(temp));
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVSS_xmm1m32_xmm2(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm2 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
m_xmm[insn.modrm().rm()].ps[0] = m_xmm[xmm2].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
u32 temp = bit_cast<u32>(m_xmm[xmm2].ps[0]);
|
||||
insn.modrm().write32(m_cpu, insn, ValueWithShadow<u32>::create_initialized(temp));
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVLPS_xmm1_xmm2m64(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
// Note: MOVHLPS
|
||||
m_xmm[xmm1].puqw[0] = m_xmm[insn.modrm().rm()].puqw[1];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
// Note: Technically we are transferring two packed floats not a quad word
|
||||
m_xmm[xmm1].puqw[0] = insn.modrm().read64(m_cpu, insn).value();
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVLPS_m64_xmm2(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm2 = insn.modrm().reg();
|
||||
// FIXME: This might not hold true for SSE2 or later
|
||||
VERIFY(!insn.modrm().is_register());
|
||||
// Note: Technically we are transferring two packed floats not a quad word
|
||||
insn.modrm().write64(m_cpu, insn, ValueWithShadow<u64>::create_initialized(m_xmm[xmm2].puqw[0]));
|
||||
}
|
||||
|
||||
void SoftVPU::UNPCKLPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
f32x4& xmm1 = m_xmm[insn.modrm().reg()].ps;
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
f32x4 dest;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
dest[0] = xmm1[0];
|
||||
dest[1] = xmm2m128[0];
|
||||
dest[2] = xmm1[1];
|
||||
dest[3] = xmm2m128[1];
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = dest;
|
||||
}
|
||||
void SoftVPU::UNPCKHPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
f32x4 xmm1 = m_xmm[insn.modrm().reg()].ps;
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
f32x4 dest;
|
||||
dest[0] = xmm1[2];
|
||||
dest[1] = xmm2m128[2];
|
||||
dest[2] = xmm1[3];
|
||||
dest[3] = xmm2m128[3];
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = dest;
|
||||
}
|
||||
|
||||
void SoftVPU::MOVHPS_xmm1_xmm2m64(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
// Note: MOVLHPS
|
||||
m_xmm[xmm1].puqw[1] = m_xmm[insn.modrm().rm()].puqw[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
// Note: Technically we are transferring two packed floats not a quad word
|
||||
m_xmm[xmm1].puqw[1] = insn.modrm().read64(m_cpu, insn).value();
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVHPS_m64_xmm2(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
VERIFY(!insn.modrm().is_register());
|
||||
// Note: Technically we are transferring two packed floats not a quad word
|
||||
insn.modrm().write64(m_cpu, insn, ValueWithShadow<u64>::create_initialized(m_xmm[xmm1].puqw[1]));
|
||||
}
|
||||
void SoftVPU::MOVAPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
m_xmm[xmm1] = m_xmm[insn.modrm().rm()];
|
||||
} else {
|
||||
// FIXME: Alignment-check 16
|
||||
auto temp = insn.modrm().read128(m_cpu, insn);
|
||||
m_xmm[xmm1].ps = bit_cast<f32x4>(temp.value());
|
||||
}
|
||||
}
|
||||
void SoftVPU::MOVAPS_xmm1m128_xmm2(X86::Instruction const& insn)
|
||||
{
|
||||
u8 xmm2 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
m_xmm[insn.modrm().rm()] = m_xmm[xmm2];
|
||||
} else {
|
||||
// FIXME: Alignment-check 16
|
||||
u128 temp = bit_cast<u128>(m_xmm[xmm2]);
|
||||
insn.modrm().write128(m_cpu, insn, ValueWithShadow<u128>::create_initialized(temp));
|
||||
}
|
||||
}
|
||||
|
||||
void SoftVPU::CVTPI2PS_xmm1_mm2m64(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Precision
|
||||
// FIXME: Honor Rounding control
|
||||
u8 xmm1 = insn.modrm().reg();
|
||||
if (insn.modrm().is_register()) {
|
||||
i32x2 mm = m_cpu.mmx_get(insn.modrm().rm()).v32;
|
||||
m_xmm[xmm1].ps[0] = mm[0];
|
||||
m_xmm[xmm1].ps[1] = mm[1];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
i32x2 m64 = bit_cast<i32x2>(insn.modrm().read64(m_cpu, insn).value());
|
||||
m_xmm[xmm1].ps[0] = m64[0];
|
||||
m_xmm[xmm1].ps[1] = m64[1];
|
||||
}
|
||||
}
|
||||
void SoftVPU::CVTSI2SS_xmm1_rm32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Precision
|
||||
// FIXME: Shadows
|
||||
// FIXME: Honor Rounding Control
|
||||
m_xmm[insn.modrm().reg()].ps[0] = (i32)insn.modrm().read32(m_cpu, insn).value();
|
||||
}
|
||||
|
||||
void SoftVPU::MOVNTPS_xmm1m128_xmm2(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::CVTTPS2PI_mm1_xmm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::CVTTSS2SI_r32_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid, Precision
|
||||
float value;
|
||||
if (insn.modrm().is_register())
|
||||
value = m_xmm[insn.modrm().rm()].ps[0];
|
||||
else
|
||||
value = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
|
||||
m_cpu.gpr32(insn.reg32()) = ValueWithShadow<u32>::create_initialized((u32)(i32)truncf(value));
|
||||
}
|
||||
void SoftVPU::CVTPS2PI_xmm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::CVTSS2SI_r32_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid, Precision
|
||||
insn.modrm().write32(m_cpu, insn,
|
||||
ValueWithShadow<u32>::create_initialized(static_cast<i32>(m_xmm[insn.modrm().reg()].ps[0])));
|
||||
}
|
||||
|
||||
void SoftVPU::UCOMISS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
float xmm1 = m_xmm[insn.modrm().reg()].ps[0];
|
||||
float xmm2m32;
|
||||
if (insn.modrm().is_register())
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
else
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
// FIXME: Raise Invalid on SNaN
|
||||
if (isnan(xmm1) || isnan(xmm2m32)) {
|
||||
m_cpu.set_zf(true);
|
||||
m_cpu.set_pf(true);
|
||||
m_cpu.set_cf(true);
|
||||
} else {
|
||||
m_cpu.set_zf(xmm1 == xmm2m32);
|
||||
m_cpu.set_pf(false);
|
||||
m_cpu.set_cf(xmm1 < xmm2m32);
|
||||
}
|
||||
m_cpu.set_of(false);
|
||||
m_cpu.set_af(false);
|
||||
m_cpu.set_sf(false);
|
||||
}
|
||||
void SoftVPU::COMISS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise on QNaN
|
||||
UCOMISS_xmm1_xmm2m32(insn);
|
||||
}
|
||||
|
||||
void SoftVPU::MOVMSKPS_reg_xmm(X86::Instruction const& insn)
|
||||
{
|
||||
VERIFY(insn.modrm().is_register());
|
||||
u8 mask = 0;
|
||||
f32x4 xmm = m_xmm[insn.modrm().rm()].ps;
|
||||
mask |= signbit(xmm[0]) << 0;
|
||||
mask |= signbit(xmm[1]) << 1;
|
||||
mask |= signbit(xmm[2]) << 2;
|
||||
mask |= signbit(xmm[3]) << 3;
|
||||
|
||||
m_cpu.gpr32(insn.reg32()) = ValueWithShadow<u32>::create_initialized(mask);
|
||||
}
|
||||
|
||||
void SoftVPU::SQRTPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid, Precision, Denormal
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = sqrt(xmm2m128);
|
||||
}
|
||||
void SoftVPU::SQRTSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid, Precision, Denormal
|
||||
float xmm2m32;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] = AK::sqrt(xmm2m32);
|
||||
}
|
||||
void SoftVPU::RSQRTPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
f32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = rsqrt(xmm2m128);
|
||||
}
|
||||
void SoftVPU::RSQRTSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
float xmm2m32;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] = AK::rsqrt(xmm2m32);
|
||||
}
|
||||
|
||||
void SoftVPU::RCPPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
f32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = 1.f / xmm2m128;
|
||||
}
|
||||
void SoftVPU::RCPSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
float xmm2m32;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] = 1.f / xmm2m32;
|
||||
}
|
||||
|
||||
void SoftVPU::ANDPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
u32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].pudw;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<u32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].pudw &= xmm2m128;
|
||||
}
|
||||
void SoftVPU::ANDNPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
u32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].pudw;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<u32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
u32x4& xmm1 = m_xmm[insn.modrm().reg()].pudw;
|
||||
xmm1 = ~xmm1 & xmm2m128;
|
||||
}
|
||||
void SoftVPU::ORPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
u32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].pudw;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<u32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].pudw |= xmm2m128;
|
||||
}
|
||||
void SoftVPU::XORPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
u32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].pudw;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<u32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].pudw ^= xmm2m128;
|
||||
}
|
||||
|
||||
void SoftVPU::ADDPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Precision, Denormal
|
||||
f32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps += xmm2m128;
|
||||
}
|
||||
void SoftVPU::ADDSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Precision, Denormal
|
||||
float xmm2m32;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] += xmm2m32;
|
||||
}
|
||||
|
||||
void SoftVPU::MULPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Precision, Denormal
|
||||
f32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps *= xmm2m128;
|
||||
}
|
||||
void SoftVPU::MULSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Precision, Denormal
|
||||
float xmm1 = m_xmm[insn.modrm().reg()].ps[0];
|
||||
float xmm2m32;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
xmm1 *= xmm2m32;
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] *= xmm1;
|
||||
}
|
||||
|
||||
void SoftVPU::SUBPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Precision, Denormal
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps -= xmm2m128;
|
||||
}
|
||||
void SoftVPU::SUBSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Precision, Denormal
|
||||
float xmm2m32;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] -= xmm2m32;
|
||||
}
|
||||
|
||||
void SoftVPU::MINPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid (including QNaN Source Operand), Denormal
|
||||
f32x4 xmm1 = m_xmm[insn.modrm().reg()].ps;
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 4; ++i) {
|
||||
// When only one is NaN or both are 0.0s (of either sign), or
|
||||
// FIXME: xmm2m32 is SNaN
|
||||
// xmm2m32 is returned unchanged
|
||||
if (isnan(xmm1[i]) || isnan(xmm2m128[i]) || xmm1[i] == xmm2m128[i])
|
||||
xmm1[i] = xmm2m128[i];
|
||||
else
|
||||
xmm1[i] = min(xmm1[i], xmm2m128[i]);
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = xmm1;
|
||||
}
|
||||
void SoftVPU::MINSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid (Including QNaN Source Operand), Denormal
|
||||
float xmm1 = m_xmm[insn.modrm().reg()].ps[0];
|
||||
float xmm2m32;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
// When only one is NaN or both are 0.0s (of either sign), or
|
||||
// FIXME: xmm2m32 is SNaN
|
||||
// xmm2m32 is returned unchanged
|
||||
if (isnan(xmm1) || isnan(xmm2m32) || xmm1 == xmm2m32)
|
||||
xmm1 = xmm2m32;
|
||||
else
|
||||
xmm1 = min(xmm1, xmm2m32);
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] = xmm1;
|
||||
}
|
||||
|
||||
void SoftVPU::DIVPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Divide-by-Zero, Precision, Denormal
|
||||
f32x4 xmm2m128;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps /= xmm2m128;
|
||||
}
|
||||
void SoftVPU::DIVSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// Raise Overflow, Underflow, Invalid, Divide-by-Zero, Precision, Denormal
|
||||
float xmm2m32;
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] /= xmm2m32;
|
||||
}
|
||||
|
||||
void SoftVPU::MAXPS_xmm1_xmm2m128(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid (including QNaN Source Operand), Denormal
|
||||
f32x4 xmm1 = m_xmm[insn.modrm().reg()].ps;
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
for (auto i = 0; i < 4; ++i) {
|
||||
// When only one is NaN or both are 0.0s (of either sign), or
|
||||
// FIXME: xmm2m32 is SNaN
|
||||
// xmm2m32 is returned unchanged
|
||||
if (isnan(xmm1[i]) || isnan(xmm2m128[i]) || xmm1[i] == xmm2m128[i])
|
||||
xmm1[i] = xmm2m128[i];
|
||||
else
|
||||
xmm1[i] = max(xmm1[i], xmm2m128[i]);
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = xmm1;
|
||||
}
|
||||
void SoftVPU::MAXSS_xmm1_xmm2m32(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Invalid (Including QNaN Source Operand), Denormal
|
||||
float xmm1 = m_xmm[insn.modrm().reg()].ps[0];
|
||||
float xmm2m32;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m32 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m32 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
// When only one is NaN or both are 0.0s (of either sign), or
|
||||
// FIXME: xmm2m32 is SNaN
|
||||
// xmm2m32 is returned unchanged
|
||||
if (isnan(xmm1) || isnan(xmm2m32) || xmm1 == xmm2m32)
|
||||
xmm1 = xmm2m32;
|
||||
else
|
||||
xmm1 = max(xmm1, xmm2m32);
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps[0] = xmm1;
|
||||
}
|
||||
|
||||
void SoftVPU::PSHUFW_mm1_mm2m64_imm8(X86::Instruction const& insn)
|
||||
{
|
||||
MMX src;
|
||||
if (insn.modrm().is_register()) {
|
||||
src = m_cpu.mmx_get(insn.modrm().rm());
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
src = bit_cast<MMX>(insn.modrm().read64(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
u8 order = insn.imm8();
|
||||
MMX dest;
|
||||
|
||||
dest.v16u[0] = src.v16u[(order >> 0) & 0b11];
|
||||
dest.v16u[1] = src.v16u[(order >> 2) & 0b11];
|
||||
dest.v16u[2] = src.v16u[(order >> 4) & 0b11];
|
||||
dest.v16u[3] = src.v16u[(order >> 6) & 0b11];
|
||||
|
||||
m_cpu.mmx_set(insn.modrm().reg(), dest);
|
||||
}
|
||||
|
||||
void SoftVPU::CMPPS_xmm1_xmm2m128_imm8(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Denormal, Invalid Operation (QNaN dependent on imm8)
|
||||
XMM& xmm1 = m_xmm[insn.modrm().reg()];
|
||||
f32x4 xmm2m128;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
using enum ComparePredicate;
|
||||
switch ((ComparePredicate)insn.imm8()) {
|
||||
case EQ:
|
||||
xmm1.ps = xmm1.ps == xmm2m128;
|
||||
break;
|
||||
case LT:
|
||||
xmm1.ps = xmm1.ps < xmm2m128;
|
||||
break;
|
||||
case LE:
|
||||
xmm1.ps = xmm1.ps <= xmm2m128;
|
||||
break;
|
||||
case UNORD:
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
xmm1.pudw[i] = 0xFFFF'FFFF * (isnan(xmm1.ps[i]) || isnan(xmm2m128[i]));
|
||||
break;
|
||||
case NEQ:
|
||||
xmm1.ps = xmm1.ps != xmm2m128;
|
||||
break;
|
||||
case NLT:
|
||||
xmm1.ps = xmm1.ps >= xmm2m128;
|
||||
break;
|
||||
case NLE:
|
||||
xmm1.ps = xmm1.ps > xmm2m128;
|
||||
break;
|
||||
case ORD:
|
||||
for (auto i = 0; i < 4; ++i)
|
||||
xmm1.pudw[i] = 0xFFFF'FFFF * (!isnan(xmm1.ps[i]) && !isnan(xmm2m128[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
void SoftVPU::CMPSS_xmm1_xmm2m32_imm8(X86::Instruction const& insn)
|
||||
{
|
||||
// FIXME: Raise Denormal, Invalid Operation (QNaN dependent on imm8)
|
||||
float xmm1 = m_xmm[insn.modrm().reg()].ps[0];
|
||||
float xmm2m128;
|
||||
bool res;
|
||||
|
||||
if (insn.modrm().is_register()) {
|
||||
xmm2m128 = m_xmm[insn.modrm().rm()].ps[0];
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
xmm2m128 = bit_cast<float>(insn.modrm().read32(m_cpu, insn).value());
|
||||
}
|
||||
using enum ComparePredicate;
|
||||
switch ((ComparePredicate)insn.imm8()) {
|
||||
case EQ:
|
||||
res = xmm1 == xmm2m128;
|
||||
break;
|
||||
case LT:
|
||||
res = xmm1 < xmm2m128;
|
||||
break;
|
||||
case LE:
|
||||
res = xmm1 <= xmm2m128;
|
||||
break;
|
||||
case UNORD:
|
||||
res = isnan(xmm1) || isnan(xmm2m128);
|
||||
break;
|
||||
case NEQ:
|
||||
res = xmm1 != xmm2m128;
|
||||
break;
|
||||
case NLT:
|
||||
res = xmm1 >= xmm2m128;
|
||||
break;
|
||||
case NLE:
|
||||
res = xmm1 > xmm2m128;
|
||||
break;
|
||||
case ORD:
|
||||
res = !isnan(xmm1) && !isnan(xmm2m128);
|
||||
break;
|
||||
}
|
||||
|
||||
m_xmm[insn.modrm().reg()].pudw[0] = 0xFFFF'FFFF * res;
|
||||
}
|
||||
|
||||
void SoftVPU::PINSRW_mm1_r32m16_imm8(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PINSRW_xmm1_r32m16_imm8(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PEXTRW_reg_mm1_imm8(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PEXTRW_reg_xmm1_imm8(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::SHUFPS_xmm1_xmm2m128_imm8(X86::Instruction const& insn)
|
||||
{
|
||||
f32x4 src;
|
||||
if (insn.modrm().is_register()) {
|
||||
src = m_xmm[insn.modrm().rm()].ps;
|
||||
} else {
|
||||
// FIXME: Shadows
|
||||
src = bit_cast<f32x4>(insn.modrm().read128(m_cpu, insn).value());
|
||||
}
|
||||
|
||||
u8 order = insn.imm8();
|
||||
f32x4 dest;
|
||||
dest[0] = src[(order >> 0) & 0b11];
|
||||
dest[1] = src[(order >> 2) & 0b11];
|
||||
dest[2] = src[(order >> 4) & 0b11];
|
||||
dest[3] = src[(order >> 6) & 0b11];
|
||||
|
||||
m_xmm[insn.modrm().reg()].ps = dest;
|
||||
}
|
||||
|
||||
void SoftVPU::PMOVMSKB_reg_mm1(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PMOVMSKB_reg_xmm1(X86::Instruction const& insn)
|
||||
{
|
||||
VERIFY(insn.modrm().is_register());
|
||||
XMM src = m_xmm[insn.modrm().rm()];
|
||||
|
||||
u32 dest = 0;
|
||||
for (int i = 0; i < 16; ++i)
|
||||
dest |= (src.pub[i] >> 7) << i;
|
||||
|
||||
m_cpu.gpr32(insn.reg32()) = ValueWithShadow<u32>::create_initialized(dest);
|
||||
}
|
||||
|
||||
void SoftVPU::PMINUB_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PMINUB_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PMAXUB_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PMAXUB_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PAVGB_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PAVGB_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PAVGW_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PAVGW_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PMULHUW_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PMULHUW_xmm1_xmm2m64(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::MOVNTQ_m64_mm1(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PMINSB_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PMINSB_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PMAXSB_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PMAXSB_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::PSADBB_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
void SoftVPU::PSADBB_xmm1_xmm2m128(X86::Instruction const&) { TODO(); }
|
||||
|
||||
void SoftVPU::MASKMOVQ_mm1_mm2m64(X86::Instruction const&) { TODO(); }
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Leon Albrecht <leon.a@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/FPControl.h>
|
||||
#include <AK/SIMD.h>
|
||||
#include <AK/Types.h>
|
||||
#include <LibX86/Instruction.h>
|
||||
#include <math.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
using AK::RoundingMode;
|
||||
using namespace AK::SIMD;
|
||||
class Emulator;
|
||||
class SoftCPU;
|
||||
|
||||
union XMM {
|
||||
f32x4 ps;
|
||||
f64x2 pd;
|
||||
i8x16 psb;
|
||||
u8x16 pub;
|
||||
i16x8 psw;
|
||||
u16x8 puw;
|
||||
u32x4 pudw;
|
||||
u64x2 puqw;
|
||||
};
|
||||
|
||||
class SoftVPU {
|
||||
public:
|
||||
SoftVPU(Emulator& emulator, SoftCPU& cpu)
|
||||
: m_emulator(emulator)
|
||||
, m_cpu(cpu)
|
||||
, m_mxcsr { 0x1F80 }
|
||||
{
|
||||
}
|
||||
|
||||
XMM& operator[](u8 index) { return m_xmm[index]; }
|
||||
|
||||
enum class RoundingMode : u8 {
|
||||
NEAREST = 0b00,
|
||||
DOWN = 0b01,
|
||||
UP = 0b10,
|
||||
TRUNC = 0b11
|
||||
};
|
||||
|
||||
enum class ComparePredicate : u8 {
|
||||
EQ = 0,
|
||||
LT = 1,
|
||||
LE = 2,
|
||||
UNORD = 3,
|
||||
NEQ = 4,
|
||||
NLT = 5,
|
||||
NLE = 6,
|
||||
ORD = 7
|
||||
// FIXME: More with VEX prefix
|
||||
};
|
||||
|
||||
private:
|
||||
friend SoftCPU;
|
||||
Emulator& m_emulator;
|
||||
SoftCPU& m_cpu;
|
||||
|
||||
XMM m_xmm[8];
|
||||
|
||||
// FIXME: Maybe unimplemented features:
|
||||
// * DAZ
|
||||
// * FTZ
|
||||
AK::MXCSR m_mxcsr;
|
||||
|
||||
void PREFETCHTNTA(X86::Instruction const&);
|
||||
void PREFETCHT0(X86::Instruction const&);
|
||||
void PREFETCHT1(X86::Instruction const&);
|
||||
void PREFETCHT2(X86::Instruction const&);
|
||||
void LDMXCSR(X86::Instruction const&);
|
||||
void STMXCSR(X86::Instruction const&);
|
||||
void MOVUPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MOVSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void MOVUPS_xmm1m128_xmm2(X86::Instruction const&);
|
||||
void MOVSS_xmm1m32_xmm2(X86::Instruction const&);
|
||||
void MOVLPS_xmm1_xmm2m64(X86::Instruction const&);
|
||||
void MOVLPS_m64_xmm2(X86::Instruction const&);
|
||||
void UNPCKLPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void UNPCKHPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MOVHPS_xmm1_xmm2m64(X86::Instruction const&);
|
||||
void MOVHPS_m64_xmm2(X86::Instruction const&);
|
||||
void MOVAPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MOVAPS_xmm1m128_xmm2(X86::Instruction const&);
|
||||
void CVTPI2PS_xmm1_mm2m64(X86::Instruction const&);
|
||||
void CVTSI2SS_xmm1_rm32(X86::Instruction const&);
|
||||
void MOVNTPS_xmm1m128_xmm2(X86::Instruction const&);
|
||||
void CVTTPS2PI_mm1_xmm2m64(X86::Instruction const&);
|
||||
void CVTTSS2SI_r32_xmm2m32(X86::Instruction const&);
|
||||
void CVTPS2PI_xmm1_mm2m64(X86::Instruction const&);
|
||||
void CVTSS2SI_r32_xmm2m32(X86::Instruction const&);
|
||||
void UCOMISS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void COMISS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void MOVMSKPS_reg_xmm(X86::Instruction const&);
|
||||
void SQRTPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void SQRTSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void RSQRTPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void RSQRTSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void RCPPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void RCPSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void ANDPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void ANDNPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void ORPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void XORPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void ADDPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void ADDSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void MULPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MULSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void SUBPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void SUBSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void MINPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MINSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void DIVPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void DIVSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void MAXPS_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MAXSS_xmm1_xmm2m32(X86::Instruction const&);
|
||||
void PSHUFW_mm1_mm2m64_imm8(X86::Instruction const&);
|
||||
void CMPPS_xmm1_xmm2m128_imm8(X86::Instruction const&);
|
||||
void CMPSS_xmm1_xmm2m32_imm8(X86::Instruction const&);
|
||||
void PINSRW_mm1_r32m16_imm8(X86::Instruction const&);
|
||||
void PINSRW_xmm1_r32m16_imm8(X86::Instruction const&);
|
||||
void PEXTRW_reg_mm1_imm8(X86::Instruction const&);
|
||||
void PEXTRW_reg_xmm1_imm8(X86::Instruction const&);
|
||||
void SHUFPS_xmm1_xmm2m128_imm8(X86::Instruction const&);
|
||||
void PMOVMSKB_reg_mm1(X86::Instruction const&);
|
||||
void PMOVMSKB_reg_xmm1(X86::Instruction const&);
|
||||
void PMINUB_mm1_mm2m64(X86::Instruction const&);
|
||||
void PMINUB_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void PMAXUB_mm1_mm2m64(X86::Instruction const&);
|
||||
void PMAXUB_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void PAVGB_mm1_mm2m64(X86::Instruction const&);
|
||||
void PAVGB_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void PAVGW_mm1_mm2m64(X86::Instruction const&);
|
||||
void PAVGW_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void PMULHUW_mm1_mm2m64(X86::Instruction const&);
|
||||
void PMULHUW_xmm1_xmm2m64(X86::Instruction const&);
|
||||
void MOVNTQ_m64_mm1(X86::Instruction const&);
|
||||
void PMINSB_mm1_mm2m64(X86::Instruction const&);
|
||||
void PMINSB_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void PMAXSB_mm1_mm2m64(X86::Instruction const&);
|
||||
void PMAXSB_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void PSADBB_mm1_mm2m64(X86::Instruction const&);
|
||||
void PSADBB_xmm1_xmm2m128(X86::Instruction const&);
|
||||
void MASKMOVQ_mm1_mm2m64(X86::Instruction const&);
|
||||
};
|
||||
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Format.h>
|
||||
#include <AK/Platform.h>
|
||||
#include <AK/UFixedBigInt.h>
|
||||
#include <string.h>
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
template<typename T>
|
||||
class ValueAndShadowReference;
|
||||
|
||||
template<typename T>
|
||||
class ValueWithShadow {
|
||||
public:
|
||||
using ValueType = T;
|
||||
using ShadowType = Array<u8, sizeof(T)>;
|
||||
|
||||
ValueWithShadow() = default;
|
||||
|
||||
ValueWithShadow(T value, T shadow)
|
||||
: m_value(value)
|
||||
{
|
||||
ReadonlyBytes { &shadow, sizeof(shadow) }.copy_to(m_shadow);
|
||||
}
|
||||
|
||||
ValueWithShadow(T value, ShadowType shadow)
|
||||
: m_value(value)
|
||||
, m_shadow(shadow)
|
||||
{
|
||||
}
|
||||
|
||||
static ValueWithShadow create_initialized(T value)
|
||||
{
|
||||
ShadowType shadow;
|
||||
shadow.fill(0x01);
|
||||
return {
|
||||
value,
|
||||
shadow,
|
||||
};
|
||||
}
|
||||
|
||||
ValueWithShadow(ValueAndShadowReference<T> const&);
|
||||
|
||||
T value() const { return m_value; }
|
||||
ShadowType const& shadow() const { return m_shadow; }
|
||||
|
||||
T shadow_as_value() const
|
||||
requires(IsTriviallyConstructible<T>)
|
||||
{
|
||||
return *bit_cast<T const*>(m_shadow.data());
|
||||
}
|
||||
|
||||
template<auto member>
|
||||
auto reference_to()
|
||||
requires(IsClass<T> || IsUnion<T>)
|
||||
{
|
||||
using ResultType = ValueAndShadowReference<RemoveReference<decltype(declval<T>().*member)>>;
|
||||
return ResultType {
|
||||
m_value.*member,
|
||||
*bit_cast<typename ResultType::ShadowType*>(m_shadow.span().offset_pointer(bit_cast<u8*>(member) - bit_cast<u8*>(nullptr))),
|
||||
};
|
||||
}
|
||||
|
||||
template<auto member>
|
||||
auto slice() const
|
||||
requires(IsClass<T> || IsUnion<T>)
|
||||
{
|
||||
using ResultType = ValueWithShadow<RemoveReference<decltype(declval<T>().*member)>>;
|
||||
return ResultType {
|
||||
m_value.*member,
|
||||
*bit_cast<typename ResultType::ShadowType*>(m_shadow.span().offset_pointer(bit_cast<u8*>(member) - bit_cast<u8*>(nullptr))),
|
||||
};
|
||||
}
|
||||
|
||||
bool is_uninitialized() const
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(ShadowType); ++i) {
|
||||
if ((m_shadow[i] & 0x01) != 0x01)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void set_initialized()
|
||||
{
|
||||
m_shadow.fill(0x01);
|
||||
}
|
||||
|
||||
private:
|
||||
T m_value {};
|
||||
ShadowType m_shadow {};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class ValueAndShadowReference {
|
||||
public:
|
||||
using ValueType = T;
|
||||
using ShadowType = Array<u8, sizeof(T)>;
|
||||
|
||||
ValueAndShadowReference(T& value, ShadowType& shadow)
|
||||
: m_value(value)
|
||||
, m_shadow(shadow)
|
||||
{
|
||||
}
|
||||
|
||||
bool is_uninitialized() const
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(ShadowType); ++i) {
|
||||
if ((m_shadow[i] & 0x01) != 0x01)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ValueAndShadowReference<T>& operator=(ValueWithShadow<T> const&);
|
||||
|
||||
T shadow_as_value() const
|
||||
requires(IsTriviallyConstructible<T>)
|
||||
{
|
||||
return *bit_cast<T const*>(m_shadow.data());
|
||||
}
|
||||
|
||||
T& value() { return m_value; }
|
||||
ShadowType& shadow() { return m_shadow; }
|
||||
|
||||
T const& value() const { return m_value; }
|
||||
ShadowType const& shadow() const { return m_shadow; }
|
||||
|
||||
private:
|
||||
T& m_value;
|
||||
ShadowType& m_shadow;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_as_initialized(T value)
|
||||
{
|
||||
return ValueWithShadow<T>::create_initialized(value);
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, U const& taint_a)
|
||||
{
|
||||
if (taint_a.is_uninitialized())
|
||||
return { value, 0 };
|
||||
return shadow_wrap_as_initialized(value);
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename V>
|
||||
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, U const& taint_a, V const& taint_b)
|
||||
{
|
||||
if (taint_a.is_uninitialized() || taint_b.is_uninitialized())
|
||||
return { value, 0 };
|
||||
return shadow_wrap_as_initialized(value);
|
||||
}
|
||||
|
||||
template<typename T, typename U, typename V, typename X>
|
||||
ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_with_taint_from(T value, U const& taint_a, V const& taint_b, X const& taint_c)
|
||||
{
|
||||
if (taint_a.is_uninitialized() || taint_b.is_uninitialized() || taint_c.is_uninitialized())
|
||||
return { value, 0 };
|
||||
return shadow_wrap_as_initialized(value);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline ValueWithShadow<T>::ValueWithShadow(ValueAndShadowReference<T> const& other)
|
||||
: m_value(other.value())
|
||||
, m_shadow(other.shadow())
|
||||
{
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline ValueAndShadowReference<T>& ValueAndShadowReference<T>::operator=(ValueWithShadow<T> const& other)
|
||||
{
|
||||
m_value = other.value();
|
||||
m_shadow = other.shadow();
|
||||
return *this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct AK::Formatter<UserspaceEmulator::ValueWithShadow<T>> : AK::Formatter<T> {
|
||||
ErrorOr<void> format(FormatBuilder& builder, UserspaceEmulator::ValueWithShadow<T> value)
|
||||
{
|
||||
return Formatter<T>::format(builder, value.value());
|
||||
}
|
||||
};
|
|
@ -1,121 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include "Emulator.h"
|
||||
#include <AK/Format.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/ArgsParser.h>
|
||||
#include <LibCore/DirIterator.h>
|
||||
#include <LibCore/Process.h>
|
||||
#include <LibFileSystem/FileSystem.h>
|
||||
#include <fcntl.h>
|
||||
#include <pthread.h>
|
||||
#include <serenity.h>
|
||||
#include <string.h>
|
||||
|
||||
bool g_report_to_debug = false;
|
||||
|
||||
int main(int argc, char** argv, char** env)
|
||||
{
|
||||
Vector<StringView> arguments;
|
||||
bool pause_on_startup { false };
|
||||
ByteString profile_dump_path;
|
||||
bool enable_roi_mode { false };
|
||||
bool dump_profile { false };
|
||||
unsigned profile_instruction_interval { 0 };
|
||||
|
||||
Core::ArgsParser parser;
|
||||
parser.set_stop_on_first_non_option(true);
|
||||
parser.add_option(g_report_to_debug, "Write reports to the debug log", "report-to-debug", 0);
|
||||
parser.add_option(pause_on_startup, "Pause on startup", "pause", 'p');
|
||||
parser.add_option(dump_profile, "Generate a ProfileViewer-compatible profile", "profile", 0);
|
||||
parser.add_option(profile_instruction_interval, "Set the profile instruction capture interval, 128 by default", "profile-interval", 'i', "num_instructions");
|
||||
parser.add_option(profile_dump_path, "File path for profile dump", "profile-file", 0, "path");
|
||||
parser.add_option(enable_roi_mode, "Enable Region-of-Interest mode for profiling", "roi", 0);
|
||||
|
||||
parser.add_positional_argument(arguments, "Command to emulate", "command");
|
||||
|
||||
parser.parse(argc, argv);
|
||||
|
||||
if (dump_profile && profile_instruction_interval == 0)
|
||||
profile_instruction_interval = 128;
|
||||
|
||||
auto executable_path_or_error = arguments[0].contains('/')
|
||||
? FileSystem::real_path(arguments[0])
|
||||
: Core::System::resolve_executable_from_environment(arguments[0]);
|
||||
if (executable_path_or_error.is_error()) {
|
||||
reportln("Cannot find executable for '{}'."sv, arguments[0]);
|
||||
return 1;
|
||||
}
|
||||
auto executable_path = executable_path_or_error.release_value().to_byte_string();
|
||||
|
||||
if (dump_profile && profile_dump_path.is_empty())
|
||||
profile_dump_path = ByteString::formatted("{}.{}.profile", LexicalPath(executable_path).basename(), getpid());
|
||||
|
||||
OwnPtr<Stream> profile_stream;
|
||||
OwnPtr<Vector<NonnullOwnPtr<ByteString>>> profile_strings;
|
||||
OwnPtr<Vector<int>> profile_string_id_map;
|
||||
|
||||
if (dump_profile) {
|
||||
auto profile_stream_or_error = Core::File::open(profile_dump_path, Core::File::OpenMode::Write);
|
||||
if (profile_stream_or_error.is_error()) {
|
||||
warnln("Failed to open '{}' for writing: {}", profile_dump_path, profile_stream_or_error.error());
|
||||
return 1;
|
||||
}
|
||||
profile_stream = profile_stream_or_error.release_value();
|
||||
profile_strings = make<Vector<NonnullOwnPtr<ByteString>>>();
|
||||
profile_string_id_map = make<Vector<int>>();
|
||||
|
||||
profile_stream->write_until_depleted(R"({"events":[)"sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
timeval tv {};
|
||||
gettimeofday(&tv, nullptr);
|
||||
profile_stream->write_until_depleted(
|
||||
ByteString::formatted(
|
||||
R"~({{"type": "process_create", "parent_pid": 1, "executable": "{}", "pid": {}, "tid": {}, "timestamp": {}, "lost_samples": 0, "stack": []}})~",
|
||||
executable_path, getpid(), gettid(), tv.tv_sec * 1000 + tv.tv_usec / 1000)
|
||||
.bytes())
|
||||
.release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
|
||||
Vector<ByteString> environment;
|
||||
for (int i = 0; env[i]; ++i) {
|
||||
environment.append(env[i]);
|
||||
}
|
||||
|
||||
// FIXME: It might be nice to tear down the emulator properly.
|
||||
auto& emulator = *new UserspaceEmulator::Emulator(executable_path, arguments, environment);
|
||||
|
||||
emulator.set_profiling_details(dump_profile, profile_instruction_interval, profile_stream, profile_strings, profile_string_id_map);
|
||||
emulator.set_in_region_of_interest(!enable_roi_mode);
|
||||
|
||||
if (!emulator.load_elf())
|
||||
return 1;
|
||||
|
||||
StringBuilder builder;
|
||||
builder.append("(UE) "sv);
|
||||
builder.append(LexicalPath::basename(arguments[0]));
|
||||
if (auto result = Core::Process::set_name(builder.string_view(), Core::Process::SetThreadName::Yes); result.is_error()) {
|
||||
reportln("Core::Process::set_name: {}"sv, result.error());
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (pause_on_startup)
|
||||
emulator.pause();
|
||||
|
||||
int rc = emulator.exec();
|
||||
|
||||
if (dump_profile) {
|
||||
emulator.profile_stream().write_until_depleted("], \"strings\": ["sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
if (emulator.profiler_strings().size()) {
|
||||
for (size_t i = 0; i < emulator.profiler_strings().size() - 1; ++i)
|
||||
emulator.profile_stream().write_until_depleted(ByteString::formatted("\"{}\", ", emulator.profiler_strings().at(i)).bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
emulator.profile_stream().write_until_depleted(ByteString::formatted("\"{}\"", emulator.profiler_strings().last()).bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
emulator.profile_stream().write_until_depleted("]}"sv.bytes()).release_value_but_fixme_should_propagate_errors();
|
||||
}
|
||||
return rc;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue