1 //===-- DarwinLogCollector.cpp ----------------------------------*- C++ -*-===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 #include "DarwinLogCollector.h"
11 #include "ActivityStreamSPI.h"
21 #include "DarwinLogTypes.h"
22 #include "LogFilterChain.h"
23 #include "LogFilterExactMatch.h"
24 #include "LogFilterRegex.h"
25 #include "LogMessageOsLog.h"
26 #include "MachProcess.h"
27 #include "RNBContext.h"
29 #include "RNBRemote.h"
31 // Use an anonymous namespace for variables and methods that have no
32 // reason to leak out through the interface.
34 /// Specify max depth that the activity parent-child chain will search
35 /// back to get the full activity chain name. If we do more than this,
36 /// we assume either we hit a loop or it's just too long.
37 static const size_t MAX_ACTIVITY_CHAIN_DEPTH = 10;
39 // Used to tap into and retrieve logs from target process.
40 // (Consumer of os_log).
41 static os_activity_stream_for_pid_t s_os_activity_stream_for_pid;
42 static os_activity_stream_resume_t s_os_activity_stream_resume;
43 static os_activity_stream_cancel_t s_os_activity_stream_cancel;
44 static os_log_copy_formatted_message_t s_os_log_copy_formatted_message;
45 static os_activity_stream_set_event_handler_t
46 s_os_activity_stream_set_event_handler;
48 bool LookupSPICalls() {
49 static std::once_flag s_once_flag;
50 static bool s_has_spi;
52 std::call_once(s_once_flag, [] {
53 s_os_activity_stream_for_pid = (os_activity_stream_for_pid_t)dlsym(
54 RTLD_DEFAULT, "os_activity_stream_for_pid");
55 s_os_activity_stream_resume = (os_activity_stream_resume_t)dlsym(
56 RTLD_DEFAULT, "os_activity_stream_resume");
57 s_os_activity_stream_cancel = (os_activity_stream_cancel_t)dlsym(
58 RTLD_DEFAULT, "os_activity_stream_cancel");
59 s_os_log_copy_formatted_message = (os_log_copy_formatted_message_t)dlsym(
60 RTLD_DEFAULT, "os_log_copy_formatted_message");
61 s_os_activity_stream_set_event_handler =
62 (os_activity_stream_set_event_handler_t)dlsym(
63 RTLD_DEFAULT, "os_activity_stream_set_event_handler");
65 // We'll indicate we're all set if every function entry point
67 s_has_spi = (s_os_activity_stream_for_pid != nullptr) &&
68 (s_os_activity_stream_resume != nullptr) &&
69 (s_os_activity_stream_cancel != nullptr) &&
70 (s_os_log_copy_formatted_message != nullptr) &&
71 (s_os_activity_stream_set_event_handler != nullptr);
73 DNBLogThreadedIf(LOG_DARWIN_LOG, "Found os_log SPI calls.");
74 // Tell LogMessageOsLog how to format messages when search
75 // criteria requires it.
76 LogMessageOsLog::SetFormatterFunction(s_os_log_copy_formatted_message);
78 DNBLogThreadedIf(LOG_DARWIN_LOG, "Failed to find os_log SPI "
86 using Mutex = std::mutex;
87 static Mutex s_collector_mutex;
88 static std::vector<DarwinLogCollectorSP> s_collectors;
90 static void TrackCollector(const DarwinLogCollectorSP &collector_sp) {
91 std::lock_guard<Mutex> locker(s_collector_mutex);
92 if (std::find(s_collectors.begin(), s_collectors.end(), collector_sp) !=
94 DNBLogThreadedIf(LOG_DARWIN_LOG,
95 "attempted to add same collector multiple times");
98 s_collectors.push_back(collector_sp);
101 static void StopTrackingCollector(const DarwinLogCollectorSP &collector_sp) {
102 std::lock_guard<Mutex> locker(s_collector_mutex);
104 std::remove(s_collectors.begin(), s_collectors.end(), collector_sp),
108 static DarwinLogCollectorSP FindCollectorForProcess(pid_t pid) {
109 std::lock_guard<Mutex> locker(s_collector_mutex);
110 for (const auto &collector_sp : s_collectors) {
111 if (collector_sp && (collector_sp->GetProcessID() == pid))
114 return DarwinLogCollectorSP();
117 static FilterTarget TargetStringToEnum(const std::string &filter_target_name) {
118 if (filter_target_name == "activity")
119 return eFilterTargetActivity;
120 else if (filter_target_name == "activity-chain")
121 return eFilterTargetActivityChain;
122 else if (filter_target_name == "category")
123 return eFilterTargetCategory;
124 else if (filter_target_name == "message")
125 return eFilterTargetMessage;
126 else if (filter_target_name == "subsystem")
127 return eFilterTargetSubsystem;
129 return eFilterTargetInvalid;
132 class Configuration {
134 Configuration(const JSONObject &config)
136 m_activity_stream_flags(OS_ACTIVITY_STREAM_PROCESS_ONLY),
137 m_filter_chain_sp(nullptr) {
138 // Parse out activity stream flags
139 if (!ParseSourceFlags(config)) {
144 // Parse filter rules
145 if (!ParseFilterRules(config)) {
150 // Everything worked.
154 bool ParseSourceFlags(const JSONObject &config) {
155 // Get the source-flags dictionary.
156 auto source_flags_sp = config.GetObject("source-flags");
157 if (!source_flags_sp)
159 if (!JSONObject::classof(source_flags_sp.get()))
162 const JSONObject &source_flags =
163 *static_cast<JSONObject *>(source_flags_sp.get());
165 // Parse out the flags.
166 bool include_any_process = false;
167 bool include_callstacks = false;
168 bool include_info_level = false;
169 bool include_debug_level = false;
170 bool live_stream = false;
172 if (!source_flags.GetObjectAsBool("any-process", include_any_process)) {
173 DNBLogThreadedIf(LOG_DARWIN_LOG, "Source-flag 'any-process' missing from "
177 if (!source_flags.GetObjectAsBool("callstacks", include_callstacks)) {
178 // We currently suppress the availability of this on the lldb
179 // side. We include here for devices when we enable in the
181 // DNBLogThreadedIf(LOG_DARWIN_LOG,
182 // "Source-flag 'callstacks' missing from "
183 // "configuration.");
185 // OK. We just skip callstacks.
188 if (!source_flags.GetObjectAsBool("info-level", include_info_level)) {
189 DNBLogThreadedIf(LOG_DARWIN_LOG, "Source-flag 'info-level' missing from "
193 if (!source_flags.GetObjectAsBool("debug-level", include_debug_level)) {
194 DNBLogThreadedIf(LOG_DARWIN_LOG, "Source-flag 'debug-level' missing from "
198 if (!source_flags.GetObjectAsBool("live-stream", live_stream)) {
199 DNBLogThreadedIf(LOG_DARWIN_LOG, "Source-flag 'live-stream' missing from "
204 // Setup the SPI flags based on this.
205 m_activity_stream_flags = 0;
206 if (!include_any_process)
207 m_activity_stream_flags |= OS_ACTIVITY_STREAM_PROCESS_ONLY;
208 if (include_callstacks)
209 m_activity_stream_flags |= OS_ACTIVITY_STREAM_CALLSTACK;
210 if (include_info_level)
211 m_activity_stream_flags |= OS_ACTIVITY_STREAM_INFO;
212 if (include_debug_level)
213 m_activity_stream_flags |= OS_ACTIVITY_STREAM_DEBUG;
215 m_activity_stream_flags |= OS_ACTIVITY_STREAM_BUFFERED;
217 DNBLogThreadedIf(LOG_DARWIN_LOG, "m_activity_stream_flags = 0x%03x",
218 m_activity_stream_flags);
223 bool ParseFilterRules(const JSONObject &config) {
224 // Retrieve the default rule.
225 bool filter_default_accept = true;
226 if (!config.GetObjectAsBool("filter-fall-through-accepts",
227 filter_default_accept)) {
228 DNBLogThreadedIf(LOG_DARWIN_LOG, "Setting 'filter-fall-through-accepts' "
229 "missing from configuration.");
232 m_filter_chain_sp.reset(new LogFilterChain(filter_default_accept));
233 DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLog no-match rule: %s.",
234 filter_default_accept ? "accept" : "reject");
236 // If we don't have the filter-rules array, we're done.
237 auto filter_rules_sp = config.GetObject("filter-rules");
238 if (!filter_rules_sp) {
239 DNBLogThreadedIf(LOG_DARWIN_LOG,
240 "No 'filter-rules' config element, all log "
241 "entries will use the no-match action (%s).",
242 filter_default_accept ? "accept" : "reject");
245 if (!JSONArray::classof(filter_rules_sp.get()))
247 const JSONArray &rules_config =
248 *static_cast<JSONArray *>(filter_rules_sp.get());
250 // Create the filters.
251 for (auto &rule_sp : rules_config.m_elements) {
252 if (!JSONObject::classof(rule_sp.get()))
254 const JSONObject &rule_config = *static_cast<JSONObject *>(rule_sp.get());
256 // Get whether this filter accepts or rejects.
257 bool filter_accepts = true;
258 if (!rule_config.GetObjectAsBool("accept", filter_accepts)) {
259 DNBLogThreadedIf(LOG_DARWIN_LOG, "Filter 'accept' element missing.");
263 // Grab the target log field attribute for the match.
264 std::string target_attribute;
265 if (!rule_config.GetObjectAsString("attribute", target_attribute)) {
266 DNBLogThreadedIf(LOG_DARWIN_LOG, "Filter 'attribute' element missing.");
269 auto target_enum = TargetStringToEnum(target_attribute);
270 if (target_enum == eFilterTargetInvalid) {
271 DNBLogThreadedIf(LOG_DARWIN_LOG, "Filter attribute '%s' unsupported.",
272 target_attribute.c_str());
276 // Handle operation-specific fields and filter creation.
277 std::string filter_type;
278 if (!rule_config.GetObjectAsString("type", filter_type)) {
279 DNBLogThreadedIf(LOG_DARWIN_LOG, "Filter 'type' element missing.");
282 DNBLogThreadedIf(LOG_DARWIN_LOG, "Reading filter of type '%s'",
283 filter_type.c_str());
285 LogFilterSP filter_sp;
286 if (filter_type == "regex") {
287 // Grab the regex for the match.
289 if (!rule_config.GetObjectAsString("regex", regex)) {
290 DNBLogError("Regex filter missing 'regex' element.");
293 DNBLogThreadedIf(LOG_DARWIN_LOG, "regex for filter: \"%s\"",
296 // Create the regex filter.
298 new LogFilterRegex(filter_accepts, target_enum, regex);
299 filter_sp.reset(regex_filter);
301 // Validate that the filter is okay.
302 if (!regex_filter->IsValid()) {
303 DNBLogError("Invalid regex in filter: "
304 "regex=\"%s\", error=%s",
305 regex.c_str(), regex_filter->GetErrorAsCString());
308 } else if (filter_type == "match") {
309 // Grab the regex for the match.
310 std::string exact_text;
311 if (!rule_config.GetObjectAsString("exact_text", exact_text)) {
312 DNBLogError("Exact match filter missing "
313 "'exact_text' element.");
317 // Create the filter.
319 new LogFilterExactMatch(filter_accepts, target_enum, exact_text));
322 // Add the filter to the chain.
323 m_filter_chain_sp->AppendFilter(filter_sp);
328 bool IsValid() const { return m_is_valid; }
330 os_activity_stream_flag_t GetActivityStreamFlags() const {
331 return m_activity_stream_flags;
334 const LogFilterChainSP &GetLogFilterChain() const {
335 return m_filter_chain_sp;
340 os_activity_stream_flag_t m_activity_stream_flags;
341 LogFilterChainSP m_filter_chain_sp;
345 bool DarwinLogCollector::IsSupported() {
346 // We're supported if we have successfully looked up the SPI entry points.
347 return LookupSPICalls();
350 bool DarwinLogCollector::StartCollectingForProcess(nub_process_t pid,
351 const JSONObject &config) {
352 // If we're currently collecting for this process, kill the existing
354 if (CancelStreamForProcess(pid)) {
355 DNBLogThreadedIf(LOG_DARWIN_LOG,
356 "%s() killed existing DarwinLog collector for pid %d.",
360 // If the process isn't alive, we're done.
361 if (!DNBProcessIsAlive(pid)) {
362 DNBLogThreadedIf(LOG_DARWIN_LOG,
363 "%s() cannot collect for pid %d: process not alive.",
368 // Validate the configuration.
369 auto spi_config = Configuration(config);
370 if (!spi_config.IsValid()) {
371 DNBLogThreadedIf(LOG_DARWIN_LOG,
372 "%s() invalid configuration, will not enable log "
378 // Create the stream collector that will manage collected data
380 DarwinLogCollectorSP collector_sp(
381 new DarwinLogCollector(pid, spi_config.GetLogFilterChain()));
382 std::weak_ptr<DarwinLogCollector> collector_wp(collector_sp);
384 // Setup the stream handling block.
385 os_activity_stream_block_t block =
386 ^bool(os_activity_stream_entry_t entry, int error) {
387 // Check if our collector is still alive.
388 DarwinLogCollectorSP inner_collector_sp = collector_wp.lock();
389 if (!inner_collector_sp)
391 return inner_collector_sp->HandleStreamEntry(entry, error);
394 os_activity_stream_event_block_t stream_event_block = ^void(
395 os_activity_stream_t stream, os_activity_stream_event_t event) {
397 case OS_ACTIVITY_STREAM_EVENT_STARTED:
398 DNBLogThreadedIf(LOG_DARWIN_LOG,
399 "received stream event: "
400 "OS_ACTIVITY_STREAM_EVENT_STARTED, stream %p.",
403 case OS_ACTIVITY_STREAM_EVENT_STOPPED:
404 DNBLogThreadedIf(LOG_DARWIN_LOG,
405 "received stream event: "
406 "OS_ACTIVITY_STREAM_EVENT_STOPPED, stream %p.",
409 case OS_ACTIVITY_STREAM_EVENT_FAILED:
410 DNBLogThreadedIf(LOG_DARWIN_LOG,
411 "received stream event: "
412 "OS_ACTIVITY_STREAM_EVENT_FAILED, stream %p.",
415 case OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED:
416 DNBLogThreadedIf(LOG_DARWIN_LOG,
417 "received stream event: "
418 "OS_ACTIVITY_STREAM_EVENT_CHUNK_STARTED, stream %p.",
421 case OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED:
422 DNBLogThreadedIf(LOG_DARWIN_LOG,
423 "received stream event: "
424 "OS_ACTIVITY_STREAM_EVENT_CHUNK_FINISHED, stream %p.",
430 // Create the stream.
431 os_activity_stream_t activity_stream = (*s_os_activity_stream_for_pid)(
432 pid, spi_config.GetActivityStreamFlags(), block);
433 collector_sp->SetActivityStream(activity_stream);
435 // Specify the stream-related event handler.
436 (*s_os_activity_stream_set_event_handler)(activity_stream,
440 (*s_os_activity_stream_resume)(activity_stream);
442 TrackCollector(collector_sp);
447 DarwinLogCollector::GetEventsForProcess(nub_process_t pid) {
448 auto collector_sp = FindCollectorForProcess(pid);
450 // We're not tracking a stream for this process.
451 return DarwinLogEventVector();
454 return collector_sp->RemoveEvents();
457 bool DarwinLogCollector::CancelStreamForProcess(nub_process_t pid) {
458 auto collector_sp = FindCollectorForProcess(pid);
460 // We're not tracking a stream for this process.
464 collector_sp->CancelActivityStream();
465 StopTrackingCollector(collector_sp);
471 DarwinLogCollector::GetActivityForID(os_activity_id_t activity_id) const {
472 auto find_it = m_activity_map.find(activity_id);
473 return (find_it != m_activity_map.end()) ? find_it->second.m_name.c_str()
477 /// Retrieve the full parent-child chain for activity names. These
478 /// can be arbitrarily deep. This method assumes the caller has already
479 /// locked the activity mutex.
480 void DarwinLogCollector::GetActivityChainForID_internal(
481 os_activity_id_t activity_id, std::string &result, size_t depth) const {
482 if (depth > MAX_ACTIVITY_CHAIN_DEPTH) {
483 // Terminating condition - too deeply nested.
485 } else if (activity_id == 0) {
486 // Terminating condition - no activity.
490 auto find_it = m_activity_map.find(activity_id);
491 if (find_it == m_activity_map.end()) {
492 // Terminating condition - no data for activity_id.
496 // Activity name becomes parent activity name chain + ':' + our activity
498 GetActivityChainForID_internal(find_it->second.m_parent_id, result,
502 result += find_it->second.m_name;
506 DarwinLogCollector::GetActivityChainForID(os_activity_id_t activity_id) const {
509 std::lock_guard<std::mutex> locker(m_activity_info_mutex);
510 GetActivityChainForID_internal(activity_id, result, 1);
515 DarwinLogCollector::DarwinLogCollector(nub_process_t pid,
516 const LogFilterChainSP &filter_chain_sp)
517 : ActivityStore(), m_pid(pid), m_activity_stream(0), m_events(),
518 m_events_mutex(), m_filter_chain_sp(filter_chain_sp),
519 m_activity_info_mutex(), m_activity_map() {}
521 DarwinLogCollector::~DarwinLogCollector() {
522 // Cancel the stream.
523 if (m_activity_stream) {
524 DNBLogThreadedIf(LOG_DARWIN_LOG, "tearing down activity stream "
527 (*s_os_activity_stream_cancel)(m_activity_stream);
528 m_activity_stream = 0;
530 DNBLogThreadedIf(LOG_DARWIN_LOG, "no stream to tear down for %d", m_pid);
534 void DarwinLogCollector::SignalDataAvailable() {
535 RNBRemoteSP remoteSP(g_remoteSP);
537 // We're done. This is unexpected.
538 StopTrackingCollector(shared_from_this());
542 RNBContext &ctx = remoteSP->Context();
543 ctx.Events().SetEvents(RNBContext::event_darwin_log_data_available);
544 // Wait for the main thread to consume this notification if it requested
546 ctx.Events().WaitForResetAck(RNBContext::event_darwin_log_data_available);
549 void DarwinLogCollector::SetActivityStream(
550 os_activity_stream_t activity_stream) {
551 m_activity_stream = activity_stream;
554 bool DarwinLogCollector::HandleStreamEntry(os_activity_stream_entry_t entry,
556 if ((error == 0) && (entry != nullptr)) {
557 if (entry->pid != m_pid) {
558 // For now, skip messages not originating from our process.
559 // Later we might want to keep all messages related to an event
560 // that we're tracking, even when it came from another process,
561 // possibly doing work on our behalf.
565 switch (entry->type) {
566 case OS_ACTIVITY_STREAM_TYPE_ACTIVITY_CREATE:
568 LOG_DARWIN_LOG, "received activity create: "
569 "%s, creator aid %" PRIu64 ", unique_pid %" PRIu64
570 "(activity id=%" PRIu64 ", parent id=%" PRIu64 ")",
571 entry->activity_create.name, entry->activity_create.creator_aid,
572 entry->activity_create.unique_pid, entry->activity_id,
575 std::lock_guard<std::mutex> locker(m_activity_info_mutex);
576 m_activity_map.insert(
577 std::make_pair(entry->activity_id,
578 ActivityInfo(entry->activity_create.name,
579 entry->activity_id, entry->parent_id)));
583 case OS_ACTIVITY_STREAM_TYPE_ACTIVITY_TRANSITION:
585 LOG_DARWIN_LOG, "received activity transition:"
586 "new aid: %" PRIu64 "(activity id=%" PRIu64
587 ", parent id=%" PRIu64 ", tid %" PRIu64 ")",
588 entry->activity_transition.transition_id, entry->activity_id,
589 entry->parent_id, entry->activity_transition.thread);
592 case OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE: {
594 LOG_DARWIN_LOG, "received log message: "
595 "(activity id=%" PRIu64 ", parent id=%" PRIu64 ", "
596 "tid %" PRIu64 "): format %s",
597 entry->activity_id, entry->parent_id, entry->log_message.thread,
598 entry->log_message.format ? entry->log_message.format
599 : "<invalid-format>");
601 // Do the real work here.
603 // Ensure our process is still alive. If not, we can
604 // cancel the collection.
605 if (!DNBProcessIsAlive(m_pid)) {
606 // We're outta here. This is the manner in which we
607 // stop collecting for a process.
608 StopTrackingCollector(shared_from_this());
612 LogMessageOsLog os_log_message(*this, *entry);
613 if (!m_filter_chain_sp ||
614 !m_filter_chain_sp->GetAcceptMessage(os_log_message)) {
615 // This log message was rejected by the filter,
616 // so stop processing it now.
620 // Copy over the relevant bits from the message.
621 const struct os_log_message_s &log_message = entry->log_message;
623 DarwinLogEventSP message_sp(new DarwinLogEvent());
624 // Indicate this event is a log message event.
625 message_sp->AddStringItem("type", "log");
627 // Add the message contents (fully expanded).
628 // Consider expanding on the remote side.
629 // Then we don't pay for expansion until when it is
631 const char *message_text = os_log_message.GetMessage();
633 message_sp->AddStringItem("message", message_text);
635 // Add some useful data fields.
636 message_sp->AddIntegerItem("timestamp", log_message.timestamp);
638 // Do we want to do all activity name resolution on this
639 // side? Maybe. For now, send IDs and ID->name mappings
640 // and fix this up on that side. Later, when we add
641 // debugserver-side filtering, we'll want to get the
642 // activity names over here, so we should probably
643 // just send them as resolved strings.
644 message_sp->AddIntegerItem("activity_id", entry->activity_id);
645 message_sp->AddIntegerItem("parent_id", entry->parent_id);
646 message_sp->AddIntegerItem("thread_id", log_message.thread);
647 if (log_message.subsystem && strlen(log_message.subsystem) > 0)
648 message_sp->AddStringItem("subsystem", log_message.subsystem);
649 if (log_message.category && strlen(log_message.category) > 0)
650 message_sp->AddStringItem("category", log_message.category);
651 if (entry->activity_id != 0) {
652 std::string activity_chain =
653 GetActivityChainForID(entry->activity_id);
654 if (!activity_chain.empty())
655 message_sp->AddStringItem("activity-chain", activity_chain);
658 // Add it to the list for later collection.
660 std::lock_guard<std::mutex> locker(m_events_mutex);
661 m_events.push_back(message_sp);
663 SignalDataAvailable();
669 DNBLogThreadedIf(LOG_DARWIN_LOG, "HandleStreamEntry: final call, "
676 DarwinLogEventVector DarwinLogCollector::RemoveEvents() {
677 DarwinLogEventVector returned_events;
679 std::lock_guard<std::mutex> locker(m_events_mutex);
680 returned_events.swap(m_events);
682 DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLogCollector::%s(): removing %lu "
683 "queued log entries",
684 __FUNCTION__, returned_events.size());
685 return returned_events;
688 void DarwinLogCollector::CancelActivityStream() {
689 if (!m_activity_stream)
692 DNBLogThreadedIf(LOG_DARWIN_LOG, "DarwinLogCollector::%s(): canceling "
693 "activity stream %p",
694 __FUNCTION__, m_activity_stream);
695 (*s_os_activity_stream_cancel)(m_activity_stream);
696 m_activity_stream = nullptr;