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 
Genealogy()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, "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, "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, "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                                           "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                                            "os_activity_iterate_activities");
48   m_os_trace_get_type =
49       (uint8_t(*)(os_trace_message_t))dlsym(RTLD_DEFAULT, "os_trace_get_type");
50   m_os_trace_copy_formatted_message = (char *(*)(os_trace_message_t))dlsym(
51       RTLD_DEFAULT, "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, "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                                 "os_activity_messages_for_thread");
61 }
62 
63 Genealogy::ThreadActivitySP
GetGenealogyInfoForThread(pid_t pid,nub_thread_t tid,const MachThreadList & thread_list,task_t task,bool & timed_out)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 
Clear()86 void Genealogy::Clear() {
87   m_thread_activities.clear();
88   m_diagnosticd_call_timed_out = false;
89 }
90 
GetActivities(pid_t pid,const MachThreadList & thread_list,task_t task)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 Activites
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_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
AddProcessExecutableInfo(ProcessExecutableInfoSP process_exe_info)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(process_exe_info);
302   return info_size + 1;
303 }
304 
305 Genealogy::ProcessExecutableInfoSP
GetProcessExecutableInfosAtIndex(size_t idx)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