From f1957bb86bd0535025bb9757b0b9f18a638e8d38 Mon Sep 17 00:00:00 2001 From: Hendiadyoin1 Date: Sat, 10 Apr 2021 23:29:32 +0200 Subject: [PATCH] UE+LibX86: Support bigger reads and writes --- .../DevTools/UserspaceEmulator/MmapRegion.cpp | 72 +++++++++++++++++++ .../DevTools/UserspaceEmulator/MmapRegion.h | 4 ++ Userland/DevTools/UserspaceEmulator/Region.h | 5 ++ .../UserspaceEmulator/SimpleRegion.cpp | 24 +++++++ .../DevTools/UserspaceEmulator/SimpleRegion.h | 4 ++ .../DevTools/UserspaceEmulator/SoftCPU.cpp | 37 ++++++++++ Userland/DevTools/UserspaceEmulator/SoftCPU.h | 18 +++++ .../DevTools/UserspaceEmulator/SoftMMU.cpp | 72 +++++++++++++++++++ Userland/DevTools/UserspaceEmulator/SoftMMU.h | 4 ++ .../UserspaceEmulator/ValueWithShadow.h | 40 +++++++++-- Userland/Libraries/LibX86/Instruction.h | 40 +++++++++++ 11 files changed, 316 insertions(+), 4 deletions(-) diff --git a/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp b/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp index c7f4b3f8ad7..a7056e47f09 100644 --- a/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp +++ b/Userland/DevTools/UserspaceEmulator/MmapRegion.cpp @@ -129,6 +129,40 @@ ValueWithShadow MmapRegion::read64(u32 offset) return { *reinterpret_cast(m_data + offset), *reinterpret_cast(m_shadow_data + offset) }; } +ValueWithShadow MmapRegion::read128(u32 offset) +{ + if (!is_readable()) { + reportln("128-bit read from unreadable MmapRegion @ {:p}", 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()); + return { *reinterpret_cast(m_data + offset), *reinterpret_cast(m_shadow_data + offset) }; +} + +ValueWithShadow MmapRegion::read256(u32 offset) +{ + if (!is_readable()) { + reportln("256-bit read from unreadable MmapRegion @ {:p}", 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()); + return { *reinterpret_cast(m_data + offset), *reinterpret_cast(m_shadow_data + offset) }; +} + void MmapRegion::write8(u32 offset, ValueWithShadow value) { if (!is_writable()) { @@ -203,6 +237,44 @@ void MmapRegion::write64(u32 offset, ValueWithShadow value) *reinterpret_cast(m_shadow_data + offset) = value.shadow(); } +void MmapRegion::write128(u32 offset, ValueWithShadow value) +{ + if (!is_writable()) { + reportln("128-bit write from unwritable MmapRegion @ {:p}", 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); + *reinterpret_cast(m_data + offset) = value.value(); + *reinterpret_cast(m_shadow_data + offset) = value.shadow(); +} + +void MmapRegion::write256(u32 offset, ValueWithShadow value) +{ + if (!is_writable()) { + reportln("256-bit write from unwritable MmapRegion @ {:p}", 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); + *reinterpret_cast(m_data + offset) = value.value(); + *reinterpret_cast(m_shadow_data + offset) = value.shadow(); +} + NonnullOwnPtr MmapRegion::split_at(VirtualAddress offset) { VERIFY(!m_malloc); diff --git a/Userland/DevTools/UserspaceEmulator/MmapRegion.h b/Userland/DevTools/UserspaceEmulator/MmapRegion.h index 366bd5b978e..c535c80aeaf 100644 --- a/Userland/DevTools/UserspaceEmulator/MmapRegion.h +++ b/Userland/DevTools/UserspaceEmulator/MmapRegion.h @@ -24,11 +24,15 @@ public: virtual ValueWithShadow read16(u32 offset) override; virtual ValueWithShadow read32(u32 offset) override; virtual ValueWithShadow read64(u32 offset) override; + virtual ValueWithShadow read128(u32 offset) override; + virtual ValueWithShadow read256(u32 offset) override; virtual void write8(u32 offset, ValueWithShadow) override; virtual void write16(u32 offset, ValueWithShadow) override; virtual void write32(u32 offset, ValueWithShadow) override; virtual void write64(u32 offset, ValueWithShadow) override; + virtual void write128(u32 offset, ValueWithShadow) override; + virtual void write256(u32 offset, ValueWithShadow) override; virtual u8* data() override { return m_data; } virtual u8* shadow_data() override { return m_shadow_data; } diff --git a/Userland/DevTools/UserspaceEmulator/Region.h b/Userland/DevTools/UserspaceEmulator/Region.h index f33df7cca1e..a027ccc553a 100644 --- a/Userland/DevTools/UserspaceEmulator/Region.h +++ b/Userland/DevTools/UserspaceEmulator/Region.h @@ -10,6 +10,7 @@ #include "ValueWithShadow.h" #include #include +#include namespace UserspaceEmulator { @@ -31,11 +32,15 @@ public: virtual void write16(u32 offset, ValueWithShadow) = 0; virtual void write32(u32 offset, ValueWithShadow) = 0; virtual void write64(u32 offset, ValueWithShadow) = 0; + virtual void write128(u32 offset, ValueWithShadow) = 0; + virtual void write256(u32 offset, ValueWithShadow) = 0; virtual ValueWithShadow read8(u32 offset) = 0; virtual ValueWithShadow read16(u32 offset) = 0; virtual ValueWithShadow read32(u32 offset) = 0; virtual ValueWithShadow read64(u32 offset) = 0; + virtual ValueWithShadow read128(u32 offset) = 0; + virtual ValueWithShadow read256(u32 offset) = 0; virtual u8* cacheable_ptr([[maybe_unused]] u32 offset) { return nullptr; } diff --git a/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp b/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp index b4a94154378..99f035c8c6a 100644 --- a/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp +++ b/Userland/DevTools/UserspaceEmulator/SimpleRegion.cpp @@ -47,6 +47,18 @@ ValueWithShadow SimpleRegion::read64(u32 offset) return { *reinterpret_cast(m_data + offset), *reinterpret_cast(m_shadow_data + offset) }; } +ValueWithShadow SimpleRegion::read128(u32 offset) +{ + VERIFY(offset + 15 < size()); + return { *reinterpret_cast(m_data + offset), *reinterpret_cast(m_shadow_data + offset) }; +} + +ValueWithShadow SimpleRegion::read256(u32 offset) +{ + VERIFY(offset + 31 < size()); + return { *reinterpret_cast(m_data + offset), *reinterpret_cast(m_shadow_data + offset) }; +} + void SimpleRegion::write8(u32 offset, ValueWithShadow value) { VERIFY(offset < size()); @@ -74,6 +86,18 @@ void SimpleRegion::write64(u32 offset, ValueWithShadow value) *reinterpret_cast(m_data + offset) = value.value(); *reinterpret_cast(m_shadow_data + offset) = value.shadow(); } +void SimpleRegion::write128(u32 offset, ValueWithShadow value) +{ + VERIFY(offset + 15 < size()); + *reinterpret_cast(m_data + offset) = value.value(); + *reinterpret_cast(m_shadow_data + offset) = value.shadow(); +} +void SimpleRegion::write256(u32 offset, ValueWithShadow value) +{ + VERIFY(offset + 31 < size()); + *reinterpret_cast(m_data + offset) = value.value(); + *reinterpret_cast(m_shadow_data + offset) = value.shadow(); +} u8* SimpleRegion::cacheable_ptr(u32 offset) { diff --git a/Userland/DevTools/UserspaceEmulator/SimpleRegion.h b/Userland/DevTools/UserspaceEmulator/SimpleRegion.h index adad49dc65f..4847d12bd4b 100644 --- a/Userland/DevTools/UserspaceEmulator/SimpleRegion.h +++ b/Userland/DevTools/UserspaceEmulator/SimpleRegion.h @@ -19,11 +19,15 @@ public: virtual ValueWithShadow read16(u32 offset) override; virtual ValueWithShadow read32(u32 offset) override; virtual ValueWithShadow read64(u32 offset) override; + virtual ValueWithShadow read128(u32 offset) override; + virtual ValueWithShadow read256(u32 offset) override; virtual void write8(u32 offset, ValueWithShadow) override; virtual void write16(u32 offset, ValueWithShadow) override; virtual void write32(u32 offset, ValueWithShadow) override; virtual void write64(u32 offset, ValueWithShadow) override; + virtual void write128(u32 offset, ValueWithShadow) override; + virtual void write256(u32 offset, ValueWithShadow) override; virtual u8* data() override { return m_data; } virtual u8* shadow_data() override { return m_shadow_data; } diff --git a/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp b/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp index e88d3e65768..c41b6eaf401 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp +++ b/Userland/DevTools/UserspaceEmulator/SoftCPU.cpp @@ -133,6 +133,25 @@ ValueWithShadow SoftCPU::read_memory64(X86::LogicalAddress address) return value; } +ValueWithShadow SoftCPU::read_memory128(X86::LogicalAddress address) +{ + VERIFY(address.selector() == 0x1b || address.selector() == 0x23 || address.selector() == 0x2b); + auto value = m_emulator.mmu().read128(address); +#if MEMORY_DEBUG + outln("\033[36;1mread_memory128: @{:04x}:{:08x} -> {:032x} ({:032x})\033[0m", address.selector(), address.offset(), value, value.shadow()); +#endif + return value; +} +ValueWithShadow SoftCPU::read_memory256(X86::LogicalAddress address) +{ + VERIFY(address.selector() == 0x1b || address.selector() == 0x23 || address.selector() == 0x2b); + auto value = m_emulator.mmu().read256(address); +#if MEMORY_DEBUG + outln("\033[36;1mread_memory256: @{:04x}:{:08x} -> {:064x} ({:064x})\033[0m", address.selector(), address.offset(), value, value.shadow()); +#endif + return value; +} + void SoftCPU::write_memory8(X86::LogicalAddress address, ValueWithShadow value) { VERIFY(address.selector() == 0x23 || address.selector() == 0x2b); @@ -161,6 +180,24 @@ void SoftCPU::write_memory64(X86::LogicalAddress address, ValueWithShadow v m_emulator.mmu().write64(address, value); } +void SoftCPU::write_memory128(X86::LogicalAddress address, ValueWithShadow value) +{ + VERIFY(address.selector() == 0x23 || address.selector() == 0x2b); +#if MEMORY_DEBUG + outln("\033[36;1mwrite_memory128: @{:04x}:{:08x} <- {:032x} ({:032x})\033[0m", address.selector(), address.offset(), value, value.shadow()); +#endif + m_emulator.mmu().write128(address, value); +} + +void SoftCPU::write_memory256(X86::LogicalAddress address, ValueWithShadow value) +{ + VERIFY(address.selector() == 0x23 || address.selector() == 0x2b); +#if MEMORY_DEBUG + outln("\033[36;1mwrite_memory256: @{:04x}:{:08x} <- {:064x} ({:064x})\033[0m", address.selector(), address.offset(), value, value.shadow()); +#endif + m_emulator.mmu().write256(address, value); +} + void SoftCPU::push_string(const StringView& string) { size_t space_to_allocate = round_up_to_power_of_two(string.length() + 1, 16); diff --git a/Userland/DevTools/UserspaceEmulator/SoftCPU.h b/Userland/DevTools/UserspaceEmulator/SoftCPU.h index 7842b667df3..1b6647861d9 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftCPU.h +++ b/Userland/DevTools/UserspaceEmulator/SoftCPU.h @@ -39,6 +39,8 @@ public: using ValueWithShadowType16 = ValueWithShadow; using ValueWithShadowType32 = ValueWithShadow; using ValueWithShadowType64 = ValueWithShadow; + using ValueWithShadowType128 = ValueWithShadow; + using ValueWithShadowType256 = ValueWithShadow; explicit SoftCPU(Emulator&); void dump() const; @@ -347,6 +349,8 @@ public: ValueWithShadow read_memory16(X86::LogicalAddress); ValueWithShadow read_memory32(X86::LogicalAddress); ValueWithShadow read_memory64(X86::LogicalAddress); + ValueWithShadow read_memory128(X86::LogicalAddress); + ValueWithShadow read_memory256(X86::LogicalAddress); template ValueWithShadow read_memory(X86::LogicalAddress address) @@ -357,12 +361,20 @@ public: return read_memory16(address); if constexpr (sizeof(T) == 4) return read_memory32(address); + if constexpr (sizeof(T) == 8) + return read_memory64(address); + if constexpr (sizeof(T) == 16) + return read_memory128(address); + if constexpr (sizeof(T) == 32) + return read_memory256(address); } void write_memory8(X86::LogicalAddress, ValueWithShadow); void write_memory16(X86::LogicalAddress, ValueWithShadow); void write_memory32(X86::LogicalAddress, ValueWithShadow); void write_memory64(X86::LogicalAddress, ValueWithShadow); + void write_memory128(X86::LogicalAddress, ValueWithShadow); + void write_memory256(X86::LogicalAddress, ValueWithShadow); template void write_memory(X86::LogicalAddress address, ValueWithShadow data) @@ -373,6 +385,12 @@ public: return write_memory16(address, data); if constexpr (sizeof(T) == 4) return write_memory32(address, data); + if constexpr (sizeof(T) == 8) + return write_memory64(address, data); + if constexpr (sizeof(T) == 16) + return write_memory128(address, data); + if constexpr (sizeof(T) == 32) + return write_memory256(address, data); } bool evaluate_condition(u8 condition) const diff --git a/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp b/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp index 430e3b93ee8..67bc3a6e8ff 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp +++ b/Userland/DevTools/UserspaceEmulator/SoftMMU.cpp @@ -163,6 +163,42 @@ ValueWithShadow SoftMMU::read64(X86::LogicalAddress address) return region->read64(address.offset() - region->base()); } +ValueWithShadow SoftMMU::read128(X86::LogicalAddress address) +{ + auto* region = find_region(address); + if (!region) { + reportln("SoftMMU::read128: No region for @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + if (!region->is_readable()) { + reportln("SoftMMU::read128: Non-readable region @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + return region->read128(address.offset() - region->base()); +} + +ValueWithShadow SoftMMU::read256(X86::LogicalAddress address) +{ + auto* region = find_region(address); + if (!region) { + reportln("SoftMMU::read256: No region for @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + if (!region->is_readable()) { + reportln("SoftMMU::read256: Non-readable region @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + return region->read256(address.offset() - region->base()); +} + void SoftMMU::write8(X86::LogicalAddress address, ValueWithShadow value) { auto* region = find_region(address); @@ -234,6 +270,42 @@ void SoftMMU::write64(X86::LogicalAddress address, ValueWithShadow value) region->write64(address.offset() - region->base(), value); } +void SoftMMU::write128(X86::LogicalAddress address, ValueWithShadow value) +{ + auto* region = find_region(address); + if (!region) { + reportln("SoftMMU::write128: No region for @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + if (!region->is_writable()) { + reportln("SoftMMU::write128: Non-writable region @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + region->write128(address.offset() - region->base(), value); +} + +void SoftMMU::write256(X86::LogicalAddress address, ValueWithShadow value) +{ + auto* region = find_region(address); + if (!region) { + reportln("SoftMMU::write256: No region for @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + if (!region->is_writable()) { + reportln("SoftMMU::write256: Non-writable region @ {:p}", address.offset()); + m_emulator.dump_backtrace(); + TODO(); + } + + region->write256(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. diff --git a/Userland/DevTools/UserspaceEmulator/SoftMMU.h b/Userland/DevTools/UserspaceEmulator/SoftMMU.h index 41ab0bafa54..8767fe2d879 100644 --- a/Userland/DevTools/UserspaceEmulator/SoftMMU.h +++ b/Userland/DevTools/UserspaceEmulator/SoftMMU.h @@ -26,11 +26,15 @@ public: ValueWithShadow read16(X86::LogicalAddress); ValueWithShadow read32(X86::LogicalAddress); ValueWithShadow read64(X86::LogicalAddress); + ValueWithShadow read128(X86::LogicalAddress); + ValueWithShadow read256(X86::LogicalAddress); void write8(X86::LogicalAddress, ValueWithShadow); void write16(X86::LogicalAddress, ValueWithShadow); void write32(X86::LogicalAddress, ValueWithShadow); void write64(X86::LogicalAddress, ValueWithShadow); + void write128(X86::LogicalAddress, ValueWithShadow); + void write256(X86::LogicalAddress, ValueWithShadow); ALWAYS_INLINE Region* find_region(X86::LogicalAddress address) { diff --git a/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h b/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h index 53fb760187e..e1b65387ee7 100644 --- a/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h +++ b/Userland/DevTools/UserspaceEmulator/ValueWithShadow.h @@ -4,13 +4,19 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include - #pragma once +#include +#include +#include +#include + namespace UserspaceEmulator { +constexpr u64 _inititalized_64 = 0x01010101'01010101LLU; +constexpr u128 _initialized_128 = u128(_inititalized_64, _inititalized_64); +constexpr u256 _initialized_256 = u256(_initialized_128, _initialized_128); + template class ValueAndShadowReference; @@ -32,6 +38,12 @@ public: bool is_uninitialized() const { + if constexpr (sizeof(T) == 32) + return (m_shadow & _initialized_256) != _initialized_256; + if constexpr (sizeof(T) == 16) + return (m_shadow & _initialized_128) != _initialized_128; + if constexpr (sizeof(T) == 8) + return (m_shadow & _inititalized_64) != _inititalized_64; if constexpr (sizeof(T) == 4) return (m_shadow & 0x01010101) != 0x01010101; if constexpr (sizeof(T) == 2) @@ -42,6 +54,12 @@ public: void set_initialized() { + if constexpr (sizeof(T) == 32) + m_shadow = _initialized_256; + if constexpr (sizeof(T) == 16) + m_shadow = _initialized_128; + if constexpr (sizeof(T) == 8) + m_shadow = _inititalized_64; if constexpr (sizeof(T) == 4) m_shadow = 0x01010101; if constexpr (sizeof(T) == 2) @@ -68,6 +86,12 @@ public: bool is_uninitialized() const { + if constexpr (sizeof(T) == 32) + return (m_shadow & _initialized_256) != _initialized_256; + if constexpr (sizeof(T) == 16) + return (m_shadow & _initialized_128) != _initialized_128; + if constexpr (sizeof(T) == 8) + return (m_shadow & _inititalized_64) != _inititalized_64; if constexpr (sizeof(T) == 4) return (m_shadow & 0x01010101) != 0x01010101; if constexpr (sizeof(T) == 2) @@ -92,8 +116,12 @@ private: template ALWAYS_INLINE ValueWithShadow shadow_wrap_as_initialized(T value) { + if constexpr (sizeof(T) == 32) + return { value, _initialized_256 }; + if constexpr (sizeof(T) == 16) + return { value, _initialized_128 }; if constexpr (sizeof(T) == 8) - return { value, 0x01010101'01010101LLU }; + return { value, _inititalized_64 }; if constexpr (sizeof(T) == 4) return { value, 0x01010101 }; if constexpr (sizeof(T) == 2) @@ -149,3 +177,7 @@ struct AK::Formatter> : AK::Formatter { return Formatter::format(builder, value.value()); } }; + +#undef INITIALIZED_64 +#undef INITIALIZED_128 +#undef INITIALIZED_256 diff --git a/Userland/Libraries/LibX86/Instruction.h b/Userland/Libraries/LibX86/Instruction.h index 72f5623621e..fed65d35936 100644 --- a/Userland/Libraries/LibX86/Instruction.h +++ b/Userland/Libraries/LibX86/Instruction.h @@ -380,6 +380,10 @@ public: void write32(CPU&, const Instruction&, T); template void write64(CPU&, const Instruction&, T); + template + void write128(CPU&, const Instruction&, T); + template + void write256(CPU&, const Instruction&, T); template typename CPU::ValueWithShadowType8 read8(CPU&, const Instruction&); @@ -389,6 +393,10 @@ public: typename CPU::ValueWithShadowType32 read32(CPU&, const Instruction&); template typename CPU::ValueWithShadowType64 read64(CPU&, const Instruction&); + template + typename CPU::ValueWithShadowType128 read128(CPU&, const Instruction&); + template + typename CPU::ValueWithShadowType256 read256(CPU&, const Instruction&); template LogicalAddress resolve(const CPU&, const Instruction&); @@ -680,6 +688,22 @@ ALWAYS_INLINE void MemoryOrRegisterReference::write64(CPU& cpu, const Instructio cpu.write_memory64(address, value); } +template +ALWAYS_INLINE void MemoryOrRegisterReference::write128(CPU& cpu, const Instruction& insn, T value) +{ + VERIFY(!is_register()); + auto address = resolve(cpu, insn); + cpu.write_memory128(address, value); +} + +template +ALWAYS_INLINE void MemoryOrRegisterReference::write256(CPU& cpu, const Instruction& insn, T value) +{ + VERIFY(!is_register()); + auto address = resolve(cpu, insn); + cpu.write_memory256(address, value); +} + template ALWAYS_INLINE typename CPU::ValueWithShadowType8 MemoryOrRegisterReference::read8(CPU& cpu, const Instruction& insn) { @@ -718,6 +742,22 @@ ALWAYS_INLINE typename CPU::ValueWithShadowType64 MemoryOrRegisterReference::rea return cpu.read_memory64(address); } +template +ALWAYS_INLINE typename CPU::ValueWithShadowType128 MemoryOrRegisterReference::read128(CPU& cpu, const Instruction& insn) +{ + VERIFY(!is_register()); + auto address = resolve(cpu, insn); + return cpu.read_memory128(address); +} + +template +ALWAYS_INLINE typename CPU::ValueWithShadowType256 MemoryOrRegisterReference::read256(CPU& cpu, const Instruction& insn) +{ + VERIFY(!is_register()); + auto address = resolve(cpu, insn); + return cpu.read_memory256(address); +} + template ALWAYS_INLINE Instruction Instruction::from_stream(InstructionStreamType& stream, bool o32, bool a32) {