[lldb-dap] Enabling instruction breakpoint support to lldb-dap. (#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 <sellendu@hu-sellendu-hyd.qualcomm.com>
Co-authored-by: Santhosh Kumar Ellendula <sellendu@hu-sellendu-lv.qualcomm.com>
This commit is contained in:
Santhosh Kumar Ellendula
2024-08-27 00:19:39 +05:30
committed by GitHub
parent e1d2251290
commit 89c27d6b07
14 changed files with 577 additions and 3 deletions

View File

@@ -1099,6 +1099,20 @@ class DebugCommunication(object):
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__(

View File

@@ -81,7 +81,10 @@ class DAPTestCaseBase(TestBase):
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

View File

@@ -0,0 +1,6 @@
CXX_SOURCES := main-copy.cpp
CXXFLAGS_EXTRAS := -O0 -g
include Makefile.rules
main-copy.cpp: main.cpp
cp -f $< $@

View File

@@ -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"]])

View File

@@ -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;
}

View File

@@ -38,6 +38,7 @@ add_lldb_tool(lldb-dap
SourceBreakpoint.cpp
DAP.cpp
Watchpoint.cpp
InstructionBreakpoint.cpp
LINK_LIBS
liblldb

View File

@@ -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",
@@ -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

View File

@@ -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"
@@ -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 };
@@ -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;
@@ -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

View File

@@ -15,6 +15,7 @@ struct ExceptionBreakpoint;
struct FunctionBreakpoint;
struct SourceBreakpoint;
struct Watchpoint;
struct InstructionBreakpoint;
} // namespace lldb_dap
namespace lldb {

View File

@@ -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

View File

@@ -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

View File

@@ -769,6 +769,70 @@ llvm::json::Value CreateStackFrame(lldb::SBFrame &frame) {
return llvm::json::Value(std::move(object));
}
// Response to `setInstructionBreakpoints` request.
// "Breakpoint": {
// "type": "object",
// "description": "Response to `setInstructionBreakpoints` request.",
// "properties": {
// "id": {
// "type": "number",
// "description": "The identifier for the breakpoint. It is needed if
// breakpoint events are used to update or remove breakpoints."
// },
// "verified": {
// "type": "boolean",
// "description": "If true, the breakpoint could be set (but not
// necessarily at the desired location."
// },
// "message": {
// "type": "string",
// "description": "A message about the state of the breakpoint.
// This is shown to the user and can be used to explain why a breakpoint
// could not be verified."
// },
// "source": {
// "type": "Source",
// "description": "The source where the breakpoint is located."
// },
// "line": {
// "type": "number",
// "description": "The start line of the actual range covered by the
// breakpoint."
// },
// "column": {
// "type": "number",
// "description": "The start column of the actual range covered by the
// breakpoint."
// },
// "endLine": {
// "type": "number",
// "description": "The end line of the actual range covered by the
// breakpoint."
// },
// "endColumn": {
// "type": "number",
// "description": "The end column of the actual range covered by the
// breakpoint. If no end line is given, then the end column is assumed to
// be in the start line."
// },
// "instructionReference": {
// "type": "string",
// "description": "A memory reference to where the breakpoint is set."
// },
// "offset": {
// "type": "number",
// "description": "The offset from the instruction reference.
// This can be negative."
// },
// },
// "required": [ "id", "verified", "line"]
// }
llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp) {
llvm::json::Object object;
ibp->CreateJsonObject(object);
return llvm::json::Value(std::move(object));
}
// "Thread": {
// "type": "object",
// "description": "A Thread",
@@ -893,7 +957,13 @@ llvm::json::Value CreateThreadStopped(lldb::SBThread &thread,
body.try_emplace("reason", "exception");
EmplaceSafeString(body, "description", exc_bp->label);
} else {
body.try_emplace("reason", "breakpoint");
InstructionBreakpoint *inst_bp =
g_dap.GetInstructionBPFromStopReason(thread);
if (inst_bp) {
body.try_emplace("reason", "instruction breakpoint");
} else {
body.try_emplace("reason", "breakpoint");
}
lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(0);
lldb::break_id_t bp_loc_id = thread.GetStopReasonDataAtIndex(1);
std::string desc_str =

View File

@@ -322,6 +322,17 @@ llvm::json::Value CreateSource(llvm::StringRef source_path);
/// definition outlined by Microsoft.
llvm::json::Value CreateStackFrame(lldb::SBFrame &frame);
/// Create a "instruction" object for a LLDB disassemble object as described in
/// the Visual Studio Code debug adaptor definition.
///
/// \param[in] bp
/// The LLDB instruction object used to populate the disassembly
/// instruction.
/// \return
/// A "Scope" JSON object with that follows the formal JSON
/// definition outlined by Microsoft.
llvm::json::Value CreateInstructionBreakpoint(BreakpointBase *ibp);
/// Create a "Thread" object for a LLDB thread object.
///
/// This function will fill in the following keys in the returned

View File

@@ -1723,6 +1723,8 @@ void request_initialize(const llvm::json::Object &request) {
body.try_emplace("supportsLogPoints", true);
// The debug adapter supports data watchpoints.
body.try_emplace("supportsDataBreakpoints", true);
// The debug adapter support for instruction breakpoint.
body.try_emplace("supportsInstructionBreakpoints", true);
// Put in non-DAP specification lldb specific information.
llvm::json::Object lldb_json;
@@ -4082,6 +4084,254 @@ void request__testGetTargetBreakpoints(const llvm::json::Object &request) {
g_dap.SendJSON(llvm::json::Value(std::move(response)));
}
// "SetInstructionBreakpointsRequest" : {
// "allOf" : [
// {"$ref" : "#/definitions/Request"}, {
// "type" : "object",
// "description" :
// "Replaces all existing instruction breakpoints. Typically, "
// "instruction breakpoints would be set from a disassembly window. "
// "\nTo clear all instruction breakpoints, specify an empty "
// "array.\nWhen an instruction breakpoint is hit, a `stopped` event "
// "(with reason `instruction breakpoint`) is generated.\nClients "
// "should only call this request if the corresponding capability "
// "`supportsInstructionBreakpoints` is true.",
// "properties" : {
// "command" : {"type" : "string", "enum" :
// ["setInstructionBreakpoints"]}, "arguments" :
// {"$ref" : "#/definitions/SetInstructionBreakpointsArguments"}
// },
// "required" : [ "command", "arguments" ]
// }
// ]
// },
// "SetInstructionBreakpointsArguments"
// : {
// "type" : "object",
// "description" : "Arguments for `setInstructionBreakpoints` request",
// "properties" : {
// "breakpoints" : {
// "type" : "array",
// "items" : {"$ref" : "#/definitions/InstructionBreakpoint"},
// "description" : "The instruction references of the breakpoints"
// }
// },
// "required" : ["breakpoints"]
// },
// "SetInstructionBreakpointsResponse"
// : {
// "allOf" : [
// {"$ref" : "#/definitions/Response"}, {
// "type" : "object",
// "description" : "Response to `setInstructionBreakpoints` request",
// "properties" : {
// "body" : {
// "type" : "object",
// "properties" : {
// "breakpoints" : {
// "type" : "array",
// "items" : {"$ref" : "#/definitions/Breakpoint"},
// "description" :
// "Information about the breakpoints. The array elements
// " "correspond to the elements of the `breakpoints`
// array."
// }
// },
// "required" : ["breakpoints"]
// }
// },
// "required" : ["body"]
// }
// ]
// },
// "InstructionBreakpoint" : {
// "type" : "object",
// "description" : "Properties of a breakpoint passed to the "
// "`setInstructionBreakpoints` request",
// "properties" : {
// "instructionReference" : {
// "type" : "string",
// "description" :
// "The instruction reference of the breakpoint.\nThis should be a "
// "memory or instruction pointer reference from an
// `EvaluateResponse`, "
// "`Variable`, `StackFrame`, `GotoTarget`, or `Breakpoint`."
// },
// "offset" : {
// "type" : "integer",
// "description" : "The offset from the instruction reference in "
// "bytes.\nThis can be negative."
// },
// "condition" : {
// "type" : "string",
// "description" : "An expression for conditional breakpoints.\nIt is only
// "
// "honored by a debug adapter if the corresponding "
// "capability `supportsConditionalBreakpoints` is true."
// },
// "hitCondition" : {
// "type" : "string",
// "description" : "An expression that controls how many hits of the "
// "breakpoint are ignored.\nThe debug adapter is expected
// " "to interpret the expression as needed.\nThe
// attribute " "is only honored by a debug adapter if the
// corresponding " "capability
// `supportsHitConditionalBreakpoints` is true."
// },
// "mode" : {
// "type" : "string",
// "description" : "The mode of this breakpoint. If defined, this must be
// "
// "one of the `breakpointModes` the debug adapter "
// "advertised in its `Capabilities`."
// }
// },
// "required" : ["instructionReference"]
// },
// "Breakpoint"
// : {
// "type" : "object",
// "description" :
// "Information about a breakpoint created in `setBreakpoints`, "
// "`setFunctionBreakpoints`, `setInstructionBreakpoints`, or "
// "`setDataBreakpoints` requests.",
// "properties" : {
// "id" : {
// "type" : "integer",
// "description" :
// "The identifier for the breakpoint. It is needed if breakpoint
// " "events are used to update or remove breakpoints."
// },
// "verified" : {
// "type" : "boolean",
// "description" : "If true, the breakpoint could be set (but not "
// "necessarily at the desired location)."
// },
// "message" : {
// "type" : "string",
// "description" : "A message about the state of the breakpoint.\nThis
// "
// "is shown to the user and can be used to explain
// why " "a breakpoint could not be verified."
// },
// "source" : {
// "$ref" : "#/definitions/Source",
// "description" : "The source where the breakpoint is located."
// },
// "line" : {
// "type" : "integer",
// "description" :
// "The start line of the actual range covered by the breakpoint."
// },
// "column" : {
// "type" : "integer",
// "description" :
// "Start position of the source range covered by the breakpoint.
// " "It is measured in UTF-16 code units and the client
// capability "
// "`columnsStartAt1` determines whether it is 0- or 1-based."
// },
// "endLine" : {
// "type" : "integer",
// "description" :
// "The end line of the actual range covered by the breakpoint."
// },
// "endColumn" : {
// "type" : "integer",
// "description" :
// "End position of the source range covered by the breakpoint. It
// " "is measured in UTF-16 code units and the client capability "
// "`columnsStartAt1` determines whether it is 0- or 1-based.\nIf
// " "no end line is given, then the end column is assumed to be
// in " "the start line."
// },
// "instructionReference" : {
// "type" : "string",
// "description" : "A memory reference to where the breakpoint is
// set."
// },
// "offset" : {
// "type" : "integer",
// "description" : "The offset from the instruction reference.\nThis "
// "can be negative."
// },
// "reason" : {
// "type" : "string",
// "description" :
// "A machine-readable explanation of why a breakpoint may not be
// " "verified. If a breakpoint is verified or a specific reason
// is " "not known, the adapter should omit this property.
// Possible " "values include:\n\n- `pending`: Indicates a
// breakpoint might be " "verified in the future, but the adapter
// cannot verify it in the " "current state.\n - `failed`:
// Indicates a breakpoint was not " "able to be verified, and the
// adapter does not believe it can be " "verified without
// intervention.",
// "enum" : [ "pending", "failed" ]
// }
// },
// "required" : ["verified"]
// },
void request_setInstructionBreakpoints(const llvm::json::Object &request) {
llvm::json::Object response;
llvm::json::Array response_breakpoints;
llvm::json::Object body;
FillResponse(request, response);
auto arguments = request.getObject("arguments");
auto breakpoints = arguments->getArray("breakpoints");
// It holds active instruction breakpoint list received from DAP.
InstructionBreakpointMap request_ibp;
if (breakpoints) {
for (const auto &bp : *breakpoints) {
auto bp_obj = bp.getAsObject();
if (bp_obj) {
// Read instruction breakpoint request.
InstructionBreakpoint inst_bp(*bp_obj);
// Store them into map for reference.
request_ibp[inst_bp.instructionAddressReference] = std::move(inst_bp);
}
}
// Iterate previous active instruction breakpoint list.
for (auto &prev_ibp : g_dap.instruction_breakpoints) {
// Find previous instruction breakpoint reference address in newly
// received instruction breakpoint list.
auto inst_reference = request_ibp.find(prev_ibp.first);
// Request for remove and delete the breakpoint, if the prev instruction
// breakpoint ID is not available in active instrcation breakpoint list.
// Means delete removed breakpoint instance.
if (inst_reference == request_ibp.end()) {
g_dap.target.BreakpointDelete(prev_ibp.second.id);
// Update Prev instruction breakpoint list.
g_dap.instruction_breakpoints.erase(prev_ibp.first);
} else {
// Instead of recreating breakpoint instance, update the breakpoint if
// there are any conditional changes.
prev_ibp.second.UpdateBreakpoint(inst_reference->second);
request_ibp.erase(inst_reference);
response_breakpoints.emplace_back(
CreateInstructionBreakpoint(&prev_ibp.second));
}
}
for (auto &req_bpi : request_ibp) {
// Add this breakpoint info to the response
g_dap.instruction_breakpoints[req_bpi.first] = std::move(req_bpi.second);
InstructionBreakpoint &new_bp =
g_dap.instruction_breakpoints[req_bpi.first];
new_bp.SetInstructionBreakpoint();
response_breakpoints.emplace_back(CreateInstructionBreakpoint(&new_bp));
}
}
body.try_emplace("breakpoints", std::move(response_breakpoints));
response.try_emplace("body", std::move(body));
g_dap.SendJSON(llvm::json::Value(std::move(response)));
}
void RegisterRequestCallbacks() {
g_dap.RegisterRequestCallback("attach", request_attach);
g_dap.RegisterRequestCallback("completions", request_completions);
@@ -4114,6 +4364,9 @@ void RegisterRequestCallbacks() {
g_dap.RegisterRequestCallback("threads", request_threads);
g_dap.RegisterRequestCallback("variables", request_variables);
g_dap.RegisterRequestCallback("disassemble", request_disassemble);
// Instruction breakpoint request
g_dap.RegisterRequestCallback("setInstructionBreakpoints",
request_setInstructionBreakpoints);
// Custom requests
g_dap.RegisterRequestCallback("compileUnits", request_compileUnits);
g_dap.RegisterRequestCallback("modules", request_modules);