1 /* |
|
2 * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved. |
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
|
4 * |
|
5 * This code is free software; you can redistribute it and/or modify it |
|
6 * under the terms of the GNU General Public License version 2 only, as |
|
7 * published by the Free Software Foundation. |
|
8 * |
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT |
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
|
12 * version 2 for more details (a copy is included in the LICENSE file that |
|
13 * accompanied this code). |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License version |
|
16 * 2 along with this work; if not, write to the Free Software Foundation, |
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
|
18 * |
|
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
|
20 * or visit www.oracle.com if you need additional information or have any |
|
21 * questions. |
|
22 * |
|
23 */ |
|
24 |
|
25 package sun.jvm.hotspot.bugspot; |
|
26 |
|
27 import java.awt.*; |
|
28 import java.awt.event.*; |
|
29 import java.io.*; |
|
30 import java.net.*; |
|
31 import java.util.*; |
|
32 import javax.swing.*; |
|
33 import javax.swing.filechooser.*; |
|
34 import sun.jvm.hotspot.debugger.*; |
|
35 import sun.jvm.hotspot.debugger.cdbg.*; |
|
36 import sun.jvm.hotspot.debugger.posix.*; |
|
37 import sun.jvm.hotspot.debugger.windbg.*; |
|
38 import sun.jvm.hotspot.livejvm.*; |
|
39 import sun.jvm.hotspot.memory.*; |
|
40 import sun.jvm.hotspot.oops.*; |
|
41 import sun.jvm.hotspot.runtime.*; |
|
42 import sun.jvm.hotspot.ui.*; |
|
43 import sun.jvm.hotspot.utilities.*; |
|
44 |
|
45 /** The BugSpot component. This is embeddable in an application by |
|
46 virtue of its being a JComponent. It (currently) requires the use |
|
47 of a menu bar which can be fetched via getMenuBar(). This is |
|
48 intended ultimately to replace HSDB. */ |
|
49 |
|
50 public class BugSpot extends JPanel { |
|
51 public BugSpot() { |
|
52 super(); |
|
53 Runtime.getRuntime().addShutdownHook(new java.lang.Thread() { |
|
54 public void run() { |
|
55 detachDebugger(); |
|
56 } |
|
57 }); |
|
58 } |
|
59 |
|
60 /** Turn on or off MDI (Multiple Document Interface) mode. When MDI |
|
61 is enabled, the BugSpot component contains a JDesktopPane and all |
|
62 windows are JInternalFrames. When disabled, only the menu bar is |
|
63 relevant. */ |
|
64 public void setMDIMode(boolean onOrOff) { |
|
65 mdiMode = onOrOff; |
|
66 } |
|
67 |
|
68 /** Indicates whether MDI mode is enabled. */ |
|
69 public boolean getMDIMode() { |
|
70 return mdiMode; |
|
71 } |
|
72 |
|
73 /** Build user interface widgets. This must be called before adding |
|
74 the BugSpot component to its parent. */ |
|
75 public void build() { |
|
76 setLayout(new BorderLayout()); |
|
77 |
|
78 menuBar = new JMenuBar(); |
|
79 |
|
80 attachMenuItems = new java.util.ArrayList(); |
|
81 detachMenuItems = new java.util.ArrayList(); |
|
82 debugMenuItems = new java.util.ArrayList(); |
|
83 suspendDebugMenuItems = new java.util.ArrayList(); |
|
84 resumeDebugMenuItems = new java.util.ArrayList(); |
|
85 |
|
86 // |
|
87 // File menu |
|
88 // |
|
89 |
|
90 JMenu menu = createMenu("File", 'F', 0); |
|
91 JMenuItem item; |
|
92 item = createMenuItem("Open source file...", |
|
93 new ActionListener() { |
|
94 public void actionPerformed(ActionEvent e) { |
|
95 openSourceFile(); |
|
96 } |
|
97 }, |
|
98 KeyEvent.VK_O, InputEvent.CTRL_MASK, |
|
99 'O', 0); |
|
100 menu.add(item); |
|
101 detachMenuItems.add(item); |
|
102 |
|
103 menu.addSeparator(); |
|
104 |
|
105 item = createMenuItem("Attach to process...", |
|
106 new ActionListener() { |
|
107 public void actionPerformed(ActionEvent e) { |
|
108 showAttachDialog(); |
|
109 } |
|
110 }, |
|
111 'A', 0); |
|
112 menu.add(item); |
|
113 attachMenuItems.add(item); |
|
114 |
|
115 item = createMenuItem("Detach", |
|
116 new ActionListener() { |
|
117 public void actionPerformed(ActionEvent e) { |
|
118 detach(); |
|
119 } |
|
120 }, |
|
121 'D', 0); |
|
122 menu.add(item); |
|
123 detachMenuItems.add(item); |
|
124 |
|
125 // Disable detach menu items at first |
|
126 setMenuItemsEnabled(detachMenuItems, false); |
|
127 |
|
128 menu.addSeparator(); |
|
129 |
|
130 menu.add(createMenuItem("Exit", |
|
131 new ActionListener() { |
|
132 public void actionPerformed(ActionEvent e) { |
|
133 detach(); |
|
134 System.exit(0); |
|
135 } |
|
136 }, |
|
137 'x', 1)); |
|
138 |
|
139 menuBar.add(menu); |
|
140 |
|
141 // |
|
142 // Debug menu |
|
143 // |
|
144 |
|
145 debugMenu = createMenu("Debug", 'D', 0); |
|
146 item = createMenuItem("Go", |
|
147 new ActionListener() { |
|
148 public void actionPerformed(ActionEvent e) { |
|
149 if (!attached) return; |
|
150 if (!isSuspended()) return; |
|
151 resume(); |
|
152 } |
|
153 }, |
|
154 KeyEvent.VK_F5, 0, |
|
155 'G', 0); |
|
156 debugMenu.add(item); |
|
157 resumeDebugMenuItems.add(item); |
|
158 |
|
159 item = createMenuItem("Break", |
|
160 new ActionListener() { |
|
161 public void actionPerformed(ActionEvent e) { |
|
162 if (!attached) { |
|
163 System.err.println("Not attached"); |
|
164 return; |
|
165 } |
|
166 if (isSuspended()) { |
|
167 System.err.println("Already suspended"); |
|
168 return; |
|
169 } |
|
170 suspend(); |
|
171 } |
|
172 }, |
|
173 'B', 0); |
|
174 debugMenu.add(item); |
|
175 suspendDebugMenuItems.add(item); |
|
176 |
|
177 debugMenu.addSeparator(); |
|
178 |
|
179 item = createMenuItem("Threads...", |
|
180 new ActionListener() { |
|
181 public void actionPerformed(ActionEvent e) { |
|
182 showThreadsDialog(); |
|
183 } |
|
184 }, |
|
185 'T', 0); |
|
186 debugMenu.add(item); |
|
187 debugMenuItems.add(item); |
|
188 // FIXME: belongs under "View -> Debug Windows" |
|
189 item = createMenuItem("Memory", |
|
190 new ActionListener() { |
|
191 public void actionPerformed(ActionEvent e) { |
|
192 showMemoryDialog(); |
|
193 } |
|
194 }, |
|
195 'M', 0); |
|
196 debugMenu.add(item); |
|
197 debugMenuItems.add(item); |
|
198 |
|
199 debugMenu.setEnabled(false); |
|
200 menuBar.add(debugMenu); |
|
201 |
|
202 if (mdiMode) { |
|
203 desktop = new JDesktopPane(); |
|
204 add(desktop, BorderLayout.CENTER); |
|
205 } |
|
206 |
|
207 fixedWidthFont = GraphicsUtilities.lookupFont("Courier"); |
|
208 |
|
209 debugEventTimer = new javax.swing.Timer(100, new ActionListener() { |
|
210 public void actionPerformed(ActionEvent e) { |
|
211 pollForDebugEvent(); |
|
212 } |
|
213 }); |
|
214 } |
|
215 |
|
216 public JMenuBar getMenuBar() { |
|
217 return menuBar; |
|
218 } |
|
219 |
|
220 public void showAttachDialog() { |
|
221 setMenuItemsEnabled(attachMenuItems, false); |
|
222 final FrameWrapper attachDialog = newFrame("Attach to process"); |
|
223 attachDialog.getContentPane().setLayout(new BorderLayout()); |
|
224 attachDialog.setClosable(true); |
|
225 attachDialog.setResizable(true); |
|
226 |
|
227 JPanel panel = new JPanel(); |
|
228 panel.setLayout(new BorderLayout()); |
|
229 panel.setBorder(GraphicsUtilities.newBorder(5)); |
|
230 attachDialog.setBackground(panel.getBackground()); |
|
231 |
|
232 JPanel listPanel = new JPanel(); |
|
233 listPanel.setLayout(new BorderLayout()); |
|
234 final ProcessListPanel plist = new ProcessListPanel(getLocalDebugger()); |
|
235 panel.add(plist, BorderLayout.CENTER); |
|
236 JCheckBox check = new JCheckBox("Update list continuously"); |
|
237 check.addItemListener(new ItemListener() { |
|
238 public void itemStateChanged(ItemEvent e) { |
|
239 if (e.getStateChange() == ItemEvent.SELECTED) { |
|
240 plist.start(); |
|
241 } else { |
|
242 plist.stop(); |
|
243 } |
|
244 } |
|
245 }); |
|
246 listPanel.add(plist, BorderLayout.CENTER); |
|
247 listPanel.add(check, BorderLayout.SOUTH); |
|
248 panel.add(listPanel, BorderLayout.CENTER); |
|
249 attachDialog.getContentPane().add(panel, BorderLayout.CENTER); |
|
250 attachDialog.setClosingActionListener(new ActionListener() { |
|
251 public void actionPerformed(ActionEvent e) { |
|
252 plist.stop(); |
|
253 setMenuItemsEnabled(attachMenuItems, true); |
|
254 } |
|
255 }); |
|
256 |
|
257 ActionListener attacher = new ActionListener() { |
|
258 public void actionPerformed(ActionEvent e) { |
|
259 plist.stop(); |
|
260 attachDialog.setVisible(false); |
|
261 removeFrame(attachDialog); |
|
262 ProcessInfo info = plist.getSelectedProcess(); |
|
263 if (info != null) { |
|
264 attach(info.getPid()); |
|
265 } |
|
266 } |
|
267 }; |
|
268 |
|
269 Box hbox = Box.createHorizontalBox(); |
|
270 hbox.add(Box.createGlue()); |
|
271 JButton button = new JButton("OK"); |
|
272 button.addActionListener(attacher); |
|
273 hbox.add(button); |
|
274 hbox.add(Box.createHorizontalStrut(20)); |
|
275 button = new JButton("Cancel"); |
|
276 button.addActionListener(new ActionListener() { |
|
277 public void actionPerformed(ActionEvent e) { |
|
278 plist.stop(); |
|
279 attachDialog.setVisible(false); |
|
280 removeFrame(attachDialog); |
|
281 setMenuItemsEnabled(attachMenuItems, true); |
|
282 } |
|
283 }); |
|
284 hbox.add(button); |
|
285 hbox.add(Box.createGlue()); |
|
286 panel = new JPanel(); |
|
287 panel.setBorder(GraphicsUtilities.newBorder(5)); |
|
288 panel.add(hbox); |
|
289 |
|
290 attachDialog.getContentPane().add(panel, BorderLayout.SOUTH); |
|
291 |
|
292 addFrame(attachDialog); |
|
293 attachDialog.pack(); |
|
294 attachDialog.setSize(400, 300); |
|
295 GraphicsUtilities.centerInContainer(attachDialog.getComponent(), |
|
296 getParentDimension(attachDialog.getComponent())); |
|
297 attachDialog.setVisible(true); |
|
298 } |
|
299 |
|
300 public void showThreadsDialog() { |
|
301 final FrameWrapper threadsDialog = newFrame("Threads"); |
|
302 threadsDialog.getContentPane().setLayout(new BorderLayout()); |
|
303 threadsDialog.setClosable(true); |
|
304 threadsDialog.setResizable(true); |
|
305 |
|
306 ThreadListPanel threads = new ThreadListPanel(getCDebugger(), getAgent().isJavaMode()); |
|
307 threads.addListener(new ThreadListPanel.Listener() { |
|
308 public void setFocus(ThreadProxy thread, JavaThread jthread) { |
|
309 setCurrentThread(thread); |
|
310 // FIXME: print this to GUI, bring some windows to foreground |
|
311 System.err.println("Focus changed to thread " + thread); |
|
312 } |
|
313 }); |
|
314 threads.setBorder(GraphicsUtilities.newBorder(5)); |
|
315 threadsDialog.getContentPane().add(threads); |
|
316 addFrame(threadsDialog); |
|
317 threadsDialog.pack(); |
|
318 GraphicsUtilities.reshapeToAspectRatio(threadsDialog.getComponent(), |
|
319 3.0f, |
|
320 0.9f, |
|
321 getParentDimension(threadsDialog.getComponent())); |
|
322 GraphicsUtilities.centerInContainer(threadsDialog.getComponent(), |
|
323 getParentDimension(threadsDialog.getComponent())); |
|
324 threadsDialog.setVisible(true); |
|
325 } |
|
326 |
|
327 public void showMemoryDialog() { |
|
328 final FrameWrapper memoryDialog = newFrame("Memory"); |
|
329 memoryDialog.getContentPane().setLayout(new BorderLayout()); |
|
330 memoryDialog.setClosable(true); |
|
331 memoryDialog.setResizable(true); |
|
332 |
|
333 memoryDialog.getContentPane().add(new MemoryViewer(getDebugger(), |
|
334 (getDebugger().getMachineDescription().getAddressSize() == 8)), |
|
335 BorderLayout.CENTER); |
|
336 addFrame(memoryDialog); |
|
337 memoryDialog.pack(); |
|
338 GraphicsUtilities.reshapeToAspectRatio(memoryDialog.getComponent(), |
|
339 1.0f, |
|
340 0.7f, |
|
341 getParentDimension(memoryDialog.getComponent())); |
|
342 GraphicsUtilities.centerInContainer(memoryDialog.getComponent(), |
|
343 getParentDimension(memoryDialog.getComponent())); |
|
344 memoryDialog.setVisible(true); |
|
345 } |
|
346 |
|
347 /** Changes the editor factory this debugger uses to display source |
|
348 code. Specified factory may be null, in which case the default |
|
349 factory is used. */ |
|
350 public void setEditorFactory(EditorFactory fact) { |
|
351 if (fact != null) { |
|
352 editorFact = fact; |
|
353 } else { |
|
354 editorFact = new DefaultEditorFactory(); |
|
355 } |
|
356 } |
|
357 |
|
358 //---------------------------------------------------------------------- |
|
359 // Internals only below this point |
|
360 // |
|
361 |
|
362 private WorkerThread workerThread; |
|
363 private boolean mdiMode; |
|
364 private JVMDebugger localDebugger; |
|
365 private BugSpotAgent agent = new BugSpotAgent(); |
|
366 private JMenuBar menuBar; |
|
367 /** List <JMenuItem> */ |
|
368 private java.util.List attachMenuItems; |
|
369 private java.util.List detachMenuItems; |
|
370 private java.util.List debugMenuItems; |
|
371 private java.util.List suspendDebugMenuItems; |
|
372 private java.util.List resumeDebugMenuItems; |
|
373 private FrameWrapper stackFrame; |
|
374 private VariablePanel localsPanel; |
|
375 private StackTracePanel stackTracePanel; |
|
376 private FrameWrapper registerFrame; |
|
377 private RegisterPanel registerPanel; |
|
378 // Used for mixed-language stack traces |
|
379 private Map threadToJavaThreadMap; |
|
380 |
|
381 private JMenu debugMenu; |
|
382 |
|
383 // MDI mode only: desktop pane |
|
384 private JDesktopPane desktop; |
|
385 |
|
386 // Attach/detach state |
|
387 private boolean attached; |
|
388 |
|
389 // Suspension (combined Java/C++) state |
|
390 private boolean suspended; |
|
391 |
|
392 // Fixed-width font |
|
393 private Font fixedWidthFont; |
|
394 |
|
395 // Breakpoint setting |
|
396 // Maps Strings to List/*<LineNumberInfo>*/ |
|
397 private Map sourceFileToLineNumberInfoMap; |
|
398 // Maps Strings (file names) to Sets of Integers (line numbers) |
|
399 private Map fileToBreakpointMap; |
|
400 |
|
401 // Debug events |
|
402 private javax.swing.Timer debugEventTimer; |
|
403 |
|
404 // Java debug events |
|
405 private boolean javaEventPending; |
|
406 |
|
407 static class BreakpointResult { |
|
408 private boolean success; |
|
409 private boolean set; |
|
410 private int lineNo; |
|
411 private String why; |
|
412 |
|
413 /** For positive results */ |
|
414 BreakpointResult(boolean success, boolean set, int lineNo) { |
|
415 this(success, set, lineNo, null); |
|
416 } |
|
417 |
|
418 /** For negative results */ |
|
419 BreakpointResult(boolean success, boolean set, int lineNo, String why) { |
|
420 this.success = success; |
|
421 this.set = set; |
|
422 this.lineNo = lineNo; |
|
423 this.why = why; |
|
424 } |
|
425 |
|
426 public boolean succeeded() { |
|
427 return success; |
|
428 } |
|
429 |
|
430 public boolean set() { |
|
431 return set; |
|
432 } |
|
433 |
|
434 /** Line at which the breakpoint was actually set; only valid if |
|
435 succeeded() returns true */ |
|
436 public int getLine() { |
|
437 return lineNo; |
|
438 } |
|
439 |
|
440 public String getWhy() { |
|
441 return why; |
|
442 } |
|
443 } |
|
444 |
|
445 |
|
446 // Editors for source code. File name-to-Editor mapping. |
|
447 private Map editors; |
|
448 private EditorFactory editorFact = new DefaultEditorFactory(); |
|
449 private EditorCommands editorComm = new EditorCommands() { |
|
450 public void windowClosed(Editor editor) { |
|
451 editors.remove(editor.getSourceFileName()); |
|
452 } |
|
453 |
|
454 public void toggleBreakpointAtLine(Editor editor, int lineNumber) { |
|
455 // FIXME: handle "lazy" breakpoints where the source file has |
|
456 // been opened with some other mechanism (File -> Open) and we |
|
457 // don't have debug information pointing to that file yet |
|
458 // FIXME: NOT FINISHED |
|
459 |
|
460 BreakpointResult res = |
|
461 handleBreakpointToggle(editor, lineNumber); |
|
462 if (res.succeeded()) { |
|
463 if (res.set()) { |
|
464 editor.showBreakpointAtLine(res.getLine()); |
|
465 } else { |
|
466 editor.clearBreakpointAtLine(res.getLine()); |
|
467 } |
|
468 } else { |
|
469 String why = res.getWhy(); |
|
470 if (why == null) { |
|
471 why = ""; |
|
472 } else { |
|
473 why = ": " + why; |
|
474 } |
|
475 showMessageDialog("Unable to toggle breakpoint" + why, |
|
476 "Unable to toggle breakpoint", |
|
477 JOptionPane.WARNING_MESSAGE); |
|
478 } |
|
479 } |
|
480 }; |
|
481 |
|
482 private void attach(final int pid) { |
|
483 try { |
|
484 getAgent().attach(pid); |
|
485 setMenuItemsEnabled(detachMenuItems, true); |
|
486 setMenuItemsEnabled(suspendDebugMenuItems, false); |
|
487 setMenuItemsEnabled(resumeDebugMenuItems, true); |
|
488 debugMenu.setEnabled(true); |
|
489 attached = true; |
|
490 suspended = true; |
|
491 |
|
492 if (getAgent().isJavaMode()) { |
|
493 System.err.println("Java HotSpot(TM) virtual machine detected."); |
|
494 } else { |
|
495 System.err.println("(No Java(TM) virtual machine detected)"); |
|
496 } |
|
497 |
|
498 // Set up editor map |
|
499 editors = new HashMap(); |
|
500 |
|
501 // Initialize breakpoints |
|
502 fileToBreakpointMap = new HashMap(); |
|
503 |
|
504 // Create combined stack trace and local variable panel |
|
505 JPanel framePanel = new JPanel(); |
|
506 framePanel.setLayout(new BorderLayout()); |
|
507 framePanel.setBorder(GraphicsUtilities.newBorder(5)); |
|
508 localsPanel = new VariablePanel(); |
|
509 JTabbedPane tab = new JTabbedPane(); |
|
510 tab.addTab("Locals", localsPanel); |
|
511 tab.setTabPlacement(JTabbedPane.BOTTOM); |
|
512 framePanel.add(tab, BorderLayout.CENTER); |
|
513 JPanel stackPanel = new JPanel(); |
|
514 stackPanel.setLayout(new BoxLayout(stackPanel, BoxLayout.X_AXIS)); |
|
515 stackPanel.add(new JLabel("Context:")); |
|
516 stackPanel.add(Box.createHorizontalStrut(5)); |
|
517 stackTracePanel = new StackTracePanel(); |
|
518 stackTracePanel.addListener(new StackTracePanel.Listener() { |
|
519 public void frameChanged(CFrame fr, JavaVFrame jfr) { |
|
520 setCurrentFrame(fr, jfr); |
|
521 } |
|
522 }); |
|
523 stackPanel.add(stackTracePanel); |
|
524 framePanel.add(stackPanel, BorderLayout.NORTH); |
|
525 stackFrame = newFrame("Stack"); |
|
526 stackFrame.getContentPane().setLayout(new BorderLayout()); |
|
527 stackFrame.getContentPane().add(framePanel, BorderLayout.CENTER); |
|
528 stackFrame.setResizable(true); |
|
529 stackFrame.setClosable(false); |
|
530 addFrame(stackFrame); |
|
531 stackFrame.setSize(400, 200); |
|
532 GraphicsUtilities.moveToInContainer(stackFrame.getComponent(), 0.0f, 1.0f, 0, 20); |
|
533 stackFrame.setVisible(true); |
|
534 |
|
535 // Create register panel |
|
536 registerPanel = new RegisterPanel(); |
|
537 registerPanel.setFont(fixedWidthFont); |
|
538 registerFrame = newFrame("Registers"); |
|
539 registerFrame.getContentPane().setLayout(new BorderLayout()); |
|
540 registerFrame.getContentPane().add(registerPanel, BorderLayout.CENTER); |
|
541 addFrame(registerFrame); |
|
542 registerFrame.setResizable(true); |
|
543 registerFrame.setClosable(false); |
|
544 registerFrame.setSize(225, 200); |
|
545 GraphicsUtilities.moveToInContainer(registerFrame.getComponent(), |
|
546 1.0f, 0.0f, 0, 0); |
|
547 registerFrame.setVisible(true); |
|
548 |
|
549 resetCurrentThread(); |
|
550 } catch (DebuggerException e) { |
|
551 final String errMsg = formatMessage(e.getMessage(), 80); |
|
552 setMenuItemsEnabled(attachMenuItems, true); |
|
553 showMessageDialog("Unable to connect to process ID " + pid + ":\n\n" + errMsg, |
|
554 "Unable to Connect", |
|
555 JOptionPane.WARNING_MESSAGE); |
|
556 getAgent().detach(); |
|
557 } |
|
558 } |
|
559 |
|
560 private synchronized void detachDebugger() { |
|
561 if (!attached) { |
|
562 return; |
|
563 } |
|
564 if (isSuspended()) { |
|
565 resume(); // Necessary for JVMDI resumption |
|
566 } |
|
567 getAgent().detach(); |
|
568 // FIXME: clear out breakpoints (both Java and C/C++) from target |
|
569 // process |
|
570 sourceFileToLineNumberInfoMap = null; |
|
571 fileToBreakpointMap = null; |
|
572 threadToJavaThreadMap = null; |
|
573 editors = null; |
|
574 attached = false; |
|
575 } |
|
576 |
|
577 private synchronized void detach() { |
|
578 detachDebugger(); |
|
579 setMenuItemsEnabled(attachMenuItems, true); |
|
580 setMenuItemsEnabled(detachMenuItems, false); |
|
581 debugMenu.setEnabled(false); |
|
582 if (mdiMode) { |
|
583 // FIXME: is this sufficient, or will I have to do anything else |
|
584 // to the components to kill them off? What about WorkerThreads? |
|
585 desktop.removeAll(); |
|
586 desktop.invalidate(); |
|
587 desktop.validate(); |
|
588 desktop.repaint(); |
|
589 } |
|
590 // FIXME: keep track of all windows and close them even in non-MDI |
|
591 // mode |
|
592 debugEventTimer.stop(); |
|
593 } |
|
594 |
|
595 // Returns a Debugger for processes on the local machine. This is |
|
596 // only used to fetch the process list. |
|
597 private Debugger getLocalDebugger() { |
|
598 if (localDebugger == null) { |
|
599 String os = PlatformInfo.getOS(); |
|
600 String cpu = PlatformInfo.getCPU(); |
|
601 |
|
602 if (os.equals("win32")) { |
|
603 if (!cpu.equals("x86")) { |
|
604 throw new DebuggerException("Unsupported CPU \"" + cpu + "\" for Windows"); |
|
605 } |
|
606 |
|
607 localDebugger = new WindbgDebuggerLocal(new MachineDescriptionIntelX86(), true); |
|
608 } else if (os.equals("linux")) { |
|
609 if (!cpu.equals("x86")) { |
|
610 throw new DebuggerException("Unsupported CPU \"" + cpu + "\" for Linux"); |
|
611 } |
|
612 |
|
613 // FIXME: figure out how to specify path to debugger module |
|
614 throw new RuntimeException("FIXME: figure out how to specify path to debugger module"); |
|
615 // localDebugger = new PosixDebuggerLocal(new MachineDescriptionIntelX86(), true); |
|
616 } else { |
|
617 // FIXME: port to Solaris |
|
618 throw new DebuggerException("Unsupported OS \"" + os + "\""); |
|
619 } |
|
620 |
|
621 // FIXME: we require that the primitive type sizes be configured |
|
622 // in order to use basic functionality in class Address such as |
|
623 // the fetching of floating-point values. There are a lot of |
|
624 // assumptions in the current code that Java floats and doubles |
|
625 // are of equivalent size to C values. The configurability of the |
|
626 // primitive type sizes hasn't seemed necessary and in this kind |
|
627 // of debugging scenario (namely, debugging arbitrary C++ |
|
628 // processes) it appears difficult to support that kind of |
|
629 // flexibility. |
|
630 localDebugger.configureJavaPrimitiveTypeSizes(1, 1, 2, 8, 4, 4, 8, 2); |
|
631 } |
|
632 |
|
633 return localDebugger; |
|
634 } |
|
635 |
|
636 private BugSpotAgent getAgent() { |
|
637 return agent; |
|
638 } |
|
639 |
|
640 private Debugger getDebugger() { |
|
641 return getAgent().getDebugger(); |
|
642 } |
|
643 |
|
644 private CDebugger getCDebugger() { |
|
645 return getAgent().getCDebugger(); |
|
646 } |
|
647 |
|
648 private void resetCurrentThread() { |
|
649 setCurrentThread((ThreadProxy) getCDebugger().getThreadList().get(0)); |
|
650 } |
|
651 |
|
652 private void setCurrentThread(ThreadProxy t) { |
|
653 // Create stack trace |
|
654 // FIXME: add ability to intermix C/Java frames |
|
655 java.util.List trace = new ArrayList(); |
|
656 CFrame fr = getCDebugger().topFrameForThread(t); |
|
657 while (fr != null) { |
|
658 trace.add(new StackTraceEntry(fr, getCDebugger())); |
|
659 try { |
|
660 fr = fr.sender(t); |
|
661 } catch (AddressException e) { |
|
662 e.printStackTrace(); |
|
663 showMessageDialog("Error while walking stack; stack trace will be truncated\n(see console for details)", |
|
664 "Error walking stack", |
|
665 JOptionPane.WARNING_MESSAGE); |
|
666 fr = null; |
|
667 } |
|
668 } |
|
669 JavaThread jthread = javaThreadForProxy(t); |
|
670 if (jthread != null) { |
|
671 // Java mode, and we have a Java thread. |
|
672 // Find all Java frames on the stack. We currently do this in a |
|
673 // manner which involves minimal interaction between the Java |
|
674 // and C/C++ debugging systems: any C frame which has a PC in an |
|
675 // unknown location (i.e., not in any DSO) is assumed to be a |
|
676 // Java frame. We merge stack segments of unknown frames with |
|
677 // segments of Java frames beginning with native methods. |
|
678 java.util.List javaTrace = new ArrayList(); |
|
679 VFrame vf = jthread.getLastJavaVFrameDbg(); |
|
680 while (vf != null) { |
|
681 if (vf.isJavaFrame()) { |
|
682 javaTrace.add(new StackTraceEntry((JavaVFrame) vf)); |
|
683 vf = vf.sender(); |
|
684 } |
|
685 } |
|
686 // Merge stack traces |
|
687 java.util.List mergedTrace = new ArrayList(); |
|
688 int c = 0; |
|
689 int j = 0; |
|
690 while (c < trace.size()) { |
|
691 StackTraceEntry entry = (StackTraceEntry) trace.get(c); |
|
692 if (entry.isUnknownCFrame()) { |
|
693 boolean gotJavaFrame = false; |
|
694 while (j < javaTrace.size()) { |
|
695 StackTraceEntry javaEntry = (StackTraceEntry) javaTrace.get(j); |
|
696 JavaVFrame jvf = javaEntry.getJavaFrame(); |
|
697 Method m = jvf.getMethod(); |
|
698 if (!m.isNative() || !gotJavaFrame) { |
|
699 gotJavaFrame = true; |
|
700 mergedTrace.add(javaEntry); |
|
701 ++j; |
|
702 } else { |
|
703 break; // Reached native method; have intervening C frames |
|
704 } |
|
705 } |
|
706 if (gotJavaFrame) { |
|
707 // Skip this sequence of unknown frames, as we've |
|
708 // successfully identified it as Java frames |
|
709 while (c < trace.size() && entry.isUnknownCFrame()) { |
|
710 ++c; |
|
711 if (c < trace.size()) { |
|
712 entry = (StackTraceEntry) trace.get(c); |
|
713 } |
|
714 } |
|
715 continue; |
|
716 } |
|
717 } |
|
718 // If we get here, we either have an unknown frame we didn't |
|
719 // know how to categorize or we have a known C frame. Add it |
|
720 // to the trace. |
|
721 mergedTrace.add(entry); |
|
722 ++c; |
|
723 } |
|
724 trace = mergedTrace; |
|
725 } |
|
726 stackTracePanel.setTrace(trace); |
|
727 |
|
728 registerPanel.update(t); |
|
729 } |
|
730 |
|
731 private void setCurrentFrame(CFrame fr, JavaVFrame jfr) { |
|
732 localsPanel.clear(); |
|
733 |
|
734 if (fr != null) { |
|
735 localsPanel.update(fr); |
|
736 |
|
737 // FIXME: load source file if we can find it, otherwise display disassembly |
|
738 LoadObject lo = getCDebugger().loadObjectContainingPC(fr.pc()); |
|
739 if (lo != null) { |
|
740 CDebugInfoDataBase db = lo.getDebugInfoDataBase(); |
|
741 if (db != null) { |
|
742 LineNumberInfo info = db.lineNumberForPC(fr.pc()); |
|
743 if (info != null) { |
|
744 System.err.println("PC " + fr.pc() + ": Source file \"" + |
|
745 info.getSourceFileName() + |
|
746 "\", line number " + |
|
747 info.getLineNumber() + |
|
748 ", PC range [" + |
|
749 info.getStartPC() + |
|
750 ", " + |
|
751 info.getEndPC() + |
|
752 ")"); |
|
753 // OK, here we go... |
|
754 showLineNumber(null, info.getSourceFileName(), info.getLineNumber()); |
|
755 } else { |
|
756 System.err.println("(No line number information for PC " + fr.pc() + ")"); |
|
757 // Dump line number information for database |
|
758 db.iterate(new LineNumberVisitor() { |
|
759 public void doLineNumber(LineNumberInfo info) { |
|
760 System.err.println(" Source file \"" + |
|
761 info.getSourceFileName() + |
|
762 "\", line number " + |
|
763 info.getLineNumber() + |
|
764 ", PC range [" + |
|
765 info.getStartPC() + |
|
766 ", " + |
|
767 info.getEndPC() + |
|
768 ")"); |
|
769 } |
|
770 }); |
|
771 } |
|
772 } |
|
773 } |
|
774 } else { |
|
775 if (Assert.ASSERTS_ENABLED) { |
|
776 Assert.that(jfr != null, "Must have either C or Java frame"); |
|
777 } |
|
778 localsPanel.update(jfr); |
|
779 // See whether we can locate source file and line number |
|
780 // FIXME: infer pathmap entries from user's locating of this |
|
781 // source file |
|
782 // FIXME: figure out what to do for native methods. Possible to |
|
783 // go to line number for the native method declaration? |
|
784 Method m = jfr.getMethod(); |
|
785 Symbol sfn = ((InstanceKlass) m.getMethodHolder()).getSourceFileName(); |
|
786 if (sfn != null) { |
|
787 int bci = jfr.getBCI(); |
|
788 int lineNo = m.getLineNumberFromBCI(bci); |
|
789 if (lineNo >= 0) { |
|
790 // FIXME: show disassembly otherwise |
|
791 showLineNumber(packageName(m.getMethodHolder().getName().asString()), |
|
792 sfn.asString(), lineNo); |
|
793 } |
|
794 } |
|
795 } |
|
796 } |
|
797 |
|
798 private String packageName(String str) { |
|
799 int idx = str.lastIndexOf('/'); |
|
800 if (idx < 0) { |
|
801 return ""; |
|
802 } |
|
803 return str.substring(0, idx).replace('/', '.'); |
|
804 } |
|
805 |
|
806 private JavaThread javaThreadForProxy(ThreadProxy t) { |
|
807 if (!getAgent().isJavaMode()) { |
|
808 return null; |
|
809 } |
|
810 if (threadToJavaThreadMap == null) { |
|
811 threadToJavaThreadMap = new HashMap(); |
|
812 Threads threads = VM.getVM().getThreads(); |
|
813 for (JavaThread thr = threads.first(); thr != null; thr = thr.next()) { |
|
814 threadToJavaThreadMap.put(thr.getThreadProxy(), thr); |
|
815 } |
|
816 } |
|
817 return (JavaThread) threadToJavaThreadMap.get(t); |
|
818 } |
|
819 |
|
820 private static JMenu createMenu(String name, char mnemonic, int mnemonicPos) { |
|
821 JMenu menu = new JMenu(name); |
|
822 menu.setMnemonic(mnemonic); |
|
823 menu.setDisplayedMnemonicIndex(mnemonicPos); |
|
824 return menu; |
|
825 } |
|
826 |
|
827 private static JMenuItem createMenuItem(String name, ActionListener l) { |
|
828 JMenuItem item = new JMenuItem(name); |
|
829 item.addActionListener(l); |
|
830 return item; |
|
831 } |
|
832 |
|
833 private static JMenuItem createMenuItemInternal(String name, ActionListener l, int accelerator, int modifiers) { |
|
834 JMenuItem item = createMenuItem(name, l); |
|
835 item.setAccelerator(KeyStroke.getKeyStroke(accelerator, modifiers)); |
|
836 return item; |
|
837 } |
|
838 |
|
839 private static JMenuItem createMenuItem(String name, ActionListener l, int accelerator) { |
|
840 return createMenuItemInternal(name, l, accelerator, 0); |
|
841 } |
|
842 |
|
843 private static JMenuItem createMenuItem(String name, ActionListener l, char mnemonic, int mnemonicPos) { |
|
844 JMenuItem item = createMenuItem(name, l); |
|
845 item.setMnemonic(mnemonic); |
|
846 item.setDisplayedMnemonicIndex(mnemonicPos); |
|
847 return item; |
|
848 } |
|
849 |
|
850 private static JMenuItem createMenuItem(String name, |
|
851 ActionListener l, |
|
852 int accelerator, |
|
853 int acceleratorMods, |
|
854 char mnemonic, |
|
855 int mnemonicPos) { |
|
856 JMenuItem item = createMenuItemInternal(name, l, accelerator, acceleratorMods); |
|
857 item.setMnemonic(mnemonic); |
|
858 item.setDisplayedMnemonicIndex(mnemonicPos); |
|
859 return item; |
|
860 } |
|
861 |
|
862 /** Punctuates the given string with \n's where necessary to not |
|
863 exceed the given number of characters per line. Strips |
|
864 extraneous whitespace. */ |
|
865 private static String formatMessage(String message, int charsPerLine) { |
|
866 StringBuffer buf = new StringBuffer(message.length()); |
|
867 StringTokenizer tokenizer = new StringTokenizer(message); |
|
868 int curLineLength = 0; |
|
869 while (tokenizer.hasMoreTokens()) { |
|
870 String tok = tokenizer.nextToken(); |
|
871 if (curLineLength + tok.length() > charsPerLine) { |
|
872 buf.append('\n'); |
|
873 curLineLength = 0; |
|
874 } else { |
|
875 if (curLineLength != 0) { |
|
876 buf.append(' '); |
|
877 ++curLineLength; |
|
878 } |
|
879 } |
|
880 buf.append(tok); |
|
881 curLineLength += tok.length(); |
|
882 } |
|
883 return buf.toString(); |
|
884 } |
|
885 |
|
886 private void setMenuItemsEnabled(java.util.List items, boolean enabled) { |
|
887 for (Iterator iter = items.iterator(); iter.hasNext(); ) { |
|
888 ((JMenuItem) iter.next()).setEnabled(enabled); |
|
889 } |
|
890 } |
|
891 |
|
892 private void showMessageDialog(final String message, final String title, final int jOptionPaneKind) { |
|
893 SwingUtilities.invokeLater(new Runnable() { |
|
894 public void run() { |
|
895 if (mdiMode) { |
|
896 JOptionPane.showInternalMessageDialog(desktop, message, title, jOptionPaneKind); |
|
897 } else { |
|
898 JOptionPane.showMessageDialog(null, message, title, jOptionPaneKind); |
|
899 } |
|
900 } |
|
901 }); |
|
902 } |
|
903 |
|
904 private FrameWrapper newFrame(String title) { |
|
905 if (mdiMode) { |
|
906 return new JInternalFrameWrapper(new JInternalFrame(title)); |
|
907 } else { |
|
908 return new JFrameWrapper(new JFrame(title)); |
|
909 } |
|
910 } |
|
911 |
|
912 private void addFrame(FrameWrapper frame) { |
|
913 if (mdiMode) { |
|
914 desktop.add(frame.getComponent()); |
|
915 } |
|
916 } |
|
917 |
|
918 private void removeFrame(FrameWrapper frame) { |
|
919 if (mdiMode) { |
|
920 desktop.remove(frame.getComponent()); |
|
921 desktop.invalidate(); |
|
922 desktop.validate(); |
|
923 desktop.repaint(); |
|
924 } |
|
925 // FIXME: do something when not in MDI mode |
|
926 } |
|
927 |
|
928 private Dimension getParentDimension(Component c) { |
|
929 if (mdiMode) { |
|
930 return desktop.getSize(); |
|
931 } else { |
|
932 return Toolkit.getDefaultToolkit().getScreenSize(); |
|
933 } |
|
934 } |
|
935 |
|
936 // Default editor implementation |
|
937 class DefaultEditor implements Editor { |
|
938 private DefaultEditorFactory factory; |
|
939 private FrameWrapper editorFrame; |
|
940 private String filename; |
|
941 private SourceCodePanel code; |
|
942 private boolean shown; |
|
943 private Object userData; |
|
944 |
|
945 public DefaultEditor(DefaultEditorFactory fact, String filename, final EditorCommands comm) { |
|
946 this.filename = filename; |
|
947 this.factory = fact; |
|
948 editorFrame = newFrame(filename); |
|
949 code = new SourceCodePanel(); |
|
950 // FIXME: when font changes, change font in editors as well |
|
951 code.setFont(fixedWidthFont); |
|
952 editorFrame.getContentPane().add(code); |
|
953 editorFrame.setClosable(true); |
|
954 editorFrame.setResizable(true); |
|
955 editorFrame.setClosingActionListener(new ActionListener() { |
|
956 public void actionPerformed(ActionEvent e) { |
|
957 comm.windowClosed(DefaultEditor.this); |
|
958 removeFrame(editorFrame); |
|
959 editorFrame.dispose(); |
|
960 factory.editorClosed(DefaultEditor.this); |
|
961 } |
|
962 }); |
|
963 editorFrame.setActivatedActionListener(new ActionListener() { |
|
964 public void actionPerformed(ActionEvent e) { |
|
965 factory.makeEditorCurrent(DefaultEditor.this); |
|
966 code.requestFocus(); |
|
967 } |
|
968 }); |
|
969 code.setEditorCommands(comm, this); |
|
970 } |
|
971 |
|
972 public boolean openFile() { return code.openFile(filename); } |
|
973 public String getSourceFileName() { return filename; } |
|
974 public int getCurrentLineNumber() { return code.getCurrentLineNumber(); } |
|
975 public void showLineNumber(int lineNo) { |
|
976 if (!shown) { |
|
977 addFrame(editorFrame); |
|
978 GraphicsUtilities.reshapeToAspectRatio(editorFrame.getComponent(), |
|
979 1.0f, |
|
980 0.85f, |
|
981 getParentDimension(editorFrame.getComponent())); |
|
982 editorFrame.setVisible(true); |
|
983 shown = true; |
|
984 } |
|
985 code.showLineNumber(lineNo); |
|
986 editorFrame.toFront(); |
|
987 } |
|
988 public void highlightLineNumber(int lineNo) { code.highlightLineNumber(lineNo); } |
|
989 public void showBreakpointAtLine(int lineNo) { code.showBreakpointAtLine(lineNo); } |
|
990 public boolean hasBreakpointAtLine(int lineNo) { return code.hasBreakpointAtLine(lineNo); } |
|
991 public void clearBreakpointAtLine(int lineNo) { code.clearBreakpointAtLine(lineNo); } |
|
992 public void clearBreakpoints() { code.clearBreakpoints(); } |
|
993 public void setUserData(Object o) { userData = o; } |
|
994 public Object getUserData() { return userData; } |
|
995 public void toFront() { editorFrame.toFront(); |
|
996 factory.makeEditorCurrent(this); } |
|
997 } |
|
998 |
|
999 class DefaultEditorFactory implements EditorFactory { |
|
1000 private LinkedList/*<Editor>*/ editors = new LinkedList(); |
|
1001 |
|
1002 public Editor openFile(String filename, EditorCommands commands) { |
|
1003 DefaultEditor editor = new DefaultEditor(this, filename, editorComm); |
|
1004 if (!editor.openFile()) { |
|
1005 return null; |
|
1006 } |
|
1007 return editor; |
|
1008 } |
|
1009 |
|
1010 public Editor getCurrentEditor() { |
|
1011 if (editors.isEmpty()) { |
|
1012 return null; |
|
1013 } |
|
1014 return (Editor) editors.getFirst(); |
|
1015 } |
|
1016 |
|
1017 void editorClosed(Editor editor) { |
|
1018 editors.remove(editor); |
|
1019 } |
|
1020 |
|
1021 void makeEditorCurrent(Editor editor) { |
|
1022 editors.remove(editor); |
|
1023 editors.addFirst(editor); |
|
1024 } |
|
1025 } |
|
1026 |
|
1027 // Helper class for loading .java files; show only those with |
|
1028 // correct file name which are also in the correct package |
|
1029 static class JavaFileFilter extends javax.swing.filechooser.FileFilter { |
|
1030 private String packageName; |
|
1031 private String fileName; |
|
1032 |
|
1033 JavaFileFilter(String packageName, String fileName) { |
|
1034 this.packageName = packageName; |
|
1035 this.fileName = fileName; |
|
1036 } |
|
1037 |
|
1038 public boolean accept(File f) { |
|
1039 if (f.isDirectory()) { |
|
1040 return true; |
|
1041 } |
|
1042 // This rejects most files |
|
1043 if (!f.getName().equals(fileName)) { |
|
1044 return false; |
|
1045 } |
|
1046 // Ensure selected file is in the correct package |
|
1047 PackageScanner scanner = new PackageScanner(); |
|
1048 String pkg = scanner.scan(f); |
|
1049 if (!pkg.equals(packageName)) { |
|
1050 return false; |
|
1051 } |
|
1052 return true; |
|
1053 } |
|
1054 |
|
1055 public String getDescription() { return "Java source files"; } |
|
1056 } |
|
1057 |
|
1058 // Auxiliary information used only for Java source files |
|
1059 static class JavaUserData { |
|
1060 private String packageName; // External format |
|
1061 private String sourceFileName; |
|
1062 |
|
1063 /** Source file name is equivalent to that found in the .java |
|
1064 file; i.e., not a full path */ |
|
1065 JavaUserData(String packageName, String sourceFileName) { |
|
1066 this.packageName = packageName; |
|
1067 this.sourceFileName = sourceFileName; |
|
1068 } |
|
1069 |
|
1070 String packageName() { return packageName; } |
|
1071 String sourceFileName() { return sourceFileName; } |
|
1072 } |
|
1073 |
|
1074 // Opens a source file. This makes it available for the setting of |
|
1075 // lazy breakpoints. |
|
1076 private void openSourceFile() { |
|
1077 JFileChooser chooser = new JFileChooser(); |
|
1078 chooser.setDialogTitle("Open source code file"); |
|
1079 chooser.setMultiSelectionEnabled(false); |
|
1080 if (chooser.showOpenDialog(null) != JFileChooser.APPROVE_OPTION) { |
|
1081 return; |
|
1082 } |
|
1083 File chosen = chooser.getSelectedFile(); |
|
1084 if (chosen == null) { |
|
1085 return; |
|
1086 } |
|
1087 |
|
1088 // See whether we have a Java source file. If so, derive a package |
|
1089 // name for it. |
|
1090 String path = chosen.getPath(); |
|
1091 String name = null; |
|
1092 JavaUserData data = null; |
|
1093 if (path.endsWith(".java")) { |
|
1094 PackageScanner scanner = new PackageScanner(); |
|
1095 String pkg = scanner.scan(chosen); |
|
1096 // Now knowing both the package name and file name, we can put |
|
1097 // this in the editor map and use it for setting breakpoints |
|
1098 // later |
|
1099 String fileName = chosen.getName(); |
|
1100 name = pkg + "." + fileName; |
|
1101 data = new JavaUserData(pkg, fileName); |
|
1102 } else { |
|
1103 // FIXME: need pathmap mechanism |
|
1104 name = path; |
|
1105 } |
|
1106 Editor editor = (Editor) editors.get(name); |
|
1107 if (editor == null) { |
|
1108 editor = editorFact.openFile(path, editorComm); |
|
1109 if (editor == null) { |
|
1110 showMessageDialog("Unable to open file \"" + path + "\" -- unexpected error.", |
|
1111 "Unable to open file", |
|
1112 JOptionPane.WARNING_MESSAGE); |
|
1113 return; |
|
1114 } |
|
1115 editors.put(name, editor); |
|
1116 if (data != null) { |
|
1117 editor.setUserData(data); |
|
1118 } |
|
1119 } else { |
|
1120 editor.toFront(); |
|
1121 } |
|
1122 editor.showLineNumber(1); |
|
1123 // Show breakpoints as well if we have any for this file |
|
1124 Set set = (Set) fileToBreakpointMap.get(editor.getSourceFileName()); |
|
1125 if (set != null) { |
|
1126 for (Iterator iter = set.iterator(); iter.hasNext(); ) { |
|
1127 editor.showBreakpointAtLine(((Integer) iter.next()).intValue()); |
|
1128 } |
|
1129 } |
|
1130 } |
|
1131 |
|
1132 // Package name may be null, in which case the file is assumed to be |
|
1133 // a C source file. Otherwise it is assumed to be a Java source file |
|
1134 // and certain filtering rules will be applied. |
|
1135 private void showLineNumber(String packageName, String fileName, int lineNumber) { |
|
1136 String name; |
|
1137 if (packageName == null) { |
|
1138 name = fileName; |
|
1139 } else { |
|
1140 name = packageName + "." + fileName; |
|
1141 } |
|
1142 Editor editor = (Editor) editors.get(name); |
|
1143 if (editor == null) { |
|
1144 // See whether file exists |
|
1145 File file = new File(fileName); |
|
1146 String realFileName = fileName; |
|
1147 if (!file.exists()) { |
|
1148 // User must specify path to file |
|
1149 JFileChooser chooser = new JFileChooser(); |
|
1150 chooser.setDialogTitle("Please locate " + fileName); |
|
1151 chooser.setMultiSelectionEnabled(false); |
|
1152 if (packageName != null) { |
|
1153 chooser.setFileFilter(new JavaFileFilter(packageName, fileName)); |
|
1154 } |
|
1155 int res = chooser.showOpenDialog(null); |
|
1156 if (res != JFileChooser.APPROVE_OPTION) { |
|
1157 // FIXME: show disassembly instead |
|
1158 return; |
|
1159 } |
|
1160 // FIXME: would like to infer more from the selection; i.e., |
|
1161 // a pathmap leading up to this file |
|
1162 File chosen = chooser.getSelectedFile(); |
|
1163 if (chosen == null) { |
|
1164 return; |
|
1165 } |
|
1166 realFileName = chosen.getPath(); |
|
1167 } |
|
1168 // Now instruct editor factory to open file |
|
1169 editor = editorFact.openFile(realFileName, editorComm); |
|
1170 if (editor == null) { |
|
1171 showMessageDialog("Unable to open file \"" + realFileName + "\" -- unexpected error.", |
|
1172 "Unable to open file", |
|
1173 JOptionPane.WARNING_MESSAGE); |
|
1174 return; |
|
1175 } |
|
1176 // Got an editor; put it in map |
|
1177 editors.put(name, editor); |
|
1178 // If Java source file, add additional information for later |
|
1179 if (packageName != null) { |
|
1180 editor.setUserData(new JavaUserData(packageName, fileName)); |
|
1181 } |
|
1182 } |
|
1183 // Got editor; show line |
|
1184 editor.showLineNumber(lineNumber); |
|
1185 editor.highlightLineNumber(lineNumber); |
|
1186 // Show breakpoints as well if we have any for this file |
|
1187 Set set = (Set) fileToBreakpointMap.get(editor.getSourceFileName()); |
|
1188 if (set != null) { |
|
1189 for (Iterator iter = set.iterator(); iter.hasNext(); ) { |
|
1190 editor.showBreakpointAtLine(((Integer) iter.next()).intValue()); |
|
1191 } |
|
1192 } |
|
1193 } |
|
1194 |
|
1195 // |
|
1196 // Suspend/resume |
|
1197 // |
|
1198 |
|
1199 private boolean isSuspended() { |
|
1200 return suspended; |
|
1201 } |
|
1202 |
|
1203 private synchronized void suspend() { |
|
1204 setMenuItemsEnabled(resumeDebugMenuItems, true); |
|
1205 setMenuItemsEnabled(suspendDebugMenuItems, false); |
|
1206 BugSpotAgent agent = getAgent(); |
|
1207 if (agent.canInteractWithJava() && !agent.isJavaSuspended()) { |
|
1208 agent.suspendJava(); |
|
1209 } |
|
1210 agent.suspend(); |
|
1211 // FIXME: call VM.getVM().fireVMSuspended() |
|
1212 resetCurrentThread(); |
|
1213 debugEventTimer.stop(); |
|
1214 suspended = true; |
|
1215 } |
|
1216 |
|
1217 private synchronized void resume() { |
|
1218 // Note: we don't wipe out the cached state like the |
|
1219 // sourceFileToLineNumberInfoMap since it is too expensive to |
|
1220 // recompute. Instead we recompute it if any DLLs are loaded or |
|
1221 // unloaded. |
|
1222 threadToJavaThreadMap = null; |
|
1223 setMenuItemsEnabled(resumeDebugMenuItems, false); |
|
1224 setMenuItemsEnabled(suspendDebugMenuItems, true); |
|
1225 registerPanel.clear(); |
|
1226 // FIXME: call VM.getVM().fireVMResumed() |
|
1227 BugSpotAgent agent = getAgent(); |
|
1228 agent.resume(); |
|
1229 if (agent.canInteractWithJava()) { |
|
1230 if (agent.isJavaSuspended()) { |
|
1231 agent.resumeJava(); |
|
1232 } |
|
1233 if (javaEventPending) { |
|
1234 javaEventPending = false; |
|
1235 // Clear it out before resuming polling for events |
|
1236 agent.javaEventContinue(); |
|
1237 } |
|
1238 } |
|
1239 agent.enableJavaInteraction(); |
|
1240 suspended = false; |
|
1241 debugEventTimer.start(); |
|
1242 } |
|
1243 |
|
1244 // |
|
1245 // Breakpoints |
|
1246 // |
|
1247 |
|
1248 private synchronized BreakpointResult handleBreakpointToggle(Editor editor, int lineNumber) { |
|
1249 // Currently we only use user data in editors to indicate Java |
|
1250 // source files. If this changes then this code will need to |
|
1251 // change. |
|
1252 JavaUserData data = (JavaUserData) editor.getUserData(); |
|
1253 String filename = editor.getSourceFileName(); |
|
1254 if (data == null) { |
|
1255 // C/C++ code |
|
1256 // FIXME: as noted above in EditorCommands.toggleBreakpointAtLine, |
|
1257 // this needs more work to handle "lazy" breakpoints in files |
|
1258 // which we don't know about in the debug information yet |
|
1259 CDebugger dbg = getCDebugger(); |
|
1260 ProcessControl prctl = dbg.getProcessControl(); |
|
1261 if (prctl == null) { |
|
1262 return new BreakpointResult(false, false, 0, "Process control not enabled"); |
|
1263 } |
|
1264 boolean mustSuspendAndResume = (!prctl.isSuspended()); |
|
1265 try { |
|
1266 if (mustSuspendAndResume) { |
|
1267 prctl.suspend(); |
|
1268 } |
|
1269 // Search debug info for all DSOs |
|
1270 LineNumberInfo info = getLineNumberInfo(filename, lineNumber); |
|
1271 if (info != null) { |
|
1272 Set bpset = (Set) fileToBreakpointMap.get(filename); |
|
1273 if (bpset == null) { |
|
1274 bpset = new HashSet(); |
|
1275 fileToBreakpointMap.put(filename, bpset); |
|
1276 } |
|
1277 Integer key = new Integer(info.getLineNumber()); |
|
1278 if (bpset.contains(key)) { |
|
1279 // Clear breakpoint at this line's PC |
|
1280 prctl.clearBreakpoint(info.getStartPC()); |
|
1281 bpset.remove(key); |
|
1282 return new BreakpointResult(true, false, info.getLineNumber()); |
|
1283 } else { |
|
1284 // Set breakpoint at this line's PC |
|
1285 System.err.println("Setting breakpoint at PC " + info.getStartPC()); |
|
1286 prctl.setBreakpoint(info.getStartPC()); |
|
1287 bpset.add(key); |
|
1288 return new BreakpointResult(true, true, info.getLineNumber()); |
|
1289 } |
|
1290 } else { |
|
1291 return new BreakpointResult(false, false, 0, "No debug information for this source file and line"); |
|
1292 } |
|
1293 } finally { |
|
1294 if (mustSuspendAndResume) { |
|
1295 prctl.resume(); |
|
1296 } |
|
1297 } |
|
1298 } else { |
|
1299 BugSpotAgent agent = getAgent(); |
|
1300 if (!agent.canInteractWithJava()) { |
|
1301 String why; |
|
1302 if (agent.isJavaInteractionDisabled()) { |
|
1303 why = "Can not toggle Java breakpoints while stopped because\nof C/C++ debug events (breakpoints, single-stepping)"; |
|
1304 } else { |
|
1305 why = "Could not talk to SA's JVMDI module to enable Java\nprogramming language breakpoints (run with -Xdebug -Xrunsa)"; |
|
1306 } |
|
1307 return new BreakpointResult(false, false, 0, why); |
|
1308 } |
|
1309 Set bpset = (Set) fileToBreakpointMap.get(filename); |
|
1310 if (bpset == null) { |
|
1311 bpset = new HashSet(); |
|
1312 fileToBreakpointMap.put(filename, bpset); |
|
1313 } |
|
1314 boolean mustResumeAndSuspend = isSuspended(); |
|
1315 try { |
|
1316 if (mustResumeAndSuspend) { |
|
1317 agent.resume(); |
|
1318 } |
|
1319 ServiceabilityAgentJVMDIModule.BreakpointToggleResult res = |
|
1320 getAgent().toggleJavaBreakpoint(data.sourceFileName(), |
|
1321 data.packageName(), |
|
1322 lineNumber); |
|
1323 if (res.getSuccess()) { |
|
1324 Integer key = new Integer(res.getLineNumber()); |
|
1325 boolean addRemRes = false; |
|
1326 if (res.getWasSet()) { |
|
1327 addRemRes = bpset.add(key); |
|
1328 System.err.println("Setting breakpoint at " + res.getMethodName() + res.getMethodSignature() + |
|
1329 ", bci " + res.getBCI() + ", line " + res.getLineNumber()); |
|
1330 } else { |
|
1331 addRemRes = bpset.remove(key); |
|
1332 System.err.println("Clearing breakpoint at " + res.getMethodName() + res.getMethodSignature() + |
|
1333 ", bci " + res.getBCI() + ", line " + res.getLineNumber()); |
|
1334 } |
|
1335 if (Assert.ASSERTS_ENABLED) { |
|
1336 Assert.that(addRemRes, "Inconsistent Java breakpoint state with respect to target process"); |
|
1337 } |
|
1338 return new BreakpointResult(true, res.getWasSet(), res.getLineNumber()); |
|
1339 } else { |
|
1340 return new BreakpointResult(false, false, 0, res.getErrMsg()); |
|
1341 } |
|
1342 } finally { |
|
1343 if (mustResumeAndSuspend) { |
|
1344 agent.suspend(); |
|
1345 resetCurrentThread(); |
|
1346 } |
|
1347 } |
|
1348 } |
|
1349 } |
|
1350 |
|
1351 // Must call only when suspended |
|
1352 private LineNumberInfo getLineNumberInfo(String filename, int lineNumber) { |
|
1353 Map map = getSourceFileToLineNumberInfoMap(); |
|
1354 java.util.List infos = (java.util.List) map.get(filename); |
|
1355 if (infos == null) { |
|
1356 return null; |
|
1357 } |
|
1358 // Binary search for line number |
|
1359 return searchLineNumbers(infos, lineNumber, 0, infos.size()); |
|
1360 } |
|
1361 |
|
1362 // Must call only when suspended |
|
1363 private Map getSourceFileToLineNumberInfoMap() { |
|
1364 if (sourceFileToLineNumberInfoMap == null) { |
|
1365 // Build from debug info |
|
1366 java.util.List loadObjects = getCDebugger().getLoadObjectList(); |
|
1367 final Map map = new HashMap(); |
|
1368 for (Iterator iter = loadObjects.iterator(); iter.hasNext(); ) { |
|
1369 LoadObject lo = (LoadObject) iter.next(); |
|
1370 CDebugInfoDataBase db = lo.getDebugInfoDataBase(); |
|
1371 if (db != null) { |
|
1372 db.iterate(new LineNumberVisitor() { |
|
1373 public void doLineNumber(LineNumberInfo info) { |
|
1374 String name = info.getSourceFileName(); |
|
1375 if (name != null) { |
|
1376 java.util.List val = (java.util.List) map.get(name); |
|
1377 if (val == null) { |
|
1378 val = new ArrayList(); |
|
1379 map.put(name, val); |
|
1380 } |
|
1381 val.add(info); |
|
1382 } |
|
1383 } |
|
1384 }); |
|
1385 } |
|
1386 } |
|
1387 // Sort all lists |
|
1388 for (Iterator iter = map.values().iterator(); iter.hasNext(); ) { |
|
1389 java.util.List list = (java.util.List) iter.next(); |
|
1390 Collections.sort(list, new Comparator() { |
|
1391 public int compare(Object o1, Object o2) { |
|
1392 LineNumberInfo l1 = (LineNumberInfo) o1; |
|
1393 LineNumberInfo l2 = (LineNumberInfo) o2; |
|
1394 int n1 = l1.getLineNumber(); |
|
1395 int n2 = l2.getLineNumber(); |
|
1396 if (n1 < n2) return -1; |
|
1397 if (n1 == n2) return 0; |
|
1398 return 1; |
|
1399 } |
|
1400 }); |
|
1401 } |
|
1402 sourceFileToLineNumberInfoMap = map; |
|
1403 } |
|
1404 return sourceFileToLineNumberInfoMap; |
|
1405 } |
|
1406 |
|
1407 private LineNumberInfo searchLineNumbers(java.util.List infoList, int lineNo, int lowIdx, int highIdx) { |
|
1408 if (highIdx < lowIdx) return null; |
|
1409 if (lowIdx == highIdx) { |
|
1410 // Base case: see whether start PC is less than or equal to addr |
|
1411 if (checkLineNumber(infoList, lineNo, lowIdx)) { |
|
1412 return (LineNumberInfo) infoList.get(lowIdx); |
|
1413 } else { |
|
1414 return null; |
|
1415 } |
|
1416 } else if (lowIdx == highIdx - 1) { |
|
1417 if (checkLineNumber(infoList, lineNo, lowIdx)) { |
|
1418 return (LineNumberInfo) infoList.get(lowIdx); |
|
1419 } else if (checkLineNumber(infoList, lineNo, highIdx)) { |
|
1420 return (LineNumberInfo) infoList.get(highIdx); |
|
1421 } else { |
|
1422 return null; |
|
1423 } |
|
1424 } |
|
1425 int midIdx = (lowIdx + highIdx) >> 1; |
|
1426 LineNumberInfo info = (LineNumberInfo) infoList.get(midIdx); |
|
1427 if (lineNo < info.getLineNumber()) { |
|
1428 // Always move search down |
|
1429 return searchLineNumbers(infoList, lineNo, lowIdx, midIdx); |
|
1430 } else if (lineNo == info.getLineNumber()) { |
|
1431 return info; |
|
1432 } else { |
|
1433 // Move search up |
|
1434 return searchLineNumbers(infoList, lineNo, midIdx, highIdx); |
|
1435 } |
|
1436 } |
|
1437 |
|
1438 private boolean checkLineNumber(java.util.List infoList, int lineNo, int idx) { |
|
1439 LineNumberInfo info = (LineNumberInfo) infoList.get(idx); |
|
1440 return (info.getLineNumber() >= lineNo); |
|
1441 } |
|
1442 |
|
1443 // |
|
1444 // Debug events |
|
1445 // |
|
1446 |
|
1447 private synchronized void pollForDebugEvent() { |
|
1448 ProcessControl prctl = getCDebugger().getProcessControl(); |
|
1449 if (prctl == null) { |
|
1450 return; |
|
1451 } |
|
1452 DebugEvent ev = prctl.debugEventPoll(); |
|
1453 if (ev != null) { |
|
1454 DebugEvent.Type t = ev.getType(); |
|
1455 if (t == DebugEvent.Type.LOADOBJECT_LOAD || |
|
1456 t == DebugEvent.Type.LOADOBJECT_UNLOAD) { |
|
1457 // Conservatively clear cached debug info state |
|
1458 sourceFileToLineNumberInfoMap = null; |
|
1459 // FIXME: would be very useful to have "stop on load/unload" |
|
1460 // events |
|
1461 // FIXME: must do work at these events to implement lazy |
|
1462 // breakpoints |
|
1463 prctl.debugEventContinue(); |
|
1464 } else if (t == DebugEvent.Type.BREAKPOINT) { |
|
1465 // Note: Visual C++ only notifies on breakpoints it doesn't |
|
1466 // know about |
|
1467 |
|
1468 // FIXME: put back test |
|
1469 // if (!prctl.isBreakpointSet(ev.getPC())) { |
|
1470 showMessageDialog("Breakpoint reached at PC " + ev.getPC(), |
|
1471 "Breakpoint reached", |
|
1472 JOptionPane.INFORMATION_MESSAGE); |
|
1473 // } |
|
1474 agent.disableJavaInteraction(); |
|
1475 suspend(); |
|
1476 prctl.debugEventContinue(); |
|
1477 } else if (t == DebugEvent.Type.SINGLE_STEP) { |
|
1478 agent.disableJavaInteraction(); |
|
1479 suspend(); |
|
1480 prctl.debugEventContinue(); |
|
1481 } else if (t == DebugEvent.Type.ACCESS_VIOLATION) { |
|
1482 showMessageDialog("Access violation attempting to " + |
|
1483 (ev.getWasWrite() ? "write" : "read") + |
|
1484 " address " + ev.getAddress() + |
|
1485 " at PC " + ev.getPC(), |
|
1486 "Access Violation", |
|
1487 JOptionPane.WARNING_MESSAGE); |
|
1488 agent.disableJavaInteraction(); |
|
1489 suspend(); |
|
1490 prctl.debugEventContinue(); |
|
1491 } else { |
|
1492 String info = "Unknown debug event encountered"; |
|
1493 if (ev.getUnknownEventDetail() != null) { |
|
1494 info = info + ": " + ev.getUnknownEventDetail(); |
|
1495 } |
|
1496 showMessageDialog(info, "Unknown debug event", JOptionPane.INFORMATION_MESSAGE); |
|
1497 suspend(); |
|
1498 prctl.debugEventContinue(); |
|
1499 } |
|
1500 return; |
|
1501 } |
|
1502 |
|
1503 // No C++ debug event; poll for Java debug event |
|
1504 if (getAgent().canInteractWithJava()) { |
|
1505 if (!javaEventPending) { |
|
1506 if (getAgent().javaEventPending()) { |
|
1507 suspend(); |
|
1508 // This does a lot of work and we want to have the page |
|
1509 // cache available to us as it runs |
|
1510 sun.jvm.hotspot.livejvm.Event jev = getAgent().javaEventPoll(); |
|
1511 if (jev != null) { |
|
1512 javaEventPending = true; |
|
1513 if (jev.getType() == sun.jvm.hotspot.livejvm.Event.Type.BREAKPOINT) { |
|
1514 BreakpointEvent bpev = (BreakpointEvent) jev; |
|
1515 showMessageDialog("Breakpoint reached in method\n" + |
|
1516 bpev.methodID().method().externalNameAndSignature() + |
|
1517 ",\nbci " + bpev.location(), |
|
1518 "Breakpoint reached", |
|
1519 JOptionPane.INFORMATION_MESSAGE); |
|
1520 } else if (jev.getType() == sun.jvm.hotspot.livejvm.Event.Type.EXCEPTION) { |
|
1521 ExceptionEvent exev = (ExceptionEvent) jev; |
|
1522 showMessageDialog(exev.exception().getKlass().getName().asString() + |
|
1523 "\nthrown in method\n" + |
|
1524 exev.methodID().method().externalNameAndSignature() + |
|
1525 "\nat BCI " + exev.location(), |
|
1526 "Exception thrown", |
|
1527 JOptionPane.INFORMATION_MESSAGE); |
|
1528 } else { |
|
1529 Assert.that(false, "Should not reach here"); |
|
1530 } |
|
1531 } |
|
1532 } |
|
1533 } |
|
1534 } |
|
1535 } |
|
1536 } |
|