Ludicrous Software

Using the Corona Debugger

I have to confess: I do not write bug-free Lua code (I know, shocking). Much of the time, judicious use of print() statements will be enough to help me figure out just where things have gone off the rails. But every so often, that’s not enough, and so I turn to the debugger that ships with the Corona SDK - that’s right, there’s a debugger hidden away in the SDK (actually a version of RemDebug). While it doesn’t have a nice GUI to make working with it a little easier - you’ll spend quite a bit of time on the command line - it has a lot of functionality. This post will show you how to start and use the debugger.

Starting the Debugger

The debugger is part of the Corona SDK, and needs to be invoked from the command line. So, fire up Terminal.app, make sure there are no instances of the Corona Simulator already running, and from the command prompt type:

1
2
# replace /Applications/CoronaSDK with the actual path to your SDK installation
/Applications/CoronaSDK/debugger

The debugger will start the Corona Simulator and prompt you to run the program you want to debug. Once you’ve selected your program to debug, the debugger will pause execution so you can set it up to, well, debug your program. If you just tell the debugger to continue running the program at this point - we’ll see how in a little bit - it actually won’t be terribly useful. That’s because you haven’t told the debugger to do anything. You can do this by setting breakpoints and watch expressions. We’ll look at both of these - and more! - but first, here’s the program that I’m using for the purpose of this tutorial:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local square = display.newRect( 0, 0, 10, 10 )
square:setFillColor(255, 0, 0)

local function foo()
  print("foo")
end

local function moveSquare(event)
  square.x = square.x + 3
  foo()
  if square.x == 100 then
      Runtime:removeEventListener("enterFrame", moveSquare)
  end
end

Runtime:addEventListener("enterFrame", moveSquare)

The code creates a red square positioned at the 0, 0 point on the screen and an enterFrame listener that moves the square three pixels to the right on every frame. When square.x is equal to 100, the enterFrame listener is removed. (foo() is just a dummy function to help demonstrate some of the capabilities of the debugger.) You can probably already see the bug in this program - since 100 isn’t evenly divisible by 3, the square never stops moving to the right. The simplest approach in this case would be to stick a print(square.x) statement in the function to see the values of square.x. With that approach you end up with a ton of output in the terminal, and in even a moderately complex program you probably have a bunch of other print() statements, so you may not even see the output as it zooms by the screen mixed in with other output. So, we’ll use the debugger to look at a couple of different ways of debugging this application: breakpoints and expressions.

Breakpoints

A breakpoint is a point where you want to pause the execution of the program. When the program hits a breakpoint, it will stop in its tracks to let you use the debugger to examine the current state of the application to figure out what’s going wrong.

The command for setting breakpoints uses the following syntax:

1
> setb <file> <line>

(Note: do not enter the greater than symbol; that symbol is the prompt from the debugger.)

You can create as many breakpoints as you like, on any line of any Lua file in your project folder. Just make sure not to put them on blank lines, or they’ll be ignored. This command will create a breakpoint inside moveSquare():

1
> setb main.lua 9

Once you’ve created the breakpoint(s), you can tell the debugger to continue running the program. The command to do this is your choice of run, continue, or simply c (short for continue). After you type c in the debugger and hit enter, the program will run until it hits a breakpoint. When that happens, the program will pause execution and the debugger will tell you where this happened. So for the breakpoint already set, you’ll see this message from the debugger:

1
Paused at file main.lua line 9

At this point, we can examine the value of square.x:

1
> print square.x

The only difference between this and the standard Lua print() function is that in the debugger you must omit the parentheses; otherwise you can print whatever value you wish to examine. You can abbreviate the command to just the letter ‘p’, so p square.x will give you the same output.

Once execution has stopped at a breakpoint, you can “step” through the code using the following commands:

step (si)

step (or si for short) will advance your progress through the program one line at a time. The short form of the command stands for “step into”. This means that when the debugger encounters a function call, it will “step into” that function and allow you to step through its code.

You can see what I mean by entering si a couple of times in the debugger after execution has been paused at line 9. If you do that, your output will look like this:

1
2
3
4
5
6
7
8
9
> setb main.lua 9
> c
Paused at file main.lua line 9
> si
Paused at file main.lua line 10
> si
Paused at file main.lua line 5
> si
foo

The program paused at line 5 in main.lua, which is the print("foo") statement inside foo(). When the debugger encountered the call to foo() on line 10, it jumped to that function to let us step through it. Once it’s finished there, program flow will return to the function that called foo(), just as it normally would.

(Also, you can see that output from print() statements in your code will be directed to the debugger.)

But what if you know that foo() is unproblematic, and you just want to let it do its thing without having to bother stepping through that function? That’s where over comes in handy.

over (so)

over is the companion to step, and as soon as I tell you that so is short for “step over”, you’ll probably guess what so does. It steps on to the next line in the program, but if that line is a function call, it simply executes that function and continues stepping through the code. In other words, it causes the debugger to “step over” function calls. So the output in the debugger for so will look like this:

1
2
3
4
5
6
7
Paused at file main.lua line 9
> so
Paused at file main.lua line 10
> so
foo
Paused at file main.lua line 11
>

We can see that foo() was called because of the “foo” output from the function, but we didn’t have to manually step through it while debugging. This is handy when we’re sure that a certain function isn’t relevant to whatever bug we’re hunting down.

run/continue (c)

The last command you can use to control the debugger after you’ve hit a breakpoint is the run or continue command (c for short). This command simply lets the program continue to run until it hits another breakpoint or watch expression. This is very handy if you’re only interested in the code immediately after your breakpoint, and don’t want to have to step through all the lines of code between it and the next breakpoint. If you enter the c command in the debugger, you’ll see this output from the program:

1
2
3
4
5
6
7
Paused at file main.lua line 9
> c
foo
Paused at file main.lua line 9
> c
foo
Paused at file main.lua line 9

As you can see from the output, after entering the continue command, the program simply continues to run. foo() is executed, and then when the program hits the breakpoint in moveSquare() again - remember, this is the handler function for the enterFrame event, so it’s getting called 30 times per second, and that breakpoint will get triggered every time - the program is paused.

(If you haven’t noticed it already, check the Corona Simulator window as you enter the continue command; you’ll see the red square move across the screen a little bit after every time you hit ‘enter’. That’s because the code is actually running, so the square is being animated across the screen.)

Deleting Breakpoints

If you’re finished with a breakpoint, but you’re not done debugging your code, you can delete it using the following command:

1
> delb <file> <line>

So to delete our breakpoint, we would enter:

1
> delb main.lua 9

Delete all breakpoints with:

1
> delallb

Listing Breakpoints

The command to see all of the breakpoints that you’ve created in your debugging session is:

1
> listb

As you can see, breakpoints can be extremely handy. But given the current example, you can also see a potential downside: the bug in our code occurs somewhere around the time when square.x is about 100. That means having to sit there and enter continue about 30 times before we finally get to the point in the program that really interests us. That would be tedious. Happily, there’s a nice way to skip ahead in the program to get closer to where we want to be, and that’s by using watch expressions.

Watch Expressions

A watch expression is a Lua expression that is being constantly evaluated by the debugger. As soon as it evaluates to true, execution of the program is paused. So you can think of it kind of like a breakpoint, where instead of specifying a particular line where you want to stop the program, you give the debugger a specific condition.

In our example program, we’re expecting square.x to be equal to 100 at some point, but that’s not happening. So we want to know the value of square.x around that point. The easiest way to do that would be to be able to pause the program as soon as square.x is greater than 99. We can create a watch expression to do that:

1
2
3
> setw square.x > 99
Inserted watch exp no. 1
>

The debugger tells us the number of the watch expression we’ve created. The particular expression can be any valid Lua condition to check - as far as I know, if you can use the expression in a Lua if statement, you can use it in the debugger. So if, for example, we were also fading out the square, we could enter a watch expression like this:

1
     setw square.x > 99 and square.alpha < 0.5

Returning to our original expression, now we can continue the program and wait until our condition is met:

1
2
3
4
5
6
> c
foo
# lots more foos snipped for the sake of brevity
foo
Paused at file main.lua line 10 (watch expression 1: [square.x > 99])
>

Now we’re talking! The program just did whatever it was supposed to do until our condition is true. Now that square.x is greater than 99, the program is paused. So we can print the value we’re interested in and do whatever else we need to help figure out what’s going on. Couple things to keep in mind:

  1. As long as a watch expression is true, the debugger will pause execution on every line of the program until either the expression is no longer true or the watch expression is removed. In this sense, it kind of acts like having a breakpoint on every line.

  2. Your watch expression can specify local variables. So if there’s a particular variable that’s within the scope of a function, and you want to set a watch expression on that, go right ahead. It doesn’t matter if the local variable doesn’t exist when you create the watch expression. Keep in mind that if you’re in the habit of using the same name for local variables in different functions, the watch expression will apply to all of them.

Deleting Watch Expressions

To delete a watch expression, use the following command:

1
> delw <number of expression to delete>

The expression number is the number provided to you by the debugger after you’ve entered the expression. So in the case of our expression, which we were told was “exp no. 1”, we would enter:

1
> delw 1

If you want to see all the watch expressions you’ve created:

1
> listw

If you want to delete all of the watch expressions you’ve created:

1
> delallw

Other Helpful Commands

There are a few other commands that may be helpful in your debugging efforts. As before, the short form of the command is in parentheses:

  • locals (l) - shows you the names and values of all of the local variables in the program.

  • backtrace (bt) - shows the function call stack (similar to what you see in the terminal when you have an error in your code).

  • eval - basically the same as print

  • exec - this command allows you to execute any chunk of valid Lua code in the current context (i.e. with access to all local variables, etc.). For example, when our example code is stopped by a breakpoint or watch expression, you could move the square to any arbitrary x or y position:

        > exec square.y = 200
        nil
    

    Since a chunk in Lua can be multiple lines of code, one way to enter a chunk would be to type it on one line, separating each statement with a semi-colon. Remember: in Lua a semi-colon is an optional end-of-line marker - you don’t need to put the semi-colon at the end of a line if the line contains only one statement. But if you want to put multiple statements on a single line, you must separate them with a semi-colon. For instance, the following will create a large green square on the screen:

        > exec local rect = display.newRect(100, 100, 100, 100); rect:setFillColor(0, 255, 0)
        nil
    
  • dump (d) - this is like print on steroids. It will give you detailed information about whatever variable you want. For example, when execution of the test program is paused in the moveSquare function, you can dump the value of any variable that exists at that point in time (remember, use locals/l to get a list of those). So you can take a look at the event object that gets passed to moveSquare like this:

        > dump event
        event(table: 0x2a443e0) =
        {
            name = "enterFrame"
            time = 8817.956
        }
    

    This can give you access to a lot of detailed information about the current state of your application (and also lets you discover some potentially interesting things about what’s going on under the hood in Corona, if you’re the nosy type and start checking out anything that comes to mind).

  • help - a list of all the commands available in the debugger.

Incidentally, some of the functionality in the debugger seems to be added on top of RemDebug, such as being able to list local variables and the backtrace. (One thing that has me confused is the frame command - not really sure what that one does, but I’d like to find out one day.) I think that’s pretty cool - they’ve put effort into something that ships with the SDK and that most users will probably never touch.

(Maybe if you all start to use the debugger and submit suggestions for improving it, we’ll see even more cool new features added, like having breakpoints and watch expressions persist between debugging sessions, the ability to restart a debugging session without having to exit out of Simulator and debugger completely first, and maybe a nice GUI on top of it all.)

Conclusion

A lengthy post, to put it mildly, but I hope you found it interesting and educational. The debugger hides a lot of power behind a not very user-friendly interface, and I think that the time spent getting familiar with it will easily repay itself the first time you can use it to track down a particularly nasty bug.