Index: .gitignore =================================================================== --- .gitignore +++ .gitignore @@ -40,7 +40,18 @@ clang-module-cache +# Skip ctags-style tags files +tags + # We should ignore Xcode-style embedding of llvm/ at lldb root dir. # Do not add trailing '/'s, they skip symlinks. /llvm /DerivedData + +# Ignore test trace directories. +20??-??-??-??_??_??/ + +# Ignore crashlog support files. +crashinfo.lock +crashinfo.so + Index: cmake/LLDBDependencies.cmake =================================================================== --- cmake/LLDBDependencies.cmake +++ cmake/LLDBDependencies.cmake @@ -47,6 +47,7 @@ lldbPluginObjectContainerMachOArchive lldbPluginObjectContainerBSDArchive lldbPluginPlatformMacOSX + lldbPluginStructuredDataDarwinLog lldbPluginDynamicLoaderMacOSXDYLD lldbPluginUnwindAssemblyInstEmulation lldbPluginUnwindAssemblyX86 Index: docs/structured_data/DarwinLog.md =================================================================== --- /dev/null +++ docs/structured_data/DarwinLog.md @@ -0,0 +1,160 @@ +# Change Notes + +## Summary + +This document describes the DarwinLog logging feature. + +## StructuredDataDarwinLog feature + +The DarwinLog feature supports logging os_log*() and NSLog() messages +to the command-line lldb console, as well as making those messages +available to LLDB clients via the event system. Starting with fall +2016 OSes, Apple platforms introduce a new fire-hose, stream-style +logging system where the bulk of the log processing happens on the log +consumer side. This reduces logging impact on the system when there +are no consumers, making it cheaper to include logging at all times. +However, it also increases the work needed on the consumer end when +log messages are desired. + +The debugserver binary has been modified to support collection of +os_log*()/NSLog() messages, selection of which messages appear in the +stream, and fine-grained filtering of what gets passed on to the LLDB +client. DarwinLog also tracks the activity chain (i.e. os_activity() +hierarchy) in effect at the time the log messages were issued. The +user is able to configure a number of aspects related to the +formatting of the log message header fields. + +The DarwinLog support is written in a way which should support the +lldb client side on non-Apple clients talking to an Apple device or +macOS system; hence, the plugin support is built into all LLDB +clients, not just those built on an Apple platform. + +StructuredDataDarwinLog implements the 'DarwinLog' feature type, and +the plugin name for it shows up as 'darwin-log'. + +The user interface to the darwin-log support is via the following: + +* 'plugin structured-data darwin-log enable' command + + This is the main entry point for enabling the command. It can be + set before launching a process or while the process is running. + If the user wants to squelch seeing info-level or debug-level + messages, which is the default behavior, then the enable command + must be made prior to launching the process; otherwise, the + info-level and debug-level messages will always show up. Also, + there is a similar "echo os_log()/NSLog() messages to target + process stderr" mechanism which is properly disabled when enabling + the DarwinLog support prior to launch. This cannot be squelched + if enabling DarwinLog after launch. + + See the help for this command. There are a number of options + to shrink or expand the number of messages that are processed + on the remote side and sent over to the client, and other + options to control the formatting of messages displayed. + + This command is sticky. Once enabled, it will stay enabled for + future process launches. + +* 'plugin structured-data darwin-log disable' command + + Executing this command disables os_log() capture in the currently + running process and signals LLDB to stop attempting to launch + new processes with DarwinLog support enabled. + +* 'settings set \ + plugin.structured-data.darwin-log.enable-on-startup' + + and + + 'settings set \ + plugin.structured-data.darwin-log.auto-enable-options -- {options}' + + When enable-on-startup is set to true, then LLDB will automatically + enable DarwinLog on startup of relevant processes. It will use the + content provided in the auto-enable-options settings as the + options to pass to the enable command. + + Note the '--' required after auto-enable-command. That is necessary + for raw commands like settings set. The '--' will not become part + of the options for the enable command. + +### Message flow and related performance considerations + +os_log()-style collection is not free. The more data that must be +processed, the slower it will be. There are several knobs available +to the developer to limit how much data goes through the pipe, and how +much data ultimately goes over the wire to the LLDB client. The +user's goal should be to ensure he or she only collects as many log +messages are needed, but no more. + +The flow of data looks like the following: + +1. Data comes into debugserver from the low-level OS facility that + receives log messages. The data that comes through this pipe can + be limited or expanded by the '--debug', '--info' and + '--all-processes' options of the 'plugin structured-data darwin-log + enable' command. options. Exclude as many categories as possible + here (also the default). The knobs here are very coarse - for + example, whether to include os_log_info()-level or + os_log_debug()-level info, or to include callstacks in the log + message event data. + +2. The debugserver process filters the messages that arrive through a + message log filter that may be fully customized by the user. It + works similar to a rules-based packet filter: a set of rules are + matched against the log message, each rule tried in sequential + order. The first rule that matches then either accepts or rejects + the message. If the log message does not match any rule, then the + message gets the no-match (i.e. fall-through) action. The no-match + action defaults to accepting but may be set to reject. + + Filters can be added via the enable command's '--filter + {filter-spec}' option. Filters are added in order, and multiple + --filter entries can be provided to the enable command. + + Filters take the following form: + + {action} {attribute} {op} + + {action} := + accept | + reject + + {attribute} := + category | // The log message category + subsystem | // The log message subsystem} + activity | // The child-most activity in force + // at the time the message was logged. + activity-chain | // The complete activity chain, specified + // as {parent-activity}:{child-activity}: + // {grandchild-activity} + message | // The fully expanded message contents. + // Note this one is expensive because it + // requires expanding the message. Avoid + // this if possible, or add it further + // down the filter chain. + + {op} := + match {exact-match-text} | + regex {search-regex} // uses C++ std::regex + // ECMAScript variant. + +e.g. + --filter "accept subsystem match com.example.mycompany.myproduct" + --filter "accept subsystem regex com.example.+" + --filter "reject category regex spammy-system-[[:digit:]]+" + +3. Messages that are accepted by the log message filter get sent to + the lldb client, where they are mapped to the + StructuredDataDarwinLog plugin. By default, command-line lldb will + issue a Process-level event containing the log message content, and + will request the plugin to print the message if the plugin is + enabled to do so. + +### Log message display + +Several settings control aspects of displaying log messages in +command-line LLDB. See the enable command's help for a description +of these. + + Index: docs/structured_data/StructuredDataPlugins.md =================================================================== --- /dev/null +++ docs/structured_data/StructuredDataPlugins.md @@ -0,0 +1,136 @@ +# Change Notes + +## Overview + +This document describes an infrastructural feature called Structured +Data plugins. See the DarwinLog.md doc for a description of one +such plugin that makes use of this feature. + +## StructuredDataPlugin + +StructuredDataPlugin instances have the following characteristics: + +* Each plugin instance is bound to a single Process instance. + +* Each StructuredData feature has a type name that identifies the + feature. For instance, the type name for the DarwinLog feature is + "DarwinLog". This feature type name is used in various places. + +* The process monitor reports the list of supported StructuredData + features advertised by the process monitor. Process goes through the + list of supported feature type names, and asks each known + StructuredDataPlugin if it can handle the feature. The first plugin + that supports the feature is mapped to that Process instance for + that feature. Plugins are only mapped when the process monitor + advertises that a feature is supported. + +* The feature may send asynchronous messages in StructuredData format + to the Process instance. Process instances route the asynchronous + structured data messages to the plugin mapped to that feature type, + if one exists. + +* Plugins can request that the Process instance forward on + configuration data to the process monitor if the plugin needs/wants + to configure the feature. Plugins may call the new Process method + + ```C++ + virtual Error + ConfigureStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP &config_sp) + ``` + + where type_name is the feature name and config_sp points to the + configuration structured data, which may be nullptr. + +* Plugins for features present in a process are notified when modules + are loaded into the Process instance via this StructuredDataPlugin + method: + + ```C++ + virtual void + ModulesDidLoad(Process &process, ModuleList &module_list); + ``` + +* Plugins may optionally broadcast their received structured data as + an LLDB process-level event via the following new Process call: + + ```C++ + void + BroadcastStructuredData(const StructuredData::ObjectSP &object_sp, + const lldb::StructuredDataPluginSP &plugin_sp); + ``` + + IDE clients might use this feature to receive information about the + process as it is running to monitor memory usage, CPU usage, and + logging. + + Internally, the event type created is an instance of + EventDataStructuredData. + +* In the case where a plugin chooses to broadcast a received + StructuredData event, the command-line LLDB Debugger instance + listens for them. The Debugger instance then gives the plugin an + opportunity to display info to either the debugger output or error + stream at a time that is safe to write to them. The plugin can + choose to display something appropriate regarding the structured + data that time. + +* Plugins can provide a ProcessLaunchInfo filter method when the + plugin is registered. If such a filter method is provided, then + when a process is about to be launched for debugging, the filter + callback is invoked, given both the launch info and the target. The + plugin may then alter the launch info if needed to better support + the feature of the plugin. + +* The plugin is entirely independent of the type of Process-derived + class that it is working with. The only requirements from the + process monitor are the following feature-agnostic elements: + + * Provide a way to discover features supported by the process + monitor for the current process. + + * Specify the list of supported feature type names to Process. + The process monitor does this by calling the following new + method on Process: + + ```C++ + void + MapSupportedStructuredDataPlugins(const StructuredData::Array + &supported_type_names) + ``` + + The supported_type_names specifies an array of string entries, + where each entry specifies the name of a StructuredData feature. + + * Provide a way to forward on configuration data for a feature type + to the process monitor. This is the manner by which LLDB can + configure a feature, perhaps based on settings or commands from + the user. The following virtual method on Process (described + earlier) does the job: + + ```C++ + virtual Error + ConfigureStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP &config_sp) + ``` + + * Listen for asynchronous structured data packets from the process + monitor, and forward them on to Process via this new Process + member method: + + ```C++ + bool + RouteAsyncStructuredData(const StructuredData::ObjectSP object_sp) + ``` + +* StructuredData producers must send their top-level data as a + Dictionary type, with a key called 'type' specifying a string value, + where the value is equal to the StructuredData feature/type name + previously advertised. Everything else about the content of the + dictionary is entirely up to the feature. + +* StructuredDataPlugin commands show up under 'plugin structured-data + plugin-name'. + +* StructuredDataPlugin settings show up under + 'plugin.structured-data.{plugin-name}. Index: include/lldb/API/LLDB.h =================================================================== --- include/lldb/API/LLDB.h +++ include/lldb/API/LLDB.h @@ -55,6 +55,7 @@ #include "lldb/API/SBSourceManager.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStringList.h" +#include "lldb/API/SBStructuredData.h" #include "lldb/API/SBSymbol.h" #include "lldb/API/SBSymbolContext.h" #include "lldb/API/SBSymbolContextList.h" Index: include/lldb/API/SBDefines.h =================================================================== --- include/lldb/API/SBDefines.h +++ include/lldb/API/SBDefines.h @@ -71,6 +71,7 @@ class LLDB_API SBSourceManager; class LLDB_API SBStream; class LLDB_API SBStringList; +class LLDB_API SBStructuredData; class LLDB_API SBSymbol; class LLDB_API SBSymbolContext; class LLDB_API SBSymbolContextList; Index: include/lldb/API/SBError.h =================================================================== --- include/lldb/API/SBError.h +++ include/lldb/API/SBError.h @@ -73,6 +73,7 @@ friend class SBHostOS; friend class SBPlatform; friend class SBProcess; + friend class SBStructuredData; friend class SBThread; friend class SBTarget; friend class SBValue; Index: include/lldb/API/SBProcess.h =================================================================== --- include/lldb/API/SBProcess.h +++ include/lldb/API/SBProcess.h @@ -32,7 +32,8 @@ eBroadcastBitInterrupt = (1 << 1), eBroadcastBitSTDOUT = (1 << 2), eBroadcastBitSTDERR = (1 << 3), - eBroadcastBitProfileData = (1 << 4) + eBroadcastBitProfileData = (1 << 4), + eBroadcastBitStructuredData = (1 << 5) }; SBProcess (); @@ -278,10 +279,16 @@ static bool GetInterruptedFromEvent (const lldb::SBEvent &event); - + + static lldb::SBStructuredData + GetStructuredDataFromEvent (const lldb::SBEvent &event); + static bool EventIsProcessEvent (const lldb::SBEvent &event); + static bool + EventIsStructuredDataEvent (const lldb::SBEvent &event); + lldb::SBBroadcaster GetBroadcaster () const; Index: include/lldb/API/SBStream.h =================================================================== --- include/lldb/API/SBStream.h +++ include/lldb/API/SBStream.h @@ -83,6 +83,7 @@ friend class SBProcess; friend class SBSection; friend class SBSourceManager; + friend class SBStructuredData; friend class SBSymbol; friend class SBSymbolContext; friend class SBSymbolContextList; Index: include/lldb/API/SBStructuredData.h =================================================================== --- /dev/null +++ include/lldb/API/SBStructuredData.h @@ -0,0 +1,52 @@ +//===-- SBStructuredData.h --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef SBStructuredData_h +#define SBStructuredData_h + +#include "lldb/API/SBDefines.h" +#include "lldb/API/SBModule.h" + +namespace lldb { + + class SBStructuredData + { + public: + + SBStructuredData(); + + SBStructuredData(const lldb::SBStructuredData &rhs); + + SBStructuredData(const lldb::EventSP &event_sp); + + ~SBStructuredData(); + + lldb::SBStructuredData & + operator =(const lldb::SBStructuredData &rhs); + + bool + IsValid() const; + + void + Clear(); + + lldb::SBError + GetAsJSON(lldb::SBStream &stream) const; + + lldb::SBError + PrettyPrint(lldb::SBStream &stream) const; + + private: + + class Impl; + std::unique_ptr m_impl_up; + }; +} + +#endif /* SBStructuredData_h */ Index: include/lldb/Core/Broadcaster.h =================================================================== --- include/lldb/Core/Broadcaster.h +++ include/lldb/Core/Broadcaster.h @@ -272,11 +272,10 @@ /// //---------------------------------------------------------- /// enum /// { -/// eBroadcastBitStateChanged = (1 << 0), -/// eBroadcastBitInterrupt = (1 << 1), -/// eBroadcastBitSTDOUT = (1 << 2), -/// eBroadcastBitSTDERR = (1 << 3), -/// eBroadcastBitProfileData = (1 << 4) +/// eBroadcastBitOne = (1 << 0), +/// eBroadcastBitTwo = (1 << 1), +/// eBroadcastBitThree = (1 << 2), +/// ... /// }; /// \endcode //---------------------------------------------------------------------- Index: include/lldb/Core/Event.h =================================================================== --- include/lldb/Core/Event.h +++ include/lldb/Core/Event.h @@ -20,6 +20,7 @@ // Project includes #include "lldb/lldb-private.h" #include "lldb/Core/ConstString.h" +#include "lldb/Core/StructuredData.h" #include "lldb/Host/Predicate.h" #include "lldb/Core/Broadcaster.h" @@ -159,6 +160,80 @@ }; //---------------------------------------------------------------------- +/// This class handles one or more StructuredData::Dictionary entries +/// that are raised for structured data events. +//---------------------------------------------------------------------- + +class EventDataStructuredData : public EventData +{ +public: + + //------------------------------------------------------------------ + // Constructors + //------------------------------------------------------------------ + EventDataStructuredData(); + + EventDataStructuredData(const lldb::ProcessSP &process_sp, + const StructuredData::ObjectSP &object_sp, + const lldb::StructuredDataPluginSP &plugin_sp); + + ~EventDataStructuredData() override; + + //------------------------------------------------------------------ + // Member functions + //------------------------------------------------------------------ + const ConstString & + GetFlavor() const override; + + void + Dump(Stream *s) const override; + + const lldb::ProcessSP& + GetProcess() const; + + const StructuredData::ObjectSP& + GetObject() const; + + const lldb::StructuredDataPluginSP& + GetStructuredDataPlugin() const; + + void + SetProcess(const lldb::ProcessSP &process_sp); + + void + SetObject(const StructuredData::ObjectSP &object_sp); + + void + SetStructuredDataPlugin(const lldb::StructuredDataPluginSP &plugin_sp); + + //------------------------------------------------------------------ + // Static functions + //------------------------------------------------------------------ + static const EventDataStructuredData* + GetEventDataFromEvent(const Event *event_ptr); + + static lldb::ProcessSP + GetProcessFromEvent(const Event *event_ptr); + + static StructuredData::ObjectSP + GetObjectFromEvent(const Event *event_ptr); + + static lldb::StructuredDataPluginSP + GetPluginFromEvent(const Event *event_ptr); + + static const ConstString & + GetFlavorString (); + +private: + + lldb::ProcessSP m_process_sp; + StructuredData::ObjectSP m_object_sp; + lldb::StructuredDataPluginSP m_plugin_sp; + + DISALLOW_COPY_AND_ASSIGN(EventDataStructuredData); +}; + +//---------------------------------------------------------------------- // lldb::Event //---------------------------------------------------------------------- class Event Index: include/lldb/Core/PluginManager.h =================================================================== --- include/lldb/Core/PluginManager.h +++ include/lldb/Core/PluginManager.h @@ -328,6 +328,65 @@ CommandInterpreter &interpreter); //------------------------------------------------------------------ + // StructuredDataPlugin + //------------------------------------------------------------------ + + //------------------------------------------------------------------ + /// Register a StructuredDataPlugin class along with optional + /// callbacks for debugger initialization and Process launch info + /// filtering and manipulation. + /// + /// @param[in] name + /// The name of the plugin. + /// + /// @param[in] description + /// A description string for the plugin. + /// + /// @param[in] create_callback + /// The callback that will be invoked to create an instance of + /// the callback. This may not be nullptr. + /// + /// @param[in] debugger_init_callback + /// An optional callback that will be made when a Debugger + /// instance is initialized. + /// + /// @param[in] filter_callback + /// An optional callback that will be invoked before LLDB + /// launches a process for debugging. The callback must + /// do the following: + /// 1. Only do something if the plugin's behavior is enabled. + /// 2. Only make changes for processes that are relevant to the + /// plugin. The callback gets a pointer to the Target, which + /// can be inspected as needed. The ProcessLaunchInfo is + /// provided in read-write mode, and may be modified by the + /// plugin if, for instance, additional environment variables + /// are needed to support the feature when enabled. + /// + /// @return + /// Returns true upon success; otherwise, false. + //------------------------------------------------------------------ + static bool + RegisterPlugin(const ConstString &name, + const char *description, + StructuredDataPluginCreateInstance create_callback, + DebuggerInitializeCallback debugger_init_callback = nullptr, + StructuredDataFilterLaunchInfo filter_callback + = nullptr); + + static bool + UnregisterPlugin(StructuredDataPluginCreateInstance create_callback); + + static StructuredDataPluginCreateInstance + GetStructuredDataPluginCreateCallbackAtIndex(uint32_t idx); + + static StructuredDataPluginCreateInstance + GetStructuredDataPluginCreateCallbackForPluginName(const ConstString &name); + + static StructuredDataFilterLaunchInfo + GetStructuredDataFilterCallbackAtIndex(uint32_t idx, + bool &iteration_complete); + + //------------------------------------------------------------------ // SymbolFile //------------------------------------------------------------------ static bool @@ -531,6 +590,16 @@ static bool CreateSettingForOperatingSystemPlugin(Debugger &debugger, const lldb::OptionValuePropertiesSP &properties_sp, const ConstString &description, bool is_global_property); + + static lldb::OptionValuePropertiesSP + GetSettingForStructuredDataPlugin(Debugger &debugger, + const ConstString &setting_name); + + static bool + CreateSettingForStructuredDataPlugin(Debugger &debugger, + const lldb::OptionValuePropertiesSP &properties_sp, + const ConstString &description, + bool is_global_property); }; } // namespace lldb_private Index: include/lldb/Interpreter/Args.h =================================================================== --- include/lldb/Interpreter/Args.h +++ include/lldb/Interpreter/Args.h @@ -451,6 +451,17 @@ LongestCommonPrefix (std::string &common_prefix); //------------------------------------------------------------------ + /// Add or replace an environment variable with the given value. + /// + /// This command adds the environment variable if it is not already + /// present using the given value. If the environment variable is + /// already in the list, it replaces the first such occurrence + /// with the new value. + //------------------------------------------------------------------ + void + AddOrReplaceEnvironmentVariable(const char *env_var_name, + const char *new_value); + /// Return whether a given environment variable exists. /// /// This command treats Args like a list of environment variables, @@ -460,12 +471,18 @@ /// @param[in] env_var_name /// Specifies the name of the environment variable to check. /// + /// @param[out] argument_index + /// If non-null, then when the environment variable is found, + /// the index of the argument position will be returned in + /// the size_t pointed to by this argument. + /// /// @return /// true if the specified env var name exists in the list in /// either of the above-mentioned formats; otherwise, false. //------------------------------------------------------------------ bool - ContainsEnvironmentVariable(const char *env_var_name) const; + ContainsEnvironmentVariable(const char *env_var_name, + size_t *argument_index = nullptr) const; protected: //------------------------------------------------------------------ Index: include/lldb/Target/Process.h =================================================================== --- include/lldb/Target/Process.h +++ include/lldb/Target/Process.h @@ -760,7 +760,8 @@ eBroadcastBitInterrupt = (1 << 1), eBroadcastBitSTDOUT = (1 << 2), eBroadcastBitSTDERR = (1 << 3), - eBroadcastBitProfileData = (1 << 4) + eBroadcastBitProfileData = (1 << 4), + eBroadcastBitStructuredData = (1 << 5), }; enum @@ -3254,6 +3255,71 @@ AdvanceAddressToNextBranchInstruction (Address default_stop_addr, AddressRange range_bounds); + //------------------------------------------------------------------ + /// Configure asynchronous structured data feature. + /// + /// Each Process type that supports using an asynchronous StructuredData + /// feature should implement this to enable/disable/configure the feature. + /// The default implementation here will always return an error indiciating + /// the feature is unsupported. + /// + /// StructuredDataPlugin implementations will call this to configure + /// a feature that has been reported as being supported. + /// + /// @param[in] type_name + /// The StructuredData type name as previously discovered by + /// the Process-derived instance. + /// + /// @param[in] config + /// Configuration data for the feature being enabled. This config + /// data, which may be null, will be passed along to the feature + /// to process. The feature will dictate whether this is a dictionary, + /// an array or some other object. If the feature needs to be + /// set up properly before it can be enabled, then the config should + /// also take an enable/disable flag. + /// + /// @return + /// Returns the result of attempting to configure the feature. + //------------------------------------------------------------------ + virtual Error + ConfigureStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP &config_sp); + + //------------------------------------------------------------------ + /// Broadcasts the given structured data object from the given + /// plugin. + /// + /// StructuredDataPlugin instances can use this to optionally + /// broadcast any of their data if they want to make it available + /// for clients. The data will come in on the structured data + /// event bit (eBroadcastBitStructuredData). + /// + /// @param[in] object_sp + /// The structured data object to broadcast. + /// + /// @param[in] plugin_sp + /// The plugin that will be reported in the event's plugin + /// parameter. + //------------------------------------------------------------------ + void + BroadcastStructuredData(const StructuredData::ObjectSP &object_sp, + const lldb::StructuredDataPluginSP &plugin_sp); + + //------------------------------------------------------------------ + /// Returns the StructuredDataPlugin associated with a given type + /// name, if there is one. + /// + /// There will only be a plugin for a given StructuredDataType if the + /// debugged process monitor claims that the feature is supported. + /// This is one way to tell whether a feature is available. + /// + /// @return + /// The plugin if one is available for the specified feature; + /// otherwise, returns an empty shared pointer. + //------------------------------------------------------------------ + lldb::StructuredDataPluginSP + GetStructuredDataPlugin(const ConstString &type_name) const; + protected: void SetState (lldb::EventSP &event_sp); @@ -3392,6 +3458,57 @@ } //------------------------------------------------------------------ + /// Loads any plugins associated with asynchronous structured data + /// and maps the relevant supported type name to the plugin. + /// + /// Processes can receive asynchronous structured data from the + /// process monitor. This method will load and map any structured + /// data plugins that support the given set of supported type names. + /// Later, if any of these features are enabled, the process monitor + /// is free to generate asynchronous structured data. The data must + /// come in as a single \b StructuredData::Dictionary. That dictionary + /// must have a string field named 'type', with a value that equals + /// the relevant type name string (one of the values in + /// \b supported_type_names). + /// + /// @param[in] supported_type_names + /// An array of zero or more type names. Each must be unique. + /// For each entry in the list, a StructuredDataPlugin will be + /// searched for that supports the structured data type name. + //------------------------------------------------------------------ + void + MapSupportedStructuredDataPlugins(const StructuredData::Array + &supported_type_names); + + //------------------------------------------------------------------ + /// Route the incoming structured data dictionary to the right plugin. + /// + /// The incoming structured data must be a dictionary, and it must + /// have a key named 'type' that stores a string value. The string + /// value must be the name of the structured data feature that + /// knows how to handle it. + /// + /// @param[in] object_sp + /// When non-null and pointing to a dictionary, the 'type' + /// key's string value is used to look up the plugin that + /// was registered for that structured data type. It then + /// calls the following method on the StructuredDataPlugin + /// instance: + /// + /// virtual void + /// HandleArrivalOfStructuredData(Process &process, + /// const ConstString &type_name, + /// const StructuredData::ObjectSP + /// &object_sp) + /// + /// @return + /// True if the structured data was routed to a plugin; otherwise, + /// false. + //------------------------------------------------------------------ + bool + RouteAsyncStructuredData(const StructuredData::ObjectSP object_sp); + + //------------------------------------------------------------------ // Type definitions //------------------------------------------------------------------ typedef std::map LanguageRuntimeCollection; @@ -3408,6 +3525,9 @@ { } }; + + using StructuredDataPluginMap = std::map; //------------------------------------------------------------------ // Member variables @@ -3477,7 +3597,9 @@ bool m_can_interpret_function_calls; // Some targets, e.g the OSX kernel, don't support the ability to modify the stack. WarningsCollection m_warnings_issued; // A set of object pointers which have already had warnings printed std::mutex m_run_thread_plan_lock; - + StructuredDataPluginMap m_structured_data_plugin_map; + + enum { eCanJITDontKnow= 0, eCanJITYes, @@ -3562,7 +3684,7 @@ void BroadcastAsyncProfileData(const std::string &one_profile_data); - + static void STDIOReadThreadBytesReceived (void *baton, const void *src, size_t src_len); Index: include/lldb/Target/StructuredDataPlugin.h =================================================================== --- /dev/null +++ include/lldb/Target/StructuredDataPlugin.h @@ -0,0 +1,223 @@ +//===-- StructuredDataPlugin.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef StructuredDataPlugin_h +#define StructuredDataPlugin_h + +#include "lldb/Core/PluginInterface.h" +#include "lldb/Core/StructuredData.h" + +namespace lldb_private +{ + +class CommandObjectMultiword; + +// ----------------------------------------------------------------------------- +/// Plugin that supports process-related structured data sent asynchronously +/// from the debug monitor (e.g. debugserver, lldb-server, etc.) +/// +/// This plugin type is activated by a Process-derived instance when that +/// instance detects that a given structured data feature is available. +/// +/// StructuredDataPlugin instances are inherently tied to a process. The +/// main functionality they support is the ability to consume asynchronously- +/// delivered structured data from the process monitor, and do something +/// reasonable with it. Something reasonable can include broadcasting a +/// StructuredData event, which other parts of the system can then do with +/// as they please. An IDE could use this facility to retrieve CPU usage, +/// memory usage, and other run-time aspects of the process. That data +/// can then be displayed meaningfully to the user through the IDE. + +/// For command-line LLDB, the Debugger instance listens for the structured +/// data events raised by the plugin, and give the plugin both the output +/// and error streams such that the plugin can display something about the +/// event, at a time when the debugger ensures it is safe to write to the +/// output or error streams. +// ----------------------------------------------------------------------------- + +class StructuredDataPlugin : + public PluginInterface, + public std::enable_shared_from_this +{ +public: + + virtual ~StructuredDataPlugin(); + + lldb::ProcessSP + GetProcess() const; + + // ------------------------------------------------------------------------- + // Public instance API + // ------------------------------------------------------------------------- + + // ------------------------------------------------------------------------- + /// Return whether this plugin supports the given StructuredData feature. + /// + /// When Process is informed of a list of process-monitor-supported + /// structured data features, Process will go through the list of plugins, + /// one at a time, and have the first plugin that supports a given feature + /// be the plugin instantiated to handle that feature. There is a 1-1 + /// correspondence between a Process instance and a StructuredDataPlugin + /// mapped to that process. A plugin can support handling multiple + /// features, and if that happens, there is a single plugin instance + /// created covering all of the mapped features for a given process. + /// + /// @param[in] type_name + /// The name of the feature tag supported by a process. + /// e.g. "darwin-log". + /// + /// @return + /// true if the plugin supports the feature; otherwise, false. + // ------------------------------------------------------------------------- + virtual bool + SupportsStructuredDataType(const ConstString &type_name) = 0; + + // ------------------------------------------------------------------------- + /// Handle the arrival of asynchronous structured data from the process. + /// + /// When asynchronous structured data arrives from the process monitor, + /// it is immediately delivered to the plugin mapped for that feature + /// if one exists. The structured data that arrives from a process + /// monitor must be a dictionary, and it must have a string field named + /// "type" that must contain the StructuredData feature name set as the + /// value. This is the manner in which the data is routed to the proper + /// plugin instance. + /// + /// @param[in] process + /// The process instance that just received the structured data. + /// This will always be the same process for a given instance of + /// a plugin. + /// + /// @param[in] type_name + /// The name of the feature tag for the asynchronous structured data. + /// Note this data will also be present in the \b object_sp dictionary + /// under the string value with key "type". + /// + /// @param[in] object_sp + /// A shared pointer to the structured data that arrived. This must + /// be a dictionary. The only key required is the aforementioned + /// key named "type" that must be a string value containing the + /// structured data type name. + // ------------------------------------------------------------------------- + virtual void + HandleArrivalOfStructuredData(Process &process, + const ConstString &type_name, + const StructuredData::ObjectSP + &object_sp) = 0; + + + // ------------------------------------------------------------------------- + /// Return whether the plugin is configured to display its structured data. + /// + /// For command-line LLDB, the Debugger instance will call this method when + /// it receives a structured data event for this plugin. If this method + /// returns true, then the \b PrettyPrintData() method will be called + /// and it will be output to the LLDB console. + /// + /// The default implementation always returns true. + /// + /// @param[in] object_sp + /// A shared pointer to the structured data that arrived. + /// + /// @return + /// True if the given data should be printed to output; otherwise, + /// false. + // ------------------------------------------------------------------------- + virtual bool + ShouldPrint(const StructuredData::ObjectSP &object_sp) const; + + // ------------------------------------------------------------------------- + /// Pretty-print the contents of the structured data to the given stream. + /// + /// In command-line LLDB, this method will be called by the Debugger + /// instance for each structured data event generated, and the output + /// will be printed to the LLDB console. + /// + /// SBAPI clients may call this method + /// + /// @param[in] object_sp + /// A shared pointer to the structured data to format. + /// + /// @param[in] stream + /// The stream where the structured data should be pretty printed. + /// + /// @return + /// The error if pretty printing failed; otherwise, success. + // ------------------------------------------------------------------------- + virtual Error + PrettyPrintData(const StructuredData::ObjectSP &object_sp, + lldb_private::Stream &stream) = 0; + + // ------------------------------------------------------------------------- + /// Returns whether the plugin's features are enabled. + /// + /// This is a convenience method for plugins that can enable or disable + /// their functionality. It allows retrieval of this state without + /// requiring a cast. + /// + /// @param[in] type_name + /// The name of the feature tag for the asynchronous structured data. + /// This is needed for plugins that support more than one feature. + // ------------------------------------------------------------------------- + virtual bool + GetEnabled(const ConstString &type_name) const; + + // ------------------------------------------------------------------------- + /// Allow the plugin to do work related to modules that loaded in the + /// the corresponding process. + /// + /// This method defaults to doing nothing. Plugins can override it + /// if they have any behavior they want to enable/modify based on loaded + /// modules. + /// + /// @param[in] process + /// The process that just was notified of modules having been loaded. + /// This will always be the same process for a given instance of + /// a plugin. + /// + /// @param[in] module_list + /// The list of modules that the process registered as having just + /// loaded. See \b Process::ModulesDidLoad(...). + // ------------------------------------------------------------------------- + virtual void + ModulesDidLoad(Process &process, ModuleList &module_list); + +protected: + + // ------------------------------------------------------------------------- + // Derived-class API + // ------------------------------------------------------------------------- + StructuredDataPlugin(const lldb::ProcessWP &process_wp); + + // ------------------------------------------------------------------------- + /// Derived classes must call this before attempting to hook up commands + /// to the 'plugin structured-data' tree. + /// + /// This ensures the relevant command and options hook points for all + /// StructuredDataPlugin derived classes are available for this debugger. + /// If this has already happened, this call is a no-op. + /// + /// @param[in] debugger + /// The Debugger instance for which we're creating the required shared + /// components for the StructuredDataPlugin derived classes. + // ------------------------------------------------------------------------- + static void + InitializeBasePluginForDebugger(Debugger &debugger); + +private: + + lldb::ProcessWP m_process_wp; + + DISALLOW_COPY_AND_ASSIGN(StructuredDataPlugin); + +}; + +} + +#endif Index: include/lldb/Target/ThreadPlanCallOnFunctionExit.h =================================================================== --- /dev/null +++ include/lldb/Target/ThreadPlanCallOnFunctionExit.h @@ -0,0 +1,69 @@ +//===-- ThreadPlanCallOnFunctionExit.h --------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef ThreadPlanCallOnFunctionExit_h +#define ThreadPlanCallOnFunctionExit_h + +#include "lldb/Target/ThreadPlan.h" + +#include + +namespace lldb_private { + +// ============================================================================= +/// This thread plan calls a function object when the current function exits. +// ============================================================================= + +class ThreadPlanCallOnFunctionExit : public ThreadPlan +{ +public: + + /// Definition for the callback made when the currently executing thread + /// finishes executing its function. + using Callback = std::function; + + ThreadPlanCallOnFunctionExit(Thread &thread, const Callback &callback); + + void + DidPush() override; + + // ------------------------------------------------------------------------- + // ThreadPlan API + // ------------------------------------------------------------------------- + + void + GetDescription (Stream *s, lldb::DescriptionLevel level) override; + + bool + ValidatePlan (Stream *error) override; + + bool + ShouldStop (Event *event_ptr) override; + + bool + WillStop () override; + +protected: + + bool + DoPlanExplainsStop (Event *event_ptr) override; + + lldb::StateType + GetPlanRunState () override; + +private: + + Callback m_callback; + lldb::ThreadPlanSP m_step_out_threadplan_sp; + +}; + +} + +#endif /* ThreadPlanCallOnFunctionExit_h */ Index: include/lldb/lldb-forward.h =================================================================== --- include/lldb/lldb-forward.h +++ include/lldb/lldb-forward.h @@ -91,6 +91,7 @@ class EvaluateExpressionOptions; class Event; class EventData; +class EventDataStructuredData; class ExecutionContext; class ExecutionContextRef; class ExecutionContextRefLocker; @@ -215,6 +216,7 @@ class StreamString; class StringList; struct StringSummaryFormat; +class StructuredDataPlugin; class SystemRuntime; class TypeSummaryImpl; class TypeSummaryOptions; @@ -333,6 +335,7 @@ typedef std::unique_ptr DynamicLoaderUP; typedef std::shared_ptr EventSP; typedef std::shared_ptr EventDataSP; + typedef std::shared_ptr EventDataStructuredDataSP; typedef std::shared_ptr ExecutionContextRefSP; typedef std::shared_ptr ExpressionVariableSP; typedef std::shared_ptr FileSP; @@ -415,6 +418,10 @@ typedef std::weak_ptr StreamWP; typedef std::shared_ptr StreamFileSP; typedef std::shared_ptr StringTypeSummaryImplSP; + typedef std::shared_ptr + StructuredDataPluginSP; + typedef std::weak_ptr + StructuredDataPluginWP; typedef std::shared_ptr SymbolFileSP; typedef std::shared_ptr SymbolFileTypeSP; typedef std::weak_ptr SymbolFileTypeWP; Index: include/lldb/lldb-private-interfaces.h =================================================================== --- include/lldb/lldb-private-interfaces.h +++ include/lldb/lldb-private-interfaces.h @@ -35,6 +35,9 @@ typedef Language *(*LanguageCreateInstance) (lldb::LanguageType language); typedef LanguageRuntime *(*LanguageRuntimeCreateInstance) (Process *process, lldb::LanguageType language); typedef lldb::CommandObjectSP (*LanguageRuntimeGetCommandObject) (CommandInterpreter& interpreter); + typedef lldb::StructuredDataPluginSP (*StructuredDataPluginCreateInstance) + (Process &process); + typedef Error (*StructuredDataFilterLaunchInfo)(ProcessLaunchInfo &launch_info, Target *target); typedef SystemRuntime *(*SystemRuntimeCreateInstance) (Process *process); typedef lldb::PlatformSP (*PlatformCreateInstance) (bool force, const ArchSpec *arch); typedef lldb::ProcessSP (*ProcessCreateInstance) (lldb::TargetSP target_sp, lldb::ListenerSP listener_sp, const FileSpec *crash_file_path); Index: lldb.xcodeproj/project.pbxproj =================================================================== --- lldb.xcodeproj/project.pbxproj +++ lldb.xcodeproj/project.pbxproj @@ -57,6 +57,8 @@ 23059A0719532B96007B8189 /* LinuxSignals.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23059A0519532B96007B8189 /* LinuxSignals.cpp */; }; 23059A101958B319007B8189 /* SBUnixSignals.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23059A0F1958B319007B8189 /* SBUnixSignals.cpp */; }; 23059A121958B3B2007B8189 /* SBUnixSignals.h in Headers */ = {isa = PBXBuildFile; fileRef = 23059A111958B37B007B8189 /* SBUnixSignals.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 230EC4591D63C3A7008DF59F /* CMakeLists.txt in CopyFiles */ = {isa = PBXBuildFile; fileRef = 230EC4571D63C3A7008DF59F /* CMakeLists.txt */; }; + 230EC45B1D63C3BA008DF59F /* ThreadPlanCallOnFunctionExit.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 230EC4581D63C3A7008DF59F /* ThreadPlanCallOnFunctionExit.cpp */; }; 2326CF441BDD643700A5CEAC /* liblldb-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2689FFCA13353D7A00698AC0 /* liblldb-core.a */; }; 2326CF491BDD67D800A5CEAC /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2326CF471BDD67C100A5CEAC /* libncurses.dylib */; }; 2326CF4B1BDD681800A5CEAC /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 2326CF4A1BDD681800A5CEAC /* libz.dylib */; }; @@ -72,9 +74,15 @@ 233B007F1960CB280090E598 /* ProcessLaunchInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 233B007E1960CB280090E598 /* ProcessLaunchInfo.cpp */; }; 236124A41986B4E2004EFC37 /* IOObject.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 236124A21986B4E2004EFC37 /* IOObject.cpp */; }; 236124A51986B4E2004EFC37 /* Socket.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 236124A31986B4E2004EFC37 /* Socket.cpp */; }; + 2374D7461D4BAA1D005C9575 /* CMakeLists.txt in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2374D7431D4BAA1D005C9575 /* CMakeLists.txt */; }; 2374D7521D4BB299005C9575 /* GDBRemoteClientBase.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 2374D74F1D4BB299005C9575 /* GDBRemoteClientBase.h */; }; 2374D7531D4BB2FF005C9575 /* GDBRemoteClientBase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2374D74E1D4BB299005C9575 /* GDBRemoteClientBase.cpp */; }; 2377C2F819E613C100737875 /* PipePosix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2377C2F719E613C100737875 /* PipePosix.cpp */; }; + 238F2B9E1D2C82D0001FF92A /* StructuredDataPlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 238F2B9D1D2C82D0001FF92A /* StructuredDataPlugin.cpp */; }; + 238F2BA11D2C835A001FF92A /* StructuredDataPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 238F2B9F1D2C835A001FF92A /* StructuredDataPlugin.h */; }; + 238F2BA21D2C835A001FF92A /* SystemRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 238F2BA01D2C835A001FF92A /* SystemRuntime.h */; }; + 238F2BA81D2C85FA001FF92A /* StructuredDataDarwinLog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 238F2BA61D2C85FA001FF92A /* StructuredDataDarwinLog.cpp */; }; + 238F2BA91D2C85FA001FF92A /* StructuredDataDarwinLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 238F2BA71D2C85FA001FF92A /* StructuredDataDarwinLog.h */; }; 239481861C59EBDD00DF7168 /* libncurses.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 239481851C59EBDD00DF7168 /* libncurses.dylib */; }; 239504DE1BDD453200963CEA /* SocketAddressTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2321F9391BDD332400BA9A93 /* SocketAddressTest.cpp */; }; 239504DF1BDD453200963CEA /* SocketTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2321F93A1BDD332400BA9A93 /* SocketTest.cpp */; }; @@ -93,6 +101,11 @@ 23D065911D4A7BEE0008EDE6 /* RenderScriptx86ABIFixups.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23D065861D4A7BDA0008EDE6 /* RenderScriptx86ABIFixups.cpp */; }; 23D4007D1C2101F2000C3885 /* DWARFDebugMacro.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23E77CD61C20F29F007192AD /* DWARFDebugMacro.cpp */; }; 23D4007E1C210201000C3885 /* DebugMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23E77CDB1C20F2F2007192AD /* DebugMacros.cpp */; }; + 23DCBE9B1D63E14B0084C36B /* SBLanguageRuntime.i in CopyFiles */ = {isa = PBXBuildFile; fileRef = 23DCBE971D63E14B0084C36B /* SBLanguageRuntime.i */; }; + 23DCBE9D1D63E14B0084C36B /* SBTypeEnumMember.i in CopyFiles */ = {isa = PBXBuildFile; fileRef = 23DCBE991D63E14B0084C36B /* SBTypeEnumMember.i */; }; + 23DCBE9E1D63E14B0084C36B /* SBUnixSignals.i in CopyFiles */ = {isa = PBXBuildFile; fileRef = 23DCBE9A1D63E14B0084C36B /* SBUnixSignals.i */; }; + 23DCBEA21D63E7190084C36B /* SBStructuredData.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23DCBEA01D63E6440084C36B /* SBStructuredData.cpp */; }; + 23DCBEA31D63E71F0084C36B /* SBStructuredData.h in Headers */ = {isa = PBXBuildFile; fileRef = 23DCBE9F1D63E3800084C36B /* SBStructuredData.h */; settings = {ATTRIBUTES = (Public, ); }; }; 23DCEA461D1C4D0F00A602B4 /* SBMemoryRegionInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23DCEA421D1C4C6900A602B4 /* SBMemoryRegionInfo.cpp */; }; 23DCEA471D1C4D0F00A602B4 /* SBMemoryRegionInfoList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23DCEA431D1C4C6900A602B4 /* SBMemoryRegionInfoList.cpp */; }; 23DDF226196C3EE600BB8417 /* CommandOptionValidators.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23DDF224196C3EE600BB8417 /* CommandOptionValidators.cpp */; }; @@ -1151,9 +1164,14 @@ 2374D7521D4BB299005C9575 /* GDBRemoteClientBase.h in CopyFiles */, 23D0658C1D4A7BDA0008EDE6 /* RenderScriptRuntime.h in CopyFiles */, 23D0658A1D4A7BDA0008EDE6 /* RenderScriptExpressionOpts.h in CopyFiles */, + 230EC4591D63C3A7008DF59F /* CMakeLists.txt in CopyFiles */, 49DEF1221CD7BD90006A7C7D /* BlockPointer.h in CopyFiles */, + 2374D7461D4BAA1D005C9575 /* CMakeLists.txt in CopyFiles */, 4CC7C6531D5299140076FF94 /* DWARFASTParserOCaml.h in CopyFiles */, + 23DCBE9D1D63E14B0084C36B /* SBTypeEnumMember.i in CopyFiles */, AF90106515AB7D3600FF120D /* lldb.1 in CopyFiles */, + 23DCBE9B1D63E14B0084C36B /* SBLanguageRuntime.i in CopyFiles */, + 23DCBE9E1D63E14B0084C36B /* SBUnixSignals.i in CopyFiles */, 23D065881D4A7BDA0008EDE6 /* CMakeLists.txt in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 1; @@ -1181,6 +1199,8 @@ 23059A0619532B96007B8189 /* LinuxSignals.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LinuxSignals.h; path = Utility/LinuxSignals.h; sourceTree = ""; }; 23059A0F1958B319007B8189 /* SBUnixSignals.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBUnixSignals.cpp; path = source/API/SBUnixSignals.cpp; sourceTree = ""; }; 23059A111958B37B007B8189 /* SBUnixSignals.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SBUnixSignals.h; path = include/lldb/API/SBUnixSignals.h; sourceTree = ""; }; + 230EC4571D63C3A7008DF59F /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = CMakeLists.txt; path = source/Target/CMakeLists.txt; sourceTree = ""; }; + 230EC4581D63C3A7008DF59F /* ThreadPlanCallOnFunctionExit.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ThreadPlanCallOnFunctionExit.cpp; path = source/Target/ThreadPlanCallOnFunctionExit.cpp; sourceTree = ""; }; 23173F8B192BA93F005C708F /* lldb-x86-register-enums.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "lldb-x86-register-enums.h"; path = "Utility/lldb-x86-register-enums.h"; sourceTree = ""; }; 2321F9381BDD332400BA9A93 /* CMakeLists.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 2321F9391BDD332400BA9A93 /* SocketAddressTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = SocketAddressTest.cpp; sourceTree = ""; }; @@ -1221,10 +1241,16 @@ 236124A31986B4E2004EFC37 /* Socket.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Socket.cpp; sourceTree = ""; }; 236124A61986B50E004EFC37 /* IOObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = IOObject.h; path = include/lldb/Host/IOObject.h; sourceTree = ""; }; 236124A71986B50E004EFC37 /* Socket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Socket.h; path = include/lldb/Host/Socket.h; sourceTree = ""; }; + 2374D7431D4BAA1D005C9575 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; 2374D74E1D4BB299005C9575 /* GDBRemoteClientBase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = GDBRemoteClientBase.cpp; sourceTree = ""; }; 2374D74F1D4BB299005C9575 /* GDBRemoteClientBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GDBRemoteClientBase.h; sourceTree = ""; }; 2377C2F719E613C100737875 /* PipePosix.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PipePosix.cpp; sourceTree = ""; }; 237C577A19AF9D9F00213D59 /* HostInfoLinux.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = HostInfoLinux.h; path = include/lldb/Host/linux/HostInfoLinux.h; sourceTree = SOURCE_ROOT; }; + 238F2B9D1D2C82D0001FF92A /* StructuredDataPlugin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StructuredDataPlugin.cpp; path = source/Target/StructuredDataPlugin.cpp; sourceTree = ""; }; + 238F2B9F1D2C835A001FF92A /* StructuredDataPlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = StructuredDataPlugin.h; path = include/lldb/Target/StructuredDataPlugin.h; sourceTree = ""; }; + 238F2BA01D2C835A001FF92A /* SystemRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SystemRuntime.h; path = include/lldb/Target/SystemRuntime.h; sourceTree = ""; }; + 238F2BA61D2C85FA001FF92A /* StructuredDataDarwinLog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = StructuredDataDarwinLog.cpp; sourceTree = ""; }; + 238F2BA71D2C85FA001FF92A /* StructuredDataDarwinLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StructuredDataDarwinLog.h; sourceTree = ""; }; 239481851C59EBDD00DF7168 /* libncurses.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libncurses.dylib; path = ../../../../../usr/lib/libncurses.dylib; sourceTree = ""; }; 239504C21BDD3FD600963CEA /* gtest_common.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = gtest_common.h; sourceTree = ""; }; 239504C61BDD3FF300963CEA /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; @@ -1242,6 +1268,12 @@ 23D065851D4A7BDA0008EDE6 /* RenderScriptRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderScriptRuntime.h; sourceTree = ""; }; 23D065861D4A7BDA0008EDE6 /* RenderScriptx86ABIFixups.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RenderScriptx86ABIFixups.cpp; sourceTree = ""; }; 23D065871D4A7BDA0008EDE6 /* RenderScriptx86ABIFixups.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RenderScriptx86ABIFixups.h; sourceTree = ""; }; + 23DCBE971D63E14B0084C36B /* SBLanguageRuntime.i */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c.preprocessed; path = SBLanguageRuntime.i; sourceTree = ""; }; + 23DCBE981D63E14B0084C36B /* SBStructuredData.i */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c.preprocessed; path = SBStructuredData.i; sourceTree = ""; }; + 23DCBE991D63E14B0084C36B /* SBTypeEnumMember.i */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c.preprocessed; path = SBTypeEnumMember.i; sourceTree = ""; }; + 23DCBE9A1D63E14B0084C36B /* SBUnixSignals.i */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c.preprocessed; path = SBUnixSignals.i; sourceTree = ""; }; + 23DCBE9F1D63E3800084C36B /* SBStructuredData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SBStructuredData.h; path = include/lldb/API/SBStructuredData.h; sourceTree = ""; }; + 23DCBEA01D63E6440084C36B /* SBStructuredData.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBStructuredData.cpp; path = source/API/SBStructuredData.cpp; sourceTree = ""; }; 23DCEA421D1C4C6900A602B4 /* SBMemoryRegionInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBMemoryRegionInfo.cpp; path = source/API/SBMemoryRegionInfo.cpp; sourceTree = ""; }; 23DCEA431D1C4C6900A602B4 /* SBMemoryRegionInfoList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = SBMemoryRegionInfoList.cpp; path = source/API/SBMemoryRegionInfoList.cpp; sourceTree = ""; }; 23DDF224196C3EE600BB8417 /* CommandOptionValidators.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = CommandOptionValidators.cpp; path = source/Interpreter/CommandOptionValidators.cpp; sourceTree = ""; }; @@ -3235,6 +3267,23 @@ path = platforms; sourceTree = ""; }; + 238F2BA41D2C858F001FF92A /* StructuredData */ = { + isa = PBXGroup; + children = ( + 238F2BA51D2C85B2001FF92A /* DarwinLog */, + ); + path = StructuredData; + sourceTree = ""; + }; + 238F2BA51D2C85B2001FF92A /* DarwinLog */ = { + isa = PBXGroup; + children = ( + 238F2BA71D2C85FA001FF92A /* StructuredDataDarwinLog.h */, + 238F2BA61D2C85FA001FF92A /* StructuredDataDarwinLog.cpp */, + ); + path = DarwinLog; + sourceTree = ""; + }; 23AB0526199FF5D3003B8084 /* FreeBSD */ = { isa = PBXGroup; children = ( @@ -3279,6 +3328,7 @@ 26C5577E132575B6008FD8FE /* Platform */, 260C898A10F57C5600BB2B04 /* Process */, 3FBA69DA1B6066D20008F44A /* ScriptInterpreter */, + 238F2BA41D2C858F001FF92A /* StructuredData */, AF11CB34182CA85A00D9B618 /* SystemRuntime */, 260C89B110F57C5600BB2B04 /* SymbolFile */, 260C89E010F57C5600BB2B04 /* SymbolVendor */, @@ -3508,9 +3558,8 @@ 2611FEEE142D83060017FEA3 /* interface */ = { isa = PBXGroup; children = ( - 254FBBA61A91672800BD6378 /* SBAttachInfo.i */, - 254FBB921A81AA5200BD6378 /* SBLaunchInfo.i */, 2611FEEF142D83060017FEA3 /* SBAddress.i */, + 254FBBA61A91672800BD6378 /* SBAttachInfo.i */, 2611FEF0142D83060017FEA3 /* SBBlock.i */, 2611FEF1142D83060017FEA3 /* SBBreakpoint.i */, 2611FEF2142D83060017FEA3 /* SBBreakpointLocation.i */, @@ -3533,6 +3582,8 @@ 2611FF00142D83060017FEA3 /* SBHostOS.i */, 2611FF02142D83060017FEA3 /* SBInstruction.i */, 2611FF03142D83060017FEA3 /* SBInstructionList.i */, + 23DCBE971D63E14B0084C36B /* SBLanguageRuntime.i */, + 254FBB921A81AA5200BD6378 /* SBLaunchInfo.i */, 2611FF04142D83060017FEA3 /* SBLineEntry.i */, 2611FF05142D83060017FEA3 /* SBListener.i */, 264297591D1DF2AA003F2BF4 /* SBMemoryRegionInfo.i */, @@ -3547,6 +3598,7 @@ 2611FF09142D83060017FEA3 /* SBSourceManager.i */, 2611FF0A142D83060017FEA3 /* SBStream.i */, 2611FF0B142D83060017FEA3 /* SBStringList.i */, + 23DCBE981D63E14B0084C36B /* SBStructuredData.i */, 2611FF0C142D83060017FEA3 /* SBSymbol.i */, 2611FF0D142D83060017FEA3 /* SBSymbolContext.i */, 2611FF0E142D83060017FEA3 /* SBSymbolContextList.i */, @@ -3556,11 +3608,13 @@ 8CCB018419BA54930009FD44 /* SBThreadCollection.i */, 2611FF11142D83060017FEA3 /* SBType.i */, 9475C18A14E5EA1C001BFC6D /* SBTypeCategory.i */, + 23DCBE991D63E14B0084C36B /* SBTypeEnumMember.i */, 9461569214E3567F003A195C /* SBTypeFilter.i */, 9461569314E3567F003A195C /* SBTypeFormat.i */, 9475C18B14E5F818001BFC6D /* SBTypeNameSpecifier.i */, 9461569414E3567F003A195C /* SBTypeSummary.i */, 9461569514E3567F003A195C /* SBTypeSynthetic.i */, + 23DCBE9A1D63E14B0084C36B /* SBUnixSignals.i */, 2611FF12142D83060017FEA3 /* SBValue.i */, 2611FF13142D83060017FEA3 /* SBValueList.i */, 94235B9D1A8D601A00EB2EED /* SBVariablesOptions.i */, @@ -3671,6 +3725,8 @@ 26C72C951243229A0068DC16 /* SBStream.cpp */, 9A357670116E7B5200E8ED2F /* SBStringList.h */, 9A357672116E7B6400E8ED2F /* SBStringList.cpp */, + 23DCBE9F1D63E3800084C36B /* SBStructuredData.h */, + 23DCBEA01D63E6440084C36B /* SBStructuredData.cpp */, 26DE205A11618FF600A093E2 /* SBSymbol.h */, 26DE20641161904E00A093E2 /* SBSymbol.cpp */, 26DE204011618AB900A093E2 /* SBSymbolContext.h */, @@ -4911,6 +4967,7 @@ 26BC7DEF10F1B80200F91463 /* Target */ = { isa = PBXGroup; children = ( + 230EC4571D63C3A7008DF59F /* CMakeLists.txt */, 8CF02AE019DCBF3B00B14BE0 /* InstrumentationRuntime.h */, 8CF02ADF19DCBF3B00B14BE0 /* InstrumentationRuntime.cpp */, 8CF02AEE19DD15CF00B14BE0 /* InstrumentationRuntimeStopInfo.h */, @@ -4974,6 +5031,9 @@ 26BC7F3A10F1B90C00F91463 /* StackID.cpp */, 2615DB841208A9C90021781D /* StopInfo.h */, 2615DB861208A9E40021781D /* StopInfo.cpp */, + 238F2B9F1D2C835A001FF92A /* StructuredDataPlugin.h */, + 238F2B9D1D2C82D0001FF92A /* StructuredDataPlugin.cpp */, + 238F2BA01D2C835A001FF92A /* SystemRuntime.h */, AF81DEF91828A23F0042CF19 /* SystemRuntime.cpp */, 26BC7DF810F1B81A00F91463 /* Target.h */, 26BC7F3B10F1B90C00F91463 /* Target.cpp */, @@ -4993,6 +5053,7 @@ 49EC3E98118F90AC00B1265E /* ThreadPlanCallFunction.cpp */, EB8375E81B553DFE00BA907D /* ThreadPlanCallFunctionUsingABI.h */, EB8375E61B553DE800BA907D /* ThreadPlanCallFunctionUsingABI.cpp */, + 230EC4581D63C3A7008DF59F /* ThreadPlanCallOnFunctionExit.cpp */, 4C7CF7E31295E10E00B4FBB5 /* ThreadPlanCallUserExpression.h */, 4C7CF7E51295E12B00B4FBB5 /* ThreadPlanCallUserExpression.cpp */, 4C56543219D1EFB5002E9C44 /* ThreadPlanPython.h */, @@ -5541,6 +5602,7 @@ 4CEE62F71145F1C70064CF93 /* GDB Remote */ = { isa = PBXGroup; children = ( + 2374D7431D4BAA1D005C9575 /* CMakeLists.txt */, 2374D74F1D4BB299005C9575 /* GDBRemoteClientBase.h */, 2374D74E1D4BB299005C9575 /* GDBRemoteClientBase.cpp */, 6D55B2931A8A808400A70529 /* GDBRemoteCommunicationServerCommon.h */, @@ -6077,6 +6139,7 @@ 9A357671116E7B5200E8ED2F /* SBStringList.h in Headers */, 26DE205B11618FF600A093E2 /* SBSymbol.h in Headers */, 262F12B71835469C00AEB384 /* SBPlatform.h in Headers */, + 23DCBEA31D63E71F0084C36B /* SBStructuredData.h in Headers */, 26DE204111618AB900A093E2 /* SBSymbolContext.h in Headers */, 268F9D53123AA15200B91E9B /* SBSymbolContextList.h in Headers */, 2668022C115FD13D008E1FE4 /* SBTarget.h in Headers */, @@ -6115,9 +6178,12 @@ 4984BA181B979C08008658D4 /* ExpressionVariable.h in Headers */, 26C7C4841BFFEA7E009BD01F /* WindowsMiniDump.h in Headers */, 30B38A001CAAA6D7009524E3 /* ClangUtil.h in Headers */, + 238F2BA11D2C835A001FF92A /* StructuredDataPlugin.h in Headers */, AF8AD62F1BEC28A400150209 /* PlatformAppleTVSimulator.h in Headers */, + 238F2BA91D2C85FA001FF92A /* StructuredDataDarwinLog.h in Headers */, AF8AD63A1BEC28C400150209 /* PlatformRemoteAppleWatch.h in Headers */, 257906651BD5AFD000178368 /* Acceptor.h in Headers */, + 238F2BA21D2C835A001FF92A /* SystemRuntime.h in Headers */, 260A63171861008E00FECF8E /* IOHandler.h in Headers */, 267F68581CC02EAE0086832B /* RegisterContextPOSIX_s390x.h in Headers */, 6D0F614F1C80AB0C00A4ECEE /* JavaLanguageRuntime.h in Headers */, @@ -6580,6 +6646,7 @@ 9461569D14E358A6003A195C /* SBTypeSynthetic.cpp in Sources */, 26680324116005D9008E1FE4 /* SBThread.cpp in Sources */, 26680326116005DB008E1FE4 /* SBTarget.cpp in Sources */, + 23DCBEA21D63E7190084C36B /* SBStructuredData.cpp in Sources */, 26680327116005DC008E1FE4 /* SBSourceManager.cpp in Sources */, 3F81692C1ABB7A1E001DA9DF /* SystemInitializerFull.cpp in Sources */, 26680328116005DE008E1FE4 /* SBProcess.cpp in Sources */, @@ -6928,6 +6995,7 @@ 268900C213353E5F00698AC0 /* DWARFDebugPubnamesSet.cpp in Sources */, 268900C313353E5F00698AC0 /* DWARFDebugRanges.cpp in Sources */, 25EF23781AC09B3700908DF0 /* AdbClient.cpp in Sources */, + 238F2BA81D2C85FA001FF92A /* StructuredDataDarwinLog.cpp in Sources */, 94380B8219940B0A00BFE4A8 /* StringLexer.cpp in Sources */, 268900C413353E5F00698AC0 /* DWARFDefines.cpp in Sources */, 945563101BEAD0650073F75F /* PlatformiOSSimulatorCoreSimulatorSupport.mm in Sources */, @@ -7080,6 +7148,7 @@ 26D5E163135BB054006EA0A7 /* OptionGroupPlatform.cpp in Sources */, 94CD131A19BA33B400DB7BED /* TypeValidator.cpp in Sources */, 26BD407F135D2AE000237D80 /* FileLineResolver.cpp in Sources */, + 230EC45B1D63C3BA008DF59F /* ThreadPlanCallOnFunctionExit.cpp in Sources */, AE44FB311BB07EB80033EB62 /* GoLexer.cpp in Sources */, 26A7A035135E6E4200FB369E /* OptionValue.cpp in Sources */, 9A22A161135E30370024DDC3 /* EmulateInstructionARM.cpp in Sources */, @@ -7251,6 +7320,7 @@ 945261C81B9A14D300BF138D /* CXXFunctionPointer.cpp in Sources */, 94CB256716B096F10059775D /* TypeCategoryMap.cpp in Sources */, 94CB257016B0A4270059775D /* TypeFormat.cpp in Sources */, + 238F2B9E1D2C82D0001FF92A /* StructuredDataPlugin.cpp in Sources */, 94CB257116B0A4270059775D /* TypeSummary.cpp in Sources */, 94CB257216B0A4270059775D /* TypeSynthetic.cpp in Sources */, 94CB257416B1D3880059775D /* FormatCache.cpp in Sources */, Index: packages/Python/lldbsuite/test/darwin_log.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/darwin_log.py @@ -0,0 +1,430 @@ +""" +Base class for DarwinLog tests. +""" + +# System imports +from __future__ import print_function + +import json +import os +import pexpect +import platform +import re +import sys +import threading + + +# lldb imports +import lldb +from lldb import SBProcess, SBTarget + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import lldbtest_config +from lldbsuite.test import lldbutil + + +def expand_darwinlog_command(command): + return "plugin structured-data darwin-log " + command + + +def expand_darwinlog_settings_set_command(command): + return "settings set plugin.structured-data.darwin-log." + command + + +class DarwinLogTestBase(lldbtest.TestBase): + """Base class for DarwinLog test cases that are pexpect-based.""" + NO_DEBUG_INFO_TESTCASE = True + + CONTINUE_REGEX = re.compile(r"Process \d+ resuming") + + def setUp(self): + # Call super's setUp(). + super(DarwinLogTestBase, self).setUp() + + # Until other systems support this, exit + # early if we're not macOS version 10.12 + # or greater. + version = platform.mac_ver()[0].split('.') + if ((int(version[0]) == 10) and (int(version[1]) < 12) or + (int(version[0]) < 10)): + self.skipTest("DarwinLog tests currently require macOS 10.12+") + return + + self.child = None + self.child_prompt = '(lldb) ' + self.strict_sources = False + self.enable_process_monitor_logging = False + + def run_lldb_to_breakpoint(self, exe, source_file, line, + enable_command=None, settings_commands=None): + + # Set self.child_prompt, which is "(lldb) ". + prompt = self.child_prompt + + # So that the child gets torn down after the test. + self.child = pexpect.spawn('%s %s %s' % (lldbtest_config.lldbExec, + self.lldbOption, exe)) + child = self.child + + # Turn on logging for what the child sends back. + if self.TraceOn(): + child.logfile_read = sys.stdout + + if self.enable_process_monitor_logging: + if platform.system() == 'Darwin': + self.runCmd("settings set target.process.extra-startup-command " + "QSetLogging:bitmask=LOG_DARWIN_LOG;") + self.expect_prompt() + + # Run the enable command if we have one. + if enable_command is not None: + self.runCmd(enable_command) + self.expect_prompt() + + # Disable showing of source lines at our breakpoint. + # This is necessary for the logging tests, because the very + # text we want to match for output from the running inferior + # will show up in the source as well. We don't want the source + # output to erroneously make a match with our expected output. + self.runCmd("settings set stop-line-count-before 0") + self.expect_prompt() + self.runCmd("settings set stop-line-count-after 0") + self.expect_prompt() + + # While we're debugging, turn on packet logging. + self.runCmd("log enable -f /tmp/packets.log gdb-remote packets") + self.expect_prompt() + + # Prevent mirroring of NSLog/os_log content to stderr. We want log + # messages to come exclusively through our log channel. + self.runCmd("settings set target.env-vars IDE_DISABLED_OS_ACTIVITY_DT_MODE=1") + self.expect_prompt() + + # Run any darwin-log settings commands now, before we enable logging. + if settings_commands is not None: + for setting_command in settings_commands: + self.runCmd( + expand_darwinlog_settings_set_command(setting_command)) + self.expect_prompt() + + # Set breakpoint right before the os_log() macros. We don't + # set it on the os_log*() calls because these are a number of + # nested-scoped calls that will cause the debugger to stop + # multiple times on the same line. That is difficult to match + # os_log() content by since it is non-deterministic what the + # ordering between stops and log lines will be. This is why + # we stop before, and then have the process run in a sleep + # afterwards, so we get the log messages while the target + # process is "running" (sleeping). + child.sendline('breakpoint set -f %s -l %d' % (source_file, line)) + child.expect_exact(prompt) + + # Now run to the breakpoint that we just set. + child.sendline('run') + child.expect_exact(prompt) + + # Ensure we stopped at a breakpoint. + self.runCmd("thread list") + self.expect(re.compile(r"stop reason = breakpoint")) + + # Now we're ready to check if DarwinLog is available. + if not self.darwin_log_available(): + self.skipTest("DarwinLog not available") + + def runCmd(self, cmd): + self.child.sendline(cmd) + + def expect_prompt(self, exactly=True): + self.expect(self.child_prompt, exactly=exactly) + + def expect(self, pattern, exactly=False, *args, **kwargs): + if exactly: + return self.child.expect_exact(pattern, *args, **kwargs) + return self.child.expect(pattern, *args, **kwargs) + + def darwin_log_available(self): + self.runCmd("plugin structured-data darwin-log status") + self.expect(re.compile(r"Availability: ([\S]+)")) + return self.child.match is not None and ( + self.child.match.group(1) == "available") + + def do_test(self, enable_options, expect_regexes=None, + settings_commands=None): + """Test that a single fall-through reject rule rejects all logging.""" + self.build(dictionary=self.d) + self.setTearDownCleanup(dictionary=self.d) + + # Build the darwin-log enable command. + enable_cmd = expand_darwinlog_command('enable') + if enable_options is not None and len(enable_options) > 0: + enable_cmd += ' ' + ' '.join(enable_options) + + exe = os.path.join(os.getcwd(), self.exe_name) + self.run_lldb_to_breakpoint(exe, self.source, self.line, + enable_command=enable_cmd, + settings_commands=settings_commands) + self.expect_prompt() + + # Now go. + self.runCmd("process continue") + self.expect(self.CONTINUE_REGEX) + + if expect_regexes is None: + # Expect matching a log line or program exit. + # Test methods determine which ones are valid. + expect_regexes = ( + [re.compile(r"source-log-([^-]+)-(\S+)"), + re.compile(r"exited with status") + ]) + self.expect(expect_regexes) + + +def remove_add_mode_entry(log_entries): + """libtrace creates an "Add Mode:..." message when logging is enabled. + Strip this out of results since our test subjects don't create it.""" + return [entry for entry in log_entries + if "message" in entry and + not entry["message"].startswith("Add Mode:")] + + +class DarwinLogEventBasedTestBase(lldbtest.TestBase): + """Base class for event-based DarwinLog tests.""" + NO_DEBUG_INFO_TESTCASE = True + + class EventListenerThread(threading.Thread): + def __init__(self, listener, process, trace_on, max_entry_count): + super(DarwinLogEventBasedTestBase.EventListenerThread, self).__init__() + self.process = process + self.listener = listener + self.trace_on = trace_on + self.max_entry_count = max_entry_count + self.exception = None + self.structured_data_event_count = 0 + self.wait_seconds = 2 + self.max_timeout_count = 4 + self.log_entries = [] + + def handle_structured_data_event(self, event): + structured_data = SBProcess.GetStructuredDataFromEvent(event) + if not structured_data.IsValid(): + if self.trace_on: + print("invalid structured data") + return + + # Track that we received a valid structured data event. + self.structured_data_event_count += 1 + + # Grab the individual log entries from the JSON. + stream = lldb.SBStream() + structured_data.GetAsJSON(stream) + dict = json.loads(stream.GetData()) + self.log_entries.extend(dict["events"]) + if self.trace_on: + print("Structured data (raw):", stream.GetData()) + + # Print the pretty-printed version. + if self.trace_on: + stream.Clear() + structured_data.PrettyPrint(stream) + print("Structured data (pretty print):", + stream.GetData()) + + def done(self, timeout_count): + """Returns True when we're done listening for events.""" + # See if we should consider the number of events retrieved. + if self.max_entry_count is not None: + if len(self.log_entries) >= self.max_entry_count: + # We've received the max threshold of events expected, + # we can exit here. + if self.trace_on: + print("Event listener thread exiting due to max " + "expected log entry count being reached.") + return True + + # If our event timeout count has exceeded our maximum timeout count, + # we're done. + if timeout_count >= self.max_timeout_count: + if self.trace_on: + print("Event listener thread exiting due to max number of " + "WaitForEvent() timeouts being reached.") + return True + + # If our process is dead, we're done. + if not self.process.is_alive: + if self.trace_on: + print("Event listener thread exiting due to test inferior " + "exiting.") + return True + + # We're not done. + return False + + def run(self): + event = lldb.SBEvent() + try: + timeout_count = 0 + + # Wait up to 4 times for the event to arrive. + while not self.done(timeout_count): + if self.trace_on: + print("Calling wait for event...") + if self.listener.WaitForEvent(self.wait_seconds, event): + while event.IsValid(): + # Check if it's a process event. + if SBProcess.EventIsStructuredDataEvent(event): + self.handle_structured_data_event(event) + else: + if self.trace_on: + print("ignoring unexpected event:", + lldbutil.get_description(event)) + # Grab the next event, if there is one. + event.Clear() + if not self.listener.GetNextEvent(event): + if self.trace_on: + print("listener has no more events " + "available at this time") + else: + if self.trace_on: + print("timeout occurred waiting for event...") + timeout_count += 1 + self.listener.Clear() + except Exception as e: + self.exception = e + + def setUp(self): + # Call super's setUp(). + super(DarwinLogEventBasedTestBase, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + # Enable debugserver logging of the darwin log collection + # mechanism. + self.runCmd("settings set target.process.extra-startup-command " + "QSetLogging:bitmask=LOG_DARWIN_LOG;") + + def do_test(self, enable_options, settings_commands=None, + run_enable_after_breakpoint=False, max_entry_count=None): + """Runs the test inferior, returning collected events. + + This method runs the test inferior to the first breakpoint hit. + It then adds a listener for structured data events, and collects + all events from that point forward until end of execution of the + test inferior. It then returns those events. + + @return + A list of structured data events received, in the order they + were received. + """ + self.build(dictionary=self.d) + self.setTearDownCleanup(dictionary=self.d) + + exe = os.path.join(os.getcwd(), self.exe_name) + + # Create a target by the debugger. + target = self.dbg.CreateTarget(exe) + self.assertTrue(target, lldbtest.VALID_TARGET) + + # Run the darwin-log settings commands. + if settings_commands is not None: + for setting_command in settings_commands: + self.runCmd( + expand_darwinlog_settings_set_command(setting_command)) + + # Build the darwin-log enable command. + enable_cmd = expand_darwinlog_command("enable") + if enable_options is not None and len(enable_options) > 0: + enable_cmd += ' ' + ' '.join(enable_options) + + # Run the darwin-log enable command now if we are not supposed + # to do it at the first breakpoint. This tests the start-up + # code, which has the benefit of being able to set os_log-related + # environment variables. + if not run_enable_after_breakpoint: + self.runCmd(enable_cmd) + + # Create the breakpoint. + breakpoint = target.BreakpointCreateByLocation(self.source, self.line) + self.assertIsNotNone(breakpoint) + self.assertTrue(breakpoint.IsValid()) + self.assertEqual(1, breakpoint.GetNumLocations(), + "Should have found one breakpoint") + + # Enable packet logging. + # self.runCmd("log enable -f /tmp/packets.log gdb-remote packets") + # self.runCmd("log enable lldb process") + + # Launch the process - doesn't stop at entry. + process = target.LaunchSimple(None, None, os.getcwd()) + self.assertIsNotNone(process, lldbtest.PROCESS_IS_VALID) + + # Keep track of whether we're tracing output. + trace_on = self.TraceOn() + + # Get the next thread that stops. + from lldbsuite.test.lldbutil import get_stopped_thread + thread = get_stopped_thread(process, lldb.eStopReasonBreakpoint) + + self.assertIsNotNone(thread, "There should be a thread stopped " + "due to breakpoint") + + # The process should be stopped at this point. + self.expect("process status", lldbtest.PROCESS_STOPPED, + patterns=['Process .* stopped']) + + # The stop reason of the thread should be breakpoint. + self.expect("thread list", lldbtest.STOPPED_DUE_TO_BREAKPOINT, + substrs=['stopped', 'stop reason = breakpoint']) + + # And our one and only breakpoint should have been hit. + self.assertEquals(breakpoint.GetHitCount(), 1) + + # Now setup the structured data listener. + # + # Grab the broadcaster for the process. We'll be attaching our + # listener to it. + broadcaster = process.GetBroadcaster() + self.assertIsNotNone(broadcaster) + + listener = lldb.SBListener("SBStructuredData listener") + self.assertIsNotNone(listener) + + rc = broadcaster.AddListener(listener, + lldb.SBProcess.eBroadcastBitStructuredData) + self.assertTrue(rc, "Successfully add listener to process broadcaster") + + # Start the listening thread to retrieve the events. + # Bump up max entry count for the potentially included Add Mode: + # entry. + if max_entry_count is not None: + max_entry_count += 1 + event_thread = self.EventListenerThread(listener, process, trace_on, + max_entry_count) + event_thread.start() + + # Continue the test inferior. We should get any events after this. + process.Continue() + + # Wait until the event thread terminates. + # print("main thread now waiting for event thread to receive events.") + event_thread.join() + + # If the process is still alive, we kill it here. + if process.is_alive: + process.Kill() + + # Fail on any exceptions that occurred during event execution. + if event_thread.exception is not None: + # Re-raise it here so it shows up as a test error. + raise event_thread + + # Return the collected logging events. + return remove_add_mode_entry(event_thread.log_entries) Index: packages/Python/lldbsuite/test/functionalities/darwin_log/basic/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/basic/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/basic/TestDarwinLogBasic.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/basic/TestDarwinLogBasic.py @@ -0,0 +1,34 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +# System imports +from __future__ import print_function + +# LLDB imports +from lldbsuite.test import darwin_log +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest + + +class TestDarwinLogBasic(darwin_log.DarwinLogEventBasedTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + @decorators.add_test_categories(['pyapi']) + @decorators.skipUnlessDarwin + def test_SBStructuredData_gets_broadcasted(self): + """Exercise SBStructuredData API.""" + + # Run the test. + log_entries = self.do_test(None, max_entry_count=2) + + # Validate that we received our two log entries. + self.assertEqual(len(log_entries), 1, + "Expected one log entry to arrive via events.") + self.assertEqual(log_entries[0]['message'], "Hello, world", + "Log message should match expected content.") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/basic/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/basic/main.c @@ -0,0 +1,32 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger = os_log_create("org.llvm.lldb.test", "basic-test"); + if (!logger) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_log(logger, "Hello, world"); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/common/darwin_log_common.h =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/common/darwin_log_common.h @@ -0,0 +1,6 @@ +// The number of seconds to wait at the end of the test inferior before +// exiting. This delay is needed to ensure the logging infrastructure +// has flushed out the message. If we finished before all messages were +// flushed, then the test will never see the unflushed messages, causing +// some test logic to fail. +#define FINAL_WAIT_SECONDS 5 Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity-chain/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity-chain/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity-chain/TestDarwinLogFilterMatchActivityChain.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity-chain/TestDarwinLogFilterMatchActivityChain.py @@ -0,0 +1,117 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterMatchActivityChain(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterMatchActivityChain, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterMatchActivityChain, self).tearDown() + + # ========================================================================== + # activity-chain filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_chain_match(self): + """Test that fall-through reject, accept full-match activity chain works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity-chain match " + "parent-activity:child-activity\""]) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_chain_partial_match(self): + """Test that fall-through reject, doesn't accept only partial match of activity-chain.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity-chain match parent-activity:child-activity\"", # Match the second fully. + "--filter \"accept activity-chain match parent-ac\""]) # Only partially match the first. + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_chain_full_match(self): + """Test that fall-through accept, reject match activity-chain works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject activity-chain match parent-activity\""]) + + # We should only see the second log message as we rejected the first + # via activity-chain rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_chain_second_rule(self): + """Test that fall-through reject, accept activity-chain on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity-chain match non-existent\"", + "--filter \"accept activity-chain match parent-activity:child-activity\""]) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the activity-chain of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity-chain/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity-chain/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity/TestDarwinLogFilterMatchActivity.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity/TestDarwinLogFilterMatchActivity.py @@ -0,0 +1,121 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterMatchActivity(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterMatchActivity, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterMatchActivity, self).tearDown() + + # ========================================================================== + # activity filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_match(self): + """Test that fall-through reject, accept match activity works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity match child-activity\""] + ) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_partial_match(self): + """Test that fall-through reject, accept match activity via partial match does not accept.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity match child-activity\"", # Fully match second message. + "--filter \"accept activity match parent-\""] # Only partially match first message. + ) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_full_match(self): + """Test that fall-through accept, reject match activity works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject activity match parent-activity\""] + ) + + # We should only see the second log message as we rejected the first + # via activity rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_second_rule(self): + """Test that fall-through reject, accept regex activity on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity match non-existent\"", + "--filter \"accept activity match child-activity\"" + ] + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the activity of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/activity/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/category/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/category/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/category/TestDarwinLogFilterMatchCategory.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/category/TestDarwinLogFilterMatchCategory.py @@ -0,0 +1,118 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterMatchCategory(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterMatchCategory, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterMatchCategory, self).tearDown() + + # ========================================================================== + # category filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_category_full_match(self): + """Test that fall-through reject, accept match single category works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept category match cat2\""] + ) + + # We should only see the second log message as we only accept + # that category. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_category_partial_match(self): + """Test that fall-through reject, accept regex category via partial match works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept category match cat2\"", # Fully match the second message. + "--filter \"accept category match at1\""] # Only partially match first message. Should not show up. + ) + + # We should only see the second log message as we only accept + # that category. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_category_full_match(self): + """Test that fall-through accept, reject match category works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject category match cat1\""] + ) + + # We should only see the second log message as we rejected the first + # via category rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_category_second_rule(self): + """Test that fall-through reject, accept match category on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept category match non-existent\"", + "--filter \"accept category match cat2\"" + ] + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the category of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/category/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/category/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/message/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/message/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/message/TestDarwinLogFilterMatchMessage.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/message/TestDarwinLogFilterMatchMessage.py @@ -0,0 +1,132 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterMatchMessage(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterMatchMessage, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + self.strict_sources = True + + # Turn on process monitor logging while we work out issues. + self.enable_process_monitor_logging = True + + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterMatchMessage, self).tearDown() + + # ========================================================================== + # category filter tests + # ========================================================================== + + EXPECT_REGEXES = [ + re.compile(r"log message ([^-]+)-(\S+)"), + re.compile(r"exited with status") + ] + + @decorators.skipUnlessDarwin + def test_filter_accept_message_full_match(self): + """Test that fall-through reject, accept match whole message works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept message match log message sub2-cat2\""], + expect_regexes=self.EXPECT_REGEXES + ) + + # We should only see the second log message as we only accept + # that message contents. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_no_accept_message_partial_match(self): + """Test that fall-through reject, match message via partial content match doesn't accept.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept message match log message sub2-cat2\"", + "--filter \"accept message match sub1-cat1\""], + expect_regexes=self.EXPECT_REGEXES + ) + + # We should only see the second log message as the partial match on + # the first message should not pass. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_category_full_match(self): + """Test that fall-through accept, reject match message works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject message match log message sub1-cat1\""], + expect_regexes=self.EXPECT_REGEXES + ) + + # We should only see the second log message as we rejected the first + # via message contents rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_category_second_rule(self): + """Test that fall-through reject, accept match category on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept message match non-existent\"", + "--filter \"accept message match log message sub2-cat2\""], + expect_regexes=self.EXPECT_REGEXES + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the category of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/message/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/message/main.c @@ -0,0 +1,35 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_log(logger_sub1, "log message sub%d-cat%d", 1, 1); + os_log(logger_sub2, "log message sub%d-cat%d", 2, 2); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(1); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/subsystem/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/subsystem/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/subsystem/TestDarwinLogFilterMatchSubsystem.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/subsystem/TestDarwinLogFilterMatchSubsystem.py @@ -0,0 +1,118 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterMatchSubsystem(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterMatchSubsystem, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterMatchSubsystem, self).tearDown() + + # ========================================================================== + # subsystem filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_subsystem_full_match(self): + """Test that fall-through reject, accept match single subsystem works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept subsystem match org.llvm.lldb.test.sub2\""] + ) + + # We should only see the second log message as we only accept + # that subsystem. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_subsystem_partial_match(self): + """Test that fall-through reject, doesn't accept match subsystem via partial-match.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept subsystem match org.llvm.lldb.test.sub2\"", # Fully match second message subsystem. + "--filter \"accept subsystem match sub1\""] # Only partially match first subsystem. + ) + + # We should only see the second log message as we only accept + # that subsystem. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_subsystem_full_match(self): + """Test that fall-through accept, reject match subsystem works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject subsystem match org.llvm.lldb.test.sub1\""] + ) + + # We should only see the second log message as we rejected the first + # via subsystem rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_subsystem_second_rule(self): + """Test that fall-through reject, accept match subsystem on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept subsystem match non-existent\"", + "--filter \"accept subsystem match org.llvm.lldb.test.sub2\"" + ] + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the subsystem of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/subsystem/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/exact_match/subsystem/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity-chain/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity-chain/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity-chain/TestDarwinLogFilterRegexActivityChain.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity-chain/TestDarwinLogFilterRegexActivityChain.py @@ -0,0 +1,132 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterRegexActivityChain(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterRegexActivityChain, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterRegexActivityChain, self).tearDown() + + # ========================================================================== + # activity-chain filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_chain_full_match(self): + """Test that fall-through reject, accept full-match activity chain works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity-chain regex " + "parent-activity:child-activity\""]) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_chain_partial_match(self): + """Test that fall-through reject, accept activity-chain via partial match works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity-chain regex :child-activity\""]) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_chain_full_match(self): + """Test that fall-through accept, reject activity-chain works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject activity-chain regex parent-activity:child-..tivity\""]) + + # We should only see the second log message as we rejected the first + # via activity-chain rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat1"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_chain_partial_match(self): + """Test that fall-through accept, reject activity-chain by partial match works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject activity-chain regex ^p[^:]+$\""]) + + # We should only see the second log message as we rejected the first + # via activity-chain rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_chain_second_rule(self): + """Test that fall-through reject, accept activity-chain on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity-chain regex non-existent\"", + "--filter \"accept activity-chain regex child-activity\""]) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the activity-chain of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity-chain/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity-chain/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity/TestDarwinLogFilterRegexActivity.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity/TestDarwinLogFilterRegexActivity.py @@ -0,0 +1,137 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterRegexActivity(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterRegexActivity, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterRegexActivity, self).tearDown() + + # ========================================================================== + # activity filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_full_match(self): + """Test that fall-through reject, accept regex full-match activity works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity regex child-activity\""] + ) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_partial_match(self): + """Test that fall-through reject, regex accept activity via partial match works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity regex child-.*\""] + ) + + # We should only see the second log message as we only accept + # that activity. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_full_match(self): + """Test that fall-through accept, reject regex activity works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject activity regex parent-activity\""] + ) + + # We should only see the second log message as we rejected the first + # via activity rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_reject_activity_partial_match(self): + """Test that fall-through accept, reject regex activity by partial match works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject activity regex p.+-activity\""] + ) + + # We should only see the second log message as we rejected the first + # via activity rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + + @decorators.skipUnlessDarwin + def test_filter_accept_activity_second_rule(self): + """Test that fall-through reject, accept regex activity on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept activity regex non-existent\"", + "--filter \"accept activity regex child-activity\"" + ] + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the activity of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/activity/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/category/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/category/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/category/TestDarwinLogFilterRegexCategory.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/category/TestDarwinLogFilterRegexCategory.py @@ -0,0 +1,133 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterRegexCategory(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterRegexCategory, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterRegexCategory, self).tearDown() + + # ========================================================================== + # category filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_category_full_match(self): + """Test that fall-through reject, accept regex single category works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept category regex cat2\""] + ) + + # We should only see the second log message as we only accept + # that category. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_category_partial_match(self): + """Test that fall-through reject, accept regex category via partial match works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept category regex .+2\""] + ) + + # We should only see the second log message as we only accept + # that category. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_category_full_match(self): + """Test that fall-through accept, reject regex category works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject category regex cat1\""] + ) + + # We should only see the second log message as we rejected the first + # via category rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_category_partial_match(self): + """Test that fall-through accept, reject regex category by partial match works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject category regex t1\""] + ) + + # We should only see the second log message as we rejected the first + # via category rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_category_second_rule(self): + """Test that fall-through reject, accept regex category on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept category regex non-existent\"", + "--filter \"accept category regex cat2\"" + ] + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the category of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/category/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/category/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/message/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/message/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/message/TestDarwinLogFilterRegexMessage.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/message/TestDarwinLogFilterRegexMessage.py @@ -0,0 +1,118 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +# System imports +from __future__ import print_function + +import re + +# LLDB imports +import lldb + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterRegexMessage(darwin_log.DarwinLogEventBasedTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + @decorators.skipUnlessDarwin + def test_filter_accept_message_full_match(self): + """Test that fall-through reject, accept regex whole message works.""" + log_entries = self.do_test( + ["--no-match-accepts false", + # Note below, the four '\' characters are to get us two + # backslashes over on the gdb-remote side, which then + # becomes one as the cstr interprets it as an escape + # sequence. This needs to be rationalized. Initially I + # supported std::regex ECMAScript, which has the + # [[:digit:]] character classes and such. That was much + # more tenable. The backslashes have to travel through + # so many layers of escaping. (And note if you take + # off the Python raw string marker here, you need to put + # in 8 backslashes to go to two on the remote side.) + r'--filter "accept message regex log message sub2-cat\\\\d+"']) + + # We should have received at least one log entry. + self.assertIsNotNone(log_entries, + "Log entry list should not be None.") + self.assertEqual(len(log_entries), 1, + "Should receive one log entry.") + self.assertRegexpMatches(log_entries[0]["message"], r"sub2-cat2", + "First os_log call should have been skipped.") + + @decorators.skipUnlessDarwin + def test_filter_accept_message_partial_match(self): + """Test that fall-through reject, accept regex message via partial + match works.""" + log_entries = self.do_test( + ["--no-match-accepts false", + "--filter \"accept message regex [^-]+2\""]) + + # We should only see the second log message as we only accept + # that message contents. + self.assertIsNotNone(log_entries, + "Log entry list should not be None.") + self.assertEqual(len(log_entries), 1, + "Should receive one log entry.") + self.assertRegexpMatches(log_entries[0]["message"], r"sub2-cat2", + "First os_log call should have been skipped.") + + @decorators.skipUnlessDarwin + def test_filter_reject_message_full_match(self): + """Test that fall-through accept, reject regex message works.""" + log_entries = self.do_test( + ["--no-match-accepts true", + "--filter \"reject message regex log message sub1-cat1\""]) + + # We should only see the second log message as we rejected the first + # via message contents rejection. + self.assertIsNotNone(log_entries, + "Log entry list should not be None.") + self.assertEqual(len(log_entries), 1, + "Should receive one log entry.") + self.assertRegexpMatches(log_entries[0]["message"], r"sub2-cat2", + "First os_log call should have been skipped.") + + @decorators.skipUnlessDarwin + def test_filter_reject_message_partial_match(self): + """Test that fall-through accept, reject regex message by partial + match works.""" + log_entries = self.do_test( + ["--no-match-accepts true", + "--filter \"reject message regex t1\""]) + + # We should only see the second log message as we rejected the first + # via partial message contents rejection. + self.assertIsNotNone(log_entries, + "Log entry list should not be None.") + self.assertEqual(len(log_entries), 1, + "Should receive one log entry.") + self.assertRegexpMatches(log_entries[0]["message"], r"sub2-cat2", + "First os_log call should have been skipped.") + + @decorators.skipUnlessDarwin + def test_filter_accept_message_second_rule(self): + """Test that fall-through reject, accept regex message on second rule + works.""" + log_entries = self.do_test( + ["--no-match-accepts false", + "--filter \"accept message regex non-existent\"", + "--filter \"accept message regex cat2\""]) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the message of the second log message. + self.assertIsNotNone(log_entries, + "Log entry list should not be None.") + self.assertEqual(len(log_entries), 1, + "Should receive one log entry.") + self.assertRegexpMatches(log_entries[0]["message"], r"sub2-cat2", + "First os_log call should have been skipped.") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/message/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/message/main.c @@ -0,0 +1,35 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_log(logger_sub1, "log message sub%d-cat%d", 1, 1); + os_log(logger_sub2, "log message sub%d-cat%d", 2, 2); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/subsystem/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/subsystem/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/subsystem/TestDarwinLogFilterRegexSubsystem.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/subsystem/TestDarwinLogFilterRegexSubsystem.py @@ -0,0 +1,150 @@ +""" +Test basic DarwinLog functionality provided by the StructuredDataDarwinLog +plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogFilterRegexSubsystem(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogFilterRegexSubsystem, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogFilterRegexSubsystem, self).tearDown() + + # ========================================================================== + # basic filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_fallthrough_reject(self): + """Test that a single fall-through reject regex rule rejects all logging.""" + self.do_test( + ["--no-match-accepts false"] + ) + + # We should not match any log lines. + self.assertIsNotNone(self.child.match) + self.assertFalse((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) in ["sub1", "sub2"]), + "log line should not have been received") + + # ========================================================================== + # subsystem filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_filter_accept_subsystem_full_match(self): + """Test that fall-through reject, accept regex single subsystem works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept subsystem regex org.llvm.lldb.test.sub2\""] + ) + + # We should only see the second log message as we only accept + # that subsystem. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_subsystem_partial_match(self): + """Test that fall-through reject, accept regex subsystem via partial-match works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept subsystem regex org.llvm.+.sub2\""] + ) + + # We should only see the second log message as we only accept + # that subsystem. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_subsystem_full_match(self): + """Test that fall-through accept, reject regex subsystem works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject subsystem regex org.llvm.lldb.test.sub1\""] + ) + + # We should only see the second log message as we rejected the first + # via subsystem rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_reject_subsystem_partial_match(self): + """Test that fall-through accept, reject regex subsystem by partial match works.""" + self.do_test( + ["--no-match-accepts true", + "--filter \"reject subsystem regex org.*sub1\""] + ) + + # We should only see the second log message as we rejected the first + # via subsystem rejection. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_filter_accept_subsystem_second_rule(self): + """Test that fall-through reject, accept regex subsystem on second rule works.""" + self.do_test( + ["--no-match-accepts false", + "--filter \"accept subsystem regex non-existent\"", + "--filter \"accept subsystem regex org.llvm.lldb.test.sub2\"" + ] + ) + + # We should only see the second message since we reject by default, + # the first filter doesn't match any, and the second filter matches + # the subsystem of the second log message. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) == "sub2"), + "first log line should not be present, second log line " + "should be") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/subsystem/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/filter/regex/subsystem/main.c @@ -0,0 +1,43 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_log(logger_sub1, "source-log-sub1-cat1"); + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub2, "source-log-sub2-cat2"); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/format/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/format/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/format/TestDarwinLogMessageFormat.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/format/TestDarwinLogMessageFormat.py @@ -0,0 +1,189 @@ +""" +Test DarwinLog log message formatting options provided by the +StructuredDataDarwinLog plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogMessageFormat(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogMessageFormat, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogMessageFormat, self).tearDown() + + # ========================================================================== + # Test settings around log message formatting + # ========================================================================== + + REGEXES = [ + re.compile(r"\[([^]]+)\] This is the log message."), # Match log + # with header. + re.compile(r"This is the log message."), # Match no-header content. + re.compile(r"exited with status") # Fallback if no log emitted. + ] + + @decorators.skipUnlessDarwin + def test_display_without_header_works(self): + """Test that turning off log message headers works as advertised.""" + self.do_test([], expect_regexes=self.REGEXES) + + # We should not match the first pattern as we shouldn't have header + # content. + self.assertIsNotNone(self.child.match) + self.assertFalse((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) != ""), + "we should not have seen a header") + + + @decorators.skipUnlessDarwin + def test_display_with_header_works(self): + """Test that displaying any header works.""" + self.do_test( + ["--timestamp-relative", "--subsystem", "--category", + "--activity-chain"], + expect_regexes=self.REGEXES, + settings_commands=[ + "display-header true" + ]) + + # We should match the first pattern as we should have header + # content. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) != ""), + "we should have printed a header") + + def assert_header_contains_timestamp(self, header): + fields = header.split(',') + self.assertGreater(len(fields), 0, + "there should have been header content present") + self.assertRegexpMatches(fields[0], + r"^\d+:\d{2}:\d{2}.\d{9}$", + "time field should match expected format") + + @decorators.skipUnlessDarwin + def test_header_timefield_only_works(self): + """Test that displaying a header with only the timestamp works.""" + self.do_test(["--timestamp-relative"], expect_regexes=self.REGEXES) + + # We should match the first pattern as we should have header + # content. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) != ""), + "we should have printed a header") + header = self.child.match.group(1) + self.assertEqual(len(header.split(',')), 1, + "there should only be one header field") + self.assert_header_contains_timestamp(header) + + @decorators.skipUnlessDarwin + def test_header_subsystem_only_works(self): + """Test that displaying a header with only the subsystem works.""" + self.do_test(["--subsystem"], expect_regexes=self.REGEXES) + + # We should match the first pattern as we should have header + # content. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) != ""), + "we should have printed a header") + header = self.child.match.group(1) + self.assertEqual(len(header.split(',')), 1, + "there should only be one header field") + self.assertEquals(header, + "subsystem=org.llvm.lldb.test.sub1") + + @decorators.skipUnlessDarwin + def test_header_category_only_works(self): + """Test that displaying a header with only the category works.""" + self.do_test(["--category"], expect_regexes=self.REGEXES) + + # We should match the first pattern as we should have header + # content. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) != ""), + "we should have printed a header") + header = self.child.match.group(1) + self.assertEqual(len(header.split(',')), 1, + "there should only be one header field") + self.assertEquals(header, + "category=cat1") + + @decorators.skipUnlessDarwin + def test_header_activity_chain_only_works(self): + """Test that displaying a header with only the activity chain works.""" + self.do_test(["--activity-chain"], expect_regexes=self.REGEXES) + + # We should match the first pattern as we should have header + # content. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 0) and + (self.child.match.group(1) != ""), + "we should have printed a header") + header = self.child.match.group(1) + self.assertEqual(len(header.split(',')), 1, + "there should only be one header field") + self.assertEquals(header, + "activity-chain=parent-activity:child-activity") + + # @decorators.skipUnlessDarwin + # def test_header_activity_no_chain_only_works(self): + # """Test that displaying a header with only the activity works.""" + # self.do_test( + # [], + # expect_regexes=self.REGEXES, + # settings_commands=[ + # "display-header true", + # "format-include-timestamp false", + # "format-include-activity true", + # "format-include-category false", + # "format-include-subsystem false", + # "display-activity-chain false" + # ]) + + # # We should match the first pattern as we should have header + # # content. + # self.assertIsNotNone(self.child.match) + # self.assertTrue((len(self.child.match.groups()) > 0) and + # (self.child.match.group(1) != ""), + # "we should have printed a header") + # header = self.child.match.group(1) + # self.assertEqual(len(header.split(',')), 1, + # "there should only be one header field") + # self.assertEquals(header, + # "activity=child-activity") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/format/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/format/main.c @@ -0,0 +1,41 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include +#include + +#include "../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + if (!logger_sub1) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_activity_t parent_activity = os_activity_create("parent-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(parent_activity, ^{ + os_activity_t child_activity = os_activity_create("child-activity", + OS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT); + os_activity_apply(child_activity, ^{ + os_log(logger_sub1, "This is the log message."); + }); + }); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/source/debug/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/source/debug/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/source/debug/TestDarwinLogSourceDebug.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/source/debug/TestDarwinLogSourceDebug.py @@ -0,0 +1,79 @@ +""" +Test DarwinLog "source include debug-level" functionality provided by the +StructuredDataDarwinLog plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogSourceDebug(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogSourceDebug, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + # Indicate we want strict-sources behavior. + self.strict_sources = True + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogSourceDebug, self).tearDown() + + # ========================================================================== + # source include/exclude debug filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + def test_source_default_exclude_debug(self): + """Test that default excluding of debug-level log messages works.""" + self.do_test([]) + + # We should only see the second log message as the first is a + # debug-level message and we're not including debug-level messages. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_source_explicitly_include_debug(self): + """Test that explicitly including debug-level log messages works.""" + self.do_test(["--debug"]) + + # We should only see the second log message as the first is a + # debug-level message and we're not including debug-level messages. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat1"), + "first log line should be present since we're " + "including debug-level log messages") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/source/debug/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/source/debug/main.c @@ -0,0 +1,34 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_log_debug(logger_sub1, "source-log-sub1-cat1"); + os_log(logger_sub2, "source-log-sub2-cat2"); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: packages/Python/lldbsuite/test/functionalities/darwin_log/source/info/Makefile =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/source/info/Makefile @@ -0,0 +1,5 @@ +LEVEL = ../../../../make + +C_SOURCES := main.c + +include $(LEVEL)/Makefile.rules Index: packages/Python/lldbsuite/test/functionalities/darwin_log/source/info/TestDarwinLogSourceInfo.py =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/source/info/TestDarwinLogSourceInfo.py @@ -0,0 +1,82 @@ +""" +Test DarwinLog "source include info-level" functionality provided by the +StructuredDataDarwinLog plugin. + +These tests are currently only supported when running against Darwin +targets. +""" + +from __future__ import print_function + +import lldb +import os +import re + +from lldbsuite.test import decorators +from lldbsuite.test import lldbtest +from lldbsuite.test import darwin_log + + +class TestDarwinLogSourceInfo(darwin_log.DarwinLogTestBase): + + mydir = lldbtest.TestBase.compute_mydir(__file__) + + def setUp(self): + # Call super's setUp(). + super(TestDarwinLogSourceInfo, self).setUp() + + # Source filename. + self.source = 'main.c' + + # Output filename. + self.exe_name = 'a.out' + self.d = {'C_SOURCES': self.source, 'EXE': self.exe_name} + + # Locate breakpoint. + self.line = lldbtest.line_number(self.source, '// break here') + + # Indicate we want strict-sources behavior. + self.strict_sources = True + + def tearDown(self): + # Shut down the process if it's still running. + if self.child: + self.runCmd('process kill') + self.expect_prompt() + self.runCmd('quit') + + # Let parent clean up + super(TestDarwinLogSourceInfo, self).tearDown() + + # ========================================================================== + # source include/exclude debug filter tests + # ========================================================================== + + @decorators.skipUnlessDarwin + @decorators.expectedFailureAll(bugnumber="rdar://27316264") + def test_source_exclude_info_level(self): + """Test that default excluding of info-level log messages works.""" + self.do_test([]) + + # We should only see the second log message as the first is an + # info-level message and we're not including debug-level messages. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat2"), + "first log line should not be present, second log line " + "should be") + + @decorators.skipUnlessDarwin + def test_source_include_info_level(self): + """Test that explicitly including info-level log messages works.""" + self.do_test( + ["--info"] + ) + + # We should only see the second log message as the first is a + # debug-level message and we're not including debug-level messages. + self.assertIsNotNone(self.child.match) + self.assertTrue((len(self.child.match.groups()) > 1) and + (self.child.match.group(2) == "cat1"), + "first log line should be present since we're " + "including info-level log messages") Index: packages/Python/lldbsuite/test/functionalities/darwin_log/source/info/main.c =================================================================== --- /dev/null +++ packages/Python/lldbsuite/test/functionalities/darwin_log/source/info/main.c @@ -0,0 +1,34 @@ +//===-- main.c --------------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include +#include + +#include "../../common/darwin_log_common.h" + +int main(int argc, char** argv) +{ + os_log_t logger_sub1 = os_log_create("org.llvm.lldb.test.sub1", "cat1"); + os_log_t logger_sub2 = os_log_create("org.llvm.lldb.test.sub2", "cat2"); + if (!logger_sub1 || !logger_sub2) + return 1; + + // Note we cannot use the os_log() line as the breakpoint because, as of + // the initial writing of this test, we get multiple breakpoints for that + // line, which confuses the pexpect test logic. + printf("About to log\n"); // break here + os_log_info(logger_sub1, "source-log-sub1-cat1"); + os_log(logger_sub2, "source-log-sub2-cat2"); + + // Sleep, as the darwin log reporting doesn't always happen until a bit + // later. We need the message to come out before the process terminates. + sleep(FINAL_WAIT_SECONDS); + + return 0; +} Index: scripts/interface/SBProcess.i =================================================================== --- scripts/interface/SBProcess.i +++ scripts/interface/SBProcess.i @@ -44,7 +44,8 @@ eBroadcastBitInterrupt = (1 << 1), eBroadcastBitSTDOUT = (1 << 2), eBroadcastBitSTDERR = (1 << 3), - eBroadcastBitProfileData = (1 << 4) + eBroadcastBitProfileData = (1 << 4), + eBroadcastBitStructuredData = (1 << 5) }; SBProcess (); @@ -351,9 +352,15 @@ static bool GetInterruptedFromEvent (const lldb::SBEvent &event); + static lldb::SBStructuredData + GetStructuredDataFromEvent (const lldb::SBEvent &event); + static bool EventIsProcessEvent (const lldb::SBEvent &event); + static bool + EventIsStructuredDataEvent (const lldb::SBEvent &event); + lldb::SBBroadcaster GetBroadcaster () const; Index: scripts/interface/SBStructuredData.i =================================================================== --- /dev/null +++ scripts/interface/SBStructuredData.i @@ -0,0 +1,42 @@ +//===-- SWIG Interface for SBStructuredData ---------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +namespace lldb { + + %feature("docstring", + "A class representing a StructuredData event. + + This class wraps the event type generated by StructuredData + features." + ) SBStructuredData; + class SBStructuredData + { + public: + + SBStructuredData(); + + SBStructuredData(const lldb::SBStructuredData &rhs); + + SBStructuredData(const lldb::EventSP &event_sp); + + ~SBStructuredData(); + + bool + IsValid() const; + + void + Clear(); + + lldb::SBError + GetAsJSON(lldb::SBStream &stream) const; + + lldb::SBError + PrettyPrint(lldb::SBStream &stream) const; + }; +} Index: scripts/interface/SBTarget.i =================================================================== --- scripts/interface/SBTarget.i +++ scripts/interface/SBTarget.i @@ -178,7 +178,7 @@ /// Some launch options specified by logical OR'ing /// lldb::LaunchFlags enumeration values together. /// - /// @param[in] stop_at_endtry + /// @param[in] stop_at_entry /// If false do not stop the inferior at the entry point. /// /// @param[out] Index: scripts/lldb.swig =================================================================== --- scripts/lldb.swig +++ scripts/lldb.swig @@ -95,6 +95,7 @@ #include "lldb/API/SBSourceManager.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStringList.h" +#include "lldb/API/SBStructuredData.h" #include "lldb/API/SBSymbol.h" #include "lldb/API/SBSymbolContext.h" #include "lldb/API/SBSymbolContextList.h" @@ -177,6 +178,7 @@ %include "./interface/SBSourceManager.i" %include "./interface/SBStream.i" %include "./interface/SBStringList.i" +%include "./interface/SBStructuredData.i" %include "./interface/SBSymbol.i" %include "./interface/SBSymbolContext.i" %include "./interface/SBSymbolContextList.i" Index: source/API/SBDebugger.cpp =================================================================== --- source/API/SBDebugger.cpp +++ source/API/SBDebugger.cpp @@ -491,7 +491,7 @@ if (err != nullptr) ::fwrite (stdio_buffer, 1, len, err); } - + if (event_type & Process::eBroadcastBitStateChanged) { StateType event_state = SBProcess::GetStateFromEvent (event); Index: source/API/SBProcess.cpp =================================================================== --- source/API/SBProcess.cpp +++ source/API/SBProcess.cpp @@ -39,6 +39,7 @@ #include "lldb/API/SBFileSpec.h" #include "lldb/API/SBMemoryRegionInfo.h" #include "lldb/API/SBMemoryRegionInfoList.h" +#include "lldb/API/SBStructuredData.h" #include "lldb/API/SBThread.h" #include "lldb/API/SBThreadCollection.h" #include "lldb/API/SBStream.h" @@ -1029,8 +1030,16 @@ SBProcess SBProcess::GetProcessFromEvent (const SBEvent &event) { - SBProcess process(Process::ProcessEventData::GetProcessFromEvent (event.get())); - return process; + ProcessSP process_sp = + Process::ProcessEventData::GetProcessFromEvent (event.get()); + if (!process_sp) + { + // StructuredData events also know the process they come from. + // Try that. + process_sp = EventDataStructuredData::GetProcessFromEvent(event.get()); + } + + return SBProcess(process_sp); } bool @@ -1039,10 +1048,26 @@ return Process::ProcessEventData::GetInterruptedFromEvent(event.get()); } +lldb::SBStructuredData +SBProcess::GetStructuredDataFromEvent (const lldb::SBEvent &event) +{ + return SBStructuredData(event.GetSP()); +} + bool SBProcess::EventIsProcessEvent (const SBEvent &event) { - return event.GetBroadcasterClass() == SBProcess::GetBroadcasterClass(); + return (event.GetBroadcasterClass() == SBProcess::GetBroadcasterClass()) && + !EventIsStructuredDataEvent (event); +} + +bool +SBProcess::EventIsStructuredDataEvent (const lldb::SBEvent &event) +{ + EventSP event_sp = event.GetSP(); + EventData *event_data = event_sp ? event_sp->GetData() : nullptr; + return event_data && + (event_data->GetFlavor() == EventDataStructuredData::GetFlavorString()); } SBBroadcaster Index: source/API/SBStructuredData.cpp =================================================================== --- /dev/null +++ source/API/SBStructuredData.cpp @@ -0,0 +1,165 @@ +//===-- SBStructuredData.cpp ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/API/SBStructuredData.h" + +#include "lldb/API/SBStream.h" +#include "lldb/Core/Error.h" +#include "lldb/Core/Event.h" +#include "lldb/Core/Stream.h" +#include "lldb/Core/StructuredData.h" +#include "lldb/Target/StructuredDataPlugin.h" + +using namespace lldb; +using namespace lldb_private; + +#pragma mark -- +#pragma mark Impl + +class SBStructuredData::Impl +{ +public: + + Impl() : + m_plugin_wp(), + m_data_sp() + { + } + + Impl(const Impl &rhs) = default; + + Impl(const EventSP &event_sp) : + m_plugin_wp(EventDataStructuredData::GetPluginFromEvent(event_sp.get())), + m_data_sp(EventDataStructuredData::GetObjectFromEvent(event_sp.get())) + { + } + + ~Impl() = default; + + Impl& + operator =(const Impl &rhs) = default; + + bool + IsValid() const + { + return m_data_sp.get() != nullptr; + } + + void + Clear() + { + m_plugin_wp.reset(); + m_data_sp.reset(); + } + + SBError + GetAsJSON(lldb::SBStream &stream) const + { + SBError sb_error; + + if (!m_data_sp) + { + sb_error.SetErrorString("No structured data."); + return sb_error; + } + + m_data_sp->Dump(stream.ref()); + return sb_error; + } + + lldb::SBError + PrettyPrint(lldb::SBStream &stream) const + { + SBError sb_error; + + if (!m_data_sp) + { + sb_error.SetErrorString("Cannot pretty print structured data: " + "no data to print."); + return sb_error; + } + + // Grab the plugin. + auto plugin_sp = StructuredDataPluginSP(m_plugin_wp); + if (!plugin_sp) + { + sb_error.SetErrorString("Cannot pretty print structured data: " + "plugin doesn't exist."); + return sb_error; + } + + // Pretty-print the data. + auto error = plugin_sp->PrettyPrintData(m_data_sp, stream.ref()); + if (!error.Success()) + sb_error.SetError(error); + + return sb_error; + } + +private: + + StructuredDataPluginWP m_plugin_wp; + StructuredData::ObjectSP m_data_sp; + +}; + +#pragma mark -- +#pragma mark SBStructuredData + + +SBStructuredData::SBStructuredData() : + m_impl_up(new Impl()) +{ +} + +SBStructuredData::SBStructuredData(const lldb::SBStructuredData &rhs) : + m_impl_up(new Impl(*rhs.m_impl_up.get())) +{ +} + +SBStructuredData::SBStructuredData(const lldb::EventSP &event_sp) : + m_impl_up(new Impl(event_sp)) +{ +} + +SBStructuredData::~SBStructuredData() +{ +} + +SBStructuredData & +SBStructuredData::operator =(const lldb::SBStructuredData &rhs) +{ + *m_impl_up = *rhs.m_impl_up; + return *this; +} + +bool +SBStructuredData::IsValid() const +{ + return m_impl_up->IsValid(); +} + +void +SBStructuredData::Clear() +{ + m_impl_up->Clear(); +} + +SBError +SBStructuredData::GetAsJSON(lldb::SBStream &stream) const +{ + return m_impl_up->GetAsJSON(stream); +} + +lldb::SBError +SBStructuredData::PrettyPrint(lldb::SBStream &stream) const +{ + return m_impl_up->PrettyPrint(stream); +} + Index: source/API/SystemInitializerFull.cpp =================================================================== --- source/API/SystemInitializerFull.cpp +++ source/API/SystemInitializerFull.cpp @@ -100,6 +100,7 @@ #include "Plugins/Process/mach-core/ProcessMachCore.h" #include "Plugins/SymbolVendor/MacOSX/SymbolVendorMacOSX.h" #endif +#include "Plugins/StructuredData/DarwinLog/StructuredDataDarwinLog.h" #if defined(__FreeBSD__) #include "Plugins/Process/FreeBSD/ProcessFreeBSD.h" @@ -381,6 +382,11 @@ PlatformRemoteAppleWatch::Initialize(); DynamicLoaderDarwinKernel::Initialize(); #endif + + // This plugin is valid on any host that talks to a Darwin remote. + // It shouldn't be limited to __APPLE__. + StructuredDataDarwinLog::Initialize(); + //---------------------------------------------------------------------- // Platform agnostic plugins //---------------------------------------------------------------------- @@ -513,6 +519,8 @@ platform_gdb_server::PlatformRemoteGDBServer::Terminate(); process_gdb_remote::ProcessGDBRemote::Terminate(); + StructuredDataDarwinLog::Terminate(); + DynamicLoaderMacOSXDYLD::Terminate(); DynamicLoaderMacOS::Terminate(); DynamicLoaderPOSIXDYLD::Terminate(); Index: source/Core/Debugger.cpp =================================================================== --- source/Core/Debugger.cpp +++ source/Core/Debugger.cpp @@ -56,6 +56,7 @@ #include "lldb/Target/RegisterContext.h" #include "lldb/Target/SectionLoadList.h" #include "lldb/Target/StopInfo.h" +#include "lldb/Target/StructuredDataPlugin.h" #include "lldb/Target/Target.h" #include "lldb/Target/Thread.h" #include "lldb/Utility/AnsiTerminal.h" @@ -1476,14 +1477,15 @@ return total_bytes; } - // This function handles events that were broadcast by the process. void Debugger::HandleProcessEvent (const EventSP &event_sp) { using namespace lldb; const uint32_t event_type = event_sp->GetType(); - ProcessSP process_sp = Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); + ProcessSP process_sp = (event_type == Process::eBroadcastBitStructuredData) + ? EventDataStructuredData::GetProcessFromEvent(event_sp.get()) + : Process::ProcessEventData::GetProcessFromEvent(event_sp.get()); StreamSP output_stream_sp = GetAsyncOutputStream(); StreamSP error_stream_sp = GetAsyncErrorStream(); @@ -1498,6 +1500,9 @@ const bool got_state_changed = (event_type & Process::eBroadcastBitStateChanged) != 0; const bool got_stdout = (event_type & Process::eBroadcastBitSTDOUT) != 0; const bool got_stderr = (event_type & Process::eBroadcastBitSTDERR) != 0; + const bool got_structured_data = (event_type & + Process::eBroadcastBitStructuredData) != 0; + if (got_state_changed) { StateType event_state = Process::ProcessEventData::GetStateFromEvent (event_sp.get()); @@ -1522,6 +1527,37 @@ GetProcessSTDERR (process_sp.get(), error_stream_sp.get()); } + // Give structured data events an opportunity to display. + if (got_structured_data) + { + StructuredDataPluginSP plugin_sp = + EventDataStructuredData::GetPluginFromEvent(event_sp.get()); + if (plugin_sp) + { + auto structured_data_sp = + EventDataStructuredData::GetObjectFromEvent(event_sp.get()); + if (plugin_sp->ShouldPrint(structured_data_sp) && + output_stream_sp) + { + Error error = plugin_sp->PrettyPrintData(structured_data_sp, + *output_stream_sp); + if (error.Success()) + { + // Add newline. + output_stream_sp->PutChar('\n'); + } + else + { + error_stream_sp->Printf("Failed to print structured " + "data with plugin %s: %s", + plugin_sp->GetPluginName() + .AsCString(), + error.AsCString()); + } + } + } + } + // Now display any stopped state changes after any STDIO if (got_state_changed && state_is_stopped) { @@ -1586,7 +1622,8 @@ BroadcastEventSpec process_event_spec (broadcaster_class_process, Process::eBroadcastBitStateChanged | Process::eBroadcastBitSTDOUT | - Process::eBroadcastBitSTDERR); + Process::eBroadcastBitSTDERR | + Process::eBroadcastBitStructuredData); BroadcastEventSpec thread_event_spec (broadcaster_class_thread, Thread::eBroadcastBitStackChanged | Index: source/Core/Event.cpp =================================================================== --- source/Core/Event.cpp +++ source/Core/Event.cpp @@ -25,6 +25,13 @@ using namespace lldb; using namespace lldb_private; +#pragma mark - +#pragma mark Event + +//------------------------------------------------------------------ +// Event functions +//------------------------------------------------------------------ + Event::Event (Broadcaster *broadcaster, uint32_t event_type, EventData *data) : m_broadcaster_wp(broadcaster->GetBroadcasterImpl()), m_type(event_type), @@ -101,6 +108,13 @@ m_data_sp->DoOnRemoval (this); } +#pragma mark - +#pragma mark EventData + +//------------------------------------------------------------------ +// EventData functions +//------------------------------------------------------------------ + EventData::EventData() = default; EventData::~EventData() = default; @@ -111,6 +125,14 @@ s->PutCString ("Generic Event Data"); } +#pragma mark - +#pragma mark EventDataBytes + +//------------------------------------------------------------------ +// EventDataBytes functions +//------------------------------------------------------------------ + + EventDataBytes::EventDataBytes () : m_bytes() { @@ -224,3 +246,150 @@ { m_bytes.swap (new_bytes); } + +#pragma mark - +#pragma mark EventStructuredData + +//------------------------------------------------------------------ +// EventDataStructuredData definitions +//------------------------------------------------------------------ + +EventDataStructuredData::EventDataStructuredData() : + EventData(), + m_process_sp(), + m_object_sp(), + m_plugin_sp() +{ +} + +EventDataStructuredData::EventDataStructuredData(const ProcessSP &process_sp, + const StructuredData::ObjectSP + &object_sp, + const + lldb::StructuredDataPluginSP + &plugin_sp) : + EventData(), + m_process_sp(process_sp), + m_object_sp(object_sp), + m_plugin_sp(plugin_sp) +{ +} + +EventDataStructuredData::~EventDataStructuredData() +{ +} + +//------------------------------------------------------------------ +// EventDataStructuredData member functions +//------------------------------------------------------------------ + +const ConstString & +EventDataStructuredData::GetFlavor() const +{ + return EventDataStructuredData::GetFlavorString(); +} + +void +EventDataStructuredData::Dump(Stream *s) const +{ + if (!s) + return; + + if (m_object_sp) + m_object_sp->Dump(*s); +} + +const ProcessSP& +EventDataStructuredData::GetProcess() const +{ + return m_process_sp; +} + +const StructuredData::ObjectSP& +EventDataStructuredData::GetObject() const +{ + return m_object_sp; +} + +const lldb::StructuredDataPluginSP& +EventDataStructuredData::GetStructuredDataPlugin() const +{ + return m_plugin_sp; +} + +void +EventDataStructuredData::SetProcess(const ProcessSP &process_sp) +{ + m_process_sp = process_sp; +} + +void +EventDataStructuredData::SetObject(const StructuredData::ObjectSP &object_sp) +{ + m_object_sp = object_sp; +} + +void +EventDataStructuredData::SetStructuredDataPlugin(const + lldb::StructuredDataPluginSP + &plugin_sp) +{ + m_plugin_sp = plugin_sp; +} + +//------------------------------------------------------------------ +// EventDataStructuredData static functions +//------------------------------------------------------------------ + +const EventDataStructuredData* +EventDataStructuredData::GetEventDataFromEvent(const Event *event_ptr) +{ + if (event_ptr == nullptr) + return nullptr; + + const EventData *event_data = event_ptr->GetData(); + if (!event_data || event_data->GetFlavor() != + EventDataStructuredData::GetFlavorString()) + return nullptr; + + return static_cast(event_data); +} + +ProcessSP +EventDataStructuredData::GetProcessFromEvent(const Event *event_ptr) +{ + auto event_data = EventDataStructuredData::GetEventDataFromEvent(event_ptr); + if (event_data) + return event_data->GetProcess(); + else + return ProcessSP(); +} + +StructuredData::ObjectSP +EventDataStructuredData::GetObjectFromEvent(const Event *event_ptr) +{ + auto event_data = EventDataStructuredData::GetEventDataFromEvent(event_ptr); + if (event_data) + return event_data->GetObject(); + else + return StructuredData::ObjectSP(); +} + +lldb::StructuredDataPluginSP +EventDataStructuredData::GetPluginFromEvent(const Event *event_ptr) +{ + auto event_data = EventDataStructuredData::GetEventDataFromEvent(event_ptr); + if (event_data) + return event_data->GetStructuredDataPlugin(); + else + return StructuredDataPluginSP(); +} + +const ConstString & +EventDataStructuredData::GetFlavorString () +{ + static ConstString s_flavor("EventDataStructuredData"); + return s_flavor; +} + + Index: source/Core/PluginManager.cpp =================================================================== --- source/Core/PluginManager.cpp +++ source/Core/PluginManager.cpp @@ -1917,6 +1917,146 @@ return none_instance(interpreter); } +#pragma mark - +#pragma mark StructuredDataPlugin + +// ----------------------------------------------------------------------------- +// StructuredDataPlugin +// ----------------------------------------------------------------------------- + +struct StructuredDataPluginInstance +{ + StructuredDataPluginInstance() : + name(), + description(), + create_callback(nullptr), + debugger_init_callback(nullptr), + filter_callback(nullptr) + { + } + + ConstString name; + std::string description; + StructuredDataPluginCreateInstance create_callback; + DebuggerInitializeCallback debugger_init_callback; + StructuredDataFilterLaunchInfo filter_callback; +}; + +typedef std::vector StructuredDataPluginInstances; + +static std::recursive_mutex & +GetStructuredDataPluginMutex() +{ + static std::recursive_mutex g_instances_mutex; + return g_instances_mutex; +} + +static StructuredDataPluginInstances & +GetStructuredDataPluginInstances () +{ + static StructuredDataPluginInstances g_instances; + return g_instances; +} + +bool +PluginManager::RegisterPlugin(const ConstString &name, + const char *description, + StructuredDataPluginCreateInstance + create_callback, + DebuggerInitializeCallback debugger_init_callback, + StructuredDataFilterLaunchInfo filter_callback) +{ + if (create_callback) + { + StructuredDataPluginInstance instance; + assert((bool)name); + instance.name = name; + if (description && description[0]) + instance.description = description; + instance.create_callback = create_callback; + instance.debugger_init_callback = debugger_init_callback; + instance.filter_callback = filter_callback; + std::lock_guard guard( + GetStructuredDataPluginMutex()); + GetStructuredDataPluginInstances().push_back(instance); + } + return false; +} + +bool +PluginManager::UnregisterPlugin(StructuredDataPluginCreateInstance create_callback) +{ + if (create_callback) + { + std::lock_guard guard( + GetStructuredDataPluginMutex()); + StructuredDataPluginInstances &instances = + GetStructuredDataPluginInstances(); + + StructuredDataPluginInstances::iterator pos, end = instances.end(); + for (pos = instances.begin(); pos != end; ++ pos) + { + if (pos->create_callback == create_callback) + { + instances.erase(pos); + return true; + } + } + } + return false; +} + +StructuredDataPluginCreateInstance +PluginManager::GetStructuredDataPluginCreateCallbackAtIndex(uint32_t idx) +{ + std::lock_guard guard(GetStructuredDataPluginMutex()); + StructuredDataPluginInstances &instances = + GetStructuredDataPluginInstances(); + if (idx < instances.size()) + return instances[idx].create_callback; + return nullptr; +} + +StructuredDataPluginCreateInstance +PluginManager::GetStructuredDataPluginCreateCallbackForPluginName( + const ConstString &name) +{ + if (name) + { + std::lock_guard guard( + GetStructuredDataPluginMutex()); + StructuredDataPluginInstances &instances = + GetStructuredDataPluginInstances(); + + StructuredDataPluginInstances::iterator pos, end = instances.end(); + for (pos = instances.begin(); pos != end; ++ pos) + { + if (name == pos->name) + return pos->create_callback; + } + } + return nullptr; +} + +StructuredDataFilterLaunchInfo +PluginManager::GetStructuredDataFilterCallbackAtIndex(uint32_t idx, + bool &iteration_complete) +{ + std::lock_guard guard(GetStructuredDataPluginMutex()); + StructuredDataPluginInstances &instances = + GetStructuredDataPluginInstances(); + if (idx < instances.size()) + { + iteration_complete = false; + return instances[idx].filter_callback; + } + else + { + iteration_complete = true; + } + return nullptr; +} + #pragma mark SymbolFile struct SymbolFileInstance @@ -2772,6 +2912,17 @@ os.debugger_init_callback(debugger); } } + + // Initialize the StructuredDataPlugin plugins + { + std::lock_guard + guard(GetStructuredDataPluginMutex()); + for (auto &plugin: GetStructuredDataPluginInstances()) + { + if (plugin.debugger_init_callback) + plugin.debugger_init_callback(debugger); + } + } } // This is the preferred new way to register plugin specific settings. e.g. @@ -2905,6 +3056,7 @@ const char* kProcessPluginName("process"); const char* kSymbolFilePluginName("symbol-file"); const char* kJITLoaderPluginName("jit-loader"); +const char* kStructuredDataPluginName("structured-data"); } // anonymous namespace @@ -3049,3 +3201,24 @@ } return false; } + +lldb::OptionValuePropertiesSP +PluginManager::GetSettingForStructuredDataPlugin(Debugger &debugger, + const ConstString &setting_name) +{ + return GetSettingForPlugin(debugger, setting_name, ConstString(kStructuredDataPluginName)); +} + +bool +PluginManager::CreateSettingForStructuredDataPlugin(Debugger &debugger, + const lldb::OptionValuePropertiesSP &properties_sp, + const ConstString &description, + bool is_global_property) +{ + return CreateSettingForPlugin(debugger, + ConstString(kStructuredDataPluginName), + ConstString("Settings for structured data plug-ins"), + properties_sp, + description, + is_global_property); +} Index: source/Interpreter/Args.cpp =================================================================== --- source/Interpreter/Args.cpp +++ source/Interpreter/Args.cpp @@ -1170,8 +1170,48 @@ } } +void +Args::AddOrReplaceEnvironmentVariable(const char *env_var_name, + const char *new_value) +{ + if (!env_var_name || !new_value) + return; + + // Build the new entry. + StreamString stream; + stream << env_var_name; + stream << '='; + stream << new_value; + stream.Flush(); + + // Find the environment variable if present and replace it. + for (size_t i = 0; i < GetArgumentCount(); ++i) + { + // Get the env var value. + const char *arg_value = GetArgumentAtIndex(i); + if (!arg_value) + continue; + + // Find the name of the env var: before the first =. + auto equal_p = strchr(arg_value, '='); + if (!equal_p) + continue; + + // Check if the name matches the given env_var_name. + if (strncmp(env_var_name, arg_value, equal_p - arg_value) == 0) + { + ReplaceArgumentAtIndex(i, stream.GetString().c_str()); + return; + } + } + + // We didn't find it. Append it instead. + AppendArgument(stream.GetString().c_str()); +} + bool -Args::ContainsEnvironmentVariable(const char *env_var_name) const +Args::ContainsEnvironmentVariable(const char *env_var_name, + size_t *argument_index) const { // Validate args. if (!env_var_name) @@ -1193,6 +1233,8 @@ equal_p - argument_value) == 0) { // We matched. + if (argument_index) + *argument_index = i; return true; } } @@ -1202,6 +1244,8 @@ if (strcmp(argument_value, env_var_name) == 0) { // We matched. + if (argument_index) + *argument_index = i; return true; } } Index: source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h +++ source/Plugins/Process/gdb-remote/GDBRemoteClientBase.h @@ -31,6 +31,16 @@ HandleAsyncMisc(llvm::StringRef data) = 0; virtual void HandleStopReply() = 0; + + // + /// Processes async structured data. + /// + /// @return + /// true if the data was handled; otherwise, false. + // + virtual bool + HandleAsyncStructuredData(const StructuredData::ObjectSP + &object_sp) = 0; }; GDBRemoteClientBase(const char *comm_name, const char *listener_name); Index: source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteClientBase.cpp @@ -11,6 +11,7 @@ #include "llvm/ADT/StringExtras.h" +#include "lldb/Target/Process.h" #include "lldb/Target/UnixSignals.h" #include "lldb/Utility/LLDBAssert.h" @@ -100,6 +101,41 @@ case 'A': delegate.HandleAsyncMisc(llvm::StringRef(response.GetStringRef()).substr(1)); break; + + case 'J': + // Asynchronous JSON packet, destined for a + // StructuredDataPlugin. + { + // Parse the content into a StructuredData instance. + auto payload_index = strlen("JSON-async:"); + StructuredData::ObjectSP json_sp = + StructuredData::ParseJSON(response.GetStringRef() + .substr(payload_index)); + if (log) + { + if (json_sp) + log->Printf( + "GDBRemoteCommmunicationClientBase::%s() " + "received Async StructuredData packet: %s", + __FUNCTION__, + response.GetStringRef(). + substr(payload_index).c_str()); + else + log->Printf("GDBRemoteCommmunicationClientBase::%s" + "() received StructuredData packet:" + " parse failure", __FUNCTION__); + } + + // Pass the data to the process to route to the + // appropriate plugin. The plugin controls what happens + // to it from there. + bool routed = delegate.HandleAsyncStructuredData(json_sp); + if (log) + log->Printf("GDBRemoteCommmunicationClientBase::%s()" + " packet %s", __FUNCTION__, + routed ? "handled" : "not handled"); + break; + } case 'T': case 'S': // Do this with the continue lock held. Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.h @@ -547,6 +547,53 @@ void ServeSymbolLookups(lldb_private::Process *process); + //------------------------------------------------------------------ + /// Return the feature set supported by the gdb-remote server. + /// + /// This method returns the remote side's response to the qSupported + /// packet. The response is the complete string payload returned + /// to the client. + /// + /// @return + /// The string returned by the server to the qSupported query. + //------------------------------------------------------------------ + const std::string& + GetServerSupportedFeatures() const + { + return m_qSupported_response; + } + + //------------------------------------------------------------------ + /// Return the array of async JSON packet types supported by the remote. + /// + /// This method returns the remote side's array of supported JSON + /// packet types as a list of type names. Each of the results are + /// expected to have an Enable{type_name} command to enable and configure + /// the related feature. Each type_name for an enabled feature will + /// possibly send async-style packets that contain a payload of a + /// binhex-encoded JSON dictionary. The dictionary will have a + /// string field named 'type', that contains the type_name of the + /// supported packet type. + /// + /// There is a Plugin category called structured-data plugins. + /// A plugin indicates whether it knows how to handle a type_name. + /// If so, it can be used to process the async JSON packet. + /// + /// @return + /// The string returned by the server to the qSupported query. + //------------------------------------------------------------------ + lldb_private::StructuredData::Array* + GetSupportedStructuredDataPlugins(); + + //------------------------------------------------------------------ + /// Configure a StructuredData feature on the remote end. + /// + /// @see \b Process::ConfigureStructuredData(...) for details. + //------------------------------------------------------------------ + Error + ConfigureRemoteStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP &config_sp); + protected: LazyBool m_supports_not_sending_acks; LazyBool m_supports_thread_suffix; @@ -617,6 +664,10 @@ uint32_t m_gdb_server_version; // from reply to qGDBServerVersion, zero if qGDBServerVersion is not supported uint32_t m_default_packet_timeout; uint64_t m_max_packet_size; // as returned by qSupported + std::string m_qSupported_response; // the complete response to qSupported + + bool m_supported_async_json_packets_is_valid; + lldb_private::StructuredData::ObjectSP m_supported_async_json_packets_sp; bool GetCurrentProcessInfo (bool allow_lazy_pid = true); Index: source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp =================================================================== --- source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp +++ source/Plugins/Process/gdb-remote/GDBRemoteCommunicationClient.cpp @@ -30,6 +30,8 @@ #include "lldb/Interpreter/Args.h" #include "lldb/Symbol/Symbol.h" #include "lldb/Target/MemoryRegionInfo.h" +#include "lldb/Target/UnixSignals.h" +#include "lldb/Utility/LLDBAssert.h" #include "lldb/Target/Target.h" // Project includes @@ -114,7 +116,10 @@ m_gdb_server_name(), m_gdb_server_version(UINT32_MAX), m_default_packet_timeout(0), - m_max_packet_size(0) + m_max_packet_size(0), + m_qSupported_response(), + m_supported_async_json_packets_is_valid(false), + m_supported_async_json_packets_sp() { } @@ -378,6 +383,9 @@ m_gdb_server_version = UINT32_MAX; m_default_packet_timeout = 0; m_max_packet_size = 0; + m_qSupported_response.clear(); + m_supported_async_json_packets_is_valid = false; + m_supported_async_json_packets_sp.reset(); } // These flags should be reset when we first connect to a GDB server @@ -413,6 +421,12 @@ /*send_async=*/false) == PacketResult::Success) { const char *response_cstr = response.GetStringRef().c_str(); + + // Hang on to the qSupported packet, so that platforms can do custom + // configuration of the transport before attaching/launching the + // process. + m_qSupported_response = response_cstr; + if (::strstr (response_cstr, "qXfer:auxv:read+")) m_supports_qXfer_auxv_read = eLazyBoolYes; if (::strstr (response_cstr, "qXfer:libraries-svr4:read+")) @@ -3906,6 +3920,126 @@ } } +StructuredData::Array* +GDBRemoteCommunicationClient::GetSupportedStructuredDataPlugins() +{ + if (!m_supported_async_json_packets_is_valid) + { + // Query the server for the array of supported asynchronous JSON + // packets. + m_supported_async_json_packets_is_valid = true; + + Log *log(ProcessGDBRemoteLog::GetLogIfAllCategoriesSet( + GDBR_LOG_PROCESS)); + + // Poll it now. + StringExtractorGDBRemote response; + const bool send_async = false; + if (SendPacketAndWaitForResponse("qStructuredDataPlugins", response, + send_async) == PacketResult::Success) + { + m_supported_async_json_packets_sp = StructuredData::ParseJSON( + response.GetStringRef()); + if (m_supported_async_json_packets_sp && + !m_supported_async_json_packets_sp->GetAsArray()) + { + // We were returned something other than a JSON array. This + // is invalid. Clear it out. + if (log) + log->Printf("GDBRemoteCommunicationClient::%s(): " + "QSupportedAsyncJSONPackets returned invalid " + "result: %s", __FUNCTION__, + response.GetStringRef().c_str()); + m_supported_async_json_packets_sp.reset(); + } + } + else + { + if (log) + log->Printf("GDBRemoteCommunicationClient::%s(): " + "QSupportedAsyncJSONPackets unsupported", + __FUNCTION__); + } + + if (log && m_supported_async_json_packets_is_valid) + { + StreamString stream; + m_supported_async_json_packets_sp->Dump(stream); + log->Printf("GDBRemoteCommunicationClient::%s(): supported async " + "JSON packets: %s", __FUNCTION__, + stream.GetString().c_str()); + } + } + + return m_supported_async_json_packets_sp + ? m_supported_async_json_packets_sp->GetAsArray() + : nullptr; +} + +Error +GDBRemoteCommunicationClient::ConfigureRemoteStructuredData( + const ConstString &type_name, + const StructuredData::ObjectSP &config_sp) +{ + Error error; + + if (type_name.GetLength() == 0) + { + error.SetErrorString("invalid type_name argument"); + return error; + } + + // Build command: Configure{type_name}: serialized config + // data. + StreamGDBRemote stream; + stream.PutCString("QConfigure"); + stream.PutCString(type_name.AsCString()); + stream.PutChar(':'); + if (config_sp) + { + // Gather the plain-text version of the configuration data. + StreamString unescaped_stream; + config_sp->Dump(unescaped_stream); + unescaped_stream.Flush(); + + // Add it to the stream in escaped fashion. + stream.PutEscapedBytes(unescaped_stream.GetData(), + unescaped_stream.GetSize()); + } + stream.Flush(); + + // Send the packet. + const bool send_async = false; + StringExtractorGDBRemote response; + auto result = SendPacketAndWaitForResponse(stream.GetString().c_str(), + response, send_async); + if (result == PacketResult::Success) + { + // We failed if the config result comes back other than OK. + if (strcmp(response.GetStringRef().c_str(), "OK") == 0) + { + // Okay! + error.Clear(); + } + else + { + error.SetErrorStringWithFormat("configuring StructuredData feature " + "%s failed with error %s", + type_name.AsCString(), + response.GetStringRef().c_str()); + } + } + else + { + // Can we get more data here on the failure? + error.SetErrorStringWithFormat("configuring StructuredData feature %s " + "failed when sending packet: " + "PacketResult=%d", type_name.AsCString(), + result); + } + return error; +} + void GDBRemoteCommunicationClient::OnRunPacketSent(bool first) { Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.h =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.h +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.h @@ -261,6 +261,10 @@ StructuredData::ObjectSP GetLoadedDynamicLibrariesInfos (lldb::addr_t image_list_address, lldb::addr_t image_count) override; + Error + ConfigureStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP &config_sp) override; + StructuredData::ObjectSP GetLoadedDynamicLibrariesInfos () override; @@ -506,6 +510,9 @@ HandleAsyncMisc(llvm::StringRef data) override; void HandleStopReply() override; + bool + HandleAsyncStructuredData(const StructuredData::ObjectSP + &object_sp) override; DISALLOW_COPY_AND_ASSIGN (ProcessGDBRemote); }; Index: source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp =================================================================== --- source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp +++ source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp @@ -1175,7 +1175,7 @@ { Log *log (ProcessGDBRemoteLog::GetLogIfAllCategoriesSet (GDBR_LOG_PROCESS)); if (log) - log->Printf ("ProcessGDBRemote::DidLaunch()"); + log->Printf ("ProcessGDBRemote::%s()", __FUNCTION__); if (GetID() != LLDB_INVALID_PROCESS_ID) { BuildDynamicRegisterInfo (false); @@ -1271,6 +1271,13 @@ GetTarget().SetArchitecture (process_arch); } } + + // Find out which StructuredDataPlugins are supported by the + // debug monitor. These plugins transmit data over async $J packets. + auto supported_packets_array = + m_gdb_comm.GetSupportedStructuredDataPlugins(); + if (supported_packets_array) + MapSupportedStructuredDataPlugins(*supported_packets_array); } } @@ -4342,6 +4349,13 @@ return object_sp; } +Error +ProcessGDBRemote::ConfigureStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP + &config_sp) +{ + return m_gdb_comm.ConfigureRemoteStructuredData(type_name, config_sp); +} // Establish the largest memory read/write payloads we should use. // If the remote stub has a max packet size, stay under that size. @@ -5235,6 +5249,13 @@ BuildDynamicRegisterInfo(true); } +bool +ProcessGDBRemote::HandleAsyncStructuredData(const StructuredData::ObjectSP + &object_sp) +{ + return RouteAsyncStructuredData(object_sp); +} + class CommandObjectProcessGDBRemoteSpeedTest: public CommandObjectParsed { public: Index: source/Plugins/StructuredData/CMakeLists.txt =================================================================== --- /dev/null +++ source/Plugins/StructuredData/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(DarwinLog) + Index: source/Plugins/StructuredData/DarwinLog/CMakeLists.txt =================================================================== --- /dev/null +++ source/Plugins/StructuredData/DarwinLog/CMakeLists.txt @@ -0,0 +1,5 @@ +list(APPEND SOURCES + StructuredDataDarwinLog.cpp + ) + +add_lldb_library(lldbPluginStructuredDataDarwinLog ${SOURCES}) Index: source/Plugins/StructuredData/DarwinLog/StructuredDataDarwinLog.h =================================================================== --- /dev/null +++ source/Plugins/StructuredData/DarwinLog/StructuredDataDarwinLog.h @@ -0,0 +1,167 @@ +//===-- StructuredDataDarwinLog.h -------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#ifndef StructuredDataDarwinLog_h +#define StructuredDataDarwinLog_h + +#include "lldb/Target/StructuredDataPlugin.h" + +#include + +// Forward declarations +namespace sddarwinlog_private +{ + class EnableCommand; +} + +namespace lldb_private +{ + +class StructuredDataDarwinLog : public StructuredDataPlugin +{ + friend sddarwinlog_private::EnableCommand; + +public: + + // ------------------------------------------------------------------------- + // Public static API + // ------------------------------------------------------------------------- + + static void + Initialize(); + + static void + Terminate(); + + static const ConstString& + GetStaticPluginName(); + + // ------------------------------------------------------------------------- + /// Return whether the DarwinLog functionality is enabled. + /// + /// The DarwinLog functionality is enabled if the user expicitly enabled + /// it with the enable command, or if the user has the setting set + /// that controls if we always enable it for newly created/attached + /// processes. + /// + /// @return + /// True if DarwinLog support is/will be enabled for existing or + /// newly launched/attached processes. + // ------------------------------------------------------------------------- + static bool + IsEnabled(); + + // ------------------------------------------------------------------------- + // PluginInterface API + // ------------------------------------------------------------------------- + + ConstString + GetPluginName() override; + + uint32_t + GetPluginVersion() override; + + // ------------------------------------------------------------------------- + // StructuredDataPlugin API + // ------------------------------------------------------------------------- + + bool + SupportsStructuredDataType(const ConstString &type_name) override; + + void + HandleArrivalOfStructuredData(Process &process, + const ConstString &type_name, + const StructuredData::ObjectSP + &object_sp) override; + + bool + ShouldPrint(const StructuredData::ObjectSP &object_sp) const override; + + Error + PrettyPrintData(const StructuredData::ObjectSP &object_sp, + lldb_private::Stream &stream) override; + + + bool + GetEnabled(const ConstString &type_name) const override; + + void + ModulesDidLoad(Process &process, ModuleList &module_list) override; + +private: + + // ------------------------------------------------------------------------- + // Private constructors + // ------------------------------------------------------------------------- + + StructuredDataDarwinLog(const lldb::ProcessWP &process_wp); + + // ------------------------------------------------------------------------- + // Private static methods + // ------------------------------------------------------------------------- + + static lldb::StructuredDataPluginSP + CreateInstance(Process &process); + + static void + DebuggerInitialize(Debugger &debugger); + + static bool + InitCompletionHookCallback(void *baton, StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id); + + static Error + FilterLaunchInfo(ProcessLaunchInfo &launch_info, Target *target); + + // ------------------------------------------------------------------------- + // Internal helper methods used by friend classes + // ------------------------------------------------------------------------- + void + SetEnabled(bool enabled); + + void + AddInitCompletionHook(Process &process); + + // ------------------------------------------------------------------------- + // Private methods + // ------------------------------------------------------------------------- + + void + DumpTimestamp(Stream &stream, uint64_t timestamp); + + size_t + DumpHeader(Stream &stream, const StructuredData::Dictionary &event); + + size_t + HandleDisplayOfEvent(const StructuredData::Dictionary &event, + Stream &stream); + + // ------------------------------------------------------------------------- + /// Call the enable command again, using whatever settings were initially + /// made. + // ------------------------------------------------------------------------- + + void + EnableNow(); + + // ------------------------------------------------------------------------- + // Private data + // ------------------------------------------------------------------------- + bool m_recorded_first_timestamp; + uint64_t m_first_timestamp_seen; + bool m_is_enabled; + std::mutex m_added_breakpoint_mutex; + bool m_added_breakpoint; +}; + +} + +#endif /* StructuredDataPluginDarwinLog_hpp */ Index: source/Plugins/StructuredData/DarwinLog/StructuredDataDarwinLog.cpp =================================================================== --- /dev/null +++ source/Plugins/StructuredData/DarwinLog/StructuredDataDarwinLog.cpp @@ -0,0 +1,2451 @@ +//===-- StructuredDataDarwinLog.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StructuredDataDarwinLog.h" + +// C includes +#include + +// C++ includes +#include + +#include "lldb/Breakpoint/StoppointCallbackContext.h" +#include "lldb/Core/Debugger.h" +#include "lldb/Core/Log.h" +#include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Core/RegularExpression.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" +#include "lldb/Interpreter/CommandReturnObject.h" +#include "lldb/Interpreter/OptionValueProperties.h" +#include "lldb/Interpreter/OptionValueString.h" +#include "lldb/Interpreter/Property.h" +#include "lldb/Target/Process.h" +#include "lldb/Target/Target.h" +#include "lldb/Target/ThreadPlanCallOnFunctionExit.h" + +#define DARWIN_LOG_TYPE_VALUE "DarwinLog" + +using namespace lldb; +using namespace lldb_private; + +#pragma mark - +#pragma mark Anonymous Namespace + +// ----------------------------------------------------------------------------- +// Anonymous namespace +// ----------------------------------------------------------------------------- + +namespace sddarwinlog_private +{ + const uint64_t NANOS_PER_MICRO = 1000; + const uint64_t NANOS_PER_MILLI = NANOS_PER_MICRO * 1000; + const uint64_t NANOS_PER_SECOND = NANOS_PER_MILLI * 1000; + const uint64_t NANOS_PER_MINUTE = NANOS_PER_SECOND * 60; + const uint64_t NANOS_PER_HOUR = NANOS_PER_MINUTE * 60; + + static bool DEFAULT_FILTER_FALLTHROUGH_ACCEPTS = true; + + //------------------------------------------------------------------ + /// Global, sticky enable switch. If true, the user has explicitly + /// run the enable command. When a process launches or is attached to, + /// we will enable DarwinLog if either the settings for auto-enable is + /// on, or if the user had explicitly run enable at some point prior + /// to the launch/attach. + //------------------------------------------------------------------ + static bool s_is_explicitly_enabled; + + class EnableOptions; + using EnableOptionsSP = std::shared_ptr; + + using OptionsMap = std::map>; + + static OptionsMap& + GetGlobalOptionsMap() + { + static OptionsMap s_options_map; + return s_options_map; + } + + static std::mutex& + GetGlobalOptionsMapLock() + { + static std::mutex s_options_map_lock; + return s_options_map_lock; + } + + EnableOptionsSP + GetGlobalEnableOptions(const DebuggerSP &debugger_sp) + { + if (!debugger_sp) + return EnableOptionsSP(); + + std::lock_guard locker(GetGlobalOptionsMapLock()); + OptionsMap &options_map = GetGlobalOptionsMap(); + DebuggerWP debugger_wp(debugger_sp); + auto find_it = options_map.find(debugger_wp); + if (find_it != options_map.end()) + return find_it->second; + else + return EnableOptionsSP(); + } + + void + SetGlobalEnableOptions(const DebuggerSP &debugger_sp, + const EnableOptionsSP &options_sp) + { + std::lock_guard locker(GetGlobalOptionsMapLock()); + OptionsMap &options_map = GetGlobalOptionsMap(); + DebuggerWP debugger_wp(debugger_sp); + auto find_it = options_map.find(debugger_wp); + if (find_it != options_map.end()) + find_it->second = options_sp; + else + options_map.insert(std::make_pair(debugger_wp, options_sp)); + } + +#pragma mark - +#pragma mark Settings Handling + + //------------------------------------------------------------------ + /// Code to handle the StructuredDataDarwinLog settings + //------------------------------------------------------------------ + + static PropertyDefinition + g_properties[] = + { + { + "enable-on-startup" , // name + OptionValue::eTypeBoolean, // type + true, // global + false, // default uint value + nullptr, // default cstring value + nullptr, // enum values + "Enable Darwin os_log collection when debugged process is launched " + "or attached." // description + }, + { + "auto-enable-options" , // name + OptionValue::eTypeString, // type + true, // global + 0, // default uint value + "", // default cstring value + nullptr, // enum values + "Specify the options to 'plugin structured-data darwin-log enable' " + "that should be applied when automatically enabling logging on " + "startup/attach." // description + }, + // Last entry sentinel. + { + nullptr, + OptionValue::eTypeInvalid, + false, + 0 , + nullptr, + nullptr, + nullptr + } + }; + + enum { + ePropertyEnableOnStartup = 0, + ePropertyAutoEnableOptions = 1 + }; + + class StructuredDataDarwinLogProperties : public Properties + { + public: + + static ConstString & + GetSettingName () + { + static ConstString g_setting_name("darwin-log"); + return g_setting_name; + } + + StructuredDataDarwinLogProperties() : + Properties () + { + m_collection_sp.reset(new OptionValueProperties(GetSettingName())); + m_collection_sp->Initialize(g_properties); + } + + virtual + ~StructuredDataDarwinLogProperties() + { + } + + bool + GetEnableOnStartup() const + { + const uint32_t idx = ePropertyEnableOnStartup; + return m_collection_sp->GetPropertyAtIndexAsBoolean(nullptr, idx, + g_properties[idx].default_uint_value != 0); + } + + const char* + GetAutoEnableOptions() const + { + const uint32_t idx = ePropertyAutoEnableOptions; + return m_collection_sp->GetPropertyAtIndexAsString(nullptr, idx, + g_properties[idx].default_cstr_value); + } + + const char* + GetLoggingModuleName() const + { + return "libsystem_trace.dylib"; + } + }; + + using StructuredDataDarwinLogPropertiesSP = + std::shared_ptr; + + static const StructuredDataDarwinLogPropertiesSP& + GetGlobalProperties() + { + static StructuredDataDarwinLogPropertiesSP g_settings_sp; + if (!g_settings_sp) + g_settings_sp.reset(new StructuredDataDarwinLogProperties()); + return g_settings_sp; + } + + const char *const s_filter_attributes[] = + { + "activity", // current activity + "activity-chain", // entire activity chain, each level separated by ':' + "category", // category of the log message + "message", // message contents, fully expanded + "subsystem" // subsystem of the log message + + // Consider impelmenting this action as it would be cheaper to filter. + // "message" requires always formatting the message, which is a waste + // of cycles if it ends up being rejected. + // "format", // format string used to format message text + }; + + static const ConstString& + GetDarwinLogTypeName() + { + static const ConstString s_key_name("DarwinLog"); + return s_key_name; + } + + static const ConstString& + GetLogEventType() + { + static const ConstString s_event_type("log"); + return s_event_type; + } + + class FilterRule; + using FilterRuleSP = std::shared_ptr; + + class FilterRule + { + public: + + virtual + ~FilterRule() + { + } + + using OperationCreationFunc = + std::function; + + static void + RegisterOperation(const ConstString &operation, + const OperationCreationFunc &creation_func) + { + GetCreationFuncMap().insert(std::make_pair(operation, + creation_func)); + } + + static + FilterRuleSP CreateRule(bool match_accepts, size_t attribute, + const ConstString &operation, + const std::string &op_arg, Error &error) + { + // Find the creation func for this type of filter rule. + auto map = GetCreationFuncMap(); + auto find_it = map.find(operation); + if (find_it == map.end()) + { + error.SetErrorStringWithFormat("unknown filter operation \"" + "%s\"", + operation.GetCString()); + return FilterRuleSP(); + } + + return find_it->second(match_accepts, attribute, op_arg, error); + } + + StructuredData::ObjectSP Serialize() const + { + StructuredData::Dictionary *dict_p = + new StructuredData::Dictionary(); + + // Indicate whether this is an accept or reject rule. + dict_p->AddBooleanItem("accept", m_accept); + + // Indicate which attribute of the message this filter references. + // This can drop into the rule-specific DoSerialization if we get + // to the point where not all FilterRule derived classes work on + // an attribute. (e.g. logical and/or and other compound + // operations). + dict_p->AddStringItem("attribute", + s_filter_attributes[m_attribute_index]); + + // Indicate the type of the rule. + dict_p->AddStringItem("type", GetOperationType().GetCString()); + + // Let the rule add its own specific details here. + DoSerialization(*dict_p); + + return StructuredData::ObjectSP(dict_p); + } + + virtual void + Dump(Stream &stream) const = 0; + + const ConstString& + GetOperationType() const + { + return m_operation; + } + + protected: + + FilterRule(bool accept, size_t attribute_index, + const ConstString &operation) : + m_accept(accept), + m_attribute_index(attribute_index), + m_operation(operation) + { + } + + virtual void + DoSerialization(StructuredData::Dictionary &dict) const = 0; + + bool + GetMatchAccepts() const + { + return m_accept; + } + + const char* + GetFilterAttribute() const + { + return s_filter_attributes[m_attribute_index]; + } + + private: + + using CreationFuncMap = std::map; + + static CreationFuncMap& + GetCreationFuncMap() + { + static CreationFuncMap s_map; + return s_map; + } + + const bool m_accept; + const size_t m_attribute_index; + const ConstString m_operation; + + }; + + using FilterRules = std::vector; + + class RegexFilterRule : public FilterRule + { + public: + + static void + RegisterOperation() + { + FilterRule::RegisterOperation(StaticGetOperation(), + CreateOperation); + } + + void + Dump(Stream &stream) const override + { + stream.Printf("%s %s regex %s", + GetMatchAccepts() ? "accept" : "reject", + GetFilterAttribute(), m_regex_text.c_str()); + } + + protected: + + void + DoSerialization(StructuredData::Dictionary &dict) const override + { + dict.AddStringItem("regex", m_regex_text); + } + + private: + + static FilterRuleSP + CreateOperation(bool accept, size_t attribute_index, + const std::string &op_arg, Error &error) + { + // We treat the op_arg as a regex. Validate it. + if (op_arg.empty()) + { + error.SetErrorString("regex filter type requires a regex " + "argument"); + return FilterRuleSP(); + } + + // Instantiate the regex so we can report any errors. + auto regex = RegularExpression(op_arg.c_str()); + if (!regex.IsValid()) + { + char error_text[256]; + error_text[0] = '\0'; + regex.GetErrorAsCString(error_text, sizeof(error_text)); + error.SetErrorString(error_text); + return FilterRuleSP(); + } + + // We passed all our checks, this appears fine. + error.Clear(); + return FilterRuleSP(new RegexFilterRule(accept, attribute_index, + op_arg)); + } + + static const ConstString & + StaticGetOperation() + { + static ConstString s_operation("regex"); + return s_operation; + } + + RegexFilterRule(bool accept, size_t attribute_index, + const std::string ®ex_text) : + FilterRule(accept, attribute_index, StaticGetOperation()), + m_regex_text(regex_text) + { + } + + const std::string m_regex_text; + }; + + class ExactMatchFilterRule : public FilterRule + { + public: + + static void + RegisterOperation() + { + FilterRule::RegisterOperation(StaticGetOperation(), + CreateOperation); + } + + void + Dump(Stream &stream) const override + { + stream.Printf("%s %s match %s", + GetMatchAccepts() ? "accept" : "reject", + GetFilterAttribute(), m_match_text.c_str()); + } + + protected: + + void + DoSerialization(StructuredData::Dictionary &dict) const override + { + dict.AddStringItem("exact_text", m_match_text); + } + + private: + + static FilterRuleSP + CreateOperation(bool accept, size_t attribute_index, + const std::string &op_arg, Error &error) + { + if (op_arg.empty()) + { + error.SetErrorString("exact match filter type requires an " + "argument containing the text that must " + "match the specified message attribute."); + return FilterRuleSP(); + } + + error.Clear(); + return FilterRuleSP(new ExactMatchFilterRule(accept, + attribute_index, + op_arg)); + } + + static const ConstString & + StaticGetOperation() + { + static ConstString s_operation("match"); + return s_operation; + } + + ExactMatchFilterRule(bool accept, size_t attribute_index, + const std::string &match_text) : + FilterRule(accept, attribute_index, StaticGetOperation()), + m_match_text(match_text) + { + } + + const std::string m_match_text; + }; + + static void + RegisterFilterOperations() + { + ExactMatchFilterRule::RegisterOperation(); + RegexFilterRule::RegisterOperation(); + } + + // ========================================================================= + // Commands + // ========================================================================= + + // ------------------------------------------------------------------------- + /// Provides the main on-off switch for enabling darwin logging. + /// + /// It is valid to run the enable command when logging is already enabled. + /// This resets the logging with whatever settings are currently set. + // ------------------------------------------------------------------------- + class EnableOptions : public Options + { + public: + + EnableOptions() : + Options(), + m_include_debug_level(false), + m_include_info_level(false), + m_include_any_process(false), + m_filter_fall_through_accepts(DEFAULT_FILTER_FALLTHROUGH_ACCEPTS), + m_echo_to_stderr(false), + m_display_timestamp_relative(false), + m_display_subsystem(false), + m_display_category(false), + m_display_activity_chain(false), + m_broadcast_events(true), + m_live_stream(true), + m_print(true), + m_filter_rules() + { + } + + void + OptionParsingStarting (ExecutionContext *execution_context) override + { + m_include_debug_level = false; + m_include_info_level = false; + m_include_any_process = false; + m_filter_fall_through_accepts = DEFAULT_FILTER_FALLTHROUGH_ACCEPTS; + m_echo_to_stderr = false; + m_display_timestamp_relative = false; + m_display_subsystem = false; + m_display_category = false; + m_display_activity_chain = false; + m_broadcast_events = true; + m_live_stream = true; + m_print = true; + m_filter_rules.clear(); + } + + Error + SetOptionValue (uint32_t option_idx, const char *option_arg, + ExecutionContext *execution_context) override + { + Error error; + + const int short_option = m_getopt_table[option_idx].val; + switch (short_option) + { + case 'a': + m_include_any_process = true; + break; + + case 'A': + m_display_timestamp_relative = true; + m_display_category = true; + m_display_subsystem = true; + m_display_activity_chain = true; + break; + + case 'b': + m_broadcast_events = + Args::StringToBoolean(option_arg, true, nullptr); + break; + + case 'c': + m_display_category = true; + break; + + case 'C': + m_display_activity_chain = true; + break; + + case 'd': + m_include_debug_level = true; + break; + + case 'e': + m_echo_to_stderr = + Args::StringToBoolean(option_arg, false, nullptr); + break; + + case 'f': + return ParseFilterRule(option_arg); + + case 'i': + m_include_info_level = true; + break; + + case 'l': + m_live_stream = + Args::StringToBoolean(option_arg, false, nullptr); + break; + + case 'n': + m_filter_fall_through_accepts = + Args::StringToBoolean(option_arg, true, nullptr); + break; + + case 'p': + m_print = Args::StringToBoolean(option_arg, true, nullptr); + break; + + case 'r': + m_display_timestamp_relative = true; + break; + + case 's': + m_display_subsystem = true; + break; + + default: + error.SetErrorStringWithFormat("unsupported option '%c'", + short_option); + } + return error; + } + + const OptionDefinition* + GetDefinitions () override + { + return g_enable_option_table; + } + + StructuredData::DictionarySP + BuildConfigurationData(bool enabled) + { + StructuredData::DictionarySP config_sp(new StructuredData:: + Dictionary()); + + // Set the basic enabled state. + config_sp->AddBooleanItem("enabled", enabled); + + // If we're disabled, there's nothing more to add. + if (!enabled) + return config_sp; + + // Handle source stream flags. + auto source_flags_sp = + StructuredData::DictionarySP(new StructuredData::Dictionary()); + config_sp->AddItem("source-flags", source_flags_sp); + + source_flags_sp->AddBooleanItem("any-process", + m_include_any_process); + source_flags_sp->AddBooleanItem("debug-level", + m_include_debug_level); + // The debug-level flag, if set, implies info-level. + source_flags_sp->AddBooleanItem("info-level", + m_include_info_level || + m_include_debug_level); + source_flags_sp->AddBooleanItem("live-stream", + m_live_stream); + + // Specify default filter rule (the fall-through) + config_sp->AddBooleanItem("filter-fall-through-accepts", + m_filter_fall_through_accepts); + + + // Handle filter rules + if (!m_filter_rules.empty()) + { + auto json_filter_rules_sp = + StructuredData::ArraySP(new StructuredData::Array); + config_sp->AddItem("filter-rules", + json_filter_rules_sp); + for (auto &rule_sp : m_filter_rules) + { + if (!rule_sp) + continue; + json_filter_rules_sp->AddItem(rule_sp->Serialize()); + } + } + return config_sp; + } + + bool + GetIncludeDebugLevel() const + { + return m_include_debug_level; + } + + bool + GetIncludeInfoLevel() const + { + // Specifying debug level implies info level. + return m_include_info_level || m_include_debug_level; + } + + const FilterRules& + GetFilterRules() const + { + return m_filter_rules; + } + + bool + GetFallthroughAccepts() const + { + return m_filter_fall_through_accepts; + } + + bool + GetEchoToStdErr() const + { + return m_echo_to_stderr; + } + + bool + GetDisplayTimestampRelative() const + { + return m_display_timestamp_relative; + } + + bool + GetDisplaySubsystem() const + { + return m_display_subsystem; + } + bool + GetDisplayCategory() const + { + return m_display_category; + } + bool + GetDisplayActivityChain() const + { + return m_display_activity_chain; + } + + bool + GetDisplayAnyHeaderFields() const + { + return + m_display_timestamp_relative || + m_display_activity_chain || + m_display_subsystem || + m_display_category; + } + + bool + GetBroadcastEvents() const + { + return m_broadcast_events; + } + + bool + GetPrint() const + { + return m_print; + } + + private: + + Error + ParseFilterRule(const char *rule_text_cstr) + { + Error error; + + if (!rule_text_cstr || !rule_text_cstr[0]) + { + error.SetErrorString("invalid rule_text"); + return error; + } + + // filter spec format: + // + // {action} {attribute} {op} + // + // {action} := + // accept | + // reject + // + // {attribute} := + // category | + // subsystem | + // activity | + // activity-chain | + // message | + // format + // + // {op} := + // match {exact-match-text} | + // regex {search-regex} + + const std::string rule_text(rule_text_cstr); + + // Parse action. + auto action_end_pos = rule_text.find(" "); + if (action_end_pos == std::string::npos) + { + error.SetErrorStringWithFormat("could not parse filter rule " + "action from \"%s\"", + rule_text_cstr); + return error; + } + auto action = rule_text.substr(0, action_end_pos); + bool accept; + if (action == "accept") + accept = true; + else if (action == "reject") + accept = false; + else + { + error.SetErrorString("filter action must be \"accept\" or " + "\"deny\""); + return error; + } + + // parse attribute + auto attribute_end_pos = rule_text.find(" ", action_end_pos + 1); + if (attribute_end_pos == std::string::npos) + { + error.SetErrorStringWithFormat("could not parse filter rule " + "attribute from \"%s\"", + rule_text_cstr); + return error; + } + auto attribute = + rule_text.substr(action_end_pos + 1, + attribute_end_pos - (action_end_pos + 1)); + auto attribute_index = MatchAttributeIndex(attribute); + if (attribute_index < 0) + { + error.SetErrorStringWithFormat("filter rule attribute unknown: " + "%s", attribute.c_str()); + return error; + } + + // parse operation + auto operation_end_pos = rule_text.find(" ", attribute_end_pos + 1); + auto operation = + rule_text.substr(attribute_end_pos + 1, + operation_end_pos - (attribute_end_pos + 1)); + + // add filter spec + auto rule_sp = + FilterRule::CreateRule(accept, attribute_index, + ConstString(operation), + rule_text.substr(operation_end_pos + 1), + error); + + if (rule_sp && error.Success()) + m_filter_rules.push_back(rule_sp); + + return error; + } + + int + MatchAttributeIndex(const std::string &attribute_name) + { + auto attribute_count = + sizeof(s_filter_attributes) / sizeof(s_filter_attributes[0]); + for (size_t i = 0; i < attribute_count; ++i) + { + if (attribute_name == s_filter_attributes[i]) + return static_cast(i); + } + + // We didn't match anything. + return -1; + } + + static OptionDefinition g_enable_option_table[]; + + bool m_include_debug_level; + bool m_include_info_level; + bool m_include_any_process; + bool m_filter_fall_through_accepts; + bool m_echo_to_stderr; + bool m_display_timestamp_relative; + bool m_display_subsystem; + bool m_display_category; + bool m_display_activity_chain; + bool m_broadcast_events; + bool m_live_stream; + bool m_print; + FilterRules m_filter_rules; + }; + + OptionDefinition + EnableOptions::g_enable_option_table[] = + { + // Source stream include/exclude options (the first-level filter). + // This one should be made as small as possible as everything that + // goes through here must be processed by the process monitor. + { LLDB_OPT_SET_ALL, false, "any-process", 'a', + OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, + "Specifies log messages from other related processes should be " + "included." }, + { LLDB_OPT_SET_ALL, false, "debug", 'd', OptionParser::eNoArgument, + nullptr, nullptr, 0, eArgTypeNone, + "Specifies debug-level log messages should be included. Specifying" + " --debug implies --info."}, + { LLDB_OPT_SET_ALL, false, "info", 'i', OptionParser::eNoArgument, + nullptr, nullptr, 0, eArgTypeNone, + "Specifies info-level log messages should be included." }, + { LLDB_OPT_SET_ALL, false, "filter", 'f', + OptionParser::eRequiredArgument, nullptr, nullptr, 0, eArgRawInput, + // There doesn't appear to be a great way for me to have these + // multi-line, formatted tables in help. This looks mostly right + // but there are extra linefeeds added at seemingly random spots, + // and indentation isn't handled properly on those lines. + "Appends a filter rule to the log message filter chain. Multiple " + "rules may be added by specifying this option multiple times, " + "once per filter rule. Filter rules are processed in the order " + "they are specified, with the --no-match-accepts setting used " + "for any message that doesn't match one of the rules.\n" + "\n" + " Filter spec format:\n" + "\n" + " --filter \"{action} {attribute} {op}\"\n" + "\n" + " {action} :=\n" + " accept |\n" + " reject\n" + "\n" + " {attribute} :=\n" + " activity | // message's most-derived activity\n" + " activity-chain | // message's {parent}:{child} activity\n" + " category | // message's category\n" + " message | // message's expanded contents\n" + " subsystem | // message's subsystem\n" + "\n" + " {op} :=\n" + " match {exact-match-text} |\n" + " regex {search-regex}\n" + "\n" + "The regex flavor used is the C++ std::regex ECMAScript format. " + "Prefer character classes like [[:digit:]] to \\d and the like, as " + "getting the backslashes escaped through properly is error-prone." + }, + { LLDB_OPT_SET_ALL, false, "live-stream", 'l', + OptionParser::eRequiredArgument, nullptr, nullptr, 0, + eArgTypeBoolean, + "Specify whether logging events are live-streamed or buffered. " + "True indicates live streaming, false indicates buffered. The " + "default is true (live streaming). Live streaming will deliver " + "log messages with less delay, but buffered capture mode has less " + "of an observer effect." }, + { LLDB_OPT_SET_ALL, false, "no-match-accepts", 'n', + OptionParser::eRequiredArgument, nullptr, nullptr, 0, + eArgTypeBoolean, + "Specify whether a log message that doesn't match any filter rule " + "is accepted or rejected, where true indicates accept. The " + "default is true." }, + { LLDB_OPT_SET_ALL, false, "echo-to-stderr", 'e', + OptionParser::eRequiredArgument, nullptr, nullptr, 0, + eArgTypeBoolean, + "Specify whether os_log()/NSLog() messages are echoed to the " + "target program's stderr. When DarwinLog is enabled, we shut off " + "the mirroring of os_log()/NSLog() to the program's stderr. " + "Setting this flag to true will restore the stderr mirroring." + "The default is false." }, + { LLDB_OPT_SET_ALL, false, "broadcast-events", 'b', + OptionParser::eRequiredArgument, nullptr, nullptr, 0, + eArgTypeBoolean, + "Specify if the plugin should broadcast events. Broadcasting " + "log events is a requirement for displaying the log entries in " + "LLDB command-line. It is also required if LLDB clients want to " + "process log events. The default is true." }, + // Message formatting options + { LLDB_OPT_SET_ALL, false, "timestamp-relative", 'r', + OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, + "Include timestamp in the message header when printing a log " + "message. The timestamp is relative to the first displayed " + "message." }, + { LLDB_OPT_SET_ALL, false, "subsystem", 's', + OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, + "Include the subsystem in the the message header when displaying " + "a log message." }, + { LLDB_OPT_SET_ALL, false, "category", 'c', + OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, + "Include the category in the the message header when displaying " + "a log message." }, + { LLDB_OPT_SET_ALL, false, "activity-chain", 'C', + OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, + "Include the activity parent-child chain in the the message header " + "when displaying a log message. The activity hierarchy is " + "displayed as {grandparent-activity}:" + "{parent-activity}:{activity}[:...]."}, + { LLDB_OPT_SET_ALL, false, "all-fields", 'A', + OptionParser::eNoArgument, nullptr, nullptr, 0, eArgTypeNone, + "Shortcut to specify that all header fields should be displayed." }, + { LLDB_OPT_SET_ALL, false, "print", 'p', + OptionParser::eRequiredArgument, nullptr, nullptr, 0, + eArgTypeBoolean, + "Specifies whether the log messages should be printed to the " + "LLDB console. Defaults to true." }, + + // Tail sentinel entry + { 0, false, nullptr, 0, 0, nullptr, nullptr, 0, eArgTypeNone, nullptr } + }; + + class EnableCommand : public CommandObjectParsed + { + public: + + EnableCommand(CommandInterpreter &interpreter, bool enable, + const char *name, const char *help, + const char *syntax) : + CommandObjectParsed(interpreter, name, help, syntax), + m_enable(enable), + m_options_sp(enable ? new EnableOptions() : nullptr) + { + } + + protected: + + void + AppendStrictSourcesWarning(CommandReturnObject &result, + const char *source_name) + { + if (!source_name) + return; + + // Check if we're *not* using strict sources. If not, + // then the user is going to get debug-level info + // anyways, probably not what they're expecting. + // Unfortunately we can only fix this by adding an + // env var, which would have had to have happened + // already. Thus, a warning is the best we can do here. + StreamString stream; + stream.Printf("darwin-log source settings specify to exclude " + "%s messages, but setting " + "'plugin.structured-data.darwin-log." + "strict-sources' is disabled. This process will " + "automatically have %s messages included. Enable" + " the property and relaunch the target binary to have" + " these messages excluded.", source_name, + source_name); + result.AppendWarning(stream.GetString().c_str()); + } + + bool + DoExecute (Args& command, CommandReturnObject &result) override + { + // First off, set the global sticky state of enable/disable + // based on this command execution. + s_is_explicitly_enabled = m_enable; + + // Next, if this is an enable, save off the option data. + // We will need it later if a process hasn't been launched or + // attached yet. + if (m_enable) + { + // Save off enabled configuration so we can apply these parsed + // options the next time an attach or launch occurs. + DebuggerSP debugger_sp = + GetCommandInterpreter().GetDebugger().shared_from_this(); + SetGlobalEnableOptions(debugger_sp, m_options_sp); + } + + // Now check if we have a running process. If so, we should + // instruct the process monitor to enable/disable DarwinLog support + // now. + Target *target = GetSelectedOrDummyTarget(); + if (!target) + { + // No target, so there is nothing more to do right now. + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + // Grab the active process. + auto process_sp = target->GetProcessSP(); + if (!process_sp) + { + // No active process, so there is nothing more to do right + // now. + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + // If the process is no longer alive, we can't do this now. + // We'll catch it the next time the process is started up. + if (!process_sp->IsAlive()) + { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + return true; + } + + // Get the plugin for the process. + auto plugin_sp = + process_sp->GetStructuredDataPlugin(GetDarwinLogTypeName()); + if (!plugin_sp || + (plugin_sp->GetPluginName() != + StructuredDataDarwinLog::GetStaticPluginName())) + { + result.AppendError("failed to get StructuredDataPlugin for " + "the process"); + result.SetStatus(eReturnStatusFailed); + } + StructuredDataDarwinLog &plugin = + *static_cast(plugin_sp.get()); + + if (m_enable) + { + // To do this next part right, we need to track whether we + // added the proper environment variable at launch time. + // It is incorrect to assume that we're enabling after launch, + // and that therefore if we needed the env var set to properly + // handle the options, that it is set incorrectly. The env var + // could have been added if this is a re-enable using different + // arguments. This is a bit tricky as the point where we + // have to set the env var, we don't yet have a process or the + // associated darwin-log plugin instance, and thus don't have a + // great place to stick this knowledge. +#if 0 + // Check if we're attempting to disable debug-level or + // info-level content but we haven't launched with the magic + // env var that suppresses the "always add" of those. In + // that scenario, the user is asking *not* to see debug or + // info level messages, but will see them anyway. Warn and + // show how to correct it. + if (!m_options_sp->GetIncludeDebugLevel() && + !GetGlobalProperties()->GetUseStrictSources()) + { + AppendStrictSourcesWarning(result, "debug-level"); + } + + if (!m_options_sp->GetIncludeInfoLevel() && + !GetGlobalProperties()->GetUseStrictSources()) + { + AppendStrictSourcesWarning(result, "info-level"); + } +#endif + + // Hook up the breakpoint for the process that detects when + // libtrace has been sufficiently initialized to really start + // the os_log stream. This is insurance to assure us that + // logging is really enabled. Requesting that logging be + // enabled for a process before libtrace is initialized + // results in a scenario where no errors occur, but no logging + // is captured, either. This step is to eliminate that + // possibility. + plugin.AddInitCompletionHook(*process_sp.get()); + } + + // Send configuration to the feature by way of the process. + // Construct the options we will use. + auto config_sp = m_options_sp->BuildConfigurationData(m_enable); + const Error error = + process_sp->ConfigureStructuredData(GetDarwinLogTypeName(), + config_sp); + + // Report results. + if (!error.Success()) + { + result.AppendError(error.AsCString()); + result.SetStatus(eReturnStatusFailed); + // Our configuration failed, so we're definitely disabled. + plugin.SetEnabled(false); + } + else + { + result.SetStatus(eReturnStatusSuccessFinishNoResult); + // Our configuration succeeeded, so we're enabled/disabled + // per whichever one this command is setup to do. + plugin.SetEnabled(m_enable); + } + return result.Succeeded(); + } + + Options* + GetOptions () override + { + // We don't have options when this represents disable. + return m_enable ? m_options_sp.get() : nullptr; + } + + private: + const bool m_enable; + EnableOptionsSP m_options_sp; + }; + + // ------------------------------------------------------------------------- + /// Provides the status command. + // ------------------------------------------------------------------------- + class StatusCommand : public CommandObjectParsed + { + public: + + StatusCommand(CommandInterpreter &interpreter) : + CommandObjectParsed(interpreter, "status", + "Show whether Darwin log supported is available" + " and enabled.", + "plugin structured-data darwin-log status") + { + } + + protected: + + bool + DoExecute (Args& command, CommandReturnObject &result) override + { + auto &stream = result.GetOutputStream(); + + // Figure out if we've got a process. If so, we can tell if + // DarwinLog is available for that process. + Target *target = GetSelectedOrDummyTarget(); + auto process_sp = target ? target->GetProcessSP() : ProcessSP(); + if (!target || !process_sp) + { + stream.PutCString("Availability: unknown (requires process)\n"); + stream.PutCString("Enabled: not applicable " + "(requires process)\n"); + } + else + { + auto plugin_sp = + process_sp->GetStructuredDataPlugin(GetDarwinLogTypeName()); + stream.Printf("Availability: %s\n", plugin_sp ? "available" : + "unavailable"); + auto &plugin_name = + StructuredDataDarwinLog::GetStaticPluginName(); + const bool enabled = plugin_sp ? + plugin_sp->GetEnabled(plugin_name) : false; + stream.Printf("Enabled: %s\n", enabled ? "true" : + "false"); + } + + // Display filter settings. + DebuggerSP debugger_sp = + GetCommandInterpreter().GetDebugger().shared_from_this(); + auto options_sp = GetGlobalEnableOptions(debugger_sp); + if (!options_sp) + { + // Nothing more to do. + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + + // Print filter rules + stream.PutCString("DarwinLog filter rules:\n"); + + stream.IndentMore(); + + if (options_sp->GetFilterRules().empty()) + { + stream.Indent(); + stream.PutCString("none\n"); + } + else + { + // Print each of the filter rules. + int rule_number = 0; + for (auto rule_sp : options_sp->GetFilterRules()) + { + ++rule_number; + if (!rule_sp) + continue; + + stream.Indent(); + stream.Printf("%02d: ", rule_number); + rule_sp->Dump(stream); + stream.PutChar('\n'); + } + } + stream.IndentLess(); + + // Print no-match handling. + stream.Indent(); + stream.Printf("no-match behavior: %s\n", + options_sp->GetFallthroughAccepts() ? "accept" : + "reject"); + + result.SetStatus(eReturnStatusSuccessFinishResult); + return true; + } + }; + + // ------------------------------------------------------------------------- + /// Provides the darwin-log base command + // ------------------------------------------------------------------------- + class BaseCommand : public CommandObjectMultiword + { + public: + BaseCommand(CommandInterpreter &interpreter) : + CommandObjectMultiword(interpreter, + "plugin structured-data darwin-log", + "Commands for configuring Darwin os_log " + "support.", + "") + { + // enable + auto enable_help = "Enable Darwin log collection, or re-enable " + "with modified configuration."; + auto enable_syntax = "plugin structured-data darwin-log enable"; + auto enable_cmd_sp = + CommandObjectSP(new EnableCommand(interpreter, + true, // enable + "enable", + enable_help, + enable_syntax)); + LoadSubCommand("enable", enable_cmd_sp); + + // disable + auto disable_help = "Disable Darwin log collection."; + auto disable_syntax = "plugin structured-data darwin-log disable"; + auto disable_cmd_sp = + CommandObjectSP(new EnableCommand(interpreter, + false, // disable + "disable", + disable_help, + disable_syntax)); + LoadSubCommand("disable", disable_cmd_sp); + + // status + auto status_cmd_sp = + CommandObjectSP(new StatusCommand(interpreter)); + LoadSubCommand("status", status_cmd_sp); + } + }; + + EnableOptionsSP + ParseAutoEnableOptions(Error &error, Debugger &debugger) + { + // We are abusing the options data model here so that we can parse + // options without requiring the Debugger instance. + + // We have an empty execution context at this point. We only want + // to parse options, and we don't need any context to do this here. + // In fact, we want to be able to parse the enable options before having + // any context. + ExecutionContext exe_ctx; + + EnableOptionsSP options_sp(new EnableOptions()); + options_sp->NotifyOptionParsingStarting(&exe_ctx); + + CommandReturnObject result; + + // Parse the arguments. + auto options_property_sp = + debugger.GetPropertyValue(nullptr, + "plugin.structured-data.darwin-log." + "auto-enable-options", + false, + error); + if (!error.Success()) + return EnableOptionsSP(); + if (!options_property_sp) + { + error.SetErrorString("failed to find option setting for " + "plugin.structured-data.darwin-log."); + return EnableOptionsSP(); + } + + const char *enable_options = options_property_sp->GetAsString()->GetCurrentValue(); + Args args(enable_options); + if (args.GetArgumentCount() > 0) + { + // Eliminate the initial '--' that would be required to set the + // settings that themselves include '-' and/or '--'. + const char *first_arg = args.GetArgumentAtIndex(0); + if (first_arg && (strcmp(first_arg, "--") == 0)) + args.Shift(); + } + + // ParseOptions calls getopt_long_only, which always skips the zero'th item in the array and starts at position 1, + // so we need to push a dummy value into position zero. + args.Unshift("dummy_string"); + bool require_validation = false; + error = args.ParseOptions(*options_sp.get(), &exe_ctx, PlatformSP(), + require_validation); + if (!error.Success()) + return EnableOptionsSP(); + + if (!options_sp->VerifyOptions(result)) + return EnableOptionsSP(); + + // We successfully parsed and validated the options. + return options_sp; + } + + bool + RunEnableCommand(CommandInterpreter &interpreter) + { + StreamString command_stream; + + command_stream << "plugin structured-data darwin-log enable"; + auto enable_options = GetGlobalProperties()->GetAutoEnableOptions(); + if (enable_options && (strlen(enable_options) > 0)) + { + command_stream << ' '; + command_stream << enable_options; + } + + // Run the command. + CommandReturnObject return_object; + interpreter.HandleCommand(command_stream.GetString().c_str(), + eLazyBoolNo, + return_object); + return return_object.Succeeded(); + } +} +using namespace sddarwinlog_private; + +#pragma mark - +#pragma mark Public static API + +// ----------------------------------------------------------------------------- +// Public static API +// ----------------------------------------------------------------------------- + +void +StructuredDataDarwinLog::Initialize() +{ + RegisterFilterOperations(); + PluginManager::RegisterPlugin(GetStaticPluginName(), + "Darwin os_log() and os_activity() support", + &CreateInstance, + &DebuggerInitialize, + &FilterLaunchInfo); +} + +void +StructuredDataDarwinLog::Terminate() +{ + PluginManager::UnregisterPlugin(&CreateInstance); +} + +const ConstString& +StructuredDataDarwinLog::GetStaticPluginName() +{ + static ConstString s_plugin_name("darwin-log"); + return s_plugin_name; +} + +#pragma mark - +#pragma mark PluginInterface API + +// ----------------------------------------------------------------------------- +// PluginInterface API +// ----------------------------------------------------------------------------- + +ConstString +StructuredDataDarwinLog::GetPluginName() +{ + return GetStaticPluginName(); +} + +uint32_t +StructuredDataDarwinLog::GetPluginVersion() +{ + return 1; +} + +#pragma mark - +#pragma mark StructuredDataPlugin API + +// ----------------------------------------------------------------------------- +// StructuredDataPlugin API +// ----------------------------------------------------------------------------- + +bool +StructuredDataDarwinLog::SupportsStructuredDataType( + const ConstString &type_name) +{ + return type_name == GetDarwinLogTypeName(); +} + +void +StructuredDataDarwinLog::HandleArrivalOfStructuredData(Process &process, + const ConstString + &type_name, + const + StructuredData::ObjectSP + &object_sp) +{ + Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + if (log) + { + StreamString json_stream; + if (object_sp) + object_sp->Dump(json_stream); + else + json_stream.PutCString(""); + log->Printf("StructuredDataDarwinLog::%s() called with json: %s", + __FUNCTION__, json_stream.GetString().c_str()); + } + + // Ignore empty structured data. + if (!object_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() StructuredData object " + "is null", __FUNCTION__); + return; + } + + // Ignore any data that isn't for us. + if (type_name != GetDarwinLogTypeName()) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() StructuredData type " + "expected to be %s but was %s, ignoring", __FUNCTION__, + GetDarwinLogTypeName().AsCString(), + type_name.AsCString()); + return; + } + + // Broadcast the structured data event if we have that enabled. + // This is the way that the outside world (all clients) get access + // to this data. This plugin sets policy as to whether we do that. + DebuggerSP debugger_sp = + process.GetTarget().GetDebugger().shared_from_this(); + auto options_sp = GetGlobalEnableOptions(debugger_sp); + if (options_sp && options_sp->GetBroadcastEvents()) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() broadcasting event", + __FUNCTION__); + process.BroadcastStructuredData(object_sp, shared_from_this()); + } + + // Later, hang on to a configurable amount of these and allow commands + // to inspect, including showing backtraces. +} + +bool +StructuredDataDarwinLog::ShouldPrint(const StructuredData::ObjectSP + &object_sp) const +{ + auto process_sp = GetProcess(); + if (!process_sp) + return false; + + auto debugger_sp = process_sp->GetTarget().GetDebugger().shared_from_this(); + auto options_sp = GetGlobalEnableOptions(debugger_sp); + return options_sp && options_sp->GetPrint(); +} + +static void +SetErrorWithJSON(Error &error, const char *message, + StructuredData::Object &object) +{ + if (!message) + { + error.SetErrorString("Internal error: message not set."); + return; + } + + StreamString object_stream; + object.Dump(object_stream); + object_stream.Flush(); + + error.SetErrorStringWithFormat("%s: %s", message, + object_stream.GetString().c_str()); +} + +Error +StructuredDataDarwinLog::PrettyPrintData(const StructuredData::ObjectSP + &object_sp, + lldb_private::Stream &stream) +{ + Error error; + + if (!object_sp) + { + error.SetErrorString("No structured data."); + return error; + } + + // Log message payload objects will be dictionaries. + const StructuredData::Dictionary *dictionary = object_sp->GetAsDictionary(); + if (!dictionary) + { + SetErrorWithJSON(error, "Structured data should have been a dictionary " + "but wasn't", *object_sp); + return error; + } + + // Validate this is really a message for our plugin. + ConstString type_name; + if (!dictionary->GetValueForKeyAsString("type", type_name)) + { + SetErrorWithJSON(error, "Structured data doesn't contain mandatory " + "type field", *object_sp); + return error; + } + + if (type_name != GetDarwinLogTypeName()) + { + // This is okay - it simply means the data we received is not a log + // message. We'll just format it as is. + object_sp->Dump(stream); + return error; + } + + // DarwinLog dictionaries store their data + // in an array with key name "events". + StructuredData::Array *events = nullptr; + if (!dictionary->GetValueForKeyAsArray("events", events) || + !events) + { + SetErrorWithJSON(error, "Log structured data is missing mandatory " + "'events' field, expected to be an array", *object_sp); + return error; + } + + events->ForEach([&stream, &error, &object_sp, this] + (StructuredData::Object *object) + { + if (!object) + { + // Invalid. Stop iterating. + SetErrorWithJSON(error, "Log event entry is null", *object_sp); + return false; + } + + auto event = object->GetAsDictionary(); + if (!event) + { + // Invalid, stop iterating. + SetErrorWithJSON(error, "Log event is not a dictionary", + *object_sp); + return false; + } + + // If we haven't already grabbed the first timestamp + // value, do that now. + if (!m_recorded_first_timestamp) + { + uint64_t timestamp = 0; + if (event->GetValueForKeyAsInteger("timestamp", + timestamp)) + { + m_first_timestamp_seen = timestamp; + m_recorded_first_timestamp = true; + } + } + + HandleDisplayOfEvent(*event, stream); + return true; + }); + + stream.Flush(); + return error; +} + +bool +StructuredDataDarwinLog::GetEnabled(const ConstString &type_name) const +{ + if (type_name == GetStaticPluginName()) + return m_is_enabled; + else + return false; +} + +void +StructuredDataDarwinLog::SetEnabled(bool enabled) +{ + m_is_enabled = enabled; +} + +void +StructuredDataDarwinLog::ModulesDidLoad(Process &process, + ModuleList &module_list) +{ + Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf("StructuredDataDarwinLog::%s called (process uid %u)", + __FUNCTION__, process.GetUniqueID()); + + // Check if we should enable the darwin log support on startup/attach. + if (!GetGlobalProperties()->GetEnableOnStartup() && + !s_is_explicitly_enabled) + { + // We're neither auto-enabled or explicitly enabled, so we shouldn't + // try to enable here. + if (log) + log->Printf("StructuredDataDarwinLog::%s not applicable, we're not " + "enabled (process uid %u)", __FUNCTION__, + process.GetUniqueID()); + return; + } + + // If we already added the breakpoint, we've got nothing left to do. + { + std::lock_guard locker(m_added_breakpoint_mutex); + if (m_added_breakpoint) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s process uid %u's " + "post-libtrace-init breakpoint is already set", + __FUNCTION__, process.GetUniqueID()); + return; + } + } + + // The logging support module name, specifies the name of + // the image name that must be loaded into the debugged process before + // we can try to enable logging. + const char *logging_module_cstr = + GetGlobalProperties()->GetLoggingModuleName(); + if (!logging_module_cstr || (logging_module_cstr[0] == 0)) + { + // We need this. Bail. + if (log) + log->Printf("StructuredDataDarwinLog::%s no logging module name " + "specified, we don't know where to set a breakpoint " + "(process uid %u)", __FUNCTION__, + process.GetUniqueID()); + return; + } + + const ConstString logging_module_name = + ConstString(logging_module_cstr); + + // We need to see libtrace in the list of modules before we can enable + // tracing for the target process. + bool found_logging_support_module = false; + for (size_t i = 0; i < module_list.GetSize(); ++i) + { + auto module_sp = module_list.GetModuleAtIndex(i); + if (!module_sp) + continue; + + auto &file_spec = module_sp->GetFileSpec(); + found_logging_support_module = + (file_spec.GetLastPathComponent() == logging_module_name); + if (found_logging_support_module) + break; + } + + if (!found_logging_support_module) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s logging module %s " + "has not yet been loaded, can't set a breakpoint " + "yet (process uid %u)", __FUNCTION__, + logging_module_name.AsCString(), + process.GetUniqueID()); + return; + } + + // Time to enqueue the breakpoint so we can wait for logging support + // to be initialized before we try to tap the libtrace stream. + AddInitCompletionHook(process); + if (log) + log->Printf("StructuredDataDarwinLog::%s post-init hook breakpoint " + "set for logging module %s (process uid %u)", __FUNCTION__, + logging_module_name.AsCString(), + process.GetUniqueID()); + + // We need to try the enable here as well, which will succeed + // in the event that we're attaching to (rather than launching) the + // process and the process is already past initialization time. In that + // case, the completion breakpoint will never get hit and therefore won't + // start that way. It doesn't hurt much beyond a bit of bandwidth + // if we end up doing this twice. It hurts much more if we don't get + // the logging enabled when the user expects it. + EnableNow(); +} + +#pragma mark - +#pragma mark Private instance methods + +// ----------------------------------------------------------------------------- +// Private constructors +// ----------------------------------------------------------------------------- + +StructuredDataDarwinLog::StructuredDataDarwinLog(const ProcessWP &process_wp) : + StructuredDataPlugin(process_wp), + m_recorded_first_timestamp(false), + m_first_timestamp_seen(0), + m_is_enabled(false), + m_added_breakpoint_mutex(), + m_added_breakpoint() +{ +} + +// ----------------------------------------------------------------------------- +// Private static methods +// ----------------------------------------------------------------------------- + +StructuredDataPluginSP +StructuredDataDarwinLog::CreateInstance(Process &process) +{ + // Currently only Apple targets support the os_log/os_activity + // protocol. + if (process.GetTarget().GetArchitecture().GetTriple().getVendor() == + llvm::Triple::VendorType::Apple) + { + auto process_wp = ProcessWP(process.shared_from_this()); + return StructuredDataPluginSP(new StructuredDataDarwinLog(process_wp)); + } + else + { + return StructuredDataPluginSP(); + } +} + +void +StructuredDataDarwinLog::DebuggerInitialize(Debugger &debugger) +{ + // Setup parent class first. + StructuredDataPlugin::InitializeBasePluginForDebugger(debugger); + + // Get parent command. + auto &interpreter = debugger.GetCommandInterpreter(); + std::string parent_command_text = "plugin structured-data"; + auto parent_command = + interpreter.GetCommandObjectForCommand(parent_command_text); + if (!parent_command) + { + // Ut oh, parent failed to create parent command. + // TODO log + return; + } + + auto command_name = "darwin-log"; + auto command_sp = CommandObjectSP(new BaseCommand(interpreter)); + bool result = parent_command->LoadSubCommand(command_name, command_sp); + if (!result) + { + // TODO log it once we setup structured data logging + } + + if (!PluginManager::GetSettingForPlatformPlugin(debugger, + StructuredDataDarwinLogProperties::GetSettingName())) + { + const bool is_global_setting = true; + PluginManager::CreateSettingForStructuredDataPlugin(debugger, + GetGlobalProperties()->GetValueProperties(), + ConstString ("Properties for the darwin-log" + " plug-in."), + is_global_setting); + } + +} + +Error +StructuredDataDarwinLog::FilterLaunchInfo(ProcessLaunchInfo &launch_info, + Target *target) +{ + Error error; + + // If we're not debugging this launched process, there's nothing for us + // to do here. + if (!launch_info.GetFlags().AnySet(eLaunchFlagDebug)) + return error; + + // Darwin os_log() support automatically adds debug-level and info-level + // messages when a debugger is attached to a process. However, with + // integrated suppport for debugging built into the command-line LLDB, + // the user may specifically set to *not* include debug-level and info-level + // content. When the user is using the integrated log support, we want + // to put the kabosh on that automatic adding of info and debug level. + // This is done by adding an environment variable to the process on launch. + // (This also means it is not possible to suppress this behavior if + // attaching to an already-running app). + // Log *log(GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PLATFORM)); + + // If the target architecture is not one that supports DarwinLog, we have + // nothing to do here. + auto &triple = target ? target->GetArchitecture().GetTriple() : + launch_info.GetArchitecture().GetTriple(); + if (triple.getVendor() != + llvm::Triple::Apple) + { + return error; + } + + // If DarwinLog is not enabled (either by explicit user command or via + // the auto-enable option), then we have nothing to do. + if (!GetGlobalProperties()->GetEnableOnStartup() && + !s_is_explicitly_enabled) + { + // Nothing to do, DarwinLog is not enabled. + return error; + } + + // If we don't have parsed configuration info, that implies we have + // enable-on-startup set up, but we haven't yet attempted to run the + // enable command. + if (!target) + { + // We really can't do this without a target. We need to be able + // to get to the debugger to get the proper options to do this right. + // TODO log. + error.SetErrorString("requires a target to auto-enable DarwinLog."); + return error; + } + + DebuggerSP debugger_sp = target->GetDebugger().shared_from_this(); + auto options_sp = GetGlobalEnableOptions(debugger_sp); + if (!options_sp && debugger_sp) + { + options_sp = ParseAutoEnableOptions(error, *debugger_sp.get()); + if (!options_sp || !error.Success()) + return error; + + // We already parsed the options, save them now so we don't generate + // them again until the user runs the command manually. + SetGlobalEnableOptions(debugger_sp, options_sp); + } + + auto &env_vars = launch_info.GetEnvironmentEntries(); + if (!options_sp->GetEchoToStdErr()) + { + // The user doesn't want to see os_log/NSLog messages echo to stderr. + // That mechanism is entirely separate from the DarwinLog support. + // By default we don't want to get it via stderr, because that would + // be in duplicate of the explicit log support here. + + // Here we need to strip out any OS_ACTIVITY_DT_MODE setting to prevent + // echoing of os_log()/NSLog() to stderr in the target program. + size_t argument_index; + if (env_vars.ContainsEnvironmentVariable("OS_ACTIVITY_DT_MODE", + &argument_index)) + env_vars.DeleteArgumentAtIndex(argument_index); + + // We will also set the env var that tells any downstream launcher + // from adding OS_ACTIVITY_DT_MODE. + env_vars.AddOrReplaceEnvironmentVariable( + "IDE_DISABLED_OS_ACTIVITY_DT_MODE", "1"); + } + + // Set the OS_ACTIVITY_MODE env var appropriately to enable/disable + // debug and info level messages. + const char *env_var_value; + if (options_sp->GetIncludeDebugLevel()) + env_var_value = "debug"; + else if (options_sp->GetIncludeInfoLevel()) + env_var_value = "info"; + else + env_var_value = ""; + + if (env_var_value) + { + const char *env_var_name = "OS_ACTIVITY_MODE"; + launch_info.GetEnvironmentEntries() + .AddOrReplaceEnvironmentVariable(env_var_name, env_var_value); + } + + return error; +} + +bool +StructuredDataDarwinLog::InitCompletionHookCallback(void *baton, + StoppointCallbackContext *context, + lldb::user_id_t break_id, + lldb::user_id_t break_loc_id) +{ + // We hit the init function. We now want to enqueue our new thread plan, + // which will in turn enqueue a StepOut thread plan. When the StepOut + // finishes and control returns to our new thread plan, that is the time + // when we can execute our logic to enable the logging support. + + Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf("StructuredDataDarwinLog::%s() called", __FUNCTION__); + + // Get the current thread. + if (!context) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: no context, " + "ignoring", __FUNCTION__); + return false; + } + + // Get the plugin from the process. + auto process_sp = context->exe_ctx_ref.GetProcessSP(); + if (!process_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: invalid " + "process in context, ignoring", __FUNCTION__); + return false; + } + if (log) + log->Printf("StructuredDataDarwinLog::%s() call is for process uid %d", + __FUNCTION__, process_sp->GetUniqueID()); + + auto plugin_sp = + process_sp->GetStructuredDataPlugin(GetDarwinLogTypeName()); + if (!plugin_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: no plugin for " + "feature %s in process uid %u", + __FUNCTION__, GetDarwinLogTypeName().AsCString(), + process_sp->GetUniqueID()); + return false; + } + + // Create the callback for when the thread plan completes. + bool called_enable_method = false; + const auto process_uid = process_sp->GetUniqueID(); + + std::weak_ptr plugin_wp(plugin_sp); + ThreadPlanCallOnFunctionExit::Callback callback = + [plugin_wp, &called_enable_method, log, process_uid]() { + if (log) + log->Printf("StructuredDataDarwinLog::post-init callback: " + "called (process uid %u)", process_uid); + + auto strong_plugin_sp = plugin_wp.lock(); + if (!strong_plugin_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::post-init callback: " + "plugin no longer exists, ignoring (process " + "uid %u)", process_uid); + return; + } + // Make sure we only call it once, just in case the + // thread plan hits the breakpoint twice. + if (!called_enable_method) + { + if (log) + log->Printf("StructuredDataDarwinLog::post-init callback: " + "calling EnableNow() (process uid %u)", + process_uid); + static_cast(strong_plugin_sp.get()) + ->EnableNow(); + called_enable_method = true; + } + else + { + // Our breakpoint was hit more than once. Unexpected but + // no harm done. Log it. + if (log) + log->Printf("StructuredDataDarwinLog::post-init callback: " + "skipping EnableNow(), already called by " + "callback [we hit this more than once] " + "(process uid %u)", process_uid); + } + }; + + // Grab the current thread. + auto thread_sp = context->exe_ctx_ref.GetThreadSP(); + if (!thread_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: failed to " + "retrieve the current thread from the execution " + "context, nowhere to run the thread plan (process uid " + "%u)", __FUNCTION__, process_sp->GetUniqueID()); + return false; + } + + // Queue the thread plan. + auto thread_plan_sp + = ThreadPlanSP(new ThreadPlanCallOnFunctionExit(*thread_sp.get(), + callback)); + const bool abort_other_plans = false; + thread_sp->QueueThreadPlan(thread_plan_sp, abort_other_plans); + if (log) + log->Printf("StructuredDataDarwinLog::%s() queuing thread plan on " + "trace library init method entry (process uid %u)", + __FUNCTION__, process_sp->GetUniqueID()); + + // We return false here to indicate that it isn't a public stop. + return false; +} + +void +StructuredDataDarwinLog::AddInitCompletionHook(Process &process) +{ + Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf("StructuredDataDarwinLog::%s() called (process uid %u)", + __FUNCTION__, process.GetUniqueID()); + + // Make sure we haven't already done this. + { + std::lock_guard locker(m_added_breakpoint_mutex); + if (m_added_breakpoint) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() ignoring request, " + "breakpoint already set (process uid %u)", + __FUNCTION__, process.GetUniqueID()); + return; + } + + // We're about to do this, don't let anybody else try to do it. + m_added_breakpoint = true; + } + + // Set a breakpoint for the process that will kick in when libtrace + // has finished its initialization. + Target &target = process.GetTarget(); + + // Build up the module list. + FileSpecList module_spec_list; + auto module_file_spec = + FileSpec(GetGlobalProperties()->GetLoggingModuleName(), false); + module_spec_list.Append(module_file_spec); + + // We aren't specifying a source file set. + FileSpecList *source_spec_list = nullptr; + + const char *func_name = "_libtrace_init"; + const lldb::addr_t offset = 0; + const LazyBool skip_prologue = eLazyBoolCalculate; + // This is an internal breakpoint - the user shouldn't see it. + const bool internal = true; + const bool hardware = false; + + auto breakpoint_sp = + target.CreateBreakpoint(&module_spec_list, source_spec_list, func_name, + eFunctionNameTypeFull, eLanguageTypeC, offset, + skip_prologue, internal, hardware); + if (!breakpoint_sp) + { + // Huh? Bail here. + if (log) + log->Printf("StructuredDataDarwinLog::%s() failed to set " + "breakpoint in module %s, function %s (process uid %u)", + __FUNCTION__, + GetGlobalProperties()->GetLoggingModuleName(), + func_name, process.GetUniqueID()); + return; + } + + // Set our callback. + breakpoint_sp->SetCallback(InitCompletionHookCallback, nullptr); + if (log) + log->Printf("StructuredDataDarwinLog::%s() breakpoint set in module %s," + "function %s (process uid %u)", + __FUNCTION__, + GetGlobalProperties()->GetLoggingModuleName(), + func_name, process.GetUniqueID()); +} + +void +StructuredDataDarwinLog::DumpTimestamp(Stream &stream, uint64_t timestamp) +{ + const uint64_t delta_nanos = timestamp - m_first_timestamp_seen; + + const uint64_t hours = delta_nanos / NANOS_PER_HOUR; + uint64_t nanos_remaining = delta_nanos % NANOS_PER_HOUR; + + const uint64_t minutes = nanos_remaining / NANOS_PER_MINUTE; + nanos_remaining = nanos_remaining % NANOS_PER_MINUTE; + + const uint64_t seconds = nanos_remaining / NANOS_PER_SECOND; + nanos_remaining = nanos_remaining % NANOS_PER_SECOND; + + stream.Printf("%02" PRIu64 ":%02" PRIu64 ":%02" PRIu64 ".%09" PRIu64, + hours, minutes, seconds, nanos_remaining); +} + +size_t +StructuredDataDarwinLog::DumpHeader(Stream &output_stream, + const StructuredData::Dictionary &event) +{ + StreamString stream; + + ProcessSP process_sp = GetProcess(); + if (!process_sp) + { + // TODO log + return 0; + } + + DebuggerSP debugger_sp = + process_sp->GetTarget().GetDebugger().shared_from_this(); + if (!debugger_sp) + { + // TODO log + return 0; + } + + auto options_sp = GetGlobalEnableOptions(debugger_sp); + if (!options_sp) + { + // TODO log + return 0; + } + + // Check if we should even display a header. + if (!options_sp->GetDisplayAnyHeaderFields()) + return 0; + + stream.PutChar('['); + + int header_count = 0; + if (options_sp->GetDisplayTimestampRelative()) + { + uint64_t timestamp = 0; + if (event.GetValueForKeyAsInteger("timestamp", timestamp)) + { + DumpTimestamp(stream, timestamp); + ++header_count; + } + } + + if (options_sp->GetDisplayActivityChain()) + { + std::string activity_chain; + if (event.GetValueForKeyAsString("activity-chain", activity_chain) && + !activity_chain.empty()) + { + if (header_count > 0) + stream.PutChar(','); + + // Switch over to the #if 0 branch once we figure out + // how we want to present settings for the tri-state of + // no-activity, activity (most derived only), or activity-chain. +#if 1 + // Display the activity chain, from parent-most to child-most + // activity, separated by a colon (:). + stream.PutCString("activity-chain="); + stream.PutCString(activity_chain.c_str()); +#else + if (GetGlobalProperties()->GetDisplayActivityChain()) + { + // Display the activity chain, from parent-most to child-most + // activity, separated by a colon (:). + stream.PutCString("activity-chain="); + stream.PutCString(activity_chain.c_str()); + } + else + { + // We're only displaying the child-most activity. + stream.PutCString("activity="); + auto pos = activity_chain.find_last_of(':'); + if (pos == std::string::npos) + { + // The activity chain only has one level, use the whole + // thing. + stream.PutCString(activity_chain.c_str()); + } + else + { + // Display everything after the final ':'. + stream.PutCString(activity_chain.substr(pos+1).c_str()); + } + } +#endif + ++header_count; + } + } + + if (options_sp->GetDisplaySubsystem()) + { + std::string subsystem; + if (event.GetValueForKeyAsString("subsystem", + subsystem) && + !subsystem.empty()) + { + if (header_count > 0) + stream.PutChar(','); + stream.PutCString("subsystem="); + stream.PutCString(subsystem.c_str()); + ++header_count; + } + } + + if (options_sp->GetDisplayCategory()) + { + std::string category; + if (event.GetValueForKeyAsString("category", + category) && + !category.empty()) + { + if (header_count > 0) + stream.PutChar(','); + stream.PutCString("category="); + stream.PutCString(category.c_str()); + ++header_count; + } + } + stream.PutCString("] "); + + auto &result = stream.GetString(); + output_stream.PutCString(result.c_str()); + + return result.size(); +} + +size_t +StructuredDataDarwinLog::HandleDisplayOfEvent(const StructuredData::Dictionary + &event, Stream &stream) +{ + // Check the type of the event. + ConstString event_type; + if (!event.GetValueForKeyAsString("type", event_type)) + { + // Hmm, we expected to get events that describe + // what they are. Continue anyway. + return 0; + } + + if (event_type != GetLogEventType()) + return 0; + + size_t total_bytes = 0; + + // Grab the message content. + std::string message; + if (!event.GetValueForKeyAsString("message", message)) + return true; + + // Display the log entry. + const auto len = message.length(); + + total_bytes += DumpHeader(stream, event); + + stream.Write(message.c_str(), len); + total_bytes += len; + + // Add an end of line. + stream.PutChar('\n'); + total_bytes += sizeof(char); + + return total_bytes; +} + +void +StructuredDataDarwinLog::EnableNow() +{ + Log *log (lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_PROCESS)); + if (log) + log->Printf("StructuredDataDarwinLog::%s() called", __FUNCTION__); + + // Run the enable command. + auto process_sp = GetProcess(); + if (!process_sp) + { + // Nothing to do. + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: failed to get " + "valid process, skipping", __FUNCTION__); + return; + } + if (log) + log->Printf("StructuredDataDarwinLog::%s() call is for process uid %u", + __FUNCTION__, process_sp->GetUniqueID()); + + // If we have configuration data, we can directly enable it now. + // Otherwise, we need to run through the command interpreter to parse + // the auto-run options (which is the only way we get here without having + // already-parsed configuration data). + DebuggerSP debugger_sp = + process_sp->GetTarget().GetDebugger().shared_from_this(); + if (!debugger_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: failed to get " + "debugger shared pointer, skipping (process uid %u)", + __FUNCTION__, process_sp->GetUniqueID()); + return; + } + + auto options_sp = GetGlobalEnableOptions(debugger_sp); + if (!options_sp) + { + // We haven't run the enable command yet. Just do that now, it'll + // take care of the rest. + auto &interpreter = debugger_sp->GetCommandInterpreter(); + const bool success = RunEnableCommand(interpreter); + if (log) + { + if (success) + log->Printf("StructuredDataDarwinLog::%s() ran enable command " + "successfully for (process uid %u)", + __FUNCTION__, process_sp->GetUniqueID()); + else + log->Printf("StructuredDataDarwinLog::%s() error: running " + "enable command failed (process uid %u)", + __FUNCTION__, process_sp->GetUniqueID()); + } + // Report failures to the debugger error stream. + auto error_stream_sp = debugger_sp->GetAsyncErrorStream(); + if (error_stream_sp) + { + error_stream_sp->Printf("failed to configure DarwinLog " + "support\n"); + error_stream_sp->Flush(); + } + return; + } + + // We've previously been enabled. We will re-enable now with the + // previously specified options. + auto config_sp = options_sp->BuildConfigurationData(true); + if (!config_sp) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() warning: failed to " + "build configuration data for enable options, skipping " + "(process uid %u)", __FUNCTION__, + process_sp->GetUniqueID()); + return; + } + + // We can run it directly. + // Send configuration to the feature by way of the process. + const Error error = + process_sp->ConfigureStructuredData(GetDarwinLogTypeName(), + config_sp); + + // Report results. + if (!error.Success()) + { + if (log) + log->Printf("StructuredDataDarwinLog::%s() " + "ConfigureStructuredData() call failed " + "(process uid %u): %s", + __FUNCTION__, process_sp->GetUniqueID(), + error.AsCString()); + auto error_stream_sp = debugger_sp->GetAsyncErrorStream(); + if (error_stream_sp) + { + error_stream_sp->Printf("failed to configure DarwinLog " + "support: %s\n", error.AsCString()); + error_stream_sp->Flush(); + } + m_is_enabled = false; + } + else + { + m_is_enabled = true; + if (log) + log->Printf("StructuredDataDarwinLog::%s() success via direct " + "configuration (process uid %u)", __FUNCTION__, + process_sp->GetUniqueID()); + } +} Index: source/Target/CMakeLists.txt =================================================================== --- source/Target/CMakeLists.txt +++ source/Target/CMakeLists.txt @@ -30,6 +30,7 @@ StackFrameList.cpp StackID.cpp StopInfo.cpp + StructuredDataPlugin.cpp SystemRuntime.cpp Target.cpp TargetList.cpp @@ -40,6 +41,7 @@ ThreadPlanBase.cpp ThreadPlanCallFunction.cpp ThreadPlanCallFunctionUsingABI.cpp + ThreadPlanCallOnFunctionExit.cpp ThreadPlanCallUserExpression.cpp ThreadPlanPython.cpp ThreadPlanRunToAddress.cpp Index: source/Target/Platform.cpp =================================================================== --- source/Target/Platform.cpp +++ source/Target/Platform.cpp @@ -1314,7 +1314,34 @@ // group, since then we can handle ^C interrupts ourselves w/o having to worry // about the target getting them as well. launch_info.SetLaunchInSeparateProcessGroup(true); - + + // Allow any StructuredData process-bound plugins to adjust the launch info + // if needed + size_t i = 0; + bool iteration_complete = false; + // Note iteration can't simply go until a nullptr callback is returned, as + // it is valid for a plugin to not supply a filter. + auto get_filter_func = + PluginManager::GetStructuredDataFilterCallbackAtIndex; + for (auto filter_callback = get_filter_func(i, iteration_complete); + !iteration_complete; + filter_callback = get_filter_func(++i, iteration_complete)) + { + if (filter_callback) + { + // Give this ProcessLaunchInfo filter a chance to adjust the launch + // info. + error = (*filter_callback)(launch_info, target); + if (!error.Success()) + { + if (log) + log->Printf("Platform::%s() StructuredDataPlugin launch " + "filter failed.", __FUNCTION__); + return process_sp; + } + } + } + error = LaunchProcess (launch_info); if (error.Success()) { Index: source/Target/Process.cpp =================================================================== --- source/Target/Process.cpp +++ source/Target/Process.cpp @@ -54,6 +54,7 @@ #include "lldb/Target/Process.h" #include "lldb/Target/RegisterContext.h" #include "lldb/Target/StopInfo.h" +#include "lldb/Target/StructuredDataPlugin.h" #include "lldb/Target/SystemRuntime.h" #include "lldb/Target/Target.h" #include "lldb/Target/TargetList.h" @@ -797,6 +798,7 @@ SetEventName(eBroadcastBitSTDOUT, "stdout-available"); SetEventName(eBroadcastBitSTDERR, "stderr-available"); SetEventName(eBroadcastBitProfileData, "profile-data-available"); + SetEventName(eBroadcastBitStructuredData, "structured-data-available"); m_private_state_control_broadcaster.SetEventName(eBroadcastInternalStateControlStop, "control-stop"); m_private_state_control_broadcaster.SetEventName(eBroadcastInternalStateControlPause, "control-pause"); @@ -804,7 +806,8 @@ m_listener_sp->StartListeningForEvents(this, eBroadcastBitStateChanged | eBroadcastBitInterrupt | eBroadcastBitSTDOUT | eBroadcastBitSTDERR | - eBroadcastBitProfileData); + eBroadcastBitProfileData | + eBroadcastBitStructuredData); m_private_state_listener_sp->StartListeningForEvents(&m_private_state_broadcaster, eBroadcastBitStateChanged | eBroadcastBitInterrupt); @@ -4795,6 +4798,25 @@ BroadcastEventIfUnique (eBroadcastBitProfileData, new ProcessEventData (shared_from_this(), GetState())); } +void +Process::BroadcastStructuredData(const StructuredData::ObjectSP &object_sp, + const StructuredDataPluginSP &plugin_sp) +{ + BroadcastEvent(eBroadcastBitStructuredData, + new EventDataStructuredData(shared_from_this(), + object_sp, plugin_sp)); +} + +StructuredDataPluginSP +Process::GetStructuredDataPlugin(const ConstString &type_name) const +{ + auto find_it = m_structured_data_plugin_map.find(type_name); + if (find_it != m_structured_data_plugin_map.end()) + return find_it->second; + else + return StructuredDataPluginSP(); +} + size_t Process::GetAsyncProfileData (char *buf, size_t buf_size, Error &error) { @@ -6381,6 +6403,13 @@ // loading shared libraries might cause a new one to try and load if (!m_os_ap) LoadOperatingSystemPlugin(false); + + // Give structured-data plugins a chance to see the modified modules. + for (auto pair : m_structured_data_plugin_map) + { + if (pair.second) + pair.second->ModulesDidLoad(*this, module_list); + } } void @@ -6598,5 +6627,129 @@ } while (range_end != LLDB_INVALID_ADDRESS); return error; +} + +Error +Process::ConfigureStructuredData(const ConstString &type_name, + const StructuredData::ObjectSP &config_sp) +{ + // If you get this, the Process-derived class needs to implement a method + // to enable an already-reported asynchronous structured data feature. + // See ProcessGDBRemote for an example implementation over gdb-remote. + return Error("unimplemented"); +} + +void +Process::MapSupportedStructuredDataPlugins(const StructuredData::Array + &supported_type_names) +{ + Log *log(lldb_private::GetLogIfAnyCategoriesSet(LIBLLDB_LOG_PROCESS)); + + // Bail out early if there are no type names to map. + if (supported_type_names.GetSize() == 0) + { + if (log) + log->Printf("Process::%s(): no structured data types supported", + __FUNCTION__); + return; + } + + // Convert StructuredData type names to ConstString instances. + std::set const_type_names; + + if (log) + log->Printf("Process::%s(): the process supports the following async " + "structured data types:", __FUNCTION__); + + supported_type_names.ForEach([&const_type_names, &log] + (StructuredData::Object *object) { + if (!object) + { + // Invalid - shouldn't be null objects in the array. + return false; + } + + auto type_name = object->GetAsString(); + if (!type_name) + { + // Invalid format - all type names should be strings. + return false; + } + + const_type_names.insert(ConstString(type_name->GetValue())); + if (log) + log->Printf("- %s", type_name->GetValue().c_str()); + return true; + }); + + // For each StructuredDataPlugin, if the plugin handles any of the + // types in the supported_type_names, map that type name to that plugin. + uint32_t plugin_index = 0; + for (auto create_instance = + PluginManager::GetStructuredDataPluginCreateCallbackAtIndex(plugin_index); + create_instance && !const_type_names.empty(); + ++plugin_index) + { + // Create the plugin. + StructuredDataPluginSP plugin_sp = (*create_instance)(*this); + if (!plugin_sp) + { + // This plugin doesn't think it can work with the process. + // Move on to the next. + continue; + } + + // For any of the remaining type names, map any that this plugin + // supports. + std::vector names_to_remove; + for (auto &type_name : const_type_names) + { + if (plugin_sp->SupportsStructuredDataType(type_name)) + { + m_structured_data_plugin_map.insert(std::make_pair(type_name, + plugin_sp)); + names_to_remove.push_back(type_name); + if (log) + log->Printf("Process::%s(): using plugin %s for type name " + "%s", __FUNCTION__, + plugin_sp->GetPluginName().GetCString(), + type_name.GetCString()); + } + } + // Remove the type names that were consumed by this plugin. + for (auto &type_name : names_to_remove) + const_type_names.erase(type_name); + } +} + +bool +Process::RouteAsyncStructuredData(const StructuredData::ObjectSP object_sp) +{ + // Nothing to do if there's no data. + if (!object_sp) + return false; + + // The contract is this must be a dictionary, so we can look up the + // routing key via the top-level 'type' string value within the dictionary. + StructuredData::Dictionary *dictionary = object_sp->GetAsDictionary(); + if (!dictionary) + return false; + + // Grab the async structured type name (i.e. the feature/plugin name). + ConstString type_name; + if (!dictionary->GetValueForKeyAsString("type", type_name)) + return false; + + // Check if there's a plugin registered for this type name. + auto find_it = m_structured_data_plugin_map.find(type_name); + if (find_it == m_structured_data_plugin_map.end()) + { + // We don't have a mapping for this structured data type. + return false; + } + + // Route the structured data to the plugin. + find_it->second->HandleArrivalOfStructuredData(*this, type_name, object_sp); + return true; } Index: source/Target/StructuredDataPlugin.cpp =================================================================== --- /dev/null +++ source/Target/StructuredDataPlugin.cpp @@ -0,0 +1,97 @@ +//===-- StructuredDataPlugin.cpp --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Target/StructuredDataPlugin.h" + +#include "lldb/Core/Debugger.h" +#include "lldb/Interpreter/CommandInterpreter.h" +#include "lldb/Interpreter/CommandObjectMultiword.h" + +using namespace lldb; +using namespace lldb_private; + +namespace +{ + class CommandStructuredData : public CommandObjectMultiword + { + public: + CommandStructuredData(CommandInterpreter &interpreter) : + CommandObjectMultiword(interpreter, + "structured-data", + "Parent for per-plugin structured data commands", + "plugin structured-data ") + { + } + + ~CommandStructuredData() + { + } + }; +} + +StructuredDataPlugin::StructuredDataPlugin(const ProcessWP &process_wp) : + PluginInterface(), + m_process_wp(process_wp) +{ +} + +StructuredDataPlugin::~StructuredDataPlugin() +{ +} + +bool +StructuredDataPlugin::GetEnabled(const ConstString &type_name) const +{ + // By default, plugins are always enabled. Plugin authors should override + // this if there is an enabled/disabled state for their plugin. +} + +ProcessSP +StructuredDataPlugin::GetProcess() const +{ + return m_process_wp.lock(); +} + +bool +StructuredDataPlugin::ShouldPrint(const StructuredData::ObjectSP + &object_sp) const +{ + // Derived classes can override as they see fit. + return true; +} + +void +StructuredDataPlugin::InitializeBasePluginForDebugger(Debugger &debugger) +{ + // Create our mutliword command anchor if it doesn't already exist. + auto &interpreter = debugger.GetCommandInterpreter(); + if (!interpreter.GetCommandObject("plugin structured-data")) + { + // Find the parent command. + auto parent_command = + debugger.GetCommandInterpreter().GetCommandObject("plugin"); + if (!parent_command) + return; + + // Create the structured-data ommand object. + auto command_name = "structured-data"; + auto command_sp = + CommandObjectSP(new CommandStructuredData(interpreter)); + + // Hook it up under the top-level plugin command. + parent_command->LoadSubCommand(command_name, + command_sp); + } +} + +void +StructuredDataPlugin::ModulesDidLoad(Process &process, ModuleList &module_list) +{ + // Default implementation does nothing. +} Index: source/Target/ThreadPlanCallOnFunctionExit.cpp =================================================================== --- /dev/null +++ source/Target/ThreadPlanCallOnFunctionExit.cpp @@ -0,0 +1,119 @@ +//===-- ThreadPlanCallOnFunctionExit.cpp ------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb/Target/ThreadPlanCallOnFunctionExit.h" + +using namespace lldb; +using namespace lldb_private; + +ThreadPlanCallOnFunctionExit::ThreadPlanCallOnFunctionExit(Thread &thread, + const Callback + &callback) : + ThreadPlan(ThreadPlanKind::eKindGeneric, "CallOnFunctionExit", + thread, + eVoteNoOpinion, eVoteNoOpinion // TODO check with Jim on these + ), + m_callback(callback) +{ + // We are not a user-generated plan. + SetIsMasterPlan(false); +} + +void +ThreadPlanCallOnFunctionExit::DidPush() +{ + // We now want to queue the "step out" thread plan so it executes + // and completes. + + // Set stop vote to eVoteNo. + m_step_out_threadplan_sp = GetThread() + .QueueThreadPlanForStepOut(false, // abort other plans + nullptr, // addr_context + true, // first instruction + true, // stop other threads + eVoteNo, // do not say "we're stopping" + eVoteNoOpinion, // don't care about + // run state broadcasting + 0, // frame_idx + eLazyBoolCalculate // avoid code w/o debinfo + ); +} + +// ------------------------------------------------------------------------- +// ThreadPlan API +// ------------------------------------------------------------------------- + +void +ThreadPlanCallOnFunctionExit::GetDescription(Stream *s, lldb::DescriptionLevel + level) +{ + if (!s) + return; + s->Printf("Running until completion of current function, then making " + "callback."); +} + +bool +ThreadPlanCallOnFunctionExit::ValidatePlan(Stream *error) +{ + // We'll say we're always good since I don't know what would make this + // invalid. + return true; +} + +bool +ThreadPlanCallOnFunctionExit::ShouldStop(Event *event_ptr) +{ + // If this is where we find out that an internal stop came in, then: + // Check if the step-out plan completed. If it did, then we want to + // run the callback here (our reason for living...) + if (m_step_out_threadplan_sp && + m_step_out_threadplan_sp->IsPlanComplete()) + { + m_callback(); + + // We no longer need the pointer to the step-out thread plan. + m_step_out_threadplan_sp.reset(); + + // Indicate that this plan is done and can be discarded. + SetPlanComplete(); + + // We're done now, but we want to return false so that we + // don't cause the thread to really stop. + } + + return false; +} + +bool +ThreadPlanCallOnFunctionExit::WillStop() +{ + // The code looks like the return value is ignored via ThreadList:: + // ShouldStop(). + // This is called when we really are going to stop. We don't care + // and don't need to do anything here. + return false; +} + +bool +ThreadPlanCallOnFunctionExit::DoPlanExplainsStop (Event *event_ptr) +{ + // We don't ever explain a stop. The only stop that is relevant + // to us directly is the step_out plan we added to do the heavy lifting + // of getting us past the current method. + return false; +} + +lldb::StateType +ThreadPlanCallOnFunctionExit::GetPlanRunState() +{ + // This value doesn't matter - we'll never be the top thread plan, so + // nobody will ask us this question. + return eStateRunning; +} Index: tools/debugserver/debugserver.xcodeproj/project.pbxproj =================================================================== --- tools/debugserver/debugserver.xcodeproj/project.pbxproj +++ tools/debugserver/debugserver.xcodeproj/project.pbxproj @@ -7,6 +7,28 @@ objects = { /* Begin PBXBuildFile section */ + 23043C9D1D35DBEC00FC25CA /* JSON.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 233B4EA51D2DB54300E98261 /* JSON.cpp */; }; + 23043C9E1D35DBFA00FC25CA /* StringConvert.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 233B4EA81D2DB96A00E98261 /* StringConvert.cpp */; }; + 2307CCCB1D4A5D630016ABC0 /* LogFilterExactMatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 237821AE1D4917D20028B7A1 /* LogFilterExactMatch.cpp */; }; + 233B4EA71D2DB54300E98261 /* JSON.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 233B4EA51D2DB54300E98261 /* JSON.cpp */; }; + 233B4EA91D2DB96A00E98261 /* StringConvert.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 233B4EA81D2DB96A00E98261 /* StringConvert.cpp */; }; + 23562ED21D3424DF00AB2BD4 /* LogMessageOsLog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23562ED01D3424DF00AB2BD4 /* LogMessageOsLog.cpp */; }; + 23562ED31D3424DF00AB2BD4 /* LogMessageOsLog.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23562ED01D3424DF00AB2BD4 /* LogMessageOsLog.cpp */; }; + 23562ED61D342A5A00AB2BD4 /* ActivityStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23562ED51D342A5A00AB2BD4 /* ActivityStore.cpp */; }; + 23562ED71D342A5A00AB2BD4 /* ActivityStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23562ED51D342A5A00AB2BD4 /* ActivityStore.cpp */; }; + 23562ED91D342B0000AB2BD4 /* LogMessage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23562ED81D342B0000AB2BD4 /* LogMessage.cpp */; }; + 23562EDA1D342B0000AB2BD4 /* LogMessage.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23562ED81D342B0000AB2BD4 /* LogMessage.cpp */; }; + 237821B01D4917D20028B7A1 /* LogFilterExactMatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 237821AE1D4917D20028B7A1 /* LogFilterExactMatch.cpp */; }; + 23AC04C61D2F41A00072351D /* LogFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AC04C41D2F41A00072351D /* LogFilter.cpp */; }; + 23AC04C71D2F41A00072351D /* LogFilter.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AC04C41D2F41A00072351D /* LogFilter.cpp */; }; + 23AC04CA1D2F42250072351D /* LogFilterChain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AC04C81D2F42250072351D /* LogFilterChain.cpp */; }; + 23AC04CB1D2F42250072351D /* LogFilterChain.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AC04C81D2F42250072351D /* LogFilterChain.cpp */; }; + 23AC04CF1D2F58AF0072351D /* LogFilterRegex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AC04CD1D2F58AF0072351D /* LogFilterRegex.cpp */; }; + 23AC04D01D2F58AF0072351D /* LogFilterRegex.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AC04CD1D2F58AF0072351D /* LogFilterRegex.cpp */; }; + 23AE72E41D25DECF00945BCE /* DarwinLogCollector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AE72E21D25DECF00945BCE /* DarwinLogCollector.cpp */; }; + 23AE72E51D25DEE100945BCE /* DarwinLogCollector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23AE72E21D25DECF00945BCE /* DarwinLogCollector.cpp */; }; + 23D1B0291D497E8B00FF831B /* OsLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23D1B0271D497E8B00FF831B /* OsLogger.cpp */; }; + 23D1B02A1D497E8B00FF831B /* OsLogger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 23D1B0271D497E8B00FF831B /* OsLogger.cpp */; }; 264D5D581293835600ED4C01 /* DNBArch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 264D5D571293835600ED4C01 /* DNBArch.cpp */; }; 2660D9CE1192280900958FBD /* StringExtractor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2660D9CC1192280900958FBD /* StringExtractor.cpp */; }; 266B5ED11460A68200E43F0A /* DNBArchImplARM64.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 266B5ECF1460A68200E43F0A /* DNBArchImplARM64.cpp */; }; @@ -82,6 +104,33 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 2307CCCC1D4A5DAE0016ABC0 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 233B4EA51D2DB54300E98261 /* JSON.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSON.cpp; sourceTree = ""; }; + 233B4EA61D2DB54300E98261 /* JSON.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSON.h; sourceTree = ""; }; + 233B4EA81D2DB96A00E98261 /* StringConvert.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = StringConvert.cpp; path = ../../../source/Host/common/StringConvert.cpp; sourceTree = ""; }; + 23562ECF1D34110D00AB2BD4 /* DarwinLogTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinLogTypes.h; sourceTree = ""; }; + 23562ED01D3424DF00AB2BD4 /* LogMessageOsLog.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LogMessageOsLog.cpp; sourceTree = ""; }; + 23562ED11D3424DF00AB2BD4 /* LogMessageOsLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogMessageOsLog.h; sourceTree = ""; }; + 23562ED41D3426DD00AB2BD4 /* ActivityStore.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStore.h; sourceTree = ""; }; + 23562ED51D342A5A00AB2BD4 /* ActivityStore.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ActivityStore.cpp; sourceTree = ""; }; + 23562ED81D342B0000AB2BD4 /* LogMessage.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LogMessage.cpp; sourceTree = ""; }; + 237821AD1D4917D20028B7A1 /* CMakeLists.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CMakeLists.txt; sourceTree = ""; }; + 237821AE1D4917D20028B7A1 /* LogFilterExactMatch.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LogFilterExactMatch.cpp; sourceTree = ""; }; + 237821AF1D4917D20028B7A1 /* LogFilterExactMatch.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogFilterExactMatch.h; sourceTree = ""; }; + 23AC04C41D2F41A00072351D /* LogFilter.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LogFilter.cpp; sourceTree = ""; }; + 23AC04C51D2F41A00072351D /* LogFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogFilter.h; sourceTree = ""; }; + 23AC04C81D2F42250072351D /* LogFilterChain.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LogFilterChain.cpp; sourceTree = ""; }; + 23AC04C91D2F42250072351D /* LogFilterChain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogFilterChain.h; sourceTree = ""; }; + 23AC04CC1D2F42F10072351D /* DarwinLogInterfaces.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinLogInterfaces.h; sourceTree = ""; }; + 23AC04CD1D2F58AF0072351D /* LogFilterRegex.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LogFilterRegex.cpp; sourceTree = ""; }; + 23AC04CE1D2F58AF0072351D /* LogFilterRegex.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LogFilterRegex.h; sourceTree = ""; }; + 23AC04D11D2F60130072351D /* LogMessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LogMessage.h; sourceTree = ""; }; + 23AE72E21D25DECF00945BCE /* DarwinLogCollector.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DarwinLogCollector.cpp; sourceTree = ""; }; + 23AE72E31D25DECF00945BCE /* DarwinLogCollector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DarwinLogCollector.h; sourceTree = ""; }; + 23AE72E61D25DEFB00945BCE /* ActivityStreamSPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamSPI.h; sourceTree = ""; }; + 23CF6F5E1D28A3760088ADC9 /* DarwinLogEvent.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DarwinLogEvent.h; sourceTree = ""; }; + 23D1B0271D497E8B00FF831B /* OsLogger.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OsLogger.cpp; sourceTree = ""; }; + 23D1B0281D497E8B00FF831B /* OsLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OsLogger.h; sourceTree = ""; }; 260828DE0CBAF7F400F95054 /* DNBRuntimeAction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNBRuntimeAction.h; sourceTree = ""; }; 260E7331114BFFE600D1DFB3 /* DNBThreadResumeActions.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DNBThreadResumeActions.cpp; sourceTree = ""; }; 260E7332114BFFE600D1DFB3 /* DNBThreadResumeActions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DNBThreadResumeActions.h; sourceTree = ""; }; @@ -217,6 +266,34 @@ name = Products; sourceTree = ""; }; + 23AC04C31D2F3E9A0072351D /* DarwinLog */ = { + isa = PBXGroup; + children = ( + 237821AD1D4917D20028B7A1 /* CMakeLists.txt */, + 23562ED41D3426DD00AB2BD4 /* ActivityStore.h */, + 23562ED51D342A5A00AB2BD4 /* ActivityStore.cpp */, + 23AE72E61D25DEFB00945BCE /* ActivityStreamSPI.h */, + 23AE72E31D25DECF00945BCE /* DarwinLogCollector.h */, + 23AE72E21D25DECF00945BCE /* DarwinLogCollector.cpp */, + 23CF6F5E1D28A3760088ADC9 /* DarwinLogEvent.h */, + 23AC04CC1D2F42F10072351D /* DarwinLogInterfaces.h */, + 23562ECF1D34110D00AB2BD4 /* DarwinLogTypes.h */, + 23AC04C51D2F41A00072351D /* LogFilter.h */, + 23AC04C41D2F41A00072351D /* LogFilter.cpp */, + 23AC04C91D2F42250072351D /* LogFilterChain.h */, + 23AC04C81D2F42250072351D /* LogFilterChain.cpp */, + 237821AF1D4917D20028B7A1 /* LogFilterExactMatch.h */, + 237821AE1D4917D20028B7A1 /* LogFilterExactMatch.cpp */, + 23AC04CE1D2F58AF0072351D /* LogFilterRegex.h */, + 23AC04CD1D2F58AF0072351D /* LogFilterRegex.cpp */, + 23AC04D11D2F60130072351D /* LogMessage.h */, + 23562ED81D342B0000AB2BD4 /* LogMessage.cpp */, + 23562ED11D3424DF00AB2BD4 /* LogMessageOsLog.h */, + 23562ED01D3424DF00AB2BD4 /* LogMessageOsLog.cpp */, + ); + path = DarwinLog; + sourceTree = ""; + }; 266B5ECE1460A68200E43F0A /* arm64 */ = { isa = PBXGroup; children = ( @@ -273,6 +350,8 @@ 26C637E20C71334A0024798E /* DNBRegisterInfo.cpp */, 260E7332114BFFE600D1DFB3 /* DNBThreadResumeActions.h */, 260E7331114BFFE600D1DFB3 /* DNBThreadResumeActions.cpp */, + 233B4EA61D2DB54300E98261 /* JSON.h */, + 233B4EA51D2DB54300E98261 /* JSON.cpp */, 264F679A1B2F9EB200140093 /* JSONGenerator.h */, AF67AC000D34604D0022D128 /* PseudoTerminal.h */, AF67ABFF0D34604D0022D128 /* PseudoTerminal.cpp */, @@ -281,6 +360,7 @@ 26C637FE0C71334A0024798E /* PThreadEvent.cpp */, 26C638000C71334A0024798E /* PThreadMutex.h */, 2672DBEE0EEF446700E92059 /* PThreadMutex.cpp */, + 233B4EA81D2DB96A00E98261 /* StringConvert.cpp */, 26C638020C71334A0024798E /* SysSignal.h */, 26C638010C71334A0024798E /* SysSignal.cpp */, 26C638060C71334A0024798E /* TTYState.h */, @@ -315,13 +395,13 @@ 26C637E60C71334A0024798E /* MacOSX */ = { isa = PBXGroup; children = ( - AF0934BA18E12B92005A11FD /* Genealogy.h */, - AF0934BB18E12B92005A11FD /* GenealogySPI.h */, + 23AC04C31D2F3E9A0072351D /* DarwinLog */, 2695DD920D3EBFF6007E4CA2 /* CFBundle.h */, 2695DD910D3EBFF6007E4CA2 /* CFBundle.cpp */, 2695DD9A0D3EC160007E4CA2 /* CFString.h */, 2695DD9B0D3EC160007E4CA2 /* CFString.cpp */, 26C637E70C71334A0024798E /* CFUtils.h */, + 2307CCCC1D4A5DAE0016ABC0 /* CMakeLists.txt */, 2675D41C0CCEB6CF000F49AF /* arm */, 266B5ECE1460A68200E43F0A /* arm64 */, 26C637E90C71334A0024798E /* i386 */, @@ -331,6 +411,8 @@ 4971AE7113D10F4F00649E37 /* HasAVX.s */, 26C637E80C71334A0024798E /* dbgnub-mig.defs */, AFEC3363194A8B0B00FF05C6 /* Genealogy.cpp */, + AF0934BA18E12B92005A11FD /* Genealogy.h */, + AF0934BB18E12B92005A11FD /* GenealogySPI.h */, 26C637EF0C71334A0024798E /* MachException.h */, 26C637EE0C71334A0024798E /* MachException.cpp */, 26C637F10C71334A0024798E /* MachProcess.h */, @@ -345,6 +427,8 @@ 26C637F80C71334A0024798E /* MachVMRegion.cpp */, 26B67DE00EE9BC30006C8BC0 /* MachTask.h */, 26B67DE10EE9BC30006C8BC0 /* MachTask.mm */, + 23D1B0281D497E8B00FF831B /* OsLogger.h */, + 23D1B0271D497E8B00FF831B /* OsLogger.cpp */, 9457ECF61419864100DFE7D8 /* stack_logging.h */, ); path = MacOSX; @@ -490,35 +574,47 @@ 26CE05A9115C36250022F371 /* debugserver.cpp in Sources */, 26CE05AA115C36260022F371 /* RNBContext.cpp in Sources */, 26CE05AB115C36270022F371 /* RNBServices.cpp in Sources */, + 23D1B0291D497E8B00FF831B /* OsLogger.cpp in Sources */, 26CE05AC115C36280022F371 /* RNBSocket.cpp in Sources */, 26CE05AD115C36280022F371 /* RNBRemote.cpp in Sources */, 26CE05AE115C36320022F371 /* dbgnub-mig.defs in Sources */, 26CE05B0115C36340022F371 /* MachException.cpp in Sources */, 26CE05B1115C36350022F371 /* MachProcess.mm in Sources */, 26CE05B2115C36360022F371 /* MachThread.cpp in Sources */, + 233B4EA71D2DB54300E98261 /* JSON.cpp in Sources */, 26CE05B3115C36370022F371 /* MachThreadList.cpp in Sources */, 26CE05B4115C36380022F371 /* MachVMMemory.cpp in Sources */, 26CE05B5115C36380022F371 /* MachVMRegion.cpp in Sources */, 26CE05B6115C36390022F371 /* MachTask.mm in Sources */, 26CE05B7115C363B0022F371 /* DNB.cpp in Sources */, AFEC3364194A8B0B00FF05C6 /* Genealogy.cpp in Sources */, + 23AC04CF1D2F58AF0072351D /* LogFilterRegex.cpp in Sources */, + 233B4EA91D2DB96A00E98261 /* StringConvert.cpp in Sources */, + 23562ED21D3424DF00AB2BD4 /* LogMessageOsLog.cpp in Sources */, 26CE05B8115C363C0022F371 /* DNBBreakpoint.cpp in Sources */, 26CE05B9115C363D0022F371 /* DNBDataRef.cpp in Sources */, + 23AC04CA1D2F42250072351D /* LogFilterChain.cpp in Sources */, + 23562ED61D342A5A00AB2BD4 /* ActivityStore.cpp in Sources */, 26CE05BA115C363E0022F371 /* DNBLog.cpp in Sources */, + 23AC04C61D2F41A00072351D /* LogFilter.cpp in Sources */, 26CE05BB115C363F0022F371 /* DNBRegisterInfo.cpp in Sources */, 26CE05BC115C36420022F371 /* PThreadEvent.cpp in Sources */, 26CE05BD115C36430022F371 /* PThreadMutex.cpp in Sources */, 26CE05BE115C36440022F371 /* SysSignal.cpp in Sources */, + 23AE72E41D25DECF00945BCE /* DarwinLogCollector.cpp in Sources */, 26CE05BF115C364D0022F371 /* DNBArchImplX86_64.cpp in Sources */, 26CE05C0115C364F0022F371 /* DNBArchImplI386.cpp in Sources */, 26CE05C1115C36510022F371 /* DNBArchImpl.cpp in Sources */, 26CE05C2115C36550022F371 /* DNBArchImpl.cpp in Sources */, - 26CE05C3115C36580022F371 /* CFString.cpp in Sources */, 26CE05C5115C36590022F371 /* CFBundle.cpp in Sources */, + 26CE05C3115C36580022F371 /* CFString.cpp in Sources */, + 26CE05C4115C36590022F371 /* CFData.cpp in Sources */, + 23562ED91D342B0000AB2BD4 /* LogMessage.cpp in Sources */, 26CE05F1115C387C0022F371 /* PseudoTerminal.cpp in Sources */, 2660D9CE1192280900958FBD /* StringExtractor.cpp in Sources */, 264D5D581293835600ED4C01 /* DNBArch.cpp in Sources */, 4971AE7213D10F4F00649E37 /* HasAVX.s in Sources */, + 237821B01D4917D20028B7A1 /* LogFilterExactMatch.cpp in Sources */, 266B5ED11460A68200E43F0A /* DNBArchImplARM64.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -530,37 +626,49 @@ 456F67461AD46CE9002850C2 /* DNBError.cpp in Sources */, 456F67471AD46CE9002850C2 /* DNBThreadResumeActions.cpp in Sources */, 456F67481AD46CE9002850C2 /* debugserver.cpp in Sources */, + 23043C9D1D35DBEC00FC25CA /* JSON.cpp in Sources */, 456F67491AD46CE9002850C2 /* RNBContext.cpp in Sources */, + 23D1B02A1D497E8B00FF831B /* OsLogger.cpp in Sources */, 456F674A1AD46CE9002850C2 /* RNBServices.cpp in Sources */, 456F674B1AD46CE9002850C2 /* RNBSocket.cpp in Sources */, 456F674C1AD46CE9002850C2 /* RNBRemote.cpp in Sources */, 456F674D1AD46CE9002850C2 /* dbgnub-mig.defs in Sources */, 456F674E1AD46CE9002850C2 /* MachException.cpp in Sources */, 456F674F1AD46CE9002850C2 /* MachProcess.mm in Sources */, + 2307CCCB1D4A5D630016ABC0 /* LogFilterExactMatch.cpp in Sources */, 456F67501AD46CE9002850C2 /* MachThread.cpp in Sources */, 456F67511AD46CE9002850C2 /* MachThreadList.cpp in Sources */, 456F67521AD46CE9002850C2 /* MachVMMemory.cpp in Sources */, 456F67531AD46CE9002850C2 /* MachVMRegion.cpp in Sources */, + 23562ED71D342A5A00AB2BD4 /* ActivityStore.cpp in Sources */, 456F67541AD46CE9002850C2 /* MachTask.mm in Sources */, 456F67551AD46CE9002850C2 /* DNB.cpp in Sources */, 456F67561AD46CE9002850C2 /* Genealogy.cpp in Sources */, 456F67571AD46CE9002850C2 /* DNBBreakpoint.cpp in Sources */, 456F67581AD46CE9002850C2 /* DNBDataRef.cpp in Sources */, 456F67591AD46CE9002850C2 /* DNBLog.cpp in Sources */, + 23562ED31D3424DF00AB2BD4 /* LogMessageOsLog.cpp in Sources */, 456F675A1AD46CE9002850C2 /* DNBRegisterInfo.cpp in Sources */, 456F675B1AD46CE9002850C2 /* PThreadEvent.cpp in Sources */, 456F675C1AD46CE9002850C2 /* PThreadMutex.cpp in Sources */, 456F675D1AD46CE9002850C2 /* SysSignal.cpp in Sources */, + 23AE72E51D25DEE100945BCE /* DarwinLogCollector.cpp in Sources */, 456F675E1AD46CE9002850C2 /* DNBArchImplX86_64.cpp in Sources */, + 23562EDA1D342B0000AB2BD4 /* LogMessage.cpp in Sources */, 456F675F1AD46CE9002850C2 /* DNBArchImplI386.cpp in Sources */, 456F67601AD46CE9002850C2 /* DNBArchImpl.cpp in Sources */, + 23AC04C71D2F41A00072351D /* LogFilter.cpp in Sources */, + 23043C9E1D35DBFA00FC25CA /* StringConvert.cpp in Sources */, 456F67611AD46CE9002850C2 /* DNBArchImpl.cpp in Sources */, 456F67621AD46CE9002850C2 /* CFString.cpp in Sources */, + 456F67631AD46CE9002850C2 /* CFData.cpp in Sources */, + 23AC04CB1D2F42250072351D /* LogFilterChain.cpp in Sources */, 456F67641AD46CE9002850C2 /* CFBundle.cpp in Sources */, 456F67651AD46CE9002850C2 /* PseudoTerminal.cpp in Sources */, 456F67661AD46CE9002850C2 /* StringExtractor.cpp in Sources */, 456F67671AD46CE9002850C2 /* DNBArch.cpp in Sources */, 456F67681AD46CE9002850C2 /* HasAVX.s in Sources */, + 23AC04D01D2F58AF0072351D /* LogFilterRegex.cpp in Sources */, 456F67691AD46CE9002850C2 /* DNBArchImplARM64.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -600,6 +708,8 @@ "LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1"; LLDB_COMPRESSION_LDFLAGS = ""; "LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression"; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 0; LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; LLDB_ZLIB_LDFLAGS = "-lz"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -642,6 +752,8 @@ "LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1"; LLDB_COMPRESSION_LDFLAGS = ""; "LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression"; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 0; LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; LLDB_ZLIB_LDFLAGS = "-lz"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -682,6 +794,8 @@ "LLDB_COMPRESSION_CFLAGS[sdk=macosx10.11]" = "-DHAVE_LIBCOMPRESSION=1"; LLDB_COMPRESSION_LDFLAGS = ""; "LLDB_COMPRESSION_LDFLAGS[sdk=macosx10.11]" = "-weak-lcompression"; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 1; LLDB_ZLIB_CFLAGS = "-DHAVE_LIBZ=1"; LLDB_ZLIB_LDFLAGS = "-lz"; MACOSX_DEPLOYMENT_TARGET = 10.10; @@ -743,6 +857,7 @@ "-DDT_VARIANT_$(DT_VARIANT)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*]" = ( "-Wparentheses", @@ -752,6 +867,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -843,6 +959,7 @@ "-DDT_VARIANT_$(DT_VARIANT)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", @@ -852,6 +969,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -942,6 +1060,7 @@ "-DDT_VARIANT_$(DT_VARIANT)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", @@ -951,6 +1070,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -1050,6 +1170,7 @@ "-Wparentheses", "-DOS_OBJECT_USE_OBJC=0", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -1119,6 +1240,7 @@ "-Wparentheses", "-DOS_OBJECT_USE_OBJC=0", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -1184,11 +1306,13 @@ "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", "-DOS_OBJECT_USE_OBJC=0", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -1255,12 +1379,15 @@ "-Wparentheses", "$(LLDB_ENERGY_CFLAGS)", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*]" = ( "-Wparentheses", "-DOS_OBJECT_USE_OBJC=0", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); + OTHER_CPLUSPLUSFLAGS = "$(OTHER_CFLAGS)"; "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( "-framework", @@ -1312,6 +1439,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx.internal; @@ -1374,6 +1503,7 @@ "-DDT_VARIANT_$(DT_VARIANT)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", @@ -1383,6 +1513,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( @@ -1451,6 +1582,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx.internal; @@ -1512,6 +1645,7 @@ "$(LLDB_ENERGY_CFLAGS)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", @@ -1521,6 +1655,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( @@ -1595,11 +1730,13 @@ "-Wparentheses", "$(LLDB_ENERGY_CFLAGS)", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", "-DOS_OBJECT_USE_OBJC=0", "-DDEBUGSERVER_PROGRAM_SYMBOL=debugserver_nonui", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( @@ -1669,6 +1806,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx.internal; @@ -1721,6 +1860,7 @@ "-DDT_VARIANT_$(DT_VARIANT)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", @@ -1730,6 +1870,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; OTHER_LDFLAGS = ""; @@ -1798,6 +1939,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + LLDB_OS_LOG_CFLAGS = "-DLLDB_USE_OS_LOG=$(LLDB_USE_OS_LOG)"; + LLDB_USE_OS_LOG = 0; MACOSX_DEPLOYMENT_TARGET = 10.10; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx.internal; @@ -1851,6 +1994,7 @@ "-DDT_VARIANT_$(DT_VARIANT)", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CFLAGS[sdk=iphoneos*][arch=*]" = ( "-Wparentheses", @@ -1860,6 +2004,7 @@ "-DOS_OBJECT_USE_OBJC=0", "$(LLDB_COMPRESSION_CFLAGS)", "$(LLDB_ZLIB_CFLAGS)", + "$(LLDB_OS_LOG_CFLAGS)", ); "OTHER_CPLUSPLUSFLAGS[sdk=iphoneos*][arch=*]" = "$(OTHER_CFLAGS)"; "OTHER_LDFLAGS[sdk=iphoneos*][arch=*]" = ( Index: tools/debugserver/source/CMakeLists.txt =================================================================== --- tools/debugserver/source/CMakeLists.txt +++ tools/debugserver/source/CMakeLists.txt @@ -1,5 +1,6 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}/..) include_directories(${LLDB_SOURCE_DIR}/source) +include_directories(MacOSX/DarwinLog) if (CMAKE_SYSTEM_NAME MATCHES "Darwin") include_directories(MacOSX) @@ -42,6 +43,11 @@ DNBLog.cpp DNBRegisterInfo.cpp DNBThreadResumeActions.cpp + JSON.cpp + # JSON reader depends on the following LLDB-common files + ${LLDB_SOURCE_DIR}/source/Host/common/StringConvert.cpp + ${LLDB_SOURCE_DIR}/source/Utility/StringExtractor.cpp + # end JSON reader dependencies libdebugserver.cpp PseudoTerminal.cpp PThreadEvent.cpp Index: tools/debugserver/source/DNB.h =================================================================== --- tools/debugserver/source/DNB.h +++ tools/debugserver/source/DNB.h @@ -14,6 +14,7 @@ #ifndef __DNB_h__ #define __DNB_h__ +#include "MacOSX/DarwinLog/DarwinLogEvent.h" #include "MacOSX/Genealogy.h" #include "MacOSX/ThreadInfo.h" #include "JSONGenerator.h" @@ -81,6 +82,8 @@ int DNBProcessMemoryRegionInfo (nub_process_t pid, nub_addr_t addr, DNBRegionInfo *region_info) DNB_EXPORT; std::string DNBProcessGetProfileData (nub_process_t pid, DNBProfileDataScanType scanType) DNB_EXPORT; nub_bool_t DNBProcessSetEnableAsyncProfiling (nub_process_t pid, nub_bool_t enable, uint64_t interval_usec, DNBProfileDataScanType scan_type) DNB_EXPORT; +DarwinLogEventVector DNBProcessGetAvailableDarwinLogEvents(nub_process_t pid); + //---------------------------------------------------------------------- // Process status Index: tools/debugserver/source/DNB.cpp =================================================================== --- tools/debugserver/source/DNB.cpp +++ tools/debugserver/source/DNB.cpp @@ -41,6 +41,7 @@ #endif #endif +#include "MacOSX/DarwinLog/DarwinLogCollector.h" #include "MacOSX/MachProcess.h" #include "MacOSX/MachTask.h" #include "MacOSX/Genealogy.h" @@ -1868,6 +1869,12 @@ return 0; } +DarwinLogEventVector +DNBProcessGetAvailableDarwinLogEvents(nub_process_t pid) +{ + return DarwinLogCollector::GetEventsForProcess(pid); +} + nub_size_t DNBProcessGetStopCount (nub_process_t pid) { Index: tools/debugserver/source/DNBDefs.h =================================================================== --- tools/debugserver/source/DNBDefs.h +++ tools/debugserver/source/DNBDefs.h @@ -142,6 +142,7 @@ #define LOG_WATCHPOINTS (1u << 11) #define LOG_STEP (1u << 12) #define LOG_TASK (1u << 13) +#define LOG_DARWIN_LOG (1u << 14) #define LOG_LO_USER (1u << 16) #define LOG_HI_USER (1u << 31) #define LOG_ALL 0xFFFFFFFFu Index: tools/debugserver/source/JSON.h =================================================================== --- /dev/null +++ tools/debugserver/source/JSON.h @@ -0,0 +1,382 @@ +//===---------------------JSON.h --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef utility_JSON_h_ +#define utility_JSON_h_ + +// This cross-project usage is fine as StringExtractor.h is entirely +// self-contained. +#include "lldb/Utility/StringExtractor.h" + +// C includes +#include +#include + +// C++ includes +#include +#include +#include +#include +#include + +class JSONValue +{ +public: + virtual void + Write (std::ostream& s) = 0; + + typedef std::shared_ptr SP; + + enum class Kind + { + String, + Number, + True, + False, + Null, + Object, + Array + }; + + JSONValue (Kind k) : + m_kind(k) + {} + + Kind + GetKind() const + { + return m_kind; + } + + virtual + ~JSONValue () = default; + +private: + const Kind m_kind; +}; + +class JSONString : public JSONValue +{ +public: + JSONString (); + JSONString (const char* s); + JSONString (const std::string& s); + + JSONString (const JSONString& s) = delete; + JSONString& + operator = (const JSONString& s) = delete; + + void + Write(std::ostream& s) override; + + typedef std::shared_ptr SP; + + std::string + GetData () { return m_data; } + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::String; + } + + ~JSONString() override = default; + +private: + + static std::string + json_string_quote_metachars (const std::string&); + + std::string m_data; +}; + +class JSONNumber : public JSONValue +{ +public: + typedef std::shared_ptr SP; + + // We cretae a constructor for all integer and floating point type with using templates and + // SFINAE to avoid having ambiguous overloads because of the implicit type promotion. If we + // would have constructors only with int64_t, uint64_t and double types then constructing a + // JSONNumber from an int32_t (or any other similar type) would fail to compile. + + template ::value && + std::is_unsigned::value>::type* = nullptr> + explicit JSONNumber (T u) : + JSONValue(JSONValue::Kind::Number), + m_data_type(DataType::Unsigned) + { + m_data.m_unsigned = u; + } + + template ::value && + std::is_signed::value>::type* = nullptr> + explicit JSONNumber (T s) : + JSONValue(JSONValue::Kind::Number), + m_data_type(DataType::Signed) + { + m_data.m_signed = s; + } + + template ::value>::type* = nullptr> + explicit JSONNumber (T d) : + JSONValue(JSONValue::Kind::Number), + m_data_type(DataType::Double) + { + m_data.m_double = d; + } + + ~JSONNumber() override = default; + + JSONNumber (const JSONNumber& s) = delete; + JSONNumber& + operator = (const JSONNumber& s) = delete; + + void + Write(std::ostream& s) override; + + uint64_t + GetAsUnsigned() const; + + int64_t + GetAsSigned() const; + + double + GetAsDouble() const; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::Number; + } + +private: + enum class DataType : uint8_t + { + Unsigned, + Signed, + Double + } m_data_type; + + union + { + uint64_t m_unsigned; + int64_t m_signed; + double m_double; + } m_data; +}; + +class JSONTrue : public JSONValue +{ +public: + JSONTrue (); + + JSONTrue (const JSONTrue& s) = delete; + JSONTrue& + operator = (const JSONTrue& s) = delete; + + void + Write(std::ostream& s) override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::True; + } + + ~JSONTrue() override = default; +}; + +class JSONFalse : public JSONValue +{ +public: + JSONFalse (); + + JSONFalse (const JSONFalse& s) = delete; + JSONFalse& + operator = (const JSONFalse& s) = delete; + + void + Write(std::ostream& s) override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::False; + } + + ~JSONFalse() override = default; +}; + +class JSONNull : public JSONValue +{ +public: + JSONNull (); + + JSONNull (const JSONNull& s) = delete; + JSONNull& + operator = (const JSONNull& s) = delete; + + void + Write(std::ostream& s) override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::Null; + } + + ~JSONNull() override = default; +}; + +class JSONObject : public JSONValue +{ +public: + JSONObject (); + + JSONObject (const JSONObject& s) = delete; + JSONObject& + operator = (const JSONObject& s) = delete; + + void + Write(std::ostream& s) override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::Object; + } + + bool + SetObject (const std::string& key, + JSONValue::SP value); + + JSONValue::SP + GetObject (const std::string& key) const; + + // ------------------------------------------------------------------------- + /// Return keyed value as bool + /// + /// @param[in] key + /// The value of the key to lookup + /// + /// @param[out] value + /// The value of the key as a bool. Undefined if the key doesn't + /// exist or if the key is not either true or false. + /// + /// @return + /// true if the key existed as was a bool value; false otherwise. + /// Note the return value is *not* the value of the bool, use + /// \b value for that. + // ------------------------------------------------------------------------- + bool + GetObjectAsBool (const std::string& key, bool& value) const; + + bool + GetObjectAsString (const std::string& key, std::string& value) const; + + ~JSONObject() override = default; + +private: + typedef std::map Map; + typedef Map::iterator Iterator; + Map m_elements; +}; + +class JSONArray : public JSONValue +{ +public: + JSONArray (); + + JSONArray (const JSONArray& s) = delete; + JSONArray& + operator = (const JSONArray& s) = delete; + + void + Write(std::ostream& s) override; + + typedef std::shared_ptr SP; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::Array; + } + +private: + typedef std::vector Vector; + typedef Vector::iterator Iterator; + typedef Vector::size_type Index; + typedef Vector::size_type Size; + +public: + bool + SetObject (Index i, + JSONValue::SP value); + + bool + AppendObject (JSONValue::SP value); + + JSONValue::SP + GetObject (Index i); + + Size + GetNumElements (); + + ~JSONArray() override = default; + + Vector m_elements; +}; + +class JSONParser : public StringExtractor +{ +public: + enum Token + { + Invalid, + Error, + ObjectStart, + ObjectEnd, + ArrayStart, + ArrayEnd, + Comma, + Colon, + String, + Integer, + Float, + True, + False, + Null, + EndOfFile + }; + + JSONParser (const char *cstr); + + int + GetEscapedChar (bool &was_escaped); + + Token + GetToken (std::string &value); + + JSONValue::SP + ParseJSONValue (); + +protected: + JSONValue::SP + ParseJSONObject (); + + JSONValue::SP + ParseJSONArray (); +}; + +#endif // utility_JSON_h_ Index: tools/debugserver/source/JSON.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/JSON.cpp @@ -0,0 +1,746 @@ +//===--------------------- JSON.cpp -----------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "JSON.h" + +// C includes +#include +#include + +// C++ includes +#include +#include +#include "lldb/Host/StringConvert.h" + +using namespace lldb_private; + +std::string +JSONString::json_string_quote_metachars (const std::string &s) +{ + if (s.find('"') == std::string::npos) + return s; + + std::string output; + const size_t s_size = s.size(); + const char *s_chars = s.c_str(); + for (size_t i = 0; i < s_size; i++) + { + unsigned char ch = *(s_chars + i); + if (ch == '"') + { + output.push_back ('\\'); + } + output.push_back (ch); + } + return output; +} + +JSONString::JSONString () : + JSONValue(JSONValue::Kind::String), + m_data() +{ +} + +JSONString::JSONString (const char* s) : + JSONValue(JSONValue::Kind::String), + m_data(s ? s : "") +{ +} + +JSONString::JSONString (const std::string& s) : + JSONValue(JSONValue::Kind::String), + m_data(s) +{ +} + +void +JSONString::Write (std::ostream& s) +{ + s << "\"" << json_string_quote_metachars(m_data).c_str() <<"\""; +} + +uint64_t +JSONNumber::GetAsUnsigned() const +{ + switch (m_data_type) + { + case DataType::Unsigned: + return m_data.m_unsigned; + case DataType::Signed: + return (uint64_t)m_data.m_signed; + case DataType::Double: + return (uint64_t)m_data.m_double; + } + assert("Unhandled data type"); +} + +int64_t +JSONNumber::GetAsSigned() const +{ + switch (m_data_type) + { + case DataType::Unsigned: + return (int64_t)m_data.m_unsigned; + case DataType::Signed: + return m_data.m_signed; + case DataType::Double: + return (int64_t)m_data.m_double; + } + assert("Unhandled data type"); +} + +double +JSONNumber::GetAsDouble() const +{ + switch (m_data_type) + { + case DataType::Unsigned: + return (double)m_data.m_unsigned; + case DataType::Signed: + return (double)m_data.m_signed; + case DataType::Double: + return m_data.m_double; + } + assert("Unhandled data type"); +} + +void +JSONNumber::Write (std::ostream& s) +{ + switch (m_data_type) + { + case DataType::Unsigned: + s << m_data.m_unsigned; + break; + case DataType::Signed: + s << m_data.m_signed; + break; + case DataType::Double: + // Set max precision to emulate %g. + s << std::setprecision(std::numeric_limits::digits10 + 1); + s << m_data.m_double; + break; + } +} + +JSONTrue::JSONTrue () : + JSONValue(JSONValue::Kind::True) +{ +} + +void +JSONTrue::Write(std::ostream& s) +{ + s << "true"; +} + +JSONFalse::JSONFalse () : + JSONValue(JSONValue::Kind::False) +{ +} + +void +JSONFalse::Write(std::ostream& s) +{ + s << "false"; +} + +JSONNull::JSONNull () : + JSONValue(JSONValue::Kind::Null) +{ +} + +void +JSONNull::Write(std::ostream& s) +{ + s << "null"; +} + +JSONObject::JSONObject () : + JSONValue(JSONValue::Kind::Object) +{ +} + +void +JSONObject::Write (std::ostream& s) +{ + bool first = true; + s << '{'; + auto iter = m_elements.begin(), end = m_elements.end(); + for (;iter != end; iter++) + { + if (first) + first = false; + else + s << ','; + JSONString key(iter->first); + JSONValue::SP value(iter->second); + key.Write(s); + s << ':'; + value->Write(s); + } + s << '}'; +} + +bool +JSONObject::SetObject (const std::string& key, + JSONValue::SP value) +{ + if (key.empty() || nullptr == value.get()) + return false; + m_elements[key] = value; + return true; +} + +JSONValue::SP +JSONObject::GetObject (const std::string& key) const +{ + auto iter = m_elements.find(key), end = m_elements.end(); + if (iter == end) + return JSONValue::SP(); + return iter->second; +} + +bool +JSONObject::GetObjectAsBool (const std::string& key, bool& value) const +{ + auto value_sp = GetObject(key); + if (!value_sp) + { + // The given key doesn't exist, so we have no value. + return false; + } + + if (JSONTrue::classof(value_sp.get())) + { + // We have the value, and it is true. + value = true; + return true; + } + else if (JSONFalse::classof(value_sp.get())) + { + // We have the value, and it is false. + value = false; + return true; + } + else + { + // We don't have a valid bool value for the given key. + return false; + } +} + +bool +JSONObject::GetObjectAsString (const std::string& key, std::string& value) const +{ + auto value_sp = GetObject(key); + if (!value_sp) + { + // The given key doesn't exist, so we have no value. + return false; + } + + if (!JSONString::classof(value_sp.get())) + return false; + + value = static_cast(value_sp.get())->GetData(); + return true; +} + +JSONArray::JSONArray () : + JSONValue(JSONValue::Kind::Array) +{ +} + +void +JSONArray::Write (std::ostream& s) +{ + bool first = true; + s << '['; + auto iter = m_elements.begin(), end = m_elements.end(); + for (;iter != end; iter++) + { + if (first) + first = false; + else + s << ','; + (*iter)->Write(s); + } + s << ']'; +} + +bool +JSONArray::SetObject (Index i, + JSONValue::SP value) +{ + if (value.get() == nullptr) + return false; + if (i < m_elements.size()) + { + m_elements[i] = value; + return true; + } + if (i == m_elements.size()) + { + m_elements.push_back(value); + return true; + } + return false; +} + +bool +JSONArray::AppendObject (JSONValue::SP value) +{ + if (value.get() == nullptr) + return false; + m_elements.push_back(value); + return true; +} + +JSONValue::SP +JSONArray::GetObject (Index i) +{ + if (i < m_elements.size()) + return m_elements[i]; + return JSONValue::SP(); +} + +JSONArray::Size +JSONArray::GetNumElements () +{ + return m_elements.size(); +} + + +JSONParser::JSONParser (const char *cstr) : + StringExtractor(cstr) +{ +} + +JSONParser::Token +JSONParser::GetToken (std::string &value) +{ + std::ostringstream error; + + value.clear(); + SkipSpaces (); + const uint64_t start_index = m_index; + const char ch = GetChar(); + switch (ch) + { + case '{': return Token::ObjectStart; + case '}': return Token::ObjectEnd; + case '[': return Token::ArrayStart; + case ']': return Token::ArrayEnd; + case ',': return Token::Comma; + case ':': return Token::Colon; + case '\0': return Token::EndOfFile; + case 't': + if (GetChar() == 'r') + if (GetChar() == 'u') + if (GetChar() == 'e') + return Token::True; + break; + + case 'f': + if (GetChar() == 'a') + if (GetChar() == 'l') + if (GetChar() == 's') + if (GetChar() == 'e') + return Token::False; + break; + + case 'n': + if (GetChar() == 'u') + if (GetChar() == 'l') + if (GetChar() == 'l') + return Token::Null; + break; + + case '"': + { + while (1) + { + bool was_escaped = false; + int escaped_ch = GetEscapedChar(was_escaped); + if (escaped_ch == -1) + { + error << "error: an error occurred getting a character from offset " <= '0') && (ch <= '9'); + bool got_frac_digits = false; + bool got_exp_digits = false; + while (!done) + { + const char next_ch = PeekChar(); + switch (next_ch) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if (exp_index != 0) + { + got_exp_digits = true; + } + else if (got_decimal_point) + { + got_frac_digits = true; + } + else + { + got_int_digits = true; + } + ++m_index; // Skip this character + break; + + case '.': + if (got_decimal_point) + { + error << "error: extra decimal point found at offset " << start_index; + value = std::move(error.str()); + return Token::Error; + } + else + { + got_decimal_point = true; + ++m_index; // Skip this character + } + break; + + case 'e': + case 'E': + if (exp_index != 0) + { + error << "error: extra exponent character found at offset " << start_index; + value = std::move(error.str()); + return Token::Error; + } + else + { + exp_index = m_index; + ++m_index; // Skip this character + } + break; + + case '+': + case '-': + // The '+' and '-' can only come after an exponent character... + if (exp_index == m_index - 1) + { + ++m_index; // Skip the exponent sign character + } + else + { + error << "error: unexpected " << next_ch << " character at offset " << start_index; + value = std::move(error.str()); + return Token::Error; + } + break; + + default: + done = true; + break; + } + } + + if (m_index > start_index) + { + value = m_packet.substr(start_index, m_index - start_index); + if (got_decimal_point) + { + if (exp_index != 0) + { + // We have an exponent, make sure we got exponent digits + if (got_exp_digits) + { + return Token::Float; + } + else + { + error << "error: got exponent character but no exponent digits at offset in float value \"" << value.c_str() << "\""; + value = std::move(error.str()); + return Token::Error; + } + } + else + { + // No exponent, but we need at least one decimal after the decimal point + if (got_frac_digits) + { + return Token::Float; + } + else + { + error << "error: no digits after decimal point \"" << value.c_str() << "\""; + value = std::move(error.str()); + return Token::Error; + } + } + } + else + { + // No decimal point + if (got_int_digits) + { + // We need at least some integer digits to make an integer + return Token::Integer; + } + else + { + error << "error: no digits negate sign \"" << value.c_str() << "\""; + value = std::move(error.str()); + return Token::Error; + } + } + } + else + { + error << "error: invalid number found at offset " << start_index; + value = std::move(error.str()); + return Token::Error; + } + } + break; + default: + break; + } + error << "error: failed to parse token at offset " << start_index << " (around character '" << ch << "')"; + value = std::move(error.str()); + return Token::Error; +} + +int +JSONParser::GetEscapedChar(bool &was_escaped) +{ + was_escaped = false; + const char ch = GetChar(); + if (ch == '\\') + { + was_escaped = true; + const char ch2 = GetChar(); + switch (ch2) + { + case '"': + case '\\': + case '/': + default: + break; + + case 'b': return '\b'; + case 'f': return '\f'; + case 'n': return '\n'; + case 'r': return '\r'; + case 't': return '\t'; + case 'u': + { + const int hi_byte = DecodeHexU8(); + const int lo_byte = DecodeHexU8(); + if (hi_byte >=0 && lo_byte >= 0) + return hi_byte << 8 | lo_byte; + return -1; + } + break; + } + return ch2; + } + return ch; +} + +JSONValue::SP +JSONParser::ParseJSONObject () +{ + // The "JSONParser::Token::ObjectStart" token should have already been consumed + // by the time this function is called + std::unique_ptr dict_up(new JSONObject()); + + std::string value; + std::string key; + while (1) + { + JSONParser::Token token = GetToken(value); + + if (token == JSONParser::Token::String) + { + key.swap(value); + token = GetToken(value); + if (token == JSONParser::Token::Colon) + { + JSONValue::SP value_sp = ParseJSONValue(); + if (value_sp) + dict_up->SetObject(key, value_sp); + else + break; + } + } + else if (token == JSONParser::Token::ObjectEnd) + { + return JSONValue::SP(dict_up.release()); + } + else if (token == JSONParser::Token::Comma) + { + continue; + } + else + { + break; + } + } + return JSONValue::SP(); +} + +JSONValue::SP +JSONParser::ParseJSONArray () +{ + // The "JSONParser::Token::ObjectStart" token should have already been consumed + // by the time this function is called + std::unique_ptr array_up(new JSONArray()); + + std::string value; + std::string key; + while (1) + { + JSONValue::SP value_sp = ParseJSONValue(); + if (value_sp) + array_up->AppendObject(value_sp); + else + break; + + JSONParser::Token token = GetToken(value); + if (token == JSONParser::Token::Comma) + { + continue; + } + else if (token == JSONParser::Token::ArrayEnd) + { + return JSONValue::SP(array_up.release()); + } + else + { + break; + } + } + return JSONValue::SP(); +} + +JSONValue::SP +JSONParser::ParseJSONValue () +{ + std::string value; + const JSONParser::Token token = GetToken(value); + switch (token) + { + case JSONParser::Token::ObjectStart: + return ParseJSONObject(); + + case JSONParser::Token::ArrayStart: + return ParseJSONArray(); + + case JSONParser::Token::Integer: + { + if (value.front() == '-') + { + bool success = false; + int64_t sval = StringConvert::ToSInt64(value.c_str(), 0, 0, &success); + if (success) + return JSONValue::SP(new JSONNumber(sval)); + } + else + { + bool success = false; + uint64_t uval = StringConvert::ToUInt64(value.c_str(), 0, 0, &success); + if (success) + return JSONValue::SP(new JSONNumber(uval)); + } + } + break; + + case JSONParser::Token::Float: + { + bool success = false; + double val = StringConvert::ToDouble(value.c_str(), 0.0, &success); + if (success) + return JSONValue::SP(new JSONNumber(val)); + } + break; + + case JSONParser::Token::String: + return JSONValue::SP(new JSONString(value)); + + case JSONParser::Token::True: + return JSONValue::SP(new JSONTrue()); + + case JSONParser::Token::False: + return JSONValue::SP(new JSONFalse()); + + case JSONParser::Token::Null: + return JSONValue::SP(new JSONNull()); + + default: + break; + } + return JSONValue::SP(); + +} Index: tools/debugserver/source/MacOSX/CMakeLists.txt =================================================================== --- tools/debugserver/source/MacOSX/CMakeLists.txt +++ tools/debugserver/source/MacOSX/CMakeLists.txt @@ -3,6 +3,7 @@ add_subdirectory(i386) #add_subdirectory(ppc) add_subdirectory(x86_64) +add_subdirectory(DarwinLog) include_directories(..) @@ -32,6 +33,7 @@ lldbUtility lldbDebugserverMacOSX_I386 lldbDebugserverMacOSX_X86_64 + lldbDebugserverMacOSX_DarwinLog ) add_lldb_executable(debugserver @@ -46,6 +48,7 @@ MachThreadList.cpp MachVMMemory.cpp MachVMRegion.cpp + OsLogger.cpp ${generated_mach_interfaces} ${DEBUGSERVER_VERS_GENERATED_FILE} ) Index: tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.h @@ -0,0 +1,36 @@ +//===-- ActivityStore.h -----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef ActivityStore_h +#define ActivityStore_h + +#include + +#include "ActivityStreamSPI.h" + +class ActivityStore +{ +public: + + virtual + ~ActivityStore(); + + virtual const char* + GetActivityForID(os_activity_id_t activity_id) const = 0; + + virtual std::string + GetActivityChainForID(os_activity_id_t activity_id) const = 0; + +protected: + + ActivityStore(); + +}; + +#endif /* ActivityStore_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.cpp @@ -0,0 +1,18 @@ +//===-- ActivityStore.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "ActivityStore.h" + +ActivityStore::ActivityStore() +{ +} + +ActivityStore::~ActivityStore() +{ +} Index: tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h @@ -0,0 +1,200 @@ +//===-- ActivityStreamAPI.h -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef ActivityStreamSPI_h +#define ActivityStreamSPI_h + +#include +#include + +#define OS_ACTIVITY_MAX_CALLSTACK 32 + +// Enums + +enum +{ + OS_ACTIVITY_STREAM_PROCESS_ONLY = 0x00000001, + OS_ACTIVITY_STREAM_SKIP_DECODE = 0x00000002, + OS_ACTIVITY_STREAM_PAYLOAD = 0x00000004, + OS_ACTIVITY_STREAM_HISTORICAL = 0x00000008, + OS_ACTIVITY_STREAM_CALLSTACK = 0x00000010, + OS_ACTIVITY_STREAM_DEBUG = 0x00000020, + OS_ACTIVITY_STREAM_BUFFERED = 0x00000040, + OS_ACTIVITY_STREAM_NO_SENSITIVE = 0x00000080, + OS_ACTIVITY_STREAM_INFO = 0x00000100, + OS_ACTIVITY_STREAM_PROMISCUOUS = 0x00000200, + OS_ACTIVITY_STREAM_PRECISE_TIMESTAMPS = 0x00000200 +}; +typedef uint32_t os_activity_stream_flag_t; + +enum +{ + OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE = 0x0201, + OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION = 0x0202, + OS_ACTIVITY_STREAM_TYPE_ACTIVITY_USERACTION = 0x0203, + + OS_ACTIVITY_STREAM_TYPE_TRACE_MESSAGE = 0x0300, + + OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE = 0x0400, + OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE = 0x0480, + + OS_ACTIVITY_STREAM_TYPE_SIGNPOST_BEGIN = 0x0601, + OS_ACTIVITY_STREAM_TYPE_SIGNPOST_END = 0x0602, + OS_ACTIVITY_STREAM_TYPE_SIGNPOST_EVENT = 0x0603, + + OS_ACTIVITY_STREAM_TYPE_STATEDUMP_EVENT = 0x0A00, +}; +typedef uint32_t os_activity_stream_type_t; + +enum +{ + OS_ACTIVITY_STREAM_EVENT_STARTED = 1, + OS_ACTIVITY_STREAM_EVENT_STOPPED = 2, + OS_ACTIVITY_STREAM_EVENT_FAILED = 3, + OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED = 4, + OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED = 5, +}; +typedef uint32_t os_activity_stream_event_t; + +// Types + +typedef uint64_t os_activity_id_t; +typedef struct os_activity_stream_s *os_activity_stream_t; +typedef struct os_activity_stream_entry_s *os_activity_stream_entry_t; + +#define OS_ACTIVITY_STREAM_COMMON() \ + uint64_t trace_id; \ + uint64_t timestamp; \ + uint64_t thread; \ + const uint8_t *image_uuid; \ + const char *image_path; \ + struct timeval tv_gmt; \ + struct timezone tz; \ + uint32_t offset \ + + +typedef struct os_activity_stream_common_s { + OS_ACTIVITY_STREAM_COMMON(); +} *os_activity_stream_common_t; + +struct os_activity_create_s { + OS_ACTIVITY_STREAM_COMMON(); + const char *name; + os_activity_id_t creator_aid; + uint64_t unique_pid; +}; + +struct os_activity_transition_s { + OS_ACTIVITY_STREAM_COMMON(); + os_activity_id_t transition_id; +}; + +typedef struct os_log_message_s { + OS_ACTIVITY_STREAM_COMMON(); + const char *format; + const uint8_t *buffer; + size_t buffer_sz; + const uint8_t *privdata; + size_t privdata_sz; + const char *subsystem; + const char *category; + uint32_t oversize_id; + uint8_t ttl; + bool persisted; +} *os_log_message_t; + +typedef struct os_trace_message_v2_s { + OS_ACTIVITY_STREAM_COMMON(); + const char *format; + const void *buffer; + size_t bufferLen; + xpc_object_t __unsafe_unretained payload; +} *os_trace_message_v2_t; + +typedef struct os_activity_useraction_s { + OS_ACTIVITY_STREAM_COMMON(); + const char *action; + bool persisted; +} *os_activity_useraction_t; + +typedef struct os_signpost_s { + OS_ACTIVITY_STREAM_COMMON(); + const char *format; + const uint8_t *buffer; + size_t buffer_sz; + const uint8_t *privdata; + size_t privdata_sz; + const char *subsystem; + const char *category; + uint64_t duration_nsec; + uint32_t callstack_depth; + uint64_t callstack[OS_ACTIVITY_MAX_CALLSTACK]; +} *os_signpost_t; + +typedef struct os_activity_statedump_s { + OS_ACTIVITY_STREAM_COMMON(); + char *message; + size_t message_size; + char image_path_buffer[PATH_MAX]; +} *os_activity_statedump_t; + +struct os_activity_stream_entry_s { + os_activity_stream_type_t type; + + // information about the process streaming the data + pid_t pid; + uint64_t proc_id; + const uint8_t *proc_imageuuid; + const char *proc_imagepath; + + // the activity associated with this streamed event + os_activity_id_t activity_id; + os_activity_id_t parent_id; + + union { + struct os_activity_stream_common_s common; + struct os_activity_create_s activity_create; + struct os_activity_transition_s activity_transition; + struct os_log_message_s log_message; + struct os_trace_message_v2_s trace_message; + struct os_activity_useraction_s useraction; + struct os_signpost_s signpost; + struct os_activity_statedump_s statedump; + }; +}; + +// Blocks + +typedef bool (^os_activity_stream_block_t)(os_activity_stream_entry_t entry, + int error); + +typedef void (^os_activity_stream_event_block_t)( + os_activity_stream_t stream, + os_activity_stream_event_t event); + +// SPI entry point prototypes + +typedef os_activity_stream_t + (*os_activity_stream_for_pid_t)(pid_t pid, os_activity_stream_flag_t flags, + os_activity_stream_block_t stream_block); + +typedef void +(*os_activity_stream_resume_t)(os_activity_stream_t stream); + +typedef void + (*os_activity_stream_cancel_t)(os_activity_stream_t stream); + +typedef char * + (*os_log_copy_formatted_message_t)(os_log_message_t log_message); + +typedef void + (*os_activity_stream_set_event_handler_t) + (os_activity_stream_t stream, os_activity_stream_event_block_t block); + +#endif /* ActivityStreamSPI_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/CMakeLists.txt =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/CMakeLists.txt @@ -0,0 +1,15 @@ +# Due to sources including headers like: +# #include "MacOSX/i386/DNBArchImplI386.h" +# we must include the grandparent directory... +include_directories(${LLDB_SOURCE_DIR}/tools/debugserver/source) + +add_library(lldbDebugserverMacOSX_DarwinLog + ActivityStore.cpp + DarwinLogCollector.cpp + LogFilter.cpp + LogFilterChain.cpp + LogFilterExactMatch.cpp + LogFilterRegex.cpp + LogMessage.cpp + LogMessageOsLog.cpp + ) Index: tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.h @@ -0,0 +1,139 @@ +//===-- DarwinLogCollector.h ------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef DarwinLogCollector_h +#define DarwinLogCollector_h + +#include + +#include +#include +#include + +#include "ActivityStore.h" +#include "ActivityStreamSPI.h" +#include "DarwinLogEvent.h" +#include "DarwinLogInterfaces.h" +#include "DNBDefs.h" +#include "JSON.h" + +class DarwinLogCollector; +typedef std::shared_ptr DarwinLogCollectorSP; + +class DarwinLogCollector: + public std::enable_shared_from_this, + public ActivityStore +{ +public: + + //------------------------------------------------------------------ + /// Return whether the os_log and activity tracing SPI is available. + /// + /// @return \b true if the activity stream support is available, + /// \b false otherwise. + //------------------------------------------------------------------ + static bool + IsSupported(); + + //------------------------------------------------------------------ + /// Return a log function suitable for DNBLog to use as the internal + /// logging function. + /// + /// @return a DNBLog-style logging function if IsSupported() returns + /// true; otherwise, returns nullptr. + //------------------------------------------------------------------ + static DNBCallbackLog + GetLogFunction(); + + static bool + StartCollectingForProcess(nub_process_t pid, const JSONObject &config); + + static bool + CancelStreamForProcess(nub_process_t pid); + + static DarwinLogEventVector + GetEventsForProcess(nub_process_t pid); + + ~DarwinLogCollector(); + + pid_t + GetProcessID() const + { + return m_pid; + } + + //------------------------------------------------------------------ + // ActivityStore API + //------------------------------------------------------------------ + const char* + GetActivityForID(os_activity_id_t activity_id) const override; + + std::string + GetActivityChainForID(os_activity_id_t activity_id) const override; + + +private: + + DarwinLogCollector() = delete; + DarwinLogCollector(const DarwinLogCollector&) = delete; + DarwinLogCollector &operator=(const DarwinLogCollector&) = delete; + + explicit + DarwinLogCollector(nub_process_t pid, + const LogFilterChainSP &filter_chain_sp); + + void + SignalDataAvailable(); + + void + SetActivityStream(os_activity_stream_t activity_stream); + + bool + HandleStreamEntry(os_activity_stream_entry_t entry, int error); + + DarwinLogEventVector + RemoveEvents(); + + void + CancelActivityStream(); + + void + GetActivityChainForID_internal(os_activity_id_t activity_id, + std::string &result, size_t depth) const; + + struct ActivityInfo + { + ActivityInfo(const char *name, os_activity_id_t activity_id, + os_activity_id_t parent_activity_id) : + m_name(name), + m_id(activity_id), + m_parent_id(parent_activity_id) + { + } + + const std::string m_name; + const os_activity_id_t m_id; + const os_activity_id_t m_parent_id; + }; + + using ActivityMap = std::unordered_map; + + const nub_process_t m_pid; + os_activity_stream_t m_activity_stream; + DarwinLogEventVector m_events; + std::mutex m_events_mutex; + LogFilterChainSP m_filter_chain_sp; + + /// Mutex to protect activity info (activity name and parent structures) + mutable std::mutex m_activity_info_mutex; + /// Map of activity id to ActivityInfo + ActivityMap m_activity_map; +}; + +#endif /* LogStreamCollector_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.cpp @@ -0,0 +1,835 @@ +//===-- DarwinLogCollector.cpp ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DarwinLogCollector.h" +#include "ActivityStreamSPI.h" + +#include + +#include +#include +#include + +#include "DarwinLogTypes.h" +#include "DNB.h" +#include "DNBLog.h" +#include "LogFilterChain.h" +#include "LogFilterExactMatch.h" +#include "LogFilterRegex.h" +#include "LogMessageOsLog.h" +#include "MachProcess.h" +#include "RNBContext.h" +#include "RNBDefs.h" +#include "RNBRemote.h" + +// Use an anonymous namespace for variables and methods that have no +// reason to leak out through the interface. +namespace +{ + /// Specify max depth that the activity parent-child chain will search + /// back to get the full activity chain name. If we do more than this, + /// we assume either we hit a loop or it's just too long. + static const size_t MAX_ACTIVITY_CHAIN_DEPTH = 10; + + // Used to tap into and retrieve logs from target process. + // (Consumer of os_log). + static os_activity_stream_for_pid_t s_os_activity_stream_for_pid; + static os_activity_stream_resume_t s_os_activity_stream_resume; + static os_activity_stream_cancel_t s_os_activity_stream_cancel; + static os_log_copy_formatted_message_t s_os_log_copy_formatted_message; + static os_activity_stream_set_event_handler_t + s_os_activity_stream_set_event_handler; + + bool + LookupSPICalls() + { + static std::once_flag s_once_flag; + static bool s_has_spi; + + std::call_once(s_once_flag, [] { + s_os_activity_stream_for_pid = (os_activity_stream_for_pid_t) + dlsym(RTLD_DEFAULT, "os_activity_stream_for_pid"); + s_os_activity_stream_resume = (os_activity_stream_resume_t) + dlsym(RTLD_DEFAULT, "os_activity_stream_resume"); + s_os_activity_stream_cancel = (os_activity_stream_cancel_t) + dlsym(RTLD_DEFAULT, "os_activity_stream_cancel"); + s_os_log_copy_formatted_message = (os_log_copy_formatted_message_t) + dlsym(RTLD_DEFAULT, "os_log_copy_formatted_message"); + s_os_activity_stream_set_event_handler = + (os_activity_stream_set_event_handler_t) + dlsym(RTLD_DEFAULT, "os_activity_stream_set_event_handler"); + + // We'll indicate we're all set if every function entry point + // was found. + s_has_spi = + (s_os_activity_stream_for_pid != nullptr) && + (s_os_activity_stream_resume != nullptr) && + (s_os_activity_stream_cancel != nullptr) && + (s_os_log_copy_formatted_message != nullptr) && + (s_os_activity_stream_set_event_handler != nullptr); + if (s_has_spi) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "Found os_log SPI calls."); + // Tell LogMessageOsLog how to format messages when search + // criteria requires it. + LogMessageOsLog::SetFormatterFunction( + s_os_log_copy_formatted_message); + } + else + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "Failed to find os_log SPI " + "calls."); + } + }); + + return s_has_spi; + } + + using Mutex = std::mutex; + static Mutex s_collector_mutex; + static std::vector s_collectors; + + static void + TrackCollector(const DarwinLogCollectorSP &collector_sp) + { + std::lock_guard locker(s_collector_mutex); + if (std::find(s_collectors.begin(), s_collectors.end(), collector_sp) + != s_collectors.end()) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "attempted to add same collector multiple times"); + return; + } + s_collectors.push_back(collector_sp); + } + + static void + StopTrackingCollector(const DarwinLogCollectorSP &collector_sp) + { + std::lock_guard locker(s_collector_mutex); + s_collectors.erase(std::remove(s_collectors.begin(), s_collectors.end(), + collector_sp), + s_collectors.end()); + } + + static DarwinLogCollectorSP + FindCollectorForProcess(pid_t pid) + { + std::lock_guard locker(s_collector_mutex); + for (const auto &collector_sp : s_collectors) + { + if (collector_sp && (collector_sp->GetProcessID() == pid)) + return collector_sp; + } + return DarwinLogCollectorSP(); + } + + static FilterTarget + TargetStringToEnum(const std::string &filter_target_name) + { + if (filter_target_name == "activity") + return eFilterTargetActivity; + else if (filter_target_name == "activity-chain") + return eFilterTargetActivityChain; + else if (filter_target_name == "category") + return eFilterTargetCategory; + else if (filter_target_name == "message") + return eFilterTargetMessage; + else if (filter_target_name == "subsystem") + return eFilterTargetSubsystem; + else + return eFilterTargetInvalid; + } + + class Configuration + { + public: + + Configuration(const JSONObject &config) : + m_is_valid(false), + m_activity_stream_flags(OS_ACTIVITY_STREAM_PROCESS_ONLY), + m_filter_chain_sp(nullptr) + { + // Parse out activity stream flags + if (!ParseSourceFlags(config)) + { + m_is_valid = false; + return; + } + + // Parse filter rules + if (!ParseFilterRules(config)) + { + m_is_valid = false; + return; + } + + // Everything worked. + m_is_valid = true; + } + + bool + ParseSourceFlags(const JSONObject &config) + { + // Get the source-flags dictionary. + auto source_flags_sp = config.GetObject("source-flags"); + if (!source_flags_sp) + return false; + if (!JSONObject::classof(source_flags_sp.get())) + return false; + + const JSONObject &source_flags = + *static_cast(source_flags_sp.get()); + + // Parse out the flags. + bool include_any_process = false; + bool include_callstacks = false; + bool include_info_level = false; + bool include_debug_level = false; + bool live_stream = false; + + if (!source_flags.GetObjectAsBool("any-process", + include_any_process)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Source-flag 'any-process' missing from " + "configuration."); + return false; + } + if (!source_flags.GetObjectAsBool("callstacks", + include_callstacks)) + { + // We currently suppress the availability of this on the lldb + // side. We include here for devices when we enable in the + // future. + // DNBLogThreadedIf(LOG_DARWIN_LOG, + // "Source-flag 'callstacks' missing from " + // "configuration."); + + // OK. We just skip callstacks. + // return false; + } + if (!source_flags.GetObjectAsBool("info-level", + include_info_level)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Source-flag 'info-level' missing from " + "configuration."); + return false; + } + if (!source_flags.GetObjectAsBool("debug-level", + include_debug_level)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Source-flag 'debug-level' missing from " + "configuration."); + return false; + } + if (!source_flags.GetObjectAsBool("live-stream", + live_stream)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Source-flag 'live-stream' missing from " + "configuration."); + return false; + } + + // Setup the SPI flags based on this. + m_activity_stream_flags = 0; + if (!include_any_process) + m_activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY; + if (include_callstacks) + m_activity_stream_flags |= OS_ACTIVITY_STREAM_CALLSTACK; + if (include_info_level) + m_activity_stream_flags |= OS_ACTIVITY_STREAM_INFO; + if (include_debug_level) + m_activity_stream_flags |= OS_ACTIVITY_STREAM_DEBUG; + if (!live_stream) + m_activity_stream_flags |= OS_ACTIVITY_STREAM_BUFFERED; + + DNBLogThreadedIf(LOG_DARWIN_LOG, "m_activity_stream_flags = 0x%03x", + m_activity_stream_flags); + + return true; + } + + bool + ParseFilterRules(const JSONObject &config) + { + // Retrieve the default rule. + bool filter_default_accept = true; + if (!config.GetObjectAsBool("filter-fall-through-accepts", + filter_default_accept)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Setting 'filter-fall-through-accepts' " + "missing from configuration."); + return false; + } + m_filter_chain_sp.reset(new LogFilterChain(filter_default_accept)); + DNBLogThreadedIf(LOG_DARWIN_LOG, + "DarwinLog no-match rule: %s.", + filter_default_accept ? "accept" : "reject"); + + // If we don't have the filter-rules array, we're done. + auto filter_rules_sp = config.GetObject("filter-rules"); + if (!filter_rules_sp) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "No 'filter-rules' config element, all log " + "entries will use the no-match action (%s).", + filter_default_accept ? "accept" : "reject"); + return true; + } + if (!JSONArray::classof(filter_rules_sp.get())) + return false; + const JSONArray &rules_config = + *static_cast(filter_rules_sp.get()); + + // Create the filters. + for (auto &rule_sp : rules_config.m_elements) + { + if (!JSONObject::classof(rule_sp.get())) + return false; + const JSONObject &rule_config = *static_cast + (rule_sp.get()); + + // Get whether this filter accepts or rejects. + bool filter_accepts = true; + if (!rule_config.GetObjectAsBool("accept", filter_accepts)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Filter 'accept' element missing."); + return false; + } + + // Grab the target log field attribute for the match. + std::string target_attribute; + if (!rule_config.GetObjectAsString("attribute", + target_attribute)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Filter 'attribute' element missing."); + return false; + } + auto target_enum = TargetStringToEnum(target_attribute); + if (target_enum == eFilterTargetInvalid) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Filter attribute '%s' unsupported.", + target_attribute.c_str()); + return false; + } + + // Handle operation-specific fields and filter creation. + std::string filter_type; + if (!rule_config.GetObjectAsString("type", filter_type)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Filter 'type' element missing."); + return false; + } + DNBLogThreadedIf(LOG_DARWIN_LOG, + "Reading filter of type '%s'", filter_type + .c_str()); + + LogFilterSP filter_sp; + if (filter_type == "regex") + { + // Grab the regex for the match. + std::string regex; + if (!rule_config.GetObjectAsString("regex", + regex)) + { + DNBLogError("Regex filter missing 'regex' element."); + return false; + } + DNBLogThreadedIf(LOG_DARWIN_LOG, + "regex for filter: \"%s\"", regex.c_str()); + + // Create the regex filter. + auto regex_filter = + new LogFilterRegex(filter_accepts, target_enum, regex); + filter_sp.reset(regex_filter); + + // Validate that the filter is okay. + if (!regex_filter->IsValid()) + { + DNBLogError("Invalid regex in filter: " + "regex=\"%s\", error=%s", + regex.c_str(), + regex_filter->GetErrorAsCString()); + return false; + } + } + else if (filter_type == "match") + { + // Grab the regex for the match. + std::string exact_text; + if (!rule_config.GetObjectAsString("exact_text", + exact_text)) + { + DNBLogError("Exact match filter missing " + "'exact_text' element."); + return false; + } + + // Create the filter. + filter_sp.reset(new LogFilterExactMatch(filter_accepts, + target_enum, + exact_text)); + } + + // Add the filter to the chain. + m_filter_chain_sp->AppendFilter(filter_sp); + } + return true; + } + + bool + IsValid() const + { + return m_is_valid; + } + + os_activity_stream_flag_t + GetActivityStreamFlags() const + { + return m_activity_stream_flags; + } + + const LogFilterChainSP & + GetLogFilterChain() const + { + return m_filter_chain_sp; + } + + private: + + bool m_is_valid; + os_activity_stream_flag_t m_activity_stream_flags; + LogFilterChainSP m_filter_chain_sp; + + }; +} + +bool +DarwinLogCollector::IsSupported() +{ + // We're supported if we have successfully looked up the SPI entry points. + return LookupSPICalls(); +} + +bool +DarwinLogCollector::StartCollectingForProcess(nub_process_t pid, + const JSONObject &config) +{ + // If we're currently collecting for this process, kill the existing + // collector. + if (CancelStreamForProcess(pid)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "%s() killed existing DarwinLog collector for pid %d.", + __FUNCTION__, pid); + } + + // If the process isn't alive, we're done. + if (!DNBProcessIsAlive(pid)) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "%s() cannot collect for pid %d: process not alive.", + __FUNCTION__, pid); + return false; + } + + // Validate the configuration. + auto spi_config = Configuration(config); + if (!spi_config.IsValid()) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, + "%s() invalid configuration, will not enable log " + "collection", __FUNCTION__); + return false; + } + + // Create the stream collector that will manage collected data + // for this pid. + DarwinLogCollectorSP collector_sp(new DarwinLogCollector(pid, + spi_config.GetLogFilterChain())); + std::weak_ptr collector_wp(collector_sp); + + // Setup the stream handling block. + os_activity_stream_block_t block = ^bool (os_activity_stream_entry_t entry, + int error) { + // Check if our collector is still alive. + DarwinLogCollectorSP inner_collector_sp = collector_wp.lock(); + if (!inner_collector_sp) + return false; + return inner_collector_sp->HandleStreamEntry(entry, error); + }; + + os_activity_stream_event_block_t stream_event_block = + ^void (os_activity_stream_t stream, os_activity_stream_event_t event) { + switch (event) + { + case OS_ACTIVITY_STREAM_EVENT_STARTED: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received stream event: " + "OS_ACTIVITY_STREAM_EVENT_STARTED, stream %p.", + (void*)stream); + break; + case OS_ACTIVITY_STREAM_EVENT_STOPPED: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received stream event: " + "OS_ACTIVITY_STREAM_EVENT_STOPPED, stream %p.", + (void*)stream); + break; + case OS_ACTIVITY_STREAM_EVENT_FAILED: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received stream event: " + "OS_ACTIVITY_STREAM_EVENT_FAILED, stream %p.", + (void*)stream); + break; + case OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received stream event: " + "OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED, stream %p.", + (void*)stream); + break; + case OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received stream event: " + "OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED, stream %p.", + (void*)stream); + break; + } + }; + + // Create the stream. + os_activity_stream_t activity_stream = + (*s_os_activity_stream_for_pid)(pid, + spi_config.GetActivityStreamFlags(), + block); + collector_sp->SetActivityStream(activity_stream); + + // Specify the stream-related event handler. + (*s_os_activity_stream_set_event_handler)( + activity_stream, stream_event_block); + + // Start the stream. + (*s_os_activity_stream_resume)(activity_stream); + + TrackCollector(collector_sp); + return true; +} + +DarwinLogEventVector +DarwinLogCollector::GetEventsForProcess(nub_process_t pid) +{ + auto collector_sp = FindCollectorForProcess(pid); + if (!collector_sp) + { + // We're not tracking a stream for this process. + return DarwinLogEventVector(); + } + + return collector_sp->RemoveEvents(); +} + +bool +DarwinLogCollector::CancelStreamForProcess(nub_process_t pid) +{ + auto collector_sp = FindCollectorForProcess(pid); + if (!collector_sp) + { + // We're not tracking a stream for this process. + return false; + } + + collector_sp->CancelActivityStream(); + StopTrackingCollector(collector_sp); + + return true; +} + +const char* +DarwinLogCollector::GetActivityForID(os_activity_id_t activity_id) const +{ + auto find_it = m_activity_map.find(activity_id); + return (find_it != m_activity_map.end()) ? + find_it->second.m_name.c_str() : + nullptr; +} + +/// Retrieve the full parent-child chain for activity names. These +/// can be arbitrarily deep. This method assumes the caller has already +/// locked the activity mutex. +void +DarwinLogCollector::GetActivityChainForID_internal(os_activity_id_t activity_id, + std::string &result, + size_t depth) const +{ + if (depth > MAX_ACTIVITY_CHAIN_DEPTH) + { + // Terminating condition - too deeply nested. + return; + } + else if (activity_id == 0) + { + // Terminating condition - no activity. + return; + } + + auto find_it = m_activity_map.find(activity_id); + if (find_it == m_activity_map.end()) + { + //Terminating condition - no data for activity_id. + return; + } + + // Activity name becomes parent activity name chain + ':' + our activity + // name. + GetActivityChainForID_internal(find_it->second.m_parent_id, result, + depth + 1); + if (!result.empty()) + result += ':'; + result += find_it->second.m_name; +} + +std::string +DarwinLogCollector::GetActivityChainForID(os_activity_id_t activity_id) const +{ + std::string result; + { + std::lock_guard locker(m_activity_info_mutex); + GetActivityChainForID_internal(activity_id, result, 1); + } + return result; +} + +DarwinLogCollector::DarwinLogCollector(nub_process_t pid, + const LogFilterChainSP &filter_chain_sp): + ActivityStore(), + m_pid(pid), + m_activity_stream(0), + m_events(), + m_events_mutex(), + m_filter_chain_sp(filter_chain_sp), + m_activity_info_mutex(), + m_activity_map() +{ +} + +DarwinLogCollector::~DarwinLogCollector() +{ + // Cancel the stream. + if (m_activity_stream) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "tearing down activity stream " + "collector for %d", m_pid); + (*s_os_activity_stream_cancel)(m_activity_stream); + m_activity_stream = 0; + } + else + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "no stream to tear down for %d", + m_pid); + } +} + +void +DarwinLogCollector::SignalDataAvailable() +{ + RNBRemoteSP remoteSP(g_remoteSP); + if (!remoteSP) + { + // We're done. This is unexpected. + StopTrackingCollector(shared_from_this()); + return; + } + + RNBContext& ctx = remoteSP->Context(); + ctx.Events().SetEvents(RNBContext::event_darwin_log_data_available); + // Wait for the main thread to consume this notification if it requested + // we wait for it. + ctx.Events().WaitForResetAck(RNBContext::event_darwin_log_data_available); +} + +void +DarwinLogCollector::SetActivityStream(os_activity_stream_t activity_stream) +{ + m_activity_stream = activity_stream; +} + +bool +DarwinLogCollector::HandleStreamEntry(os_activity_stream_entry_t entry, + int error) +{ + if ((error == 0) && (entry != nullptr)) + { + if (entry->pid != m_pid) + { + // For now, skip messages not originating from our process. + // Later we might want to keep all messages related to an event + // that we're tracking, even when it came from another process, + // possibly doing work on our behalf. + return true; + } + + switch (entry->type) + { + case OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received activity create: " + "%s, creator aid %" PRIu64 ", unique_pid %" PRIu64 + "(activity id=%" PRIu64 ", parent id=%" PRIu64 ")", + entry->activity_create.name, + entry->activity_create.creator_aid, + entry->activity_create.unique_pid, entry->activity_id, + entry->parent_id + ); + { + std::lock_guard locker(m_activity_info_mutex); + m_activity_map.insert(std::make_pair( + entry->activity_id, + ActivityInfo( + entry->activity_create.name, + entry->activity_id, + entry->parent_id))); + } + break; + + case OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION: + DNBLogThreadedIf(LOG_DARWIN_LOG, "received activity transition:" + "new aid: %" PRIu64 "(activity id=%" PRIu64 + ", parent id=%" PRIu64 ", tid %" PRIu64 ")", + entry->activity_transition.transition_id, + entry->activity_id, entry->parent_id, + entry->activity_transition.thread); + break; + + case OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE: + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "received log message: " + "(activity id=%" PRIu64 ", parent id=%" PRIu64 ", " + "tid %" PRIu64 "): format %s", + entry->activity_id, entry->parent_id, + entry->log_message.thread, + entry->log_message.format ? entry->log_message.format : + ""); + + // Do the real work here. + { + // Ensure our process is still alive. If not, we can + // cancel the collection. + if (!DNBProcessIsAlive(m_pid)) + { + // We're outta here. This is the manner in which we + // stop collecting for a process. + StopTrackingCollector(shared_from_this()); + return false; + } + + LogMessageOsLog os_log_message(*this, *entry); + if (!m_filter_chain_sp || + !m_filter_chain_sp->GetAcceptMessage(os_log_message)) + { + // This log message was rejected by the filter, + // so stop processing it now. + return true; + } + + // Copy over the relevant bits from the message. + const struct os_log_message_s &log_message = + entry->log_message; + + DarwinLogEventSP message_sp(new DarwinLogEvent()); + // Indicate this event is a log message event. + message_sp->AddStringItem("type", "log"); + + // Add the message contents (fully expanded). + // Consider expanding on the remote side. + // Then we don't pay for expansion until when it is + // used. + const char *message_text = os_log_message.GetMessage(); + if (message_text) + message_sp->AddStringItem("message", message_text); + + // Add some useful data fields. + message_sp->AddIntegerItem("timestamp", + log_message.timestamp); + + // Do we want to do all activity name resolution on this + // side? Maybe. For now, send IDs and ID->name mappings + // and fix this up on that side. Later, when we add + // debugserver-side filtering, we'll want to get the + // activity names over here, so we should probably + // just send them as resolved strings. + message_sp->AddIntegerItem("activity_id", + entry->activity_id); + message_sp->AddIntegerItem("parent_id", + entry->parent_id); + message_sp->AddIntegerItem("thread_id", + log_message.thread); + if (log_message.subsystem && strlen(log_message.subsystem) + > 0) + message_sp->AddStringItem("subsystem", + log_message.subsystem); + if (log_message.category && strlen(log_message.category) + > 0) + message_sp->AddStringItem("category", + log_message.category); + if (entry->activity_id != 0) + { + std::string activity_chain = + GetActivityChainForID(entry->activity_id); + if (!activity_chain.empty()) + message_sp->AddStringItem("activity-chain", + activity_chain); + } + + // Add it to the list for later collection. + { + std::lock_guard locker(m_events_mutex); + m_events.push_back(message_sp); + } + SignalDataAvailable(); + } + break; + } + } + } + else + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "HandleStreamEntry: final call, " + "error %d", error); + } + return true; +} + +DarwinLogEventVector +DarwinLogCollector::RemoveEvents() +{ + DarwinLogEventVector returned_events; + { + std::lock_guard locker(m_events_mutex); + returned_events.swap(m_events); + } + DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLogCollector::%s(): removing %lu " + "queued log entries", __FUNCTION__, + returned_events.size()); + return returned_events; +} + +void +DarwinLogCollector::CancelActivityStream() +{ + if (!m_activity_stream) + return; + + DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLogCollector::%s(): canceling " + "activity stream %p", __FUNCTION__, + m_activity_stream); + (*s_os_activity_stream_cancel)(m_activity_stream); + m_activity_stream = nullptr; +} Index: tools/debugserver/source/MacOSX/DarwinLog/DarwinLogEvent.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/DarwinLogEvent.h @@ -0,0 +1,27 @@ +//===-- DarwinLogEvent.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef DarwinLogEvent_h +#define DarwinLogEvent_h + +#include +#include + +#include "JSONGenerator.h" + +// ============================================================================= +/// Each discrete unit of information is described as an event, such as +/// the emission of a single log message. +// ============================================================================= + +using DarwinLogEvent = JSONGenerator::Dictionary; +using DarwinLogEventSP = std::shared_ptr; +using DarwinLogEventVector = std::vector; + +#endif Index: tools/debugserver/source/MacOSX/DarwinLog/DarwinLogInterfaces.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/DarwinLogInterfaces.h @@ -0,0 +1,25 @@ +//===-- DarwinLogInterfaces.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef DarwinLogInterfaces_h +#define DarwinLogInterfaces_h + +#include + +class ActivityStore; + +class LogFilter; +using LogFilterSP = std::shared_ptr; + +class LogFilterChain; +using LogFilterChainSP = std::shared_ptr; + +class LogMessage; + +#endif /* DarwinLogInterfaces_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/DarwinLogTypes.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/DarwinLogTypes.h @@ -0,0 +1,23 @@ +//===-- DarwinLogTypes.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef DarwinLogTypes_h +#define DarwinLogTypes_h + +enum FilterTarget +{ + eFilterTargetInvalid, + eFilterTargetActivity, + eFilterTargetActivityChain, + eFilterTargetCategory, + eFilterTargetMessage, + eFilterTargetSubsystem +}; + +#endif /* DarwinLogTypes_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilter.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilter.h @@ -0,0 +1,44 @@ +//===-- LogFilter.h ---------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LogFilter_h +#define LogFilter_h + +#include "DarwinLogInterfaces.h" + +class LogFilter +{ +public: + + virtual + ~LogFilter(); + + virtual bool + DoesMatch(const LogMessage &message) const = 0; + + bool + MatchesAreAccepted() const + { + return m_matches_accept; + } + +protected: + + LogFilter(bool matches_accept) : + m_matches_accept(matches_accept) + { + } + +private: + + bool m_matches_accept; + +}; + +#endif /* LogFilter_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilter.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilter.cpp @@ -0,0 +1,14 @@ +//===-- LogFilter.cpp -------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LogFilter.h" + +LogFilter::~LogFilter() +{ +} Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.h @@ -0,0 +1,48 @@ +//===-- LogFilterChain.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#ifndef LogFilterChain_h +#define LogFilterChain_h + +#include + +#include "DarwinLogInterfaces.h" + +class LogFilterChain +{ +public: + + LogFilterChain(bool default_accept); + + void + AppendFilter(const LogFilterSP &filter_sp); + + void + ClearFilterChain(); + + bool + GetDefaultAccepts() const; + + void + SetDefaultAccepts(bool default_accepts); + + bool + GetAcceptMessage(const LogMessage &message) const; + +private: + + using FilterVector = std::vector; + + FilterVector m_filters; + bool m_default_accept; + +}; + +#endif /* LogFilterChain_hpp */ Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.cpp @@ -0,0 +1,61 @@ +//===-- LogFilterChain.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LogFilterChain.h" + +#include "LogFilter.h" + +LogFilterChain::LogFilterChain(bool default_accept) : + m_filters(), + m_default_accept(default_accept) +{ +} + +void +LogFilterChain::AppendFilter(const LogFilterSP &filter_sp) +{ + if (filter_sp) + m_filters.push_back(filter_sp); +} + +void +LogFilterChain::ClearFilterChain() +{ + m_filters.clear(); +} + +bool +LogFilterChain::GetDefaultAccepts() const +{ + return m_default_accept; +} + +void +LogFilterChain::SetDefaultAccepts(bool default_accept) +{ + m_default_accept = default_accept; +} + +bool +LogFilterChain::GetAcceptMessage(const LogMessage &message) const +{ + for (auto filter_sp : m_filters) + { + if (filter_sp->DoesMatch(message)) + { + // This message matches this filter. If the filter accepts matches, + // this message matches; otherwise, it rejects matches. + return filter_sp->MatchesAreAccepted(); + } + } + + // None of the filters matched. Therefore, we do whatever the + // default fall-through rule says. + return m_default_accept; +} Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.h @@ -0,0 +1,36 @@ +//===-- LogFilterExactMatch.h -----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LogFilterExactMatch_h +#define LogFilterExactMatch_h + +#include + +#include "DarwinLogInterfaces.h" +#include "DarwinLogTypes.h" +#include "LogFilter.h" + +class LogFilterExactMatch : public LogFilter +{ +public: + + LogFilterExactMatch(bool match_accepts, FilterTarget filter_target, + const std::string &match_text); + + bool + DoesMatch(const LogMessage &message) const override; + +private: + + const FilterTarget m_filter_target; + const std::string m_match_text; + +}; + +#endif Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.cpp @@ -0,0 +1,57 @@ +//===-- LogFilterExactMatch.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LogFilterExactMatch.h" +#include "LogMessage.h" + +LogFilterExactMatch::LogFilterExactMatch(bool match_accepts, + FilterTarget filter_target, + const std::string &match_text) : + LogFilter(match_accepts), + m_filter_target(filter_target), + m_match_text(match_text) +{ +} + +bool +LogFilterExactMatch::DoesMatch(const LogMessage &message) const +{ + switch (m_filter_target) + { + case eFilterTargetActivity: + // Empty fields never match a condition. + if (!message.HasActivity()) + return false; + return m_match_text == message.GetActivity(); + case eFilterTargetActivityChain: + // Empty fields never match a condition. + if (!message.HasActivity()) + return false; + return m_match_text == message.GetActivityChain(); + case eFilterTargetCategory: + // Empty fields never match a condition. + if (!message.HasCategory()) + return false; + return m_match_text == message.GetCategory(); + case eFilterTargetMessage: + { + const char *message_text = message.GetMessage(); + return (message_text != nullptr) && + (m_match_text == message_text); + } + case eFilterTargetSubsystem: + // Empty fields never match a condition. + if (!message.HasSubsystem()) + return false; + return m_match_text == message.GetSubsystem(); + default: + // We don't know this type. + return false; + } +} Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.h @@ -0,0 +1,58 @@ +//===-- LogFilterRegex.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#ifndef LogFilterRegex_h +#define LogFilterRegex_h + +// C includes +#include + +// C++ includes +#include + +#include "DarwinLogInterfaces.h" +#include "DarwinLogTypes.h" +#include "LogFilter.h" + +class LogFilterRegex : public LogFilter +{ +public: + + LogFilterRegex(bool match_accepts, FilterTarget filter_target, + const std::string ®ex); + + virtual + ~LogFilterRegex(); + + bool + IsValid() const + { + return m_is_valid; + } + + const char* + GetErrorAsCString() const + { + return m_error_text.c_str(); + } + + bool + DoesMatch(const LogMessage &message) const override; + +private: + + const FilterTarget m_filter_target; + const std::string m_regex_text; + regex_t m_regex; + bool m_is_valid; + std::string m_error_text; +}; + +#endif /* LogFilterSubsystemRegex_hpp */ Index: tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.cpp @@ -0,0 +1,118 @@ +//===-- LogFilterRegex.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "LogFilterRegex.h" + +#include "DNBLog.h" +#include "LogMessage.h" + +//---------------------------------------------------------------------- +// Enable enhanced mode if it is available. This allows for things like +// \d for digit, \s for space, and many more, but it isn't available +// everywhere. +//---------------------------------------------------------------------- +#if defined(REG_ENHANCED) +#define DEFAULT_COMPILE_FLAGS (REG_ENHANCED|REG_EXTENDED) +#else +#define DEFAULT_COMPILE_FLAGS (REG_EXTENDED) +#endif + +LogFilterRegex::LogFilterRegex(bool match_accepts, + FilterTarget filter_target, + const std::string ®ex) : + LogFilter(match_accepts), + m_filter_target(filter_target), + m_regex_text(regex), + m_regex(), + m_is_valid(false), + m_error_text() +{ + // Clear it. + memset(&m_regex, 0, sizeof(m_regex)); + + // Compile it. + if (!regex.empty()) + { + auto comp_err = ::regcomp(&m_regex, regex.c_str(), + DEFAULT_COMPILE_FLAGS); + m_is_valid = (comp_err == 0); + if (!m_is_valid) + { + char buffer[256]; + buffer[0] = '\0'; + ::regerror(comp_err, &m_regex, buffer, sizeof(buffer)); + m_error_text = buffer; + } + } +} + +LogFilterRegex::~LogFilterRegex() +{ + if (m_is_valid) + { + // Free the regex internals. + regfree(&m_regex); + } +} + +bool +LogFilterRegex::DoesMatch(const LogMessage &message) const +{ + switch (m_filter_target) + { + case eFilterTargetActivity: + // Empty fields never match a condition. + if (!message.HasActivity()) + return false; + return ::regexec(&m_regex, message.GetActivity(), 0, nullptr, 0) + == 0; + case eFilterTargetActivityChain: + // Empty fields never match a condition. + if (!message.HasActivity()) + return false; + return ::regexec(&m_regex, message.GetActivityChain().c_str(), 0, + nullptr, 0) == 0; + case eFilterTargetCategory: + // Empty fields never match a condition. + if (!message.HasCategory()) + return false; + return ::regexec(&m_regex, message.GetCategory(), 0, nullptr, + 0) == 0; + case eFilterTargetMessage: + { + const char *message_text = message.GetMessage(); + if (!message_text) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "LogFilterRegex: regex " + "\"%s\" no match due to nullptr message.", + m_regex_text.c_str()); + return false; + } + + bool match = ::regexec(&m_regex, message_text, 0, + nullptr, 0) == 0; + DNBLogThreadedIf(LOG_DARWIN_LOG, "LogFilterRegex: regex " + "\"%s\" %s message \"%s\".", + m_regex_text.c_str(), + match ? "matches" : "does not match", + message_text); + return match; + } + case eFilterTargetSubsystem: + // Empty fields never match a condition. + if (!message.HasSubsystem()) + return false; + return ::regexec(&m_regex, message.GetSubsystem(), 0, nullptr, + 0) == 0; + default: + // We don't know this type. + return false; + } +} Index: tools/debugserver/source/MacOSX/DarwinLog/LogMessage.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogMessage.h @@ -0,0 +1,53 @@ +//===-- LogMessage.h --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LogMessage_h +#define LogMessage_h + +#include + +class LogMessage +{ +public: + + virtual + ~LogMessage(); + + virtual bool + HasActivity() const = 0; + + virtual const char* + GetActivity() const = 0; + + virtual std::string + GetActivityChain() const = 0; + + virtual bool + HasCategory() const = 0; + + virtual const char* + GetCategory() const = 0; + + virtual bool + HasSubsystem() const = 0; + + virtual const char* + GetSubsystem() const = 0; + + // This can be expensive, so once we ask for it, we'll cache the result. + virtual const char* + GetMessage() const = 0; + +protected: + + LogMessage(); + +}; + +#endif /* LogMessage_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/LogMessage.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogMessage.cpp @@ -0,0 +1,19 @@ +//===-- LogMessage.cpp ------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#include "LogMessage.h" + +LogMessage::LogMessage() +{ +} + +LogMessage::~LogMessage() +{ +} Index: tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.h @@ -0,0 +1,73 @@ +//===-- LogMessageOsLog.h ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + + +#ifndef LogMessageOsLog_h +#define LogMessageOsLog_h + +#include "DarwinLogInterfaces.h" + +#include "ActivityStreamSPI.h" +#include "LogMessage.h" + +using ActivityStreamEntry = struct os_activity_stream_entry_s; + +// ----------------------------------------------------------------------------- +/// Provides a unified wrapper around os_log()-style log messages. +/// +/// The lifetime of this class is intended to be very short. The caller +/// must ensure that the passed in ActivityStore and ActivityStreamEntry +/// outlive this LogMessageOsLog entry. +// ----------------------------------------------------------------------------- + +class LogMessageOsLog : public LogMessage +{ +public: + + static void + SetFormatterFunction(os_log_copy_formatted_message_t format_func); + + LogMessageOsLog(const ActivityStore &activity_store, + ActivityStreamEntry &entry); + + // API methods + + bool + HasActivity() const override; + + const char* + GetActivity() const override; + + std::string + GetActivityChain() const override; + + bool + HasCategory() const override; + + const char* + GetCategory() const override; + + bool + HasSubsystem() const override; + + const char* + GetSubsystem() const override; + + const char* + GetMessage() const override; + +private: + + const ActivityStore &m_activity_store; + ActivityStreamEntry &m_entry; + mutable std::string m_message; + +}; + +#endif /* LogMessageOsLog_h */ Index: tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.cpp @@ -0,0 +1,95 @@ +//===-- LogMessageOsLog.cpp -------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "LogMessageOsLog.h" + +#include "ActivityStore.h" +#include "ActivityStreamSPI.h" + +namespace +{ + static os_log_copy_formatted_message_t s_log_copy_formatted_message; +} + +void +LogMessageOsLog::SetFormatterFunction(os_log_copy_formatted_message_t + format_func) +{ + s_log_copy_formatted_message = format_func; +} + +LogMessageOsLog::LogMessageOsLog(const ActivityStore &activity_store, + ActivityStreamEntry &entry) : + LogMessage(), + m_activity_store(activity_store), + m_entry(entry), + m_message() +{ +} + +bool +LogMessageOsLog::HasActivity() const +{ + return m_entry.activity_id != 0; +} + +const char* +LogMessageOsLog::GetActivity() const +{ + return m_activity_store.GetActivityForID(m_entry.activity_id); +} + +std::string +LogMessageOsLog::GetActivityChain() const +{ + return m_activity_store.GetActivityChainForID(m_entry.activity_id); +} + +bool +LogMessageOsLog::HasCategory() const +{ + return m_entry.log_message.category && + (m_entry.log_message.category[0] != 0); +} + +const char* +LogMessageOsLog::GetCategory() const +{ + return m_entry.log_message.category; +} + +bool +LogMessageOsLog::HasSubsystem() const +{ + return m_entry.log_message.subsystem && + (m_entry.log_message.subsystem[0] != 0); +} + +const char* +LogMessageOsLog::GetSubsystem() const +{ + return m_entry.log_message.subsystem; +} + +const char* +LogMessageOsLog::GetMessage() const +{ + if (m_message.empty()) + { + std::unique_ptr formatted_message( + s_log_copy_formatted_message(&m_entry.log_message)); + if (formatted_message) + m_message = formatted_message.get(); + // else + // TODO log + } + + // This is safe to return as we're not modifying it once we've formatted it. + return m_message.c_str(); +} Index: tools/debugserver/source/MacOSX/OsLogger.h =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/OsLogger.h @@ -0,0 +1,24 @@ +//===-- OsLogger.h ----------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef OsLogger_h +#define OsLogger_h + +#include "DNBDefs.h" + +class OsLogger +{ +public: + + static DNBCallbackLog + GetLogFunction(); + +}; + +#endif /* OsLogger_h */ Index: tools/debugserver/source/MacOSX/OsLogger.cpp =================================================================== --- /dev/null +++ tools/debugserver/source/MacOSX/OsLogger.cpp @@ -0,0 +1,71 @@ +//===-- OsLogger.cpp --------------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OsLogger.h" + +#if LLDB_USE_OS_LOG + +#include + +#include "DNBDefs.h" +#include "DNBLog.h" + +#define LLDB_OS_LOG_MAX_BUFFER_LENGTH 256 + +namespace +{ + //---------------------------------------------------------------------- + // Darwin os_log logging callback that can be registered with + // DNBLogSetLogCallback + //---------------------------------------------------------------------- + void + DarwinLogCallback(void *baton, uint32_t flags, const char *format, + va_list args) + { + if (format == nullptr) + return; + + static os_log_t g_logger; + if (!g_logger) + { + g_logger = os_log_create("com.apple.dt.lldb", "debugserver"); + if (!g_logger) + return; + } + + os_log_type_t log_type; + if (flags & DNBLOG_FLAG_FATAL) log_type = OS_LOG_TYPE_FAULT; + else if (flags & DNBLOG_FLAG_ERROR) log_type = OS_LOG_TYPE_ERROR; + else if (flags & DNBLOG_FLAG_WARNING) log_type = OS_LOG_TYPE_DEFAULT; + else if (flags & DNBLOG_FLAG_VERBOSE) log_type = OS_LOG_TYPE_DEBUG; + else log_type = OS_LOG_TYPE_DEFAULT; + + // This code is unfortunate. os_log* only takes static strings, but + // our current log API isn't set up to make use of that style. + char buffer[LLDB_OS_LOG_MAX_BUFFER_LENGTH]; + vsnprintf(buffer, sizeof(buffer), format, args); + os_log_with_type(g_logger, log_type, "%{public}s", buffer); + } +} + +DNBCallbackLog +OsLogger::GetLogFunction() +{ + return _os_log_impl ? DarwinLogCallback : nullptr; +} + +#else + +DNBCallbackLog +OsLogger::GetLogFunction() +{ + return nullptr; +} + +#endif Index: tools/debugserver/source/RNBContext.h =================================================================== --- tools/debugserver/source/RNBContext.h +++ tools/debugserver/source/RNBContext.h @@ -25,21 +25,23 @@ public: enum { - event_proc_state_changed = 0x01, - event_proc_thread_running = 0x02, // Sticky - event_proc_thread_exiting = 0x04, - event_proc_stdio_available = 0x08, - event_proc_profile_data = 0x10, - event_read_packet_available = 0x20, - event_read_thread_running = 0x40, // Sticky - event_read_thread_exiting = 0x80, + event_proc_state_changed = 0x001, + event_proc_thread_running = 0x002, // Sticky + event_proc_thread_exiting = 0x004, + event_proc_stdio_available = 0x008, + event_proc_profile_data = 0x010, + event_read_packet_available = 0x020, + event_read_thread_running = 0x040, // Sticky + event_read_thread_exiting = 0x080, + event_darwin_log_data_available = 0x100, normal_event_bits = event_proc_state_changed | event_proc_thread_exiting | event_proc_stdio_available | event_proc_profile_data | event_read_packet_available | - event_read_thread_exiting, + event_read_thread_exiting | + event_darwin_log_data_available, sticky_event_bits = event_proc_thread_running | event_read_thread_running, Index: tools/debugserver/source/RNBContext.cpp =================================================================== --- tools/debugserver/source/RNBContext.cpp +++ tools/debugserver/source/RNBContext.cpp @@ -245,6 +245,8 @@ s += "proc_stdio_available "; if (events & event_proc_profile_data) s += "proc_profile_data "; + if (events & event_darwin_log_data_available) + s += "darwin_log_data_available "; if (events & event_read_packet_available) s += "read_packet_available "; if (events & event_read_thread_running) Index: tools/debugserver/source/RNBRemote.h =================================================================== --- tools/debugserver/source/RNBRemote.h +++ tools/debugserver/source/RNBRemote.h @@ -135,6 +135,8 @@ speed_test, // 'qSpeedTest:' set_detach_on_error, // 'QSetDetachOnError:' query_transfer, // 'qXfer:' + query_supported_async_json_packets, // 'QSupportedAsyncJSONPackets' + configure_darwin_log, // 'ConfigureDarwinLog:' unknown_type } PacketEnum; @@ -251,6 +253,8 @@ rnb_err_t HandlePacket_qXfer (const char *p); rnb_err_t HandlePacket_stop_process (const char *p); rnb_err_t HandlePacket_QSetDetachOnError (const char *p); + rnb_err_t HandlePacket_qStructuredDataPlugins (const char *p); + rnb_err_t HandlePacket_QConfigureDarwinLog (const char *p); rnb_err_t SendStopReplyPacketForThread (nub_thread_t tid); rnb_err_t SendHexEncodedBytePacket (const char *header, const void *buf, size_t buf_len, const char *footer); @@ -259,6 +263,8 @@ void FlushSTDIO (); void SendAsyncProfileData (); rnb_err_t SendAsyncProfileDataPacket (char *buf, nub_size_t buf_size); + void SendAsyncDarwinLogData (); + rnb_err_t SendAsyncJSONPacket (const JSONGenerator::Dictionary &dictionary); RNBContext& Context() { return m_ctx; } RNBSocket& Comm() { return m_comm; } Index: tools/debugserver/source/RNBRemote.cpp =================================================================== --- tools/debugserver/source/RNBRemote.cpp +++ tools/debugserver/source/RNBRemote.cpp @@ -26,14 +26,18 @@ #include #endif +#include "DarwinLogCollector.h" +#include "DarwinLogEvent.h" #include "DNB.h" #include "DNBDataRef.h" #include "DNBLog.h" #include "DNBThreadResumeActions.h" #include "JSONGenerator.h" +#include "OsLogger.h" #include "RNBContext.h" #include "RNBServices.h" #include "RNBSocket.h" +#include "JSON.h" #include "lldb/Utility/StringExtractor.h" #include "MacOSX/Genealogy.h" #include "JSONGenerator.h" @@ -52,6 +56,15 @@ #include // for endianness predefines //---------------------------------------------------------------------- +// constants +//---------------------------------------------------------------------- + +static const std::string OS_LOG_EVENTS_KEY_NAME("events"); +static const std::string JSON_ASYNC_TYPE_KEY_NAME("type"); +static const DarwinLogEventVector::size_type + DARWIN_LOG_MAX_EVENTS_PER_PACKET = 10; + +//---------------------------------------------------------------------- // std::iostream formatting macros //---------------------------------------------------------------------- #define RAW_HEXBASE std::setfill('0') << std::hex << std::right @@ -77,6 +90,12 @@ #define INDENT_WITH_TABS(iword_idx) std::setfill('\t') << std::setw((iword_idx)) << "" // Class to handle communications via gdb remote protocol. +//---------------------------------------------------------------------- +// Prototypes +//---------------------------------------------------------------------- + +static std::string +binary_encode_string (const std::string &s); //---------------------------------------------------------------------- // Decode a single hex character and return the hex value as a number or @@ -314,9 +333,10 @@ t.push_back (Packet (set_detach_on_error, &RNBRemote::HandlePacket_QSetDetachOnError, NULL, "QSetDetachOnError:", "Set whether debugserver will detach (1) or kill (0) from the process it is controlling if it loses connection to lldb.")); t.push_back (Packet (speed_test, &RNBRemote::HandlePacket_qSpeedTest, NULL, "qSpeedTest:", "Test the maximum speed at which packet can be sent/received.")); t.push_back (Packet (query_transfer, &RNBRemote::HandlePacket_qXfer, NULL, "qXfer:", "Support the qXfer packet.")); + t.push_back (Packet (query_supported_async_json_packets, &RNBRemote::HandlePacket_qStructuredDataPlugins, NULL, "qStructuredDataPlugins", "Query for the structured data plugins supported by the remote.")); + t.push_back (Packet (configure_darwin_log, &RNBRemote::HandlePacket_QConfigureDarwinLog, NULL, "QConfigureDarwinLog:", "Configure the DarwinLog structured data plugin support.")); } - void RNBRemote::FlushSTDIO () { @@ -364,6 +384,86 @@ } } +void +RNBRemote::SendAsyncDarwinLogData () +{ + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): enter", + __FUNCTION__); + + if (!m_ctx.HasValidProcessID()) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): ignoring due to" + "invalid process id", __FUNCTION__); + return; + } + + nub_process_t pid = m_ctx.ProcessID(); + DarwinLogEventVector::size_type entry_count = 0; + + // NOTE: the current looping structure here does nothing + // to guarantee that we can send off async packets faster + // than we generate them. It will keep sending as long + // as there's data to send. + do + { + DarwinLogEventVector events = + DNBProcessGetAvailableDarwinLogEvents(pid); + entry_count = events.size(); + + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): outer loop enter", + __FUNCTION__); + + for (DarwinLogEventVector::size_type base_entry = 0; + base_entry < entry_count; + base_entry += DARWIN_LOG_MAX_EVENTS_PER_PACKET) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): inner loop enter", + __FUNCTION__); + + // We limit the total number of entries we pack + // into a single JSON async packet just so it + // doesn't get too large. + JSONGenerator::Dictionary async_dictionary; + + // Specify the type of the JSON async data we're sending. + async_dictionary.AddStringItem( + JSON_ASYNC_TYPE_KEY_NAME, "DarwinLog"); + + // Create an array entry in the dictionary to hold all + // the events going in this packet. + JSONGenerator::ArraySP events_array(new JSONGenerator::Array()); + async_dictionary.AddItem(OS_LOG_EVENTS_KEY_NAME, events_array); + + // We bundle up to DARWIN_LOG_MAX_EVENTS_PER_PACKET events in + // a single packet. + const auto inner_loop_bound = + std::min(base_entry + DARWIN_LOG_MAX_EVENTS_PER_PACKET, + entry_count); + for (DarwinLogEventVector::size_type i = base_entry; + i < inner_loop_bound; ++i) + { + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): adding " + "entry index %lu to the JSON packet", + __FUNCTION__, i); + events_array->AddItem(events[i]); + } + + // Send off the packet. + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): sending JSON " + "packet, %lu entries remain", __FUNCTION__, + entry_count - inner_loop_bound); + SendAsyncJSONPacket(async_dictionary); + } + + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): outer loop exit", + __FUNCTION__); + + } while (entry_count > 0); + + DNBLogThreadedIf(LOG_DARWIN_LOG, "RNBRemote::%s(): exit", + __PRETTY_FUNCTION__); +} + rnb_err_t RNBRemote::SendHexEncodedBytePacket (const char *header, const void *buf, size_t buf_len, const char *footer) { @@ -412,6 +512,19 @@ return SendPacket(packet); } +rnb_err_t +RNBRemote::SendAsyncJSONPacket(const JSONGenerator::Dictionary &dictionary) +{ + std::ostringstream stream; + // We're choosing something that is easy to spot if we somehow get one + // of these coming out at the wrong time (i.e. when the remote side + // is not waiting for a process control completion response). + stream << "JSON-async:"; + dictionary.Dump(stream); + const std::string payload = binary_encode_string(stream.str()); + return SendPacket(payload); +} + // Given a std::string packet contents to send, possibly encode/compress it. // If compression is enabled, the returned std::string will be in one of two // forms: @@ -1099,12 +1212,11 @@ while (len--) { - unsigned char c = *str; + unsigned char c = *str++; if (c == 0x7d && len > 0) { len--; - str++; - c = *str ^ 0x20; + c = *str++ ^ 0x20; } bytes.push_back (c); } @@ -1114,7 +1226,7 @@ // Quote any meta characters in a std::string as per the binary // packet convention in the gdb-remote protocol. -std::string +static std::string binary_encode_string (const std::string &s) { std::string output; @@ -2037,6 +2149,11 @@ p += sizeof ("LOG_RNB_DEFAULT") - 1; bitmask |= LOG_RNB_DEFAULT; } + else if (strncmp (p, "LOG_DARWIN_LOG", sizeof ("LOG_DARWIN_LOG") - 1) == 0) + { + p += sizeof ("LOG_DARWIN_LOG") - 1; + bitmask |= LOG_DARWIN_LOG; + } else if (strncmp (p, "LOG_RNB_NONE", sizeof ("LOG_RNB_NONE") - 1) == 0) { p += sizeof ("LOG_RNB_NONE") - 1; @@ -2068,14 +2185,26 @@ // Did we get a properly formatted logging bitmask? if (p && *p == ';') { - // Enable DNB logging - DNBLogSetLogCallback(ASLLogCallback, NULL); + // Enable DNB logging. + // Use the existing log callback if one was already configured. + if (!DNBLogGetLogCallback()) + { + // Use the os_log()-based logger if available; otherwise, + // fallback to ASL. + auto log_callback = OsLogger::GetLogFunction(); + if (log_callback) + DNBLogSetLogCallback(log_callback, nullptr); + else + DNBLogSetLogCallback(ASLLogCallback, nullptr); + } + + // Update logging to use the configured log channel bitmask. DNBLogSetLogMask (bitmask); p++; } } // We're not going to support logging to a file for now. All logging - // goes through ASL. + // goes through ASL or the previously arranged log callback. #if 0 else if (strncmp (p, "mode=", sizeof ("mode=") - 1) == 0) { @@ -2306,6 +2435,99 @@ } rnb_err_t +RNBRemote::HandlePacket_qStructuredDataPlugins(const char *p) +{ + // We'll return a JSON array of supported packet types. + // The type is significant. For each of the supported + // packet types that have been enabled, there will be a + // 'J' async packet sent to the client with payload data. + // This payload data will be a JSON dictionary, and the + // top level dictionary will contain a string field with + // its value set to the relevant packet type from this list. + JSONGenerator::Array supported_json_packets; + + // Check for DarwinLog (libtrace os_log/activity support). + if (DarwinLogCollector::IsSupported()) + supported_json_packets.AddItem(JSONGenerator::StringSP( + new JSONGenerator::String("DarwinLog"))); + + // Send back the array. + std::ostringstream stream; + supported_json_packets.Dump(stream); + return SendPacket(stream.str()); +} + +rnb_err_t +RNBRemote::HandlePacket_QConfigureDarwinLog(const char *p) +{ + if (!DarwinLogCollector::IsSupported()) + { + // We should never have been given this request. + return SendPacket ("E89"); + } + + // Ensure we have a process. We expect a separate configure request for + // each process launched/attached. + const nub_process_t pid = m_ctx.ProcessID(); + if (pid == INVALID_NUB_PROCESS) + return SendPacket ("E94"); + + // Get the configuration dictionary. + p += strlen("QConfigureDarwinLog:"); + + // The configuration dictionary is binary encoded. + std::vector unescaped_config_data = decode_binary_data(p, -1); + std::string unescaped_config_string((const char*)&unescaped_config_data[0], + unescaped_config_data.size()); + DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLog: received config data: \"%s\"", + unescaped_config_string.c_str()); + auto configuration_sp = + JSONParser(unescaped_config_string.c_str()).ParseJSONValue(); + if (!configuration_sp) + { + // Malformed request - we require configuration data + // indicating whether we're enabling or disabling. + return SendPacket("E90"); + } + + if (!JSONObject::classof(configuration_sp.get())) + { + // Configuration data is not of the right type. + return SendPacket("E91"); + } + JSONObject &config_dict = *static_cast(configuration_sp.get()); + + // Check if we're enabling or disabling. + auto enabled_sp = config_dict.GetObject("enabled"); + if (!enabled_sp) + { + // Missing required "enabled" field. + return SendPacket("E92"); + } + if (!JSONTrue::classof(enabled_sp.get()) && + !JSONFalse::classof(enabled_sp.get())) + { + // Should be a boolean type, but wasn't. + return SendPacket("E93"); + } + const bool enabling = JSONTrue::classof(enabled_sp.get()); + + // TODO - handle other configuration parameters here. + + // Shut down any active activity stream for the process. + DarwinLogCollector::CancelStreamForProcess(pid); + + if (enabling) + { + // Look up the procecess. + if (!DarwinLogCollector::StartCollectingForProcess(pid, config_dict)) + return SendPacket("E95"); + } + + return SendPacket("OK"); +} + +rnb_err_t RNBRemote::HandlePacket_QListThreadsInStopReply (const char *p) { // If this packet is received, it allows us to send an extra key/value @@ -5510,26 +5732,33 @@ if (need_vouchers_comma_sep) json << ","; need_vouchers_comma_sep = true; - json << "\"process_infos\":["; bool printed_one_process_info = false; for (auto iter = process_info_indexes.begin(); iter != process_info_indexes.end(); ++iter) { if (printed_one_process_info) json << ","; - else - printed_one_process_info = true; Genealogy::ProcessExecutableInfoSP image_info_sp; uint32_t idx = *iter; image_info_sp = DNBGetGenealogyImageInfo (pid, idx); - json << "{"; - char uuid_buf[37]; - uuid_unparse_upper (image_info_sp->image_uuid, uuid_buf); - json << "\"process_info_index\":" << idx << ","; - json << "\"image_path\":\"" << json_string_quote_metachars (image_info_sp->image_path) << "\","; - json << "\"image_uuid\":\"" << uuid_buf <<"\""; - json << "}"; + if (image_info_sp) + { + if (!printed_one_process_info) + { + json << "\"process_infos\":["; + printed_one_process_info = true; + } + + json << "{"; + char uuid_buf[37]; + uuid_unparse_upper (image_info_sp->image_uuid, uuid_buf); + json << "\"process_info_index\":" << idx << ","; + json << "\"image_path\":\"" << json_string_quote_metachars (image_info_sp->image_path) << "\","; + json << "\"image_uuid\":\"" << uuid_buf <<"\""; + json << "}"; + } } - json << "]"; + if (printed_one_process_info) + json << "]"; } } else Index: tools/debugserver/source/debugserver.cpp =================================================================== --- tools/debugserver/source/debugserver.cpp +++ tools/debugserver/source/debugserver.cpp @@ -34,6 +34,7 @@ #include "DNB.h" #include "DNBLog.h" #include "DNBTimer.h" +#include "OsLogger.h" #include "PseudoTerminal.h" #include "RNBContext.h" #include "RNBServices.h" @@ -524,6 +525,9 @@ // Clear some bits if we are not running so we don't send any async packets event_mask &= ~RNBContext::event_proc_stdio_available; event_mask &= ~RNBContext::event_proc_profile_data; + // When we enable async structured data packets over another logical channel, + // this can be relaxed. + event_mask &= ~RNBContext::event_darwin_log_data_available; } // We want to make sure we consume all process state changes and have @@ -548,6 +552,11 @@ remote->SendAsyncProfileData(); } + if (set_events & RNBContext::event_darwin_log_data_available) + { + remote->SendAsyncDarwinLogData(); + } + if (set_events & RNBContext::event_read_packet_available) { // handleReceivedPacket will take care of resetting the @@ -1307,7 +1316,20 @@ else { // Enable DNB logging - DNBLogSetLogCallback(ASLLogCallback, NULL); + + // if os_log() support is available, log through that. + auto log_callback = OsLogger::GetLogFunction(); + if (log_callback) + { + DNBLogSetLogCallback(log_callback, nullptr); + DNBLog("debugserver will use os_log for internal logging."); + } + else + { + // Fall back to ASL support. + DNBLogSetLogCallback(ASLLogCallback, NULL); + DNBLog("debugserver will use ASL for internal logging."); + } DNBLogSetLogMask (log_flags); } Index: tools/lldb-mi/MICmnLLDBDebugger.cpp =================================================================== --- tools/lldb-mi/MICmnLLDBDebugger.cpp +++ tools/lldb-mi/MICmnLLDBDebugger.cpp @@ -403,7 +403,8 @@ bOk = bOk && RegisterForEvent(strDbgId, CMIUtilString(lldb::SBThread::GetBroadcasterClassName()), eventMask); eventMask = lldb::SBProcess::eBroadcastBitStateChanged | lldb::SBProcess::eBroadcastBitInterrupt | - lldb::SBProcess::eBroadcastBitSTDOUT | lldb::SBProcess::eBroadcastBitSTDERR | lldb::SBProcess::eBroadcastBitProfileData; + lldb::SBProcess::eBroadcastBitSTDOUT | lldb::SBProcess::eBroadcastBitSTDERR | lldb::SBProcess::eBroadcastBitProfileData | + lldb::SBProcess::eBroadcastBitStructuredData; bOk = bOk && RegisterForEvent(strDbgId, CMIUtilString(lldb::SBProcess::GetBroadcasterClassName()), eventMask); eventMask = lldb::SBCommandInterpreter::eBroadcastBitQuitCommandReceived | lldb::SBCommandInterpreter::eBroadcastBitThreadShouldExit | Index: tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.cpp =================================================================== --- tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.cpp +++ tools/lldb-mi/MICmnLLDBDebuggerHandleEvents.cpp @@ -179,6 +179,9 @@ case lldb::SBProcess::eBroadcastBitProfileData: pEventType = "eBroadcastBitProfileData"; break; + case lldb::SBProcess::eBroadcastBitStructuredData: + pEventType = "eBroadcastBitStructuredData"; + break; case lldb::SBProcess::eBroadcastBitStateChanged: pEventType = "eBroadcastBitStateChanged"; bOk = HandleProcessEventBroadcastBitStateChanged(vEvent);