Browse Source

UserspaceEmulator+LibX86: Add support for 64-bit memory reads and writes (#3584)

This is useful for reading and writing doubles for #3329.
It is also useful for emulating 64-bit binaries.

MemoryOrRegisterReference assumes that 64-bit values are always
memory references since that's enough for fpu support. If we
ever want to emulate 64-bit binaries, that part will need minor
updating.
Nico Weber 4 years ago
parent
commit
f1c0f661f4

+ 36 - 0
DevTools/UserspaceEmulator/MmapRegion.cpp

@@ -116,6 +116,23 @@ ValueWithShadow<u32> MmapRegion::read32(u32 offset)
     return { *reinterpret_cast<const u32*>(m_data + offset), *reinterpret_cast<const u32*>(m_shadow_data + offset) };
 }
 
+ValueWithShadow<u64> MmapRegion::read64(u32 offset)
+{
+    if (!is_readable()) {
+        warn() << "64-bit read from unreadable MmapRegion @ " << (const void*)(base() + offset);
+        Emulator::the().dump_backtrace();
+        TODO();
+    }
+
+    if (is_malloc_block()) {
+        if (auto* tracer = Emulator::the().malloc_tracer())
+            tracer->audit_read(base() + offset, 8);
+    }
+
+    ASSERT(offset + 7 < size());
+    return { *reinterpret_cast<const u64*>(m_data + offset), *reinterpret_cast<const u64*>(m_shadow_data + offset) };
+}
+
 void MmapRegion::write8(u32 offset, ValueWithShadow<u8> value)
 {
     if (!is_writable()) {
@@ -171,4 +188,23 @@ void MmapRegion::write32(u32 offset, ValueWithShadow<u32> value)
     *reinterpret_cast<u32*>(m_shadow_data + offset) = value.shadow();
 }
 
+void MmapRegion::write64(u32 offset, ValueWithShadow<u64> value)
+{
+    if (!is_writable()) {
+        warn() << "64-bit write to unreadable MmapRegion @ " << (const void*)(base() + offset);
+        Emulator::the().dump_backtrace();
+        TODO();
+    }
+
+    if (is_malloc_block()) {
+        if (auto* tracer = Emulator::the().malloc_tracer())
+            tracer->audit_write(base() + offset, 8);
+    }
+
+    ASSERT(offset + 7 < size());
+    ASSERT(m_data != m_shadow_data);
+    *reinterpret_cast<u64*>(m_data + offset) = value.value();
+    *reinterpret_cast<u64*>(m_shadow_data + offset) = value.shadow();
+}
+
 }

+ 2 - 0
DevTools/UserspaceEmulator/MmapRegion.h

@@ -40,10 +40,12 @@ public:
     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 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;
 
     u8* data() { return m_data; }
     u8* shadow_data() { return m_shadow_data; }

+ 13 - 0
DevTools/UserspaceEmulator/SharedBufferRegion.cpp

@@ -70,6 +70,12 @@ ValueWithShadow<u32> SharedBufferRegion::read32(u32 offset)
     return { *reinterpret_cast<const u32*>(m_data + offset), *reinterpret_cast<const u32*>(m_shadow_data + offset) };
 }
 
+ValueWithShadow<u64> SharedBufferRegion::read64(u32 offset)
+{
+    ASSERT(offset + 7 < size());
+    return { *reinterpret_cast<const u64*>(m_data + offset), *reinterpret_cast<const u64*>(m_shadow_data + offset) };
+}
+
 void SharedBufferRegion::write8(u32 offset, ValueWithShadow<u8> value)
 {
     ASSERT(offset < size());
@@ -91,6 +97,13 @@ void SharedBufferRegion::write32(u32 offset, ValueWithShadow<u32> value)
     *reinterpret_cast<u32*>(m_shadow_data + offset) = value.shadow();
 }
 
+void SharedBufferRegion::write64(u32 offset, ValueWithShadow<u64> value)
+{
+    ASSERT(offset + 7 < size());
+    *reinterpret_cast<u64*>(m_data + offset) = value.value();
+    *reinterpret_cast<u64*>(m_shadow_data + offset) = value.shadow();
+}
+
 int SharedBufferRegion::allow_all()
 {
     return syscall(SC_shbuf_allow_all, m_shbuf_id);

+ 2 - 0
DevTools/UserspaceEmulator/SharedBufferRegion.h

@@ -39,10 +39,12 @@ public:
     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 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;
 
     u8* data() { return m_data; }
 

+ 13 - 0
DevTools/UserspaceEmulator/SimpleRegion.cpp

@@ -61,6 +61,12 @@ ValueWithShadow<u32> SimpleRegion::read32(u32 offset)
     return { *reinterpret_cast<const u32*>(m_data + offset), *reinterpret_cast<const u32*>(m_shadow_data + offset) };
 }
 
+ValueWithShadow<u64> SimpleRegion::read64(u32 offset)
+{
+    ASSERT(offset + 7 < size());
+    return { *reinterpret_cast<const u64*>(m_data + offset), *reinterpret_cast<const u64*>(m_shadow_data + offset) };
+}
+
 void SimpleRegion::write8(u32 offset, ValueWithShadow<u8> value)
 {
     ASSERT(offset < size());
@@ -82,6 +88,13 @@ void SimpleRegion::write32(u32 offset, ValueWithShadow<u32> value)
     *reinterpret_cast<u32*>(m_shadow_data + offset) = value.shadow();
 }
 
+void SimpleRegion::write64(u32 offset, ValueWithShadow<u64> value)
+{
+    ASSERT(offset + 7 < size());
+    *reinterpret_cast<u64*>(m_data + offset) = value.value();
+    *reinterpret_cast<u64*>(m_shadow_data + offset) = value.shadow();
+}
+
 u8* SimpleRegion::cacheable_ptr(u32 offset)
 {
     return m_data + offset;

+ 2 - 0
DevTools/UserspaceEmulator/SimpleRegion.h

@@ -38,10 +38,12 @@ public:
     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 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;
 
     u8* data() { return m_data; }
     u8* shadow_data() { return m_shadow_data; }

+ 19 - 0
DevTools/UserspaceEmulator/SoftCPU.cpp

@@ -158,6 +158,16 @@ ValueWithShadow<u32> SoftCPU::read_memory32(X86::LogicalAddress address)
     return value;
 }
 
+ValueWithShadow<u64> SoftCPU::read_memory64(X86::LogicalAddress address)
+{
+    ASSERT(address.selector() == 0x18 || address.selector() == 0x20 || address.selector() == 0x28);
+    auto value = m_emulator.mmu().read64(address);
+#ifdef MEMORY_DEBUG
+    printf("\033[36;1mread_memory64: @%04x:%08x -> %016llx (%016llx)\033[0m\n", address.selector(), address.offset(), value.value(), value.shadow());
+#endif
+    return value;
+}
+
 void SoftCPU::write_memory8(X86::LogicalAddress address, ValueWithShadow<u8> value)
 {
     ASSERT(address.selector() == 0x20 || address.selector() == 0x28);
@@ -185,6 +195,15 @@ void SoftCPU::write_memory32(X86::LogicalAddress address, ValueWithShadow<u32> v
     m_emulator.mmu().write32(address, value);
 }
 
+void SoftCPU::write_memory64(X86::LogicalAddress address, ValueWithShadow<u64> value)
+{
+    ASSERT(address.selector() == 0x20 || address.selector() == 0x28);
+#ifdef MEMORY_DEBUG
+    printf("\033[35;1mwrite_memory64: @%04x:%08x <- %016llx (%016llx)\033[0m\n", address.selector(), address.offset(), value.value(), value.shadow());
+#endif
+    m_emulator.mmu().write64(address, value);
+}
+
 void SoftCPU::push_string(const StringView& string)
 {
     size_t space_to_allocate = round_up_to_power_of_two(string.length() + 1, 16);

+ 14 - 0
DevTools/UserspaceEmulator/SoftCPU.h

@@ -360,6 +360,7 @@ public:
     ValueWithShadow<u8> read_memory8(X86::LogicalAddress);
     ValueWithShadow<u16> read_memory16(X86::LogicalAddress);
     ValueWithShadow<u32> read_memory32(X86::LogicalAddress);
+    ValueWithShadow<u64> read_memory64(X86::LogicalAddress);
 
     template<typename T>
     ValueWithShadow<T> read_memory(X86::LogicalAddress address)
@@ -375,6 +376,7 @@ public:
     void write_memory8(X86::LogicalAddress, ValueWithShadow<u8>);
     void write_memory16(X86::LogicalAddress, ValueWithShadow<u16>);
     void write_memory32(X86::LogicalAddress, ValueWithShadow<u32>);
+    void write_memory64(X86::LogicalAddress, ValueWithShadow<u64>);
 
     template<typename T>
     void write_memory(X86::LogicalAddress address, ValueWithShadow<T> data)
@@ -456,6 +458,7 @@ public:
     virtual u8 read8() override;
     virtual u16 read16() override;
     virtual u32 read32() override;
+    virtual u64 read64() override;
 
 private:
     // ^X86::Interpreter
@@ -1154,4 +1157,15 @@ ALWAYS_INLINE u32 SoftCPU::read32()
     return value;
 }
 
+ALWAYS_INLINE u64 SoftCPU::read64()
+{
+    if (!m_cached_code_ptr || (m_cached_code_ptr + 8) >= m_cached_code_end)
+        update_code_cache();
+
+    u64 value = *reinterpret_cast<const u64*>(m_cached_code_ptr);
+    m_cached_code_ptr += 8;
+    m_eip += 8;
+    return value;
+}
+
 }

+ 22 - 0
DevTools/UserspaceEmulator/SoftMMU.cpp

@@ -97,6 +97,17 @@ ValueWithShadow<u32> SoftMMU::read32(X86::LogicalAddress address)
     return region->read32(address.offset() - region->base());
 }
 
+ValueWithShadow<u64> SoftMMU::read64(X86::LogicalAddress address)
+{
+    auto* region = find_region(address);
+    if (!region) {
+        warn() << "SoftMMU::read64: No region for @" << (const void*)address.offset();
+        TODO();
+    }
+
+    return region->read64(address.offset() - region->base());
+}
+
 void SoftMMU::write8(X86::LogicalAddress address, ValueWithShadow<u8> value)
 {
     auto* region = find_region(address);
@@ -130,6 +141,17 @@ void SoftMMU::write32(X86::LogicalAddress address, ValueWithShadow<u32> value)
     region->write32(address.offset() - region->base(), value);
 }
 
+void SoftMMU::write64(X86::LogicalAddress address, ValueWithShadow<u64> value)
+{
+    auto* region = find_region(address);
+    if (!region) {
+        warn() << "SoftMMU::write64: No region for @" << (const void*)address.offset();
+        TODO();
+    }
+
+    region->write64(address.offset() - region->base(), value);
+}
+
 void SoftMMU::copy_to_vm(FlatPtr destination, const void* source, size_t size)
 {
     // FIXME: We should have a way to preserve the shadow data here as well.

+ 4 - 0
DevTools/UserspaceEmulator/SoftMMU.h

@@ -52,10 +52,12 @@ public:
         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 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 u8* cacheable_ptr([[maybe_unused]] u32 offset) { return nullptr; }
         virtual bool is_shared_buffer() const { return false; }
@@ -85,10 +87,12 @@ public:
     ValueWithShadow<u8> read8(X86::LogicalAddress);
     ValueWithShadow<u16> read16(X86::LogicalAddress);
     ValueWithShadow<u32> read32(X86::LogicalAddress);
+    ValueWithShadow<u64> read64(X86::LogicalAddress);
 
     void write8(X86::LogicalAddress, ValueWithShadow<u8>);
     void write16(X86::LogicalAddress, ValueWithShadow<u16>);
     void write32(X86::LogicalAddress, ValueWithShadow<u32>);
+    void write64(X86::LogicalAddress, ValueWithShadow<u64>);
 
     Region* find_region(X86::LogicalAddress);
 

+ 2 - 0
DevTools/UserspaceEmulator/ValueWithShadow.h

@@ -111,6 +111,8 @@ private:
 template<typename T>
 ALWAYS_INLINE ValueWithShadow<T> shadow_wrap_as_initialized(T value)
 {
+    if constexpr (sizeof(T) == 8)
+        return { value, 0x01010101'01010101LLU };
     if constexpr (sizeof(T) == 4)
         return { value, 0x01010101 };
     if constexpr (sizeof(T) == 2)

+ 27 - 0
Libraries/LibX86/Instruction.h

@@ -315,6 +315,7 @@ public:
     virtual u8 read8() = 0;
     virtual u16 read16() = 0;
     virtual u32 read32() = 0;
+    virtual u64 read64() = 0;
 };
 
 class SimpleInstructionStream final : public InstructionStream {
@@ -347,6 +348,12 @@ public:
         return ((u32)msw << 16) | (u32)lsw;
     }
 
+    virtual u64 read64() override
+    {
+        u32 lsw = read32();
+        u32 msw = read32();
+        return ((u64)msw << 32) | (u64)lsw;
+    }
     size_t offset() const { return m_offset; }
 
 private:
@@ -385,6 +392,8 @@ public:
     void write16(CPU&, const Instruction&, T);
     template<typename CPU, typename T>
     void write32(CPU&, const Instruction&, T);
+    template<typename CPU, typename T>
+    void write64(CPU&, const Instruction&, T);
 
     template<typename T, typename CPU>
     T read8(CPU&, const Instruction&);
@@ -392,6 +401,8 @@ public:
     T read16(CPU&, const Instruction&);
     template<typename T, typename CPU>
     T read32(CPU&, const Instruction&);
+    template<typename T, typename CPU>
+    T read64(CPU&, const Instruction&);
 
     template<typename CPU>
     LogicalAddress resolve(const CPU&, const Instruction&);
@@ -752,6 +763,14 @@ ALWAYS_INLINE void MemoryOrRegisterReference::write32(CPU& cpu, const Instructio
     cpu.write_memory32(address, value);
 }
 
+template<typename CPU, typename T>
+ALWAYS_INLINE void MemoryOrRegisterReference::write64(CPU& cpu, const Instruction& insn, T value)
+{
+    ASSERT(!is_register());
+    auto address = resolve(cpu, insn);
+    cpu.write_memory64(address, value);
+}
+
 template<typename T, typename CPU>
 ALWAYS_INLINE T MemoryOrRegisterReference::read8(CPU& cpu, const Instruction& insn)
 {
@@ -782,6 +801,14 @@ ALWAYS_INLINE T MemoryOrRegisterReference::read32(CPU& cpu, const Instruction& i
     return cpu.read_memory32(address);
 }
 
+template<typename T, typename CPU>
+ALWAYS_INLINE T MemoryOrRegisterReference::read64(CPU& cpu, const Instruction& insn)
+{
+    ASSERT(!is_register());
+    auto address = resolve(cpu, insn);
+    return cpu.read_memory64(address);
+}
+
 template<typename InstructionStreamType>
 ALWAYS_INLINE Instruction Instruction::from_stream(InstructionStreamType& stream, bool o32, bool a32)
 {