Skip to content

[lldb-dap] persistent assembly breakpoints #148061

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lldb/include/lldb/API/SBTarget.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "lldb/API/SBValue.h"
#include "lldb/API/SBWatchpoint.h"
#include "lldb/API/SBWatchpointOptions.h"
#include "lldb/lldb-types.h"

namespace lldb_private {
namespace python {
Expand Down Expand Up @@ -737,6 +738,11 @@ class LLDB_API SBTarget {

lldb::SBBreakpoint BreakpointCreateBySBAddress(SBAddress &address);

lldb::SBBreakpoint
BreakpointCreateByFileAddress(const SBFileSpec &file_spec, addr_t file_addr,
addr_t offset = 0,
addr_t instructions_offset = 0);
Comment on lines +741 to +744
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to create a new API for this?

If we're storing the file path + offset, we should be able to create the breakpoint by checking if the module is loaded with the same path and then looking up the address for the file offset.

Something along the lines of:

path = # persisted path
offset = # persisted offset
for m in lldb.target.modules:
  if m.path == path:
    addr = m.ResolveFileAddress(offset)
    lldb.target.BreakpointCreateByAddress(addr.GetLoadAddress(lldb.target))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According the specification the only persistent data we can add is to the Source object in the adapterData field (https://microsoft.github.io/debug-adapter-protocol//specification.html#Types_Source).

The source object is common for all breakpoints in the same function, so the only way I found to differentiate between two breakpoints in the same Source is the line field in the Breakpoint object (where we can't add custom persistent data), which is equivalent to the instructions_offset from the start of the function.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add new APIs for lldb-dap, but they should make sense on their own. Right now, what kind of idea what kind of breakpoint this would set. Is the FileSpec the name of the module you're setting the breakpoint in? And does file_addr identify a file in that module?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@labath Yes, the intention is to set a breakpoint in a virtual address that relates to the file address in a given module.

A function like this already exists in main in Target.h (CreateAddressInModuleBreakpoint) although not exported in the public API.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The breakpoint resolvers got a "user provided" offset field because it was useful for some breakpoint types to allow the user to specify symbol + offset or file & line address + offset. That was handy - especially symbol + offset - in the case where the user has no easy way to go from the initial specifier to an address when they are making the breakpoint, and so that's not math they can do up front.

But that wasn't used for Address breakpoints (and thus NOT passed in the original CreateAddressInModuleBreakpoint) because there's no point. You are starting with an lldb::addr_t, so you can immediately do whatever offsetting math you need to do before setting the breakpoint. There's no reason not to just add the offset to the file_addr you are passing in to create the breakpoint.

You are also adding to that another form of offset - the "instruction count offset" which is the size of a certain number of instructions after the initial addr (*). That's slightly more useful than the offset for address breakpoints, in that that computation is less trivial than adding two addresses.

But first off, if this IS useful as another way to specify "offset from some resolved location" it is useful for the other cases where we actually use offsets. So doing it only for breakpoints set by address doesn't make sense.

Secondly, It should be another variant of offsets. I don't think we anywhere need to offer an API that takes both an lldb::addr_t offset AND an "instruction count" offset. That seems unnecessary, and error prone (do you offset with the addr_t THEN count instructions or vice versa).

(*) You need to check that that addr disassembles, BTW. This is user input, so there's no guarantee that address lies on an instruction boundary...


/// Create a breakpoint using a scripted resolver.
///
/// \param[in] class_name
Expand Down
10 changes: 7 additions & 3 deletions lldb/include/lldb/Breakpoint/BreakpointResolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/RegularExpression.h"
#include "lldb/lldb-private.h"
#include "lldb/lldb-types.h"
#include <optional>

namespace lldb_private {
Expand Down Expand Up @@ -45,9 +46,9 @@ class BreakpointResolver : public Searcher {
/// The breakpoint that owns this resolver.
/// \param[in] resolverType
/// The concrete breakpoint resolver type for this breakpoint.
BreakpointResolver(const lldb::BreakpointSP &bkpt,
unsigned char resolverType,
lldb::addr_t offset = 0);
BreakpointResolver(const lldb::BreakpointSP &bkpt, unsigned char resolverType,
lldb::addr_t offset = 0,
lldb::addr_t instructions_offset = 0);

/// The Destructor is virtual, all significant breakpoint resolvers derive
/// from this class.
Expand Down Expand Up @@ -182,6 +183,7 @@ class BreakpointResolver : public Searcher {
SearchDepth,
SkipPrologue,
SymbolNameArray,
InstructionsOffset,
LastOptionName
};
static const char
Expand Down Expand Up @@ -220,6 +222,8 @@ class BreakpointResolver : public Searcher {
lldb::BreakpointWP m_breakpoint; // This is the breakpoint we add locations to.
lldb::addr_t m_offset; // A random offset the user asked us to add to any
// breakpoints we set.
lldb::addr_t m_instructions_offset; // Number of instructions to add to the
// resolved breakpoint address.

// Subclass identifier (for llvm isa/dyn_cast)
const unsigned char SubclassID;
Expand Down
4 changes: 4 additions & 0 deletions lldb/include/lldb/Breakpoint/BreakpointResolverAddress.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ class BreakpointResolverAddress : public BreakpointResolver {
const Address &addr,
const FileSpec &module_spec);

BreakpointResolverAddress(const lldb::BreakpointSP &bkpt, const Address &addr,
const FileSpec &module_spec, lldb::addr_t offset,
lldb::addr_t instructions_offset);

~BreakpointResolverAddress() override = default;

static lldb::BreakpointResolverSP
Expand Down
2 changes: 2 additions & 0 deletions lldb/include/lldb/Core/Disassembler.h
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,8 @@ class InstructionList {

size_t GetSize() const;

size_t GetTotalByteSize() const;

uint32_t GetMaxOpcocdeByteSize() const;

lldb::InstructionSP GetInstructionAtIndex(size_t idx) const;
Expand Down
15 changes: 10 additions & 5 deletions lldb/include/lldb/Target/Target.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include "lldb/Breakpoint/BreakpointList.h"
#include "lldb/Breakpoint/BreakpointName.h"
#include "lldb/Breakpoint/WatchpointList.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/Architecture.h"
#include "lldb/Core/Disassembler.h"
#include "lldb/Core/ModuleList.h"
Expand Down Expand Up @@ -723,11 +724,11 @@ class Target : public std::enable_shared_from_this<Target>,
lldb::BreakpointSP CreateBreakpoint(lldb::addr_t load_addr, bool internal,
bool request_hardware);

// Use this to create a breakpoint from a load address and a module file spec
lldb::BreakpointSP CreateAddressInModuleBreakpoint(lldb::addr_t file_addr,
bool internal,
const FileSpec &file_spec,
bool request_hardware);
// Use this to create a breakpoint from a file address and a module file spec
lldb::BreakpointSP CreateAddressInModuleBreakpoint(
lldb::addr_t file_addr, bool internal, const FileSpec &file_spec,
bool request_hardware, lldb::addr_t offset = 0,
lldb::addr_t instructions_offset = 0);

// Use this to create Address breakpoints:
lldb::BreakpointSP CreateBreakpoint(const Address &addr, bool internal,
Expand Down Expand Up @@ -1328,6 +1329,10 @@ class Target : public std::enable_shared_from_this<Target>,
const lldb_private::RegisterFlags &flags,
uint32_t byte_size);

lldb::DisassemblerSP ReadInstructions(const Address &start_addr,
uint32_t count,
const char *flavor_string = nullptr);

// Target Stop Hooks
class StopHook : public UserID {
public:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,24 +107,33 @@ def dump_dap_log(log_file):

class Source(object):
def __init__(
self, path: Optional[str] = None, source_reference: Optional[int] = None
self,
path: Optional[str] = None,
source_reference: Optional[int] = None,
raw_dict: Optional[dict[str, Any]] = None,
):
self._name = None
self._path = None
self._source_reference = None
self._raw_dict = None

if path is not None:
self._name = os.path.basename(path)
self._path = path
elif source_reference is not None:
self._source_reference = source_reference
elif raw_dict is not None:
self._raw_dict = raw_dict
else:
raise ValueError("Either path or source_reference must be provided")

def __str__(self):
return f"Source(name={self.name}, path={self.path}), source_reference={self.source_reference})"

def as_dict(self):
if self._raw_dict is not None:
return self._raw_dict

source_dict = {}
if self._name is not None:
source_dict["name"] = self._name
Expand All @@ -135,6 +144,19 @@ def as_dict(self):
return source_dict


class Breakpoint(object):
def __init__(self, obj):
self._breakpoint = obj

def is_verified(self):
"""Check if the breakpoint is verified."""
return self._breakpoint.get("verified", False)

def source(self):
"""Get the source of the breakpoint."""
return self._breakpoint.get("source", {})


class NotSupportedError(KeyError):
"""Raised if a feature is not supported due to its capabilities."""

Expand Down Expand Up @@ -170,7 +192,7 @@ def __init__(
self.initialized = False
self.frame_scopes = {}
self.init_commands = init_commands
self.resolved_breakpoints = {}
self.resolved_breakpoints: dict[str, Breakpoint] = {}

@classmethod
def encode_content(cls, s: str) -> bytes:
Expand Down Expand Up @@ -326,8 +348,8 @@ def _process_continued(self, all_threads_continued: bool):
def _update_verified_breakpoints(self, breakpoints: list[Event]):
for breakpoint in breakpoints:
if "id" in breakpoint:
self.resolved_breakpoints[str(breakpoint["id"])] = breakpoint.get(
"verified", False
self.resolved_breakpoints[str(breakpoint["id"])] = Breakpoint(
breakpoint
)

def send_packet(self, command_dict: Request, set_sequence=True):
Expand Down Expand Up @@ -484,7 +506,14 @@ def wait_for_breakpoints_to_be_verified(
if breakpoint_event is None:
break

return [id for id in breakpoint_ids if id not in self.resolved_breakpoints]
return [
id
for id in breakpoint_ids
if (
id not in self.resolved_breakpoints
or not self.resolved_breakpoints[id].is_verified()
)
]

def wait_for_exited(self, timeout: Optional[float] = None):
event_dict = self.wait_for_event("exited", timeout=timeout)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,24 +59,22 @@ def set_source_breakpoints(
Each object in data is 1:1 mapping with the entry in lines.
It contains optional location/hitCondition/logMessage parameters.
"""
response = self.dap_server.request_setBreakpoints(
Source(source_path), lines, data
return self.set_source_breakpoints_from_source(
Source(path=source_path), lines, data, wait_for_resolve
)
if response is None or not response["success"]:
return []
breakpoints = response["body"]["breakpoints"]
breakpoint_ids = []
for breakpoint in breakpoints:
breakpoint_ids.append("%i" % (breakpoint["id"]))
if wait_for_resolve:
self.wait_for_breakpoints_to_resolve(breakpoint_ids)
return breakpoint_ids

def set_source_breakpoints_assembly(
self, source_reference, lines, data=None, wait_for_resolve=True
):
return self.set_source_breakpoints_from_source(
Source(source_reference=source_reference), lines, data, wait_for_resolve
)

def set_source_breakpoints_from_source(
self, source: Source, lines, data=None, wait_for_resolve=True
):
response = self.dap_server.request_setBreakpoints(
Source(source_reference=source_reference),
source,
lines,
data,
)
Expand Down
43 changes: 20 additions & 23 deletions lldb/source/API/SBTarget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -949,6 +949,24 @@ SBBreakpoint SBTarget::BreakpointCreateBySBAddress(SBAddress &sb_address) {
return sb_bp;
}

SBBreakpoint
SBTarget::BreakpointCreateByFileAddress(const SBFileSpec &file_spec,
addr_t file_addr, addr_t offset,
addr_t instructions_offset) {
LLDB_INSTRUMENT_VA(this, file_spec, file_addr);

SBBreakpoint sb_bp;
if (TargetSP target_sp = GetSP()) {
std::lock_guard<std::recursive_mutex> guard(target_sp->GetAPIMutex());
const bool hardware = false;
sb_bp = target_sp->CreateAddressInModuleBreakpoint(
file_addr, false, *file_spec.get(), hardware, offset,
instructions_offset);
}

return sb_bp;
}

lldb::SBBreakpoint
SBTarget::BreakpointCreateBySourceRegex(const char *source_regex,
const lldb::SBFileSpec &source_file,
Expand Down Expand Up @@ -1955,29 +1973,8 @@ lldb::SBInstructionList SBTarget::ReadInstructions(lldb::SBAddress base_addr,

if (TargetSP target_sp = GetSP()) {
if (Address *addr_ptr = base_addr.get()) {
DataBufferHeap data(
target_sp->GetArchitecture().GetMaximumOpcodeByteSize() * count, 0);
bool force_live_memory = true;
lldb_private::Status error;
lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
const size_t bytes_read =
target_sp->ReadMemory(*addr_ptr, data.GetBytes(), data.GetByteSize(),
error, force_live_memory, &load_addr);

const bool data_from_file = load_addr == LLDB_INVALID_ADDRESS;
if (!flavor_string || flavor_string[0] == '\0') {
// FIXME - we don't have the mechanism in place to do per-architecture
// settings. But since we know that for now we only support flavors on
// x86 & x86_64,
const llvm::Triple::ArchType arch =
target_sp->GetArchitecture().GetTriple().getArch();
if (arch == llvm::Triple::x86 || arch == llvm::Triple::x86_64)
flavor_string = target_sp->GetDisassemblyFlavor();
}
sb_instructions.SetDisassembler(Disassembler::DisassembleBytes(
target_sp->GetArchitecture(), nullptr, flavor_string,
target_sp->GetDisassemblyCPU(), target_sp->GetDisassemblyFeatures(),
*addr_ptr, data.GetBytes(), bytes_read, count, data_from_file));
sb_instructions.SetDisassembler(
target_sp->ReadInstructions(*addr_ptr, count, flavor_string));
}
}

Expand Down
24 changes: 18 additions & 6 deletions lldb/source/Breakpoint/BreakpointResolver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/lldb-forward.h"
#include <optional>

using namespace lldb_private;
Expand All @@ -42,10 +43,12 @@ const char *BreakpointResolver::g_ty_to_name[] = {"FileAndLine", "Address",

const char *BreakpointResolver::g_option_names[static_cast<uint32_t>(
BreakpointResolver::OptionNames::LastOptionName)] = {
"AddressOffset", "Exact", "FileName", "Inlines", "Language",
"LineNumber", "Column", "ModuleName", "NameMask", "Offset",
"PythonClass", "Regex", "ScriptArgs", "SectionName", "SearchDepth",
"SkipPrologue", "SymbolNames"};
"AddressOffset", "Exact", "FileName",
"Inlines", "Language", "LineNumber",
"Column", "ModuleName", "NameMask",
"Offset", "PythonClass", "Regex",
"ScriptArgs", "SectionName", "SearchDepth",
"SkipPrologue", "SymbolNames", "InstructionsOffset"};

const char *BreakpointResolver::ResolverTyToName(enum ResolverTy type) {
if (type > LastKnownResolverType)
Expand All @@ -65,8 +68,10 @@ BreakpointResolver::NameToResolverTy(llvm::StringRef name) {

BreakpointResolver::BreakpointResolver(const BreakpointSP &bkpt,
const unsigned char resolverTy,
lldb::addr_t offset)
: m_breakpoint(bkpt), m_offset(offset), SubclassID(resolverTy) {}
lldb::addr_t offset,
lldb::addr_t instructions_offset)
: m_breakpoint(bkpt), m_offset(offset),
m_instructions_offset(instructions_offset), SubclassID(resolverTy) {}

BreakpointResolver::~BreakpointResolver() = default;

Expand Down Expand Up @@ -364,6 +369,13 @@ void BreakpointResolver::AddLocation(SearchFilter &filter,

BreakpointLocationSP BreakpointResolver::AddLocation(Address loc_addr,
bool *new_location) {
if (m_instructions_offset != 0) {
Target &target = GetBreakpoint()->GetTarget();
const DisassemblerSP instructions =
target.ReadInstructions(loc_addr, m_instructions_offset);
loc_addr.Slide(instructions->GetInstructionList().GetTotalByteSize());
}

loc_addr.Slide(m_offset);
return GetBreakpoint()->AddLocation(loc_addr, new_location);
}
Expand Down
13 changes: 13 additions & 0 deletions lldb/source/Breakpoint/BreakpointResolverAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ BreakpointResolverAddress::BreakpointResolverAddress(const BreakpointSP &bkpt,
: BreakpointResolver(bkpt, BreakpointResolver::AddressResolver),
m_addr(addr), m_resolved_addr(LLDB_INVALID_ADDRESS) {}

BreakpointResolverAddress::BreakpointResolverAddress(
const BreakpointSP &bkpt, const Address &addr, const FileSpec &module_spec,
lldb::addr_t offset, lldb::addr_t instructions_offset)
: BreakpointResolver(bkpt, BreakpointResolver::AddressResolver, offset,
instructions_offset),
m_addr(addr), m_resolved_addr(LLDB_INVALID_ADDRESS),
m_module_filespec(module_spec) {}

BreakpointResolverSP BreakpointResolverAddress::CreateFromStructuredData(
const StructuredData::Dictionary &options_dict, Status &error) {
llvm::StringRef module_name;
Expand Down Expand Up @@ -133,6 +141,11 @@ Searcher::CallbackReturn BreakpointResolverAddress::SearchCallback(
Address tmp_address;
if (module_sp->ResolveFileAddress(m_addr.GetOffset(), tmp_address))
m_addr = tmp_address;
else
return Searcher::eCallbackReturnStop;
} else {
// If we didn't find the module, then we can't resolve the address.
return Searcher::eCallbackReturnStop;
}
}

Expand Down
10 changes: 10 additions & 0 deletions lldb/source/Core/Disassembler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,16 @@ uint32_t InstructionList::GetMaxOpcocdeByteSize() const {
return max_inst_size;
}

size_t InstructionList::GetTotalByteSize() const {
size_t total_byte_size = 0;
collection::const_iterator pos, end;
for (pos = m_instructions.begin(), end = m_instructions.end(); pos != end;
++pos) {
total_byte_size += (*pos)->GetOpcode().GetByteSize();
}
return total_byte_size;
}

InstructionSP InstructionList::GetInstructionAtIndex(size_t idx) const {
InstructionSP inst_sp;
if (idx < m_instructions.size())
Expand Down
Loading
Loading