3

The x86 architecture provides a hardware single-step trap for debugging. How much does it slow down the running program?

If, say, a Linux kernel function was created to do nothing but single step a process, how much slower would that process run? Does anybody have a good estimate?

I'm wondering this after spending a week tracking down a threading bug. It'd be nice if these bugs could be reproduced. How about a feature that executed two threads sequentially, alternating between executing an instruction on one thread and then an instruction on the other, in a predictable manner. I'm thinking of a pseudo-random number generator that would produce a string of bits - 0 means execute an instruction on thread 1, 1 means execute an instruction on thread 2.

Then you could seed the PRNG and get a reproducible interleaving of instructions. Different PRNG seeds would produce different interleaving patterns. You could run a test case under a bunch of PRNG seeds, and if you found one that triggered a failure, reproduce it.

Anybody heard of anything like this being done?

Update:

How could it be done?

Assume we're running on something like a Core i5, where you've got 4 processor states and 2 cores. We're using the single-step trap to bounce a process back and forth from its user space to its kernel space. So that's two of the states, right? Then we've got the other thread running on the other core with its user space and kernel space states, right? There's something like a spinlock (probably two spinlocks) synchronizing the two kernel threads. Each one spinlocks while the other one steps its user space a few instructions, then they synchronize and exchange roles.

Sounds like we've got just the right number of threads and cores so that everything fits on the chip at once. But how fast does it run?

We could just try it. Somebody could write some kernel code. Or maybe somebody knows.

All that fancy stuff these new chips do. I'd be impressed, and not totally surprised, if it ran quick.

1
  • The hardware single-step trap is a feature of the x86 architecture and is not specific to Intel processors.
    – bwDraco
    Aug 2, 2014 at 2:40

2 Answers 2

1

The single-step trap works by raising an exception after each instruction is complete. The usual use for that trap is that your debugger catches this exception and allows you to look around at things before "stepping" through the next instruction.

If you're thinking about doing this for tracing, making a detailed log of what your code is doing, your tracer/debugger is going to be invoked as an exception handler, log whatever you wanted to log, and then dismiss the exception - repeat. I expect this would slow the execution rate of the code you're tracing by a factor of one to several hundred... at least.

Regarding your ideas for interleaving instructions from multiple threads, this is not the way to solve your serialization problem. You need to solve it - provably - in the design.

4
  • Thank you for your perspective on provable design! I'm not that good a programmer. I just slap together 15,000 lines of a mishmash of C, C++, C++11, Intel thread primitives mixed in with the C++11 stuff, inline assembly, all kinds of GNU dependencies, and call it hoffman. I'd really like to have a debugging tool like what I'm describing! Aug 2, 2014 at 6:34
  • More seriously, as I explained in the update to my original post, I don't think the trace/debugger needs to be involved at all. A specialized kernel module is called for. Aug 2, 2014 at 6:53
  • A debugging tool like you're describing isn't a bad idea at all. I'm just saying it's not a great way to try to find, fix, or prove non-existent, serialization bugs. Aug 2, 2014 at 7:45
  • 1
    ...that's if it worked at normal speed. As it is, the slowdown may well mask serialization problems. (Or it might bring them out...) Aug 2, 2014 at 8:12
0

Your approach seems useful and I have pondered about a similar problem.
How can this be done? (There are some quite alternative ways too, including static analysis, or reflection and coroutines).
But your method can be greatly optimised in two alternative ways in case you are randomly going to step over multiple instructions (perhaps even over many instructions, which seems also natural thing to do):

1) Decide the random length of next instruction sequence before starting the sequence. Use disassembler and put int 3 at the end of the sequence, instead of single stepping.

2) In case you do not want to use int 3 for some reason or do not entirely trust your disassembler, you can use single stepping and then copy the executed instructions to a new memory area.
Now next time the random generator decides to execute same amount of sequential steps starting from same program location, just jump to the new memory area containing the copied instructions and run until the end of that sequence there without single stepping. At the end of the instruction sequence you then need to call back to your debugging framework.

For both approaches you need to add special handling for calls, jumps and conditional jumps.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .