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 A → B 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:
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.
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:00 • Cite • Imprint