mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 12:28:12 +00:00
UserspaceEmulator: Support dynamically loaded programs
When loading dynamic objects, the emulator loads the interpreter, generates an auxiliary vector and starts executing the loader. Additionally, this commits also makes the MallocTracer and backtrace symbolication work for dynamically loaded programs.
This commit is contained in:
parent
28cda567c1
commit
72ca45e300
9 changed files with 247 additions and 64 deletions
|
@ -31,7 +31,11 @@
|
|||
#include "SoftCPU.h"
|
||||
#include <AK/Format.h>
|
||||
#include <AK/LexicalPath.h>
|
||||
#include <AK/MappedFile.h>
|
||||
#include <Kernel/API/Syscall.h>
|
||||
#include <LibELF/AuxiliaryVector.h>
|
||||
#include <LibELF/Image.h>
|
||||
#include <LibELF/Validation.h>
|
||||
#include <LibPthread/pthread.h>
|
||||
#include <LibX86/ELFSymbolProvider.h>
|
||||
#include <fcntl.h>
|
||||
|
@ -55,7 +59,7 @@
|
|||
# pragma GCC optimize("O3")
|
||||
#endif
|
||||
|
||||
//#define DEBUG_SPAM
|
||||
// #define DEBUG_SPAM
|
||||
|
||||
namespace UserspaceEmulator {
|
||||
|
||||
|
@ -70,20 +74,45 @@ Emulator& Emulator::the()
|
|||
return *s_the;
|
||||
}
|
||||
|
||||
Emulator::Emulator(const Vector<String>& arguments, const Vector<String>& environment, NonnullRefPtr<ELF::Loader> elf)
|
||||
: m_elf(move(elf))
|
||||
Emulator::Emulator(const String& executable_path, const Vector<String>& arguments, const Vector<String>& environment)
|
||||
: m_executable_path(executable_path)
|
||||
, m_arguments(arguments)
|
||||
, m_environment(environment)
|
||||
, m_mmu(*this)
|
||||
, m_cpu(*this)
|
||||
{
|
||||
m_malloc_tracer = make<MallocTracer>(*this);
|
||||
ASSERT(!s_the);
|
||||
s_the = this;
|
||||
setup_stack(arguments, environment);
|
||||
// setup_stack(arguments, environment);
|
||||
register_signal_handlers();
|
||||
setup_signal_trampoline();
|
||||
}
|
||||
|
||||
void Emulator::setup_stack(const Vector<String>& arguments, const Vector<String>& environment)
|
||||
Vector<AuxiliaryValue> Emulator::generate_auxiliary_vector(FlatPtr load_base, FlatPtr entry_eip, String executable_path, int executable_fd) const
|
||||
{
|
||||
// FIXME: This is not fully compatible with the auxiliary vector the kernel generates, this is just the bare
|
||||
// minimum to get the loader going.
|
||||
Vector<AuxiliaryValue> auxv;
|
||||
// PHDR/EXECFD
|
||||
// PH*
|
||||
auxv.append({ AuxiliaryValue::PageSize, PAGE_SIZE });
|
||||
auxv.append({ AuxiliaryValue::BaseAddress, (void*)load_base });
|
||||
|
||||
auxv.append({ AuxiliaryValue::Entry, (void*)entry_eip });
|
||||
|
||||
// FIXME: Don't hard code this? We might support other platforms later.. (e.g. x86_64)
|
||||
auxv.append({ AuxiliaryValue::Platform, "i386" });
|
||||
|
||||
auxv.append({ AuxiliaryValue::ExecFilename, executable_path });
|
||||
|
||||
auxv.append({ AuxiliaryValue::ExecFileDescriptor, executable_fd });
|
||||
|
||||
auxv.append({ AuxiliaryValue::Null, 0L });
|
||||
return auxv;
|
||||
}
|
||||
|
||||
void Emulator::setup_stack(Vector<AuxiliaryValue> aux_vector)
|
||||
{
|
||||
auto stack_region = make<SimpleRegion>(stack_location, stack_size);
|
||||
stack_region->set_stack(true);
|
||||
|
@ -92,18 +121,30 @@ void Emulator::setup_stack(const Vector<String>& arguments, const Vector<String>
|
|||
|
||||
Vector<u32> argv_entries;
|
||||
|
||||
for (auto& argument : arguments) {
|
||||
for (auto& argument : m_arguments) {
|
||||
m_cpu.push_string(argument.characters());
|
||||
argv_entries.append(m_cpu.esp().value());
|
||||
}
|
||||
|
||||
Vector<u32> env_entries;
|
||||
|
||||
for (auto& variable : environment) {
|
||||
for (auto& variable : m_environment) {
|
||||
m_cpu.push_string(variable.characters());
|
||||
env_entries.append(m_cpu.esp().value());
|
||||
}
|
||||
|
||||
for (auto& auxv : aux_vector) {
|
||||
if (!auxv.optional_string.is_empty()) {
|
||||
m_cpu.push_string(auxv.optional_string.characters());
|
||||
auxv.auxv.a_un.a_ptr = (void*)m_cpu.esp().value();
|
||||
}
|
||||
}
|
||||
|
||||
for (ssize_t i = aux_vector.size() - 1; i >= 0; --i) {
|
||||
auto& value = aux_vector[i].auxv;
|
||||
m_cpu.push_buffer((const u8*)&value, sizeof(value));
|
||||
}
|
||||
|
||||
m_cpu.push32(shadow_wrap_as_initialized<u32>(0)); // char** envp = { envv_entries..., nullptr }
|
||||
for (ssize_t i = env_entries.size() - 1; i >= 0; --i)
|
||||
m_cpu.push32(shadow_wrap_as_initialized(env_entries[i]));
|
||||
|
@ -125,54 +166,70 @@ void Emulator::setup_stack(const Vector<String>& arguments, const Vector<String>
|
|||
|
||||
bool Emulator::load_elf()
|
||||
{
|
||||
m_elf->image().for_each_program_header([&](const ELF::Image::ProgramHeader& program_header) {
|
||||
MappedFile mapped_executable(m_executable_path);
|
||||
if (!mapped_executable.is_valid()) {
|
||||
reportln("Unable to map {}", m_executable_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
ELF::Image executable_elf((const u8*)mapped_executable.data(), mapped_executable.size());
|
||||
|
||||
if (!executable_elf.is_dynamic()) {
|
||||
// FIXME: Support static objects
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
|
||||
String interpreter_path;
|
||||
if (!ELF::validate_program_headers(*(Elf32_Ehdr*)mapped_executable.data(), mapped_executable.size(), (u8*)mapped_executable.data(), mapped_executable.size(), &interpreter_path)) {
|
||||
reportln("failed to validate ELF file");
|
||||
return false;
|
||||
}
|
||||
|
||||
ASSERT(!interpreter_path.is_null());
|
||||
dbgln("interpreter: {}", interpreter_path);
|
||||
|
||||
auto interpreter_file = make<MappedFile>(interpreter_path);
|
||||
ASSERT(interpreter_file->is_valid());
|
||||
ELF::Image interpreter_image((const u8*)interpreter_file->data(), interpreter_file->size());
|
||||
|
||||
constexpr FlatPtr interpreter_load_offset = 0x08000000;
|
||||
interpreter_image.for_each_program_header([&](const ELF::Image::ProgramHeader& program_header) {
|
||||
// Loader is not allowed to have its own TLS regions
|
||||
ASSERT(program_header.type() != PT_TLS);
|
||||
|
||||
if (program_header.type() == PT_LOAD) {
|
||||
auto region = make<SimpleRegion>(program_header.vaddr().get(), program_header.size_in_memory());
|
||||
auto region = make<SimpleRegion>(program_header.vaddr().offset(interpreter_load_offset).get(), program_header.size_in_memory());
|
||||
if (program_header.is_executable() && !program_header.is_writable())
|
||||
region->set_text(true);
|
||||
memcpy(region->data(), program_header.raw_data(), program_header.size_in_image());
|
||||
memset(region->shadow_data(), 0x01, program_header.size_in_memory());
|
||||
if (program_header.is_executable()) {
|
||||
m_loader_text_base = region->base();
|
||||
m_loader_text_size = region->size();
|
||||
}
|
||||
mmu().add_region(move(region));
|
||||
return;
|
||||
}
|
||||
if (program_header.type() == PT_TLS) {
|
||||
auto tcb_region = make<SimpleRegion>(0x20000000, program_header.size_in_memory());
|
||||
memcpy(tcb_region->data(), program_header.raw_data(), program_header.size_in_image());
|
||||
memset(tcb_region->shadow_data(), 0x01, program_header.size_in_memory());
|
||||
|
||||
auto tls_region = make<SimpleRegion>(0, 4);
|
||||
tls_region->write32(0, shadow_wrap_as_initialized(tcb_region->base() + program_header.size_in_memory()));
|
||||
memset(tls_region->shadow_data(), 0x01, 4);
|
||||
|
||||
mmu().add_region(move(tcb_region));
|
||||
mmu().set_tls_region(move(tls_region));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
m_cpu.set_eip(m_elf->image().entry().get());
|
||||
auto entry_point = interpreter_image.entry().offset(interpreter_load_offset).get();
|
||||
m_cpu.set_eip(entry_point);
|
||||
|
||||
auto malloc_symbol = m_elf->find_demangled_function("malloc");
|
||||
auto free_symbol = m_elf->find_demangled_function("free");
|
||||
auto realloc_symbol = m_elf->find_demangled_function("realloc");
|
||||
auto malloc_size_symbol = m_elf->find_demangled_function("malloc_size");
|
||||
// executable_fd will be used by the loader
|
||||
int executable_fd = open(m_executable_path.characters(), O_RDONLY);
|
||||
if (executable_fd < 0)
|
||||
return false;
|
||||
|
||||
m_malloc_symbol_start = malloc_symbol.value().value();
|
||||
m_malloc_symbol_end = m_malloc_symbol_start + malloc_symbol.value().size();
|
||||
m_free_symbol_start = free_symbol.value().value();
|
||||
m_free_symbol_end = m_free_symbol_start + free_symbol.value().size();
|
||||
m_realloc_symbol_start = realloc_symbol.value().value();
|
||||
m_realloc_symbol_end = m_realloc_symbol_start + realloc_symbol.value().size();
|
||||
m_malloc_size_symbol_start = malloc_size_symbol.value().value();
|
||||
m_malloc_size_symbol_end = m_malloc_size_symbol_start + malloc_size_symbol.value().size();
|
||||
auto aux_vector = generate_auxiliary_vector(interpreter_load_offset, entry_point, m_executable_path, executable_fd);
|
||||
setup_stack(move(aux_vector));
|
||||
|
||||
m_debug_info = make<Debug::DebugInfo>(m_elf);
|
||||
return true;
|
||||
}
|
||||
|
||||
int Emulator::exec()
|
||||
{
|
||||
X86::ELFSymbolProvider symbol_provider(*m_elf);
|
||||
// X86::ELFSymbolProvider symbol_provider(*m_elf);
|
||||
X86::ELFSymbolProvider* symbol_provider = nullptr;
|
||||
|
||||
bool trace = false;
|
||||
|
||||
|
@ -182,7 +239,7 @@ int Emulator::exec()
|
|||
auto insn = X86::Instruction::from_stream(m_cpu, true, true);
|
||||
|
||||
if (trace)
|
||||
outln("{:p} \033[33;1m{}\033[0m", m_cpu.base_eip(), insn.to_string(m_cpu.base_eip(), &symbol_provider));
|
||||
outln("{:p} \033[33;1m{}\033[0m", m_cpu.base_eip(), insn.to_string(m_cpu.base_eip(), symbol_provider));
|
||||
|
||||
(m_cpu.*insn.handler())(insn);
|
||||
|
||||
|
@ -217,16 +274,58 @@ Vector<FlatPtr> Emulator::raw_backtrace()
|
|||
return backtrace;
|
||||
}
|
||||
|
||||
const MmapRegion* Emulator::find_text_region(FlatPtr address)
|
||||
{
|
||||
const MmapRegion* matching_region = nullptr;
|
||||
mmu().for_each_region([&](auto& region) {
|
||||
if (!region.is_mmap())
|
||||
return IterationDecision::Continue;
|
||||
const auto& mmap_region = static_cast<const MmapRegion&>(region);
|
||||
if (!(mmap_region.is_executable() && address >= mmap_region.base() && address < mmap_region.base() + mmap_region.size()))
|
||||
return IterationDecision::Continue;
|
||||
matching_region = &mmap_region;
|
||||
return IterationDecision::Break;
|
||||
});
|
||||
return matching_region;
|
||||
}
|
||||
|
||||
String Emulator::create_backtrace_line(FlatPtr address)
|
||||
{
|
||||
String minimal = String::format("=={%d}== %p", getpid(), address);
|
||||
const auto* region = find_text_region(address);
|
||||
if (!region)
|
||||
return minimal;
|
||||
auto separator_index = region->name().index_of(":");
|
||||
if (!separator_index.has_value())
|
||||
return minimal;
|
||||
|
||||
String lib_name = region->name().substring(0, separator_index.value());
|
||||
String lib_path = lib_name;
|
||||
if (region->name().contains(".so"))
|
||||
lib_path = String::format("/usr/lib/%s", lib_path.characters());
|
||||
|
||||
auto mapped_file = make<MappedFile>(lib_path);
|
||||
if (!mapped_file->is_valid())
|
||||
return minimal;
|
||||
|
||||
auto loader = ELF::Loader::create((const u8*)mapped_file->data(), mapped_file->size());
|
||||
String symbol = loader->symbolicate(address - region->base());
|
||||
|
||||
auto line_without_source_info = String::format("=={%d}== %p [%s]: %s", getpid(), address, lib_name.characters(), symbol.characters());
|
||||
auto debug_info = make<Debug::DebugInfo>(loader);
|
||||
|
||||
auto source_position = debug_info->get_source_position(address - region->base());
|
||||
if (source_position.has_value())
|
||||
return String::format("=={%d}== %p [%s]: %s (\033[34;1m%s\033[0m:%u)", getpid(), address, lib_name.characters(), symbol.characters(), LexicalPath(source_position.value().file_path).basename().characters(), source_position.value().line_number);
|
||||
else {
|
||||
return line_without_source_info;
|
||||
}
|
||||
}
|
||||
|
||||
void Emulator::dump_backtrace(const Vector<FlatPtr>& backtrace)
|
||||
{
|
||||
for (auto& address : backtrace) {
|
||||
u32 offset = 0;
|
||||
String symbol = m_elf->symbolicate(address, &offset);
|
||||
auto source_position = m_debug_info->get_source_position(address);
|
||||
if (source_position.has_value())
|
||||
reportln("=={}== {:p} {} (\033[34;1m{}\033[0m:{})", getpid(), address, symbol, LexicalPath(source_position.value().file_path).basename(), source_position.value().line_number);
|
||||
else
|
||||
reportln("=={}== {:p} {} +{:x}", getpid(), address, symbol, offset);
|
||||
reportln("{}", create_backtrace_line(address));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -406,6 +505,8 @@ u32 Emulator::virt_syscall(u32 function, u32 arg1, u32 arg2, u32 arg3)
|
|||
return virt$clock_nanosleep(arg1);
|
||||
case SC_readlink:
|
||||
return virt$readlink(arg1);
|
||||
case SC_allocate_tls:
|
||||
return virt$allocate_tls(arg1);
|
||||
default:
|
||||
reportln("\n=={}== \033[31;1mUnimplemented syscall: {}\033[0m, {:p}", getpid(), Syscall::to_string((Syscall::Function)function), function);
|
||||
dump_backtrace();
|
||||
|
@ -870,8 +971,6 @@ u32 Emulator::virt$mmap(u32 params_addr)
|
|||
Syscall::SC_mmap_params params;
|
||||
mmu().copy_from_vm(¶ms, params_addr, sizeof(params));
|
||||
|
||||
ASSERT(params.addr == 0);
|
||||
|
||||
u32 final_size = round_up_to_power_of_two(params.size, PAGE_SIZE);
|
||||
u32 final_address = allocate_vm(final_size, params.alignment);
|
||||
if (params.addr != 0) {
|
||||
|
@ -886,14 +985,18 @@ u32 Emulator::virt$mmap(u32 params_addr)
|
|||
if (params.flags & MAP_ANONYMOUS)
|
||||
mmu().add_region(MmapRegion::create_anonymous(final_address, final_size, params.prot));
|
||||
else {
|
||||
dbgln("chars: {:p}, len: {}", params.name.characters, params.name.length);
|
||||
String name_str;
|
||||
if (params.name.characters) {
|
||||
auto name = ByteBuffer::create_uninitialized(params.name.length);
|
||||
mmu().copy_from_vm(name.data(), (FlatPtr)params.name.characters, params.name.length);
|
||||
name_str = { name.data(), name.size() };
|
||||
}
|
||||
mmu().add_region(MmapRegion::create_file_backed(final_address, final_size, params.prot, params.flags, params.fd, params.offset, name_str));
|
||||
auto region = MmapRegion::create_file_backed(final_address, final_size, params.prot, params.flags, params.fd, params.offset, name_str);
|
||||
if (region->name() == "libc.so: .text (Emulated)") {
|
||||
bool rc = find_malloc_symbols(*region);
|
||||
ASSERT(rc);
|
||||
}
|
||||
mmu().add_region(move(region));
|
||||
}
|
||||
|
||||
return final_address;
|
||||
|
@ -1567,4 +1670,46 @@ int Emulator::virt$readlink(FlatPtr params_addr)
|
|||
return rc;
|
||||
}
|
||||
|
||||
u32 Emulator::virt$allocate_tls(size_t size)
|
||||
{
|
||||
// TODO: Why is this needed? without this, the loader overflows the bounds of the TLS region.
|
||||
constexpr size_t TLS_SIZE_HACK = 8;
|
||||
auto tcb_region = make<SimpleRegion>(0x20000000, size + TLS_SIZE_HACK);
|
||||
bzero(tcb_region->data(), size);
|
||||
memset(tcb_region->shadow_data(), 0x01, size);
|
||||
|
||||
auto tls_region = make<SimpleRegion>(0, 4);
|
||||
tls_region->write32(0, shadow_wrap_as_initialized(tcb_region->base() + (u32)size));
|
||||
memset(tls_region->shadow_data(), 0x01, 4);
|
||||
|
||||
u32 tls_base = tcb_region->base();
|
||||
mmu().add_region(move(tcb_region));
|
||||
mmu().set_tls_region(move(tls_region));
|
||||
return tls_base;
|
||||
}
|
||||
|
||||
bool Emulator::find_malloc_symbols(const MmapRegion& libc_text)
|
||||
{
|
||||
auto mapped_file = make<MappedFile>("/usr/lib/libc.so");
|
||||
if (!mapped_file->is_valid())
|
||||
return {};
|
||||
|
||||
ELF::Image image((const u8*)mapped_file->data(), mapped_file->size());
|
||||
auto malloc_symbol = image.find_demangled_function("malloc");
|
||||
auto free_symbol = image.find_demangled_function("free");
|
||||
auto realloc_symbol = image.find_demangled_function("realloc");
|
||||
auto malloc_size_symbol = image.find_demangled_function("malloc_size");
|
||||
if (!malloc_symbol.has_value() || !free_symbol.has_value() || !realloc_symbol.has_value() || !malloc_size_symbol.has_value())
|
||||
return false;
|
||||
|
||||
m_malloc_symbol_start = malloc_symbol.value().value() + libc_text.base();
|
||||
m_malloc_symbol_end = m_malloc_symbol_start + malloc_symbol.value().size();
|
||||
m_free_symbol_start = free_symbol.value().value() + libc_text.base();
|
||||
m_free_symbol_end = m_free_symbol_start + free_symbol.value().size();
|
||||
m_realloc_symbol_start = realloc_symbol.value().value() + libc_text.base();
|
||||
m_realloc_symbol_end = m_realloc_symbol_start + realloc_symbol.value().size();
|
||||
m_malloc_size_symbol_start = malloc_size_symbol.value().value() + libc_text.base();
|
||||
m_malloc_size_symbol_end = m_malloc_size_symbol_start + malloc_size_symbol.value().size();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue