COWARD.0 13 Aug 1991/WK33/Tue 10 May 1992/WK20/Sun 18 Jan 1993/WK03/Mon ASSEMBLY LANGUAGE PROGRAMMING FOR COWARDS Homer B. Tilton Copyright 1991, 1992, 1993 Homer B. Tilton Echo Electronic Press 8401 E. Desert Steppes Drive Tucson, AZ 85710 USA The hanging 25+25 1ogo indicates that numera1s 0 and 1 may at times be used for big 1etter o and sma11 1etter L in the text. ------------- Other books available from Echo Electronic Press ------------- PC USER'S GOODYBOOK: Vas Ist Das DOS? The computer manual that should have come with your computer but didn't. DOS FROM A DIFFERENT ANGLE: The Complete Handbook of DOS Redirection and Piping; Fifth Edition. For all computer users who want to know how to do these things at the DOS prompt and in batch files without buying special programs: Perform calculations; play tunes; graph mathematical curves; display calendars for any month of any year; much more. SMASHING THE LIGHT BARRIER. A popular scientific work containing facts and speculation about the light barrier, relativity, and gravitation. If our grandchildren are to explore the far universe, then we and our children must find a way exceed the speed of 1ight. This book te11s what's involved. COWARDS Page ii Foreword Perhaps you didn't know that assembly language has commands for addition (ADD), subtraction (SUB), and even multiplication (MUL) and division (DIV). That information may surprise and please you. So, is assembler like BASIC? Hardly; but if you have knowledge of BASIC, that will help you. Nearly every book on assembly language you pick up is written by an expert in the field. Now, experts are a funny lot; when you become an "expert" at this or that craft, it's a natural consequence to (erroneously) assume that everyone was born with certain "obvious" parts of that knowledge "wired into" their neural circuits. (You forgot there was a learning process you went through to learn those things!) Other parts you may be reluctant, down deep, to divulge. This means that the expert's assumptions as to what his reader knows when he starts reading the book may be 'way off track. It can be a real struggle for anyone but another expert to understand such a book. Also, an expert may tend to incorporate superfluous information; causing still more confusion. Another thing those other assembly-language books do (all of them, it seems!) is to assume the reader is made of money. They all tout software (assemblers) used by professional programmers - not cheap. This book is centered around one of the best and most popular shareware assemblers - Eric Isaacson's A86. It may already be on your computer; it was on mine, unbeknown to me, when I bought it! This book was written from notes I started taking even before I tackled assembly language. Thus it is written from the standpoint of the beginner; meaning it assumes very little pre-knowledge about assembly language on the part of the reader. That doesn't mean every concept presented is thoroughly explained; but I have tried to give enough explanation of the concepts presented to get the reader started on the right track. I am still not an expert assembly-language programmer - as any expert will probably be anxious to tell you! But I enjoy trying; and I am a professional technical writer - something that perhaps most expert programmers are not; and this book is the only one I am aware of that does not require the user to have one of the expensive assemblers. So this book may be just what the "wannabe" assembly-language programmer needs. Homer Tilton Tucson, Arizona May 1992 Glory ASCII Zero! .................... Little Annie Rooney MOVS MOV CMP POP?! ................... The Little Hitch-hiker [For those of you who don't know of Little Annie Rooney and The Little Hitch- hiker... They were cartoon characters, circa 1939. I don't know if they're still around. They would say things like those quotes given above. That was long before assembly language was invented; so, were they prophetic or what? Another character used to say "F00" a 1ot. Isn't that a hexadecima1 number?] COWARDS Page iii Table of Contents Foreword....................................... ii The ground rules............................... vi Introduction................................... vii Chapter-by-Chapter Outline.....................viii Background assumed............................. ix Getting started................................ x Acknowledgment................................. xi Disclaimer..................................... xi Trademarks and registered trademarks........... xi Glossary....................................... xii Part I. Making .CoM files using DEBUG Chapter 1. Assembling 1-1 Introduction Do it! Description of HELLO.BAT Assembler commands Writing HELLO.ASM Commentary Chapter 2. Disassembling 2-1 Disassembling HELLO.COM Printing the unassembled file Machine language Using "screen files" with DEBUG Still another way to use machine language Chapter 3. Interrupts 3-1 BEEP.COM We now interrupt this program... PRINTSCR.COM PRNSTAT.COM Part II. Getting started with A86 Chapter 4. Procedures 4-1 Introduction An example Summary Chapter 5. Equates and macros 5-1 Introduction Equates Macros Summary COWARDS Page iv Chapter 6. Assembly source code framework 6-1 Introduction Explanation of framework Part III. Developing a calendar program Chapter 7. CALENDAR.COM 7-1 Introduction Testing the Gregorian Algorithm Modifying the algorithm for assembly language use Calendar facts Chapter 8. CALENDAR.BSM 8-1 Structure The MAIN procedure Dscription of MAIN Front matter Description of front matter Messages What's next? Chapter 9. CAL4.LIB 9-1 Introduction The procedures GET_TAIL procedure READ_MONTH procedure READ_YEAR procedure DAY_OF_WEEK and JAN_FEB procedures CLEARS, CURSOR, SHOWIT procedures ERASE_31 and TRIM_FEB procedures Evaluation of CALENDAR.COM Part IV. On to bigger and better things! Chapter 10. Developing a streamlined calendar program 10-1 Introduction INT 21h service 40h File "handles" and byte pointers The software breadboard CAL6.BSM Chapter 11. Other tools 11-1 Data structures (STRUC) Records Final words COWARDS Page v APPENDICES Appendix A. Hexadecimal numbers and the DEBUG dump display A-1 Appendix B. Relative, absolute, and effective addresses (EA) B-1 Appendix C. Backward number notation C-1 Appendix D. DEBUG register display D-1 Appendix E. Assembler instructions and operators E-1 Appendix F. Conditional jump instructions F-1 Appendix G. DOS and BIOS interrupts G-1 Appendix H. References H-1 INDEX I-1 COWARDS Page vi The ground rules To keep this book to a manageble size (and to keep from boring you) only a portion of the many things one can do with assembly language is covered. The nature of this portion is revealed through the following ground rules. This book is for MS-DOS IBM compatible computers; call that ground rule number 0. Assembly language programming is an involved subject and it may not be possible to organize its presentation in a way that is optimum for all potential readers. A survey of the available assembly language books reveals that they are all (as nearly as I can determine) organized quite differently from each other and they place their emphasis on different aspects of assembly language. Thus we have ground rule number 1: 1. This book is for beginners. For example, both .COM and .EXE programs can be produced using assembly language but the requirements for the two types of programs are quite different. Although most programs today are .EXE programs, historically the programming discipline began with .COM files since they are easier to write and understand. Thus we have ground rule number 2: 2. This book deals only with .COM files. However, on that point, did you know that a .COM program will run just as well if you rename it to have an .EXE extension? The reverse is also true! As another example of the involved nature of assembly language programming, the assemblers (the assembly language "compilers") used by professional programmers are quite expensive and their use is quite involved. Two such professional assemblers are called MASM (from Microsoft) and TASM (from Borland). On the other hand there are shareware assemblers available for a nominal cost that are more than adequate to get a beginner started. One of those is Eric Isaacson's A86. (Indeed, Eric can give some very heated arguments as to why the A86 is "superior" to "those other" assemblers - and does so in his instruction manual that comes on disk as part of the shareware package.) Finally, very simple .COM programs can be assembled using nothing more than DEBUG which comes with DOS. Thus we have ground rule number 3: 3. This book deals only with DEBUG and the A86 assembler. In fact, I believe this to be the only beginning book on the A86. Note that Eric also has his own debugger called D86 designed to completely replace Microsoft's DEBUG program. It does many things that DEBUG doesn't, but this book does not deal with it because DEBUG has all the capability you will need for awhile. And even though this book is tilted towards the A86 assembler, Eric Isaacson says that "A86 is now 99.83% MASM-compatible" meaning once you learn assembly language programming with the A86, you will be essentially COWARDS Page vii ready to use the expensive assemblers if that's what your boss requires! In this book you will begin using strictly DEBUG; even after assembling using A86 (or other assembler) you will still find DEBUG to be an indispensible tool in your further pursuit of assembly language. * * * * * Introduction Many books on assembly language (assembler) start with a grand tour of the 8088 microprocessor; they enumerate the registers, telling you what each one is for. This is information you will need eventually, but you don't need it all up front! Here you'll receive it little-by-little as it is needed, and it is all contained in an appendix for easy reference. Do books on BASIC give you a grand tour of the structure of the BASIC interpreter or compiler up front? Hardly. They tell you how to use the language. Must one understand all the mechanisms in an automobile before one learns to drive? (Case in point: Take my wife...) If you have some background with batch files, that will be helpful - or with BASIC. Your first .COM program will be written using a batch file! Also, some parallels will be drawn from time-to-time between assembler commands and batch or BASIC commands, so if you have that background you may be able to get on board faster. As an example, you'll see early-on that "DB" in assembler acts a lot like "ECHO" in batch; and "MOV DX,2" acts a lot like "LET DX=2" in BASIC. It's just that you can't use completely arbitrary names for variables; thus DX has a specific meaning but XX doesn't unless you've previously defined it. DX is called a "register" and there are only a small number of such symbols (registers) available for you to use in this manner. Also, "INT" is an "interrupt"; it's like BASIC's GOSUB only better - it's a call to a subroutine that's already written for you! Sound easy? Actually, instead of an approach enumerating and explaining the individual registers, this book begins with descriptions of some of the interrupts; routines already provided by your PC as part of DOS and BIOS. These are "wired-in" routines that you can call up very simply to do various routine tasks such as writing to the screen or reading from the keyboard. The most extensive set of interrupt "functions" or services are based on DOS interrupt INT 21h. Various useful tools of assembly language are introduced next; procedures, equates, and macros. Another useful set of assembler commands are the math commands; integer addition, subtraction, multiplication, and even division. These are built into assembly language and are easy to use, once you become familiar with their likes and dislikes. Using these, you can then synthesize more advanced operations such as sine and cosine. Indeed "libraries" of source code are available for assembly language that already do those things in subroutines or "procedures," as the assembly-language programmer calls them. You will be shown how to use procedures that you write yourself. Once you have a procedure to do this-or-that task on disk, you can easily use it in any program you write in assembler from then on! COWARDS Page viii Chapter-by-Chapter Outline Part I "Making .COM files using DEBUG" contains Chapters 1, 2 and 3. Chapter 1 shows the use of INT 21h function 9 to write a message to the screen. It also shows how to use DEBUG's mini-assembler in a batch file to create a .COM program using assembly language. This is a technique that's useful for creating many small .COM programs. Chapter 2 takes the .COM file you made in Chapter 1 and "unassembles" (disassembles) it, showing the correspondence between the "source code" or text file you wrote and the final machine-language executable .COM file. Some insights into machine language are also provided here. Chapter 3 then considers other DOS and BIOS interrupts, and provides you with source code for other simple .COM programs you can make using only a batch file. Part II "Getting started with A86" contains Chapters 4, 5 and 6. Chapter 4 discusses one useful tool of A86 assembly language: procedures. Procedures are similar to subroutines in BASIC. Chapter 5 discusses some other useful tools of A86 assembly language: equates and macros. Chapter 6 presents the source code framework or outline we'll be using with Eric Isaacson's A86 assembler. Part III "Developing a calendar program" contains Chapters 7, 8 and 9. Chapter 7 introduces the plan of our first real program, CALENDAR.COM. Chapter 8 presents the source code, CALENDAR.BSM, for CALENDAR.COM. Chapter 9 presents the library files to be used for producing CALENDAR.COM. Part IV "On to bigger and better things!" contains the remaining chapters. Chapter 10 presents the plan for a streamlined calendar program. Chapter 11 discusses data structures (STRUC) and other assembly language tools. Some good words from Eric Isaacson also appear here. Several appendices are provided for easy reference of useful information. Appendix A discusses hexadecimal numbers and the DEBUG dump display. Appendix B discusses relative, absolute, and effective addresses (EA). Appendix C explains backward number notation (BNN). Appendix D describes the DEBUG registers and flags. Appendix E lists the 8086/8088 instructions and operators with brief descriptions. Appendix F lists the conditional jump instructions. Appendix G lists the DOS and BIOS interrupts. Appendix H lists the literature referenced in the text. COWARDS Page ix Background assumed If you don't have a background in either batch files or BASIC programming, you can still learn assembler from this book. You do need to be familiar with the rudimentary DOS commands such as COPY, DIR, MD, CD, PATH, and such; and you need to know how to write and save a text file. This book will not teach you the ins and outs of all assembler commands and all DOS interrupts and BIOS interrupts; but it will get you started down the right road so you can easily continue to expand your horizons on your own. Easily? Well, it will require consistent hard work on your part to become an expert assembly language programmer, just as it does to become an expert in any field. No, this book won't make you an instant expert; but it is designed to get you painlessly over that first, initial learning hump. If you can use a text editor, then you can write batch files; and you can create assembly language programs even if you've never written one before. You don't need a $200 assembler; all you need is DEBUG which comes with DOS. That's right; if you have DOS, you already have all you need to get started! (I wish someone had told me that when I was getting started!) Even if you've never written a batch file before don't despair; this can be your first time! You are given enough information to make that possible. You may need to review the material in Appendix A concerning the hexadecimal (hex) number system. Hexadecimal numbers are used extensively in assembly language. Also, keep your DOS book handy as you will need to refer to the section on DEBUG from time to time. So often one sees works on computer programming that tell you in bits and pieces the capabilities of this or that computer language, but not how to write and assemble or compile a program! They may say somewhere, "Oh yes by the way, you need to buy such-and-such software before you can proceed." At times, those authors seem to assume you are already an expert on the subject! COWARDS Page x Getting started You will start by using the mini-assembler of DEBUG to assemble your source files into .COM files. But there are limits to what DEBUG's mini- assembler can conveniently do, and for those larger .COM files there is shareware available at nominal cost that will handle those cases. Eric Isaacson's A86 assembler is one that I highly recommend. The approach taken here is to start you out in Chapter 1 by writing that first .COM program, the one that says "Hello, world!" by using nothing more than a batch file. When you run the batch file, a .COM program is produced using the mini-assembler that is part of DEBUG. (It's "mini" only in the sense that to write a really big program using it would be extremely difficult.) You are then given the grand tour through the program, so you can see how it works. The theory behind this approach is that no-one needs the frustration of trying, unaided, to figure out how to create that first .COM file! It is assumed that you are a normally intelligent human being, but perhaps naive in the ways of assembly language in particular and programming in general. There is no disk available containing the programs in this book. Why? Because you learn assembly language by doing. You learn by typing in the programs, assembling, and debugging them yourself. In that way you experience early-on the problems you're sure to encounter when you begin writing your own programs. This book leads you by the hand for awhile and gives you a shove in the right direction. It's then up to you to take the ball and run with it; practice makes perfect, and all that! So fire up your computer and proceed! This book assumes DOS 3.30 but of course is not limited to it. COWARDS Page xi Acknowledgment I wish to thank Eric Isaacson for developing the A86 assembler, and for being so kind as to stop by to see me one day in 1990 when he was in Tucson. I also want to thank him for encouraging me in my stated goal of writing a book on A86 for the beginning programmer. He said it in a letter: Of course I'd be delighted if you wrote a book on A86 for beginners. See the final section of the final chapter for more of Eric's words. I also wish to thank you, dear reader, for choosing to examine this book! * * * * * Disclaimer The information contained in this book is believed to be accurate, however no guarantee to that effect is expressed or implied. * * * * * Trademarks and registered trademarks IBM, PC/XT, PC/AT, PCjr, PS/2 - IBM Corp. PC-Write - Quicksoft Co. A86, D86 - Eric Isaacson Software 416 E.University Ave. Bloomington, IN 47401-4739 MASM - Microsoft Corp. TASM - Borland International OPTASM - SLR Systems QUANTASM - Quantasm Corp. Spontaneous Assembly - Base Two Development/Acclaim Tech.,Inc. COWARDS Page xii Glossary A86 - Eric Isaacson's assembler. *ASM - Generic name for MASM, TASM, and others (not A86). Absolute address - The five-hex-digit address of a byte in memory. See effective address. Argument - A modifier used with an instruction or function. See also parameter, operand. Assembler - 1. Proper name meaning assembly language. 2. Software for "assembling" - converting - an assembly language source file to a binary file. Assembly language - The computer programming language consisting of the set of commands including MOV, PUSH, POP, INT, and RET. Assembly language is a "low-level" language; second only to machine language in that respect. Binary file - A machine language file. What you see when you call up a Dump (D command) using DEBUG. BIOS - Acronym for Basic Input-Output System. BIOS is a chip or chips containing ROM for the purposes of "hard-wiring" software into the computer. Bit - Binary digit. Byte - Eight bits. A two-digit hexadecimal number. CPU - Central processing unit. This is the chip that is the brains of your computer. It might be a type 8088, used in the PC XT. Other types are: 8086 (used in the PS/2), 80286, 80386, 80486. Delimiter - A character used to separate parts of a command. Examples are space [ ], comma [,], apostrophe ['], and quote ["]. The latter two examples are generally used in pairs to enclose a string of characters that need to be grouped together. DOS - Acroynm for Disk Operating System. DOS is a particular set of software (programs) that boot the computer and provide commands such as DIR and COPY. COWARDS Page xiii Effective address (EA) - The five-digit address specifier formed by taking the segment:offset form and collapsing it as described in Appendix B. For example, the address F000:1234 represents an EA of F1234. (Ref. Miller.) Effective address calculation (time) - The calculation (time) associated with instructions such as MOV and ADD. (Ref. Abrash.) Effective address, load (LEA) - The LEA instruction loads the offset of the source operand into the destination operand. (Ref. App. E.) Effective address specifier - Examples are the AX and BH operands of the MOV instruction. (Ref. Appendix E.) Enter - Refers to the Enter key, also called CR for carriage return. When the text says "Enter command XXX" that means you are to type the characters (the command) XXX then tap the Enter key. That instruction might also appear in this form: XXX . If it says "type" or "key-in" XXX that means you are not to follow it with a tap on the Enter key unless later explicitly told to do so. Sometimes the notation will be used to indicate you are to tap the Enter key. Machine language - The code that results from assembling an assembly- language source-code file. Machine language is the "lowest" level language that you can produce at the keyboard. Nybble - Four bits Offset - A specific address within a segment. Opcode - A machine language instruction. Operand - 1. A modifier which is part of an operator; just as "integrand" refers to the expression within an integral, and "radicand" refers to the expression within a radical. 2. A parameter or argument of an assembler instruction. Parameter - A variable or an argument. Programming - The process of generating a source code, then converting it into a .COM or .EXE file, testing it, debugging it and so on around and around until you get it right! Quote, double - The regular quote mark (inch mark) ["]. single - The apostrophe [']. RAM - Acronym for Random Access Memory. RAM is a volatile memory meaning everything that's stored there disappears when the power is removed. COWARDS Page xiv Registers - Places in the CPU where you or the machine places or removes data bytes. ROM - Acronym for Read-Only Memory. ROM is a permanent memory. Segment - 1. A particular 64k block of RAM. 2. A portion of assembly language source code. Source code - A text file containing the information necessary for an assembler or compiler to produce a COM, .EXE, or other binary file. String - A group of characters. Word - Two bytes. Note that an English word is not a "word" in computereze, but a "string"! CoWARD.1 9 Aug 1991/WK32/Fri 18 Jan 1993/WK03/Mon Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Part I. Making .CoM files using DEBUG Chapter 1. Assembling Introduction ------------ In this chapter you will be getting aquainted with DOS interrupt 21h, function 9, called "Display String." You will also see how DEBUG can be enlisted by a batch file to assemble a .CoM program that you will write as part of the batch file using assembly language. DEBUG came with DOS. Whatever directory you choose to do the following exercise in is immaterial; however, if you are not in the same directory with DEBUG, then DEBUG's directory must be on the PATH command. To see if it is, enter this at the DOS prompt: Echo Quick Henry, the Flit! | DEBUG (no kidding). If DEBUG is "on path" or in the current directory, the message "-Quick Henry, the Flit!" will be returned. If you get the ubiquitous "Bad command or file name" message instead, then you must either put DEBUG's directory on path or perform the following work within DEBUG's directory. In all seriousness, you could have used the command ECHo Q|DEBUG instead, to get back the response "-Q" (or "Bad command or file name"). DEBUG only looks at the initial "Q" or "q" before quitting; it doesn't care what you put after that. Do it! ------ We are now going to create that first .CoM file! Be sure there are no files named HELLO.BAT and HELLO.CoM in the current directory. To test for that, enter the command DIR HELLO.* and if "File not found" is returned you may proceed. Otherwise you will need to use different names for the following files; but keep the same extensions. You might use HOWDY.BAT and HOWDY.CoM instead, for example. Invoke your text editor and type this batch file, naming it HELLO.BAT or whatever name you've chosen: COWARDS Page 2 Goto start Takes batch interpreter to "Start" label. a100 JMP 112 ;Jumps to offset (line) 112 hex. ; ;102 - This number is assigned by DEBUG, not you, as will be explained. DB'Hello, world!'0D,0A'$' ;Defines message bytes. ; ;112 - This number is also assigned by DEBUG. MoV DX,102 ;Moves offset of message start into ; ;register DX. Needed by DOS ; ;interrupt 21h, function 9 ; ;invoked next. MoV AH,9 ;Moves 9 into register AH. INT 21 ;Invokes "Display string" function. RET ;Returns DOS prompt when .CoM program ;is run. rcx 1A w q This quits DEBUG, returns DOS prompt. :Start The next step invokes DEBUG. DEBUG HELLO.CoM < %0.BAT :This is HELLO.BAT. It makes HELLO.CoM You don't need to type the comments. You can write that file using a text editor, EDLIN, or even CoPY CoN. To use CoPY CoN, just enter CoPY CoN HELLO.BAT then type the file. When you are finished use the F6 key followed by Enter to return to DOS. This also saves the file. Read it using TYPE HELLO.BAT to check it for accuracy. Capitalization is not important. Spacing is not important either, except that there must be a blank line after RET. You may put comments after assembler commands or on separate lines, prefixed with semicolons as shown. The assembler ignores everything starting with a semicolon to the end of the line. You may also put comments at selected lines elsewhere, but only where indicated in the above listing. You don't need to type the comments when you create this file. When you are sure HELLO.BAT contains no typos, run it to produce the program HELLO.CoM. To do that enter HELLO at the DOS prompt. You may see an error message or three, but don't worry about those. DEBUG is being overly cautious, that's all. When the DOS prompt returns, enter HELLO again - this time running HELLO.CoM. If "Hello, world!" is returned you have been successful. You have just created a .CoM program using a batch file. If the computer hangs up instead, you'll know the batch file contains an error. Reboot and begin again. Proceed more carefully this time. All HELLO.CoM does is print the message "Hello, world!" but what HELLO.BAT does is two things: It illustrates how you can produce a .CoM file using a batch file that invokes DEBUG, and it shows how you can even put messages - things to be printed out on-screen - into those .CoM files. (DB COWARDS Page 3 appears to be the assembler equivalent of the batch "ECHo" command!) This program illustrates the use of one of the DOS interrupts, of which there are many. See an appendix for a listing of DOS and BIOS interrupts. DEBUG is a handy assembler; and you already have it if you have DOS. A chapter in your DOS manual describes it. Your attention is directed to that part describing the A (Assemble) command for the above exercise. Description of HELLO.BAT ------------------------ How does HELLO.BAT work? Look at the listing again. The first line transfers control to the line "DEBUG..." by jumping to the start label. At that time, DEBUG is told to be ready to accept input "script" from HELLO.BAT to be used to create a file named HELLO.CoM. DEBUG then looks at the first line of the batch file, doesn't recognize it as a valid command, and so it issues an error message but it continues. The next line tells it to begin assembling at offset 100. (You could have used "a" or "A" alone here instead of "a100" and the same thing would have happened.) The actual assembly language program starts with the line JMP 112 and ends with the line RET. (JMP means "jump" and RET means "return.") JMP 112 is like batch's or BASIC's Goto command. The "112" is like a line number in BASIC, but it's called an offset number here. It's an address in RAM. RET is like BASIC's Return, or like BASIC's SYSTEM command if there's nowhere to return to inside the program. Rcx tells DEBUG to be ready to accept a byte count for writing to disk. (Rcx stands for "register CX.") That byte count is 1A hexadecimal in the next line; the w and q tell DEBUG to write then quit, returning control to the batch file. The batch file then picks up where it left off and runs out without doing anything further, and the DOS prompt returns. If the batch file ran successfully, it creates HELLO.CoM and "writes" it to your disk. Pay special attention to the line starting DB. That tells the mini- assembler to define data bytes for the message that follows. The 0D and 0A are carriage return and line feed bytes; the dollar sign tells the mini- assembler that that is the end of this string of data. The mini-assembler figures out how much space to save for that string. Note that when hexadecimal numbers are used to define bytes they are simply separated by commas [,] or spaces [ ]; but when the characters corresponding to bytes are used they must be enclosed in a pair of apostrophes ['] or regular quotes ["]. The 112 in "JMP 112" is the hexadecimal offset for the first program byte following the data, and the 102 in "MoV DX,102" is the hexadecimal offset for the first byte of data. If you want to write a variation of this file, those numbers may change. How to determine the proper numbers to use? Use dummy numbers there; then go to DEBUG after assembling, tell DEBUG to "unassemble" (i.e., disassemble using the U command), visually check to see what those numbers should be and change them accordingly. You'll be shown soon how to do all that. The number on the line after rcx may also change if you change the program. That's the total byte count (in hexadecimal) of the program. You will be shown how to determine what number to use there also. In a later section, it is shown that if you use a different assembler, then it will figure out what those numbers should be so you don't have to. That's one reason the DEBUG assembler is called "mini." COWARDS Page 4 Assembler commands ------------------ An explanation of the assembly language commands used in HELLO.BAT is in order. It has been pointed out that the assembly language commands start in that file with JMP 112 and end with RET. Those commands are referred to collectively as "source code" because it is the source of the .CoM program that finally results from its assembly - assembly by DEBUG in this case. The first command, JMP 112, says "jump to offset 112 hex." That takes program execution around all the stuff (data, in this case) which occupies offsets 102 thru 111 here; that is just the message we put at DB. What happens next is that the hexadecimal number 102 gets moved into register DX. That's the first byte (character) of the message that we want printed to the screen. We've already told the machine where the end of the message is; that's the purpose of the dollar sign [$] at the end of the DB string. The reason we move the offset of the start of the message into register DX (the command MoV DX,102) is because that is a requirement imposed by DOS interrupt 21 hexadecimal (INT 21h) which is used to print the message on the screen. The next step (MoV AH,9) moves 9 into register AH. That specifies that function 9h of INT 21h "Display string" is to be used. The next command, INT 21h, actually puts the message on the screen. Using the DEBUG miniassembler, numbers (such as 102 or 21 in the above listing) are assumed by DEBUG to be hexadecimal. However when you're using a different assembler, the default number base may be decimal; so you will have to specify hexadecimal numbers as 102h or 21h, for example; or, with Eric Isaacson's A86 assembler, you may specify 0102 or 021 in those examples - the leading zero [0] telling A86 that this is a hexadecimal number. Now that your program has "done its thing" it only remains to return to DOS. That's the purpose of the step "RET." Another way to return to DOS is to use interrupt 20h; thus you could have specified "INT 20" instead of RET. Two other interrupts are available to return to DOS: INT 21h function 31h (Terminate and stay resident), and INT 21h function 4Ch (Terminate process with return code). These assembler commands will be the same in the source-code file (source file) for the A86 or other assembler, except that one must be careful about number bases. There are ways to specify numbers for decimal (base ten), hexadecimal (base 16), octal (base 8), and binary (base 2) in assembly language commands. This gives assembly language more versatility than you may think it needs, but if you continue assembly language programming you might be glad that that versatility's there! Writing HELLO.ASM ----------------- Now you can transfer what you have learned to an assembler source file to be used by one of the big boy's assemblers: MASM, TASM, OPTASM, QUANTASM, CHASM, ORGASM, ... (collectively, *ASM in this book; CHASM is shareware) or even the undemanding A86. You may initially like the A86 assembler because you can try the full-blown model, with the complete manual on-disk, for the price of one shareware disk. Eric also throws in his own unique debugger! But you can continue to use DEBUG if you want to. COWARDS Page 5 Later you may decide you like A86 just for itself! It is mostly compatible with the famous *ASM models from the giant software companies, but you don't have to lay out hundreds of dollars to try it. Of course if you find you are using A86 a lot, Eric will expect to see some payment from you; that's between you, Eric, and your conscience! Actually, you may already have A86 on your PC without knowing it. (Computer dealers have been known to put various shareware programs on a customer's hard disk without providing a list!) If you have the WHEREIS utility you can use that to look for it. If you don't have WHEREIS, you can still find it if it's on your hard disk. Just enter this command CHKDSK/V|FIND "A86" Be sure to leave a space after FIND and be sure you capitalize the "A" in "A86." Every filename on the default disk that contains "A86" in it will be displayed after about a minute. Look for "A86.CoM" to be returned. Joshua Auerbach* says the following source file will compile using *ASM to the same HELLO.CoM program you produced above using HELLO.BAT. I have tried it using A86 and it works. The source code listing is given next. Joshua might call it HELLO.ASM. I call it HELLO.BSM, having used A86 instead of *ASM to assemble it. (.BSM being a step above .ASM if you believe Eric!) You could call it HELLO.A86, or HELLO.EI for Eric Isaacson. Eric would probably call it HELLO.8. Actually it doesn't matter much what you call it. Here it is: HELLO SEGMENT ;Start of segment called "Hello." ASSUME CS:HELLO,DS:HELLO ;Tells assembler to start CS and DS ;segment registers with the ;Hello segment. oRG 100H ;Like the "a100" command you used with ;DEBUG. MAIN: JMP BEGIN ;Start of procedure called "Main." ;Instead of a number here, we use ;"BEGIN" to jump to Begin label. ;The assembler then figures out what ;offset number to put here. MSG DB 'Hello, world!'0D,0A'$' BEGIN: MoV DX,oFFSET MSG ;Instead of a number, we use "oFFSET MSG" MoV AH,9 INT 21H RET HELLO ENDS ;"ENDS" stands for "End segment." END MAIN ; End of source code. ________ *Joshua Auerbach, IBM Personal Computer Assembly Language Tutorial, Joshua Auerbach / Yale University /Yale Computer Center / 175 Whitney Ave. / P.O.Box 2112 / New Haven, CT 06520; or write to BRIGHT FUTURES / P.O.Box 1030 / East Windsor, CT 06088 and ask for shareware disk BFI No.190, "Assembly Language Tutorial." COWARDS Page 6 Horizontal spacing doesn't matter to the assembler - one space being equivalent to six; the form shown is for programmer satisfaction only. Notice that instead of writing "INT 21" as we did for DEBUG, we've written "INT 21H" - otherwise the *ASM and A86 assemblers will take the 21 to be a decimal number. If you've named that file HELLO.BSM, then enter this command to assemble it into HELLO.CoM using A86: A86 HELLO.BSM You must be in A86's directory or that directory must be on path when you enter that command. If not, then you'll simply get the message "Bad command or file name." After a successful assembly, entering HELLO will get you "Hello, world!" With the A86 assembler, you can trim down the source code dramatically and still obtain a successful assembly. Specifically, you don't need the first two lines nor the last two lines given above. You can temporarily "rem them out" (disable them) to test that claim by prefixing each of those four lines with a semicolon, making the assembler treat them as remarks (rems) or comments. In larger programs you will need lines like the first two with A86; and you may need all four and then some with *ASM! While we're on the subject of comments (again), in addition to inserting comments using semicolons, you can do it another way with files intended for assembly by *ASM and A86: You can put a whole block (paragraph) of comments in without using semicolons if you start the block with CoMMENT & and end it with &. Or you can use * or almost any other single character in place of &. (You can do that with DEBUG too, except DEBUG doesn't ignore those lines; what it does is issue an error message at each line, and you run the risk that a comment may accidentally be a legitimate command; the resulting confusion won't be pretty!) Use lots of comments throughout your program to tell you later what you did at the time. You'll be glad later that you did! Commentary ---------- With *ASM, there are multiple steps to go through before a .CoM file can be obtained from an .ASM file. Truly not a task for cowards. On the other hand the neat thing about using DEBUG (either via batch files or directly) to assemble .CoM files is that it is easy to obtain final, assembled files - therefore it is easy to test variations in commands, program flow, etc. to see what works and what doesn't. It is also easy to do that with A86. The other great thing about A86 is that it is a full-blown assembler - not "mini"! COWARD.2 14 Aug 1991/WK33/Wed 18 Jan 1993/WK03/Mon Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Chapter 2. Disassembling Disassembling HELLO.COM ----------------------- So now you've assembled a simple .COM program; the reverse process is the focus of attention next. With the program HELLO.COM on disk, enter this command: DEBUG HELLO.COM When you get the DEBUG hyphen prompt, enter the single letter U (for Unassemble). Or use U100 if this is not the first time you've used the Unassemble command after invoking DEBUG. You should see a listing like this: -U 3355:0100 EB10 JMP 0112 <--This is the first program line. 3355:0102 48 DEC AX 3355:0103 65 DB 65 3355:0104 6C DB 6C 3355:0105 6C DB 6C 3355:0106 6F DB 6F 3355:0107 2C20 SUB AL,20 3355:0109 776F JA 017A 3355:010B 726C JB 0179 3355:010D 64 DB 64 3355:010E 210D AND [DI],CX 3355:0110 0A24 OR AH,[SI] 3355:0112 BA0201 MOV DX,0102 3355:0115 B409 MOV AH,09 3355:0117 CD21 INT 21 3355:0119 C3 RET <--This is the last program line. 3355:011A ... ... The number in the first column (3355) will probably be different. That number simply identifies the particular 64k byte segment of RAM that the program is currently loaded into. You should recognize the commands JMP 0112, then later MOV DX,0102, MOV AH,09, INT 21, and RET. But what are those commands DEC AX, SUB AL,20, and so on? If you don't recognize them, that's because those bytes are actually part of the message you defined using DB. To see that part, you need to do a "dump" of that portion of memory. Enter D or D100 to get a display like this: -D100 3355:0100 EB 10 48 65 6C 6C 6F 2C-20 77 6F 72 6C 64 21 0D ..Hello, world!. 3355:0110 0A 24 BA 02 01 B4 09 CD-21 C3 6E 63 65 6C 20 46 .$......!.ncel F ... ... ... ... ... ... Now you can see your message "Hello, world!" The ASCII hexadecimal code for the letter H is 48, and so on. You may now enter Q to return to DOS. COWARDS Page 2-2 In the Dump display, the bytes are numbered as shown here: 0 1 2 3 4 5 6 7 8 9 A B C D E F 3355:0100 EB 10 48 65 6C 6C 6F 2C-20 77 6F 72 6C 64 21 0D ..Hello, world!. For example byte 2C near the center has the offset number 0107. If you compare the Dump (D) display with the Unassemble (U) display, you'll see the correspondence. Printing the "unassembled" file ------------------------------- If you want to get really classy, you can easily make a printout of the Unassemble and Dump displays for HELLO.COM. Just run this batch file; name it LOOK.BAT: Goto start U100 L1A D100 L1A Q :Start DEBUG HELLO.COM < %0.BAT > PRN Ignore error messages. The "L1A" parts tell DEBUG to print only 1Ah bytes; the length of the program. On the resulting printout, you can make annotations for future reference. For example, you can write the message characters on the Unassemble printout where they belong, replacing the commands that DEBUG put there in its attempt to disassemble the file something like this (I've also added column headings and comments): -Address- Segment Opcode DB Assembler command Offset ~~~~~~ ~~ ~~~~~~~~~~~~~~~~~ 3355:0100 EB10 JMP 0112 ;ÄÄÄÄÄÄÄÄ¿ 3355:0102 48 H DEC AX ;Program jumps to offset 112. 3355:0103 65 e DB 65 ; ³ 3355:0104 6C l DB 6C ; v 3355:0105 6C l DB 6C 3355:0106 6F o DB 6F 3355:0107 2C20 , SUB AL,20 ;20 is space. 3355:0109 776F wo JA 017A 3355:010B 726C rl JB 0179 3355:010D 64 d DB 64 3355:010E 210D ! AND [DI],CX ;Recognize 0D?... 3355:0110 0A24 $ OR AH,[SI] ;...and 0A? 3355:0112 BA0201 MOV DX,0102 ;0102 is offset of letter H. 3355:0115 B409 MOV AH,09 3355:0117 CD21 INT 21 3355:0119 C3 RET ;RET, the last command in this file, ;requires one byte (opcode C3 as shown). Thus the length of the file ;is confirmed as being 0119 - 0100 + 1 = 1A, all in hex! COWARDS Page 2-3 Now you can see that the message starts at offset 102, and the program resumes at offset 112. Those are the two numbers in MOV DX,102 and JMP 112. Thus if you want to use a different message of a different length, you can see how to change the number at JMP 0112 to reflect the number assigned by DEBUG. The number following rcx in HELLO.BAT will also have to be changed, as described next. Notice the offset of the first byte after the end of the program: 011A. If you subtract the offset of the first byte of the file (0100) from 011A, you get 1A which is the length of the file! That is the number you put in HELLO.BAT after the rcx command. This method of determining the length of a program works every time, as long as you know which is the last command in the program! Also, it is important to remember that .COM programs always start at offset 0100. The offset number is kind of like a line number, and so assembly-language programming has a certain convenience in this regard when compared to high level languages that do not use line numbers. Or you can enter RCX in this case; the number returned is the length of the file. (Then just tap the Enter key to get the DEBUG hyphen [-] prompt back.) Before leaving this display, notice one more thing. In the line MOV DX,0102 notice the opcode BA0201. The two bytes 01 02 are shown in the opcode in reverse order, 02 01. Be on the lookout for further instances of this kind of reversal! Machine language ---------------- The code you see in the Dump display is machine code. Thus the file HELLO.COM can be represented in machine language as this sequence of bytes: EB 10 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 0D 235 016 072 101 108 108 111 044 032 119 111 114 108 100 033 013 H e l l o , w o r l d ! 0A 24 BA 02 01 B4 09 CD 21 C3 010 036 186 002 001 180 009 205 033 195 $ ! The code is given here in hexadecimal (two-digit numbers) and in decimal (three-digit numbers). Where codes correspond to actual keyboard characters, those characters are also indicated. (Notice that a leading 0 does not necessarily indicate a hex number here with this two-digit/three-digit notation which we'll use only temporarily.) You can produce this file directly in machine language quite easily by running a batch file like this (name it ASSEMBLE.BAT): COWARDS Page 2-4 Goto start E100 EB 10 48 65 6C 6C 6F 2C 20 77 6F 72 6C 64 21 0D E110 0A 24 BA 02 01 B4 09 CD 21 C3 rcx 1A w q :Start DEBUG HELLO2.COM < %0.BAT E is DEBUG's Enter command. It allows you to write bytes directly to specific locations in memory. If done properly, when that batch file is run it will produce a file identical to HELLO.COM that you produced using the procedure described in Chapter 1. In the above file you can substitute the actual message characters for the hexadecimal numbers in the first line of data, but use quotes like this: E100 EB 10 "Hello, world!" 0D Using "screen files" with DEBUG ------------------------------- As an alternative to the two batch files given above, LOOK.BAT and ASSEMBLE.BAT, you can use "screen files" to accomplish those tasks as described next. A screen file is one that you type only to the screen, not to disk. You'll start by calling up a "DOS screen," then piping to DEBUG. To do that, you'll use variations of this command: TYPE CON |DEBUG That will get you a flashing cursor without a prompt. Next, type your file being sure there are no typos at each line before you tap the Enter key. (You cannot go back to correct errors; instead you'll have to abort with Ctrl- Break, then start over.) When you have finished the screen file, tap the F6 key then the Enter key to return to DOS and to pipe the screen file to DEBUG. To do what LOOK.BAT did using this method, initiate a DOS screen with this command: TYPE CON |DEBUG >PRN Then type this screen file: U100 L1A D100 L1A Q ^Z COWARDS Page 2-5 The ^Z indicates that you have tapped the F6 key. Now tap the Enter key and your printout should be delivered to you if your printer was on line! In place of ASSEMBLE.BAT initiate a DOS screen with this command: TYPE CON |DEBUG HELLO3.COM Then type this screen file: E100 EB 10 "Hello, world!" 0D E110 0A 24 BA 02 01 B4 09 CD 21 C3 RCX 1A W Q ^Z Here, we've replaced bytes 48 thru 21 with the message string in quotes. (That could have been done in ASSEMBLE.BAT, also.) Again, the final ^Z indicates you have tapped the F6 key. When you subsequently tap the Enter key, this file will be piped to DEBUG and HELLO.COM will be created. Be on the lookout for error messages returned by DEBUG. You can also work directly in "DEBUG-land" to perform the second task, although the first task (printing an output) is more easily done using a batch file or a screen file. The second example shows one way to create .COM files directly in machine language. Another, more esoteric way is described in the next section. Gaining confidence in the use of DEBUG will give you a "leg up" early on with assembler. Practice the above exercises until you feel confident using them and understand exactly how and why they work. Still another way to use machine language ----------------------------------------- You can also type the machine code directly into a file named HELLO4.COM with your text editor using the Alt-nnn keypad trick for non-keyboard characters and control codes. You first need to become familiar with the requirements of your text editor for putting all ASCII and extended ASCII characters into a file. Once you are familiar with the likes and dislikes of your text editor, you can reproduce simple .COM files this way. Just type in the message, tapping the Enter key when you come to 0D 0A. don't leave any space between the characters, except the one inside the message. The completed file should look like this in your text editor: ëHello, world! $º´ Í!à You can also see that display by entering the command TYPE HELLO.COM after you have HELLO.COM on disk, except that the tab character will expand as an actual tabulator space! With this method, superflous bytes may be added to the end of your file by your text editor. These should not cause any trouble, but will extend the length of your file. You can remove them with DEBUG after you've become proficient in its use. COWARDS Page 2-6 In case you're not familiar with the Alt-nnn keypad trick, here is a brief description. To type a character you would normally hit its key. But what about the non-keyboard characters? You can produce them by depressing an Alt key while keying-in the character's decimal ASCII code in the numeric keypad. When you release the Alt key, the character will appear. Nearly all characters from 000 to 255 can be produced this way at the DOS prompt or in COPY CON. Those that can't are 000, 003, 008, 009, 010, 013, 016, 019, 020, 027, and 127. You can produce 000 while in COPY CON by tapping the F7 key. Some text editors will let you produce all but one or two characters using the Alt-nnn keypad trick. PC-Write is one that does, except that to produce the 000 character (ASCII zero) in PC-Write, use Ctrl-U (depress and hold a Ctrl key while tapping the U key); and to do the 013 010 (0D 0A) combination, just tap the Enter key. If there are any characters you cannot put in a file using your editor, insert a dummy character for them (say ASCII 032 space - you can just tap the spacebar once to do that) then load the file into DEBUG and replace those bytes. They'll appear as "20" there. To replace a byte, load the file into DEBUG as described at the beginning of this chapter. Then enter D to give a Dump display. Identify the first byte you want to change; it might be the byte at offset 0101. To change that byte to 10, type E0101 10 . Proceed similarly with the other bytes you want to change. When you have changed them all, be sure you have DEBUG's hyphen prompt (tap Enter to obtain it if necessary). Do a new dump (enter D100) and check it again. When you have it the way you want it, enter W then Q to write the new information to disk and to quit DEBUG. As a final note, you can obtain a printout (on your printer) of HELLO.COM with this command at the DOS prompt: ECHO L;E |EDLIN HELLO.COM >PRN The resulting printout should look like this: End of input file *L;E 1:*ë^PHello, world! 2: $º^B^A´ Í!à The 1:* and 2: parts are artifacts added by EDLIN. The carriage return (0D), line feed (0A), and horizontal tab (09) bytes are interpreted (expanded) in the resulting printout and not explicitly printed as ^M, ^J and ^I. COWARD.3 14 Aug 1991/WK33/Wed Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Chapter 3. Interrupts BEEP.COM -------- Interrupts were considered in Chapter 1, and it is now time to consider them once again. BEEP.COM, shown next, uses DOS interrupt 21h function 2, "Character output," to output a single character to the console (CON) screen or - as in this case - speaker. It produces a beep tone when the command BEEP is entered at the DOS prompt - just like the BASIC BEEP command does in BASIC. The batch file to produce BEEP.COM is listed here: Goto start a100 MOV DL,7 ;Character to be output. ASCII 7 is the DOS beep. MOV AH,2 ;Putting 2 in register AH will call function 2 INT 21 ;Execute interrupt 21h function 2 to output character to CON. RET ;Return to DOS rcx 7 w q :Start DEBUG BEEP.COM < %0.BAT Name this file anything you want, but use the .BAT extension. Run the batch file, then enter BEEP to see if it works. If not, reboot (if necessary) and try again - more carefully this time. We now interrupt this program ... --------------------------------- So far you have used these two DOS interrupts: INT 21h function 9, Display string INT 21h function 2, Character output Actually, both of these tasks can be performed by interrupt 21h function 40h, but it is easier to use functions 9 and 2 for the purposes shown and they require fewer program steps. Two down and only some 92 DOS interrupts to go! Then there are the BIOS interrupts. ...And assembly can use them all! This is indeed a rich language. It appears that what is needed now is a compilation of all the DOS and BIOS interrupts (there are a bunch!) showing how to use them! Among the DOS interrupts there are these catagories: COWARDS Page 3-2 1. Character input 2. Character output 3. Disk management 4. File management 5. Information management 6. Directory management 7. Process management 8. Memory management 9. Miscellaneous system management Among the BIOS interrupts there are these catagories: 1. Keyboard service 2. Video service 3. Diskette service 4. Fixed disk service 5. Serial communications service 6. System service 7. Parallel printer service 8. Time-of-day and date service 9. Single function service The BIOS catagories traditionally do not parallel the DOS catagories because their focus is different. Thus, for example, "time-of-day and date" is a separate catagory of service in BIOS, but the "get and set time and date" functions are contained within the "miscellaneous system management" catagory in DOS. Another complication is the fact that the BIOS interrupts that are available in any given PC depends to a large extent on the particular ROM chips it contains, whereas DOS interrupts depend entirely on the version of DOS that is being used. Must one always use an interrupt to perform a task when using assembly language? No; but the job of the programmer (that's you!) will be greatly simplified that way. But don't despair; although there is a considerable richness in the number of DOS and BIOS functions available in the form of interrupts, that doesn't mean you need to immediately learn all of them - any more than you need to know the meaning of every single word in Webster's unabridged dictionary in order to speak English. You should, however, know generally what's available and where to find a description of how to use it when and if you ever do need it. One of the best sources for details on the DOS interrupts is "The MS- DOS Encyclopedia." See Section V, System Calls. One of the best sources for details on the BIOS interrupts is "System BIOS for IBM PC/XT/AT Computers and Compatibles," Chapters 8 thru 16. Another source is "The Ultimate DOS Programmer's Manual." That book deals with both DOS and BIOS interrupts, but it is strictly for experts! (Refer to appendix for details on those books.) The next section discusses an assembly language program that uses a BIOS interrupt. COWARDS Page 3-3 PRINTSCR.COM ------------ This program uses BIOS interrupt 5, Print screen service. It allows you to activate the PrintScreen feature by use of the command PRINTSCR at the DOS prompt or in a batch file. The listing follows. Goto start a100 INT 5 ;Executes BIOS interrupt 5, Print screen service. RET ;Takes you back to DOS rcx 3 w q :Start DEBUG PRINTSCR.COM < %0.BAT BIOS interrupt 5 is easy to use; all you do is call it. You can use RET to exit as before. If you use PRINTSCR when the printer is not on line, the computer can hang up. Therefore you need a companion file, which is called PRNSTAT.COM here (for "printer status"), to check on the condition of the printer before PRINTSCR is invoked, as for example, in a batch file. PRNSTAT.COM ----------- A batch file to create PRNSTAT.COM is listed next. Goto start a100 MOV DX,0000 ;Printer number - 0000=LPT1, 0001=LPT2, 0002=LPT3. MOV AH,2 ;Prepare to "Read printer status." INT 17h ;Do it. Exit code (return code) is in AH. MOV AL,AH ;Put exit code in AL. MOV AH,4Ch ;Prepare to "Terminate process with return code." INT 21h ;Do it. Exit code is now in AL where a batch file ;can find it! rcx D w q :Start DEBUG PRNSTAT.COM < %0.BAT COWARDS Page 3-4 This file uses BIOS interrupt 17h function 2, and DOS interrupt 21h function 4C. Decimal exit codes returned are: 24 Printer is off-line 56 Printer is off-line & paper is out 136 Printer is turned off 144 Printer is ready 176 Printer is on-line but paper is out To use PRNSTAT in a batch file to allow program execution to continue only if the printer is on-line, use these steps: ... :Start PRNSTAT If errorlevel=144 if not errorlevel=145 goto continue Echo Please put printer on line. Pause Goto start :Continue ... COWARD.4 14 Aug 1991/WK33/Wed Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Part II. Getting started with A86 Chapter 4. Procedures Introduction ------------ As discussed in previous chapters, DOS and BIOS interrupts are handy when you need to deal with some aspect of the computer - such as the screen, keyboard, files, or disk drives. But what about other things you may want to do, like perform simple math or print a calendar for an arbitrary year? That's where procedures come in. If interrupts are subroutines already "wired-into" your computer, then procedures are subroutines you write. The simple programs you've written so far contain a single procedure, traditionally referred to as the MAIN procedure. Since there was only the one procedure, we didn't bother to name it at all. There is nothing magic about the name "MAIN" and you could name it "SUBORDINATE" instead; it is named MAIN only by tradition, but that tradition is strong. Procedures beyond MAIN are like BASIC's subroutines. You'll use the assembly CALL command to reach them. CALL is like BASIC's GOSUB command. It takes program execution directly to the specified procedure. Then when the procedure has run its course, program execution returns to the MAIN procedure where it left off. Just as with a subroutine, a given procedure can be used over-and-over by a program but it only needs to appear once. Thus by the judicious use of procedures, quite complicated operations can be performed by a program of reasonable size. Another advantage of procedures is that - once written - they can be put in a separate "library" file, and used when assembling other programs. It is only necessary that the name of that library file be placed in the command line at the DOS prompt at the time of assembly. Thus, if you want to assemble a file TEST.BSM using A86 and incorporate procedures from a file named TEST.LIB, the command line (assembler "invocation line" Eric calls it) would be A86 TEST.BSM TEST.LIB Within the file TEST.BSM you will have specified which of the procedures contained in TEST.LIB you want to use in the final program TEST.COM. Then the A86 assembler will pull only those procedures from TEST.LIB and incorporate them into the assembled program. Neat! An example ---------- An example of a procedure is given next. This procedure is called CURSOR. It is used to position the cursor at a desired location. It is a NEAR procedure because it resides in the same 64k byte area of memory as the MAIN procedure; it is part of the source code for a .COM file. The source code listing is COWARDS Page 4-2 CURSOR PROC NEAR ;Row must be in DH register, column in DL. PUSH AX,BX ;Save the numbers in AX and BX registers. SUB BH,BH ;Change BH register contents to 0 ("clear" it). MOV AX,0200h ;Puts 2 in AH and clears AL. (AH is the "high" ;byte of AX; AL is the low byte.) INT 10h ;Invokes BIOS INT 10h function 2, ;"Set Cursor Position." POP BX,AX ;Restores previous values to BX and AX registers. ;(Isn't "PULL" the opposite of "PUSH"? 'Guess not!) RET ;Returns program execution to MAIN procedure. ENDP ;End procedure. This line not needed with A86. You'll see that BIOS interrupt 10h function 2 has been invoked here to do the actual cursor positioning. Eric Isaacson recommends that for use with the A86, the line "CURSOR PROC NEAR" be replaced with the line "CURSOR:" alone. (The colon [:] must be there as shown. That makes CURSOR a label.) You'll see how to use this procedure in a program soon. It may seem that a procedure is a special entity; actually it is just another routine (or subroutine), and therefore it can be called by prefixing it with a label as Eric recommends. Will that approach work with the *ASM assemblers? I wrote and asked Eric. I think he answered "yes." With *ASM assemblers the two lines PUSH AX,BX and POP BX,AX must be expanded into four lines, like this: PUSH AX PUSH BX ... POP BX POP AX This illustrates still another reason I like A86. Summary ------- Procedures are subroutines you write. Interrupts are "subroutines" built into the software. DOS interrupts are in RAM and BIOS interrupts are in ROM. Interrupts used so far in this book are these: DOS: INT 21h function 2 Character output INT 21h function 9 Display string INT 21h function 4Ch Terminate process with return code BIOS: INT 5h Print screen service INT 7h function 2 Read printer status INT 10h function 2 Set cursor position COWARDS Page 4-3 ... and commands are these: Like BASIC's Like batch's ~~~~~~~~~~~~ ~~~~~~~~~~~~ JMP n Goto n Goto n DB "String" Print "String" Echo String MOV a,b LET a=b RET Return or System SUB a,b a=a-b PUSH a ¿ PUSH saves a quantity on the "stack" and POP pulls POP b à a quantity off the "top" of the stack. These commands Ù are necessary because of the limited number of registers available. In BASIC, POKE and PEEK are rough equivalents. Please be aware that parallels drawn in the above table between assembler and BASIC commands or between assembler and batch commands are not always exact. For example, the command SUB a,b is subtraction without borrow. COWARD.5 14 Aug 1991/WK33/Wed Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Chapter 5. Equates and macros Introduction ------------ You have seen how procedures can permit a given routine to be used over and over in a program without having to duplicate the same code every time. That is one of several useful tools in assembly language; two more useful tools are equates and macros. Just as is the case with procedures, equates and macros are identified on their first line by a unique name. By "unique" is meant that a name must be selected by you, the programmer, that is not used in any other context within the program. Equates and macros are for use only with *ASM and A86 assemblers. They won't work with the DEBUG mini-assembler. ...Or will they? You can try them, of course! Equates ------- An equate is a symbolic equation wherein you tell the assembler to substitute a particular symbol for another. For example, 13h is the code for a carriage return. A simple equate applying to this case might be CR EQU 13h ;"CR equates to 13h." CR is the unique name of this equate. If you put that line ahead of the program code the assembler then knows that when it sees "CR" it should substitute "13h" for it. (You are still free to use 13h directly.) Thus, when you write the code and want to specify a carriage return, simply use CR and the assembler will know what you mean. Other commonly-used equates are LF EQU 10h ;Line feed ESC EQU 1Bh ;Escape character EndStr EQU 24h ;End string character, dollar sign [$] Exit EQU INT 20h ;Exit to DOS Traditionally, the symbol EQU is used in equates, but you can use an equal sign [=] instead if you want to. There is a subtle difference in the effect of the two symbols; a symbol definition using EQU cannot be changed halfway through a program but one using an equal sign can be. (Another use for the equal sign is presented in Appendix E.) Macros ------ An equate is a single-line symbol substitution definition. A macro is a multi-line definition. Macros are short routines that you place near the beginning of the source code file (or in a separate macro library file. They define a sequence of commands - macros resemble procedures in that respect. One difference between the *ASM assemblers and the A86 lies in the design of macros. COWARDS Page 5-2 When source code containing macros is assembled, the assembler "expands" the macros so that the lines of codes they represent are, themselves, placed in the assembled program at each point where the macro is called (i.e., where its name appears). This action points out an important difference between macros and procedures; the code corresponding to a given procedure will appear only once in the assembled program, but the code corresponding to a given macro will appear everywhere that macro is used in the source code. (But it need only appear once in expanded form in the source code file.) Macros are usually shorter than procedures, although they don't have to be. Macros may appear near the beginning of any procedure. Thus, if a macro is used only within a particular procedure, it need appear only within that procedure. Parameters can be used with macros. That is, you can design a macro to accept one or more numeric arguments to modify the action of the code as needed. Thus if you design a macro to position the cursor on the screen, two parameters you would need to specify are row and column numbers. A macro may contain within it a call to a procedure. An example of a macro for use with A86 to position the cursor follows. It accepts two parameters and contains a call to the CURSOR procedure discussed in the previous chapter: LOCATE MACRO ;Command is LOCATE row,column. MOV DH,#1 ;This puts the first parameter in register DH. MOV DL,#2 ;This puts the second parameter in register DL. CALL CURSOR ;This calls a procedure named "CURSOR." #EM ;This line marks the end of this macro. The name of this macro is "LOCATE." With this macro near the start of your source code file, you can use the following command within the MAIN procedure to position the cursor at row 10, column 30: LOCATE 10,30 Neat! In order for this macro to work, the CURSOR procedure must be found towards the end of the source code, or it must be in a library file whose name appears on the command line along with the name of the source code file when A86 is invoked to assemble the program. The CURSOR procedure for use with this macro is listed in the previous chapter. COWARDS Page 5-3 Summary ------- Notice in both of the above entities, the first line carries the name, then something called the "directive" then perhaps some kind of qualifier. Note that a similar anatomy applies for procedures. The examples given so far (plus one) are summarized in Table 5-1. Table 5-1. Elements of four assembly language tools. ----------------------------------------------------- The tool Name Directive Qualifier ~~~~~~~~~~~~~~~~~~ ~~~~~~ ~~~~~~~~~ ~~~~~~~~~ A "procedure" CURSOR PROC NEAR An "equate" CR EQU 13h CR = 13h A "macro" LOCATE MACRO A "data structure" LINE STRUC [BP] Data structures are discussed in a later chapter. Although no qualifier element is needed with the example macro, some macros do take one or more of those. We've added a new command in this chapter. The list now looks like this: Like BASIC's Like batch's ~~~~~~~~~~~~ ~~~~~~~~~~~~ JMP n Goto n Goto n DB "String" Print "String" Echo String MOV a,b SET a=b RET Return or System SUB a,b a=a-b PUSH a POP b CALL name Gosub nnn COWARD.6 16 Aug 1991/WK33/Fri Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Chapter 6. Assembly source code framework Introduction ------------ The framework for an assembly language program intended for assembly by A86 to a .COM file is presented next. ;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ;-------------------- Start of Front Matter -------------------------------- ;Introduction and comments ... ;--------------------------------------------------------------------------- DATA SEGMENT;*********;List of variables ******************************; ORG ... ; ... ; CODE SEGMENT;*********;End of data segment; start of code segment. ****; ... ORG 100h ... ;--------------------------------------------------------------------------- JMP MAIN ;-------------------- Message Land ----------------------------------------- ECHO1: DB '... ... ;--------------------------------------------------------------------------- ; Macro ; Land ;-------------------- End of Front Matter ---------------------------------- MAIN: ;--------------------------------------------------------------------------- ; PUT YOUR PROGRAM NEXT ;--------------------------------------------------------------------------- ... ;-------------------- End of MAIN procedure -------------------------------- ;---- Remaining procedures follow, or put them in a separate .LIB file. ---- ; ... ;-------------------- Th-th-th-that's all, folks! -------------------------- ;ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ COWARDS Page 6-2 Explanation of framework ------------------------ A description of the program, how to use it, and how to assemble it should go first, at "Introduction and comments." Be sure to put a semicolon at the start of each line of comments. A list of variables comes next, in the DATA SEGMENT. Variables are temporary symbols for storing data, and we'll see how to use that part in the calendar program to be developed in subsequent chapters. The declaration CODE SEGMENT puts an end to the data segment with A86. Certain items of "boiler plate" go here; such as an ORG 0100 or ORG 100h statement telling the assembler to start code at offset 0100h. It will do that even without that statement, but putting it here informs everyone that this is to be a .COM program. Put your equates here also. The first line of code is produced by the instruction JMP MAIN. It will be assembled at offset 0100h. Messages follow. These are strings of characters that will be assembled into data to be called up later by the program to put messages on the screen. Macro Land is next. Here you will define all the macros to be used in the MAIN procedure. After that the "actual program" starts; i.e., the coded instructions that put the microprocessor through its paces when the resulting .COM program is run. The first procedure is called MAIN and that is the one that pulls all the strings - calling other procedures when they are needed, using the instructions coded into macros, using raw assembler instructions, and using DOS and BIOS interrupts. When execution of the resulting .COM program reaches the end of the MAIN procedure, it will be all done; and the DOS prompt will be returned at that time. In your source code, all other procedures follow the MAIN procedure - either inside the .BSM source code file, or in a .LIB file which will be assembled with the .BSM code. Save this source code file, name it Something.BSM and exit. The above description is only a skeletal outline of the program; we'll fill in the blanks for a particular example in following chapters. COWARD.7 17 Aug 1991/WK33/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Part III. Developing a calendar program Chapter 7. CALENDAR.COM Introduction ------------ In the chapters that follow this one we'll be building two assembly language source files to make an executable file that displays the Gregorian calendar for any month. Although the algorithm we'll use is good for all time under the Gregorian scheme, the Gregorian calendar has limited applicability to planet Earth, and that will be discussed in detail in the section "Calendar facts." We'll call the source files CALENDAR.BSM and CAL4.LIB, and the executable file CALENDAR.COM. CALENDAR.BSM will contain the introductory material (front matter) and the MAIN procedure. CAL4.LIB will contain all of the remaining procedures. Plan of action -------------- The calendar will be constructed as a standard 31-day matrix 13 days wide, like this: 1 2 3 4 5 6 2 3 4 5 6 7 8 9 10 11 12 13 9 10 11 12 13 14 15 16 17 18 19 20 16 17 18 19 20 21 22 23 24 25 26 27 23 24 25 26 27 28 29 30 31 30 31 That matrix will be positioned horizontally on the screen under a Su Mo ... heading in the proper location according to the month and year specified when CALENDAR.COM is run. Then - in a flash - all the overhanging and other excess days will be written over with spaces. That is the plan. The key to making the plan work is to be able to position the matrix in the proper horizontal location. For this, we need an algorithm which determines the day of the week (day o'week) for the first day (or any other day) of the specified month. The algorithm we'll use is called the Gregorian Algorithm. There are more elegant ways to design a calendar program, but one must walk before one runs. This was my first .COM file calendar that worked well. We'll look at another way in a subsequent chapter. The Gregorian Algorithm ----------------------- Had I known in 1955 that someone named Zeller had already produced an algorithm for the Gregorian calendar, I would not have needed to design my own! COWARDS Page 7-2 Calendars have fascinated me ever since I can remember; so during 1955-56 I set out to find an algorithm (formula) to determine the day o'week under the Julian scheme, and subsequently under the Gregorian scheme. There being no PCs then and no BASIC either, the formula was strictly for hand- processing use. The BASIC form of that Gregorian Algorithm is short and not too complicated: W = (Y+Y\4-Y\100+Y\400+2.6*M+D+1.2)MOD 7 The month multiplier and the constant term were determined empirically (that means by trial and error!) so that when the integer operation MOD is applied, everything falls into place! In the algorithm, W is the day o'week - a number from 0 (Sunday) to 6 (Saturday). Its repetitive, cyclic nature is assured by the final "MOD 7." Y is the year in ordinary notation. Thus for 1991, Y=1991. M is the month such that for March M=3 and so on; but now here's the tricky part. Since the extra day in leap years is at the end of February, that means January and February must be specified as months 13 and 14 of the previous year. that anomaly is overcome in CALENDAR.COM, as you'll see. D is the day of the month. The four terms Y+Y\4-Y\100+Y\400 express the Gregorian leap year rule in which every fourth year (the +Y\4 part) is a leap year except that century years are not (the -Y\100 part) except that every fourth century year is (the +Y\400 part). The initial Y expresses the fact that the day o'week for a particular date progresses by one day each successive common (non-leap) year. You can see that action by comparing, say, March 1st of 1990 with March 1st of 1991, two successive common years. The backslash [\] indicates BASIC's integer division. Testing the Gregorian Algorithm ------------------------------- A programmer I admire has said that he doesn't trust any calendar algorithm that contains arcane constants like 2.6 and 1.2; so you, too, may wish to confirm that the Gregorian Algorithm does indeed work. With the following one-line command, you can test it at the DOS prompt for any date you wish: ECHO M=7:D=4:Y=1776:U=LOG(M-2):?(Y+Y\4-Y\100+Y\400+2.6*M+D+1.2)MOD 7"'sday |GWBASIC All that stuff between ECHO and question mark [?] isn't needed, but it makes using the Algorithm over-and-over more convenient if you have Chris Dunford's CED command editor, DOS 5.0, or similar capability. To use that command, GWBASIC must be on path. Enter ECHO ?"On path|GWBASIC to see if it is. The message "On path" will be returned if it is; otherwise you'll see "Bad command or file name." COWARDS Page 7-3 The above command returns the day o'week for July 4, 1776. Try any month (M), day of month (D) and year (Y) you want to. Just change the part "M=7:D=4:Y=1776" you don't need to change any other part of the command. Remember to use M=13 or 14 of the previous year for January or February. The program won't let you use M=1 or 2; the "U=LOG(M-2)" part does that, since LOG(-1) and LOG(0) return an "Illegal function call" message. This command codes Tuesday as 2'sday, and so on. (I hope I can remember that!) July 4, 1776 was a Thursday. If you try M=10:D=12:Y=1492, you won't get 5'sday - the day o'week Columbus sighted America. That's because October 12th was the date under the Julian calendar, and the Julian calendar was running 9 days slow at the time! Try M=10:D=21:Y=1492 instead. That was the (extrapolated) Gregorian date on that day in history. So October 21, 1992 is actually the 500th anniversary of that event - not October 12th! Modifying the algorithm for assembly language use ------------------------------------------------- Since in the present application the only interest is in determining W for a particular day of the month, one can set D=0, giving W = (Y+Y\4-Y\100+Y\400+2.6*M+1.2)MOD 7 To better suit it for use in an assembly language program, the form is changed to eliminate the decimal fractions: W = (Y+Y\4-Y\100+Y\400+(26*M+12)\10)MOD 7 Still another change is necessary because of the difference in the rounding effects of ()\10 and ()MOD 7. The final form to be implemented is W = (Y+Y\4-Y\100+Y\400+(26*M+16)\10)MOD 7 (This form of the Gregorian Algorithm also works properly in the ECHO ... |GWBASIC implementation, as you can easily confirm, but it is longer.) In addition to the Gregorian algorithm, a "leap-year algorithm" for the Gregorian calendar is developed. While this is really implicit in the Gregorian algorithm, an algorithm designed explicitly for this purpose will prove useful. The leap-year algorithm to be used is L = Y\4-Y\100+Y\400 - (U\4-U\100+U\400) where Y is year and U is Y-1. L comes out to 1 if Y is leap year, 0 if not. Calendar facts -------------- When talking about any particular calendar (Julian, Gregorian or other) it is helpful to think of it simply as an algorithm in its own right, independent of any concept of time - like a recipe for brownies. In this sense a calendar does not change, nor does it have a beginning or an end. Only our application of it changes as time goes by. COWARDS Page 7-4 In this way one should have no difficulty extrapolating any particular calendar to any year one pleases. Thus, even though the Gregorian calendar was not used in America prior to September 1752, still George Washington wisely reckoned his birthday in terms of it, as we continue to do to this day. He was born some 20 years earlier in Westmoreland County, Virginia. In keeping with the "brownie recipe" idea, instead of saying a particular year "was" or "will be" a leap year, the verb "is" is used in that connection in what follows. (In UNIX, if you enter CAL 9 1752 you get a hybrid calendar for September 1752; the first two days are Julian, and the rest of the month is Gregorian. The first week of that UNIX display looks like this: 1 2 14 15 16. A clever bit of programming, but be aware of what it means!) The civil calendar presently in use nearly world-wide is called the Gregorian calendar. It was introduced by Pope Gregory XIII and put into official use by the Roman Catholic church in 1582. It replaced the Julian calendar. Under the Julian calendar (the "Old Style" calendar named for Julius Caesar), every fourth year is a leap year. Under the Gregorian calendar, this rule is modified so that century years not evenly divisible by 400 are not leap years. Thus 1900 is not leap (because 19/4 is not an integer) but 2000 is. The Gregorian calendar repeats exactly every 400 years. That is its "major cycle." The Gregorian calendar is phased relative to the Julian calendar in such a way that the Julian calendar is nine days "slow" in the 15th century, 10 days slow in the 16th and 17th centuries, 11 days slow in the 18th century (Washington's birthday was 22-11=11th of February 1732, Old Style), 12 days slow in the 19th century, and 13 days slow in the 20th and 21st centuries. In the 22nd century the difference amounts to exactly two weeks. At the other end of the scale (extrapolating backwards), the Julian calendar is eight days slow in the 14th century, seven days slow in the 12th and 13th centuries, six days slow in the 11th century, five days slow in the 10th century, four days slow in the 8th and 9th centuries, three days slow in the 7th century, two days slow in the 6th century, one day slow in the 4th and 5th centuries, and right on time in the 3rd century. Whew! How accurate is the Gregorian calendar? Well, let's see. The length of the mean solar year to seven digits is 365.2422 days. The average length of a Gregorian year is exactly 365.2425 days. Thus the Gregorian calendar is in error by about one day in 3000 years. It follows that the Gregorian calendar will be reasonably accurate until about AD 3000. You may at some time encounter the terms "Julian Day" (JD) and "Julian date." One should not confuse these terms with the date under the Old Style (Julian) calendar. The JD is named for the father of astronomer Joseph Scaliger, and has nothing to do with Julius Caesar. The JD is used by astronomers to mark the date in terms of the number of days that have elapsed since a particular point in time about 6700 years ago. The textbooks say "since January 1, 4713 BC" but they don't say by what calendar! To tie the JD down to a definite, unambiguous date, JD 2447893 starts at noon, Greenwich mean time January 1, AD 1990 (Gregorian). COWARDS Page 7-5 The term "Julian date" is sometimes used to refer to the day of the year (the civil, or Gregorian year). It is used primarily in military circles. The origin of the name "Julian date" in this context is obscure; it may simply be left-over from the time the Julian calendar was used, and nobody saw a necessity to change this useage. What about "calendar reform"? All the calendar reform schemes I have seen would exchange the variations in the months for a syncopation in the week; for example inserting a day between Saturday and Sunday once a year, or twice in leap years. Thus the historical regularity of the weekly cycle that has been maintained for thousands of years would be destroyed! Would that be an improvement? Beware! COWARD.8 17 Aug 1991/WK33/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Chapter 8. CALENDAR.BSM Structure --------- Source file CALENDAR.BSM consists of two distinct parts: the front matter and the MAIN procedure. They appear in that order. The front matter starts with an extended comment that tells something about the program and how to assemble it into an executable file. After that there is the DATA SEGMENT instructing the assembler to reserve space in memory - following the program - for variables whose values will be determined during program execution. Following that is a CODE SEGMENT statement. This tells the assembler there are no more variables. The line after CODE SEGMENT tells the assembler where to start assembling: at offset 0100h for .COM programs. The instruction JMP MAIN appears next, telling the CPU to jump around the following material upon program execution. That material to be "jumped around" consists of messages which will be called up and placed on-screen at various points in the program, and macros which are only of interest to the assembler. When all macros have been defined, the label MAIN: appears, identifying this as the point referred to by the JMP MAIN instruction. The label MAIN: marks the start of the MAIN procedure; this is the main program code. When the MAIN procedure ends, so does CALENDAR.BSM. The MAIN procedure ------------------ The MAIN procedure is discussed first because without it, the macros and messages in the front matter don't make a lot of sense. The MAIN procedure is like a road map telling the program where you want it to go. Its listing follows. MAIN: ;--------------------------------------------------------------------------- ; PUT YOUR PROGRAM NEXT ;--------------------------------------------------------------------------- CALL GET_TAIL ;If command-line tail, put it in TAIL message. IF Z HELP ;If no tail, show help. Exit. CALL READ_MONTH ;If good month number, put it in MO variable. IF Z HELP ;If bad month number, show help. Exit. CALL READ_YEAR ;For use by DAY_OF_WEEK procedure. CALL DAY_OF_WEEK ;Put scaled day-of-week in DOW variable. WIPE ;Clear screen. LOCATE 1,1 ;Show banner. ECHO 1 ; LOCATE 3,24 ;Show command-line tail. PRINT TAIL ; LOCATE 5,0 ;Show Su Mo... heading ECHO 4 ; COWARDS Page 8-2 LOCATE 6,DOW ;Show number matrix ECHO 5 ; LOCATE 7,DOW ; ECHO 6 ; LOCATE 8,DOW ; ECHO 7 ; LOCATE 9,DOW ; ECHO 8 ; LOCATE 10,DOW ; ECHO 9 ; LOCATE 11,DOW ; ECHO 10 ; LOCATE 6,39 ;Erase unwanted numbers ECHO 12 ; LOCATE 7,39 ; Echo 12 ; LOCATE 8,39 ; Echo 12 ; LOCATE 9,39 ; Echo 12 ; LOCATE 10,39 ; Echo 12 ; LOCATE 7,0 ; Echo 12 ; LOCATE 8,0 ; Echo 12 ; LOCATE 9,0 ; Echo 12 ; LOCATE 10,0 ; Echo 12 ; LOCATE 11,0 ; Echo 12 ; DAYS_30 4,6,9,11 ;Erase 31 in 30-day months TEST_FEB: CMP MO,2 ;Trim February if MO is indeed Feb. JNZ EXIT ; CALL TRIM_FEB ; EXIT: ECHO 14 ;Return to DOS INT 20h ; ;--------------------- End of MAIN procedure ------------------------------- COWARDS Page 8-3 Description of MAIN ------------------- In the first instruction, CALL GET_TAIL, GET_TAIL is a procedure contained in the file CAL4.LIB which retrieves the command-line tail and puts it in the "constant" TAIL in "Message Land." HELP in the next line is the name of a macro designed to put help text on screen. Help text is presented if there is no command line tail, or if the tail does not fit the proper format. READ_MONTH, READ_YEAR, DAY_OF_WEEK are procedures that do those things. WIPE, LOCATE, ECHO, and PRINT are macros. DOW is the day o'week variable, scaled to position the month matrix properly. DAYS_30 is a macro, and TRIM_FEB is a procedure. These cause excess days in non-31-day months to be overwritten with spaces. This completes the program's duties. Finally, INT 20h returns the DOS prompt. Okay, the easy part is done! Now we need to design the procedures and macros. You can see that the MAIN procedure is something that you can make look like batch, or BASIC, or like whatever you want - within limits. It all depends on what macros you decide to use and on what procedures you need to do the job you set out for the program. Front matter ------------ The front matter is shown in the following program listing. This is all the material from the beginning of the CALENDAR.BSM file to the MAIN procedure. ;------------------- Front matter ------------------------------------------ ;Assembly language programming for batch file programmers. H.Tilton 6/90. ;Assembly Language Programming for Cowards. H.Tilton 16 Aug 1991/WK33/Fri, ;10 May 1992/WK20/Sun. Based on Eric Isaacson's A86 assembler. ;This is CALENDAR.BSM; a Gregorian calendar program. ;To assemble it, invoke this command: A86 CALENDAR.BSM CAL4.LIB . ;--------------------------------------------------------------------------- DATA SEGMENT;**********;List variables ********************************; ORG 2000 ;2000 is judged to be beyond the program area. ; DOW DB ? ;DOW=scaled day of week ; MO DB ? ;MO=month ; CODE SEGMENT;**********;End of data segment; start of code segment. ***; ORG 0100 ;Tells assembler to put program at offset 0100h. LF = 0Ah ;An equate to let you use LF for line feed. CR = 0Dh ;An equate to let you use CR for carriage return. ;--------------------------------------------------------------------------- JMP MAIN ;--------------------- Message Land ---------------------------------------- ECHO1:DB'Gregorian Calendar by Homer Tilton, Copyright 1990-91.',LF,CR,'$' ECHO2:DB'Example: Enter CALENDAR 07 1776 for July 1776 Gregorian calendar.' DB LF,CR,'$' ECHO3:DB'Month must have 2 digits, year must have 4 digits.$' ECHO4:DB' Su Mo Tu We Th Fr Sa$' ECHO5:DB' 1 2 3 4 5 6 7$' ECHO6:DB' 2 3 4 5 6 7 8 9 10 11 12 13 14$' COWARDS Page 8-4 ECHO7:DB' 9 10 11 12 13 14 15 16 17 18 19 20 21$' ECHO8:DB'16 17 18 19 20 21 22 23 24 25 26 27 28$' ECHO9:DB'23 24 25 26 27 28 29 30 31$' ECHO10:DB'30 31$' TAIL: DB 20 dup(0),'$' ;Provides 20 null bytes. ECHO12:DB 17 dup(" "),'$' ;Provides 17 spaces. ECHO14:DB LF,CR,'$' ;--------------------------------------------------------------------------- ECHO MACRO ;Echo 1,2,3,.. ; #RY1L ; Prints ECHOn MOV DX,ECHO#Y ; messages to screen. CALL SHOWIT ; #ER#EM ; ;--------------------------------------;--------------------- PRINT MACRO ;Print msg ; MOV DX,#1 ; Prints other CALL SHOWIT ; messages to screen. #EM ; ;--------------------------------------;--------------------- LOCATE MACRO ;Locate row,column ; MOV DH,#1 ; Positions MOV DL,#2 ; cursor. CALL CURSOR ; #EM ; ;--------------------------------------;--------------------- WIPE MACRO ; MOV DH,24 ; Clears CALL CLEARS ; screen. #EM ; ;--------------------------------------;--------------------- HELP MACRO ; ECHO 1,2,3 ; Prints 3 help JMP EXIT ; messages to screen. #EM ; ;--------------------------------------;--------------------- DAYS_30 MACRO ; #RY1L ; Erases "31" from CMP MO,#Y ; 30-day months; JZ M2 ; also tests for #ER ; February. JMP SHORT TEST_FEB ; M2: CALL ERASE_31 ; #EM ; ;--------------------------------------;--------------------- ;--------------------- End of Front Matter --------------------------------- COWARDS Page 8-5 Description of front matter --------------------------- Look at the HELP macro first, because that's the first macro called by MAIN. You see it is designed to put the three messages ECHO1, ECHO2, AND ECHO3 on screen by invoking the ECHO macro. Now look at Message Land. Those three messages are: Gregorian Calendar by Homer Tilton, Copyright 1990-91. Example: Enter CALENDAR 07 1776 for July 1776 Gregorian calendar. Month must have 2 digits, year must have 4 digits. The next macro is WIPE. WIPE calls a procedure CLEARS designed to clear a portion of the screen. With 24 (decimal) in register DH, CLEARS clears the entire screen through line 24. The LOCATE macro calls a procedure CURSOR to position the cursor at the row and column specified by the contents of registers DH and DL. Notice that for the A86 language, we can use decimal numbers. A86 is told that these are decimal numbers by the way we write them, and A86 makes the necessary conversion to hexadecimal for inclusion in the assembled file. The ECHO macro moves message address into the DX register and calls the SHOWIT procedure to put that message on-screen at the current cursor position. PRINT also calls SHOWIT to display a message. The last macro, DAYS_30, checks to see if the month is a 30-day month and - if it is - calls the ERASE_31 procedure. February is truncated by using only a procedure with no associated macro. Messages -------- The help message has already been described. The remaining messages in Message Land produce the standard 13-day wide month matrix with its Su Mo... heading, display the command line tail (which shows the month and year selected for display), and provide "canned" blank spaces with which to overwrite unwanted numbers of the month matrix. What's next? ------------ In the next chapter the procedures that form CAL4.LIB are presented. COWARD.9 17 Aug 1991/WK33/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991 Chapter 9. CAL4.LIB Introduction ------------ Why is the calendar library file called CAL4.LIB? Why not call it CAL.LIB? Well, it's my fourth (fifth?) attempt to get what I wanted; and, rather than hide that fact from you, I decided not to, so that a better idea of the process involved in developing procedures might thereby be conveyed. Specifically, what happens is that you will save to disk intermediate forms of procedures during their development; so that if you goof at any point in the development, you can go back to a previous version and begin again. CAL4.LIB contains ten procedures. Although it wasn't particularly easy developing these ten procedures, now that we've got 'em, we've got 'em! What I mean is, they are useful procedures which can now be used in other, future programs. The procedures -------------- CAL4.LIB contains all procedures required to make CALENDAR.COM except the MAIN procedure. You'll recall that the MAIN procedure appears in the CALENDAR.BSM source file. The procedures in CAL4.LIB are listed here in the order in which they are used by MAIN: GET_TAIL Fetches command-line tail. READ_MONTH Fetches month number. READ_YEAR Fetches year number. DAY_OF_WEEK Calculates day o'week. JAN_FEB Modifies month and year numbers for Jan, Feb as required by the Gregorian Algorithm. CLEARS Clears a portion or all of the screen. CURSOR Positions cursor. SHOWIT Displays to the screen. ERASE_31 Erases day 31 for non-31-day months. TRIM_FEB Trims Feb to 28 or 29 days, as required. They could appear in any order; A86 sorts them out at the time of assembly. All of these are called by MAIN except JAN_FEB; it is called by procedure DAY_OF_WEEK. GET_TAIL procedure ------------------ The GET_TAIL procedure fetches the command-line tail. The command-line tail resides in two places in memory when a .COM program is run. Refer to the DEBUG dump display shown below. This was produced on- screen with the command DEBUG CALENDAR.COM 12 1992. (It was copied to the end of this file by the command TYPE CON |DEBUG CALENDAR.COM 12 1992 >>COWARD.9 COWARDS Page 9-2 giving a DOS screen; then this "screen file" was typed: d0 d q ^Z where ^Z means the F6 key was tapped followed by an Enter.) The month, 12, appears at bytes 5Dh and 5Eh, also at bytes 82h and 83h. The year, 1992, appears at bytes 6Dh thru 70h, and also at bytes 95h thru 98h. Further, the byte at 80h gives the total number of bytes in the tail - here 8; that includes the six digits of the month and year and the space before the month and the space before the year. Another point: If you put extra spaces in the command line, they'll show up in memory starting at byte 80h, but they won't show up in memory starting at byte 5Dh. -d0 36E0:0000 CD 20 00 80 00 9A F0 FE-1D F0 F4 02 20 33 2F 03 . .......... 3/. 36E0:0010 20 33 BC 02 20 33 E8 32-03 04 01 00 02 FF FF FF 3.. 3.2........ 36E0:0020 FF FF FF FF FF FF FF FF-FF FF FF FF 7D 14 4C 01 ............}.L. 36E0:0030 5A 36 14 00 18 00 E0 36-FF FF FF FF 00 00 00 00 Z6.....6........ 36E0:0040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 36E0:0050 CD 21 CB 00 00 00 00 00-00 00 00 00 00 31 32 20 .!...........12 36E0:0060 20 20 20 20 20 20 20 20-00 00 00 00 00 31 39 39 .....199 36E0:0070 32 20 20 20 20 20 20 20-00 00 00 00 00 00 00 00 2 ........ -d 36E0:0080 08 20 31 32 20 31 39 39-32 0D 20 31 32 20 31 39 . 12 1992. 12 19 36E0:0090 39 32 0D 20 4D 61 79 20-31 39 39 32 2F 57 4B 32 92. May 1992/WK2 36E0:00A0 30 2F 53 75 6E 20 0D 00-00 00 00 00 00 00 00 00 0/Sun .......... 36E0:00B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 36E0:00C0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 36E0:00D0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 36E0:00E0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ 36E0:00F0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ -q The program will use the byte at 80h to see if there really is a command-line tail. If there isn't, the program aborts; if there is, it is loaded into the variable TAIL for display. The listing for GET_TAIL follows. COWARDS Page 9-3 ;----------------------------------------------------------------------------- GET_TAIL PROC NEAR ;Procedure to "get" command-line tail; i.e., to load ;it into the variable called "TAIL." MOV SI,80h ;Sense number of characters (#char) in tail. MOV CL,[SI] ;Move that number into CL. CMP CL,0 ;Compare #char with 0. JZ GT_EX ;If #char=0 (no tail) goto next RET and subsequently ;exit program. Otherwise continue. DEC CL ;This command reduces the count in CL by 1 - ;something we want to do because of the leading ;space on the tail. (But what happens, Dr. Ruth, ;if there is more than one space there? - No sweat ;dearie; they just become part of the TAIL. Since ;the TAIL variable is only used to display the ;month and year in the final display, no harm done!) SUB CH,CH ;Insure that CH=00. Remember, CL holds the count; ;but "REP MOVSB" below steps CX times, so CH must be ;zero or we're in trouble! This command also sets the ;zero flag (ZR). (Something to be aware of!) INC SI,2 ;SI=80h originally, but now SI=82h - the offset of ;the tail. MOV DI,TAIL ;Put offset of TAIL (memory space reserved for tail) ;in DI. CLD ;Insure direction flag is clear (UP). REP MOVSB ;Repeat MOVSB CX times. This moves the command-line ;tail string of bytes starting at the offset in SI ;into memory starting at the offset in DI. (Also ;increments DI to end of tail.) MOV AL,0 ;The next command requires that AL=0. STOSB ;Store AL to byte ES:[DI], advance DI so that DI now ;holds the offset immediately following the tail. GT_EX: RET ;Return ;---------------------------------------------------------------------------- READ_MONTH procedure -------------------- The READ_MONTH procedure fetches the month number from the command-line tail. The READ_MONTH procedure contains internal procedures or subroutines; notice the labels N2, N3, N4 and N5. The listing follows. COWARDS Page 9-4 ;---------------------------------------------------------------------------- READ_MONTH PROC NEAR ;Takes two digits at offsets 5Dh and 5Eh, tests them ;for acceptable limits. ;If they fail test, then zero flag is set to ZR. ;Capture ASCII codes of the two month digits. mov si,5Dh ;Put ASCII code of tens digit in BH. mov bh,[si] ; mov si,5Eh ;Put ASCII code of units digit in BL. mov bl,[si] ; ;Test ASCII of tens digit. Must be 30h or 31h (digit 0 or 1). cmp bh,30h ;Is its ASCII below 30h? jb get_out ;If so, get out by setting ZR before RET. cmp bh,31h ;Is its ASCII above 31h? ja get_out ;If so, get out. cmp bh,30h ;Goto N3 if 31h. ja N3 ;Continue if 30h. ;Test units digit for tens digit of 0. ASCII must be 31h thru 39h. N2: cmp bl,31h ;Is its ASCII below 31h? jb get_out ;If so get out. cmp bl,39h ;Is its ASCII above 39h? ja get_out ;If so get out. ;Convert ASCII of units digit to the digit itself. sub bl,30h ;This sets zero flag. Need to clear it ;before getting to RET jmp N4 ;Test units digit for tens digit of 1. ASCII must be 30h thru 32h. N3: cmp bl,30h ;Is ASCII below 30h? jb get_out ;If so, get out. cmp bl,32h ;Is ASCII above 32h? ja get_out ;If so, get out. add bl,11h ;BL contains ASCII of month in hex; A thru E ;(October thru FebFollowing). sub bl,55 ;BL contains month in hex; A thru E N4: mov bh,0 ;BX contains ASCII of month in hex; 3 thru 9 ;(March thru September). ;Flag NZ. Valid month number mov MO,BL jmp N5 GET_OUT: sub bh,bh ;Flag ZR. Invalid month number N5: ret ;If month is bad, ZR; if good, NZ. ;Returns to MAIN procedure. ;---------------------------------------------------------------------------- COWARDS Page 9-5 READ_YEAR procedure ------------------- The READ_YEAR procedure fetches the year number you entered at the command line as part of the command-line tail. As you can see from the listing below, the program is not yet smart enough to ensure that valid digits were entered. ;---------------------------------------------------------------------------- READ_YEAR PROC NEAR ;Takes four digits at offsets 6Dh,6Eh,6Fh,70h and ;converts that decimal number to hex. PUSH BX ;Save month number in BX. ;Test for valid digits. Test each of 4 digits to see if between 0 and 9. ;TO BE WRITTEN; ;Capture ASCII code of the thousands digit and put the hex value of that ;many thousands in CX. MOV SI,6Dh ;Put thousands digit in AX. MOV AX,[SI] ; AND AX,0Fh ; MOV BX,1000 ;Multiply it by 1000. MUL BX ; MOV CX,AX ;Move it to CX. ;Capture ASCII code of the hundreds digit and add the hex value of that ;many hundreds to the value in CX. MOV SI,6Eh ;Put hundreds digit in AX MOV AX,[SI] ; AND AX,0Fh ; MOV BX,100 ;Multiply it by 100. MUL BX ; ADD CX,AX ;Add it to the value in CX. ;Capture ASCII code of the tens digit and add the hex value of that ;many tens to the value in CX. MOV SI,6Fh ;Put tens digit in AX. MOV AX,[SI] ; AND AX,0Fh ; MOV BX,10 ;Multiply it by 10. MUL BX ; ADD CX,AX ;Add it to the value in CX. ;Capture ASCII code of the units digit and add its hex value to the value ;in CX. MOV SI,70h ;Put units digit in AX MOV AX,[SI] ; AND AX,0Fh ; ADD CX,AX ;Add it to the value in CX. ;Hex of year is now in CX. ;Put month number back in BX and exit. POP BX RET ;----------------------------------------------------------------------------- COWARDS Page 9-6 DAY_OF_WEEK and JAN_FEB procedures ---------------------------------- The DAY_OF_WEEK procedure calculates the day of the week for day 0 of the given month. It is used to determine how much the month matrix must be set in from the left-hand margin. The JAN_FEB procedure modifies the month and year numbers for January and February as required by the Gregorian Algorithm. The procedures are listed next. ;---------------------------------------------------------------------------- DAY_OF_WEEK PROC NEAR ;Calculate variable DOW. ;Formula to be calculated is ;DOW=(Y+Y\4-Y\100+Y\400+(26*M+16)\10)MOD 7. ;Month no. (M) in BX, Year no. (Y) in CX. PUSH AX,DX ;Save the values in those registers. Multiple ;operands are okay for PUSH and POP with A86 only! CMP BX,3 ;If M=1 or 2, must add 12 to month, sub 1 from year. IF B CALL JAN_FEB ; MOV AX,26 ;AX=26. MUL BX ;Multiply M*26. Answer in AX. ; MOV BX,16 ;BX=16. ADD AX,BX ;Add 16 to M*26. Answer in AX. ; MOV BX,10 ;BX=10. SUB DX,DX ;DX=0. DIV BX ;Divide M*26+16 by 10. Answer in AX (integer part). MOV SI,AX ;Put answer in SI. INC SI ;SI=SI+1. ; ADD SI,CX ;Add Y to SI. Answer in SI. ; MOV AX,CX ;AX=Y. MOV BX,4 ;BX=4. SUB DX,DX ;DX=0. DIV BX ;Divide Y by 4. Answer in AX (integer part). ADD SI,AX ;Add Y\4 to SI. Answer in SI. ; MOV AX,CX ;AX=Y. MOV BX,100 ;BX=100. SUB DX,DX ;DX=0. DIV BX ;Divide Y by 100. Answer in AX (integer part). SUB SI,AX ;Subtract Y\100 from SI. Answer in SI. ; COWARDS Page 9-7 MOV AX,CX ;AX=Y. MOV BX,400 ;BX=400. SUB DX,DX ;DX=0. DIV BX ;Divide Y by 400. Answer in AX (integer part). ADD SI,AX ;Add Y\400 to SI. Answer in SI. ; MOV AX,SI ;AX=SI. MOV BX,7 ;BX=7. SUB DX,DX ;DX=0. DIV BX ;Divide AX by 7. Answer in DX (remainder part). ;DX contains DOW (day-of-week; 0=Sun,etc.). ;Now must scale DOW=DOW*3. ;----------------------; ;The following scaling moves the number matrix. The formula is DOW=DOW*3. MOV AX,3 ;AX=3. MOV BX,DX ;Put DOW in BX. MUL BX ;Multiply DOW*3. Answer in AX (actually AL). MOV DOW,AL ;Done. Variable DOW now contains scaled day-of-week. POP DX,AX ;Restore the original values in those registers. RET ;Return to MAIN. ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- JAN_FEB PROC NEAR ;BX=M, CX=Y ;This procedure adds 12 to M and subtracts ;1 from Y so that the DAY_OF_WEEK procedure ;will see Jan and Feb as month numbers 13 ;and 14 of the previous year as is its ;requirement. ADD BX,12 ;Modifies month number. SUB CX,1 ;Modifies year number. RET ;Return. ;---------------------------------------------------------------------------- CLEARS, CURSOR, SHOWIT procedures --------------------------------- The CLEARS procedure clears a portion or all of the screen using INT 10h function 6. The CURSOR procedure positions the cursor using INT 10h function 2. The SHOWIT procedure displays to the screen using INT 21h function 9. The listings follow. COWARDS Page 9-8 ;---------------------------------------------------------------------------- CLEARS PROC NEAR ;Entry DH has row of bottom of screen. PUSH AX,BX,CX ;Save register values. SUB CX,CX ;Top row and column both 0. MOV DL,79 ;Column of bottom of window. MOV AX,0600h ;AL=0=blank window. MOV BH,07h ;Attribute, gray on black. INT 10h ;BIOS video display interrupt. POP CX,BX,AX ;Restore original register values. RET ;Return to MAIN procedure. ;---------------------------------------------------------------------------- CURSOR PROC NEAR ;Entry at DH=row, DL=col. PUSH AX,BX ;Save those numbers. SUB BH,BH ;Screen display page is 0. MOV AX, 0200h ;Set cursor. INT 10h ;BIOS video display interrupt. POP BX,AX ;Restore those numbers. RET ;Back to MAIN. ;---------------------------------------------------------------------------- SHOWIT PROC NEAR ;Procedure to take message in DX, display it. Entry ;point for this procedure is at DS:DX (segment:offset ;address) for $-terminated string. PUSH AX ;Save AX. MOV AH,9h ;Prepare to print string with offset in DX. INT 21h ;Execute. POP AX ;Restore AX. RET ;Return. ;---------------------------------------------------------------------------- ERASE_31 and TRIM_FEB procedures -------------------------------- The ERASE_31 procedure erases day 31 for non-31-day months. The TRIM_FEB procedure trims February to the proper number of days, either 29 or 28. Listings follow. ;---------------------------------------------------------------------------- ERASE_31 PROC NEAR ADD DOW,3 ;Positions cursor. LOCATE 11,DOW ; ECHO 12 ;Erases 31 in one place. ADD DOW,21 ;Repositions cursor. LOCATE 10,DOW ; ECHO 12 ;Erases 31 in the other place. RET ;Returns to MAIN. ;---------------------------------------------------------------------------- COWARDS Page 9-9 ;---------------------------------------------------------------------------- TRIM_FEB PROC NEAR ;Month is in MO, Year-1=U is in CX. ;This procedure is to be called only if MO=2. ;First, erase days 30 and 31 of calendar matrix in the next 5 steps. LOCATE 11,15 ECHO 12 ADD DOW,21 LOCATE 10,DOW ECHO 12 ;Next, check to see if year is leap. ;Formula is L=U\100+Y\4+Y\400-Y\100-U\4-U\400. L=1 means leap year,29 days. ;Answer (L) will be stored in SI. First, zero out SI. SUB SI,SI ;Next, define a couple of handy macros: ;------------------------------------ DIVADD MACRO ;Divadd divisor SUB DX,DX ;Zero out DX. MOV AX,CX ;Move U into AX. MOV BX,#1 ;Move value of #1 param.into BX. DIV BX ;Divide DX:AX/BX; Answer in AX ;(integer part), DX (remainder). ADD SI,AX ;Add answer to SI #EM ;------------------------------------ DIVSUB MACRO ;divsub divisor SUB DX,DX ;Zero out DX. MOV AX,CX ;Move U into AX. MOV BX,#1 ;Move value of #1 param.into BX. DIV BX ;Divide. SUB SI,AX ;Subtract answer from SI #EM ;------------------------------------ ;Do all the additions first, then the subtractions. U is presently in CX. DIVADD 100 INC CX ;Y is now in CX DIVADD 4 DIVADD 400 DIVSUB 100 DEC CX ;U is now in CX DIVSUB 400 DIVSUB 4 ;L=1 or L=0 is now in SI. If L=1, jump to RET CMP SI,1 ;If leap year (ZR), jump to RET. IF Z RET ; SUB DOW,3 ;If not leap year (NZ), erase day 29. LOCATE 10,DOW ; ECHO 12 ; RET ;Return to MAIN. ;----------------------------------------------------------------------- COWARDS Page 9-10 Evaluation of CALENDAR.COM -------------------------- CALENDAR.COM was my first attempt at an assembly- language calendar. It has some shortcomings; for example, it won't accept month names or even single-digit month numbers. It does have one advantage over a popular calendar program that came with my computer; that one won't give a calendar for any year before 1900 or after 2100! By contrast, CALENDAR.COM gives the correct (Gregorian) month for any year for which the Gregorian calendar applies - and then some! It actually extrapolates the Gregorian scheme to the range of years from 0000 to 9999. In the next chapter we'll look at another way of generating a month matrix. COWARD.10 14 May 1992/WK20/Thu Assembly Language Programming for Cowards Copyright Homer B. Tilton 1992 Part IV. On to bigger and better things! Chapter 10. Developing a streamlined calendar program Introduction ------------ CALENDAR.COM shows one way to generate a calendar. In this chapter we'll investigate perhaps a more elegant way. Why not print the matrix directly, 1, 2, 3,... just the way you would if you were writing it out on a sheet of paper? Turns out that is not too easy; but we'll look at a way of doing it none the less. Consider this command (you can use it at the DOS prompt if GWBASIC is on path). It will display a month matrix just like 1, 2, 3,...: Echo M=7:Y=1776:N=31:For j=1 to N:D=(Y+Y\4-Y\100+Y\400+2.6*M+1.2)MOD 7 +j:R=D\7+6:C=3*(D MOD 7)+3:Locate R,C:?j:Next|GWBASIC For Jan, Feb use M=13, 14 of previous year. It will print a matrix that looks like this: Ok M=7:Y=1776:N=31:For j=1 to N:D=(Y+Y\4-Y\100+Y\400+2.6*M+1.2)MOD 7+j:R=D\7+6:C= 3*(D MOD 7)+3:Locate R,C:?j:Next 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 Ok and the DOS prompt will return. That is a calendar for July 1776. How to implement that in assembly language? We'll develop a procedure for drawing the month matrix in this chapter. We'll make use of BIOS INT 10h service 2 to position the cursor at each day's position inside the matrix; we'll then use DOS INT 21h service 40h to "print" (display) the number before moving on to the next matrix location. It seems natural to use a procedure which loops back upon itself repeatedly - printing one number on each pass - until all numbers in the calendar matrix have been printed in their proper positions. COWARDS Page 10-2 INT 21h service 40h ------------------- First, let's investigate INT 21h service 40h. For that we'll use a "software breadboard" called BB1.BSM. Here it is: ;This is BB1.BSM. A software breadboard to test INT 21h function 40h. ;Reference page 1308 in the MS-DOS Encyclopedia. ;AH =40h ;BX =handle (=1 for the screen) ;CX =number of bytes to write ;DS:DX =segment:offset of data buffer DATA SEGMENT ;INT 21h function 40h requires data. ORG 02000h ;Data segment starts at offset 2000h. DOM DB ? ;DB "defines byte" in memory for data. CODE SEGMENT ;This statement also ends the data segment, ;but only with the A86 assembler! mov dom,058h ;058h is ASCII code of X character to be printed. ;Now, as required by INT 21h function 40h... mov ah,040h mov bx,1 mov cx,1 mov dx,02000h INT 021h ;Do it! INT 020h ;Exit to DOS When that is assembled and then disassembled using DEBUG's U command, you should get this (the command to use is ucs:100 L14 as shown): -ucs:100 L14 36E0:0100 C606002058 MOV BYTE PTR [2000],58 36E0:0105 B440 MOV AH,40 36E0:0107 BB0100 MOV BX,0001 36E0:010A B90100 MOV CX,0001 36E0:010D BA0020 MOV DX,2000 36E0:0110 CD21 INT 21 36E0:0112 CD20 INT 20 -q Two questions may come to mind: "What is a 'file handle'?" and "What is a BYTE PTR?" Before we get to those, try running BB1.COM. It prints the letter "X" that's all; but this demonstrates the use of this DOS interrupt to display one character. We'll soon see how to use that same interrupt/service to display more than one character. COWARDS Page 10-3 File "handles" and byte pointers -------------------------------- The thing called a file handle that you need for INT 21h service 40h is explained in this small table: Device Handle ~~~~~~~~~~~~~~~~~~~ ~~~~~~ CON (keyboard) 0 CON (screen & spkr) 1 CON (std error dev) 2 AUX 3 PRN 4 You can also assign higher numbers to files that you open. As you'll see, we used handle 1 in BB1 because we want to print to the screen. Now BYTE PTR. The sequence DATA SEGMENT ORG 02000H DOM DB ? CODE SEGMENT MOV DOM,058H was converted by A86 to the single command MOV BYTE PTR [2000],58 Isn't that interesting!? That means the next time we want to perform that operation, we don't need to put all that stuff in the file! You need to remember that the native number base of A86 is decimal, but of DEBUG is hexadecimal. Thus in our new calendar program which we'll call CAL6, we'll simply use the command MOV BYTE PTR [0....h],... when we want to put a character in memory. Thus we do not need to explicitly declare a data segment. This shows the value of using a software breadboard to evaluate portions of a proposed program. The software breadboard ----------------------- The CAL6 software breadboard is presented next. This procedure did not sprout full-grown from my cerebellum, but evolved. Most of that evolution took place by way of multiple interactions among my fingers, the source file CAL6.BSM, the A86 assembler, and DEBUG. A considerable amount of mental discipline is required to develop even a simple procedure like CAL6; discipline is not my big point, but persistence (moderated by an occasional break) helps to compensate. The discipline comes in because you must know at each step precisely what command you must use to meet your overall plan for the breadboard. Make copious comments in your source files - even when you're programming for no-one's enlightenment but your own. Otherwise you'll come back six months or more later and not know what you did; I guarantee it! COWARDS Page 10-4 Software breadboards must be able to run on their own as fully- qualified .COM files. Thus it's important that you use INT 020h or a similar exit-to-DOS command at the very end. Otherwise when you try to run it, your computer will surely hang up and you'll have to do a cold reboot. In any event, resist the temptation to run a .COM breadboard until you have traced through its steps as described next. When debugging, you'll find the T (trace) and G (go) commands of DEBUG very useful. After loading your .COM file into DEBUG, call up a register display with the R command (see Appendix D). Then, T will single step you through the program so you can see if the registers and flags are changing at each command the way you intended. When you come to an interrupt command such as INT 21, do not use the T command as that will take you out of your program; instead use the G nnnn command where nnnn is the offset number of the first step after the interrupt. If you want to start over from the top, reset the instruction pointer (IP register) with the commands RIP then 100. CAL6.BSM -------- The source code for CAL6 is presented next. Read through the comments contained in it for explanations of the various functions it performs. Hexadecimal numbers used within program steps are written with an initial 0 and a trailing H or h. In the comments, hexadecimal numbers have a trailing h (a "hexadecimal point") and decimal numbers have a trailing dot (a decimal point). (Since hexadecimal and decimal numbers 0-9 are the same, you needn't use a point of either variety with them.) ;-------------------------------------------------------------------------- ;CAL6.BSM - A software breadboard by H.B.Tilton - 13 May 1992/WK20/Wed ;Being a routine to display calendar matrix of 31 days for DOW=0. ;DOW=0 means the matrix starts with the day before the 1st on day o'week 0 ;(Sunday). That makes Monday be the 1st. ;-------------------------------------------------------------------------- ;Algorithm to be calculated: FOR J=1 to 31 ; (BASIC notation) Row = (DOW + J)\7 + 6 ; Col = 3*((DOW+J)MOD 7 + 1) ; LOCATE Row,Col ; PRINT J ; NEXT ;-------------------------------------------------------------------------- ;-------------------------------------------------------------------------- DOW = 0 ;In this software breadboard, we let DOW = 0 (Sunday) NUM = 31 ;and NUM (number of days in month) be 31. ;-------------------------------------------------------------------------- ;-------------------------------------------------------------------------- xor cl,cl ;Zero out CL (count register). XOR CL,CL acts like ;SUB CL,CL but is 50% faster! ;---------------------------------------------------------- START: ;-------"START" is a label we'll need to call ; near the end of this routine. -------------------- inc cl ;Increment CL register to start count; CL will equal J ;as it is incremented with each loop around. COWARDS Page 10-5 ;Calculate Row variable... ;Prepare to do the division (DOW+J)\7, with J=CL ;Must have DOW+J in DX:AX and 7 in BX. ; ^Dividend ^Divisor ;Answer will be in AX (whole number part) ;and in DX (remainder). BX will be unchanged. ;DOW+J will never be larger than 31.+7=1Fh+7=38.=26h thus ;it will always fit in AX, and DX can be set to zero and ;left there. (When DIV command is executed, it may change.) ;---------------------------------------------------------- xor dx,dx ;Zero out word register DX. xor ax,ax ;Zero out word register AX. xor bx,bx ;Zero out word register BX mov al,DOW ;Move DOW (0 here) into byte register AL. ;Word register AX now contains DOW (0000 here). add al,cl ;Add J to DOW and put in AL. ;AX now contains the dividend DX:AX = 0000:001F. mov bx,7 ;BX now contains the divisor BX = 0007. DIV BX ;Do the division. ;AX now contains the whole number part, ;and DX contains the remainder of the division. ADD AX,6 ;AX now contains the Row number. push ax ;Push AX onto the stack to save it. The stack now looks ;like this: ; STACK ÚÄÄÄÄÄÄ¿ ; ³ Row ³ ; ÃÄÄÄÄÄÄ´ ; ³ ³ <-- Top of stack. ; (See Appendix A.) ;Always know what the stack is doing! ;---------------------------------------------------------- ;Calculate Col variable... ;DX still contains the remainder. This is the ;(DOW+J)MOD 7 part of the Col variable we need. ;Add 1 to give (DOW+J)MOD 7 + 1. add dx,1 ;It's now time to think about multiplication. The command ;is MUL BX where the two numbers to be multiplied must ;first be in DX:AX and BX. The answer will appear in DX:AX. mov ax,dx ;Put DX in AX. xor dx,dx ;DX set to zero. mov bx,3 ;Put 3 in BX. MUL BX ;Answer in AX. (Actually in AL.) ;AL now contains the Col variable. We could push this onto ;the stack for safe keeping, but it turns out that that ;isn't necessary as you'll see - reading on. mov dl,al ;Put AL in DL in preparation for the following... ;-------------------------------------------------------------------------- COWARDS Page 10-6 ;We're now ready to move cursor to Row,Col position and display J. ;J is the day of the month currently being displayed. ;---------------------------------------------------------- ;Locate Row,Col. We'll use INT 10h service 2. ;Row must be in DH, Col in DL. ;Col is already in DL. POP BX ;Row is now in BL, stack is empty. ;CANNOT POP DIRECTLY TO BYTE REGISTER. mov dh,bl ;Row is now in dh xor bh,bh ;Screen display is page 0 for INT 10 service 2. ;2 must be in AH. That's done in next step. mov ah,02h INT 010h ;Cursor now at Row,Col. ;-------Now to put numbers to be displayed at offsets 02000h and 02001h--- ;This is done in one four ways, depending on ten's digit. ;First way; no ten's digit: cmp cl,0ah ;If CL is 0Ah (10.) or greater, jnb N1 ;goto N1. add cl,030h ;CL is 0 to 9; add 30h to get ASCII code. MOV BYTE PTR [02000H]," " ;Put space in byte at offset 2000h. MOV [02001H],CL ;Put ASCII for 0-9 at offset 2001h. sub cl,030h ;Restore CL. jmp continue ;Bypass routines N1, N2, N3. N1: ;Second way; ten's digit is 1. cmp cl,014h ;If CL is 14h (20.) or greater, jnb N2 ;goto N2. add cl,026h ;CL is 10. to 19.; add 26h to get ASCII code. MOV BYTE PTR [02000H],"1" ;Put "1" in byte at offset 2000h. MOV BYTE PTR [02001H],CL ;Put ASCII for 0-9 at offset 2001h. sub cl,026h ;Restore CL. jmp continue ;Bypass routines N2, N3. N2: ;Third way; ten's digit is 2. cmp cl,01Eh ;If CL is 1Eh (30.) or greater, jnb N3 ;goto N3. add cl,01Ch ;CL is 20. to 29.; add 1Ch to get ASCII code. MOV BYTE PTR [02000H],"2" ;Put "2" in byte at offset 2000h. MOV [02001H],CL ;Put ASCII for 0-9 at offset 2001h. sub cl,01Ch ;Restore CL. jmp continue ;Bypass routine N3. COWARDS Page 10-7 N3: ;Fourth way; ten's digit is 3. add cl,012h ;CL is 30. or 31.; add 12h to get ASCII code. MOV BYTE PTR [02000H],"3" ;Put "3" in byte at offset 2000h. MOV [02001H],CL ;Put ascii for 0 or 1 at offset 2001h. sub cl,012h ;Restore CL. CONTINUE: ;Must now display J. Use INT 21h service 40h. ;AH =40h ;BX =handle (=1 for the screen) ;CX =number of bytes to write ;DS:DX =segment:offset of data buffer push cx ;Save J. mov ah,040h ;As required for service 40h. mov bx,1 ;Handle #1 says, "Write to screen." mov cx,2 ;Two bytes to write. mov dx,02000h ;DX needs address of data buffer. INT 021h ;Do it. ;Number now on screen. Go around again to START label if ;not at end (day 31). pop cx ;Retrieve J. cmp cl,NUM ;Compare CL with NUM (=31 here, or 1Fh). IF B JMP START ;If CL is below NUM, jump to START label. This command ;is unique to the A86 assembler. INT 020h ;When program reaches this point, exit. When compiled (use the command A86 CAL6.BSM to do that) and run, it will print the calendar matrix for July 1776. It uses the same sort of process for printing the calendar matrix as the ECHO...|GWBASIC command presented earlier. CAL6.COM disassembles like this: -ucs:100 L9 36E0:0100 32C9 XOR CL,CL 36E0:0102 FEC1 INC CL 36E0:0104 33D2 XOR DX,DX 36E0:0106 33C0 XOR AX,AX 36E0:0108 33DB XOR BX,BX 36E0:010A B000 MOV AL,00 36E0:010C 02C1 ADD AL,CL 36E0:010E BB0700 MOV BX,0007 36E0:0111 F7F3 DIV BX 36E0:0113 050600 ADD AX,0006 36E0:0116 50 PUSH AX 36E0:0117 83C201 ADD DX,+01 COWARDS Page 10-8 36E0:011A 8BC2 MOV AX,DX 36E0:011C 33D2 XOR DX,DX 36E0:011E BB0300 MOV BX,0003 36E0:0121 F7E3 MUL BX 36E0:0123 8AD0 MOV DL,AL 36E0:0125 5B POP BX 36E0:0126 88DE MOV DH,BL 36E0:0128 32FF XOR BH,BH 36E0:012A B402 MOV AH,02 36E0:012C CD10 INT 10 36E0:012E 80F90A CMP CL,0A 36E0:0131 7312 JNB 0145 36E0:0133 80C130 ADD CL,30 36E0:0136 C606002020 MOV BYTE PTR [2000],20 36E0:013B 880E0120 MOV [2001],CL 36E0:013F 80E930 SUB CL,30 36E0:0142 E93D00 JMP 0182 36E0:0145 80F914 CMP CL,14 36E0:0148 7312 JNB 015C 36E0:014A 80C126 ADD CL,26 36E0:014D C606002031 MOV BYTE PTR [2000],31 36E0:0152 880E0120 MOV [2001],CL 36E0:0156 80E926 SUB CL,26 36E0:0159 E92600 JMP 0182 36E0:015C 80F91E CMP CL,1E 36E0:015F 7312 JNB 0173 36E0:0161 80C11C ADD CL,1C 36E0:0164 C606002032 MOV BYTE PTR [2000],32 36E0:0169 880E0120 MOV [2001],CL 36E0:016D 80E91C SUB CL,1C 36E0:0170 E90F00 JMP 0182 36E0:0173 80C112 ADD CL,12 36E0:0176 C606002033 MOV BYTE PTR [2000],33 36E0:017B 880E0120 MOV [2001],CL 36E0:017F 80E912 SUB CL,12 36E0:0182 51 PUSH CX 36E0:0183 B440 MOV AH,40 36E0:0185 BB0100 MOV BX,0001 36E0:0188 B90200 MOV CX,0002 36E0:018B BA0020 MOV DX,2000 36E0:018E CD21 INT 21 36E0:0190 59 POP CX 36E0:0191 80F91F CMP CL,1F 36E0:0194 7303 JNB 0199 36E0:0196 E969FF JMP 0102 36E0:0199 CD20 INT 20 -q COWARDS Page 10-9 Since INT 20 (the last command in the file) occupies two bytes, the total byte count in the file is 2 + (199h - 100h) = 2 + 99h = 9Bh. Notice that the MOV BYTE PTR [02001H],CL command in label N1 had its "BYTE PTR" part removed (it was "A86'd"!) upon assembly. That's because the "CL" told the A86 assembler that a byte is involved, as opposed to a word (two bytes). Thus we needn't bother to put that part in the command in the first place! Note also in the step just above that one that "1" was specified as the character to be printed; the assembler was smart enough to convert that to the hexadecimal ASCII code for "1" so we didn't have to! Study the source code and the disassembled file to see the relationship of the two forms of each command. When you understand all those relationships, you will have learned much useful material. Refer to Appendix E and the other appendices for help. Read about the stack towards the end of Appendix A. COWARD.11 10 May 1992/WK20/Sun Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991-92 Chapter 11. Other tools Data structures (STRUC) ----------------------- Other tools than those used so far are available in assembler. One of those is the data structure. A data structure (STRUC) causes the assembler to enter into a data mode when its unique name is encountered. A STRUC is similar to a macro in that it consists of several instructions. It is said that a STRUC defines a data "template." An example of a STRUC named "LINE" given by Eric Isaacson is LINE STRUC [BP] ;This template starts at whatever address ;is in the base pointer register, BP. DB 80 DUP(?) ;This advances the program 80 decimal bytes ;to [BP+80]. LSIZE DB ? ;This advances another 1 byte to [BP+81] LPROT DB ? ;This advances one more byte to [BP+82] ENDS ;This line marks the end of this STRUC. The command DB 80 DUP(?) puts null bytes (00) in 80 places. Records ------- The MASM assembly language has another tool called RECORD. A86 does not support the RECORD directive or its operators WIDTH and MASK; and it does not support the use of angle brackets to initialize structure records. From page 106 of the A86 manual: If your old program does use these features, ... macros can [be designed] to duplicate the record and structure initializations [for assembly with A86]. Explicit symbol declarations can replace... WIDTH and MASK operators. If you're a registered A86 user, you can ask Eric precisely how to do that! Final words ----------- When I began this volume, I asked Eric if he would like to participate; he replied that he works better alone. But I was able to get some nominal input from him through correspondence. The gist of those letters is reproduced here, with his permission. Q: With the A86, I understand that the PROC directive can be replaced with a label. For example if you might normally have CURSOR PROC NEAR then you can replace that with simply CURSOR: with a colon as shown. Will that work with the *ASM assemblers as well? COWARDS Page 11-2 Eric: The only thing the PROC does and a simple label-and-colon doesn't do is provide management for the near-vs.-far RET instruction. If you don't have any FAR PROC declarations in your program, all RETs will be NEAR. This is true no matter whose assembler you're using. If you wish to code a far RET, you can use the RETF mnemonic in A86 (you can define a RETF macro for the other assemblers). Q: I notice the A86 manual doesn't mention anything about a "STACK SEGMENT" declaration. How is the stack allocated by A86? Eric: When a COM file starts up, SS:SP points to the top [two bytes] of your program's segment - CS:0FFFE unless you're extremely tight on memory. Your program starts at 0100 and uses up what- ever its size it. Everything in between is unused unless your program specifically chooses to use it. My suggested usage of DATA SEGMENT allows you to allocate that memory between the program and the stack. The only thing you need to make sure of, is that the memory you allocate doesn't creep up too close to the stack. Unless my program is heavily recursive, I've never seen the stack grow to more than a couple hundred bytes; but I try not to use anything above 0F000 (4K below the top), just to be conservative. Q: I've heard "Spontaneous Assembly" has an extensive library of 700 functions and macros, but they are for "those other" assemblers! My question is this: Do you have a neat, quick "fix" to make S.A. compatible with A86? Eric: I've had several people mention Spontaneous Assembly to me... The OBJ form of [that] library ought to be perfectly compatible for linking to A86-produced OBJ files. As to source level compatibility, it would depend on how macro-heavy SA is. If there aren't too many macros, it shouldn't be too difficult to get it to work under A86. As a final note, a technique is now described for investigating the correspondence between A86 instructions and *ASM instructions via DEBUG. The A86 assembler doesn't care if your program makes sense - it only checks for obvious syntax errors. Therefore you can assemble fragments of code then subsequently Unassemble the resulting file to check correspondence to the original code. This a useful device when you have a question that this book, the A86 manual, or other documentation doesn't directly address. One example of this technique was given in Chapter 10, in BB1.BSM. But don't leave one of these pseudo programs on disk as a .COM file; if it is later inadvertently run, it will surely lock up your computer. Either delete such files or rename to have a .KOM extension, for example. COWARD.A 16 Aug 1991/WK33/Fri Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991-92 Appendix A. Hexadecimal numbers DEBUG's dump display -------------------- Consider DEBUG's dump display showing a portion of the contents of the computer's RAM: 0 1 2 3 4 5 6 7 8 9 A B C D E F -D 31FC:0100 EB 10 48 65 6C 6C 6F 2C-20 77 6F 72 6C 64 21 0D ..Hello, world!. 31FC:0110 0A 24 BA 02 01 B4 09 CD-21 C3 6E 63 65 6C 20 46 .$......!.ncel F 31FC:0120 38 3A 64 69 72 29 3A 20-22 00 4B 46 69 6C 65 20 8:dir): ".KFile 31FC:0130 6E 6F 74 20 66 6F 75 6E-64 20 6F 72 20 6F 74 68 not found or oth 31FC:0140 65 72 20 44 4F 53 20 65-72 72 6F 72 20 69 6E 73 er DOS error ins 31FC:0150 65 72 74 69 6E 67 20 22-00 4C 46 69 6C 65 20 74 erting ".LFile t 31FC:0160 6F 20 70 72 69 6E 74 20-28 45 73 63 3A 63 61 6E o print (Esc:can 31FC:0170 63 65 6C 20 46 38 3A 64-69 72 29 3A 20 22 00 4D cel F8:dir): ".M -Q ÀÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÁÄÄÄÄ¿ Notice that this part is arranged in an array sixteen bytes wide. The reason is to fit the makeup of the 8088 microprocessor. Thus we really need a number system that has sixteen digits instead of ten. Therefore six more symbols are needed for digits having values of ten, eleven, twelve, thirteen, fourteen, and fifteen. We use the first six letters of the Roman alphabet, thus, A = ten D = thirteen B = eleven E = fourteen C = twelve F = fifteen All 16 digits have been inserted above the dump display shown here. This illustrates that the byte "2C" near the center of the top line, for example, is byte number 0107h. We say that is its "offset" number. The full address of that byte in RAM is segment 31FCh offset 0107h. 2C itself is a two-digit hexadecimal number evaluating to 2x16+12=44 decimal. (That is the ASCII code for the comma character following "Hello.") Hexadecimal numbers ------------------- The numbers along the left (31FC and 0160, for example) are four-digit hexadecimal numbers. ("Hexadecimal" means hex + decimal, or 6 + 10, which is 16. Thus the slang word in common useage for hexadecimal, "hex," literally means 6, not 16, and so it could be misleading.) To convert a hexadecimal number to a decimal number (i.e., to "evaluate" it), proceed as follows. Consider 31FC shown along the left-most column. To evaluate it, write it out like this: 3 2 31FC = 3x16 + 1x16 + 15x16 + 12x1 = 3x4096 + 256 + 15x16 + 12 = 12796 ³ ³ ³ ³ 3 1 F C COWARDS Page A-2 That's 12796 decimal, and it is the decimal equivalent (the "value") of the hexadecimal number 31FC. We often write "31FCH" or "31FCh" using H or h as a "hexadecimal point" to head off possible confusion, just as we commonly use a period as a decimal point. We multiplied 3 by 16 cubed instead of by 10 cubed as we would have done in evaluating a decimal number; the reason being that this is a base 16 number; not a base 10 number. Similarly for the multipliers of the ~~ ~~ digits 1 and F. The units' digit (C here) is always multiplied by 1, regardless of number base. Up is down ---------- Look at the DEBUG dump display at the top of this appendix. Notice that the addresses along the left side increase as you go down the page. Now are you ready for this? When you go DOWN the DEBUG display you are said to be going UP in memory (RAM)! That is a convention you'll encounter in books that talk about things like "higher memory" and "lower memory." Again; going DOWN the DEBUG dump display is going UP in memory; in both cases the addresses are getting bigger. Perhaps you've noticed a similar phenomenon while watching golf on TV. The announcer might say "the ball went left" when actually it went RIGHT in the picture! The explanation is that the announcer means the GOLFER'S LEFT which happens to be the CAMERA'S RIGHT because the camera is facing the golfer. When watching golf on TV, I have a long narrow sign that I put on top of the TV. It has an arrow at each end pointing outwards; the right-hand arrow is marked "left" and the left-hand arrow is marked "right." Similarly, when looking at a DEBUG dump display, you might imagine a vertical sign with an arrow pointing up labled "down" and an arrow pointing down labled "up." The "up is down syndrome" has its basis in the following popular graphic representation of memory.  ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ F000:FFFF ³ (1 MB) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ÷ ÷ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ Top of RAM for IBM PC ³ A000:0000 ³ (640 KB) ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ (Higher memory) ÷ ÷ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 0000:0002 ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ 0000:0001 ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ (Lower memory) ³ 0000:0000 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Bottom of RAM  In this kind of representation, up is up. COWARDS Page A-3 So now just as you think you see some logic in this madness, brace yourself for a curve ball: When referring to the stack, the expression "top of stack" is used to refer to the last byte pushed onto it with the PUSH command. But that top of stack is at a lower address in memory than the bottom of the stack! Thus the top of the stack really does appear above the bottom of the stack in a DEBUG display, but it appears below the "bottom" of the stack in a graphic representation of memory! That "bottom of the stack" is at offset FFFE of the current data segment (DS) for .COM programs unless the programmer has specifically made other provisions for it in the source code. Check your stack pointer (SP) register to see where the top of the stack is at any given program step when you are tracing through a program in DEBUG with the T command. Wherever the top of the stack is at any time during program execution is kept track of by the stack pointer, register SP. COWARD.B 14 Nov 1991/WK46/Thu Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991-92 Appendix B. Relative, Absolute, and Effective Addresses (EA) If you have ever been browsing through RAM using the DEBUG Dump display, then you may have noticed from time-to-time that the same byte patterns can often be seen in different places. Does this mean that those byte patterns are really duplicated in more than one place in RAM? Probably not. What is happening can be understood by considering a room with many doors. You can look into that same room through any of the several doors. Similarly, you can look at the same place in RAM through any of several segment:offset addresses. How can this be? When RAM amounted to only 64K bytes, four hexadecimal digits were adequate to address every one of those bytes. The hexadecimal number FFFF equates to 65535 decimal; add 1 to that (for 0000) and you get 65536 bytes. That is 65.536 thousand bytes, expressed as 64K bytes in the vernacular, since K means 1024 in computereze. The PC with a so-called 640K-byte RAM requires only one more hexadecimal digit, for a total of five, to address each byte in RAM. To show that this is true, convert FFFFF to decimal and add 1 to give 1048576. This allows us to address 1024x1024 = 1M byte. Now since the popular 8088 chip was designed with 16-bit (four hexadecimal digit) registers, it was necessary to take over an entire other register to hold the "fifth" hexadecimal digit. Only one of the four hexadecimal digits in the newly-dedicated register (the "segment" register), was needed to round-out the five-hexadecimal-digit address (the "absolute" address). What was done with the remaining three? Actually nothing; meaning they would be free to do what comes naturally, which in this case has the meaning described next. Say you want to know the absolute address represented by the segment:offset address 1A2B:3C4D; here's how to proceed: (1) Affix 0 to the segment part to give 1A2B0; (2) Add that to the offset part, 3C4D, giving 1A2B0+3C4D = 1DEFD. 1DEFD is the absolute address, also called the "effective address" EA, corresponding to 1A2B:3C4D. Eric Isaacson talks a lot about the EA in his A86 manual. You may see from the above example that you need to practice your hexadecimal number addition. It is easy to see that there are other segment:offset addresses that equate to the absolute address 1DEFD. Some obvious ones are: 1DEF:000D = 1DE0:00FD = 1D00:0EFD = 1000:DEFD = 1A2B:3C4D = 1DEFD. Invoke DEBUG and look at those five segment:offset addresses; you'll see they all have the same byte value. Actually, you are looking at the same identical byte in RAM through five different doors. So which address is the "right" one? You may use any; however, it is suggested you focus on the form s000:oooo. In the above example that would be 1000:DEFD. The reason this particular form is handy is that it is easy to see from it what the absolute address is; just think of the left-most digit (most- significant digit, MSD) of the segment as being the "fifth" digit (MSD) of the absolute address - the offset being the remaining four. In your mind's-eye, just look at 1000:DEFD and collapse it, deleting the "000:" part to give 1DEFD. COWARDS Page B-2 As to the question of where a 64K segment "starts," the answer is anywhere there is a 0 in the right-most digit (least significant digit, LSD) of the absolute address expressed in hexadecimal. That equates to a segment:offset address of the form ssss:ooo0. Thus two (and more) segments can actually overlap. COWARD.C 16 Nov 1991/WK46/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991-92 Appendix C. Backward Number Notation If you assemble the command MOV AX,0706 using DEBUG then list it using the Unassemble [U] command, you'll see something like this: 5B3B:0100 B80607 MOV AX,0706 ~~~~ ~~~~ Notice that the two bytes 07 06 have become 06 07. This reversal is referred to as Backward Number Notation, which I shall christen BNN. It results from the fact that the least-significant byte, 06, is placed in memory first - before the most-significant one, 07. (Obviously!) Another example of BNN is shown in the partial DEBUG dump display below. Look at the four bytes starting at offset 0014; namely 54 FF 00 F0. This happens to be the address of INT 5; but don't try entering it as 54FF:00F0 - that won't take you to the right place. You must enter it as F000:FF54. The sequence of four bytes is reversed in the dump display. -d0:0 0000:0000 E8 56 34 02 5C 07 70 00-5F F8 00 F0 5C 07 70 00 .V4.\.p._...\.p. 0000:0010 5C 07 70 00 54 FF 00 F0-23 FF 00 F0 23 FF 00 F0 \.p.T...#...#... 0000:0020 A5 FE 00 F0 87 E9 00 F0-23 FF 00 F0 23 FF 00 F0 ........#...#... Some have condemmed this property of the 80x86 family, others have tried to justify it. In any case backward number notation is a fact; a characteristic one must learn to work with. How did we know that the four bytes starting at 0:0014 "point to" the INT 5h routine? First, you can look it up in reference 9 (see page 455 of that reference). Or you can remember that low RAM contains an "interrupt vector table" such that the "vector" to INT 0 is at 0000:0000 to 0000:0003, the vector to INT 1 is at 0000:0004 to 0000:0007 and so on. A general rule can easily be devised: It says that INT n vector starts at absolute address 4n. Thus INT 5 starts at 4x5 = 20. = 14h. By following this rule, and the procedure for "looking inside" interrupts described in Appendix G, you can do a lot without referring to books which you may or may not have! But always be aware of the BNN notation used in the interrupt vector table. COWARD.D 15 Aug 1991/WK33/Thu Assembly Language Programming for Cowards Copyright Homer B. Tilton 1991-92 Appendix D. DEBUG Register Display With the file HELLO.COM on disk and DEBUG on path, if you enter the command DEBUG HELLO.COM then enter R you will get this display: -R AX=0000 BX=0000 CX=001A DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=31FC ES=31FC SS=31FC CS=31FC IP=0100 NV UP EI PL NZ NA PO NC 31FC:0100 EB10 JMP 0112 - This is a DEBUG register display. The purpose of this appendix is to analyze it. By so doing, an insight can be gained into the purpose and use of the various registers of the 8088 CPU. The register display is redrawn below and annotated with pertinent information. All numbers are hexadecimal.  ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ ³<ÄÄÄÄÄÄÄÄÄ Data Registers ÄÄÄÄÄÄÄÄ>³ ³ AH AL BH BL CH CL DH DL ³Pointer Registers³Index Registers ³ ÚÄÄÄÀÄÀÄ ÂÄÄÄÀÄÀÄ ÂÄÄÄÀÄÀÄ ÂÄÄÄÀÄÀÄ ÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ¿ ³AX=0000 ³BX=0000 ³CX=001A ³DX=0000 ³SP=FFFE ³BP=0000 ³SI=0000 ³DI=0000³ ÀÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÙ Accumulator³Base ³Count ³Data ³Stack ³Base ³Source ³Destina- Register³Register³Register³Register³Pointer ³Pointer ³Index ³tion Index ³-Segment is at SS³is at DS³is at ES ³<ÄÄÄÄÄÄÄ Segment Registers ÄÄÄÄÄÄÄ>³ ³ Flag Register states ³ ÚÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄ¿ ÚÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄÂÄÄ¿ ³DS=31FC ³ES=31FC ³SS=31FC ³CS=31FC ³IP=0100³ ³NV³UP³DI³PL³NZ³NA³PO³NC³Clr ÀÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÄÙ ÃÄÄÅÄÄÅÄÄÅÄÄÅÄÄÅÄÄÅÄÄÅÄÄ´ ³Data ³Extra ³Stack ³ Code ³Instruc³ ³OV³DN³EI³NG³ZR³AC³PE³CY³Set ³Segment ³Segment ³Segment ³Segment ³Pointer³ ÀÂÄÁÂÄÁÂÄÁÂÄÁÂÄÁÂÄÁÂÄÁÂÄÙ ³ ³ ³ ³ ³ ³ ³Carry fl ³ CS : IP ³Opcode³<ÄÄ Instruction ÄÄ>³ ³ ³ ³ ³ ³ ³Parity flag ÚÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ AuxCarry flag ³31FC:0100³EB10 ³ JMP 0112³ ³ ³ ³ ³ ÀÄ Zero flag ÀÄÄÄÄÄÄÄÄÄÁÄÄÄÄÄÄÁÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³ ³ ³ ÀÄ Sign flag ³ Next instruction to be executed. ³ ³ ³ ÀÄ Interrupt flag ³ To execute, enter T (trace); ³ ³ ÀÄ Direction flag but don't do that if next ÀÄ Overflow flag instruction is INT n! ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ  COWARDS Page D-2 An address in RAM consists of segment:offset. Thus, Source address is DS:SI ¿ Data Destination address is ES:DI Ù Stack address is SS:SP ¿ Stack Base address is SS:BP Ù Code address is CS:IP Code DS=ES=SS=CS for COM programs. COM programs always start at IP=0100 Use Base Register (BX) to point to offset in SI, to retrieve or store data in memory. Use Index Registers SI and DI for string operations. Use Count Register (CX) to specify the number of times a loop is to be executed. Use Base Pointer Register (BP) to point to data in stack at SP. There is redundancy in the DEBUG register display. CS and IP both appear two places. You really need focus only on those items shown here: -R AX=0000 BX=0000 CX=001A DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 DS=31FC ES=31FC SS=31FC NV UP EI PL NZ NA PO NC 31FC:0100 EB10 JMP 0112 - In the case of COM programs, there is still more redundancy, and you need focus only on these items: -R AX=0000 BX=0000 CX=001A DX=0000 SP=FFFE BP=0000 SI=0000 DI=0000 NV UP EI PL NZ NA PO NC 31FC:0100 EB10 JMP 0112 - Notice that the Interrupt Flag is the only flag that is set here. Flag mnemonics... Name of Flag Clear (Clr) (0) Set (1) ~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~ Overflow (OF) No overflow (NV) Overflow (OV) Direction (DF) Up (UP) Down (DN) Interrupt (IF) Disabled (DI) Enabled (EI) Sign (SF) Plus (PL) Negative (NG) Zero (ZF) Not zero (NZ) Zero (ZR) AuxCarry (AF) No aux carry (NA) Aux carry (AC) Parity (PF) Parity odd (PO) Parity even (PE) Carry (CF) No carry (NC) Carry (CY) There is also a Trap Flag (Trace Flag) that puts the 8088 into single-step mode when the DEBUG register display is being used. COWARDS Page D-3 If you enter DEBUG alone from the DOS prompt then enter R, you'll sometimes (like right after booting the computer) get a modified register display in which added information appears in the lower right-hand corner, like this: -r AX=0000 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000 DS=36CF ES=36CF SS=36CF CS=36CF IP=0100 NV UP EI PL NZ NA PO NC 36CF:0100 02B63AB5 ADD DH,[BP+B53A] SS:B53A=FE - What does the SS:B53A=FE mean? Damned if I know! The MS-DOS Encyclopedia doesn't talk about it. Maybe it means the byte at SS:B53A is FE (whatever the significance of that information might be)!? I tried a dump starting at address 36CF:B53A but the byte there was 05, not FE. The only publication where I found that lower right-hand tail is Jeff Duntemann's fine little book "Assembly Language from Square One" (on pages 159 and 160). But a close reading of the book reveals no explanation. So if you find out what that tail means and why it's only there sometimes, please let me know! COWARD.E 23 May 1992/WK21/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1990-91-92 Appendix E. Assembler instructions and operators Introduction ------------ This is a partial listing of 8086/8088 instructions for the A86 assembler, organized according to function. These are mostly compatible with the *ASM assemblers; many with the DEBUG miniassembler. Descriptions are adapted from the A86 manual (Isaacson). See Appendix F herein for jump instructions. Operators from Chapter 8 of the A86 manual are also included in this appendix. An excellent detailed description for the complete 8086/8088 instruction set is Appendix A of Abrash; for the best partial set, see pages 183-187 and Appendix A of Duntemann. There is a complete list with telegraphic descriptions on pages 37-44 of the A86 manual (Isaacson). See Appendix H herein for references. Page numbers refer to the A86 manual. Instructions ------------ Some of these instructions normally require one or two parameters or "operands" as indicated by [1] or [2]. ("Operand" is the generally accepted misnomer.) See Chapter 6 of A86 manual for details. Add: ADD [2] Add 2nd operand to 1st operand (Ignore carry) AAA ASCII adjust AL (carry into AH) after addition ADC [2] Add with carry (in carry flag) DAA Decimal adjust after addition INC [1] Increment operand by 1. INC accepts multiple operands in A86. See p.27. Subtract: SUB [2] Subtract 2nd operand from 1st operand (No borrow) AAS ASCII adjust AL (borrow from AH) after subtraction CMP [2] Compare the two operands DAS Decimal adjust after subtraction DEC [1] Decrement operand by 1. DEC accepts multiple operands in A86. See p.27. NEG [1] Two's complement negate SBB [2] Subtract with borrow (from carry flag) Multiply: MUL [1] Unsigned multiply AAM ASCII adjust after multiply IMUL [1] Signed multiply SAL [2], SHL [2] Multiply byte or word by 2 COWARDS Page E-2 Divide: DIV [1] Unsigned divide AAD ASCII adjust before divide IDIV [1] Signed divide SAR [2], SHR [2] Signed divide byte or word by 2 Logic: AND [2] Bit-by-bit AND operation NOT [1] Bil-by-bit NOT operation OR [2] Bil-by-bit OR operation TEST [2] AND for flags only. Single operand allowed in A86. See p.30. XOR [2] Bit-by-bit exclusive-OR operation Flags: CLC Clear carry flag CLD Clear direction flag CLI Clear interrupt enable flag CMC Complement carry flag LAHF Load AH from flags register (5 flags: SF, ZF, AF, PF, CF) SAHF Store AH in flags register. (Reverses effect of LAHF.) STC Set carry flag STD Set direction flag STI Set interrupt enable flag Loop: LOOP [1] Loop while CX not zero LOOPE [1] Loop while CX not 0 and last result was equal LOOPNE [1] Loop while CX not 0 and last result was not equal LOOPNZ [1] Loop while CX not 0 and zero flag reset (0) LOOPZ [1] Loop while CX not 0 and zero flag set (1) Move: MOV [2] Copy second operand to first LEA [2] Load effective address. LEA AX,[BX] is equivalent to MOV AX,BX XCHG [2] Exchange operands XLAT [1] Translate from table. Set AL to memory byte [BX+unsigned AL]. XLATB Translate from table. Set AL to memory byte DS:[BX+unsigned AL]. LDS [2] Load doubleword into DS and word register (Load DS pointer) LES [2] Load doubleword into ES and word register (Load ES pointer) COWARDS Page E-3 Strings: CMPS [2] Compare string LODS [1] Load string MOVS [2] Move string SCAS [1] Scan string STOS [1] Store string Repeat (prefixes): REP() Repeat SI times. Use with LODS, MOVS, STOS REPE(), REPZ() Repeat SI times or until zero flag is reset (0). Use with CMPS, SCAS. REPNE(), REPNZ() Repeat SI times or until zero flag is set (1). Use with CMPS, SCAS. Push/pop: PUSH [1] Push word onto top of stack. PUSH accepts multiple operands in A86. See p.27. PUSHF Push flags register onto top of stack POP [1] "Pop" (pull) byte off top of stack. POP accepts multiple operands in A86. See p.27. POPF Pop word off top of stack into flags register Rotate: RCL [2] Rotate thru carry left RCR [2] Rotate thru carry right ROL [2] Rotate left ROR [2] Rotate right Call/return: CALL() Call procedure RET Near return from procedure RETF Far return from procedure Convert: CBW Convert signed byte in AL to signed word in AX CWD Convert signed word in AX to signed doubleword in DX:AX Hardware: HLT Halt until hardware interrupt WAIT Wait until BUSY pin is inactive (high) IN [2] Input byte or word from I/O port OUT [2] Output byte or word to I/O port COWARDS Page E-4 No-operation instructions/operators: COMMENT & (... ...) & Marks a multi-line block of comments. The ampersand [&] may be replaced with any other symbol. ;() Marks a single-line comment. May be used on a line by itself or on the same line after an instruction. NIL() A transparent symbol. Has no effect on whatever follows it. See p.72. NOP No operation; assembles to a one-byte opcode (90h). ()ST() Floating-point stack specifier in *ASM; transparent symbol in A86. See p.63. Operators --------- See Chapter 8 of A86 manual for details on these operators. For an example of the meaning of the parentheses, see first entry ()+(). ()+() As in MOV AX,BX+2 to be calculated at assembly time. See p.57. ().() Same as +. ()PTR() Same as +. ()() (Juxtaposition) - Same as +. ()-() As in MOV AX,BX-2 to be calculated at assembly time. See p.57. ()*() Multiply as in CMP AL,2*4. See p.58. ()/() Divide as in MOV BX,0123/16. See p.58. ()MOD() Modulo. See p.58. HIGH(), LOW(), ()BY() See p.56. !() Negation. See p.59. Relational (See p.59): ()EQ() Equal ()NE() Not equal ()LT() Less than ()LE() Less than or equal to ()GT() Greater than ()GE() Greater than or equal to ()=() Equal, ignoring case (for strings) Convert operand (See p.60): B(), ()B Convert to byte W(), ()W Convert to word D(), ()D Convert to doubleword Q(), ()Q Convert to quadword T(), ()T Convert to tenbyte COWARDS Page E-5 Shift (See p.58): ()SHR() Shift right ()SHL() Shift left BIT() Same as 1 SHL() Others (See p.61): SHORT() "Short" refers to within 127 bytes. LONG() "Long" refers to outside 127 bytes. OFFSET() Load constant pointer to a variable. [] Quantity inside brackets given a "memory variable" type. ():() Used to attach a segment register value to an operand. (): Used to indicate a label. ;() Used to indicate a comment. TYPE() Return a numerical value depending on operand type. See p.63. For compatibility with *ASM assemblers only (See p.62): NEAR() Convert to label. THIS Return current location count. $ Same as THIS. COWARD.EA 23 May 1992/WK21/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1990-91-92 Appendix EA. More on assembler instructions and operators Instructions ------------ A nearly exhaustive list of 8088 instructions usable with both *ASM and A86 assemblers follows. Some of these instructions normally require one or two operands, as indicated by [1], [2]. Page numbers refer to the A86 manual. (*ASM refers to assemblers other than A86, such as MASM and TASM.) See Appendix FA for conditional jumps. See glossary at end of this appendix for terms. ---------------------------------------------------------------------------- ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ARITHMETIC ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Add: ADD [2] Add two operands (Ignore carry); put result in first operand. Example: ADD AH,AL does AH'=AH+AL. (AH' indicates the new value in AH.) ADC [2] Add with carry (in carry flag); put result in first operand. INC [1] Increment operand by 1. INC accepts multiple operands in A86. See p.27 of A86 manual. Example: INC BX does BX'=BX+1. ß Subtract: SUB [2] Subtract 2nd operand from 1st operand (No borrow); put result in first operand. Example: SUB AH,AL does AH'=AH-AL. CMP [2] Compare the two operands. Neither operand changes. Frequently used just before a conditional jump instruction. DEC [1] Decrement operand by 1. DEC accepts multiple operands in A86. See p.27 of A86 manual. NEG [1] Two's complement negate; result in operand. SBB [2] Subtract with borrow (from carry flag); result in operand. COWARDS Page EA-2 ß Multiply: MUL [1] Unsigned multiply. MUL AH multiplies AL by AH, placing result in AX; MUL CX multiplies AX by CX, placing result in DX:AX. IMUL [1] Signed multiply. Works like MUL, except accounts for sign. ß Divide: DIV [1] Unsigned divide. DIV BH divides AX by BH, placing integer portion of result in AL and remainder in AH. DIV CX divides DX:AX by CX, placing integer portion of result in AX and remainder in DX. Note: If you attempt to divide by zero, INT 0 (Divide by zero) is executed. IDIV [1] Signed divide. Works similarly to DIV except as follows: With IDIV AL, you must "sign extend" AL to a word in AX (use CBW instruction for that). Also, with IDIV AX, must sign extend it to a doubleword in DX:AX (use CWD). Also, for 16x8 division, the quotient must be no larger than 8 bits including sign bit; and for 32x16 division, the quotient must be no larger than 16 bits including sign bit. Note: If you attempt to divide by zero, INT 0 is executed. ß ASCII adjust (Ref. p.333 of Abrash and p.30 of A86 manual): AAA ASCII adjust AL (carry into AH) after addition. Adjusts AL to the correct unpacked BCD result of the addition of two nibbles. Assumes that an ADD or ADC instruction has just executed. AAS ASCII adjust AL (borrow from AH) after subtraction. Adjusts AL to the correct unpackeded BCD result of the subtraction of two nibbles. Assumes that a SUB or SSB instruction has just executed. AAM ASCII adjust after multiply (AL/10:AH=Quo,AL=Rem). Adjusts AL after multiplication of two single-digit unpacked BCD values to a valid two-digit unpacked BCD value in AX. With A86 you can use one operand with AAM thus: AAM 16 will unpack AL into nibbles AH and AL. ~~~~~~ COWARDS Page EA-3 AAD ASCII adjust before divide (AX=10*AH+AL). Converts a two-digit unpacked BCD value in AX into the binary equivalent in AX. The binary result of AAD can then be divided by a single- digit BCD value to generate a single-digit BCD result. With A86 you can use one operand with AAD thus: AAD 16 will pack nibbles AH and AL into AL. ~~~~~~ Note: This feature of A86 doesn't work for AAD with NEC V20 and V30 microprocessors. It still works for AAM, however. ß Decimal adjust (Ref. p.333 of Abrash): DAA Decimal adjust AL after addition. Adjusts AL to the correct value after addition of two packed BCD operands. DAS Decimal adjust AL after subtraction. Adjusts AL to the correct value after subtraction of two packed BCD operands. See Shift operations (SAL,SHL,SAR,SHR) for base 16 arithmetic. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ BIT MANIPULATION ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Rotate/shift bits: SAL [2] Multiply byte or word by 2. Shifts all bits to left (L). Bit 7 is shifted into carry flag; bit 0 becomes 0. SHL [2] Same as SAL. SAR [2] Signed divide byte or word by 2. Shifts all bits to right (R), except bit 7 is unchanged. Bit 0 is moved into the carry flag. SHR [2] Signed divide byte or word by 2. Shifts all bits to right (R). Bit 7 becomes 0. Bit 0 is moved into the carry flag. RCL [2] Rotate 9 bits one position left for byte (carry flag holds ninth bit); rotate 17 bits one position left for word. RCR [2] Rotate 9 or 17 bits one position right. ROL [2] Rotate 8 or 16 bits one position left. ROR [2] Rotate 8 or 16 bits one position right. COWARDS Page EA-4 ß Logic: AND [2] Bit-by-bit AND operation; bit "strainer." In AND BH,AH, individual bits of BH and AH are ANDed against each other to form a new BH. Bit strainer operation: in AND BX,AX, AX acts like a strainer for BX in that the "1" bits of AX act like openings to let bits of BX in those positions "drop thru" (whether "1" or "0") to form corresponding bits of the new value of AX, other bits of AX being replaced with "0". NOT [1] Bit-by-bit NOT operation; bit "inverter." In NOT AX, a new value for AX is formed in which "0" bits of the original AX are now "1" and in which "1" bits of the original AX are now "0". OR [2] Bit-by-bit OR operation; bit "mask." In OR BH,AH, individual bits of BH and AH are ORed against each other to form a new BH. Bit mask operation: in OR BX,AX, AX acts to set to "1" each bit of BX corresponding to a "1" bit in AX, leaving other bits of BX unchanged. XOR [2] Bit-by-bit exclusive-OR operation; bit "animator." IN XOR BH,AH, individual bits of BS and AH are XORed against each other to form a new BH. If OR means "and/or" (which it does), then XOR (exclusive OR) means "or." Bit animator operation: XOR BX,AX followed by another XOR BX,AX restores BX to its original value. Useful in animation to erase a particular portion of a drawing. TEST [2] Acts like AND followed by CMP for flags only. Single operand allowed in A86; thus TEST DL is equivalent to TEST DL,DL. See p.30 of A86 manual. ß Flags: CLC Clear carry flag. CLD Clear direction flag. CLI Clear interrupt enable flag. CMC Complement carry flag. LAHF Load AH from flags register (5 flags: SF, ZF, AF, PF, CF). SAHF Store AH in flags register. (Reverses effect of LAHF.) STC Set carry flag. STD Set direction flag. STI Set interrupt enable flag. INTO Announces "Overflow" if overflow flag set. It does this by calling INT 4. COWARDS Page EA-5 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ BYTE MANIPULATION ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Move: MOV [2] Copy second operand to first. Example: MOV AX,BX does LET AX=BX. Example: MOV AX,[040F] puts the byte at offset 040Fh in AX. LEA [2] Load effective address. LEA AX,[BX] is equivalent to MOV AX,BX. XCHG [2] Exchange operands. XLAT [1] Translate from table. Set AL to memory byte [BX+unsigned AL]. XLATB Translate from table. Set AL to memory byte DS:[BX+unsigned AL]. LDS [2] Load doubleword into DS and word register (Load DS pointer). LES [2] Load doubleword into ES and word register (Load ES pointer). ß Strings: CMPS [2] Compare string. LODS [1] Load string. MOVS [2] Move string. SCAS [1] Scan string. STOS [1] Store string. ß Repeat (prefixes): REP() Repeat SI times. Use with LODS, MOVS, STOS. REPE() Repeat SI times or until zero flag is reset (0). Use with CMPS, SCAS. REPZ() Same as REPE. REPNE() Repeat SI times or until zero flag is set (1). Use with CMPS, SCAS. REPNZ() Same as REPNE. COWARDS Page EA-6 ß Push/pop: PUSH [1] Push word onto top of stack. PUSH accepts multiple operands in A86. See p.27. PUSHF Push flags register onto top of stack. POP [1] "Pop" (pull) byte off top of stack. POP accepts multiple operands in A86. See p.27. POPF Pop word off top of stack into flags register ß Convert: CBW Convert signed byte in AL to signed word in AX. Useful in conjunction with IDIV. CWD Convert signed word in AX to signed doubleword in DX:AX. Useful in conjunction with IDIV. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ PROCESS CONTROL ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Branching: LOOP [1] Loop while CX not zero. (Loop unless CX is zero.) LOOP acts like DEC CX / CMP CX,0 / JNZ . A count must initially be loaded into CX. LOOPZ [1] Loop while CX not 0 and zero flag is 1. (Loop unless CX is 0 or zero flag is 0.) Handy for testing for an event that clears ZF. LOOPNZ [1] Loop while CX not 0 and zero flag is 0. (Loop unless CX is 0 or zero flag is 1.) Handy for testing for an event that sets ZF. LOOPE [1] Loop while CX not 0 and last result was equal. Same as LOOPZ. LOOPNE [1] Loop while CX not 0 and last result was not equal. Same as LOOPNZ. JCXZ [1] Branch only if CX is 0. COWARDS Page EA-7 JMP [1] Jump unconditionally. Branch to location specified in operand. Example: JMP THERE jumps to lable THERE: Example: JMP 02AF jumps to offset 02AFh. JMP>[1] Jump forward. A86 only. See p.29. See Appendix F for conditional jumps. CALL() Call procedure or subroutine; branch to label. Examples: CALL NearLabel; CALL FarLabel; CALL NEAR PTR NearTarget; CALL BX; CALL WORD PTR[Vecs+SI]; CALL FAR PTR FarTarget; CALL DWORD PTR[FarVec] RET Near return from procedure. RETF Far return from procedure. (In A86 and MASM from version 5.0.) Notifies 8088 to POP both IP and CS from stack for return to main program. ß Hardware: HLT Halt until hardware interrupt. WAIT Wait until BUSY pin is inactive (high). IN [2] Input byte or word from I/O port. OUT [2] Output byte or word to I/O port. IRET Return from hardware or software interrupt. ß No operation: NOP No-operation instruction; assembles to a one-byte opcode. NIL() A transparent prefix symbol. Has no effect on whatever follows it. A86 only - see p.72. **************************************************************************** COWARDS Page EA-8 Operators --------- "Operators" perform operations at assembly time, as opposed to "instructions" (discussed above) which are performed at run time. For an example of the meaning of the parentheses, see first entry ()+(). Page numbers refer to the A86 manual. ---------------------------------------------------------------------------- ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ARITHMETIC ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Operational (See pp.56-59): ()+() Add as in MOV AX,BX+2. This adds 2 to whatever's in BX before moving that number into AX. BX remains unchanged. ().() Same as +. ()PTR() Same as +. (PTR also appears in WORD PTR, BYTE PTR. See Ch.10.) ()() (Juxtaposition) - Same as +. ()-() Subtract as in MOV AX,BX-2. This subtracts 2 from whatever's in BX before moving that number into AX. BX remains unchanged. ()*() Multiply as in CMP AL,2*4, same as CMP AL,8 ()/() Divide as in MOV BX,0123/16, same as MOV BX,012h ()MOD() Modulo as in MOV BX,16 MOD 7, same as MOV BX,2 since 2 is the remainder after dividing 16d by 7. !() Negation. If operand is non-zero or a defined name, !() is zero; if operand is zero or an undefined name, !() is FFFFh. ß Relational (See p.59): ()EQ() Equal. Example: MOV AL,3 EQ 0 produces the same thing as MOV AL,0 since 3 EQ 0 is false (=0). ()NE() Not equal ()LT() Less than ()LE() Less than or equal to. Example: MOV AX,2 LE 15 produces the same thing as MOV AX,FFFFh since 2 LE 15 is true (=FFFFh) ()GT() Greater than ()GE() Greater than or equal to ()=() Equal, ignoring case (for strings) COWARDS Page EA-9 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ BIT MANIPULATION ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Shift (See p.58): ()SHR() Shift right. Example: MOV BX,0FACBh SHR 4 produces BX=0FACh, since shifting by four binary digits is equivalent to shifting by one hexadecimal digit. ()SHL() Shift left. BIT() Same as 1 SHL(). See p.58. Example: OR AL,BIT 6 (meaning OR AL, 01h SHL 6) takes the byte 01h = 0000 0001 b and shifts everything left six positions to give 0100 0000 b = 40h, producing OR AL,040h. Here, 040h is called the "mask" for bit 6 since bit 6 is "1" while all other digits are "0". ß Logical operators (See p.58): ()OR() Bitwise OR. Example: 1111 0000 b OR 0011 0011 b = 1111 0011 b; so that F0h OR 33h = F3h. ()XOR() Bitwise exclusive-or. Example: 1111 0000 b XOR 0011 0011 b = 1100 0011 b; so that F0h XOR 33h = C3h. ()AND() Bitwise AND. Example: 1111 0000 b AND 0011 0011 b = 0011 0000 b; so that F0h AND 33h = 30h. NOT() Bitwise inversion. Example: NOT 0011 0011 b = 1100 1100 b; so that NOT 33h = CCh. COWARDS Page EA-10 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ BYTE MANIPULATION ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Byte isolation (See p.56): HIGH() Returns high byte of word operand. Example: MOV AL, HIGH(BX) moves the high byte of BX into AL. LOW() Returns low byte of word operand. ß Byte combining (See p.57): ()BY() Byte "splicer." Returns the word whose high byte is the left operand and whose low byte is the right operand. For A86 use only. Example: MOV AX,'A'BY 10 is equivalent to MOV AX 0410A; with the interpretation of operand 0410A being 41h 0Ah 'A' ÁÙ ÀÁ 10d ß Conversion (See p.60); A86 specific. Expand B to BYTE and so on for compatibility with *ASM. B(), ()B Short for BYTE. Convert to byte. Example: MOV AL, ARRAY_PTR B loads AL with first byte of ARRAY_PTR. Example: MOV AX, WVAR B loads AL with byte of WVAR. W(), ()W Short for WORD. Convert to word. Example: MOV AX, W[01000] loads AX with memory word at offset 01000. Assembles/Unassembles to MOV AX, [1000]. D(), ()D Short for DWORD. Convert to doubleword. Example: LDS BX, D[01000] loads DS:BX with doubleword at offset 01000. Assembles/Unassembles to LDS BX, [1000]. Q(), ()Q Short for QWORD. Convert to quadword. T(), ()T Short for TBYTE. Convert to tenbyte. COWARDS Page EA-11 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ MISCELLANEOUS ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ß Attributes (See p.61): SHORT() "Short" is within 127 bytes. LONG() "Long" is not within 127 bytes. FAR() In a CALL FAR ... to a far procedure, 8088 pushes CS then IP onto the stack so execution will continue properly when the called procedure is done. (Such a procedure requires a "far" RET to insure that both words are POPed at completion.) NEAR() In a CALL NEAR ..., 8088 pushes only IP onto the stack. Not required with A86. OFFSET() Loads constant pointer to a variable. TYPE() Returns numerical value depending on operand type (see p.62). 1=byte var; 2=word var; 4=doubleword var; 8=quadword var; 10 if tenbyte var; 0=constant. If structure name, number returned is number of bytes allocated by the structure. [] Quantity inside brackets is given a "memory variable" type. Extracts memory contents at specified address. ():() Used to attach a segment register value to an operand. (): Used to indicate a label. ;() Marks a single-line comment. May be used on a line by itself or on the same line after an instruction. COMMENT & (... ...) & Marks a multi-line block of comments. The ampersand [&] may be replaced with any other symbol. ß For compatibility with *ASM assemblers only (See p.62): NEAR() Convert to type "code label." THIS Return current location count. $ Same as THIS. ()ST() Floating-point stack specifier in *ASM; transparent symbol in A86. See p.63. **************************************************************************** COWARDS Page EA-12 Glossary: BCD - Binary coded decimal. In "packed" BCD a hexadecimal number has the same appearance as the equivalent decimal number. For example, 1000h(BCD) equals 1000d. Bit - Binary digit. Hash sign (as used in A86 manual) - Pound symbol, #. Nibble, nybble - Half a byte; one hexadecimal digit; four binary digits. Opcode - The hexadecimal machine code corresponding to a given assembler instrunction. Packed BCD - Each nibble holds one decimal digit. Unpacked BCD - Each byte holds one decimal digit. COWARD.FA 15 Aug 1991/WK33/Thu Assembly Language Programming for Cowards Copyright Homer B. Tilton 1990-91-92 Appendix FA. More on Conditional Jump Instructions JMP is an unconditional jump and does not appear in the lists below. All instructions summarized below are "Jump Short" instructions. DEBUG only recognizes those forms shown in the clear; *ASM and A86 assemblers also recognize those forms in parentheses. The full instruction requires a jump destination. Thus for JA, use JA label. (1) Jumps based on a comparison: CMP X,Y JUMPS IF INSTRUC FLAGS ALTERNATE INSTRUC(A86) ~~~~~~~~ ~~~~~~~ ~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ X>Y JA NZ,NC IF A ³ Ref. (JNBE) ³ Page 27 JG * NZ and SF=OF IF G ³ of A86 (JNLE) ³ manual ³ XòY JNB NC IF NB ³ (JAE) ³ JGE * SF=OF IF GE (JNL) XOF IF L (JNGE) XóY JBE ZR or CY IF BE (JNA) JLE * ZR or SF<>OF IF LE (JNG) X=Y JZ ZR IF Z (JE) X<>Y JNZ NZ IF NZ (JNE) COWARDS Page FA-2 (2) Jumps based on flag settings. JUMPS IF INSTRUC ALTERNATE INSTRUC(A86) ~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~ Overflow flag set (OV) JO * IF O ³Ref. Overflow flag clear (NV) JNO * IF NO ³page 27 ³of A86 Sign flag set (NG) JS * IF S ³manual Sign flag clear (PL) JNS * IF NS ³ ³ Zero flag set (ZR) JZ IF Z ³ Zero flag clear (NZ) JNZ IF NZ Parity flag set (PE) JPE IF PE (JP) Parity flag clear (PO) JPO IF PO (JNP) Carry flag set (CY) JB IF B (JC) Carry flag clear (NC) JNB IF NB (JNC) (3) Jump based on register contents. JUMPS IF INSTRUC ~~~~~~~~ ~~~~~~~ CX=0000 JCXZ ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ Key: SF=OF means sign flag equals overflow flag. SF<>OF means sign flag does not equal overflow flag. * Pertinent for signed arithmetic. COWARDS Page FA-3 Examples -------- Use of the Jump and IF instructions is illustrated below. These are elaborations of an example that appears on page 27 of the A86 manual. ;This says: CMP SI,DI ;Examine SI-DI. JNZ >L1 ;Jump if Not Zero, forward to L1 label. MOV AX,BX ;Otherwise set AX=BX. (Do this if SI-DI=0.) L1: ;This is L1 label. Another way to write the same code... ;This says: CMP SI,DI ;Compare SI with DI. JNE >L1 ;Jump if Not Equal, forward to L1 label. MOV AX,BX ;Otherwise set AX=BX. (Do this if SI=DI.) L1: ;This is L1 label. Code that is equivalent to the above if you use the A86 assembler... ;This says: CMP SI,DI ;Examine SI-DI. IF Z MOV AX,BX ;If Zero, set AX=BX. ;No label is needed. The same thing again, but without explicit use of the CMP instruction... ;This says: IF Z MOV AX,BX ;If Zero flag is set, set AX=BX. COWARD.G 23 May 1992/WK21/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1990-91-92 Appendix G. DOS and BIOS interrupts Interrupts ---------- Among the DOS interrupts there are the following. Numbers are hexadecimal. For details on how to use them, see Appendix H references 3 and 7 for DOS interrupts and references 9 and 7 for BIOS interrupts. INT 21 functions - 1. Character input - 01, 03, 06-08, 0A-0C 2. Character output - 02, 04-06, 09 3. Disk management - 0D, 0E, 19, 1B, 1C, 2E, 36, 54 4. File management - 0F, 10-13, 16, 17, 1A, 23, 2F, 3C-3E, 41, 43, 45, 46, 4E, 4F, 56, 57, 5A-5C 5. Information management - 14, 15, 21, 22, 24, 27, 28, 3F, 40, 42 6. Directory management - 39-3B, 47 7. Process management - 00, 31, 4B-4D, 59 8. Memory management - 48-4A, 58 9. Miscellaneous system management - 25, 26, 29, 2A-2D, 30, 33-35, 38, 44, 5E, 5F, 62, 63 INT 20 - Terminate program INT 22 - Terminate routine address INT 23 - Control-C handler address INT 24 - Critical error handler address INT 25 - Absolute disk read INT 26 - Absolute disk write INT 27 - Terminate and stay resident (TSR) INT 2F - Multiplex interrupt Among the BIOS interrupts there are these: 1. Keyboard service - INT 09, INT 16 2. Video service - INT 10 3. Diskette service - INT 13, functions 00-05, 08, 15-18 4. Fixed disk service - INT 13, functions 00-15 5. Serial communications service - INT 14 6. System service - INT 15 7. Parallel printer service - INT 17 8. Time-of-day and date service - INT 1A 9. Single function service - INT 05, Print screen INT 11, Equipment list INT 12, Memory size INT 19, Boostrap loader COWARDS Page G-2 Illustration ------------ Just what is an interrupt? Where is it located in RAM and how is it constituted? These questions are answered next using BIOS INT 5h as an example. A "road map" to INT 5h begins in the interrupt vector table discussed in Appendix C. In that appendix, it is stated that the INT 5h routine starts at F000:FF54h. Just what does that mean? If we call up DEBUG then use the commands UF000:FF54 then U then U then U then U then Q (using as many U's as it takes), we get an Unassemble display of this interrupt routine that describes the PrintScreen action. That listing looks like this: -UF000:FF54 F000:FF54 FB STI F000:FF55 52 PUSH DX F000:FF56 51 PUSH CX F000:FF57 53 PUSH BX F000:FF58 50 PUSH AX F000:FF59 1E PUSH DS F000:FF5A 33C0 XOR AX,AX F000:FF5C 8ED8 MOV DS,AX F000:FF5E 803E000501 CMP BYTE PTR [0500],01 F000:FF63 7506 JNZ FF6B F000:FF65 1F POP DS F000:FF66 58 POP AX F000:FF67 5B POP BX F000:FF68 59 POP CX F000:FF69 5A POP DX F000:FF6A CF IRET F000:FF6B C606000501 MOV BYTE PTR [0500],01 F000:FF70 B40F MOV AH,0F F000:FF72 CD10 INT 10 -U F000:FF74 8ACC MOV CL,AH F000:FF76 B519 MOV CH,19 F000:FF78 E84C00 CALL FFC7 F000:FF7B 51 PUSH CX F000:FF7C B403 MOV AH,03 F000:FF7E CD10 INT 10 F000:FF80 59 POP CX F000:FF81 52 PUSH DX F000:FF82 33D2 XOR DX,DX F000:FF84 B402 MOV AH,02 F000:FF86 CD10 INT 10 F000:FF88 B408 MOV AH,08 F000:FF8A CD10 INT 10 COWARDS Page G-3 F000:FF8C A8FF TEST AL,FF F000:FF8E 7502 JNZ FF92 F000:FF90 B020 MOV AL,20 F000:FF92 52 PUSH DX F000:FF93 33D2 XOR DX,DX -U F000:FF95 8AE6 MOV AH,DH F000:FF97 CD17 INT 17 F000:FF99 5A POP DX F000:FF9A F6C425 TEST AH,25 F000:FF9D 7521 JNZ FFC0 F000:FF9F FEC2 INC DL F000:FFA1 3ACA CMP CL,DL F000:FFA3 75DF JNZ FF84 F000:FFA5 B200 MOV DL,00 F000:FFA7 32E4 XOR AH,AH F000:FFA9 52 PUSH DX F000:FFAA E81A00 CALL FFC7 F000:FFAD 5A POP DX F000:FFAE FEC6 INC DH F000:FFB0 3AEE CMP CH,DH F000:FFB2 75D0 JNZ FF84 F000:FFB4 C606000500 MOV BYTE PTR [0500],00 -U F000:FFB9 5A POP DX F000:FFBA B402 MOV AH,02 F000:FFBC CD10 INT 10 F000:FFBE EBA5 JMP FF65 F000:FFC0 C6060005FF MOV BYTE PTR [0500],FF F000:FFC5 EBF2 JMP FFB9 F000:FFC7 33D2 XOR DX,DX F000:FFC9 B80A00 MOV AX,000A F000:FFCC CD17 INT 17 F000:FFCE B80D00 MOV AX,000D F000:FFD1 CD17 INT 17 F000:FFD3 C3 RET F000:FFD4 00B00350 ADD [BX+SI+5003],DH F000:FFD8 E858E0 CALL E033 -U F000:FFDB B048 MOV AL,48 F000:FFDD E661 OUT 61,AL F000:FFDF 58 POP AX F000:FFE0 FEC8 DEC AL F000:FFE2 75F3 JNZ FFD7 F000:FFE4 29C9 SUB CX,CX F000:FFE6 E2FE LOOP FFE6 F000:FFE8 E460 IN AL,60 F000:FFEA C3 RET COWARDS Page G-4 F000:FFEB D7 XLAT F000:FFEC 0000 ADD [BX+SI],AL F000:FFEE 00FF ADD BH,BH F000:FFF0 EA5BE000F0 JMP F000:E05B F000:FFF5 3031 XOR [BX+DI],DH F000:FFF7 2F DAS F000:FFF8 3330 XOR SI,[BX+SI] F000:FFFA 2F DAS -Q The INT 5h routine ends at offset FFD3. How do we know that? First, scanning down the list of instructions, we come to one that doesn't make sense: the one at offset FFE2. That instruction is JNZ FFD7, but there isn't an entry point at offset FFD7; the nearest one is at FFD8. This means that DEBUG, in its attempt to disassemble these bytes, has erroneously embedded offset FFD7 inside the instruction ADD [BX+SI+5003],DH - meaning that is a fictitious instruction. (If you do a new Unassemble display starting at FFD7, then you'll get a true disassembly for a portion of that next routine whatever it is!) Now we scan up the list of instructions until we come to the RET at offset FFD3. Analysis of the instructions from the top of the listing to that point eventually convinces us that that is the end of the INT 5h routine. As the clincher, if we assemble a new program (call it PRNSCRN.COM) which is an (almost) exact copy of that listing from offset FF54 to FFD3, we'll find that it works like PrintScreen. If you exactly duplicate the INT 5 routine in PRNSCRN.COM then invoke the command PRNSCRN, sure enough you'll get a printout of the screen; but now the computer locks up! Why did it lock up?! Well, look at the two instructions at offsets FF54 (STI) and FF6A (IRET). STI is "set interrupt enable flag" and IRET is "return from interrupt." You guess that somehow one or both of those commands may not be appropriate for use in PRNSCRN.COM because this is a program - not an interrupt! Sure enough, if you replace STI with NOP and IRET with RET in your program, then things work as they should. Here is a source code listing for PRNSCRN.COM, heavily annotated to help you along: CODE SEGMENT ;PRNSCRN.BSM H TILTON 11 Jun 1992/WK24/Thu ORG 0100 ; LF = 0Ah ;Assemble using Eric Isaacson's A86 assembler CR = 0Dh ;with the command A86 PRNSCRN.BSM . ; JMP MAIN ;No data, no messages, no macros. ; COWARDS Page G-5 MAIN: ; NOP ;STI replacement. PUSH DX,CX,BX,AX,DS ;Save those register contents.ÄÄÄÄÄ>>>ÄÄPUSHÄ¿ XOR AX,AX ;Zero AX. ³ MOV DS,AX ;Set DS (data segment) to 00. Why? ³ ;Because the PrintScreen status byte ³ ;is located at 0:0500h. We look at ³ ;that next. ³ CMP BYTE PTR [0500h],01 ;01 means PrintScreen function busy. ³ ;If busy, program "drops thru" and ³ ;terminates. (Thus you can disable ³ ;the PrintScreen key by simply poking ³ ;01 into that byte position!) ³ JNZ M1 ;Ok to do a print-screen. ³ ;Jump to M1 and do it. ÷ ;Exit routine starts next. ³ M0: POP DS,AX,BX,CX,DX ;Restore those registers before exiting.ÄÄ<<<ÄÄÄPOPÄÙ RET ;IRET replacement. ;This ultimately terminates the program. ; ;**************************************************************************** ; M1: MOV BYTE PTR [0500h],01 ;01 signals the world that PrintScreen ;function is now in progress. MOV AH,0Fh ;Prepare to... INT 010h ;Return video status. ;Returns in AH: number of columns on screen; ; MOV CL,AH ;No.of columns now in CL. MOV CH,019h ;19h=25. lines (rows) to scan - put in CH. CALL M6 ;Do LF/CR pair. ; PUSH CX ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>>>ÄÄÄÄÄ¿PUSH MOV AH,03 ;Prepare to... ³ INT 010h ;Read cursor position. ³ Total rows ;Returns in DH: Row number; ³ & columns. ; in DL: Column number. ³ POP CX ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ<<<ÄÄÄÄÄÙPOP ; COWARDS Page G-6 PUSH DX ;DX has original cursor position. Save to stack ;so it can be restored at end of program. Ä>>>ÄPUSHÄ¿ XOR DX,DX ;Set starting row/col at 0/0 (upper LH corner ³ ;of screen). ³ ; ³ ;**************** LOOP TO THIS POINT UNTIL DONE *************************** ³ ; ³ M2: MOV AH,02 ;Prepare to... ³ INT 010h ;Set cursor position. ³ ; ³ MOV AH,08 ;Prepare to... ³ INT 010h ;Read character/attribute from screen. ³ ;Returns in AL: Character read ³ ; ³ TEST AL,0FFh ;If character in AL is not FFh, then... ³ JNZ M3 ;jump to M3. ³ MOV AL,020h ;otherwise print space (20h=32.) ³ ; ³ M3: PUSH DX ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>>>ÄÄÄÄ¿PUSH ³ XOR DX,DX ;DX=0 is printer LPT1. ³ Current ³ MOV AH,DH ;Prepare to... ³ cursor ³ INT 017h ;Print character. ³ position. ³ ;Returns in AH: Printer status ³ ³ POP DX ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ<<<ÄÄÄÄÙPOP ³ TEST AH,025h ;AH contains printer status. ³ JNZ M5 ;Signal error if AH is not 025h = 0010 0101 b. ³ INC DL ;Increment column number. ³ CMP CL,DL ;Last column done? (DL=80. is 81st col.) ³ JNZ M2 ;Do M2 again if not last column... ³ MOV DL,0 ;Otherwise set column to 0 to start again. ³ XOR AH,AH ;Zero AH to prepare for M6. ³ PUSH DX ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ>>>ÄÄÄ¿PUSH ³ CALL M6 ;Do LF/CR pair. ³ Current cursor position. ³ POP DX ;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ<<<ÄÄÄÙPOP ³ INC DH ;Increment row number. ³ CMP CH,DH ;Last row done? (DH=25. is 26th row.) ³ JNZ M2 ;Do M2 again if not last row; otherwise drop thru. ³ ; ³ ;*************** LOOP BACK TO M2 UNTIL DONE ******************************* ³ ; ³ MOV BYTE PTR [0500h],00 ;00 signals PrintScreen action was ³ ;successful. ³ M4: ;Prepare for exit routine. ³ POP DX ;Recover original cursor position info. ÄÄÄ<<<ÄPOPÄÄÙ MOV AH,02 ;Prepare to... INT 010h ;Restore original cursor position. JMP M0 ;Restore registers and exit to DOS. ; COWARDS Page G-7 M5: ;Error routine. MOV BYTE PTR [0500h],0FFh ;0FFh signals PrintScreen action was ;unsuccessful. JMP M4 ;Go to exit routine. ; M6: ;Routine to print line feed/carriage return pair at LPT1. XOR DX,DX ;DX=0 is printer LPT1. MOV AX,LF ;AX=000Ah. Prepare to... INT 017h ;Print character in AL (line feed). ; MOV AX,CR ;AX=000Dh. Prepare to... INT 017h ;Print character in AL (carriage return). RET ;END of program. All instructions except TEST have been encountered in the main text. TEST acts like AND followed by CMP, except that the first operand register value is changed by AND but not by TEST. Flags are changed in the same way by both. This is explained next. The AND instruction does bit-by-bit logical AND operations on its two operands. Thus AND AH,025h performs these bit-by-bit operations: ÚÄÄÄÄÙ ÀÁÄÄÂÄÄÄ¿ ³ 2 5 h stuv wxyz & 0010 0101 b. and the new result is placed in AH. (Lower case letters "stuv wxyz" here represent arbitrary binary digits 0 or 1.) Thus the new bits of AH become s&0 t&0 u&1 v&0 w&0 x&1 y&0 z&1 = 00u0 0x0z since n&0=0 and n&1=n. This has the effect of replacing all bits of AH with 0 that correspond to "0" bits of the second operand, and leaving unchanged all bits of AH that correspond to "1" bits of the second operand. The second operand acts like a strainer in which "1" bits are openings, allowing bits of the first operand to "drop thru" unchanged; all other bits of the first operand being replaced with 0's. The upshot is this: When we do an AND, then a subsequent CMP (compare) operation followed by a conditional jump, we can effectively test only particular bits of AH to see if they have the values we think they should. Finally, the code AND AH,025h ¿ Is equivalent Ú TEST AH,025h CMP AH,025h à for flags to ´ ; JNZ M5 Ù À JNZ M5 COWARDS Page G-8 AH is changed by AND but not by TEST. The flags are changed in the same way for both AND/CMP and TEST, and the flag status is what is sensed by the conditional jump JNZ and other conditional jumps. Conditional jumps and the CMP instruction are discussed in Appendix F. COWARD.GA 23 May 1992/WK21/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1990-91-92 Appendix GA. More on DOS interrupts INT 21h service --------------- Among the DOS interrupts there are the following. Interrupt and function numbers are hexadecimal. To call a function, place the function number in register AH. For unfamiliar terms and abbreviations, see glossary at end of Appendix GB. ---------------------------------------------------------------------------- ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 21 functions ³ Vector at 84ha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ 1. Character input - Functions 01,03,06,07,08 became obsolete with DOS 2.0; use function 3Fh instead of those. See section 5, Information management. ÚÄÄÄÄÄÄ¿ ³AH=0A ³ Buffered keyboard input - ÀÄÄÄÄÄÄÙ Collects characters from standard input and places them in a user- specified memory buffer. Input is accepted until either a carriage return (0Dh) is encountered or the buffer is unable to accept more. The characters are echoed to standard output. DS:DX = address of input buffer. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=0B ³ Check keyboard status - ÀÄÄÄÄÄÄÙ Returns a value in AL that indicates whether a character is available from standard input. Returns in AL: 00 if no character available; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=0C ³ Flush buffer, read keyboard - ÀÄÄÄÄÄÄÙ Clears standard input buffer, then performs function 0A. AL=input function no.to execute (01,06,07,08,0A) Returns in AL: 8-bit ASCII character from standard input (if AL was 01,06,07 or 08 on call); Returns nothing if AL was 0A on call. ---------------------------------------------------------------------------- 2. Character output - Functions 02,04,05,06,09 became obsolete with DOS 2.0; Use function 3Fh or 40h instead of 06; use function 40h instead for others. See section 5, Information management. ---------------------------------------------------------------------------- COWARDS Page GA-2 3. Disk management - ÚÄÄÄÄÄÄ¿ ³AH=0D ³ Disk reset - ÀÄÄÄÄÄÄÙ Writes to disk all internal MS-DOS file buffers in memory that have been modified since the last write. All buffers are then marked as "free." Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=0E ³ Select disk - ÀÄÄÄÄÄÄÙ Set default disk drive to drive specified in DL. The default is the disk drive MS-DOS chooses for file access when a filename is specified without a drive designator. A successful call to this function returns the number of logical (not physical) drives in the system. DL = drive number ( 0 is drive A, 1 is drive B, and so on). Returns in AL: Number of logical drives in the system. ÚÄÄÄÄÄÄ¿ ³AH=19 ³ Get current disk - ÀÄÄÄÄÄÄÙ Returns in AL: Drive code for current disk drive (0 is drive A, 1 is drive B, and so on). ÚÄÄÄÄÄÄ¿ ³AH=1B ³ Get default drive data - ÀÄÄÄÄÄÄÙ Returns in AL: Number of sectors per cluster (allocation unit); in CX: Number of bytes per sector; in DX: Number of clusters; in DS:BX: Address of FAT identification byte; in AL: FFh if function is not successful. ÚÄÄÄÄÄÄ¿ ³AH=1C ³ Get drive data - ÀÄÄÄÄÄÄÙ DL = drive code (0=default drive, 1=drive A, 2=drive B, and so on). Returns in AL: Number of sectors per cluster (allocation unit); in CX: Number of bytes per sector in DX: Number of clusters in DS:BX: Address of FAT identification byte; in AL: FFh if function is not successful. ÚÄÄÄÄÄÄ¿ ³AH=2E ³ Set/reset verify flag - ÀÄÄÄÄÄÄÙ Turns internal MS-DOS verify flag on or off. AL = 00 to turn verify off; = 01 to turn verify on; DL = 00 (MS-DOS versions 1.x and 2.x only). Returns nothing. COWARDS Page GA-3 ÚÄÄÄÄÄÄ¿ ³AH=36 ³ Get disk free space - ÀÄÄÄÄÄÄÙ Returns disk storage information for the specified drive. DL = drive (0=default drive, 1=drive A, 2=drive B, and so on). Returns in AX: Number of sectors per cluster in BX: Number of clusters available in CX: Number of bytes per sector in DX: Number of clusters on drive in AX: FFFFh if invalid drive number in DL. ÚÄÄÄÄÄÄ¿ ³AH=54 ³ Get verify flag - ÀÄÄÄÄÄÄÙ Returns current value of MS-DOS verify flag. Returns in AL: 00 if verify is off; 01 if verify is on. ---------------------------------------------------------------------------- 4. File management - ÚÄÄÄÄÄÄ¿ ³AH=0F ³ Open file with FCB - ÀÄÄÄÄÄÄÙ Opens file named in FCB pointed to by DS:DX. DS:DX = address of an unopened FCB. Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=10 ³ Close file with FCB - ÀÄÄÄÄÄÄÙ Flushes file related information to disk, closes file named in FCB pointed to by DS:DX, and updates the file's directory entry. DS:DX = Address of previously opened FCB. Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=11 ³ Find first file - ÀÄÄÄÄÄÄÙ Searches current directory for the first file that matches a specified name and extension. DS:DX = Address of unopened FCB. Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=12 ³ Find next file - ÀÄÄÄÄÄÄÙ Searches current directory for the next file that matches a specified filename and extension. Assumes a previous successful call to function 11h with the same FCB. DS:DX = Address of search FCB. Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=13 ³ Delete file (Allows wildcard characters) - ÀÄÄÄÄÄÄÙ Deletes all files matching a specified name and extension from the current directory. DS:DX = Address of an unopened FCB. Returns in AL: 00 if function is successful; FFh otherwise. COWARDS Page GA-4 ÚÄÄÄÄÄÄ¿ ³AH=16 ³ Create file with FCB - ÀÄÄÄÄÄÄÙ Creates a directory entry in the current directory for a specified file, and opens the file for use. If the file already exists, it is opened and truncated to zero length. DS:DX = Address of an unopened FCB. Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=17 ³ Rename file - ÀÄÄÄÄÄÄÙ Renames one or more files in the current directory. DS:DX = Address of modified FCB in the following format: Bytes Contents ~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 00 Drive number 01-08 Old filename (padded with spaces if needed) 09-0Bh Old file extension (padded ... if needed) 0C-10h Zeroed out 11-18h New filename (padded ... if needed) 19-1Bh New file extension (padded ... if needed) 1C-24h Zeroed out Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=1A ³ Set DTA address - ÀÄÄÄÄÄÄÙ Specifies location of DTA to be used for FCB disk I/O operations. DS:DX = Address of DTA. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=23 ³ Get file size - ÀÄÄÄÄÄÄÙ Searches the current directory for a specified file and returns the size of the file in records. DS:DX = Address of unopened FCB with record size field set appropriately. Returns in AL: 00 if function is successful; FFh otherwise. ÚÄÄÄÄÄÄ¿ ³AH=2F ³ Get DTA address - ÀÄÄÄÄÄÄÙ Returns the current DTA address. Returns in ES:BX: Address of current DTA address. COWARDS Page GA-5 ÚÄÄÄÄÄÄ¿ ³AH=3C ³ Create file with handle - ÀÄÄÄÄÄÄÙ Creates a file, assigns it the attributes specified, and returns a 16-bit handle for the file. If the named file already exists, function 3Ch opens it and truncates it to zero length. CX = attribute; DS:DX = Address of ASCIIZ pathname. Returns in AX: Handle number if function is successful; 03 if path not found; 04 if too many open files; 05 if access denied; Returns in carry flag: Clear if successful; set otherwise. ÚÄÄÄÄÄÄ¿ ³AH=3D ³ Open file with handle - ÀÄÄÄÄÄÄÙ Opens the specified file and returns a 16-bit handle number for subsequent access to the file. DS:DX = Address of ASCIIZ pathname; AL = file access code for MS-DOS 3.x: Bit 7 = 0 Child process inherits file = 1 Child process doesn't inherit Bits 6-4 = 000b, Compatibility mode = 001b, Deny read/write access = 010b, Deny write access = 011b, Deny read access = 100b, Deny none Bits 2-0 = 000b, Read-only = 001b, Write-only = 010b, Read/write Returns in AX: Handle number if function is successful; 02 if file not found; 03 if path not found; 04 if too many open files; 05 if access denied; 0Ch if invalid access code. Returns in carry flag: Clear if successful; set otherwise. ÚÄÄÄÄÄÄ¿ ³AH=3E ³ Close file - ÀÄÄÄÄÄÄÙ Closes the file referenced by the specified handle. BX = handle number Returns in carry flag: Clear if successful; set otherwise. Returns in AX: 06 if invalid handle number. ÚÄÄÄÄÄÄ¿ ³AH=41 ³ Delete file (Does not allow wildcard characters) - ÀÄÄÄÄÄÄÙ Deletes the directory entry of the specified file. DS:DX = Address of ASCIIZ pathname. Returns in carry flag: Clear if successful; set otherwise. Returns in AX: 02 if file not found; 03 if path not found; 05 if access denied. COWARDS Page GA-6 ÚÄÄÄÄÄÄ¿ ³AH=43 ³ Get/set file attributes - ÀÄÄÄÄÄÄÙ AH = 43h; DS:DX = Address of ASCIIZ pathname; AL = 00 to get attributes; 01 to set; CX = Attributes to set: Bit 0, read-only file; bit 1, hidden file; bit 2, system file; bit 5, archive. Returns in carry flag: Clear if successful; set otherwise; Returns in CX: attribute: Bit 0, read-only file; bit 1, hidden file; bit 2, system file; bit 5, archive; Returns in AX: 01 if invalid function (AL not 00 or 01); 02 file not found; 03 path not found; 05 access denied. ÚÄÄÄÄÄÄ¿ ³AH=45 ³ Duplicate file handle - ÀÄÄÄÄÄÄÙ Obtains an additional handle for a currently open file or device. BX = Handle for open file or device. Returns in carry flag: Clear if successful; set otherwise; AX: New file handle if function is successful; otherwise 04 if too many open files, 06 if invalid handle. ÚÄÄÄÄÄÄ¿ ³AH=46 ³ Force duplicate file handle - ÀÄÄÄÄÄÄÙ Forces the open handle specified in CX to track the same file or device specified by the handle in BX. BX = open handle to be duplicated; DX = open handle to be forced. Returns in carry flag: Clear if successful; set otherwise; Returns in AX: 04 if too many open files; 06 if invalid handle. ÚÄÄÄÄÄÄ¿ ³AH=4E ³ Find first file - ÀÄÄÄÄÄÄÙ Searches specified directory for the first matching entry. CX = attribute word DS:DX = Address of ASCIIZ pathname. Returns in carry flag: Clear if successful; set otherwise. Returns in AX: 02 if file not found; 03 if path not found; 12h if no more files, no match found. Returns other information in DTA. ÚÄÄÄÄÄÄ¿ ³AH=4F ³ Find next file - ÀÄÄÄÄÄÄÙ Continues a search initiated by a previously successful call to function 4Eh. The search is based on the pathname and attributes specified in the call to function 4Eh and uses information left in the current DTA by the call to function 4Eh or by a preceding call to function 4Fh. DTA contains information from prior search. Returns in carry flag: Clear if successful; set otherwise. Returns in AX: 12h if no more files, no match found, or no previous call to function 4Eh. Returns other information in DTA. COWARDS Page GA-7 ÚÄÄÄÄÄÄ¿ ³AH=56 ³ Rename file - ÀÄÄÄÄÄÄÙ Renames a file and/or moves it to a new location in the hierarchical directory structure. DS:DX = address of existing ASCIIZ pathname for file; ES:DI = address of new ASCIIZ pathname for file. Returns in carry flag: Clear if successful; set otherwise. Returns in AX: 02 if file not fouind; 03 if path not found; 05 if access denied; 11h if not the same device. ÚÄÄÄÄÄÄ¿ ³AH=57 ³ Get/set date/time of file - ÀÄÄÄÄÄÄÙ Retrieves or sets the date and time of a file's directory entry. AH = 00 to get date and time; 01 to set date and time; BX = handle number; CX = new time; bits 0-4, number of seconds divided by 2; bits 5-10, minutes (0 thru 59); 11-15, hours (0 thru 23); DX = new date; bits 0-4, date of month; 5-8, month; 9-15, year minus 1980. Returns in carry flag: Clear if successful; set otherwise; Returns in CX (for AL=00): time file was last modified, format as above; Returns in DX (for AL=00): date file was last modified, format as above. Returns in AX: 01 if invalid function (AL not 00 or 01); 06 if invalid handle. ÚÄÄÄÄÄÄ¿ ³AH=5A ³ Create temporary file - ÀÄÄÄÄÄÄÙ Uses the system clock to create a unique filename, appends the filename to the specified path, opens the temporary file, and returns a file handle that can be used for subsequent file operations. CX = 00 for normal file; 01 for read-only file; 02 for hidden file; 04 for system file; DS:DX = Address of ASCIIZ path, ending with backslash character [\] and followed by 13. bytes of memory to receive the generated filename. Returns in carry flag: Clear if successful; set otherwise; Returns in AX: handle; 03 if path not found; 04 if too many open files; 05 if access denied; Returns in DS:DX: address of full pathname for temporary file; ÚÄÄÄÄÄÄ¿ ³AH=5B ³ Create new file - ÀÄÄÄÄÄÄÙ Creates new file with the specified pathname. Operates like function 3Ch (Create file with handle) but fails if pathname references a file that already exists. CX = 00 if normal file; 01 if read-only file; 02 if hidden file; 04 if system file; DS:DX = address of ASCIIZ pathname; Returns in carry flag: Clear if successful; set otherwise; Returns in AX: handle if function is successful; 03 if path not found; 04 if too many open files; 05 if access denied; 50h if file already exists. COWARDS Page GA-8 ÚÄÄÄÄÄÄ¿ ³AH=5C ³ Lock/unlock file region - ÀÄÄÄÄÄÄÙ Enable a process running in a networking or multitasking environment to lock or unlock a range of bytes in an open file. AL = 00 to lock region; 01 to unlock region; BX = handle CX:DX = 4-byte integer specifying beginning of region to be locked or unlocked (offset in bytes from beginning of file); SI:DI = 4-byte integer specifying length of region, in bytes. Returns in carry flag: Clear if successful; set otherwise; Returns in AX: 01 if invalid function; 06 if invalid handle; 21h if lock violation; 24h if sharing buffer exceeded. ---------------------------------------------------------------------------- 5. Information management - ÚÄÄÄÄÄÄ¿ ³AH=14 ³ Sequential read - ÀÄÄÄÄÄÄÙ Reads the next sequential block of data from a file and places the data in the current DTA. DS:DX = address of a previously opened FCB. Returns in AL: 00 if successful, DTA contains data read from file; 01 if end of file encountered, no data in record; 02 if DTA too small, read canceled; 03 if end of file, partial record read, DTA contains data read from file. ÚÄÄÄÄÄÄ¿ ³AH=15 ³ Sequential write - ÀÄÄÄÄÄÄÙ Writes the next sequential block of data from the DTA to a specified file. DS:DX = address of a previously opened FCB; DTA contains data to write. Returns in AL: 00 if block written successfully; 01 if disk full, write canceled; 02 if DTA too small, write canceled. ÚÄÄÄÄÄÄ¿ ³AH=21 ³ Random read - ÀÄÄÄÄÄÄÙ Reads a selected record from disk into memory. DS:DX = address of previously opened FCB. Returns in AL: 00 if record read successfully; 01 if end of file, no record read; 02 if DTA too small, read canceled; 03 if end of file, partial record transferred. Returns in DTA: data read from file. ÚÄÄÄÄÄÄ¿ ³AH=22 ³ Random write - ÀÄÄÄÄÄÄÙ Writes data from current DTA to a specified record location in a file. DS:DX = address of previously opened FCB; DTA contains data to write. Returns in AL: 00 if record written successfully; 01 if disk full; 02 if DTA too small, write cancelled. COWARDS Page GA-9 ÚÄÄÄÄÄÄ¿ ³AH=24 ³ Set relative record - ÀÄÄÄÄÄÄÙ Sets relative record field of an FCB to match the file position indicated by the current block field and current record field of the same FCB. DS:DX = address of previously opened FCB. Returns in AL: 00; Relative record filed is modified in FCB. ÚÄÄÄÄÄÄ¿ ³AH=27 ³ Random block read - ÀÄÄÄÄÄÄÙ Reads one or more records into memory, placing the records in the current DTA. CX = number of records to read; DS:DX = address of previously opened FCB. Returns in AL: 00 if read successful; 01 if end of file, no record read; 02 if DTA too small, no record read; 03 if end of file, partial record read. Returns in CX: number of records read if AL is 00 or 03; Returns in DTA: data read from file. ÚÄÄÄÄÄÄ¿ ³AH=28 ³ Random block write - ÀÄÄÄÄÄÄÙ Writes one or more records from the current DTA. CX = number of records to write; DS:DX = address of previously opened FCB; DTA contains data to write. Returns in AL: 00 if write successful; 01 if disk full; 02 if DTA too small, write canceled. CX: number of records written if AL is 00 or 01. ÚÄÄÄÄÄÄ¿ ³AH=3F ³ Read file or device - ÀÄÄÄÄÄÄÙ Reads from the file or device referenced by a handle. BX = handle number; handle numbers for standard devices are shown in the table: Standard device Handle ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~ Standard input (stdin, CON) 0 Standard output (stdout, CON) 1 Standard error (stderr, CON) 2 Standard auxiliary (stdaux, AUX) 3 Standard printer (stdprn, PRN) 4 CX = number of bytes to read; DS:DX = address of data buffer. Returns in carry flag: clear if successful; set otherwise; Returns in AX: number of bytes read from file; 05 if access denied; 06 if invalid handle; Returns in DS:DX: address of data read from file; COWARDS Page GA-10 ÚÄÄÄÄÄÄ¿ ³AH=40 ³ Write file or device - ÀÄÄÄÄÄÄÙ Writes the specified number of bytes to a file or device referenced by a handle. BX = handle; CX = number of bytes to write; DS:DX = address of data buffer. Returns in carry flag: clear if successful; set otherwise; Returns in AX: number of bytes written to file or device; 05 if access denied; 06 if invalid handle; ÚÄÄÄÄÄÄ¿ ³AH=42 ³ Move file pointer - ÀÄÄÄÄÄÄÙ Sets the file pointer (for the next read/write operation) for the file associated with the specified handle. AL = 00 if byte offset from beginning of file; 01 if byte offset from current location of file pointer; 02 if byte offset from end of file; BX = handle number; CX:DX = offset value to move pointer, where CX is MSH and DX is LSH of a doubleword value. Returns in carry flag: clear if successful; set otherwise; Returns in DX:AX if successful: new file pointer position (absolute byte offset from beginning of file); Returns in AX if not successful: 01 invalid function; 06 invalid handle. ---------------------------------------------------------------------------- 6. Directory management - ÚÄÄÄÄÄÄ¿ ³AH=39 ³ Create directory - ÀÄÄÄÄÄÄÙ Creates a subdirectory using the specified path. DS:DX = address of ASCIIZ path. Returns in carry flag: clear if successful; set otherwise; Returns in AX: 03 path not found; 05 access denied. ÚÄÄÄÄÄÄ¿ ³AH=3A ³ Remove directory - ÀÄÄÄÄÄÄÙ Deletes the specified directory. DS:DX = address of ASCIIZ path. Returns in carry flag: clear if successful; set otherwise; Returns in AX: 03 if path not found; 05 if access denied; 10h if current directory was specified. ÚÄÄÄÄÄÄ¿ ³AH=3B ³ Change current directory - ÀÄÄÄÄÄÄÙ Changes the current directory to the specified path. DS:DX = address of ASCIIZ path. Returns in carry flag: clear if successful; set otherwise; Returns in AX: 03 if path not found. COWARDS Page GA-11 ÚÄÄÄÄÄÄ¿ ³AH=47 ³ Get current directory - ÀÄÄÄÄÄÄÙ Returns the path - excluding the drive and leading backslash - of the current directory for the specified drive. DL = drive number (0=default drive, 1=drive A, and so on); DS:SI = address of 64-byte buffer. Returns in carry flag: clear if successful; set otherwise; Buffer is filled in with ASCIIZ pathname if successful; Returns in AX: 0Fh if invalid drive. ---------------------------------------------------------------------------- 7. Process management - ÚÄÄÄÄÄÄ¿ ³AH=00 ³ Terminate process - ÀÄÄÄÄÄÄÙ Obsolete. Use INT 21 AH=31 (Terminate and stay resident) or INT 21 AH=4C (Terminate process with return code) instead. ÚÄÄÄÄÄÄ¿ ³AH=31 ³ Terminate and stay resident - ÀÄÄÄÄÄÄÙ Terminates a program and returns control to the parent process (usually COMMAND.COM) but keeps the terminated program resident in memory. AL = return code DX = number of paragraphs of memory to be reserved for current process. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=4B ³ Load and execute program (EXEC) - ÀÄÄÄÄÄÄÙ Loads a program file into memory and optional executed the program. This function can also be used to load a program overlay. AL = 00 to load and execute program; 03 to load overlay; DS:DX = address of ASCIIZ pathname for an executable program file; ES:BX = address of parameter block. Returns in carry flag: clear if successful; set otherwise; Returns in AX: 01 if invalid function (AL not 00 or 03); 02 if file not found; 03 if path not found; 05 if access denied; 08 if insufficient memory; 0Ah if bad environment; 0Bh if bad format (for AL=00 only). Note: With MS-DOS 2.x, all registers except CS and IP can be destroyed; with 3.x, registers are preserved. ÚÄÄÄÄÄÄ¿ ³AH=4C ³ Terminate process with return code - ÀÄÄÄÄÄÄÙ Terminates the current process with a return code and returns control to the calling (parent) process. AL = return code. Returns nothing. COWARDS Page GA-12 ÚÄÄÄÄÄÄ¿ ³AH=4D ³ Get return code of child process - ÀÄÄÄÄÄÄÙ Retrieves the return code of a child process that was invoked with function 4Bh (Load and execute program) and terminated with either function 31h (Terminate and stay resident) or function 4Ch (Terminate process with return code). Returns in AH: 00 if normal termination (INT 20h or INT 21h AH=00 or AH=4Ch); 01 if terminated by Ctrl-C; 02 if terminated by critical error handler (for example, user responded Abort to Abort, Retry,Ignore? prompt); 03 if terminated and stayed resident (INT 27h or INT 21h AH=31h); Returns in AL: return code passed by child process; 00 if terminated in INT 20h AH=00 or INT 27h. ÚÄÄÄÄÄÄ¿ ³AH=59 ³ Get extended error information - ÀÄÄÄÄÄÄÙ Returns extended error information - including a suggested response - for the function call immediately preceding it. BX = 00. Returns in AX: extended error code; Returns in BH: error class; Returns in BL: suggested action; Returns in CL: location of error. ---------------------------------------------------------------------------- 8. Memory management - ÚÄÄÄÄÄÄ¿ ³AH=48 ³ Allocate memory block - ÀÄÄÄÄÄÄÙ Allocates a block of memory - in paragraphs - to the requesting process. BX = number of paragraphs to allocate. Returns in carry flag: clear if successful; set otherwise; Returns in AX: segment address of base of allocated block; 07 if memory control blocks damaged; 08 if insufficient memory to allocate as requested; Returns in BX: size of larges available block (paragraphs). ÚÄÄÄÄÄÄ¿ ³AH=49 ³ Free memory block - ÀÄÄÄÄÄÄÙ Releases a block of memory previously allocated with function 48h (Allocate memory block)l ES = segment address of memory block to release. Returns in carry flag: clear if successful; set otherwise; Returns in AX: 07 if memory control blocks damaged; 09 if incorrect memory segment specified. COWARDS Page GA-13 ÚÄÄÄÄÄÄ¿ ³AH=4A ³ Resize memory block - ÀÄÄÄÄÄÄÙ Adjusts the size of a previously allocated block of memory. BX = new size of memory block, in paragraphs; ES = segment address of previously allocated memory block. Returns in carry flag; clear if successful; set otherwise Returns in AX: 07 if memory control blocks damaged; 08 if insufficient memory to allocate as requested; 09 if incorrect memory segment specified; Returns in BX: maximum number of paragraphs available (if an increase was requested). ÚÄÄÄÄÄÄ¿ ³AH=58 ³ Get/set allocation strategy - ÀÄÄÄÄÄÄÙ Retrieves or sets the method MS-DOS uses to allocate memory blocks for a process that issues a memory allocation request. AL = 00 to get allocation strategy; 01 to set allocation strategy; BX (if AL=01): 00 to use first (lowest available) block that fits; 01 to use block that fits best; 02 to use last (highest available) block that fits. Returns in carry flag: clear if successful; set otherwise; Returns in AX (if successful with AL=00 on call): 00 if first fit; 01 if best fit; 02 if last fit; Returns in AX (if unsuccessful): 01, invalid function called in AL. ---------------------------------------------------------------------------- 9. Miscellaneous system management - ³AH=25 ³ Set interrupt vector - ³AH=26 ³ Create new PSP - ³AH=29 ³ Parse filename - ÚÄÄÄÄÄÄ¿ ³AH=2A ³ Get date - ÀÄÄÄÄÄÄÙ Returns current system date - year, month, day, dayo'week - in binary form. Returns in AL: Dayo'week (0=Sunday, and so on); Returns in CX: Year (1980 thru 2099); Returns in DH: Month number; Returns in DL: Day of month. ³AH=2B ³ Set date - ÚÄÄÄÄÄÄ¿ ³AH=2C ³ Get time - ÀÄÄÄÄÄÄÙ Reports the current system time - hours (based on 24-hour clock), minutes, seconds, and hundredths of a second - in binary form. Returns in CH: hour; Returns in CL: minute; Returns in DH: second; Returns in DL: hundredths of second. COWARDS Page GA-14 ³AH=2D ³ Set time - ³AH=30 ³ Get MS-DOS version number - ³AH=33 ³ Get/set Control-C check flag - ³AH=34 ³ Return address of InDOS flag - ³AH=35 ³ Get interrupt vector - ³AH=38 ³ Get/set current country - ³AH=44 ³ IOCTL - ³AH=5E ³ Network machine name/printer setup: Set printer setup - ³AH=5F ³ Get/make assign-list entry - ÚÄÄÄÄÄÄ¿ ³AH=62 ³ Get PSP address - ÀÄÄÄÄÄÄÙ Gets the segment address of the PSP for the current process. Returns in BX: segment address of PSP for current process. ³AH=63 ³ Get lead byte table - Subfunctions support 2-byte characters such as Japanese kanji. Note: Available only in MS-DOS 2.25. **************************************************************************** Single-function service ----------------------- ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 20 ³ Vector at 80ha ÀÄÄÄÄÄÄÄÄÄÄÙ Terminate program - Releases memory occupied by the program and returns control to the operating system. CS = segment address PSP. Returns nothing. Note: Instead of INT 20, the preferred methods of program termination use INT 21 AH=31 (Terminate and stay resident), and INT 21 AH=4C (Terminate process with return code). See section 7, Process management. COWARDS Page GA-15 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 22,23,24 ³ Vectors at 88,8C,9Eha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ INT 22: Terminate routine address - This interrupt should never be called directly. INT 23: Control-C handler address - This interrupt should never be called directly. INT 24: Critical error handler address - This interrupt should never be called directly. ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 25 ³ Vector at 94ha ÀÄÄÄÄÄÄÄÄÄÄÙ Absolute disk read - Provides direct linkage to the BSlDOS BIOS module to read data from a logical disk sector into a specified memory location. AL = drive number (0=drive A, 1=drive B, and so on); CX = number of sectors to read; DX = starting relative (logical) sector number; DS:BX = address of DTA. Returns in carry flag: clear if successful; set otherwise; Returns in AX: error code. ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 26 ³ Vector at 98ha ÀÄÄÄÄÄÄÄÄÄÄÙ Absolute disk write - Provides direct linkage to the MS-DOS BIOS module to write data from a specified memory buffer to a logical disk sector. AL = drive number (0=drive A, 1=drive B, and so on); CX = number of sectors to write; DX = starting relative (logical) sector number DS:BX = address of DTA. Returns in carry flag: clear if successful; set otherwise; Returns in AX: error code. ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 27 ³ Vector at 9Cha ÀÄÄÄÄÄÄÄÄÄÄÙ Terminate and stay resident (TSR) - Obsolete. Use INT 21 AH=31 instead. See section 7, Process management. COWARDS Page GA-16 ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 2F ³ Vector at BCha ÀÄÄÄÄÄÄÄÄÄÄÙ Multiplex interrupt - Submits a file to the print spooler, removes a file from the print spooler's queue of pending files, or obtains the status of the printer. Other values for AH are used by various MS-DOS extensions, such as APPEND. AH = 01 for print spooler call; AL = 00 to get installed status; 01 to submit file to be printed; 02 to remove file from print queue; 03 to cancel all files in queue; 04 to hold print jobs for status read; 05 to end hold for status read; DS:DX = address of packet address (if AL=01); = address of ASCIIZ file specification (if AL=02). Returns in carry flag: clear if successful; set otherwise; Returns in AL (if AL=00 when called): 00 if not installed, okay to install; 01 if not instally, not okay to install; FFh if installed; Returns in DX (if AL=04 when called): error count; Returns in DS:SI (if AL=04 when called): address of print queue; Returns in AX: 01 if function invalid; 02 if file not found; 03 if path not found. COWARD.GB 13 Jun 1992/WK24/Sat Assembly Language Programming for Cowards Copyright Homer B. Tilton 1990-91-92 Appendix GB. More on BIOS interrupts Multi-function service ---------------------- Among the BIOS interrupts there are the following. Interrupt and function numbers are hexadecimal. To call a function, place the function number in register AH. These BIOS interrupts do not all work on all PC, XT, and AT models - nor with all monitor driver cards (graphics adapters). For unfamiliar terms and abbreviations, see glossary at end of this appendix. ---------------------------------------------------------------------------- 1. Keyboard service (XT and AT except as noted) - ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 09 ³ Vector at 24ha ÀÄÄÄÄÄÄÄÄÄÄÙ Keyboard ISR - This ISR is programmed to interpret certain predefined key combinations as requests for internal functions. These are: Ctrl-Alt-Del - SYSTEM RESET Ctrl-Break or Ctrl-ScrollLock - BREAK Ctrl-NumLock - PAUSE Shift-PrintScreen - PRINT SCREEN SysReq - SYSTEM REQUEST (AT only) Note: INT 09 calls INT 15 function 4F, Keyboard intercept function, each time a key is pressed. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 16 functions ³ Vector at 58ha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄ¿ ³AH=00 ³ Read keyboard input - ÀÄÄÄÄÄÄÙ Returns all standard 83-key/84-key keyboard character codes as is; Adjusts 101-key deyboard duplicate keyboard characters so that they take on the same two-byte key code as their 83-key/84-key counterparts; and destroys any 101-key character codes not compatible with 83-key/84-key keyboards. ÚÄÄÄÄÄÄ¿ ³AH=01 ³ Return keyboard status - ÀÄÄÄÄÄÄÙ Allows programs to check for keyboard input while continuing to run if there is none. Returns in AH: scan code or character ID for special character; Returns in AL: ASCII code or other translation of character; Returns in zero flag: 0 (NZ) if character is ready; 1 (ZR) otherwise. COWARDS Page GB-2 ÚÄÄÄÄÄÄ¿ ³AH=02 ³ Return shift flag status - ÀÄÄÄÄÄÄÙ Returns the current shift status from the keyboard shift flags byte (40:17h). Returns in AL: bit 7=1, insert actice; bit 6=1, CapsLock active; bit 5=1, NumLock active; bit 4=1, ScrollLock active; bit 3=1, Alt pressed; bit 2=1, Ctrl pressed; bit 1=1, left Shift pressed; bit 0=1, right Shift pressed. Note: in PC/AT and compatible systems, the LED status is not updated. ÚÄÄÄÄÄÄ¿ ³AH=03 ³ Set typematic rate and delay - ÀÄÄÄÄÄÄÙ Changes the typematic rate and the delay before beginning typematic action. AL = 05 BH = delay value in milliseconds (ms); with 00=250ms, 01=500ms, 02=750ms, 03=1000ms; BL = typematic rate in characters per second (cps); with 00=30.0cps, 01=26.7cps, ... 1Eh=2.1cps, 1Fh=2.0cps. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=05 ³ Store key data - ÀÄÄÄÄÄÄÙ Stores program-generated data into the keyboard buffer just as if a key were pressed. The buffer pointer (40:1Ch) is adjusted to point to the next available location in the keyboard buffer. CH = scan code; CL = ASCII character. Returns in AL: 00 if successful; 01 if keyboard buffer full. Note: This function is only supported by later version XT BIOS's. ÚÄÄÄÄÄÄ¿ ³AH=10 ³ Read extended keyboard input - ÀÄÄÄÄÄÄÙ Read the next two-byte character code in the keyboard buffer and returns the value in AX. It is designed for use with the 101/102-key keyboard. Unlike function 00, this function does not modify character codes for 84- key keyboard compatibility. Returns in AH: scan code or character ID for special character; Returns in AL: ASCII code or other translation of character. Note: This function is only supported by later version XT BIOS's. ÚÄÄÄÄÄÄ¿ ³AH=11 ³ Return extended keyboard status - ÀÄÄÄÄÄÄÙ Like function 01, except it returns unique scan codes for all keys on the 101/102-key keyboard. Returns in AH (if zero flag is set (=1,ZR)): scan code of character ID for special character; Returns in AL (if zero flag is set): ASCII code or other translation of character ID; Returns in zero flag: 0 if no key in buffer; 1 if key waiting. Note: This function is only supported by later version XT BIOS's. COWARDS Page GB-3 ÚÄÄÄÄÄÄ¿ ³AH=12 ³ Return extended shift flags status - ÀÄÄÄÄÄÄÙ Similar to function 02, except it returns information on the shift keys provided on the 101/102-key keyboard. It also returns an additional byte of flags in the AH register. Returns in AH: bit 7=1, SysReq pressed; bit 6=1, CapsLock active; bit 5=1 NumLock active; bit 4=1 ScollLock active; bit 3=1, right Alt active; bit 2=1 left Alt active; bit 0=1, left Ctrl active; Returns in AL: bit 7=1, insert active; bit 6=1, CapsLock active; bit 5=1, NumLock active; bit 4=1, ScrollLock active; bit 3=1, Alt pressed; but 2=1, Ctrl pressed; bit 1=1, left Shift pressed; bit 0=1, right Shift pressed. Note: This function is only supported by later version XT BIOS's. ---------------------------------------------------------------------------- 2. Video service (MDA,CGA,EGA,MCGA,VGA except as noted) - ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 10 functions ³ Vector at 40ha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄ¿ ³AH=00 ³ Set video mode - ÀÄÄÄÄÄÄÙ Sets video mode registers for operation in any supported mode. Selects the active mode if more than one is installed, clears the screen, positions the cursor at 0,0 and resets the color palette to default color values. AL = video mode. Returns in AL: video mode. ÚÄÄÄÄÄÄ¿ ³AH=01 ³ Set text mode cursor size - ÀÄÄÄÄÄÄÙ Sets the size of the cursor that appears in text modes. Cursor size and location within the character box is determined by the starting a ending scan lines indicated in bits 4 thru 0 of registers CH and CL, respectively. CH = bits 7 and 6 = 00; bit 5 = hide cursor; bits 4 thru 0 set top scan line; CL = bits 6 and 5 = show cursor; bits 4 thru 0 set lower scan line. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=02 ³ Set cursor position - ÀÄÄÄÄÄÄÙ Sets cursor position for the display page in BL. BH = display page number; DH = row (0 is top row); DL = column (0 is leftmost column). Returns in AX: 00 COWARDS Page GB-4 ÚÄÄÄÄÄÄ¿ ³AH=03 ³ Read cursor position - ÀÄÄÄÄÄÄÙ Reads cursor position for the given video page. BH = display page number. Returns in AX: 00; Returns in CH: starting cursor scan line; Returns in CL: ending cursor scan line; Returns in DH: row number; Returns in DL: column number. ÚÄÄÄÄÄÄ¿ ³AH=04 ³ Read light pen position (CGA,EGA) - ÀÄÄÄÄÄÄÙ Reads light pen's status and position. ÚÄÄÄÄÄÄ¿ ³AH=05 ³ Select new video page - ÀÄÄÄÄÄÄÙ Sets the active page for the video mode selected. AL = new page number. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=06 ³ Scroll current page up - ÀÄÄÄÄÄÄÙ Scrolls page up. AL = 00 blanks; otherwise, scroll distance in units of character rows; BH = sttribute to use on blanked lines; CH = top row of scroll window; CL = left column of scroll window; DH = bottom row of scroll window; DL = right column of scroll window. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=07 ³ Scroll current page down - ÀÄÄÄÄÄÄÙ Scrolls page down. AL = 00 blanks; otherwise, scroll distance in units of character rows; BH = sttribute to use on blanked lines; CH = top row of scroll window; CL = left column of scroll window; DH = bottom row of scroll window; DL = right column of scroll window. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=08 ³ Read character/attribute from screen - ÀÄÄÄÄÄÄÙ Read character at the current cursor location. For text modes, the attribute is also returned. BH = display page. Returns in AH: attribute (text modes only); Returns in AL: character read. COWARDS Page GB-5 ÚÄÄÄÄÄÄ¿ ³AH=09 ³ Write character/attribute to screen - ÀÄÄÄÄÄÄÙ Writes character to the screen starting at the current cursor location for as many times as indicated in the CX register. AL = ASCII character to write; BH = display page; BL = character attribute (text modes); CX = repeat count. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=0A ³ Write character only to screen - ÀÄÄÄÄÄÄÙ Like AH=09 except that for text modes the attribute bytes remain unchanged. Use only in text modes. AH = 0A; AL = character to write; BH = display page; CX = repeat count. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=0B ³ Set color palette (CGA,EGA,MCGA,VGA) - ÀÄÄÄÄÄÄÙ Selects color for medium resolution graphics modes. ÚÄÄÄÄÄÄ¿ ³AH=0C ³ Write pixel (CGA,EGA,MCGA,VGA) - ÀÄÄÄÄÄÄÙ Writes to video memory the pixel specified by row and column number in DX and CX. AL = color BH = page number CX = pixel column number; DX = pixel row number. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=0D ³ Read pixel (CGA,EGA,MCGA,VGA) - ÀÄÄÄÄÄÄÙ Returns value of addressed pixel to the low order bits of the AL register. BH = page number; CX = column number; DX = row number; Returns in AL: color value of pixel read. ÚÄÄÄÄÄÄ¿ ³AH=0E ³ Write teletype to active page - ÀÄÄÄÄÄÄÙ Makes the display appear as a serial terminal. AL = character to write; BL = foreground color (graphics modes only); BH = active page. Returns nothing. COWARDS Page GB-6 ÚÄÄÄÄÄÄ¿ ³AH=0F ³ Return video status - ÀÄÄÄÄÄÄÙ Returns current display mode information. Returns in AH: number of columns on screen; Returns is BL: current mode; Returns in BH: active display page number. ÚÄÄÄÄÄÄ¿ ³AH=10 ³ Set palette/color registers (EGA,VGA) - ÀÄÄÄÄÄÄÙ Controls operation on the color palette registers in EGA/VGA video controllers. ÚÄÄÄÄÄÄ¿ ³AH=11 ³ Load character generator (EGA,VGA) - ÀÄÄÄÄÄÄÙ Permits loading and/or enabling of text mode and graphics mode character generators (fonts). Consists of 15 subfunctions (number in AL). AL = 00,01,02,03,04,10,11,12,14: Allow user to load character font sets into memory map 2 for BIOS access. AL = 20,21,22,23,24: Deal with fonts for graphics modes. AL = 30: Returns font pointer information. ÚÄÄÄÄÄÄ¿ ³AH=12 ³ Alternate select (EGA,VGA) - ÀÄÄÄÄÄÄÙ Allows user to enable or disable video mode defaults. Consists of nine subfunctions (number in BL), seven of which are supported on VGA adapter only. BL = 10: Returns configuration information. BL = 20: switches to alternate print-screen routine. BL = 30 thru 36: VGA. ÚÄÄÄÄÄÄ¿ ³AH=13 ³ Write string - ÀÄÄÄÄÄÄÙ Operates similarly to AH=0E, except an entire string is handled with each call. AH = 13; ES:BP = pointer to start of string; BH = page number (for text modes); BL = attribute for characters (graphics modes); CX = length of string, less attributes; DX = starting cursor position (DH=row, DL=column); AL (all modes) = 00, cursor not moved; 01, cursor moved; AL (text mode only) = 02, cursor not moved; 03, cursor moved. Returns nothing. ÚÄÄÄÄÄÄ¿ ³AH=1A ³ Read/write display combination code (PS/2) - ÀÄÄÄÄÄÄÙ Consists of two subfunctions (numbers in AL). AL=00 reads display combination code; AL=01 writes display combination code. COWARDS Page GB-7 ÚÄÄÄÄÄÄ¿ ³AH=1B ³ Return functionality/state information (PS/2) - ÀÄÄÄÄÄÄÙ Outputs a table describing the current state of the video hardware. BX = 0000; ES:DI = pointer to 40h byte buffer containing functionality/state information; Returns in AL: 1Bh if function successful; 00 otherwise. Returns in AH: 1B if function not successful. ÚÄÄÄÄÄÄ¿ ³AH=1C ³ Save/restore video state (PS/2 VGA) - ÀÄÄÄÄÄÄÙ Consists of three subfunctions (number in AL). AL=00 returns buffer size needed; AL=01 saves current video state; AL=02 restores current video state. ---------------------------------------------------------------------------- 3. Diskette service (XT and AT except as noted) - 4. Fixed disk service (XT and AT except as noted) - ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 13 functions ³ Vector at 4Cha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Diskette Fixed Disk ~~~~~~~~ ~~~~~~~~~~ ³AH=00 ³ Reset disk system - X X ³AH=01 ³ Read disk status - X X ³AH=02 ³ Read sectors - X X ³AH=03 ³ Write sectors - X X ³AH=04 ³ Verify sectors - X X ³AH=05 ³ Format disk track/cylinder - X X ³AH=06 ³ Format bad fixed-disk track - XT only ³AH=07 ³ Format fixed disk - XT only ³AH=08 ³ Read drive parameters - AT only X ³AH=09 ³ Initialize drive parameters - X ³AH=0A ³ Read long sectors - X ³AH=0B ³ Write long sectors - X COWARDS Page GB-8 Diskette Fixed Disk ~~~~~~~~ ~~~~~~~~~~ ³AH=0C ³ Seek to cylinder - X ³AH=0D ³ Alternate fixed disk reset - X ³AH=0E ³ Hardware interrupt - X Diagnostics 1: Read test buffer - XT only ³AH=0F ³ Diagnostics 2: Write test buffer - XT only ³AH=10 ³ Test for Drive Ready - X ³AH=11 ³ Recalibrate drive - X ³AH=12 ³ Controller RAM diagnostic - X ³AH=13 ³ Controller drive diagnostic - X ³AH=14 ³ Controller internal diagnostic - X ³AH=15 ³ Read drive/disk type - AT only X ³AH=16 ³ Detect diskette media change - AT only ³AH=17 ³ Set diskette type - AT only ³AH=18 ³ Set diskette media type for format - AT only ---------------------------------------------------------------------------- 5. Serial communications service (XT and AT) - ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 14 functions ³ Vector at 50ha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄ¿ ³AH=00 ³ Initialize serial communications port - ÀÄÄÄÄÄÄÙ Initializes the selected adapter card. AL = bits 7 thru 5: baud rate, where 000b=110 baud; 001b=150 baud;...110b=4800 baud; 111b=9600 baud; bits 4 and 3: parity, where 00b=none; 01b=odd; 10b=none; 11b=even; bit 2: 0 for one stop bit; 1 for two stop bits; bits 1 and 0: character size, where 10b=seven-bit characters; 11b=eight-bit characters; DX = serial port number, where 0=COM1, 1=COM2, and 2=COM3, and 3=COM4. Returns in AH: Line status; Returns in AL: Modem status. COWARDS Page GB-9 ÚÄÄÄÄÄÄ¿ ³AH=01 ³ Send character - ÀÄÄÄÄÄÄÙ Transmits character over communications line. AL = character; DX = serial port number (as defined in AH=00). Returns in AH: Line status register; Returns in AL: Character sent (unchanged). ÚÄÄÄÄÄÄ¿ ³AH=02 ³ Receive character - ÀÄÄÄÄÄÄÙ Receives character from the serial port. DX = serial port number (as defined in AH=00). Returns in AH: Line status register; Returns in AL: Character received. ÚÄÄÄÄÄÄ¿ ³AH=03 ³ Read serial port status - ÀÄÄÄÄÄÄÙ Returns the current modem status. DX = serial port number (as defined in AH=00). Returns in AH: Line status; Returns in AL: Modem status. ---------------------------------------------------------------------------- 6. System service (AT only except as noted) - ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 15 functions ³ Vector 54ha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³AH=00 ³ Turn cassette motor on (original PC only) - ³AH=01 ³ Turn cassette motor off (original PC only - ³AH=02 ³ Read cassette (original PC only) - ³AH=03 ³ Write to cassette (original PC only) - ³AH=4F ³ Keyboard intercept - Can be used to create alternate keyboard layouts and/or cause the system to ignore certain keystrokes. AL = scan code input by INT 09 ISR. Returns in AL: scan code; Returns in carry flag: clear (=0,NC) if keystroke unchanged; set (=1,CY) if keystroke changed (new scan code in AL). Note: This function is supported only by later versions of XT and AT BIOS's. COWARDS Page GB-10 ³AH=80 ³ Device open - ³AH=81 ³ Device close - ³AH=82 ³ Program termination - ³AH=83 ³ Set event wait interval - ³AH=84 ³ Joystick support - ³AH=85 ³ System Request key - ³AH=86 ³ Wait - ³AH=87 ³ Move block - ³AH=88 ³ Read extended memory size - ³AH=89 ³ Switch processor to protected mode - ³AH=90 ³ Device busy - ³AH=91 ³ Interrupt complete - ³AH=C0 ³ Return system configuration parameters - ---------------------------------------------------------------------------- 7. Parallel printer service (XT and AT) - ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 17 functions ³ Vector at 5Cha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄ¿ ³AH=00 ³ Print character - ÀÄÄÄÄÄÄÙ Prints a character. AL = character to print; DX = printer number (0=LPT1, 2=LPT3); Returns in AH: Printer status: bit 7=1, printer not busy; bit 6=1, acknowledgment from printer; bit 5=1, out of paper; bit 4=1, printer selected; bit 3=1, I/O error; bit 0=1, time out error. ÚÄÄÄÄÄÄ¿ ³AH=01 ³ Initialize printer - ÀÄÄÄÄÄÄÙ Initializes printer. DX = printer number (as defined in AH=00); Returns in AH: Printer status (as defined in AH=00). COWARDS Page GB-11 ÚÄÄÄÄÄÄ¿ ³AH=02 ³ Read printer status - ÀÄÄÄÄÄÄÙ Reads and returns status of printer. DX = printer number (as defined in AH=00); Returns in AH: printer status (as defined in AH=00). ---------------------------------------------------------------------------- 8. Time-of-day and date service (AT only except as noted) - ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 1A functions ³ Vector at 68ha ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ³AH=00 ³ Read system timer time counter - ³AH=01 ³ Set system timer time counter - ³AH=02 ³ Read real time clock time - ³AH=03 ³ Set real time clock time - ³AH=04 ³ Read real time clock date - ³AH=05 ³ Set real time clock date - ³AH=06 ³ Set real time clock alarm - ³AH=07 ³ Reset real time clock alarm - ³AH=80 ³ Set sound source (PCjr only) - ---------------------------------------------------------------------------- Single function service (XT and AT) - ----------------------- ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 05 ³ Vector at 14ha ÀÄÄÄÄÄÄÄÄÄÄÙ Print screen - Prints what is on the screen. No registers need be set. Returns nothing; registers are preserved. Note: INT 05 is usually invoked by the keyboard interrupt handler (INT 09) when the PrintScreen key is pressed; however, users may invoke INT 05 directly in software. COWARDS Page GB-12 ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 11 ³ Vector at 44ha ÀÄÄÄÄÄÄÄÄÄÄÙ Equipment list - Returns the contents of system RAM location 40:10h in AX. No registers need be set. Returns in AX: contents of system RAM location 40:10h, where Bits 15,14 = number of printer adapters; Bits 11-9 = number of RS-232-C asynchronous adapters; Bits 7,6 = number of diskette drives (if bit 0=1); Bits 5,4 = initial video mode: 00b=VGA/EGA/PGA; 01b=40x25 color; 10b=80x25 color; 11b=80x25 monochrome; Bit 2 = 1 if pointing device installed; Bit 1 = 1 if math coprocessor installed; Bit 0 = 1 if diskette available for boot. ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 12 ³ Vector at 48ha ÀÄÄÄÄÄÄÄÄÄÄÙ Memory size - Returns the contents of system RAM location 40:13h in AX. No registers need be set. Returns in AX: contents of location 40:13h in binary form. ÚÄÄÄÄÄÄÄÄÄÄ¿ ³ INT 19 ³ Vector at 64ha ÀÄÄÄÄÄÄÄÄÄÄÙ Bootstrap loader - Attempts to load the 512-byte boot sector code from either diskette or fixed disk to address 0:7C00h, transferring control to the code segment at that address. No registers need be set. Returns nothing; registers are preserved. Note: INT 19 is usually invoked by the POST routine at the end of all POST processing. Users are free to invoke INT 19 independently; this will not reset or reinitialize the system, but simply causes the system to reboot. **************************************************************************** Example ------- An example involving the use of INT 21 AH=25 (Set interrupt vector), INT 21 AH=1C (Get drive data), INT 21 AH=4C (Terminate process with return code), and INT 24 (Critical error handler address) follows. This is a practical program that will test drive A to see whether or not it is ready. COWARDS Page GB-13 Consider this small program, ISAREADY.COM presented in machine code by Kris Jamsa on page 258 of the July 1992 issue of PC/Computing magazine, presented here in fully annotated assembly language: MOV AX,2524 ;Prepare to execute INT 21 AH=25, "Set interrupt vector." ;AL=24 is the interrupt number whose vector we want to set. ;(INT 24 is "Critical error handler address.") MOV DX,0113 ;113 is the address of the "interrupt handler" i.e.,the ;offset of the routine that will "handle" the situation if ;INT 24 detects a "critical error." INT 21 ;Do INT 21 AH=25. ;We have now tapped into the critcal error handler so that if drive A ;is not ready, then instead of the error message we'd normally see, ; ; Not ready reading drive A / Abort, Retry, Fail? ; ;the DOS prompt will return and an error code will be placed in register AL. MOV AH,1C ;Prepare to execute INT 21 AH=1C, "Get drive data." MOV DL,01 ;Drive 01 is drive of interest. (That's drive A.) INT 21 ;Do it. ;If no error was detected during the execution of INT 21 ;AH=1C, the program drops thru to here; ;otherwise it branches to offset 113. MOV AX,4C00 ;Prepare to execute INT 21 AH=4C, with error code 0. INT 21 ;Do it. Exit to DOS. ;113 ;If drive A not ready, the following program steps are ;executed instead of those two immediately above. MOV AX,4C01 ;Prepare to execute INT 21 AH=4C with error code 1. INT 21 ;Do it. Exit to DOS. Thus if drive A is ready, error code 0 is returned; otherwise error code 1 is returned. You can test that with the batch IF ERRORLEVEL command. Glossary for Appendices GA and GB: ---------------------------------- ASCIIZ - ASCII zero, the null character with code 00 ASCIIZ path - Path terminated with a 00 byte B,b - Binary (base 2) number. An eight digit binary number corresponds to one byte (two hexadecimal digits). For example, the byte FA is given here in binary, showing the way the binary digits (bits) are numbered: Hexadecimal number: ÚÄÄFÄÄ¿ ÚÄÄAÄÄ¿ h Binary number: 1 1 1 1 1 0 1 0 b Bit --> 7 6 5 4 3 2 1 0 We say FAh = 1111 1010 b. COWARDS Page GB-14 BIOS - Basic input/output system D,d,.(dot) - Decimal (base 10) number DOS - Disk operating system DSR - Device service routine DTA - Disk transfer area FAT - File allocation table FCB - File control block H,h - Hexadecimal (base 16) number ha - Absolute hexadecimal address (in RAM) ID - Identification I/O - Input/output IOCTL - I/O control ISR - Interrupt status register LED - Light-emitting diode LSH - Least significant half MSH - Most significant half Paragraph - 16 bytes (eight words, or 32 hexadecimal digits, or one row in DEBUG's Dump display) POST - Power-on self test Program overlay - Section of a program that remains on disk until the program actually requires it. PSP - Program segment prefix Word - Two bytes Computer types (in order of historical introduction) - PC - Personal computer PCjr - PC junior XT - Extended technology AT - Advanced technology PS/2 - Personal system 2 Video display cards (in order of sophistication) - MDA - Monochrome display adapter HGA - Hercules graphics adapter CGA - Color graphics adapter EGA - Enhanced graphics adapter PGA - Professional graphics adapter MCGA - Multi-color graphics array (for use in PS/2) VGA - Video graphics array (for use in PS/2) COWARD.H 24 May 1992/WK22/Sun Assembly Language Programming for COwards Copyright Homer B. Tilton 1990-91-92 Appendix H. References 1. ISBN 0-673-38602-3 Abrash, M., ZEN OF ASSEMBLY LANGUAGE, Volume I; Scott, Foresman and Co., 1990. Has the most complete 8086/8088 instruction set description I've seen. 2. ISBN 0-8306-9484-6; Paperback ISBN: 0-8306-3484-3 Dorfman, L., STRUCTURED ASSEMBLY LANGUAGE, disk available; Windcrest Books, 1990. Contains many, many assembly-language procedures for the MASM assembler. These may need modification for use with the A86 assembler - especially macros. 3. ISBN 1-55615-174-8 Duncan, R. (General Editor), THE MS-DOS ENCYCLOPEDIA; Microsoft Press, 1988. See this volume for detailed descriptions of the DOS interrupts. 4. ISBN 0-673-38590-6 Duntemann, J., ASSEMBLY LANGUAGE FROM SQUARE ONE, with disk; Scott, Foresman and Co., 1990. Really from "square one"! The first half of this book is quite basic; and if you're just starting out, may be just what you need. Contains 8086/8088 instruction set description like Abrash, above; but not a complete set. 5. ISBN: none Isaacson, E., A86 MACRO ASSEMBLER/D86 DEBUGGER REFERENCE MANUAL with disk; Eric Isaacson Software, 416 E. University / Bloomington, IN 47401-4739, 1990. This is the printed instruction manual you get when you register your A86 software. A version of this comes on disk with the A86 shareware package. 6. ISBN 0-89588-487-9 Miller, A.R., DOS ASSEMBLY LANGUAGE PROGRAMMING; SYBEX, Inc., 1988. An interesting volume that has descriptions of some of the interrupts in addition to code snippets for doing this or that. An appendix contains a useful description of the 8086/8088 instruction set. 7. ISBN 0-8306-7534-5 Mueller, J. and Wang, W., THE ULTIMATE DOS PROGRAMMER'S MANUAL, disk available; Windcrest Books, 1991. Reference volume for the expert assembly- language programmer. Thru DOS 4.01. DOS and BIOS interrupts and more. 8. ISBN: none ------------, SPONTANEOUS ASSEMBLY: ASSEMBLY LANGUAGE LIBRARY, with disk; Base Two Development, 11 East 200 North / Orem, UT 84057, 1990. Contains many, many assembly language procedures. May need modification for use with the A86 assembler - especially macros. COWARDS Page H-2 9. ISBN 0-201-51806-6 -----------, SYSTEM BIOS FOR IBM PC/XT/AT COMPUTERS AND COMPATIBLES; Phoenix Technologies Ltd. Published by Addison-Wesley Publishing Co,. Inc., 1989. See this volume for detailed descriptions of the BIOS interrupts. COWARD.0A 15 Jun 1992/WK25/Mon Companion Volume to Assembly Language Programming for Cowards Copyright Homer B. Tilton 1992 Echo Electronic Press 8401 E.Desert Steppes Drive Tucson, AZ 85710 COWARDS Page ii Table of Contents Introduction Appendix EA. More on assembler instructions and operators Instructions ......................................... EA-1 Operators ............................................ EA-8 Glossary ............................................. EA-12 Appendix FA. More on conditional jump instructions .......... FA-1 Appendix GA. More on DOS interrupts INT 21h service ...................................... GA-1 1. Character input .................................. GA-1 2. Character output ................................. GA-1 3. Disk management .................................. GA-2 4. File management .................................. GA-3 5. Information management ........................... GA-8 6. Directory management ............................. GA-10 7. Process management ............................... GA-11 8. Memory management ................................ GA-12 9. Miscellaneous system management .................. GA-13 Single-function service .............................. GA-14 Appendix GB. More on BIOS interrupts Multi-function service ............................... GB-1 1. Keyboard service ................................. GB-1 2. Video service .................................... GB-3 3. Diskette service ................................. GB-7 4. Fixed disk service ............................... GB-7 5. Serial communications service .................... GB-8 6. System service ................................... GB-9 7. Parallel printer service ......................... GB-10 8. Time-of-day and date service ..................... GB-11 Single-function service .............................. GB-11 Example .............................................. GB-12 Glossary for Appendices GA and GB .................... GB-13 COWARDS Page iii Introduction This Companion Volume is a continuation of Assembly Language Programming for Cowards It consists of four new appendices; EA, FA, GA and GB. These are numbered to follow Appendices E, F and G in the main volume, and are elaborations thereon. They are bound separately because you will find them to be useful continuing references even after you've committed all the material in the main volume to memory. * * * * * Trademarks and registered trademarks IBM, PC/XT, PC/AT, PCjr, PS/2 - IBM Corp. PC-Write - Quicksoft Co. A86, D86 - Eric Isaacson Software 416 E.University Ave. Bloomington, IN 47401-4739 MASM - Microsoft Corp. TASM - Borland International OPTASM - SLR Systems QUANTASM - Quantasm Corp. Spontaneous Assembly - Base Two Development/Acclaim Tech.,Inc. Hercules - Hercules Computer Technology