Skip to content

Commit

Permalink
Adds support for generating Tenet traces with bochs backend (#12)
Browse files Browse the repository at this point in the history
This PR adds experimental support for generating Tenet traces with the bochscpu backend.

Co-authored-by: Axel Souchet <1476421+0vercl0k@users.noreply.github.com>
  • Loading branch information
gaasedelen and 0vercl0k authored Aug 15, 2021
1 parent bbc75e3 commit 445a94f
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 10 deletions.
180 changes: 175 additions & 5 deletions src/wtf/bochscpu_backend.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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.
//
Expand Down Expand Up @@ -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<uint8_t, 64> 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");
}
49 changes: 46 additions & 3 deletions src/wtf/bochscpu_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

//
Expand Down Expand Up @@ -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<BochscpuMemAccess_t> 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.
Expand Down Expand Up @@ -139,7 +176,7 @@ class BochscpuBackend_t : public Backend_t {
// Stats of the run.
//

BochscpuRunStats_t RunStats_;
BochscpuRunStats_t RunStats_ = {};

uint64_t Seed_ = 0;

Expand Down Expand Up @@ -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);
};
9 changes: 8 additions & 1 deletion src/wtf/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

};

//
Expand Down
5 changes: 4 additions & 1 deletion src/wtf/wtf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ int main(int argc, const char *argv[]) {
->check(CLI::ExistingDirectory);

const std::unordered_map<std::string, TraceType_t> 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))
Expand Down

0 comments on commit 445a94f

Please sign in to comment.