starts at an epilogue. If a prologue is accessed during a backtracking procedure, the previous values are restored by executing the prologue. Then either the next execu- tion block or the previous prologue is executed depending on whether a backtracking procedure ends or not.
To calculate reverse code, the following questions must be answered:
1. How can we decide which program point is the previous one if there exist several previous program points at static analysis time?
2. How can we restore the previous variable values?
Concerning the first question, the previous program point can be decided with the help of predicate values. For example, in Figure 6.3, suppose that the program point is at the epilogue ofB4and the value ofCis not changed afterB1. Then if the value ofC is true, the program pointer is restored to the prologue ofB1. Otherwise, the program pointer is restored to the prologue ofB2. In order to prevent the predicate value of an if-statement from being changed, a given program can be translated to its SSA (Static Single Assignment) form.
Assuming that a given program is transformed to its SSA form, and also that sub- procedure call does not exist in a given program, the following three kinds of informa- tion should be able to be restored:
1. The local variable values in the loops of a program, 2. The shared variable values, and
3. References (aka pointers) to objects.
Currently2, we are working on the restoration of the first one. The local variable value in a loop can be restored by a recurrence equation of the variable. That is, the variable value at the nth iteration can be retrieved from the currently known variable value at the (n+1)th iteration and its recurrence equation.
Sometimes, state saving may not be avoidable. For example, if a procedure back- tracks to the end of its previous sub-procedure (i.e., callee), it is necessary to restore the stack frame (aka activation record) already destroyed when the sub-procedure re- turned. However, not all variables of the sub-procedure may need to be saved. It would be possible to minimize the number of variables to be saved using a dependency graph of variables.
6.4 Results
Currently3, we are working on the construction and insertion of reverse-code blocks of a procedure. We also developing a prototype tool to evaluate the initial result of reverse-code generation.
2As of 2004 November.
Chapter 7
Dynamic Reverse Code Generation for Backward Execution
Abstract
The need for backward execution in debuggers has been raised a number of times. Backward execution helps a user naturally think backwards and, in turn, easily locate the cause of a bug. Backward execution has been implemented mostly by state-saving or checkpointing, which are inherently not scalable. In this paper, we present a method to generate reverse code, so that backtracking can be performed by executing reverse code. The novelty of our work is that we generate reverse code on-the-fly, while running a debugger, which makes it possible to apply the method even to debugging multi-threaded programs.
7.1 Introduction
It has been pointed out in a number of papers that enabling backward execution in de- buggers would be of great help in a debugging process [ADS91, AM02, BM99, BJ97].
A typical debugger-aided bug-finding, where a debugger does not support backward execution, is performed in iterative steps of: (1) guess a problematic point which may cause an unexpected behavior of a program, and set a breakpoint there (2) restart a debugging session and watch a program state on the breakpoint. This procedure is time-consuming, not only because a user must repeat starting and stopping debugging sessions until finally identifying the cause of the error, but also because guesses made by a user are often not precise. What is worse, as a mainstream language like Java begins to support multi-threading, the traditional debugging procedure often even does not work because one cannot keep the scheduling order between threads the same as before, by only restarting a program. On the other hand, if a debugger can run a pro- gram backwards, a user can naturally see what happened in the past and, as a result, trace the error back to its source, not depending on an error-prone and time-consuming guess-restart procedure.
There have been several works that aim to support backward execution.1 However most of them rely on state-saving or checkpointing, a periodic state-saving, and that makes their debuggers suffer from memory blow-up. Recently Akgul and Mooney suggested a way to generate reverse code by static analysis (control/data dependency
1Related work is shown in Section 7.7
59
analysis), and showed the memory efficiency of it [AM02, AM04]. We aim to generate reverse code in the same spirit as [AM02], but we also want to be able to deal with multi-threaded programs, unlike/in addition to [AM02]. To achieve the goal, we cal- culate reverse code on-the-fly, while a debugger is running, based on logged history of transitions, basically pointers to program locations, each of which may require only a few bits of information.2
After introducing our input language and motivating example in the next two sec- tions, we demonstrate in detail our reverse code generation method (Section 7.4). In the subsequent two sections (Section 7.5,7.6), we also explain auxiliary techniques necessary for reverse code generation. Then related work, discussion and conclusion come in order.