Mercurial > hg > Members > kono > jpf-core
comparison src/main/gov/nasa/jpf/listener/DeadlockAnalyzer.java @ 0:61d41facf527
initial v8 import (history reset)
author | Peter Mehlitz <Peter.C.Mehlitz@nasa.gov> |
---|---|
date | Fri, 23 Jan 2015 10:14:01 -0800 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:61d41facf527 |
---|---|
1 /* | |
2 * Copyright (C) 2014, United States Government, as represented by the | |
3 * Administrator of the National Aeronautics and Space Administration. | |
4 * All rights reserved. | |
5 * | |
6 * The Java Pathfinder core (jpf-core) platform is licensed under the | |
7 * Apache License, Version 2.0 (the "License"); you may not use this file except | |
8 * in compliance with the License. You may obtain a copy of the License at | |
9 * | |
10 * http://www.apache.org/licenses/LICENSE-2.0. | |
11 * | |
12 * Unless required by applicable law or agreed to in writing, software | |
13 * distributed under the License is distributed on an "AS IS" BASIS, | |
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
15 * See the License for the specific language governing permissions and | |
16 * limitations under the License. | |
17 */ | |
18 package gov.nasa.jpf.listener; | |
19 | |
20 import gov.nasa.jpf.Config; | |
21 import gov.nasa.jpf.JPF; | |
22 import gov.nasa.jpf.ListenerAdapter; | |
23 import gov.nasa.jpf.jvm.bytecode.EXECUTENATIVE; | |
24 import gov.nasa.jpf.report.ConsolePublisher; | |
25 import gov.nasa.jpf.report.Publisher; | |
26 import gov.nasa.jpf.search.Search; | |
27 import gov.nasa.jpf.vm.ElementInfo; | |
28 import gov.nasa.jpf.vm.Instruction; | |
29 import gov.nasa.jpf.vm.VM; | |
30 import gov.nasa.jpf.vm.StackFrame; | |
31 import gov.nasa.jpf.vm.ThreadInfo; | |
32 | |
33 import java.io.PrintWriter; | |
34 import java.util.ArrayList; | |
35 import java.util.Collection; | |
36 import java.util.HashMap; | |
37 import java.util.HashSet; | |
38 import java.util.LinkedHashSet; | |
39 import java.util.ListIterator; | |
40 import java.util.Stack; | |
41 | |
42 /** | |
43 * example of a listener that creates property specific traces. The interesting | |
44 * thing is that it does so without the need to store steps, i.e. it maintains | |
45 * it's own transition stack. | |
46 * this is still work in progress, analyzing the trace can be much more | |
47 * elaborate (we just dump up to a max history size for now) | |
48 * | |
49 * <2do> DeadlockAnalyzer output can be confusing if a reorganizing | |
50 * ThreadList is used (which reassigns thread ids) | |
51 */ | |
52 public class DeadlockAnalyzer extends ListenerAdapter { | |
53 | |
54 enum OpType { block, lock, unlock, wait, notify, notifyAll, started, terminated }; | |
55 static String[] opTypeMnemonic = { "B", "L", "U", "W", "N", "A", "S", "T" }; | |
56 | |
57 static class ThreadOp { | |
58 ThreadInfo ti; | |
59 ElementInfo ei; | |
60 Instruction insn; | |
61 | |
62 // kind of redundant, but there might be context attributes in addition | |
63 // to the insn itself | |
64 OpType opType; | |
65 | |
66 // we could identify this with the insn, but only in case this is | |
67 // a transition boundary, which is far less general than we can be | |
68 int stateId; | |
69 ThreadOp prevTransition; | |
70 ThreadOp prevOp; | |
71 | |
72 ThreadOp (ThreadInfo ti, ElementInfo ei, OpType type) { | |
73 this.ti = ti; | |
74 this.ei = ei; | |
75 insn = getReportInsn(ti); // we haven't had the executeInsn notification yet | |
76 opType = type; | |
77 | |
78 prevOp = null; | |
79 } | |
80 | |
81 Instruction getReportInsn(ThreadInfo ti){ | |
82 StackFrame frame = ti.getTopFrame(); | |
83 if (frame != null) { | |
84 Instruction insn = frame.getPC(); | |
85 if (insn instanceof EXECUTENATIVE) { | |
86 frame = frame.getPrevious(); | |
87 if (frame != null) { | |
88 insn = frame.getPC(); | |
89 } | |
90 } | |
91 | |
92 return insn; | |
93 } else { | |
94 return null; | |
95 } | |
96 } | |
97 | |
98 void printLocOn (PrintWriter pw) { | |
99 pw.print(String.format("%6d", new Integer(stateId))); | |
100 | |
101 if (insn != null) { | |
102 pw.print(String.format(" %18.18s ", insn.getMnemonic())); | |
103 pw.print(insn.getFileLocation()); | |
104 String line = insn.getSourceLine(); | |
105 if (line != null){ | |
106 pw.print( " : "); | |
107 pw.print(line.trim()); | |
108 //pw.print(insn); | |
109 } | |
110 } | |
111 } | |
112 | |
113 void printOn (PrintWriter pw){ | |
114 pw.print( stateId); | |
115 pw.print( " : "); | |
116 pw.print( ti.getName()); | |
117 pw.print( " : "); | |
118 pw.print( opType.name()); | |
119 pw.print( " : "); | |
120 pw.println(ei); | |
121 } | |
122 | |
123 @Override | |
124 public String toString() { | |
125 StringBuilder sb = new StringBuilder(); | |
126 sb.append( stateId); | |
127 sb.append( " : "); | |
128 sb.append( ti.getName()); | |
129 sb.append( " : "); | |
130 sb.append( opType.name()); | |
131 sb.append( " : "); | |
132 sb.append(ei); | |
133 return sb.toString(); | |
134 } | |
135 | |
136 void printColumnOn(PrintWriter pw, Collection<ThreadInfo> tlist){ | |
137 for (ThreadInfo t : tlist) { | |
138 if (ti == t) { | |
139 if (opType == OpType.started || opType == OpType.terminated) { | |
140 pw.print(String.format(" %1$s ", opTypeMnemonic[opType.ordinal()])); | |
141 } else { | |
142 pw.print(String.format("%1$s:%2$-5x ", opTypeMnemonic[opType.ordinal()], ei.getObjectRef())); | |
143 } | |
144 //break; | |
145 } else { | |
146 pw.print(" | "); | |
147 } | |
148 } | |
149 } | |
150 } | |
151 | |
152 ThreadOp lastOp; | |
153 ThreadOp lastTransition; | |
154 | |
155 int maxHistory; | |
156 String format; | |
157 | |
158 VM vm; | |
159 Search search; | |
160 | |
161 public DeadlockAnalyzer (Config config, JPF jpf){ | |
162 jpf.addPublisherExtension(ConsolePublisher.class, this); | |
163 | |
164 maxHistory = config.getInt("deadlock.max_history", Integer.MAX_VALUE); | |
165 format = config.getString("deadlock.format", "essential"); | |
166 | |
167 vm = jpf.getVM(); | |
168 search = jpf.getSearch(); | |
169 } | |
170 | |
171 boolean requireAllOps() { | |
172 return (format.equalsIgnoreCase("essential")); | |
173 } | |
174 | |
175 void addOp (ThreadInfo ti, ElementInfo ei, OpType opType){ | |
176 ThreadOp op = new ThreadOp(ti, ei, opType); | |
177 if (lastOp == null){ | |
178 lastOp = op; | |
179 } else { | |
180 assert lastOp.stateId == 0; | |
181 | |
182 op.prevOp = lastOp; | |
183 lastOp = op; | |
184 } | |
185 } | |
186 | |
187 void printRawOps (PrintWriter pw) { | |
188 int i=0; | |
189 | |
190 for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){ | |
191 for (ThreadOp op = tOp; op != null; op=op.prevOp) { | |
192 if (i++ >= maxHistory){ | |
193 pw.println("..."); | |
194 return; | |
195 } | |
196 op.printOn(pw); | |
197 } | |
198 } | |
199 } | |
200 | |
201 /** | |
202 * include all threads that are currently blocked or waiting, and | |
203 * all the threads that had the last interaction with them. Note that | |
204 * we do this completely on the basis of the recorded ThreadOps, i.e. | |
205 * don't rely on when this is called | |
206 */ | |
207 void printEssentialOps(PrintWriter pw) { | |
208 LinkedHashSet<ThreadInfo> threads = new LinkedHashSet<ThreadInfo>(); | |
209 ArrayList<ThreadOp> ops = new ArrayList<ThreadOp>(); | |
210 HashMap<ElementInfo,ThreadInfo> waits = new HashMap<ElementInfo,ThreadInfo>(); | |
211 HashMap<ElementInfo,ThreadInfo> blocks = new HashMap<ElementInfo,ThreadInfo>(); | |
212 HashSet<ThreadInfo> runnables = new HashSet<ThreadInfo>(); | |
213 | |
214 // collect all relevant threads and ops | |
215 for (ThreadOp trans = lastTransition; trans != null; trans = trans.prevTransition){ | |
216 for (ThreadOp tOp = trans; tOp != null; tOp = tOp.prevOp) { | |
217 OpType ot = tOp.opType; | |
218 ThreadInfo oti = tOp.ti; | |
219 | |
220 if (ot == OpType.wait || ot == OpType.block) { | |
221 if (!runnables.contains(oti) && !threads.contains(oti)){ | |
222 HashMap<ElementInfo, ThreadInfo> map = (ot == OpType.block) ? blocks : waits; | |
223 threads.add(oti); | |
224 map.put(tOp.ei, oti); | |
225 ops.add(tOp); | |
226 } | |
227 | |
228 } else if (ot == OpType.notify || ot == OpType.notifyAll || ot == OpType.lock) { | |
229 HashMap<ElementInfo, ThreadInfo> map = (ot == OpType.lock) ? blocks : waits; | |
230 ThreadInfo ti = map.get(tOp.ei); | |
231 | |
232 if (ti != null && ti != oti){ | |
233 if (!threads.contains(oti)){ | |
234 threads.add(oti); | |
235 } | |
236 map.remove(tOp.ei); | |
237 ops.add(tOp); | |
238 } | |
239 | |
240 runnables.add(oti); | |
241 | |
242 } else if (ot == OpType.unlock) { | |
243 // not relevant | |
244 runnables.add(oti); | |
245 | |
246 } else if (ot == OpType.terminated || ot == OpType.started) { | |
247 ops.add(tOp); // might be removed later-on | |
248 } | |
249 } | |
250 } | |
251 | |
252 // remove all starts/terminates of irrelevant threads | |
253 for (ListIterator<ThreadOp> it = ops.listIterator(); it.hasNext(); ){ | |
254 ThreadOp tOp = it.next(); | |
255 if (tOp.opType == OpType.terminated || tOp.opType == OpType.started) { | |
256 if (!threads.contains(tOp.ti)){ | |
257 it.remove(); | |
258 } | |
259 } | |
260 } | |
261 | |
262 // now we are ready to print | |
263 printHeader(pw,threads); | |
264 | |
265 for (ThreadOp tOp : ops) { | |
266 tOp.printColumnOn(pw,threads); | |
267 tOp.printLocOn(pw); | |
268 pw.println(); | |
269 } | |
270 } | |
271 | |
272 | |
273 Collection<ThreadInfo> getThreadList() { | |
274 ArrayList<ThreadInfo> tcol = new ArrayList<ThreadInfo>(); | |
275 boolean allOps = requireAllOps(); | |
276 int i=0; | |
277 | |
278 prevTrans: | |
279 for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){ | |
280 i++; | |
281 if (!allOps && (i >= maxHistory)){ | |
282 break; | |
283 } | |
284 | |
285 for (ThreadInfo ti : tcol) { | |
286 if (ti == tOp.ti) continue prevTrans; | |
287 } | |
288 tcol.add(tOp.ti); | |
289 } | |
290 | |
291 return tcol; | |
292 } | |
293 | |
294 void printHeader (PrintWriter pw, Collection<ThreadInfo> tlist){ | |
295 for (ThreadInfo ti : tlist){ | |
296 pw.print(String.format(" %1$2d ", ti.getId())); | |
297 } | |
298 pw.print(" trans insn loc : stmt"); | |
299 pw.println(); | |
300 | |
301 for (int i=0; i<tlist.size(); i++){ | |
302 pw.print("------- "); | |
303 } | |
304 pw.print("---------------------------------------------------"); | |
305 pw.println(); | |
306 } | |
307 | |
308 | |
309 void printColumnOps (PrintWriter pw){ | |
310 int i = 0; | |
311 Collection<ThreadInfo> tlist = getThreadList(); | |
312 printHeader(pw,tlist); | |
313 | |
314 // and now the data | |
315 for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){ | |
316 for (ThreadOp op = tOp; op != null; op=op.prevOp) { | |
317 if (i++ >= maxHistory){ | |
318 pw.println("..."); | |
319 return; | |
320 } | |
321 | |
322 op.printColumnOn(pw,tlist); | |
323 op.printLocOn(pw); | |
324 pw.println(); | |
325 } | |
326 } | |
327 } | |
328 | |
329 /** | |
330 * this is the workhorse - filter which ops should be shown, and which | |
331 * are irrelevant for the deadlock | |
332 */ | |
333 boolean showOp (ThreadOp op, ThreadInfo[] tlist, | |
334 boolean[] waitSeen, boolean[] notifySeen, | |
335 boolean[] blockSeen, boolean[] lockSeen, | |
336 Stack<ElementInfo>[] unlocked) { | |
337 ThreadInfo ti = op.ti; | |
338 ElementInfo ei = op.ei; | |
339 int idx; | |
340 for (idx=0; idx < tlist.length; idx++){ | |
341 if (tlist[idx] == ti) break; | |
342 } | |
343 | |
344 // we could delegate this to the enum type, but let's not be too fancy | |
345 switch (op.opType) { | |
346 case block: | |
347 // only report the last one if thread is blocked | |
348 if (ti.isBlocked()) { | |
349 if (!blockSeen[idx]) { | |
350 blockSeen[idx] = true; | |
351 return true; | |
352 } | |
353 } | |
354 return false; | |
355 | |
356 case unlock: | |
357 unlocked[idx].push(ei); | |
358 return false; | |
359 | |
360 case lock: | |
361 // if we had a corresponding unlock, ignore | |
362 if (!unlocked[idx].isEmpty() && (unlocked[idx].peek() == ei)) { | |
363 unlocked[idx].pop(); | |
364 return false; | |
365 } | |
366 | |
367 // only report the last one if there is a thread that's currently blocked on it | |
368 for (int i=0; i<tlist.length; i++){ | |
369 if ((i != idx) && tlist[i].isBlocked() && (tlist[i].getLockObject() == ei)) { | |
370 if (!lockSeen[i]){ | |
371 lockSeen[i] = true; | |
372 return true; | |
373 } | |
374 } | |
375 } | |
376 | |
377 return false; | |
378 | |
379 case wait: | |
380 if (ti.isWaiting()){ // only show the last one if this is a waiting thread | |
381 if (!waitSeen[idx]) { | |
382 waitSeen[idx] = true; | |
383 return true; | |
384 } | |
385 } | |
386 | |
387 return false; | |
388 | |
389 case notify: | |
390 case notifyAll: | |
391 // only report the last one if there's a thread waiting on it | |
392 for (int i=0; i<tlist.length; i++){ | |
393 if ((i != idx) && tlist[i].isWaiting() && (tlist[i].getLockObject() == ei)) { | |
394 if (!notifySeen[i]) { | |
395 notifySeen[i] = true; | |
396 return true; | |
397 } | |
398 } | |
399 } | |
400 | |
401 return false; | |
402 | |
403 case started: | |
404 case terminated: | |
405 return true; | |
406 } | |
407 | |
408 return false; | |
409 } | |
410 | |
411 void storeLastTransition(){ | |
412 if (lastOp != null) { | |
413 int stateId = search.getStateId(); | |
414 ThreadInfo ti = lastOp.ti; | |
415 | |
416 for (ThreadOp op = lastOp; op != null; op = op.prevOp) { | |
417 assert op.stateId == 0; | |
418 | |
419 op.stateId = stateId; | |
420 } | |
421 | |
422 lastOp.prevTransition = lastTransition; | |
423 lastTransition = lastOp; | |
424 | |
425 lastOp = null; | |
426 } | |
427 } | |
428 | |
429 //--- VM listener interface | |
430 | |
431 @Override | |
432 public void objectLocked (VM vm, ThreadInfo ti, ElementInfo ei) { | |
433 addOp(ti, ei, OpType.lock); | |
434 } | |
435 | |
436 @Override | |
437 public void objectUnlocked (VM vm, ThreadInfo ti, ElementInfo ei) { | |
438 addOp(ti, ei, OpType.unlock); | |
439 } | |
440 | |
441 @Override | |
442 public void objectWait (VM vm, ThreadInfo ti, ElementInfo ei) { | |
443 addOp(ti, ei, OpType.wait); | |
444 } | |
445 | |
446 @Override | |
447 public void objectNotify (VM vm, ThreadInfo ti, ElementInfo ei) { | |
448 addOp(ti, ei, OpType.notify); | |
449 } | |
450 | |
451 @Override | |
452 public void objectNotifyAll (VM vm, ThreadInfo ti, ElementInfo ei) { | |
453 addOp(ti, ei, OpType.notifyAll); | |
454 } | |
455 | |
456 @Override | |
457 public void threadBlocked (VM vm, ThreadInfo ti, ElementInfo ei){ | |
458 addOp(ti, ei, OpType.block); | |
459 } | |
460 | |
461 @Override | |
462 public void threadStarted (VM vm, ThreadInfo ti){ | |
463 addOp(ti, null, OpType.started); | |
464 } | |
465 | |
466 @Override | |
467 public void threadTerminated (VM vm, ThreadInfo ti){ | |
468 addOp(ti, null, OpType.terminated); | |
469 } | |
470 | |
471 //--- SearchListener interface | |
472 | |
473 @Override | |
474 public void stateAdvanced (Search search){ | |
475 if (search.isNewState()) { | |
476 storeLastTransition(); | |
477 } | |
478 } | |
479 | |
480 @Override | |
481 public void stateBacktracked (Search search){ | |
482 int stateId = search.getStateId(); | |
483 while ((lastTransition != null) && (lastTransition.stateId > stateId)){ | |
484 lastTransition = lastTransition.prevTransition; | |
485 } | |
486 lastOp = null; | |
487 } | |
488 | |
489 // for HeuristicSearches. Ok, that's braindead but at least no need for cloning | |
490 HashMap<Integer,ThreadOp> storedTransition = new HashMap<Integer,ThreadOp>(); | |
491 | |
492 @Override | |
493 public void stateStored (Search search) { | |
494 // always called after stateAdvanced | |
495 storedTransition.put(search.getStateId(), lastTransition); | |
496 } | |
497 | |
498 @Override | |
499 public void stateRestored (Search search) { | |
500 int stateId = search.getStateId(); | |
501 ThreadOp op = storedTransition.get(stateId); | |
502 if (op != null) { | |
503 lastTransition = op; | |
504 storedTransition.remove(stateId); // not strictly required, but we don't come back | |
505 } | |
506 } | |
507 | |
508 @Override | |
509 public void publishPropertyViolation (Publisher publisher) { | |
510 PrintWriter pw = publisher.getOut(); | |
511 publisher.publishTopicStart("thread ops " + publisher.getLastErrorId()); | |
512 | |
513 if ("column".equalsIgnoreCase(format)){ | |
514 printColumnOps(pw); | |
515 } else if ("essential".equalsIgnoreCase(format)) { | |
516 printEssentialOps(pw); | |
517 } else { | |
518 printRawOps(pw); | |
519 } | |
520 } | |
521 } |