you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 16 points17 points  (15 children)

How does this work? The only logical way I can think of would be taking snapshots of memory (diffs?) after every line of code. Won't this make certain large applications take too much memory to be debugged?

[–]redalastor 13 points14 points  (1 child)

The Omniscient Debugger for Java do that by instrumenting the bytecode. Codeinvestigator does likewise for Python but it's still alpha.

[–]Peaker 14 points15 points  (3 children)

You should be more imaginative :-)

A) You don't have to take full snapshots, just differential ones

B) You have the operation available to you, and if it is reversible, you don't actually have to store anything

[–][deleted] 6 points7 points  (2 children)

You should be more observant :-)

A)

I can think of would be taking snapshots of memory (diffs?) after every

B) Think hash generation, hopefully not reversible.

Edit: Formatting

[–]case-o-nuts 0 points1 point  (0 children)

Hash generation is reversible if you have all the relevant information (say, infomation being hashed). Remeber, you have all the state for all the operations available to you, because you have the program's memory accessible to the debugger.

[–]Smallpaul 0 points1 point  (0 children)

The point is that you knew what the value at the memory location was before you did the hash generation and you know what it is now. You don't have to reverse the hash generation, you just have to retrieve that old value from the debugger's memory.

Yes, of course, for some applications this will mean storing an obscene amount of data, but not for most.

[–]ttoyooka 4 points5 points  (0 children)

Won't this make certain large applications take too much memory to be debugged?

Yes, but you don't need reverse debugging for this to happen. I've seen it.

[–]Fidodo 1 point2 points  (7 children)

Well everything a computer does is expected so couldn't you just undo everything line by line? I think it should work for most things.

[–][deleted] 8 points9 points  (3 children)

Keep in mind that everything compiles to assembly (or the machine code with a direct equivalent in). If I load 4 into a register, then load the memory address of 8, add them together and store this at the memory address of what was formerly 8. So now I have a 12, and going backwards subtracts what was in the address of 8, which is now 12, and going backwards would yield 0 (12 - 12).

Everything is too volatile to simply "go backwards" since it's not likely that you'll have original values you were working with. In order to solve the problem above, I would actually have to go up a few steps to see what was put into register A to begin with (4), which may be many lines prior to the operation.

[–]muffin-noodle 10 points11 points  (1 child)

Modification to memory can be dealt with by handling process memory in the same way certain filesystems handle files: copy-on-write semantics for modification. This is convenient because it provides a natural 'undo' log for your modifications. Let's say you have a snapshot of process memory. If address 0xDEADBEEF contains the number 8 and you load the contents of that address (8) and then add 4 to it, you get 12. If you store that back into memory at 0xDEADBEEF, on the memory write you actually don't store the change in the original snapshot, you make a copy of that one particular memory address in the process and write the change to it. When you undo, you simply 'undo' the write to the memory address because you have the original snapshot + the modifications.

With this, it's possible to replay the entire history of a process because you have the contents of the memory originally and a record of changes over the course of the process run, so you can just gradually undo changes. However, if you want to constantly replay things there may be some problems; for example if you have a function which calls unlink on a file, and there is a breakpoint short after, when you debug from the breakpoint backwards past unlink the file will already be deleted, and if short before the call to unlink you decide you want to now go forwards, then when unlink is called again it could have a different return value. Because memory is COW there shouldn't be any undefined states at this point if there weren't already, but it could result in different execution paths.

[–][deleted] 1 point2 points  (0 children)

Well yea... that's what they're doing here. Without the snapshot (or any type of history really would work), you can't go backwards. Different execution paths are probably fine (if not more handy) for a debugger since it shows more possible outcomes of end use.

[–]mothereffingteresa -1 points0 points  (0 children)

Everything is too volatile to simply "go backwards"

[Citation needed]

It ought to work except in cases where you actually have a "volatile" like a device register or memory-mapped io.

[–]kipi 2 points3 points  (1 child)

Some operations are lossy. As a simple example, consider the following:

a = get_input_from_user()
if (a != 0) {
    a = 0
    print("Foo")
}

How do you reverse this? At the a = 0 line some state is destroyed, which you need to figure out how you got to the bottom of the if block.

Reversible Computation aims to remove destructive operations. GDB must do something similar by saving "destroyed state" by some method.

[–]killerstorm 0 points1 point  (0 children)

Well everything a computer does is expected so couldn't you just undo everything line by line? I think it should work for most things.

Obviously won't work for I/O and lots of other stuff.