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/CommandOptionArgumentTable.h"
13#include "lldb/Interpreter/CommandReturnObject.h"
14#include "lldb/Interpreter/OptionArgParser.h"
15#include "lldb/Interpreter/OptionValueEnumeration.h"
16#include "lldb/Interpreter/OptionValueUInt64.h"
17#include "lldb/Interpreter/Options.h"
18#include "lldb/Utility/Args.h"
19#include "lldb/Utility/FileSpec.h"
20#include "lldb/Utility/Log.h"
21#include "lldb/Utility/Stream.h"
22#include "lldb/Utility/Timer.h"
23
24using namespace lldb;
25using namespace lldb_private;
26
27#define LLDB_OPTIONS_log_enable
28#include "CommandOptions.inc"
29
30#define LLDB_OPTIONS_log_dump
31#include "CommandOptions.inc"
32
33/// Common completion logic for log enable/disable.
34static void CompleteEnableDisable(CompletionRequest &request) {
35 size_t arg_index = request.GetCursorIndex();
36 if (arg_index == 0) { // We got: log enable/disable x[tab]
37 for (llvm::StringRef channel : Log::ListChannels())
38 request.TryCompleteCurrentArg(completion: channel);
39 } else if (arg_index >= 1) { // We got: log enable/disable channel x[tab]
40 llvm::StringRef channel = request.GetParsedLine().GetArgumentAtIndex(idx: 0);
41 Log::ForEachChannelCategory(
42 channel, lambda: [&request](llvm::StringRef name, llvm::StringRef desc) {
43 request.TryCompleteCurrentArg(completion: name, description: desc);
44 });
45 }
46}
47
48class CommandObjectLogEnable : public CommandObjectParsed {
49public:
50 // Constructors and Destructors
51 CommandObjectLogEnable(CommandInterpreter &interpreter)
52 : CommandObjectParsed(interpreter, "log enable",
53 "Enable logging for a single log channel.",
54 nullptr) {
55 CommandArgumentEntry arg1;
56 CommandArgumentEntry arg2;
57 CommandArgumentData channel_arg;
58 CommandArgumentData category_arg;
59
60 // Define the first (and only) variant of this arg.
61 channel_arg.arg_type = eArgTypeLogChannel;
62 channel_arg.arg_repetition = eArgRepeatPlain;
63
64 // There is only one variant this argument could be; put it into the
65 // argument entry.
66 arg1.push_back(x: channel_arg);
67
68 category_arg.arg_type = eArgTypeLogCategory;
69 category_arg.arg_repetition = eArgRepeatPlus;
70
71 arg2.push_back(x: category_arg);
72
73 // Push the data for the first argument into the m_arguments vector.
74 m_arguments.push_back(x: arg1);
75 m_arguments.push_back(x: arg2);
76 }
77
78 ~CommandObjectLogEnable() override = default;
79
80 Options *GetOptions() override { return &m_options; }
81
82 class CommandOptions : public Options {
83 public:
84 CommandOptions() = default;
85
86 ~CommandOptions() override = default;
87
88 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
89 ExecutionContext *execution_context) override {
90 Status error;
91 const int short_option = m_getopt_table[option_idx].val;
92
93 switch (short_option) {
94 case 'f':
95 log_file.SetFile(path: option_arg, style: FileSpec::Style::native);
96 FileSystem::Instance().Resolve(file_spec&: log_file);
97 break;
98 case 'h':
99 handler = (LogHandlerKind)OptionArgParser::ToOptionEnum(
100 s: option_arg, enum_values: GetDefinitions()[option_idx].enum_values, fail_value: 0, error);
101 if (!error.Success())
102 error.SetErrorStringWithFormat(
103 "unrecognized value for log handler '%s'",
104 option_arg.str().c_str());
105 break;
106 case 'b':
107 error =
108 buffer_size.SetValueFromString(value: option_arg, op: eVarSetOperationAssign);
109 break;
110 case 'v':
111 log_options |= LLDB_LOG_OPTION_VERBOSE;
112 break;
113 case 's':
114 log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE;
115 break;
116 case 'T':
117 log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP;
118 break;
119 case 'p':
120 log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD;
121 break;
122 case 'n':
123 log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME;
124 break;
125 case 'S':
126 log_options |= LLDB_LOG_OPTION_BACKTRACE;
127 break;
128 case 'a':
129 log_options |= LLDB_LOG_OPTION_APPEND;
130 break;
131 case 'F':
132 log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION;
133 break;
134 default:
135 llvm_unreachable("Unimplemented option");
136 }
137
138 return error;
139 }
140
141 void OptionParsingStarting(ExecutionContext *execution_context) override {
142 log_file.Clear();
143 buffer_size.Clear();
144 handler = eLogHandlerStream;
145 log_options = 0;
146 }
147
148 llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
149 return llvm::ArrayRef(g_log_enable_options);
150 }
151
152 FileSpec log_file;
153 OptionValueUInt64 buffer_size;
154 LogHandlerKind handler = eLogHandlerStream;
155 uint32_t log_options = 0;
156 };
157
158 void
159 HandleArgumentCompletion(CompletionRequest &request,
160 OptionElementVector &opt_element_vector) override {
161 CompleteEnableDisable(request);
162 }
163
164protected:
165 void DoExecute(Args &args, CommandReturnObject &result) override {
166 if (args.GetArgumentCount() < 2) {
167 result.AppendErrorWithFormat(
168 format: "%s takes a log channel and one or more log types.\n",
169 m_cmd_name.c_str());
170 return;
171 }
172
173 if (m_options.handler == eLogHandlerCircular &&
174 m_options.buffer_size.GetCurrentValue() == 0) {
175 result.AppendError(
176 in_string: "the circular buffer handler requires a non-zero buffer size.\n");
177 return;
178 }
179
180 if ((m_options.handler != eLogHandlerCircular &&
181 m_options.handler != eLogHandlerStream) &&
182 m_options.buffer_size.GetCurrentValue() != 0) {
183 result.AppendError(in_string: "a buffer size can only be specified for the circular "
184 "and stream buffer handler.\n");
185 return;
186 }
187
188 if (m_options.handler != eLogHandlerStream && m_options.log_file) {
189 result.AppendError(
190 in_string: "a file name can only be specified for the stream handler.\n");
191 return;
192 }
193
194 // Store into a std::string since we're about to shift the channel off.
195 const std::string channel = std::string(args[0].ref());
196 args.Shift(); // Shift off the channel
197 char log_file[PATH_MAX];
198 if (m_options.log_file)
199 m_options.log_file.GetPath(path: log_file, max_path_length: sizeof(log_file));
200 else
201 log_file[0] = '\0';
202
203 std::string error;
204 llvm::raw_string_ostream error_stream(error);
205 bool success = GetDebugger().EnableLog(
206 channel, categories: args.GetArgumentArrayRef(), log_file, log_options: m_options.log_options,
207 buffer_size: m_options.buffer_size.GetCurrentValue(), log_handler_kind: m_options.handler,
208 error_stream);
209 result.GetErrorStream() << error_stream.str();
210
211 if (success)
212 result.SetStatus(eReturnStatusSuccessFinishNoResult);
213 else
214 result.SetStatus(eReturnStatusFailed);
215 }
216
217 CommandOptions m_options;
218};
219
220class CommandObjectLogDisable : public CommandObjectParsed {
221public:
222 // Constructors and Destructors
223 CommandObjectLogDisable(CommandInterpreter &interpreter)
224 : CommandObjectParsed(interpreter, "log disable",
225 "Disable one or more log channel categories.",
226 nullptr) {
227 CommandArgumentEntry arg1;
228 CommandArgumentEntry arg2;
229 CommandArgumentData channel_arg;
230 CommandArgumentData category_arg;
231
232 // Define the first (and only) variant of this arg.
233 channel_arg.arg_type = eArgTypeLogChannel;
234 channel_arg.arg_repetition = eArgRepeatPlain;
235
236 // There is only one variant this argument could be; put it into the
237 // argument entry.
238 arg1.push_back(x: channel_arg);
239
240 category_arg.arg_type = eArgTypeLogCategory;
241 category_arg.arg_repetition = eArgRepeatPlus;
242
243 arg2.push_back(x: category_arg);
244
245 // Push the data for the first argument into the m_arguments vector.
246 m_arguments.push_back(x: arg1);
247 m_arguments.push_back(x: arg2);
248 }
249
250 ~CommandObjectLogDisable() override = default;
251
252 void
253 HandleArgumentCompletion(CompletionRequest &request,
254 OptionElementVector &opt_element_vector) override {
255 CompleteEnableDisable(request);
256 }
257
258protected:
259 void DoExecute(Args &args, CommandReturnObject &result) override {
260 if (args.empty()) {
261 result.AppendErrorWithFormat(
262 format: "%s takes a log channel and one or more log types.\n",
263 m_cmd_name.c_str());
264 return;
265 }
266
267 const std::string channel = std::string(args[0].ref());
268 args.Shift(); // Shift off the channel
269 if (channel == "all") {
270 Log::DisableAllLogChannels();
271 result.SetStatus(eReturnStatusSuccessFinishNoResult);
272 } else {
273 std::string error;
274 llvm::raw_string_ostream error_stream(error);
275 if (Log::DisableLogChannel(channel, categories: args.GetArgumentArrayRef(),
276 error_stream))
277 result.SetStatus(eReturnStatusSuccessFinishNoResult);
278 result.GetErrorStream() << error_stream.str();
279 }
280 }
281};
282
283class CommandObjectLogList : public CommandObjectParsed {
284public:
285 // Constructors and Destructors
286 CommandObjectLogList(CommandInterpreter &interpreter)
287 : CommandObjectParsed(interpreter, "log list",
288 "List the log categories for one or more log "
289 "channels. If none specified, lists them all.",
290 nullptr) {
291 AddSimpleArgumentList(arg_type: eArgTypeLogChannel, repetition_type: eArgRepeatStar);
292 }
293
294 ~CommandObjectLogList() override = default;
295
296 void
297 HandleArgumentCompletion(CompletionRequest &request,
298 OptionElementVector &opt_element_vector) override {
299 for (llvm::StringRef channel : Log::ListChannels())
300 request.TryCompleteCurrentArg(completion: channel);
301 }
302
303protected:
304 void DoExecute(Args &args, CommandReturnObject &result) override {
305 std::string output;
306 llvm::raw_string_ostream output_stream(output);
307 if (args.empty()) {
308 Log::ListAllLogChannels(stream&: output_stream);
309 result.SetStatus(eReturnStatusSuccessFinishResult);
310 } else {
311 bool success = true;
312 for (const auto &entry : args.entries())
313 success =
314 success && Log::ListChannelCategories(channel: entry.ref(), stream&: output_stream);
315 if (success)
316 result.SetStatus(eReturnStatusSuccessFinishResult);
317 }
318 result.GetOutputStream() << output_stream.str();
319 }
320};
321class CommandObjectLogDump : public CommandObjectParsed {
322public:
323 CommandObjectLogDump(CommandInterpreter &interpreter)
324 : CommandObjectParsed(interpreter, "log dump",
325 "dump circular buffer logs", nullptr) {
326 AddSimpleArgumentList(arg_type: eArgTypeLogChannel);
327 }
328
329 ~CommandObjectLogDump() override = default;
330
331 Options *GetOptions() override { return &m_options; }
332
333 class CommandOptions : public Options {
334 public:
335 CommandOptions() = default;
336
337 ~CommandOptions() override = default;
338
339 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
340 ExecutionContext *execution_context) override {
341 Status error;
342 const int short_option = m_getopt_table[option_idx].val;
343
344 switch (short_option) {
345 case 'f':
346 log_file.SetFile(path: option_arg, style: FileSpec::Style::native);
347 FileSystem::Instance().Resolve(file_spec&: log_file);
348 break;
349 default:
350 llvm_unreachable("Unimplemented option");
351 }
352
353 return error;
354 }
355
356 void OptionParsingStarting(ExecutionContext *execution_context) override {
357 log_file.Clear();
358 }
359
360 llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
361 return llvm::ArrayRef(g_log_dump_options);
362 }
363
364 FileSpec log_file;
365 };
366
367 void
368 HandleArgumentCompletion(CompletionRequest &request,
369 OptionElementVector &opt_element_vector) override {
370 CompleteEnableDisable(request);
371 }
372
373protected:
374 void DoExecute(Args &args, CommandReturnObject &result) override {
375 if (args.empty()) {
376 result.AppendErrorWithFormat(
377 format: "%s takes a log channel and one or more log types.\n",
378 m_cmd_name.c_str());
379 return;
380 }
381
382 std::unique_ptr<llvm::raw_ostream> stream_up;
383 if (m_options.log_file) {
384 const File::OpenOptions flags = File::eOpenOptionWriteOnly |
385 File::eOpenOptionCanCreate |
386 File::eOpenOptionTruncate;
387 llvm::Expected<FileUP> file = FileSystem::Instance().Open(
388 file_spec: m_options.log_file, options: flags, permissions: lldb::eFilePermissionsFileDefault, should_close_fd: false);
389 if (!file) {
390 result.AppendErrorWithFormat(format: "Unable to open log file '%s': %s",
391 m_options.log_file.GetPath().c_str(),
392 llvm::toString(E: file.takeError()).c_str());
393 return;
394 }
395 stream_up = std::make_unique<llvm::raw_fd_ostream>(
396 args: (*file)->GetDescriptor(), /*shouldClose=*/args: true);
397 } else {
398 stream_up = std::make_unique<llvm::raw_fd_ostream>(
399 args: GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/args: false);
400 }
401
402 const std::string channel = std::string(args[0].ref());
403 std::string error;
404 llvm::raw_string_ostream error_stream(error);
405 if (Log::DumpLogChannel(channel, output_stream&: *stream_up, error_stream)) {
406 result.SetStatus(eReturnStatusSuccessFinishNoResult);
407 } else {
408 result.SetStatus(eReturnStatusFailed);
409 result.GetErrorStream() << error_stream.str();
410 }
411 }
412
413 CommandOptions m_options;
414};
415
416class CommandObjectLogTimerEnable : public CommandObjectParsed {
417public:
418 // Constructors and Destructors
419 CommandObjectLogTimerEnable(CommandInterpreter &interpreter)
420 : CommandObjectParsed(interpreter, "log timers enable",
421 "enable LLDB internal performance timers",
422 "log timers enable <depth>") {
423 AddSimpleArgumentList(arg_type: eArgTypeCount, repetition_type: eArgRepeatOptional);
424 }
425
426 ~CommandObjectLogTimerEnable() override = default;
427
428protected:
429 void DoExecute(Args &args, CommandReturnObject &result) override {
430 result.SetStatus(eReturnStatusFailed);
431
432 if (args.GetArgumentCount() == 0) {
433 Timer::SetDisplayDepth(UINT32_MAX);
434 result.SetStatus(eReturnStatusSuccessFinishNoResult);
435 } else if (args.GetArgumentCount() == 1) {
436 uint32_t depth;
437 if (args[0].ref().consumeInteger(Radix: 0, Result&: depth)) {
438 result.AppendError(
439 in_string: "Could not convert enable depth to an unsigned integer.");
440 } else {
441 Timer::SetDisplayDepth(depth);
442 result.SetStatus(eReturnStatusSuccessFinishNoResult);
443 }
444 }
445
446 if (!result.Succeeded()) {
447 result.AppendError(in_string: "Missing subcommand");
448 result.AppendErrorWithFormat(format: "Usage: %s\n", m_cmd_syntax.c_str());
449 }
450 }
451};
452
453class CommandObjectLogTimerDisable : public CommandObjectParsed {
454public:
455 // Constructors and Destructors
456 CommandObjectLogTimerDisable(CommandInterpreter &interpreter)
457 : CommandObjectParsed(interpreter, "log timers disable",
458 "disable LLDB internal performance timers",
459 nullptr) {}
460
461 ~CommandObjectLogTimerDisable() override = default;
462
463protected:
464 void DoExecute(Args &args, CommandReturnObject &result) override {
465 Timer::DumpCategoryTimes(s&: result.GetOutputStream());
466 Timer::SetDisplayDepth(0);
467 result.SetStatus(eReturnStatusSuccessFinishResult);
468
469 if (!result.Succeeded()) {
470 result.AppendError(in_string: "Missing subcommand");
471 result.AppendErrorWithFormat(format: "Usage: %s\n", m_cmd_syntax.c_str());
472 }
473 }
474};
475
476class CommandObjectLogTimerDump : public CommandObjectParsed {
477public:
478 // Constructors and Destructors
479 CommandObjectLogTimerDump(CommandInterpreter &interpreter)
480 : CommandObjectParsed(interpreter, "log timers dump",
481 "dump LLDB internal performance timers", nullptr) {}
482
483 ~CommandObjectLogTimerDump() override = default;
484
485protected:
486 void DoExecute(Args &args, CommandReturnObject &result) override {
487 Timer::DumpCategoryTimes(s&: result.GetOutputStream());
488 result.SetStatus(eReturnStatusSuccessFinishResult);
489
490 if (!result.Succeeded()) {
491 result.AppendError(in_string: "Missing subcommand");
492 result.AppendErrorWithFormat(format: "Usage: %s\n", m_cmd_syntax.c_str());
493 }
494 }
495};
496
497class CommandObjectLogTimerReset : public CommandObjectParsed {
498public:
499 // Constructors and Destructors
500 CommandObjectLogTimerReset(CommandInterpreter &interpreter)
501 : CommandObjectParsed(interpreter, "log timers reset",
502 "reset LLDB internal performance timers", nullptr) {
503 }
504
505 ~CommandObjectLogTimerReset() override = default;
506
507protected:
508 void DoExecute(Args &args, CommandReturnObject &result) override {
509 Timer::ResetCategoryTimes();
510 result.SetStatus(eReturnStatusSuccessFinishResult);
511
512 if (!result.Succeeded()) {
513 result.AppendError(in_string: "Missing subcommand");
514 result.AppendErrorWithFormat(format: "Usage: %s\n", m_cmd_syntax.c_str());
515 }
516 }
517};
518
519class CommandObjectLogTimerIncrement : public CommandObjectParsed {
520public:
521 // Constructors and Destructors
522 CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)
523 : CommandObjectParsed(interpreter, "log timers increment",
524 "increment LLDB internal performance timers",
525 "log timers increment <bool>") {
526 AddSimpleArgumentList(arg_type: eArgTypeBoolean);
527 }
528
529 ~CommandObjectLogTimerIncrement() override = default;
530
531 void
532 HandleArgumentCompletion(CompletionRequest &request,
533 OptionElementVector &opt_element_vector) override {
534 request.TryCompleteCurrentArg(completion: "true");
535 request.TryCompleteCurrentArg(completion: "false");
536 }
537
538protected:
539 void DoExecute(Args &args, CommandReturnObject &result) override {
540 result.SetStatus(eReturnStatusFailed);
541
542 if (args.GetArgumentCount() == 1) {
543 bool success;
544 bool increment =
545 OptionArgParser::ToBoolean(s: args[0].ref(), fail_value: false, success_ptr: &success);
546
547 if (success) {
548 Timer::SetQuiet(!increment);
549 result.SetStatus(eReturnStatusSuccessFinishNoResult);
550 } else
551 result.AppendError(in_string: "Could not convert increment value to boolean.");
552 }
553
554 if (!result.Succeeded()) {
555 result.AppendError(in_string: "Missing subcommand");
556 result.AppendErrorWithFormat(format: "Usage: %s\n", m_cmd_syntax.c_str());
557 }
558 }
559};
560
561class CommandObjectLogTimer : public CommandObjectMultiword {
562public:
563 CommandObjectLogTimer(CommandInterpreter &interpreter)
564 : CommandObjectMultiword(interpreter, "log timers",
565 "Enable, disable, dump, and reset LLDB internal "
566 "performance timers.",
567 "log timers < enable <depth> | disable | dump | "
568 "increment <bool> | reset >") {
569 LoadSubCommand(cmd_name: "enable", command_obj: CommandObjectSP(
570 new CommandObjectLogTimerEnable(interpreter)));
571 LoadSubCommand(cmd_name: "disable", command_obj: CommandObjectSP(new CommandObjectLogTimerDisable(
572 interpreter)));
573 LoadSubCommand(cmd_name: "dump",
574 command_obj: CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));
575 LoadSubCommand(
576 cmd_name: "reset", command_obj: CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));
577 LoadSubCommand(
578 cmd_name: "increment",
579 command_obj: CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));
580 }
581
582 ~CommandObjectLogTimer() override = default;
583};
584
585CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
586 : CommandObjectMultiword(interpreter, "log",
587 "Commands controlling LLDB internal logging.",
588 "log <subcommand> [<command-options>]") {
589 LoadSubCommand(cmd_name: "enable",
590 command_obj: CommandObjectSP(new CommandObjectLogEnable(interpreter)));
591 LoadSubCommand(cmd_name: "disable",
592 command_obj: CommandObjectSP(new CommandObjectLogDisable(interpreter)));
593 LoadSubCommand(cmd_name: "list",
594 command_obj: CommandObjectSP(new CommandObjectLogList(interpreter)));
595 LoadSubCommand(cmd_name: "dump",
596 command_obj: CommandObjectSP(new CommandObjectLogDump(interpreter)));
597 LoadSubCommand(cmd_name: "timers",
598 command_obj: CommandObjectSP(new CommandObjectLogTimer(interpreter)));
599}
600
601CommandObjectLog::~CommandObjectLog() = default;
602

source code of lldb/source/Commands/CommandObjectLog.cpp