1 //===-- CommandObjectLog.cpp ------------------------------------*- C++ -*-===// 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/Core/Module.h" 12 #include "lldb/Core/StreamFile.h" 13 #include "lldb/Host/OptionParser.h" 14 #include "lldb/Interpreter/CommandInterpreter.h" 15 #include "lldb/Interpreter/CommandReturnObject.h" 16 #include "lldb/Interpreter/OptionArgParser.h" 17 #include "lldb/Interpreter/Options.h" 18 #include "lldb/Symbol/LineTable.h" 19 #include "lldb/Symbol/ObjectFile.h" 20 #include "lldb/Symbol/SymbolFile.h" 21 #include "lldb/Symbol/SymbolVendor.h" 22 #include "lldb/Target/Process.h" 23 #include "lldb/Target/Target.h" 24 #include "lldb/Utility/Args.h" 25 #include "lldb/Utility/FileSpec.h" 26 #include "lldb/Utility/Log.h" 27 #include "lldb/Utility/RegularExpression.h" 28 #include "lldb/Utility/Stream.h" 29 #include "lldb/Utility/Timer.h" 30 31 using namespace lldb; 32 using namespace lldb_private; 33 34 static constexpr OptionDefinition g_log_options[] = { 35 // clang-format off 36 { LLDB_OPT_SET_1, false, "file", 'f', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeFilename, "Set the destination file to log to." }, 37 { LLDB_OPT_SET_1, false, "threadsafe", 't', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Enable thread safe logging to avoid interweaved log lines." }, 38 { LLDB_OPT_SET_1, false, "verbose", 'v', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Enable verbose logging." }, 39 { LLDB_OPT_SET_1, false, "sequence", 's', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Prepend all log lines with an increasing integer sequence id." }, 40 { LLDB_OPT_SET_1, false, "timestamp", 'T', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Prepend all log lines with a timestamp." }, 41 { LLDB_OPT_SET_1, false, "pid-tid", 'p', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Prepend all log lines with the process and thread ID that generates the log line." }, 42 { LLDB_OPT_SET_1, false, "thread-name",'n', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Prepend all log lines with the thread name for the thread that generates the log line." }, 43 { LLDB_OPT_SET_1, false, "stack", 'S', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Append a stack backtrace to each log line." }, 44 { LLDB_OPT_SET_1, false, "append", 'a', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Append to the log file instead of overwriting." }, 45 { LLDB_OPT_SET_1, false, "file-function",'F',OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Prepend the names of files and function that generate the logs." }, 46 // clang-format on 47 }; 48 49 class CommandObjectLogEnable : public CommandObjectParsed { 50 public: 51 //------------------------------------------------------------------ 52 // Constructors and Destructors 53 //------------------------------------------------------------------ 54 CommandObjectLogEnable(CommandInterpreter &interpreter) 55 : CommandObjectParsed(interpreter, "log enable", 56 "Enable logging for a single log channel.", 57 nullptr), 58 m_options() { 59 CommandArgumentEntry arg1; 60 CommandArgumentEntry arg2; 61 CommandArgumentData channel_arg; 62 CommandArgumentData category_arg; 63 64 // Define the first (and only) variant of this arg. 65 channel_arg.arg_type = eArgTypeLogChannel; 66 channel_arg.arg_repetition = eArgRepeatPlain; 67 68 // There is only one variant this argument could be; put it into the 69 // argument entry. 70 arg1.push_back(channel_arg); 71 72 category_arg.arg_type = eArgTypeLogCategory; 73 category_arg.arg_repetition = eArgRepeatPlus; 74 75 arg2.push_back(category_arg); 76 77 // Push the data for the first argument into the m_arguments vector. 78 m_arguments.push_back(arg1); 79 m_arguments.push_back(arg2); 80 } 81 82 ~CommandObjectLogEnable() override = default; 83 84 Options *GetOptions() override { return &m_options; } 85 86 class CommandOptions : public Options { 87 public: 88 CommandOptions() : Options(), log_file(), log_options(0) {} 89 90 ~CommandOptions() override = default; 91 92 Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, 93 ExecutionContext *execution_context) override { 94 Status error; 95 const int short_option = m_getopt_table[option_idx].val; 96 97 switch (short_option) { 98 case 'f': 99 log_file.SetFile(option_arg, FileSpec::Style::native); 100 FileSystem::Instance().Resolve(log_file); 101 break; 102 case 't': 103 log_options |= LLDB_LOG_OPTION_THREADSAFE; 104 break; 105 case 'v': 106 log_options |= LLDB_LOG_OPTION_VERBOSE; 107 break; 108 case 's': 109 log_options |= LLDB_LOG_OPTION_PREPEND_SEQUENCE; 110 break; 111 case 'T': 112 log_options |= LLDB_LOG_OPTION_PREPEND_TIMESTAMP; 113 break; 114 case 'p': 115 log_options |= LLDB_LOG_OPTION_PREPEND_PROC_AND_THREAD; 116 break; 117 case 'n': 118 log_options |= LLDB_LOG_OPTION_PREPEND_THREAD_NAME; 119 break; 120 case 'S': 121 log_options |= LLDB_LOG_OPTION_BACKTRACE; 122 break; 123 case 'a': 124 log_options |= LLDB_LOG_OPTION_APPEND; 125 break; 126 case 'F': 127 log_options |= LLDB_LOG_OPTION_PREPEND_FILE_FUNCTION; 128 break; 129 default: 130 error.SetErrorStringWithFormat("unrecognized option '%c'", 131 short_option); 132 break; 133 } 134 135 return error; 136 } 137 138 void OptionParsingStarting(ExecutionContext *execution_context) override { 139 log_file.Clear(); 140 log_options = 0; 141 } 142 143 llvm::ArrayRef<OptionDefinition> GetDefinitions() override { 144 return llvm::makeArrayRef(g_log_options); 145 } 146 147 // Instance variables to hold the values for command options. 148 149 FileSpec log_file; 150 uint32_t log_options; 151 }; 152 153 protected: 154 bool DoExecute(Args &args, CommandReturnObject &result) override { 155 if (args.GetArgumentCount() < 2) { 156 result.AppendErrorWithFormat( 157 "%s takes a log channel and one or more log types.\n", 158 m_cmd_name.c_str()); 159 return false; 160 } 161 162 // Store into a std::string since we're about to shift the channel off. 163 const std::string channel = args[0].ref; 164 args.Shift(); // Shift off the channel 165 char log_file[PATH_MAX]; 166 if (m_options.log_file) 167 m_options.log_file.GetPath(log_file, sizeof(log_file)); 168 else 169 log_file[0] = '\0'; 170 171 std::string error; 172 llvm::raw_string_ostream error_stream(error); 173 bool success = m_interpreter.GetDebugger().EnableLog( 174 channel, args.GetArgumentArrayRef(), log_file, m_options.log_options, 175 error_stream); 176 result.GetErrorStream() << error_stream.str(); 177 178 if (success) 179 result.SetStatus(eReturnStatusSuccessFinishNoResult); 180 else 181 result.SetStatus(eReturnStatusFailed); 182 return result.Succeeded(); 183 } 184 185 CommandOptions m_options; 186 }; 187 188 class CommandObjectLogDisable : public CommandObjectParsed { 189 public: 190 //------------------------------------------------------------------ 191 // Constructors and Destructors 192 //------------------------------------------------------------------ 193 CommandObjectLogDisable(CommandInterpreter &interpreter) 194 : CommandObjectParsed(interpreter, "log disable", 195 "Disable one or more log channel categories.", 196 nullptr) { 197 CommandArgumentEntry arg1; 198 CommandArgumentEntry arg2; 199 CommandArgumentData channel_arg; 200 CommandArgumentData category_arg; 201 202 // Define the first (and only) variant of this arg. 203 channel_arg.arg_type = eArgTypeLogChannel; 204 channel_arg.arg_repetition = eArgRepeatPlain; 205 206 // There is only one variant this argument could be; put it into the 207 // argument entry. 208 arg1.push_back(channel_arg); 209 210 category_arg.arg_type = eArgTypeLogCategory; 211 category_arg.arg_repetition = eArgRepeatPlus; 212 213 arg2.push_back(category_arg); 214 215 // Push the data for the first argument into the m_arguments vector. 216 m_arguments.push_back(arg1); 217 m_arguments.push_back(arg2); 218 } 219 220 ~CommandObjectLogDisable() override = default; 221 222 protected: 223 bool DoExecute(Args &args, CommandReturnObject &result) override { 224 if (args.empty()) { 225 result.AppendErrorWithFormat( 226 "%s takes a log channel and one or more log types.\n", 227 m_cmd_name.c_str()); 228 return false; 229 } 230 231 const std::string channel = args[0].ref; 232 args.Shift(); // Shift off the channel 233 if (channel == "all") { 234 Log::DisableAllLogChannels(); 235 result.SetStatus(eReturnStatusSuccessFinishNoResult); 236 } else { 237 std::string error; 238 llvm::raw_string_ostream error_stream(error); 239 if (Log::DisableLogChannel(channel, args.GetArgumentArrayRef(), 240 error_stream)) 241 result.SetStatus(eReturnStatusSuccessFinishNoResult); 242 result.GetErrorStream() << error_stream.str(); 243 } 244 return result.Succeeded(); 245 } 246 }; 247 248 class CommandObjectLogList : public CommandObjectParsed { 249 public: 250 //------------------------------------------------------------------ 251 // Constructors and Destructors 252 //------------------------------------------------------------------ 253 CommandObjectLogList(CommandInterpreter &interpreter) 254 : CommandObjectParsed(interpreter, "log list", 255 "List the log categories for one or more log " 256 "channels. If none specified, lists them all.", 257 nullptr) { 258 CommandArgumentEntry arg; 259 CommandArgumentData channel_arg; 260 261 // Define the first (and only) variant of this arg. 262 channel_arg.arg_type = eArgTypeLogChannel; 263 channel_arg.arg_repetition = eArgRepeatStar; 264 265 // There is only one variant this argument could be; put it into the 266 // argument entry. 267 arg.push_back(channel_arg); 268 269 // Push the data for the first argument into the m_arguments vector. 270 m_arguments.push_back(arg); 271 } 272 273 ~CommandObjectLogList() override = default; 274 275 protected: 276 bool DoExecute(Args &args, CommandReturnObject &result) override { 277 std::string output; 278 llvm::raw_string_ostream output_stream(output); 279 if (args.empty()) { 280 Log::ListAllLogChannels(output_stream); 281 result.SetStatus(eReturnStatusSuccessFinishResult); 282 } else { 283 bool success = true; 284 for (const auto &entry : args.entries()) 285 success = 286 success && Log::ListChannelCategories(entry.ref, output_stream); 287 if (success) 288 result.SetStatus(eReturnStatusSuccessFinishResult); 289 } 290 result.GetOutputStream() << output_stream.str(); 291 return result.Succeeded(); 292 } 293 }; 294 295 class CommandObjectLogTimer : public CommandObjectParsed { 296 public: 297 //------------------------------------------------------------------ 298 // Constructors and Destructors 299 //------------------------------------------------------------------ 300 CommandObjectLogTimer(CommandInterpreter &interpreter) 301 : CommandObjectParsed(interpreter, "log timers", 302 "Enable, disable, dump, and reset LLDB internal " 303 "performance timers.", 304 "log timers < enable <depth> | disable | dump | " 305 "increment <bool> | reset >") {} 306 307 ~CommandObjectLogTimer() override = default; 308 309 protected: 310 bool DoExecute(Args &args, CommandReturnObject &result) override { 311 result.SetStatus(eReturnStatusFailed); 312 313 if (args.GetArgumentCount() == 1) { 314 auto sub_command = args[0].ref; 315 316 if (sub_command.equals_lower("enable")) { 317 Timer::SetDisplayDepth(UINT32_MAX); 318 result.SetStatus(eReturnStatusSuccessFinishNoResult); 319 } else if (sub_command.equals_lower("disable")) { 320 Timer::DumpCategoryTimes(&result.GetOutputStream()); 321 Timer::SetDisplayDepth(0); 322 result.SetStatus(eReturnStatusSuccessFinishResult); 323 } else if (sub_command.equals_lower("dump")) { 324 Timer::DumpCategoryTimes(&result.GetOutputStream()); 325 result.SetStatus(eReturnStatusSuccessFinishResult); 326 } else if (sub_command.equals_lower("reset")) { 327 Timer::ResetCategoryTimes(); 328 result.SetStatus(eReturnStatusSuccessFinishResult); 329 } 330 } else if (args.GetArgumentCount() == 2) { 331 auto sub_command = args[0].ref; 332 auto param = args[1].ref; 333 334 if (sub_command.equals_lower("enable")) { 335 uint32_t depth; 336 if (param.consumeInteger(0, depth)) { 337 result.AppendError( 338 "Could not convert enable depth to an unsigned integer."); 339 } else { 340 Timer::SetDisplayDepth(depth); 341 result.SetStatus(eReturnStatusSuccessFinishNoResult); 342 } 343 } else if (sub_command.equals_lower("increment")) { 344 bool success; 345 bool increment = OptionArgParser::ToBoolean(param, false, &success); 346 if (success) { 347 Timer::SetQuiet(!increment); 348 result.SetStatus(eReturnStatusSuccessFinishNoResult); 349 } else 350 result.AppendError("Could not convert increment value to boolean."); 351 } 352 } 353 354 if (!result.Succeeded()) { 355 result.AppendError("Missing subcommand"); 356 result.AppendErrorWithFormat("Usage: %s\n", m_cmd_syntax.c_str()); 357 } 358 return result.Succeeded(); 359 } 360 }; 361 362 CommandObjectLog::CommandObjectLog(CommandInterpreter &interpreter) 363 : CommandObjectMultiword(interpreter, "log", 364 "Commands controlling LLDB internal logging.", 365 "log <subcommand> [<command-options>]") { 366 LoadSubCommand("enable", 367 CommandObjectSP(new CommandObjectLogEnable(interpreter))); 368 LoadSubCommand("disable", 369 CommandObjectSP(new CommandObjectLogDisable(interpreter))); 370 LoadSubCommand("list", 371 CommandObjectSP(new CommandObjectLogList(interpreter))); 372 LoadSubCommand("timers", 373 CommandObjectSP(new CommandObjectLogTimer(interpreter))); 374 } 375 376 CommandObjectLog::~CommandObjectLog() = default; 377