From 90ba81150ef36277659edcc880d5440bd00ba997 Mon Sep 17 00:00:00 2001 From: Greg Clayton Date: Wed, 5 Dec 2012 00:16:59 +0000 Subject: [PATCH] Added the ability to debug through your process exec'ing itself to the same architecture. llvm-svn: 169340 --- lldb/include/lldb/API/SBThread.h | 1 + lldb/include/lldb/Target/DynamicLoader.h | 15 +++++- lldb/include/lldb/Target/Process.h | 21 +++++++++ lldb/include/lldb/Target/StopInfo.h | 3 ++ lldb/include/lldb/Target/Target.h | 2 + lldb/include/lldb/lldb-enumerations.h | 1 + lldb/scripts/Python/interface/SBThread.i | 1 + lldb/source/API/SBThread.cpp | 10 ++++ .../MacOSX-DYLD/DynamicLoaderMacOSXDYLD.cpp | 54 +++++++++++++++++++++- .../MacOSX-DYLD/DynamicLoaderMacOSXDYLD.h | 4 ++ .../Process/Utility/StopInfoMachException.cpp | 34 ++++++++++++++ .../Process/gdb-remote/ProcessGDBRemote.cpp | 11 +++++ .../Plugins/Process/gdb-remote/ProcessGDBRemote.h | 3 ++ lldb/source/Target/Process.cpp | 19 ++++++++ lldb/source/Target/StackFrameList.cpp | 1 + lldb/source/Target/StopInfo.cpp | 49 ++++++++++++++++++++ lldb/source/Target/Target.cpp | 27 +++++++---- lldb/source/Target/Thread.cpp | 1 + lldb/source/Target/ThreadPlanBase.cpp | 8 ++++ lldb/source/Target/ThreadPlanStepInRange.cpp | 1 + lldb/source/Target/ThreadPlanStepOut.cpp | 1 + lldb/source/Target/ThreadPlanStepOverRange.cpp | 1 + lldb/source/Target/ThreadPlanStepUntil.cpp | 1 + lldb/tools/driver/Driver.cpp | 1 + 24 files changed, 257 insertions(+), 13 deletions(-) diff --git a/lldb/include/lldb/API/SBThread.h b/lldb/include/lldb/API/SBThread.h index 947ef14..a77bd5b 100644 --- a/lldb/include/lldb/API/SBThread.h +++ b/lldb/include/lldb/API/SBThread.h @@ -67,6 +67,7 @@ public: /// eStopReasonWatchpoint 1 watchpoint id /// eStopReasonSignal 1 unix signal number /// eStopReasonException N exception data + /// eStopReasonExec 0 /// eStopReasonPlanComplete 0 //-------------------------------------------------------------------------- uint64_t diff --git a/lldb/include/lldb/Target/DynamicLoader.h b/lldb/include/lldb/Target/DynamicLoader.h index ec982fd..2c87657 100644 --- a/lldb/include/lldb/Target/DynamicLoader.h +++ b/lldb/include/lldb/Target/DynamicLoader.h @@ -93,7 +93,20 @@ public: DidLaunch () = 0; - + //------------------------------------------------------------------ + /// Helper function that can be used to detect when a process has + /// called exec and is now a new and different process. This can + /// be called when necessary to try and detect the exec. The process + /// might be able to answer this question, but sometimes it might + /// not be able and the dynamic loader often knows what the program + /// entry point is. So the process and the dynamic loader can work + /// together to detect this. + //------------------------------------------------------------------ + virtual bool + ProcessDidExec () + { + return false; + } //------------------------------------------------------------------ /// Get whether the process should stop when images change. /// diff --git a/lldb/include/lldb/Target/Process.h b/lldb/include/lldb/Target/Process.h index d9a087c..2409bdb 100644 --- a/lldb/include/lldb/Target/Process.h +++ b/lldb/include/lldb/Target/Process.h @@ -2055,6 +2055,27 @@ public: //------------------------------------------------------------------ + /// Called after a process re-execs itself. + /// + /// Allow Process plug-ins to execute some code after a process has + /// exec'ed itself. Subclasses typically should override DoDidExec() + /// as the lldb_private::Process class needs to remove its dynamic + /// loader, runtime, ABI and other plug-ins, as well as unload all + /// shared libraries. + //------------------------------------------------------------------ + virtual void + DidExec (); + + //------------------------------------------------------------------ + /// Subclasses of Process should implement this function if they + /// need to do anything after a process exec's itself. + //------------------------------------------------------------------ + virtual void + DoDidExec () + { + } + + //------------------------------------------------------------------ /// Called before launching to a process. /// /// Allow Process plug-ins to execute some code before launching a diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h index cf2719c..0bb15fb 100644 --- a/lldb/include/lldb/Target/StopInfo.h +++ b/lldb/include/lldb/Target/StopInfo.h @@ -129,6 +129,9 @@ public: static lldb::StopInfoSP CreateStopReasonWithException (Thread &thread, const char *description); + static lldb::StopInfoSP + CreateStopReasonWithExec (Thread &thread); + static lldb::ValueObjectSP GetReturnValueObject (lldb::StopInfoSP &stop_info_sp); diff --git a/lldb/include/lldb/Target/Target.h b/lldb/include/lldb/Target/Target.h index e19b910..5306c32 100644 --- a/lldb/include/lldb/Target/Target.h +++ b/lldb/include/lldb/Target/Target.h @@ -390,6 +390,8 @@ public: void DeleteCurrentProcess (); + void + CleanupProcess (); //------------------------------------------------------------------ /// Dump a description of this object to a Stream. /// diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index ce28142..99c5673 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -176,6 +176,7 @@ namespace lldb { eStopReasonWatchpoint, eStopReasonSignal, eStopReasonException, + eStopReasonExec, // Program was re-exec'ed eStopReasonPlanComplete } StopReason; diff --git a/lldb/scripts/Python/interface/SBThread.i b/lldb/scripts/Python/interface/SBThread.i index 76a014a..0401ec1 100644 --- a/lldb/scripts/Python/interface/SBThread.i +++ b/lldb/scripts/Python/interface/SBThread.i @@ -88,6 +88,7 @@ public: /// eStopReasonWatchpoint 1 watchpoint id /// eStopReasonSignal 1 unix signal number /// eStopReasonException N exception data + /// eStopReasonExec 0 /// eStopReasonPlanComplete 0 //-------------------------------------------------------------------------- ") GetStopReasonDataAtIndex; diff --git a/lldb/source/API/SBThread.cpp b/lldb/source/API/SBThread.cpp index 4dd29d7..e083939 100644 --- a/lldb/source/API/SBThread.cpp +++ b/lldb/source/API/SBThread.cpp @@ -147,6 +147,7 @@ SBThread::GetStopReasonDataCount () case eStopReasonInvalid: case eStopReasonNone: case eStopReasonTrace: + case eStopReasonExec: case eStopReasonPlanComplete: // There is no data for these stop reasons. return 0; @@ -204,6 +205,7 @@ SBThread::GetStopReasonDataAtIndex (uint32_t idx) case eStopReasonInvalid: case eStopReasonNone: case eStopReasonTrace: + case eStopReasonExec: case eStopReasonPlanComplete: // There is no data for these stop reasons. return 0; @@ -336,6 +338,14 @@ SBThread::GetStopDescription (char *dst, size_t dst_len) } break; + case eStopReasonExec: + { + char exc_desc[] = "exec"; + stop_desc = exc_desc; + stop_desc_len = sizeof(exc_desc); // Include the NULL byte for size + } + break; + default: break; } diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.cpp b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.cpp index 466e664..8cedda65 100644 --- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.cpp +++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.cpp @@ -139,7 +139,8 @@ DynamicLoaderMacOSXDYLD::DynamicLoaderMacOSXDYLD (Process* process) : m_break_id(LLDB_INVALID_BREAK_ID), m_dyld_image_infos(), m_dyld_image_infos_stop_id (UINT32_MAX), - m_mutex(Mutex::eMutexTypeRecursive) + m_mutex(Mutex::eMutexTypeRecursive), + m_process_image_addr_is_all_images_infos (false) { } @@ -179,6 +180,54 @@ DynamicLoaderMacOSXDYLD::DidLaunch () SetNotificationBreakpoint (); } +bool +DynamicLoaderMacOSXDYLD::ProcessDidExec () +{ + if (m_process) + { + // If we are stopped after an exec, we will have only one thread... + if (m_process->GetThreadList().GetSize() == 1) + { + // We know if a process has exec'ed if our "m_dyld_all_image_infos_addr" + // value differs from the Process' image info address. When a process + // execs itself it might cause a change if ASLR is enabled. + const addr_t shlib_addr = m_process->GetImageInfoAddress (); + if (m_process_image_addr_is_all_images_infos == true && shlib_addr != m_dyld_all_image_infos_addr) + { + // The image info address from the process is the 'dyld_all_image_infos' + // address and it has changed. + return true; + } + + if (m_process_image_addr_is_all_images_infos == false && shlib_addr == m_dyld.address) + { + // The image info address from the process is the mach_header + // address for dyld and it has changed. + return true; + } + + // ASLR might be disabled and dyld could have ended up in the same + // location. We should try and detect if we are stopped at '_dyld_start' + ThreadSP thread_sp (m_process->GetThreadList().GetThreadAtIndex(0)); + if (thread_sp) + { + lldb::StackFrameSP frame_sp (thread_sp->GetStackFrameAtIndex(0)); + if (frame_sp) + { + const Symbol *symbol = frame_sp->GetSymbolContext(eSymbolContextSymbol).symbol; + if (symbol) + { + if (symbol->GetName() == ConstString("_dyld_start")) + return true; + } + } + } + } + } + return false; +} + + //---------------------------------------------------------------------- // Clear out the state of this class. @@ -224,7 +273,6 @@ DynamicLoaderMacOSXDYLD::LocateDYLD() // mach header for dyld, or it might point to the // dyld_all_image_infos struct const addr_t shlib_addr = m_process->GetImageInfoAddress (); - ByteOrder byte_order = m_process->GetTarget().GetArchitecture().GetByteOrder(); uint8_t buf[4]; DataExtractor data (buf, sizeof(buf), byte_order, 4); @@ -239,6 +287,7 @@ DynamicLoaderMacOSXDYLD::LocateDYLD() case llvm::MachO::HeaderMagic64: case llvm::MachO::HeaderMagic32Swapped: case llvm::MachO::HeaderMagic64Swapped: + m_process_image_addr_is_all_images_infos = false; return ReadDYLDInfoFromMemoryAndSetNotificationCallback(shlib_addr); default: @@ -247,6 +296,7 @@ DynamicLoaderMacOSXDYLD::LocateDYLD() } // Maybe it points to the all image infos? m_dyld_all_image_infos_addr = shlib_addr; + m_process_image_addr_is_all_images_infos = true; } if (m_dyld_all_image_infos_addr != LLDB_INVALID_ADDRESS) diff --git a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.h b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.h index f7e9ac5..2e0d250 100644 --- a/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.h +++ b/lldb/source/Plugins/DynamicLoader/MacOSX-DYLD/DynamicLoaderMacOSXDYLD.h @@ -62,6 +62,9 @@ public: virtual void DidLaunch (); + virtual bool + ProcessDidExec (); + virtual lldb::ThreadPlanSP GetStepThroughTrampolinePlan (lldb_private::Thread &thread, bool stop_others); @@ -372,6 +375,7 @@ protected: uint32_t m_dyld_image_infos_stop_id; // The process stop ID that "m_dyld_image_infos" is valid for mutable lldb_private::Mutex m_mutex; lldb_private::Process::Notifications m_notification_callbacks; + bool m_process_image_addr_is_all_images_infos; private: DISALLOW_COPY_AND_ASSIGN (DynamicLoaderMacOSXDYLD); diff --git a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp index 7db5729..5999e13 100644 --- a/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp +++ b/lldb/source/Plugins/Process/Utility/StopInfoMachException.cpp @@ -16,6 +16,8 @@ #include "lldb/Breakpoint/Watchpoint.h" #include "lldb/Core/ArchSpec.h" #include "lldb/Core/StreamString.h" +#include "lldb/Symbol/Symbol.h" +#include "lldb/Target/DynamicLoader.h" #include "lldb/Target/ExecutionContext.h" #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" @@ -300,7 +302,39 @@ StopInfoMachException::CreateStopReasonWithMachException case 5: // EXC_SOFTWARE if (exc_code == 0x10003) // EXC_SOFT_SIGNAL + { + if (exc_sub_code == 5) + { + // On MacOSX, a SIGTRAP can signify that a process has called + // exec, so we should check with our dynamic loader to verify. + ProcessSP process_sp (thread.GetProcess()); + if (process_sp) + { + DynamicLoader *dynamic_loader = process_sp->GetDynamicLoader(); + if (dynamic_loader && dynamic_loader->ProcessDidExec()) + { + // The program was re-exec'ed + return StopInfo::CreateStopReasonWithExec (thread); + } +// if (!process_did_exec) +// { +// // We have a SIGTRAP, make sure we didn't exec by checking +// // for the PC being at "_dyld_start"... +// lldb::StackFrameSP frame_sp (thread.GetStackFrameAtIndex(0)); +// if (frame_sp) +// { +// const Symbol *symbol = frame_sp->GetSymbolContext(eSymbolContextSymbol).symbol; +// if (symbol) +// { +// if (symbol->GetName() == ConstString("_dyld_start")) +// process_did_exec = true; +// } +// } +// } + } + } return StopInfo::CreateStopReasonWithSignal (thread, exc_sub_code); + } break; case 6: // EXC_BREAKPOINT diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp index 475e6d2..7bc0300 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1058,6 +1058,17 @@ ProcessGDBRemote::DidAttach () DidLaunchOrAttach (); } +void +ProcessGDBRemote::DoDidExec () +{ + // The process exec'ed itself, figure out the dynamic loader, etc... + BuildDynamicRegisterInfo (true); + m_gdb_comm.ResetDiscoverableSettings(); + DidLaunchOrAttach (); +} + + + Error ProcessGDBRemote::WillResume () { diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h index 4cf2a4f..d19878f 100644 --- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -113,6 +113,9 @@ public: virtual void DidAttach (); + virtual void + DoDidExec (); + //------------------------------------------------------------------ // PluginInterface protocol //------------------------------------------------------------------ diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp index 0f43040..5455a24 100644 --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -5111,3 +5111,22 @@ Process::Flush () { m_thread_list.Flush(); } + +void +Process::DidExec () +{ + Target &target = GetTarget(); + target.CleanupProcess (); + ModuleList unloaded_modules (target.GetImages()); + target.ModulesDidUnload (unloaded_modules); + target.GetSectionLoadList().Clear(); + m_dynamic_checkers_ap.reset(); + m_abi_sp.reset(); + m_os_ap.reset(); + m_dyld_ap.reset(); + m_image_tokens.clear(); + m_allocated_memory_cache.Clear(); + m_language_runtimes.clear(); + DoDidExec(); + CompleteAttach (); +} diff --git a/lldb/source/Target/StackFrameList.cpp b/lldb/source/Target/StackFrameList.cpp index 082fddd..0a45d33 100644 --- a/lldb/source/Target/StackFrameList.cpp +++ b/lldb/source/Target/StackFrameList.cpp @@ -147,6 +147,7 @@ StackFrameList::ResetCurrentInlinedDepth () { case eStopReasonWatchpoint: case eStopReasonException: + case eStopReasonExec: case eStopReasonSignal: // In all these cases we want to stop in the deepest most frame. m_current_inlined_pc = curr_pc; diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp index bae1e4a..de76748 100644 --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -862,6 +862,49 @@ private: ThreadPlanSP m_plan_sp; ValueObjectSP m_return_valobj_sp; }; + +class StopInfoExec : public StopInfo +{ +public: + + StopInfoExec (Thread &thread) : + StopInfo (thread, LLDB_INVALID_UID), + m_performed_action (false) + { + } + + virtual + ~StopInfoExec () + { + } + + virtual StopReason + GetStopReason () const + { + return eStopReasonExec; + } + + virtual const char * + GetDescription () + { + return "exec"; + } +protected: +protected: + + virtual void + PerformAction (Event *event_ptr) + { + // Only perform the action once + if (m_performed_action) + return; + m_performed_action = true; + m_thread.GetProcess()->DidExec(); + } + + bool m_performed_action; +}; + } // namespace lldb_private StopInfoSP @@ -906,6 +949,12 @@ StopInfo::CreateStopReasonWithException (Thread &thread, const char *description return StopInfoSP (new StopInfoException (thread, description)); } +StopInfoSP +StopInfo::CreateStopReasonWithExec (Thread &thread) +{ + return StopInfoSP (new StopInfoExec (thread)); +} + ValueObjectSP StopInfo::GetReturnValueObject(StopInfoSP &stop_info_sp) { diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp index 77daef6..433ddd0 100644 --- a/lldb/source/Target/Target.cpp +++ b/lldb/source/Target/Target.cpp @@ -130,6 +130,21 @@ Target::Dump (Stream *s, lldb::DescriptionLevel description_level) } void +Target::CleanupProcess () +{ + // Do any cleanup of the target we need to do between process instances. + // NB It is better to do this before destroying the process in case the + // clean up needs some help from the process. + m_breakpoint_list.ClearAllBreakpointSites(); + m_internal_breakpoint_list.ClearAllBreakpointSites(); + // Disable watchpoints just on the debugger side. + Mutex::Locker locker; + this->GetWatchpointList().GetListMutex(locker); + DisableAllWatchpoints(false); + ClearAllWatchpointHitCounts(); +} + +void Target::DeleteCurrentProcess () { if (m_process_sp.get()) @@ -140,16 +155,8 @@ Target::DeleteCurrentProcess () m_process_sp->Finalize(); - // Do any cleanup of the target we need to do between process instances. - // NB It is better to do this before destroying the process in case the - // clean up needs some help from the process. - m_breakpoint_list.ClearAllBreakpointSites(); - m_internal_breakpoint_list.ClearAllBreakpointSites(); - // Disable watchpoints just on the debugger side. - Mutex::Locker locker; - this->GetWatchpointList().GetListMutex(locker); - DisableAllWatchpoints(false); - ClearAllWatchpointHitCounts(); + CleanupProcess (); + m_process_sp.reset(); } } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp index 4c6995c..7a5aafb 100644 --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -1574,6 +1574,7 @@ Thread::StopReasonAsCString (lldb::StopReason reason) case eStopReasonWatchpoint: return "watchpoint"; case eStopReasonSignal: return "signal"; case eStopReasonException: return "exception"; + case eStopReasonExec: return "exec"; case eStopReasonPlanComplete: return "plan complete"; } diff --git a/lldb/source/Target/ThreadPlanBase.cpp b/lldb/source/Target/ThreadPlanBase.cpp index f8e4b6a..ebf4686 100644 --- a/lldb/source/Target/ThreadPlanBase.cpp +++ b/lldb/source/Target/ThreadPlanBase.cpp @@ -139,6 +139,14 @@ ThreadPlanBase::ShouldStop (Event *event_ptr) m_thread.DiscardThreadPlans(false); return true; + case eStopReasonExec: + // If we crashed, discard thread plans and stop. Don't force the discard, however, + // since on rerun the target may clean up this exception and continue normally from there. + if (log) + log->Printf("Base plan discarding thread plans for thread tid = 0x%4.4" PRIx64 " (exec.)", m_thread.GetID()); + m_thread.DiscardThreadPlans(false); + return true; + case eStopReasonSignal: if (stop_info_sp->ShouldStop(event_ptr)) { diff --git a/lldb/source/Target/ThreadPlanStepInRange.cpp b/lldb/source/Target/ThreadPlanStepInRange.cpp index ca24172..f4339a4 100644 --- a/lldb/source/Target/ThreadPlanStepInRange.cpp +++ b/lldb/source/Target/ThreadPlanStepInRange.cpp @@ -335,6 +335,7 @@ ThreadPlanStepInRange::PlanExplainsStop () case eStopReasonWatchpoint: case eStopReasonSignal: case eStopReasonException: + case eStopReasonExec: { LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_STEP)); if (log) diff --git a/lldb/source/Target/ThreadPlanStepOut.cpp b/lldb/source/Target/ThreadPlanStepOut.cpp index 540539e..5343324 100644 --- a/lldb/source/Target/ThreadPlanStepOut.cpp +++ b/lldb/source/Target/ThreadPlanStepOut.cpp @@ -251,6 +251,7 @@ ThreadPlanStepOut::PlanExplainsStop () case eStopReasonWatchpoint: case eStopReasonSignal: case eStopReasonException: + case eStopReasonExec: return false; default: diff --git a/lldb/source/Target/ThreadPlanStepOverRange.cpp b/lldb/source/Target/ThreadPlanStepOverRange.cpp index ba21eda..66754d2 100644 --- a/lldb/source/Target/ThreadPlanStepOverRange.cpp +++ b/lldb/source/Target/ThreadPlanStepOverRange.cpp @@ -319,6 +319,7 @@ ThreadPlanStepOverRange::PlanExplainsStop () case eStopReasonWatchpoint: case eStopReasonSignal: case eStopReasonException: + case eStopReasonExec: default: if (log) log->PutCString ("ThreadPlanStepInRange got asked if it explains the stop for some reason other than step."); diff --git a/lldb/source/Target/ThreadPlanStepUntil.cpp b/lldb/source/Target/ThreadPlanStepUntil.cpp index 1e454e7..95b0a35 100644 --- a/lldb/source/Target/ThreadPlanStepUntil.cpp +++ b/lldb/source/Target/ThreadPlanStepUntil.cpp @@ -291,6 +291,7 @@ ThreadPlanStepUntil::AnalyzeStop() case eStopReasonWatchpoint: case eStopReasonSignal: case eStopReasonException: + case eStopReasonExec: m_explains_stop = false; break; default: diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp index 7a4eded..29fa37d 100644 --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -806,6 +806,7 @@ Driver::UpdateSelectedThread () case eStopReasonWatchpoint: case eStopReasonSignal: case eStopReasonException: + case eStopReasonExec: if (!other_thread.IsValid()) other_thread = thread; break; -- 2.7.4