[lldb-dap] Add an option to provide a format for stack frames (#71843)

When this option gets enabled, descriptions of stack frames will be
generated using the format provided in the launch configuration instead
of simply calling `SBFrame::GetDisplayFunctionName`. This allows
lldb-dap to show an output similar to the one in the CLI.
This commit is contained in:
Walter Erquinigo 2023-11-13 21:10:16 -05:00 committed by GitHub
parent 0e1a52f556
commit d9ec4b24a8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 514 additions and 238 deletions

View File

@ -30,6 +30,7 @@
#include "lldb/API/SBFile.h"
#include "lldb/API/SBFileSpec.h"
#include "lldb/API/SBFileSpecList.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBFrame.h"
#include "lldb/API/SBFunction.h"
#include "lldb/API/SBHostOS.h"

View File

@ -0,0 +1,5 @@
%feature("docstring",
"Class that represents a format string that can be used to generate "
"descriptions of objects like frames and threads. See "
"https://lldb.llvm.org/use/formatting.html for more information."
) lldb::SBFormat;

View File

@ -34,6 +34,7 @@
%include "./interface/SBFileDocstrings.i"
%include "./interface/SBFileSpecDocstrings.i"
%include "./interface/SBFileSpecListDocstrings.i"
%include "./interface/SBFormatDocstrings.i"
%include "./interface/SBFrameDocstrings.i"
%include "./interface/SBFunctionDocstrings.i"
%include "./interface/SBHostOSDocstrings.i"
@ -106,6 +107,7 @@
%include "lldb/API/SBFile.h"
%include "lldb/API/SBFileSpec.h"
%include "lldb/API/SBFileSpecList.h"
%include "lldb/API/SBFormat.h"
%include "lldb/API/SBFrame.h"
%include "lldb/API/SBFunction.h"
%include "lldb/API/SBHostOS.h"

View File

@ -71,6 +71,7 @@ class LLDB_API SBExpressionOptions;
class LLDB_API SBFile;
class LLDB_API SBFileSpec;
class LLDB_API SBFileSpecList;
class LLDB_API SBFormat;
class LLDB_API SBFrame;
class LLDB_API SBFunction;
class LLDB_API SBHostOS;

View File

@ -80,6 +80,7 @@ protected:
friend class SBData;
friend class SBDebugger;
friend class SBFile;
friend class SBFormat;
friend class SBHostOS;
friend class SBPlatform;
friend class SBProcess;

View File

@ -0,0 +1,65 @@
//===-- SBFormat.h ----------------------------------------------*- 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 LLDB_API_SBFORMAT_H
#define LLDB_API_SBFORMAT_H
#include "lldb/API/SBDefines.h"
namespace lldb_private {
namespace python {
class SWIGBridge;
} // namespace python
namespace lua {
class SWIGBridge;
} // namespace lua
} // namespace lldb_private
namespace lldb {
/// Class that represents a format string that can be used to generate
/// descriptions of objects like frames and threads. See
/// https://lldb.llvm.org/use/formatting.html for more information.
class LLDB_API SBFormat {
public:
SBFormat();
/// Create an \a SBFormat by parsing the given format string. If parsing
/// fails, this object is initialized as invalid.
///
/// \param[in] format
/// The format string to parse.
///
/// \param[out] error
/// An object where error messages will be written to if parsing fails.
SBFormat(const char *format, lldb::SBError &error);
SBFormat(const lldb::SBFormat &rhs);
lldb::SBFormat &operator=(const lldb::SBFormat &rhs);
~SBFormat();
/// \return
/// \b true if and only if this object is valid and can be used for
/// formatting.
explicit operator bool() const;
protected:
friend class SBFrame;
/// \return
/// The underlying shared pointer storage for this object.
lldb::FormatEntrySP GetFormatEntrySP() const;
/// The storage for this object.
lldb::FormatEntrySP m_opaque_sp;
};
} // namespace lldb
#endif // LLDB_API_SBFORMAT_H

View File

@ -88,7 +88,7 @@ public:
const char *GetDisplayFunctionName();
const char *GetFunctionName() const;
// Return the frame function's language. If there isn't a function, then
// guess the language type from the mangled name.
lldb::LanguageType GuessLanguage() const;
@ -193,6 +193,21 @@ public:
bool GetDescription(lldb::SBStream &description);
/// Similar to \a GetDescription() but the format of the description can be
/// configured via the \p format parameter. See
/// https://lldb.llvm.org/use/formatting.html for more information on format
/// strings.
///
/// \param[in] format
/// The format to use for generating the description.
///
/// \param[out] output
/// The stream where the description will be written to.
///
/// \return
/// An error object with an error message in case of failures.
SBError GetDescriptionWithFormat(const SBFormat &format, SBStream &output);
protected:
friend class SBBlock;
friend class SBExecutionContext;

View File

@ -35,227 +35,217 @@ class StringRef;
}
namespace lldb_private {
class FormatEntity {
public:
struct Entry {
enum class Type {
Invalid,
ParentNumber,
ParentString,
EscapeCode,
Root,
String,
Scope,
Variable,
VariableSynthetic,
ScriptVariable,
ScriptVariableSynthetic,
AddressLoad,
AddressFile,
AddressLoadOrFile,
ProcessID,
ProcessFile,
ScriptProcess,
ThreadID,
ThreadProtocolID,
ThreadIndexID,
ThreadName,
ThreadQueue,
ThreadStopReason,
ThreadStopReasonRaw,
ThreadReturnValue,
ThreadCompletedExpression,
ScriptThread,
ThreadInfo,
TargetArch,
ScriptTarget,
ModuleFile,
File,
Lang,
FrameIndex,
FrameNoDebug,
FrameRegisterPC,
FrameRegisterSP,
FrameRegisterFP,
FrameRegisterFlags,
FrameRegisterByName,
FrameIsArtificial,
ScriptFrame,
FunctionID,
FunctionDidChange,
FunctionInitialFunction,
FunctionName,
FunctionNameWithArgs,
FunctionNameNoArgs,
FunctionMangledName,
FunctionAddrOffset,
FunctionAddrOffsetConcrete,
FunctionLineOffset,
FunctionPCOffset,
FunctionInitial,
FunctionChanged,
FunctionIsOptimized,
LineEntryFile,
LineEntryLineNumber,
LineEntryColumn,
LineEntryStartAddress,
LineEntryEndAddress,
CurrentPCArrow
};
struct Definition {
/// The name/string placeholder that corresponds to this definition.
const char *name;
/// Insert this exact string into the output
const char *string = nullptr;
/// Entry::Type corresponding to this definition.
const Entry::Type type;
/// Data that is returned as the value of the format string.
const uint64_t data = 0;
/// The number of children of this node in the tree of format strings.
const uint32_t num_children = 0;
/// An array of "num_children" Definition entries.
const Definition *children = nullptr;
/// Whether the separator is kept during parsing or not. It's used
/// for entries with parameters.
const bool keep_separator = false;
constexpr Definition(const char *name, const FormatEntity::Entry::Type t)
: name(name), type(t) {}
constexpr Definition(const char *name, const char *string)
: name(name), string(string), type(Entry::Type::EscapeCode) {}
constexpr Definition(const char *name, const FormatEntity::Entry::Type t,
const uint64_t data)
: name(name), type(t), data(data) {}
constexpr Definition(const char *name, const FormatEntity::Entry::Type t,
const uint64_t num_children,
const Definition *children,
const bool keep_separator = false)
: name(name), type(t), num_children(num_children), children(children),
keep_separator(keep_separator) {}
};
template <size_t N>
static constexpr Definition
DefinitionWithChildren(const char *name, const FormatEntity::Entry::Type t,
const Definition (&children)[N],
bool keep_separator = false) {
return Definition(name, t, N, children, keep_separator);
}
Entry(Type t = Type::Invalid, const char *s = nullptr,
const char *f = nullptr)
: string(s ? s : ""), printf_format(f ? f : ""), type(t) {}
Entry(llvm::StringRef s);
Entry(char ch);
void AppendChar(char ch);
void AppendText(const llvm::StringRef &s);
void AppendText(const char *cstr);
void AppendEntry(const Entry &&entry) { children.push_back(entry); }
void Clear() {
string.clear();
printf_format.clear();
children.clear();
type = Type::Invalid;
fmt = lldb::eFormatDefault;
number = 0;
deref = false;
}
static const char *TypeToCString(Type t);
void Dump(Stream &s, int depth = 0) const;
bool operator==(const Entry &rhs) const {
if (string != rhs.string)
return false;
if (printf_format != rhs.printf_format)
return false;
const size_t n = children.size();
const size_t m = rhs.children.size();
for (size_t i = 0; i < std::min<size_t>(n, m); ++i) {
if (!(children[i] == rhs.children[i]))
return false;
}
if (children != rhs.children)
return false;
if (type != rhs.type)
return false;
if (fmt != rhs.fmt)
return false;
if (deref != rhs.deref)
return false;
return true;
}
std::string string;
std::string printf_format;
std::vector<Entry> children;
Type type;
lldb::Format fmt = lldb::eFormatDefault;
lldb::addr_t number = 0;
bool deref = false;
namespace FormatEntity {
struct Entry {
enum class Type {
Invalid,
ParentNumber,
ParentString,
EscapeCode,
Root,
String,
Scope,
Variable,
VariableSynthetic,
ScriptVariable,
ScriptVariableSynthetic,
AddressLoad,
AddressFile,
AddressLoadOrFile,
ProcessID,
ProcessFile,
ScriptProcess,
ThreadID,
ThreadProtocolID,
ThreadIndexID,
ThreadName,
ThreadQueue,
ThreadStopReason,
ThreadStopReasonRaw,
ThreadReturnValue,
ThreadCompletedExpression,
ScriptThread,
ThreadInfo,
TargetArch,
ScriptTarget,
ModuleFile,
File,
Lang,
FrameIndex,
FrameNoDebug,
FrameRegisterPC,
FrameRegisterSP,
FrameRegisterFP,
FrameRegisterFlags,
FrameRegisterByName,
FrameIsArtificial,
ScriptFrame,
FunctionID,
FunctionDidChange,
FunctionInitialFunction,
FunctionName,
FunctionNameWithArgs,
FunctionNameNoArgs,
FunctionMangledName,
FunctionAddrOffset,
FunctionAddrOffsetConcrete,
FunctionLineOffset,
FunctionPCOffset,
FunctionInitial,
FunctionChanged,
FunctionIsOptimized,
LineEntryFile,
LineEntryLineNumber,
LineEntryColumn,
LineEntryStartAddress,
LineEntryEndAddress,
CurrentPCArrow
};
static bool Format(const Entry &entry, Stream &s, const SymbolContext *sc,
const ExecutionContext *exe_ctx, const Address *addr,
ValueObject *valobj, bool function_changed,
bool initial_function);
struct Definition {
/// The name/string placeholder that corresponds to this definition.
const char *name;
/// Insert this exact string into the output
const char *string = nullptr;
/// Entry::Type corresponding to this definition.
const Entry::Type type;
/// Data that is returned as the value of the format string.
const uint64_t data = 0;
/// The number of children of this node in the tree of format strings.
const uint32_t num_children = 0;
/// An array of "num_children" Definition entries.
const Definition *children = nullptr;
/// Whether the separator is kept during parsing or not. It's used
/// for entries with parameters.
const bool keep_separator = false;
static bool FormatStringRef(const llvm::StringRef &format, Stream &s,
const SymbolContext *sc,
const ExecutionContext *exe_ctx,
const Address *addr, ValueObject *valobj,
bool function_changed, bool initial_function);
constexpr Definition(const char *name, const FormatEntity::Entry::Type t)
: name(name), type(t) {}
static bool FormatCString(const char *format, Stream &s,
const SymbolContext *sc,
const ExecutionContext *exe_ctx,
const Address *addr, ValueObject *valobj,
bool function_changed, bool initial_function);
constexpr Definition(const char *name, const char *string)
: name(name), string(string), type(Entry::Type::EscapeCode) {}
static Status Parse(const llvm::StringRef &format, Entry &entry);
constexpr Definition(const char *name, const FormatEntity::Entry::Type t,
const uint64_t data)
: name(name), type(t), data(data) {}
static Status ExtractVariableInfo(llvm::StringRef &format_str,
llvm::StringRef &variable_name,
llvm::StringRef &variable_format);
constexpr Definition(const char *name, const FormatEntity::Entry::Type t,
const uint64_t num_children,
const Definition *children,
const bool keep_separator = false)
: name(name), type(t), num_children(num_children), children(children),
keep_separator(keep_separator) {}
};
static void AutoComplete(lldb_private::CompletionRequest &request);
template <size_t N>
static constexpr Definition
DefinitionWithChildren(const char *name, const FormatEntity::Entry::Type t,
const Definition (&children)[N],
bool keep_separator = false) {
return Definition(name, t, N, children, keep_separator);
}
// Format the current elements into the stream \a s.
//
// The root element will be stripped off and the format str passed in will be
// either an empty string (print a description of this object), or contain a
// `.`-separated series like a domain name that identifies further
// sub-elements to display.
static bool FormatFileSpec(const FileSpec &file, Stream &s,
llvm::StringRef elements,
llvm::StringRef element_format);
Entry(Type t = Type::Invalid, const char *s = nullptr,
const char *f = nullptr)
: string(s ? s : ""), printf_format(f ? f : ""), type(t) {}
/// For each variable in 'args' this function writes the variable
/// name and it's pretty-printed value representation to 'out_stream'
/// in following format:
///
/// \verbatim
/// name_1=repr_1, name_2=repr_2 ...
/// \endverbatim
static void PrettyPrintFunctionArguments(Stream &out_stream,
VariableList const &args,
ExecutionContextScope *exe_scope);
Entry(llvm::StringRef s);
Entry(char ch);
protected:
static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
uint32_t depth);
void AppendChar(char ch);
void AppendText(const llvm::StringRef &s);
void AppendText(const char *cstr);
void AppendEntry(const Entry &&entry) { children.push_back(entry); }
void Clear() {
string.clear();
printf_format.clear();
children.clear();
type = Type::Invalid;
fmt = lldb::eFormatDefault;
number = 0;
deref = false;
}
static const char *TypeToCString(Type t);
void Dump(Stream &s, int depth = 0) const;
bool operator==(const Entry &rhs) const {
if (string != rhs.string)
return false;
if (printf_format != rhs.printf_format)
return false;
const size_t n = children.size();
const size_t m = rhs.children.size();
for (size_t i = 0; i < std::min<size_t>(n, m); ++i) {
if (!(children[i] == rhs.children[i]))
return false;
}
if (children != rhs.children)
return false;
if (type != rhs.type)
return false;
if (fmt != rhs.fmt)
return false;
if (deref != rhs.deref)
return false;
return true;
}
std::string string;
std::string printf_format;
std::vector<Entry> children;
Type type;
lldb::Format fmt = lldb::eFormatDefault;
lldb::addr_t number = 0;
bool deref = false;
};
bool Format(const Entry &entry, Stream &s, const SymbolContext *sc,
const ExecutionContext *exe_ctx, const Address *addr,
ValueObject *valobj, bool function_changed, bool initial_function);
bool FormatStringRef(const llvm::StringRef &format, Stream &s,
const SymbolContext *sc, const ExecutionContext *exe_ctx,
const Address *addr, ValueObject *valobj,
bool function_changed, bool initial_function);
bool FormatCString(const char *format, Stream &s, const SymbolContext *sc,
const ExecutionContext *exe_ctx, const Address *addr,
ValueObject *valobj, bool function_changed,
bool initial_function);
Status Parse(const llvm::StringRef &format, Entry &entry);
Status ExtractVariableInfo(llvm::StringRef &format_str,
llvm::StringRef &variable_name,
llvm::StringRef &variable_format);
void AutoComplete(lldb_private::CompletionRequest &request);
// Format the current elements into the stream \a s.
//
// The root element will be stripped off and the format str passed in will be
// either an empty string (print a description of this object), or contain a
// `.`-separated series like a domain name that identifies further
// sub-elements to display.
bool FormatFileSpec(const FileSpec &file, Stream &s, llvm::StringRef elements,
llvm::StringRef element_format);
/// For each variable in 'args' this function writes the variable
/// name and it's pretty-printed value representation to 'out_stream'
/// in following format:
///
/// \verbatim
/// name_1=repr_1, name_2=repr_2 ...
/// \endverbatim
void PrettyPrintFunctionArguments(Stream &out_stream, VariableList const &args,
ExecutionContextScope *exe_scope);
} // namespace FormatEntity
} // namespace lldb_private
#endif // LLDB_CORE_FORMATENTITY_H

View File

@ -14,6 +14,7 @@
#include "lldb/Utility/Flags.h"
#include "lldb/Core/FormatEntity.h"
#include "lldb/Core/ValueObjectList.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Target/ExecutionContextScope.h"
@ -324,8 +325,23 @@ public:
/// C string with the assembly instructions for this function.
const char *Disassemble();
/// Print a description of this frame using the provided frame format.
///
/// \param[out] strm
/// The Stream to print the description to.
///
/// \param[in] frame_marker
/// Optional string that will be prepended to the frame output description.
///
/// \return
/// \b true if and only if dumping with the given \p format worked.
bool DumpUsingFormat(Stream &strm,
const lldb_private::FormatEntity::Entry *format,
llvm::StringRef frame_marker = {});
/// Print a description for this frame using the frame-format formatter
/// settings.
/// settings. If the current frame-format settings are invalid, then the
/// default formatter will be used (see \a StackFrame::Dump()).
///
/// \param [in] strm
/// The Stream to print the description to.

View File

@ -96,6 +96,9 @@ class File;
class FileSpec;
class FileSpecList;
class Flags;
namespace FormatEntity {
struct Entry;
} // namespace FormatEntity
class FormatManager;
class FormattersMatchCandidate;
class FuncUnwinders;
@ -335,6 +338,7 @@ typedef std::shared_ptr<lldb_private::ExecutionContextRef>
typedef std::shared_ptr<lldb_private::ExpressionVariable> ExpressionVariableSP;
typedef std::unique_ptr<lldb_private::File> FileUP;
typedef std::shared_ptr<lldb_private::File> FileSP;
typedef std::shared_ptr<lldb_private::FormatEntity::Entry> FormatEntrySP;
typedef std::shared_ptr<lldb_private::Function> FunctionSP;
typedef std::shared_ptr<lldb_private::FuncUnwinders> FuncUnwindersSP;
typedef std::shared_ptr<lldb_private::InlineFunctionInfo> InlineFunctionInfoSP;

View File

@ -732,6 +732,7 @@ class DebugCommunication(object):
enableAutoVariableSummaries=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix="`",
customFrameFormat=None,
):
args_dict = {"program": program}
if args:
@ -773,6 +774,9 @@ class DebugCommunication(object):
args_dict["runInTerminal"] = runInTerminal
if postRunCommands:
args_dict["postRunCommands"] = postRunCommands
if customFrameFormat:
args_dict["customFrameFormat"] = customFrameFormat
args_dict["enableAutoVariableSummaries"] = enableAutoVariableSummaries
args_dict["enableSyntheticChildDebugging"] = enableSyntheticChildDebugging
args_dict["commandEscapePrefix"] = commandEscapePrefix

View File

@ -352,6 +352,7 @@ class DAPTestCaseBase(TestBase):
enableAutoVariableSummaries=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix="`",
customFrameFormat=None,
):
"""Sending launch request to dap"""
@ -391,6 +392,7 @@ class DAPTestCaseBase(TestBase):
enableAutoVariableSummaries=enableAutoVariableSummaries,
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
commandEscapePrefix=commandEscapePrefix,
customFrameFormat=customFrameFormat,
)
if expectFailure:
@ -428,6 +430,7 @@ class DAPTestCaseBase(TestBase):
enableAutoVariableSummaries=False,
enableSyntheticChildDebugging=False,
commandEscapePrefix="`",
customFrameFormat=None,
):
"""Build the default Makefile target, create the DAP debug adaptor,
and launch the process.
@ -459,4 +462,5 @@ class DAPTestCaseBase(TestBase):
enableAutoVariableSummaries=enableAutoVariableSummaries,
enableSyntheticChildDebugging=enableSyntheticChildDebugging,
commandEscapePrefix=commandEscapePrefix,
customFrameFormat=customFrameFormat,
)

View File

@ -45,6 +45,7 @@ add_lldb_library(liblldb SHARED ${option_framework}
SBFileSpec.cpp
SBFile.cpp
SBFileSpecList.cpp
SBFormat.cpp
SBFrame.cpp
SBFunction.cpp
SBHostOS.cpp

View File

@ -0,0 +1,44 @@
//===-- SBFormat.cpp ------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "lldb/API/SBFormat.h"
#include "Utils.h"
#include "lldb/Core/FormatEntity.h"
#include "lldb/lldb-types.h"
#include <lldb/API/SBError.h>
#include <lldb/Utility/Status.h>
using namespace lldb;
using namespace lldb_private;
SBFormat::SBFormat() : m_opaque_sp() {}
SBFormat::SBFormat(const SBFormat &rhs) {
m_opaque_sp = clone(rhs.m_opaque_sp);
}
SBFormat::~SBFormat() = default;
SBFormat &SBFormat::operator=(const SBFormat &rhs) {
if (this != &rhs)
m_opaque_sp = clone(rhs.m_opaque_sp);
return *this;
}
SBFormat::operator bool() const { return (bool)m_opaque_sp; }
SBFormat::SBFormat(const char *format, lldb::SBError &error) {
FormatEntrySP format_entry_sp = std::make_shared<FormatEntity::Entry>();
Status status = FormatEntity::Parse(format, *format_entry_sp);
error.SetError(status);
if (error.Success())
m_opaque_sp = format_entry_sp;
}
lldb::FormatEntrySP SBFormat::GetFormatEntrySP() const { return m_opaque_sp; }

View File

@ -45,6 +45,7 @@
#include "lldb/API/SBAddress.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBExpressionOptions.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBStream.h"
#include "lldb/API/SBSymbolContext.h"
#include "lldb/API/SBThread.h"
@ -947,6 +948,40 @@ SBValue SBFrame::FindRegister(const char *name) {
return result;
}
SBError SBFrame::GetDescriptionWithFormat(const SBFormat &format,
SBStream &output) {
Stream &strm = output.ref();
std::unique_lock<std::recursive_mutex> lock;
ExecutionContext exe_ctx(m_opaque_sp.get(), lock);
StackFrame *frame = nullptr;
Target *target = exe_ctx.GetTargetPtr();
Process *process = exe_ctx.GetProcessPtr();
SBError error;
if (!format) {
error.SetErrorString("The provided SBFormat object is invalid");
return error;
}
if (target && process) {
Process::StopLocker stop_locker;
if (stop_locker.TryLock(&process->GetRunLock())) {
frame = exe_ctx.GetFramePtr();
if (frame &&
frame->DumpUsingFormat(strm, format.GetFormatEntrySP().get())) {
return error;
}
}
}
error.SetErrorStringWithFormat(
"It was not possible to generate a frame "
"description with the given format string '%s'",
format.GetFormatEntrySP()->string.c_str());
return error;
}
bool SBFrame::GetDescription(SBStream &description) {
LLDB_INSTRUMENT_VA(this, description);

View File

@ -286,13 +286,6 @@ void FormatEntity::Entry::AppendText(const char *cstr) {
return AppendText(llvm::StringRef(cstr));
}
Status FormatEntity::Parse(const llvm::StringRef &format_str, Entry &entry) {
entry.Clear();
entry.type = Entry::Type::Root;
llvm::StringRef modifiable_format(format_str);
return ParseInternal(modifiable_format, entry, 0);
}
#define ENUM_TO_CSTR(eee) \
case FormatEntity::Entry::Type::eee: \
return #eee
@ -1991,8 +1984,8 @@ static const Definition *FindEntry(const llvm::StringRef &format_str,
return parent;
}
Status FormatEntity::ParseInternal(llvm::StringRef &format, Entry &parent_entry,
uint32_t depth) {
static Status ParseInternal(llvm::StringRef &format, Entry &parent_entry,
uint32_t depth) {
Status error;
while (!format.empty() && error.Success()) {
const size_t non_special_chars = format.find_first_of("${}\\");
@ -2017,7 +2010,7 @@ Status FormatEntity::ParseInternal(llvm::StringRef &format, Entry &parent_entry,
case '{': {
format = format.drop_front(); // Skip the '{'
Entry scope_entry(Entry::Type::Scope);
error = FormatEntity::ParseInternal(format, scope_entry, depth + 1);
error = ParseInternal(format, scope_entry, depth + 1);
if (error.Fail())
return error;
parent_entry.AppendEntry(std::move(scope_entry));
@ -2467,3 +2460,10 @@ void FormatEntity::PrettyPrintFunctionArguments(
out_stream.Printf("%s=<unavailable>", var_name);
}
}
Status FormatEntity::Parse(const llvm::StringRef &format_str, Entry &entry) {
entry.Clear();
entry.type = Entry::Type::Root;
llvm::StringRef modifiable_format(format_str);
return ParseInternal(modifiable_format, entry, 0);
}

View File

@ -1779,18 +1779,30 @@ void StackFrame::CalculateExecutionContext(ExecutionContext &exe_ctx) {
exe_ctx.SetContext(shared_from_this());
}
bool StackFrame::DumpUsingFormat(Stream &strm,
const FormatEntity::Entry *format,
llvm::StringRef frame_marker) {
GetSymbolContext(eSymbolContextEverything);
ExecutionContext exe_ctx(shared_from_this());
StreamString s;
s.PutCString(frame_marker);
if (format && FormatEntity::Format(*format, s, &m_sc, &exe_ctx, nullptr,
nullptr, false, false)) {
strm.PutCString(s.GetString());
return true;
}
return false;
}
void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique,
const char *frame_marker) {
if (strm == nullptr)
return;
GetSymbolContext(eSymbolContextEverything);
ExecutionContext exe_ctx(shared_from_this());
StreamString s;
if (frame_marker)
s.PutCString(frame_marker);
const FormatEntity::Entry *frame_format = nullptr;
Target *target = exe_ctx.GetTargetPtr();
if (target) {
@ -1800,10 +1812,7 @@ void StackFrame::DumpUsingSettingsFormat(Stream *strm, bool show_unique,
frame_format = target->GetDebugger().GetFrameFormat();
}
}
if (frame_format && FormatEntity::Format(*frame_format, s, &m_sc, &exe_ctx,
nullptr, nullptr, false, false)) {
strm->PutCString(s.GetString());
} else {
if (!DumpUsingFormat(*strm, frame_format, frame_marker)) {
Dump(strm, true, false);
strm->EOL();
}

View File

@ -0,0 +1,24 @@
"""
Test the lldb Python SBFormat API.
"""
import lldb
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
class FormatAPITestCase(TestBase):
def test_format(self):
format = lldb.SBFormat()
self.assertFalse(format)
error = lldb.SBError()
format = lldb.SBFormat("${bad}", error)
self.assertIn("invalid top level item 'bad'", error.GetCString())
self.assertFalse(format) # We expect an invalid object back if we have an error
self.assertTrue(error.Fail())
format = lldb.SBFormat("${frame.index}", error)
self.assertIs(error.GetCString(), None)
self.assertTrue(format)
self.assertTrue(error.Success())

View File

@ -3,12 +3,13 @@ Test lldb-dap setBreakpoints request
"""
import os
import dap_server
import lldbdap_testcase
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbdap_testcase
import os
class TestDAP_stackTrace(lldbdap_testcase.DAPTestCaseBase):
@ -187,3 +188,19 @@ class TestDAP_stackTrace(lldbdap_testcase.DAPTestCaseBase):
self.assertEquals(
0, len(stackFrames), "verify zero frames with startFrame out of bounds"
)
@skipIfWindows
@skipIfRemote
def test_functionNameWithArgs(self):
"""
Test that the stack frame without a function name is given its pc in the response.
"""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program, customFrameFormat="${function.name-with-args}")
source = "main.c"
self.set_source_breakpoints(source, [line_number(source, "recurse end")])
self.continue_to_next_stop()
frame = self.get_stackFrames()[0]
self.assertEquals(frame["name"], "recurse(x=1)")

View File

@ -824,4 +824,19 @@ bool ReplModeRequestHandler::DoExecute(lldb::SBDebugger debugger,
return true;
}
void DAP::SetFrameFormat(llvm::StringRef format) {
if (format.empty())
return;
lldb::SBError error;
g_dap.frame_format = lldb::SBFormat(format.data(), error);
if (error.Fail()) {
g_dap.SendOutput(
OutputType::Console,
llvm::formatv(
"The provided frame format '{0}' couldn't be parsed: {1}\n", format,
error.GetCString())
.str());
}
}
} // namespace lldb_dap

View File

@ -36,6 +36,7 @@
#include "lldb/API/SBCommunication.h"
#include "lldb/API/SBDebugger.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBFormat.h"
#include "lldb/API/SBHostOS.h"
#include "lldb/API/SBInstruction.h"
#include "lldb/API/SBInstructionList.h"
@ -189,6 +190,7 @@ struct DAP {
ReplMode repl_mode;
bool auto_repl_mode_collision_warning;
std::string command_escape_prefix = "`";
lldb::SBFormat frame_format;
DAP();
~DAP();
@ -305,6 +307,8 @@ struct DAP {
/// \return Error if waiting for the process fails, no error if succeeds.
lldb::SBError WaitForProcessToStop(uint32_t seconds);
void SetFrameFormat(llvm::StringRef format);
private:
// Send the JSON in "json_str" to the "out" stream. Correctly send the
// "Content-Length:" field followed by the length, followed by the raw

View File

@ -785,11 +785,18 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
int64_t frame_id = MakeDAPFrameID(frame);
object.try_emplace("id", frame_id);
// `function_name` can be a nullptr, which throws an error when assigned to an
// `std::string`.
const char *function_name = frame.GetDisplayFunctionName();
std::string frame_name =
function_name == nullptr ? std::string() : function_name;
std::string frame_name;
lldb::SBStream stream;
if (g_dap.frame_format &&
frame.GetDescriptionWithFormat(g_dap.frame_format, stream).Success()) {
frame_name = stream.GetData();
// `function_name` can be a nullptr, which throws an error when assigned to
// an `std::string`.
} else if (const char *name = frame.GetDisplayFunctionName()) {
frame_name = name;
}
if (frame_name.empty()) {
// If the function name is unavailable, display the pc address as a 16-digit
// hex string, e.g. "0x0000000000012345"

View File

@ -653,6 +653,7 @@ void request_attach(const llvm::json::Object &request) {
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
g_dap.command_escape_prefix =
GetString(arguments, "commandEscapePrefix", "`");
g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
// This is a hack for loading DWARF in .o files on Mac where the .o files
// in the debug map of the main executable have relative paths which require
@ -1805,6 +1806,7 @@ void request_launch(const llvm::json::Object &request) {
GetBoolean(arguments, "enableSyntheticChildDebugging", false);
g_dap.command_escape_prefix =
GetString(arguments, "commandEscapePrefix", "`");
g_dap.SetFrameFormat(GetString(arguments, "customFrameFormat"));
// This is a hack for loading DWARF in .o files on Mac where the .o files
// in the debug map of the main executable have relative paths which require

View File

@ -255,6 +255,11 @@
"type": "string",
"description": "The escape prefix to use for executing regular LLDB commands in the Debug Console, instead of printing variables. Defaults to a back-tick (`). If it's an empty string, then all expression in the Debug Console are treated as regular LLDB commands.",
"default": "`"
},
"customFrameFormat": {
"type": "string",
"description": "If non-empty, stack frames will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html for an explanation on format strings for frames. If the format string contains errors, an error message will be displayed on the Debug Console and the default frame names will be used. This might come with a performance cost because debug information might need to be processed to generate the description.",
"default": ""
}
}
},
@ -349,6 +354,11 @@
"type": "string",
"description": "The escape prefix character to use for executing regular LLDB commands in the Debug Console, instead of printing variables. Defaults to a back-tick (`). If empty, then all expression in the Debug Console are treated as regular LLDB commands.",
"default": "`"
},
"customFrameFormat": {
"type": "string",
"description": "If non-empty, stack frames will have descriptions generated based on the provided format. See https://lldb.llvm.org/use/formatting.html for an explanation on format strings for frames. If the format string contains errors, an error message will be displayed on the Debug Console and the default frame names will be used. This might come with a performance cost because debug information might need to be processed to generate the description.",
"default": ""
}
}
}