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

Your Own Protected-Mode Debugger


SEP92: YOUR OWN PROTECTED-MODE DEBUGGER

This article contains the following executables: DB.ARC

Rick is a software engineer specializing in systems programming and is the coautbor of Screen Machine, a screen design/prototyping/code generation utility. He can be reached at P.O. Box 1109, Half Moon Bay, CA 94019.


The 80386's debug registers and capabilities for virtualizing an 8086 real-mode environment have fostered some powerful software debuggers. Designing and implementing such debuggers is a good vehicle for exploring these 80386 features. This article describes a debugger I recently developed, DB.EXE, that utilizes the 80386 debug registers and protected and virtual 8086 modes. Note that because of their length, the full source code listings for DB are only available electronically.

DB enables breakpoints to be generated on code execution (including ROM code), interrupts, data accesses, and I/O accesses. Since DB does not use DOS or the BIOS, it facilitates system-level debugging. For example, you can debug a program which takes over INT 16h. In addition, DB can coexist and work with a real-mode debugger such as Debug or Codeview.

DB Architecture

DB consists of two layers which work together to utilize protected-mode features, provide interaction with the user, and process commands. DBISR.ASM contains all protected-mode code that handles exceptions and manipulates areas restricted to privilege-level 0 code. All other resident DB code operates in V86 mode at privilege level 3. DB was designed in this fashion to simplify debugging. It is very difficult to debug protected-mode code because most stand-alone software debuggers cannot be used. Thus, by placing most of the debugger's logic in the privilege-level 3 layer, that code could be debugged using a software debugger. It may also facilitate using a high-level language for that portion of the code, although in this implementation, DB has been written entirely in assembler.

In cases where privilege-level 3 code needs to manipulate an area (such as the debug registers) that can only be accessed by more privileged code, the privilege-level 3 layer of DB issues a user software interrupt (such as INT 60h). This causes a general-protection exception. The protected-mode exception handler in the privilege-level 0 layer of DB recognizes the user software interrupt as a request for privileged service and dispatches it to the appropriate routine.

Initialization

Initialization begins with the reprogramming of the master PIC, so that interrupts start at 20h rather than at 08h. This eliminates the possibility of collision between protected-mode exceptions and standard PC-hardware interrupts. For example, the protected-mode general-protection exception 0dh (one of the areas at the heart of DB) will no longer conflict with IRQ5, which normally generates interrupt 0dh.

The global descriptor table (GDT) is the next area to be initialized. Entries are created for all segments to be used by DB, including the interrupt descriptor table (IDT) and task state segment (TSS). After all these data areas are established, virtual 8086 mode is entered by creating a protected-mode exception stack frame, setting the VM bit in the EFLAGS, and executing an IRETD. DB then terminates and stays resident in your system.

Breakpoints on Interrupts

Since, in protected mode, interrupts pass through gates in the IDT rather than through the interrupt vectors, DB can get control as each interrupt occurs. In fact, DB must route all interrupts to their respective real-mode interrupt service routines per the real-mode interrupt vectors. (See pass_thru in Listing One, page 108.)

DB conveniently forces all software interrupts to take a slightly more indirect route. All IDT entries for software interrupts are defined with descriptor privilege level (DPL) equal to 0. When this is compared against the current privilege level (CPL) of the V86 code (CPL=3) attempting to execute an INT n instruction, a general-protection exception (interrupt 0dh) occurs. Using the CS:IP of the faulting instruction, DB detects the attempted software interrupt (see gen_prot_isr in Listing Two, page 108) and can generate a breakpoint before it routes the interrupt to the appropriate real-mode ISR.

Breakpoints on I/O

The key to generating breakpoints on I/O is the I/O permission bitmap located at the end of the TSS. This bitmap specifies which I/O addresses a task may access. (Refer to the 80386 Programmer's Reference Manual for more details.) When code in the V86 task tries to execute an I/O instruction, the processor consults the I/O permission bitmap to see if the task has been allowed access to the particular I/O address. If the corresponding bit in the bitmap is set, a general-protection exception is generated.

By setting such bits, DB generates a breakpoint via gen_prot_isr when an I/O access occurs at a particular address. In such cases, DB temporarily clears the bitmap entry and allows the instruction to continue, with one slight difference--it sets the trap flag causing an INT 1h just after the I/O instruction completes. Upon receiving INT 1h, DB recognizes that it is single-stepping through an I/O instruction. At that point, it checks for further user-specified conditions and either activates the debugger (if the conditions are met) or simply clears the trap flag and exits. When the INT 1h service routine is exited, the bits corresponding to the I/O address are again set in the I/O permission bitmap.

Breakpoints on Data Accesses

DB uses the 80386 debug registers to generate breakpoints on data accesses and code execution. DB supports all of the debug-register data-access options, including break-on-write or read/write accesses to bytes, words, or dwords. Since DB utilizes debug registers for execution breakpoints (as opposed to the INT 3h method), breakpoints can be set in ROM as well as RAM. The do_debug_reg routine (see Listing Three, page 109) performs all of the debug-register manipulation.

Using DB

Once DB is resident in your system, it can be activated either by simultaneously pressing the left and right Shift keys or by an INT 1h Software interrupt.

All of the currently supported DB command are shown in Table 1. Note that the XUD (exit to user debugger) command can be used to pass control to another debugger. For example, you can load your application under Debug, hotkey into DB to define a breakpoint, quick back to Debug, and execute a Go command. When the specified breakpoint occurs, DB gets control. You can then return to Debug by executing XUD. This is accomplished by generating an INT 1h, which will activate most debuggers. Optionally, an alternative interrupt can be specified with XUD. For example, issuing the command XUD 3 causes an INT 3 to be generated. Once specified, the alternative interrupt will be generated by subsequent XUD commands until it is specifically overridden. Int 1h, the default, seems to work well with Debug. For Codeview, XUD 2 (causing the INT 2h associated with NMI) also works well.

Table 1: DB commands.

  Command                          Description
  ----------------------------------------------------------------

  R [register name]                 Display or change register.
  E [address]                       Edit memory.
  D [address]                       Dump memory.
  T [number]                        Trace.
  G [address]                       Go.
  BPX address                       Break on code execution.
  BP[B] address [RW | W]           Break on byte data access.
  BPW address [RW | W]             Break on word data access.
  BPD address [RW | W]             Break on dword data access.
  BPIO port address [R | W | RW]  Break on I/O port access.
  BPINT int number [AX | AH | AL  Break on interrupt.
                   EQ |=hex value]
  BL                                List breakpoints.
  BC [* | breakpoint number]       Clear breakpoints.
  XUD [int number]                  Exit to user debugger.
  Q                                 Quit.

The XUD command is useful when you want to supplement your debugger's capabilities by utilizing DB's more specialized features such as breaking on I/O accesses.

Limitations and Enhancements

Since the purpose of the debugger code provided with this article is to highlight the 80386 capabilities, I've not dealt with all the video aspects of the debugger. To keep the code simple, DB always assumes text mode (25 line) and makes no attempt to handle other video modes. (There are comments in the code which indicate where this could be added.)

Although DB supports setting breakpoints on interrupts, there is one limitation. Currently, DB does not operate correctly if you specify a condition which causes a breakpoint within an interrupt service routine for an interrupt with equal or higher priority than the keyboard. Therefore, don't specify conditions which cause breakpoints within INT 8h or 9h code. The reason is that DB requires IRQ1 interrupts to get its own keyboard input. Unfortunately, interrupts can not occur if breakpoints are reached within INT 8h or 9h code before the ISRs issue EOI to the PIC.

One solution may be to have DB provide the EOI, and then trap and ignore the EOI issued by the interrupt service routine. This would necessitate trapping I/O on PIC accesses and keeping track of which interrupt is currently being serviced.

Currently, DB doesn't employ the 80386 paging feature. If this were implemented, DB could provide more robust breakpoint capabilities on memory accesses: You could set breakpoints on ranges of memory accesses, as opposed to the simple debug register-type memory-access breakpoints utilized here. Also, via paging, DB could further verify memory accesses to prevent "runaway" programs from overwriting the debugger.

Note that an errant program could potentially clear interrupts and then "go out to lunch," rendering DB virtually (no pun intended) useless. This is because DB initializes the V86 task to run with I/O privilege level (IOPL) equal to 3, thereby allowing V86 mode "sensitive" instructions (CLI, STI, PUSHF, POPF, INT n, and IRET) to affect the interrupt flag.

An option could be added to DB in which the IOPL of the V86 task would be set to a more privileged level than 3, thus causing general-protection exceptions when the V86 mode sensiti e instructions are attempted. These instructions would need to be detected and emulated.

Conclusion

As you can see, these 80386 features provide enormous benefits when utilized for debugger applications. The techniques presented here show how to apply these processor capabilities. There's room for enhancements, but this is the foundation.

References

80386 Programmer's Reference Manual. Santa Clara, CA: Intel Corp., 1986.

Green, Thomas. "80386 Protected Mode and Multitasking." Dr. Dobb's Journal (September, 1989).

Margulis, Neil, "Advanced 80386 Memory Management." Dr. Dobb's Journal (April, 1989).

Turley, James L. Advanced 80386 Programming Techniques. Berkeley, CA: Osborne/McGraw-Hill, 1988.

Williams, Al. "Homegrown Debugging--386 Style!" Dr. Dobb's Journal (March, 1990).

Williams, Al. "Roll Your Own DOS Extender: Part II." Dr. Dobb's Journal (November, 1990).



_YOUR OWN PROTECTED-MODE DEBUGGER_
by Rick Knoblaugh


[LISTING ONE]
<a name="01e6_000f">

;---------------------------------------------------------------------------
;pass_thru - This procedure is JMPed to by any interrupt handler which wishes
;  to pass control to the original ISR per the interrupt vector table. Also,
;  it checks to see if there are any breakpoints set on the int. If there are,
;  the int being passed through is checked to see if it matches the condition
;  for the break point. If the condition for the break point is met, DR0 is
;  used to cause a break at the ISR. Enter: See stack_area struc for stack
;  layout. Any error code has been removed from stack. EIP on stack has been
;  adjusted if necessary.
;---------------------------------------------------------------------------
pass_thru   proc   near
      mov   bp, sp
      pushad
      call   adjust_ustack      ;adjust user stack
;returns with [esi][edx] pointing to user stack area
      mov   cx, [bp].s_cs      ;put on user cs
      mov   [esi][edx].user_cs, cx
      mov   ecx, [bp].s_eip    ;put on ip
      mov   [esi][edx].user_ip, cx
      movzx   ebx, [bp].s_pushed_int   ;get int number
      movzx   ecx, [ebx * 4].d_offset ;offset portion
      mov   [bp].s_eip, ecx
      mov   cx, [ebx * 4].d_seg   ;segment portion
      mov   [bp].s_cs, cx

      mov   cx, offset gdt_seg:sel_data
      mov   fs, cx
      assume   fs:data
      push   fs
      cmp   fs:trap_clear, TRUE   ;tracing through an int?
      jne   short pass_thru500
      mov   fs:trap_clear, FALSE   ;reset it
      mov   fs:int1_active, TRUE   ;debugger active
;If

      mov   [esi][edx].user_cs, cx
      mov   ecx, [bp].s_eip
      mov   [esi][edx].user_ip, cx

      mov   cx, ZCODE
      mov   [bp].s_cs, cx
      mov   cx, offset int_1_isr
      movzx   ecx, cx
      mov   [bp].s_eip, ecx
pass_thru500:
      pop   ds         ;get data seg (was fs)
      assume   ds:data

      cmp   int1_active, TRUE   ;is debugger active?
      je   short pass_thru999   ;if so, don't even think
                  ;of breaking
      mov   cx, num_int_bp      ;number of defined int breaks
      jcxz   pass_thru999      ;if no int type break points
      mov   si, offset int_bpdat
pass_thru700:
      cmp   [si].int_stat, ACTIVE   ;is break on int enabled?
      jne   short pass_thru800
      dec   cx
      cmp   [si].int_num, bl   ;is this the int specified?
      jne   short pass_thru800
      cmp   [si].int_reg, NO_CONDITION ;no conditions?
      je   short pass_thru750   ;if none go ahead and set break
      mov   dx, [si].int_val   ;get data for comparison
      cmp   [si].int_reg, INT_AL_COMP ;condition compare on al?
      jne   short pass_thru730
      cmp   al, dl         ;condition met?
      je   pass_thru750          ;if so, go ahead and set break
      jmp   short pass_thru800     ;if != look for more conditions
pass_thru730:
      cmp   [si].int_reg, INT_AH_COMP ;condition compare on ah?
      jne   short pass_thru740
      cmp   ah, dl             ;condition met
      je   short pass_thru750     ;if so, go ahead and set break
      jmp   short pass_thru800     ;if != look for more conditions
pass_thru740:                   ;condition compare on ax
      cmp   ax, dx
      jne   short pass_thru800     ;if != look for more conditions
pass_thru750:
      mov   ebx, [bp].s_eip    ;get offset and
      movzx   edx, [bp].s_cs      ;segment of ISR
      shl   edx, 4         ;convert to linear
      add   edx, ebx

      mov   ch, 1         ;set debug register
      mov   al, DEB_DAT_LEN1   ;exec breaks use 1 byte length
      mov   ah, DEB_TYPE_EXEC
      sub   cl, cl         ;debug reg zero
      call   do_debug_reg
      jmp   short pass_thru999
pass_thru800:
      add   si, size info_int   ;advance to next int break
      or   cl, cl         ;all int breaks checked?
      jnz   short pass_thru700   ;if not, check the next one
pass_thru999:
      popad
      add   sp, 2         ;get rid of int number
      pop   bp
      iretd
pass_thru   endp




<a name="01e6_0010">
<a name="01e6_0011">
[LISTING TWO]
<a name="01e6_0011">

;---------------------------------------------------------------------------
;gen_prot_isr - JMP here if int 0dh. Look for software int. If a software int
;   caused the exception then: If debugger is active, look for user software
;   interrupts issued by PL3 layer of debugger. If int 15h function 89h deny.
;   If int 15h function 87h, emulate it. If none of these, simply route the
;   interrupt per the real mode interrupt vector table. If exception was not
;   caused by a software int and there are breakpoints defined on I/O accesses,
;   look for I/O instruction. If it is an I/O instruction, temporarily clear
;   the corresponding TSS I/O permission bit map bit and set trap flag to
;   single step through the instruction. If other than software int or I/O,
;   display cs:ip, 0dh and then halt.
;---------------------------------------------------------------------------
gen_prot_isr   proc   near
      pushad
;Note: Don't use DX or AX below as DX may contain an I/O port address; in the
;  case of a software interrupt, AH will have a function code. Also, don't use
;  SI or CX as they are inputs for extended memory block move function
      mov   bx, offset gdt_seg:sel_databs
      mov   ds, bx
      movzx   ebx, [bp].e_cs   ;get cs of user instruction
      shl   ebx, 4      ;make linear
      add   ebx, [bp].e_eip ;add ip
      mov   bx, [ebx]   ;get bytes at cs:ip

      mov   di, offset gdt_seg:sel_data
      mov   ds, di      ;debugger's data

      cmp   bl, INT_OPCODE
      je   short gen_prot020

      cmp   bl, INT3_OPCODE
      jne   gen_prot150   ;go look for I/O instruction
      mov   bh, 3      ;interrupt 3
gen_prot020:
      cmp   trace_count, 0   ;is debugger tracing?
      je   short gen_prot040 ;if not, skip test below
;See if this software interrupt is the instruction through which the user
;is tracing. If it is, set flag.
      mov   di, [bp].e_cs
      cmp   di, tuser_cs
      jne   short gen_prot040
      mov   edi, [bp].e_eip
      cmp   di, tuser_ip
      jne   short gen_prot040
; Clear trap bit so that it will not be set on user stack. Note: If user is
; doing a "trace n" where n is a number of instructions exceeding the number of
; instructions in the ISR, instructions executing upon return from ISR will
; still be trapped through as the int 1 code will again set the trap flag.
      btr   [bp].e_eflags, trapf
      mov   trap_clear, TRUE
gen_prot040:
      inc   [bp].e_eip   ;get past the 0cdh (or 0cch)
      cmp   bh, 3      ;int 3?
      je   short gen_prot060 ;if so, only 1 byte
gen_prot050:
      inc   [bp].e_eip
gen_prot060:

;See if the debugger is active and if this software interrupt is one of the
;ones used by the PL3 portion of the debugger to get PL0 services.
      cmp   int1_active, TRUE   ;is debugger active?
      jne   short gen_prot085
;Note:   In the event that an interrupt occuring while debugger is active
;   (e.g. timer) actually uses these user software interrupts,
;   code to verify caller would need to be added here.
      cmp   bh, 60h       ;do debug registers?
      jne   short gen_prot080

      popad
      call   do_debug_reg
      jmp   gen_prot299
gen_prot080:
      cmp   bh, 61h       ;do I/O bit map?
      jne   short gen_prot085

      popad
; Unlike accessing of debug registers, PL3 code could actually manipulate TSS
; I/O bit map directly. However, this interface keeps this in one location.
      call   do_bit_map
      jmp   gen_prot299
gen_prot085:
      cmp   bh, 15h       ;int 15?
      jne   short gen_prot100
      cmp   ah, 89h       ;request for protected mode?
      jne   short gen_prot090
                  ;if so, can't allow
      bts   [bp].e_eflags, carry   ;set carry
      popad
      jmp   gen_prot299      ;and return
gen_prot090:
      cmp   ah, 87h       ;request for extended move?
      jne   short gen_prot100
      call   emulate_blk_mov    ;if so, we must do it
      popad
      mov   ah, 0         ;default to success
      jnz   gen_prot299      ;exit if success
      mov   ah, 3         ;indicate a20 gate failed
      jmp   gen_prot299      ;and return
gen_prot100:
;Adjust stack so that error code goes away and int number retrieved from
;instruction goes in spot on stack where pushed int number is (for stacks
;with no error code).  Stack will be the way pass_thru routine likes it.
      mov   ax, bx
      mov   bx, [bp].e_pushed_bp
      shl   ebx, 16       ;get into high word
      mov   bl, ah         ;interrupt number
      mov   [bp].e_errcode, ebx

      cmp   bl, 1         ;software int 1?
      jne   short gen_prot140
; Check to see if we are already in debugger. This is to handle the unlikely
; case where there is an actual INT 1 instruction inside of an interrupt
; handler. If there is and debugger is active, instruction will be ignored.
      popad
      cmp   int1_active, TRUE   ;already in debugger?
      jne   short gen_prot130   ;if not, go enter int 1

                  ;else ignore it by returning
      add   sp, 2         ;get rid of int number
      pop   bp
      iretd
gen_prot130:
      add   sp, 4         ;error code gone
      mov   bp, sp
      pushad
      jmp   int_1_210      ;go enter int 1
gen_prot140:
      popad
      add   sp, 4         ;error code gone
      jmp   pass_thru      ;route the int via vectors
gen_prot150:
      cmp   num_io_bp, 0      ;any I/O break points defined?
      je   short gen_prot400   ;if not, don't look for I/O

      xor   ah, ah         ;use as string flag
      cmp   bl, REP_PREFIX      ;rep ?
      jne   short gen_prot190
      mov   ah, STRING      ;only string type use rep
      mov   bl, bh         ;get 2nd byte
gen_prot190:
; If repeat prefix was found, ah now has a flag indicating only string type
; I/O instructions should be expected and bl now contains the byte of object
; code past the repeat prefix. Note: To be complete, this code should also
; look for the operand-size prefix and segment overrides.
      mov   si, offset io_table
      mov   cx, IO_TAB_ENTRIES
gen_prot200:
      or   ah, ah         ;strings only?
      jz   short gen_prot225   ;if not, go test

      test   [si].io_info, ah       ;if table entry is not a string
      jz   short gen_prot300   ;type I/O, go try next one
gen_prot225:
      cmp   bl, [si].io_opcode
      jne   short gen_prot300
      mov   io_instrucf, TRUE   ;instruction found
      mov   cl, [si].io_info   ;get info about instruction

      mov   io_inst_info, cl
      test   cl, CONSTANT      ;port number in instruction?
      jz   short gen_prot250   ;if not, we have it
      movzx   dx, bh         ;get port
gen_prot250:
      mov   io_inst_port, dx  ;save port
      mov   cx, 1         ;number of bits
      sub   ah, ah         ;indicate clear
      call   do_bit_map
gen_prot260:
      bts   [bp].e_eflags, trapf ;single step i/o
      popad
gen_prot299:
      add   sp, 2         ;int number pushed
      pop   bp
      add   sp, 4         ;error code
      iretd
gen_prot300:
      add   si, size io_struc ;advance to next table entry
      loop   gen_prot200
gen_prot400:
;Also need to add code here to check

;    ch = 3 get bn portion of debug status register into ax
;    If clearing and eax !=0, eax holds other bits to be cleared (used for
;    also clearing ge or le bits). cl = debug register number (0-3) if setting
;    also have: al = length (0=1 byte, 1=2 bytes, 3=4 bytes); ah = type
;    (0=execution, 1=write, 3=read/write); edx = linear address for break
;    Also, if al='*' simply reactivate the breakpoint keeping the existing
;    type and address. Exit: if disabling, specified debug register breakpoint
;    is disabled. If enabling, specified debug register is loaded and
;    breakpoint is enabled. If getting debug status register, bn portion of
;    DR6 is returned in AX.
;  Save ebx.


<a name="01e6_0012">
<a name="01e6_0013">
[LISTING THREE]
<a name="01e6_0013">

;---------------------------------------------------------------------------
do_debug_reg   proc   near
      cmp   ch, 3         ;requesting status?
      jne   short do_deb050    ;if not
      mov   eax, dr6      ;debug status register
      and   ax, 0fh       ;isolate bn status
      ret            ;and return
do_deb050:
      push   ebx
      mov   ebx, dr7      ;get debug control reg
      cmp   ch, 1         ;determine function
      jb   short do_deb850    ;if clear function go do it
      ja   short do_deb100    ;setup, but not enable
      cmp   al, '*'       ;simply reset?
      je   short do_deb850
do_deb100:
      push   cx         ;save function/reg #
      push   edx         ;save linear address
      mov   edx, 0fh      ;4 on bits
      shl   cl, 2         ;reg # * bits associated
      add   cl, 16         ;upper portion of 32 bit reg
      shl   edx, cl
      not   edx         ;associated bits off
      and   ebx, edx      ;in the dr7 value
      shl   al, 2         ;length bits to len position
      or   al, ah         ;put in the type
      mov   dl, ah         ;save type
      sub   ah, ah
      shl   eax, cl       ;move len/rw to position
      or   ebx, eax
      or   dl, dl         ;execution type?
      jz   short do_deb500    ;if so, don't need ge
      bts   bx, ge_bit
do_deb500:
      pop   edx         ;restore linear address
      pop   cx         ;and debug register #
      cmp   cl, 1
      je   short do_deb600
      ja   short do_deb700
      mov   dr0, edx
      jmp   short do_deb800
do_deb600:
      mov   dr1, edx
      jmp   short do_deb800
do_deb700:
      cmp   cl, 3
      je   short do_deb750
      mov   dr2, edx
      jmp   short do_deb800
do_deb750:
      mov   dr3, edx
do_deb800:
      cmp   ch, 2         ;setup, but not enable?
      je   short do_deb900    ;if so, skip enable
do_deb850:
      shl   cl, 1         ;get to global enable for #
      inc   cl
      movzx   dx, cl         ;bit number to turn on
      bts   bx, dx         ;set on in dr7 value
      or   ch, ch         ;set function?
      jnz   short do_deb900
      btr   bx, dx         ;if not, disable break
      or   ax, ax         ;clear ge or le?
      jz   short do_deb900    ;if not continue
      btr   bx, ax         ;if so, clear ge or le bit
do_deb900:
      mov   dr7, ebx      ;put adjusted value back
      pop   ebx
do_deb999:
      ret
do_debug_reg   endp

isrcode    ends
      end
End Listings


Copyright © 1992, 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.