Tuesday, January 4, 2011

Debugging Builds

Many teams find it helpful to create a debugging build, which differs from a release build in various ways designed to help reproduce and diagnose problems.


Compiler Options
Most compilers provide you with a wide range of options that allow you to control exactly how they translate your source code into object code. Often it makes sense to use a different set of options during development and debugging from those used in production. Here are a few examples:

Optimization: Modern compilers can perform wonders, generating object code that is as efficient, or better, than hand-rolled machine code. In the process of doing so, however, they often restructure things so much that the relationship between source code and object code can become muddied. This can, for example, make single-stepping in a debugger confusing or even impossible. As a result, debug builds often disable optimization. Debugging information: To be able to single step through the source, debuggers need to know how to map lines of source code to regions of object code. Typically these are excluded from a production release because they add size and may give away information we would rather keep to ourselves.

Bounds checking: Some C/C++ compilers provide an ability to add bounds checking to arrays and other data structures.

There’s more to a debugging build than just choosing different compiler options, however.


Debugging Subsystems
Sometimes it’s worth thinking about replacing an entire subsystem with a version specifically designed to make debugging easier. This can be particularly useful if we can’t easily control the behavior of the production version of the subsystem (because it’s under the control of a third-party, for example, or because its behavior has some random element).

Imagine, for example, that our software interfaces with a server provided by a third-party and we’re trying to debug a problem that occurs only when it returns a specific sequence of results. It may not be easy, or even possible, to find a way to ensure that it always returns that exact sequence on demand. Even if we can, its owners may not thank us for bombarding it with requests—especially if those requests aren’t well-formed (which is likely to be the case during debugging).

There is some overlap between a debugging subsystem and the test doubles, Mocks, Stubs, and Other Test Doubles. The difference is one of scale and scope. A test double is a short-lived object only intended for use within a single test. A debugging subsystem is normally a complete replacement for its associated production subsystem, implementing all of its interfaces and operating correctly across a wide range of use cases. It may even make sense for us to ship a debugging subsystem with the software so that end users can enable it in order to help us debug a problem in situ.

A debugging subsystem either can entirely replace its corresponding production system (emulating its entire behavior) or can be implemented as a shim that sits between the rest of the software and the production system, modifying its behavior as appropriate.

One particular subsystem you might want to consider bypassing during debugging is the user interface.


Solving the User Interface Problem
The needs of the end user and the needs of a developer are often very different. A graphical or web-based user interface might make it very easy for end users to achieve their goals, but it can get in the way during development and debugging because such interfaces are difficult to control programmatically.

For this reason (among others), it makes sense to ensure that the user interface layer is as thin as possible, just looking after the details of displaying information and soliciting input from the user. In particular, it should contain no business logic whatsoever. This should mean that you can replace it with an alternative such as a scripting language that can drive the rest of the software, which is likely to be much easier to work with from a debugging standpoint.

This might fall out in the wash if your software implements an object model such as (OLE) Automation under Windows or AppleScript support on the Mac. It might even be worth adding support for such an object model exclusively for debugging. Another subsystem commonly replaced with a debugging version is the memory allocator.


Debugging Memory Allocators
In languages like C and C++, which don’t provide automatic memory management, a debugging memory allocator can be worth its weight in gold. A debugging allocator can help you detect and solve a number of common problems:

• By keeping track of memory allocation and deallocation, it can detect memory leaks (memory that is allocated but not freed).

• By placing guards before and after allocated memory, it can detect buffer overflows and memory corruption.

• By filling memory regions with known patterns, it can detect instances where memory is used without being initialized.

• By filling deallocated memory with a known pattern and holding onto it, it can detect instances where memory is written to after it has been deallocated.


Built-in Control
As well as modifying the behavior of third-party code, we can also choose to have our own code behave differently in a debug build, building in the control that will prove useful during diagnosis. Examples include the following:

Disabling features: Sometimes your software might include features that are valuable in production but obfuscate things during debugging. Communication between one part of the application and another might be encrypted for security reasons, for example. Or data structures might be optimized to improve memory usage and execution speed. You are likely to make problems in these areas much easier to diagnose if you allow such features to be selectively disabled.

Providing alternative implementations: Sometimes there is more than one way to implement a module—one that is simple and easy to understand and another that is complex and optimized. By including both within the code and providing a means to switch between them, you can validate the results of the complex version against the simple one. This can help pinpoint whether the bug resides in the optimized version or elsewhere, and it can help with debugging even if it does lie elsewhere by making things simpler to understand.

Although we tend to talk about two different builds, debug and release, there’s nothing to stop you from building other flavors. Many teams, for example, have an integration build that acts as a halfway house between a debug and a release build. It might, for example, have debugging symbols and assertions enabled like a debug build but have optimizations enabled like a release build.

Source of Information : Paul Butcher - Debug it Find repair and prevent bugs
Debugging BuildsSocialTwist Tell-a-Friend
Digg Google Bookmarks reddit Mixx StumbleUpon Technorati Yahoo! Buzz DesignFloat Delicious BlinkList Furl

0 comments: on "Debugging Builds"

Post a Comment