xref: /freebsd/contrib/llvm-project/lldb/source/Commands/CommandObjectLog.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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.
CompleteEnableDisable(CompletionRequest & request)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
CommandObjectLogEnable(CommandInterpreter & interpreter)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 
GetOptions()80   Options *GetOptions() override { return &m_options; }
81 
82   class CommandOptions : public Options {
83   public:
84     CommandOptions() = default;
85 
86     ~CommandOptions() override = default;
87 
SetOptionValue(uint32_t option_idx,llvm::StringRef option_arg,ExecutionContext * execution_context)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 
OptionParsingStarting(ExecutionContext * execution_context)141     void OptionParsingStarting(ExecutionContext *execution_context) override {
142       log_file.Clear();
143       buffer_size.Clear();
144       handler = eLogHandlerStream;
145       log_options = 0;
146     }
147 
GetDefinitions()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
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)159   HandleArgumentCompletion(CompletionRequest &request,
160                            OptionElementVector &opt_element_vector) override {
161     CompleteEnableDisable(request);
162   }
163 
164 protected:
DoExecute(Args & args,CommandReturnObject & result)165   void 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;
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;
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;
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;
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   }
216 
217   CommandOptions m_options;
218 };
219 
220 class CommandObjectLogDisable : public CommandObjectParsed {
221 public:
222   // Constructors and Destructors
CommandObjectLogDisable(CommandInterpreter & interpreter)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(channel_arg);
239 
240     category_arg.arg_type = eArgTypeLogCategory;
241     category_arg.arg_repetition = eArgRepeatPlus;
242 
243     arg2.push_back(category_arg);
244 
245     // Push the data for the first argument into the m_arguments vector.
246     m_arguments.push_back(arg1);
247     m_arguments.push_back(arg2);
248   }
249 
250   ~CommandObjectLogDisable() override = default;
251 
252   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)253   HandleArgumentCompletion(CompletionRequest &request,
254                            OptionElementVector &opt_element_vector) override {
255     CompleteEnableDisable(request);
256   }
257 
258 protected:
DoExecute(Args & args,CommandReturnObject & result)259   void DoExecute(Args &args, CommandReturnObject &result) override {
260     if (args.empty()) {
261       result.AppendErrorWithFormat(
262           "%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, args.GetArgumentArrayRef(),
276                                  error_stream))
277         result.SetStatus(eReturnStatusSuccessFinishNoResult);
278       result.GetErrorStream() << error_stream.str();
279     }
280   }
281 };
282 
283 class CommandObjectLogList : public CommandObjectParsed {
284 public:
285   // Constructors and Destructors
CommandObjectLogList(CommandInterpreter & interpreter)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(eArgTypeLogChannel, eArgRepeatStar);
292   }
293 
294   ~CommandObjectLogList() override = default;
295 
296   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)297   HandleArgumentCompletion(CompletionRequest &request,
298                            OptionElementVector &opt_element_vector) override {
299     for (llvm::StringRef channel : Log::ListChannels())
300       request.TryCompleteCurrentArg(channel);
301   }
302 
303 protected:
DoExecute(Args & args,CommandReturnObject & result)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(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(entry.ref(), output_stream);
315       if (success)
316         result.SetStatus(eReturnStatusSuccessFinishResult);
317     }
318     result.GetOutputStream() << output_stream.str();
319   }
320 };
321 class CommandObjectLogDump : public CommandObjectParsed {
322 public:
CommandObjectLogDump(CommandInterpreter & interpreter)323   CommandObjectLogDump(CommandInterpreter &interpreter)
324       : CommandObjectParsed(interpreter, "log dump",
325                             "dump circular buffer logs", nullptr) {
326     AddSimpleArgumentList(eArgTypeLogChannel);
327   }
328 
329   ~CommandObjectLogDump() override = default;
330 
GetOptions()331   Options *GetOptions() override { return &m_options; }
332 
333   class CommandOptions : public Options {
334   public:
335     CommandOptions() = default;
336 
337     ~CommandOptions() override = default;
338 
SetOptionValue(uint32_t option_idx,llvm::StringRef option_arg,ExecutionContext * execution_context)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(option_arg, FileSpec::Style::native);
347         FileSystem::Instance().Resolve(log_file);
348         break;
349       default:
350         llvm_unreachable("Unimplemented option");
351       }
352 
353       return error;
354     }
355 
OptionParsingStarting(ExecutionContext * execution_context)356     void OptionParsingStarting(ExecutionContext *execution_context) override {
357       log_file.Clear();
358     }
359 
GetDefinitions()360     llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
361       return llvm::ArrayRef(g_log_dump_options);
362     }
363 
364     FileSpec log_file;
365   };
366 
367   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)368   HandleArgumentCompletion(CompletionRequest &request,
369                            OptionElementVector &opt_element_vector) override {
370     CompleteEnableDisable(request);
371   }
372 
373 protected:
DoExecute(Args & args,CommandReturnObject & result)374   void DoExecute(Args &args, CommandReturnObject &result) override {
375     if (args.empty()) {
376       result.AppendErrorWithFormat(
377           "%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           m_options.log_file, flags, lldb::eFilePermissionsFileDefault, false);
389       if (!file) {
390         result.AppendErrorWithFormat("Unable to open log file '%s': %s",
391                                      m_options.log_file.GetPath().c_str(),
392                                      llvm::toString(file.takeError()).c_str());
393         return;
394       }
395       stream_up = std::make_unique<llvm::raw_fd_ostream>(
396           (*file)->GetDescriptor(), /*shouldClose=*/true);
397     } else {
398       stream_up = std::make_unique<llvm::raw_fd_ostream>(
399           GetDebugger().GetOutputFile().GetDescriptor(), /*shouldClose=*/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, *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 
416 class CommandObjectLogTimerEnable : public CommandObjectParsed {
417 public:
418   // Constructors and Destructors
CommandObjectLogTimerEnable(CommandInterpreter & interpreter)419   CommandObjectLogTimerEnable(CommandInterpreter &interpreter)
420       : CommandObjectParsed(interpreter, "log timers enable",
421                             "enable LLDB internal performance timers",
422                             "log timers enable <depth>") {
423     AddSimpleArgumentList(eArgTypeCount, eArgRepeatOptional);
424   }
425 
426   ~CommandObjectLogTimerEnable() override = default;
427 
428 protected:
DoExecute(Args & args,CommandReturnObject & result)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(0, depth)) {
438         result.AppendError(
439             "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("Missing subcommand");
448       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
449     }
450   }
451 };
452 
453 class CommandObjectLogTimerDisable : public CommandObjectParsed {
454 public:
455   // Constructors and Destructors
CommandObjectLogTimerDisable(CommandInterpreter & interpreter)456   CommandObjectLogTimerDisable(CommandInterpreter &interpreter)
457       : CommandObjectParsed(interpreter, "log timers disable",
458                             "disable LLDB internal performance timers",
459                             nullptr) {}
460 
461   ~CommandObjectLogTimerDisable() override = default;
462 
463 protected:
DoExecute(Args & args,CommandReturnObject & result)464   void DoExecute(Args &args, CommandReturnObject &result) override {
465     Timer::DumpCategoryTimes(result.GetOutputStream());
466     Timer::SetDisplayDepth(0);
467     result.SetStatus(eReturnStatusSuccessFinishResult);
468 
469     if (!result.Succeeded()) {
470       result.AppendError("Missing subcommand");
471       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
472     }
473   }
474 };
475 
476 class CommandObjectLogTimerDump : public CommandObjectParsed {
477 public:
478   // Constructors and Destructors
CommandObjectLogTimerDump(CommandInterpreter & interpreter)479   CommandObjectLogTimerDump(CommandInterpreter &interpreter)
480       : CommandObjectParsed(interpreter, "log timers dump",
481                             "dump LLDB internal performance timers", nullptr) {}
482 
483   ~CommandObjectLogTimerDump() override = default;
484 
485 protected:
DoExecute(Args & args,CommandReturnObject & result)486   void DoExecute(Args &args, CommandReturnObject &result) override {
487     Timer::DumpCategoryTimes(result.GetOutputStream());
488     result.SetStatus(eReturnStatusSuccessFinishResult);
489 
490     if (!result.Succeeded()) {
491       result.AppendError("Missing subcommand");
492       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
493     }
494   }
495 };
496 
497 class CommandObjectLogTimerReset : public CommandObjectParsed {
498 public:
499   // Constructors and Destructors
CommandObjectLogTimerReset(CommandInterpreter & interpreter)500   CommandObjectLogTimerReset(CommandInterpreter &interpreter)
501       : CommandObjectParsed(interpreter, "log timers reset",
502                             "reset LLDB internal performance timers", nullptr) {
503   }
504 
505   ~CommandObjectLogTimerReset() override = default;
506 
507 protected:
DoExecute(Args & args,CommandReturnObject & result)508   void DoExecute(Args &args, CommandReturnObject &result) override {
509     Timer::ResetCategoryTimes();
510     result.SetStatus(eReturnStatusSuccessFinishResult);
511 
512     if (!result.Succeeded()) {
513       result.AppendError("Missing subcommand");
514       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
515     }
516   }
517 };
518 
519 class CommandObjectLogTimerIncrement : public CommandObjectParsed {
520 public:
521   // Constructors and Destructors
CommandObjectLogTimerIncrement(CommandInterpreter & interpreter)522   CommandObjectLogTimerIncrement(CommandInterpreter &interpreter)
523       : CommandObjectParsed(interpreter, "log timers increment",
524                             "increment LLDB internal performance timers",
525                             "log timers increment <bool>") {
526     AddSimpleArgumentList(eArgTypeBoolean);
527   }
528 
529   ~CommandObjectLogTimerIncrement() override = default;
530 
531   void
HandleArgumentCompletion(CompletionRequest & request,OptionElementVector & opt_element_vector)532   HandleArgumentCompletion(CompletionRequest &request,
533                            OptionElementVector &opt_element_vector) override {
534     request.TryCompleteCurrentArg("true");
535     request.TryCompleteCurrentArg("false");
536   }
537 
538 protected:
DoExecute(Args & args,CommandReturnObject & result)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(args[0].ref(), false, &success);
546 
547       if (success) {
548         Timer::SetQuiet(!increment);
549         result.SetStatus(eReturnStatusSuccessFinishNoResult);
550       } else
551         result.AppendError("Could not convert increment value to boolean.");
552     }
553 
554     if (!result.Succeeded()) {
555       result.AppendError("Missing subcommand");
556       result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str());
557     }
558   }
559 };
560 
561 class CommandObjectLogTimer : public CommandObjectMultiword {
562 public:
CommandObjectLogTimer(CommandInterpreter & interpreter)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("enable", CommandObjectSP(
570                                  new CommandObjectLogTimerEnable(interpreter)));
571     LoadSubCommand("disable", CommandObjectSP(new CommandObjectLogTimerDisable(
572                                   interpreter)));
573     LoadSubCommand("dump",
574                    CommandObjectSP(new CommandObjectLogTimerDump(interpreter)));
575     LoadSubCommand(
576         "reset", CommandObjectSP(new CommandObjectLogTimerReset(interpreter)));
577     LoadSubCommand(
578         "increment",
579         CommandObjectSP(new CommandObjectLogTimerIncrement(interpreter)));
580   }
581 
582   ~CommandObjectLogTimer() override = default;
583 };
584 
CommandObjectLog(CommandInterpreter & interpreter)585 CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter)
586     : CommandObjectMultiword(interpreter, "log",
587                              "Commands controlling LLDB internal logging.",
588                              "log <subcommand> [<command-options>]") {
589   LoadSubCommand("enable",
590                  CommandObjectSP(new CommandObjectLogEnable(interpreter)));
591   LoadSubCommand("disable",
592                  CommandObjectSP(new CommandObjectLogDisable(interpreter)));
593   LoadSubCommand("list",
594                  CommandObjectSP(new CommandObjectLogList(interpreter)));
595   LoadSubCommand("dump",
596                  CommandObjectSP(new CommandObjectLogDump(interpreter)));
597   LoadSubCommand("timers",
598                  CommandObjectSP(new CommandObjectLogTimer(interpreter)));
599 }
600 
601 CommandObjectLog::~CommandObjectLog() = default;
602