ARM/ARM64 instruction analysis, hook up to handler

This commit is contained in:
Henrik Rydgard 2019-02-12 10:58:20 +01:00 committed by Henrik Rydgård
parent aa802ecc0f
commit c988d42b04
18 changed files with 202 additions and 51 deletions

View file

@ -19,7 +19,7 @@ public:
CodeBlockCommon() {} CodeBlockCommon() {}
virtual ~CodeBlockCommon() {} virtual ~CodeBlockCommon() {}
bool IsInSpace(const u8 *ptr) { bool IsInSpace(const u8 *ptr) const {
return (ptr >= region) && (ptr < (region + region_size)); return (ptr >= region) && (ptr < (region + region_size));
} }

View file

@ -3,7 +3,6 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include "Common/ExceptionHandlerSetup.h" #include "Common/ExceptionHandlerSetup.h"
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
@ -84,6 +83,7 @@ void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
return; return;
} }
INFO_LOG(SYSTEM, "Installing exception handler");
g_badAccessHandler = badAccessHandler; g_badAccessHandler = badAccessHandler;
g_vectoredExceptionHandle = AddVectoredExceptionHandler(TRUE, Handler); g_vectoredExceptionHandle = AddVectoredExceptionHandler(TRUE, Handler);
} }
@ -91,6 +91,7 @@ void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
void UninstallExceptionHandler() { void UninstallExceptionHandler() {
RemoveVectoredExceptionHandler(g_vectoredExceptionHandle); RemoveVectoredExceptionHandler(g_vectoredExceptionHandle);
g_badAccessHandler = nullptr; g_badAccessHandler = nullptr;
INFO_LOG(SYSTEM, "Removed exception handler");
} }
#elif defined(__APPLE__) && !defined(USE_SIGACTION_ON_APPLE) #elif defined(__APPLE__) && !defined(USE_SIGACTION_ON_APPLE)
@ -186,7 +187,14 @@ static void ExceptionThread(mach_port_t port) {
} }
void InstallExceptionHandler(BadAccessHandler badAccessHandler) { void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
g_badAccessHandler = badAccessHandler; if (!g_badAccessHandler) {
g_badAccessHandler = badAccessHandler;
} else {
// The rest of the setup we don't need to do again.
g_badAccessHandler = badAccessHandler;
return;
}
INFO_LOG(SYSTEM, "Installing exception handler");
mach_port_t port; mach_port_t port;
CheckKR("mach_port_allocate", CheckKR("mach_port_allocate",
mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port)); mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &port));
@ -275,6 +283,10 @@ static void sigsegv_handler(int sig, siginfo_t* info, void* raw_context) {
} }
void InstallExceptionHandler(BadAccessHandler badAccessHandler) { void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
if (g_badAccessHandler) {
return;
}
NOTICE_LOG(SYSTEM, "Installed exception handler");
g_badAccessHandler = badAccessHandler; g_badAccessHandler = badAccessHandler;
stack_t signal_stack; stack_t signal_stack;
@ -308,10 +320,14 @@ void UninstallExceptionHandler() {
#ifdef __APPLE__ #ifdef __APPLE__
sigaction(SIGBUS, &old_sa_bus, nullptr); sigaction(SIGBUS, &old_sa_bus, nullptr);
#endif #endif
NOTICE_LOG(SYSTEM, "Uninstalled exception handler");
g_badAccessHandler = nullptr;
} }
#else // _M_GENERIC or unsupported platform #else // _M_GENERIC or unsupported platform
void InstallExceptionHandler(BadAccessHandler badAccessHandler) { } void InstallExceptionHandler(BadAccessHandler badAccessHandler) {
ERROR_LOG(SYSTEM, "Exception handler not implemented on this platform, can't install");
}
void UninstallExceptionHandler() { } void UninstallExceptionHandler() { }
#endif #endif

View file

@ -17,7 +17,7 @@
#include "x64Analyzer.h" #include "x64Analyzer.h"
bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info) bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
{ {
int accessType = 0; int accessType = 0;

View file

@ -19,7 +19,7 @@
#include "Common.h" #include "Common.h"
struct InstructionInfo struct LSInstructionInfo
{ {
int operandSize; //8, 16, 32, 64 int operandSize; //8, 16, 32, 64
int instructionSize; int instructionSize;
@ -60,4 +60,4 @@ enum AccessType {
OP_ACCESS_WRITE = 1 OP_ACCESS_WRITE = 1
}; };
bool DisassembleMov(const unsigned char *codePtr, InstructionInfo &info); bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info);

View file

@ -237,6 +237,7 @@ void ArmJit::GenerateFixedCode() {
CMP(R0, 0); CMP(R0, 0);
B_CC(CC_EQ, outerLoop); B_CC(CC_EQ, outerLoop);
const uint8_t *quitLoop = GetCodePtr();
SetJumpTarget(badCoreState); SetJumpTarget(badCoreState);
SaveDowncount(); SaveDowncount();
@ -251,6 +252,12 @@ void ArmJit::GenerateFixedCode() {
POP(9, R4, R5, R6, R7, R8, R9, R10, R11, R_PC); // Returns POP(9, R4, R5, R6, R7, R8, R9, R10, R11, R_PC); // Returns
crashHandler = GetCodePtr();
MOVP2R(R0, &coreState);
MOVI2R(R1, CORE_ERROR);
STR(R1, R0, 0);
B(quitLoop);
// Uncomment if you want to see the output... // Uncomment if you want to see the output...
if (disasm) { if (disasm) {
INFO_LOG(JIT, "THE DISASM ========================"); INFO_LOG(JIT, "THE DISASM ========================");

View file

@ -51,6 +51,8 @@ public:
void Compile(u32 em_address) override; // Compiles a block at current MIPS PC void Compile(u32 em_address) override; // Compiles a block at current MIPS PC
const u8 *GetCrashHandler() const override { return crashHandler; }
bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); }
bool DescribeCodePtr(const u8 *ptr, std::string &name) override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
MIPSOpcode GetOriginalOp(MIPSOpcode op) override; MIPSOpcode GetOriginalOp(MIPSOpcode op) override;
@ -313,6 +315,8 @@ public:
const u8 *restoreRoundingMode; const u8 *restoreRoundingMode;
const u8 *applyRoundingMode; const u8 *applyRoundingMode;
const u8 *crashHandler;
}; };
} // namespace MIPSComp } // namespace MIPSComp

View file

@ -277,6 +277,7 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) {
CMP(SCRATCH1, 0); CMP(SCRATCH1, 0);
B(CC_EQ, outerLoop); B(CC_EQ, outerLoop);
const uint8_t *quitLoop = GetCodePtr();
SetJumpTarget(badCoreState); SetJumpTarget(badCoreState);
SaveStaticRegisters(); SaveStaticRegisters();
@ -286,6 +287,12 @@ void Arm64Jit::GenerateFixedCode(const JitOptions &jo) {
RET(); RET();
crashHandler = GetCodePtr();
MOVP2R(SCRATCH1_64, &coreState);
MOVI2R(SCRATCH2, CORE_ERROR);
STR(INDEX_UNSIGNED, SCRATCH2, SCRATCH1_64, 0);
B(quitLoop);
// Generate some integer conversion funcs. // Generate some integer conversion funcs.
// MIPS order! // MIPS order!
static const RoundingMode roundModes[8] = { ROUND_N, ROUND_Z, ROUND_P, ROUND_M, ROUND_N, ROUND_Z, ROUND_P, ROUND_M }; static const RoundingMode roundModes[8] = { ROUND_N, ROUND_Z, ROUND_P, ROUND_M, ROUND_N, ROUND_Z, ROUND_P, ROUND_M };

View file

@ -52,6 +52,8 @@ public:
void Compile(u32 em_address) override; // Compiles a block at current MIPS PC void Compile(u32 em_address) override; // Compiles a block at current MIPS PC
const u8 *DoJit(u32 em_address, JitBlock *b); const u8 *DoJit(u32 em_address, JitBlock *b);
const u8 *GetCrashHandler() const override { return crashHandler; }
bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); }
bool DescribeCodePtr(const u8 *ptr, std::string &name) override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
MIPSOpcode GetOriginalOp(MIPSOpcode op) override; MIPSOpcode GetOriginalOp(MIPSOpcode op) override;
@ -281,6 +283,8 @@ public:
const u8 *applyRoundingMode; const u8 *applyRoundingMode;
const u8 *updateRoundingMode; const u8 *updateRoundingMode;
const u8 *crashHandler;
int jitStartOffset; int jitStartOffset;
// Indexed by FPCR FZ:RN bits for convenience. Uses SCRATCH2. // Indexed by FPCR FZ:RN bits for convenience. Uses SCRATCH2.

View file

@ -160,7 +160,12 @@ public:
void InvalidateCacheAt(u32 em_address, int length = 4) override; void InvalidateCacheAt(u32 em_address, int length = 4) override;
void UpdateFCR31() override; void UpdateFCR31() override;
bool CodeInRange(const u8 *ptr) const override {
return false;
}
const u8 *GetDispatcher() const override { return nullptr; } const u8 *GetDispatcher() const override { return nullptr; }
const u8 *GetCrashHandler() const override { return nullptr; }
void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override; void LinkBlock(u8 *exitPoint, const u8 *checkedEntry) override;
void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override; void UnlinkBlock(u8 *checkedEntry, u32 originalAddress) override;

View file

@ -121,8 +121,10 @@ namespace MIPSComp {
public: public:
virtual ~JitInterface() {} virtual ~JitInterface() {}
virtual bool CodeInRange(const u8 *ptr) const = 0;
virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0; virtual bool DescribeCodePtr(const u8 *ptr, std::string &name) = 0;
virtual const u8 *GetDispatcher() const = 0; virtual const u8 *GetDispatcher() const = 0;
virtual const u8 *GetCrashHandler() const = 0;
virtual JitBlockCache *GetBlockCache() = 0; virtual JitBlockCache *GetBlockCache() = 0;
virtual JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() = 0; virtual JitBlockCacheDebugInterface *GetBlockCacheDebugInterface() = 0;
virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0; virtual void InvalidateCacheAt(u32 em_address, int length = 4) = 0;

View file

@ -206,11 +206,21 @@ void Jit::GenerateFixedCode(JitOptions &jo) {
} }
J_CC(CC_Z, outerLoop, true); J_CC(CC_Z, outerLoop, true);
const uint8_t *quitLoop = GetCodePtr();
SetJumpTarget(badCoreState); SetJumpTarget(badCoreState);
RestoreRoundingMode(true); RestoreRoundingMode(true);
ABI_PopAllCalleeSavedRegsAndAdjustStack(); ABI_PopAllCalleeSavedRegsAndAdjustStack();
RET(); RET();
crashHandler = GetCodePtr();
if (RipAccessible((const void *)&coreState)) {
MOV(32, M(&coreState), Imm32(CORE_RUNTIME_ERROR));
} else {
MOV(PTRBITS, R(RAX), ImmPtr((const void *)&coreState));
MOV(32, MatR(RAX), Imm32(CORE_RUNTIME_ERROR));
}
JMP(quitLoop, true);
// Let's spare the pre-generated code from unprotect-reprotect. // Let's spare the pre-generated code from unprotect-reprotect.
endOfPregeneratedCode = AlignCodePage(); endOfPregeneratedCode = AlignCodePage();
EndWrite(); EndWrite();

View file

@ -442,6 +442,8 @@ bool Jit::DescribeCodePtr(const u8 *ptr, std::string &name) {
name = "enterDispatcher"; name = "enterDispatcher";
else if (ptr == restoreRoundingMode) else if (ptr == restoreRoundingMode)
name = "restoreRoundingMode"; name = "restoreRoundingMode";
else if (ptr == crashHandler)
name = "crashHandler";
else { else {
u32 jitAddr = blocks.GetAddressFromBlockPtr(ptr); u32 jitAddr = blocks.GetAddressFromBlockPtr(ptr);

View file

@ -63,6 +63,8 @@ public:
void Compile(u32 em_address) override; // Compiles a block at current MIPS PC void Compile(u32 em_address) override; // Compiles a block at current MIPS PC
const u8 *DoJit(u32 em_address, JitBlock *b); const u8 *DoJit(u32 em_address, JitBlock *b);
const u8 *GetCrashHandler() const override { return crashHandler; }
bool CodeInRange(const u8 *ptr) const override { return IsInSpace(ptr); }
bool DescribeCodePtr(const u8 *ptr, std::string &name) override; bool DescribeCodePtr(const u8 *ptr, std::string &name) override;
void Comp_RunBlock(MIPSOpcode op) override; void Comp_RunBlock(MIPSOpcode op) override;
@ -325,6 +327,8 @@ private:
const u8 *endOfPregeneratedCode; const u8 *endOfPregeneratedCode;
const u8 *crashHandler;
friend class JitSafeMem; friend class JitSafeMem;
friend class JitSafeMemFuncs; friend class JitSafeMemFuncs;
}; };

View file

@ -28,8 +28,20 @@
#include "Common/MemoryUtil.h" #include "Common/MemoryUtil.h"
#include "Common/MemArena.h" #include "Common/MemArena.h"
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#if defined(PPSSPP_ARCH_AMD64) || defined(PPSSPP_ARCH_X86)
#include "Common/MachineContext.h" #include "Common/MachineContext.h"
#include "Common/x64Analyzer.h" #include "Common/x64Analyzer.h"
#elif defined(PPSSPP_ARCH_ARM64)
#include "Core/Util/DisArm64.h"
typedef sigcontext SContext;
#define CTX_PC pc
#elif defined(PPSSPP_ARCH_ARM)
#include "ext/disarm.h"
typedef sigcontext SContext;
#define CTX_PC arm_pc
#endif
#include "Core/MemMap.h" #include "Core/MemMap.h"
#include "Core/HDRemaster.h" #include "Core/HDRemaster.h"
@ -43,6 +55,8 @@
#include "Core/ConfigValues.h" #include "Core/ConfigValues.h"
#include "Core/HLE/ReplaceTables.h" #include "Core/HLE/ReplaceTables.h"
#include "Core/MIPS/JitCommon/JitBlockCache.h" #include "Core/MIPS/JitCommon/JitBlockCache.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "UI/OnScreenDisplay.h"
namespace Memory { namespace Memory {
@ -87,6 +101,8 @@ u32 g_PSPModel;
std::recursive_mutex g_shutdownLock; std::recursive_mutex g_shutdownLock;
static int64_t g_numReportedBadAccesses = 0;
// We don't declare the IO region in here since its handled by other means. // We don't declare the IO region in here since its handled by other means.
static MemoryView views[] = static MemoryView views[] =
{ {
@ -292,6 +308,8 @@ void Init() {
INFO_LOG(MEMMAP, "Memory system initialized. Base at %p (RAM at @ %p, uncached @ %p)", INFO_LOG(MEMMAP, "Memory system initialized. Base at %p (RAM at @ %p, uncached @ %p)",
base, m_pPhysicalRAM, m_pUncachedRAM); base, m_pPhysicalRAM, m_pUncachedRAM);
g_numReportedBadAccesses = 0;
} }
void Reinit() { void Reinit() {
@ -464,7 +482,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
const uint8_t *codePtr = (uint8_t *)(context->CTX_PC); const uint8_t *codePtr = (uint8_t *)(context->CTX_PC);
// TODO: Check that codePtr is within the current JIT space. // TODO: Check that codePtr is within the current JIT space.
bool inJitSpace = true; // MIPSComp::jit->IsInSpace(codePtr); bool inJitSpace = MIPSComp::jit->CodeInRange(codePtr);
if (!inJitSpace) { if (!inJitSpace) {
// This is a crash in non-jitted code. Not something we want to handle here, ignore. // This is a crash in non-jitted code. Not something we want to handle here, ignore.
return false; return false;
@ -486,32 +504,49 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) {
// OK, a guest executable did a bad access. Take care of it. // OK, a guest executable did a bad access. Take care of it.
uint32_t guestAddress = hostAddress - baseAddress; uint32_t guestAddress = hostAddress - baseAddress;
ERROR_LOG(SYSTEM, "Bad memory access detected and ignored: %08x (%p)", guestAddress, hostAddress);
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC // TODO: Share the struct between the various analyzers, that will allow us to share most of
// the implementations here.
#if defined(PPSSPP_ARCH_AMD64) || defined(PPSSPP_ARCH_X86) #if defined(PPSSPP_ARCH_AMD64) || defined(PPSSPP_ARCH_X86)
// X86, X86-64. Variable instruction size so need to analyze the mov instruction in detail.
InstructionInfo info; // To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
DisassembleMov(codePtr, info); LSInstructionInfo info;
X86AnalyzeMOV(codePtr, info);
#elif defined(PPSSPP_ARCH_ARM64)
uint32_t word;
memcpy(&word, codePtr, 4);
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
Arm64LSInstructionInfo info;
Arm64AnalyzeLoadStore((uint64_t)codePtr, word, &info);
#elif defined(PPSSPP_ARCH_ARM)
uint32_t word;
memcpy(&word, codePtr, 4);
// To ignore the access, we need to disassemble the instruction and modify context->CTX_PC
ArmLSInstructionInfo info;
ArmAnalyzeLoadStore((uint32_t)codePtr, word, &info);
#endif
if (g_Config.bIgnoreBadMemAccess) { if (g_Config.bIgnoreBadMemAccess) {
if (!info.isMemoryWrite) { if (!info.isMemoryWrite) {
// Must have been a read. Fill the register with 0. // It was a read. Fill the destination register with 0.
// TODO // TODO
} }
// Move on to the next instruction. // Move on to the next instruction.
context->CTX_PC += info.instructionSize; context->CTX_PC += info.instructionSize;
// Fall through to logging.
} else { } else {
// Jump to a crash handler. // Jump to a crash handler that will exit the game.
// TODO context->CTX_PC = (uintptr_t)MIPSComp::jit->GetCrashHandler();
context->CTX_PC += info.instructionSize; ERROR_LOG(SYSTEM, "Bad memory access detected! %08x (%p) Stopping emulation.", guestAddress, (void *)hostAddress);
return true;
} }
#else g_numReportedBadAccesses++;
// ARM, ARM64 : All instructions are always 4 bytes in size. As an initial implementation, if (g_numReportedBadAccesses < 100) {
// let's just skip the offending instruction. ERROR_LOG(SYSTEM, "Bad memory access detected and ignored: %08x (%p)", guestAddress, (void *)hostAddress);
context->CTX_PC += 4; }
#endif
return true; return true;
} }

View file

@ -265,6 +265,41 @@ static void BranchExceptionAndSystem(uint32_t w, uint64_t addr, Instruction *ins
} }
} }
void Arm64AnalyzeLoadStore(uint64_t addr, uint32_t w, Arm64LSInstructionInfo *info) {
*info = {};
info->instructionSize = 4;
int id = (w >> 25) & 0xF;
switch (id) {
case 4: case 6: case 0xC: case 0xE:
info->isLoadOrStore = true;
break;
default:
ERROR_LOG(CPU, "Tried to disassemble %08x at %p as a load/store instruction", w, (void *)addr);
return; // not the expected instruction
}
info->size = w >> 30;
info->Rt = (w & 0x1F);
info->Rn = ((w >> 5) & 0x1F);
info->Rm = ((w >> 16) & 0x1F);
int opc = (w >> 22) & 0x3;
if (opc == 0 || opc == 2) {
info->isMemoryWrite = true;
}
if (((w >> 27) & 7) == 7) {
int V = (w >> 26) & 1;
if (V == 0) {
info->isIntegerLoadStore = true;
} else {
info->isFPLoadStore = true;
}
} else {
info->isPairLoadStore = true;
// TODO
}
}
static void LoadStore(uint32_t w, uint64_t addr, Instruction *instr) { static void LoadStore(uint32_t w, uint64_t addr, Instruction *instr) {
int size = w >> 30; int size = w >> 30;
int imm9 = SignExtend9((w >> 12) & 0x1FF); int imm9 = SignExtend9((w >> 12) & 0x1FF);

View file

@ -24,3 +24,24 @@ typedef bool (*SymbolCallback)(char *buffer, int bufsize, uint8_t *address);
void Arm64Dis(uint64_t addr, uint32_t w, char *output, int bufsize, bool includeWord, SymbolCallback symbolCallback = nullptr); void Arm64Dis(uint64_t addr, uint32_t w, char *output, int bufsize, bool includeWord, SymbolCallback symbolCallback = nullptr);
// Information about a load/store instruction.
struct Arm64LSInstructionInfo {
int instructionSize;
bool isLoadOrStore;
bool isIntegerLoadStore;
bool isFPLoadStore;
bool isPairLoadStore;
int size; // 0 = 8-bit, 1 = 16-bit, 2 = 32-bit, 3 = 64-bit
bool isMemoryWrite;
int Rt;
int Rn;
int Rm;
// TODO: more.
};
void Arm64AnalyzeLoadStore(uint64_t addr, uint32_t op, Arm64LSInstructionInfo *info);

View file

@ -72,6 +72,7 @@
#include "base/basictypes.h" #include "base/basictypes.h"
#include "Common/ArmEmitter.h" #include "Common/ArmEmitter.h"
#include "ext/disarm.h"
static const char *CCFlagsStr[] = { static const char *CCFlagsStr[] = {
"EQ", // Equal "EQ", // Equal
@ -759,30 +760,12 @@ static bool DisasmNeon(uint32_t op, char *text) {
return false; return false;
} }
void ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info) {
*info = {};
info->instructionSize = 4;
// TODO
}
typedef unsigned int word; typedef unsigned int word;

View file

@ -17,18 +17,34 @@
#pragma once #pragma once
#include <cstdint>
// This stuff only disassembles very old ARM but should be sufficient // This stuff used to only disassemble very old ARM but has now
// for the basics except for MOVW/MOVT. // been extended to support most (but not all) modern instructions, including NEON.
// Disarm itself has the license you can see in the cpp file. // Disarm itself has the license you can see in the cpp file.
// I'm not entirely sure it's 100% gpl compatible but it's nearly // I'm not entirely sure it's 100% gpl compatible but it's nearly
// public domain so meh. // public domain so meh.
// The only changes I've done is C++ compat and replaced the main
// program with this function.
const char *ArmRegName(int r); const char *ArmRegName(int r);
void ArmDis(unsigned int addr, unsigned int w, char *output, int bufsize, bool includeWord); void ArmDis(unsigned int addr, unsigned int w, char *output, int bufsize, bool includeWord);
// Information about a load/store instruction.
struct ArmLSInstructionInfo {
int instructionSize;
bool isIntegerLoadStore;
bool isFPLoadStore;
bool isMultiLoadStore;
int size; // 0 = 8-bit, 1 = 16-bit, 2 = 32-bit, 3 = 64-bit
bool isMemoryWrite;
int Rt;
int Rn;
int Rm;
// TODO: more.
};
void ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info);