1 //===-- CommandObjectLog.cpp ----------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8
9 #include "CommandObjectLog.h"
10 #include "lldb/Core/Debugger.h"
11 #include "lldb/Host/OptionParser.h"
12 #include "lldb/Interpreter/CommandReturnObject.h"
13 #include "lldb/Interpreter/OptionArgParser.h"
14 #include "lldb/Interpreter/Options.h"
15 #include "lldb/Utility/Args.h"
16 #include "lldb/Utility/FileSpec.h"
17 #include "lldb/Utility/Log.h"
18 #include "lldb/Utility/Stream.h"
19 #include "lldb/Utility/Timer.h"
20
21 using namespace lldb;
22 using namespace lldb_private;
23
24 #define LLDB_OPTIONS_log
25 #include "CommandOptions.inc"
26
27 /// Common completion logic for log enable/disable.
CompleteEnableDisable(CompletionRequest & request)28 static void CompleteEnableDisable(CompletionRequest &request) {
29 size_t arg_index = request.GetCursorIndex();
30 if (arg_index == 0) { // We got: log enable/disable x[tab]
31 for (llvm::StringRef channel : Log::ListChannels())
32 request.TryCompleteCurrentArg(channel);
33 } else if (arg_index >= 1) { // We got: log enable/disable channel x[tab]
34 llvm::StringRef channel = request.GetParsedLine().GetArgumentAtIndex(0);
35 Log::ForEachChannelCategory(
36 channel, [&request](llvm::StringRef name, llvm::StringRef desc) {
37 request.TryCompleteCurrentArg(name, desc);
38 });
39 }
40 }
41
42 class CommandObjectLogEnable : public CommandObjectParsed {
43 public:
44 // Constructors and Destructors
CommandObjectLogEnable(CommandInterpreter & interpreter)45 CommandObjectLogEnable(CommandInterpreter &interpreter)
46 : CommandObjectParsed(interpreter, "log enable",
47 "Enable logging for a single log channel.",
48 nullptr),
49 m_options() {
50 CommandArgumentEntry arg1;
51 CommandArgumentEntry arg2;
52 CommandArgumentData channel_arg;
53 CommandArgumentData category_arg;
54
55 // Define the first (and only) variant of this arg.
56 channel_arg.arg_type = eArgTypeLogChannel;
57 channel_arg.arg_repetition = eArgRepeatPlain;
58
59 // There is only one variant this argument could be; put it into the
60 // argument entry.
61 arg1.push_back(channel_arg);
62
63 category_arg.arg_type = eArgTypeLogCategory;
64 category_arg.arg_repetition = eArgRepeatPlus;
65
66 arg2.push_back(category_arg);
67
68 // Push the data for the first argument into the m_arguments vector.
69 m_arguments.push_back(arg1);
70 m_arguments.push_back(arg2);
71 }
72
73 ~CommandObjectLogEnable() override = default;
74
GetOptions()75 Options *GetOptions() override { return &m_options; }
76
77 class CommandOptions : public Options {
78 public:
CommandOptions()79 CommandOptions() : Options(), log_file(), log_options(0) {}
80
81 ~CommandOptions() override = default;
82
SetOptionValue(uint32_t option_idx,llvm::StringRef option_arg,ExecutionContext * execution_context)83 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
84 ExecutionContext *execution_context) override {
85 Status error;
86 const int short_option = m_getopt_table[option_idx].val;
87
88 switch (short_option) {
89 case 'f':
90 log_file.SetFile(option_arg, FileSpec::Style::native);
91 FileSystem::Instance().Resolve(log_file);
92 break;
93 case 't':
94 log_options |= LLDB_LOG_OPTION_THREADSAFE;
95 break;
96 case 'v':
97 log_options |= LLDB_LOG_OPTION_VERBOSE;
98 break;
99 case 's':
100 log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE;
101 break;
102 case 'T':
103 log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP;
104 break;
105 case 'p':
106 log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD;
107 break;
108 case 'n':
109 log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME;
110 break;
111 case 'S':
112 log_options |= LLDB_LOG_OPTION_BACKTRACE;
113 break;
114 case 'a':
115 log_options |= LLDB_LOG_OPTION_APPEND;
116 break;
117 case 'F':
118 log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION;
119 break;
120 default:
121 llvm_unreachable("Unimplemented option");
122 }
123
124 return error;
125 }
126
OptionParsingStarting(ExecutionContext * execution_context)127 void OptionParsingStarting(ExecutionContext *execution_context) override {
128 log_file.Clear();
129 log_options = 0;
130 }
131
GetDefinitions()132 llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
133 return llvm::makeArrayRef(g_log_options);
134 }
135
136 // Instance variables to hold the values for command options.
137
138 FileSpec log_file;
139 uint32_t log_options;
140 };
141
142 void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)143 HandleArgumentCompletion(CompletionRequest &request,
144 OptionElementVector &opt_element_vector) override {
145 CompleteEnableDisable(request);
146 }
147
148 protected:
DoExecute(Args & args,CommandReturnObject & result)149 bool DoExecute(Args &args, CommandReturnObject &result) override {
150 if (args.GetArgumentCount() < 2) {
151 result.AppendErrorWithFormat(
152 "%s takes a log channel and one or more log types.\n",
153 m_cmd_name.c_str());
154 result.SetStatus(eReturnStatusFailed);
155 return false;
156 }
157
158 // Store into a std::string since we're about to shift the channel off.
159 const std::string channel = std::string(args[0].ref());
160 args.Shift(); // Shift off the channel
161 char log_file[PATH_MAX];
162 if (m_options.log_file)
163 m_options.log_file.GetPath(log_file, sizeof(log_file));
164 else
165 log_file[0] = '\0';
166
167 std::string error;
168 llvm::raw_string_ostream error_stream(error);
169 bool success =
170 GetDebugger().EnableLog(channel, args.GetArgumentArrayRef(), log_file,
171 m_options.log_options, error_stream);
172 result.GetErrorStream() << error_stream.str();
173
174 if (success)
175 result.SetStatus(eReturnStatusSuccessFinishNoResult);
176 else
177 result.SetStatus(eReturnStatusFailed);
178 return result.Succeeded();
179 }
180
181 CommandOptions m_options;
182 };
183
184 class CommandObjectLogDisable : public CommandObjectParsed {
185 public:
186 // Constructors and Destructors
CommandObjectLogDisable(CommandInterpreter & interpreter)187 CommandObjectLogDisable(CommandInterpreter &interpreter)
188 : CommandObjectParsed(interpreter, "log disable",
189 "Disable one or more log channel categories.",
190 nullptr) {
191 CommandArgumentEntry arg1;
192 CommandArgumentEntry arg2;
193 CommandArgumentData channel_arg;
194 CommandArgumentData category_arg;
195
196 // Define the first (and only) variant of this arg.
197 channel_arg.arg_type = eArgTypeLogChannel;
198 channel_arg.arg_repetition = eArgRepeatPlain;
199
200 // There is only one variant this argument could be; put it into the
201 // argument entry.
202 arg1.push_back(channel_arg);
203
204 category_arg.arg_type = eArgTypeLogCategory;
205 category_arg.arg_repetition = eArgRepeatPlus;
206
207 arg2.push_back(category_arg);
208
209 // Push the data for the first argument into the m_arguments vector.
210 m_arguments.push_back(arg1);
211 m_arguments.push_back(arg2);
212 }
213
214 ~CommandObjectLogDisable() override = default;
215
216 void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)217 HandleArgumentCompletion(CompletionRequest &request,
218 OptionElementVector &opt_element_vector) override {
219 CompleteEnableDisable(request);
220 }
221
222 protected:
DoExecute(Args & args,CommandReturnObject & result)223 bool DoExecute(Args &args, CommandReturnObject &result) override {
224 if (args.empty()) {
225 result.AppendErrorWithFormat(
226 "%s takes a log channel and one or more log types.\n",
227 m_cmd_name.c_str());
228 result.SetStatus(eReturnStatusFailed);
229 return false;
230 }
231
232 const std::string channel = std::string(args[0].ref());
233 args.Shift(); // Shift off the channel
234 if (channel == "all") {
235 Log::DisableAllLogChannels();
236 result.SetStatus(eReturnStatusSuccessFinishNoResult);
237 } else {
238 std::string error;
239 llvm::raw_string_ostream error_stream(error);
240 if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(),
241 error_stream))
242 result.SetStatus(eReturnStatusSuccessFinishNoResult);
243 result.GetErrorStream() << error_stream.str();
244 }
245 return result.Succeeded();
246 }
247 };
248
249 class CommandObjectLogList : public CommandObjectParsed {
250 public:
251 // Constructors and Destructors
CommandObjectLogList(CommandInterpreter & interpreter)252 CommandObjectLogList(CommandInterpreter &interpreter)
253 : CommandObjectParsed(interpreter, "log list",
254 "List the log categories for one or more log "
255 "channels. If none specified, lists them all.",
256 nullptr) {
257 CommandArgumentEntry arg;
258 CommandArgumentData channel_arg;
259
260 // Define the first (and only) variant of this arg.
261 channel_arg.arg_type = eArgTypeLogChannel;
262 channel_arg.arg_repetition = eArgRepeatStar;
263
264 // There is only one variant this argument could be; put it into the
265 // argument entry.
266 arg.push_back(channel_arg);
267
268 // Push the data for the first argument into the m_arguments vector.
269 m_arguments.push_back(arg);
270 }
271
272 ~CommandObjectLogList() override = default;
273
274 void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)275 HandleArgumentCompletion(CompletionRequest &request,
276 OptionElementVector &opt_element_vector) override {
277 for (llvm::StringRef channel : Log::ListChannels())
278 request.TryCompleteCurrentArg(channel);
279 }
280
281 protected:
DoExecute(Args & args,CommandReturnObject & result)282 bool DoExecute(Args &args, CommandReturnObject &result) override {
283 std::string output;
284 llvm::raw_string_ostream output_stream(output);
285 if (args.empty()) {
286 Log::ListAllLogChannels(output_stream);
287 result.SetStatus(eReturnStatusSuccessFinishResult);
288 } else {
289 bool success = true;
290 for (const auto &entry : args.entries())
291 success =
292 success && Log::ListChannelCategories(entry.ref(), output_stream);
293 if (success)
294 result.SetStatus(eReturnStatusSuccessFinishResult);
295 }
296 result.GetOutputStream() << output_stream.str();
297 return result.Succeeded();
298 }
299 };
300
301 class CommandObjectLogTimerEnable : public CommandObjectParsed {
302 public:
303 // Constructors and Destructors
CommandObjectLogTimerEnable(CommandInterpreter & interpreter)304 CommandObjectLogTimerEnable(CommandInterpreter &interpreter)
305 : CommandObjectParsed(interpreter, "log timers enable",
306 "enable LLDB internal performance timers",
307 "log timers enable <depth>") {
308 CommandArgumentEntry arg;
309 CommandArgumentData depth_arg;
310
311 // Define the first (and only) variant of this arg.
312 depth_arg.arg_type = eArgTypeCount;
313 depth_arg.arg_repetition = eArgRepeatOptional;
314
315 // There is only one variant this argument could be; put it into the
316 // argument entry.
317 arg.push_back(depth_arg);
318
319 // Push the data for the first argument into the m_arguments vector.
320 m_arguments.push_back(arg);
321 }
322
323 ~CommandObjectLogTimerEnable() override = default;
324
325 protected:
DoExecute(Args & args,CommandReturnObject & result)326 bool DoExecute(Args &args, CommandReturnObject &result) override {
327 result.SetStatus(eReturnStatusFailed);
328
329 if (args.GetArgumentCount() == 0) {
330 Timer::SetDisplayDepth(UINT32_MAX);
331 result.SetStatus(eReturnStatusSuccessFinishNoResult);
332 } else if (args.GetArgumentCount() == 1) {
333 uint32_t depth;
334 if (args[0].ref().consumeInteger(0, depth)) {
335 result.AppendError(
336 "Could not convert enable depth to an unsigned integer.");
337 } else {
338 Timer::SetDisplayDepth(depth);
339 result.SetStatus(eReturnStatusSuccessFinishNoResult);
340 }
341 }
342
343 if (!result.Succeeded()) {
344 result.AppendError("Missing subcommand");
345 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
346 }
347 return result.Succeeded();
348 }
349 };
350
351 class CommandObjectLogTimerDisable : public CommandObjectParsed {
352 public:
353 // Constructors and Destructors
CommandObjectLogTimerDisable(CommandInterpreter & interpreter)354 CommandObjectLogTimerDisable(CommandInterpreter &interpreter)
355 : CommandObjectParsed(interpreter, "log timers disable",
356 "disable LLDB internal performance timers",
357 nullptr) {}
358
359 ~CommandObjectLogTimerDisable() override = default;
360
361 protected:
DoExecute(Args & args,CommandReturnObject & result)362 bool DoExecute(Args &args, CommandReturnObject &result) override {
363 Timer::DumpCategoryTimes(&result.GetOutputStream());
364 Timer::SetDisplayDepth(0);
365 result.SetStatus(eReturnStatusSuccessFinishResult);
366
367 if (!result.Succeeded()) {
368 result.AppendError("Missing subcommand");
369 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
370 }
371 return result.Succeeded();
372 }
373 };
374
375 class CommandObjectLogTimerDump : public CommandObjectParsed {
376 public:
377 // Constructors and Destructors
CommandObjectLogTimerDump(CommandInterpreter & interpreter)378 CommandObjectLogTimerDump(CommandInterpreter &interpreter)
379 : CommandObjectParsed(interpreter, "log timers dump",
380 "dump LLDB internal performance timers", nullptr) {}
381
382 ~CommandObjectLogTimerDump() override = default;
383
384 protected:
DoExecute(Args & args,CommandReturnObject & result)385 bool DoExecute(Args &args, CommandReturnObject &result) override {
386 Timer::DumpCategoryTimes(&result.GetOutputStream());
387 result.SetStatus(eReturnStatusSuccessFinishResult);
388
389 if (!result.Succeeded()) {
390 result.AppendError("Missing subcommand");
391 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
392 }
393 return result.Succeeded();
394 }
395 };
396
397 class CommandObjectLogTimerReset : public CommandObjectParsed {
398 public:
399 // Constructors and Destructors
CommandObjectLogTimerReset(CommandInterpreter & interpreter)400 CommandObjectLogTimerReset(CommandInterpreter &interpreter)
401 : CommandObjectParsed(interpreter, "log timers reset",
402 "reset LLDB internal performance timers", nullptr) {
403 }
404
405 ~CommandObjectLogTimerReset() override = default;
406
407 protected:
DoExecute(Args & args,CommandReturnObject & result)408 bool DoExecute(Args &args, CommandReturnObject &result) override {
409 Timer::ResetCategoryTimes();
410 result.SetStatus(eReturnStatusSuccessFinishResult);
411
412 if (!result.Succeeded()) {
413 result.AppendError("Missing subcommand");
414 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
415 }
416 return result.Succeeded();
417 }
418 };
419
420 class CommandObjectLogTimerIncrement : public CommandObjectParsed {
421 public:
422 // Constructors and Destructors
CommandObjectLogTimerIncrement(CommandInterpreter & interpreter)423 CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)
424 : CommandObjectParsed(interpreter, "log timers increment",
425 "increment LLDB internal performance timers",
426 "log timers increment <bool>") {
427 CommandArgumentEntry arg;
428 CommandArgumentData bool_arg;
429
430 // Define the first (and only) variant of this arg.
431 bool_arg.arg_type = eArgTypeBoolean;
432 bool_arg.arg_repetition = eArgRepeatPlain;
433
434 // There is only one variant this argument could be; put it into the
435 // argument entry.
436 arg.push_back(bool_arg);
437
438 // Push the data for the first argument into the m_arguments vector.
439 m_arguments.push_back(arg);
440 }
441
442 ~CommandObjectLogTimerIncrement() override = default;
443
444 void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)445 HandleArgumentCompletion(CompletionRequest &request,
446 OptionElementVector &opt_element_vector) override {
447 request.TryCompleteCurrentArg("true");
448 request.TryCompleteCurrentArg("false");
449 }
450
451 protected:
DoExecute(Args & args,CommandReturnObject & result)452 bool DoExecute(Args &args, CommandReturnObject &result) override {
453 result.SetStatus(eReturnStatusFailed);
454
455 if (args.GetArgumentCount() == 1) {
456 bool success;
457 bool increment =
458 OptionArgParser::ToBoolean(args[0].ref(), false, &success);
459
460 if (success) {
461 Timer::SetQuiet(!increment);
462 result.SetStatus(eReturnStatusSuccessFinishNoResult);
463 } else
464 result.AppendError("Could not convert increment value to boolean.");
465 }
466
467 if (!result.Succeeded()) {
468 result.AppendError("Missing subcommand");
469 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
470 }
471 return result.Succeeded();
472 }
473 };
474
475 class CommandObjectLogTimer : public CommandObjectMultiword {
476 public:
CommandObjectLogTimer(CommandInterpreter & interpreter)477 CommandObjectLogTimer(CommandInterpreter &interpreter)
478 : CommandObjectMultiword(interpreter, "log timers",
479 "Enable, disable, dump, and reset LLDB internal "
480 "performance timers.",
481 "log timers < enable <depth> | disable | dump | "
482 "increment <bool> | reset >") {
483 LoadSubCommand("enable", CommandObjectSP(
484 new CommandObjectLogTimerEnable(interpreter)));
485 LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable(
486 interpreter)));
487 LoadSubCommand("dump",
488 CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));
489 LoadSubCommand(
490 "reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));
491 LoadSubCommand(
492 "increment",
493 CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));
494 }
495
496 ~CommandObjectLogTimer() override = default;
497 };
498
CommandObjectLog(CommandInterpreter & interpreter)499 CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
500 : CommandObjectMultiword(interpreter, "log",
501 "Commands controlling LLDB internal logging.",
502 "log <subcommand> [<command-options>]") {
503 LoadSubCommand("enable",
504 CommandObjectSP(new CommandObjectLogEnable(interpreter)));
505 LoadSubCommand("disable",
506 CommandObjectSP(new CommandObjectLogDisable(interpreter)));
507 LoadSubCommand("list",
508 CommandObjectSP(new CommandObjectLogList(interpreter)));
509 LoadSubCommand("timers",
510 CommandObjectSP(new CommandObjectLogTimer(interpreter)));
511 }
512
513 CommandObjectLog::~CommandObjectLog() = default;
514