Skip to content

Commit

Permalink
[lldb-dap] Enabling instruction breakpoint support to lldb-dap. (llvm…
Browse files Browse the repository at this point in the history
…#105278)

Added support for "supportsInstructionBreakpoints" capability and now it
this command is triggered when we set instruction breakpoint.
We need this support as part of enabling disassembly view debugging.
Following features should work as part of this feature enablement:

1. Settings breakpoints in disassembly view: Unsetting the breakpoint is
not happening from the disassembly view. Currently we need to unset
breakpoint manually from the breakpoint List. Multiple breakpoints are
getting set for the same $

2. Step over, step into, continue in the disassembly view

The format for DisassembleRequest and DisassembleResponse at
https://raw.githubusercontent.com/microsoft/vscode/master/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts
.

Ref Images:
Set instruction breakpoint in disassembly view:

![image](https://github.com/user-attachments/assets/833bfb34-86f4-40e2-8c20-14b638a612a2)

After issuing continue:

![image](https://github.com/user-attachments/assets/884572a3-915e-422b-b8dd-d132e5c00de6)

---------

Co-authored-by: Santhosh Kumar Ellendula <[email protected]>
Co-authored-by: Santhosh Kumar Ellendula <[email protected]>
  • Loading branch information
3 people authored and 5c4lar committed Aug 29, 2024
1 parent 4b2aa89 commit 5474995
Show file tree
Hide file tree
Showing 14 changed files with 577 additions and 3 deletions.
14 changes: 14 additions & 0 deletions lldb/packages/Python/lldbsuite/test/tools/lldb-dap/dap_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -1099,6 +1099,20 @@ def terminate(self):
self.send.close()
# self.recv.close()

def request_setInstructionBreakpoints(self, memory_reference=[]):
breakpoints = []
for i in memory_reference:
args_dict = {
"instructionReference": i,
}
breakpoints.append(args_dict)
args_dict = {"breakpoints": breakpoints}
command_dict = {
"command": "setInstructionBreakpoints",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)

class DebugAdaptorServer(DebugCommunication):
def __init__(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,10 @@ def verify_breakpoint_hit(self, breakpoint_ids):
body = stopped_event["body"]
if "reason" not in body:
continue
if body["reason"] != "breakpoint":
if (
body["reason"] != "breakpoint"
and body["reason"] != "instruction breakpoint"
):
continue
if "description" not in body:
continue
Expand Down
6 changes: 6 additions & 0 deletions lldb/test/API/tools/lldb-dap/instruction-breakpoint/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CXX_SOURCES := main-copy.cpp
CXXFLAGS_EXTRAS := -O0 -g
include Makefile.rules

main-copy.cpp: main.cpp
cp -f $< $@
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import dap_server
import shutil
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
import lldbdap_testcase
import os
import lldb


class TestDAP_InstructionBreakpointTestCase(lldbdap_testcase.DAPTestCaseBase):
NO_DEBUG_INFO_TESTCASE = True

def setUp(self):
lldbdap_testcase.DAPTestCaseBase.setUp(self)

self.main_basename = "main-copy.cpp"
self.main_path = os.path.realpath(self.getBuildArtifact(self.main_basename))

def test_instruction_breakpoint(self):
self.build()
self.instruction_breakpoint_test()

def instruction_breakpoint_test(self):
"""Sample test to ensure SBFrame::Disassemble produces SOME output"""
# Create a target by the debugger.
target = self.createTestTarget()

main_line = line_number("main.cpp", "breakpoint 1")

program = self.getBuildArtifact("a.out")
self.build_and_launch(program)

# Set source breakpoint 1
response = self.dap_server.request_setBreakpoints(self.main_path, [main_line])
breakpoints = response["body"]["breakpoints"]
self.assertEquals(len(breakpoints), 1)
breakpoint = breakpoints[0]
self.assertEqual(
breakpoint["line"], main_line, "incorrect breakpoint source line"
)
self.assertTrue(breakpoint["verified"], "breakpoint is not verified")
self.assertEqual(
self.main_basename, breakpoint["source"]["name"], "incorrect source name"
)
self.assertEqual(
self.main_path, breakpoint["source"]["path"], "incorrect source file path"
)
other_breakpoint_id = breakpoint["id"]

# Continue and then verifiy the breakpoint
self.dap_server.request_continue()
self.verify_breakpoint_hit([other_breakpoint_id])

# now we check the stack trace making sure that we got mapped source paths
frames = self.dap_server.request_stackTrace()["body"]["stackFrames"]
intstructionPointerReference = []
setIntstructionBreakpoints = []
intstructionPointerReference.append(frames[0]["instructionPointerReference"])
self.assertEqual(
frames[0]["source"]["name"], self.main_basename, "incorrect source name"
)
self.assertEqual(
frames[0]["source"]["path"], self.main_path, "incorrect source file path"
)

# Check disassembly view
instruction = self.disassemble(frameIndex=0)
self.assertEqual(
instruction["address"],
intstructionPointerReference[0],
"current breakpoint reference is not in the disaasembly view",
)

# Get next instruction address to set instruction breakpoint
disassembled_instruction_list = self.dap_server.disassembled_instructions
instruction_addr_list = list(disassembled_instruction_list.keys())
index = instruction_addr_list.index(intstructionPointerReference[0])
if len(instruction_addr_list) >= (index + 1):
next_inst_addr = instruction_addr_list[index + 1]
if len(next_inst_addr) > 2:
setIntstructionBreakpoints.append(next_inst_addr)
instruction_breakpoint_response = (
self.dap_server.request_setInstructionBreakpoints(
setIntstructionBreakpoints
)
)
inst_breakpoints = instruction_breakpoint_response["body"][
"breakpoints"
]
self.assertEqual(
inst_breakpoints[0]["instructionReference"],
next_inst_addr,
"Instruction breakpoint has not been resolved or failed to relocate the instruction breakpoint",
)
self.dap_server.request_continue()
self.verify_breakpoint_hit([inst_breakpoints[0]["id"]])
18 changes: 18 additions & 0 deletions lldb/test/API/tools/lldb-dap/instruction-breakpoint/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#include <stdio.h>
#include <unistd.h>

int function(int x) {

if (x == 0) // breakpoint 1
return x;

if ((x % 2) != 0)
return x;
else
return function(x - 1) + x;
}

int main(int argc, char const *argv[]) {
int n = function(2);
return n;
}
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ add_lldb_tool(lldb-dap
SourceBreakpoint.cpp
DAP.cpp
Watchpoint.cpp
InstructionBreakpoint.cpp

LINK_LIBS
liblldb
Expand Down
30 changes: 29 additions & 1 deletion lldb/tools/lldb-dap/DAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ static std::string capitalize(llvm::StringRef str) {

void DAP::PopulateExceptionBreakpoints() {
llvm::call_once(init_exception_breakpoints_flag, [this]() {
exception_breakpoints = std::vector<ExceptionBreakpoint> {};
exception_breakpoints = std::vector<ExceptionBreakpoint>{};

if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
exception_breakpoints->emplace_back("cpp_catch", "C++ Catch",
Expand Down Expand Up @@ -996,4 +996,32 @@ void DAP::SetThreadFormat(llvm::StringRef format) {
}
}

InstructionBreakpoint *
DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) {
for (auto &bp : instruction_breakpoints) {
if (bp.second.id == bp_id)
return &bp.second;
}
return nullptr;
}

InstructionBreakpoint *
DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) {
const auto num = thread.GetStopReasonDataCount();
InstructionBreakpoint *inst_bp = nullptr;
for (size_t i = 0; i < num; i += 2) {
// thread.GetStopReasonDataAtIndex(i) will return the bp ID and
// thread.GetStopReasonDataAtIndex(i+1) will return the location
// within that breakpoint. We only care about the bp ID so we can
// see if this is an instruction breakpoint that is getting hit.
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
inst_bp = GetInstructionBreakpoint(bp_id);
// If any breakpoint is not an instruction breakpoint, then stop and
// report this as a normal breakpoint
if (inst_bp == nullptr)
return nullptr;
}
return inst_bp;
}

} // namespace lldb_dap
8 changes: 8 additions & 0 deletions lldb/tools/lldb-dap/DAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include "ExceptionBreakpoint.h"
#include "FunctionBreakpoint.h"
#include "IOStream.h"
#include "InstructionBreakpoint.h"
#include "ProgressEvent.h"
#include "RunInTerminal.h"
#include "SourceBreakpoint.h"
Expand All @@ -68,6 +69,8 @@ namespace lldb_dap {

typedef llvm::DenseMap<uint32_t, SourceBreakpoint> SourceBreakpointMap;
typedef llvm::StringMap<FunctionBreakpoint> FunctionBreakpointMap;
typedef llvm::DenseMap<lldb::addr_t, InstructionBreakpoint>
InstructionBreakpointMap;

enum class OutputType { Console, Stdout, Stderr, Telemetry };

Expand Down Expand Up @@ -160,6 +163,7 @@ struct DAP {
std::unique_ptr<std::ofstream> log;
llvm::StringMap<SourceBreakpointMap> source_breakpoints;
FunctionBreakpointMap function_breakpoints;
InstructionBreakpointMap instruction_breakpoints;
std::optional<std::vector<ExceptionBreakpoint>> exception_breakpoints;
llvm::once_flag init_exception_breakpoints_flag;
std::vector<std::string> pre_init_commands;
Expand Down Expand Up @@ -334,6 +338,10 @@ struct DAP {

void SetThreadFormat(llvm::StringRef format);

InstructionBreakpoint *GetInstructionBreakpoint(const lldb::break_id_t bp_id);

InstructionBreakpoint *GetInstructionBPFromStopReason(lldb::SBThread &thread);

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
Expand Down
1 change: 1 addition & 0 deletions lldb/tools/lldb-dap/DAPForward.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ struct ExceptionBreakpoint;
struct FunctionBreakpoint;
struct SourceBreakpoint;
struct Watchpoint;
struct InstructionBreakpoint;
} // namespace lldb_dap

namespace lldb {
Expand Down
28 changes: 28 additions & 0 deletions lldb/tools/lldb-dap/InstructionBreakpoint.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//===-- InstructionBreakpoint.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
//
//===----------------------------------------------------------------------===//

#include "InstructionBreakpoint.h"
#include "DAP.h"

namespace lldb_dap {

// Instruction Breakpoint
InstructionBreakpoint::InstructionBreakpoint(const llvm::json::Object &obj)
: Breakpoint(obj), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0),
offset(GetSigned(obj, "offset", 0)) {
GetString(obj, "instructionReference")
.getAsInteger(0, instructionAddressReference);
instructionAddressReference += offset;
}

void InstructionBreakpoint::SetInstructionBreakpoint() {
bp = g_dap.target.BreakpointCreateByAddress(instructionAddressReference);
id = bp.GetID();
}
} // namespace lldb_dap
36 changes: 36 additions & 0 deletions lldb/tools/lldb-dap/InstructionBreakpoint.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//===-- InstructionBreakpoint.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_TOOLS_LLDB_DAP_INSTRUCTIONBREAKPOINT_H
#define LLDB_TOOLS_LLDB_DAP_INSTRUCTIONBREAKPOINT_H

#include "Breakpoint.h"
#include "llvm/ADT/StringRef.h"

namespace lldb_dap {

// Instruction Breakpoint
struct InstructionBreakpoint : public Breakpoint {

lldb::addr_t instructionAddressReference;
int32_t id;
int32_t offset;

InstructionBreakpoint()
: Breakpoint(), instructionAddressReference(LLDB_INVALID_ADDRESS), id(0),
offset(0) {}
InstructionBreakpoint(const llvm::json::Object &obj);

// Set instruction breakpoint in LLDB as a new breakpoint
void SetInstructionBreakpoint();
};

} // namespace lldb_dap

#endif
Loading

0 comments on commit 5474995

Please sign in to comment.