practical object-oriented back-in-time debugging
DESCRIPTION
TRANSCRIPT
Practical Object-Oriented Back-in-Time Debugging
Adrian Lienhard, Tudor Gîrba and Oscar Nierstrasz
Software Composition Group University of Bern, Switzerland
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
Debugger call stack
....
....
....
....
..
NullPointerException >>
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
where does the
account object
come from?
Debugger call stack
...
NullPointerException >>
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
return
where does the
account object
come from?
Debugger call stack
...
NullPointerException >>
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
returnfield read
where does the
account object
come from?
Debugger call stack
...
NullPointerException >>
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Person {
void createAccount(Bank bank) {
this.account = bank.openAccount();
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
returnfield readfield writereturn
where does the
account object
come from?
Debugger call stack
...
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Bank {
Account openAccount() {
return new Account();
}
}
class Person {
void createAccount(Bank bank) {
this.account = bank.openAccount();
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
returnfield readfield writereturnallocation
where does the
account object
come from?
Debugger call stack
...
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Bank {
Account openAccount() {
return new Account();
}
}
class Person {
void createAccount(Bank bank) {
this.account = bank.openAccount();
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
returnfield readfield writereturnallocation
where does the
account object
come from?
Debugger call stack
...
NullPointerException >>
In 50% of the cases the execution stack contains essentially no information about the bug’s cause.
[Liblit PLDI’05]
class Company {
void pay(Money money, Person person) {
person.account().deposit(money);
}
}
class Bank {
Account openAccount() {
return new Account();
}
}
class Person {
void createAccount(Bank bank) {
this.account = bank.openAccount();
}
}
class Account {
Money balance;
void deposit(Money amount) {
this.balance += money;
}
...
}
returnfield readfield writereturnallocation
where does the
account object
come from?
Debugger call stack
...
NullPointerException >>
t
Challenges: amount of data execution overhead
History recorded by a back-in-time debugger
ApproachesOmniscient Debugger1
Trace-oriented debugger2 (full)
+ limited memory usage
Trace-oriented debugger2 (partial)
+ complete history– loss of old history
2) Pothier etal. OOPSLA’071) Lewis AADEBUG’03
– requires extensive hardware resources
– loss of history
– overhead 100x – overhead 100x+ overhead 10x
ApproachesOmniscient Debugger1
Trace-oriented debugger2 (full)
+ limited memory usage
Trace-oriented debugger2 (partial)
+ complete history– loss of old history
2) Pothier etal. OOPSLA’071) Lewis AADEBUG’03
– requires extensive hardware resources
– loss of history
– overhead 100x – overhead 100x+ overhead 10x
Best of all?
+ limited memory
+ low overhead
+ complete history
ApproachesOmniscient Debugger1
Trace-oriented debugger2 (full)
+ limited memory usage
Trace-oriented debugger2 (partial)
+ complete history– loss of old history
2) Pothier etal. OOPSLA’071) Lewis AADEBUG’03
– requires extensive hardware resources
– loss of history
– overhead 100x – overhead 100x+ overhead 10x
Best of all?
+ limited memory
+ low overhead
+ relevant history
Relevant questions:
At runtime delete history when it gets irrelevant
How was this object passed here?
What were the previous values of a fieldand 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 references as direct pointers
Object Flow VM: references are represented as alias objects
header
field_1
field_2
....
field_n
header
value
context
origin
predecessor
...
header
...
header
field_1
field_2
....
field_n
header
...
regular objects
pointer
alias
Object Flow VM model
MethodInvocation
context
AliasValuevalue
caller 0..1
*
*field or arrayelement
target parameter
1
*
1
Object Flow VM model
MethodInvocation
context
AliasValuevalue
origin0..1 * 0..1
0..1
predecessor
caller 0..1
*
*field or arrayelement
target parameter
1
*
1
Historical object state
:Account init@t1 null
valuebalance
account = new Account() t1
Historical object state
:Account field-write@t2
init@t1
17
null
predecessor
value
valuebalance
account = new Account() t1
...
account.balance = 17 t2
Historical object state
:Account
field-write@t2
field-write@t3
init@t1
17
42
null
predecessor
predecessor
value
value
valuebalance
account = new Account() t1
...
account.balance = 17 t2
...
account.balance = 42 t3
ObjectFlow
class Person {
void printOn(Stream stream) {
stream.print(this.account);
}
}
returnfield readfield writereturnallocation
parameterfield read
active
invocation
allocation
field-write field-read
parameter
field-read
return
return
running/completed method invocationLegend alias originalias
Effect of GC
active
invocation
running/completed method invocationLegend alias originalias
garbage collection
sn
ap
sh
ot
2sn
ap
sh
ot
1
Evaluation memory usage (1)
0
5e+08
1e+09
1.5e+09
2e+09
2.5e+09
0 200 400 600 800 1000 0
2e+06
4e+06
6e+06
8e+06
1e+07
#classes
Number of aliases allocated (left Y-axis)Number of aliases in memory (right Y-axis)Number of objects in memory (right Y-axis)
Evaluation memory usage (2)
0
1e+06
2e+06
3e+06
4e+06
5e+06
6e+06
7e+06
0 2 4 6 8 10 12 14 16 18 0
10
20
30
40
50
%
#samples
Number of aliases allocatedNumber of aliases in memoryNumber of objects in memory
Ratio between aliases in memory and allocated
Evaluation memory usage (3)
0
1e+06
2e+06
3e+06
4e+06
5e+06
6e+06
7e+06
8e+06
9e+06
1e+07
5 10 15 20 25
#requests
Number of aliases allocatedNumber of aliases in memoryNumber of objects in memory
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