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

Location Is Everything!


JAN90: LOCATION IS EVERYTHING!

This article contains the following executables: LOCATE.EXE

Mark is a programmer for Greenleaf Software, Inc., Dallas, Texas. He can be reached through the DDJ office.


A few years ago, most embedded controllers were 4- or 8-bit microcontrollers such as the 8048, 8051, 68HC11, or COPS-400. But the popularity of the PC has brought the 8088 into this arena for two reasons. First, the price of 8088 processors and support chips was driven down to levels where they can compete with microcontrollers. Second, just as important, sophisticated 8088 software development tools are widely available at low prices.

For example, a typical price for an 8051 compiler/linker/locator package can easily run over $1000. This compares with street prices of under $100 for PC-based C compilers. The support tools (CodeView or Turbo Debugger, for instance)for the PC-based compilers generally outperform those for the microcontroller cross compilers. Even more important, there exists a huge group of programmers that are familiar with the PC-based development tools.

But just because you have a copy of Turbo C or Microsoft C doesn't mean you are ready to go out and start developing code to run an atomic toaster.

There is still one key ingredient missing in your software development environment. The missing link is a piece of software called a "Locator," which takes the output from the linker and moves code and data around so that they match up with the RAM and ROM in the target hardware.

A simple 8088-based system will generally have EPROM in high memory and RAM in low memory. This mode of operation is more or less dictated by the architecture of the processor. You have to have ROM or EPROM in high memory because that is where the 8088 begins executing code when it powers up. Because the interrupt vectors for the part are in low memory, hardware designers typically place the system RAM there. So, in a system with 64K of RAM and 64K of EPROM, the RAM will have a base address of 0000:0000, while the EPROM will have a base address of F000:0000.

EXE File Structure

Though it may not be obvious, MS-DOS does, in fact, provide a locator. When COMMAND.COM loads an EXE file, it needs to locate the file at the currently available location in the RAM of the PC. This location changes up and down depending on many factors (the version of DOS, whether or not TSRs are loaded in, and so on). Thus, the EXE file has all the information in it necessary to inform DOS about what it needs to do to run the program anywhere in memory. This information is stored in what is known as the "header"of the file. The structure of the EXE file is shown in Figure 1, and a detail of the header structure is shown in Figure 2.

Figure 1: Structure of an EXE file

Software dependent debugger data

Code

Relocation Table

EXE Header

Figure 2: Structure of the header

1A Overlay number

18 Displacement of relocation table in bytes

16 Displacement of CODE segment in paragraphs

14 Initial value of IP

12 Word checksum

10 Initial value of SP

0E Displacement of STACK segment in paragraphs

0C Maximum number of paragraphs required

0A Minimum number of paragraphs required

08 Size of the header in 16 byte paragraphs

06 Number of relocation table items

04 Size of the file in 512 byte pages

02 Length of image mod 512

00 EXE Program Signature

As Figure 1 shows, the code image is just one of four sections in the EXE file. The code found in that section is ready to be loaded into RAM and executed with no changes, provided it is loaded starting at location 0000:0000.

In order to load it into a different section, the loader needs to go into the code image and adjust any segment references upward by the value of the load segment.

A general - purpose locator should also be able to place code anywhere in memory. But to complicate matters, it needs to be able to split up the code and data sections of the program, locating them in arbitrarily different sections of memory. It needs to be able to adjust any segment references in the code image. The locator also needs to be able to produce a located output file suitable for loading into an embedded system and it needs to provide some sort of initialization so that our software in the target will begin execution on when the processor is reset.

A Locate Program

The program LOCATE (see Listing One, page 152) accomplishes the just mentioned goals. All that needs to be done is to include the startup module, START.ASM (Listing Two, page 153), as the first file in the link. The file can then be run through LOCATE. This produces a HEX output file suitable for loading into an embedded system.

The first thing LOCATE does in order to produce the HEX output file is to read in the header of the EXE file. This is done in the module read_header_ data( ). The header is read into the exe_header structure defined at the top of the file. Once the header is read in, most of the information needed to locate the program is ready. read_header_ data( ) also makes a few more calculations before exiting. First, it computes the offset to the start of the actual code in the EXE file. The header contains the offset in paragraphs, which just has to be multiplied by 16 to create an offset in bytes.

Next, LOCATE calculates the size of the program. This is somewhat more complicated, involving three different numbers in the header. Finally, it determines where in the program the STACK segment is located. This point will mark the line of demarcation between RAM and ROM. As you will see later, the routine START.ASM orders the segments in such a way that this is the case.

After LOCATE has determined where the code image is, and how long it is, it calls the routine read_input( ), allocates a buffer, then loads the code image into the buffer. This code image is ready to run as is on an 8088 if it is loaded in at address 0000:0000.

After reading in the image, all that is left to do is to process all of the relocation table entries. The header has two items that say where the relocation table is in the EXE file, and how many entries are in it. Each entry in the table is just a segment:offset pair, specifying a long address in the code image that needs to be relocated.

The procedure called process_ relocation_table( )is a loop that gets each relocation table entry, computes where that is in the code image, then modifies the value so that it conforms with the target hardware. The item pointed to by the relocation table entry is a segment value in the code image. For example, when a program starts, there is often a statement that looks like this:

  
MOV AX, XXXX   
MOV DS, AX

The relocation table points to the XXXX value, so that it can be modified to reflect the real memory address that should be loaded in. The MS-DOS loader has a simple job here. It just adds the base segment address of the program to the segment in memory. For example, if the code is being loaded in at 2134:0000, it adds 2134 to XXXX and stores the new value in the code image.

Our locator has to make a slightly more complicated decision. It needs to look at the value of the segment to be relocated. If the segment value is less than that of the stack segment, it means that it is a ROM value, and should be recalculated to go into high memory. If the segment value is greater than, or equal to, that of the stack segment, it should be recalculated to go into low memory. Remember, that the stack segment value is in the EXE header, and we have defined our segments so that all code is below the stack segment and all data is above it.

The relocation values in LOCATE are hardcoded as F000H for the program, and 40H for the data. The 8088 has 256 interrupt vectors that go from address 0 to 3FFH, so that the first available segment is at 40H. After the relocation process is complete, the image is ready to be output to the HEX file.

The most common format for transferring code or data to an embedded system is to use Intel MCS-86 Hex code, and that is what this program does. Virtually every emulator and debugger will support downloads in this format. Because it is the closest thing to a universal format, it is used here. Before the code image is sent, a single record is sent specifying which segment the load is to go into. In this case, that will be F000H. The image is then formatted into records, and output -- one by one -- until they have all been issued.

Finally, LOCATE outputs a single JMP instruction that will be located at FFFF:0000. This is the restart location for the 8088 processor. When the processor powers up, or receives an external reset signal, control is transferred to this location and execution begins. Because both the code segment and initial IP values are found in the EXE header, they can be output as part of the jump instruction, causing it to automatically point to our startup code.

The last detail at this point is to output a single EOF record in Intel hex format. The code is at this point ready to be sent to our target hardware.

The Startup Code

Although LOCATE will try to relocate any EXE program in any language, it will not produce useful code without a few items being taken into account. Most of them are covered by the inclusion of a startup file, START.ASM. When compiled, START.OBJ will replace the startup code produced by the C compiler, usually found in CRT0.ASM.

The first problem is the ordering of the segments. As mentioned previously, the only reference point available in the EXE file regarding the location of data in the EXE file is the stack segment value. This means that we need to force the stack segment to be the first segment in RAM. The sample file shown for START.ASM does this by listing the segments used in a C program in the desired order. By having START.OBJ be the first file read in by the linker, the segment order shown will be the one used. In START.ASM, the DGROUP segment is defined to contain the stack, so the stack segment listed in the EXE header will be the first segment after the code segments. The example shown in the next section of this article will work for Microsoft C or Turbo C. Turbo C does not use a CONST class of segment, but will not object to the presence of one.

A second problem for embedded systems is how to handle initialized data. In just about any program there will be some data in the CONST, BSS, and DATA segment types. Loading them into RAM would work, but only once. If the system were to lose power, the code would still be present in the ROM when restarted, but all of the down-loaded data would have vanished.

This version of LOCATE and START.ASM handles this in a straightforward way. All of the initialized data in the image can be found immediately after the code segments. The initialized data values in the image are loaded into ROM just as they are found in the code image. When the program restarts, control is transferred to START.ASM. All it does is make a copy of all the initialized data from ROM into RAM. This actually takes less space in the ROM than would be used to initialize them using code.

A few precautions are needed to create code that will actually work under this system. First, some compilers will generate code that performs stack checking each time a function call is invoked. Because we are tinkering with the stack segment, this code frequently will not work. Both Turbo C and Microsoft C allow you to disable stack checking with command line switches, and these should be used.

C library routines should always be viewed with distrust. Some of the library routines perform DOS calls, and these will obviously not work on an embedded system. Others have built in linker commands that will reorder your segment definitions. Unfortunately, the only way to tell if a library routine will work is usually to just try linking it in. Microsoft C will generate in line code for many library routines, and these will almost always work. Once again, this can be invoked with command line options.

An Example

A sample program that uses this system is shown in Listing Three, page 153. The program performs the standard first function of a C program, which is to print simply a "Hello, world!" message on the screen. Naturally, printf will not work in my embedded system, so it has been replaced with my_printf( ).

The function my_printf( ) is writing characters to an imaginary printer port that has to be polled before it can be written to. It calls the library routines outportb and inportb to access the hardware. The only predefined data it will have will be the string "Hello, world!\ n". Using Turbo C, this program can be compiled with the command line:

  TCc -ms -c HELLO.C

The Microsoft C command line would be:

  CL/AS/C/Oi/Gs HELLO.C

The Microsoft C command line specifies no stack checking, as well as generation of intrinsic functions.

After compiling, the code would be linked with the line:

TLINK START.OBJ+HELLO.OBJ,HELLO.EXE,HELLO.MAP, \TC\LIB\CS;

or

LINK START.OBJ+HELLO.OBJ,HELLO.EXE,HELLO.MAP/MAP;

The resulting map file should look similar to the one in Figure 3. This shows that the program and startup code will be located first, followed by constant data, the uninitialized data, and the stack. The program is then located using the following command line:

  LOCATE HELLO.EXE HELLO.HEX

This produces an Intel hex file that looks like the one in Figure 4. This program is ready to be loaded and executed in the target system. The only real complication to debugging at this point is the relocation of symbol values. The offset values in the map will all be correct, but the segments will all be wrong.

Figure 3: Map file after compilation

               Start    Stop    Length    Name       Class
------------------------------------------------------------------

               00000H   0008DH  0008EH   _TEXT       CODE
               00090H   000B4H  00025H   END_OF_ROM  STARTUP_CODE
               000C0H   000C0H  00000H   _CONST      CONST
               000C0H   000C0H  00000H   _BSS        BSS
               000C0H   000CFH  00010H   _DATA       DATA
               000D0H   002CFH  00200H   _STACK      STACK
               Program entry point at 0009:0000

Figure 4: Contents of hex file

  :02000002F0000C
  :22000000B8060050E8060059EB00EBFEC3558BEC568B7604EB40803C0A750FEB00B80002
   50E879
  :22002200450059A9800074F3B80D0050B8000250E84D005959EB00B8000250E8290059A
   9800000
  :2200440074F38BDE468A079850B8000250E82E005959803C0075BB5E5DC3558BEC8B560
   4EDEB46
  :22006600005DC3558BEC8B5604EC32E4EB005DC3558BEC8B56048B4606EF5DC3558BEC8
   B560452
  :220088008A4606EE5DC30000B80BF08ED0BC00088CC80503008ED8B80BF08EC033F6B9F
   FFBF30B
  :1B00AA00A48CC08ED8FBEA000000F00048656C6C6F2C20776F726C64210A007D
  :02000002FFFFFE
  :05000000EA000009F018
  :00000001FF

_LOCATION IS EVERYTHING!_ by Mark Nelson

[LISTING ONE]

<a name="0047_000d">

/********************************************************************
**                      ---- LOCATE.C -----
**           Copyright (C) 1989 by Mark R. Nelson
**  Program:  LOCATE.C
**  Author:   Mark R. Nelson
**  Summary:  LOCATE reads in MS-DOS formate EXE files and writes
**            out relocated code in Intel Hex format.  The code
**            segment is relocated to start at F000:0000.  Data
**            is relocated to start at 0040:0000.
********************************************************************/

#include <stdio.h>
#include <stdlib.h>

#define TRUE 1
#define FALSE 0

struct exe_header {
                      unsigned int signature;
                      unsigned int image_length_mod_512;
                      unsigned int file_size_in_pages;
                      unsigned int num_of_relocation_table_items;
                      unsigned int size_of_header_in_paragraphs;
                      unsigned int min_num_of_paragraphs_required;
                      unsigned int max_num_of_paragraphs_required;
                      unsigned int disp_of_stack_in_paragraphs;
                      unsigned int initial_sp;
                      unsigned int word_checksum;
                      unsigned int initial_ip;
                      unsigned int disp_of_code_in_paragraphs;
                      unsigned int disp_of_relocation_table;
                      unsigned int overlay_number;
                  } header;

FILE *exe_file;
FILE *hex_file;
unsigned char *image;
unsigned long int image_size;
unsigned long int image_offset;
unsigned long first_data_segment_in_exe_file;
int verbose=TRUE;
unsigned int output_base_code_segment=0xF000;
unsigned int output_base_data_segment=0x0040;

main(int argc,char *argv[])
{
    printf("Locate 1.0  Copyright (C) 1989 by Mark R. Nelson\n");

    open_files(argc,argv);          /* Open the input and output files      */
    read_header_data();             /* Read in the EXE file header          */
    read_input();                   /* Read the code image into a buffer    */
    process_relocation_table();     /* Relocate all segment references      */
    dump_output();                  /* Write the code image to the HEX file */
    output_restart_code();          /* Write the restart code line          */
    output_intel_hex(0,0,1,NULL);   /* Output an EOF record                 */
}

/********************************************************************
**                       ---- open_files ----
**   This routine opens an EXE file and a HEX file.  If they are specified
**   on the command line, those names are used.  Otherwise the user is
**   prompted for file names
********************************************************************/

open_files(int argc,char *argv[])
{
char exe_file_name[81];
char hex_file_name[81];

    if (argc>1)
        strcpy(exe_file_name,argv[1]);
    else
    {
        printf("EXE file name? ");
        scanf("%s",exe_file_name);
    }

    exe_file=fopen(exe_file_name,"rb");
    if (exe_file==NULL)
        fatal_error("Had trouble opening the input file!");

    if (argc > 2)
        strcpy(hex_file_name,argv[2]);
    else
    {
        printf("Hex file name? ");
        scanf("%s",hex_file_name);
    }

    hex_file=fopen(hex_file_name,"w");
    if (hex_file==NULL)
        fatal_error("Had trouble opening the output file!");

}

/********************************************************************
**                     ---- read_header_data ----
**   This routine reads in the EXE header structure and computes both
**   the image offset and size.  The compuataions are all done using
**   numbers found in the header.  This program arbitrarily limits
**   the code image size to 64K, but could easily be expanded to go
**   to larger sizes.
********************************************************************/

read_header_data()
{
    if (fread(&header,sizeof(struct exe_header),1,exe_file) != 1)
        fatal_error("Couldn't read header from file!");
    if (verbose)
        print_header();
    image_offset=header.size_of_header_in_paragraphs*16;
    image_size = (header.file_size_in_pages-1)*512;
    image_size -= image_offset;
    image_size += header.image_length_mod_512;
    if (image_size > 0xFFFFL)
        fatal_error("The EXE image is larger than I can handle!");
    first_data_segment_in_exe_file=header.disp_of_stack_in_paragraphs;
}

/********************************************************************
**                       ---- read_input --
**   This routine reads the code image into a buffer.  Any trouble with
**   the buffer or the file generates a fatal error.
********************************************************************/

read_input()
{
    image=malloc(image_size);
    if (image==NULL)
        fatal_error("Couldn't allocate output image space!");
    if (fseek(exe_file,image_offset,SEEK_SET) != 0)
        fatal_error("Couldn't seek to image in the input file!");
    if (fread(image,1,(int)image_size,exe_file) != (int)image_size)
        fatal_error("Couldn't read in the image!");
}

/********************************************************************
**                ---- process_relocation_table ----
**   This routine loops through all of the entries in the relocation
**   table.  Each entry points to a segment value in the code image.
**   That segment value is checked to see if it points to code or
**   data.  If it points to data, it is relocated to start at the
**   output_base_data_segment.  Code segments are relocated to start
**   at output_base_code_segment.
********************************************************************/

process_relocation_table()
{
int i;
unsigned int reloc[2];
unsigned long int spot;
unsigned int *guy;
unsigned int old_value;
unsigned int new_value;

 fseek(exe_file,(long)header.disp_of_relocation_table,0);
 for (i=0;i<header.num_of_relocation_table_items;i++)
 {
  if (fread(reloc,2,2,exe_file) != 2)
    fatal_error("Couldn't read relocation data from file!");
  printf("Record %3d:  %04X:%04X:",i,reloc[1],reloc[0]);
  spot=reloc[1]*16 + reloc[0];
  old_value=*(int *)(image+spot);
  printf("  was: %04X", old_value);
  if (old_value < first_data_segment_in_exe_file)
   new_value=old_value+output_base_code_segment;
  else
   new_value=old_value-first_data_segment_in_exe_file+output_base_data_segment;
  *(int *)(image+spot)=new_value;
  printf("  now is: %04X\r", new_value);
 }
 printf("\n");
}

/********************************************************************
**                   ---- dump_output ----
**   This routine loops the entire code image.  It outputs 34 bytes
**   at a time in Intel Hex format until it is done.  While it is
**   doing this it keeps the user posted by writing the addresses
**   to the screen.  Note that this module would need some modifications
**   to handle images greater than 64K.
********************************************************************/

dump_output()
{
unsigned char *output_pointer;
long int output_size;
int record_size;
unsigned int output_address;
unsigned char segment_address[2];

    output_pointer=image;
    output_size=image_size;
    output_address=0;
    segment_address[0]=output_base_code_segment>>8;
    segment_address[1]=output_base_code_segment & 0xff;
    output_intel_hex(2,0,2,segment_address);
    while (output_size > 0)
    {
        printf("%04X\r",output_address);
        record_size=(output_size > 34) ? 34 : output_size;
        output_intel_hex(record_size,output_address,0,output_pointer);
        output_pointer += record_size;
        output_size -= record_size;
        output_address += record_size;
    }
    printf("\n");
}

/********************************************************************
**                   ---- output_restart_code ----
**   This routine writes a JMP START instruction out at location
**   at FFFF:0000.  The address of START is contained in the EXE
**   header block.
********************************************************************/

output_restart_code()
{
unsigned char jmp_code[5];
unsigned char segment_address[2];

    segment_address[0]=0xff;
    segment_address[1]=0xff;
    output_intel_hex(2,0,2,segment_address);
    jmp_code[0]=0xea;                       /*  JMP ????:????             */
    jmp_code[1]=header.initial_ip & 0xff;
    jmp_code[2]=header.initial_ip >> 8;
    header.disp_of_code_in_paragraphs += output_base_code_segment;
    jmp_code[3]=header.disp_of_code_in_paragraphs & 0xff;
    jmp_code[4]=header.disp_of_code_in_paragraphs >> 8;
    header.disp_of_code_in_paragraphs -= output_base_code_segment;
    output_intel_hex(5,0,0,jmp_code);
}

/********************************************************************
**                   ---- output_intel_hex ----
**   This routine writes a single record of Intel Hex.
********************************************************************/

output_intel_hex(int size,unsigned int address,int type,unsigned char buffer[])
{
int checksum;
int i;

    fprintf(hex_file,":%02X%04X%02X",size,address,type);
    checksum=size+address+(address>>8)+type;
    for (i=0;i<size;i++)
    {
        fprintf(hex_file,"%02X",buffer[i]);
        checksum += buffer[i];
    }
    checksum = -checksum & 0xff;
    fprintf(hex_file,"%02X\n",checksum);
}

/********************************************************************
**                     ---- print_header ----
**   This is a routine that lets the program print out the contents
**   of the header.  It is here primarily for assistance in debugging.
********************************************************************/

print_header()
{
    printf("Link program signature:                                    ");
    printf("%4.4X\n",header.signature);
    printf("Length of image mod 512:                                   ");
    printf("%4.4X\n",header.image_length_mod_512);
    printf("Size of file in 512 byte pages, including header:          ");
    printf("%4.4X\n",header.file_size_in_pages);
    printf("Number of relocation table items:                          ");
    printf("%4.4X\n",header.num_of_relocation_table_items);
    printf("Size of header in 16 byte paragraphs:                      ");
    printf("%4.4X\n",header.size_of_header_in_paragraphs);
    printf("Minimum # of 16 byte paragraphs needed above program:      ");
    printf("%4.4X\n",header.min_num_of_paragraphs_required);
    printf("Maximum # of 16 byte paragraphs needed above program:      ");
    printf("%4.4X\n",header.max_num_of_paragraphs_required);
    printf("Displacement of stack within load module in paragraphs:    ");
    printf("%4.4X\n",header.disp_of_stack_in_paragraphs);
    printf("Offset to be loaded in SP:                                 ");
    printf("%4.4X\n",header.initial_sp);
    printf("Word checksum:                                             ");
    printf("%4.4X\n",header.word_checksum);
    printf("Offset to be loaded in IP:                                 ");
    printf("%4.4X\n",header.initial_ip);
    printf("Displacement of code segment in 16 byte paragraphs:        ");
    printf("%4.4X\n",header.disp_of_code_in_paragraphs);
    printf("Displacement of 1st relocation table item:                 ");
    printf("%4.4X\n",header.disp_of_relocation_table);
    printf("Overlay number:                                            ");
    printf("%4.4X\n",header.overlay_number);
}
/********************************************************************
**                     ---- fatal_error ----
**   A self-documenting utility.
********************************************************************/

fatal_error(char *message)
{
    printf(message);
    exit(1);
}




<a name="0047_000e"><a name="0047_000e">
<a name="0047_000f">
[LISTING TWO]
<a name="0047_000f">

;********************************************************************
;                        ---- START.ASM ----
;           Copyright (C) 1989 by Mark R. Nelson
;  Module:   START.ASM
;  Author:   Mark R. Nelson
;  Summary:  This module is an alternate startup routine for Microsoft
;            or Turbo C programs running on non DOS hardware.  It has
;            three main jobs.  First, it sets up the segment definitions
;            so that the STACK segment is the first segment in RAM.
;            Second, it initializes all predefined data.  Third, it jumps
;            to the user's main() routine.
;*******************************************************************

;
;   Note here that if DGROUP does not contain the stack, which may
;   be true for larger models, the STACK segment needs to be moved
;   to be the first one after END_OF_ROM.
;
;   Also note that for the startup code to work properly, the first
;   segment in the data area must be paragraph aligned.  This insures
;   that for the startup code, the first data segment is exactly 3
;   larger than the END_OF_ROM segment.
;
_TEXT              SEGMENT BYTE PUBLIC 'CODE'
_TEXT              ENDS
END_OF_ROM         SEGMENT PARA PUBLIC 'STARTUP_CODE'
END_OF_ROM         ENDS
_CONST             SEGMENT PARA PUBLIC 'CONST'
_CONST             ENDS
_BSS               SEGMENT WORD PUBLIC 'BSS'
_BSS               ENDS
_DATA              SEGMENT WORD PUBLIC 'DATA'
_DATA              ENDS
_STACK             SEGMENT WORD STACK 'STACK'

MYSTACK            DB 512 DUP (?)

_STACK             ENDS
;
;   Note here that if DGROUP does not contain the stack, which may
;   be true for larger models, the STACK segment needs to be moved
;   to be the first one after END_OF_ROM.
;
DGROUP             GROUP   _CONST,_BSS,_DATA,_STACK

                   extrn   _main:far

                   public  __acrtused        ;This value makes many of the
__acrtused = 9876h                           ;library routines happy

END_OF_ROM SEGMENT PARA PUBLIC 'STARTUP_CODE'

                   ASSUME  CS:END_OF_ROM

                   PUBLIC  START

START              PROC    FAR

                   MOV     AX,DGROUP             ;This code initializes the
                   MOV     SS,AX                 ;SS:SP pair with the proper
                   MOV     SP,OFFSET MYSTACK+512 ;values.
;
;   This section of code is charged with moving all predefined values out
;   of ROM and into RAM.  This is done by copying all values out of the
;   part of ROM immediately following the last code segment into the
;   data section.
;
                   MOV     AX,CS          ;The present code segment is the
                   ADD     AX,3           ;last code segment, and we know
                   MOV     DS,AX          ;that the first data segment will
                                          ;be three up form here.  Put that
                                          ;value into DS

                   MOV     AX,DGROUP      ;Now set ES to point to the first
                   MOV     ES,AX          ;section of RAM.
                   XOR     SI,SI          ;SI and DI are the registers
                   XOR     DI,DI          ;used in the MOVSB instruction.
                   MOV     CX,0FBFFH      ;This rep instruction will fill
                   REP     MOVSB          ;everything in a 64K RAM following
                                          ;the interrupt vector space.

                   MOV     AX,ES          ;Now set up DS to point to DGROUP
                   MOV     DS,AX
                   STI                    ;Enable interrupts and the jump
                   JMP     _main          ;to the start of the C code

START              ENDP

END_OF_ROM         ENDS

                   END     START




<a name="0047_0010"><a name="0047_0010">
<a name="0047_0011">
[LISTING THREE]
<a name="0047_0011">

/********************************************************************
**                      ---- HELLO.C -----
**           Copyright (C) 1989 by Mark R. Nelson
**  Program:  HELLO.C
**  Author:   Mark R. Nelson
**  Summary:  Hello demonstrates the LOCATE program.  It simulates
**            output of a string to a printer on an embedded system.
********************************************************************/

main()
{

    my_print("Hello, world!\n");
    while (1) ;
}

my_print(char *message)
{
    while (*message)
    {
        if (*message=='\n')
        {
            while ((inportb(0x200) & 0x80) == 0) ;
            outportb(0x200,'\r');
        }
        while ((inportb(0x200) & 0x80) == 0) ;
        outportb(0x200,*message++);
    }
}











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.