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 | |