fredag 10 december 2010

GDB reverse debugging

GDB reverse debugging


Let's start with a simple C++ program in the file gdb-test.cpp:


#include <iostream>
 
const unsigned int SIZE = 10;
 
int main() {
 
  int b[SIZE];
 
  // intitialize
  for(int i=0; i <= SIZE; i++) {
    b[i]=0;
  }
 
  std::cout << "done!" << std::endl;
  return 0;
}

Let's comple and run it.


$ g++ -g gdb-test.cpp -o gdb-test
$ ./gdb-test

??? It doesn't stop!


Let's load it into gdb.


$ gdb ./gdb-test
GNU gdb (GDB) 7.1-ubuntu
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
[...]
Reading symbols from /home/larsr/gdb-test...done.
(gdb)

Now run it:


(gdb) r
Starting program: /home/larsr/gdb-test

Still dosn't stop! Stop it with ctrl-c:


^C
Program received signal SIGINT, Interrupt.
0x08048704 in main () at gdb-test.cpp:11
11    for(int i=0; i <= SIZE; i++) {
(gdb)

Let's look at the variable i.


(gdb) print i
$1 = 4
(gdb)

Looks normal enough! But strange that it didn't get further than 4... Well, let's run some more, break, and look at i.


(gdb) continue
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x080486fc in main () at gdb-test.cpp:11
11    for(int i=0; i <= SIZE; i++) {
(gdb) print i
$2 = 7
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0x080486f8 in main () at gdb-test.cpp:11
11    for(int i=0; i <= SIZE; i++) {
(gdb) print i
$4 = 6
(gdb)

What? First i was 7 and then it was 6! How did that happen? Lets start to record, and run some more!


(gdb) record
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
main () at gdb-test.cpp:11
11    for(int i=0; i <= SIZE; i++) {
(gdb) print i
$6 = 2
(gdb)

Something has modified i to become smaller! When did this happen? Let's watch i and go backwards.


(gdb) reverse-continue 
Continuing.
Hardware watchpoint 1: i
 
Old value = 2
New value = 1
main () at gdb-test.cpp:11
11    for(int i=0; i <= SIZE; i++) {

This makes sense. We are walking backswards, and in that direction we had a value of 2 which turned into 1. Let's keep going backwards.


(gdb) reverse-continue 
Continuing.
Hardware watchpoint 1: i
 
Old value = 1
New value = 0
main () at gdb-test.cpp:11
11    for(int i=0; i <= SIZE; i++) {
(gdb) reverse-continue 
Continuing.
Hardware watchpoint 1: i
 
Old value = 0
New value = 10
0x080486eb in main () at gdb-test.cpp:12
12      b[i]=0;
(gdb)

Here we see that as we went backwards, we went from 0 to 10. That means that when we ran forward, i went from 10 to 0, and it happened when we executed


b[i]=0;

Could it be that writing to b[10] overwrites the memory of i? Where are they located in memory?


(gdb) print &i
$7 = (int *) 0xbffff7bc
(gdb) print &b[i]
$8 = (int *) 0xbffff7bc
(gdb)

Yup, they are at the same address. Why? Doh! An array indexing error! b[10] is not within the array bounds! The last element is b[9].


Line 11 should use < instead of <= and read


for(int i=0; i <  SIZE; i++) {

Case closed.


An ugly observation is that programs like this bug out silently and "work" if you change the program to be initialized to 100 (or something bigger than SIZE).

Tic-tac-toe in python

Represent a board with a nine character string with spaces, "X", and "O". The rows of the board are concatenated to make the string, so the board


 X |   |   
---+---+---
   | O |   
---+---+---
   | X | O 


is the string "X   O  XO".

Given a board string, the function tic() returns the next board.  However, if the opponent can win (provided he makes the right moves), then tic() doesn't try to fight back, and may return bad moves.  A smarter program might continue to fight back as long as the game is not actually over.