From 7cd18122ad881e17a64d9ef63ceb2c56899e21b6 Mon Sep 17 00:00:00 2001 From: John Date: Mon, 5 Feb 2024 06:17:42 -0600 Subject: [PATCH] Add DbgEng TTD API and UI --- api/debuggerapi.h | 11 + api/debuggercontroller.cpp | 52 +++++ api/ffi.h | 19 +- api/python/debuggercontroller.py | 133 ++++++++++- core/adapters/dbgengttdadapter.cpp | 57 +++++ core/adapters/dbgengttdadapter.h | 7 + core/debugadapter.cpp | 23 ++ core/debugadapter.h | 9 + core/debuggercontroller.cpp | 364 ++++++++++++++++++++++++++++- core/debuggercontroller.h | 15 ++ core/ffi.cpp | 52 +++++ ui/controlswidget.cpp | 83 +++++++ ui/controlswidget.h | 9 + ui/ui.cpp | 74 ++++++ 14 files changed, 905 insertions(+), 3 deletions(-) diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 6e1fcc2..9ca912c 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -505,17 +505,26 @@ namespace BinaryNinjaDebuggerAPI { DebugStopReason AttachAndWait(); bool Go(); + bool GoReverse(); bool StepInto(BNFunctionGraphType il = NormalFunctionGraph); + bool StepIntoReverse(BNFunctionGraphType il = NormalFunctionGraph); bool StepOver(BNFunctionGraphType il = NormalFunctionGraph); + bool StepOverReverse(BNFunctionGraphType il = NormalFunctionGraph); bool StepReturn(); + bool StepReturnReverse(); + bool RunTo(uint64_t remoteAddresses); bool RunTo(const std::vector& remoteAddresses); void Pause(); DebugStopReason GoAndWait(); + DebugStopReason GoReverseAndWait(); DebugStopReason StepIntoAndWait(BNFunctionGraphType il = NormalFunctionGraph); + DebugStopReason StepIntoReverseAndWait(BNFunctionGraphType il = NormalFunctionGraph); DebugStopReason StepOverAndWait(BNFunctionGraphType il = NormalFunctionGraph); + DebugStopReason StepOverReverseAndWait(BNFunctionGraphType il); DebugStopReason StepReturnAndWait(); + DebugStopReason StepReturnReverseAndWait(); DebugStopReason RunToAndWait(uint64_t remoteAddresses); DebugStopReason RunToAndWait(const std::vector& remoteAddresses); DebugStopReason PauseAndWait(); @@ -562,6 +571,7 @@ namespace BinaryNinjaDebuggerAPI { size_t RegisterEventCallback( std::function callback, const std::string& name = ""); + void RecordTrace(); static void DebuggerEventCallback(void* ctxt, BNDebuggerEvent* view); void RemoveEventCallback(size_t index); @@ -580,6 +590,7 @@ namespace BinaryNinjaDebuggerAPI { std::string GetAddressInformation(uint64_t address); bool IsFirstLaunch(); + bool IsTTD(); void PostDebuggerEvent(const DebuggerEvent& event); }; diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index a439eeb..b8d26bd 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -299,6 +299,11 @@ bool DebuggerController::Go() return BNDebuggerGo(m_object); } +bool DebuggerController::GoReverse() +{ + return BNDebuggerGoReverse(m_object); +} + DebugStopReason DebuggerController::GoAndWait() { @@ -306,6 +311,12 @@ DebugStopReason DebuggerController::GoAndWait() } +DebugStopReason DebuggerController::GoReverseAndWait() +{ + return BNDebuggerGoReverseAndWait(m_object); +} + + bool DebuggerController::Launch() { return BNDebuggerLaunch(m_object); @@ -404,18 +415,36 @@ bool DebuggerController::StepInto(BNFunctionGraphType il) } +bool DebuggerController::StepIntoReverse(BNFunctionGraphType il) +{ + return BNDebuggerStepIntoReverse(m_object, il); +} + + bool DebuggerController::StepOver(BNFunctionGraphType il) { return BNDebuggerStepOver(m_object, il); } +bool DebuggerController::StepOverReverse(BNFunctionGraphType il) +{ + return BNDebuggerStepOverReverse(m_object, il); +} + + bool DebuggerController::StepReturn() { return BNDebuggerStepReturn(m_object); } +bool DebuggerController::StepReturnReverse() +{ + return BNDebuggerStepReturnReverse(m_object); +} + + bool DebuggerController::RunTo(uint64_t remoteAddresses) { return RunTo(std::vector {remoteAddresses}); @@ -434,17 +463,34 @@ DebugStopReason DebuggerController::StepIntoAndWait(BNFunctionGraphType il) } +DebugStopReason DebuggerController::StepIntoReverseAndWait(BNFunctionGraphType il) +{ + return BNDebuggerStepIntoReverseAndWait(m_object, il); +} + + DebugStopReason DebuggerController::StepOverAndWait(BNFunctionGraphType il) { return BNDebuggerStepOverAndWait(m_object, il); } +DebugStopReason DebuggerController::StepOverReverseAndWait(BNFunctionGraphType il) +{ + return BNDebuggerStepOverReverseAndWait(m_object, il); +} + + DebugStopReason DebuggerController::StepReturnAndWait() { return BNDebuggerStepReturnAndWait(m_object); } +DebugStopReason DebuggerController::StepReturnReverseAndWait() +{ + return BNDebuggerStepReturnReverseAndWait(m_object); +} + DebugStopReason DebuggerController::RunToAndWait(uint64_t remoteAddresses) { @@ -829,6 +875,12 @@ bool DebuggerController::IsFirstLaunch() } +bool DebuggerController::IsTTD() +{ + return BNDebuggerIsTTD(m_object); +} + + void DebuggerController::PostDebuggerEvent(const DebuggerEvent &event) { BNDebuggerEvent* evt = new BNDebuggerEvent; diff --git a/api/ffi.h b/api/ffi.h index 151edf5..de00d43 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -321,7 +321,11 @@ extern "C" DebugAdapterStepReturn, DebugAdapterPause, DebugAdapterQuit, - DebugAdapterDetach + DebugAdapterDetach, + DebugAdapterStepIntoReverse, + DebugAdapterStepOverReverse, + DebugAdapterGoReverse, + DebugAdapterStepReturnReverse, } BNDebuggerAdapterOperation; @@ -395,19 +399,31 @@ extern "C" DEBUGGER_FFI_API BNDebugStopReason BNDebuggerAttachAndWait(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerGo(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerGoReverse(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerStepInto(BNDebuggerController* controller, BNFunctionGraphType il); + DEBUGGER_FFI_API bool BNDebuggerStepIntoReverse(BNDebuggerController* controller, BNFunctionGraphType il); DEBUGGER_FFI_API bool BNDebuggerStepOver(BNDebuggerController* controller, BNFunctionGraphType il); + DEBUGGER_FFI_API bool BNDebuggerStepOverReverse(BNDebuggerController* controller, BNFunctionGraphType il); DEBUGGER_FFI_API bool BNDebuggerStepReturn(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerStepReturnReverse(BNDebuggerController* controller); DEBUGGER_FFI_API bool BNDebuggerRunTo( BNDebuggerController* controller, const uint64_t* remoteAddresses, size_t count); DEBUGGER_FFI_API void BNDebuggerPause(BNDebuggerController* controller); DEBUGGER_FFI_API BNDebugStopReason BNDebuggerGoAndWait(BNDebuggerController* controller); + DEBUGGER_FFI_API BNDebugStopReason BNDebuggerGoReverseAndWait(BNDebuggerController* controller); DEBUGGER_FFI_API BNDebugStopReason BNDebuggerStepIntoAndWait( BNDebuggerController* controller, BNFunctionGraphType il); + DEBUGGER_FFI_API BNDebugStopReason BNDebuggerStepIntoReverseAndWait( + BNDebuggerController* controller, BNFunctionGraphType il); + DEBUGGER_FFI_API BNDebugStopReason BNDebuggerStepOverAndWait( BNDebuggerController* controller, BNFunctionGraphType il); + DEBUGGER_FFI_API BNDebugStopReason BNDebuggerStepOverReverseAndWait( + BNDebuggerController* controller, BNFunctionGraphType il); DEBUGGER_FFI_API BNDebugStopReason BNDebuggerStepReturnAndWait(BNDebuggerController* controller); + DEBUGGER_FFI_API BNDebugStopReason BNDebuggerStepReturnReverseAndWait(BNDebuggerController* controller); DEBUGGER_FFI_API BNDebugStopReason BNDebuggerRunToAndWait( BNDebuggerController* controller, const uint64_t* remoteAddresses, size_t count); DEBUGGER_FFI_API BNDebugStopReason BNDebuggerPauseAndWait(BNDebuggerController* controller); @@ -471,6 +487,7 @@ extern "C" DEBUGGER_FFI_API char* BNDebuggerGetAddressInformation(BNDebuggerController* controller, uint64_t address); DEBUGGER_FFI_API bool BNDebuggerIsFirstLaunch(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerIsTTD(BNDebuggerController* controller); DEBUGGER_FFI_API void BNDebuggerPostDebuggerEvent(BNDebuggerController* controller, BNDebuggerEvent* event); diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index d499dfc..e7f4a0f 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -829,6 +829,17 @@ def go(self) -> bool: """ return dbgcore.BNDebuggerGo(self.handle) + # TODO: Improve the documentation on reverse-execution functions + def go_reverse(self) -> bool: + """ + Resume the target in reverse. + + The call is asynchronous and returns before the target stops. + + :return: the reason for the stop + """ + return dbgcore.BNDebuggerGoReverse(self.handle) + def step_into(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGraphType.NormalFunctionGraph) -> bool: """ Perform a step into on the target. @@ -850,6 +861,27 @@ def step_into(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGrap """ return dbgcore.BNDebuggerStepInto(self.handle, il) + def step_into_reverse(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGraphType.NormalFunctionGraph) -> bool: + """ + Perform a step into on the target in reverse. + + When the previous instruction is not a call, step backwards. When the previous instruction is a call, + follow the call into the last instruction of the function. + + The operation can be performed on an IL level specified by the ``il`` parameter, which then either executes the + next IL instruction, or follow into the IL function. Note, the underlying operation is still performed at the + disassembly level because that is the only thing a debugger understands. The high-level operations are simulated + on top of the disassembly and analysis. + + Some limitations are known with stepping into on IL. + + The call is asynchronous and returns before the target stops. + + :param il: optional IL level to perform the operation at + :return: the reason for the stop + """ + return dbgcore.BNDebuggerStepIntoReverse(self.handle, il) + def step_over(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGraphType.NormalFunctionGraph) -> bool: """ Perform a step over on the target. @@ -871,6 +903,27 @@ def step_over(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGrap """ return dbgcore.BNDebuggerStepOver(self.handle, il) + def step_over_reverse(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGraphType.NormalFunctionGraph) -> bool: + """ + Perform a step over on the target in reverse. + + When the previous instruction is not a call, step backwards. When the previous instruction is a call, + step back over the call. + + The operation can be performed on an IL level specified by the ``il`` parameter, which then either executes the + next IL instruction, or completes the IL function. Note, the underlying operation is still performed at the + disassembly level because that is the only thing a debugger understands. The high-level operations are simulated + on top of the disassembly and analysis. + + Some limitations are known with stepping over on IL. + + The call is asynchronous and returns before the target stops. + + :param il: optional IL level to perform the operation at + :return: the reason for the stop + """ + return dbgcore.BNDebuggerStepOverReverse(self.handle, il) + def step_return(self) -> bool: """ Perform a step return on the target. @@ -890,6 +943,26 @@ def step_return(self) -> bool: :return: the reason for the stop """ return dbgcore.BNDebuggerStepReturn(self.handle) + + def step_return_reverse(self) -> bool: + """ + Perform a step return on the target in reverse. + + Step return reverses the execution of the current function and returns to its caller. This operation relies + heavily on stack frame analysis, which is done by the DebugAdapters. + + If a DebugAdapter does not support (i.e., overload) this function, a fallback handling is provided by the + DebuggerController. It checks the MLIL function and put breakpoints on all returning instructions and then resume + the target. By the time it breaks, the target is about to return from the current function. + + This fallback behavior is slightly different from that offered by the LLDB and DbgEng adapter, which returns + from the current function and break afterwards. + + The call is asynchronous and returns before the target stops. + + :return: the reason for the stop + """ + return dbgcore.BNDebuggerStepReturnReverse(self.handle) def run_to(self, address) -> bool: """ @@ -925,10 +998,20 @@ def go_and_wait(self) -> DebugStopReason: """ return DebugStopReason(dbgcore.BNDebuggerGoAndWait(self.handle)) + def go_reverse_and_wait(self) -> DebugStopReason: + """ + Resume the target in reverse. + + The call is blocking and only returns when the target stops. + + :return: the reason for the stop + """ + return DebugStopReason(dbgcore.BNDebuggerGoReverseAndWait(self.handle)) + def step_into_and_wait(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGraphType.NormalFunctionGraph) -> DebugStopReason: """ - Perform a step into on the target. + Perform a step into on the target in reverse. When the next instruction is not a call, execute the next instruction. When the next instruction is a call, follow the call the get into the first instruction of the call. @@ -947,6 +1030,28 @@ def step_into_and_wait(self, il: binaryninja.FunctionGraphType = """ return DebugStopReason(dbgcore.BNDebuggerStepIntoAndWait(self.handle, il)) + + def step_into_reverse_and_wait(self, il: binaryninja.FunctionGraphType = + binaryninja.FunctionGraphType.NormalFunctionGraph) -> DebugStopReason: + """ + Perform a reverse step into on the target. + + When the previous instruction is not a call, reverse the program to the previous state. When the previous instruction is a call, + follow the call to get into the last instruction of the call. + + The operation can be performed on an IL level specified by the ``il`` parameter, which then either reverses the current IL instruction, or follow into the previous IL function. Note, the underlying operation is still performed at the + disassembly level because that is the only thing a debugger understands. The high-level operations are simulated + on top of the disassembly and analysis. + + Some limitations are known with stepping into on IL. + + The call is blocking and only returns when the target stops. + + :param il: optional IL level to perform the operation at + :return: the reason for the stop + """ + return DebugStopReason(dbgcore.BNDebuggerStepIntoReverseAndWait(self.handle, il)) + def step_over_and_wait(self, il: binaryninja.FunctionGraphType = binaryninja.FunctionGraphType.NormalFunctionGraph) -> DebugStopReason: """ @@ -969,6 +1074,28 @@ def step_over_and_wait(self, il: binaryninja.FunctionGraphType = """ return DebugStopReason(dbgcore.BNDebuggerStepOverAndWait(self.handle, il)) + def step_over_reverse_and_wait(self, il: binaryninja.FunctionGraphType = + binaryninja.FunctionGraphType.NormalFunctionGraph) -> DebugStopReason: + """ + Perform a step over on the target in reverse. + + When the next instruction is not a call, execute the next instruction. When the next instruction is a call, + complete the execution of the function and break at next instruction. + + The operation can be performed on an IL level specified by the ``il`` parameter, which then either executes the + next IL instruction, or completes the IL function. Note, the underlying operation is still performed at the + disassembly level because that is the only thing a debugger understands. The high-level operations are simulated + on top of the disassembly and analysis. + + Some limitations are known with stepping over on IL. + + The call is blocking and only returns when the target stops. + + :param il: optional IL level to perform the operation at + :return: the reason for the stop + """ + return DebugStopReason(dbgcore.BNDebuggerStepOverReverseAndWait(self.handle, il)) + def step_return_and_wait(self) -> DebugStopReason: """ Perform a step return on the target. @@ -1392,6 +1519,10 @@ def get_addr_info(self, addr: int): def is_first_launch(self): return dbgcore.BNDebuggerIsFirstLaunch(self.handle) + @property + def is_ttd(self): + return dbgcore.BNDebuggerIsTTD(self.handle) + def __del__(self): if dbgcore is not None: dbgcore.BNDebuggerFreeController(self.handle) diff --git a/core/adapters/dbgengttdadapter.cpp b/core/adapters/dbgengttdadapter.cpp index 8a4f878..d3ba5e6 100644 --- a/core/adapters/dbgengttdadapter.cpp +++ b/core/adapters/dbgengttdadapter.cpp @@ -187,6 +187,63 @@ void DbgEngTTDAdapter::Reset() } +bool DbgEngTTDAdapter::GoReverse() +{ + if (ExecStatus() != DEBUG_STATUS_BREAK) + return false; + + m_lastOperationIsStepInto = false; + if (this->m_debugControl->SetExecutionStatus(DEBUG_STATUS_REVERSE_GO) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + + +bool DbgEngTTDAdapter::StepIntoReverse() +{ + if (ExecStatus() != DEBUG_STATUS_BREAK) + return false; + + m_lastOperationIsStepInto = true; + if (this->m_debugControl->SetExecutionStatus(DEBUG_STATUS_REVERSE_STEP_INTO) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + + +bool DbgEngTTDAdapter::StepOverReverse() +{ + if (ExecStatus() != DEBUG_STATUS_BREAK) + return false; + + m_lastOperationIsStepInto = true; + if (this->m_debugControl->SetExecutionStatus(DEBUG_STATUS_REVERSE_STEP_OVER) != S_OK) + return false; + + m_debugClient->ExitDispatch(reinterpret_cast(m_debugClient)); + return true; +} + +bool DbgEngTTDAdapter::StepReturnReverse() +{ + if (ExecStatus() != DEBUG_STATUS_BREAK) + return false; + + InvokeBackendCommand("g-u"); + return true; +} + + +bool DbgEngTTDAdapter::SupportFeature(DebugAdapterCapacity feature) +{ + return DbgEngAdapter::SupportFeature(feature) || feature == DebugAdapterSupportTTD; +} + + bool DbgEngTTDAdapter::Quit() { m_aboutToBeKilled = true; diff --git a/core/adapters/dbgengttdadapter.h b/core/adapters/dbgengttdadapter.h index 8f8450f..3889fa7 100644 --- a/core/adapters/dbgengttdadapter.h +++ b/core/adapters/dbgengttdadapter.h @@ -31,6 +31,13 @@ namespace BinaryNinjaDebugger { bool Start() override; void Reset() override; + bool GoReverse() override; + bool StepIntoReverse() override; + bool StepOverReverse() override; + bool StepReturnReverse() override; + + bool SupportFeature(DebugAdapterCapacity feature) override; + bool Quit() override; }; diff --git a/core/debugadapter.cpp b/core/debugadapter.cpp index 95e2c7f..e1827c4 100644 --- a/core/debugadapter.cpp +++ b/core/debugadapter.cpp @@ -93,11 +93,34 @@ bool DebugModule::IsSameBaseModule(const std::string& module1, const std::string } +bool DebugAdapter::GoReverse() +{ + return false; +} + + +bool DebugAdapter::StepIntoReverse() +{ + return false; +} + + +bool DebugAdapter::StepOverReverse() +{ + return false; +} + + bool DebugAdapter::StepReturn() { return false; } +bool DebugAdapter::StepReturnReverse() +{ + return false; +} + uint64_t DebugAdapter::GetStackPointer() { diff --git a/core/debugadapter.h b/core/debugadapter.h index 7341c79..ccfdfcf 100644 --- a/core/debugadapter.h +++ b/core/debugadapter.h @@ -54,6 +54,7 @@ namespace BinaryNinjaDebugger { DebugAdapterSupportStepOver, DebugAdapterSupportModules, DebugAdapterSupportThreads, + DebugAdapterSupportTTD, }; @@ -280,13 +281,21 @@ namespace BinaryNinjaDebugger { virtual bool Go() = 0; + virtual bool GoReverse(); + virtual bool StepInto() = 0; + + virtual bool StepIntoReverse(); virtual bool StepOver() = 0; + + virtual bool StepOverReverse(); // virtual bool RunTo(std::uintptr_t address) = 0; virtual bool StepReturn(); + virtual bool StepReturnReverse(); + virtual std::string InvokeBackendCommand(const std::string& command) = 0; virtual uint64_t GetInstructionOffset() = 0; diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index 562dc80..a56670c 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -336,6 +336,17 @@ bool DebuggerController::Go() return true; } +bool DebuggerController::GoReverse() +{ + // This is an API function of the debugger. We only do these checks at the API level. + if (!CanResumeTarget()) + return false; + + std::thread([&]() { GoReverseAndWait(); }).detach(); + + return true; +} + DebugStopReason DebuggerController::GoAndWait() { @@ -350,6 +361,19 @@ DebugStopReason DebuggerController::GoAndWait() return reason; } +DebugStopReason DebuggerController::GoReverseAndWait() +{ + if (!m_targetControlMutex.try_lock()) + return InternalError; + + auto reason = GoReverseAndWaitInternal(); + if (!m_userRequestedBreak && (reason != ProcessExited)) + NotifyStopped(reason); + + m_targetControlMutex.unlock(); + return reason; +} + DebugStopReason DebuggerController::StepIntoIL(BNFunctionGraphType il) { @@ -456,6 +480,118 @@ DebugStopReason DebuggerController::StepIntoIL(BNFunctionGraphType il) } +DebugStopReason DebuggerController::StepIntoReverseIL(BNFunctionGraphType il) +{ + switch (il) + { + case NormalFunctionGraph: + { + return StepIntoReverseAndWaitInternal(); + } + case LowLevelILFunctionGraph: + { + // TODO: This might cause infinite loop + while (true) + { + DebugStopReason reason = StepIntoReverseAndWaitInternal(); + if (!ExpectSingleStep(reason)) + return reason; + + uint64_t newRemoteRip = m_state->IP(); + std::vector functions = m_liveView->GetAnalysisFunctionsContainingAddress(newRemoteRip); + if (functions.empty()) + return SingleStep; + + for (FunctionRef& func : functions) + { + LowLevelILFunctionRef llil = func->GetLowLevelILIfAvailable(); + if (!llil) + return SingleStep; + + size_t start = llil->GetInstructionStart(m_liveView->GetDefaultArchitecture(), newRemoteRip); + if (start < llil->GetInstructionCount()) + { + if (llil->GetInstruction(start).address == newRemoteRip) + return SingleStep; + } + } + } + break; + } + case MediumLevelILFunctionGraph: + { + // TODO: This might cause infinite loop + while (true) + { + DebugStopReason reason = StepIntoReverseAndWaitInternal(); + if (!ExpectSingleStep(reason)) + return reason; + + uint64_t newRemoteRip = m_state->IP(); + std::vector functions = m_liveView->GetAnalysisFunctionsContainingAddress(newRemoteRip); + if (functions.empty()) + return SingleStep; + + for (FunctionRef& func : functions) + { + MediumLevelILFunctionRef mlil = func->GetMediumLevelILIfAvailable(); + if (!mlil) + return SingleStep; + + size_t start = mlil->GetInstructionStart(m_liveView->GetDefaultArchitecture(), newRemoteRip); + if (start < mlil->GetInstructionCount()) + { + if (mlil->GetInstruction(start).address == newRemoteRip) + return SingleStep; + } + } + } + break; + } + case HighLevelILFunctionGraph: + case HighLevelLanguageRepresentationFunctionGraph: + { + // TODO: This might cause infinite loop + while (true) + { + DebugStopReason reason = StepIntoReverseAndWaitInternal(); + if (!ExpectSingleStep(reason)) + return reason; + + uint64_t newRemoteRip = m_state->IP(); + std::vector functions = m_liveView->GetAnalysisFunctionsContainingAddress(newRemoteRip); + if (functions.empty()) + return SingleStep; + + for (FunctionRef& func : functions) + { + HighLevelILFunctionRef hlil = func->GetHighLevelILIfAvailable(); + if (!hlil) + return SingleStep; + + for (size_t i = 0; i < hlil->GetInstructionCount(); i++) + { + if (hlil->GetInstruction(i).address == newRemoteRip) + return SingleStep; + } + } + } + break; + } + default: + LogWarn("step into unimplemented in the current il type"); + return InvalidStatusOrOperation; + } +} + + +DebugStopReason DebuggerController::StepIntoReverseAndWaitInternal() +{ + m_userRequestedBreak = false; + // TODO: check if StepInto() succeeds + return ExecuteAdapterAndWait(DebugAdapterStepIntoReverse); +} + bool DebuggerController::StepInto(BNFunctionGraphType il) { if (!CanResumeTarget()) @@ -466,6 +602,28 @@ bool DebuggerController::StepInto(BNFunctionGraphType il) return true; } +bool DebuggerController::StepIntoReverse(BNFunctionGraphType il) +{ + if (!CanResumeTarget()) + return false; + + std::thread([&, il]() { StepIntoReverseAndWait(il); }).detach(); + + return true; +} + +DebugStopReason DebuggerController::StepIntoReverseAndWait(BNFunctionGraphType il) +{ + if (!m_targetControlMutex.try_lock()) + return InternalError; + + auto reason = StepIntoReverseIL(il); + if (!m_userRequestedBreak && (reason != ProcessExited)) + NotifyStopped(reason); + + m_targetControlMutex.unlock(); + return reason; +} DebugStopReason DebuggerController::StepIntoAndWait(BNFunctionGraphType il) { @@ -480,7 +638,6 @@ DebugStopReason DebuggerController::StepIntoAndWait(BNFunctionGraphType il) return reason; } - DebugStopReason DebuggerController::StepOverIL(BNFunctionGraphType il) { switch (il) @@ -584,6 +741,109 @@ DebugStopReason DebuggerController::StepOverIL(BNFunctionGraphType il) } } +DebugStopReason DebuggerController::StepOverReverseIL(BNFunctionGraphType il) +{ + switch (il) + { + case NormalFunctionGraph: + { + return StepOverReverseAndWaitInternal(); + } + case LowLevelILFunctionGraph: + { + // TODO: This might cause infinite loop + while (true) + { + DebugStopReason reason = StepOverReverseAndWaitInternal(); + if (!ExpectSingleStep(reason)) + return reason; + + uint64_t newRemoteRip = m_state->IP(); + std::vector functions = m_liveView->GetAnalysisFunctionsContainingAddress(newRemoteRip); + if (functions.empty()) + return SingleStep; + + for (FunctionRef& func : functions) + { + LowLevelILFunctionRef llil = func->GetLowLevelILIfAvailable(); + if (!llil) + return SingleStep; + + size_t start = llil->GetInstructionStart(m_liveView->GetDefaultArchitecture(), newRemoteRip); + if (start < llil->GetInstructionCount()) + { + if (llil->GetInstruction(start).address == newRemoteRip) + return SingleStep; + } + } + } + break; + } + case MediumLevelILFunctionGraph: + { + // TODO: This might cause infinite loop + while (true) + { + DebugStopReason reason = StepOverReverseAndWaitInternal(); + if (!ExpectSingleStep(reason)) + return reason; + uint64_t newRemoteRip = m_state->IP(); + std::vector functions = m_liveView->GetAnalysisFunctionsContainingAddress(newRemoteRip); + if (functions.empty()) + return SingleStep; + + for (FunctionRef& func : functions) + { + MediumLevelILFunctionRef mlil = func->GetMediumLevelILIfAvailable(); + if (!mlil) + return SingleStep; + + size_t start = mlil->GetInstructionStart(m_liveView->GetDefaultArchitecture(), newRemoteRip); + if (start < mlil->GetInstructionCount()) + { + if (mlil->GetInstruction(start).address == newRemoteRip) + return SingleStep; + } + } + } + break; + } + case HighLevelILFunctionGraph: + case HighLevelLanguageRepresentationFunctionGraph: + { + // TODO: This might cause infinite loop + while (true) + { + DebugStopReason reason = StepOverReverseAndWaitInternal(); + if (!ExpectSingleStep(reason)) + return reason; + + uint64_t newRemoteRip = m_state->IP(); + std::vector functions = m_liveView->GetAnalysisFunctionsContainingAddress(newRemoteRip); + if (functions.empty()) + return SingleStep; + + for (FunctionRef& func : functions) + { + HighLevelILFunctionRef hlil = func->GetHighLevelILIfAvailable(); + if (!hlil) + return SingleStep; + + for (size_t i = 0; i < hlil->GetInstructionCount(); i++) + { + if (hlil->GetInstruction(i).address == newRemoteRip) + return SingleStep; + } + } + } + break; + } + default: + LogWarn("reverse step over unimplemented in the current il type"); + return InvalidStatusOrOperation; + } +} + bool DebuggerController::StepOver(BNFunctionGraphType il) { @@ -596,6 +856,17 @@ bool DebuggerController::StepOver(BNFunctionGraphType il) } +bool DebuggerController::StepOverReverse(BNFunctionGraphType il) +{ + if (!CanResumeTarget()) + return false; + + std::thread([&, il]() { StepOverReverseAndWait(il); }).detach(); + + return true; +} + + DebugStopReason DebuggerController::StepOverAndWait(BNFunctionGraphType il) { if (!m_targetControlMutex.try_lock()) @@ -610,6 +881,20 @@ DebugStopReason DebuggerController::StepOverAndWait(BNFunctionGraphType il) } +DebugStopReason DebuggerController::StepOverReverseAndWait(BNFunctionGraphType il) +{ + if (!m_targetControlMutex.try_lock()) + return InternalError; + + auto reason = StepOverReverseIL(il); + if (!m_userRequestedBreak && (reason != ProcessExited)) + NotifyStopped(reason); + + m_targetControlMutex.unlock(); + return reason; +} + + DebugStopReason DebuggerController::EmulateStepReturnAndWait() { uint64_t address = m_state->IP(); @@ -647,6 +932,17 @@ DebugStopReason DebuggerController::StepReturnAndWaitInternal() } +DebugStopReason DebuggerController::StepReturnReverseAndWaitInternal() +{ + m_userRequestedBreak = false; + + if (true /* StepReturnReverseAvailable() */) + { + return ExecuteAdapterAndWait(DebugAdapterStepReturnReverse); + } +} + + bool DebuggerController::StepReturn() { if (!CanResumeTarget()) @@ -658,6 +954,17 @@ bool DebuggerController::StepReturn() } +bool DebuggerController::StepReturnReverse() +{ + if (!CanResumeTarget()) + return false; + + std::thread([&]() { StepReturnReverseAndWait(); }).detach(); + + return true; +} + + DebugStopReason DebuggerController::StepReturnAndWait() { if (!m_targetControlMutex.try_lock()) @@ -672,6 +979,20 @@ DebugStopReason DebuggerController::StepReturnAndWait() } +DebugStopReason DebuggerController::StepReturnReverseAndWait() +{ + if (!m_targetControlMutex.try_lock()) + return InternalError; + + auto reason = StepReturnReverseAndWaitInternal(); + if (!m_userRequestedBreak && (reason != ProcessExited)) + NotifyStopped(reason); + + m_targetControlMutex.unlock(); + return reason; +} + + DebugStopReason DebuggerController::RunToAndWaitInternal(const std::vector& remoteAddresses) { m_userRequestedBreak = false; @@ -979,6 +1300,12 @@ DebugStopReason DebuggerController::GoAndWaitInternal() return ExecuteAdapterAndWait(DebugAdapterGo); } +DebugStopReason DebuggerController::GoReverseAndWaitInternal() +{ + m_userRequestedBreak = false; + return ExecuteAdapterAndWait(DebugAdapterGoReverse); +} + DebugStopReason DebuggerController::StepIntoAndWaitInternal() { @@ -1045,6 +1372,21 @@ DebugStopReason DebuggerController::StepOverAndWaitInternal() } } +DebugStopReason DebuggerController::StepOverReverseAndWaitInternal() +{ + m_userRequestedBreak = false; + + if (true /* StepOverAvailable() */) + { + return ExecuteAdapterAndWait(DebugAdapterStepOverReverse); + } + else + { + // Emulate a step over + return EmulateStepOverAndWait(); + } +} + void DebuggerController::LaunchOrConnect() { @@ -1871,15 +2213,27 @@ DebugStopReason DebuggerController::ExecuteAdapterAndWait(const DebugAdapterOper case DebugAdapterGo: resumeOK = m_adapter->Go(); break; + case DebugAdapterGoReverse: + resumeOK = m_adapter->GoReverse(); + break; case DebugAdapterStepInto: resumeOK = m_adapter->StepInto(); break; + case DebugAdapterStepIntoReverse: + resumeOK = m_adapter->StepIntoReverse(); + break; case DebugAdapterStepOver: resumeOK = m_adapter->StepOver(); break; + case DebugAdapterStepOverReverse: + resumeOK = m_adapter->StepOverReverse(); + break; case DebugAdapterStepReturn: resumeOK = m_adapter->StepReturn(); break; + case DebugAdapterStepReturnReverse: + resumeOK = m_adapter->StepReturnReverse(); + break; case DebugAdapterPause: operationRequested = m_adapter->BreakInto(); break; @@ -2186,3 +2540,11 @@ bool DebuggerController::IsFirstLaunch() { return m_firstLaunch; } + + +bool DebuggerController::IsTTD() +{ + if(!m_adapter) + return false; + return m_adapter->SupportFeature(DebugAdapterSupportTTD); +} diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 94313ae..576a127 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -105,7 +105,9 @@ namespace BinaryNinjaDebugger { void SetLiveView(BinaryViewRef view) { m_liveView = view; } DebugStopReason StepIntoIL(BNFunctionGraphType il); + DebugStopReason StepIntoReverseIL(BNFunctionGraphType il); DebugStopReason StepOverIL(BNFunctionGraphType il); + DebugStopReason StepOverReverseIL(BNFunctionGraphType il); // Low-level internal synchronous APIs. They resume the target and wait for the adapter to stop. // They do NOT dispatch the debugger event callbacks. Higher-level APIs must take care of notifying @@ -115,11 +117,15 @@ namespace BinaryNinjaDebugger { DebugStopReason ConnectAndWaitInternal(); DebugStopReason PauseAndWaitInternal(); DebugStopReason GoAndWaitInternal(); + DebugStopReason GoReverseAndWaitInternal(); DebugStopReason StepIntoAndWaitInternal(); + DebugStopReason StepIntoReverseAndWaitInternal(); DebugStopReason EmulateStepOverAndWait(); DebugStopReason StepOverAndWaitInternal(); + DebugStopReason StepOverReverseAndWaitInternal(); DebugStopReason EmulateStepReturnAndWait(); DebugStopReason StepReturnAndWaitInternal(); + DebugStopReason StepReturnReverseAndWaitInternal(); DebugStopReason RunToAndWaitInternal(const std::vector &remoteAddresses); // Whether we can resume the execution of the target, including stepping. @@ -231,10 +237,14 @@ namespace BinaryNinjaDebugger { bool Attach(); void Detach(); bool Go(); + bool GoReverse(); void Quit(); bool StepInto(BNFunctionGraphType il = NormalFunctionGraph); + bool StepIntoReverse(BNFunctionGraphType il = NormalFunctionGraph); bool StepOver(BNFunctionGraphType il = NormalFunctionGraph); + bool StepOverReverse(BNFunctionGraphType il); bool StepReturn(); + bool StepReturnReverse(); bool RunTo(const std::vector& remoteAddresses); bool Pause(); @@ -243,11 +253,15 @@ namespace BinaryNinjaDebugger { // Synchronous APIs DebugStopReason LaunchAndWait(); DebugStopReason GoAndWait(); + DebugStopReason GoReverseAndWait(); DebugStopReason AttachAndWait(); DebugStopReason ConnectAndWait(); DebugStopReason StepIntoAndWait(BNFunctionGraphType il = NormalFunctionGraph); + DebugStopReason StepIntoReverseAndWait(BNFunctionGraphType il = NormalFunctionGraph); DebugStopReason StepOverAndWait(BNFunctionGraphType il = NormalFunctionGraph); + DebugStopReason StepOverReverseAndWait(BNFunctionGraphType il); DebugStopReason StepReturnAndWait(); + DebugStopReason StepReturnReverseAndWait(); DebugStopReason RunToAndWait(const std::vector& remoteAddresses); DebugStopReason PauseAndWait(); void DetachAndWait(); @@ -279,5 +293,6 @@ namespace BinaryNinjaDebugger { std::string GetAddressInformation(uint64_t address); bool IsFirstLaunch(); + bool IsTTD(); }; }; // namespace BinaryNinjaDebugger diff --git a/core/ffi.cpp b/core/ffi.cpp index aad31eb..bc6dfb6 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -477,11 +477,22 @@ bool BNDebuggerGo(BNDebuggerController* controller) } +bool BNDebuggerGoReverse(BNDebuggerController* controller) +{ + return controller->object->GoReverse(); +} + + bool BNDebuggerStepInto(BNDebuggerController* controller, BNFunctionGraphType il) { return controller->object->StepInto(il); } +bool BNDebuggerStepIntoReverse(BNDebuggerController* controller, BNFunctionGraphType il) + { + return controller->object->StepIntoReverse(il); + } + bool BNDebuggerStepOver(BNDebuggerController* controller, BNFunctionGraphType il) { @@ -489,12 +500,24 @@ bool BNDebuggerStepOver(BNDebuggerController* controller, BNFunctionGraphType il } +bool BNDebuggerStepOverReverse(BNDebuggerController* controller, BNFunctionGraphType il) +{ + return controller->object->StepOverReverse(il); +} + + bool BNDebuggerStepReturn(BNDebuggerController* controller) { return controller->object->StepReturn(); } +bool BNDebuggerStepReturnReverse(BNDebuggerController* controller) +{ + return controller->object->StepReturnReverse(); +} + + bool BNDebuggerRunTo(BNDebuggerController* controller, const uint64_t* remoteAddresses, size_t count) { std::vector addresses; @@ -513,17 +536,34 @@ BNDebugStopReason BNDebuggerGoAndWait(BNDebuggerController* controller) } +BNDebugStopReason BNDebuggerGoReverseAndWait(BNDebuggerController* controller) +{ + return controller->object->GoReverseAndWait(); +} + + BNDebugStopReason BNDebuggerStepIntoAndWait(BNDebuggerController* controller, BNFunctionGraphType il) { return controller->object->StepIntoAndWait(il); } +BNDebugStopReason BNDebuggerStepIntoReverseAndWait(BNDebuggerController* controller, BNFunctionGraphType il) +{ + return controller->object->StepIntoReverseAndWait(il); +} + + BNDebugStopReason BNDebuggerStepOverAndWait(BNDebuggerController* controller, BNFunctionGraphType il) { return controller->object->StepOverAndWait(il); } +BNDebugStopReason BNDebuggerStepOverReverseAndWait(BNDebuggerController* controller, BNFunctionGraphType il) +{ + return controller->object->StepOverReverseAndWait(il); +} + BNDebugStopReason BNDebuggerStepReturnAndWait(BNDebuggerController* controller) { @@ -531,6 +571,12 @@ BNDebugStopReason BNDebuggerStepReturnAndWait(BNDebuggerController* controller) } +BNDebugStopReason BNDebuggerStepReturnReverseAndWait(BNDebuggerController* controller) +{ + return controller->object->StepReturnReverseAndWait(); +} + + BNDebugStopReason BNDebuggerRunToAndWait( BNDebuggerController* controller, const uint64_t* remoteAddresses, size_t count) { @@ -980,6 +1026,12 @@ bool BNDebuggerIsFirstLaunch(BNDebuggerController* controller) } +bool BNDebuggerIsTTD(BNDebuggerController* controller) +{ + return controller->object->IsTTD(); +} + + void BNDebuggerPostDebuggerEvent(BNDebuggerController* controller, BNDebuggerEvent* event) { DebuggerEvent evt; diff --git a/ui/controlswidget.cpp b/ui/controlswidget.cpp index fe80360..75a2f28 100644 --- a/ui/controlswidget.cpp +++ b/ui/controlswidget.cpp @@ -105,6 +105,29 @@ DebugControlsWidget::DebugControlsWidget(QWidget* parent, const std::string name performSettings(); }); m_actionSettings->setToolTip(getToolTip("Debug Adapter Settings")); + + if(m_controller->IsTTD()) + addSeparator(); //TODO: IsTTD only updates when the adapter is connected. This leaves the separator in place when the adapter is disconnected. + + m_actionGoBack = addAction(getColoredIcon(":/debugger/resume", red), "Go Backwards", [this]() { + performGoReverse(); + }); + m_actionGoBack->setToolTip(getToolTip("Go Backwards")); + + m_actionStepIntoBack = addAction(getColoredIcon(":/debugger/step-into", red), "Step Into Backwards", [this]() { + performStepIntoReverse(); + }); + m_actionStepIntoBack->setToolTip(getToolTip("Step Into Backwards")); + + m_actionStepOverBack = addAction(getColoredIcon(":/debugger/step-over", red), "Step Over Backwards", [this]() { + performStepOverReverse(); + }); + m_actionStepOverBack->setToolTip(getToolTip("Step Over Backwards")); + + m_actionStepReturnBack = addAction(getColoredIcon(":/debugger/step-out", red), "Step Return Backwards", [this]() { + performStepReturnReverse(); + }); + m_actionStepReturnBack->setToolTip(getToolTip("Step Return Backwards")); updateButtons(); } @@ -272,6 +295,12 @@ void DebugControlsWidget::performResume() } +void DebugControlsWidget::performGoReverse() +{ + m_controller->GoReverse(); +} + + void DebugControlsWidget::performStepInto() { BNFunctionGraphType graphType = NormalFunctionGraph; @@ -286,6 +315,19 @@ void DebugControlsWidget::performStepInto() } +void DebugControlsWidget::performStepIntoReverse() +{ + BNFunctionGraphType graphType = NormalFunctionGraph; + UIContext* context = UIContext::contextForWidget(this); + if (context && context->getCurrentView()) + graphType = context->getCurrentView()->getILViewType(); + + if (graphType == InvalidILViewType) + graphType = NormalFunctionGraph; + m_controller->StepIntoReverse(graphType); +} + + void DebugControlsWidget::performStepOver() { BNFunctionGraphType graphType = NormalFunctionGraph; @@ -300,11 +342,29 @@ void DebugControlsWidget::performStepOver() } +void DebugControlsWidget::performStepOverReverse() +{ + BNFunctionGraphType graphType = NormalFunctionGraph; + UIContext* context = UIContext::contextForWidget(this); + if (context && context->getCurrentView()) + graphType = context->getCurrentView()->getILViewType(); + + if (graphType == InvalidILViewType) + graphType = NormalFunctionGraph; + + m_controller->StepOverReverse(graphType); +} + + void DebugControlsWidget::performStepReturn() { m_controller->StepReturn(); } +void DebugControlsWidget::performStepReturnReverse() +{ + m_controller->StepReturnReverse(); +} void DebugControlsWidget::performSettings() { @@ -363,6 +423,17 @@ void DebugControlsWidget::setSteppingEnabled(bool enabled) } +void DebugControlsWidget::setReverseSteppingEnabled(bool enabled) +{ + m_actionStepIntoBack->setEnabled(enabled); + m_actionStepIntoBack->setVisible(enabled); + m_actionStepOverBack->setEnabled(enabled); + m_actionStepOverBack->setVisible(enabled); + m_actionStepReturnBack->setEnabled(enabled); + m_actionStepReturnBack->setVisible(enabled); +} + + void DebugControlsWidget::updateButtons() { this->setIconSize(QSize(20, 20)); @@ -375,35 +446,47 @@ void DebugControlsWidget::updateButtons() setStartingEnabled(true); setStoppingEnabled(false); setSteppingEnabled(false); + setReverseSteppingEnabled(false); m_actionPause->setEnabled(false); m_actionResume->setEnabled(false); + m_actionGoBack->setEnabled(false); m_actionRun->setVisible(true); m_actionPause->setVisible(false); m_actionResume->setVisible(false); + m_actionGoBack->setVisible(false); } else if (status == DebugAdapterRunningStatus) { setStartingEnabled(false); setStoppingEnabled(true); setSteppingEnabled(false); + setReverseSteppingEnabled(false); + m_actionStepIntoBack->setVisible(m_controller->IsTTD()); + m_actionStepOverBack->setVisible(m_controller->IsTTD()); + m_actionPause->setEnabled(true); m_actionResume->setEnabled(false); + m_actionGoBack->setEnabled(false); m_actionRun->setVisible(false); m_actionPause->setVisible(true); m_actionResume->setVisible(false); + m_actionGoBack->setVisible(false); } else // status == DebugAdapterPausedStatus { setStartingEnabled(false); setStoppingEnabled(true); setSteppingEnabled(true); + setReverseSteppingEnabled(m_controller->IsTTD()); m_actionPause->setEnabled(false); m_actionResume->setEnabled(true); + m_actionGoBack->setEnabled(m_controller->IsTTD()); m_actionRun->setVisible(false); m_actionPause->setVisible(false); m_actionResume->setVisible(true); + m_actionGoBack->setVisible(m_controller->IsTTD()); } } diff --git a/ui/controlswidget.h b/ui/controlswidget.h index 055dd1e..49fa435 100644 --- a/ui/controlswidget.h +++ b/ui/controlswidget.h @@ -43,9 +43,13 @@ class DebugControlsWidget : public QToolBar QAction* m_actionDetach; QAction* m_actionPause; QAction* m_actionResume; + QAction* m_actionGoBack; QAction* m_actionStepInto; + QAction* m_actionStepIntoBack; QAction* m_actionStepOver; + QAction* m_actionStepOverBack; QAction* m_actionStepReturn; + QAction* m_actionStepReturnBack; QAction* m_actionSettings; @@ -62,6 +66,7 @@ class DebugControlsWidget : public QToolBar void setStartingEnabled(bool enabled); void setStoppingEnabled(bool enabled); void setSteppingEnabled(bool enabled); + void setReverseSteppingEnabled(bool enabled); void updateButtons(); @@ -74,9 +79,13 @@ public Q_SLOTS: void performPause(); void performResume(); + void performGoReverse(); void performStepInto(); + void performStepIntoReverse(); void performStepOver(); + void performStepOverReverse(); void performStepReturn(); + void performStepReturnReverse(); void performSettings(); }; diff --git a/ui/ui.cpp b/ui/ui.cpp index 276c223..757f3dd 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -274,6 +274,18 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) return controller->IsConnected() && (!controller->IsRunning()); }; + auto connectedAndStoppedWithTTD = [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return false; + if (!DebuggerController::ControllerExists(ctxt.binaryView)) + return false; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return false; + + return controller->IsConnected() && (!controller->IsRunning()) && controller->IsTTD(); + }; + auto connectedAndRunning = [=](const UIActionContext& ctxt) { if (!ctxt.binaryView) return false; @@ -431,6 +443,20 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) connectedAndStopped)); debuggerMenu->addAction("Resume", "Control"); + UIAction::registerAction("Go Backwards", QKeySequence(Qt::ShiftModifier | Qt::Key_F9)); + context->globalActions()->bindAction("Go Backwards", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + controller->GoReverse(); + }, + connectedAndStoppedWithTTD)); + UIAction::registerAction("Step Into", QKeySequence(Qt::Key_F7)); context->globalActions()->bindAction("Step Into", UIAction( @@ -449,6 +475,23 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) connectedAndStopped)); debuggerMenu->addAction("Step Into", "Control"); + UIAction::registerAction("Step Into Backwards", QKeySequence(Qt::ShiftModifier | Qt::Key_F7)); + context->globalActions()->bindAction("Step Into Backwards", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + BNFunctionGraphType graphType = NormalFunctionGraph; + if (ctxt.context && ctxt.context->getCurrentView()) + graphType = ctxt.context->getCurrentView()->getILViewType(); + controller->StepIntoReverse(graphType); + }, + connectedAndStoppedWithTTD)); + UIAction::registerAction("Step Over", QKeySequence(Qt::Key_F8)); context->globalActions()->bindAction("Step Over", UIAction( @@ -467,6 +510,23 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) connectedAndStopped)); debuggerMenu->addAction("Step Over", "Control"); + UIAction::registerAction("Step Over Backwards", QKeySequence(Qt::ShiftModifier | Qt::Key_F8)); + context->globalActions()->bindAction("Step Over Backwards", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + BNFunctionGraphType graphType = NormalFunctionGraph; + if (ctxt.context && ctxt.context->getCurrentView()) + graphType = ctxt.context->getCurrentView()->getILViewType(); + controller->StepOverReverse(graphType); + }, + connectedAndStoppedWithTTD)); + UIAction::registerAction("Step Return", QKeySequence(Qt::ControlModifier | Qt::Key_F9)); context->globalActions()->bindAction("Step Return", UIAction( @@ -482,6 +542,20 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) connectedAndStopped)); debuggerMenu->addAction("Step Return", "Control"); + UIAction::registerAction("Step Return Backwards", QKeySequence( Qt::ControlModifier | Qt::ShiftModifier | Qt::Key_F9 )); + context->globalActions()->bindAction("Step Return Backwards", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller) + return; + + controller->StepReturnReverse(); + }, + connectedAndStoppedWithTTD)); + UIAction::registerAction("Detach"); context->globalActions()->bindAction("Detach", UIAction(