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