[lldb/Target] Add Assert StackFrame Recognizer

When a thread stops, this checks depending on the platform if the top frame is
an abort stack frame. If so, it looks for an assert stack frame in the upper
frames and set it as the most relavant frame when found.

To do so, the StackFrameRecognizer class holds a "Most Relevant Frame" and a
"cooked" stop reason description. When the thread is about to stop, it checks
if the current frame is recognized, and if so, it fetches the recognized frame's
attributes and applies them.

rdar://58528686

Differential Revision: https://reviews.llvm.org/D73303

Signed-off-by: Med Ismail Bennani <medismail.bennani@gmail.com>
This commit is contained in:
Med Ismail Bennani 2020-02-04 17:53:26 +01:00
parent 3627c91ead
commit 2b7f32892b
17 changed files with 400 additions and 26 deletions

View File

@ -134,7 +134,9 @@ A complete list of currently supported format string variables is listed below:
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``thread.queue`` | The queue name of the thread if the target OS supports dispatch queues |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``thread.stop-reason`` | A textual reason each thread stopped |
| ``thread.stop-reason`` | A textual reason why the thread stopped. If the thread have a recognized frame, this displays its recognized stop reason. Otherwise, gets the stop info description. |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``thread.stop-reason-raw`` | A textual reason why the thread stopped. Always returns stop info description. |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| ``thread.return-value`` | The return value of the latest step operation (currently only for step-out.) |
+---------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

View File

@ -61,6 +61,7 @@ public:
ThreadName,
ThreadQueue,
ThreadStopReason,
ThreadStopReasonRaw,
ThreadReturnValue,
ThreadCompletedExpression,
ScriptThread,

View File

@ -0,0 +1,54 @@
//===-- AssertFrameRecognizer.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_AssertFrameRecognizer_h_
#define liblldb_AssertFrameRecognizer_h_
#include "lldb/Target/Process.h"
#include "lldb/Target/StackFrameRecognizer.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/FileSpec.h"
#include <tuple>
namespace lldb_private {
/// Registers the assert stack frame recognizer.
///
/// \param[in] process
/// The process that is currently asserting. This will give us information on
/// the target and the platform.
void RegisterAssertFrameRecognizer(Process *process);
/// \class AssertRecognizedStackFrame
///
/// Holds the stack frame where the assert is called from.
class AssertRecognizedStackFrame : public RecognizedStackFrame {
public:
AssertRecognizedStackFrame(lldb::StackFrameSP most_relevant_frame_sp);
lldb::StackFrameSP GetMostRelevantFrame() override;
private:
lldb::StackFrameSP m_most_relevant_frame;
};
/// \class AssertFrameRecognizer
///
/// When a thread stops, it checks depending on the platform if the top frame is
/// an abort stack frame. If so, it looks for an assert stack frame in the upper
/// frames and set it as the most relavant frame when found.
class AssertFrameRecognizer : public StackFrameRecognizer {
public:
std::string GetName() override { return "Assert StackFrame Recognizer"; }
lldb::RecognizedStackFrameSP
RecognizeFrame(lldb::StackFrameSP frame_sp) override;
};
} // namespace lldb_private
#endif // liblldb_AssertFrameRecognizer_h_

View File

@ -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,10 +34,14 @@ public:
virtual lldb::ValueObjectSP GetExceptionObject() {
return lldb::ValueObjectSP();
}
virtual lldb::StackFrameSP GetMostRelevantFrame() { return nullptr; };
virtual ~RecognizedStackFrame(){};
std::string GetStopDescription() { return m_stop_desc; }
protected:
lldb::ValueObjectListSP m_arguments;
std::string m_stop_desc;
};
/// \class StackFrameRecognizer

View File

@ -216,6 +216,12 @@ public:
virtual void RefreshStateAfterStop() = 0;
void SelectMostRelevantFrame();
std::string GetStopDescription();
std::string GetStopDescriptionRaw();
void WillStop();
bool ShouldStop(Event *event_ptr);

View File

@ -107,16 +107,22 @@ class AssertingInferiorTestCase(TestBase):
if matched:
# On android until API-16 the abort() call ended in a sigsegv
# instead of in a sigabrt
stop_reason = 'stop reason = signal SIGSEGV'
stop_reason = 'signal SIGSEGV'
else:
stop_reason = 'stop reason = signal SIGABRT'
stop_reason = 'signal SIGABRT'
target = self.dbg.GetTargetAtIndex(0)
process = target.GetProcess()
thread = lldbutil.get_stopped_thread(process, lldb.eStopReasonSignal)
pattern = "stop reason = (" + stop_reason + "|hit program assert)"
# The stop reason of the thread should be an abort signal or exception.
self.expect("thread list", STOPPED_DUE_TO_ASSERT,
substrs=['stopped',
stop_reason])
patterns=[pattern],
substrs=['stopped'])
return stop_reason
return pattern
def setUp(self):
# Call super's setUp().
@ -134,12 +140,13 @@ class AssertingInferiorTestCase(TestBase):
# And it should report a backtrace that includes the assert site.
self.expect("thread backtrace all",
substrs=[stop_reason, 'main', 'argc', 'argv'])
patterns=[stop_reason],
substrs=['main', 'argc', 'argv'])
# And it should report the correct line number.
self.expect("thread backtrace all",
substrs=[stop_reason,
'main.c:%d' % self.line])
patterns=[stop_reason],
substrs=['main.c:%d' % self.line])
def inferior_asserting_python(self):
"""Inferior asserts upon launching; lldb should catch the event and stop."""
@ -173,6 +180,10 @@ class AssertingInferiorTestCase(TestBase):
self.runCmd("run", RUN_SUCCEEDED)
self.check_stop_reason()
# change current frame to frame 0, since recognizer might have selected
# different frame.
self.runCmd("frame select 0", RUN_SUCCEEDED)
# lldb should be able to read from registers from the inferior after
# asserting.
lldbplatformutil.check_first_register_readable(self)
@ -311,5 +322,5 @@ class AssertingInferiorTestCase(TestBase):
# And it should report the correct line number.
self.expect("thread backtrace all",
substrs=[stop_reason,
'main.c:%d' % self.line])
patterns=[stop_reason],
substrs=['main.c:%d' % self.line])

View File

@ -25,8 +25,8 @@ class ObjCExceptionsTestCase(TestBase):
launch_info = lldb.SBLaunchInfo(["a.out", "0"])
lldbutil.run_to_name_breakpoint(self, "objc_exception_throw", launch_info=launch_info)
self.expect("thread list", STOPPED_DUE_TO_BREAKPOINT,
substrs=['stopped', 'stop reason = breakpoint'])
self.expect("thread list",
substrs=['stopped', 'stop reason = hit Objective-C exception'])
self.expect('thread exception', substrs=[
'(NSException *) exception = ',

View File

@ -325,7 +325,8 @@ size_t SBThread::GetStopDescription(char *dst, size_t dst_len) {
StopInfoSP stop_info_sp = exe_ctx.GetThreadPtr()->GetStopInfo();
if (stop_info_sp) {
const char *stop_desc = stop_info_sp->GetDescription();
const char *stop_desc =
exe_ctx.GetThreadPtr()->GetStopDescription().data();
if (stop_desc) {
if (dst)
return ::snprintf(dst, dst_len, "%s", stop_desc);

View File

@ -166,6 +166,7 @@ static FormatEntity::Entry::Definition g_thread_child_entries[] = {
ENTRY("queue", ThreadQueue),
ENTRY("name", ThreadName),
ENTRY("stop-reason", ThreadStopReason),
ENTRY("stop-reason-raw", ThreadStopReasonRaw),
ENTRY("return-value", ThreadReturnValue),
ENTRY("completed-expression", ThreadCompletedExpression),
};
@ -328,6 +329,7 @@ const char *FormatEntity::Entry::TypeToCString(Type t) {
ENUM_TO_CSTR(ThreadName);
ENUM_TO_CSTR(ThreadQueue);
ENUM_TO_CSTR(ThreadStopReason);
ENUM_TO_CSTR(ThreadStopReasonRaw);
ENUM_TO_CSTR(ThreadReturnValue);
ENUM_TO_CSTR(ThreadCompletedExpression);
ENUM_TO_CSTR(ScriptThread);
@ -1273,15 +1275,23 @@ bool FormatEntity::Format(const Entry &entry, Stream &s,
case Entry::Type::ThreadStopReason:
if (exe_ctx) {
Thread *thread = exe_ctx->GetThreadPtr();
if (thread) {
StopInfoSP stop_info_sp = thread->GetStopInfo();
if (stop_info_sp && stop_info_sp->IsValid()) {
const char *cstr = stop_info_sp->GetDescription();
if (cstr && cstr[0]) {
s.PutCString(cstr);
return true;
}
if (Thread *thread = exe_ctx->GetThreadPtr()) {
std::string stop_description = thread->GetStopDescription();
if (!stop_description.empty()) {
s.PutCString(stop_description);
return true;
}
}
}
return false;
case Entry::Type::ThreadStopReasonRaw:
if (exe_ctx) {
if (Thread *thread = exe_ctx->GetThreadPtr()) {
std::string stop_description = thread->GetStopDescriptionRaw();
if (!stop_description.empty()) {
s.PutCString(stop_description);
return true;
}
}
}

View File

@ -2666,6 +2666,8 @@ class ObjCExceptionRecognizedStackFrame : public RecognizedStackFrame {
m_arguments = ValueObjectListSP(new ValueObjectList());
m_arguments->Append(exception);
m_stop_desc = "hit Objective-C exception";
}
ValueObjectSP exception;

View File

@ -0,0 +1,195 @@
#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/AssertFrameRecognizer.h"
using namespace llvm;
using namespace lldb;
using namespace lldb_private;
namespace lldb_private {
/// Checkes if the module containing a symbol has debug info.
///
/// \param[in] target
/// The target containing the module.
/// \param[in] module_spec
/// The module spec that should contain the symbol.
/// \param[in] symbol_name
/// The symbol's name that should be contained in the debug info.
/// \return
/// If \b true the symbol was found, \b false otherwise.
bool ModuleHasDebugInfo(Target &target, FileSpec &module_spec,
StringRef symbol_name) {
ModuleSP module_sp = target.GetImages().FindFirstModule(module_spec);
if (!module_sp)
return false;
return module_sp->FindFirstSymbolWithNameAndType(ConstString(symbol_name));
}
/// Fetches the abort frame location depending on the current platform.
///
/// \param[in] process_sp
/// The process that is currently aborting. This will give us information on
/// the target and the platform.
/// \return
/// If the platform is supported, returns an optional tuple containing
/// the abort module as a \a FileSpec and the symbol name as a \a StringRef.
/// Otherwise, returns \a llvm::None.
llvm::Optional<std::tuple<FileSpec, StringRef>>
GetAbortLocation(Process *process) {
Target &target = process->GetTarget();
FileSpec module_spec;
StringRef symbol_name;
switch (target.GetArchitecture().GetTriple().getOS()) {
case llvm::Triple::Darwin:
case llvm::Triple::MacOSX:
module_spec = FileSpec("libsystem_kernel.dylib");
symbol_name = "__pthread_kill";
break;
case llvm::Triple::Linux:
module_spec = FileSpec("libc.so.6");
symbol_name = "__GI_raise";
if (!ModuleHasDebugInfo(target, module_spec, symbol_name))
symbol_name = "raise";
break;
default:
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND));
LLDB_LOG(log, "AssertFrameRecognizer::GetAbortLocation Unsupported OS");
return llvm::None;
}
return std::make_tuple(module_spec, symbol_name);
}
/// Fetches the assert frame location depending on the current platform.
///
/// \param[in] process_sp
/// The process that is currently asserting. This will give us information on
/// the target and the platform.
/// \return
/// If the platform is supported, returns an optional tuple containing
/// the asserting frame module as a \a FileSpec and the symbol name as a \a
/// StringRef.
/// Otherwise, returns \a llvm::None.
llvm::Optional<std::tuple<FileSpec, StringRef>>
GetAssertLocation(Process *process) {
Target &target = process->GetTarget();
FileSpec module_spec;
StringRef symbol_name;
switch (target.GetArchitecture().GetTriple().getOS()) {
case llvm::Triple::Darwin:
case llvm::Triple::MacOSX:
module_spec = FileSpec("libsystem_c.dylib");
symbol_name = "__assert_rtn";
break;
case llvm::Triple::Linux:
module_spec = FileSpec("libc.so.6");
symbol_name = "__GI___assert_fail";
if (!ModuleHasDebugInfo(target, module_spec, symbol_name))
symbol_name = "__assert_fail";
break;
default:
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_UNWIND));
LLDB_LOG(log, "AssertFrameRecognizer::GetAssertLocation Unsupported OS");
return llvm::None;
}
return std::make_tuple(module_spec, symbol_name);
}
void RegisterAssertFrameRecognizer(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 AssertFrameRecognizer()),
module_spec.GetFilename(), ConstString(function_name), false);
});
}
} // namespace lldb_private
lldb::RecognizedStackFrameSP
AssertFrameRecognizer::RecognizeFrame(lldb::StackFrameSP frame_sp) {
ThreadSP thread_sp = frame_sp->GetThread();
ProcessSP process_sp = thread_sp->GetProcess();
auto assert_location = GetAssertLocation(process_sp.get());
if (!assert_location.hasValue())
return RecognizedStackFrameSP();
FileSpec module_spec;
StringRef function_name;
std::tie(module_spec, function_name) = *assert_location;
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)) {
// We go a frame beyond the assert location because the most relevant
// frame for the user is the one in which the assert function was called.
// If the assert location is the last frame fetched, then it is set as
// the most relevant frame.
StackFrameSP most_relevant_frame_sp = thread_sp->GetStackFrameAtIndex(
std::min(frame_index + 1, last_frame_index));
// Pass assert location to AbortRecognizedStackFrame to set as most
// relevant frame.
return lldb::RecognizedStackFrameSP(
new AssertRecognizedStackFrame(most_relevant_frame_sp));
}
}
return RecognizedStackFrameSP();
};
AssertRecognizedStackFrame::AssertRecognizedStackFrame(
StackFrameSP most_relevant_frame_sp)
: m_most_relevant_frame(most_relevant_frame_sp) {
m_stop_desc = "hit program assert";
}
lldb::StackFrameSP AssertRecognizedStackFrame::GetMostRelevantFrame() {
return m_most_relevant_frame;
}

View File

@ -8,6 +8,7 @@ lldb_tablegen(TargetPropertiesEnum.inc -gen-lldb-property-enum-defs
add_lldb_library(lldbTarget
ABI.cpp
AssertFrameRecognizer.cpp
ExecutionContext.cpp
InstrumentationRuntime.cpp
InstrumentationRuntimeStopInfo.cpp

View File

@ -38,6 +38,7 @@
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Target/ABI.h"
#include "lldb/Target/AssertFrameRecognizer.h"
#include "lldb/Target/DynamicLoader.h"
#include "lldb/Target/InstrumentationRuntime.h"
#include "lldb/Target/JITLoader.h"
@ -538,6 +539,8 @@ Process::Process(lldb::TargetSP target_sp, ListenerSP listener_sp,
target_sp->GetPlatform()->GetDefaultMemoryCacheLineSize();
if (!value_sp->OptionWasSet() && platform_cache_line_size != 0)
value_sp->SetUInt64Value(platform_cache_line_size);
RegisterAssertFrameRecognizer(this);
}
Process::~Process() {
@ -934,11 +937,17 @@ bool Process::HandleProcessStateChangedEvent(const EventSP &event_sp,
Debugger &debugger = process_sp->GetTarget().GetDebugger();
if (debugger.GetTargetList().GetSelectedTarget().get() ==
&process_sp->GetTarget()) {
ThreadSP thread_sp = process_sp->GetThreadList().GetSelectedThread();
if (!thread_sp || !thread_sp->IsValid())
return false;
const bool only_threads_with_stop_reason = true;
const uint32_t start_frame = 0;
const uint32_t start_frame = thread_sp->GetSelectedFrameIndex();
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,

View File

@ -92,8 +92,8 @@ public:
}
StackFrameRecognizerSP GetRecognizerForFrame(StackFrameSP frame) {
const SymbolContext &symctx =
frame->GetSymbolContext(eSymbolContextModule | eSymbolContextFunction);
const SymbolContext &symctx = frame->GetSymbolContext(
eSymbolContextModule | eSymbolContextFunction | eSymbolContextSymbol);
ConstString function_name = symctx.GetFunctionName();
ModuleSP module_sp = symctx.module_sp;
if (!module_sp) return StackFrameRecognizerSP();

View File

@ -573,9 +573,64 @@ void Thread::SetState(StateType state) {
m_state = state;
}
std::string Thread::GetStopDescription() {
StackFrameSP frame_sp = GetStackFrameAtIndex(0);
if (!frame_sp)
return GetStopDescriptionRaw();
auto recognized_frame_sp = frame_sp->GetRecognizedFrame();
if (!recognized_frame_sp)
return GetStopDescriptionRaw();
std::string recognized_stop_description =
recognized_frame_sp->GetStopDescription();
if (!recognized_stop_description.empty())
return recognized_stop_description;
return GetStopDescriptionRaw();
}
std::string Thread::GetStopDescriptionRaw() {
StopInfoSP stop_info_sp = GetStopInfo();
std::string raw_stop_description;
if (stop_info_sp && stop_info_sp->IsValid())
raw_stop_description = stop_info_sp->GetDescription();
return raw_stop_description;
}
void Thread::SelectMostRelevantFrame() {
Log *log = lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_THREAD);
auto frames_list_sp = GetStackFrameList();
// Only the top frame should be recognized.
auto frame_sp = frames_list_sp->GetFrameAtIndex(0);
auto recognized_frame_sp = frame_sp->GetRecognizedFrame();
if (!recognized_frame_sp) {
LLDB_LOG(log, "Frame #0 not recognized");
return;
}
if (StackFrameSP most_relevant_frame_sp =
recognized_frame_sp->GetMostRelevantFrame()) {
LLDB_LOG(log, "Found most relevant frame at index {0}",
most_relevant_frame_sp->GetFrameIndex());
SetSelectedFrame(most_relevant_frame_sp.get());
} else {
LLDB_LOG(log, "No relevant 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.

View File

@ -0,0 +1,9 @@
#include <assert.h>
int main() {
int a = 42;
assert(a == 42);
a--;
assert(a == 42);
return 0;
}

View File

@ -0,0 +1,13 @@
# UNSUPPORTED: system-windows
# RUN: %clang_host -g -O0 %S/Inputs/assert.c -o %t.out
# RUN: %lldb -b -s %s %t.out | FileCheck %s
run
# CHECK: thread #{{.*}}stop reason = hit program assert
frame info
# CHECK: frame #{{.*}}`main at assert.c
frame recognizer info 0
# CHECK: frame 0 is recognized by Assert StackFrame Recognizer
set set thread-format "{${thread.stop-reason-raw}}\n"
thread info
# CHECK: signal SIGABRT
q