Index: lldb/trunk/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp =================================================================== --- lldb/trunk/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp +++ lldb/trunk/source/Plugins/Process/gdb-remote/GDBRemoteCommunication.cpp @@ -850,12 +850,21 @@ debugserver_args.AppendArgument(arg_cstr); } +#if defined(__APPLE__) const char *env_debugserver_log_flags = getenv("LLDB_DEBUGSERVER_LOG_FLAGS"); if (env_debugserver_log_flags) { ::snprintf (arg_cstr, sizeof(arg_cstr), "--log-flags=%s", env_debugserver_log_flags); debugserver_args.AppendArgument(arg_cstr); } +#else + const char *env_debugserver_log_channels = getenv("LLDB_SERVER_LOG_CHANNELS"); + if (env_debugserver_log_channels) + { + ::snprintf (arg_cstr, sizeof(arg_cstr), "--log-channels=%s", env_debugserver_log_channels); + debugserver_args.AppendArgument(arg_cstr); + } +#endif // Add additional args, starting with LLDB_DEBUGSERVER_EXTRA_ARG_1 until an env var doesn't come back. uint32_t env_var_index = 1; Index: lldb/trunk/test/dotest.py =================================================================== --- lldb/trunk/test/dotest.py +++ lldb/trunk/test/dotest.py @@ -32,6 +32,7 @@ import time import inspect import unittest2 +import lldbtest_config if sys.version_info >= (2, 7): argparse = __import__('argparse') @@ -337,7 +338,36 @@ In case there is any test failure/error, a similar message is appended at the end of the stderr output for your convenience. -Environment variables related to loggings: +ENABLING LOGS FROM TESTS + +Option 1: + +Writing logs into different files per test case:: + +This option is particularly useful when multiple dotest instances are created +by dosep.py + +$ ./dotest.py --channel "lldb all" + +$ ./dotest.py --channel "lldb all" --channel "gdb-remote packets" + +These log files are written to: + +/.log (logs from lldb host process) +/-server.log (logs from debugserver/lldb-server) +/-trace-.log (console logs) + +By default, logs from successful runs are deleted. Use the --log-success flag +to create reference logs for debugging. + +$ ./dotest.py --log-success + +Option 2: (DEPRECATED) + +The following options can only enable logs from the host lldb process. +Only categories from the "lldb" or "gdb-remote" channels can be enabled +They also do not automatically enable logs in locally running debug servers. +Also, logs from all test case are written into each log file o LLDB_LOG: if defined, specifies the log file pathname for the 'lldb' subsystem with a default option of 'event process' if LLDB_LOG_OPTION is not defined. @@ -522,6 +552,8 @@ group.add_argument('-x', metavar='breakpoint-spec', help='Specify the breakpoint specification for the benchmark executable') group.add_argument('-y', type=int, metavar='count', help="Specify the iteration count used to collect our benchmarks. An example is the number of times to do 'thread step-over' to measure stepping speed.") group.add_argument('-#', type=int, metavar='sharp', dest='sharp', help='Repeat the test suite for a specified number of times') + group.add_argument('--channel', metavar='channel', dest='channels', action='append', help=textwrap.dedent("Specify the log channels (and optional categories) e.g. 'lldb all' or 'gdb-remote packets' if no categories are specified, 'default' is used")) + group.add_argument('--log-success', dest='log_success', action='store_true', help="Leave logs/traces even for successful test runs (useful for creating reference log files during debugging.)") # Configuration options group = parser.add_argument_group('Remote platform options') @@ -589,6 +621,12 @@ else: compilers = ['clang'] + if args.channels: + lldbtest_config.channels = args.channels + + if args.log_success: + lldbtest_config.log_success = args.log_success + # Set SDKROOT if we are using an Apple SDK if platform_system == 'Darwin' and args.apple_sdk: os.environ['SDKROOT'] = commands.getoutput('xcrun --sdk "%s" --show-sdk-path 2> /dev/null' % (args.apple_sdk)) Index: lldb/trunk/test/lldbtest.py =================================================================== --- lldb/trunk/test/lldbtest.py +++ lldb/trunk/test/lldbtest.py @@ -32,6 +32,7 @@ """ import abc +import glob import os, sys, traceback import os.path import re @@ -42,6 +43,7 @@ import types import unittest2 import lldb +import lldbtest_config from _pyio import __metaclass__ # See also dotest.parseOptionsAndInitTestdirs(), where the environment variables @@ -1284,6 +1286,8 @@ for dict in reversed(self.dicts): self.cleanup(dictionary=dict) + self.disableLogChannelsForCurrentTest() + # Decide whether to dump the session info. self.dumpSessionInfo() @@ -1342,7 +1346,33 @@ def getRerunArgs(self): return " -f %s.%s" % (self.__class__.__name__, self._testMethodName) - + + def getLogBasenameForCurrentTest(self, prefix=None): + """ + returns a partial path that can be used as the beginning of the name of multiple + log files pertaining to this test + + /--.. + """ + dname = os.path.join(os.environ["LLDB_TEST"], + os.environ["LLDB_SESSION_DIRNAME"]) + if not os.path.isdir(dname): + os.mkdir(dname) + + compiler = self.getCompiler() + + if compiler[1] == ':': + compiler = compiler[2:] + + fname = "{}-{}-{}".format(self.getArchitecture(), "_".join(compiler.split(os.path.sep)), self.id()) + if len(fname) > 200: + fname = "{}-{}-{}".format(self.getArchitecture(), compiler.split(os.path.sep)[-1], self.id()) + + if prefix is not None: + fname = "{}-{}".format(prefix, fname) + + return os.path.join(dname, fname) + def dumpSessionInfo(self): """ Dump the debugger interactions leading to a test error/failure. This @@ -1363,6 +1393,9 @@ # formatted tracebacks. # # See http://docs.python.org/library/unittest.html#unittest.TestResult. + src_log_basename = self.getLogBasenameForCurrentTest() + + pairs = [] if self.__errored__: pairs = lldb.test_result.errors prefix = 'Error' @@ -1377,8 +1410,18 @@ elif self.__unexpected__: prefix = "UnexpectedSuccess" else: - # Simply return, there's no session info to dump! - return + prefix = "Success" + if not lldbtest_config.log_success: + # delete log files, return (don't output trace) + for i in glob.glob(src_log_basename + "*"): + os.unlink(i) + return + + # rename all log files - prepend with result + dst_log_basename = self.getLogBasenameForCurrentTest(prefix) + for src in glob.glob(self.getLogBasenameForCurrentTest() + "*"): + dst = src.replace(src_log_basename, dst_log_basename) + os.rename(src, dst) if not self.__unexpected__ and not self.__skipped__: for test, traceback in pairs: @@ -1391,18 +1434,7 @@ else: benchmarks = False - dname = os.path.join(os.environ["LLDB_TEST"], - os.environ["LLDB_SESSION_DIRNAME"]) - if not os.path.isdir(dname): - os.mkdir(dname) - compiler = self.getCompiler() - if compiler[1] == ':': - compiler = compiler[2:] - - fname = "%s-%s-%s-%s.log" % (prefix, self.getArchitecture(), "_".join(compiler.split(os.path.sep)), self.id()) - if len(fname) > 255: - fname = "%s-%s-%s-%s.log" % (prefix, self.getArchitecture(), compiler.split(os.path.sep)[-1], self.id()) - pname = os.path.join(dname, fname) + pname = "{}.log".format(dst_log_basename) with open(pname, "w") as f: import datetime print >> f, "Session info generated @", datetime.datetime.now().ctime() @@ -1835,6 +1867,55 @@ folder = os.path.dirname(folder) continue + def enableLogChannelsForCurrentTest(self): + if len(lldbtest_config.channels) == 0: + return + + # if debug channels are specified in lldbtest_config.channels, + # create a new set of log files for every test + log_basename = self.getLogBasenameForCurrentTest() + + # confirm that the file is writeable + host_log_path = "{}-host.log".format(log_basename) + open(host_log_path, 'w').close() + + log_enable = "log enable -Tpn -f {} ".format(host_log_path) + for channel_with_categories in lldbtest_config.channels: + channel_then_categories = channel_with_categories.split(' ', 1) + channel = channel_then_categories[0] + if len(channel_then_categories) > 1: + categories = channel_then_categories[1] + else: + categories = "default" + + if channel == "gdb-remote": + # communicate gdb-remote categories to debugserver + os.environ["LLDB_DEBUGSERVER_LOG_FLAGS"] = categories + + self.ci.HandleCommand(log_enable + channel_with_categories, self.res) + if not self.res.Succeeded(): + raise Exception('log enable failed (check LLDB_LOG_OPTION env variable)') + + # Communicate log path name to debugserver & lldb-server + server_log_path = "{}-server.log".format(log_basename) + open(server_log_path, 'w').close() + os.environ["LLDB_DEBUGSERVER_LOG_FILE"] = server_log_path + + # Communicate channels to lldb-server + os.environ["LLDB_SERVER_LOG_CHANNELS"] = ":".join(lldbtest_config.channels) + + if len(lldbtest_config.channels) == 0: + return + + def disableLogChannelsForCurrentTest(self): + # close all log files that we opened + for channel_and_categories in lldbtest_config.channels: + # channel format - [ [ ...]] + channel = channel_and_categories.split(' ', 1)[0] + self.ci.HandleCommand("log disable " + channel, self.res) + if not self.res.Succeeded(): + raise Exception('log disable failed (check LLDB_LOG_OPTION env variable)') + def setUp(self): #import traceback #traceback.print_stack() @@ -1871,6 +1952,33 @@ if not self.dbg: raise Exception('Invalid debugger instance') + # Retrieve the associated command interpreter instance. + self.ci = self.dbg.GetCommandInterpreter() + if not self.ci: + raise Exception('Could not get the command interpreter') + + # And the result object. + self.res = lldb.SBCommandReturnObject() + + # Create the debugger instance if necessary. + try: + self.dbg = lldb.DBG + except AttributeError: + self.dbg = lldb.SBDebugger.Create() + + if not self.dbg: + raise Exception('Invalid debugger instance') + + # Retrieve the associated command interpreter instance. + self.ci = self.dbg.GetCommandInterpreter() + if not self.ci: + raise Exception('Could not get the command interpreter') + + # And the result object. + self.res = lldb.SBCommandReturnObject() + + self.enableLogChannelsForCurrentTest() + # # Warning: MAJOR HACK AHEAD! # If we are running testsuite remotely (by checking lldb.lldbtest_remote_sandbox), Index: lldb/trunk/test/lldbtest_config.py =================================================================== --- lldb/trunk/test/lldbtest_config.py +++ lldb/trunk/test/lldbtest_config.py @@ -0,0 +1,17 @@ +""" + The LLVM Compiler Infrastructure + + This file is distributed under the University of Illinois Open Source + License. See LICENSE.TXT for details. + +Configuration options for lldbtest.py set by dotest.py during initialization +""" + +# array of strings +# each string has the name of an lldb channel followed by +# zero or more categories in that channel +# ex. "gdb-remote packets" +channels = [] + +# leave logs/traces even for successful test runs +log_success = False Index: lldb/trunk/tools/lldb-server/CMakeLists.txt =================================================================== --- lldb/trunk/tools/lldb-server/CMakeLists.txt +++ lldb/trunk/tools/lldb-server/CMakeLists.txt @@ -2,6 +2,7 @@ if ( CMAKE_SYSTEM_NAME MATCHES "Linux" ) include_directories( + ../../../../llvm/include ../../source/Plugins/Process/Linux ../../source/Plugins/Process/POSIX ) @@ -9,6 +10,7 @@ if ( CMAKE_SYSTEM_NAME MATCHES "FreeBSD" ) include_directories( + ../../../../llvm/include ../../source/Plugins/Process/FreeBSD ../../source/Plugins/Process/POSIX ) Index: lldb/trunk/tools/lldb-server/lldb-gdbserver.cpp =================================================================== --- lldb/trunk/tools/lldb-server/lldb-gdbserver.cpp +++ lldb/trunk/tools/lldb-server/lldb-gdbserver.cpp @@ -23,6 +23,8 @@ // C++ Includes // Other libraries and framework includes +#include "llvm/ADT/StringRef.h" + #include "lldb/Core/Error.h" #include "lldb/Core/ConnectionMachPort.h" #include "lldb/Core/Debugger.h" @@ -48,6 +50,7 @@ #define LLGS_VERSION_STR "local_build" #endif +using namespace llvm; using namespace lldb; using namespace lldb_private; using namespace lldb_private::process_gdb_remote; @@ -73,9 +76,8 @@ { "debug", no_argument, &g_debug, 1 }, { "platform", required_argument, NULL, 'p' }, { "verbose", no_argument, &g_verbose, 1 }, - { "lldb-command", required_argument, NULL, 'c' }, { "log-file", required_argument, NULL, 'l' }, - { "log-flags", required_argument, NULL, 'f' }, + { "log-channels", required_argument, NULL, 'c' }, { "attach", required_argument, NULL, 'a' }, { "named-pipe", required_argument, NULL, 'N' }, { "pipe", required_argument, NULL, 'U' }, @@ -161,21 +163,6 @@ } } -static void -run_lldb_commands (const lldb::DebuggerSP &debugger_sp, const std::vector &lldb_commands) -{ - for (const auto &lldb_command : lldb_commands) - { - printf("(lldb) %s\n", lldb_command.c_str ()); - - lldb_private::CommandReturnObject result; - debugger_sp->GetCommandInterpreter ().HandleCommand (lldb_command.c_str (), eLazyBoolNo, result); - const char *output = result.GetOutputData (); - if (output && output[0]) - puts (output); - } -} - static lldb::PlatformSP setup_platform (const std::string &platform_name) { @@ -554,13 +541,14 @@ argc--; argv++; int long_option_index = 0; - StreamSP log_stream_sp; Args log_args; Error error; int ch; std::string platform_name; std::string attach_target; std::string named_pipe_path; + std::string log_file; + StringRef log_channels; // e.g. "lldb process threads:gdb-remote default:linux all" int unnamed_pipe_fd = -1; bool reverse_connect = false; @@ -595,41 +583,12 @@ case 'l': // Set Log File if (optarg && optarg[0]) - { - if ((strcasecmp(optarg, "stdout") == 0) || (strcmp(optarg, "/dev/stdout") == 0)) - { - log_stream_sp.reset (new StreamFile (stdout, false)); - } - else if ((strcasecmp(optarg, "stderr") == 0) || (strcmp(optarg, "/dev/stderr") == 0)) - { - log_stream_sp.reset (new StreamFile (stderr, false)); - } - else - { - FILE *log_file = fopen(optarg, "w"); - if (log_file) - { - setlinebuf(log_file); - log_stream_sp.reset (new StreamFile (log_file, true)); - } - else - { - const char *errno_str = strerror(errno); - fprintf (stderr, "Failed to open log file '%s' for writing: errno = %i (%s)", optarg, errno, errno_str ? errno_str : "unknown error"); - } - - } - } - break; - - case 'f': // Log Flags - if (optarg && optarg[0]) - log_args.AppendArgument(optarg); + log_file.assign(optarg); break; - case 'c': // lldb commands + case 'c': // Log Channels if (optarg && optarg[0]) - lldb_commands.push_back(optarg); + log_channels = StringRef(optarg); break; case 'p': // platform name @@ -696,12 +655,29 @@ exit(option_error); } - if (log_stream_sp) - { - if (log_args.GetArgumentCount() == 0) - log_args.AppendArgument("default"); - ProcessGDBRemoteLog::EnableLog (log_stream_sp, 0,log_args.GetConstArgumentVector(), log_stream_sp.get()); + SmallVector channel_array; + log_channels.split(channel_array, ":"); + uint32_t log_options = 0; + for (auto channel_with_categories : channel_array) + { + StreamString error_stream; + Args channel_then_categories(channel_with_categories); + std::string channel(channel_then_categories.GetArgumentAtIndex(0)); + channel_then_categories.Shift (); // Shift off the channel + bool success = debugger_sp->EnableLog (channel.c_str(), + channel_then_categories.GetConstArgumentVector(), + log_file.c_str(), + log_options, + error_stream); + if (!success) + { + fprintf(stderr, "Unable to open log file '%s' for channel \"%s\"", + log_file.c_str(), + channel_with_categories.str().c_str()); + return -1; + } } + Log *log(lldb_private::GetLogIfAnyCategoriesSet (GDBR_LOG_VERBOSE)); if (log) { @@ -722,9 +698,6 @@ exit(255); } - // Run any commands requested. - run_lldb_commands (debugger_sp, lldb_commands); - // Setup the platform that GDBRemoteCommunicationServerLLGS will use. lldb::PlatformSP platform_sp = setup_platform (platform_name);