//===-- Genealogy.cpp -------------------------------------------*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include #include #include #include #include "DNBDefs.h" #include "Genealogy.h" #include "GenealogySPI.h" #include "MachThreadList.h" /// Constructor Genealogy::Genealogy() : m_os_activity_diagnostic_for_pid(nullptr), m_os_activity_iterate_processes(nullptr), m_os_activity_iterate_breadcrumbs(nullptr), m_os_activity_iterate_messages(nullptr), m_os_activity_iterate_activities(nullptr), m_os_trace_get_type(nullptr), m_os_trace_copy_formatted_message(nullptr), m_os_activity_for_thread(nullptr), m_os_activity_for_task_thread(nullptr), m_thread_activities(), m_process_executable_infos(), m_diagnosticd_call_timed_out(false) { m_os_activity_diagnostic_for_pid = (bool (*)(pid_t, os_activity_t, uint32_t, os_diagnostic_block_t))dlsym( RTLD_DEFAULT, "os_activity_diagnostic_for_pid"); m_os_activity_iterate_processes = (void (*)(os_activity_process_list_t, bool (^)(os_activity_process_t))) dlsym(RTLD_DEFAULT, "os_activity_iterate_processes"); m_os_activity_iterate_breadcrumbs = (void (*)(os_activity_process_t, bool (^)(os_activity_breadcrumb_t))) dlsym(RTLD_DEFAULT, "os_activity_iterate_breadcrumbs"); m_os_activity_iterate_messages = (void (*)( os_trace_message_list_t, os_activity_process_t, bool (^)(os_trace_message_t)))dlsym(RTLD_DEFAULT, "os_activity_iterate_messages"); m_os_activity_iterate_activities = (void (*)( os_activity_list_t, os_activity_process_t, bool (^)(os_activity_entry_t)))dlsym(RTLD_DEFAULT, "os_activity_iterate_activities"); m_os_trace_get_type = (uint8_t(*)(os_trace_message_t))dlsym(RTLD_DEFAULT, "os_trace_get_type"); m_os_trace_copy_formatted_message = (char *(*)(os_trace_message_t))dlsym( RTLD_DEFAULT, "os_trace_copy_formatted_message"); m_os_activity_for_thread = (os_activity_t(*)(os_activity_process_t, uint64_t))dlsym( RTLD_DEFAULT, "os_activity_for_thread"); m_os_activity_for_task_thread = (os_activity_t(*)(task_t, uint64_t))dlsym( RTLD_DEFAULT, "os_activity_for_task_thread"); m_os_activity_messages_for_thread = (os_trace_message_list_t(*)( os_activity_process_t process, os_activity_t activity, uint64_t thread_id))dlsym(RTLD_DEFAULT, "os_activity_messages_for_thread"); } Genealogy::ThreadActivitySP Genealogy::GetGenealogyInfoForThread(pid_t pid, nub_thread_t tid, const MachThreadList &thread_list, task_t task, bool &timed_out) { ThreadActivitySP activity; // // if we've timed out trying to get the activities, don't try again at this // process stop. // (else we'll need to hit the timeout for every thread we're asked about.) // We'll try again at the next public stop. if (m_thread_activities.size() == 0 && !m_diagnosticd_call_timed_out) { GetActivities(pid, thread_list, task); } std::map::const_iterator search; search = m_thread_activities.find(tid); if (search != m_thread_activities.end()) { activity = search->second; } timed_out = m_diagnosticd_call_timed_out; return activity; } void Genealogy::Clear() { m_thread_activities.clear(); m_diagnosticd_call_timed_out = false; } void Genealogy::GetActivities(pid_t pid, const MachThreadList &thread_list, task_t task) { if (m_os_activity_diagnostic_for_pid != nullptr && m_os_activity_iterate_processes != nullptr && m_os_activity_iterate_breadcrumbs != nullptr && m_os_activity_iterate_messages != nullptr && m_os_activity_iterate_activities != nullptr && m_os_trace_get_type != nullptr && m_os_trace_copy_formatted_message != nullptr && (m_os_activity_for_thread != nullptr || m_os_activity_for_task_thread != nullptr)) { __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); __block BreadcrumbList breadcrumbs; __block ActivityList activities; __block MessageList messages; __block std::map thread_activity_mapping; os_activity_diagnostic_flag_t flags = OS_ACTIVITY_DIAGNOSTIC_ALL_ACTIVITIES | OS_ACTIVITY_DIAGNOSTIC_PROCESS_ONLY; if (m_os_activity_diagnostic_for_pid( pid, 0, flags, ^(os_activity_process_list_t processes, int error) { if (error == 0) { m_os_activity_iterate_processes(processes, ^bool( os_activity_process_t process_info) { if (pid == process_info->pid) { // Collect all the Breadcrumbs m_os_activity_iterate_breadcrumbs( process_info, ^bool(os_activity_breadcrumb_t breadcrumb) { Breadcrumb bc; bc.breadcrumb_id = breadcrumb->breadcrumb_id; bc.activity_id = breadcrumb->activity_id; bc.timestamp = breadcrumb->timestamp; if (breadcrumb->name) bc.name = breadcrumb->name; breadcrumbs.push_back(bc); return true; }); // Collect all the Activites m_os_activity_iterate_activities( process_info->activities, process_info, ^bool(os_activity_entry_t activity) { Activity ac; ac.activity_start = activity->activity_start; ac.activity_id = activity->activity_id; ac.parent_id = activity->parent_id; if (activity->activity_name) ac.activity_name = activity->activity_name; if (activity->reason) ac.reason = activity->reason; activities.push_back(ac); return true; }); // Collect all the Messages -- messages not associated with // any thread m_os_activity_iterate_messages( process_info->messages, process_info, ^bool(os_trace_message_t trace_msg) { Message msg; msg.timestamp = trace_msg->timestamp; msg.trace_id = trace_msg->trace_id; msg.thread = trace_msg->thread; msg.type = m_os_trace_get_type(trace_msg); msg.activity_id = 0; if (trace_msg->image_uuid && trace_msg->image_path) { ProcessExecutableInfoSP process_info_sp( new ProcessExecutableInfo()); uuid_copy(process_info_sp->image_uuid, trace_msg->image_uuid); process_info_sp->image_path = trace_msg->image_path; msg.process_info_index = AddProcessExecutableInfo(process_info_sp); } const char *message_text = m_os_trace_copy_formatted_message(trace_msg); if (message_text) msg.message = message_text; messages.push_back(msg); return true; }); // Discover which activities are said to be running on // threads currently const nub_size_t num_threads = thread_list.NumThreads(); for (nub_size_t i = 0; i < num_threads; ++i) { nub_thread_t thread_id = thread_list.ThreadIDAtIndex(i); os_activity_t act = 0; if (m_os_activity_for_task_thread != nullptr) { act = m_os_activity_for_task_thread(task, thread_id); } else if (m_os_activity_for_thread != nullptr) { act = m_os_activity_for_thread(process_info, thread_id); } if (act != 0) thread_activity_mapping[thread_id] = act; } // Collect all Messages -- messages associated with a thread // When there's no genealogy information, an early version // of os_activity_messages_for_thread // can crash in rare circumstances. Check to see if this // process has any activities before // making the call to get messages. if (process_info->activities != nullptr && thread_activity_mapping.size() > 0) { std::map::const_iterator iter; for (iter = thread_activity_mapping.begin(); iter != thread_activity_mapping.end(); ++iter) { nub_thread_t thread_id = iter->first; os_activity_t act = iter->second; os_trace_message_list_t this_thread_messages = m_os_activity_messages_for_thread(process_info, act, thread_id); m_os_activity_iterate_messages( this_thread_messages, process_info, ^bool(os_trace_message_t trace_msg) { Message msg; msg.timestamp = trace_msg->timestamp; msg.trace_id = trace_msg->trace_id; msg.thread = trace_msg->thread; msg.type = m_os_trace_get_type(trace_msg); msg.activity_id = act; if (trace_msg->image_uuid && trace_msg->image_path) { ProcessExecutableInfoSP process_info_sp( new ProcessExecutableInfo()); uuid_copy(process_info_sp->image_uuid, trace_msg->image_uuid); process_info_sp->image_path = trace_msg->image_path; msg.process_info_index = AddProcessExecutableInfo(process_info_sp); } const char *message_text = m_os_trace_copy_formatted_message(trace_msg); if (message_text) msg.message = message_text; messages.push_back(msg); return true; }); } } } return true; }); } dispatch_semaphore_signal(semaphore); }) == true) { // Wait for the diagnosticd xpc calls to all finish up -- or half a second // to elapse. dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2); bool success = dispatch_semaphore_wait(semaphore, timeout) == 0; if (!success) { m_diagnosticd_call_timed_out = true; return; } } // breadcrumbs, activities, and messages have all now been filled in. std::map::const_iterator iter; for (iter = thread_activity_mapping.begin(); iter != thread_activity_mapping.end(); ++iter) { nub_thread_t thread_id = iter->first; uint64_t activity_id = iter->second; ActivityList::const_iterator activity_search; for (activity_search = activities.begin(); activity_search != activities.end(); ++activity_search) { if (activity_search->activity_id == activity_id) { ThreadActivitySP thread_activity_sp(new ThreadActivity()); thread_activity_sp->current_activity = *activity_search; BreadcrumbList::const_iterator breadcrumb_search; for (breadcrumb_search = breadcrumbs.begin(); breadcrumb_search != breadcrumbs.end(); ++breadcrumb_search) { if (breadcrumb_search->activity_id == activity_id) { thread_activity_sp->breadcrumbs.push_back(*breadcrumb_search); } } MessageList::const_iterator message_search; for (message_search = messages.begin(); message_search != messages.end(); ++message_search) { if (message_search->thread == thread_id) { thread_activity_sp->messages.push_back(*message_search); } } m_thread_activities[thread_id] = thread_activity_sp; break; } } } } } uint32_t Genealogy::AddProcessExecutableInfo(ProcessExecutableInfoSP process_exe_info) { const uint32_t info_size = static_cast(m_process_executable_infos.size()); for (uint32_t idx = 0; idx < info_size; ++idx) { if (uuid_compare(m_process_executable_infos[idx]->image_uuid, process_exe_info->image_uuid) == 0) { return idx + 1; } } m_process_executable_infos.push_back(process_exe_info); return info_size + 1; } Genealogy::ProcessExecutableInfoSP Genealogy::GetProcessExecutableInfosAtIndex(size_t idx) { ProcessExecutableInfoSP info_sp; if (idx > 0) { idx--; if (idx <= m_process_executable_infos.size()) { info_sp = m_process_executable_infos[idx]; } } return info_sp; }