The Debugging Book

Sitemap

While the chapters of this book can be read one after the other, there are many possible paths through the book. In this graph, an arrow AB means that chapter A is a prerequisite for chapter B. You can pick arbitrary paths in this graph to get to the topics that interest you most:

Tracer Tracing Executions Debugger How Debuggers Work Tracer->Debugger Assertions Asserting Expectations Tracer->Assertions DeltaDebugger Reducing Failure- Inducing Inputs Tracer->DeltaDebugger StatisticalDebugger Statistical Debugging Tracer->StatisticalDebugger DynamicInvariants Mining Function Specifications Tracer->DynamicInvariants ChangeDebugger Isolating Failure- Inducing Changes DeltaDebugger->ChangeDebugger DDSetDebugger Generalizing Failure Circumstances DeltaDebugger->DDSetDebugger PerformanceDebugger Debugging Performance Issues DeltaDebugger->PerformanceDebugger Repairer Repairing Code Automatically DeltaDebugger->Repairer StatisticalDebugger->PerformanceDebugger StatisticalDebugger->Repairer Intro_Debugging Introduction to Debugging Intro_Debugging->Tracer Slicer Tracking Failure Origins Intro_Debugging->Slicer Tracking Tracking Bugs Intro_Debugging->Tracking ChangeCounter Where the Bugs are Tracking->ChangeCounter

Table of Contents

Part I: Whetting Your Appetite

In this part, we introduce the topics of the book.

Tours through the Book

Introduction to Debugging

In this book, we want to explore debugging - the art and science of fixing bugs in computer software. In particular, we want to explore techniques that automatically answer questions like: Where is the bug? When does it occur? And how can we repair it? But before we start automating the debugging process, we first need to understand what this process is.

Part II: Observing Executions

In this part, we show how to observe executions – by tracing, by interactively debugging, and more.

Tracing Executions

In this chapter, we show how to observe program state during an execution – a prerequisite for logging and interactive debugging. Thanks to the power of Python, we can do this in a few lines of code.

How Debuggers Work

Interactive debuggers are tools that allow you to selectively observe the program state during an execution. In this chapter, you will learn how such debuggers work – by building your own debugger.

Asserting Expectations

In the previous chapters on tracing and interactive debugging, we have seen how to observe executions. By checking our observations against our expectations, we can find out when and how the program state is faulty. So far, we have assumed that this check would be done by humans – that is, us. However, having this check done by a computer, for instance as part of the execution, is infinitely more rigorous and efficient. In this chapter, we introduce techniques to specify our expectations and to check them at runtime, enabling us to detect faults right as they occur.

Part III: Flows and Dependencies

In this part, we show how to follow where specific (faulty) values come from, and why they came to be.

Tracking Failure Origins

The question of "Where does this value come from?" is fundamental for debugging. Which earlier variables could possibly have influenced the current erroneous state? And how did their values come to be?

Part IV: Reducing Failure Causes

In this part, we show how to narrow down failures by systematic experimentation.

Reducing Failure-Inducing Inputs

A standard problem in debugging is this: Your program fails after processing some large input. Only a part of this input, however, is responsible for the failure. Reducing the input to a failure-inducing minimum not only eases debugging – it also helps in understanding why and when the program fails. In this chapter, we present techniques that automatically reduce and simplify failure-inducing inputs to a minimum, notably the popular Delta Debugging technique.

Isolating Failure-Inducing Changes

"Yesterday, my program worked. Today, it does not. Why?" In debugging, as elsewhere in software development, code keeps on changing. Thus, it can happen that a piece of code that yesterday was working perfectly, today no longer runs – because we (or others) have made some changes to it that cause it to fail. The good news is that for debugging, we can actually exploit this version history to narrow down the changes that caused the failure – be it by us or by others.

Part V: Abstracting Failures

In this part, we show how to determine abstract failure conditions.

Statistical Debugging

In this chapter, we introduce statistical debugging – the idea that specific events during execution could be statistically correlated with failures. We start with coverage of individual lines and then proceed towards further execution features.

Mining Function Specifications

In the chapter on assertions, we have seen how important it is to check whether the result is as expected. In this chapter, we introduce a technique that allows us to mine function specifications from a set of given executions, resulting in abstract and formal descriptions of what the function expects and what it delivers.

Generalizing Failure Circumstances

One central question in debugging is: Does this bug occur in other situations, too? In this chapter, we present a technique that is set to generalize the circumstances under which a failure occurs. The DDSET algorithm takes a failure-inducing input, breaks it into individual elements. For each element, it tries to find whether it can be replaced by others in the same category, and if so, it generalizes the concrete element to the very category. The result is a pattern that characterizes the failure condition: "The failure occurs for all inputs of the form (<expr> * <expr>)".

Debugging Performance Issues

Most chapters of this book deal with functional issues – that is, issues related to the functionality (or its absence) of the code in question. However, debugging can also involve nonfunctional issues, however – performance, usability, reliability, and more. In this chapter, we give a short introduction on how to debug such nonfunctional issues, notably performance issues.

Part VI: Automatic Repair

In this part, we show how to automatically repair code.

Repairing Code Automatically

So far, we have discussed how to track failures and how to locate defects in code. Let us now discuss how to repair defects – that is, to correct the code such that the failure no longer occurs. We will discuss how to repair code automatically – by systematically searching through possible fixes and evolving the most promising candidates.

Part VII: Debugging in the Large

In this part, we show how to track failures, changes, and fixes.

Tracking Bugs

So far, we have assumed that failures would be discovered and fixed by a single programmer during development. But what if the user who discovers a bug is different from the developer who eventually fixes it? In this case, users have to report bugs, and one needs to ensure that reported bugs are systematically tracked. This is the job of dedicated bug tracking systems, which we will discuss (and demo) in this chapter.

Where the Bugs are

Every time a bug is fixed, developers leave a trace – in the version database when they commit the fix, or in the bug database when they close the bug. In this chapter, we learn how to mine these repositories for past changes and bugs, and how to map them to individual modules and functions, highlighting those project components that have seen most changes and fixes over time.

Appendices

This part holds notebooks and modules that support other notebooks.

Error Handling

The code in this notebook helps with handling errors. Normally, an error in notebook code causes the execution of the code to stop; while an infinite loop in notebook code causes the notebook to run without end. This notebook provides two classes to help address these concerns.

Timer

The code in this notebook helps with measuring time.

Timeout

The code in this notebook helps in interrupting execution after a given time.

Class Diagrams

This is a simple viewer for class diagrams. Customized towards the book.

Inspecting Call Stacks

In this book, for many purposes, we need to look up a function's location, source code, or simply definition. The class StackInspector provides a number of convenience methods for this purpose.

Creative Commons License The content of this project is licensed under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The source code that is part of the content, as well as the source code used to format and display that content is licensed under the MIT License. Last change: 2024-07-01 12:00:41+02:00CiteImprint