From f38965d0bd0432b4d52319c356ccd02d94c74340 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 27 May 2019 18:44:09 -0700 Subject: [PATCH] dmnt: implement debug log opcode --- stratosphere/dmnt/source/dmnt_cheat_vm.cpp | 276 ++++++++++++++++----- stratosphere/dmnt/source/dmnt_cheat_vm.hpp | 26 ++ 2 files changed, 235 insertions(+), 67 deletions(-) diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp index 1aa5126e7..26c71194c 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.cpp +++ b/stratosphere/dmnt/source/dmnt_cheat_vm.cpp @@ -13,13 +13,28 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + +#include #include #include "dmnt_cheat_types.hpp" #include "dmnt_cheat_vm.hpp" #include "dmnt_cheat_manager.hpp" #include "dmnt_hid.hpp" +void DmntCheatVm::DebugLog(u32 log_id, u64 value) { + /* Just unconditionally try to create the log folder. */ + mkdir("/atmosphere/cheat_vm_logs", 0777); + FILE *f_log = NULL; + { + char log_path[FS_MAX_PATH]; + snprintf(log_path, sizeof(log_path), "/atmosphere/cheat_vm_logs/%x.log", log_id); + f_log = fopen(log_path, "ab"); + } + if (f_log != NULL) { + ON_SCOPE_EXIT { fclose(f_log); }; + fprintf(f_log, "%016lx\n", value); + } +} void DmntCheatVm::OpenDebugLogFile() { #ifdef DMNT_CHEAT_VM_DEBUG_LOG @@ -152,55 +167,86 @@ void DmntCheatVm::LogOpcode(const CheatVmOpcode *opcode) { break; } break; - case CheatVmOpcodeType_BeginRegisterConditionalBlock: + case CheatVmOpcodeType_BeginRegisterConditionalBlock: this->LogToDebugFile("Opcode: Begin Register Conditional\n"); this->LogToDebugFile("Bit Width: %x\n", opcode->begin_reg_cond.bit_width); this->LogToDebugFile("Cond Type: %x\n", opcode->begin_reg_cond.cond_type); this->LogToDebugFile("V Reg Idx: %x\n", opcode->begin_reg_cond.val_reg_index); - switch (opcode->begin_reg_cond.comp_type) { - case CompareRegisterValueType_StaticValue: - this->LogToDebugFile("Comp Type: Static Value\n"); - this->LogToDebugFile("Value: %lx\n", opcode->begin_reg_cond.value.bit64); - break; - case CompareRegisterValueType_OtherRegister: - this->LogToDebugFile("Comp Type: Other Register\n"); - this->LogToDebugFile("X Reg Idx: %x\n", opcode->begin_reg_cond.other_reg_index); - break; - case CompareRegisterValueType_MemoryRelAddr: - this->LogToDebugFile("Comp Type: Memory Relative Address\n"); - this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); - break; - case CompareRegisterValueType_MemoryOfsReg: - this->LogToDebugFile("Comp Type: Memory Offset Register\n"); - this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); - this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); - break; - case CompareRegisterValueType_RegisterRelAddr: - this->LogToDebugFile("Comp Type: Register Relative Address\n"); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); - this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); - break; - case CompareRegisterValueType_RegisterOfsReg: - this->LogToDebugFile("Comp Type: Register Offset Register\n"); - this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); - this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); - break; - } + switch (opcode->begin_reg_cond.comp_type) { + case CompareRegisterValueType_StaticValue: + this->LogToDebugFile("Comp Type: Static Value\n"); + this->LogToDebugFile("Value: %lx\n", opcode->begin_reg_cond.value.bit64); + break; + case CompareRegisterValueType_OtherRegister: + this->LogToDebugFile("Comp Type: Other Register\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode->begin_reg_cond.other_reg_index); + break; + case CompareRegisterValueType_MemoryRelAddr: + this->LogToDebugFile("Comp Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_MemoryOfsReg: + this->LogToDebugFile("Comp Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->begin_reg_cond.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); + break; + case CompareRegisterValueType_RegisterRelAddr: + this->LogToDebugFile("Comp Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->begin_reg_cond.rel_address); + break; + case CompareRegisterValueType_RegisterOfsReg: + this->LogToDebugFile("Comp Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->begin_reg_cond.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->begin_reg_cond.ofs_reg_index); + break; + } break; - case CheatVmOpcodeType_SaveRestoreRegister: + case CheatVmOpcodeType_SaveRestoreRegister: this->LogToDebugFile("Opcode: Save or Restore Register\n"); this->LogToDebugFile("Dst Idx: %x\n", opcode->save_restore_reg.dst_index); this->LogToDebugFile("Src Idx: %x\n", opcode->save_restore_reg.src_index); this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_reg.op_type); break; - case CheatVmOpcodeType_SaveRestoreRegisterMask: + case CheatVmOpcodeType_SaveRestoreRegisterMask: this->LogToDebugFile("Opcode: Save or Restore Register Mask\n"); this->LogToDebugFile("Op Type: %d\n", opcode->save_restore_regmask.op_type); for (size_t i = 0; i < NumRegisters; i++) { this->LogToDebugFile("Act[%02x]: %d\n", i, opcode->save_restore_regmask.should_operate[i]); } break; + case CheatVmOpcodeType_DebugLog: + this->LogToDebugFile("Opcode: Debug Log\n"); + this->LogToDebugFile("Bit Width: %x\n", opcode->debug_log.bit_width); + this->LogToDebugFile("Log ID: %x\n", opcode->debug_log.log_id); + this->LogToDebugFile("Val Type: %x\n", opcode->debug_log.val_type); + switch (opcode->debug_log.val_type) { + case DebugLogValueType_RegisterValue: + this->LogToDebugFile("Val Type: Register Value\n"); + this->LogToDebugFile("X Reg Idx: %x\n", opcode->debug_log.val_reg_index); + break; + case DebugLogValueType_MemoryRelAddr: + this->LogToDebugFile("Val Type: Memory Relative Address\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + this->LogToDebugFile("Val Type: Memory Offset Register\n"); + this->LogToDebugFile("Mem Type: %x\n", opcode->debug_log.mem_type); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); + break; + case DebugLogValueType_RegisterRelAddr: + this->LogToDebugFile("Val Type: Register Relative Address\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); + this->LogToDebugFile("Rel Addr: %lx\n", opcode->debug_log.rel_address); + break; + case DebugLogValueType_RegisterOfsReg: + this->LogToDebugFile("Val Type: Register Offset Register\n"); + this->LogToDebugFile("A Reg Idx: %x\n", opcode->debug_log.addr_reg_index); + this->LogToDebugFile("O Reg Idx: %x\n", opcode->debug_log.ofs_reg_index); + break; + } default: this->LogToDebugFile("Unknown opcode: %x\n", opcode->opcode); break; @@ -217,7 +263,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { *out = opcode; } }; - + /* Helper function for getting instruction dwords. */ auto GetNextDword = [&]() { if (this->instruction_ptr >= this->num_opcodes) { @@ -226,11 +272,11 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { } return this->program[this->instruction_ptr++]; }; - + /* Helper function for parsing a VmInt. */ auto GetNextVmInt = [&](const u32 bit_width) { VmInt val = {0}; - + const u32 first_dword = GetNextDword(); switch (bit_width) { case 1: @@ -246,16 +292,16 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { val.bit64 = (((u64)first_dword) << 32ul) | ((u64)GetNextDword()); break; } - + return val; }; - + /* Read opcode. */ const u32 first_dword = GetNextDword(); if (!valid) { return valid; } - + opcode.opcode = (CheatVmOpcodeType)(((first_dword >> 28) & 0xF)); if (opcode.opcode >= CheatVmOpcodeType_ExtendedWidth) { opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 24) & 0xF)); @@ -263,7 +309,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { if (opcode.opcode >= CheatVmOpcodeType_DoubleExtendedWidth) { opcode.opcode = (CheatVmOpcodeType)((((u32)opcode.opcode) << 4) | ((first_dword >> 20) & 0xF)); } - + /* detect condition start. */ switch (opcode.opcode) { case CheatVmOpcodeType_BeginConditionalBlock: @@ -275,7 +321,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { opcode.begin_conditional_block = false; break; } - + switch (opcode.opcode) { case CheatVmOpcodeType_StoreStatic: { @@ -314,7 +360,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { /* Parse register, whether loop start or loop end. */ opcode.ctrl_loop.start_loop = ((first_dword >> 24) & 0xF) == 0; opcode.ctrl_loop.reg_index = ((first_dword >> 20) & 0xF); - + /* Read number of iters if loop start. */ if (opcode.ctrl_loop.start_loop) { opcode.ctrl_loop.num_iters = GetNextDword(); @@ -451,7 +497,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { opcode.begin_reg_cond.cond_type = (ConditionalComparisonType)((first_dword >> 16) & 0xF); opcode.begin_reg_cond.val_reg_index = ((first_dword >> 12) & 0xF); opcode.begin_reg_cond.comp_type = (CompareRegisterValueType)((first_dword >> 8) & 0xF); - + switch (opcode.begin_reg_cond.comp_type) { case CompareRegisterValueType_StaticValue: opcode.begin_reg_cond.value = GetNextVmInt(opcode.begin_reg_cond.bit_width); @@ -503,6 +549,51 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { } } break; + case CheatVmOpcodeType_DebugLog: + { + /* FFFTIX## */ + /* FFFTI0Ma aaaaaaaa */ + /* FFFTI1Mr */ + /* FFFTI2Ra aaaaaaaa */ + /* FFFTI3Rr */ + /* FFFTI4X0 */ + /* FFF = opcode 0xFFF */ + /* T = bit width. */ + /* I = log id. */ + /* X = value operand type, 0 = main/heap with relative offset, 1 = main/heap with offset register, */ + /* 2 = register with relative offset, 3 = register with offset register, 4 = register value. */ + /* M = memory type. */ + /* R = address register. */ + /* a = relative address. */ + /* r = offset register. */ + /* X = value register. */ + opcode.debug_log.bit_width = (first_dword >> 16) & 0xF; + opcode.debug_log.log_id = ((first_dword >> 12) & 0xF); + opcode.debug_log.val_type = (DebugLogValueType)((first_dword >> 8) & 0xF); + + switch (opcode.debug_log.val_type) { + case DebugLogValueType_RegisterValue: + opcode.debug_log.val_reg_index = ((first_dword >> 4) & 0xF); + break; + case DebugLogValueType_MemoryRelAddr: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_MemoryOfsReg: + opcode.debug_log.mem_type = (MemoryAccessType)((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + case DebugLogValueType_RegisterRelAddr: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.rel_address = (((u64)(first_dword & 0xF) << 32ul) | ((u64)GetNextDword())); + break; + case DebugLogValueType_RegisterOfsReg: + opcode.debug_log.addr_reg_index = ((first_dword >> 4) & 0xF); + opcode.debug_log.ofs_reg_index = (first_dword & 0xF); + break; + } + } + break; case CheatVmOpcodeType_ExtendedWidth: case CheatVmOpcodeType_DoubleExtendedWidth: default: @@ -510,7 +601,7 @@ bool DmntCheatVm::DecodeNextOpcode(CheatVmOpcode *out) { valid = false; break; } - + /* End decoding. */ return valid; } @@ -519,7 +610,7 @@ void DmntCheatVm::SkipConditionalBlock() { if (this->condition_depth > 0) { /* We want to continue until we're out of the current block. */ const size_t desired_depth = this->condition_depth - 1; - + CheatVmOpcode skip_opcode; while (this->condition_depth > desired_depth && this->DecodeNextOpcode(&skip_opcode)) { /* Decode instructions until we see end of the current conditional block. */ @@ -527,7 +618,7 @@ void DmntCheatVm::SkipConditionalBlock() { /* Gateway currently checks for "0x2" instead of "0x20000000" */ /* In addition, they do a linear scan instead of correctly decoding opcodes. */ /* This causes issues if "0x2" appears as an immediate in the conditional block... */ - + /* We also support nesting of conditional blocks, and Gateway does not. */ if (skip_opcode.begin_conditional_block) { this->condition_depth++; @@ -585,7 +676,7 @@ void DmntCheatVm::ResetState() { bool DmntCheatVm::LoadProgram(const CheatEntry *cheats, size_t num_cheats) { /* Reset opcode count. */ this->num_opcodes = 0; - + for (size_t i = 0; i < num_cheats; i++) { if (cheats[i].enabled) { /* Bounds check. */ @@ -593,34 +684,34 @@ bool DmntCheatVm::LoadProgram(const CheatEntry *cheats, size_t num_cheats) { this->num_opcodes = 0; return false; } - + for (size_t n = 0; n < cheats[i].definition.num_opcodes; n++) { this->program[this->num_opcodes++] = cheats[i].definition.opcodes[n]; } } } - + return true; } void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { CheatVmOpcode cur_opcode; u64 kDown = 0; - + /* Get Keys down. */ HidManagement::GetKeysDown(&kDown); - + this->OpenDebugLogFile(); ON_SCOPE_EXIT { this->CloseDebugLogFile(); }; - + this->LogToDebugFile("Started VM execution.\n"); this->LogToDebugFile("Main NSO: %012lx\n", metadata->main_nso_extents.base); this->LogToDebugFile("Heap: %012lx\n", metadata->main_nso_extents.base); this->LogToDebugFile("Keys Down: %08x\n", (u32)(kDown & 0x0FFFFFFF)); - + /* Clear VM state. */ this->ResetState(); - + /* Loop until program finishes. */ while (this->DecodeNextOpcode(&cur_opcode)) { this->LogToDebugFile("Instruction Ptr: %04x\n", (u32)this->instruction_ptr); @@ -633,12 +724,12 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { this->LogToDebugFile("SavedRegs[%02x]: %016lx\n", i, this->saved_values[i]); } this->LogOpcode(&cur_opcode); - + /* Increment conditional depth, if relevant. */ if (cur_opcode.begin_conditional_block) { this->condition_depth++; } - + switch (cur_opcode.opcode) { case CheatVmOpcodeType_StoreStatic: { @@ -814,10 +905,10 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { case CheatVmOpcodeType_PerformArithmeticRegister: { const u64 operand_1_value = this->registers[cur_opcode.perform_math_reg.src_reg_1_index]; - const u64 operand_2_value = cur_opcode.perform_math_reg.has_immediate ? + const u64 operand_2_value = cur_opcode.perform_math_reg.has_immediate ? GetVmInt(cur_opcode.perform_math_reg.value, cur_opcode.perform_math_reg.bit_width) : this->registers[cur_opcode.perform_math_reg.src_reg_2_index]; - + u64 res_val = 0; /* Do requested math. */ switch (cur_opcode.perform_math_reg.math_type) { @@ -852,8 +943,8 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { res_val = operand_1_value; break; } - - + + /* Apply bit width. */ switch (cur_opcode.perform_math_reg.bit_width) { case 1: @@ -869,7 +960,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { res_val = static_cast(res_val); break; } - + /* Save to register. */ this->registers[cur_opcode.perform_math_reg.dst_reg_index] = res_val; } @@ -899,7 +990,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { dst_address = GetCheatProcessAddress(metadata, cur_opcode.str_register.mem_type, this->registers[cur_opcode.str_register.addr_reg_index] + cur_opcode.str_register.rel_address); break; } - + /* Write value to memory. Write only on valid bitwidth. */ switch (cur_opcode.str_register.bit_width) { case 1: @@ -909,7 +1000,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { DmntCheatManager::WriteCheatProcessMemoryForVm(dst_address, &dst_value, cur_opcode.str_register.bit_width); break; } - + /* Increment register if relevant. */ if (cur_opcode.str_register.increment_reg) { this->registers[cur_opcode.str_register.addr_reg_index] += cur_opcode.str_register.bit_width; @@ -934,7 +1025,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { src_value = static_cast(this->registers[cur_opcode.begin_reg_cond.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); break; } - + /* Read value from memory. */ u64 cond_value = 0; if (cur_opcode.begin_reg_cond.comp_type == CompareRegisterValueType_StaticValue) { @@ -981,7 +1072,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { break; } } - + /* Check against condition. */ bool cond_met = false; switch (cur_opcode.begin_reg_cond.cond_type) { @@ -1004,7 +1095,7 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { cond_met = src_value != cond_value; break; } - + /* Skip conditional block if condition not met. */ if (!cond_met) { this->SkipConditionalBlock(); @@ -1062,6 +1153,57 @@ void DmntCheatVm::Execute(const CheatProcessMetadata *metadata) { } } break; + case CheatVmOpcodeType_DebugLog: + { + /* Read value from memory. */ + u64 log_value = 0; + if (cur_opcode.debug_log.val_type == DebugLogValueType_RegisterValue) { + switch (cur_opcode.debug_log.bit_width) { + case 1: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFul); + break; + case 2: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFul); + break; + case 4: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFul); + break; + case 8: + log_value = static_cast(this->registers[cur_opcode.debug_log.val_reg_index] & 0xFFFFFFFFFFFFFFFFul); + break; + } + } else { + u64 val_address = 0; + switch (cur_opcode.debug_log.val_type) { + case DebugLogValueType_MemoryRelAddr: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, cur_opcode.debug_log.rel_address); + break; + case DebugLogValueType_MemoryOfsReg: + val_address = GetCheatProcessAddress(metadata, cur_opcode.debug_log.mem_type, this->registers[cur_opcode.debug_log.ofs_reg_index]); + break; + case DebugLogValueType_RegisterRelAddr: + val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + cur_opcode.debug_log.rel_address; + break; + case DebugLogValueType_RegisterOfsReg: + val_address = this->registers[cur_opcode.debug_log.addr_reg_index] + this->registers[cur_opcode.debug_log.ofs_reg_index]; + break; + default: + break; + } + switch (cur_opcode.debug_log.bit_width) { + case 1: + case 2: + case 4: + case 8: + DmntCheatManager::ReadCheatProcessMemoryForVm(val_address, &log_value, cur_opcode.debug_log.bit_width); + break; + } + } + + /* Log value. */ + this->DebugLog(cur_opcode.debug_log.log_id, log_value); + } + break; default: /* By default, we do a no-op. */ break; diff --git a/stratosphere/dmnt/source/dmnt_cheat_vm.hpp b/stratosphere/dmnt/source/dmnt_cheat_vm.hpp index 210f949d8..2241947b0 100644 --- a/stratosphere/dmnt/source/dmnt_cheat_vm.hpp +++ b/stratosphere/dmnt/source/dmnt_cheat_vm.hpp @@ -49,6 +49,9 @@ enum CheatVmOpcodeType : u32 { /* This is a meta entry, and not a real opcode. */ /* This is to facilitate multi-nybble instruction decoding. */ CheatVmOpcodeType_DoubleExtendedWidth = 0xF0, + + /* Double-extended width opcodes. */ + CheatVmOpcodeType_DebugLog = 0xFFF, }; enum MemoryAccessType : u32 { @@ -106,6 +109,14 @@ enum SaveRestoreRegisterOpType : u32 { SaveRestoreRegisterOpType_ClearRegs = 3, }; +enum DebugLogValueType : u32 { + DebugLogValueType_MemoryRelAddr = 0, + DebugLogValueType_MemoryOfsReg = 1, + DebugLogValueType_RegisterRelAddr = 2, + DebugLogValueType_RegisterOfsReg = 3, + DebugLogValueType_RegisterValue = 4, +}; + union VmInt { u8 bit8; u16 bit16; @@ -215,6 +226,17 @@ struct SaveRestoreRegisterMaskOpcode { bool should_operate[0x10]; }; +struct DebugLogOpcode { + u32 bit_width; + u32 log_id; + DebugLogValueType val_type; + MemoryAccessType mem_type; + u32 addr_reg_index; + u32 val_reg_index; + u32 ofs_reg_index; + u64 rel_address; +}; + struct CheatVmOpcode { CheatVmOpcodeType opcode; bool begin_conditional_block; @@ -233,6 +255,7 @@ struct CheatVmOpcode { BeginRegisterConditionalOpcode begin_reg_cond; SaveRestoreRegisterOpcode save_restore_reg; SaveRestoreRegisterMaskOpcode save_restore_regmask; + DebugLogOpcode debug_log; }; }; @@ -254,6 +277,9 @@ class DmntCheatVm { void SkipConditionalBlock(); void ResetState(); + /* For implementing the DebugLog opcode. */ + void DebugLog(u32 log_id, u64 value); + /* For debugging. These will be IFDEF'd out normally. */ void OpenDebugLogFile(); void CloseDebugLogFile();