| 1 | //===-- Genealogy.cpp -------------------------------------------*- C++ -*-===// |
| 2 | // |
| 3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| 4 | // See https://llvm.org/LICENSE.txt for license information. |
| 5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| 6 | // |
| 7 | //===----------------------------------------------------------------------===// |
| 8 | |
| 9 | #include <Availability.h> |
| 10 | #include <dlfcn.h> |
| 11 | #include <string> |
| 12 | #include <uuid/uuid.h> |
| 13 | |
| 14 | #include "DNBDefs.h" |
| 15 | #include "Genealogy.h" |
| 16 | #include "GenealogySPI.h" |
| 17 | #include "MachThreadList.h" |
| 18 | |
| 19 | /// Constructor |
| 20 | |
| 21 | Genealogy::Genealogy() |
| 22 | : m_os_activity_diagnostic_for_pid(nullptr), |
| 23 | m_os_activity_iterate_processes(nullptr), |
| 24 | m_os_activity_iterate_breadcrumbs(nullptr), |
| 25 | m_os_activity_iterate_messages(nullptr), |
| 26 | m_os_activity_iterate_activities(nullptr), m_os_trace_get_type(nullptr), |
| 27 | m_os_trace_copy_formatted_message(nullptr), |
| 28 | m_os_activity_for_thread(nullptr), m_os_activity_for_task_thread(nullptr), |
| 29 | m_thread_activities(), m_process_executable_infos(), |
| 30 | m_diagnosticd_call_timed_out(false) { |
| 31 | m_os_activity_diagnostic_for_pid = |
| 32 | (bool (*)(pid_t, os_activity_t, uint32_t, os_diagnostic_block_t))dlsym( |
| 33 | RTLD_DEFAULT, name: "os_activity_diagnostic_for_pid" ); |
| 34 | m_os_activity_iterate_processes = |
| 35 | (void (*)(os_activity_process_list_t, bool (^)(os_activity_process_t))) |
| 36 | dlsym(RTLD_DEFAULT, name: "os_activity_iterate_processes" ); |
| 37 | m_os_activity_iterate_breadcrumbs = |
| 38 | (void (*)(os_activity_process_t, bool (^)(os_activity_breadcrumb_t))) |
| 39 | dlsym(RTLD_DEFAULT, name: "os_activity_iterate_breadcrumbs" ); |
| 40 | m_os_activity_iterate_messages = (void (*)( |
| 41 | os_trace_message_list_t, os_activity_process_t, |
| 42 | bool (^)(os_trace_message_t)))dlsym(RTLD_DEFAULT, |
| 43 | name: "os_activity_iterate_messages" ); |
| 44 | m_os_activity_iterate_activities = (void (*)( |
| 45 | os_activity_list_t, os_activity_process_t, |
| 46 | bool (^)(os_activity_entry_t)))dlsym(RTLD_DEFAULT, |
| 47 | name: "os_activity_iterate_activities" ); |
| 48 | m_os_trace_get_type = |
| 49 | (uint8_t(*)(os_trace_message_t))dlsym(RTLD_DEFAULT, name: "os_trace_get_type" ); |
| 50 | m_os_trace_copy_formatted_message = (char *(*)(os_trace_message_t))dlsym( |
| 51 | RTLD_DEFAULT, name: "os_trace_copy_formatted_message" ); |
| 52 | m_os_activity_for_thread = |
| 53 | (os_activity_t(*)(os_activity_process_t, uint64_t))dlsym( |
| 54 | RTLD_DEFAULT, name: "os_activity_for_thread" ); |
| 55 | m_os_activity_for_task_thread = (os_activity_t(*)(task_t, uint64_t))dlsym( |
| 56 | RTLD_DEFAULT, "os_activity_for_task_thread" ); |
| 57 | m_os_activity_messages_for_thread = (os_trace_message_list_t(*)( |
| 58 | os_activity_process_t process, os_activity_t activity, |
| 59 | uint64_t thread_id))dlsym(RTLD_DEFAULT, |
| 60 | name: "os_activity_messages_for_thread" ); |
| 61 | } |
| 62 | |
| 63 | Genealogy::ThreadActivitySP |
| 64 | Genealogy::GetGenealogyInfoForThread(pid_t pid, nub_thread_t tid, |
| 65 | const MachThreadList &thread_list, |
| 66 | task_t task, bool &timed_out) { |
| 67 | ThreadActivitySP activity; |
| 68 | // |
| 69 | // if we've timed out trying to get the activities, don't try again at this |
| 70 | // process stop. |
| 71 | // (else we'll need to hit the timeout for every thread we're asked about.) |
| 72 | // We'll try again at the next public stop. |
| 73 | |
| 74 | if (m_thread_activities.size() == 0 && !m_diagnosticd_call_timed_out) { |
| 75 | GetActivities(pid, thread_list, task); |
| 76 | } |
| 77 | std::map<nub_thread_t, ThreadActivitySP>::const_iterator search; |
| 78 | search = m_thread_activities.find(tid); |
| 79 | if (search != m_thread_activities.end()) { |
| 80 | activity = search->second; |
| 81 | } |
| 82 | timed_out = m_diagnosticd_call_timed_out; |
| 83 | return activity; |
| 84 | } |
| 85 | |
| 86 | void Genealogy::Clear() { |
| 87 | m_thread_activities.clear(); |
| 88 | m_diagnosticd_call_timed_out = false; |
| 89 | } |
| 90 | |
| 91 | void Genealogy::GetActivities(pid_t pid, const MachThreadList &thread_list, |
| 92 | task_t task) { |
| 93 | if (m_os_activity_diagnostic_for_pid != nullptr && |
| 94 | m_os_activity_iterate_processes != nullptr && |
| 95 | m_os_activity_iterate_breadcrumbs != nullptr && |
| 96 | m_os_activity_iterate_messages != nullptr && |
| 97 | m_os_activity_iterate_activities != nullptr && |
| 98 | m_os_trace_get_type != nullptr && |
| 99 | m_os_trace_copy_formatted_message != nullptr && |
| 100 | (m_os_activity_for_thread != nullptr || |
| 101 | m_os_activity_for_task_thread != nullptr)) { |
| 102 | __block dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); |
| 103 | __block BreadcrumbList breadcrumbs; |
| 104 | __block ActivityList activities; |
| 105 | __block MessageList messages; |
| 106 | __block std::map<nub_thread_t, uint64_t> thread_activity_mapping; |
| 107 | |
| 108 | os_activity_diagnostic_flag_t flags = |
| 109 | OS_ACTIVITY_DIAGNOSTIC_ALL_ACTIVITIES | |
| 110 | OS_ACTIVITY_DIAGNOSTIC_PROCESS_ONLY; |
| 111 | if (m_os_activity_diagnostic_for_pid( |
| 112 | pid, 0, flags, ^(os_activity_process_list_t processes, int error) { |
| 113 | if (error == 0) { |
| 114 | m_os_activity_iterate_processes(processes, ^bool( |
| 115 | os_activity_process_t |
| 116 | process_info) { |
| 117 | if (pid == process_info->pid) { |
| 118 | // Collect all the Breadcrumbs |
| 119 | m_os_activity_iterate_breadcrumbs( |
| 120 | process_info, |
| 121 | ^bool(os_activity_breadcrumb_t breadcrumb) { |
| 122 | Breadcrumb bc; |
| 123 | bc.breadcrumb_id = breadcrumb->breadcrumb_id; |
| 124 | bc.activity_id = breadcrumb->activity_id; |
| 125 | bc.timestamp = breadcrumb->timestamp; |
| 126 | if (breadcrumb->name) |
| 127 | bc.name = breadcrumb->name; |
| 128 | breadcrumbs.push_back(bc); |
| 129 | return true; |
| 130 | }); |
| 131 | |
| 132 | // Collect all the Activities |
| 133 | m_os_activity_iterate_activities( |
| 134 | process_info->activities, process_info, |
| 135 | ^bool(os_activity_entry_t activity) { |
| 136 | Activity ac; |
| 137 | ac.activity_start = activity->activity_start; |
| 138 | ac.activity_id = activity->activity_id; |
| 139 | ac.parent_id = activity->parent_id; |
| 140 | if (activity->activity_name) |
| 141 | ac.activity_name = activity->activity_name; |
| 142 | if (activity->reason) |
| 143 | ac.reason = activity->reason; |
| 144 | activities.push_back(ac); |
| 145 | return true; |
| 146 | }); |
| 147 | |
| 148 | // Collect all the Messages -- messages not associated with |
| 149 | // any thread |
| 150 | m_os_activity_iterate_messages( |
| 151 | process_info->messages, process_info, |
| 152 | ^bool(os_trace_message_t trace_msg) { |
| 153 | Message msg; |
| 154 | msg.timestamp = trace_msg->timestamp; |
| 155 | msg.trace_id = trace_msg->trace_id; |
| 156 | msg.thread = trace_msg->thread; |
| 157 | msg.type = m_os_trace_get_type(trace_msg); |
| 158 | msg.activity_id = 0; |
| 159 | if (trace_msg->image_uuid && trace_msg->image_path) { |
| 160 | ProcessExecutableInfoSP process_info_sp( |
| 161 | new ProcessExecutableInfo()); |
| 162 | uuid_copy(process_info_sp->image_uuid, |
| 163 | trace_msg->image_uuid); |
| 164 | process_info_sp->image_path = trace_msg->image_path; |
| 165 | msg.process_info_index = |
| 166 | AddProcessExecutableInfo(process_exe_info: process_info_sp); |
| 167 | } |
| 168 | const char *message_text = |
| 169 | m_os_trace_copy_formatted_message(trace_msg); |
| 170 | if (message_text) |
| 171 | msg.message = message_text; |
| 172 | messages.push_back(msg); |
| 173 | return true; |
| 174 | }); |
| 175 | |
| 176 | // Discover which activities are said to be running on |
| 177 | // threads currently |
| 178 | const nub_size_t num_threads = thread_list.NumThreads(); |
| 179 | for (nub_size_t i = 0; i < num_threads; ++i) { |
| 180 | nub_thread_t thread_id = thread_list.ThreadIDAtIndex(i); |
| 181 | os_activity_t act = 0; |
| 182 | if (m_os_activity_for_task_thread != nullptr) { |
| 183 | act = m_os_activity_for_task_thread(task, thread_id); |
| 184 | } else if (m_os_activity_for_thread != nullptr) { |
| 185 | act = m_os_activity_for_thread(process_info, thread_id); |
| 186 | } |
| 187 | if (act != 0) |
| 188 | thread_activity_mapping[thread_id] = act; |
| 189 | } |
| 190 | |
| 191 | // Collect all Messages -- messages associated with a thread |
| 192 | |
| 193 | // When there's no genealogy information, an early version |
| 194 | // of os_activity_messages_for_thread |
| 195 | // can crash in rare circumstances. Check to see if this |
| 196 | // process has any activities before |
| 197 | // making the call to get messages. |
| 198 | if (process_info->activities != nullptr && |
| 199 | thread_activity_mapping.size() > 0) { |
| 200 | std::map<nub_thread_t, uint64_t>::const_iterator iter; |
| 201 | for (iter = thread_activity_mapping.begin(); |
| 202 | iter != thread_activity_mapping.end(); ++iter) { |
| 203 | nub_thread_t thread_id = iter->first; |
| 204 | os_activity_t act = iter->second; |
| 205 | os_trace_message_list_t this_thread_messages = |
| 206 | m_os_activity_messages_for_thread(process_info, act, |
| 207 | thread_id); |
| 208 | m_os_activity_iterate_messages( |
| 209 | this_thread_messages, process_info, |
| 210 | ^bool(os_trace_message_t trace_msg) { |
| 211 | Message msg; |
| 212 | msg.timestamp = trace_msg->timestamp; |
| 213 | msg.trace_id = trace_msg->trace_id; |
| 214 | msg.thread = trace_msg->thread; |
| 215 | msg.type = m_os_trace_get_type(trace_msg); |
| 216 | msg.activity_id = act; |
| 217 | if (trace_msg->image_uuid && |
| 218 | trace_msg->image_path) { |
| 219 | ProcessExecutableInfoSP process_info_sp( |
| 220 | new ProcessExecutableInfo()); |
| 221 | uuid_copy(process_info_sp->image_uuid, |
| 222 | trace_msg->image_uuid); |
| 223 | process_info_sp->image_path = |
| 224 | trace_msg->image_path; |
| 225 | msg.process_info_index = |
| 226 | AddProcessExecutableInfo(process_info_sp); |
| 227 | } |
| 228 | const char *message_text = |
| 229 | m_os_trace_copy_formatted_message(trace_msg); |
| 230 | if (message_text) |
| 231 | msg.message = message_text; |
| 232 | messages.push_back(msg); |
| 233 | return true; |
| 234 | }); |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | return true; |
| 239 | }); |
| 240 | } |
| 241 | dispatch_semaphore_signal(semaphore); |
| 242 | }) == true) { |
| 243 | // Wait for the diagnosticd xpc calls to all finish up -- or half a second |
| 244 | // to elapse. |
| 245 | dispatch_time_t timeout = |
| 246 | dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 2); |
| 247 | bool success = dispatch_semaphore_wait(semaphore, timeout) == 0; |
| 248 | if (!success) { |
| 249 | m_diagnosticd_call_timed_out = true; |
| 250 | return; |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | // breadcrumbs, activities, and messages have all now been filled in. |
| 255 | |
| 256 | std::map<nub_thread_t, uint64_t>::const_iterator iter; |
| 257 | for (iter = thread_activity_mapping.begin(); |
| 258 | iter != thread_activity_mapping.end(); ++iter) { |
| 259 | nub_thread_t thread_id = iter->first; |
| 260 | uint64_t activity_id = iter->second; |
| 261 | ActivityList::const_iterator activity_search; |
| 262 | for (activity_search = activities.begin(); |
| 263 | activity_search != activities.end(); ++activity_search) { |
| 264 | if (activity_search->activity_id == activity_id) { |
| 265 | ThreadActivitySP thread_activity_sp(new ThreadActivity()); |
| 266 | thread_activity_sp->current_activity = *activity_search; |
| 267 | |
| 268 | BreadcrumbList::const_iterator breadcrumb_search; |
| 269 | for (breadcrumb_search = breadcrumbs.begin(); |
| 270 | breadcrumb_search != breadcrumbs.end(); ++breadcrumb_search) { |
| 271 | if (breadcrumb_search->activity_id == activity_id) { |
| 272 | thread_activity_sp->breadcrumbs.push_back(*breadcrumb_search); |
| 273 | } |
| 274 | } |
| 275 | MessageList::const_iterator message_search; |
| 276 | for (message_search = messages.begin(); |
| 277 | message_search != messages.end(); ++message_search) { |
| 278 | if (message_search->thread == thread_id) { |
| 279 | thread_activity_sp->messages.push_back(*message_search); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | m_thread_activities[thread_id] = thread_activity_sp; |
| 284 | break; |
| 285 | } |
| 286 | } |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | uint32_t |
| 292 | Genealogy::AddProcessExecutableInfo(ProcessExecutableInfoSP process_exe_info) { |
| 293 | const uint32_t info_size = |
| 294 | static_cast<uint32_t>(m_process_executable_infos.size()); |
| 295 | for (uint32_t idx = 0; idx < info_size; ++idx) { |
| 296 | if (uuid_compare(m_process_executable_infos[idx]->image_uuid, |
| 297 | process_exe_info->image_uuid) == 0) { |
| 298 | return idx + 1; |
| 299 | } |
| 300 | } |
| 301 | m_process_executable_infos.push_back(x: process_exe_info); |
| 302 | return info_size + 1; |
| 303 | } |
| 304 | |
| 305 | Genealogy::ProcessExecutableInfoSP |
| 306 | Genealogy::GetProcessExecutableInfosAtIndex(size_t idx) { |
| 307 | ProcessExecutableInfoSP info_sp; |
| 308 | if (idx > 0) { |
| 309 | idx--; |
| 310 | if (idx <= m_process_executable_infos.size()) { |
| 311 | info_sp = m_process_executable_infos[idx]; |
| 312 | } |
| 313 | } |
| 314 | return info_sp; |
| 315 | } |
| 316 | |