diff options
| author | Todd Fiala <todd.fiala@gmail.com> | 2016-08-19 04:21:48 +0000 |
|---|---|---|
| committer | Todd Fiala <todd.fiala@gmail.com> | 2016-08-19 04:21:48 +0000 |
| commit | 759300192abe8e38e8a40ab95934ba602a78c252 (patch) | |
| tree | d284c51cf10c23d3023ab8e1308e26ad1ee218a7 /lldb/tools/debugserver/source | |
| parent | e2ca3b65fcba7c4c5dbd10c1e8925177b7718806 (diff) | |
| download | bcm5719-llvm-759300192abe8e38e8a40ab95934ba602a78c252.tar.gz bcm5719-llvm-759300192abe8e38e8a40ab95934ba602a78c252.zip | |
Add StructuredData plugin type; showcase with new DarwinLog feature
Take 2, with missing cmake line fixed. Build tested on
Ubuntu 14.04 with clang-3.6.
See docs/structured_data/StructuredDataPlugins.md for details.
differential review: https://reviews.llvm.org/D22976
reviewers: clayborg, jingham
llvm-svn: 279202
Diffstat (limited to 'lldb/tools/debugserver/source')
35 files changed, 3527 insertions, 29 deletions
diff --git a/lldb/tools/debugserver/source/CMakeLists.txt b/lldb/tools/debugserver/source/CMakeLists.txt index 94cef6c3120..b309d99a555 100644 --- a/lldb/tools/debugserver/source/CMakeLists.txt +++ b/lldb/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 @@ add_library(lldbDebugserverCommon 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 diff --git a/lldb/tools/debugserver/source/DNB.cpp b/lldb/tools/debugserver/source/DNB.cpp index bb360364919..fc36dcec078 100644 --- a/lldb/tools/debugserver/source/DNB.cpp +++ b/lldb/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 @@ DNBProcessGetAvailableProfileData (nub_process_t pid, char *buf, nub_size_t buf_ return 0; } +DarwinLogEventVector +DNBProcessGetAvailableDarwinLogEvents(nub_process_t pid) +{ + return DarwinLogCollector::GetEventsForProcess(pid); +} + nub_size_t DNBProcessGetStopCount (nub_process_t pid) { diff --git a/lldb/tools/debugserver/source/DNB.h b/lldb/tools/debugserver/source/DNB.h index fbaf5e34813..3e928e663d7 100644 --- a/lldb/tools/debugserver/source/DNB.h +++ b/lldb/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 @@ nub_bool_t DNBProcessMemoryDeallocate (nub_process_t pid, nub_addr_t addr) 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 diff --git a/lldb/tools/debugserver/source/DNBDefs.h b/lldb/tools/debugserver/source/DNBDefs.h index e3757e903e5..36efad22417 100644 --- a/lldb/tools/debugserver/source/DNBDefs.h +++ b/lldb/tools/debugserver/source/DNBDefs.h @@ -142,6 +142,7 @@ enum #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 diff --git a/lldb/tools/debugserver/source/JSON.cpp b/lldb/tools/debugserver/source/JSON.cpp new file mode 100644 index 00000000000..e7e0423e9e8 --- /dev/null +++ b/lldb/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 <assert.h> +#include <limits.h> + +// C++ includes +#include <iomanip> +#include <sstream> +#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<double>::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<JSONString*>(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 " <<start_index; + value = std::move(error.str()); + return Token::Error; + + } + else + { + const bool is_end_quote = escaped_ch == '"'; + const bool is_null = escaped_ch == 0; + if (was_escaped || (!is_end_quote && !is_null)) + { + if (CHAR_MIN <= escaped_ch && escaped_ch <= CHAR_MAX) + { + value.append(1, (char)escaped_ch); + } + else + { + error << "error: wide character support is needed for unicode character 0x" << std::setprecision(4) << std::hex << escaped_ch; + error << " at offset " << start_index; + value = std::move(error.str()); + return Token::Error; + } + } + else if (is_end_quote) + { + return Token::String; + } + else if (is_null) + { + value = "error: missing end quote for string"; + return Token::Error; + } + } + } + } + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + bool done = false; + bool got_decimal_point = false; + uint64_t exp_index = 0; + bool got_int_digits = (ch >= '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<JSONObject> 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<JSONArray> 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(); + +} diff --git a/lldb/tools/debugserver/source/JSON.h b/lldb/tools/debugserver/source/JSON.h new file mode 100644 index 00000000000..d1723d87a38 --- /dev/null +++ b/lldb/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 <inttypes.h> +#include <stdint.h> + +// C++ includes +#include <map> +#include <memory> +#include <ostream> +#include <string> +#include <vector> + +class JSONValue +{ +public: + virtual void + Write (std::ostream& s) = 0; + + typedef std::shared_ptr<JSONValue> 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<JSONString> 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<JSONNumber> 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 <typename T, + typename std::enable_if<std::is_integral<T>::value && + std::is_unsigned<T>::value>::type* = nullptr> + explicit JSONNumber (T u) : + JSONValue(JSONValue::Kind::Number), + m_data_type(DataType::Unsigned) + { + m_data.m_unsigned = u; + } + + template <typename T, + typename std::enable_if<std::is_integral<T>::value && + std::is_signed<T>::value>::type* = nullptr> + explicit JSONNumber (T s) : + JSONValue(JSONValue::Kind::Number), + m_data_type(DataType::Signed) + { + m_data.m_signed = s; + } + + template <typename T, + typename std::enable_if<std::is_floating_point<T>::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<JSONTrue> 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<JSONFalse> 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<JSONNull> 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<JSONObject> 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<std::string, JSONValue::SP> 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<JSONArray> SP; + + static bool classof(const JSONValue *V) + { + return V->GetKind() == JSONValue::Kind::Array; + } + +private: + typedef std::vector<JSONValue::SP> 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_ diff --git a/lldb/tools/debugserver/source/MacOSX/CMakeLists.txt b/lldb/tools/debugserver/source/MacOSX/CMakeLists.txt index a154dcc485a..49b0931ee59 100644 --- a/lldb/tools/debugserver/source/MacOSX/CMakeLists.txt +++ b/lldb/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 @@ set(DEBUGSERVER_USED_LIBS lldbUtility lldbDebugserverMacOSX_I386 lldbDebugserverMacOSX_X86_64 + lldbDebugserverMacOSX_DarwinLog ) add_lldb_executable(debugserver @@ -46,6 +48,7 @@ add_lldb_executable(debugserver MachThreadList.cpp MachVMMemory.cpp MachVMRegion.cpp + OsLogger.cpp ${generated_mach_interfaces} ${DEBUGSERVER_VERS_GENERATED_FILE} ) diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.cpp new file mode 100644 index 00000000000..e98a131512a --- /dev/null +++ b/lldb/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() +{ +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStore.h new file mode 100644 index 00000000000..2e998ba367c --- /dev/null +++ b/lldb/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 <string> + +#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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/ActivityStreamSPI.h new file mode 100644 index 00000000000..4ddf13b3fcd --- /dev/null +++ b/lldb/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 <sys/time.h> +#include <xpc/xpc.h> + +#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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/CMakeLists.txt b/lldb/tools/debugserver/source/MacOSX/DarwinLog/CMakeLists.txt new file mode 100644 index 00000000000..47e7362f0d5 --- /dev/null +++ b/lldb/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 + ) diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.cpp new file mode 100644 index 00000000000..30f3e521669 --- /dev/null +++ b/lldb/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 <dlfcn.h> + +#include <cinttypes> +#include <mutex> +#include <vector> + +#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<DarwinLogCollectorSP> s_collectors; + + static void + TrackCollector(const DarwinLogCollectorSP &collector_sp) + { + std::lock_guard<Mutex> 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<Mutex> 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<Mutex> 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<JSONObject*>(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<JSONArray*>(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<JSONObject*> + (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<DarwinLogCollector> 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<std::mutex> 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<std::mutex> 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 : + "<invalid-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<std::mutex> 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<std::mutex> 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; +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.h new file mode 100644 index 00000000000..8263423fce1 --- /dev/null +++ b/lldb/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 <sys/types.h> + +#include <memory> +#include <mutex> +#include <unordered_map> + +#include "ActivityStore.h" +#include "ActivityStreamSPI.h" +#include "DarwinLogEvent.h" +#include "DarwinLogInterfaces.h" +#include "DNBDefs.h" +#include "JSON.h" + +class DarwinLogCollector; +typedef std::shared_ptr<DarwinLogCollector> DarwinLogCollectorSP; + +class DarwinLogCollector: + public std::enable_shared_from_this<DarwinLogCollector>, + 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<os_activity_id_t, ActivityInfo>; + + 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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogEvent.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogEvent.h new file mode 100644 index 00000000000..6be3b81f3e1 --- /dev/null +++ b/lldb/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 <memory> +#include <vector> + +#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<DarwinLogEvent>; +using DarwinLogEventVector = std::vector<DarwinLogEventSP>; + +#endif diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogInterfaces.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogInterfaces.h new file mode 100644 index 00000000000..afb979f6cfe --- /dev/null +++ b/lldb/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 <memory> + +class ActivityStore; + +class LogFilter; +using LogFilterSP = std::shared_ptr<LogFilter>; + +class LogFilterChain; +using LogFilterChainSP = std::shared_ptr<LogFilterChain>; + +class LogMessage; + +#endif /* DarwinLogInterfaces_h */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogTypes.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogTypes.h new file mode 100644 index 00000000000..a090fba0c14 --- /dev/null +++ b/lldb/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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilter.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilter.cpp new file mode 100644 index 00000000000..80af86ec505 --- /dev/null +++ b/lldb/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() +{ +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilter.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilter.h new file mode 100644 index 00000000000..4d54394cb32 --- /dev/null +++ b/lldb/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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.cpp new file mode 100644 index 00000000000..888fbd9bfb1 --- /dev/null +++ b/lldb/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; +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterChain.h new file mode 100644 index 00000000000..8774c15d98c --- /dev/null +++ b/lldb/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 <vector> + +#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<LogFilterSP>; + + FilterVector m_filters; + bool m_default_accept; + +}; + +#endif /* LogFilterChain_hpp */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.cpp new file mode 100644 index 00000000000..b85458618fd --- /dev/null +++ b/lldb/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; + } +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterExactMatch.h new file mode 100644 index 00000000000..131afaf234b --- /dev/null +++ b/lldb/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 <string> + +#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 diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.cpp new file mode 100644 index 00000000000..128c7514493 --- /dev/null +++ b/lldb/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; + } +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogFilterRegex.h new file mode 100644 index 00000000000..796a62500ff --- /dev/null +++ b/lldb/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 <regex.h> + +// C++ includes +#include <string> + +#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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessage.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessage.cpp new file mode 100644 index 00000000000..d98bb3de3c1 --- /dev/null +++ b/lldb/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() +{ +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessage.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessage.h new file mode 100644 index 00000000000..0ec2b4277a4 --- /dev/null +++ b/lldb/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 <string> + +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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.cpp b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.cpp new file mode 100644 index 00000000000..f3b6e443918 --- /dev/null +++ b/lldb/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<char[]> 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(); +} diff --git a/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.h b/lldb/tools/debugserver/source/MacOSX/DarwinLog/LogMessageOsLog.h new file mode 100644 index 00000000000..7c02059fc57 --- /dev/null +++ b/lldb/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 */ diff --git a/lldb/tools/debugserver/source/MacOSX/OsLogger.cpp b/lldb/tools/debugserver/source/MacOSX/OsLogger.cpp new file mode 100644 index 00000000000..1f424e7da17 --- /dev/null +++ b/lldb/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 <os/log.h> + +#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 diff --git a/lldb/tools/debugserver/source/MacOSX/OsLogger.h b/lldb/tools/debugserver/source/MacOSX/OsLogger.h new file mode 100644 index 00000000000..6733b925335 --- /dev/null +++ b/lldb/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 */ diff --git a/lldb/tools/debugserver/source/RNBContext.cpp b/lldb/tools/debugserver/source/RNBContext.cpp index 74ba5c45cb4..0b44fdbd581 100644 --- a/lldb/tools/debugserver/source/RNBContext.cpp +++ b/lldb/tools/debugserver/source/RNBContext.cpp @@ -245,6 +245,8 @@ RNBContext::EventsAsString (nub_event_t events, std::string& s) 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) diff --git a/lldb/tools/debugserver/source/RNBContext.h b/lldb/tools/debugserver/source/RNBContext.h index 34fb9796ebe..83237ebccd9 100644 --- a/lldb/tools/debugserver/source/RNBContext.h +++ b/lldb/tools/debugserver/source/RNBContext.h @@ -25,21 +25,23 @@ class RNBContext 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, diff --git a/lldb/tools/debugserver/source/RNBRemote.cpp b/lldb/tools/debugserver/source/RNBRemote.cpp index 30b804316a1..11abe3c5d78 100644 --- a/lldb/tools/debugserver/source/RNBRemote.cpp +++ b/lldb/tools/debugserver/source/RNBRemote.cpp @@ -26,14 +26,18 @@ #include <sched.h> #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 <TargetConditionals.h> // 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 @@ RNBRemote::CreatePacketTable () 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 @@ RNBRemote::SendAsyncProfileData () } } +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 @@ RNBRemote::SendAsyncProfileDataPacket (char *buf, nub_size_t buf_size) 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 @@ decode_binary_data (const char *str, size_t len) 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 @@ decode_binary_data (const char *str, size_t len) // 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 @@ set_logging (const char *p) 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 @@ set_logging (const char *p) // 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 @@ RNBRemote::HandlePacket_QSetDetachOnError (const char *p) } 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<uint8_t> 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<JSONObject*>(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 @@ RNBRemote::HandlePacket_jThreadExtendedInfo (const char *p) 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 diff --git a/lldb/tools/debugserver/source/RNBRemote.h b/lldb/tools/debugserver/source/RNBRemote.h index 1bf7535e141..57c2a22e8df 100644 --- a/lldb/tools/debugserver/source/RNBRemote.h +++ b/lldb/tools/debugserver/source/RNBRemote.h @@ -135,6 +135,8 @@ public: 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 @@ public: 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 @@ public: 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; } diff --git a/lldb/tools/debugserver/source/debugserver.cpp b/lldb/tools/debugserver/source/debugserver.cpp index e9ddbf47faa..e1ee2c12131 100644 --- a/lldb/tools/debugserver/source/debugserver.cpp +++ b/lldb/tools/debugserver/source/debugserver.cpp @@ -34,6 +34,7 @@ extern "C" int proc_set_wakemon_params(pid_t, int, int); // <libproc_internal.h> #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 @@ RNBRunLoopInferiorExecuting (RNBRemote *remote) // 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 @@ RNBRunLoopInferiorExecuting (RNBRemote *remote) 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 @@ main (int argc, char *argv[]) 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); } |

