xref: /freebsd-src/contrib/llvm-project/lldb/source/Commands/CommandObjectLog.cpp (revision fc55c20355d889bf3d3f81d94b3614a0c4253fa0)
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           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(option_arg, 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 
164 protected:
165   bool DoExecute(Args &args, CommandReturnObject &result) override {
166     if (args.GetArgumentCount() < 2) {
167       result.AppendErrorWithFormat(
168           "%s takes a log channel and one or more log types.\n",
169           m_cmd_name.c_str());
170       return false;
171     }
172 
173     if (m_options.handler == eLogHandlerCircular &&
174         m_options.buffer_size.GetCurrentValue() == 0) {
175       result.AppendError(
176           "the circular buffer handler requires a non-zero buffer size.\n");
177       return false;
178     }
179 
180     if ((m_options.handler != eLogHandlerCircular &&
181          m_options.handler != eLogHandlerStream) &&
182         m_options.buffer_size.GetCurrentValue() != 0) {
183       result.AppendError("a buffer size can only be specified for the circular "
184                          "and stream buffer handler.\n");
185       return false;
186     }
187 
188     if (m_options.handler != eLogHandlerStream && m_options.log_file) {
189       result.AppendError(
190           "a file name can only be specified for the stream handler.\n");
191       return false;
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(log_file, 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, args.GetArgumentArrayRef(), log_file, m_options.log_options,
207         m_options.buffer_size.GetCurrentValue(), 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     return result.Succeeded();
216   }
217 
218   CommandOptions m_options;
219 };
220 
221 class CommandObjectLogDisable : public CommandObjectParsed {
222 public:
223   // Constructors and Destructors
224   CommandObjectLogDisable(CommandInterpreter &interpreter)
225       : CommandObjectParsed(interpreter, "log disable",
226                             "Disable one or more log channel categories.",
227                             nullptr) {
228     CommandArgumentEntry arg1;
229     CommandArgumentEntry arg2;
230     CommandArgumentData channel_arg;
231     CommandArgumentData category_arg;
232 
233     // Define the first (and only) variant of this arg.
234     channel_arg.arg_type = eArgTypeLogChannel;
235     channel_arg.arg_repetition = eArgRepeatPlain;
236 
237     // There is only one variant this argument could be; put it into the
238     // argument entry.
239     arg1.push_back(channel_arg);
240 
241     category_arg.arg_type = eArgTypeLogCategory;
242     category_arg.arg_repetition = eArgRepeatPlus;
243 
244     arg2.push_back(category_arg);
245 
246     // Push the data for the first argument into the m_arguments vector.
247     m_arguments.push_back(arg1);
248     m_arguments.push_back(arg2);
249   }
250 
251   ~CommandObjectLogDisable() override = default;
252 
253   void
254   HandleArgumentCompletion(CompletionRequest &request,
255                            OptionElementVector &opt_element_vector) override {
256     CompleteEnableDisable(request);
257   }
258 
259 protected:
260   bool DoExecute(Args &args, CommandReturnObject &result) override {
261     if (args.empty()) {
262       result.AppendErrorWithFormat(
263           "%s takes a log channel and one or more log types.\n",
264           m_cmd_name.c_str());
265       return false;
266     }
267 
268     const std::string channel = std::string(args[0].ref());
269     args.Shift(); // Shift off the channel
270     if (channel == "all") {
271       Log::DisableAllLogChannels();
272       result.SetStatus(eReturnStatusSuccessFinishNoResult);
273     } else {
274       std::string error;
275       llvm::raw_string_ostream error_stream(error);
276       if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(),
277                                  error_stream))
278         result.SetStatus(eReturnStatusSuccessFinishNoResult);
279       result.GetErrorStream() << error_stream.str();
280     }
281     return result.Succeeded();
282   }
283 };
284 
285 class CommandObjectLogList : public CommandObjectParsed {
286 public:
287   // Constructors and Destructors
288   CommandObjectLogList(CommandInterpreter &interpreter)
289       : CommandObjectParsed(interpreter, "log list",
290                             "List the log categories for one or more log "
291                             "channels.  If none specified, lists them all.",
292                             nullptr) {
293     CommandArgumentEntry arg;
294     CommandArgumentData channel_arg;
295 
296     // Define the first (and only) variant of this arg.
297     channel_arg.arg_type = eArgTypeLogChannel;
298     channel_arg.arg_repetition = eArgRepeatStar;
299 
300     // There is only one variant this argument could be; put it into the
301     // argument entry.
302     arg.push_back(channel_arg);
303 
304     // Push the data for the first argument into the m_arguments vector.
305     m_arguments.push_back(arg);
306   }
307 
308   ~CommandObjectLogList() override = default;
309 
310   void
311   HandleArgumentCompletion(CompletionRequest &request,
312                            OptionElementVector &opt_element_vector) override {
313     for (llvm::StringRef channel : Log::ListChannels())
314       request.TryCompleteCurrentArg(channel);
315   }
316 
317 protected:
318   bool DoExecute(Args &args, CommandReturnObject &result) override {
319     std::string output;
320     llvm::raw_string_ostream output_stream(output);
321     if (args.empty()) {
322       Log::ListAllLogChannels(output_stream);
323       result.SetStatus(eReturnStatusSuccessFinishResult);
324     } else {
325       bool success = true;
326       for (const auto &entry : args.entries())
327         success =
328             success && Log::ListChannelCategories(entry.ref(), output_stream);
329       if (success)
330         result.SetStatus(eReturnStatusSuccessFinishResult);
331     }
332     result.GetOutputStream() << output_stream.str();
333     return result.Succeeded();
334   }
335 };
336 class CommandObjectLogDump : public CommandObjectParsed {
337 public:
338   CommandObjectLogDump(CommandInterpreter &interpreter)
339       : CommandObjectParsed(interpreter, "log dump",
340                             "dump circular buffer logs", nullptr) {
341     CommandArgumentEntry arg1;
342     CommandArgumentData channel_arg;
343 
344     // Define the first (and only) variant of this arg.
345     channel_arg.arg_type = eArgTypeLogChannel;
346     channel_arg.arg_repetition = eArgRepeatPlain;
347 
348     // There is only one variant this argument could be; put it into the
349     // argument entry.
350     arg1.push_back(channel_arg);
351 
352     // Push the data for the first argument into the m_arguments vector.
353     m_arguments.push_back(arg1);
354   }
355 
356   ~CommandObjectLogDump() override = default;
357 
358   Options *GetOptions() override { return &m_options; }
359 
360   class CommandOptions : public Options {
361   public:
362     CommandOptions() = default;
363 
364     ~CommandOptions() override = default;
365 
366     Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg,
367                           ExecutionContext *execution_context) override {
368       Status error;
369       const int short_option = m_getopt_table[option_idx].val;
370 
371       switch (short_option) {
372       case 'f':
373         log_file.SetFile(option_arg, FileSpec::Style::native);
374         FileSystem::Instance().Resolve(log_file);
375         break;
376       default:
377         llvm_unreachable("Unimplemented option");
378       }
379 
380       return error;
381     }
382 
383     void OptionParsingStarting(ExecutionContext *execution_context) override {
384       log_file.Clear();
385     }
386 
387     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
388       return llvm::ArrayRef(g_log_dump_options);
389     }
390 
391     FileSpec log_file;
392   };
393 
394   void
395   HandleArgumentCompletion(CompletionRequest &request,
396                            OptionElementVector &opt_element_vector) override {
397     CompleteEnableDisable(request);
398   }
399 
400 protected:
401   bool DoExecute(Args &args, CommandReturnObject &result) override {
402     if (args.empty()) {
403       result.AppendErrorWithFormat(
404           "%s takes a log channel and one or more log types.\n",
405           m_cmd_name.c_str());
406       return false;
407     }
408 
409     std::unique_ptr<llvm::raw_ostream> stream_up;
410     if (m_options.log_file) {
411       const File::OpenOptions flags = File::eOpenOptionWriteOnly |
412                                       File::eOpenOptionCanCreate |
413                                       File::eOpenOptionTruncate;
414       llvm::Expected<FileUP> file = FileSystem::Instance().Open(
415           m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);
416       if (!file) {
417         result.AppendErrorWithFormat("Unable to open log file '%s': %s",
418                                      m_options.log_file.GetPath().c_str(),
419                                      llvm::toString(file.takeError()).c_str());
420         return false;
421       }
422       stream_up = std::make_unique<llvm::raw_fd_ostream>(
423           (*file)->GetDescriptor(), /*shouldClose=*/true);
424     } else {
425       stream_up = std::make_unique<llvm::raw_fd_ostream>(
426           GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/false);
427     }
428 
429     const std::string channel = std::string(args[0].ref());
430     std::string error;
431     llvm::raw_string_ostream error_stream(error);
432     if (Log::DumpLogChannel(channel, *stream_up, error_stream)) {
433       result.SetStatus(eReturnStatusSuccessFinishNoResult);
434     } else {
435       result.SetStatus(eReturnStatusFailed);
436       result.GetErrorStream() << error_stream.str();
437     }
438 
439     return result.Succeeded();
440   }
441 
442   CommandOptions m_options;
443 };
444 
445 class CommandObjectLogTimerEnable : public CommandObjectParsed {
446 public:
447   // Constructors and Destructors
448   CommandObjectLogTimerEnable(CommandInterpreter &interpreter)
449       : CommandObjectParsed(interpreter, "log timers enable",
450                             "enable LLDB internal performance timers",
451                             "log timers enable <depth>") {
452     CommandArgumentEntry arg;
453     CommandArgumentData depth_arg;
454 
455     // Define the first (and only) variant of this arg.
456     depth_arg.arg_type = eArgTypeCount;
457     depth_arg.arg_repetition = eArgRepeatOptional;
458 
459     // There is only one variant this argument could be; put it into the
460     // argument entry.
461     arg.push_back(depth_arg);
462 
463     // Push the data for the first argument into the m_arguments vector.
464     m_arguments.push_back(arg);
465   }
466 
467   ~CommandObjectLogTimerEnable() override = default;
468 
469 protected:
470   bool DoExecute(Args &args, CommandReturnObject &result) override {
471     result.SetStatus(eReturnStatusFailed);
472 
473     if (args.GetArgumentCount() == 0) {
474       Timer::SetDisplayDepth(UINT32_MAX);
475       result.SetStatus(eReturnStatusSuccessFinishNoResult);
476     } else if (args.GetArgumentCount() == 1) {
477       uint32_t depth;
478       if (args[0].ref().consumeInteger(0, depth)) {
479         result.AppendError(
480             "Could not convert enable depth to an unsigned integer.");
481       } else {
482         Timer::SetDisplayDepth(depth);
483         result.SetStatus(eReturnStatusSuccessFinishNoResult);
484       }
485     }
486 
487     if (!result.Succeeded()) {
488       result.AppendError("Missing subcommand");
489       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
490     }
491     return result.Succeeded();
492   }
493 };
494 
495 class CommandObjectLogTimerDisable : public CommandObjectParsed {
496 public:
497   // Constructors and Destructors
498   CommandObjectLogTimerDisable(CommandInterpreter &interpreter)
499       : CommandObjectParsed(interpreter, "log timers disable",
500                             "disable LLDB internal performance timers",
501                             nullptr) {}
502 
503   ~CommandObjectLogTimerDisable() override = default;
504 
505 protected:
506   bool DoExecute(Args &args, CommandReturnObject &result) override {
507     Timer::DumpCategoryTimes(result.GetOutputStream());
508     Timer::SetDisplayDepth(0);
509     result.SetStatus(eReturnStatusSuccessFinishResult);
510 
511     if (!result.Succeeded()) {
512       result.AppendError("Missing subcommand");
513       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
514     }
515     return result.Succeeded();
516   }
517 };
518 
519 class CommandObjectLogTimerDump : public CommandObjectParsed {
520 public:
521   // Constructors and Destructors
522   CommandObjectLogTimerDump(CommandInterpreter &interpreter)
523       : CommandObjectParsed(interpreter, "log timers dump",
524                             "dump LLDB internal performance timers", nullptr) {}
525 
526   ~CommandObjectLogTimerDump() override = default;
527 
528 protected:
529   bool DoExecute(Args &args, CommandReturnObject &result) override {
530     Timer::DumpCategoryTimes(result.GetOutputStream());
531     result.SetStatus(eReturnStatusSuccessFinishResult);
532 
533     if (!result.Succeeded()) {
534       result.AppendError("Missing subcommand");
535       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
536     }
537     return result.Succeeded();
538   }
539 };
540 
541 class CommandObjectLogTimerReset : public CommandObjectParsed {
542 public:
543   // Constructors and Destructors
544   CommandObjectLogTimerReset(CommandInterpreter &interpreter)
545       : CommandObjectParsed(interpreter, "log timers reset",
546                             "reset LLDB internal performance timers", nullptr) {
547   }
548 
549   ~CommandObjectLogTimerReset() override = default;
550 
551 protected:
552   bool DoExecute(Args &args, CommandReturnObject &result) override {
553     Timer::ResetCategoryTimes();
554     result.SetStatus(eReturnStatusSuccessFinishResult);
555 
556     if (!result.Succeeded()) {
557       result.AppendError("Missing subcommand");
558       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
559     }
560     return result.Succeeded();
561   }
562 };
563 
564 class CommandObjectLogTimerIncrement : public CommandObjectParsed {
565 public:
566   // Constructors and Destructors
567   CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)
568       : CommandObjectParsed(interpreter, "log timers increment",
569                             "increment LLDB internal performance timers",
570                             "log timers increment <bool>") {
571     CommandArgumentEntry arg;
572     CommandArgumentData bool_arg;
573 
574     // Define the first (and only) variant of this arg.
575     bool_arg.arg_type = eArgTypeBoolean;
576     bool_arg.arg_repetition = eArgRepeatPlain;
577 
578     // There is only one variant this argument could be; put it into the
579     // argument entry.
580     arg.push_back(bool_arg);
581 
582     // Push the data for the first argument into the m_arguments vector.
583     m_arguments.push_back(arg);
584   }
585 
586   ~CommandObjectLogTimerIncrement() override = default;
587 
588   void
589   HandleArgumentCompletion(CompletionRequest &request,
590                            OptionElementVector &opt_element_vector) override {
591     request.TryCompleteCurrentArg("true");
592     request.TryCompleteCurrentArg("false");
593   }
594 
595 protected:
596   bool DoExecute(Args &args, CommandReturnObject &result) override {
597     result.SetStatus(eReturnStatusFailed);
598 
599     if (args.GetArgumentCount() == 1) {
600       bool success;
601       bool increment =
602           OptionArgParser::ToBoolean(args[0].ref(), false, &success);
603 
604       if (success) {
605         Timer::SetQuiet(!increment);
606         result.SetStatus(eReturnStatusSuccessFinishNoResult);
607       } else
608         result.AppendError("Could not convert increment value to boolean.");
609     }
610 
611     if (!result.Succeeded()) {
612       result.AppendError("Missing subcommand");
613       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
614     }
615     return result.Succeeded();
616   }
617 };
618 
619 class CommandObjectLogTimer : public CommandObjectMultiword {
620 public:
621   CommandObjectLogTimer(CommandInterpreter &interpreter)
622       : CommandObjectMultiword(interpreter, "log timers",
623                                "Enable, disable, dump, and reset LLDB internal "
624                                "performance timers.",
625                                "log timers < enable <depth> | disable | dump | "
626                                "increment <bool> | reset >") {
627     LoadSubCommand("enable", CommandObjectSP(
628                                  new CommandObjectLogTimerEnable(interpreter)));
629     LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable(
630                                   interpreter)));
631     LoadSubCommand("dump",
632                    CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));
633     LoadSubCommand(
634         "reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));
635     LoadSubCommand(
636         "increment",
637         CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));
638   }
639 
640   ~CommandObjectLogTimer() override = default;
641 };
642 
643 CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
644     : CommandObjectMultiword(interpreter, "log",
645                              "Commands controlling LLDB internal logging.",
646                              "log <subcommand> [<command-options>]") {
647   LoadSubCommand("enable",
648                  CommandObjectSP(new CommandObjectLogEnable(interpreter)));
649   LoadSubCommand("disable",
650                  CommandObjectSP(new CommandObjectLogDisable(interpreter)));
651   LoadSubCommand("list",
652                  CommandObjectSP(new CommandObjectLogList(interpreter)));
653   LoadSubCommand("dump",
654                  CommandObjectSP(new CommandObjectLogDump(interpreter)));
655   LoadSubCommand("timers",
656                  CommandObjectSP(new CommandObjectLogTimer(interpreter)));
657 }
658 
659 CommandObjectLog::~CommandObjectLog() = default;
660