This document describes an approach called Object Flow Virtual Machine (VM) that enables efficient back-in-time debugging by modeling object references and history as first-class objects. The VM uses garbage collection to delete irrelevant historical data, keeping memory usage low. Evaluation shows the approach retains important history with limited memory growth and reasonable overhead compared to no recording.
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 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
14. 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
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 VM model
caller 0..1
MethodInvocation
*
context 1
target parameter
field or array *
1 element
Value Alias
value *
17. 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
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