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

Embedded Systems

A coff File Loader for the 34010


JUL91: A COFF FILE LOADER FOR THE 34010

A COFF FILE LOADER FOR THE 34010

When your target system is RAM-based

Don Morgan

Don is a consulting engineer in the area of embedded systems and automation and can be contacted care of Don Morgan Electronics, 2669 N. Wanda, Simi Valley, CA 93065.


The Texas Instruments 34010 is one of the most widely used graphics processors around. It is also among a series of TI processors that lets you develop software that produces files conforming to the Common Object Format File (COFF) definition.

A COFF file is like an EXE file in that it contains code and data -- and the information necessary to load both into RAM. Depending on the development language, the COFF file may also contain variables that must be initialized during the load cycle.

For a ROM-based target, the COFF file is converted to a HEX file for PROM programmers. However, if your target is RAM-based -- and not a Texas Instrument Graphics (TIGA) application -- you must write a COFF file loader that suits your target and application software.

I recently had to develop software using the 34010. A RAM-based target was to be used in an 80x86 environment and the software didn't use the TIGA interface. I needed a COFF file loader small enough to be embedded in an application and robust enough to download fully linked C and assembly language programs. I developed a function, presented here as a stand-alone program, that allows me to load COFF files whose source was either assembly or C. The function can also initialize the startup variables for a C program or load them for initialization at boot time. (The COFF file loader was originally written for Pacific Precision Laboratories in Chatsworth, Calif.)

The Host Interface

Besides parsing the COFF file, there's one important point to consider about loading a program into a target's local memory: Before anything is written to the target, the processor needs to be halted and the system prepared, so that once the download is complete, it can be restarted correctly. The host interface takes care of this.

The host interface (Listing One, page 93) consists of four 16-bit pointer registers accessible to the host system through the HFS0 and HFS1 address bits and HCS\, the host chip select. These bits are normally decoded from either the memory map or I/O map.

HSTADRH and HSTADRL, the two pointers for the address, allow access to any space within the 32-bit local bus, and on-board 34010 registers. HSTDATA is the data buffer register, where data is placed when writing to local RAM and read when reading from local RAM. In addition, the control register, HSTCTL, provides for autoincrementing the address pointers on both reads and writes. Here too, are bits that control the cache and the nonmaskable interrupt, halt, input and output interrupts and message passing.

By setting the address pointers to the beginning address for your download, and setting the bit in HSTCTL that causes the address pointers to be automatically incremented with each write, you can write a block of code simply by placing word after word (or byte after byte for an 8-bit interface) in HSTDATA.

A typical procedure for downloading code to a target card might be as follows:

  1. Set both the HALT bit and Non-Maskable Interrupt (NMI) bits in HSTCTL, shortening the latency of the action to that of the NMI.
  2. Download the code, using the facilities of the host interface.
  3. Flush the cache, so that no old code can be executed after the processor is restarted.
  4. Write the nonmaskable interrupt vector to point to the new code to be executed after restart.
  5. Set the NMI and NMIM bits in HSTCTL to abandon the current context and to see that the restart is uniform.
  6. Restart the 34010 by resetting the HLT bit in the HSTCTL.

COFF.H

The target I was writing for was memory mapped, but it could just as easily have been I/O mapped. This is an important consideration in the design of the system. For one thing, memory mapping requires that all memory accesses within the decoded segment(s) be the same width. This can cause conflicts between 8-bit and 16-bit cards mapped to the same area. If the target is I/O mapped, programmers are allowed the very fast string instructions available on the 80x86, and can also use other video cards without fear of incompatibility.

Whichever you use, the loader must know where to find the host interface. In part A of the COFF.H include file (Listing Two, page 95) there is a define called MM that differentiates between I/O and memory mapped targets. The appropriate host interface registers are presented for each mode, and their addresses must be filled in before compilation. In this example, the code is defined for memory mapped, and the addresses are given in based pointer notation. If the target is I/O mapped, MM is made false and the appropriate addresses are placed in the section marked "I/O mapped addresses."

Part B of Listing Two contains the defines for section offsets, bit names for the HSTCTL register, flag definitions, and interrupt vectors. Finally, in part C you'll find the structure definitions used to access information about the program being loaded.

The Loader

The first part of the program (part A) looks for a named file and loads it if possible.

The second part (part B) performs whatever initialization the TMS34010 requires. Instructions for setting up refresh timing and rates can be placed here if necessary. (If, however, the loader is implemented as a function, that may be taken care of elsewhere.) This is also where I prepare the target for the down-load by halting it and flushing the cache.

In part C, the the program determines whether or not it has a valid COFF file and uses the offsets to initialize pointers to the various headers.

The main header is the first data block in the COFF file, the address of file_buffer[O], is cast as a pointer to a structure of the type m_hdr. The first variable in this structure contains the Magic number (90h), which, if present, tells the program that the file can be executed on a 34010. This is followed by the number of sections in the file, the date and time, a pointer to the symbol table, the number of entries in the symbol table, a field that indicates whether or not there is an optional header (included to perform relocation at down-load time), and a flags byte. The program checks the magic number to see that it is a good file, retrieves the number of sections, checks to see if there is an optional header, and gets the flags.

A pointer is also initialized for the optional header, and the first two bytes are checked to see that they hold the magic number for the 34010. If so, parsing is continued, otherwise the program is aborted. The entry point is an important bit of information because the reset vector in the 34010's trap table will have to be set to this address. If it is C code we are loading, it is the address of the beginning of the startup code (Boot.obj).

More Details.

In part D, the section headers are examined one at a time to find the BSS section. Although all the sections are basically handled in the same way, the BSS section is loaded first. Even though it is technically "uninitialized," it may be forced with fill data and as such, could overwrite initialization variables if loaded out of order.

There is only one structure definition for all the sections (refer to COFF.H). Each section is handled in the same manner: Its offset within the file_buffer is used to initialize a pointer to a structure of type sect_header. This is done in a function called get_sect, after the new section header has been identified and accepted.

Next, the section headers are loaded one at a time into another structure, and that information is used to find the raw data it is associated with and load it. As you will see from the table showing the information in the section header, the section header contains an offset to raw data and to 2 bytes containing the flags describing that data. These flags are listed in COFF.H under section header flags and indicate whether the raw data is code, data, or bss, and how it is to be treated.

Part E contains the functions that actually load and verify the data. As far as C program loading is concerned, one section is treated as special: The CINIT section, which contains initialization tables for the C compiler. When this program is loaded into a RAM-based target, the data is used to initialize predetermined locations in memory to speed boot time, this is done with the function put_data. In a ROM-based target, these variables may be located in ROM and used by the boot code to reinitialize those same variables each time the unit is reset, this is accomplished though the regular function, load block.

Finally in part D, the proper reset vector is set on the target and the 34010 is allowed to restart.

Conclusion

The information presented here is fairly rudimentary, but it could easily be extended to get the symbol table and line numbers for debugging purposes, or to do relocation on partially linked files.

Bibliography

TMS34010 User's Guide. Texas Instruments, 1988.

TMS34010 Assembly Language Tools User's Guide. Texas Instruments, 1987.

TMS34010 C Compiler Reference Guide. Texas Instruments, 1988.

The Common Object File Format

The Common Object Format File (COFF) was originally developed by AT&T for Unix. The assembler supplied by Texas Instruments for use with the 34010 produces a COFF file, which when linked contains code, data, and symbolic debugging information.

COFF is said to encourage modular programming because it operates in blocks of code called sections. There are two basic types of sections, initialized and unitialized. Sections are relocatable and will eventually occupy contiguous space in memory. Assembler directives allow you to create any number of these sections.

A COFF file has at least three sections: a TEXT (usually executable code), DATA (initialized data), and BSS (uninitialized variables). The compiler produces an assembly language file which can be further modified or immediately assembled into an object file. The object file is then linked with libraries or other files according to directives on the command line or in a CMD file. With these directives, the linker determines where to place each of the sections found in the COFF file.

As Figure 1 shows, the COFF.OUT has three headers that provide information about the kind of file it is, the positioning of the code and data, and whether or not it is to be loaded.

  • The main header indicates what kind of system the code can be executed on, how many section headers there are, whether there is an optional header available, and other data (the date/time stamp, a flags byte, and so on).
  • The optional header is always included in a fully linked file. It describes the size of the executable code and data, as well as providing the entry point for the code and the addresses of TEXT and DATA.
  • A section header for each named section in the program. There will always be a TEXT, a DATA, and a BSS section, and there may be more. There are assembly language directives that allow you to create as many sections as you like, and if the program was originally written in C, it will have a CINIT section containing initialization data.
After the section headers, there is the raw data, the code or data associated with each section. Finally, line number entries, a symbol table, and a string table (for labels over eight characters) provides debugging information.

--D.M.


_A COFF FILE LOADER FOR THE 34010_
by Don Morgan



[LISTING ONE]
<a name="0193_000c">

#include <fcntl.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <io.h>
#include <conio.h>
#include <string.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include "coff.h"

struct main_header *m_hdr;
struct opt_header *opt;
struct sect_header section;

void main(int argc, char **argv)
{
/**************** PART A *****************/
        int module, size, header, sect, j;
        unsigned long result;
        int bss_done = FALSE;

        unsigned char *receive_buffer;
        char tmp_buf[100];
        int data, i;
        if(argc != 2) {
                printf("\nno file given!");
                exit(-1);
                }
        module = open(argv[1], O_BINARY|O_RDONLY);
        if(module == -1){
                perror("\nopen failed!");
                exit(-1);
                              }
        /**************************/
        /*read coff file into buffer*/
        size = filelength(module);
        file_buffer = (char *)malloc(size);
        if(file_buffer == NULL) {
                perror("\nnot enough memory!");
                exit (-1);
                }
        if((result = (long)read(module, file_buffer, size)) <= 0) {
                perror("\ncan't find file!\n");
                exit(-1);
                }
        close(module);

/************  PART B    *************/
/*set up 34010 for loading*/
/**************************/
/*it is the users responsibility to set up the cntrl register*/
/*please note that some of the register settings, such as the following*/
/*are application dependent, this code is included only to show an*/
/*example of low level setup for the 34010*/
        gsp_poke(cntrl, 0x4);  /*sets cas before ras refresh*/

/**************************/
/*set up 34010 to restart correctly after loading program*/
        put_hstctl(hlt | cf | incr | incw | nmim | nmi_flg);
        data = get_hstctl();
        if(data != (hlt | cf | incr | incw | nmim | nmi_flg)) {
                printf("\nerror writing to hstctl!");
                exit(-1);
                              }

/************ PART C  *************/
/**************************/
/*get contents of main header*/
        m_hdr = (struct main_header *) file_buffer;
/*see if the file has a magic number*/
        if(m_hdr->magic_num !=FILE_MAGIC) {
                printf("\nnot a standard coff .out file!");
                exit(-1);
                }
/*check to see whether there is an optional header*/
        if((m_hdr->opt_head != OPT_XST)){
                printf("file is not fully linked!");
                exit(-1);
                }
/*get contents of optional header*/
        opt = (struct opt_header *) &file_buffer[OPT_OFST];
/*see if the optional header has a magic number*/
        if(opt->magic_num !=OPT_MAGIC) {
                printf("\nnon standard file!");
                exit(-1);
                }

/*************************************************/
/*begin searching for and loading section headers find bss section first! */
       i = FIRST_HDR;
       for (j=0;((j<m_hdr->num_sects) && !bss_done) ;j++){
               strcpy(tmp_buf,&file_buffer[i]);
               if(!bss_done) {
                       if(!(strcmp(tmp_buf, ".bss"))) {
                               strcpy(section.name, tmp_buf);
                               header = i;
                               get_sect(header);
                               bss_done =     TRUE;
                               }
                       }
               i += SEC_OFST;
               }
       /*now load the other sections*/
       i = FIRST_HDR;
       for (j=0; j<m_hdr->num_sects;j++){
               strcpy(tmp_buf,&file_buffer[i]);
               if(strcmp(tmp_buf, ".bss")) {
                       strcpy(section.name, tmp_buf);
                       header = i;
                       get_sect(header);
                       }
               i += SEC_OFST;
               }

         /*release memory for file buffer*/
       free(file_buffer);

/************ PART D *************/
/*set up reset and interrupt vectors for the 34010                 */
/*usually, both the nmi and halt bits are set and then released    */
/*this code may differ depending upon the desires of the programmer */
     gsp_poke(intenb,0x0);  /*no interrupts*/
     gsp_poke(nmi_vect, opt->entry_point);
     gsp_poke(nmi_vect+0x10, opt->entry_point >> 0x10);
     gsp_poke(reset,opt->entry_point);
     gsp_poke(reset+0x10,opt->entry_point >> 0x10);
     put_hstctl(hlt | incr | nmi_flg | incw | nmim);
                  /*toggle the halt bit and go*/
     data = get_hstctl();
     data &= ~hlt;
     put_hstctl( data );
}

/*********** PART E *************/
void get_sect(header)
int header;
{
     struct sect_header * ptr = (struct sect_header *)&file_buffer[header];
     load(ptr);
}
void load(ptr)
struct sect_header *ptr;
{
/*here the flags are checked to determine whether the section is to be loaded or copied or ignored*/
       if((ptr->sect_size) && !(ptr->flags & STYP_DSECT) &&
               !(ptr->flags & STYP_NOLOAD)) {
                       if(!(strcmp(ptr->name,".cinit"))
                               && (ptr->flags & STYP_COPY))
                                       put_data(ptr);
                       else
                       if(((ptr->flags & STYP_TEXT)
                               || (ptr->flags & STYP_DATA)
                                       || (!(strcmp(ptr->name,".cinit")
                                          && !(ptr->flags &   STYP_COPY)))))
                                                       load_block(ptr);
       }
}
void load_block(ptr)
struct sect_header *ptr;
{
       int data, temporary, hldr, limit;
       long i, j, file_pointer;
       file_pointer = ptr->raw_data;
/*set the host interface up to point at the correct address*/
#if MM
       *(gsp:>hstadrl) = ptr->virt_addr;
       *(gsp:>hstadrh) = ptr->virt_addr >> 0x10;
#else
       outpw(io_hstadrl, (unsigned int)ptr->virt_addr);
       outpw(io_hstadrh, (unsigned int)ptr->virt_addr >> 0x10);
#endif
       limit = (ptr->sect_size/0x10)-1;
       j=0;
       /*write each word to host interface*/
       for(i=0; i<=limit; i++){
/*get the data from the file buffer and get it in the correct order before writing to the host interface*/
                 data = (file_buffer[file_pointer+j++]&0xff);
           data += ((file_buffer[file_pointer+j++]&0xff)*0x100);
#if MM
           *(gsp:>hstdata) = data;
#else
           outpw(io_hstdata,data);
#endif
                 }
       /*compare data*/
      /*point at the correct address*/
#if MM
       *(gsp:>hstadrl) = ptr->virt_addr;
       *(gsp:>hstadrh) = ptr->virt_addr >> 0x10;
#else
       outpw(io_hstadrl, (unsigned int)ptr->virt_addr);
       outpw(io_hstadrh, (unsigned int)ptr->virt_addr>> 0x10);
#endif
       limit = (ptr->sect_size/0x10)-1;
       j=0;
       for(i=0; i<=limit; i++){
                       /*get the data*/
                 data = (file_buffer[file_pointer+j++]&0xff);
           data += ((file_buffer[file_pointer+j++]&0xff)*0x100);
#if MM
           hldr = *(gsp:>hstdata);
#else
           hldr = inpw(io_hstdata);
#endif
           if(hldr != data)
                 printf("\ncompare error!");
           }
}
void put_data(ptr)
struct sect_header *ptr;
{
       int data, temporary, hldr, limit, num_words, num;
       long i, j, reloc_address, file_pointer;
       struct init_table * init;
       file_pointer = ptr->raw_data;
       do{
     init = (struct init_table *)&file_buffer[file_pointer];
                 reloc_address = init->ptr_to_var;
           file_pointer += 6;

/*point at relocation address*/
#if MM
           *(gsp:>hstadrl) = reloc_address;
           *(gsp:>hstadrh) = reloc_address >> 0x10;
#else
           outpw(io_hstadrl, (unsigned int)reloc_address);
           outpw(io_hstadrh, (unsigned int)reloc_address >> 0x10);
#endif

/*determine the amount of data to transfer and do it*/
           num_words = init->num_words;
           limit = --num_words;
           j=0;
           for(i=0; i<=limit; i++){
                 data = (file_buffer[file_pointer+j++]&0xff);
                 data += ((file_buffer[file_pointer+j++]&0xff)*0x100);
#if MM
              *(gsp:>hstdata) = data;
#else
              outpw(io_hstdata,data);
#endif
              }
               /*now, do a data compare*/
#if MM
           *(gsp:>hstadrl) = reloc_address;
           *(gsp:>hstadrh) = reloc_address >> 0x10;
#else
           outpw(io_hstadrl, (unsigned int)reloc_address);
           outpw(io_hstadrh, (unsigned int)reloc_address >> 0x10);
#endif
           num_words = init->num_words;
           limit = --num_words;
           j=0;
           for(i=0; i<=limit; i++){
                 data = (file_buffer[file_pointer+j++]&0xff);
              data += ((file_buffer[file_pointer+j++]&0xff)*0x100);
#if MM
              hldr = *(gsp:>hstdata);
#else
              hldr = inpw(io_hstdata);
#endif
              if(hldr != data)
                        printf("\ndata compare error!");
              }
       file_pointer += j;
     }while(((int)file_buffer[file_pointer]) != 0x0);
}
/*set up the hstctl*/
void put_hstctl(unsigned int value)
{
#if MM
       *(gsp:>hstctl) = value;
#else
       outpw(io_hstctl,value);
#endif
}
/*get current Hstclt setting*/
unsigned int get_hstctl()
{
       int value;
#if MM
       value = *(gsp:>hstctl);
#else
       value = inpw(io_hstctl);
#endif
       return value;
}
/*set host interface to point at correct address*/
void set_addr(unsigned long address)
{
#if MM
       *(gsp:>hstadrl) = address;
       *(gsp:>hstadrh) = address >> 0x10;
#else
       outpw(io_hstadrl,(unsigned int)address);
       outpw(io_hstadrh,(unsigned int)address >> 0x10);
#endif
}
void gsp_poke(unsigned long address, unsigned long value)
{
       set_addr(address);
#if MM
       *(gsp:>hstdata) = value;
#else
       outpw(io_hstdata,(unsigned int)value);
#endif
}
unsigned int gsp_peek(unsigned long address)
{
       int value;

       set_addr(address);
#if MM
       value = *(gsp:>hstdata);
#else
       value = inpw(io_hstdata);
#endif
       return value;
}




<a name="0193_000d">
<a name="0193_000e">
[LISTING TWO]
<a name="0193_000e">

#define FALSE 0
#define TRUE 0x1

#define MM TRUE

/*physical addresses of memory mapped host interface*/
#if MM
_segment gsp = 0xc700;
int _based(void) *hstctl = (int _based(void)*)0xe00;
int _based(void) *hstdata = (int _based(void)*)0xf00;
int _based(void) *hstadrh = (int _based(void)*)0xc00;
int _based(void) *hstadrl = (int _based(void)*)0xd00;
#else
/*io mapped addresses of host interface*/
io_hstctl = 0;
io_hstdata = 0;
io_hstadrh = 0;
io_hstadrl = 0;
#endif

#define FILE_MAGIC 0x90
#define OPT_MAGIC 0x108
#define OPT_XST 0x1c
#define OPT_OFST 0x14
#define SEC_OFST 0x28
#define FIRST_HDR 0x30

#define cf 0x4000
#define hlt 0x8000
#define nmi_flg 0x100
#define nmim 0x200
#define incw 0x800
#define incr 0x1000

/*definitions*/
/*file header flags*/
#define F_RELFLG 0x1               /*relocation information stripped*/
#define F_EXEC 0x2                 /*file is relocateable*/
#define F_LNNO 0x4                 /*line numbers stripped*/
#define F_LSYMS 0x10               /*local symbos stripped*/
#define F_QR32WR 0x40              /*34010 byte ordering*/

/*section header flags*/
#define STYP_REG 0x0               /*regular section*/
#define STYP_DSECT 0x1             /*dummy section*/
#define STYP_NOLOAD 0x2            /*noload section*/
#define STYP_GROUP 0x4             /*grouped section*/
#define STYP_PAD 0x8               /*padding section*/
#define STYP_COPY 0x10             /*copy section, important for .cinit*/
#define STYP_TEXT 0x20             /*executable code*/
#define STYP_DATA 0x40             /*initialized data*/
#define STYP_BSS 0x80              /*uninitialized data*/
#define STYP_ALIGN 0x100           /*aligned on cache boundary*/

/*******************************************************/
/*interrupt vector:*/
#define reset          0xffffffe0
#define nmi_vect       0xfffffee0
/*******************************************************/
/*i/o registers:*/
#define refcnt         0xc00001f0
#define dpyadr         0xc00001e0
#define vcount         0xc00001d0
#define hcount         0xc00001c0
#define dpytap         0xc00001b0
#define pmask          0xc0000160
#define psize          0xc0000150
#define convdp         0xc0000140
#define convsp         0xc0000130
#define intpend        0xc0000120
#define intenb         0xc0000110
#define hstctlh        0xc0000100
#define hstctll        0xc00000f0
#define hst_adrh       0xc00000d0
#define hst_adrl       0xc00000e0
#define hst_data       0xc00000c0
#define cntrl          0xc00000b0
#define dpyint         0xc00000a0
#define dpystrt        0xc0000090
#define dpyctl         0xc0000080
#define vtotal         0xc0000070
#define vsblnk         0xc0000060
#define veblnk         0xc0000050
#define vesync         0xc0000040
#define htotal         0xc0000030
#define hsblnk         0xc0000020
#define heblnk         0xc0000010
#define hesync         0xc0000000
#define dac_wr         0xc7800
#define ppl_rd         0xc7000

/*header structures*/
struct main_header {
     unsigned short int magic_num;
     unsigned short int num_sects;
     long int date_stamp;
     long int sym_table;
     long int entries;
     unsigned short int opt_head;
     unsigned short int flags;
     };
struct opt_header {
     short int magic_num;
     short int version;
     unsigned long code_size;
     unsigned long init_size;
     unsigned long uninit_size;
     unsigned long entry_point;
     unsigned long start_text;
     unsigned long start_data;
     };
struct sect_header {
     unsigned char name[8];
     unsigned long phys_addr;
     unsigned long virt_addr;
     unsigned long sect_size;
     unsigned long raw_data;
     unsigned long reloc;
     unsigned long num_entries;
     unsigned short int reloc_entries;
     unsigned short int line_entries;
     unsigned short int flags;
     unsigned char ch1;
     unsigned char page;
     };
struct init_table {
     int num_words;
     long ptr_to_var;
     };
/*video pointer*/
char far *vid_mem;
/*variable declarations*/
int len, text_ptr, debug, coff_debug, fake;
unsigned char *file_buffer;
/*function prototypes*/
long getint(int, int);
void load(struct sect_header *);
void load_block(struct sect_header *);
void put_data(struct sect_header *);
void put_hstctl(unsigned int);
unsigned int get_hstctl(void);
void set_addr(unsigned long int);
void gsp_poke(unsigned long int, unsigned long int);
unsigned int gsp_peek(unsigned long int);
void get_sect(int);


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