OK, since we aren't getting any suggestions
, here is my brainstorming on the subject. Please add your thoughts, and maybe we can come up with a nice outline for how to make a generic debugger. I think I have to assume that the OS is not singletasking.
1. In general, there are 2 types of tasks running. There are "jobs", which are "supposed" to be protected from each other. And there are threads, which are little individual tasks that run as semi-independent sub-components of a job. Threads usually share memory with their parent job, whereas jobs have to go to some effort to share memory with each other.
2. There has to be some method of forking off new threads. To create a new job, you fork a thread, then the thread turns itself into a new job, by loading and executing a "job-type" app off the disk. A typical method is to use some form of a UNIX-like exec command.
3. When a user wants to debug an app, generally the debugger must be a second, independent application -- rather than something built into the kernel, or shell, or something. This allows the user to choose their favorite debugger, etc.
4. The way Intel built all their debugger stuff, "breakpoint" exceptions are generated while the targeted application is running. These presumably show up as "signal" events, which should be caught and handled.
5. (3) and (4) together mean that you have two apps that are supposed to be protected from each other, but you need signals from one app to be caught and handled by the other app.
6. The debugger app also needs to be able to access all of the user app's memory, to be able to properly display/modify/debug the memory of the user app.
7. (5) and (6) together seem to clearly mean that the user app must somehow be run as a *thread* of the debugger, rather than as an independent job.
8. (7) and (2) together seem to clearly indicate that there needs to be a special, secret way of calling the exec function. The exec function would do all the normal loading, but would not separate the exec'ed user app's memory space from the parent debugger app's memory space. It also probably leaves the parent debugger as the recipient of all system messages -- to be passed on to its child threads (including the user app) as the debugger chooses. Obviously, the debugger needs to remain in full control of all signal catching. Somehow, in fact, the debugger (as the parent job) needs to be able to completely override any subsequent "signal" calls made by the user app -- and then perhaps even simulate their function (the debugger cannot allow its signal catcher functions to be overridden by the user app)? The debugger parent function would set a breakpoint at the (system defined) load address of the user app, to start with. At that point, it could recover any other info it needed from the registers that got saved into a structure by the exception handler.
9. So, you have a debugger application that gets loaded into its own virtual memory space normally. Then you have a normal user app that expects to be loaded into an empty virtual memory space, that is actually getting loaded into the debugger's virtual memory space. This seems to clearly indicate that the debugger needs to relocate itself into a memory area that normal user apps would not be allowed to touch -- so that the two apps could coexist in the same virtual memory area without stomping on each other.
10. Not only does the debugger need to relocate itself in virtual memory, but it may also be necessary to temporarily replace bytes in the user app's executable with INT 3 breakpoint bytes. This means that the debugger must be able to somehow request that any write-protection on the executable code stored in memory be turned off, so that these "self-modifying code" techniques will work. (Of course, the debugger needs to be written to architecture-specific standards for self-modfying code.)
11. So, to make debuggers work, you probably need special features in exec(), and signal(), and you need some ability to manipulate the permissions in the debugger app's virtual memory area, to permit self-modifying code.