Skip to content

Commit aa20767

Browse files
[lldb-dap] Implement command directives (llvm#74808)
This adds support for optionally prefixing any command with `?` and/or `!`. - `?` prevents the output of a commands to be printed to the console unless it fails. - `!` aborts the dap if the command fails. They come in handy when programmatically running commands on behalf of the user without wanting them to know unless they fail, or when a critical setup is required as part of launchCommands and it's better to abort on failures than to silently skip.
1 parent 68ea91d commit aa20767

File tree

10 files changed

+287
-60
lines changed

10 files changed

+287
-60
lines changed

lldb/packages/Python/lldbsuite/test/tools/lldb-dap/lldbdap_testcase.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ def attach(
291291
postRunCommands=None,
292292
sourceMap=None,
293293
sourceInitFile=False,
294+
expectFailure=False,
294295
):
295296
"""Build the default Makefile target, create the DAP debug adaptor,
296297
and attach to the process.
@@ -322,6 +323,8 @@ def cleanup():
322323
postRunCommands=postRunCommands,
323324
sourceMap=sourceMap,
324325
)
326+
if expectFailure:
327+
return response
325328
if not (response and response["success"]):
326329
self.assertTrue(
327330
response["success"], "attach failed (%s)" % (response["message"])
@@ -437,6 +440,8 @@ def build_and_launch(
437440
commandEscapePrefix=None,
438441
customFrameFormat=None,
439442
customThreadFormat=None,
443+
launchCommands=None,
444+
expectFailure=False,
440445
):
441446
"""Build the default Makefile target, create the DAP debug adaptor,
442447
and launch the process.
@@ -470,4 +475,6 @@ def build_and_launch(
470475
commandEscapePrefix=commandEscapePrefix,
471476
customFrameFormat=customFrameFormat,
472477
customThreadFormat=customThreadFormat,
478+
launchCommands=launchCommands,
479+
expectFailure=expectFailure,
473480
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
CXX_SOURCES := main.cpp
2+
3+
include Makefile.rules
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import os
2+
3+
import dap_server
4+
import lldbdap_testcase
5+
from lldbsuite.test import lldbtest, lldbutil
6+
from lldbsuite.test.decorators import *
7+
8+
9+
class TestDAP_commands(lldbdap_testcase.DAPTestCaseBase):
10+
def test_command_directive_quiet_on_success(self):
11+
program = self.getBuildArtifact("a.out")
12+
command_quiet = (
13+
"settings set target.show-hex-variable-values-with-leading-zeroes false"
14+
)
15+
command_not_quiet = (
16+
"settings set target.show-hex-variable-values-with-leading-zeroes true"
17+
)
18+
self.build_and_launch(
19+
program,
20+
initCommands=["?" + command_quiet, command_not_quiet],
21+
terminateCommands=["?" + command_quiet, command_not_quiet],
22+
stopCommands=["?" + command_quiet, command_not_quiet],
23+
exitCommands=["?" + command_quiet, command_not_quiet],
24+
)
25+
full_output = self.collect_console(duration=1.0)
26+
self.assertNotIn(command_quiet, full_output)
27+
self.assertIn(command_not_quiet, full_output)
28+
29+
def do_test_abort_on_error(
30+
self,
31+
use_init_commands=False,
32+
use_launch_commands=False,
33+
use_pre_run_commands=False,
34+
use_post_run_commands=False,
35+
):
36+
program = self.getBuildArtifact("a.out")
37+
command_quiet = (
38+
"settings set target.show-hex-variable-values-with-leading-zeroes false"
39+
)
40+
command_abort_on_error = "settings set foo bar"
41+
commands = ["?!" + command_quiet, "!" + command_abort_on_error]
42+
self.build_and_launch(
43+
program,
44+
initCommands=commands if use_init_commands else None,
45+
launchCommands=commands if use_launch_commands else None,
46+
preRunCommands=commands if use_pre_run_commands else None,
47+
postRunCommands=commands if use_post_run_commands else None,
48+
expectFailure=True,
49+
)
50+
full_output = self.collect_console(duration=1.0)
51+
self.assertNotIn(command_quiet, full_output)
52+
self.assertIn(command_abort_on_error, full_output)
53+
54+
def test_command_directive_abort_on_error_init_commands(self):
55+
self.do_test_abort_on_error(use_init_commands=True)
56+
57+
def test_command_directive_abort_on_error_launch_commands(self):
58+
self.do_test_abort_on_error(use_launch_commands=True)
59+
60+
def test_command_directive_abort_on_error_pre_run_commands(self):
61+
self.do_test_abort_on_error(use_pre_run_commands=True)
62+
63+
def test_command_directive_abort_on_error_post_run_commands(self):
64+
self.do_test_abort_on_error(use_post_run_commands=True)
65+
66+
def test_command_directive_abort_on_error_attach_commands(self):
67+
program = self.getBuildArtifact("a.out")
68+
command_quiet = (
69+
"settings set target.show-hex-variable-values-with-leading-zeroes false"
70+
)
71+
command_abort_on_error = "settings set foo bar"
72+
self.build_and_create_debug_adaptor()
73+
self.attach(
74+
program,
75+
attachCommands=["?!" + command_quiet, "!" + command_abort_on_error],
76+
expectFailure=True,
77+
)
78+
full_output = self.collect_console(duration=1.0)
79+
self.assertNotIn(command_quiet, full_output)
80+
self.assertIn(command_abort_on_error, full_output)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
int main() { return 0; }

lldb/tools/lldb-dap/DAP.cpp

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -434,20 +434,54 @@ ExpressionContext DAP::DetectExpressionContext(lldb::SBFrame &frame,
434434
return ExpressionContext::Variable;
435435
}
436436

437-
void DAP::RunLLDBCommands(llvm::StringRef prefix,
438-
const std::vector<std::string> &commands) {
439-
SendOutput(OutputType::Console,
440-
llvm::StringRef(::RunLLDBCommands(prefix, commands)));
437+
bool DAP::RunLLDBCommands(llvm::StringRef prefix,
438+
llvm::ArrayRef<std::string> commands) {
439+
bool required_command_failed = false;
440+
std::string output =
441+
::RunLLDBCommands(prefix, commands, required_command_failed);
442+
SendOutput(OutputType::Console, output);
443+
return !required_command_failed;
444+
}
445+
446+
static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) {
447+
return llvm::createStringError(
448+
llvm::inconvertibleErrorCode(),
449+
llvm::formatv(
450+
"Failed to run {0} commands. See the Debug Console for more details.",
451+
category)
452+
.str()
453+
.c_str());
454+
}
455+
456+
llvm::Error
457+
DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) {
458+
if (!RunLLDBCommands("Running attachCommands:", attach_commands))
459+
return createRunLLDBCommandsErrorMessage("attach");
460+
return llvm::Error::success();
441461
}
442462

443-
void DAP::RunInitCommands() {
444-
RunLLDBCommands("Running initCommands:", init_commands);
463+
llvm::Error
464+
DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) {
465+
if (!RunLLDBCommands("Running launchCommands:", launch_commands))
466+
return createRunLLDBCommandsErrorMessage("launch");
467+
return llvm::Error::success();
445468
}
446469

447-
void DAP::RunPreRunCommands() {
448-
RunLLDBCommands("Running preRunCommands:", pre_run_commands);
470+
llvm::Error DAP::RunInitCommands() {
471+
if (!RunLLDBCommands("Running initCommands:", init_commands))
472+
return createRunLLDBCommandsErrorMessage("initCommands");
473+
return llvm::Error::success();
449474
}
450475

476+
llvm::Error DAP::RunPreRunCommands() {
477+
if (!RunLLDBCommands("Running preRunCommands:", pre_run_commands))
478+
return createRunLLDBCommandsErrorMessage("preRunCommands");
479+
return llvm::Error::success();
480+
}
481+
482+
void DAP::RunPostRunCommands() {
483+
RunLLDBCommands("Running postRunCommands:", post_run_commands);
484+
}
451485
void DAP::RunStopCommands() {
452486
RunLLDBCommands("Running stopCommands:", stop_commands);
453487
}

lldb/tools/lldb-dap/DAP.h

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ struct DAP {
158158
std::vector<ExceptionBreakpoint> exception_breakpoints;
159159
std::vector<std::string> init_commands;
160160
std::vector<std::string> pre_run_commands;
161+
std::vector<std::string> post_run_commands;
161162
std::vector<std::string> exit_commands;
162163
std::vector<std::string> stop_commands;
163164
std::vector<std::string> terminate_commands;
@@ -227,11 +228,17 @@ struct DAP {
227228
ExpressionContext DetectExpressionContext(lldb::SBFrame &frame,
228229
std::string &text);
229230

230-
void RunLLDBCommands(llvm::StringRef prefix,
231-
const std::vector<std::string> &commands);
232-
233-
void RunInitCommands();
234-
void RunPreRunCommands();
231+
/// \return
232+
/// \b false if a fatal error was found while executing these commands,
233+
/// according to the rules of \a LLDBUtils::RunLLDBCommands.
234+
bool RunLLDBCommands(llvm::StringRef prefix,
235+
llvm::ArrayRef<std::string> commands);
236+
237+
llvm::Error RunAttachCommands(llvm::ArrayRef<std::string> attach_commands);
238+
llvm::Error RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands);
239+
llvm::Error RunInitCommands();
240+
llvm::Error RunPreRunCommands();
241+
void RunPostRunCommands();
235242
void RunStopCommands();
236243
void RunExitCommands();
237244
void RunTerminateCommands();

lldb/tools/lldb-dap/LLDBUtils.cpp

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,81 @@
1111

1212
namespace lldb_dap {
1313

14-
void RunLLDBCommands(llvm::StringRef prefix,
14+
bool RunLLDBCommands(llvm::StringRef prefix,
1515
const llvm::ArrayRef<std::string> &commands,
16-
llvm::raw_ostream &strm) {
16+
llvm::raw_ostream &strm, bool parse_command_directives) {
1717
if (commands.empty())
18-
return;
18+
return true;
19+
20+
bool did_print_prefix = false;
21+
1922
lldb::SBCommandInterpreter interp = g_dap.debugger.GetCommandInterpreter();
20-
if (!prefix.empty())
21-
strm << prefix << "\n";
22-
for (const auto &command : commands) {
23+
for (llvm::StringRef command : commands) {
2324
lldb::SBCommandReturnObject result;
24-
strm << "(lldb) " << command << "\n";
25-
interp.HandleCommand(command.c_str(), result);
26-
auto output_len = result.GetOutputSize();
27-
if (output_len) {
28-
const char *output = result.GetOutput();
29-
strm << output;
25+
bool quiet_on_success = false;
26+
bool check_error = false;
27+
28+
while (parse_command_directives) {
29+
if (command.starts_with("?")) {
30+
command = command.drop_front();
31+
quiet_on_success = true;
32+
} else if (command.starts_with("!")) {
33+
command = command.drop_front();
34+
check_error = true;
35+
} else {
36+
break;
37+
}
3038
}
31-
auto error_len = result.GetErrorSize();
32-
if (error_len) {
33-
const char *error = result.GetError();
34-
strm << error;
39+
40+
interp.HandleCommand(command.str().c_str(), result);
41+
const bool got_error = !result.Succeeded();
42+
// The if statement below is assuming we always print out `!` prefixed
43+
// lines. The only time we don't print is when we have `quiet_on_success ==
44+
// true` and we don't have an error.
45+
if (quiet_on_success ? got_error : true) {
46+
if (!did_print_prefix && !prefix.empty()) {
47+
strm << prefix << "\n";
48+
did_print_prefix = true;
49+
}
50+
strm << "(lldb) " << command << "\n";
51+
auto output_len = result.GetOutputSize();
52+
if (output_len) {
53+
const char *output = result.GetOutput();
54+
strm << output;
55+
}
56+
auto error_len = result.GetErrorSize();
57+
if (error_len) {
58+
const char *error = result.GetError();
59+
strm << error;
60+
}
3561
}
62+
if (check_error && got_error)
63+
return false; // Stop running commands.
3664
}
65+
return true;
3766
}
3867

3968
std::string RunLLDBCommands(llvm::StringRef prefix,
40-
const llvm::ArrayRef<std::string> &commands) {
69+
const llvm::ArrayRef<std::string> &commands,
70+
bool &required_command_failed,
71+
bool parse_command_directives) {
72+
required_command_failed = false;
4173
std::string s;
4274
llvm::raw_string_ostream strm(s);
43-
RunLLDBCommands(prefix, commands, strm);
75+
required_command_failed =
76+
!RunLLDBCommands(prefix, commands, strm, parse_command_directives);
4477
strm.flush();
4578
return s;
4679
}
4780

81+
std::string
82+
RunLLDBCommandsVerbatim(llvm::StringRef prefix,
83+
const llvm::ArrayRef<std::string> &commands) {
84+
bool required_command_failed = false;
85+
return RunLLDBCommands(prefix, commands, required_command_failed,
86+
/*parse_command_directives=*/false);
87+
}
88+
4889
bool ThreadHasStopReason(lldb::SBThread &thread) {
4990
switch (thread.GetStopReason()) {
5091
case lldb::eStopReasonTrace:

lldb/tools/lldb-dap/LLDBUtils.h

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ namespace lldb_dap {
2323
/// All output from every command, including the prompt + the command
2424
/// is placed into the "strm" argument.
2525
///
26+
/// Each individual command can be prefixed with \b ! and/or \b ? in no
27+
/// particular order. If \b ? is provided, then the output of that command is
28+
/// only emitted if it fails, and if \b ! is provided, then the output is
29+
/// emitted regardless, and \b false is returned without executing the
30+
/// remaining commands.
31+
///
2632
/// \param[in] prefix
2733
/// A string that will be printed into \a strm prior to emitting
2834
/// the prompt + command and command output. Can be NULL.
@@ -33,9 +39,17 @@ namespace lldb_dap {
3339
/// \param[in] strm
3440
/// The stream that will receive the prefix, prompt + command and
3541
/// all command output.
36-
void RunLLDBCommands(llvm::StringRef prefix,
42+
///
43+
/// \param[in] parse_command_directives
44+
/// If \b false, then command prefixes like \b ! or \b ? are not parsed and
45+
/// each command is executed verbatim.
46+
///
47+
/// \return
48+
/// \b true, unless a command prefixed with \b ! fails and parsing of
49+
/// command directives is enabled.
50+
bool RunLLDBCommands(llvm::StringRef prefix,
3751
const llvm::ArrayRef<std::string> &commands,
38-
llvm::raw_ostream &strm);
52+
llvm::raw_ostream &strm, bool parse_command_directives);
3953

4054
/// Run a list of LLDB commands in the LLDB command interpreter.
4155
///
@@ -49,11 +63,26 @@ void RunLLDBCommands(llvm::StringRef prefix,
4963
/// \param[in] commands
5064
/// An array of LLDB commands to execute.
5165
///
66+
/// \param[out] required_command_failed
67+
/// If parsing of command directives is enabled, this variable is set to
68+
/// \b true if one of the commands prefixed with \b ! fails.
69+
///
70+
/// \param[in] parse_command_directives
71+
/// If \b false, then command prefixes like \b ! or \b ? are not parsed and
72+
/// each command is executed verbatim.
73+
///
5274
/// \return
5375
/// A std::string that contains the prefix and all commands and
54-
/// command output
76+
/// command output.
5577
std::string RunLLDBCommands(llvm::StringRef prefix,
56-
const llvm::ArrayRef<std::string> &commands);
78+
const llvm::ArrayRef<std::string> &commands,
79+
bool &required_command_failed,
80+
bool parse_command_directives = true);
81+
82+
/// Similar to the method above, but without parsing command directives.
83+
std::string
84+
RunLLDBCommandsVerbatim(llvm::StringRef prefix,
85+
const llvm::ArrayRef<std::string> &commands);
5786

5887
/// Check if a thread has a stop reason.
5988
///

0 commit comments

Comments
 (0)