3 ms·
There is a lot of tail-chasing going on here. When a question like "How X works" is posed, it's best to pause for a minute and solve for the most general form
by mahmud 16y ago
There is a lot of tail-chasing going on here.
When a question like "How X works" is posed, it's best to pause for a minute and solve for the most general form of X, not specific instances.
Whenever you see C, C++, Unix or assembly in what should be a very "foundations" type article, you need to pause and ask yourself "why?". These platform-dependent details don't help us learn anything at all, and their presence is essay-smell.
For debuggers, no two are ever exactly alike. It's a generic term for a class of software that's more of a continuum. At a minimum, they help us set "breakpoints" at specific locations, and allow for manual intervention when that location is executed, and permit us to inspect or edit the application state at that moment.
That's the most generic description of it, and every italized word above is a semantic mine-field, if we take into account the breadth of programming paradigms and their vast differences in machine implementation, program-shape (is "code" a vector? tree? graph?) and the subtleties of their execution models, distribution (where is program location), and temporal properties (when is the program running? time, what a precious concept that we take for granted!)
For the interruption problem, there are a few common approaches:
+ By instruction editing, for linearly executable programs where the "code" is a writable vector, it's common to insert specific debugging instructions (HLL code instrumentation falls under this)
+ By an interrupt table; for programs where the executing machine is virtual or itself programmable, it's sometimes easier to assign breakpoints to locations within the program, and trigger an interrupt when that location is reached. The machine maintains an "interrupt table", either a global one, or per "application" (i.e. process, thread, your granularity du-jour, etc.)
+ A rule-based approach where the break-point is triggered based on semantic meanings produced by the program as it executes, or based on the shapes of its execution and dataflow graphs. Requires the machine to be programmed almost at the same abstraction level as the application, and perhaps tight integration. Most debuggers for logic programming languages operate at this level; they're more equation solvers than blunt shells for machine memory.
And many, may more. There are as many debugger designs as there are programming paradigms, execution models or language implementations. Free yourself from the short-sighted tyranny of antique designs (blech Unix and x86!) and discover the wonderful world of formal systems and abstraction! languages, machines, type-systems and semantics, all on a whim, as weird and wonderful as you want them to be.
- barrkel 16y agoIt's all very well to understand something in the abstract, but frequently that doesn't easily translate to the specifics without a lot of running around mapping concepts. When your machine is abstract, or even better, a concrete implementation of something virtual, the kinds of manipulations you need for debugging are pretty trivial. But if that's all you know about debugging - only understanding it at that high a level - it will limit your practical usefulness actually getting work done in many scenarios. Device drivers generally don't run on virtual machines; likewise most C programs, nor most VM implementations, nor most interop through foreign function interfaces in heterogeneous environments. Understanding how these things can be debugged on any particular architecture is applied knowledge, but it is knowledge nonetheless, and no less worthy of a blog post written for those who would learn more about the topic. Considering a concrete case that people using Delphi for Windows take for granted: integer divide by zero diagnostics, communicated via exceptions on Win32. If your implementation is a virtual machine, creating a debugger to handle this kind of situation is ridiculously trivial; it's just a special case in however you implement divisions. On the x86, it's a little trickier; but Windows makes it easier for you. I don't remember all the details now (it actually may not even be for integer divide, but I think it is), Windows will handle the CPU interrupt, look at the faulting code, disassemble it, figure out what kind of problem it was, and then make sure to dispatch a structured exception corresponding to an integer divide by zero, rather than a more general arithmetic exception. It does this work for Win32, but not Win64, as I recall. Now, the mechanisms for how structured exceptions are dispatched is an article in itself, and very different for Win32 vs Win64; then there's the details of how debugger events are propagated to the debugger in Windows, etc. All this is part of "how debuggers work" in the concrete. But if all you have is an abstract understanding of debuggers, it won't help you much when you're thrown into the deep pool of real-world writing of debuggers. The devil is in the details; and you get paid for dealing with that devil, not for floating around with the angels of "clarity of thought". The fact is, you won't get much done in the large without also having that clarity of thought at the higher level, and in particular, you won't innovate much.
- mahmud 16y agoWhen the title of the article is "How debuggers work", I thought it might be more profitable to actually fulfill that premise, and not get side-tracked by the quirks of ptrace. (For example, no one could implement a debugger for, say, python scripts just reading this article.) A better title might have been "how to trace child processes under POSIX". Solving the general case is not just an academic exercise, it's also a richly rewarding learning experience. When people learn to think abstractly, none of these implementation details really matter. You can always specialize and learn the details of an specific instance; it's best to get a good grasp of the big-picture. [Edit: Instead of discussing "debuggers", in general, I hoped to narrow down and focus on just interruption mechanisms. Third-party execution tracing and stepping are usually outside language semantics, so there is some "art" to gaining control from an executing program. It would be nice to catalog the lore, and I hope others add to the three I have suggested above]