Practical Object-Oriented
Back-in-Time Debugging
Adrian Lienhard, Tudor Gîrba and Oscar Nierstrasz
                           Software Composition Group
                          University of Bern, Switzerland
Debugger call stack
                                  class Account {
                                    Money balance;
                                    void deposit(Money amount) {
NullPointerException >>               this.balance += money;
                                    }
                                    ...
                                  }


                     ..................
Debugger call stack
                          class Account {
                            Money balance;
                            void deposit(Money amount) {
NullPointerException >>       this.balance += money;
                            }
                            ...
                          }

                          class Company {
                            void pay(Money money, Person person) {
                              person.account().deposit(money);
                            }
                          }
                     ...




                                                    where does the
                                                    account object
                                                    come from?
Debugger call stack
                          class Account {
                            Money balance;
                            void deposit(Money amount) {
NullPointerException >>       this.balance += money;
                            }
                            ...
                          }

                          class Company {
                            void pay(Money money, Person person) {
                              person.account().deposit(money);
                            }
                          }
                     ...




                                                    where does the
                                          return    account object
                                                    come from?
Debugger call stack
                          class Account {
                            Money balance;
                            void deposit(Money amount) {
NullPointerException >>       this.balance += money;
                            }
                            ...
                          }

                          class Company {
                            void pay(Money money, Person person) {
                              person.account().deposit(money);
                            }
                          }
                     ...




                                                    where does the
                     field read            return    account object
                                                    come from?
Debugger call stack
                                           class Account {
                                             Money balance;
                                             void deposit(Money amount) {
                                               this.balance += money;
                                             }
                                             ...
                                           }

class Person {                             class Company {
  void createAccount(Bank bank) {            void pay(Money money, Person perso
    this.account = bank.openAccount();         person.account().deposit(money);
  }                                          }
}                                          }




                                         ...
                                                                     where doe
                return    field write     field read         return    account o
                                                                     come from
class Acc
                                                                           Money b
                                                                           void de
                                                                             this.
                                                                           }
                                                                           ...
                                                                         }

class Bank {                  class Person {                             class Com
  Account openAccount() {       void createAccount(Bank bank) {            void pa
    return new Account();         this.account = bank.openAccount();         perso
  }                             }                                          }
}                             }                                          }




                                                                       ...
                     allocation               return    field write     field read
Debugger call stack
                                                                                 class Account {
                                                                                   Money balance;
                                                                                   void deposit(Money amount) {
                                                       NullPointerException >>       this.balance += money;
                                                                                   }
                                                                                   ...
                                                                                 }

class Bank {                  class Person {                                     class Company {
  Account openAccount() {       void createAccount(Bank bank) {                    void pay(Money money, Person person) {
    return new Account();         this.account = bank.openAccount();                 person.account().deposit(money);
  }                             }                                                  }
}                             }                                                  }




                                                                            ...
                                                                                                           where does the
                     allocation               return        field write      field read            return    account object
                                                                                                           come from?




                                  In 50% of the cases the execution stack contains
                                  essentially no information about the bug’s cause.
                                                                                                          [Liblit PLDI’05]
class Bank {                  class Person {                             class Company {
  Account openAccount() {       void createAccount(Bank bank) {            void pay(Money money, Person person) {
    return new Account();         this.account = bank.openAccount();         person.account().deposit(money);
  }                             }                                          }
}                             }                                          }




                                                                       ...
                                                                                                   where does the
                     allocation               return    field write     field read         return    account object
                                                                                                   come from?




                                               History recorded by a
                                               back-in-time debugger
                                      t

                                                Challenges:
                                                 amount of data
                                                 execution overhead
Approaches
     Omniscient           Trace-oriented             Trace-oriented
     Debugger1           debugger2 (partial)         debugger2 (full)




+ limited memory usage   – requires extensive hardware resources
– loss of old history    – loss of history       + complete history
– overhead 100x          + overhead 10x          – overhead 100x



                               1) Lewis AADEBUG’03   2) Pothier etal. OOPSLA’07
Approaches
     Omniscient           Trace-oriented              Trace-oriented
     Debugger1           debugger2 (partial)          debugger2 (full)

                             st o f all?
                         Be       dm   emor
                                             y
                          + limite erhead
                                    v
                            + low o      histor
                                                y
                                      te
                           + co mple

+ limited memory usage   – requires extensive hardware resources
– loss of old history    – loss of history          + complete history
– overhead 100x          + overhead 10x             – overhead 100x



                               1) Lewis AADEBUG’03    2) Pothier etal. OOPSLA’07
Approaches
     Omniscient           Trace-oriented             Trace-oriented
     Debugger1           debugger2 (partial)         debugger2 (full)

                             st o f all?
                         Be          emor
                                          y
                               ited m ead
                          + lim overh
                           + low        istory
                                e vant h
                          + rel
+ limited memory usage   – requires extensive hardware resources
– loss of old history    – loss of history       + complete history
– overhead 100x          + overhead 10x          – overhead 100x



                               1) Lewis AADEBUG’03   2) Pothier etal. OOPSLA’07
At runtime delete history when it gets irrelevant



                          Relevant questions:
                           How was this object passed here?
                           What were the previous values of a field
                           and where (call stack) were they assigned?

                           Only history of live objects needed!


           Efficient mechanism required to
           identify irrelevant datapoints at runtime
How we can do it

VM that models history as first-class objects
Keep history together with regular objects in memory
Use GC to efficiently delete no longer needed history
First-class references
    Typical model: object                     Object Flow VM: references are
references as direct pointers                  represented as alias objects

            regular objects                                 alias

   header                              header
                            header                          header
 field_1                              field_1                                header
                      ...                             value
 field_2                              field_2                          ...
                                                      context
 ....                                ....
 field_n
               pointer               field_n
                                                      origin
                                                      predecessor
                                                      ...
Object Flow VM model
                                         caller   0..1

                                  MethodInvocation
                                                         *
                             context              1

                                       target     parameter
                  field or array                    *
          1            element
  Value                                Alias
          value              *
Object Flow VM model
                                            caller   0..1

                                   MethodInvocation
                                                            *
                             context                 1

                                          target     parameter
                  field or array                       *
          1            element                           predecessor
  Value                                   Alias
          value              *                              0..1
                                   0..1    *         0..1
                                 origin
Historical object state
account = new Account()      t1




                   balance                  value
 :Account                         init@t1           null
Historical object state
account = new Account()      t1
...
account.balance = 17         t2




                                                    value
                                  init@t1                   null
                                      predecessor

                   balance                          value
  :Account                   field-write@t2                  17
Historical object state
account = new Account()    t1
...
account.balance = 17       t2
...
account.balance = 42       t3

                                                  value
                                init@t1                   null
                                    predecessor

                                                  value
                           field-write@t2                  17
                                    predecessor

                 balance                          value
  :Account                 field-write@t3                  42
Object
 Flow                   allocation



                             class Person {
                                                  return   field write


                                                                         field read
                                                                                        field read


                                                                                     parameter
                                                                                                    return




                               void printOn(Stream stream) {
                                 stream.print(this.account);
                               }
                             }




                                                                return


   field-write                        field-read

                                                                           field-read
       return
                                            parameter

                allocation

                                                                               active
                                                                             invocation

  Legend          running/completed method invocation          alias            alias origin
Effect of GC
    snapshot 1




                                                                                      active
                                                                                    invocation
                 garbage collection
    snapshot 2




                     Legend           running/completed method invocation   alias    alias origin
Evaluation memory usage (1)
 2.5e+09                                                              1e+07
                 Number of aliases allocated (left Y-axis)
               Number of aliases in memory (right Y-axis)
               Number of objects in memory (right Y-axis)
  2e+09                                                               8e+06



 1.5e+09                                                              6e+06



  1e+09                                                               4e+06



  5e+08                                                               2e+06



      0                                                               0
           0            200           400              600   800   1000
                                            #classes
Evaluation memory usage (2)
  7e+06                                                                50
               Number of aliases allocated
              Number of aliases in memory
  6e+06       Number of objects in memory
                 Ratio between aliases in                              40
                   memory and allocated
  5e+06

                                                                       30
  4e+06




                                                                            %
  3e+06
                                                                       20

  2e+06
                                                                       10
  1e+06


      0                                                                0
          0       2      4       6      8     10   12   14   16   18
                                        #samples
Evaluation memory usage (3)
  1e+07
           Number of aliases allocated
  9e+06   Number of aliases in memory
          Number of objects in memory
  8e+06

  7e+06

  6e+06

  5e+06

  4e+06

  3e+06

  2e+06

  1e+06

      0
                   5             10              15   20   25
                                         #requests
Evaluation run-time overhead


                        Overhead           GC
     Recording off            1.15       1.6%
     Recording on             3.84     27.6%
    Average over 6 standard Smalltalk benchmarks


           Largest overhead: 6.91
Conclusion

✔   Retains important history – even if old
✔   Memory consumption limited in the best case,
    slowly growing in the worst case
✔   Relative low run-time overhead

✖   Missing control flow dependencies

Practical Object-Oriented Back-in-Time Debugging

  • 1.
    Practical Object-Oriented Back-in-Time Debugging AdrianLienhard, Tudor Gîrba and Oscar Nierstrasz Software Composition Group University of Bern, Switzerland
  • 2.
    Debugger call stack class Account { Money balance; void deposit(Money amount) { NullPointerException >> this.balance += money; } ... } ..................
  • 3.
    Debugger call stack class Account { Money balance; void deposit(Money amount) { NullPointerException >> this.balance += money; } ... } class Company { void pay(Money money, Person person) { person.account().deposit(money); } } ... where does the account object come from?
  • 4.
    Debugger call stack class Account { Money balance; void deposit(Money amount) { NullPointerException >> this.balance += money; } ... } class Company { void pay(Money money, Person person) { person.account().deposit(money); } } ... where does the return account object come from?
  • 5.
    Debugger call stack class Account { Money balance; void deposit(Money amount) { NullPointerException >> this.balance += money; } ... } class Company { void pay(Money money, Person person) { person.account().deposit(money); } } ... where does the field read return account object come from?
  • 6.
    Debugger call stack class Account { Money balance; void deposit(Money amount) { this.balance += money; } ... } class Person { class Company { void createAccount(Bank bank) { void pay(Money money, Person perso this.account = bank.openAccount(); person.account().deposit(money); } } } } ... where doe return field write field read return account o come from
  • 7.
    class Acc Money b void de this. } ... } class Bank { class Person { class Com Account openAccount() { void createAccount(Bank bank) { void pa return new Account(); this.account = bank.openAccount(); perso } } } } } } ... allocation return field write field read
  • 8.
    Debugger call stack class Account { Money balance; void deposit(Money amount) { NullPointerException >> this.balance += money; } ... } class Bank { class Person { class Company { Account openAccount() { void createAccount(Bank bank) { void pay(Money money, Person person) { return new Account(); this.account = bank.openAccount(); person.account().deposit(money); } } } } } } ... where does the allocation return field write field read return account object come from? In 50% of the cases the execution stack contains essentially no information about the bug’s cause. [Liblit PLDI’05]
  • 9.
    class Bank { class Person { class Company { Account openAccount() { void createAccount(Bank bank) { void pay(Money money, Person person) { return new Account(); this.account = bank.openAccount(); person.account().deposit(money); } } } } } } ... where does the allocation return field write field read return account object come from? History recorded by a back-in-time debugger t Challenges: amount of data execution overhead
  • 10.
    Approaches Omniscient Trace-oriented Trace-oriented Debugger1 debugger2 (partial) debugger2 (full) + limited memory usage – requires extensive hardware resources – loss of old history – loss of history + complete history – overhead 100x + overhead 10x – overhead 100x 1) Lewis AADEBUG’03 2) Pothier etal. OOPSLA’07
  • 11.
    Approaches Omniscient Trace-oriented Trace-oriented Debugger1 debugger2 (partial) debugger2 (full) st o f all? Be dm emor y + limite erhead v + low o histor y te + co mple + limited memory usage – requires extensive hardware resources – loss of old history – loss of history + complete history – overhead 100x + overhead 10x – overhead 100x 1) Lewis AADEBUG’03 2) Pothier etal. OOPSLA’07
  • 12.
    Approaches Omniscient Trace-oriented Trace-oriented Debugger1 debugger2 (partial) debugger2 (full) st o f all? Be emor y ited m ead + lim overh + low istory e vant h + rel + limited memory usage – requires extensive hardware resources – loss of old history – loss of history + complete history – overhead 100x + overhead 10x – overhead 100x 1) Lewis AADEBUG’03 2) Pothier etal. OOPSLA’07
  • 13.
    At runtime deletehistory when it gets irrelevant Relevant questions: How was this object passed here? What were the previous values of a field and where (call stack) were they assigned? Only history of live objects needed! Efficient mechanism required to identify irrelevant datapoints at runtime
  • 14.
    How we cando it VM that models history as first-class objects Keep history together with regular objects in memory Use GC to efficiently delete no longer needed history
  • 15.
    First-class references Typical model: object Object Flow VM: references are references as direct pointers represented as alias objects regular objects alias header header header header field_1 field_1 header ... value field_2 field_2 ... context .... .... field_n pointer field_n origin predecessor ...
  • 16.
    Object Flow VMmodel caller 0..1 MethodInvocation * context 1 target parameter field or array * 1 element Value Alias value *
  • 17.
    Object Flow VMmodel caller 0..1 MethodInvocation * context 1 target parameter field or array * 1 element predecessor Value Alias value * 0..1 0..1 * 0..1 origin
  • 18.
    Historical object state account= new Account() t1 balance value :Account init@t1 null
  • 19.
    Historical object state account= new Account() t1 ... account.balance = 17 t2 value init@t1 null predecessor balance value :Account field-write@t2 17
  • 20.
    Historical object state account= new Account() t1 ... account.balance = 17 t2 ... account.balance = 42 t3 value init@t1 null predecessor value field-write@t2 17 predecessor balance value :Account field-write@t3 42
  • 21.
    Object Flow allocation class Person { return field write field read field read parameter return void printOn(Stream stream) { stream.print(this.account); } } return field-write field-read field-read return parameter allocation active invocation Legend running/completed method invocation alias alias origin
  • 22.
    Effect of GC snapshot 1 active invocation garbage collection snapshot 2 Legend running/completed method invocation alias alias origin
  • 23.
    Evaluation memory usage(1) 2.5e+09 1e+07 Number of aliases allocated (left Y-axis) Number of aliases in memory (right Y-axis) Number of objects in memory (right Y-axis) 2e+09 8e+06 1.5e+09 6e+06 1e+09 4e+06 5e+08 2e+06 0 0 0 200 400 600 800 1000 #classes
  • 24.
    Evaluation memory usage(2) 7e+06 50 Number of aliases allocated Number of aliases in memory 6e+06 Number of objects in memory Ratio between aliases in 40 memory and allocated 5e+06 30 4e+06 % 3e+06 20 2e+06 10 1e+06 0 0 0 2 4 6 8 10 12 14 16 18 #samples
  • 25.
    Evaluation memory usage(3) 1e+07 Number of aliases allocated 9e+06 Number of aliases in memory Number of objects in memory 8e+06 7e+06 6e+06 5e+06 4e+06 3e+06 2e+06 1e+06 0 5 10 15 20 25 #requests
  • 26.
    Evaluation run-time overhead Overhead GC Recording off 1.15 1.6% Recording on 3.84 27.6% Average over 6 standard Smalltalk benchmarks Largest overhead: 6.91
  • 27.
    Conclusion ✔ Retains important history – even if old ✔ Memory consumption limited in the best case, slowly growing in the worst case ✔ Relative low run-time overhead ✖ Missing control flow dependencies