Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

18KB

Developing Your Own 32-Bit Operating System — Chapter-12: The Debugger

  • Source: /library/Developing Your Own 32-Bit Operating System/source-file.pdf
  • PDF pages: 203–210
  • Pages without text: 203

12 The Debugger

182 Part III ■ MMURTL-Specific Details The debugger is built into the operating system. It is not a separate run file. The debugger provides the following functions: Display of instructions (disassembled) Dumping of linear memory as bytes or dwords Display of exchanges, and waiting messages or tasks Display of active tasks Set and clear an instruction breakpoint Full register display Selection of linear address to display Display of important OS structure addresses Using the Debugger In its current incarnation, the debugger is an assembly language, nonsymbolic debugger. Ex- tensive knowledge of the Intel processor instruction set is required to properly use the debugger. Entering the Debugger The debugger may be entered using the Debugger function key in the monitor, the Debug command in the command-line interpreter, or by placing an INT 03 instruction anywhere in your application's assembly language file. See the “Debugger Theory” section for more infor- mation on INT 03 instruction usage. The debugger may also start on its own if certain exceptions occur. Some exceptions are de- signed to take you directly into the debugger and display an error code indicating the problem. The most common exceptions are the General Protection Fault (OD hex) and the Page Fault (OE hex). When the exception occurs, the debugger is entered, and a red banner is displayed, along with the exception number. The registers can be examined to see where it happened, and why. This usually provides enough information to correct the offending code. Exiting the Debugger Pressing the Esc key will exit the debugger and begin execution at the next instruction. If the debugger was entered on a fault, pressing Esc will restart the debugger at the offending address and you will re-enter the debugger. See the “Debugger Theory” section for more infor- mation. The debugger will also exit using the Single Step command (Fl function key), and will be re- entered immediately following the execution of the next complete instruction.

Chapter 12 The Debugger Debugger Display The debugger display is divided into three sections. The complete general register set is dis- played along the right side of the screen, function keys are across the bottom, and the left side is the instruction and data display area. Certain debugger display functions, such as dumping data, will clear the screen for display. The registers and function keys will be redisplayed when the function is finished. Debugger Function Keys The debugger function key actions are described here: Fl SStep — Single Step. This returns to the application and executes one instruction, after which it immediately returns to the debugger where the next active code address and its associated instruction are displayed. F2 SetBP — Set Breakpoint. This sets the breakpoint at the currently displayed instruction. You may move down through the instructions, without executing them, using the down arrow key. You may also use F8 CrntAdd (Current Address) to select and display a new address to display before setting the breakpoint. F3 ClrBP — Clear Breakpoint. This clears the single breakpoint. F4 CS:EIP — Goto CS:EIP. This redisplays the current instruction pointer address. This be- comes the active address. F5 Exch — Display Exchanges. This displays all exchanges and shows messages or tasks that may be waiting there. Only exchanges that actually have a message or task will be displayed. F6 Tasks — Display Tasks. This displays all active tasks on the system. The task number, the associated job number, the address of the task's JCB, the address of the TSS, and the priority of the task are displayed with each one. F8 CrntAdd — Change Current Address. This enables you to set the code address the debugger is displaying (disassembling). This does not change the address scheduled for execution. The F4 CS:EIP function key can be used to return to the next address scheduled for execution. F9 DumpB — Dump Bytes. This requests a linear address, and then dumps the bytes located at that address. The address is displayed on the left, and all the data, including the ASCII, is shown to the right. The format is the same as the dump command in the CLI. F 1 0 — Dump Dwords. This requests a linear address, and then dumps the dwords located at that address. The address is displayed on the left, and all the data, including the ASCII, is shown to the right. Each byte of a dword is displayed in the proper order (high order, next, next, low order). This command is useful for dumping structures that are dword oriented, as most of them are.

I
184 Part III ■ MMURTL-Specific Details F12 Addlnfo — Address Information. This lists names and linear addresses of important struc- tures used during OS development. These structures can also be very useful during program debugging. These values are hard coded at system build. The abbreviations are given in the following list, with the full name of the structure and what use it may be during debugging. IDT — Interrupt Descriptor Table. This can be dumped to view interrupt vector types and addresses. This may be useful if you have set up an interrupt and you want to ensure it was encoded properly and placed in the table. GDT — Global Descriptor Table. This can be dumped to view all of the GDT entries. The GDT contains many items, including the TSS descriptor entries for your pro- gram. TSS descriptors point to your task state segment and also indicate the privilege level of the task. RQBs — Request Block Array. This is the address of the first request block in the array of request blocks allocated during system initialization. The request handle that you receive from the request primitive is actually a pointer, a linear address, that will be somewhere in this array. TSS1 — First TSS. The first two task state segments are static structures and are used for the operating system Monitor and debugger. This is the address of the monitor TSS. The debugger TSS is 512 bytes after this address. TSS3 — Dynamic TSSs. The rest of the task state segments are in several pages of allocated memory and are initialized during system startup. TSSs are numbered sequentially, which makes the first of the dynamic TSSs numbered three. Each TSS is 512 bytes. LBs — Link Blocks. These are 16-byte structures used as links in chains of messages and tasks waiting at an exchange. The array of link blocks are allocated as a static array. RdyQ — Ready Queue. This is the address of the 32 queue structures where tasks that are ready to run are queued. JCBs — Job Control Blocks. This the address of the array of Job Control Blocks. This is an array of 512-byte structures. The JCB is discussed in detail in Chapter 9, “Application Programming.” S VCs — Services Array. This is the address of the array of currently registered services. The names of the services and the exchanges they service are contained here. Exch — Exchanges. This is the address of the array of exchanges in allocated memory. When you receive an exchange number from the AllocExch( ) function, it is the index of that exchange in the array. Each exchange is 1 6 bytes. aTmr — Timer Blocks. This is the address of the array of timer blocks. The Sleep ( ) and Alarm ( ) functions each set up a timer block.

Chapter 12 ■ The Debugger H Debugging Your Application If your application is “going south” on you (crashing, locking up, entering the debugger, or whatever), you can set breakpoints in several locations of your program by editing the .ASM files, listed in your ATF file. You would insert the INT 03 instruction wherever you feel it would be useful. In some cases, just knowing how far your program made it is enough to solve the problem. You can start by searching for main in your primary assembly language file generated by the C compiler, or for .START in an assembly language program, and placing an INT 03 instruction there. It may help you to be able to identify high-level entry and exit code sequences (begin and end of a C function), and also how local variables are accessed. The following example shows typical entry and exit code found in a high-level procedure. PUSH EBP MOV EBP, ESP ; Lots of code here MOV ESP, EBP POP EBP RETN When local variables are accessed, they are always referenced below the frame pointer in memory. A single 4-byte integer is referenced as [EBP-4] or [EBP+FFFFFFFC], and displayed as an unsigned number. Stack parameters, or arguments, are always referenced above the frame pointer, such as [EBP+12]. With near calls like you would make inside your program, the last parameter is al- ways [EBP+8]. There is a symbolic debugger in MMURTL's near future, but for right now, it's down and dirty troubleshooting. Debugger Theory The Intel 32-bit processors have very powerful debugging features. They have internal debug registers that allow you to set code and data breakpoints. What this means is that you can set the processor to execute an interrupt, actually a trap, whenever any linear memory location is accessed, or at the beginning of any instruction. The INT 03 instruction may also be placed in your code, like with the older processors, without having to fill in special registers. There are four debug address registers in the processor, which means you can have four active breakpoints in your program if you use only the registers. The MMURTL debugger currently uses only one of these registers and allows only instruction breakpoints to be set.

I
186 Part III ■ MMURTL-Specific Details The INT 03 interrupt is just like any other on the system. When it occurs, entry 3 in the inter- rupt table is used to vector to a procedure or task. In MMURTL, it is an interrupt procedure that will switch to the debugger task after some fix-ups have been completed. Debugging in a multitasking operating system is complicated to say the least. The 386/486 processors make it much easier because they let you determine if the breakpoint is local to a single task or global to all tasks. Remember that several tasks may be executing the same piece of code. If you set a breakpoint using the INT 03 instruction, every task that executes it will produce the interrupt and enter the debugger. For applications, this really doesn't apply as much because they each have their own linear address space. The paging hardware translates the lin- ear address to a physical address for its own use. MMURTL's debugger is set up as a separate job. It has its own Job Control Block (JCB) and one task (its own TSS). The debugger is the highest priority task on the system (level 1). No other task operates at a priority that high. The debugger also has its own virtual screen, just like applications do. When writing an operating system, the debugger becomes one of the most important tools you can have. If you have problems before the debugger is initialized, it can mean hours of grueling debugging with very little indication of what's going on, usually a blank screen and nothing else. Even though the debugger has its own JCB and looks like another application, it has special privileges that allow it to operate outside the direct control of the kernel. It can remove the currently running task and make itself run, and do the reverse without the kernel even being aware of what has happened. The debugger must be able to do this to get its job done. When the debug interrupt activates, you initially enter an interrupt procedure that places the running task in a hold status. Hold status simply means that it is not placed on the ready queue as if it were a normal task switch. Instead, the current pRunTSS, a pointer to the currently run- ning TSS, is saved by the debugger, and the interrupt procedure does a task switch to the debugger's task. The debugger becomes the active job. This effectively replaces the running task with the debugger task. A few housekeeping chores must be done before the task switch is made. These include copy- ing the interrupted task's page directory entry into the debugger's JCB and the debugger's TSS. This is so the debugger is using the task's exact linear memory image. If you are going to debug a task, you must be able to access all of its memory, including allocated memory. The debugger cannot debug itself. It's actually an orphan when it comes to being a program in its own right. It doesn't have its own Page Directory or Page Tables like other jobs (programs). It almost, but not quite, becomes a task in the job it interrupted. The INT 03 interrupt is not the only way to enter MMURTL's debugger. The debugger is also entered when other fatal processor exceptions occur. If you look through the 386/486 docu- mentation, you will see that there are many interrupts, called exceptions, or faults, that can

Chapter 12 M The Debugger occur. Some of these are used by the OS to indicate something needs to be done, while others should not happen and are considered fatal for the task that caused them. Until MMURTL uses demand page virtual memory, a page fault is fatal to a task. It means it tried to access memory that didn't belong to it. The exceptions that cause entry into the debugger mc\ide faults, traps, and aborts (processor fatal) and are shown in Table 12.1. |
187 Table 12.1. Processor exceptions. No. Type Description 0* F Divide by zero 1 T/F Debug exception (debugger uses for single step) 3 T Breakpoint (set by debugger, or INT 03 in code) 4 T Overflow (lNT0 instruction) 5* F Bounds check (from bound instruction) 6* F Invalid opcodes (reserved instructions) 7* F Coprocessor not available (on Esc and wait) 8* 9 10* A Double fault (real bad news...) F Invalid TSS 11* F Segment not present 12* F Stack fault 13* F/T General protection fault 14* F Page fault 16 F Coprocessor error The asterisk (*) indicates that the return address points to faulting instruction. This return address is used by the debugger entry code. Some of the exceptions even place an error code on the stack after the return address. MMURTL has a very small interrupt procedure for each of these exceptions. These procedures get the information from the stack before switching to the debugger. The debugger uses this informa- tion to help you find the problem. When the debugger is entered, it displays the registers from the interrupted task. To do this, it uses the values in the task's Task State Segment (TSS). All the values are the same as when the exception occurred, except the CS and EIP. These contain the address of the exception handler. To be able to see the proper address of the code that was interrupted, you must get the return address from the stack and place it back into the TSS. This also has the effect of starting the task back at the instruction after it was interrupted, which is the next instruction to execute. This makes the debugger transparent, which is exactly what you want. This also allows you to restart the application after a breakpoint at the proper place.

Part III m MMURTL-Specific Details If the debugger was entered because of a fault, you should kill the offending job and restart, setting a breakpoint before the exception occurs, then single step while watching the registers to see where the problem is. The debugger exit code is just about the reverse of the code used to enter the debugger. The debugger removes itself as the running task and returns the interrupted task to the running state with the values in the TSS. This also includes switching the video back to the rightful owner. While you are in the debugger, all the registers from the interrupted task are displayed, along with any errors that may have been removed from the stack on a fault.

Powered by TurnKey Linux.