Tuesday, November 20, 2007

IBM PC Assembly Language Tutorial 8

IBM PC Assembly Language Tutorial 8

A final example

I leave you with a more substantial example of code which illustrates

some good elementary techniques; I won't claim its style is perfect,

but I think it is adequate. I think this is a much more useful example

than what you will get with the assembler:

TITLE SETSCRN -- Establish correct monitor use at boot time;

This program is a variation on many which toggle the equipment

flags to support the use of either video option (monochrome or

color).

The thing about this one is it prompts the user in such a way that he

can select the use of the monitor he is currently looking at (or which

is currently connected or turned on) without really having to know

which is which. SETSCRN is a good program to put first in an

AUTOEXEC.BAT file.

This program is highly dependent on the hardware and BIOS of the

IBMPC and is hardly portable, except to very exact clones. For this

reason, BIOS calls are used in lieu of DOS function calls where both

provide equal function.

OK. That's the first page of the program. Notice the PAGE

statement,which you can use to tell the assembler how to format the

listing. You give it lines per page and characters per line. I have

mine setup to print on the host lineprinter; I routinely upload my

listings at 9600 baud and print them on the host; it is faster than

using the PC printer.

There is also a TITLE statement. This simply provides a nice title for

each page of your listing. Now for the second page:

SUBTTL -- Provide .COM type environment and Data

PAGE

First, describe the one BIOS byte we are interested in

BIOSDATA SEGMENT AT 40H ;Describe where BIOS keeps his

data ORG 10H ;Skip parts we are not interested in

EQUIP DB ? ;Equipment flag location

MONO EQU 00110000B ;These bits on if monochrome

COLOR EQU 11101111B ;Mask to make BIOS think of the color

board BIOSDATA ENDS ;End of interesting part

Next, describe some values for interrupts and functions

DOS EQU 21H ;DOS Function Handler INT code

PRTMSG EQU 09H ;Function code to print a message

KBD EQU 16H ;BIOS keyboard services INT code

GETKEY EQU 00H ;Function code to read a character

SCREEN EQU 10H ;BIOS Screen services INT code

MONOINIT EQU 02H ;Value to initialize monochrome screen

COLORINIT EQU 03H ;Value to initialize color screen (80x25)

COLORINIT EQU 01H ;Value to initialize color screen (40X25)

Now, describe our own segment

SETSCRN SEGMENT ;Set operating segment for CODE and

DATA

ASSUME

CS:SETSCRN,DS:SETSCRN,ES:SETSCRN,SS:SETSCRN ;All

segments

ORG 100H ;Begin assembly at standard .COM offset

MAIN PROC NEAR ;COM files use NEAR linkage

JMP BEGIN ;And, it is helpful to put the data first, but then you must

branch around it.

Data used in SETSCRN

CHANGELOC DD EQUIP ;Location of the EQUIP, recorded as far

pointer

MONOPROMPT DB 'Please press the plus ( + ) key.$' ;User sees

on mono

COLORPROMPT DB 'Please press the minus ( - ) key.$' ;User sees

on color

Several things are illustrated on this page. First, in addition to titles,

the assembler supports subtitles: hence the SUBTTL pseudo-op.

Second, the PAGE pseudo-op can be used to go to a new page in

the listing. You see an example here of the DSECT-style segment in

the "SEGMENT AT 40H". Here, our our interest is in correctly

describing the location of some data in the BIOS work area which

really is located at segment 40H.

You will also see illustrated the EQU instruction, which just gives a

symbolic name to a number. I don't make a fetish of giving a name

to every single number in a program. I do feel strongly, though, that

interrupts and function codes, where the number is arbitrary and the

function being performed is the thing of interest, should always be

given symbolic names.

One last new element in this section is the define doubleword (DD)

instruction. A doubleword constant can refer, as in this case, to a

location in another segment. The assembler will be happy to use

information at its disposal to properly assemble it. In this case, the

assembler knows that EQUIP is offset 10 in the segment

BIOSDATA which is at 40H.

SUBTTL -- Perform function

PAGE

BEGIN: CALL MONOON ;Turn on mono display

MOV DX,OFFSET MONOPROMPT ;GET MONO PROMPT

MOV AH,PRTMSG ;ISSUE

INT DOS ;IT

CALL COLORON ;Turn on color display

MOV DX,OFFSET COLORPROMPT ;GET COLOR PROMPT

MOV AH,PRTMSG ;ISSUE

INT DOS ;IT

MOV AH,GETKEY ;Obtain user response

INT KBD

CMP AL,'+' ;Does he want MONO?

JNZ NOMONO

CALL MONOON ;yes. give it to him

NOMONO: RET

MAIN ENDP

The main code section makes use of subroutines to keep the basic

flow simple. About all that's new to you in this section is the use of

the BIOS interrupt KBD to read a character from the keyboard.

Now for the subroutines, MONOON and COLORON:

SUBTTL -- Routines to turn monitors on

PAGE

MONOON PROC NEAR ;Turn mono on

LES DI,CHANGELOC ;Get location to change

ASSUME ES:BIOSDATA ;TELL ASSEMBLER ABOUT CHANGE

TO ES OR EQUIP,MONO

MOV AX,MONOINIT ;Get screen initialization value

INT SCREEN ;Initialize screen

RET

MONOON ENDP

COLORON PROC NEAR ;Turn color on

LES DI,CHANGELOC ;Get location to change

ASSUME ES:BIOSDATA ;TELL ASSEMBLER ABOUT CHANGE

TO ES AND EQUIP,COLOR

MOV AX,COLORINIT ;Get screen initialization value

INT SCREEN ;Initialize screen

RET

COLORON ENDP

SETSCRN ENDS ;End of segment

END MAIN ;End of assembly; execution at MAIN

The instructions LES and LDS are useful ones for dealing with

doubleword addresses. The offset is loaded into the operand

register and the segment into ES (for LES) or DS (for LDS). By

telling the assembler, with an ASSUME, that ES now addresses the

BIOSDATA segment, it is able to correctly assemble the OR and

AND instructions which refer to the EQUIP byte. An ES segment

prefix is added.

To understand the action here, you simply need to know that flags in

that particular byte control how the BIOS screen service initializes

the adapters. BIOS will only work with one adapter at a time; by

setting the equipment flags to show one or the other as installed and

calling BIOS screen initialization, we achieve the desired effect.


IBM PC Assembly Language Tutorial 7

IBM PC Assembly Language Tutorial 7

Learning about BIOS and the hardware

You can't do everything with DOS calls. You may need to learn

something about the BIOS and about the hardware itself. In this, the

Technical Reference is a very good thing to look at.

The first thing you look at in the Technical Reference, unless you are

really determined to master the whole ball of wax, is the BIOS

listings presented in Appendix A. Glory be: here is the whole 8K of

ROM which deals with low level hardware support layed out with

comments and everything.

In fact, if you are just interested in learning what BIOS can do for you,

you just need to read the header comments at the beginning of each

section of the listing.

BIOS services are invoked by means of the INT instruction; the BIOS

occupies interrupts 10H through 1FH and also interrupt 5H; actually,

of these seventeen interrupts, five are used for user exit points or

data pointers, leaving twelve actual services.

In most cases, a service deals with a particular hardware interface;

for example, BIOS interrupt 10H deals with the screen. As with DOS

function calls, many BIOS services can be passed a function code in

the AH register and possible other arguments.

I am not going to summarize the most useful BIOS features here; you

will see some examples in the next sample program we will look at.

The other thing you might want to get into with the Tech reference is

the description of some hardware options, particularly the asynch

adapter, which are not well supported in the BIOS. The writeup on

the asynch adapter is pretty complete.

Actually, the Tech reference itself is pretty complete and very nice as

far as it goes. One thing which is missing from the Tech reference is

information on the programmable peripheral chips on the system

board. These include

the 8259 interrupt controller

the 8253 timer

the 8237 DMA controller and

the 8255 peripheral interface

To make your library absolutely complete, you should order the

INTEL data sheets for these beasts.

I should say, though, that the only I ever found I needed to know

about was the interrupt controller. If you happen to have the 8086

Family User's Manual, the big book put out by INTEL, which is one of

the things people sometimes buy to learn about 8086 architecture,

there is an appendix there which gives an adequate description of

the 8259.

IBM PC Assembly Language Tutorial 6

IBM PC Assembly Language Tutorial 6

What about subroutines?

I started with a simple COM program because I actually think they

are easier to create than subroutines to be called from high level

languages, but maybe its really the latter you are interested in. Here,

I think you should get comfortable with the assembler FIRST with

little exercises like the one above and also another one which I will

finish up with.

Next you are ready to look at the interface information for your

particular language. You usually find this in some sort of an

appendix. For example, the BASIC manual has Appendix C on

Machine Language Subroutines. The PASCAL manual buries the

information a little more deeply: the interface to a separately

compiled routine can be found in the Chapter on Procedures

and Functions, in a subsection called Internal Calling Conventions.

Each language is slightly different, but here are what I think are

some common issues in subroutine construction.

1. NEAR versus FAR? Most of the time, your language will probably

call your assembler routine as a FAR routine. In this case, you need

to make sure the assembler will generate the right kind of return. You

do this with a PROC...ENDP statement pair. The PROC statement

is probably a good idea for a NEAR routine too even though it is not

strictly required:

FAR linkage: | NEAR linkage:

| ARBITRARY SEGMENT | SPECIFIC SEGMENT PUBLIC

PUBLIC THENAME | PUBLIC THENAME

ASSUME CS:ARBITRARY | ASSUME

CS:SPECIFIC,DS:SPECIFIC

THENAME PROC FAR | ASSUME ES:SPECIFIC,SS:SPECIFIC

..... code and data | THENAME PROC NEAR

THENAME ENDP | ..... code and data ....

ARBITRARY ENDS | THENAME ENDP

END | SPECIFIC ENDS

| END

With FAR linkage, it doesn't really matter what you call the

segment. you must declare the name by which you will be called in a

PUBLIC pseu-do-op and also show that it is a FAR procedure. Only

CS will be initialized to your segment when you are called.

Generally, the other segment registers will continue to point to the

caller's segments.

With NEAR linkage, you are executing in the same segment as the

caller. Therefore, you must give the segment a specific name as

instructed by the language manual. However, you may be able to

count on all segment registers pointing to your own segment

(sometimes the situation can be more complicated but I cannot

really go into all of the details). You should be aware that the code

you write will not be the only thing in the segment and will be

physically relocated within the segment by the linker. However, all

OFFSET references will be relocated and will be correct at

execution time.

2. Parameters passed on the stack. Usually, high level languages

pass parameters to subroutines by pushing words onto the stack

prior to calling you. What may differ from language to language is

the nature of what is pushed (OFFSET only or OFFSET and

SEGMENT) and the order in which it is pushed (left to right, right to

left within the CALL statement). However, you will need to study the

examples to figure out how to retrieve the parameters from the

stack. A useful fact to exploit is the fact that a reference involving the

BP register defaults to a reference to the stack segment. So, the

following strategy can work:

ARGS STRUC

DW 3 DUP(?) ;Saved BP and return address

ARG3 DW ?

ARG2 DW ?

ARG1 DW ?

ARGS ENDS

...........

PUSH BP ;save BP register

MOV BP,SP ;Use BP to address stack

MOV ...,[BP].ARG2 ;retrieve second argument

(etc.)

This example uses something called a structure, which is only

available in the large assembler; furthermore, it uses it without

allocating it, which is not a well-documented option. However, I find

the above approach generally pleasing. The STRUC is like a

DSECT in that it establishes labels as being offset a certain

distance from an arbitrary point; these labels are then used in the

body of code by beginning them with a period; the construction

".ARG2" means, basically, " + (ARG2-ARGS)."

What you are doing here is using BP to address the stack,

accounting for the word where you saved the caller's BP and also for

the two words which were pushed by the CALL instruction.

3. How big is the stack? BASIC only gives you an eight word stack

to play with. On the other hand, it doesn't require you to save any

registers except the segment registers. Other languages give you a

liberal stack, which makes things a lot easier. If you have to create a

new stack segment for yourself, the easiest thing is to place the stac

at the end of your program and:

CLI ;suppress interrupts while changing the stack

MOV SSAVE,SS ;save old SS in local storage (old SP

; already saved in BP)

MOV SP,CS ;switch

MOV SS,SP ;the

MOV SP,OFFSET STACKTOP ;stack

STI ;(maybe)

Later, you can reverse these steps before returning to the caller. At

the end of your program, you place the stack itself:

DW 128 DUP(?) ;stack of 128 words (liberal)

STACKTOP LABEL WORD

4. Make sure you save and restore those registers required by the

caller.

5. Be sure to get the right kind of addressibility. In the FAR call

example, only CS addresses your segment. If you are careful with

your ASSUME statements the assembler will keep track of this fact

and generate CS prefixes when you make data references;

however, you might want to do something like

MOV AX,CS ;get current segment address

MOV DS,AX ;To DS

ASSUME DS:THISSEG

Be sure you keep your ASSUMEs in synch with reality.

IBM PC Assembly Language Tutorial 5

IBM PC Assembly Language Tutorial 5

Learning the assembler

It is my feeling that many people can teach themselves to use the

assembler by reading the MACRO Assembler manual if

1. You have read and understood a book like Morse and thus have a

feeling for the instruction set

2. You know something about DOS services and so can

communicate with the keyboard and screen and do something

marginally useful with files. In the absence of this kind of knowledge,

you can't write meaningful practice programs and so will not

progress.

3. You have access to some good examples (the ones supplied with

the assembler are not good, in my opinion. I will try to supply you

with some more relevant ones.

4. You ignore the things which are most confusing and least useful.

Some of the most confusing aspects of the assembler include the

facilities combining segments. But, you can avoid using all but the

simplest of these facilities in many cases, even while writing quite

substantial applications.

5. The easiest kind of assembler program to write is a COM

program. They might seem harder, at first, then EXE programs

because there is an extra step involved in creating the executable

file, but COM programs are structurally very much simpler.

At this point, it is necessary to talk about COM programs and EXE

programs.As you probably know, DOS supports two kinds of

executable files. EXE programs are much more general, can

contain many segments, and are generally built by compilers and

sometimes by the assembler. If you follow the lead given by the

samples distributed with the assembler, you will end up with EXE

programs. A COM program, in contrast, always contains just one

segment, and receives control with all four segment registers

containing the same value. A COM program, thus, executes in a

simplified environment, a 64K address space. You can go outside

this address space simply by temporarily changing one segment

register, but you don't have to, and that is the thing which makes

COM programs nice and simple. Let's look at a very simple one.

The classic text on writing programs for the C language says that the

first thing you should write is a program which says

HELLO, WORLD.

when invoked. What's sauce for C is sauce for assembler, so let's

start with a HELLO program of our own. My first presentation of this

will be bare bones, not stylistically complete, but just an illustration of

what an assembler program absolutely has to have:

HELLO SEGMENT ;Set up HELLO code and data section

ASSUME CS:HELLO,DS:HELLO ;Tell assembler about conditions

at entry

ORG 100H ;A .COM program begins with 100H byte prefix

MAIN: JMP BEGIN ;Control must start here

MSG DB 'Hello, world.$' ;But it is generally useful to put data first

BEGIN: MOV DX,OFFSET MSG ;Let DX --> message.

MOV AH,9 ;Set DOS function code for printing a message

INT 21H ;Invoke DOS

RET ;Return to system

HELLO ENDS ;End of code and data section

END MAIN ;Terminate assembler and specify entry point

First, let's attend to some obvious points. The macro assembler

uses the general form name opcode operands

Unlike the 370 assembler, though, comments are NOT set off from

operands by blanks. The syntax uses blanks as delimiters within the

operand field (see line 6 of the example) and so all comments must

be set off by semi-colons.

Line comments are frequently set off with a semi-colon in column 1. I

use this approach for block comments too, although there is a

COMMENT statement which can be used to introduce a block

comment.

Being an old 370 type, I like to see assembler code in upper case,

although my comments are mixed case. Actually, the assembler is

quite happy with mixed case anywhere.

As with any assembler, the core of the opcode set consists of

opcodes which generate machine instructions but there are also

opcodes which generate data and ones which function as

instructions to the assembler itself, sometimes called pseudo-ops. In

the example, there are five lines which generate machine code

(JMP, MOV, MOV, INT, RET), one line which generates data (DB)

and five pseudo-ops (SEGMENT, ASSUME, ORG, ENDS, and

END).

We will discuss all of them.

Now, about labels. You will see that some labels in the example end

in a colon and some don't. This is just a bit confusing at first, but no

real mystery. If a label is attached to a piece of code (as opposed to

data), then the assembler needs to know what to do when you JMP

to or CALL that label. By convention, if the label ends in a colon, the

assembler will use the NEAR form of JMP or CALL. If the label does

not end in a colon, it will use the FAR form. In practice, you will

always use the colon on any label you are jumping to inside your

program because such jumps are always NEAR; there is no reason

to use a FAR jump within a single code section. I mention this,

though, because leaving off the colon isn't usually trapped as a

syntax error, it will generally cause something more abstruse to go

wrong.

On the other hand, a label attached to a piece of data or a pseudo-

op never ends in a colon.

Machine instructions will generally take zero, one or two operands.

Where there are two operands, the one which receives the result

goes on the left as in 370 assembler.

I tried to explain this before, now maybe it will be even clearer: there

are many more 8086 machine opcodes then there are assembler

opcodes to represent them. For example, there are five kinds of

JMP, four kinds of CALL, two kinds of RET, and at least five kinds of

MOV depending on how you count them. The macro assembler

makes a lot of decisions for you based on the form taken by the

operands or on attributes assigned to symbols elsewhere in your

program. In the example above, the assembler will generate the

NEAR DIRECT form of JMP because the target label BEGIN labels

a piece of code instead of a piece of data (this makes the JMP

DIRECT) and ends in a colon (this makes the JMP NEAR). The

assembler will generate the immediate forms of MOV because the

form OFFSET MSG refers to immediate data and because 9 is a

constant. The assembler will generate the NEAR form of RET

because that is the default and you have not told it otherwise.

The DB (define byte) pseudo-op is an easy one: it is used to put one

or more bytes of data into storage. There is also a DW (define word)

pseudo-op and a DD (define doubleword) pseudo-op; in the PC

MACRO assembler, the fact that a label refers to a byte of storage,

a word of storage, or a doubleword of storage can be very

significant in ways which we will see presently.

About that OFFSET operator, I guess this is the best way to make

the point about how the assembler decides what instruction to

assemble: an analogy with 370 assembler:

PLACE DC ......

...

LA R1,PLACE

L R1,PLACE

In 370 assembler, the first instruction puts the address of label

PLACE in register 1, the second instruction puts the contents of

storage at label PLACE in register 1. Notice that two different

opcodes are used. In the PC assembler, the analogous instructions

would be

PLACE DW ......

...

MOV DX,OFFSET PLACE

MOV DX,PLACE

If PLACE is the label of a word of storage, then the second

instruction will be understood as a desire to fetch that data into DX.

If X is a label, then "OFFSET X" means "the ordinary number which

represents X's offset from the start of the segment." And, if the

assembler sees an ordinary number, as opposed to a label, it uses

the instruction which is equivalent to LA.

If PLACE were the label of a DB pseudo-op, instead of a DW, then

MOV DX,PLACE

would be illegal. The assembler worries about length attributes of its

operands.

Next, numbers and constants in general. The assembler's default

radix is decimal. You can change this, but I don't recommend it. If

you want to represent numbers in other forms of notation such as

hex or bit, you generally use a trailing letter. For example,

21H

is hexidecimal 21,

00010000B

is the eight bit binary number pictured.

The next elements we should point to are the SEGMENT...ENDS

pair and the END instruction. Every assembler program has to have

these elements.

SEGMENT tells the assembler you are starting a section of

contiguous material (code and/or data). The symmetrically named

ENDS statement tells the assembler you are finished with a section

of contiguous material. I wish they didn't use the word SEGMENT in

this context. To me, a "segment" is a hardware construct: it is the

64K of real storage which becomes addressable by virtue of having

a particular value in a segment register. Now, it is true that the

"segments" you make with the assembler often correspond to real

hardware "segments" at execution time. But, if you look at things like

the GROUP and CLASS options supported by the linker, you will

discover that this correspondence is by no means exact. So, at risk

of maybe confusing you even more, I am going to use the more

informal term "section" to refer to the area set off by means of the

SEGMENT and ENDS instructions.

The sections delimited by SEGMENT...ENDS pairs are really a lot

like CSECTs and DSECTs in the 370 world.

I strongly recommend that you be selective in your study of the

SEGMENT pseudo-op as described in the manual. Let me just

touch on it here.

name SEGMENT

name SEGMENT PUBLIC

name SEGMENT AT nnn

Basically, you can get away with just the three forms given above.

The first form is what you use when you are writing a single section

of assembler code which will not be combined with other pieces of

code at link time. The second form says that this assembly only

contains part of the section; other parts might be assembled

separately and combined later by the linker.

I have found that one can construct reasonably large modular

applications in assembler by simply making every assembly use the

same segment name and declaring the name to be PUBLIC each

time. If you read the assembler and linker documentation, you will

also be bombarded by information about more complex options

such as the GROUP statement and the use of other "combine types"

and "classes." I don't recommend getting into any of that. I will talk

more about the linker and modular construction of programs a little

later. The assembler manual also implies that a STACK segment is

required. This is not really true. There are numerous ways to assure

that you have a valid stack at execution time.

Of course, if you plan to write applications in assembler which are

more than 64K in size, you will need more than what I have told you;

but who is really going to do that? Any application that large is likely

to be coded in a higher level language.

The third form of the SEGMENT statement makes the delineated

section into something like a "DSECT;" that is, it doesn't generate

any code, it just describes what is present somewhere already in the

computer's memory. Sometimes the AT value you give is

meaningful. For example, the BIOS work area is located at location

40 hex. So, you might see

BIOSAREA SEGMENT AT 40H ;Map BIOS work area

ORG BIOSAREA+10H

EQUIP DB ? ;Location of equipment flags, first byte

BIOSAREA ENDS

in a program which was interested in mucking around in the BIOS

work area.

At other times, the AT value you give may be arbitrary, as when you

are mapping a repeated control block:

PROGPREF SEGMENT AT 0 ;Really a DSECT mapping the

program prefix

ORG PROGPREF+6

MEMSIZE DW ? ;Size of available memory

PROGPREF ENDS

Really, no matter whether the AT value represents truth or fiction, it is

your responsibility, not the assembler's, to get set up a segment

register so that you can really reach the storage in question. So, you

can't say

MOV AL,EQUIP

unless you first say something like

MOV AX,BIOSAREA ;BIOSAREA becomes a symbol with value

40H MOV ES,AX ASSUME ES:BIOSAREA

Enough about SEGMENT. The END statement is simple. It goes at

the end of every assembly. When you are assembling a subroutine,

you just say

END

but when you are assembling the main routine of a program you say

END label

where 'label' is the place where execution is to begin.

Another pseudo-op illustrated in the program is ASSUME.

ASSUME is like the USING statement in 370 assembler. However,

ASSUME can ONLY refer to segment registers. The assembler

uses ASSUME information to decide whether to assemble segment

override prefixes and to check that the data you are trying to access

is really accessible. In this case, we can reassure the assembler that

both the CS and DS registers will address the section called

HELLO at execution time. Actually, the SS and ES registers will too,

but the assembler never needs to make use of this information.

I guess I have explained everything in the program except that ORG

pseudo-op. ORG means the same thing as it does in many

assembly languages. It tells the assembler to move its location

counter to some particular address. In this case, we have asked the

assembler to start assembling code hex 100 bytes from the start of

the section called HELLO instead of at the very beginning. This

simply reflects the way COM programs are loaded.

When a COM program is loaded by the system, the system sets up

all four segment registers to address the same 64K of storage. The

first 100 hex bytes of that storage contains what is called the

program prefix; this area is described in appendix E of the DOS

manual. Your COM program physically begins after this. Execution

begins with the first physical byte of your program; that is why the

JMP instruction is there.

Wait a minute, you say, why the JMP instruction at all? Why not put

the data at the end? Well, in a simple program like this I probably

could have gotten away with that. However, I have the habit of putting

data first and would encourage you to do the same because of the

way the assembler has of assembling different instructions

depending on the nature of the operand.

Unfortunately, sometimes the different choices of instruction which

can assemble from a single opcode have different lengths. If the

assembler has already seen the data when it gets to the instructions

it has a good chance of reserving the right number of bytes on the

first pass. If the data is at the end, the assembler may not have

enough information on the first pass to reserve the right number of

bytes for the instruction. Sometimes the assembler will complain

about this, something like "Forward reference is illegal" but at other

times, it will make some default assumption. On the second pass, if

the assumption turned out to be wrong, it will report what is called a

"Phase error," a very nasty error to track down. So get in the habit of

putting data and equated symbols ahead of code.

OK. Maybe you understand the program now. Let's walk through the

steps involved in making it into a real COM file.

1. The file should be created with the name HELLO.ASM (actually

the name is arbitrary but the extension .ASM is conventional and

useful)

2.

ASM HELLO,,;

(this is just one example of invoking the assembler; it uses the

small assembler ASM, it produces an object file and a listing file

with the same name as the source file. I am not going exhaustively

into how to invoke the assembler, which the manual goes into pretty

well. I guess this is the first time I mentioned that there are really two

assemblers; the small assembler ASM will run in a 64K machine

and doesn't support macros. I used to use it all the time; now that I

have a bigger machine and a lot of macro libraries I use the full

function assembler MASM. You get both when you buy the

package).

3. If you issue DIR at this point, you will discover that you have

acquired HELLO.OBJ (the object code resulting from the assembly)

and HELLO.LST (a listing file). I guess I can digress for a second

here concerning the listing file. It contains TAB characters. I have

found there are two good ways to get it printed and one bad way.

The bad way is to use LPT1: as the direct target of the listing file or

to try copying the LST file to LPT1 without first setting the tabs on

the printer. The two good ways are to either

a. direct it to the console and activate the printer with CTRL-

PRTSC. In this case, DOS will expand the tabs for you.

b. direct to LPT1: but first send the right escape sequence to LPT1

to set the tabs every eight columns. I have found that on some early

serial numbers of the IBM PC printer, tabs don't work quite right,

which forces you to the first option.

4.

LINK HELLO;

(again, there are lots of linker options but this is the simplest. It

takes HELLO.OBJ and makes HELLO.EXE). HELLO.EXE? I

thought we were making a COM program, not an EXE program.

Right. HELLO.EXE isn't really executable; its just that the linker

doesn't know about COM programs. That requires another utility.

You don't have this utility if you are using DOS 1.0; you have it if you

are using DOS 1.1 or DOS 2.0. Oh, by the way, the linker will warn

you that you have no stack segment. Don't worry about it.

5.

EXE2BIN HELLO HELLO.COM

This is the final step. It produces the actual program you will exe-

cute. Note that you have to spell out HELLO.COM; for a nominally

rational but actually perverse reason, EXE2BIN uses the default

extension BIN instead of COM for its output file. At this point, you

might want to erase HELLO.EXE; it looks a lot more useful than it is.

Chances are you won't need to recreate HELLO.COM unless you

change the source and then you are going to have to redo the whole

thing.

6.

HELLO

You type hello, that invokes the program, it says

HELLO YOURSELF!!!

(oops, what did I do wrong....?)