1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
|
import * as vscode from "vscode";
import * as child_process from "child_process";
import * as util from "util";
import { LLDBDapServer } from "./lldb-dap-server";
import { createDebugAdapterExecutable } from "./debug-adapter-factory";
import { ConfigureButton, showErrorMessage } from "./ui/show-error-message";
import { ErrorWithNotification } from "./ui/error-with-notification";
const exec = util.promisify(child_process.execFile);
/**
* Determines whether or not the given lldb-dap executable supports executing
* in server mode.
*
* @param exe the path to the lldb-dap executable
* @returns a boolean indicating whether or not lldb-dap supports server mode
*/
async function isServerModeSupported(exe: string): Promise<boolean> {
const { stdout } = await exec(exe, ["--help"]);
return /--connection/.test(stdout);
}
interface BoolConfig {
type: "boolean";
default: boolean;
}
interface StringConfig {
type: "string";
default: string;
}
interface NumberConfig {
type: "number";
default: number;
}
interface StringArrayConfig {
type: "stringArray";
default: string[];
}
type DefaultConfig =
| BoolConfig
| NumberConfig
| StringConfig
| StringArrayConfig;
const configurations: Record<string, DefaultConfig> = {
// Keys for debugger configurations.
commandEscapePrefix: { type: "string", default: "`" },
customFrameFormat: { type: "string", default: "" },
customThreadFormat: { type: "string", default: "" },
detachOnError: { type: "boolean", default: false },
disableASLR: { type: "boolean", default: true },
disableSTDIO: { type: "boolean", default: false },
displayExtendedBacktrace: { type: "boolean", default: false },
enableAutoVariableSummaries: { type: "boolean", default: false },
enableSyntheticChildDebugging: { type: "boolean", default: false },
timeout: { type: "number", default: 30 },
// Keys for platform / target configuration.
platformName: { type: "string", default: "" },
targetTriple: { type: "string", default: "" },
// Keys for debugger command hooks.
initCommands: { type: "stringArray", default: [] },
preRunCommands: { type: "stringArray", default: [] },
postRunCommands: { type: "stringArray", default: [] },
stopCommands: { type: "stringArray", default: [] },
exitCommands: { type: "stringArray", default: [] },
terminateCommands: { type: "stringArray", default: [] },
};
export class LLDBDapConfigurationProvider
implements vscode.DebugConfigurationProvider
{
constructor(private readonly server: LLDBDapServer) {}
async resolveDebugConfiguration(
folder: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,
token?: vscode.CancellationToken,
): Promise<vscode.DebugConfiguration> {
let config = vscode.workspace.getConfiguration("lldb-dap");
for (const [key, cfg] of Object.entries(configurations)) {
if (Reflect.has(debugConfiguration, key)) {
continue;
}
const value = config.get(key);
if (value === undefined || value === cfg.default) {
continue;
}
switch (cfg.type) {
case "string":
if (typeof value !== "string") {
throw new Error(`Expected ${key} to be a string, got ${value}`);
}
break;
case "number":
if (typeof value !== "number") {
throw new Error(`Expected ${key} to be a number, got ${value}`);
}
break;
case "boolean":
if (typeof value !== "boolean") {
throw new Error(`Expected ${key} to be a boolean, got ${value}`);
}
break;
case "stringArray":
if (typeof value !== "object" && Array.isArray(value)) {
throw new Error(
`Expected ${key} to be a array of strings, got ${value}`,
);
}
if ((value as string[]).length === 0) {
continue;
}
break;
}
debugConfiguration[key] = value;
}
return debugConfiguration;
}
async resolveDebugConfigurationWithSubstitutedVariables(
folder: vscode.WorkspaceFolder | undefined,
debugConfiguration: vscode.DebugConfiguration,
_token?: vscode.CancellationToken,
): Promise<vscode.DebugConfiguration | null | undefined> {
try {
if (
"debugAdapterHostname" in debugConfiguration &&
!("debugAdapterPort" in debugConfiguration)
) {
throw new ErrorWithNotification(
"A debugAdapterPort must be provided when debugAdapterHostname is set. Please update your launch configuration.",
new ConfigureButton(),
);
}
// Check if we're going to launch a debug session or use an existing process
if ("debugAdapterPort" in debugConfiguration) {
if (
"debugAdapterExecutable" in debugConfiguration ||
"debugAdapterArgs" in debugConfiguration
) {
throw new ErrorWithNotification(
"The debugAdapterPort property is incompatible with debugAdapterExecutable and debugAdapterArgs. Please update your launch configuration.",
new ConfigureButton(),
);
}
} else {
// Always try to create the debug adapter executable as this will show the user errors
// if there are any.
const executable = await createDebugAdapterExecutable(
folder,
debugConfiguration,
);
if (!executable) {
return undefined;
}
// Server mode needs to be handled here since DebugAdapterDescriptorFactory
// will show an unhelpful error if it returns undefined. We'd rather show a
// nicer error message here and allow stopping the debug session gracefully.
const config = vscode.workspace.getConfiguration("lldb-dap", folder);
if (
config.get<boolean>("serverMode", false) &&
(await isServerModeSupported(executable.command))
) {
const serverInfo = await this.server.start(
executable.command,
executable.args,
executable.options,
);
if (!serverInfo) {
return undefined;
}
// Use a debug adapter host and port combination rather than an executable
// and list of arguments.
delete debugConfiguration.debugAdapterExecutable;
delete debugConfiguration.debugAdapterArgs;
debugConfiguration.debugAdapterHostname = serverInfo.host;
debugConfiguration.debugAdapterPort = serverInfo.port;
}
}
return debugConfiguration;
} catch (error) {
// Show a better error message to the user if possible
if (!(error instanceof ErrorWithNotification)) {
throw error;
}
return await error.showNotification({
modal: true,
showConfigureButton: true,
});
}
}
}
|