diff --git a/lldb/include/lldb/Target/AbortRecognizer.h b/lldb/include/lldb/Target/AbortRecognizer.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/Target/AbortRecognizer.h @@ -0,0 +1,52 @@ +//===-- AbortRecognizer.cpp -------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef liblldb_AbortRegognizer_h_ +#define liblldb_AbortRegognizer_h_ + +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrameRecognizer.h" +#include "lldb/Utility/ConstString.h" +#include "lldb/Utility/FileSpec.h" + +#include + +namespace lldb_private { + +void RegisterAbortRecognizer(Process *process); + +llvm::Optional> +GetAbortLocation(Process *process_sp); + +llvm::Optional> +GetAssertLocation(Process *process_sp); + +#pragma mark Frame recognizers + +class AbortRecognizedStackFrame : public RecognizedStackFrame { +public: + AbortRecognizedStackFrame(lldb::ThreadSP thread_sp, FileSpec module_spec, + llvm::StringRef function_name); + lldb::StackFrameSP GetMostRelevantFrame() override; + lldb::StopInfoSP GetStopInfo() override; + +private: + lldb::StackFrameSP m_most_relevant_frame; + lldb::StopInfoSP m_stop_info_sp; +}; + +class AbortFrameRecognizer : public StackFrameRecognizer { +public: + std::string GetName() override { return "Abort StackFrame Recognizer"; } + lldb::RecognizedStackFrameSP + RecognizeFrame(lldb::StackFrameSP frame) override; +}; + +} // namespace lldb_private + +#endif // liblldb_AbortRecognizer_h_ diff --git a/lldb/include/lldb/Target/StackFrameRecognizer.h b/lldb/include/lldb/Target/StackFrameRecognizer.h --- a/lldb/include/lldb/Target/StackFrameRecognizer.h +++ b/lldb/include/lldb/Target/StackFrameRecognizer.h @@ -12,6 +12,7 @@ #include "lldb/Core/ValueObject.h" #include "lldb/Core/ValueObjectList.h" #include "lldb/Symbol/VariableList.h" +#include "lldb/Target/StopInfo.h" #include "lldb/Utility/StructuredData.h" #include "lldb/lldb-private-forward.h" #include "lldb/lldb-public.h" @@ -33,6 +34,8 @@ virtual lldb::ValueObjectSP GetExceptionObject() { return lldb::ValueObjectSP(); } + virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; }; + virtual lldb::StopInfoSP GetStopInfo() { return nullptr; } virtual ~RecognizedStackFrame(){}; protected: diff --git a/lldb/include/lldb/Target/StopInfo.h b/lldb/include/lldb/Target/StopInfo.h --- a/lldb/include/lldb/Target/StopInfo.h +++ b/lldb/include/lldb/Target/StopInfo.h @@ -127,6 +127,9 @@ static lldb::StopInfoSP CreateStopReasonWithException(Thread &thread, const char *description); + static lldb::StopInfoSP + CreateStopReasonForRecognizedFrame(Thread &thread, const char *description); + static lldb::StopInfoSP CreateStopReasonWithExec(Thread &thread); static lldb::ValueObjectSP diff --git a/lldb/include/lldb/Target/Thread.h b/lldb/include/lldb/Target/Thread.h --- a/lldb/include/lldb/Target/Thread.h +++ b/lldb/include/lldb/Target/Thread.h @@ -216,6 +216,8 @@ virtual void RefreshStateAfterStop() = 0; + void SelectMostRelevantFrame(); + void WillStop(); bool ShouldStop(Event *event_ptr); diff --git a/lldb/source/Target/AbortRecognizer.cpp b/lldb/source/Target/AbortRecognizer.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/Target/AbortRecognizer.cpp @@ -0,0 +1,174 @@ +#include "lldb/Core/Module.h" +#include "lldb/Symbol/Function.h" +#include "lldb/Symbol/SymbolContext.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/StackFrameList.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/Thread.h" + +#include "lldb/Utility/Log.h" +#include "lldb/Utility/Logging.h" + +#include "lldb/Target/AbortRecognizer.h" + +using namespace llvm; +using namespace lldb; +using namespace lldb_private; + +AbortRecognizedStackFrame::AbortRecognizedStackFrame(ThreadSP thread_sp, + FileSpec module_spec, + StringRef function_name) { + const uint32_t frames_to_fetch = 5; + const uint32_t last_frame_index = frames_to_fetch - 1; + StackFrameSP prev_frame_sp = nullptr; + + // Fetch most relevant frame + for (uint32_t frame_index = 0; frame_index < frames_to_fetch; frame_index++) { + prev_frame_sp = thread_sp->GetStackFrameAtIndex(frame_index); + + if (!prev_frame_sp) { + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND)); + LLDB_LOG(log, "Abort Recognizer: Hit unwinding bound ({1} frames)!", + frames_to_fetch); + break; + } + + SymbolContext sym_ctx = + prev_frame_sp->GetSymbolContext(eSymbolContextEverything); + + if (sym_ctx.module_sp->GetFileSpec().FileEquals(module_spec) && + sym_ctx.GetFunctionName() == ConstString(function_name)) { + m_most_relevant_frame = thread_sp->GetStackFrameAtIndex( + std::min(frame_index + 1, last_frame_index)); + break; + } + } + + // FIXME: This breaks several tests. + // m_stop_info_sp = StopInfo::CreateStopReasonForRecognizedFrame( + // *thread_sp.get(), "hit program assert"); + + m_stop_info_sp = nullptr; +} + +lldb::RecognizedStackFrameSP +AbortFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame) { + // Check if called with Frame #0 + if (frame->GetFrameIndex()) + return RecognizedStackFrameSP(); + + ThreadSP thread_sp = frame->GetThread(); + ProcessSP process_sp = thread_sp->GetProcess(); + + // Fetch abort location + auto abort_location = GetAbortLocation(process_sp.get()); + + if (!abort_location.hasValue()) + return RecognizedStackFrameSP(); + + FileSpec module_spec; + StringRef function_name; + std::tie(module_spec, function_name) = *abort_location; + + SymbolContext sc = frame->GetSymbolContext(eSymbolContextModule); + + // Check if frame is in abort module + if (!sc.module_sp || + sc.module_sp->GetFileSpec().GetFilename() != module_spec.GetFilename()) + return RecognizedStackFrameSP(); + + // Fetch assert location + auto assert_location = GetAssertLocation(process_sp.get()); + + if (!assert_location.hasValue()) + return RecognizedStackFrameSP(); + + std::tie(module_spec, function_name) = *assert_location; + + // Pass assert location to AbortRecognizedStackFrame to set as most relevant + // frame. + return lldb::RecognizedStackFrameSP( + new AbortRecognizedStackFrame(thread_sp, module_spec, function_name)); +}; + +lldb::StackFrameSP AbortRecognizedStackFrame::GetMostRelevantFrame() { + return m_most_relevant_frame; +} + +lldb::StopInfoSP AbortRecognizedStackFrame::GetStopInfo() { + return m_stop_info_sp; +} + +namespace lldb_private { + +void RegisterAbortRecognizer(Process *process) { + static llvm::once_flag g_once_flag; + llvm::call_once(g_once_flag, [process]() { + auto abort_location = GetAbortLocation(process); + + if (!abort_location.hasValue()) + return; + + FileSpec module_spec; + StringRef function_name; + std::tie(module_spec, function_name) = *abort_location; + + StackFrameRecognizerManager::AddRecognizer( + StackFrameRecognizerSP(new AbortFrameRecognizer()), + module_spec.GetFilename(), ConstString(function_name), false); + }); +} + +llvm::Optional> +GetAbortLocation(Process *process) { + Target &target = process->GetTarget(); + + std::string module_name; + StringRef symbol_name; + + switch (target.GetArchitecture().GetTriple().getOS()) { + case llvm::Triple::Darwin: + case llvm::Triple::MacOSX: + module_name = "libsystem_kernel.dylib"; + symbol_name = "__pthread_kill"; + break; + case llvm::Triple::Linux: + module_name = "libc.so.6"; + symbol_name = "__GI_raise"; + break; + default: + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND)); + LLDB_LOG(log, "Abort Recognizer::GetAbortLocation Unsupported OS"); + return llvm::None; + } + + return std::make_tuple(FileSpec(module_name), symbol_name); +} + +llvm::Optional> +GetAssertLocation(Process *process) { + Target &target = process->GetTarget(); + + std::string module_name; + StringRef symbol_name; + + switch (target.GetArchitecture().GetTriple().getOS()) { + case llvm::Triple::Darwin: + case llvm::Triple::MacOSX: + module_name = "libsystem_c.dylib"; + symbol_name = "__assert_rtn"; + break; + case llvm::Triple::Linux: + module_name = "libc.so.6"; + symbol_name = "__GI___assert_fail"; + break; + default: + Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND)); + LLDB_LOG(log, "Abort Recognizer::GetAbortLocation Unsupported OS"); + return llvm::None; + } + + return std::make_tuple(FileSpec(module_name), symbol_name); +} + +} // namespace lldb_private diff --git a/lldb/source/Target/CMakeLists.txt b/lldb/source/Target/CMakeLists.txt --- a/lldb/source/Target/CMakeLists.txt +++ b/lldb/source/Target/CMakeLists.txt @@ -8,6 +8,7 @@ add_lldb_library(lldbTarget ABI.cpp + AbortRecognizer.cpp ExecutionContext.cpp InstrumentationRuntime.cpp InstrumentationRuntimeStopInfo.cpp diff --git a/lldb/source/Target/Process.cpp b/lldb/source/Target/Process.cpp --- a/lldb/source/Target/Process.cpp +++ b/lldb/source/Target/Process.cpp @@ -38,6 +38,7 @@ #include "lldb/Symbol/Function.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/ABI.h" +#include "lldb/Target/AbortRecognizer.h" #include "lldb/Target/DynamicLoader.h" #include "lldb/Target/InstrumentationRuntime.h" #include "lldb/Target/JITLoader.h" @@ -538,6 +539,8 @@ target_sp->GetPlatform()->GetDefaultMemoryCacheLineSize(); if (!value_sp->OptionWasSet() && platform_cache_line_size != 0) value_sp->SetUInt64Value(platform_cache_line_size); + + RegisterAbortRecognizer(this); } Process::~Process() { @@ -934,11 +937,27 @@ Debugger &debugger = process_sp->GetTarget().GetDebugger(); if (debugger.GetTargetList().GetSelectedTarget().get() == &process_sp->GetTarget()) { + ThreadSP cur_thread = process_sp->GetThreadList().GetSelectedThread(); + + if (!cur_thread || !cur_thread->IsValid()) + return false; + + uint32_t mrf_idx = 0; + + auto frame_sp = cur_thread->GetStackFrameAtIndex(mrf_idx); + + if (RecognizedStackFrameSP recognized_frame_sp = + frame_sp->GetRecognizedFrame()) + if (StackFrameSP mrf_sp = + recognized_frame_sp->GetMostRelevantFrame()) + mrf_idx = mrf_sp->GetFrameIndex(); + const bool only_threads_with_stop_reason = true; - const uint32_t start_frame = 0; + const uint32_t start_frame = mrf_idx; const uint32_t num_frames = 1; const uint32_t num_frames_with_source = 1; const bool stop_format = true; + process_sp->GetStatus(*stream); process_sp->GetThreadStatus(*stream, only_threads_with_stop_reason, start_frame, num_frames, diff --git a/lldb/source/Target/StackFrameRecognizer.cpp b/lldb/source/Target/StackFrameRecognizer.cpp --- a/lldb/source/Target/StackFrameRecognizer.cpp +++ b/lldb/source/Target/StackFrameRecognizer.cpp @@ -92,7 +92,7 @@ StackFrameRecognizerSP GetRecognizerForFrame(StackFrameSP frame) { const SymbolContext &symctx = - frame->GetSymbolContext(eSymbolContextModule | eSymbolContextFunction); + frame->GetSymbolContext(eSymbolContextEverything); ConstString function_name = symctx.GetFunctionName(); ModuleSP module_sp = symctx.module_sp; if (!module_sp) return StackFrameRecognizerSP(); diff --git a/lldb/source/Target/StopInfo.cpp b/lldb/source/Target/StopInfo.cpp --- a/lldb/source/Target/StopInfo.cpp +++ b/lldb/source/Target/StopInfo.cpp @@ -1016,6 +1016,23 @@ } }; +// StopInfoRecognizedFrame + +class StopInfoRecognizedFrame : public StopInfoException { +public: + StopInfoRecognizedFrame(Thread &thread, const char *description) + : StopInfoException(thread, description) {} + + ~StopInfoRecognizedFrame() override = default; + + const char *GetDescription() override { + if (m_description.empty()) + return "recognized frame:"; + else + return m_description.c_str(); + } +}; + // StopInfoThreadPlan class StopInfoThreadPlan : public StopInfo { @@ -1133,6 +1150,12 @@ return StopInfoSP(new StopInfoException(thread, description)); } +StopInfoSP +StopInfo::CreateStopReasonForRecognizedFrame(Thread &thread, + const char *description) { + return StopInfoSP(new StopInfoRecognizedFrame(thread, description)); +} + StopInfoSP StopInfo::CreateStopReasonWithExec(Thread &thread) { return StopInfoSP(new StopInfoExec(thread)); } diff --git a/lldb/source/Target/Thread.cpp b/lldb/source/Target/Thread.cpp --- a/lldb/source/Target/Thread.cpp +++ b/lldb/source/Target/Thread.cpp @@ -573,9 +573,41 @@ m_state = state; } +void Thread::SelectMostRelevantFrame() { + if (!m_curr_frames_sp) + return; + + auto cur_frame = m_curr_frames_sp->GetFrameAtIndex(0); + + auto recognized_frame_sp = cur_frame->GetRecognizedFrame(); + + if (!recognized_frame_sp) + return; + + Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_TARGET); + + if (StackFrameSP most_relevant_frame_sp = + recognized_frame_sp->GetMostRelevantFrame()) { + LLDB_LOG(log, "Found most relevant frame {0}", + most_relevant_frame_sp->GetFrameIndex()); + SetSelectedFrame(most_relevant_frame_sp.get()); + } else { + LLDB_LOG(log, "No relevant frame!"); + } + + if (StopInfoSP stop_info_sp = recognized_frame_sp->GetStopInfo()) { + LLDB_LOG(log, "Found stop reason {0}", stop_info_sp->GetStopReason()); + SetStopInfo(stop_info_sp); + } else { + LLDB_LOG(log, "No stop reason for recognized frame!"); + } +} + void Thread::WillStop() { ThreadPlan *current_plan = GetCurrentPlan(); + SelectMostRelevantFrame(); + // FIXME: I may decide to disallow threads with no plans. In which // case this should go to an assert. diff --git a/lldb/test/Shell/Recognizer/Inputs/abort.c b/lldb/test/Shell/Recognizer/Inputs/abort.c new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/Recognizer/Inputs/abort.c @@ -0,0 +1,9 @@ +#include + +int main() { + int a = 42; + assert(a == 42); + a--; + assert(a == 42); + return 0; +} diff --git a/lldb/test/Shell/Recognizer/abort.test b/lldb/test/Shell/Recognizer/abort.test new file mode 100644 --- /dev/null +++ b/lldb/test/Shell/Recognizer/abort.test @@ -0,0 +1,10 @@ +# UNSUPPORTED: system-windows +# RUN: %clang_host -g -O0 %S/Inputs/abort.c -o %t.out +# RUN: %lldb -b -s %s %t.out | FileCheck %s +run +# CHECK: thread #{{.*}}stop reason = signal SIGABRT +frame info +# CHECK: frame #4: {{.*}}`main at abort.c +frame recognizer info 0 +# CHECK: frame 0 is recognized by Abort StackFrame Recognizer +q