diff --git a/api/debuggerapi.h b/api/debuggerapi.h index 67d653fa..031de2da 100644 --- a/api/debuggerapi.h +++ b/api/debuggerapi.h @@ -769,6 +769,11 @@ namespace BinaryNinjaDebuggerAPI { uint64_t RelativeAddressToAbsolute(const ModuleNameAndOffset& address); ModuleNameAndOffset AbsoluteAddressToRelative(uint64_t address); + // rebasing + bool RebaseToRemoteBase(); + bool RebaseToAddress(uint64_t address); + bool GetRemoteBase(uint64_t& address); + size_t RegisterEventCallback( std::function callback, const std::string& name = ""); void RecordTrace(); diff --git a/api/debuggercontroller.cpp b/api/debuggercontroller.cpp index d4c5173b..a19d2799 100644 --- a/api/debuggercontroller.cpp +++ b/api/debuggercontroller.cpp @@ -900,6 +900,24 @@ ModuleNameAndOffset DebuggerController::AbsoluteAddressToRelative(uint64_t addre } +bool DebuggerController::RebaseToRemoteBase() +{ + return BNDebuggerRebaseToRemoteBase(m_object); +} + + +bool DebuggerController::RebaseToAddress(uint64_t address) +{ + return BNDebuggerRebaseToAddress(m_object, address); +} + + +bool DebuggerController::GetRemoteBase(uint64_t& address) +{ + return BNDebuggerGetRemoteBase(m_object, &address); +} + + uint64_t DebuggerController::IP() { return BNDebuggerGetIP(m_object); diff --git a/api/ffi.h b/api/ffi.h index 22c74bc0..84386767 100644 --- a/api/ffi.h +++ b/api/ffi.h @@ -643,6 +643,10 @@ extern "C" DEBUGGER_FFI_API BNModuleNameAndOffset BNDebuggerAbsoluteAddressToRelative( BNDebuggerController* controller, uint64_t address); + DEBUGGER_FFI_API bool BNDebuggerRebaseToRemoteBase(BNDebuggerController* controller); + DEBUGGER_FFI_API bool BNDebuggerRebaseToAddress(BNDebuggerController* controller, uint64_t address); + DEBUGGER_FFI_API bool BNDebuggerGetRemoteBase(BNDebuggerController* controller, uint64_t* address); + 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..8ad398eb 100644 --- a/api/python/debuggercontroller.py +++ b/api/python/debuggercontroller.py @@ -21,7 +21,7 @@ # import debugger from . import _debuggercore as dbgcore from .debugger_enums import * -from typing import Callable, List, Union +from typing import Callable, List, Optional, Union # TTD (Time Travel Debugging) Memory Access Type parsing @@ -1356,6 +1356,47 @@ 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) + + def rebase_to_address(self, address: int) -> bool: + """ + Rebase the input binary view to the specified base address. + + This allows manual rebasing to a user-specified address, which is useful when the + auto-detected remote base is incorrect. + + Note: In UI mode, this returns True immediately and the rebase completes asynchronously. + + :param address: The new base address for the binary view + :return: True if the rebase was initiated successfully, False otherwise + """ + return dbgcore.BNDebuggerRebaseToAddress(self.handle, address) + + def get_remote_base(self) -> Optional[int]: + """ + Get the detected remote base address of the target module. + + This returns the base address that the debugger detected for the input binary + in the remote process. Returns None if not connected or detection failed. + + :return: The remote base address, or None if unavailable + """ + address = ctypes.c_uint64() + if not dbgcore.BNDebuggerGetRemoteBase(self.handle, ctypes.byref(address)): + return None + return address.value + @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..f329297a 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); + bool autoRebase = Settings::Instance()->Get("debugger.autoRebase"); - // 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(); - } - - 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,93 @@ bool DebuggerController::FunctionExistsInOldView(uint64_t address) } return false; } + + +bool DebuggerController::RebaseToRemoteBase() +{ + uint64_t remoteBase; + if (!GetRemoteBase(remoteBase)) + return false; + + return RebaseToAddress(remoteBase); +} + + +bool DebuggerController::GetRemoteBase(uint64_t& address) +{ + if (!m_state->IsConnected()) + return false; + + return m_state->GetRemoteBase(address); +} + + +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); + ReAddDebuggerMemoryRegion(); + return false; + } + + const auto rebasedView = m_file->GetViewOfType(viewType); + if (!rebasedView) + { + ReAddDebuggerMemoryRegion(); + 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..9d6f8ba0 100644 --- a/core/debuggercontroller.h +++ b/core/debuggercontroller.h @@ -204,6 +204,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 +279,12 @@ 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(); + bool RebaseToAddress(uint64_t address); + bool GetRemoteBase(uint64_t& address); + // arch ArchitectureRef GetRemoteArchitecture(); diff --git a/core/ffi.cpp b/core/ffi.cpp index 43db6e8c..0dc282d0 100644 --- a/core/ffi.cpp +++ b/core/ffi.cpp @@ -1040,6 +1040,24 @@ BNModuleNameAndOffset BNDebuggerAbsoluteAddressToRelative(BNDebuggerController* } +bool BNDebuggerRebaseToRemoteBase(BNDebuggerController* controller) +{ + return controller->object->RebaseToRemoteBase(); +} + + +bool BNDebuggerRebaseToAddress(BNDebuggerController* controller, uint64_t address) +{ + return controller->object->RebaseToAddress(address); +} + + +bool BNDebuggerGetRemoteBase(BNDebuggerController* controller, uint64_t* address) +{ + return controller->object->GetRemoteBase(*address); +} + + 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..c6a23a94 100644 --- a/ui/ui.cpp +++ b/ui/ui.cpp @@ -485,6 +485,55 @@ 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; + + uint64_t detectedBase = 0; + controller->GetRemoteBase(detectedBase); + const QString defaultValue = detectedBase ? QString("0x%1").arg(detectedBase, 0, 16) : QString(); + + bool ok; + const QString input = QInputDialog::getText( + nullptr, + "Rebase to Remote Base", + "Enter the new base address:", + QLineEdit::Normal, + defaultValue, + &ok); + + if (!ok || input.isEmpty()) + return; + + // TODO: Switch to ViewFrame::getAddressFromInput once + // https://github.com/Vector35/binaryninja-api/issues/7915 is fixed + uint64_t address = 0; + std::string errorString; + if (!BinaryView::ParseExpression( + controller->GetData(), input.trimmed().toStdString(), address, 0, errorString)) + { + LogWarn("Invalid address expression: %s", errorString.c_str()); + return; + } + + if (!controller->RebaseToAddress(address)) + LogWarn("Failed to rebase to address 0x%" PRIx64, address); + }, + [=](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 +1825,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..28870500 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");