diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 67d653fa..4b721490 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -769,6 +769,9 @@ namespace BinaryNinjaDebuggerAPI { uint64_t RelativeAddressToAbsolute(const ModuleNameAndOffset& address); ModuleNameAndOffset AbsoluteAddressToRelative(uint64_t address); + // rebasing + bool RebaseToRemoteBase(); + size_t RegisterEventCallback( std::function callback, const std::string& name = ""); void RecordTrace(); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index d4c5173b..6cc777cf 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -900,6 +900,12 @@ ModuleNameAndOffset DebuggerController::AbsoluteAddressToRelative(uint64_t addre } +bool DebuggerController::RebaseToRemoteBase() +{ + return BNDebuggerRebaseToRemoteBase(m_object); +} + + uint64_t DebuggerController::IP() { return BNDebuggerGetIP(m_object); diff --git a/api/ffi.h b/api/ffi.h index 22c74bc0..a46da1d7 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -643,6 +643,8 @@ extern "C" DEBUGGER_FFI_API BNModuleNameAndOffset BNDebuggerAbsoluteAddressToRelative( BNDebuggerController* controller, uint64_t address); + DEBUGGER_FFI_API bool BNDebuggerRebaseToRemoteBase(BNDebuggerController* controller); + DEBUGGER_FFI_API uint32_t BNDebuggerGetExitCode(BNDebuggerController* controller); DEBUGGER_FFI_API void BNDebuggerWriteStdin(BNDebuggerController* controller, const char* data, size_t len); diff --git a/api/python/debuggercontroller.py b/api/python/debuggercontroller.py index b24d8d77..b8edfae2 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -1356,6 +1356,19 @@ def modules(self) -> DebugModules: dbgcore.BNDebuggerFreeModules(modules, count.value) return DebugModules(result) + def rebase_to_remote_base(self) -> bool: + """ + Rebase the input binary view to match the remote base address. + + This is useful when auto-rebase is disabled (via the ``debugger.autoRebase`` setting) + and you want to manually trigger a rebase after the target has been launched. + + Note: In UI mode, this returns True immediately and the rebase completes asynchronously. + + :return: True if the rebase was initiated successfully, False otherwise + """ + return dbgcore.BNDebuggerRebaseToRemoteBase(self.handle) + @property def regs(self) -> DebugRegisters: """ diff --git a/core/debugger.cpp b/core/debugger.cpp index cdb68440..4ff2033f 100644 --- a/core/debugger.cpp +++ b/core/debugger.cpp @@ -177,6 +177,15 @@ static void RegisterSettings() "description" : "When enabled, module name comparisons are case-insensitive. This is useful when debug adapters report module names with different casing than the actual binary file names.", "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] })"); + + settings->RegisterSetting("debugger.autoRebase", + R"({ + "title" : "Auto-Rebase on Module Load", + "type" : "boolean", + "default" : true, + "description" : "When enabled, automatically rebase the input binary view to match the remote base address when the module is loaded. Disable this to keep the original base address and use the 'Rebase to Remote Base' action in the Debugger menu to manually trigger rebasing.", + "ignore" : ["SettingsProjectScope", "SettingsResourceScope"] + })"); } extern "C" diff --git a/core/debuggercontroller.cpp b/core/debuggercontroller.cpp index a51e0d3a..6aaa1b72 100644 --- a/core/debuggercontroller.cpp +++ b/core/debuggercontroller.cpp @@ -1392,60 +1392,17 @@ void DebuggerController::DetectLoadedModule() m_inputFileLoaded = true; auto oldBase = GetViewFileSegmentsStart(); + if (remoteBase == oldBase) return; - m_ranges.clear(); - m_oldViewBase = oldBase; - m_newViewBase = remoteBase; - auto data = GetData(); - for (const auto& func: data->GetAnalysisFunctionList()) - { - for (const auto& range: func->GetAddressRanges()) - m_ranges.emplace_back(range); - } - - if (BinaryNinja::IsUIEnabled()) - { - // When the UI is enabled, let the debugger UI do the work. It can show a progress bar if the operation takes - // a while. - if (m_uiCallbacks) - m_uiCallbacks->NotifyRebaseBinaryView(remoteBase); - } - else - { - // Halt analysis before rebasing. Otherwise, the old view may continue analysis which leads to various issues - data->AbortAnalysis(); - data->UpdateAnalysisAndWait(); - - RemoveDebuggerMemoryRegion(); - - auto shouldHoldAnalysis = Settings::Instance()->Get("debugger.holdAnalysis"); - if (shouldHoldAnalysis) - data->SetAnalysisHold(false); - - // remote base is different from the local base, first need a rebase - auto viewType = data->GetTypeName(); - if (!m_file->Rebase(data, remoteBase, [&](size_t cur, size_t total) { return true; })) - { - LogWarn("rebase failed"); - } - auto rebasedView = m_file->GetViewOfType(viewType); - if (!rebasedView) - return; - - if (shouldHoldAnalysis) - { - static auto completionEvent = rebasedView->AddAnalysisCompletionEvent([=](){ - rebasedView->SetAnalysisHold(true); - }); - rebasedView->UpdateAnalysis(); - } + bool autoRebase = Settings::Instance()->Get("debugger.autoRebase"); - ReAddDebuggerMemoryRegion(); - } + if (!autoRebase) + return; - GetData()->UpdateAnalysis(); + if (!RebaseToAddress(remoteBase)) + LogWarn("Failed to rebase to remote base 0x%" PRIx64, remoteBase); } @@ -3406,7 +3363,6 @@ bool DebuggerController::ReAddDebuggerMemoryRegion() } - // TODO: these 3 functions should be moved to the BinaryNinjaAPI namespace for wider audiences static intx::uint512 MaskToSize(intx::uint512 value, size_t size) { @@ -4506,3 +4462,83 @@ bool DebuggerController::FunctionExistsInOldView(uint64_t address) } return false; } + + +bool DebuggerController::RebaseToRemoteBase() +{ + if (!m_state->IsConnected()) + return false; + + uint64_t remoteBase; + if (!m_state->GetRemoteBase(remoteBase)) + return false; + + return RebaseToAddress(remoteBase); +} + + +bool DebuggerController::RebaseToAddress(uint64_t newBase) +{ + const auto data = GetData(); + if (!data) + return false; + + const uint64_t oldBase = GetViewFileSegmentsStart(); + + if (newBase == oldBase) + return true; + + // Check UI callbacks early before modifying state + if (BinaryNinja::IsUIEnabled() && !m_uiCallbacks) + return false; + + m_oldViewBase = oldBase; + m_newViewBase = newBase; + + m_ranges.clear(); + for (const auto& func: data->GetAnalysisFunctionList()) + { + for (const auto& range: func->GetAddressRanges()) + m_ranges.emplace_back(range); + } + + if (BinaryNinja::IsUIEnabled()) + { + m_uiCallbacks->NotifyRebaseBinaryView(newBase); + return true; // Rebase completes asynchronously via UI callback + } + + data->AbortAnalysis(); + data->UpdateAnalysisAndWait(); + + RemoveDebuggerMemoryRegion(); + + const auto shouldHoldAnalysis = Settings::Instance()->Get("debugger.holdAnalysis"); + if (shouldHoldAnalysis) + data->SetAnalysisHold(false); + + const auto viewType = data->GetTypeName(); + if (!m_file->Rebase(data, newBase, [&](size_t, size_t) { return true; })) + { + LogWarn("Failed to rebase to remote base 0x%" PRIx64, newBase); + return false; + } + + const auto rebasedView = m_file->GetViewOfType(viewType); + if (!rebasedView) + return false; + + if (shouldHoldAnalysis) + { + // Store in member variable to keep alive until callback fires + m_rebaseCompletionEvent = rebasedView->AddAnalysisCompletionEvent([=]() { + rebasedView->SetAnalysisHold(true); + }); + rebasedView->UpdateAnalysis(); + } + + ReAddDebuggerMemoryRegion(); + GetData()->UpdateAnalysis(); + + return true; +} diff --git a/core/debuggercontroller.h b/core/debuggercontroller.h index 1014bb97..26f30c0f 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -129,6 +129,7 @@ namespace BinaryNinjaDebugger { bool m_shouldAnnotateStackVariable = false; void EventHandler(const DebuggerEvent& event); + bool RebaseToAddress(uint64_t newBase); void UpdateStackVariables(); void AddRegisterValuesToExpressionParser(); void AddModuleValuesToExpressionParser(); @@ -204,6 +205,7 @@ namespace BinaryNinjaDebugger { uint64_t m_oldViewBase, m_newViewBase; std::vector m_ranges; + BinaryNinja::Ref m_rebaseCompletionEvent; // TTD Code Coverage Analysis std::unordered_set m_executedInstructions; @@ -278,6 +280,10 @@ namespace BinaryNinjaDebugger { ModuleNameAndOffset AbsoluteAddressToRelative(uint64_t absoluteAddress); uint64_t RelativeAddressToAbsolute(const ModuleNameAndOffset& relativeAddress); + // rebasing + // Note: Returns true immediately in UI mode (rebase completes asynchronously via UI callback) + bool RebaseToRemoteBase(); + // arch ArchitectureRef GetRemoteArchitecture(); diff --git a/core/ffi.cpp b/core/ffi.cpp index 43db6e8c..f9a8bc68 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1040,6 +1040,12 @@ BNModuleNameAndOffset BNDebuggerAbsoluteAddressToRelative(BNDebuggerController* } +bool BNDebuggerRebaseToRemoteBase(BNDebuggerController* controller) +{ + return controller->object->RebaseToRemoteBase(); +} + + bool BNDebuggerIsSameBaseModule(const char* module1, const char* module2) { return DebugModule::IsSameBaseModule(module1, module2); diff --git a/ui/ui.cpp b/ui/ui.cpp index 5091570e..0f251522 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -485,6 +485,27 @@ void GlobalDebuggerUI::SetupMenu(UIContext* context) Menu::setMainMenuOrder("Debugger", MENU_ORDER_LATE); debuggerMenu->addAction("Debug Adapter Settings...", "Settings", MENU_ORDER_FIRST); + UIAction::registerAction("Rebase to Remote Base"); + context->globalActions()->bindAction("Rebase to Remote Base", + UIAction( + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return; + const auto controller = DebuggerController::GetController(ctxt.binaryView); + if (!controller || !controller->IsConnected()) + return; + if (!controller->RebaseToRemoteBase()) + LogWarn("Failed to rebase to remote base"); + }, + [=](const UIActionContext& ctxt) { + if (!ctxt.binaryView) + return false; + const auto controller = DebuggerController::GetController(ctxt.binaryView); + return controller && controller->IsConnected(); + })); + + debuggerMenu->addAction("Rebase to Remote Base", "Rebase"); + UIAction::registerAction("Launch", QKeySequence(Qt::Key_F6)); context->globalActions()->bindAction("Launch", UIAction( @@ -1776,7 +1797,7 @@ void DebuggerUI::checkRebaseBinaryView(uint64_t remoteBase) if (!result) { - LogWarn("failed to rebase the input view"); + LogWarn("Failed to rebase to remote base 0x%" PRIx64, remoteBase); return; } diff --git a/ui/uinotification.cpp b/ui/uinotification.cpp index fe2badaa..563a84ca 100644 --- a/ui/uinotification.cpp +++ b/ui/uinotification.cpp @@ -191,6 +191,7 @@ void NotificationListener::OnContextMenuCreated(UIContext *context, View* view, menu.addAction("Debugger", "Run Back To Here", "Control"); menu.addAction("Debugger", "Create Stack View", "Misc"); menu.addAction("Debugger", "Override IP", "Misc"); + menu.addAction("Debugger", "Rebase to Remote Base", "Misc"); #ifdef WIN32 // TTD Memory Access context menu items menu.addAction("Debugger", "Navigate to TTD Timestamp...", "TTD");