BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Debugging in MonoTouch

Debugging in MonoTouch

When you run into bugs in application development, it’s important to be able to track them down quickly and efficiently. To this end, debuggers allow you to track your code during execution and see exactly what’s happening; what the data is in variables, what path the code is taking, etc.

It is hard to stress just how valuable a good debugger is, and without one, trying to track down where your code is going wrong can be a frustrating endeavor.

In the early days of MonoTouch, your only option was to call Console.WriteLine in your code and manually write out to the Application Output window in MonoDevelop, whatever you wanted to examine. This meant a lot of extra work, especially if you didn’t know what the data would look like, or what path the code would take.

The reason was, Apple is very, very tight-lipped about the details of the iPhone OS innards. Modern debuggers typically work by “attaching” to a running process. This means that your application starts up and the debugger reads the information in memory that belongs to the threads that your application is using. Most sophisticated debuggers (the kind that you’re probably used to if you’re coding in .NET, or other modern platforms), actually inject small bits of code into your code that allow them to do things like pause your application and allow you to step through it.

In order to do this however, the debugger must have intimate knowledge of the underlying runtime of the code (and privileges to change the memory where that code is running), as well as communicate via the proper protocol to access the runtime to do what it needs to do. Because Apple is so secretive, however, they don’t publish the information about the debugging protocol.

You could still use Apple’s XCode application to debug your application, but it wasn’t very useful because it had no way of mapping the compiled instructions (assembly) back to the original source code (symbols).

So the MonoTouch team would have had to either reverse-engineer the protocol, or implement a new methodology altogether. They chose the latter, and in MonoTouch 1.2, they released the Mono “Soft-Debugger”.

The Soft-Debugger is actually based on a Java pattern for debugging and is called a “Soft-Debugger” because it’s actually a piece of software that is integrated into the MonoDevelop (or other IDE) and calls an interface which is now included as part of the Mono Runtime.

What this means for you, is that debugging in MonoTouch now acts, essentially, the same way you’ve come to expect debugging for any other .NET (or Mono) application. There are a few limitations (of which we’ll take a look at later), but for most application development, you likely won’t run into them.

Additionally, what’s really cool about the Soft-Debugger, is that they loosely coupled the underlying protocol over which the debugger uses. This is interesting to us because you can actually debug MonoTouch code running on the iPhone itself, over a Wi-Fi network!

When you run a MonoTouch application as a debug build, it tries to establish communication with MonoDevelop, if it finds a listening MonoDevelop, it performs a handshake and then continues on with application.

A Note About Debug Builds

Debug builds are much slower and much larger than release builds because every line of code is instrumented to have information that points back to the original source code. This allows the MonoDevelop IDE to know where in the source code the compiled code maps to.

Because of this, you should make sure that when you distribute your applications, or if you’re doing performance testing, your application should be done via a Release build.

Ok, enough theory, let’s actually see the debugger in action.

Debugger in Action

We’re going to use the sample application here as a basis to use the MonoTouch debugger. Download the application, unzip it and open the .sln file in MonoDevelop. It should look like this:

This is a very simple application that allows us to test out a few different features of the debugger. It only has one screen with some buttons that we’ll use to debug a few different things.

If you run it, you should get the following:

Double-click on MainScreen.xib.cs to open it up in the editor. Let’s look at the event handler for our first button:

protected void BtnClickMe_TouchUpInside (object sender, EventArgs e)
{
	string foo = "Iteration";
	for (int i = 0; i < 1000; i++)
	{
		this.lblOutput.Text = foo + i.ToString ();
	}
}

Let’s just test our debugger out. We’re going to add a breakpoint on this line:

string foo = "Iteration";

Adding a breakpoint is easy, simply click to the left of the line where you want the debugger to stop:

You can also add a breakpoint by choosing Run : Toggle Breakpoint in the menu.

In order for your breakpoints to actually work, you have to make sure that you’re creating a “Debug” build of your application. If you’re running this in the simulator, you want to make sure that Debug|iPhoneSimulator is selected in the build type menu:

If you’re going to debug on your device, make sure that Debug:iPhone is selected.

If you run this, and click on the first button your application should pause when it hits the breakpoint, and MonoDevelop will highlight the line in yellow:

Once the application is in this state, you can do a lot of interesting things. If you hover over any variable with your mouse-cursor, you get a popup with information about that variable. In the following screen shot, I’m hovering over the sender parameter in our event handler:

If you want, you can expand the little arrows to traverse the containing members and get a look at what is happening with them.

Let’s look at some of the other debug information we get. There are a number of windows that appear when MonoDevelop goes into debug mode. Let’s look first at the Locals window:

If the you can’t see the window, you can view it by selecting View : Pads : Debug Windows : Locals, in the menu.

The Locals window is automatically populated with variables that are within the scope of whatever point you’re at in your debugging. As before, you can click on the arrow to expand any item to see more information. The Locals window is nice because it gives you quick access to local variables without too much effort.

Let’s look at the Watch window:

This window gives you an opportunity to pick variables to watch. The locals window is nice because it’s automatically populated, but it doesn’t show you anything but variables local to whatever function you’re in. Also, if you wanted to watch a particular member in one of those variables, you’d have to drill down to it, and as the window got updated, it might keep collapsing your view.

Adding a new variable to watch is easy, just click Click here to add a new watch and then type in the name of your variable. For instance, you can watch the global variable UIApplication.SharedApplication:

The nice thing here is that once you add a variable to the Watch window, it persists in there, whereas with locals, they go away as they go out of scope.

The Breakpoints window gives you a nice tidy place to view and manage your breakpoints:

If you want to run the application without any breakpoints, but want to keep them for future use, you can disable all your breakpoints in here by clicking the red circle with the white center. You can also disable individual breakpoints by clicking the checkmark next to them. When you’re done with them entirely, you can click the red circle with the black “x” and it will delete them all.

The Threads window shows you active threads and which thread you’re in:

As you can see, there’s a green arrow next to thread 1, because we’re in the main thread.

The Call Stack window shows you a history list of what calls have been made. It starts with the most recent call first:

If you scroll to the right, it’ll tell you what line of code even that was run. This window is invaluable if you’re not sure what happened before your breakpoint was hit and can really help figure out code logic errors.

Finally, the Immediate window is one of the most powerful tools when debugging:

Think of the Immediate window as a console where you can run code. Code you enter into this window will execute immediately, and it’s extremely useful for controlling program flow during debugging. For example, you can use this window to actually change the value of variables. We’ll check this out in just a bit.

Now that we have a grasp on the windows, let’s look at some of the tools we have for stepping through our application. If you look at the toolbar, you should see:

These buttons are (in the following order):

  • Step Over – Allows you to go past the current line of execution. The line still runs, but it won’t step into the call and show you what’s happening in it. This is useful when your debugging cursor is on a method call and you know that a call works fine, or you don’t want to examine it, but still want to continue through the execution flow.
  • Step Into – Allows you to step into the current line of execution. If for instance the current line was a method call, it would go to the point in the source code where that method was and would allow you to step through that.
  • Step Out – Step out allows you to step out of the current scope of execution. For example, if you’re in a loop, it will execute the loop and then step to the next execution after the loop. If you’re in a method, it will finish the method and then go to the next line of execution.

There are also the following buttons:

Which are:

  • Debug – When your application is not running, this starts it so it can be debugged. When your application is running, it runs it until the next breakpoint.
  • Stop – This terminates the application at whatever breakpoint it is at. No more execution is performed. In MonoTouch, using this is known to have intermittent problems, so it’s best to use the “home” button on the simulator or device to stop the application.

Now that we got all that covered, let’s go back to our code and use some of these tools. Go back to the btnClickMe handler:

protected void BtnClickMe_TouchUpInside (object sender, EventArgs e)
{
	string foo = "Iteration";
	for (int i = 0; i < 1000; i++)
	{
		this.lblOutput.Text = foo + i.ToString ();
	}
}

This isn’t terribly interesting code, it’s simply a for-loop that counts to 1000, but it represents an interesting debug problem. What if we needed to step in here and examine the code on the 836th time through? It would really suck to set a breakpoint and keep stepping through until the variable i is equal to 835. Instead, what we can do is set a conditional breakpoint. A conditional breakpoint allows us to stop when a certain condition is met. Set a breakpoint on the following line:

this.lblOutput.Text = foo + i.ToString (); 

Then, right click on the red circle that represents the breakpoint and choose Breakpoint Properties. Let’s set it to break when our variable hits 835:

Notice that you can do some other things in here as well. For example, you can make the breakpoint get hit when the expression changes. You can also change the Action of the breakpoint to write information out to the console, instead of stopping the application.

If we run the application now and click that first button, the application will now stop when the 836th time through the loop.

Now that we know how to create conditional breakpoints, let’s look at something different. Let’s look at how we can use the stack window to determine code path. Take a look at the btnCodePaths handler and associated code in our file:

protected void BtnCodePaths_TouchUpInside (object sender, EventArgs e)
{
	//---- generate a random number of 1 or 2
	int random = rndGen.Next (1, 3);
	
	switch (random)
	{
		case 1:
			this.MakeGirl();
			break;
		case 2:
			this.MakeBoy();
			break;
		default:
			break;
	}
}

protected void MakeBoy ()
{
	this.MakeChild ("boy");
}

protected void MakeGirl ()
{
	this.MakeChild ("girl");
}

protected void MakeChild (string type)
{
	this.lblOutput.Text = "it's a " + type + "!";
}

In this code example, we call the MakeChild method in the end, but depending on our random number generator, it may take a different path to get there. Put a breakpoint on the following line:

this.lblOutput.Text = "it's a " + type + "!";

Next, run the app and click the Which Path? button. If you look at the Call Stack window, you’ll see what methods were called, and in which order:

In my case, MakeGirl was called, which then called MakeChild. Obviously this is a simple example, but it illustrates a powerful concept. Sometimes you have no idea how your code has been called. The Call Stack window illuminates that for us.

The last example we’ll look at is debugging multiple threads. Take a look at the following code in the file:

protected void BtnThreads_TouchUpInside (object sender, EventArgs e)
{
	//---- create a background thread
	Thread backgroundThread = new Thread (DoSomeAsynchronousWork);

	//---- spin it up
	backgroundThread.Start ();
	
	//---- let this thread sleep while our background thread is running
	while (backgroundThread.ThreadState == ThreadState.Running)
	{
		Console.WriteLine ("sleeping main thread");
		//---- wait half a second
		Thread.Sleep (500);
	}
}

protected void DoSomeAsynchronousWork ()
{
	//---- let's pretend the thread takes five seconds to execute
	DateTime fiveSecondsForward = DateTime.Now.AddSeconds (5);

	//---- kill five seconds
	while (DateTime.Now < fiveSecondsForward) {}
	
	Console.WriteLine ("thread is complete");
}

This code simple starts a new thread, which calls the method DoSomeAsynchronousWork and then waits for that method to complete. Let’s set a breakpoint on the following line:

Console.WriteLine ("thread is complete");

This line is executed on the second thread. If we run the app and click the Uh oh! Threads! button, we’ll hit that breakpoint, and then in our Threads window we will see that we’re now on a second thread:

Ok, so we’ve debugged on the simulator let’s debug on the device. First thing we need to do is change our build type to Debug:iPhone:

Note: if you’re running the evaluation version of MonoTouch, you don’t have the option to debug on the device.

Once you’ve changed the build type, plug your phone in and choose Run : Upload to Device from the menu. This will build the application and install it.

Because the debugger runs over Wi-Fi, we have to configure some things so the device can find the computer you’re running MonoDevelop on. Open the Settings application on your iPhone, and scroll down until you see the Example_Debug application:

Select it and you should see the debug settings:

Debugger Host refers to the IP of the computer running MonoDevelop that you’ll debug on. Make sure it has the correct IP address.

Once you’ve got the application configured on the device, click the Debug icon (or Run:Debug in the menu) in MonoDevelop and it should pop up the following window:

This means that MonoDevelop is listening, but it’s waiting for you application to start. It can’t start the application itself, so you have to start it manually. Check to make sure the IP in that window is the correct IP of your computer. It should match whatever you put in the settings of your application. Sometimes MonoDevelop picks up the wrong IP.

If it shows the wrong IP, open up finder and in the menu choose Go:Go To Folder and put in "~/.config/MonoDevelop" – this will open up your MonoDevelop settings folder. Then, edit MonoDevelopProperties.xml and search for the following key:

key="MonoTouch.Debugger.HostIP"

Change the value of that key to be your IP, save the file, close it, and then re-open MonoDevelop.

If your IP is correct, navigate to the application icon on your iPhone and launch the application. If all is well, your application should start up and you should be able to debug just as if you were debugging in the simulator.

Congratulations! You now have a solid understanding of how to debug in MonoTouch.

Troubleshooting

Debugging on the device is awesome, but it can be tricky to setup. If you’re having trouble with it, the first thing to check is the IP configuration on the phone and in your MonoDevelop settings as mentioned earlier.

When you start up your application on the device, if something is wrong, the device will vibrate to one or more times to indicate the issue:

  • One Vibration – The application is in debug mode, but it cannot reach MonoDevelop on the specified IP. If you get this error, make sure your IPs are setup correctly in both the application and the MonoDevelop settings. You should also check to make sure that you don’t have a firewall running that might block the port, and that your device and computer are on the same network, or that your computer IP is accessible to your device.
  • Two Vibrations – The format of the IP that you’ve entered in the application settings is incorrect and the application cannot parse it.
  • Three Vibrations – The application could not setup the second port in the application settings. This port is needed to send output and error information to MonoDevelop. Make sure that there isn’t a firewall blocking that port.

Limitations

The soft-debugger in MonoTouch only debugs managed code, if you’re using any C, C++, or Objective-C libraries, you cannot debug them. If you want to debug those libraries, you need to use the GDB debugger with XCode. For more information see the http://monotouch.net/Documentation/Debugging/GDB_Debugging page on the MonoTouch web site.

Also, unlike the debugger in .NET, the soft-debugger cannot step into the Mono libraries, even though they’re managed code.

Other Considerations

The iPhone gives your application 10 seconds to get through the FinishedLaunching method in your Application Delegate class. If the method does not return during that time, the iPhone OS will kill your application. That means any debug breakpoints set during FinishedLaunching will likely wind up with your application terminated.

If you want to debug any code that you would normally put in your FinishedLaunching method, you should refactor it out to run afterward.

Rate this Article

Adoption
Style

BT