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