home *** CD-ROM | disk | FTP | other *** search
Text File | 1986-11-03 | 37.6 KB | 1,020 lines |
- .. 90 lines per page
- .. leave 6/12 lines for article headline
- .. put cursor on ruler line & hit Ctrl-OF
- .. !------!----!----!----!----!----!----!----!-----
-
-
-
-
-
- IBM Personal Computer Assembly Language Tutorial
- Joshua Auerbach, Yale University
-
- Yale Computer Center
- 175 Whitney Avenue
- P. O. Box 2112
- New Haven, Connecticut 06520
-
-
-
-
-
-
-
-
-
-
-
- Learning the assembler
- _____________________
-
- It is my feeling that many people can teach them-
- selves 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, than 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 assem-
- ; bler 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 instruc-
- tions 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 repre-
- sent 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 double-
- word 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 conti-
- guous 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 assem-
- bler 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 arbi-
- trary, 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 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 instruc-
- tions 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 execute. 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....?)
-
- 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:
-
- ARBITRARY SEGMENT
- PUBLIC THENAME
- ASSUME CS:ARBITRARY
- THENAME PROC FAR
- ..... code and data
- THENAME ENDP
- ARBITRARY ENDS
- END
-
-
- NEAR linkage:
-
- SPECIFIC SEGMENT PUBLIC
- PUBLIC THENAME
- ASSUME CS:SPECIFIC,DS:SPECIFIC
- ASSUME ES:SPECIFIC,SS:SPECIFIC
- THENAME PROC NEAR
- ..... code and data ....
- THENAME ENDP
- 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
- pseudo-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
- ...........
- (continued at top of next column)
-
-
- 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 stack 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 addressibili-
- ty. 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.
-
- 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 one 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.
-
- 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:
-
- PAGE 61,132
- 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
- ASSUME 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 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.
-
- The rest is up to you.
- r the
- other as installed and calling BIOS scr