Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Tools

Debugging TSR Programs


FEB89: DEBUGGING TSR PROGRAMS

Costas Menico is a senior software developer and part owner of The Software Bottling Company, where he has worked with assembly language and TSR programs for more than five years. He can be reached at 6600 Long Island Expressway, Maspeth, NY 11378.


One of the most unnerving aspects of writing terminate-and-stay-resident (TSR) programs is debugging them. Compounding this troublesome fact is that many commercial debuggers, including Debug, don't have the ability to debug TSR programs. One solution, of course, is to buy a sophisticated (and expensive) debugger, but this isn't always the most attractive or economical solution unless you are developing TSR programs on a full-time basis.

Because I found Borland's Turbo Debugger to be a good, low-cost debugger, I decided to investigate ways of making it handle TSR programs in an easy and respectable manner. The techniques I developed, and that I describe here, have been used to debug commercially available TSR programs (Software Bottling's Flash-Up and Speed Screen, for instance).

Furthermore, there's no reason why the same techniques can't be applied to debuggers other than Turbo Debugger. In this article, I'll briefly describe how TSR programs work, then talk about how you can debug them when things go wrong. As an example, I'll use a typical (but bare boned) TSR program called KEYSWAP (assembled in Turbo Assembler) and look into why you can't normally debug it with Turbo Debugger. This sample TSR program makes the keys F8 and F10 act as if you had pressed the up arrow and down arrow, respectively. In many ways, KEYSWAP is the foundation of a keyboard macro program.

The TSR Problem

A TSR program really has two parts: The first part actually performs the functions you designed it to perform, and the second part consists of the code that loads the program, chains to any interrupt vectors, and terminates with a special DOS int 21h call to stay resident. Example 1 illustrates the flow of a typical TSR program.

Example 1: Typical structure of a TSR program

  ; This portion remains resident
       tsr data
       tsr interrupt handlers
       tsr other programs

  ; This portion is freed when the DOS Terminate
  ; and stay resident function is executed.
       data area
  load entry point:
       create stack
       call other check routines
       (e.g., DOS version, parameters, available memory, etc.)
       chain tsr interrupt handlers
       determine size of tsr to keep
       call DOS TSR function
  end

If you run a TSR program such as KEYSWAP with a debugger in the normal manner --that is, you do a file/load and then run it --you will simply get a message saying that the program and terminated and some error code. When you quit the debugger, your program will not be resident because both the debugger and your program will terminate. If you've chained to any interrupts, your system will probably crash because the chained interrupt vectors will still be there but your program and the debugger will be gone. This means any chained interrupt vectors will be pointing into what could be random instructions and data.

The concept behind the solution to this sort of problem is actually quite simple. Instead of terminating KEYSWAP using the normal terminate-but-stay-resident DOS interrupt function, I take advantage of the DOS load-and-execute function to load and execute COMMAND.COM. The effect of this is twofold: first KEYSWAP seems to be memory-resident, and second, Turbo Debugger is there at the touch of a hot key if you need to debug the program.

Using and Debugging a Typical TSR Program

To use the sample TSR program as an executable program, assemble KEYSWAP.ASM (Listing One, page 104) into KEYSWAP.OBJ by typing TASM KEYSWAP (assuming you're using Turbo Assembler). Do not include the file TSRDEBUG.ASM in Listing Two, page 105. Next create the executable file KEYSWAP.COM from KEYSWAP.OBJ by typing TLINK KEYSWAP /T. You can then load the TSR program into memory by typing KEYSWAP at the DOS prompt. (As this article is about debugging TSR programs and not about how to write them, I have not provided KEYSWAP with the ability to check if it has been already loaded in memory or the ability to be unloaded from memory, except by rebooting.)

To debug the program, you assemble and link KEYSWAP.ASM as before, except now you include the file TSRDEBUG.ASM (Listing Two). To assemble KEYSWAP.ASM and include the special debugging code in file TSRDEBUG.ASM, type TASM KEYSWAP /DDEBUG. A file called KEYSWAP.OBJ is then created. Next, link KEYSWAP.OBJ to create KEYSWAP .COM and KEYSWAP.MAP by typing TLINK KEYSWAP /M /T. Finally, type TMAP KEYSWAP to create the file KEYSWAP.TDS, which contains symbolic information for Turbo Debugger, from KEYSWAP.MAP.

You should now have files called KEYSWAP.COM and KEYSWAP.TDS ready to use for debugging. All other files are not required by the debugger.

To run, start Turbo Debugger and load KEYSWAP.COM using the file/load menu selection, then press F9 to execute. KEYSWAP will chain int16 handler on interrupt 16h. It will then call the procedure tsr_simulate, which frees all memory starting from the label load_tsr. It then proceeds to chain int9handler. After saving the stack registers into a memory location, KEYSWAP loads and executes COMMAND.COM, which must be in the root directory of drive C:. At this point you will get the DOS prompt. Your program and Turbo Debugger are now both memory-resident, and you can debug and set breakpoints by activating the debugger.

To activate the debugger, press Ctrl-Enter. The program will be interrupted because int9handler checks whether Ctrl-Enter was pressed, and if it was, the handler executes the breakpoint int 3, which is the breakpoint interrupt to activate any debugger. (If you have a hardware switch, known as an NMI switch, you may use it instead of Ctrl-Enter.)

The CPU window will show the point at which execution was interrupted. Because the int 3 instruction is in KEYSWAP, you can press PgUp or PgDn and view both code and symbols. You can also go to a procedure by pressing Alt-G (Goto) and entering a label that is public in your program.

You can now set breakpoints in the KEYSWAP code at the points where you want to break and start tracing. As an example, set a breakpoint at the label f10pressed, which is in int16handler and was declared public. The instruction at this label is executed when you press the F10 key while in your application. Press Alt-F2 and enter the label name. This sets a breakpoint. Press F9 (Run) to exit from the debugger and return to the DOS prompt.

(Make sure that you do not alter any registers or flags or start executing at another instruction. Do not try to do any disk access either, such as loading a different program. You may have interrupted DOS in the middle, and because DOS is not reentrant, another DOS call is all DOS needs to crash your system.)

To run your application and use KEYSWAP, press the F10 key when you want to move the cursor down, and your debugger will activate because the breakpoint has been reached. Trace to check what your program is doing, and debug as you would with any other program. Pressing F9 (Run) will return you to your application.

To exit from the debugger and terminate KEYSWAP, type the Exit command at the DOS prompt. This command returns you to KEYSWAP at the instruction immediately following the load-and-execute function in the tsr_simulate procedure. The procedure will then unchain the interrupt handlers and terminate using the normal DOS terminate function. (For a list of the DOS functions used in this program, see Example 2.) You can now quit the debugger or restart KEYSWAP.

Example 2: DOS functions used in KEYSWAP

Note: The following is a list of the DOS INt 21h functions used in KEYSWAP. Remember that if any DOS function returns with the CARRY flag set, the function had a problem. Usually reason for the error is returned in the AX register. For more information, see the DOS Technical Reference manual or one of the dozens of books on the subject.

  Set the vector of interrupt:
  Input:     AH = 25h, AL = interrupt #
             DS:DX = segment & offset value of new interrupt handler

  Terminate but stay resident:
  Input:     AH = 31h, AL = errorlevel
             DX = # of paragraphs to keep

  Get the vector of interrupt:
  Input:
       AH = 35h, AL = interrupt #
  Output:
       ES:BX = segment & offset value of the current interrupt handler

  Shrink allocated memory:
  Input:     AH = 4Ah, ES = Segment to shrink
             BX = paragraphs to keep

  Load & Execute:
  Input:     AH = 4Bh, AL = 0 (If AL = 3 then it only loads)
             ES:BX = segment and offset of params block
                      (for param block format see the program)
             DS:DX = segment & offset of command (ASCIIZ string)
  Output: None, but all registers including SS and SP are
          destroyed.

  Terminate program:

  Input:     AH = 4Ch, AL = error level.

Flow of KEYSWAP Without the DEBUG Code

When KEYSWAP starts up, it first sets up its own stack. Always use your own stack as if it were your own parachute. The next step is to chain the int16handler keyboard handler so that you can check for the F8 and F10 keys. The last step is to determine the size of the program you wish to keep in memory. You normally do not want to keep the loading portion of KEYSWAP, so you figure out the size by taking the offset of the loader's starting address, which is the label load_tsr. You now call DOS to terminate but stay resident.

On entry, int16handler checks to see if the keyboard function called for is a "getkey". If not, it jumps directly to the old interrupt vector. If the function is a get key, int16handler calls the old interrupt to get the key. Upon return, it inspects the ax register for an F8 or F10 key. If either of those keys was pressed, it substitutes the corresponding up or down arrow keys into ax and returns to the original caller.

KEYSWAP with the Debug Code

To debug KEYSWAP you must compile it with the /ddebug option, as noted earlier. You will notice that I use the conditional-assembly directives if ... def ... else ... endif to include the debugging code. This code is in the file TSRDEBUG.ASM. The debugging code adds the following procedures: int9handler, tsr_simulate, and unchain_vector.

The procedure int9handler checks to see if the Ctrl-Enter key was pressed and then executes an int 3 to activate Turbo Debugger. The procedure tsr_simulate frees memory, starting at the label load_tsr. It then executes COMMAND.COM, at which point the DOS prompt appears. When you terminate your debugging and wish to go back, type Exit at the DOS prompt, which returns you to the debugger to recover the stack registers and unchains the vectors int9handler and int16handler. It will then execute the normal DOS terminate function. The procedure unchain_vector unchains the int16handler vector when you have finished and puts back the original one. If you do not do this, and exit from the debugger, you will eventually crash the computer. The debug code also adds working storage for these three procedures.

Caveats, Conditions, and Other Warnings

While debugging your memory-resident program with Turbo Debugger, the program memory size is the size of Turbo Debugger plus your program size. Do a CHKDSK after your program runs to see how much memory is left.

If you interrupted your program by pressing Ctrl-Enter or with a breakpoint, you should not try to use any of Turbo Debugger's menu functions that access the disk. These include such choices as file/load and options/save. If you do attempt to use filing choices, you may crash DOS. The reason is that DOS is not reentrant, and so it cannot be called again when busy. This includes calls to DOS from the debugger. Microsoft provides an undocumented function to detect if Dos is busy but I will not discuss it here.

If Ctrl-Enter does not respond, the chances are that your program crashed in some loop and interrupts have been turned off, which means that the keyboard interrupts are ignored by the CPU. If you have an NMI switch, try using it to see if your program is stuck in a loop. Beware, though, because the only way out may be by rebooting.

Never leave int 3 (breakpoint) instructions in your finished program. Although they are supposed to be harmless if no debugger is loaded, I have known some machines that will not run programs containing int 3s. (I had to write a TSR program to trap the int 3s!)

The reason why I use Ctrl-Enter and int9handler to activate the debugger is because Turbo Debugger's hot key (Ctrl-Break) will not activate when inside applications. You could, however, remove int9handler if you have an NMI hardware switch. I also recommend changing the Turbo Debugger hot key from Ctrl-Break to something else.

The debugging concept presented in this article will work in a similar manner for .EXE programs that run TSR programs, although you do have to take the different segments into account. You can also extend this concept to languages such as C and Pascal when you create TSR programs using them. These languages have a built-in load-and-execute function; the trick is to know how much memory to free before you call it.

Conclusions

No matter how well we design, no matter how well we code, and no matter how much we know, our programs will not necessarily run bug free once compiled. That goes twice for TSR programs. The trick is to minimize the time it takes to debug using tools such as Turbo Debugger. I hope this article will give you an extra tool in the battle against Tyranno-Saurus Rex (TSR) bugs.

_Debugging TSR Programs_ by Costas Menico

[LISTING ONE]

<a name="006e_000d">

;
;    Set TABS to 8 for viewing with editor.
;
;-------------------------------------------------------;
; KEYSWAP program                         ;
; By    Costas Menico.               ;
;      Software Bottling Company      ;
;       6600 Long Island Expressway      ;
;      Maspeth, N.Y. 11378         ;
;      718-458-3700            ;
; This program will swap the F8 & F10 key for the    ;
;   Up and Down arrow keys.            ;
; Demonstrator program for debugging memory resident   ;
;   program using Turbo Debugger.         ;
;-------------------------------------------------------;

   .model small

; Set key values.
; For the SCAN and ASCII codes of any other keys, see the BIOS manual
f8key   equ 4200h      ; define the F8 key
f10key   equ 4400h      ; define the F10 key
upkey   equ 4800h      ; define the up arrow key
downkey   equ 5000h      ; define the down arrow key

DOSCALL   equ <int 21h>       ; DOS interrupt call
LOADSEG MACRO segr1, segr2   ; Load segment register
   push segr2      ;   macro.
   pop segr1
   ENDM


; Publics area
public start, oldint16, int16handler, exit, test_for_key
public load_tsr, tsr_terminate, chain_vector, f10pressed

; Start Code
   .code
   assume cs:_text, ds:_text, es:_text, ss:nothing

;-------------------;
; Our program start ;
;-------------------;
   org 100h      ; COM file starting program address.
start:
   jmp load_tsr      ; Load our program resident.

; Data area. Since this is COM file all data
; and code are in the Code segment.
   evendata
oldint16    dd   0   ; Area for old interrupt 16h vector.
stackbot   dw  128 dup('?'); Our stack bottom.
stacktop   equ $-2      ; Our stack top.

;-----------------------------------------------;
; Entry to keyboard handler.          ;
; We get here when any application or DOS   ;
; executes an INT 16h.            ;
; Input:   AH=0   -   Get a key   ;
; Output:   The key in AX         ;
; Input:   AH=1   -   Check for key   ;
; Ouput:   The available key in AX      ;
; Input:   AH=2   -   Get shift flags   ;
; Output:   The shift flags in AL      ;
;-----------------------------------------------;
int16handler proc far
   pushf         ; Save flags status.
   or ah, ah      ; Are we getting a key?
   je test_for_key      ; Yes - goto to test for F10.

   popf         ; No - pop flags and
   jmp cs:oldint16      ;   jump to old interrupt handler.

; Get a pressed key and test if it was
;   and F10
test_for_key:
   popf         ; Pop the original flags.
   pushf         ; Push them again
   call cs:oldint16   ;   and simulate INT 16h.

   pushf         ; Save flags.
   cmp ax, f10key       ; is this an F10 key?
   jne is_it_f8      ;   no - check for F8.
f10pressed:
   mov ax, downkey      ;   yes - set swap key in ax.
   jmp short exit
is_it_f8:
   cmp ax, f8key       ; is this an F8 key?
   jne exit      ;   no - exit
   mov ax, upkey      ;   yes - set swap key in ax.
exit:
   popf         ; Pop the saved flags.

   iret         ; Return to caller with key in ax.
int16handler endp

IFDEF DEBUG
   ; Assemble this section to debug the TSR with Turbo Debugger.
   ; You must assemble with the /DDEBUG option.
   include tsrdebug.asm
ENDIF

;===============================================;
; Load KEYSWAP, and terminate but stay resident.;
; Once KEYSWAP is loaded all code from here on  ;
;   is discarded.            ;
;===============================================;

load_tsr:

   ; Set SP to our internal stack area
   cli         ; Do not interrupt while
   mov sp, offset stacktop   ;   stack pointer is being adjusted.
   sti         ; Now interrupt.

   call chain_vector   ; Install our keyboard interrupt.

IFDEF DEBUG
   ; Assemble this section to debug the TSR with Turbo Debugger.
   ; You must assemble with the /DDEBUG option.
   call tsr_simulate
ELSE
   ; Assemble this section to run without the debugging.
   call tsr_terminate
ENDIF

;-----------------------------;
; Save old interrupt 16 vector;
; and point it to our handler ;
;-----------------------------;
chain_vector proc uses ax bx dx es
   mov ax, 3516h      ; Get the int 16h keyboard vector.
   DOSCALL
   mov word ptr oldint16, bx; Save old vector in our data area.
   mov word ptr oldint16[2], es

   mov ax, 2516h      ; Set the new vector of our
   mov dx, offset int16handler; interrupt 16h keyboard handler.
   DOSCALL
   ret
chain_vector endp

;-----------------------------;
; Terminate and stay resident ;
;-----------------------------;
tsr_terminate proc
   mov dx, offset load_tsr   ; Get offset address of program
   mov cl, 4      ;   code to discard.
   shr dx, cl      ; Convert to pargraphs.
   inc dx         ; Round off to the next paragraph.
   mov ax, 3100h      ; Terminate & stay resident function.
   DOSCALL
tsr_terminate endp

@CurSeg ends

   end start




<a name="006e_000e"><a name="006e_000e">
<a name="006e_000f">
[LISTING TWO]
<a name="006e_000f">

;---------------------------------------------------------------;
; Include file TSRDEBUG.ASM                                     ;
;                        ;
; Assemble this code if you will debug with Turbo Debugger   ;
; You must assemble with the /DDEBUG option.         ;
;---------------------------------------------------------------;

public param, command_com
public tsr_simulate, unchain_vector, int9handler

BREAKPOINT   equ <int 3>   ; Debugger break point
ctrlflag   equ 4      ; Ctrl key BIOS indicator flag.
enterscan   equ 28      ; Scan code for Enter key.
kbdflags   equ 417h   ; BIOS keyboard flags location

; Load & Execute parameter block structure
execparam struc
envstr_addr   dw 0      ; Environment pointer
cmdline_ofs    dw 0      ; Offset to command line.
cmdline_seg   dw 0      ; Segment to command line.
fcb1_ofs   dw 0      ; Offset of fcb1.
fcb1_seg   dw 0      ; Segment of fcb1
fcb2_ptr   dw 0      ; Offset of fcb2.
fcb2_seg   dw 0      ; Segment of fcb2.
execparam ends


oldint9      dd 0      ; Area for old int 9 vector.
paramvals   equ <0,offset cmd_line,?,offset fcb1,?,offset fcb2,?>
;
; Shows below how the data above is initialized.
;
comment ^
   0,         ; Use current environment
   offset cmd_line,?    ; Point to command line.
   offset fcb1,?      ; Offset & Seg of fcb1.
   offset fcb2,?       ; Offset & Seg of fcb2.
^
param      execparam <paramvals>
command_com   db 'c:\command.com',0
cmd_line   db 0, 0dh   ; Command line is null.
savess      dw 0      ; Save SS here.
savesp      dw 0      ; Save SP here.
fcb1      db 16 dup(0)   ; Blank area for FCB1
fcb2      db 27 dup(0)   ; Blank area for FCB2

;-----------------------------------------------;
; Int 9 handler. Ctrl-Enter key check.      ;
; Each time a key is pressed it is checked   ;
;    if the Ctrl & Enter key were pressed   ;
;   simultaneously. If they were then we   ;
;   interrupt Turbo Debugger      ;
;-----------------------------------------------;
int9handler proc far
   pushf         ; Save the flags
   push ax         ; Save ax
   push ds         ; Save dx
   xor   ax, ax      ; Point DS to segment 0
   mov ds, ax
   ; Is the Ctrl key pressed?
   test byte ptr ds:[kbdflags], ctrlflag
   je exitint9      ; No - call old int 9 & exit.

   in al, 60h            ; Yes - get the scan code of key.
   cmp al, enterscan      ; Is it the Enter key?
   je breakkey      ; Yes - go to break Turbo Debug
exitint9:         ; No - call old int 9 & exit

   pop ds         ; Pop ds & ax before jump.
   pop ax
   popf         ; Pop flags
       jmp cs:oldint9      ; Jump to the old int 9

breakkey:
   pop ds         ; Pop ds & ax before jump.
   pop ax
   popf         ; Pop flags

   in al, 61h      ; Reset the keyboard controller
   mov ah, al      ;   for next key.
   or al, 80h
   out 61h, al
   xchg ah, al
   out 61h, al
   mov al, 20h      ; Enable interrupts
   out 20h, al

   BREAKPOINT           ; Execute our break point.
            ; Turbo Debugger STOPS HERE!.
            ;   To find out what the program
            ;   was doing press F7 (Trace)
            ;   twice.
   iret
int9handler endp

;---------------------------------------;
; Simulate Terminate and stay resident   ;
; Frees unecessary memory and loads &   ;
;    executes COMMAND.COM         ;
;---------------------------------------;
tsr_simulate proc
   ; Free unused memory.

   LOADSEG ES, CS      ; Point ES to our segment.
   mov bx, offset load_tsr   ; Get offset address of program
   mov cl, 4      ;   code to discard.
   shr bx, cl
   inc bx         ; round off to the next paragraph
   mov ah, 4ah      ; Shrink allocated block function
   DOSCALL         ; call DOS

   ; Save stack registers
   mov savess, ss      ; Save stack segment.
   mov savesp, sp      ; Save stack pointer.

   ; Chain unto int 9 for debugger break key.
   mov ax, 3509h      ; Get the int 9 keyboard vector.
   DOSCALL
   mov word ptr oldint9, bx; Save old vector in our data area.
   mov word ptr oldint9[2], es
   mov ax, 2509h      ; Set the new vector of our
   mov dx, offset int9handler; interrupt 9 keyboard handler.
   DOSCALL

   ; Load and execute COMMAND.COM

   LOADSEG ES, CS      ; Point ES to our segment
   mov dx, offset command_com; Point to COMMAND.COM file path.
   mov bx, offset param   ; Get address of param block.
   mov [bx].cmdline_seg, ds; Get segment of the command line.
   mov [bx].fcb1_seg, ds   ; Get segment of fcb1
   mov [bx].fcb2_seg, ds   ; Get segment of fcb2
   mov ax, 4b00h
   DOSCALL         ; Load and execute COMMAND.COM.


   ; Upon return form load and execute, all registers are
   ; destroyed and the necessary ones, must be recovered.

   cli         ; Turn interrupts off.
   mov ss, cs:savess   ; Recover stack segment.
   mov sp, cs:savesp   ; Recover stack pointer.
   sti

   LOADSEG DS, CS      ; Recover data segment

   ; Unchain all our handlers, and terminate program.

   mov ax, 2509h      ; Unchain our int 9 vector and put
   lds dx, oldint9      ;    original in.
   DOSCALL

   call unchain_vector   ; Unchain our keyboard handler

   mov ax, 4c00h      ; Terminate program.
   DOSCALL
tsr_simulate endp

;----------------------------------------;
; Unchain vectors before exiting debugger ;
;----------------------------------------;
unchain_vector proc uses ds ax
   mov ax, 2516h      ; Unchain our vector and put
   lds dx, oldint16   ;    original in.
   DOSCALL
   ret
unchain_vector endp




[Example 1: Typical structure of a TSR program]


; This portion remains resident
   tsr data
   tsr interrupt handlers
   tsr other programs

; This portion is freed when the DOS Terminate
; and Stay resident function is executed.
   data area
load entry point:
   create stack
   call other check routines
   (e.g. DOS version, parameters, available memory, etc.)
   chain tsr interrupt handlers
   determine size of tsr to keep
   call DOS TSR function
end




[Example 2: DOS functions used in KEYSWAP]

Note: The following is a list of the DOS INT 21h functions used
in KEYSWAP.  Remember that if any DOS function returns with the
CARRY flag set, the function had a problem. Usually the error
reason is returned in the AX register. For more information, see
the DOS Technical Reference manual, or one of the dozens of books
on the subject.

Set the vector of interrupt:
Input:   AH = 25h, AL = interrupt #
   DS:DX = segment & offset value of new interrupt handler.

Terminate but stay resident:
Input:   AH = 31h, AL = errorlevel
   DX = # of paragraphs to keep.

Get the vector of interrupt:
Input:
   AH = 35h, AL = interrupt #
Output:
   ES:BX = segment & offset value of the current interrupt handler.

Shrink allocated memory:
Input:   AH = 4Ah, ES = Segment to shrink
   BX = paragraphs to keep.

Load & Execute:
Input:   AH = 4Bh, AL = 0 (If AL = 3 then it only loads)
   ES:BX = segment and offset of params block
      (for param block format see the program)
   DS:DX = segement & offset of command (ASCIIZ string).
Output: None, but all registers including SS and SP are
   destroyed.

Terminate program:
Input:   AH = 4Ch, AL = errorlevel













Copyright © 1989, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.