Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""
Test the output of `frame diagnose` for dereferencing a function's return value
"""

from __future__ import print_function

import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

class TestDiagnoseDereferenceFunctionReturn(TestBase):
mydir = TestBase.compute_mydir(__file__)

@skipUnlessDarwin
def test_diagnose_dereference_function_return(self):
TestBase.setUp(self)
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
self.runCmd("run", RUN_SUCCEEDED)
self.expect("thread list", "Thread should be stopped",
substrs = ['stopped'])
self.expect("frame diagnose", "Crash diagnosis was accurate", substrs = ["GetAFoo", "->b"])

Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
struct Foo {
int a;
int b;
};

struct Foo *GetAFoo() {
return 0;
}

int main() {
return GetAFoo()->b;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LEVEL = ../../../make

CXX_SOURCES := main.cpp

include $(LEVEL)/Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Test the output of `frame diagnose` for dereferencing `this`
"""

from __future__ import print_function

import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

class TestDiagnoseDereferenceThis(TestBase):
mydir = TestBase.compute_mydir(__file__)

@skipUnlessDarwin
def test_diagnose_dereference_this(self):
TestBase.setUp(self)
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
self.runCmd("run", RUN_SUCCEEDED)
self.expect("thread list", "Thread should be stopped",
substrs = ['stopped'])
self.expect("frame diagnose", "Crash diagnosis was accurate", "this->a")
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
struct Foo {
int a;
int b;
int Sum() { return a + b; }
};

struct Foo *GetAFoo() {
return (struct Foo*)0;
}

int main() {
struct Foo *foo = GetAFoo();
return foo->Sum();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LEVEL = ../../../make

CXX_SOURCES := main.cpp

include $(LEVEL)/Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Test the output of `frame diagnose` for calling virtual methods
"""

from __future__ import print_function

import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

class TestDiagnoseInheritance(TestBase):
mydir = TestBase.compute_mydir(__file__)

@skipUnlessDarwin
def test_diagnose_inheritance(self):
TestBase.setUp(self)
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
self.runCmd("run", RUN_SUCCEEDED)
self.expect("thread list", "Thread should be stopped",
substrs = ['stopped'])
self.expect("frame diagnose", "Crash diagnosis was accurate", "d")
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <stdio.h>
#include <stdint.h>

class A
{
public:
A(int a) :
m_a(a)
{
}
virtual ~A(){}
virtual int get2() const { return m_a; }
virtual int get() const { return m_a; }
protected:
int m_a;
};

class B : public A
{
public:
B(int a, int b) :
A(a),
m_b(b)
{
}

~B() override
{
}

int get2() const override
{
return m_b;
}
int get() const override
{
return m_b;
}

protected:
int m_b;
};

struct C
{
C(int c) : m_c(c){}
virtual ~C(){}
int m_c;
};

class D : public C, public B
{
public:
D(int a, int b, int c, int d) :
C(c),
B(a, b),
m_d(d)
{
}
protected:
int m_d;
};
int main (int argc, char const *argv[], char const *envp[])
{
D *good_d = new D(1, 2, 3, 4);
D *d = nullptr;
return d->get();
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LEVEL = ../../../make

C_SOURCES := main.c

include $(LEVEL)/Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Test the output of `frame diagnose` for dereferencing a local variable
"""

from __future__ import print_function

import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

class TestLocalVariable(TestBase):
mydir = TestBase.compute_mydir(__file__)

@skipUnlessDarwin
def test_local_variable(self):
TestBase.setUp(self)
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
self.runCmd("run", RUN_SUCCEEDED)
self.expect("thread list", "Thread should be stopped",
substrs = ['stopped'])
self.expect("frame diagnose", "Crash diagnosis was accurate", "myInt")
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
int main() {
int *myInt = 0;
return *myInt;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LEVEL = ../../../make

CXX_SOURCES := main.cpp

include $(LEVEL)/Makefile.rules
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""
Test the output of `frame diagnose` for calling virtual methods
"""

from __future__ import print_function

import os
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil

class TestDiagnoseVirtualMethodCall(TestBase):
mydir = TestBase.compute_mydir(__file__)

@skipUnlessDarwin
def test_diagnose_virtual_method_call(self):
TestBase.setUp(self)
self.build()
exe = os.path.join(os.getcwd(), "a.out")
self.runCmd("file " + exe, CURRENT_EXECUTABLE_SET)
self.runCmd("run", RUN_SUCCEEDED)
self.expect("thread list", "Thread should be stopped",
substrs = ['stopped'])
self.expect("frame diagnose", "Crash diagnosis was accurate", "foo")
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Foo {
public:
int a;
int b;
virtual int Sum() { return a + b; }
};

struct Foo *GetAFoo() {
return (struct Foo*)0;
}

int main() {
struct Foo *foo = GetAFoo();
return foo->Sum();
}

203 changes: 203 additions & 0 deletions lldb/source/Commands/CommandObjectFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,214 @@
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/Process.h"
#include "lldb/Target/StackFrame.h"
#include "lldb/Target/StopInfo.h"
#include "lldb/Target/Thread.h"
#include "lldb/Target/Target.h"
#include "lldb/Utility/LLDBAssert.h"

using namespace lldb;
using namespace lldb_private;

#pragma mark CommandObjectFrameDiagnose

//-------------------------------------------------------------------------
// CommandObjectFrameInfo
//-------------------------------------------------------------------------

//-------------------------------------------------------------------------
// CommandObjectFrameDiagnose
//-------------------------------------------------------------------------

class CommandObjectFrameDiagnose : public CommandObjectParsed
{
public:
class CommandOptions : public Options
{
public:
CommandOptions() :
Options()
{
OptionParsingStarting(nullptr);
}

~CommandOptions() override = default;

Error
SetOptionValue(uint32_t option_idx, const char *option_arg,
ExecutionContext *execution_context) override
{
Error error;
const int short_option = m_getopt_table[option_idx].val;
switch (short_option)
{
case 'r':
reg = ConstString(option_arg);
break;

case 'a':
{
bool success = false;

address = StringConvert::ToUInt64 (option_arg, 0, 0, &success);
if (!success)
{
address.reset();
error.SetErrorStringWithFormat ("invalid address argument '%s'", option_arg);
}
}
break;

case 'o':
{
bool success = false;

offset = StringConvert::ToSInt64 (option_arg, 0, 0, &success);
if (!success)
{
offset.reset();
error.SetErrorStringWithFormat ("invalid offset argument '%s'", option_arg);
}
}
break;

default:
error.SetErrorStringWithFormat ("invalid short option character '%c'", short_option);
break;
}

return error;
}

void
OptionParsingStarting(ExecutionContext *execution_context) override
{
address.reset();
reg.reset();
offset.reset();
}

const OptionDefinition*
GetDefinitions () override
{
return g_option_table;
}

// Options table: Required for subclasses of Options.
static OptionDefinition g_option_table[];

// Options.
llvm::Optional<lldb::addr_t> address;
llvm::Optional<ConstString> reg;
llvm::Optional<int64_t> offset;
};

CommandObjectFrameDiagnose(CommandInterpreter &interpreter)
: CommandObjectParsed(interpreter, "frame diagnose",
"Try to determine what path path the current stop location used to get to a register or address",
nullptr, eCommandRequiresThread | eCommandTryTargetAPILock | eCommandProcessMustBeLaunched |
eCommandProcessMustBePaused),
m_options()
{
CommandArgumentEntry arg;
CommandArgumentData index_arg;

// Define the first (and only) variant of this arg.
index_arg.arg_type = eArgTypeFrameIndex;
index_arg.arg_repetition = eArgRepeatOptional;

// There is only one variant this argument could be; put it into the argument entry.
arg.push_back (index_arg);

// Push the data for the first argument into the m_arguments vector.
m_arguments.push_back (arg);
}

~CommandObjectFrameDiagnose() override = default;

Options *
GetOptions () override
{
return &m_options;
}

protected:
bool
DoExecute (Args& command, CommandReturnObject &result) override
{
Thread *thread = m_exe_ctx.GetThreadPtr();
StackFrameSP frame_sp = thread->GetSelectedFrame();

ValueObjectSP valobj_sp;

if (m_options.address.hasValue())
{
if (m_options.reg.hasValue() || m_options.offset.hasValue())
{
result.AppendError("`frame diagnose --address` is incompatible with other arguments.");
result.SetStatus(eReturnStatusFailed);
return false;
}
valobj_sp = frame_sp->GuessValueForAddress(m_options.address.getValue());
}
else if (m_options.reg.hasValue())
{
valobj_sp = frame_sp->GuessValueForRegisterAndOffset(m_options.reg.getValue(), m_options.offset.getValueOr(0));
}
else
{
StopInfoSP stop_info_sp = thread->GetStopInfo();
if (!stop_info_sp)
{
result.AppendError("No arguments provided, and no stop info.");
result.SetStatus(eReturnStatusFailed);
return false;
}

valobj_sp = StopInfo::GetCrashingDereference(stop_info_sp);
}

if (!valobj_sp)
{
result.AppendError("No diagnosis available.");
result.SetStatus(eReturnStatusFailed);
return false;
}

const bool qualify_cxx_base_classes = false;

DumpValueObjectOptions::DeclPrintingHelper helper = [&valobj_sp](ConstString type,
ConstString var,
const DumpValueObjectOptions &opts,
Stream &stream) -> bool {
const ValueObject::GetExpressionPathFormat format = ValueObject::GetExpressionPathFormat::eGetExpressionPathFormatHonorPointers;
valobj_sp->GetExpressionPath(stream, qualify_cxx_base_classes, format);
stream.PutCString(" =");
return true;
};

DumpValueObjectOptions options;
options.SetDeclPrintingHelper(helper);
ValueObjectPrinter printer(valobj_sp.get(), &result.GetOutputStream(), options);
printer.PrintValueObject();

return true;
}

protected:
CommandOptions m_options;
};

OptionDefinition
CommandObjectFrameDiagnose::CommandOptions::g_option_table[] =
{
// clang-format off
{LLDB_OPT_SET_1, false, "register", 'r', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeRegisterName, "A register to diagnose."},
{LLDB_OPT_SET_1, false, "address", 'a', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeAddress, "An address to diagnose."},
{LLDB_OPT_SET_1, false, "offset", 'o', OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgTypeOffset, "An optional offset. Requires --register."},
{0, false, nullptr, 0, 0, nullptr, nullptr, 0, eArgTypeNone, nullptr}
// clang-format on
};

#pragma mark CommandObjectFrameInfo

//-------------------------------------------------------------------------
Expand Down Expand Up @@ -612,6 +814,7 @@ CommandObjectMultiwordFrame::CommandObjectMultiwordFrame(CommandInterpreter &int
"Commands for selecting and examing the current thread's stack frames.",
"frame <subcommand> [<subcommand-options>]")
{
LoadSubCommand ("diagnose", CommandObjectSP (new CommandObjectFrameDiagnose (interpreter)));
LoadSubCommand ("info", CommandObjectSP (new CommandObjectFrameInfo (interpreter)));
LoadSubCommand ("select", CommandObjectSP (new CommandObjectFrameSelect (interpreter)));
LoadSubCommand ("variable", CommandObjectSP (new CommandObjectFrameVariable (interpreter)));
Expand Down
148 changes: 148 additions & 0 deletions lldb/source/Expression/DWARFExpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
#include "lldb/Host/Endian.h"
#include "lldb/Host/Host.h"

#include "lldb/Symbol/Function.h"

#include "lldb/Target/ABI.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Process.h"
Expand Down Expand Up @@ -3336,3 +3338,149 @@ DWARFExpression::PrintDWARFLocationList(Stream &s,
offset += loc_length;
}
}

bool
DWARFExpression::GetOpAndEndOffsets(StackFrame &frame, lldb::offset_t &op_offset, lldb::offset_t &end_offset)
{
SymbolContext sc = frame.GetSymbolContext(eSymbolContextFunction);
if (!sc.function)
{
return false;
}

addr_t loclist_base_file_addr = sc.function->GetAddressRange().GetBaseAddress().GetFileAddress();
if (loclist_base_file_addr == LLDB_INVALID_ADDRESS)
{
return false;
}

addr_t pc_file_addr = frame.GetFrameCodeAddress().GetFileAddress();
lldb::offset_t opcodes_offset, opcodes_length;
if (!GetLocation(loclist_base_file_addr, pc_file_addr, opcodes_offset, opcodes_length))
{
return false;
}

if (opcodes_length == 0)
{
return false;
}

op_offset = opcodes_offset;
end_offset = opcodes_offset + opcodes_length;
return true;
}

bool
DWARFExpression::IsRegister(StackFrame &frame,
const RegisterInfo *&register_info)
{
lldb::offset_t op_offset;
lldb::offset_t end_offset;
if (!GetOpAndEndOffsets(frame, op_offset, end_offset))
{
return false;
}

if (!m_data.ValidOffset(op_offset) || op_offset >= end_offset)
{
return false;
}

RegisterContextSP reg_ctx_sp = frame.GetRegisterContext();
if (!reg_ctx_sp)
{
return false;
}

DataExtractor opcodes = m_data;
uint8_t opcode = opcodes.GetU8(&op_offset);

if (opcode >= DW_OP_reg0 && opcode <= DW_OP_breg31)
{
register_info = reg_ctx_sp->GetRegisterInfo(m_reg_kind, opcode - DW_OP_reg0);
return register_info != nullptr;
}
switch (opcode)
{
default:
return false;
case DW_OP_regx:
{
uint32_t reg_num = m_data.GetULEB128(&op_offset);
register_info = reg_ctx_sp->GetRegisterInfo(m_reg_kind, reg_num);
return register_info != nullptr;
}
}
}

bool
DWARFExpression::IsDereferenceOfRegister(StackFrame &frame,
const RegisterInfo *&register_info,
int64_t &offset)
{
lldb::offset_t op_offset;
lldb::offset_t end_offset;
if (!GetOpAndEndOffsets(frame, op_offset, end_offset))
{
return false;
}

if (!m_data.ValidOffset(op_offset) || op_offset >= end_offset)
{
return false;
}

RegisterContextSP reg_ctx_sp = frame.GetRegisterContext();
if (!reg_ctx_sp)
{
return false;
}

DataExtractor opcodes = m_data;
uint8_t opcode = opcodes.GetU8(&op_offset);

switch (opcode)
{
default:
return false;
case DW_OP_bregx:
{
uint32_t reg_num = static_cast<uint32_t>(opcodes.GetULEB128(&op_offset));
int64_t breg_offset = opcodes.GetSLEB128(&op_offset);

const RegisterInfo *reg_info = reg_ctx_sp->GetRegisterInfo(m_reg_kind, reg_num);
if (!reg_info)
{
return false;
}

register_info = reg_info;
offset = breg_offset;
return true;
}
case DW_OP_fbreg:
{
int64_t fbreg_offset = opcodes.GetSLEB128(&op_offset);

DWARFExpression *dwarf_expression = frame.GetFrameBaseExpression(nullptr);

if (!dwarf_expression)
{
return false;
}

const RegisterInfo *fbr_info;

if (!dwarf_expression->IsRegister(frame, fbr_info))
{
return false;
}

register_info = fbr_info;
offset = fbreg_offset;
return true;
}
}
}

7 changes: 7 additions & 0 deletions lldb/source/Plugins/ABI/SysV-arm64/ABISysV_arm64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,13 @@ ABISysV_arm64::GetRegisterInfoArray (uint32_t &count)
return g_register_infos;
}

bool
ABISysV_arm64::GetPointerReturnRegister (const char *&name)
{
name = "x0";
return true;
}

size_t
ABISysV_arm64::GetRedZoneSize () const
{
Expand Down
3 changes: 3 additions & 0 deletions lldb/source/Plugins/ABI/SysV-arm64/ABISysV_arm64.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ class ABISysV_arm64 : public lldb_private::ABI

const lldb_private::RegisterInfo *
GetRegisterInfoArray (uint32_t &count) override;

bool
GetPointerReturnRegister (const char *&name) override;

//------------------------------------------------------------------
// Static Functions
Expand Down
7 changes: 7 additions & 0 deletions lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ ABISysV_x86_64::GetRegisterInfoArray (uint32_t &count)
return g_register_infos;
}

bool
ABISysV_x86_64::GetPointerReturnRegister (const char *&name)
{
name = "rax";
return true;
}

size_t
ABISysV_x86_64::GetRedZoneSize () const
{
Expand Down
5 changes: 4 additions & 1 deletion lldb/source/Plugins/ABI/SysV-x86_64/ABISysV_x86_64.h
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,10 @@ class ABISysV_x86_64 :

const lldb_private::RegisterInfo *
GetRegisterInfoArray(uint32_t &count) override;


bool
GetPointerReturnRegister (const char *&name) override;

//------------------------------------------------------------------
// Static Functions
//------------------------------------------------------------------
Expand Down
566 changes: 566 additions & 0 deletions lldb/source/Plugins/Disassembler/llvm/DisassemblerLLVMC.cpp

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions lldb/source/Plugins/Disassembler/llvm/DisassemblerLLVMC.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class DisassemblerLLVMC : public lldb_private::Disassembler
void SetStyle (bool use_hex_immed, HexImmediateStyle hex_style);
bool CanBranch (llvm::MCInst &mc_inst);
bool HasDelaySlot (llvm::MCInst &mc_inst);
bool IsCall (llvm::MCInst &mc_inst);
bool IsValid()
{
return m_is_valid;
Expand Down
18 changes: 18 additions & 0 deletions lldb/source/Target/Process.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1150,6 +1150,7 @@ Process::HandleProcessStateChangedEvent (const EventSP &event_sp,
}
else
{
StopInfoSP curr_thread_stop_info_sp;
// Lock the thread list so it doesn't change on us, this is the scope for the locker:
{
ThreadList &thread_list = process_sp->GetThreadList();
Expand All @@ -1159,7 +1160,10 @@ Process::HandleProcessStateChangedEvent (const EventSP &event_sp,
ThreadSP thread;
StopReason curr_thread_stop_reason = eStopReasonInvalid;
if (curr_thread)
{
curr_thread_stop_reason = curr_thread->GetStopReason();
curr_thread_stop_info_sp = curr_thread->GetStopInfo();
}
if (!curr_thread ||
!curr_thread->IsValid() ||
curr_thread_stop_reason == eStopReasonInvalid ||
Expand Down Expand Up @@ -1244,6 +1248,20 @@ Process::HandleProcessStateChangedEvent (const EventSP &event_sp,
start_frame,
num_frames,
num_frames_with_source);
if (curr_thread_stop_info_sp)
{
lldb::addr_t crashing_address;
ValueObjectSP valobj_sp = StopInfo::GetCrashingDereference(curr_thread_stop_info_sp, &crashing_address);
if (valobj_sp)
{
const bool qualify_cxx_base_classes = false;

const ValueObject::GetExpressionPathFormat format = ValueObject::GetExpressionPathFormat::eGetExpressionPathFormatHonorPointers;
stream->PutCString("Likely cause: ");
valobj_sp->GetExpressionPath(*stream, qualify_cxx_base_classes, format);
stream->Printf(" accessed 0x%llx\n", crashing_address);
}
}
}
else
{
Expand Down
583 changes: 583 additions & 0 deletions lldb/source/Target/StackFrame.cpp

Large diffs are not rendered by default.

46 changes: 46 additions & 0 deletions lldb/source/Target/StopInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1219,3 +1219,49 @@ StopInfo::GetExpressionVariable(StopInfoSP &stop_info_sp)
else
return ExpressionVariableSP();
}

lldb::ValueObjectSP
StopInfo::GetCrashingDereference (StopInfoSP &stop_info_sp, lldb::addr_t *crashing_address)
{
if (!stop_info_sp)
{
return ValueObjectSP();
}

const char *description = stop_info_sp->GetDescription();
if (!description)
{
return ValueObjectSP();
}

ThreadSP thread_sp = stop_info_sp->GetThread();
if (!thread_sp)
{
return ValueObjectSP();
}

StackFrameSP frame_sp = thread_sp->GetSelectedFrame();

if (!frame_sp)
{
return ValueObjectSP();
}

const char address_string[] = "address=";

const char *address_loc = strstr(description, address_string);
if (!address_loc)
{
return ValueObjectSP();
}

address_loc += (sizeof(address_string) - 1);

uint64_t address = std::stoull(address_loc, 0, 0);
if (crashing_address)
{
*crashing_address = address;
}

return frame_sp->GuessValueForAddress(address);
}