linux命令行 jdb,JDB调试用法指南

uvm.gif

JDB Debugging Tutorial

By:

For:

Last Modified: 18 October 2005

Part 1

Contents

This tutorial is designed to show how to use JDB (the Java

Debugger) to find and fix errors in Java programs. It assumes no

prior knowledge of debugging tools or techniques, and so should be most

useful to programming beginners. Any programmer who has not had

experience with a command-line debugger (or with any other debugger,

for that matter) may find it useful, however.

The tutorial is split into two parts. Part 1 (this document)

describes the basics of debugging and includes a lot of conceptual

detail behind the most important JDB commands. covers a wider range of commands, but

offers less detail for the rationale behind them.

Any debugger, whether it be JDB running at the command prompt or a

graphical debugger running in a fancy development environment, is

designed to let you do two basic things:

Trace Execution. You can execute one line of your program at a

time, so you'll be able to see exactly what code is executing and find

exactly which lines are causing problems.

Watch Variables. Look at what values all your variables have as

the code executes so you can see which ones have incorrect values and

where in the code they got those values.

A variety of tools inside JDB can help accomplish these things.

That's what this tutorial explains.

Before we begin, it's important to distinguish between the two

kinds of errors you can have in a program. Compiler errors occur when

you first compile your source code, and identify basic syntactic

mistakes. Believe it or not, they're the easy ones to fix. If javac

says Program.java:15: ';' expected you know that

somewhere on or around line 15 there's a problem. Even if it's not

really a missing semicolon at least you know where to look.

The errors that are hard to fix are the kind that occur as your

program is running. You get it to compile without any errors, but

when it runs it doesn't behave as you expect. Those are called

"runtime" errors, and the debugger exists to help correct them.

Let's start by looking at an extremely simple program that has a

very simple bug, just to get the hang of this. Mosquito is a

program that's supposed to get two numbers from the user, multiply

them, and display the result. This program "compiles clean" (no

errors, no warnings) but it does not behave as expected. Here's an

example of what it does:

Prompt for the numbers:

mosquito_number1.jpg

mosquito_number2.jpg

Display the result:

mosquito_result.jpg

Sample Run of Mosquito

Clearly something is wrong, even though it compiled without any

trouble. First, the numbers it's supposedly multiplying aren't the

numbers I entered. Second, the product of 2 and 0 is not 2.

The code for Mosquito is below. Copy and paste it into a new

file, compile it, and run it to see how it works. You can probably

identify the bugs just by looking at the code, but leave them there.

The point is to see how the debugger can help.

/** Mosquito: Buggy Program #1

* Prompts the user for two numbers and multiplies them.

* Demonstrating Basic Debugger Tools

* Debugger Tutorial, 6 February 2004

*/

import javax.swing.JOptionPane;

/**

* @author Benjamin.Fenster@uvm.edu

* @version 19 January 2004

*/

class Mosquito {

public static void main(String[] args) {

int num1 = 0;

int num2 = 0;

int result = 0;

// Get two numbers from the user

num1 = getIntegerInput("Enter the first number", "Mosquito");

num1 = getIntegerInput("Enter the second number", "Mosquito");

// Calculate the product of those two numbers

result = num1 + num2;

// Provide the result for the user

JOptionPane.showMessageDialog(null,"" + num1 + " x " + num2 + " = " + result, "Mosquito", JOptionPane.INFORMATION_MESSAGE);

// And we're done!

System.exit(0);

}

/** Prompts the user for a number and returns the result as an integer.

* Pre: 'message' is the message to be displayed in the dialog box

* 'title' is the title of the dialog box

* Post: The value entered by the user is returned as an integer.

*/

public static int getIntegerInput(String message, String title) {

String temp = JOptionPane.showInputDialog(null, message, title, JOptionPane.QUESTION_MESSAGE);

return Integer.parseInt(temp);

}

}

First, compile your program with javac -g Mosquito.java.

The "-g" option tells the compiler to include extra information the

debugger is going to use. If you forget and compile the program

normally, some debugger functions will still work but others

(like looking at variables' values) will not.

Next, instead of running your program with the java

command, start the debugger by typing jdb Mosquito.

H:\>javac -g Mosquito.java

H:\>jdb Mosquito

Initializing jdb ...

>

Notice that the DOS prompt (H:\> in this example)

is gone, and just a > prompt is left. This is the JDB

prompt where you'll type debugging commands. For example, type

help to get a list of possible commands. Some of the

commands listed will make absolutely no sense, but others will sound

perfectly reasonable. We'll explore the most important ones in this

tutorial.

The whole idea behind the debugger is to execute only part of your

program at a time. Otherwise you'd just run it the usual way. The

first thing we need to do, then, is tell the debugger to "break" (stop

executing and wait for your command) at some point in the program.

That, logically enough, is called a "breakpoint."

You can put a breakpiont on any executable line of Java code (so

not a comment or something like an import statement, but any line of

code that actually does something). To start, let's just put a

breakpoint right at the beginning of main() by typing stop in

Mosquito.main.

>stop in Mosquito.main

Deferring breakpoint Mosquito.main.

It will be set after the class is loaded.

>

In so many words, the debugger has agreed to set that breakpoint

just as soon as you start running the program. Without further ado,

then, let's run the program so it can get on with it. The command,

shockingly, is run.

>run

run Mosquito

Set uncaught java.lang.Throwable

Set deferred uncaught java.lang.Throwable

>

VM Started: Set deferred breakpoint Mosquito.main

Breakpoint hit: "thread=main", Mosquito.main(), line=14 bci=0

14 int num1 = 0;

main[1]

Yep, that's a lot of junk to interpret. You can ignore the two

lines about Set uncaught java.lang.Throwable. There's a

> prompt next, but you don't get a chance to type

anything there so don't worry about that either.

Next, after "VM Started," the debugger confirms that it has set a

breakpoint in main() like you asked. That's simple enough, although

really not very exciting.

Finally, then, we get to the point where something interesting

happens: the breakpoint is hit. The useful part of that line says

Mosquito.main(), line 14. That's telling you which

statement in your program will execute next. It even shows you the

statement itself on the next line:

14              int

num1 = 0;

Now, just to make things confusing, the prompt changes again. Gone

is the recognizable > prompt and in its place you get

main[1]. That might at least have made sense if "main"

referred to the method currently running, but it doesn't. There is a

reason for it, but for now don't worry about it. Just note that

you've got a new prompt.

We can see just looking at it that int num1 = 0 is a

pretty boring statement. Let's just execute it and get it over with.

How? Type next.

main[1] next

>

Step completed: main[1] "thread=main", Mosquito.main(), line=15 bci=2

15 int num2 = 0;

You've asked jdb to execute one line of code and move on to the

next. You again see the next line of code to execute. That's

important. You're looking at the next line of code to

execute. That will confuse you as you debug your own (more complex)

programs, since it may seem like it's showing the code it just

executed. It's always showing what will happen next.

Fine, so that means num1 is now officially declared and should have

a value of zero. Since that's pretty dull go ahead and execute the

next two lines too:

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=16 bci=4

16 int result = 0;

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=19 bci=6

19 num1 = getIntegerInput("Enter the first number", "Mosquito

");

Now we see line 19 ready to execute, and it seems to do something a

little more interesting than just declaring a variable. Before you

execute an interesting line of code, make a prediction about what it

will do. Then, when it's done running, make sure your prediction was

right. In this case, I predict, "I'll type 34, so num1 will be 34

when I'm done."

When you type next, two things happen. First, that

funny > prompt comes up again with a blinking cursor.

Then the JOptionPane shows up on the screen. Just to annoy you,

however, if you start typing it'll show up at the >

prompt, not in the JOptionPane. Use Alt+Tab or click the JOptionPane

to select it and then type your number. If you don't see the

JOptionPane at all, use Alt+Tab to find it (it won't be in

your taskbar).

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=20 bci=14

20 num1 = getIntegerInput("Enter the second number", "Mosquit

o");

main[1]

Great. So the statement executed. Now it's time to check whether

your prediction ("num1 will be 34") is correct. Ask what the value of

num1 is by using the print command:

main[1] print num1

num1 = 34

Finally we find a command with output that doesn't require

clarification! What's more, the results match our prediction so we

know there's nothing wrong with the line of code that just

executed.

Here's the secret to debugging a program. Every

time you execute a statement, make a prediction about what it will

do. Will a variable's value change? Will you run a method? Will the

'if' statement happen or not? Then, when you execute it, see whether

your prediction was right or not.

If your prediction was right, then the program did exactly what you

thought it should, and there's no point wasting time examining that

statement. If everything in your program worked the way you

thought it did you wouldn't have a bug! On the other hand, if your

prediction is wrong, you've just found a problem that needs to be solved.

My prediction about the next line of code (line 20) is that num1

will still be 34 and num2 will be 2, because I intend to enter 2 when

prompted.

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=23 bci=22

23 result = num1 + num2;

main[1] print num2

num2 = 0

Note that this time the JOptionPane gets focus right away, so you

don't have to click on it. The first time you use a JOptionPane it

won't get focus at first. Every time after that it will. That'll

annoy you to death.

Clearly, we see that the prediction was wrong. num2 is still 0.

Now, how do you figure out what did happen? Start by taking

a look at all the variables. No, don't just print them

all. Use the locals command instead:

main[1] locals

Method arguments:

args = instance of java.lang.String[0] (id=1243)

Local variables:

num1 = 2

num2 = 0

result = 0

You get separate lists of any arguments to the current method

(which is just 'args' in this case since we're in main()) and ordinary

variables just declared somewhere inside the method. We can see

immediately that the 2 I had wanted to end up in num2 actually ended

up in num1.

Well, we know we just executed a problematic line of code. You may

find it convenient to see where that line is in context before going

back to your editor to make any changes. Try the list

command:

main[1] list

19 num1 = getIntegerInput("Enter the first number", "Mosquito

");

20 num1 = getIntegerInput("Enter the second number", "Mosquit

o");

21

22 // Calculate the product of those two numbers

23 => result = num1 + num2;

24

25 // Provide the result for the user

26 JOptionPane.showMessageDialog(null,"" + num1 + " x " + num

2 + " = " + result, "Mosquito", JOptionPane.INFORMATION_MESSAGE);

27

28 // And we're done!

Note the => arrow pointing to the next statement to

be executed. Now go back to the editor and change it to num1 =

.... Recompile the program and run it again without

the debugger. Assume your change worked and test it. If it doesn't

work, come back and try again with the debugger.

Don't forget to type exit to quit jdb.

We have to add one little twist when it comes to running methods.

Notice that the getIntegerInput() function just executed from top to

bottom without waiting for you to keep typing "next" for each

statement. What if your bug were in that method? Let's debug the

program again, this time so that we get to step through each statement

in getIntegerInput().

This time, set a breakpoint just before it calls getIntegerInput()

on line 19 by typing stop on Mosquito:19. Then

run the program and see the breakpoint come up.

> stop on Mosquito:19

Deferring breakpoint Mosquito:19.

It will be set after the class is loaded.

> run

run Mosquito

Set uncaught java.lang.Throwable

Set deferred uncaught java.lang.Throwable

>

VM Started: Set deferred breakpoint Mosquito:19

Breakpoint hit: "thread=main", Mosquito.main(), line=19 bci=6

19 num1 = getIntegerInput("Enter the first number", "Mosquito

");

Now, suppose you've been stepping through some big program and come

to this line where getIntegerInput() function is called. You know

there may be a problem inside getIntegerInput() so you'd like to see

those statements executed individually. If you type next

it'll just call the function, do everything it says to do, and then

come back. Instead, type step.

main[1] step

>

Step completed: "thread=main", Mosquito.getIntegerInput(), line=39 bci=0

39 String temp = JOptionPane.showInputDialog(null, message, t

itle, JOptionPane.QUESTION_MESSAGE);

Sure enough, we're now inside the method. Use the list

to get some context if you want. Now you can go back to using

next and the other commands we've seen to explore this

method. If you ever step into a method and then realize it was a

mistake, use step up to execute the rest of the method and

stop when it's done. (It steps UP to the calling function.)

These features (setting breakpoints, stepping over one line of

code, stepping into a method, and looking at variables' values) are

the basic features you should expect to find in any debugger. They're

also the ones you'll use most often. There are other debugger

commands that aren't necessarily "mainstream" but which are exactly

the right tools for particular situations

If you've mastered the basics of debugging, move on to and see more of what JDB can do for

you. If you're still getting used to these basic commands, don't just

dive in head first to the advanced commands. Wait until you've gotten

the basics figured out and then come back.

Part 2

Contents

Go back to of this tutorial, if you

haven't already, and compile the Mosquito program. If

you're new to using the debugger, you may want to walk through the

entire first part before proceeding with these new commands.

You may have noticed that there's a lot of repetition in a

command-line debugger. Every time you want to check up on a variable

you have to type print variable, for example. If you're

watching a bunch of variables over lots of lines of code, that can be

a huge hassle.

Fortunately, JDB offers a feature called a monitor,

which is just a command that gets executed every time the debugger

stops. That means every time you hit a breakpoint and every time you

step over a line of code (or into a method) your command will get

run.

For example, type monitor print num and then try

executing some statements with the next command. The

print num command gets executed automatically each time

the program stops:

> run

run Mosquito

Set uncaught java.lang.Throwable

Set deferred uncaught java.lang.Throwable

>

VM Started: Set deferred breakpoint Mosquito.main

Breakpoint hit: "thread=main", Mosquito.main(), line=14 bci=0

14 int num1 = 0;

main[1] monitor print num1

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=15 bci=2

15 int num2 = 0;

main[1] num1 = 0

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=16 bci=4

16 int result = 0;

main[1] num1 = 0

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=19 bci=6

19 num1 = getIntegerInput("Enter the first number", "Mosquito

");

main[1] num1 = 0

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=20 bci=14

20 num1 = getIntegerInput("Enter the second number", "Mosqui

o");

main[1] num1 = 34

main[1] next

>

Step completed: "thread=main", Mosquito.main(), line=23 bci=22

23 result = num1 + num2;

main[1] num1 = 2

Note that the num1 = ... outputs appear next to the

main[1] prompt. That's just where they appear; it's not

anything I typed.

You can monitor any command you like (you might monitor

list, for example, if you find the single line of source

code isn't helpful enough). You can just type monitor by

itself to see what monitors you have right now, and use unmonitor

# with the number listed to turn it off:

main[1] monitor

1: print num1

main[1] unmonitor 1

Unmonitoring 1: print num1

Being able to have a command execute automatically is nice, but is

still insufficient if you don't even know which region of code is

responsible for "breaking" a variable.

Consider another program called Wasp. This one doesn't really have

a bug, per se. It doesn't even have a clear purpose. It constructs a

Wasp object, and then at some point in the future (in the middle of

the update() method after 100 iterations of a loop) it updates a class

variable. We'll suppose this particular change to the variable is not

one we expect.

Wasp.java

/** Wasp: Buggy Program #2

* Mutates a simple state variable

* Demonstrating Watchpoints

* Debugger Tutorial, 6 February 2004

*/

/** Wasp

* @author Benjamin Fenster

* @version 23 January 2004

*/

class Wasp {

public int state;

public Wasp() {

state = 5;

}

public static void main(String[] args) {

Wasp w = new Wasp();

w.update();

}

public void update() {

for (int i = 0; i < 1000; i++) {

if (i == 100) {

System.out.println(state);

state = 7;

}

}

}

}

This may seem a little contrived, but the idea that a variable

would get messed up in the middle of a huge loop is entirely

realistic. It would also be utterly painful to have to do

next 100 times every single time you wanted to test it in

the debugger.

Start JDB with the Wasp program ( jdb Wasp ) and tell it

to watch the state variable by typing the watch

Wasp.state command. Then run the program (you do not

need to set any breakpoints: that's the whole point of this).

H:\>jdb Wasp

Initializing jdb ...

> watch Wasp.state

Deferring watch modification of Wasp.state.

It will be set after the class is loaded.

> run

run Wasp

>

VM Started: Set deferred watch modification of Wasp.state

Field (Wasp.state) is 0, will be 5: "thread=main", Wasp.(), line=16 bci=6

16 state = 5;

main[1]

Just like when we set a breakpoint upon first starting the

debugger, it now defers setting the watch. Upon typing run

it starts running and doesn't stop until just before the

variable gets changed.

That's an important thing to understand. Just as a breakpoint

causes the debugger to stop before executing the statement on

that line, a watch causes the debugger to stop before the

variable is modified. The language in the output makes this clear:

Field (Wasp.state) is 0, will be 5.

Use the list command (and any others you may find

helpful) to determine where you are in the code and whether this

particular change to the variable is important or not.

Already in the output from when the watch was hit we can see that

it's in Wasp.() (where is just

the constructor). This is a change I expect, so I want to keep going.

Type cont to continue execution. (If you accidentally type

run, you'll just get an error telling you to use

cont.)

main[1] cont

5

>

Field (Wasp.state) is 5, will be 7: "thread=main", Wasp.update(), line=28 bci=24

28 state = 7;

Now, perhaps, you'd be surprised to see state changing

in the update() method. You could again use

list and print to learn more:

main[1] list

24 public void update() {

25 for (int i = 0; i < 1000; i++) {

26 if (i == 100) {

27 System.out.println(state);

28 => state = 7;

29 }

30 }

31 }

32 }

33

main[1] print i

i = 100

main[1]

Of course, since this program has no particular purpose it would be

silly to talk about what you might infer from this information, but

when looking at a real piece of code you'd presumably find something

askew that has caused state to change at this unexpected

point in the code.

For all JDB's great features, there are a few key areas it's not

very strong. For example, it would be nice to have it print out the

entire call stack (i.e. the list of functions currently executing:

main() called func1() called func2() called...) so you could see how

you got to where you are (particularly if you call the same method

from multiple locations. JDB doesn't offer such a feature, but other

debuggers definitely do.

The power of JDB is largely its ubiquity. Everywhere there's a

Java compiler there ought to be JDB, so even if you don't have access

to a full-featured development environment you can still use some

powerful debugging tools. A development environment like

(which is free) will contain a debugger that does everything JDB does,

and usually more.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值