Re-land [lldb-dap] Add support for data breakpoint. (#81909)

This implements functionality to handle DataBreakpointInfo request and
SetDataBreakpoints request.

Previous commit
8c56e78ec5
was reverted because setting 1 byte watchpoint failed in the new test on
ARM64. So, I changed the test to setting 4 byte watchpoint instead, and
hope this won't break it again. It also adds the fixes from
https://github.com/llvm/llvm-project/pull/81680.
This commit is contained in:
Zequan Wu
2024-02-22 16:11:40 -05:00
committed by GitHub
parent 7f71fa909a
commit df6f756a19
9 changed files with 590 additions and 34 deletions

View File

@@ -501,6 +501,18 @@ class DebugCommunication(object):
return variable["value"]
return None
def get_local_variable_child(self, name, child_name, frameIndex=0, threadId=None):
local = self.get_local_variable(name, frameIndex, threadId)
if local["variablesReference"] == 0:
return None
children = self.request_variables(local["variablesReference"])["body"][
"variables"
]
for child in children:
if child["name"] == child_name:
return child
return None
def replay_packets(self, replay_file_path):
f = open(replay_file_path, "r")
mode = "invalid"
@@ -895,6 +907,41 @@ class DebugCommunication(object):
}
return self.send_recv(command_dict)
def request_dataBreakpointInfo(
self, variablesReference, name, frameIndex=0, threadId=None
):
stackFrame = self.get_stackFrame(frameIndex=frameIndex, threadId=threadId)
if stackFrame is None:
return []
args_dict = {
"variablesReference": variablesReference,
"name": name,
"frameId": stackFrame["id"],
}
command_dict = {
"command": "dataBreakpointInfo",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)
def request_setDataBreakpoint(self, dataBreakpoints):
"""dataBreakpoints is a list of dictionary with following fields:
{
dataId: (address in hex)/(size in bytes)
accessType: read/write/readWrite
[condition]: string
[hitCondition]: string
}
"""
args_dict = {"breakpoints": dataBreakpoints}
command_dict = {
"command": "setDataBreakpoints",
"type": "request",
"arguments": args_dict,
}
return self.send_recv(command_dict)
def request_compileUnits(self, moduleId):
args_dict = {"moduleId": moduleId}
command_dict = {

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -0,0 +1,131 @@
"""
Test lldb-dap dataBreakpointInfo and setDataBreakpoints requests
"""
from lldbsuite.test.decorators import *
from lldbsuite.test.lldbtest import *
import lldbdap_testcase
class TestDAP_setDataBreakpoints(lldbdap_testcase.DAPTestCaseBase):
def setUp(self):
lldbdap_testcase.DAPTestCaseBase.setUp(self)
self.accessTypes = ["read", "write", "readWrite"]
@skipIfWindows
@skipIfRemote
def test_expression(self):
"""Tests setting data breakpoints on expression."""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.cpp"
first_loop_break_line = line_number(source, "// first loop breakpoint")
self.set_source_breakpoints(source, [first_loop_break_line])
self.continue_to_next_stop()
self.dap_server.get_stackFrame()
# Test setting write watchpoint using expressions: &x, arr+2
response_x = self.dap_server.request_dataBreakpointInfo(0, "&x")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(0, "arr+2")
# Test response from dataBreakpointInfo request.
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "4")
self.assertEquals(response_arr_2["body"]["accessTypes"], self.accessTypes)
dataBreakpoints = [
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
self.assertEquals(
set_response["body"]["breakpoints"],
[{"verified": True}, {"verified": True}],
)
self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(x_val, "2")
self.assertEquals(i_val, "1")
self.continue_to_next_stop()
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(arr_2["value"], "42")
self.assertEquals(i_val, "2")
@skipIfWindows
@skipIfRemote
def test_functionality(self):
"""Tests setting data breakpoints on variable."""
program = self.getBuildArtifact("a.out")
self.build_and_launch(program)
source = "main.cpp"
first_loop_break_line = line_number(source, "// first loop breakpoint")
self.set_source_breakpoints(source, [first_loop_break_line])
self.continue_to_next_stop()
self.dap_server.get_local_variables()
# Test write watchpoints on x, arr[2]
response_x = self.dap_server.request_dataBreakpointInfo(1, "x")
arr = self.dap_server.get_local_variable("arr")
response_arr_2 = self.dap_server.request_dataBreakpointInfo(
arr["variablesReference"], "[2]"
)
# Test response from dataBreakpointInfo request.
self.assertEquals(response_x["body"]["dataId"].split("/")[1], "4")
self.assertEquals(response_x["body"]["accessTypes"], self.accessTypes)
self.assertEquals(response_arr_2["body"]["dataId"].split("/")[1], "4")
self.assertEquals(response_arr_2["body"]["accessTypes"], self.accessTypes)
dataBreakpoints = [
{"dataId": response_x["body"]["dataId"], "accessType": "write"},
{"dataId": response_arr_2["body"]["dataId"], "accessType": "write"},
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
self.assertEquals(
set_response["body"]["breakpoints"],
[{"verified": True}, {"verified": True}],
)
self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(x_val, "2")
self.assertEquals(i_val, "1")
self.continue_to_next_stop()
arr_2 = self.dap_server.get_local_variable_child("arr", "[2]")
i_val = self.dap_server.get_local_variable_value("i")
self.assertEquals(arr_2["value"], "42")
self.assertEquals(i_val, "2")
self.dap_server.request_setDataBreakpoint([])
# Test hit condition
second_loop_break_line = line_number(source, "// second loop breakpoint")
breakpoint_ids = self.set_source_breakpoints(source, [second_loop_break_line])
self.continue_to_breakpoints(breakpoint_ids)
dataBreakpoints = [
{
"dataId": response_x["body"]["dataId"],
"accessType": "write",
"hitCondition": "2",
}
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
self.assertEquals(set_response["body"]["breakpoints"], [{"verified": True}])
self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
self.assertEquals(x_val, "3")
# Test condition
dataBreakpoints = [
{
"dataId": response_x["body"]["dataId"],
"accessType": "write",
"condition": "x==10",
}
]
set_response = self.dap_server.request_setDataBreakpoint(dataBreakpoints)
self.assertEquals(set_response["body"]["breakpoints"], [{"verified": True}])
self.continue_to_next_stop()
x_val = self.dap_server.get_local_variable_value("x")
self.assertEquals(x_val, "10")

View File

@@ -0,0 +1,17 @@
int main(int argc, char const *argv[]) {
// Test for data breakpoint
int x = 0;
int arr[4] = {1, 2, 3, 4};
for (int i = 0; i < 5; ++i) { // first loop breakpoint
if (i == 1) {
x = i + 1;
} else if (i == 2) {
arr[i] = 42;
}
}
x = 1;
for (int i = 0; i < 10; ++i) { // second loop breakpoint
++x;
}
}

View File

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

View File

@@ -14,6 +14,7 @@ struct BreakpointBase;
struct ExceptionBreakpoint;
struct FunctionBreakpoint;
struct SourceBreakpoint;
struct Watchpoint;
} // namespace lldb_dap
namespace lldb {
@@ -39,6 +40,7 @@ class SBStringList;
class SBTarget;
class SBThread;
class SBValue;
class SBWatchpoint;
} // namespace lldb
#endif

View File

@@ -0,0 +1,48 @@
//===-- Watchpoint.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 "Watchpoint.h"
#include "DAP.h"
#include "JSONUtils.h"
#include "llvm/ADT/StringExtras.h"
namespace lldb_dap {
Watchpoint::Watchpoint(const llvm::json::Object &obj) : BreakpointBase(obj) {
llvm::StringRef dataId = GetString(obj, "dataId");
std::string accessType = GetString(obj, "accessType").str();
auto [addr_str, size_str] = dataId.split('/');
lldb::addr_t addr;
size_t size;
llvm::to_integer(addr_str, addr, 16);
llvm::to_integer(size_str, size);
lldb::SBWatchpointOptions options;
options.SetWatchpointTypeRead(accessType != "write");
if (accessType != "read")
options.SetWatchpointTypeWrite(lldb::eWatchpointWriteTypeOnModify);
wp = g_dap.target.WatchpointCreateByAddress(addr, size, options, error);
SetCondition();
SetHitCondition();
}
void Watchpoint::SetCondition() { wp.SetCondition(condition.c_str()); }
void Watchpoint::SetHitCondition() {
uint64_t hitCount = 0;
if (llvm::to_integer(hitCondition, hitCount))
wp.SetIgnoreCount(hitCount - 1);
}
void Watchpoint::CreateJsonObject(llvm::json::Object &object) {
if (error.Success()) {
object.try_emplace("verified", true);
} else {
object.try_emplace("verified", false);
EmplaceSafeString(object, "message", error.GetCString());
}
}
} // namespace lldb_dap

View File

@@ -0,0 +1,34 @@
//===-- Watchpoint.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_WATCHPOINT_H
#define LLDB_TOOLS_LLDB_DAP_WATCHPOINT_H
#include "BreakpointBase.h"
#include "lldb/API/SBError.h"
#include "lldb/API/SBWatchpoint.h"
#include "lldb/API/SBWatchpointOptions.h"
namespace lldb_dap {
struct Watchpoint : public BreakpointBase {
// The LLDB breakpoint associated wit this watchpoint.
lldb::SBWatchpoint wp;
lldb::SBError error;
Watchpoint() = default;
Watchpoint(const llvm::json::Object &obj);
Watchpoint(lldb::SBWatchpoint wp) : wp(wp) {}
void SetCondition() override;
void SetHitCondition() override;
void CreateJsonObject(llvm::json::Object &object) override;
};
} // namespace lldb_dap
#endif

View File

@@ -7,6 +7,8 @@
//===----------------------------------------------------------------------===//
#include "DAP.h"
#include "Watchpoint.h"
#include "lldb/API/SBMemoryRegionInfo.h"
#include <cassert>
#include <climits>
@@ -560,6 +562,46 @@ void EventThreadFunction() {
}
}
lldb::SBValue FindVariable(uint64_t variablesReference, llvm::StringRef name) {
lldb::SBValue variable;
if (lldb::SBValueList *top_scope = GetTopLevelScope(variablesReference)) {
bool is_duplicated_variable_name = name.contains(" @");
// variablesReference is one of our scopes, not an actual variable it is
// asking for a variable in locals or globals or registers
int64_t end_idx = top_scope->GetSize();
// Searching backward so that we choose the variable in closest scope
// among variables of the same name.
for (int64_t i = end_idx - 1; i >= 0; --i) {
lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
std::string variable_name = CreateUniqueVariableNameForDisplay(
curr_variable, is_duplicated_variable_name);
if (variable_name == name) {
variable = curr_variable;
break;
}
}
} else {
// This is not under the globals or locals scope, so there are no duplicated
// names.
// We have a named item within an actual variable so we need to find it
// withing the container variable by name.
lldb::SBValue container = g_dap.variables.GetVariable(variablesReference);
variable = container.GetChildMemberWithName(name.data());
if (!variable.IsValid()) {
if (name.starts_with("[")) {
llvm::StringRef index_str(name.drop_front(1));
uint64_t index = 0;
if (!index_str.consumeInteger(0, index)) {
if (index_str == "]")
variable = container.GetChildAtIndex(index);
}
}
}
}
return variable;
}
// Both attach and launch take a either a sourcePath or sourceMap
// argument (or neither), from which we need to set the target.source-map.
void SetSourceMapFromArguments(const llvm::json::Object &arguments) {
@@ -1647,6 +1689,8 @@ void request_initialize(const llvm::json::Object &request) {
body.try_emplace("supportsProgressReporting", true);
// The debug adapter supports 'logMessage' in breakpoint.
body.try_emplace("supportsLogPoints", true);
// The debug adapter supports data watchpoints.
body.try_emplace("supportsDataBreakpoints", true);
response.try_emplace("body", std::move(body));
g_dap.SendJSON(llvm::json::Value(std::move(response)));
@@ -2593,6 +2637,264 @@ void request_setFunctionBreakpoints(const llvm::json::Object &request) {
g_dap.SendJSON(llvm::json::Value(std::move(response)));
}
// "DataBreakpointInfoRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
// "description": "Obtains information on a possible data breakpoint that
// could be set on an expression or variable.\nClients should only call this
// request if the corresponding capability `supportsDataBreakpoints` is
// true.", "properties": {
// "command": {
// "type": "string",
// "enum": [ "dataBreakpointInfo" ]
// },
// "arguments": {
// "$ref": "#/definitions/DataBreakpointInfoArguments"
// }
// },
// "required": [ "command", "arguments" ]
// }]
// },
// "DataBreakpointInfoArguments": {
// "type": "object",
// "description": "Arguments for `dataBreakpointInfo` request.",
// "properties": {
// "variablesReference": {
// "type": "integer",
// "description": "Reference to the variable container if the data
// breakpoint is requested for a child of the container. The
// `variablesReference` must have been obtained in the current suspended
// state. See 'Lifetime of Object References' in the Overview section for
// details."
// },
// "name": {
// "type": "string",
// "description": "The name of the variable's child to obtain data
// breakpoint information for.\nIf `variablesReference` isn't specified,
// this can be an expression."
// },
// "frameId": {
// "type": "integer",
// "description": "When `name` is an expression, evaluate it in the scope
// of this stack frame. If not specified, the expression is evaluated in
// the global scope. When `variablesReference` is specified, this property
// has no effect."
// }
// },
// "required": [ "name" ]
// },
// "DataBreakpointInfoResponse": {
// "allOf": [ { "$ref": "#/definitions/Response" }, {
// "type": "object",
// "description": "Response to `dataBreakpointInfo` request.",
// "properties": {
// "body": {
// "type": "object",
// "properties": {
// "dataId": {
// "type": [ "string", "null" ],
// "description": "An identifier for the data on which a data
// breakpoint can be registered with the `setDataBreakpoints`
// request or null if no data breakpoint is available. If a
// `variablesReference` or `frameId` is passed, the `dataId` is
// valid in the current suspended state, otherwise it's valid
// indefinitely. See 'Lifetime of Object References' in the Overview
// section for details. Breakpoints set using the `dataId` in the
// `setDataBreakpoints` request may outlive the lifetime of the
// associated `dataId`."
// },
// "description": {
// "type": "string",
// "description": "UI string that describes on what data the
// breakpoint is set on or why a data breakpoint is not available."
// },
// "accessTypes": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/DataBreakpointAccessType"
// },
// "description": "Attribute lists the available access types for a
// potential data breakpoint. A UI client could surface this
// information."
// },
// "canPersist": {
// "type": "boolean",
// "description": "Attribute indicates that a potential data
// breakpoint could be persisted across sessions."
// }
// },
// "required": [ "dataId", "description" ]
// }
// },
// "required": [ "body" ]
// }]
// }
void request_dataBreakpointInfo(const llvm::json::Object &request) {
llvm::json::Object response;
FillResponse(request, response);
llvm::json::Object body;
lldb::SBError error;
llvm::json::Array accessTypes{"read", "write", "readWrite"};
const auto *arguments = request.getObject("arguments");
const auto variablesReference =
GetUnsigned(arguments, "variablesReference", 0);
llvm::StringRef name = GetString(arguments, "name");
lldb::SBFrame frame = g_dap.GetLLDBFrame(*arguments);
lldb::SBValue variable = FindVariable(variablesReference, name);
std::string addr, size;
if (variable.IsValid()) {
lldb::addr_t load_addr = variable.GetLoadAddress();
size_t byte_size = variable.GetByteSize();
if (load_addr == LLDB_INVALID_ADDRESS) {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"does not exist in memory, its location is " +
std::string(variable.GetLocation()));
} else if (byte_size == 0) {
body.try_emplace("dataId", nullptr);
body.try_emplace("description", "variable size is 0");
} else {
addr = llvm::utohexstr(load_addr);
size = llvm::utostr(byte_size);
}
} else if (variablesReference == 0 && frame.IsValid()) {
lldb::SBValue value = frame.EvaluateExpression(name.data());
if (value.GetError().Fail()) {
lldb::SBError error = value.GetError();
const char *error_cstr = error.GetCString();
body.try_emplace("dataId", nullptr);
body.try_emplace("description", error_cstr && error_cstr[0]
? std::string(error_cstr)
: "evaluation failed");
} else {
uint64_t load_addr = value.GetValueAsUnsigned();
addr = llvm::utohexstr(load_addr);
lldb::SBMemoryRegionInfo region;
lldb::SBError err =
g_dap.target.GetProcess().GetMemoryRegionInfo(load_addr, region);
if (err.Success()) {
if (!(region.IsReadable() || region.IsWritable())) {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"memory region for address " + addr +
" has no read or write permissions");
} else {
lldb::SBData data = value.GetPointeeData();
if (data.IsValid())
size = llvm::utostr(data.GetByteSize());
else {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"unable to get byte size for expression: " +
name.str());
}
}
} else {
body.try_emplace("dataId", nullptr);
body.try_emplace("description",
"unable to get memory region info for address " +
addr);
}
}
} else {
body.try_emplace("dataId", nullptr);
body.try_emplace("description", "variable not found: " + name.str());
}
if (!body.getObject("dataId")) {
body.try_emplace("dataId", addr + "/" + size);
body.try_emplace("accessTypes", std::move(accessTypes));
body.try_emplace("description",
size + " bytes at " + addr + " " + name.str());
}
response.try_emplace("body", std::move(body));
g_dap.SendJSON(llvm::json::Value(std::move(response)));
}
// "SetDataBreakpointsRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
// "description": "Replaces all existing data breakpoints with new data
// breakpoints.\nTo clear all data breakpoints, specify an empty
// array.\nWhen a data breakpoint is hit, a `stopped` event (with reason
// `data breakpoint`) is generated.\nClients should only call this request
// if the corresponding capability `supportsDataBreakpoints` is true.",
// "properties": {
// "command": {
// "type": "string",
// "enum": [ "setDataBreakpoints" ]
// },
// "arguments": {
// "$ref": "#/definitions/SetDataBreakpointsArguments"
// }
// },
// "required": [ "command", "arguments" ]
// }]
// },
// "SetDataBreakpointsArguments": {
// "type": "object",
// "description": "Arguments for `setDataBreakpoints` request.",
// "properties": {
// "breakpoints": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/DataBreakpoint"
// },
// "description": "The contents of this array replaces all existing data
// breakpoints. An empty array clears all data breakpoints."
// }
// },
// "required": [ "breakpoints" ]
// },
// "SetDataBreakpointsResponse": {
// "allOf": [ { "$ref": "#/definitions/Response" }, {
// "type": "object",
// "description": "Response to `setDataBreakpoints` request.\nReturned is
// information about each breakpoint created by this request.",
// "properties": {
// "body": {
// "type": "object",
// "properties": {
// "breakpoints": {
// "type": "array",
// "items": {
// "$ref": "#/definitions/Breakpoint"
// },
// "description": "Information about the data breakpoints. The array
// elements correspond to the elements of the input argument
// `breakpoints` array."
// }
// },
// "required": [ "breakpoints" ]
// }
// },
// "required": [ "body" ]
// }]
// }
void request_setDataBreakpoints(const llvm::json::Object &request) {
llvm::json::Object response;
lldb::SBError error;
FillResponse(request, response);
const auto *arguments = request.getObject("arguments");
const auto *breakpoints = arguments->getArray("breakpoints");
llvm::json::Array response_breakpoints;
g_dap.target.DeleteAllWatchpoints();
if (breakpoints) {
for (const auto &bp : *breakpoints) {
const auto *bp_obj = bp.getAsObject();
if (bp_obj) {
Watchpoint wp(*bp_obj);
AppendBreakpoint(&wp, response_breakpoints);
}
}
}
llvm::json::Object body;
body.try_emplace("breakpoints", std::move(response_breakpoints));
response.try_emplace("body", std::move(body));
g_dap.SendJSON(llvm::json::Value(std::move(response)));
}
// "SourceRequest": {
// "allOf": [ { "$ref": "#/definitions/Request" }, {
// "type": "object",
@@ -3076,7 +3378,6 @@ void request_setVariable(const llvm::json::Object &request) {
const auto variablesReference =
GetUnsigned(arguments, "variablesReference", 0);
llvm::StringRef name = GetString(arguments, "name");
bool is_duplicated_variable_name = name.contains(" @");
const auto value = GetString(arguments, "value");
// Set success to false just in case we don't find the variable by name
@@ -3097,40 +3398,8 @@ void request_setVariable(const llvm::json::Object &request) {
const auto id_value = GetUnsigned(arguments, "id", UINT64_MAX);
if (id_value != UINT64_MAX) {
variable = g_dap.variables.GetVariable(id_value);
} else if (lldb::SBValueList *top_scope =
GetTopLevelScope(variablesReference)) {
// variablesReference is one of our scopes, not an actual variable it is
// asking for a variable in locals or globals or registers
int64_t end_idx = top_scope->GetSize();
// Searching backward so that we choose the variable in closest scope
// among variables of the same name.
for (int64_t i = end_idx - 1; i >= 0; --i) {
lldb::SBValue curr_variable = top_scope->GetValueAtIndex(i);
std::string variable_name = CreateUniqueVariableNameForDisplay(
curr_variable, is_duplicated_variable_name);
if (variable_name == name) {
variable = curr_variable;
break;
}
}
} else {
// This is not under the globals or locals scope, so there are no duplicated
// names.
// We have a named item within an actual variable so we need to find it
// withing the container variable by name.
lldb::SBValue container = g_dap.variables.GetVariable(variablesReference);
variable = container.GetChildMemberWithName(name.data());
if (!variable.IsValid()) {
if (name.starts_with("[")) {
llvm::StringRef index_str(name.drop_front(1));
uint64_t index = 0;
if (!index_str.consumeInteger(0, index)) {
if (index_str == "]")
variable = container.GetChildAtIndex(index);
}
}
}
variable = FindVariable(variablesReference, name);
}
if (variable.IsValid()) {
@@ -3613,6 +3882,10 @@ void RegisterRequestCallbacks() {
request_setExceptionBreakpoints);
g_dap.RegisterRequestCallback("setFunctionBreakpoints",
request_setFunctionBreakpoints);
g_dap.RegisterRequestCallback("dataBreakpointInfo",
request_dataBreakpointInfo);
g_dap.RegisterRequestCallback("setDataBreakpoints",
request_setDataBreakpoints);
g_dap.RegisterRequestCallback("setVariable", request_setVariable);
g_dap.RegisterRequestCallback("source", request_source);
g_dap.RegisterRequestCallback("stackTrace", request_stackTrace);