1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bugreport.inspector;
18 
19 import com.android.bugreport.anr.Anr;
20 import com.android.bugreport.stacks.ProcessSnapshot;
21 import com.android.bugreport.stacks.JavaStackFrameSnapshot;
22 import com.android.bugreport.stacks.LockSnapshot;
23 import com.android.bugreport.stacks.StackFrameSnapshot;
24 import com.android.bugreport.stacks.ThreadSnapshot;
25 import com.android.bugreport.stacks.VmTraces;
26 
27 import java.util.Map;
28 import java.util.Set;
29 import java.util.TreeSet;
30 import java.util.HashMap;
31 
32 /**
33  * Class to inspect an Anr object and determine which, if any threads are
34  * in a cycle of lcoks and binder transactions.
35  */
36 public class DeadlockDetector {
37 
38     /**
39      * Entry in our growing list of involved threads.
40      */
41     private static class ThreadRecord implements Comparable<ThreadRecord> {
42         public ProcessSnapshot process;
43         public ThreadSnapshot thread;
44 
ThreadRecord(ProcessSnapshot process, ThreadSnapshot thread)45         public ThreadRecord(ProcessSnapshot process, ThreadSnapshot thread) {
46             this.process = process;
47             this.thread = thread;
48         }
49 
equals(ThreadRecord that)50         public boolean equals(ThreadRecord that) {
51             return this.process == that.process
52                     && this.thread == that.thread;
53         }
54 
55         @Override
hashCode()56         public int hashCode() {
57             int hash = 7;
58             hash = hash * 31 + this.process.hashCode();
59             hash = hash * 31 + this.thread.hashCode();
60             return hash;
61         }
62 
compareTo(ThreadRecord that)63         public int compareTo(ThreadRecord that) {
64             int cmp = this.process.compareTo(that.process);
65             if (cmp != 0) {
66                 return cmp;
67             }
68             return this.thread.compareTo(that.thread);
69         }
70     }
71 
72     /**
73      * Entry in our growing list of involved threads.
74      */
75     private static class LockRecord implements Comparable<LockRecord> {
76         public ProcessSnapshot process;
77         public LockSnapshot lock;
78 
LockRecord(ProcessSnapshot process, LockSnapshot lock)79         public LockRecord(ProcessSnapshot process, LockSnapshot lock) {
80             this.process = process;
81             this.lock = lock;
82         }
83 
equals(LockRecord that)84         public boolean equals(LockRecord that) {
85             return this.process == that.process
86                     && (this.lock.address == that.lock.address
87                             || (this.lock.address != null && that.lock.address != null
88                                 && this.lock.address.equals(that.lock.address)));
89         }
90 
91         @Override
hashCode()92         public int hashCode() {
93             int hash = 7;
94             hash = hash * 31 + this.process.hashCode();
95             if (this.lock.address != null) {
96                 hash = hash * 31 + this.lock.address.hashCode();
97             }
98             return hash;
99         }
100 
compareTo(LockRecord that)101         public int compareTo(LockRecord that) {
102             int cmp = this.process.compareTo(that.process);
103             if (cmp != 0) {
104                 return cmp;
105             }
106             if (this.lock.address == that.lock.address) {
107                 return 0;
108             } else if (this.lock.address == null) {
109                 return -1;
110             } else if (that.lock.address == null) {
111                 return 1;
112             } else {
113                 return this.lock.address.compareTo(that.lock.address);
114             }
115         }
116     }
117 
118     /**
119      * Detect any thread cycles that are affecting the main thread of the given pid.
120      */
detectDeadlocks(VmTraces vmTraces, int pid)121     public static Set<ProcessSnapshot> detectDeadlocks(VmTraces vmTraces, int pid) {
122         final boolean dump = false;
123 
124         final TreeSet<ThreadRecord> involvedThreads = new TreeSet<ThreadRecord>();
125 
126         final TreeSet<LockRecord> locksToVisit = new TreeSet<LockRecord>();
127         final TreeSet<LockRecord> locksVisited = new TreeSet<LockRecord>();
128 
129         // Seed the traversal with the locks held by the main thread.
130         final ProcessSnapshot offendingProcess = vmTraces.getProcess(pid);
131         if (offendingProcess == null) {
132             return new TreeSet<ProcessSnapshot>();
133         }
134         final ThreadSnapshot offendingThread = offendingProcess.getThread("main");
135         if (offendingThread == null) {
136             return new TreeSet<ProcessSnapshot>();
137         }
138         addLockRecordsForThread(locksToVisit, locksVisited, offendingProcess, offendingThread);
139 
140         if (dump) {
141             if (offendingThread.outboundBinderPackage != null
142                     || offendingThread.outboundBinderClass != null
143                     || offendingThread.inboundBinderMethod != null) {
144                 System.out.println("Offending thread:");
145                 System.out.print("  pid=" + offendingProcess.pid + " \"" + offendingThread.name
146                         + "\" (tid=" + offendingThread.tid + ")");
147                 if (offendingThread.outboundBinderClass != null) {
148                     System.out.print(" outbound=" + offendingThread.outboundBinderPackage + "."
149                             + offendingThread.outboundBinderClass
150                             + "." + offendingThread.outboundBinderMethod);
151                 }
152                 if (offendingThread.inboundBinderClass != null) {
153                     System.out.print(" inbound=" + offendingThread.inboundBinderPackage + "."
154                             + offendingThread.inboundBinderClass
155                             + "." + offendingThread.inboundBinderMethod);
156                 }
157                 System.out.println();
158             }
159         }
160 
161         if (locksToVisit.size() == 0) {
162             // There weren't any locks. Just stop here.
163             return new TreeSet<ProcessSnapshot>();
164         }
165 
166         involvedThreads.add(new ThreadRecord(offendingProcess, offendingThread));
167 
168         // Terminate when we stop finding new locks to look at. We will terminate
169         // eventually because there are a finite number of locks in the system.
170         while (locksToVisit.size() > 0) {
171             final LockRecord lr = locksToVisit.pollFirst();
172 
173             // Don't allow ourselves to re-add this lock
174             locksVisited.add(lr);
175 
176             // Find all the threads holding this lock.
177             for (ThreadSnapshot thread: lr.process.threads) {
178                 final Map<String,LockSnapshot> locks = thread.locks;
179                 if (locks.containsKey(lr.lock.address)) {
180                     if (dump) {
181                         System.out.println("Thread " + thread.tid
182                                 + " contains lock " + lr.lock.address);
183                     }
184                     // This thread is holding the lock (or trying to).
185                     // Enqeue its other locks that we haven't already done.
186                     addLockRecordsForThread(locksToVisit, locksVisited, lr.process, thread);
187                     involvedThreads.add(new ThreadRecord(lr.process, thread));
188                 }
189             }
190         }
191 
192         final HashMap<Integer,ProcessSnapshot> results = new HashMap<Integer,ProcessSnapshot>();
193 
194         // Add the process / thread pairs into the results
195         if (dump) System.out.println("Involved threads:");
196         for (ThreadRecord tr: involvedThreads) {
197             if (dump) {
198                 System.out.print("  pid=" + tr.process.pid + " \"" + tr.thread.name
199                         + "\" (tid=" + tr.thread.tid + ")");
200                 if (tr.thread.outboundBinderClass != null) {
201                     System.out.print(" outbound=" + tr.thread.outboundBinderPackage + "."
202                             + tr.thread.outboundBinderClass + "." + tr.thread.outboundBinderMethod);
203                 }
204                 if (tr.thread.inboundBinderClass != null) {
205                     System.out.print(" inbound=" + tr.thread.inboundBinderPackage + "."
206                             + tr.thread.inboundBinderClass + "." + tr.thread.inboundBinderMethod);
207                 }
208                 System.out.println();
209             }
210 
211             ProcessSnapshot cloneProcess = results.get(tr.process.pid);
212             if (cloneProcess == null) {
213                 cloneProcess = tr.process.clone();
214                 cloneProcess.threads.clear();
215                 results.put(tr.process.pid, cloneProcess);
216             }
217             cloneProcess.threads.add(tr.thread);
218         }
219         if (dump) {
220             System.out.println("Involved locks:");
221             for (LockRecord lr: locksVisited) {
222                 System.out.println("  pid=" + lr.process.pid + " " + lr.lock.packageName
223                         + "." + lr.lock.className + " - " + lr.lock.address);
224             }
225         }
226 
227         return new TreeSet<ProcessSnapshot>(results.values());
228     }
229 
230     /**
231      * Add the LockRecords for the locks held by the thread to toVisit, unless
232      * they're already in alreadyVisited.
233      */
addLockRecordsForThread(TreeSet<LockRecord> toVisit, TreeSet<LockRecord> alreadyVisited, ProcessSnapshot process, ThreadSnapshot thread)234     private static void addLockRecordsForThread(TreeSet<LockRecord> toVisit,
235             TreeSet<LockRecord> alreadyVisited, ProcessSnapshot process, ThreadSnapshot thread) {
236         for (LockSnapshot lock: thread.locks.values()) {
237             final LockRecord next = new LockRecord(process, lock);
238             if (!alreadyVisited.contains(next)) {
239                 toVisit.add(next);
240             }
241         }
242     }
243 }
244