diff --git a/src/wtf/bochscpu_backend.cc b/src/wtf/bochscpu_backend.cc index 0632829..d3347e8 100644 --- a/src/wtf/bochscpu_backend.cc +++ b/src/wtf/bochscpu_backend.cc @@ -286,12 +286,28 @@ BochscpuBackend_t::Run(const uint8_t *Buffer, const uint64_t BufferSize) { RunStats_.Reset(); + // + // Force dumping all the registers if this is a Tenet trace. + // + + if (TraceType_ == TraceType_t::Tenet) { + DumpTenetDelta(true); + } + // // Lift off. // bochscpu_cpu_run(Cpu_, HookChain_); + // + // Dump the last delta for Tenet traces. + // + + if (TraceType_ == TraceType_t::Tenet) { + DumpTenetDelta(); + } + // // Fill in the stats. // @@ -391,18 +407,38 @@ __declspec(safebuffers) LastNewCoverage_.emplace(Rip); } + const bool TenetTrace = TraceType_ == TraceType_t::Tenet; if (TraceFile_) { const bool RipTrace = TraceType_ == TraceType_t::Rip; const bool UniqueRipTrace = TraceType_ == TraceType_t::UniqueRip; const bool NewRip = Res.second; - // - // On Linux we don't have access to dbgeng so just write the plain address - // for both Windows & Linux. - // - if (RipTrace || (UniqueRipTrace && NewRip)) { + + // + // On Linux we don't have access to dbgeng so just write the plain + // address for both Windows & Linux. + // + fmt::print(TraceFile_, "{:#x}\n", Rip); + } else if (TenetTrace) { + if (Tenet_.PastFirstInstruction_) { + + // + // If we already executed an instruction, dump register + mem changes if + // generating Tenet traces. + // + + DumpTenetDelta(); + } + + // + // Save a complete copy of the registers so that we can diff them against + // the next step when taking Tenet traces. + // + + bochscpu_cpu_state(Cpu_, &Tenet_.CpuStatePrev_); + Tenet_.PastFirstInstruction_ = true; } } @@ -434,6 +470,14 @@ void BochscpuBackend_t::LinAccessHook(/*void *Context, */ uint32_t, RunStats_.NumberMemoryAccesses += Len; + // + // Log explicit details about the memory access if taking a full-trace. + // + + if (TraceFile_ && TraceType_ == TraceType_t::Tenet) { + Tenet_.MemAccesses_.emplace_back(VirtualAddress, Len, MemAccess); + } + // // If this is not a write access, we don't care to go further. // @@ -1018,3 +1062,129 @@ uint64_t BochscpuBackend_t::SetReg(const Registers_t Reg, Setter(Cpu_, Value); return Value; } + +[[nodiscard]] constexpr const char * +MemAccessToTenetLabel(const uint32_t MemAccess) { + switch (MemAccess) { + case BOCHSCPU_HOOK_MEM_READ: { + return "mr"; + } + + case BOCHSCPU_HOOK_MEM_RW: { + return "mrw"; + } + + case BOCHSCPU_HOOK_MEM_WRITE: { + return "mw"; + break; + } + + default: { + fmt::print("Unexpected MemAccess type, aborting\n"); + std::abort(); + } + } +} + +void BochscpuBackend_t::DumpTenetDelta(const bool Force) { + + // + // Dump register deltas. + // + +#define __DeltaRegister(Reg, Comma) \ + { \ + if (bochscpu_cpu_##Reg(Cpu_) != Tenet_.CpuStatePrev_.Reg || Force) { \ + fmt::print(TraceFile_, #Reg "={:#x}", bochscpu_cpu_##Reg(Cpu_)); \ + if (Comma) { \ + fmt::print(TraceFile_, ","); \ + } \ + } \ + } + +#define DeltaRegister(Reg) __DeltaRegister(Reg, true) +#define DeltaRegisterEnd(Reg) __DeltaRegister(Reg, false) + + DeltaRegister(rax); + DeltaRegister(rbx); + DeltaRegister(rcx); + DeltaRegister(rdx); + DeltaRegister(rbp); + DeltaRegister(rsp); + DeltaRegister(rsi); + DeltaRegister(rdi); + DeltaRegister(r8); + DeltaRegister(r9); + DeltaRegister(r10); + DeltaRegister(r11); + DeltaRegister(r12); + DeltaRegister(r13); + DeltaRegister(r14); + DeltaRegister(r15); + DeltaRegisterEnd(rip); + +#undef DeltaRegisterEnd +#undef DeltaRegister +#undef __DeltaRegister + + // + // Dump memory deltas. + // + + for (const auto &AccessInfo : Tenet_.MemAccesses_) { + + // + // Determine the label to use for this memory access. + // + + const char *MemoryType = MemAccessToTenetLabel(AccessInfo.MemAccess); + + // + // Fetch the memory that was read or written by the last executed + // instruction. The largest load that can happen today is an AVX512 + // load which is 64 bytes long. + // + + std::array Buffer; + if (AccessInfo.Len > Buffer.size()) { + fmt::print("A memory access was bigger than {} bytes, aborting\n", + AccessInfo.Len); + std::abort(); + } + + if (!VirtRead(AccessInfo.VirtualAddress, Buffer.data(), AccessInfo.Len)) { + fmt::print("VirtRead at {:#x} failed, aborting\n", + AccessInfo.VirtualAddress); + std::abort(); + } + + // + // Convert the raw memory bytes to a human-readable hex string. + // + + std::string HexString; + for (size_t Idx = 0; Idx < AccessInfo.Len; Idx++) { + HexString = fmt::format("{}{:02X}", HexString, Buffer[Idx]); + } + + // + // Write the formatted memory access to file, eg + // 'mr=0x140148040:0000000400080040'. + // + + fmt::print(TraceFile_, ",{}={:#x}:{}", MemoryType, + AccessInfo.VirtualAddress, HexString); + } + + // + // Clear out the saved memory accesses as they are no longer needed. + // + + Tenet_.MemAccesses_.clear(); + + // + // End of deltas. + // + + fmt::print(TraceFile_, "\n"); +} diff --git a/src/wtf/bochscpu_backend.h b/src/wtf/bochscpu_backend.h index 601dad6..06a363d 100644 --- a/src/wtf/bochscpu_backend.h +++ b/src/wtf/bochscpu_backend.h @@ -39,6 +39,20 @@ struct BochscpuRunStats_t { } }; +// +// A structure to capture information about a single memory access; used for +// Tenet traces. +// + +struct BochscpuMemAccess_t { + const Gva_t VirtualAddress; + const uintptr_t Len; + const uint32_t MemAccess; + explicit BochscpuMemAccess_t(const uint64_t VirtualAddress, + const uintptr_t Len, const uint32_t MemAccess) + : VirtualAddress(VirtualAddress), Len(Len), MemAccess(MemAccess) {} +}; + class BochscpuBackend_t : public Backend_t { // @@ -90,18 +104,41 @@ class BochscpuBackend_t : public Backend_t { bochscpu_cpu_t Cpu_ = nullptr; + struct Tenet_t { + + // + // A copy of Cpu registers at t-1 (the previous instruction); used for Tenet + // traces. + // + + bochscpu_cpu_state_t CpuStatePrev_ = {}; + + // + // Boolean that tracks if the execution is past the first execution; used + // for Tenet traces. + // + + bool PastFirstInstruction_ = false; + + // + // List of memory accesses; used for Tenet traces. + // + + std::vector MemAccesses_; + } Tenet_; + // // The hooks we define onto the Cpu. // - bochscpu_hooks_t Hooks_; + bochscpu_hooks_t Hooks_ = {}; // // The chain of hooks. We only use a set of hooks, so we need // only two entries (it has to end with a nullptr entry). // - bochscpu_hooks_t *HookChain_[2]; + bochscpu_hooks_t *HookChain_[2] = {}; // // Instruction limit. @@ -139,7 +176,7 @@ class BochscpuBackend_t : public Backend_t { // Stats of the run. // - BochscpuRunStats_t RunStats_; + BochscpuRunStats_t RunStats_ = {}; uint64_t Seed_ = 0; @@ -273,4 +310,10 @@ class BochscpuBackend_t : public Backend_t { const uint8_t *GetTestcaseBuffer(); uint64_t GetTestcaseSize(); + + // + // Dump the register & memory deltas for Tenet. + // + + void DumpTenetDelta(const bool Force = false); }; diff --git a/src/wtf/globals.h b/src/wtf/globals.h index c0b94b5..1f7a3f6 100644 --- a/src/wtf/globals.h +++ b/src/wtf/globals.h @@ -1200,7 +1200,14 @@ enum class TraceType_t { // This is a trace of only unique rip locations. // - UniqueRip + UniqueRip, + + // + // This is a Tenet trace of register & mem changes. + // + + Tenet + }; // diff --git a/src/wtf/wtf.cc b/src/wtf/wtf.cc index 8d2d0bc..23b9d2e 100644 --- a/src/wtf/wtf.cc +++ b/src/wtf/wtf.cc @@ -152,7 +152,10 @@ int main(int argc, const char *argv[]) { ->check(CLI::ExistingDirectory); const std::unordered_map TraceTypeMap = { - {"rip", TraceType_t::Rip}, {"cov", TraceType_t::UniqueRip}}; + {"rip", TraceType_t::Rip}, + {"cov", TraceType_t::UniqueRip}, + {"tenet", TraceType_t::Tenet} + }; TraceOpt->add_option("--trace-type", Opts.Run.TraceType, "Trace type") ->transform(CLI::CheckedTransformer(TraceTypeMap, CLI::ignore_case))