W2601 Debugging First Steps

From Coder Merlin
Revision as of 00:04, 17 February 2019 by Chukwuemeka-tinashe (talk | contribs) (Created page with "DRAFT ICON = Debugging - First Steps = == Research == [https://en.wikipedia.org/wiki/Debugging Debugging] [https://en.wikipedia.org/wiki/Call_stack C...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Within these castle walls be forged Mavens of Computer Science ...
— Merlin, The Coder

DRAFT ICON

Debugging - First Steps[edit]

Research[edit]

Debugging Call Stack

Terminology[edit]

This tutorial is meant to get you used to using the LLVM debugger, lldb. As you read through the first part of the tutorial, you are not expected to remember everything -- there is a reference list at the end of this tutorial, and is also contained on the LLDB command summary page. This tutorial will guide you through the process of using those commands.

Some terminology:

  • lldb is a command-line debugger

What is Debugging?[edit]

A debugger is a utility program that allows you to run a program under development while controlling its execution and examining the internal values of variables. We think of a program running "inside" a debugger. The debugger allows us to control the execution of the program by pausing its execution and then resuming it. While paused, we can find out where we are in the program, what values variables have, reset the values of variables, etc. If a program crashes, the debugger can tell you exactly where the program crashed.

Note that by definition, a debugger cannot help us find compile-time errors, because these occur prior to the successful production of a program's binary representation. It is only after we successfully compile that we are able to use a debugger.

All computer scientists should learn the basics of debugging and how to use a debugger. It will save you literally hours of time when finding and fixing problems in your programs. The few minutes of investment you put into learning how to use a debugger will pay off tremendously in a matter of weeks. Work smart!

Sample Program[edit]

Use emacs to create the following sample program. Save it as "main.swift".

func division(dividend:Int, divisor:Int) -> Int {
    let quotient =  dividend / divisor
    return quotient
}

// This program will print the quotient of a division operation
func main() {
    var dividend : Int = 0
    var divisor : Int = 0

    dividend = dividend + 114
    divisor = divisor * 2

    let quotient = division(dividend:dividend, divisor:divisor)
    print("The quotient is: ", quotient)
}

// Invoke main
main()

Put emacs to sleep; compile and execute the program.

swiftc main.swift
./main

What happens?

Preparing for Debugging[edit]

Programs normally have to be compiled with a special option to allow debugging to take place. For swift, this is the -g option. Before proceeding, examine the size of your "main" executable file:

ls -l

Write down the size of the file. Now, compile with the debugging option:

swiftc -g main.swift

Again, examine the size of the new file that was produced with the debugging option. Write down the size of this file and compare it to the version without the debugging information.

Emblem-question-green.svg Question: Is the new file smaller or larger than the original? Why?

The -g option causes the compiler to include information about the source file (main.swift) that is needed for debugging as part of the executable file. This causes the executable to be larger in size, and slightly slower, but allows for debugging. So when you run the debugger, you specify the executable file (not the source file) as the input to the debugger.

Starting the Debugger[edit]

Start the debugger by typing:

lldb main

Once in lldb, use the run command to start your program running. It will run until it completes, until it crashes, or until it reaches a breakpoint that you set (more on this later) -- and it will pause for input, of course. Once it finishes, you're still in lldb, so you can run it again from the beginning.

If your program requires command-line arguments, you can give them after the run command. If you would normally run the program on the command line by entering prog1 100 test1.dat in the debugger, you would enter: run 100 test1.dat Note, however, that the main.swift that we are editing here does not need any command line parameters, so just type:

run


Where Am I? Where Did It Crash?[edit]

One of the most frustrating things about running a program is that they normally give little useful information when they crash -- usually they just say, 'segmentation fault'. Part of the reason is that by default the executable file doesn't include information about the source code that is needed to print an error message (like the line number). But when you run a program inside a debugger, you can easily see what the current line is when a program crashes. Type f to see the current and surrounding lines. Note that this may be more or less useful depending on where you are in the code. It may be significantly less useful if your program crashed deep inside of a library. In such cases, you may only be able to view some cryptic text. But don't despair. We can walk up through the frames which invoked the procedure until we reach our own familiar code.

f

More usefully, you can see a list of the function calls that led you to this point in your program. Your program may have died deep inside a function that is called many times in your program, and you need to know which sequence of nested functions calls led to the failure. In the command-line mode, type bt, backtrace, to show this list. Examine the list closely. The frame at the bottom is the one that began execution of your program. Each frame up was the result of a new frame being introduced, often by a function invocation. Thus, the frame listed at the top is the most recent, or "deepest".

Oxygen480-actions-help-hint.svg Helpful hint: This command, bt, is one of the most important and useful debugging commands you'll see in this lesson.
bt

While we're talking about reaching a point in a sequence of nested function calls, sometimes in lldb you will need to understand the concept of frames. When a program stops, you can examine local variables, view lines of code, etc. that are local to that function. If you need to move up to the function that called this one, you need to move up to the higher frame using the up command to debug there. The down command moves you back down a frame towards where you started. The up and down commands let you move up and down the calling stack (of nested function calls) so you can issue debug commands about the function that's "active" at each level.

Type up until you see code that you recognize:

up
Oxygen480-actions-help-hint.svg Helpful hint: If you need to enter the same command again and again, you can just press the <RETURN> key instead.

References[edit]

Key Concepts[edit]

  • lldb is a command-line debugger
  • A debugger is a utility program that allows you to run a program under development while controlling its execution and examining the internal values of variables.
  • A stack frame is the collection of all data on the stack associated with one subprogram call and generally includes the return address, argument variables passed on the stack, local variables, and saved copies of any registers that need to be restored.
  • Command-line arguments may be specified within lldb immediately after the run command