home *** CD-ROM | disk | FTP | other *** search
Text File | 2013-11-08 | 829.6 KB | 20,649 lines |
- Microsoft(R) QuickAssembler Programmer's Guide Version 2.01
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- Microsoft(R) QuickAssembler Programmer's Guide Version 2.01
-
-
- ════════════════════════════════════════════════════════════════════════════
-
-
- Information in this document is subject to change without notice and does
- not represent a commitment on the part of Microsoft Corporation. The
- software described in this document is furnished under a license agreement
- or nondisclosure agreement. The software may be used or copied only in
- accordance with the terms of the agreement. It is against the law to copy
- the software on any medium except as specifically allowed in the license
- or nondisclosure agreement. No part of this manual may be reproduced or
- transmitted in any form or by any means, electronic or mechanical,
- including photocopying and recording, for any purpose without the express
- written permission of Microsoft.
-
- (C)Copyright Microsoft Corporation, 1989. All rights reserved.
- Simultaneously published in the U.S. and Canada.
-
- Printed and bound in the United States of America.
-
- Microsoft, MS, MS-DOS, GW-BASIC, QuickC, and XENIX are registered
- trademarks of Microsoft Corporation.
-
- IBM is a registered trademark of International Business Machines
- Corporation.
-
- Intel is a registered trademark of Intel Corporation.
-
- Document No. LN0114-201-R00-0689
- Part No. 06792
- 10 9 8 7 6 5 4 3 2 1
-
-
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Table of Contents
-
- Introduction
-
- Chapter 1 The QuickAssembler Interface
- 1.1 Creating the Program
- 1.2 Building and Running a Program
- 1.3 Assembling from the Command Line
- 1.4 Choosing C or Assembler Defaults
- 1.5 Using the Quick Advisor (Help)
- 1.6 Debugging Assembly Code
- 1.6.1 Debugging .COM Files
- 1.6.2 Specifying Expressions
- 1.6.3 Tracing Execution
- 1.6.4 Modifying Registers and Flags
- 1.7 Viewing a Listing File
-
- Chapter 2 Introducing 8086 Assembly Language
- 2.1 Programming the 8086 Family
- 2.2 Instructions, Directives, and Operands
- 2.2.1 The Name Field
- 2.2.2 The Operation Field
- 2.2.3 The Operand Field
- 2.2.4 The Comment Field
- 2.2.5 Entering Numbers in Different Bases
- 2.2.6 Line-Continuation Character
- 2.3 8086-Family Instructions
- 2.3.1 Data-Manipulation Instructions
- 2.3.1.1 The MOV Instruction
- 2.3.1.2 The ADD Instruction
- 2.3.1.3 The SUB Instruction
- 2.3.1.4 The INC and DEC Instructions
- 2.3.1.5 The AND Instruction
- 2.3.1.6 The MUL Instruction
- 2.3.2 Control-Flow Instructions
- 2.3.2.1 The JMP Instruction
- 2.3.2.2 The CMP Instruction
- 2.3.2.3 The Conditional Jump Instructions
- 2.4 Declaring Simple Data Objects
- 2.5 8086-Family Registers
- 2.5.1 The General-Purpose Registers
- 2.5.1.1 The AX Register
- 2.5.1.2 The BX Register
- 2.5.1.3 The CX Register
- 2.5.1.4 The DX Register
- 2.5.2 The Index Registers
- 2.5.3 The Pointer Registers
- 2.5.3.1 The BP Register
- 2.5.3.2 The SP Register
- 2.5.3.3 The IP Register
- 2.5.4 The Flags Register
- 2.6 Addressing Modes
- 2.6.1 Immediate Operands
- 2.6.2 Register Operands
- 2.6.3 Direct Memory Operands
- 2.6.4 Indirect Memory Operands
- 2.7 Segmented Addressing and Segment Registers
-
- Chapter 3 Writing Assembly Modules for C Programs
- 3.1 A Skeleton for Procedure Modules
- 3.1.1 The .MODEL Directive
- 3.1.2 The .CODE Directive
- 3.1.3 The PROC Directive
- 3.1.4 The ENDP and END Statements
- 3.2 Instructions Used in This Chapter
- 3.3 Decimal Conversion Example
- 3.4 Decimal Conversion with Far Data Pointers
- 3.4.1 Writing a Model-Independent Procedure
- 3.4.2 Accessing Far Data through ES
- 3.5 Hexadecimal Conversion Example
-
- Chapter 4 Writing Stand-Alone Assembly Programs
- 4.1 A Skeleton for Stand-Alone Programs
- 4.1.1 The .MODEL Directive
- 4.1.2 The .STACK, .CODE, and .DATA Directives
- 4.1.3 The .STARTUP Directive
- 4.2 Instructions Used in This Chapter
- 4.3 A Program That Says Hello
- 4.4 Inside the Stack Segment
- 4.5 Inside the Data Segment
- 4.6 Inside the Code Segment
- 4.7 Making the Program Repeat Itself
- 4.8 Creating .COM Files
- 4.9 Creating .COM Files with Full Segment Definitions
-
- Chapter 5 Defining Segment Structure
- 5.1 Simplified Segment Directives
- 5.1.1 Understanding Memory Models
- 5.1.2 Specifying DOS Segment Order
- 5.1.3 Defining Basic Attributes of the Module
- 5.1.4 Defining Simplified Segments
- 5.1.4.1 How to Use Simplified Segments
- 5.1.4.2 How Simplified Segments Are Implemented
- 5.1.5 Using Predefined Segment Equates
- 5.1.6 Simplified Segment Defaults
- 5.1.7 Default Segment Names
- 5.2 Full Segment Definitions
- 5.2.1 Setting the Segment-Order Method
- 5.2.2 Defining Full Segments
- 5.2.2.1 Controlling Alignment with Align Type
- 5.2.2.2 Defining Segment Combinations with Combine Type
- 5.2.2.3 Controlling Segment Structure with Class Type
- 5.3 Defining Segment Groups
- 5.4 Associating Segments with Registers
- 5.5 Initializing Segment Registers
- 5.5.1 Initializing the CS and IP Registers
- 5.5.2 Initializing the DS Register
- 5.5.3 Initializing the SS and SP Registers
- 5.5.4 Initializing the ES Register
- 5.6 Nesting Segments
-
- Chapter 6 Defining Constants, Labels, and Variables
- 6.1 Constants
- 6.1.1 Integer Constants
- 6.1.1.1 Specifying Integers with Radix Specifiers
- 6.1.1.2 Setting the Default Radix
- 6.1.2 Packed Binary Coded Decimal Constants
- 6.1.3 Real-Number Constants
- 6.1.4 String Constants
- 6.1.5 Determining Floating-Point Format
- 6.2 Assigning Names to Symbols
- 6.3 Using Type Specifiers
- 6.4 Defining Code Labels
- 6.4.1 Near-Code Labels
- 6.4.2 Anonymous Labels
- 6.4.3 Procedure Labels
- 6.4.4 Code Labels Defined with the LABEL Directive
- 6.5 Defining and Initializing Data
- 6.5.1 Variables
- 6.5.1.1 Integer Variables
- 6.5.1.2 Binary Coded Decimal Variables
- 6.5.1.3 String Variables
- 6.5.1.4 Real-Number Variables
- 6.5.2 Arrays and Buffers
- 6.5.3 Labeling Variables
- 6.5.4 Pointer Variables
- 6.6 Setting the Location Counter
- 6.7 Aligning Data
-
- Chapter 7 Using Structures and Records
- 7.1 Structures
- 7.1.1 Declaring Structure Types
- 7.1.2 Defining Structure Variables
- 7.1.3 Using Structure Operands
- 7.2 Records
- 7.2.1 Declaring Record Types
- 7.2.2 Defining Record Variables
- 7.2.3 Using Record Operands and Record Variables
- 7.2.4 Record Operators
- 7.2.4.1 The MASK Operator
- 7.2.4.2 The WIDTH Operator
- 7.2.5 Using Record-Field Operands
-
- Chapter 8 Creating Programs from Multiple Modules
- 8.1 Declaring Symbols Public
- 8.2 Declaring Symbols External
- 8.3 Using Multiple Modules
- 8.4 Declaring Symbols Communal
- 8.5 Specifying Library Files
-
- Chapter 9 Using Operands and Expressions
- 9.1 Using Operands with Directives
- 9.2 Using Operators
- 9.2.1 Calculation Operators
- 9.2.1.1 Arithmetic Operators
- 9.2.1.2 Structure-Field-Name Operator
- 9.2.1.3 Index Operator
- 9.2.1.4 Shift Operators
- 9.2.1.5 Bitwise Logical Operators
- 9.2.2 Relational Operators
- 9.2.3 Segment-Override Operator
- 9.2.4 Type Operators
- 9.2.4.1 PTR Operator
- 9.2.4.2 SHORT Operator
- 9.2.4.3 THIS Operator
- 9.2.4.4 HIGH and LOW Operators
- 9.2.4.5 SEG Operator
- 9.2.4.6 OFFSET Operator
- 9.2.4.7 .TYPE Operator
- 9.2.4.8 TYPE Operator
- 9.2.4.9 LENGTH Operator
- 9.2.4.10 SIZE Operator
- 9.2.5 Operator Precedence
- 9.3 Using the Location Counter
- 9.4 Using Forward References
- 9.4.1 Forward References to Labels
- 9.4.2 Forward References to Variables
- 9.5 Strong Typing for Memory Operands
-
- Chapter 10 Assembling Conditionally
- 10.1 Using Conditional-Assembly Directives
- 10.1.1 Testing Expressions with IF and IFE Directives
- 10.1.2 Testing the Pass with IF1 and IF2 Directives
- 10.1.3 Testing Symbol Definition with IFDEF and IFNDEF Directi
- 10.1.4 Verifying Macro Parameters with IFB and IFNB Directives
- 10.1.5 Comparing Macro Arguments with IFIDN and IFDIF Directiv
- 10.1.6 ELSEIF Directives
- 10.2 Using Conditional-Error Directives
- 10.2.1 Generating Unconditional Errors with .ERR, .ERR1, and .
- Directives
- 10.2.2 Testing Expressions with .ERRE or .ERRNZ Directives
- 10.2.3 Verifying Symbol Definition with .ERRDEF and .ERRNDEF
- Directives
- 10.2.4 Testing for Macro Parameters with .ERRB and .ERRNB
- Directives
- 10.2.5 Comparing Macro Arguments with .ERRIDN and .ERRDIF
- Directives
-
- Chapter 11 Using Equates, Macros, and Repeat Blocks
- 11.1 Using Equates
- 11.1.1 Redefinable Numeric Equates
- 11.1.2 Nonredefinable Numeric Equates
- 11.1.3 String Equates
- 11.1.4 Predefined Equates
- 11.2 Using Macros
- 11.2.1 Defining Macros
- 11.2.2 Calling Macros
- 11.2.3 Using Local Symbols
- 11.2.4 Exiting from a Macro
- 11.3 Text-Macro String Directives
- 11.3.1 The SUBSTR Directive
- 11.3.2 The CATSTR Directive
- 11.3.3 The SIZESTR Directive
- 11.3.4 The INSTR Directive
- 11.3.5 Using String Directives Inside Macros
- 11.4 Defining Repeat Blocks
- 11.4.1 The REPT Directive
- 11.4.2 The IRP Directive
- 11.4.3 The IRPC Directive
- 11.5 Using Macro Operators
- 11.5.1 Substitute Operator
- 11.5.2 Literal-Text Operator
- 11.5.3 Literal-Character Operator
- 11.5.4 Expression Operator
- 11.5.5 Macro Comments
- 11.6 Using Recursive, Nested, and Redefined Macros
- 11.6.1 Using Recursion
- 11.6.2 Nesting Macro Definitions
- 11.6.3 Nesting Macro Calls
- 11.6.4 Redefining Macros
- 11.6.5 Avoiding Inadvertent Substitutions
- 11.7 Managing Macros and Equates
- 11.7.1 Using Include Files
- 11.7.2 Purging Macros from Memory
-
- Chapter 12 Controlling Assembly Output
- 12.1 Sending Messages to the Standard Output Device
- 12.2 Controlling Page Format in Listings
- 12.2.1 Setting the Listing Title
- 12.2.2 Setting the Listing Subtitle
- 12.2.3 Controlling Page Breaks
- 12.2.4 Naming the Module
- 12.3 Controlling the Contents of Listings
- 12.3.1 Suppressing and Restoring Listing Output
- 12.3.2 Controlling Listing of Conditional Blocks
- 12.3.3 Controlling Listing of Macros
-
- Chapter 13 Loading, Storing, and Moving Data
- 13.1 Transferring Data
- 13.1.1 Copying Data
- 13.1.2 Exchanging Data
- 13.1.3 Looking Up Data
- 13.1.4 Transferring Flags
- 13.2 Converting between Data Sizes
- 13.2.1 Extending Signed Values
- 13.2.2 Extending Unsigned Values
- 13.3 Loading Pointers
- 13.3.1 Loading Near Pointers
- 13.3.2 Loading Far Pointers
- 13.4 Transferring Data to and from the Stack
- 13.4.1 Pushing and Popping
- 13.4.2 Using the Stack
- 13.4.3 Saving Flags on the Stack
- 13.4.4 Saving All Registers on the Stack
- 13.5 Transferring Data to and from Ports
-
- Chapter 14 Doing Arithmetic and Bit Manipulations
- 14.1 Adding
- 14.1.1 Adding Values Directly
- 14.1.2 Adding Values in Multiple Registers
- 14.2 Subtracting
- 14.2.1 Subtracting Values Directly
- 14.2.2 Subtracting with Values in Multiple Registers
- 14.3 Multiplying
- 14.4 Dividing
- 14.5 Calculating with Binary Coded Decimals
- 14.5.1 Unpacked BCD Numbers
- 14.5.2 Packed BCD Numbers
- 14.6 Doing Logical Bit Manipulations
- 14.6.1 AND Operations
- 14.6.2 OR Operations
- 14.6.3 XOR Operations
- 14.6.4 NOT Operations
- 14.7 Shifting and Rotating Bits
- 14.7.1 Multiplying and Dividing by Constants
- 14.7.2 Moving Bits to the Least-Significant Position
- 14.7.3 Adjusting Masks
- 14.7.4 Shifting Multiword Values
-
- Chapter 15 Controlling Program Flow
- 15.1 Jumping
- 15.1.1 Jumping Unconditionally
- 15.1.2 Jumping Conditionally
- 15.1.2.1 Comparing and Jumping
- 15.1.2.2 Jumping Based on Flag Status
- 15.1.2.3 Testing Bits and Jumping
- 15.2 Looping
- 15.3 Using Procedures
- 15.3.1 Calling Procedures
- 15.3.2 Defining Procedures
- 15.3.3 Passing Arguments on the Stack
- 15.3.4 Declaring Parameters with the PROC Directive
- 15.3.5 Using Local Variables
- 15.3.6 Creating Locals Automatically
- 15.3.7 Variable Scope
- 15.3.8 Setting Up Stack Frames
- 15.4 Using Interrupts
- 15.4.1 Calling Interrupts
- 15.4.2 Defining and Redefining Interrupt Routines
- 15.5 Checking Memory Ranges
-
- Chapter 16 Processing Strings
- 16.1 Setting Up String Operations
- 16.2 Moving Strings
- 16.3 Searching Strings
- 16.4 Comparing Strings
- 16.5 Filling Strings
- 16.6 Loading Values from Strings
- 16.7 Transferring Strings to and from Ports
-
- Chapter 17 Calculating with a Math Coprocessor
- 17.1 Coprocessor Architecture
- 17.1.1 Coprocessor Data Registers
- 17.1.2 Coprocessor Control Registers
- 17.2 Emulation
- 17.3 Using Coprocessor Instructions
- 17.3.1 Using Implied Operands in the Classical-Stack Form
- 17.3.2 Using Memory Operands
- 17.3.3 Specifying Operands in the Register Form
- 17.3.4 Specifying Operands in the Register-Pop Form
- 17.4 Coordinating Memory Access
- 17.5 Transferring Data
- 17.5.1 Transferring Data to and from Registers
- 17.5.1.1 Real Transfers
- 17.5.1.2 Integer Transfers
- 17.5.1.3 Packed BCD Transfers
- 17.5.2 Loading Constants
- 17.5.3 Transferring Control Data
- 17.6 Doing Arithmetic Calculations
- 17.7 Controlling Program Flow
- 17.7.1 Comparing Operands to Control Program Flow
- 17.7.1.1 Compare
- 17.7.1.2 Compare and Pop
- 17.7.2 Testing Control Flags after Other Instructions
- 17.8 Using Transcendental Instructions
- 17.9 Controlling the Coprocessor
-
- Chapter 18 Controlling the Processor
- 18.1 Controlling Timing and Alignment
- 18.2 Controlling the Processor
- 18.3 Processor Directives
-
- Appendix A Mixed-Language Mechanics
- A.1 Writing the Assembly Procedure
- A.1.1 Setting Up the Procedure
- A.1.2 Entering the Procedure
- A.1.3 Allocating Local Data (Optional)
- A.1.4 Preserving Register Values
- A.1.5 Accessing Parameters
- A.1.6 Returning a Value (Optional)
- A.1.7 Exiting the Procedure
- A.2 Calls from Modules Using C Conventions
- A.3 Calls from Non-C Modules
- A.4 Calling High-Level Languages from Assembly Language
- A.5 Using Full Segment Definitions
-
- Appendix B Using Assembler Options with QCL
- B.1 Specifying the Segment-Order Method
- B.2 Checking Code for Tiny Model
- B.3 Selecting Case Sensitivity
- B.4 Defining Assembler Symbols
- B.5 Displaying Error Lines on the Screen
- B.6 Creating Code for a Floating-Point Emulator
- B.7 Creating Listing Files
- B.8 Enabling One-Pass Assembly
- B.9 Listing All Lines of Macro Expansions
- B.10 Creating a Pass 1 Listing
- B.11 Specifying an Editor-Oriented Listing
- B.12 Suppressing Tables in the Listing File
- B.13 Adding a Line-Number Index to the Listing
- B.14 Listing False Conditionals
- B.15 Controlling Display of Assembly Statistics
- B.16 Setting the Warning Level
-
- Appendix C Reading Assembly Listings
- C.1 Reading Code in a Listing
- C.2 Reading a Macro Table
- C.3 Reading a Structure and Record Table
- C.4 Reading a Segment and Group Table
- C.5 Reading a Symbol Table
- C.6 Reading Assembly Statistics
- C.7 Reading a Pass 1 Listing
-
- Index
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Introduction
-
-
- If you're a C programmer who has been wanting to try out the full power of
- assembly language, this is the product for you.
-
- Microsoft(R) QuickC(R) with QuickAssembler is a package you install along
- with Microsoft QuickC Version 2.0 in order to create a single powerful
- environment in which you can develop C, assembly, and mixed-language
- programs. What's more, QuickAssembler is an integrated environment,
- containing tools for editing, assembling, compiling, and linking.
- Integrated tools help you achieve faster development of assembly-language
- programs.
-
- Each MS-DOS(R) and IBM(R) PC-DOS computer is driven by one of the
- processors in the 8086 family. A processor is the central motor of a
- computer. It responds to its own numeric language, called "machine code."
- Assembly language is very close to machine code, but it lets you use
- meaningful keywords and variable names instead of difficult-to-remember
- numeric codes. As a result, assembly language is convenient to use, but
- gives you the ultimate in ability to control hardware and optimize code.
-
- To support the low-level operations of assembly language, QuickAssembler
- expands the general power of the QuickC environment. Increased debugging
- capabilities let you change flag settings and modify registers──including
- registers of the 8087 math coprocessor. Furthermore, the Quick Advisor
- (the on-line Help system) is expanded to provide help on QuickAssembler
- keywords as well as DOS and ROM-BIOS services.
-
- A Note about Operating-System Terms
-
- Microsoft documentation uses the term "OS/2" to refer to the OS/2 system──
- Microsoft Operating System/2 (MS(R) OS/2) and IBM OS/2. Similarly, the
- term "DOS" refers to both the MS-DOS and IBM Personal Computer DOS
- operating systems. The name of a specific operating system is used when it
- is necessary to note features unique to that system.
-
-
- General Features
-
- QuickAssembler does not replace the QuickC in-line assembler, which you
- can continue to use inside .C files. The joint QuickC/QuickAssembler
- environment puts both QuickAssember and the in-line assembler at your
- disposal. But Microsoft QuickAssembler supports a number of features
- beyond those supported by the in-line assembler:
-
- ■ You can write stand-alone assembly programs. These programs begin and
- end with assembly code and do not include the C start-up code. Unlike
- programs written from within C modules, useful stand-alone assembly
- programs can be 1K (kilobyte) or even smaller.
-
- ■ You can use the assembler's rich set of macro-definition capabilities,
- which go far beyond the macro capabilities supported by C. An
- assembly-language macro can handle variable parameter lists, recursion,
- and repeated operations. These macros are roughly as powerful and
- flexible as procedure calls, but execute faster.
-
- ■ Your assembly modules can be shared by many different programs. Since
- an assembly-language module is in its own file, you can write the
- module once and link it to any program you want.
-
- ■ QuickAssembler is a full implementation of 8086 assembly language. You
- can use the full set of the Microsoft Macro Assembler 5.1 directives
- and operators.
-
- In addition, QuickAssembler provides the best set of keywords yet
- available for simplifying tedious programming tasks, such as initializing
- registers at the beginning of a program or determining how to access
- parameters on the stack. (Part 1 of this manual focuses on the use of
- these keywords.)
-
- QuickAssembler for QuickC is a DOS-based product, and it does not include
- the following extensions to 8086 assembly language:
-
- ■ 80386 extended registers and special instructions
-
- ■ 80387 extended instructions
-
- ■ OS/2 protected-mode operation
-
- QuickAssembler does support the 80286 extended instruction set, as well as
- the 8087 and 80287 coprocessors. The 80386 processor can run all
- QuickAssembler programs; the only limitation is that QuickAssembler does
- not support extended capabilities of the 80386.
-
- The Microsoft Macro Assembler supports 80386 extended features and
- development of protected-mode applications.
-
-
- System Requirements
-
- In addition to a computer with one of the 8086-family processors, you must
- have Version 2.1 or later of the MS-DOS or IBM PC-DOS operating system.
- You can also run QuickAssembler in the 3.x compatibility box of OS/2
- systems. Your computer system must have approximately 512K of memory. A
- hard-disk setup is strongly recommended.
-
- To enable the use of QuickAssembler, you should first choose Full Menus
- from the Options menu.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The 8086 family is a set of processors that all support the same
- basic instruction set. This family includes the 8088, 8086, 80188, 80186,
- 80286, and 80386 chips. All of these processors support the entire
- instruction set of the 8086 itself; some support additional instructions.
- Rather than list the entire set of chips, this manual often discusses the
- core instruction set by referring only to the 8086.
- ──────────────────────────────────────────────────────────────────────────
-
-
- Installing QuickAssembler
-
- If you purchased QuickC and QuickAssembler together, the installation
- procedure described in Up and Running automatically installs both
- products. A few of the questions shown in that booklet are reworded in the
- install program to make more sense for the joint QuickC/QuickAssembler
- installation.
-
- If you purchased QuickAssembler separately, run the installation program
- on the QuickAssembler distribution disks. The first screen asks you the
- following questions:
-
- Source of assembler files [A:]:
- Installing on a hard disk drive [Y]:
- Copy QuickAssembler documentation files [Y]:
- Copy sample Assembler programs [N]:
-
- Do you want to change any of the above options? [N]
-
- As with the QuickC installation program, the default responses are
- indicated in brackets ([]). Each of these questions is accompanied by an
- explanation at the bottom of the screen. To accept a default response,
- press ENTER. If you enter an incorrect response, just answer no (N) to the
- last question.
-
- The second screen asks you the following questions:
-
- Directory for QuickC executable files [C:\QC2\BIN]:
- Directory for Sample files [C:\QC2\SAMPLES]:
-
- Do you want to change any of the above options? [N]
-
- The QuickAssembler installation program replaces some of the existing
- QuickC files. QuickAssembler must be installed in the directory that
- currently contains QC.EXE. Make sure you enter the location of your
- current QuickC executable files. If you're not sure, press CTRL+C to stop
- the installation and examine your setup.
-
-
- Getting Information about Assembly Language
-
- The combined paper and on-line documentation with QuickAssembler gives you
- a complete reference to the language. This manual provides three basic
- kinds of information:
-
- ■ Part 1, "Introducing QuickAssembler," provides a basic introduction to
- programming in assembly language. Chapter 1 describes how the
- interface changes when you install QuickAssembler. Chapter 2 gives a
- general background to 8086 architecture and assembly-language concepts.
- Chapters 3 and 4 demonstrate how to use special QuickAssembler
- keywords to simplify programming. Even if you have used assembly
- language before, you should take a look at these chapters.
-
- ■ Parts 2 ("Using Directives") and 3 ("Using Instructions") give a
- reference to the use of directives and instructions. This material is
- much less tutorial than Part 1, but it does illustrate the use of each
- directive and instruction in context.
-
- ■ The appendixes explain low-level mixed-language techniques, the use of
- assembly options with the QCL driver, and how to read listing files.
-
- This manual does not teach systems programming or advanced programming
- techniques. Even with the tutorial material provided in this manual, you
- may want to purchase other books on assembly language, such as the ones
- listed in the next section.
-
- In addition, this manual assumes you understand certain basic concepts of
- programming, such as modules, variables, and pointers. If you need more
- background in one of these topics, you should first read the appropriate
- sections in C For Yourself. Part 1 of this manual often explains concepts
- by comparing a language feature to C.
-
- The Quick Advisor (the on-line Help system) is an integral part of the
- overall documentation. As explained in Section 1.5, "Using the Quick
- Advisor (Help)," QuickAssembler provides help on all keywords──in
- particular, you get instant reference information on each instruction,
- including timing, encoding, and flag settings. The Help Contents and Index
- screens also provide information on each DOS service.
-
-
- Books on Assembly Language
-
- The following books may be useful in learning to program in assembly
- language:
-
- Duncan, Ray. Advanced MS-DOS. Redmond, WA: Microsoft Corporation, 1986.
-
- An intermediate book on writing C and assembly-language programs that
- interact with MS-DOS (includes DOS and BIOS function descriptions)
-
- Jourdain, Robert. Programmer's Problem Solver for the IBM PC, XT and AT.
- New York: Brady Communications Company, Inc., 1986.
-
- Reference of routines and techniques for interacting with hardware
- devices through DOS, BIOS, and ports (high-level routines in BASIC and
- low- or medium-level routines in assembler)
-
- Lafore, Robert. Assembly Language Primer for the IBM PC & XT. New York:
- Plume/Waite, 1984.
-
- An introduction to assembly language, including some information on DOS
- function calls and IBM-type BIOS
-
- Metcalf, Christopher D., and Sugiyama, Marc B. COMPUTE!'s Beginner's Guide
- to Machine Language on the IBM PC & PCjr. Greensboro, NC: COMPUTE!
- Publications, Inc., 1985.
-
- Beginning discussion of assembly language, including information on the
- instruction set and MS-DOS function calls
-
- Microsoft MS-DOS Programmer's Reference. Redmond, WA: Microsoft Press,
- 1986, 1987.
-
- Reference manual for MS-DOS
-
- Morgan, Christopher, and the Waite Group. Bluebook of Assembly Routines
- for the IBM PC. New York: New American Library, 1984.
-
- Sample assembly routines that can be integrated into assembly or
- high-level-language programs
-
- Norton, Peter. The Peter Norton Programmer's Guide to the IBM PC. Redmond,
- WA: Microsoft Press, 1985.
-
- Information on using IBM-type BIOS and MS-DOS function calls
-
- Scanlon, Leo J. IBM PC Assembly Language: A Guide for Programmers. Bowie,
- MD: Robert J. Brady Co., 1983.
-
- An introduction to assembly language, including information on DOS
- function calls
-
- Schneider, Al. Fundamentals of IBM PC Assembly Language. Blue Ridge
- Summit, PA: Tab Books Inc., 1984.
-
- An introduction to assembly language, including information on DOS
- function calls
-
- These books are listed for your convenience only. Microsoft Corporation
- does not endorse these books (with the exception of those published by
- Microsoft) or recommend them over others on the same subjects.
-
-
- Document Conventions
-
- The following document conventions are used throughout this manual:
-
- Example of Description
- Convention
- ──────────────────────────────────────────────────────────────────────────
- SAMPLE2.ASM Uppercase letters indicate file names, segment names,
- registers, and terms used at the DOS-command level.
-
- .MODEL Boldface type indicates assembly-language directives,
- instructions, type specifiers, and predefined equates,
- as well as keywords in other programming languages.
-
- placeholders Italic letters indicate placeholders for information
- you must supply, such as a file name. Italics are also
- occasionally used for emphasis in the text.
-
- target This font is used to indicate example programs, user
- input, and screen output.
-
- SHIFT Names of keys on the keyboard appear in small capital
- letters. Notice that a plus (+) indicates a
- combination of keys. For example, CTRL+E means to hold
- down the CTRL key while pressing the E key.
-
- [[argument ]] Items inside double square brackets are optional.
-
- {register | memory} Braces and a vertical bar indicate a choice between
- two or more items. You must choose one of the items
- unless double square brackets surround the braces.
-
- Repeating Three dots following an item indicate that more items
- elements... having the same form may appear.
-
- Program A column of three dots tells you that part of a
- . program has been intentionally omitted.
- .
- .
- Fragment
-
- "processor flag" The first time a new term is defined, it is enclosed
- in quotation marks.
-
- Color Graphics The first time an acronym is used, it is spelled out.
- Adapter (CGA)
-
-
- Getting Assistance or Reporting Problems
-
- If you need help or feel you have discovered a problem in the software,
- please provide the following information to help us locate the problem:
-
- ■ The version of DOS you are running (use the DOS VER command)
-
- ■ Your system configuration (the type of machine you are using, its total
- memory, and its total free memory at assembler execution time, as well
- as any other information you think might be useful)
-
- ■ The assembly command line used (or the link command line if the problem
- occurred during linking)
-
- ■ Any object files or libraries you linked with if the problem occurred
- at link time
-
- If your program is very large, please try to reduce its size to the
- smallest possible program that still produces the problem.
-
- Use the Product Assistance Request form at the back of this manual to send
- this information to Microsoft.
-
- If you have comments or suggestions regarding any of the manuals
- accompanying this product, please indicate them on the Document Feedback
- card at the back of this manual.
-
- If you are not already a registered QuickAssembler owner, you should fill
- out and return the Registration Card. This enables Microsoft to keep you
- informed of updates and other information about the assembler.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- PART 1: Using Assembler Programs
-
-
- Part 1 of the Programmer's Guide (comprising Chapters 1-4) will help
- you start using assembly language quickly.
-
- Chapter 1 summarizes all the differences between the standard QuickC
- interface and the expanded QuickC/QuickAssembler interface. Read this
- chapter to learn how to enter, assemble, and run an assembly-language
- program.
-
- Read Chapter 2 if you are new to 8086 assembly language or need to review
- basic concepts. Chapter 2 explains the architecture of 8086-family
- processors, as well as how to write simple code and data statements.
-
- Whether or not you're new to assembly language, you'll want to read
- Chapters 3 and 4, which show the use of QuickAssembler's simplified
- keywords in useful examples. These keywords make programming easier.
-
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 1: The QuickAssembler Interface
-
-
- After you install Microsoft QuickC with QuickAssembler, you'll have a
- single environment for both compiling and assembling. You can create C
- programs, assembly-language programs, and programs that combine both
- languages.
-
- The environment completely supports the standard QuickC features,
- including all editing commands as well as mouse, keyboard, and menu
- techniques. This manual assumes you have read QuickC Up and Running and
- have used the on-line Help system to learn how to use each menu. Refer to
- these sources of information for basic help on using the interface.
-
- The combined QuickC/QuickAssembler interface provides some new menu
- selections and dialog boxes to support development of assembly-language
- programs. This chapter describes the new features, focusing on areas where
- the interface adds new functionality: creating a program, building a
- program, getting help, debugging, and viewing a listing file. To enable
- all the features described in this chapter, you should first choose Full
- Menus from the Options menu if you are not already using full menus.
-
-
- 1.1 Creating the Program
-
- Start the environment with the QC command, regardless of whether you're
- creating a C or assembly-language source file. You can type QC by itself
- or QC followed by the name of a file.
-
- By default, QC assumes that a file name on the command line has a .C
- extension. You'll learn how to change this behavior later (by choosing
- Display from the Options menu), but for now, make sure you include the
- .ASM file extension when you want to create an assembly-language module:
-
- QC SAMPLE.ASM
-
- If the file is new, the QuickC/QuickAssembler environment asks you if you
- would like to create the file.
-
- Once inside the QuickC/QuickAssembler environment, you can enter a program
- by using all the QuickC editing commands. You can get started by entering
- the following stand-alone assembly program. By default, QuickAssembler is
- not case sensitive (except for external symbols), so you can enter
- statements as uppercase or lowercase.
-
- .MODEL small
- .STACK
- .CODE
- .STARTUP
- mov ah,2
- mov dl,7
- int 21h
- mov ax,4c00h
- int 21h
- END
-
- Enter the program above in a file with a .ASM extension. No other modules
- and no special assembly or link flags are required. When run, the program
- beeps and exits.
-
- For now, you may just want to run the program to see how the
- QuickC/Quick-Assembler environment works. However, you can read the rest
- of this section to get a brief explanation of why the program works.
-
- The four statements are directives──nonexecutable statements that give
- basic structure to the program by declaring a memory model, stack segment,
- and code segment.
-
- The next five statements perform the actions of the program. The first
- three set up a call to a DOS function that prints the beep character. (The
- QuickAssembler Advisor, which you access through the Help menu, provides
- information on each DOS function.) The first three statements are shown
- below, with comments:
-
- mov ah,2 ; Move 2 to AH (select Print function)
- mov dl,7 ; Move 7 to DL (select Beep character)
- int 21h ; Call DOS function
-
- The next two statements, shown below with comments, call DOS to exit
- gracefully. Unlike C programs, assembly-language programs must make an
- explicit function call to exit, or else cause the processor to execute
- meaningless instructions beyond the end of the program.
-
- mov ax,4c00h ; Move 4c00h to AX (select Exit
- ; function and 0 return code)
- int 21h ; Call DOS function
-
- The last statement ends the module.
-
-
- 1.2 Building and Running a Program
-
- Once inside the QuickC/QuickAssembler environment, you build an
- assembly-language program the same way you build a C program. Choose the
- Go command from the Run menu, or press the F5 key.
-
- The environment assembles and links the program if it needs to be built.
- Then, if there are no errors, it executes the program. You can also
- assemble a program by using the Make menu. The Compile File command
- assembles your file rather than compiling it, assuming the current file
- has a .ASM extension.
-
- To help you create assembly-language programs, the QuickC/QuickAssembler
- interface adds the following extensions to QuickC:
-
- ■ A program list can now have .ASM files as well as .C, .OBJ, and .LIB
- files if you work with multiple modules.
-
- ■ The Make dialog box from the Options menu has a new option button:
- Assembler Flags.
-
- ■ The Assembler Flags dialog box lets you control how .ASM files are
- assembled.
-
- If your program has multiple modules, you can add .ASM files to the
- program list as well as other kinds of files. When you build the program,
- the environment compiles each .C file module that needs to be built and
- assembles each .ASM module that needs to be built.
-
- For example, the program list in Figure 1.1 creates a mixed-language
- program with both C and assembly-language source files.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 1.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The environment sets the default file extension by looking at the
- extension of the last file loaded. If the last file loaded had a .ASM
- extension, the File List field now displays all the .ASM files for the
- current directory. If the last file loaded had a .C extension, the File
- List field displays all .C files.
-
- You can alter this behavior by choosing Display from the Options menu, as
- explained in Section 1.4, "Choosing C or Assembler Defaults." In any
- case, you can always control which files are displayed by entering a
- wildcard expression, such as *.asm, in the File Name field.
-
- The environment lets you set assembler options as well as compiler
- options. When you open the Options menu and choose Make, the dialog box
- shown in Figure 1.2 appears.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 1.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- This dialog box contains one new field: Assembler Flags. When you choose
- this field, a new dialog box, shown in Figure 1.3, appears.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 1.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- By setting flags in the Assembler Flags dialog box, you control the action
- of the assembler whenever it builds a program. These settings have no
- effect on .C modules, but do affect how each .ASM module is assembled.
-
- This dialog box contains a Debug Flags section, which has options that
- apply only to Debug builds, and a Global Flags section, which has options
- that apply to every build. Choose the Help button for an explanation of
- each option.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE You control the type of build operation (Debug or Release) by
- choosing the appropriate option button in the dialog box shown in Figure
- 1.2. You can return to that dialog box by choosing the OK or Cancel
- command button. By choosing Debug (the default), you can use all of the
- QuickC debugging commands while running the program. By choosing Release,
- you produce a program that cannot be debugged but is somewhat smaller.
- ──────────────────────────────────────────────────────────────────────────
-
- The Custom Flags section lets you enter additional options. In the three
- Custom Flags text boxes, you can type any of the assembly options accepted
- by the QCL driver. See Appendix B for a description of these options. The
- next section describes how to use the QCL driver to assemble programs.
-
-
- 1.3 Assembling from the Command Line
-
- You can run QuickAssembler from the command line, just as you can run
- QuickC. One utility, QCL, invokes both the assembler and compiler. You can
- even use it to compile, assemble, and link mixed-language programs in one
- step. However, make sure you use the version of QCL copied during
- QuickAssembler installation.
-
- If you type a file name that has a .C extension, QCL invokes the C
- compiler. For example, the following command compiles and links the file
- SAMPLE1.C:
-
- QCL SAMPLE1.C
-
- If you type a file name that has a .ASM extension, QCL invokes the
- QuickAssembler. For example, the following command assembles and links the
- file SAMPLE2.ASM:
-
- QCL SAMPLE2.ASM
-
- In any case, QCL links all resulting object files to create a .EXE file,
- unless you specify /c on the command line. (You can also create a .COM
- file if the program is written entirely in assembly language.) For
- example, the following command compiles SAMPLE1.C and assembles
- SAMPLE2.ASM, but does not link the resulting object files:
-
- QCL /c SAMPLE1.C /Cl SAMPLE2.ASM
-
- As always, you can specify .LIB files and .OBJ files on the QCL command
- line. A file with no extension is assumed to have a .OBJ extension by
- default. For example, the following QCL command compiles M1.C, assembles
- M2.ASM (with lowercase symbols preserved), and links M1.OBJ, M2.OBJ, and
- M3.OBJ. Finally, QCL searches M4.LIB for any unresolved references.
-
- QCL /Cx M1.C M2.ASM M3 M4.LIB
-
- You can specify a number of QuickAssembler options, in addition to the
- ones provided specifically for C. See Appendix B, "Using Assembler
- Options with QCL," for a description of all these options.
-
-
- 1.4 Choosing C or Assembler Defaults
-
- At all times, you can use the QuickC/QuickAssembler environment to create
- either C modules or assembly-language modules. However, there are some
- details of operation that make it a little easier to work with one
- language or another.
-
- For example, one consideration is whether the dialog box starts by
- displaying all the C files in the directory (*.c) or all the
- assembly-language files (*.asm) when you choose the Open command from the
- File menu. You can control this behavior by choosing Display from the
- Options menu. Figure 1.4 shows the dialog box that appears.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 1.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- In the Language section of this dialog box, select either C, Assembler, or
- Auto. The Auto selection uses C or Assembler defaults, depending on what
- file was last loaded into the active window. For example, if you load the
- PROG.ASM file into the source window, all the defaults (described below)
- change to assembly-language settings.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE When you first use QuickAssembler, the environment starts up in Auto
- mode. Thereafter, it looks at the settings in QC.INI to determine what
- mode to start in; this feature has the effect of saving display-mode
- settings between sessions.
- ──────────────────────────────────────────────────────────────────────────
-
- The following items change when the display mode changes──either because
- you change the mode manually or because you are in Auto mode and load a
- different kind of file:
-
- ■ For commands on the File menu, the default file name changes to *.c or
- *.asm.
-
- ■ The Include command on the View menu responds to .H files if the
- display mode is C, or .INC files if the display mode is Assembler.
-
- ■ The Index and Contents items from the Help menu bring up lists of
- topics for either C or Assembly, as determined by the display mode.
-
- Auto display mode assumes C defaults until you load a .ASM file. When you
- start the environment with the QC command, QC assumes that file names on
- the command line have .C extensions, unless the environment is in
- Assembler display mode.
-
-
- 1.5 Using the Quick Advisor (Help)
-
- QuickAssembler extends the number of topics you can get information on,
- and updates QCENV.HLP so you can get context-sensitive help on the new
- menu items and dialog boxes. In addition, you still continue to get help
- on all of the C-language topics. The new topics, added for use with
- assembly language, are shown below:
-
- ■ QuickAssembler instructions
-
- ■ QuickAssembler directives and operators
-
- ■ DOS and ROM-BIOS services
-
- You can get help on assembly-language topics by using one of two different
- methods:
-
- 1. Topical Help (press F1)
-
- 2. The Help menu
-
- At all times, the expanded environment provides topical Help for both
- assembler and C keywords. Place the cursor on the keyword, then press F1.
- You can also get topical Help by moving the mouse cursor to the desired
- word and clicking the Right mouse button. The display mode (described in
- the previous section) determines whether C help files or assembly help
- files are searched first.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE If the keyword starts with a dot (.), do not place the cursor on the
- dot or click on the dot to get topical Help. Place the cursor on the
- keyword or click on the keyword.
- ──────────────────────────────────────────────────────────────────────────
-
- QuickAssembler keywords include instructions, directives, and operators.
- Chapter 2, "Introducing 8086 Assembly Language" provides information on
- each of these concepts. An "instruction" is a specific action that the
- processor executes. Instructions are the primary building blocks of an
- assembly-language program.
-
- The Help screens on instructions are particularly useful, because they
- provide detailed information on timing, syntax, and processor flags. This
- manual features a topical discussion of instructions, but provides only
- limited information on timing and flags. To write the most efficient
- assembly-language programs, you should refer often to the on-line Help for
- instructions.
-
- To get help on DOS or ROM-BIOS services, select Contents or Index from the
- Help menu. These menu items give you help on assembly-language topics
- rather than C topics whenever the display mode (described in the previous
- section) is set to Assembler.
-
- The Help system offers other paths to get to information on DOS and BIOS
- functions. Move the mouse cursor to an interrupt number (such as 21H or
- 33) and click the Right mouse button, or move the cursor to the number and
- press F1. The Help system responds by showing a screen listing of all the
- functions accessed through that interrupt number. You can then go to the
- specific Help screen you want. You can also get help on interrupt
- functions by selecting context-sensitive help for the INT keyword.
-
- You call these DOS and BIOS functions by using the INT instruction, as
- described in Chapter 4. These services perform basic input and output
- functions for you, giving you access to DOS and to hardware.
-
- By default, the Smart Help display option is on. This option makes the
- system more flexible by ignoring the presence or absence of a leading
- underscore (_) in front of a name. Consequently, pressing F1 while on
- _printf gives you help for the printf function.
-
-
- 1.6 Debugging Assembly Code
-
- You can run a Debug build by choosing Debug in the dialog box opened by
- the Options menu's Make command. Debug is the default setting, so you
- probably won't need to choose it.
-
- You can use all of QuickC's debugging commands with programs written in
- assembly language. But keep in mind these considerations:
-
- ■ You must use an extra file with a .DBG file extension to debug programs
- in .COM format.
-
- ■ You must use C syntax to specify expressions to watch or modify, even
- when you debug assembly code. In addition, you can use the BY, WO, and
- DW memory operators, register names, and the colon (:) operator. The
- colon operator helps to specify segmented addresses.
-
- ■ When you trace execution of an assembly-language module, the behavior
- of the environment changes. Screen swapping is turned on by default,
- and the first line of code is never highlighted.
-
- ■ You can alter flag values and registers from within the environment.
-
- Sections 1.6.1-1.6.4 discuss each debugging feature in turn.
-
-
- 1.6.1 Debugging .COM Files
-
- Section 4.8, "Creating .COM Files," explains how to use tiny memory
- model, along with a linker flag, to generate a program in the .COM-file
- format. A .COM file has a total size limitation of 64K, but is slightly
- smaller and loads faster than a similar .EXE file.
-
- When you run a Debug build that creates a .COM file, the linker places
- debugging information in a separate file with the same base name as the
- program and with a .DBG extension. If you delete the .DBG file, you cannot
- debug your program until you run another Debug build.
-
- Otherwise, all the considerations that apply to debugging .EXE files apply
- to .COM files as well.
-
-
- 1.6.2 Specifying Expressions
-
- The Debug menu provides two commands──Watch Value and Watchpoint──that let
- you specify an expression for the QuickC/QuickAssembler environment to
- dynamically update and display. The environment displays the updated
- values in the Watch window. When you choose one of these commands, a
- dialog box appears, prompting you to enter an expression. Figure 1.5 shows
- the dialog box for the Watch Value command.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 1.6.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- You can enter any combination of variable names, constants, and C-language
- syntax. You cannot enter assembly-language keywords. However, the
- environment does recognize all valid register names (including names of
- both 8-bit and 16-bit registers). See Chapter 2, "Introducing 8086
- Assembly Language," for information on registers.
-
- In addition to register names, the expanded environment supports the
- optional use of the colon operator (:) for specifying segmented addresses:
-
- segment:offset
-
- In the syntax display above, segment can be a constant or a register;
- offset can be any expression. The QuickC/QuickAssembler environment
- combines the segment and offset addresses to determine a physical address,
- as described in Section 2.7, "Segmented Addressing and Segment
- Registers."
-
- The following examples demonstrate the use of the colon in valid
- expressions. Note that you use C-language syntax to specify hexadecimal
- numbers:
-
- 0xb000:0x0000
- es:0x0100
- es:(array[2])
- ss:bp
-
- The QuickC/QuickAssembler environment considers a segmented-address
- expression to be a pointer to a character, which the Watch window
- evaluates by displaying the character pointed to. However, you can use
- QuickC type specifiers to alter how an expression is displayed. For
- example, the Watch window evaluates the following expression by displaying
- the numeric value of the address es:(warray+3):
-
- es:(warray+3),p
-
- You can use the three memory operators──BY, WO, and DW──to view the byte,
- word, or doubleword of memory at a given address.
-
- With pointer expressions and registers, BY returns the byte pointed to by
- the expression. (Segmented addresses are pointer expressions, as are
- procedure parameters declared with PTR.) With nonpointer variables, BY
- returns the byte at the same address as the variable. WO and DW work the
- same way, but return a word or doubleword, respectively.
-
- The rest of this section demonstrates how to use the three memory
- operators to specify useful expressions.
-
- To watch the contents of a register, enter just the register's name. To
- examine the value that the register points to, enter the BY, WO, and DW
- operators followed by the register name.
-
- Example Value Specified
- ──────────────────────────────────────────────────────────────────────────
- bx The contents of the BX register
-
- BY bx The byte pointed to by the BX register
-
- WO bx The word pointed to by the BX register
-
- DW es:si The doubleword pointed to by the SI register, relative
- to the segment address in ES
-
-
- To watch the value of a variable, enter the variable's name. To watch the
- byte, word, or doubleword at the same address as the variable, use the BY,
- WO, and DW operators. In this context, these operators function as the
- QuickAssembler PTR operator does: they change the size of data to be
- examined. They are similar, but not identical, to C type casts. In the
- following examples, assume that Var is a word variable defined with DW:
-
- Example Value Specified
- ──────────────────────────────────────────────────────────────────────────
- Var The variable Var (the word at the address of Var)
-
- BY Var The byte at the address of Var
-
- DW Var The doubleword at the address of Var
-
-
- You can use BY, WO, and DW to specify an array element, but you must
- understand that expressions in the Debug window are treated like C
- expressions rather than assembler expressions. As a result, the syntax you
- use to watch a memory location in the Debug window is often different from
- the syntax in your assembly source. For example, assume you have the
- following data and code:
-
- warr DW 1, 2, 3, 4, 5, 6
- .
- .
- .
- mov bx,0
- mov cx,5
-
- loop1: add ax,warr[bx]
- add bx,2
- loop loop1
-
- You cannot watch the assembler expression warr[bx] directly. However, you
- can put an equivalent C expression in the Debug window:
-
- WO (char*)&warr + bx
-
- The address-of operator is necessary to make the C debugger look at the
- MASM array as a C array──that is, as an address. The value must cast to a
- character pointer because the debugger looks at it as a scaled C index
- rather than an unscaled assembler index. In this case, the assembler code
- adds 2 to the pointer BX to adjust for the variable size. You must tell
- the debugger to ignore its normal word scaling.
-
- Expressions are only scaled when there is a variable in the expression. In
- the expression WO BP+6 the 6 is not scaled──the expression means, "look at
- the word six bytes beyond the address that is in BP." However, in the
- expression WO &warr+6, the 6 is scaled because of the word size of the
- variable. Note that the variable size, not the expression type ( BY, WO,
- or DW), determines the size of scaling.
-
- If you are comfortable with C, you can also use C expressions to look at
- assembler expressions. Here are some examples you might find useful:
-
- Example Value Specified
- ──────────────────────────────────────────────────────────────────────────
- &Var The address of Var
-
- es:0x81,s The string at es:[81h] (the DOS command line when a
- program is started)
-
- &Arr[3] The third element of an array (note that the 3 will be
- scaled)
-
- *(&Arr+3) Equivalent to the previous expression
-
-
-
- 1.6.3 Tracing Execution
-
- The Run menu's Trace Into, Animate, and Step Over commands execute one
- statement of your program at a time. These commands are fully functional
- with assembly-language programs. However, debugging commands behave
- differently when you trace execution of an assembly-language module, as
- summarized below:
-
- ■ By default, screen swapping is on.
-
- ■ If the main module of the program is an assembly-language module, the
- first line of the program is never highlighted.
-
- ■ The Calls menu does not function unless you write your program
- according to certain guidelines.
-
- The rest of this section elaborates on these differences.
-
- When you trace execution of an assembly-language module, screen swapping
- is turned on. The environment does not support an Auto screen-swapping
- mode for assembly-language programs because it cannot detect when a
- program writes to the screen. Therefore, when executing a .ASM file, the
- environment equates the Auto screen-swapping selection with screen
- swapping turned on.
-
- You can always turn screen swapping off manually by choosing the Run/Debug
- command from the Options menu. When a dialog box appears, choose the Off
- option button in the Screen Swapping field.
-
- Screen swapping causes the environment to switch to a full output screen
- each time the program executes code. The effect is particularly noticeable
- when you choose the Animate command. Leaving screen swapping on preserves
- program output. However, if large portions of your program do not write to
- the video display, you may want to turn screen swapping off temporarily.
-
- The second debugging feature that operates differently for assembly-
- language programs is current-line highlighting. When you restart a
- program, the environment does not highlight the first line of code. The
- debugging facility does not know which line of code is the first to be
- executed, since this information is stored in the executable-file header.
- After you execute a trace, the second program line is highlighted, and
- thereafter current-line highlighting works as you would expect.
-
- The third feature that operates differently is the Calls command from the
- Debug menu. To ensure that the command works with assembly-language
- modules, either use the PROC directive with an argument list or local
- variables, as described in Chapter 3, "Writing Assembly Modules for C
- Programs," or else set up the framepointer (the BP register) as described
- in Appendix A, "Mixed-Language Mechanics." Both these methods set up a
- stack frame for each procedure, using the standard Microsoft methods. The
- environment checks stack frames to see what procedures have been called,
- and with what parameters.
-
-
- 1.6.4 Modifying Registers and Flags
-
- With the expanded QuickC/QuickAssembler environment, you can get much
- greater use from the Registers window. The Registers window displays more
- information than it does in the simple QuickC environment, and you can
- also use the window to alter register and flag values.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE By default, the environment does not display the Registers window.
- To open this window, choose the Window command from the View menu. A
- dialog box appears that lists all windows. Move the cursor to Registers
- and press the ENTER key, or move the mouse cursor to Registers and double
- click the Left mouse button. To close the window, repeat the procedure.
- ──────────────────────────────────────────────────────────────────────────
-
- The Registers window displays the contents of both 8086 and 8087
- registers. You can remove 8087 registers from the Registers window by
- choosing Display from the Options menu. When the dialog box appears, turn
- the Show 8087 option button off. The environment only displays 8087
- registers if you have a math coprocessor or have a program that calls
- floating-point emulator routines from a high-level language.
-
- You can alter values in the window by either using the mouse or the
- keyboard. To alter a value, you first select the item you want to change:
-
- ■ To alter a value with the mouse, select an item by clicking the Left
- mouse button.
-
- ■ To alter a value with the keyboard, first place the cursor on an item
- in the window. (Press TAB or SHIFT+TAB to cycle quickly through the
- items.) Then select the item by pressing the ENTER key. The List field
- has no function in this context and should be ignored.
-
- Choosing a flag toggles the flag to the opposite setting. Choosing a
- register brings up a dialog box. Type the new value for the register and
- press ENTER.
-
-
- 1.7 Viewing a Listing File
-
- When you assemble a module with the Debug build setting (the default),
- QuickAssembler can create a listing file. Choose the type of listing by
- using the Assembler Flags dialog box. (To access this dialog box, choose
- Make from the Options menu, then choose Assembler Flags.) You should also
- make sure that the One Pass Assembly option is not selected.
-
- A QuickAssembler listing file shows precisely how the assembler translated
- each line of code during the last program build. Each instruction in the
- source code is listed next to its corresponding numeric code (machine
- instruction).
-
- Listing files are particularly useful if your program uses macro calls or
- include files. The listing file displays each statement generated by a
- macro call and each line of code copied from an include file. Tables at
- the end of the listing file give information on macros, symbols,
- structures, groups, and records. Part 2 of this manual describes each of
- these features of assembly language.
-
- To view the listing file, assemble the source code at least once. You can
- view the listing file for the current module by choosing the Listing
- command from the View menu. You can also view the file with the CTRL+F2
- shortcut key.
-
- The listing file is then displayed in the Source window, as shown in
- Figure 1.6. You can page through this file by using all the normal
- cursor-movement commands. When you want to return to the previous file,
- press F2 or use the File menu. You can also leave the listing file by
- choosing the Listing command again; this action causes the environment to
- switch to the original line of source code that generated the current line
- of code. In particular, if you are in a listing file and move the cursor
- to a line generated from an include file (.INC), the Listing command
- switches directly to that include file.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 1.7 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Normally, you would choose the Listing command when in a .LST file or in a
- .ASM file with a corresponding .LST file (previously generated by a
- program build). If you are not in either of these types of files, the
- environment responds by displaying a dialog box for opening a file; *.lst
- is the default file name.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 2: Introducing 8086 Assembly Language
-
-
- Assembly-language programs control hardware directly, giving you the
- ability to write the fastest, smallest programs possible and to execute
- any operation. But assembly-language programming also requires an
- understanding of the architecture of 8086-family processors.
-
- Assembly language is close to machine code──the processor's numeric
- language of 1's and 0's. Each QuickAssembler instruction corresponds to an
- 8086 instruction but consists of a meaningful name (mnemonic) instead of a
- number. For example, the ADD instruction computes the sum of two items.
- QuickAssembler translates this instruction to produce a numeric code, such
- as 10000010 binary. The processor responds to this code when you run the
- program.
-
- This process of translation is called "assembling." Before you can
- assemble a program, you need to understand the basic concepts of the
- processor and of assembly language. This chapter presents these concepts.
-
-
- 2.1 Programming the 8086 Family
-
- If you have programmed in C, you can get a good grasp of 8086 assembly
- language by focusing on the differences between the two languages:
-
- 1. A C statement may combine many complex operations, but each line of
- assembly language specifies just one limited action called an
- "instruction." QuickAssembler also supports a number of nonexecutable
- statements called "directives," which provide structure to the program,
- declare data objects, and provide other information.
-
- Sections 2.2-2.4 explain the basics of writing instructions and
- directives.
-
- 2. C programs deal with memory locations (known as variables), but
- assembly-language programs must deal with registers as well. A
- "register" is a special memory location inside the processor itself,
- having a permanent name rather than a numeric address.
-
- Section 2.5, "8086-Family Registers," describes the use of each
- register.
-
- 3. A data object in a C program can be arbitrarily complicated.
- Assembly-language statements work on objects accessed through four
- specific modes: immediate, register, direct memory, and indirect
- memory. Each mode has specific properties and limitations imposed by
- the processor.
-
- Section 2.6, "Addressing Modes," explains each of these four modes and
- gives examples.
-
- 4. The processor combines two 16-bit addresses to access each memory
- location. This mechanism is called "segmented addressing." Assembly
- language often requires a more complete understanding of segmented
- addressing than C does.
-
- Section 2.7, "Segmented Addressing and Segment Registers," explains
- the full implications of segmented addressing.
-
- Of the features listed above, segmented addressing is unique to the 8086
- family. The 8086 is further distinguished from other processors by its set
- of string operations, which permit fast initialization and copying of
- blocks of data. You can read more about the string operations in Chapter
- 16, "Processing Strings."
-
-
- 2.2 Instructions, Directives, and Operands
-
- The 8086-family processors understand only one kind of statement: an
- instruction. QuickAssembler understands two kinds of statements:
- instructions and directives.
-
- As explained above, an instruction corresponds to a specific action that
- the processor executes at run time. The fundamental task of the assembler
- is to correctly translate each of these statements to specific
- machine-code instructions.
-
- As nonexecutable statements, directives are not translated to machine
- actions. However, they give information to the assembler that affects how
- other statements are translated. For example, some of the most important
- directives declare data. These directives, in turn, help the assembler
- correctly interpret instructions that refer to the data.
-
- The rest of this section explains each part of an assembly-language
- statement; the general syntax applies to both instructions and directives.
- The section ends by stating the basics of entering numbers in different
- radixes.
-
- Syntax
-
- Each line of source code consists of a blank line or a statement. Each
- statement is an instruction or directive, and can contain as many as 512
- characters. Statements can have up to four fields, as shown below:
-
- [[name]] [[operation]] [[operands]] [[;comment]]
-
- Each field (except the comment field) must be separated from the other
- fields by a space or tab character. You can enter statements in uppercase
- or lowercase letters. By default, QuickAssembler is not case sensitive,
- but it does preserve case for external variables──thus providing
- compatibility with C, which is case sensitive. You can control case
- sensitivity by using the Assembler Flags dialog box.
-
- As a convention, sample code in this manual uses uppercase letters for
- directives, hexadecimal letter digits, and segment definitions.
-
-
- 2.2.1 The Name Field
-
- The name field labels the statement with a symbolic name that other parts
- of the program can reference. The meaning of the name depends on the type
- of statement.
-
- One of the most important uses of this field occurs in data declarations.
- These declarations are much like variable declarations in C. The statement
- defines the type and initial value. You use the name elsewhere in the
- program, when you want to access the data.
-
- QuickAssembler is different from C, however, in that the symbolic name
- occurs in the first field. For example, the following DB directive
- (Declare Bytes) associates the name string with a series of characters:
-
- string DB "Hello, world"
-
- In instructions, the name field functions like a program label in C: it
- provides a target for a jump or call instruction elsewhere in the program.
-
- To label an instruction, follow the name field with a colon (:). You can
- place the name on the same line as the rest of the instruction or, to
- improve readability, on a separate line. The following example shows the
- latter case:
-
- top: ; This label marks the top of the loop
- mov ax,1 ; This is first instruction in the loop
-
- There are other ways to label instructions. See Section 6.4, "Defining
- Code Labels," for more information on how to declare labels.
-
-
- 2.2.2 The Operation Field
-
- The operation field states the action of the statement. This field
- determines the fundamental type of the statement──instruction or
- directive. It also determines what additional syntax, if any, is required.
- Some operations require an entry in the name field; most do not. If the
- operation is an instruction, it strictly determines how many and what kind
- of operands are legal.
-
- This field contains exactly one item──an instruction or directive
- mnemonic. "Mnemonics" are abbreviated, easy-to-remember names that each
- symbolize a different operation (for instance, ADD, SUB, and OR). Examples
- of directive mnemonics include EQU (Equate) and DB (Declare Bytes).
-
-
- 2.2.3 The Operand Field
-
- The operand field lists the objects on which the statement operates.
- Multiple operands are separated by commas. These objects can be registers,
- constants, or memory locations. A memory location is typically represented
- as a variable, although it can also be expressed as a numeric address or
- complex expression.
-
- Registers and constants require no previous declaration. To refer to a
- variable, however, you should first declare the name with a data
- directive, such as DB (Declare Bytes). The following example declares the
- variable count and then uses it in an instruction:
-
- count DB 7 ; Declare count as a byte variable
- .
- .
- .
- inc count ; count = count + 1
-
- In the first statement, count appears in the name field and the number 7
- appears in the operand field. The DB directive associates count with the
- address of a byte initialized to 7. In the second statement, count appears
- in the operand field. The INC instruction (increment) adds 1 to count,
- thus increasing the value of the data to 8.
-
- The next section gives more information on how to declare memory locations
- as data types. Section 2.6, "Addressing Modes," gives a complete
- description of all the different methods for specifying operands.
-
-
- 2.2.4 The Comment Field
-
- The comment field lets you add text that appears in source code but is
- ignored by the assembler. You can enter any text you want in this field.
- Typically, you would use it to document the purpose of the statement. The
- purpose of an assembly-language statement is not always self-explanatory,
- and for this reason, programs often contain at least one comment for each
- instruction.
-
- Single-line comments always begin with a semicolon (;). You can also
- create a multiline comment by one of two methods. You can enter successive
- comment lines as shown below:
-
- add count,5 ; Add 5 to count.
- ; ADD is the operation.
- ; count and 5 are operands.
- sub Sum,12 ; Subtract 12 from Sum.
- ; SUB is the operation.
- ; Sum and 12 are operands.
-
- You can also use the COMMENT directive, which lets you enter multiline
- comments without using the semicolon. This directive has the following
- syntax:
-
- COMMENT delimiter [[text]]
- text
- [[text]] delimiter [[text]]
-
- All text between the first delimiter and the line containing a second
- delimiter is ignored by the assembler. The delimiter character is the
- first nonblank character after the COMMENT directive. The text includes
- the comments up to and including the line containing the next occurrence
- of the delimiter.
-
- Example
-
- COMMENT + The plus
- sign is the delimiter. The
- assembler ignores the statement
- following the last delimiter
- + mov ax,1 (ignored)
-
-
- 2.2.5 Entering Numbers in Different Bases
-
- As with C, you can enter assembly-language constants as decimal,
- hexadecimal, or octal. You can also enter binary constants. By default,
- all constants are decimal, but you specify a different default with the
- RADIX directive.
-
- Hexadecimal constants appear frequently in assembly-language programs. To
- indicate a hexadecimal constant, add an uppercase or lowercase H suffix.
- If the first digit is one of the letters A-F, prefix the constant with a
- leading 0 to indicate that the number is not a symbolic name.
-
- Examples
-
- 100H
- 10FAh
- 0be03H
- 0FFh
-
- You may often want to enter binary constants as well, particularly when
- constructing bit masks. To indicate a binary constant, simply add an
- uppercase or lowercase B suffix.
-
- For more information on using different bases and using the RADIX
- directive, see Section 6.1.1.2, "Setting the Default Radix."
-
-
- 2.2.6 Line-Continuation Character
-
- You can create program lines that extend over more than one physical line
- by using the backslash (\) as a line-continuation character. The backslash
- must be the last character on the line. Comments cannot follow it. A
- backslash is not considered a continuation character if it occurs in a
- comment.
-
- Example
-
- BigProc PROC FAR \
- USES DS SI DI, \
- IntArg:WORD, \
- String:FAR PTR BYTE, \
- Ptr:FAR PTR BIGSTRUC, \
- Long:DWORD
- .
- .
- .
-
- ret
- BigProc ENDP
-
- In this example, the line continuation-character is used to specify
- multiple procedure arguments with the extended PROC syntax. All the
- arguments must be placed on a single logical line, but they would go past
- the edge of the editor screen if not placed on separate lines. The
- continuation character is also useful for long macro calls and data
- initializations.
-
-
- 2.3 8086-Family Instructions
-
- The 8086-family processors support more than 80 instructions, but you
- don't need to memorize the entire instruction set. Once inside the
- expanded QuickC environment, you can get instant information on any
- instruction. Move the cursor to an instruction keyword on the screen, then
- press F1. To find the appropriate instruction for the action you want to
- perform, refer to Part 3 of this book, which provides a topical survey of
- instructions.
-
- Many programs can be written with just a few of the most common
- instructions. Sections 2.3.1 and 2.3.2 introduce some of these
- instructions, grouping them into two sets: instructions that manipulate
- data and instructions that control program flow. The programs in Chapters
- 3 and 4 use these same instructions to illustrate basic concepts of 8086
- assembly language.
-
-
- 2.3.1 Data-Manipulation Instructions
-
- The first group of instructions manipulate data. Each causes the processor
- to copy data or perform a calculation at run time. Some of the simpler C
- statements translate directly into a single instruction, so this section
- uses C statements for illustration. Here are the six basic data-
- manipulation instructions introduced in this section:
-
- ■ MOV (move data)
-
- ■ ADD (add second operand to first)
-
- ■ SUB (subtract second operand from first)
-
- ■ INC (increment operand)
-
- ■ DEC (decrement operand)
-
- ■ MUL (integer multiplication)
-
- The processor supports a great many other data-manipulation instructions,
- which are covered in Part 3 of this manual.
-
-
- 2.3.1.1 The MOV Instruction
-
- The MOV instruction, probably the most frequently used 8086 instruction,
- copies data from one location to another. The instruction leaves the
- source data unaffected, so it is more a copy than a move. The MOV
- instruction takes two operands:
-
- MOV destination,source
-
- The instruction copies the value of the source to the destination. It
- might seem more logical to place the source operand first, until you
- consider that C and BASIC assignments use the same order. For example, the
- instruction
-
- mov count,5
-
- places the value 5 at the memory location count and thus performs the same
- action as the C statement
-
- count = 5;
-
- The destination operand is similar to an "lvalue" in C. Instructions that
- have two operands always interpret the leftmost operand as the
- destination, or lvalue. The destination is the operand that the
- instruction can alter; thus, it can't be a constant. Another limitation on
- instructions with two operands is that the operands cannot both be memory
- locations.
-
-
- 2.3.1.2 The ADD Instruction
-
- The ADD instruction, like MOV, takes two operands: a destination and a
- source. The processor adds the two operands together, storing the result
- in the destination (on the left). This action will be familiar to C
- programmers, since the instruction
-
- add sum,10
-
- adds 10 to the memory location sum and thus performs the same action as
- the C statement
-
- sum += 10;
-
- The 8086 does not perform automatic scaling for pointer addition as C
- does. The program itself must perform scaling for all pointer arithmetic.
-
-
- 2.3.1.3 The SUB Instruction
-
- The SUB instruction is the counterpart of ADD: it subtracts the source
- operand from the destination operand, storing the result in the
- destination (on the left). Thus, the instruction
-
- sub total,7
-
- performs the same action as the C statement
-
- total -= 7;
-
-
- 2.3.1.4 The INC and DEC Instructions
-
- The INC (Increment) and DEC (Decrement) instructions add and subtract 1,
- respectively. They are similar to, but faster than, ADD and SUB, and are
- provided because adding and subtracting by 1 are such common operations.
- The instruction
-
- inc count
-
- performs the same action as the C statement
-
- count++;
-
-
- 2.3.1.5 The AND Instruction
-
- The AND instruction is one of several bitwise logic operations supported
- by the 8086. AND provides an efficient way to mask out bits. The
- instruction
-
- and stuff,0FFF0h
-
- masks out the four lowest bits of stuff, as does the C statement
-
- stuff &= 0x0FFF0;
-
-
- 2.3.1.6 The MUL Instruction
-
- The MUL instruction multiplies two items, but one of these items is an
- "implied operand"──that is, an operand you do not specify. For example,
- the 16-bit version of the MUL instruction takes one explicit 16-bit
- operand:
-
- mul factor
-
- The other operand is the AX register. The processor multiplies factor by
- the value of AX, storing the low 16 bits of the result in AX. The
- description of the AX register in Section 2.5.1, "The General-Purpose
- Registers," gives more information on MUL.
-
-
- 2.3.2 Control-Flow Instructions
-
- The control-flow instructions enable the program to execute loops and to
- make decisions. Some of these instructions transfer control of the program
- to a new address. The conditional jump instructions let you provide
- program logic: they look at the result of a previous operation, and then
- decide whether to jump or not. Here are the five basic control-flow
- instructions introduced in this section:
-
- ■ JMP (Jump unconditionally)
-
- ■ CMP (Compare──subtract without storing result)
-
- ■ JE (Jump If Equal)
-
- ■ JA (Jump If Above)
-
- ■ JB (Jump If Below)
-
- The processor supports a number of other control-flow instructions,
- including several conditional jumps. See Section 15.1.2, "Jumping
- Conditionally," for a description of these instructions.
-
-
- 2.3.2.1 The JMP Instruction
-
- The JMP instruction causes the processor to jump to a new program address.
- Like the C goto statement, JMP takes one operand: a label associated with
- another statement. The instruction
-
- jmp begin
-
- jumps to the label begin, and thus performs the same action as the C
- statement
-
- goto begin;
-
-
- 2.3.2.2 The CMP Instruction
-
- The CMP instruction, like SUB, performs a subtraction. But CMP doesn't
- store the result; instead, it just sets processor flags in preparation for
- a conditional jump (such as JE, JA, or JB).
-
- A "processor flag" is a bit that resides in the processor and indicates
- whether a specific condition is on or off. For example, the Zero flag
- indicates that the result of the last operation produced zero. The JE
- instruction (Jump If Equal) checks this one flag only, jumping if it is
- set. Other conditional jumps determine a result by checking a combination
- of flag settings. See Section 2.5.4, "The Flags Register," for a
- description of all the flags.
-
- Many instructions, including SUB, set processor flags. However, some of
- these instructions have strong side effects. Use ADD or SUB to prepare for
- a conditional jump when convenient. But use CMP when you need to make a
- simple comparison without altering data.
-
-
- 2.3.2.3 The Conditional Jump Instructions
-
- The JE, JA, and JB instructions are conditional jumps (meaning Jump On
- Equal, Jump If Above, and Jump If Below, respectively). Like JMP, they
- each take one argument: a program label to which to jump. Unlike JMP, they
- cause the processor to jump only when certain flag settings are detected.
- The result is that when you use CMP in combination with a conditional jump
- instruction, you create an if-then relationship similar to an if statement
- in a high-level language. Consider the following instructions:
-
- cmp sum,10 ; Compare sum to 10
- ja top ; If sum > 10, jump to top
-
- This logic is a little different from a C program. The first instruction
- makes the comparison. The second states, "If the result of the previous
- instruction was above zero, then jump." Taken together, these two
- instructions perform the same action as the C statement
-
- if( sum > 10 )
- goto top;
-
- Of course, most C programmers do not use many goto statements. Typically,
- you would test for a condition and execute a series of statements if the
- condition is true, as in the following code:
-
- if( sum >= 10 )
- {
- sum = 1;
- count += 2;
- delta = 5;
- }
-
- To implement this code in assembly language, test for the opposite
- condition, then jump past statements if they should not be executed. For
- example, the following code executes the three statements inside the if
- block only if sum is greater than or equal to 10:
-
- TopOfBlock:
- cmp sum,10 ; Compare sum to 10
- jb SumNotGreater ; If sum < 10, do NOT do
- ; next three statements
- mov sum,1 ; sum = 1
- add count,2 ; count = count + 2
- mov delta,5 ; delta = 5
- SumNotGreater:
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE JA (Jump If Above) and JB (Jump If Below) each work properly when
- you compare unsigned integers. To compare signed integers, use JG (Jump If
- Greater) and JL (Jump If Less Than). See Section 15.1.2, "Jumping
- Conditionally," for a complete list of conditional jump instructions.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 2.4 Declaring Simple Data Objects
-
- This section describes how to declare global variables──often called
- "static" because each corresponds to a fixed memory location.
-
- Programs generally require data. If you wrote a program in machine code,
- you'd have to reserve locations in memory for data, determine the address
- of each data object, and remember these addresses whenever you operated on
- memory. Fortunately, the assembler reserves memory locations for you and
- associates each location with a symbolic name.
-
- You use data directives to tell the assembler how to allocate and refer to
- memory. The most common data directives for characters and integers are:
-
- Directive Description
- ──────────────────────────────────────────────────────────────────────────
- DB Declare byte (either a small integer or a character)
-
- DW Declare word (2-byte integer)
-
- DD Declare doubleword (4-byte integer)
-
-
- To use these directives, place the name of the variable first, then enter
- the data directive. The third column (operand field) contains one or more
- initial values. Use a question mark to indicate an item with no initial
- value.
-
- aByte DB 1 ; aByte is a 1-byte integer, initialized to 1
- area DW 500 ; area is a 2-byte integer, initialized to 500
- population DD ? ; population is a 4-byte integer, no initial value
-
- These directives correspond roughly to the following C statements:
-
- char aByte = 1;
- int area = 500;
- long population;
-
- Assembly data declarations are different from C declarations, however, in
- that assembly data declarations are not declared signed or unsigned.
- Instead, you must remember whether you intend to treat a variable as
- signed or unsigned, and choose the appropriate operations.
-
- Data directives reserve memory in the object file. They also associate
- each variable with a name and a size attribute.
-
- The assembler uses this information to correctly assemble instructions
- that operate on variables. For example, at the machine-code level, the INC
- instruction can be encoded to increment either a byte or a word of data.
- The way the assembler encodes the instruction
-
- inc myvar
-
- depends on whether myvar was declared as a byte or word. (If it was
- declared a doubleword, the instruction is illegal.) Another important use
- of size attributes is in checking the validity of two operands. For
- example, the following instruction causes the assembler to print a warning
- message, because aByte and bx do not share the size attribute:
-
- mov bx,aByte ; Move aByte into a word register
-
- Moving a byte into a word location is not possible. After issuing the
- warning, the assembler adjusts the instruction as if it were written as
- follows:
-
- mov bx,WORD PTR aByte ; Move the word at aByte to BX
-
- The PTR operator temporarily modifies the size attribute of the object
- that follows it. PTR can be used with a number of different data types, as
- shown below:
-
- Keywords Refers to
- ──────────────────────────────────────────────────────────────────────────
- BYTE PTR object The byte at address of object
-
- WORD PTR object The word at address of object
-
- DWORD PTR object The doubleword at address of object
-
-
- However, this adjustment may not produce the action you really want. The
- PTR operator is not quite the same as a type cast in C. The C (int) type
- cast manipulates data so that it represents the same value, but in a
- different format. WORD PTR does no data manipulation──it simply causes the
- instruction to operate on the word at the given address. In the example
- above, the use of WORD PTR causes two adjacent bytes of data to be loaded
- from memory into BX. If what you really want is to move a single byte of
- data to BX, but convert it to a word, use the following code:
-
- mov bl,aByte ; Lower byte of BX = aByte
- sub bh,bh ; Higher byte of BX = 0
-
- The example above only works properly when handling unsigned numbers. When
- working with signed quantities, use the CBW instruction, as described in
- Section 13.2.1, "Extending Signed Values."
-
- By far the most common use of WORD PTR is in operations on objects 32 bits
- or longer. An 8086 instruction can operate only on a byte or a word. You
- use WORD PTR to tell the assembler to operate on one word at a time. For
- example, the following code uses two moves to copy the 32-bit integer X to
- a similar integer, Y:
-
- X DD 80000 ; X is a long integer = 80,000
- Y DD ? ; Y is a long integer
- .
- .
- .
- mov ax, WORD PTR X ; Move word at X to word at Y
- mov WORD PTR Y, ax ; (using AX as intermediate register)
- mov ax, WORD PTR X[2] ; Move word 2 bytes past X to
- mov WORD PTR Y[2], ax ; word 2 bytes past Y
-
- Brackets ([ ]) are used with arrays as well as portions of large data
- objects as shown here; they also let you add a displacement to an address.
- The use of brackets is further explained in the next few paragraphs.
-
- Assembly language makes almost no distinction between simple variables and
- arrays. You refer to the first element of an array just as you would a
- simple variable──index brackets are optional. To declare an array or
- string, just give a series of initial values:
-
- warray DW ?,?,?,?
- xarray DW 1,2,3,4
- mystring DB "Hello, there."
-
- To refer to the first element of warray, type warray into your program (no
- brackets required). To refer to the next element, use either of these two
- forms, each of which refers to the object two bytes past the beginning of
- warray:
-
- warray+2
- warray[2]
-
- When used with a variable name, the brackets do nothing but add a number
- to the address. If warray refers to the address 2400h, then warray[2]
- refers to the address 2402h. However, the brackets have an additional
- function when used with registers. See Section 2.6.4, "Indirect Memory
- Operands," for more information.
-
- In assembly language, array indexes are zero-based, as in C; but unlike C,
- they are unscaled. The number inside brackets always represents an
- absolute distance in bytes.
-
- In practical terms, the fact that indexes are unscaled means that if the
- size of an element is larger than one byte, you must multiply the index of
- the element by its size (in this case, 2), then add the result to the
- address of the array. Thus, the expression warray[4] represents the third
- element, which is 4 bytes past the beginning of the array. Similarly, the
- expression warray[6] represents the fourth element.
-
- In general, the numeric offset required to access an array element can be
- calculated as shown in the following formula:
-
- Nth element of Array = Array[(N-1) * size of element]
-
-
- 2.5 8086-Family Registers
-
- A "register" is a special memory location inside the processor itself.
- Operations on registers execute faster than operations on main memory. The
- processor has a limited number of registers. Moreover, many operations on
- the 8086 are impossible without the use of registers at some point. For
- example, you cannot copy data between two memory locations without first
- moving it into a register.
-
- Figure 2.1 shows the registers common to all the 8086-family processors.
- The 8086 registers can be grouped by function into the following sets:
- general-purpose registers, index registers, pointer registers, and segment
- registers. Each set corresponds to a different ending letter (X, I, P, or
- S). The registers in each set are as follows:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 2.5 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- ■ The four general-purpose registers are AX, BX, CX, and DX. These
- registers exist for the general use of the program. You can use these
- registers to store temporary values and perform calculations.
-
- ■ The two index registers are SI (Source Index) and DI (Destination
- Index). These registers can also be used for general storage, but are
- less flexible than the general-purpose registers. SI and DI have a
- special purpose in string instructions.
-
- ■ The pointer registers are IP (Instruction Pointer), SP (Stack Pointer),
- and BP (Base Pointer). These registers should not be confused with BX,
- which is the register normally used for pointer indirection. IP, SP,
- and BP each have a special purpose in conjunction with procedure calls.
- SP and BP should be altered with care; IP cannot be altered or
- referenced directly at all.
-
- ■ The segment registers are CS, DS, SS, and ES. This section does not
- describe these registers. You generally don't alter or reference them
- except when starting the program or accessing data from multiple
- segments. Section 2.7, "Segmented Addressing and Segment Registers,"
- describes each segment register and how it is important to programs.
-
- In addition, there is a flags register that indicates the status of the
- process.
-
-
- 2.5.1 The General-Purpose Registers
-
- The general-purpose registers have many important uses in an 8086
- assembly-language program, including:
-
- ■ Storing the values most frequently used. Operations on registers are
- much faster than operations on memory. Therefore, place the program's
- principal values in registers. In larger programs, you will probably
- have too many variables to place them all into registers. You can,
- however, place a value in a register while it is in heavy use.
-
- ■ Supporting operations with two or more variables. Direct
- memory-to-memory operations are illegal with 8086 processors. To
- operate on two memory locations, you need to first load one of the
- values into a register.
-
- ■ Enabling use of all the instructions. Many instructions require the use
- of a particular register. For example, the MUL instruction always works
- with the AX register (or AL, if you specify a byte operand).
-
- ■ Passing or returning values in a procedure or interrupt call.
-
- Each of the general-purpose registers──AX, BX, CX, and DX──can be accessed
- as single 16-bit registers, or as two 8-bit registers. As shown in Figure
- 2.1, the AH, BH, CH, and DH registers represent the high-order 8 bits of
- the corresponding registers. Similarly, AL, BL, CL, and DL represent the
- low-order 8 bits.
-
- This design lets you operate directly on two-byte and one-byte objects. It
- also lets you load a two-byte object and then manipulate one byte at a
- time.
-
- Each of the general-purpose registers has special uses, discussed below.
-
-
- 2.5.1.1 The AX Register
-
- The AX (Accumulator) register is ideal for repeated calculations. It
- accumulates totals as well as the results of multiplication and division.
- Using AX can add speed to your program, because some instructions have
- special encodings optimized for use with AX.
-
- Multiplication instructions always use AX. In the 16-bit version of the
- MUL instruction, you specify one 16-bit value. The processor multiplies
- this value by the contents of AX and stores the 16 least significant
- binary digits of the result in AX. (The 16 most significant digits are
- stored in DX.)
-
- The following example multiplies base times height, and stores the result
- in area. These instructions are sufficient if the result does not exceed
- the limit for two-byte numbers (otherwise, the DX register will contain
- the overflow):
-
- base DW 5 ; base is a word, initialized to 5
- height DW 3 ; height is a word, initialized to 3
- area DW ? ; area stores 16-bit (word) product
- .
- .
- .
- mov ax,base ; AX = base
- mul height ; AX = AX * height
- mov area,ax ; area = result
-
- AX has a similar use in division instructions (DIV and IDIV). See Section
- 14.4, "Dividing," for examples of division. Also, in port I/O
- instructions, AX holds the data to write to a port and receives data read
- from a port.
-
- By convention, AX has another special use. Microsoft high-level languages
- expect AX to contain a function's return value. If the return value is
- longer than four bytes, the high-level languages expect DX:AX to point to
- the location of the return value.
-
-
- 2.5.1.2 The BX Register
-
- The BX (Base) register has great importance as a pointer or address
- register. All 16-bit registers can hold addresses, but not all registers
- can be used to retrieve the contents of an address. In C this operation is
- called "pointer dereferencing," or "indirection." The C source code to
- implement this action might look like this:
-
- value = *pVar;
-
- The following assembly code achieves the same effect:
-
- mov bx,pVar ; BX = pVar
- mov value,[bx] ; value = object pointed to by BX
-
- The brackets around BX in the second instruction direct QuickAssembler to
- consider BX a pointer to the actual operand. The item [bx] is an example
- of an indirect memory operand. See Section 2.6.4, "Indirect Memory
- Operands," for more information.
-
-
- 2.5.1.3 The CX Register
-
- The CX (Count) register has special meaning to instructions with a
- repeat-operation feature. The contents of CX indicate how many times to
- repeat execution. Loops, string operations, certain jump instructions, and
- shifts and rotates all use CX this way.
-
- A common instruction that uses CX to repeat execution is LOOP, which is
- analogous to the C for statement. This instruction subtracts one from CX,
- then jumps to the given label if CX is not equal to 0. Thus, the following
- loop executes 20 times:
-
- mov cx,20
- top:
- .
- .
- .
- loop top
-
- In the case of shifts and rotates, CL (the lower byte of CX) indicates how
- many bit positions to shift. See Section 14.7, "Shifting and Rotating
- Bits," for more information. Also, when an instruction has a REP (repeat)
- prefix, the value in CX determines how many times the instruction is
- executed.
-
-
- 2.5.1.4 The DX Register
-
- The DX (Data) register often is used only for storage of temporary values.
- However, DX has a special function in some versions of the multiplication,
- division, and port instructions. Each of these uses is closely related to
- AX. In fact, DX is located next to AX in the actual physical layout of the
- 8086 chip. (Figure 2.1 places the registers in the order AX, BX, CX, and
- DX merely for ease of reference.)
-
- When you multiply 16-bit values with MUL, DX holds the high 16 bits of the
- 32-bit result. The following example is a variation of the one given for
- AX. In this example, Area is a 32-bit value (a long integer), and it
- stores the entire 32-bit result of the MUL instruction:
-
- base DW 500 ; base is a word, initialized to 500
- height DW 300 ; height is a word, initialized to 3
- area DD ? ; area stores doubleword product
- .
- .
- .
- mov ax,base ; AX = base
- mul height ; DX:AX = AX * height
- mov WORD PTR area[0],ax ; Store low 16 bits
- mov WORD PTR area[2],dx ; Store high 16 bits
-
- By convention, Microsoft high-level languages use both DX and AX to return
- four-byte values from procedures. The high 16 bits are placed in DX.
-
-
- 2.5.2 The Index Registers
-
- The two index registers are SI (Source Index) and DI (Destination Index).
- These registers are similar to the general-purpose registers, but cannot
- be accessed one byte at a time. Index registers are efficient places to
- store general data, pointers, array indexes, and pointers to blocks of
- memory. They have the following special uses:
-
- ■ You can use both SI and DI for pointer indirection, as you can BX and
- BP. "Pointer indirection" is the process of retrieving the value that a
- pointer points to.
-
- ■ You can use SI or DI to hold an array index. Indirect memory operands
- can combine this index with a base address stored in BX or BP.
-
- ■ You prepare for string instructions, which execute highly efficient
- block operations, by loading SI with a source address and DI with a
- destination address.
-
- See Chapter 16, "Processing Strings," for information on how to use
- string instructions.
-
- When you write a procedure to be called by C, be careful to leave SI and
- DI in the same state they were in before C called your procedure.
- Microsoft QuickC allocates register variables in SI and DI.
-
-
- 2.5.3 The Pointer Registers
-
- The pointer registers──BP, SP, and IP──are all special-purpose registers
- that help implement procedure calls. The processor alters SP (Stack
- Pointer) and IP (Instruction Pointer) whenever you call a procedure, and
- you can use BP (Base Pointer) to access parameters placed on the stack.
-
- Despite their names, pointer registers are not good places to store
- pointer variables or other general program data; you should generally use
- BX, SI, and DI for that purpose.
-
-
- 2.5.3.1 The BP Register
-
- You can use BP (Base Pointer) to retrieve the contents pointed to by an
- address. However, by default, the BP register points into the stack
- segment rather than the data segment. Therefore, BP is typically used to
- access items on the stack.
-
- The "stack" is the area of memory that holds parameters, local variables,
- and return addresses for each procedure being executed. Although you can
- store general data in BP, it is commonly used to access parameters of the
- current procedure.
-
- When you use the PROC statement with a parameter list as explained in the
- next chapter, avoid altering the value of BP. The PROC directive generates
- instructions that set BP to point to the procedure's local stack area, and
- then use BP to access parameters and local data. If BP changes, all your
- references to parameters will be wrong.
-
- To learn how to set BP yourself, see Section 15.3.3, "Passing Arguments
- on the Stack," or Appendix A, "Mixed-Language Mechanics."
-
-
- 2.5.3.2 The SP Register
-
- The SP (Stack Pointer) register points to the current location within the
- stack segment. As you add or remove items from the stack, the processor
- changes the value of SP, so that SP always points to the top of the stack.
-
- The processor stack works like a stack of dishes: you push items onto the
- top of the stack as you need to save them, then pop them off the stack
- when you're ready to use them again. The stack is a last-in-first-out
- mechanism. You can only remove the item currently at the top of the stack.
- Items must be removed in the reverse order they were placed there.
-
- The processor automatically pushes and pops return addresses for you when
- you call or return from a procedure. A "return address" is the place a
- procedure or routine returns to when done. You can also place other values
- on the stack by using the PUSH and POP instructions.
-
- The PUSH instruction saves the value of a register or memory location by
- placing it on the stack. POP removes the value from the stack and places
- it back in the original location. (You can also pop the contents into some
- other location if you wish.) Use these instructions when you need to
- preserve a value. In the following example, BX holds an important value,
- but the program needs temporary use of BX:
-
- push bx ; Save BX on the stack
- mov bx,pointer ; Load pointer into BX
- mov value,[bx] ; value = *pointer
- pop bx ; Pop old value back into BX
-
- The stack also holds parameters and local variables during procedure
- calls. Sections 13.4.2, "Using the Stack," and 15.3.3, "Passing
- Arguments on the Stack," provide more information on using the stack.
- Appendix A, "Mixed-Language Mechanics," explains how to manipulate the
- stack to make room for local variables──one of the few times you should
- change the value of SP directly.
-
-
- 2.5.3.3 The IP Register
-
- You cannot adjust the IP (Instruction Pointer) register directly; it can
- only be adjusted indirectly, through control-flow instructions. For this
- reason, Quick-Assembler does not even recognize IP as a keyword.
-
- The IP register contains the address of the next instruction to execute.
- The instructions that control program flow (calls, jumps, loops, and
- interrupts) automatically set the instruction pointer to the proper value.
- The processor pushes the address of the next instruction onto the stack
- when you call a procedure. The processor pops this instruction into IP
- when the procedure returns. Normally, the processor increments IP to point
- to the next instruction in memory.
-
-
- 2.5.4 The Flags Register
-
- The flags register, shown in Figure 2.2, is a 16-bit register made up of
- bits that each indicate some specific condition. Most of the flags help
- determine the behavior of conditional jump instructions. Many
- instructions──most notably CMP──set these flags in a meaningful way. Other
- flags (Trap, Interrupt Enable, and Direction) do not affect conditional
- jump instructions but control the processor's general operation.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 2.5.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The nine flags common to all 8086-family processors are summarized below,
- progressing from the low-order to high-order flags. In these descriptions,
- the term "set" means the bit value is 1, and "cleared" means the bit value
- is 0.
-
- Instructions actively set and clear various flags. For example, if the
- result of a SUB or CMP instruction is zero, it sets the Zero flag. This
- flag setting can, in turn, affect subsequent instructions──in particular,
- conditional jumps. Some instructions do not set the flags at all, or have
- random effects on some flags. Consult on-line Help for each instruction to
- see precisely how it affects flag settings.
-
- Flag Description
- ──────────────────────────────────────────────────────────────────────────
- Carry Is set if an operation generates a carry to or a
- borrow from a destination operand. (Operation viewed
- as unsigned.)
-
- Parity Is set if the low-order bits of the result of an
- operation contain an even number of set bits.
-
- Auxiliary Carry Is set if an operation generates a carry to or a
- borrow from the low-order four bits of an operand.
- This flag is used for binary coded decimal arithmetic.
-
- Zero Is set if the result of an operation is 0.
-
- Sign Equal to the high-order bit of the result of an
- operation (0 is positive, 1 is negative).
-
- Trap If set, the processor generates a single-step
- interrupt after each instruction. Debugging programs,
- including the QuickC/QuickAssembler debugging
- facility, use this feature to execute a program one
- instruction at a time.
-
- Interrupt Enable If set, interrupts will be recognized and acted on as
- they are received. The bit can be cleared to
- temporarily turn off interrupt processing.
-
- Direction Can be set to make string operations process down from
- high addresses to low addresses, or can be cleared to
- make string operations process up from low addresses
- to high addresses.
-
- Overflow Is set if the result of an operation is too large or
- small to fit in the destination operand. (Operation
- viewed as signed.)
-
-
- The Carry and Overflow flags are similar, but have one major difference:
- the Carry flag is set according to the rules of unsigned operations, and
- the Overflow flag is set according to the rules of signed operations. A
- signed operation uses two's complement arithmetic to represent negative
- numbers. One of the features of this system is that a number is negative
- if the most significant bit is set. Unsigned operations do not view any
- number as negative.
-
- Thus, the same ADD operation can be viewed as adding FFFFH to FFFEH
- (unsigned) or -1 to -2 (signed). This operation would set the Carry flag
- (because the maximum unsigned value is FFFFH), but not the Overflow flag.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE This manual does not describe the details of two's-complement
- arithmetic. For more information, see one of the references listed in the
- Introduction.
- ──────────────────────────────────────────────────────────────────────────
-
- Each of the conditional jump instructions responds to a particular flag or
- combination of flags. For example, the JZ (Jump If Zero) instruction jumps
- if the Zero flag is set. The JBE (Jump If Below or Equal) jumps if either
- the Zero flag or the Carry flag is set. For a description of all the
- conditional jump instructions, see Section 15.1.2, "Jumping
- Conditionally."
-
-
- 2.6 Addressing Modes
-
- You can specify several kinds of operands: immediate, register, direct
- memory, and indirect memory. Each type of operand corresponds to a
- different addressing mode. The "addressing mode" is the method that the
- processor uses to calculate the actual value of the operand at run time.
-
- You don't specify addressing modes explicitly. You simply give an operand,
- and the assembler determines the corresponding addressing mode.
-
- The four types of operands are summarized below, and described at length
- in the rest of this section.
-
- Operand Type Description
- ──────────────────────────────────────────────────────────────────────────
- Immediate A constant value contained in the instruction itself
-
- Register A 16-bit or 8-bit register
-
- Direct memory A fixed location in memory
-
- Indirect memory A memory location determined at run time by using the
- address stored in one or two registers
-
-
- Direct memory and indirect memory operands are closely related. Syntax
- displays in this manual, as well as in on-line Help, often refer to memory
- operands. You can use either type of memory operand wherever memory is
- specified. From the processor's viewpoint, the only difference between
- these types of operands is how the address is determined. The address
- specified in the memory operand is called the "effective address" of the
- instruction.
-
- Most two-operand instructions require operands of the same size. When one
- of the operands is a register, QuickAssembler adjusts the size of the
- other, if possible, to be the size of the register──either 8 or 16 bits.
- An instruction that operates on AX and BL is illegal, since these
- registers are different sizes.
-
- If the sizes conflict, you can sometimes use the PTR operator to override
- the size attribute of an operand.
-
- Sections 2.6.1-2.6.4 discuss each of the four operand types (and
- corresponding addressing modes) in detail.
-
-
- 2.6.1 Immediate Operands
-
- An "immediate operand" is a constant value on which the instruction
- operates directly. This is the only addressing mode that involves no
- further access of registers or memory. The data follows the instruction
- right inside the executable code, thus giving rise to the name
- "immediate."
-
- Use immediate operands for the same reasons you would use a literal or
- symbolic constant in C. The value of an immediate operand never changes.
-
- An immediate operand can be a symbolic constant declared with the EQU
- operand. This operand is often used for the same purpose as the C #define
- directive. For example, consider the constant declaration:
-
- magic EQU 7243
-
- You could use this the same way as the C statement:
-
- #define magic 7243
-
- Chapter 11, "Using Equates, Macros, and Repeat Blocks," tells more about
- defining constants with the EQU or = operator.
-
- An immediate operand can also be an expression made up of constants. For
- example, the following code directs QuickAssembler to calculate the
- difference between two ASCII values, then use this difference as the
- source (rightmost) operand:
-
- mov bigdiff,'a'-'A'
-
- The assembler interprets the one-byte strings 'a' and 'A' as the ASCII
- values 97 and 65. The assembler calculates the difference──in this case,
- 32──and places the resulting value into the object code. At run time, this
- value is fixed. Each time the instruction is executed, the processor moves
- the value 32 into the memory location bigdiff. This instruction is
- precisely equivalent to, but more readable than, the following:
-
- mov bigdiff,32
-
- One-byte and two-byte strings can be immediate operands. Larger strings
- cannot be processed by a single 8086 instruction. Chapter 3, "Writing
- Assembly Modules for C Programs," explains how to process longer strings,
- one character at a time.
-
- The OFFSET and SEG operators turn variable names (which normally are
- memory operands) into immediate operands. These operators are similar to
- the address operator (&) in C. In Chapter 4, "Writing Stand-Alone
- Assembly Programs," you'll see how to use the OFFSET operator to treat an
- address as immediate data.
-
- When an instruction has two operands, you cannot place immediate data in
- the destination (leftmost) operand. (The OUT instruction is the one
- exception.)
-
- Examples
-
- var DW ?
- college DW 1636
- nine EQU 5+4 ; Declare nine as symbolic constant
- .
- .
- .
- mov var,nine ; Move immediate data to memory
- mov bx,'ab' ; Move ASCII values for 'a' and 'b'
- ; into BH and BL
- mov college,1701 ; Move immediate data to memory
- mov ax,1+2+3+4 ; Move immediate data to AX
- mov ax,OFFSET var ; Move address of var to AX
- int 21h ; Immediate data is single operand
- ; 21 hexadecimal (33 decimal)
-
-
- 2.6.2 Register Operands
-
- A register operand consists of one of the 20 register names. The processor
- operates directly on the data stored in the register. "Register-direct"
- mode refers to the direct use of the value of the register rather than a
- memory location. Registers can also be used indirectly, to point to memory
- locations as described in Section 2.6.4, "Indirect Memory Operands."
-
- Most instructions can take one or more register operands. You generally
- can use any of the general-purpose registers with these instructions,
- although some instructions require specific registers. The use of segment
- registers (CS, DS, SS, and ES) is restricted. You can refer to segment
- registers only under special circumstances.
-
- Table 2.1 shows all the valid register names for 8086 processors. You can
- use any of these names as a register-direct operand.
-
- Table 2.1 Register Operands
-
- Register Type Register
- Name
- ──────────────────────────────────────────────────────────────────────────
- 8-bit high registers AH BH CH DH
-
- 8-bit low registers AL BL CL DL
-
- 16-bit general AX BX CX DX
- purpose
- 16-bit pointer and SP BP SI DI
- index
- 16-bit segment CS DS SS ES
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- Section 2.5, "8086-Family Registers," discusses registers in more detail.
- Limitations on register use for specific instructions are discussed in
- sections on the specific instructions throughout Part 3, "Using
- Instructions."
-
- Examples
-
- mov ds,ax ; Both operands are register direct
- mov stuff,dx ; Source operand is register direct
- mov ax,1 ; Destination is register direct
- mul bx ; Single operand, register direct
-
-
- 2.6.3 Direct Memory Operands
-
- A direct memory operand specifies a fixed address in main memory
- containing the data to operate on. At the machine level, a direct memory
- operand is a numeric address. In your QuickAssembler source code, you
- usually represent a direct memory operand by entering a symbolic name
- previously declared with a data directive such as DB (Declare Bytes).
-
- A direct memory operand is similar to a simple variable in C or an array
- element with a constant index. Any object in memory can be a direct memory
- operand as long as the exact location is fixed in the executable code. The
- data at the location can change, but the location itself is the same each
- time the processor executes the instruction. This fact gives direct memory
- operands a static character. For more dynamic operations, use indirect
- memory operands.
-
- Examples
-
- mov ax,count ; Source operand is direct memory
- mov count,ax ; Destination operand is direct memory
- inc total ; Single operand is direct memory
-
- Typically, a direct memory operand is a simple label. As with immediate
- operands, you can specify a direct memory operand by entering an
- expression. As long as the address can be determined at assembly time, the
- operand is direct memory.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Technically, a program address is not determined until link time (in
- the case of near addresses) or load time (in the case of segment
- addresses). These adjustments are necessary to support multiple modules
- and to enable the program to run anywhere in memory. However, you can
- ignore these details. If the assembler can determine the operand's address
- relative to the rest of the module, the operand is direct memory.
- ──────────────────────────────────────────────────────────────────────────
-
- The following example uses an expression that translates to a direct
- memory operand. This example could be used to load the value of DX into
- the third element of an array of bytes. QuickAssembler considers area[2]
- as equivalent to area+2.
-
- mov area[2],dx ; Move DX to memory location 2 bytes
- ; past the address of "area"
-
- In the statement above, the assembler calculates an address by adding 2 to
- the address of area. The resulting address will be the same no matter what
- values are stored in registers. At run time, the address is fixed. Thus,
- the operand is direct memory.
-
- You can use a numeric constant as a direct memory operand. Normally,
- Quick-Assembler interprets a numeric constant as an immediate operand. To
- ensure interpretation as a memory operand, prefix the number with a
- segment register and colon (:). Brackets are optional. The following
- instructions each load AX with the contents of memory address 100
- hexadecimal in the data segment:
-
- mov ax,ds:[100h]
- mov ax,ds:100h
-
- Section 2.7, "Segmented Addressing and Segment Registers," provides more
- information on segment registers and the use of the colon (:). By default,
- the processor assumes that data references lie in the segment pointed to
- by DS.
-
-
- 2.6.4 Indirect Memory Operands
-
- With indirect memory operands, the processor calculates the address of the
- data at execution time, by referring to the contents of one or two
- registers. Since values in the registers can change at run time, indirect
- memory operands provide the most dynamic method for accessing data.
-
- Indirect memory operands make possible run-time operations such as pointer
- indirection, dynamic indexing of array elements──including indexing of
- multi-dimensional arrays──and dynamic accessing of members of a structure.
- All these operations are similar to operations in high-level languages.
- The major difference is that assembly language requires you to use one of
- several specific registers: BX, BP, SI, and DI.
-
- You indicate an indirect memory operand by using at least one pair of
- brackets. Use of the index operator ([ ]) is explained in more detail in
- Section 9.2.1.3.
-
- When you place a register name in brackets, the processor uses the data
- pointed to by the register. For example, the following instruction
- accesses the data at the address contained in BX, and then moves this data
- into AX:
-
- mov ax,[bx]
-
- When you specify more than one register, the processor adds the contents
- together to determine the effective address (the address of the data to
- operate on). One register must be a base register (BX or BP), and the
- other must be an index register (SI or DI):
-
- mov ax,[bx+si]
-
- You can specify one or more displacements. A "displacement" is a constant
- value to add to the effective address. A simple use of a displacement is
- to add a base address to a register:
-
- mov ax,table[si]
-
- In the example above, the displacement table is the address of an array;
- SI holds an index to an array element. (Unlike C, an assembly-language
- index always indicates the distance in bytes between the beginning of the
- array and the element.) Each time the instruction executes, it may load a
- different element into AX. The value of SI determines which array element
- to load.
-
- Each displacement can be an address or numeric constant. If there is more
- than one displacement, the assembler adds them all together at assembly
- time, and places the total displacement into the executable code. For
- example, in the statement
-
- mov ax,table[bx][di]+6
-
- both table and 6 are displacements. The assembler adds the value of table
- to 6 to get the total displacement.
-
- Table 2.2 shows the modes in which registers can be used to specify
- indirect memory operands.
-
- Table 2.2 Indirect Addressing Modes
-
- Mode Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- Register indirect [BX] [BP] [DI] [DI] Effective address is contents
- of register
-
- ──────────────────────────────────────────────────────────────────────────
- Based or indexed displacement[BX] Effective address is contents
- displacement[BP] of register plus displacement
- displacement[DI]
- displacement[SI]
- ──────────────────────────────────────────────────────────────────────────
- Based indexed [BX][DI] [BP][DI] Effective address is contents
- [BX][SI] [BP][SI] of base register plus contents
- of index register
-
- ──────────────────────────────────────────────────────────────────────────
- Based indexed with displacement[BX][DI] Effective address is the sum
- displacement displacement[BP][DI] of base register, index
- displacement[BX][SI] register, plus displacement
- displacement[BP][SI]
- ──────────────────────────────────────────────────────────────────────────
-
-
- You can enclose each register in its own pair of brackets, or you can
- place the registers in the same pair of brackets separated by a plus sign
- (+). The period (.) is normally used with structures, but it also
- indicates addition. The following statements are equivalent:
-
- mov ax,table[bx][di]
- mov ax,table[bx+di]
- mov ax,[table+bx+di]
- mov ax,[bx][di].table
- mov ax,[bx][di]+table
- mov ax,table[di][bx]
-
-
- 2.7 Segmented Addressing and Segment Registers
-
- "Segmented addressing" is the internal mechanism that enables the
- processor to address up to one megabyte of main memory. This mechanism
- accesses each physical memory location by combining two 16-bit addresses.
- The two addresses can be represented in source code as follows:
-
- segment:offset
-
- The first 16-bit address is the "segment address." The second 16-bit
- address is the "offset address." In effect, the segment address selects a
- 64K region of memory, and the offset address selects a byte within this
- region. Here's how it works:
-
- 1. The processor shifts the segment address left by four places, producing
- a 20-bit address ending in four zeros. This operation has the effect of
- multiplying the segment address by 16.
-
- 2. The processor adds this 20-bit address to the 16-bit offset address.
- The offset address is not shifted.
-
- 3. The processor uses the resulting 20-bit address, often called the
- "physical address," to access an actual location in the one-megabyte
- address space.
-
- Figure 2.3 illustrates this process. The 8086-family processors were
- developed to use this mechanism because 16 bits (the size of an 8086
- register) can only address 64K at a time. However, the combined 20-bit
- address is sufficient to address a full megabyte. Note that DOS and ROM
- BIOS reserve part of this area, so that no more than 640K is available for
- program addresses.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 2.7 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- A "segment" consists of a series of addresses that share the same segment
- address, but different offsets. Segments can be no more than 64K in size.
- To create large programs, you need to divide your program into multiple
- segments. Even with smaller programs, it is convenient to have separate
- code, data, and stack segments. (With tiny-model programs, the linker
- combines these segments into a single physical segment.)
-
- The following example helps illustrate segmented-address calculations
- further. The processor calculates the address 53C2:107A by multiplying the
- segment portion of the address by 16 (10H), and then adding the offset
- portion, as shown below:
-
- 53C20h Segment times 10h
-
- + 107Ah Offset
-
- 54C9Ah Physical address
-
- The use of segmented architecture doesn't mean that you have to specify
- two addresses every time you access memory. The 8086-family processors use
- four segment registers, which simplify programming in the following ways:
-
- ■ Normally, you don't specify a segment address when you access data.
- Every data reference is relative to one of the four segment
- registers──CS, DS, SS, or ES──so the segment address is implied.
-
- ■ Most of the time, you don't need to tell the processor which segment
- register to use. By default, the processor uses CS for code addresses,
- DS for data addresses, and SS for stack addresses, except where
- otherwise noted in this section.
-
- ■ You initialize segment registers at the beginning of your program. Once
- initialized, you can continue to use the segment addresses stored in
- those registers.
-
- If the program uses medium, large, huge, or compact model, you may need to
- periodically reload one or more of the segment registers. These memory
- models let you use more than 64K of code or 64K of data.
-
- However, if the program uses small or tiny model, you never reload a
- segment register except in the following situations: to access a special
- hardware-defined location in memory, such as the video-display area, or to
- access far memory allocated to the program by DOS function 48H.
-
- Although each memory operand has a default segment register (usually DS,
- unless the operand uses BP), you can specify another segment register by
- using the segment override operator (:). The following example loads the
- variable far_away residing in the segment pointed to by ES:
-
- mov ax,es:far_away
-
- For more information on this operator, see Section 9.2.3,
- "Segment-Override Operator."
-
- The CS Register
-
- The processor always uses the CS (Code Segment) register as the segment
- address of the next instruction to execute; IP (Instruction Pointer) holds
- the offset address. CS:IP represents the full address of the next
- instruction.
-
- Near jumps and procedure calls alter the value of IP. Far jumps and
- procedure calls alter both CS and IP. You never alter CS directly because
- the far jump and call instructions do so automatically. Furthermore, DOS
- initializes CS for you at the beginning of the program.
-
- The DS Register
-
- By default, the processor uses the DS (Data Segment) register as the
- segment address for program data. String instructions and indirect memory
- operands present some exceptions to this rule. With indirect memory
- operands, the use of BP anywhere in the operand causes SS to be the
- default segment register. Otherwise, DS is the default.
-
- All the Microsoft standard memory models place the most frequently used
- data in an area pointed to by DS. This area is commonly called the
- "default data area," and it can be no larger than 64K. These memory models
- use the ES register to access data outside the default data area. Your own
- programs can either use this technique, or else reload DS whenever you
- enter a new module. The standard method has the advantage of providing
- fast access to the most frequently used data.
-
- The SS Register
-
- When the processor accesses data on the stack, it uses the SS (Stack
- Segment) register as the segment register. (See the description of SP in
- Section 2.5.3 for more information about the stack.) Thus, SS:SP always
- points to the current stack position. Indirect memory operands involving
- BP also use SS as the default segment register.
-
- The Microsoft standard memory models set SS equal to DS. This setting
- makes some programming tasks easier. In particular, it lets you address
- stack or data addresses with either register. If you have to reload DS,
- you can always access items in the default data area by using an SS
- override.
-
- The ES Register
-
- The ES (Extra Segment) register is convenient for accessing data outside
- of the default data area. As demonstrated in Section 3.4, "Decimal
- Conversion with Far Data Pointers," you access far data by loading ES with
- the desired segment address, and then giving a segment override. Section
- 13.3.2, "Loading Far Pointers," provides further information.
-
- ES also plays a role in string instructions. With these instructions, the
- DI (Destination Index) register is always relative to the segment address
- in ES.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 3: Writing Assembly Modules for C Programs
-
-
- As a C programmer, you can take advantage of the superior speed and
- compactness of assembly-language routines. You can write most of your
- program in C, then write time-critical routines in assembly language.
-
- This chapter presents QuickAssembler programming techniques for
- interfacing to C. You can use similar techniques to interface with other
- languages. By using C with assembly language, however, you gain the
- advantage of being able to develop the entire program from within the
- integrated environment.
-
- If you've read Chapter 2, read this chapter to see how to use assembly
- language in a complete example module. If you skipped over Chapter 2, you
- may want to refer to it occasionally for basic concepts, such as
- instructions and registers.
-
-
- 3.1 A Skeleton for Procedure Modules
-
- Let's start by looking at the skeleton of a module with one procedure. The
- "skeleton" consists of statements that give basic structure to the module.
- Within this structure, you can supply most any instructions you want.
- Later sections of this chapter flesh out the skeleton by supplying useful
- code.
-
- The following skeleton assumes that the module is called by a small-model
- C program, and consists of one procedure which takes a single parameter, a
- pointer to a byte:
-
- .MODEL small,c
- .CODE
-
- dectoint PROC Array:PTR BYTE
- ;
- ; (supply executable code here)
- ;
- dectoint ENDP
- END
-
- Some features of the skeleton change when you write different procedures.
- Other parts may remain the same. In particular, you'll need to add a PROC
- and ENDP statement each time you add another procedure to the module.
-
- Before looking at a full program example, let's examine each part of the
- skeleton.
-
-
- 3.1.1 The .MODEL Directive
-
- The .MODEL directive gives general information about the module. It uses
- the following syntax:
-
- .MODEL memorymodel [[,langtype [[,stacktype]]]]
-
- The last two fields are optional. Commas are field separators and are only
- required if you use more than one field. Usually, you'll want to enter
- values in the first two fields.
-
- The memorymodel and langtype fields correspond to the memory model and
- language, respectively, of the calling module. If your C program declares
- your procedure to be of type pascal or fortran, use Pascal, BASIC, or
- FORTRAN in the langtype field. These keywords specify the use of the non-C
- calling and naming conventions. Otherwise, specify C as the langtype.
- Although the langtype field is optional, you should supply it since the
- PROC features described later in this chapter require it.
-
- Don't use the stacktype field unless the calling C program is compiled
- with SS not equal to DS, in which case you should type in farStack.
- (QuickC does not generate code that sets SS not equal to DS, but other
- versions of Microsoft C do support this option.) The default is nearStack,
- which assumes SS is equal to DS.
-
-
- 3.1.2 The .CODE Directive
-
- The .CODE directive marks the beginning of the code segment, which is the
- section of your program that contains the actual steps to execute:
-
- .CODE
-
- Statements that follow this directive are considered part of the code
- segment. The segment continues to the end of the module or the next
- segment directive. Typically, the code segment consists of instructions
- and procedure definitions. It can also contain macro calls.
-
- Some procedures work with static data. In Chapter 4, "Writing Stand-Alone
- Assembly Programs," you'll see how to declare a data segment in which you
- can place data declarations.
-
-
- 3.1.3 The PROC Directive
-
- Use the PROC directive to define a procedure. The name of the procedure
- appears in the first column:
-
- dectoint PROC Array:PTR BYTE
-
- Because the .MODEL statement specified C-language conventions, the
- assembler prefixes the name dectoint with an underscore (_), and places
- the name into object code as a public code label.
-
- If your procedure alters registers that should be preserved, the optional
- USES keyword automatically generates code to push the value of these
- registers on the stack and pop them when the procedure returns. Procedures
- called by C should not corrupt the value of SI, DI, or the segment
- registers CS, DS, or SS. (The value of BP is automatically preserved.) The
- following example shows how to preserve SI and DI for a procedure that
- changes these registers:
-
- dectoint PROC USES si di, Array:PTR BYTE
-
- The last part of the statement declares one or more parameters. In this
- case, the procedure declares a single parameter, Array, as a pointer to a
- byte. The most common parameter types you can declare are listed below:
-
- Declaration Meaning
- ──────────────────────────────────────────────────────────────────────────
- WORD Word (two bytes)
-
- DWORD Doubleword (four bytes)
-
- PTR BYTE Pointer to a byte; most commonly, a pointer to a
- character string
-
- PTR WORD Pointer to a word; typically, the address of an array
- of integers
-
- PTR DWORD Pointer to a doubleword
-
-
- For example, the following procedure definition declares a procedure named
- MidStr, which takes as parameters two pointers to character strings and
- one integer:
-
- MidStr PROC Str1:PTR BYTE, Str2:PTR BYTE, Index:WORD
-
- References to parameters are really references to locations on the stack.
- C modules pass parameters by pushing them on the stack just before calling
- the procedure. The BP register serves as a framepointer (a pointer to the
- procedure's stack area), and each parameter is an offset from BP. The
- exact offset of each parameter depends on the memory model and calling
- convention, both established by the .MODEL directive.
-
- When you use QuickAssembler procedure definitions, the assembler automates
- the work of referring to parameters. Instead of setting up the
- framepointer or calculating parameter offsets, you simply refer to
- parameters by name. You can also use these names with debugging commands.
-
- Appendix A, "Mixed-Language Mechanics," shows the actual code that
- establishes BP as the framepointer. It also shows how to calculate
- parameter offsets.
-
- Section 6.4.3, "Procedure Labels," gives the complete syntax and rules
- for using the PROC statement.
-
-
- 3.1.4 The ENDP and END Statements
-
- The module ends with two statements: ENDP, which declares the end of a
- procedure, and END, which declares the end of the module:
-
- dectoint ENDP
- END
-
- You can place any number of procedures in the same module. Each time you
- end a procedure, use ENDP. However, END should only occur once, at the end
- of the module.
-
-
- 3.2 Instructions Used in This Chapter
-
- The instructions below were introduced in Chapter 2, "Introducing 8086
- Assembly Language." They are summarized here briefly for review. The first
- group of instructions manipulates data:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- MOV destination, source Copies value of source to destination
-
- ADD destination, source Adds source to destination, storing result in
- destination
-
- SUB destination, source Subtracts source from destination, storing
- result in destination
-
- INC destination Increment──adds 1 to destination
-
- DEC destination Decrement──subtracts 1 from destination
-
- MUL source Multiplies source by AX (if operand is 16 bits),
- storing high 16 bits in DX and low 16 bits in AX
-
-
- The second group of instructions controls the flow of program execution:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- CMP destination, Compare──subtracts source from destination, ignoring
- source result but setting processor flags appropriately
-
- JE label Jumps to label if result of last operation was equal
- to zero
-
- JAE label Jumps to label if result of last operation was equal
- to or above zero (unsigned operations)
-
- JMP label Jumps unconditionally to label
-
-
-
- 3.3 Decimal Conversion Example
-
- This section uses a decimal-conversion example to illustrate the use of
- some basic instructions and directives. It features an assembly module
- that takes a pointer to a null-terminated string of characters as input
- and returns an unsigned integer value. This chapter assumes that the value
- is unsigned.
-
- You can compute the value of a decimal string by multiplying each digit by
- a power of 10:
-
- 2035 = 2 x 10 cubed + 0 x 10 squared + 3 x 10 + 5
-
- One way to calculate the value of the number is to calculate each power of
- 10 separately, then multiply each digit by the corresponding power. For
- example, you can calculate 10 cubed, and then multiply by 2.
-
- A much more efficient algorithm combines the calculations for powers of
- 10. The algorithm adds each digit to a running total, then multiplies the
- total by 10 after every digit but the last. The following pseudo-code
- represents this algorithm, and assumes that the first character in the
- string is the most significant digit:
-
- initialize total to 0
- while there's another digit
- add value of digit to total
- advance to next digit
- if no more digits
- we're done
- else
- multiply total by 10
-
- A simple C program that calls the procedure might look like this:
-
- extern unsigned int dectoint( char * );
- main()
- {
- char digits[81];
-
- gets( digits );
- printf( "Numeric value is: %d", dectoint( digits ) );
- }
-
- The procedure itself could be written in C as:
-
- unsigned int dectoint( char *Array)
- {
- unsigned int total = 0; /* Initialize total */
-
- while( *Array != '\0' ) /* While there's another digit
- {
- total += *Array - '0'; /* Add value to total */
- Array++; /* Advance to next digit */
- if( *Array == '\0' ) /* If no more digits, */
- break; /* we're done */
- total *= 10; /* Else, multiply by 10 */
- }
- return( total );
- }
-
- This chapter shows how to write the same procedure in assembly language.
- The assembly-language version will be faster because it can make strategic
- use of registers and choose optimal instructions. You can write a main
- module with C code, place the assembly routine in a separate module with a
- .ASM extension, then link them together by creating a program list.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE You can build mixed-language programs by placing both .C and .ASM
- files in a program list. Place the main module first. In the Assembler
- Flags dialog box, make sure that you select either Preserve Case or
- Preserve Extrn (the default). From the QCL command line, use the /Cl
- (preserve case) or /Cx (preserve case of external symbols) option. QC
- calls the linker with case sensitivity on, so C and assembler symbols must
- match exactly.
- ──────────────────────────────────────────────────────────────────────────
-
- Before writing the assembly procedure, we first need to develop a strategy
- for using registers.
-
- The AX (Accumulator) register is ideal for keeping the running total. The
- algorithm changes this total through both addition and multiplication. The
- MUL instruction requires the use of AX. By keeping the total in AX at all
- times, the procedure avoids having to constantly reload this register.
-
- The BX register should be used to access the individual digits. The
- procedure receives the address of the digit string, and then retrieves
- each ASCII byte through pointer indirection. BX is one of the few
- registers that supports this operation. SI and DI could also be used this
- way, but C-generated code requires that SI and DI be preserved. BX can be
- freely altered.
-
- The procedure needs to allocate two more registers: one for holding the
- multiplication factor (10), and another for adjusting the binary value of
- the digit. The procedure uses CX and DX for these purposes. In this case,
- CX and DX are interchangeable. However, we use CX for multiplication now,
- because in the hex conversion example, CX will be needed for a special
- kind of multiplication──shifting bits. We use DX as an intermediate
- location to receive a byte and then add a word to AX.
-
- The complete assembly-language module is shown below:
-
- .MODEL small,c
- .CODE
-
- dectoint PROC Array:PTR BYTE
-
- sub ax,ax ; ax = 0
- mov bx,Array ; bx = Array
- mov cx,10 ; factor = CX = 10
- sub dx,dx ; dx = 0
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
- top:
- mov dl,BYTE PTR [bx] ; Get next digit
- sub dl,'0' ; Convert numeral
- add ax,dx ; Add to total
- inc bx ; Point to next byte
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
- mul cx ; AX = AX * 10
- jmp SHORT top ; Goto top of loop
- done:
- ret ; Exit procedure
- dectoint ENDP
- END
-
- We'll examine each section of the module in turn. The first three
- statements are directives that form part of the module's skeleton. The
- PROC directive, when used with one or more parameters as it is here,
- generates code to set the framepointer (BP) properly so that you can
- access parameters.
-
- .MODEL small,c
- .CODE
-
- dectoint PROC Array:PTR BYTE
-
- The rest of the module consists of instructions──the actual core of the
- program. The first four instructions initialize the registers AX, BX, CX,
- and DX. Note that when initializing a register to 0, the procedure uses
- SUB in preference to MOV. Any value subtracted from itself leaves zero in
- the destination operand. Although the result is the same, the SUB
- instruction is smaller and faster because it involves no immediate data.
-
- sub ax,ax ; ax = 0
- mov bx,Array ; bx = Array
- mov cx,10 ; factor = CX = 10
- sub dx,dx ; dx = 0
-
- The next two instructions handle a special case──that of a string
- containing no digits at all. Recall that the procedure is passed a
- null-terminated string. The operand BYTE PTR [bx] is a memory operand
- referring to the byte pointed to by BX. If the string is empty, Array
- points to a null byte. The two instructions test for a 0 (null) value and
- jump to the end of the procedure if 0 is detected:
-
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
-
- In the CMP instruction above, the BYTE PTR operator is strictly required,
- because otherwise the assembler would have no way of knowing whether to
- compare 0 to the byte or a word pointed to by BX. However, when one of the
- operands is a register (as is the case with the MOV instruction below),
- the BYTE PTR operator is optional.
-
- The next eight instructions consist of a loop executed once for every
- digit character in the string. The label top indicates the top of the
- loop, and the first three instructions add the value of the digit to AX:
-
- top:
- mov dl,BYTE PTR [bx] ; Get next digit
- sub dl,'0' ; Convert numeral
- add ax,dx ; Add to total
-
- The first instruction above retrieves the digit. The next instruction
- converts the digit's ASCII value to the numeric value by subtracting the
- value of the character '0' (48 decimal). This statement works because the
- ASCII character set places all digit characters in sequence from 0 to 9.
- Finally, the procedure adds the resulting value to the running total
- stored in AX. Note that the operands in each case are the same size. The
- first two instructions above access DL, the low byte of DX.
-
- The next three instructions advance to the next byte in the string, and
- test it for equality to zero. Getting the next byte is just a matter of
- adding the value 1 to BX (with the INC instruction), so that BX points to
- the next byte. The other two instructions are identical to previous
- instructions that tested for zero value.
-
- inc bx ; Point to next byte
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
-
- If the next byte is a null byte, the processor jumps to the end of the
- program. Otherwise, the processor continues executing the bottom of the
- loop, which multiplies the current total by 10 (stored in CX), and then
- jumps to the top:
-
- mul CX ; AX = AX * 10
- jmp SHORT top ; Goto top of loop
-
- Notice the operator SHORT used with the jmp instruction. This optional
- operator makes the encoded instruction smaller and faster, but it can be
- used only if the destination of the jump is less than 128 bytes away.
- SHORT is explained in more detail in Section 9.2.4.2.
-
- The loop is now complete. The rest of the module exits and marks the end
- of the segment and the module. The RET statement causes the assembler to
- generate instructions to do the following: restore the stack, restore the
- framepointer (BP), and return properly for the memory model (small) and
- calling convention (C).
-
- done:
- ret ; Exit procedure
- dectoint ENDP
- END
-
- Microsoft high-level languages always look for function return values in
- AX, if two bytes long, or in DX and AX, if four bytes long. If the return
- value is longer than four bytes, DX:AX points to the value returned. If
- the return value is one byte, AL contains the value.
-
- The C module that calls this procedure looks in AX for the return
- value──as does all high-level-language code that calls a function
- returning a two-byte value. In this case, AX already contains the results
- of the calculation. No further action is required.
-
-
- 3.4 Decimal Conversion with Far Data Pointers
-
- This section uses the same basic algorithm introduced in the last section,
- but presents coding techniques for different memory models.
-
- The .MODEL directive resolves all differences in the size of code
- addresses. However, when you use memory models that use far data pointers
- (compact, large, and huge), you must make some additional adjustments.
-
- The program below shows the module rewritten for large memory model. This
- example works for compact model if large in the first line is replaced
- with compact.
-
- .MODEL large,c
- .CODE
-
- dectoint PROC USES ds, Array:PTR BYTE
-
- sub ax,ax ; ax = 0
- lds bx,Array ; ds:bx = Array
- mov cx,10 ; factor = CX = 10
- sub dx,dx ; dx = 0
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
- top:
- mov dl,BYTE PTR [bx] ; Get next digit
- sub dl,'0' ; Convert numeral
- add ax,dx ; Add to total
- inc bx ; Point to next byte
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
- mul cx ; AX = AX * 10
- jmp SHORT top ; Goto top of loop
- done:
- ret ; Exit procedure
- dectoint ENDP
- END
-
- This procedure is the same as the one in the last section, except for two
- lines. The PROC directive now includes a USES clause, and the LDS
- instruction replaces the first MOV instruction.
-
- The procedure loads the DS register with the segment address of Array,
- thus causing subsequent data references to be relative to the new segment
- address. However, procedures called from C must preserve DS. The PROC
- statement, therefore, includes USES ds, which generates code to place DS
- on the stack.
-
- The LDS instruction (Load Data Segment) does the actual loading of the DS
- register. This instruction is similar to the MOV instruction:
-
- mov bx,Array ; bx = Array
- ; 2-byte data pointer
- lds bx,Array ; ds:bx = Array
- ; 4-byte data pointer
-
- The LDS instruction accomplishes two moves. First, it loads the offset
- portion of the pointer into the specified register (BX). Second, it loads
- the segment portion of the pointer into DS.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE For the LDS and LES instructions to work properly, the segment
- portion must be stored in the upper word of the four-byte (far) pointer. C
- meets this requirement by always pushing the segment portion of the
- pointer on the stack first. (The stack grows downward.) In your own
- programs, you declare far pointers with the DD directive. You initialize
- them by loading a segment address into the upper word of the pointer
- variable and an offset address into the lower word.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 3.4.1 Writing a Model-Independent Procedure
-
- In the case of this procedure, the use of the LDS instruction is most
- convenient. Once DS is loaded with the new segment address, all subsequent
- memory references are automatically correct. No further adjustments are
- needed.
-
- The simplicity of this technique makes it easy to write a module that is
- completely independent of memory models. This module can then be linked
- with any C program. To adjust memory model, you simply change the .MODEL
- directive, and recompile. In fact, the memory model itself can even be
- specified with a compile flag so that source code never need change.
-
- The model-independent version contains only a few lines different from the
- previous example:
-
- % .MODEL mem,c
- .CODE
-
- dectoint PROC USES ds, Array:PTR BYTE
-
- sub ax,ax ; ax = 0
- IF @DataSize
- lds bx,Array ; ds:bx = Array
- ELSE
- mov bx,Array ; bx = Array
- ENDIF
-
- The .MODEL directive operates on an undefined variable, mem. You define
- this variable on the QCL command line or in the Assembler Flags dialog
- box. For example, to assemble with QCL in compact model, enter the
- following text in the defines text box:
-
- /Dmem=compact
-
- The IF, ELSE, and ENDIF directives cause conditional assembly. The
- @DataSize predefined macro is equal to 1 (true) if the memory model uses
- far data pointers, and 0 (false) otherwise. The statement IF @DataSize
- begins a conditional-assembly block that assembles the LDS instruction if
- the memory model uses far data pointers; it assembles the MOV instruction
- otherwise.
-
- For more information on conditional assembly, see Chapter 10, "Assembling
- Conditionally."
-
- The USES clause is retained for all memory models, since even with small
- model it does no harm. However, to increase efficiency, you may wish to
- include the PROC statement inside conditional-assembly blocks.
-
-
- 3.4.2 Accessing Far Data through ES
-
- The LDS instruction is inconvenient if you need to access items in the
- default data segment, because you have no guarantee that DS still points
- to that area of memory. Therefore, it's sometimes more efficient to leave
- DS alone and use the ES register to access far data.
-
- The standard C memory models all use the LES instruction to access far
- data. You can also use this method, but it is not required, since it has
- no effect on the interface between modules. Give the LES instruction to
- load a far data pointer, which will load the ES register with the new
- segment address. Then give the ES override whenever you refer to data in
- the far segment. This method requires alteration of all instructions that
- access the string data:
-
- les bx,Array ; es:bx = Array
- .
- .
- .
- cmp es:BYTE PTR [bx],0 ; Compare byte to NULL
-
- Once ES is loaded with the segment address of far data, access objects in
- the default data area (the segment containing near data) as you normally
- would. Use the ES override to access the far data.
-
-
- 3.5 Hexadecimal Conversion Example
-
- The following example builds on the decimal example in Section 3.3,
- adding the additional logic needed to convert hexadecimal rather than
- decimal strings.
-
- Hexadecimal conversion can use an algorithm similar to the one used
- earlier for decimal conversion, with these adjustments made:
-
- ■ The procedure multiplies the running total by 16, not 10.
-
- ■ The procedure converts the letters A-F to numeric values, in addition
- to converting the numerals 0-9.
-
- You could make the first adjustment by loading CX with 16 instead of 10. A
- much more efficient method is to use the SHL (Shift Left) instruction to
- shift an object's bits left by four places. This effectively multiplies
- the object by 16.
-
- The second adjustment requires more complex logic. Hexadecimal digits can
- consist of either letters or numerals. The procedure must consider three
- different cases──one for each sequence of hexadecimal characters:
-
- Range of Characters Conversion Required
- ──────────────────────────────────────────────────────────────────────────
- 0-9 Convert to face value. Subtract ASCII value of '0'.
-
- A-F, and a-f Convert to values 10-15. Convert all letters to
- uppercase, then subtract ASCII value of 'A' and add
- 10.
-
-
- We convert all letters to uppercase in an optimized fashion by taking
- advantage of the ASCII coding sequence. Uppercase letters are coded as 41H
- onward. Lowercase letters are coded as 61H onward. Consequently, each
- lowercase letter differs from the corresponding uppercase letter by
- exactly one bit. We use the AND instruction, with the immediate operand
- 0DFH, to mask out this bit. This operation has the effect of setting the
- third highest bit to 0.
-
- 0110 0001 61h = 'a' 0100 0001 41h = 'A'
- AND 1101 1111 DFh 1101 1111 DFh
- ====================== ======================
- result 0100 0001 41h = 'A' 0100 0001 41h = 'A'
-
- 0110 0010 62h = 'b' 0100 0010 42h = 'B'
- AND 1101 1111 DFh 1101 1111 DFh
- ====================== ======================
- result 0100 0010 42h = 'B' 0100 0010 42h = 'B'
-
- The beauty of the operation is that it converts lowercase letters to
- uppercase, but leaves uppercase letters alone. If the third highest bit is
- already 0 (as is the case with uppercase letters), doing an AND operation
- with 0DFH has no effect. This operation removes the need to handle
- lowercase letters as a separate case.
-
- The revised algorithm does the following:
-
- initialize total to zero
- while there's another digit
- move byte to temporary location
- if ascii value < 'A'
- Subtract '0'
- else
- Convert lowercase to uppercase
- Subtract 'A'-10
- add byte value to total
- advance to next digit
- if no more digits
- we're done
- else
- shift total left by four bits
-
- The assembly-language code below implements this algorithm. The code tests
- for each range, performing a different conversion for each case. Note the
- use of JB (Jump If Below), which jumps to the specified label if the
- previous comparison or subtraction produced a negative value──that is, if
- the first operand is less than the second.
-
- .MODEL small,c
- .CODE
-
- hextoint PROC Array:PTR BYTE
-
- sub ax,ax ; ax = 0
- mov bx,Array ; bx = Array
- mov cl,4 ; Prepare to shift left by 4
- sub dx,dx ; dx = 0
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; if byte=0 we're done
- top:
- mov dl,BYTE PTR [bx] ; Move byte to DL
- cmp dl,'A' ; ASCII value >= 'A'?
- jae isletter ; If so, goto isletter
- sub dl,'0' ; Convert ascii to numeric
- jmp addbyte ; Go add value of byte
- isletter:
- and dl,0DFh ; Convert to uppercase
- sub dl,'A'-10 ; Convert ascii to numeric
- addbyte:
- add ax,dx ; Add value to total
- inc bx ; Point to next byte
- cmp BYTE PTR [bx],0 ; Compare byte to NULL
- je done ; If byte=0 we're done
- shl ax,cl ; AX = AX * 16
- jmp SHORT top ; Goto top of loop
- done:
- ret
- hextoint ENDP
- END
-
- The beginning of the procedure initializes the CL register to 4. This step
- is necessary, because you can use the SHL instruction (Shift Left) in only
- two ways: you can shift by exactly one bit, or you can shift by the number
- of places indicated in CL. Clearly, using CL is more efficient than a
- sequence of four shift instructions.
-
- The main loop reads a character, tests it, and makes one basic decision:
- is the character a letter or not? This test takes advantage of the ASCII
- coding sequence. If the value of the character is equal to or greater than
- 'A', it cannot be one of the digits 0-9. The procedure uses the JAE
- instruction (Jump If Above or Equal) to test for this condition.
-
- top:
- mov dl,BYTE PTR [bx] ; Move byte to DL
- cmp dl,'a' ; ASCII value >= 'A'?
- jae isletter ; If so, goto isletter
-
- If the character is a letter, the procedure first converts the letter to
- uppercase──using an AND instruction that converts lowercase letters but
- leaves uppercase letters unchanged. The following instruction can then
- properly handle all letters the same way, regardless of their original
- case:
-
- isletter:
- and dl,0DFh ; Convert to uppercase
- sub dl,'A'-10 ; Convert ascii to numeric
-
- For simplicity, the procedure accepts invalid letters. You could easily
- enhance it to verify that the letters are hexadecimal.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 4: Writing Stand-Alone Assembly Programs
-
-
- With QuickAssembler, you can write stand-alone assembly programs to
- produce small, efficient utilities. For example, you might write a utility
- in assembly language to count the number of lines or paragraphs in a file.
- These programs start and end with assembly code and generally do not
- involve any links to high-level languages.
-
- Stand-alone assembly programs can yield remarkably small .EXE files. They
- require relatively little space, because they do not include the start-up
- code for a high-level language. And often you can make your assembly
- program even smaller by converting it to a .COM file as shown in this
- chapter. Some useful .COM files take up less than 100 bytes of memory.
-
- This chapter first describes the directives you need to write stand-alone
- assembly programs, reviews instructions used in the chapter's examples,
- and then presents a simple stand-alone program. Next, Sections 4.4-4.6
- look closely at each segment of the program: stack, data, and code.
- Finally, the chapter describes how to create a program in the .COM format.
-
-
- 4.1 A Skeleton for Stand-Alone Programs
-
- This chapter uses the simplified segment directives described in the
- previous chapter, and introduces three more directives──.STACK, .DATA, and
- .STARTUP. The simplified segment directives produce programs using the
- Microsoft standard segment format.
-
- This format is not required, since your stand-alone program need not be
- compatible with a high-level-language module. However, the standard format
- is convenient because you can specify a number of different memory models,
- and you are freed from having to specify segment names, attributes, and
- register assumptions.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Occasionally, you may need a customized segment structure. Linking
- assembly code to a non-Microsoft language is the most common situation
- that requires customized segments. QuickAssembler lets you use full
- segment definitions any time you need to customize segments. However, you
- should find that simplified segment directives support the vast majority
- of assembly-language programming you do──even when you write .COM files.
- ──────────────────────────────────────────────────────────────────────────
-
- The skeleton for the programs in this chapter includes a stack, data, and
- code segment. Note that one of the directives, .MODEL, will change when
- you alter the memory model. The other statements remain the same.
-
- .MODEL small ; Use small memory model
-
- .STACK 100h ; Declare 256-byte stack
-
- .DATA
- ;
- ; (place data declarations here)
- ;
- .CODE
- .STARTUP ; Set up DS, SS, and SP registers
- ;
- ; (place executable code here)
- ;
- END
-
- Sections 4.1.1-4.1.3 examine each of the statements in this skeleton more
- closely.
-
-
- 4.1.1 The .MODEL Directive
-
- The .MODEL directive performs the same role that it did in the previous
- chapter; it defines the overall attributes of the module. Note, however,
- that with a stand-alone program, a language type is not always required. A
- language type is useful when a module contains one or more procedures.
- Otherwise, you need only type .MODEL followed by a memory model:
-
- .MODEL small ; Use small memory model
-
- The memory model can be TINY, SMALL, MEDIUM, COMPACT, LARGE, or HUGE. Most
- of these memory models may be familiar to you if you have used QuickC. For
- a complete description of each memory model, see Section 5.1.1.
-
- The TINY memory model is new; it alone results in the creation of a .COM
- file rather than a .EXE file. Section 4.8, "Creating .COM Files," gives a
- complete example featuring the use of tiny memory model.
-
- Generally, to change memory model you change the .MODEL directive. You
- also change the way you load and use data pointers, as described in
- Chapter 3, "Writing Assembly Modules for C Programs." With these changes
- made, many programs can readily be reassembled for a new memory model.
- (However, as you'll see in Chapter 5, "Defining Segment Structure," you
- cannot use .FARDATA segments in tiny, small, or medium model, and this may
- require further revision of code in some cases.)
-
-
- 4.1.2 The .STACK, .CODE, and .DATA Directives
-
- Each of the segment directives──.STACK, .CODE, and .DATA──declares the
- beginning of a segment.
-
- The code and data segments begin with .CODE and .DATA, respectively. Each
- of these segments continues to the next segment directive or the end of
- the program. The data segment contains data and symbolic constant
- declarations. The code segment contains instructions.
-
- However, the stack segment consists of only one line:
-
- .STACK [[size]]
-
- By default, QuickAssembler interprets size according to the current radix,
- which by default is decimal. You can specify a hexadecimal constant by
- using the H suffix. (Example: 200h.) The size argument is optional. If you
- leave it out, the assembler creates a stack 1024 bytes long.
-
- Unless the program is written in tiny memory model, you should always
- declare a stack segment in your main module. Section 4.4, "Inside the
- Stack Segment," explains the purpose of this segment.
-
-
- 4.1.3 The .STARTUP Directive
-
- Unlike C programs, assembly-language programs have to initialize register
- values. Specifically, the program has to initialize DS, the Data Segment
- register; CS and IP, which point to the first instruction to execute; and
- SS and SP, the stack registers.
-
- By far the easiest way to initialize all these registers is to just
- include .STARTUP, a simple directive that takes no arguments:
-
- .STARTUP ; Set up DS, SS, and SP registers
-
- When you use this directive, the assembler generates code to initialize
- your registers the way Microsoft high-level languages do. The generated
- code is similar to some of the instructions in the C start-up code. The
- directive takes care of minimal start-up, but many programs will need to
- do additional start-up tasks, such as releasing unused memory.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The start-up sequence adjusts SS and SP so that SS is equal to DS.
- This starting condition gives you some advantages. If you later have to
- alter the value of DS, you can always access a data object as an indirect
- operand using BP, or through an SS segment override. To avoid this
- starting sequence, so that the stack and data are separate physical
- segments, use the farStack keyword with the .MODEL directive, as described
- in Section 5.1.3.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 4.2 Instructions Used in This Chapter
-
- This section summarizes the instructions used in this chapter. Because the
- program examples are simple, only a very few of the 80-odd instructions of
- the 8086 are featured here.
-
- This chapter features four instructions:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- MOV destination, Moves source to destination
- source
- INT number Generates the indicated interrupt signal, causing
- processor to call a memory-resident interrupt routine
-
- DEC destination Decrement──subtracts 1 from destination
-
- JNZ label Jump If Not Zero──jumps to label if result of last
- operation was not zero
-
-
- Most of the instructions above were introduced in Chapter 2, "Introducing
- 8086 Assembly Language." The new instruction is INT.
-
- The INT instruction generates a software interrupt signal, causing the
- processor to call an interrupt service routine usually residing in a DOS
- or ROM-BIOS memory area. This call is much like a procedure call; the
- processor executes a specific function and returns to the program when the
- routine is complete.
-
- There are two major differences between an interrupt call and a procedure
- call. First, instead of calling a procedure you have written, an INT
- instruction calls a DOS system routine or ROM-BIOS service. These
- low-level routines carry out a variety of basic operations, such as
- reading the keyboard, writing to the screen, or using the file system.
- Most DOS services are accessed through interrupt 21H (33 decimal).
-
- The second major difference is syntactic. You follow the INT keyword by an
- interrupt number (in the range 0 to 255), rather than a procedure name. In
- many cases, you further specify the interrupt routine by loading AH with a
- function number.
-
-
- 4.3 A Program That Says Hello
-
- The following sample program prints Hello world and then successfully
- exits back to DOS. You can use this program as a template and insert your
- own code and data.
-
- .MODEL small ; Use small model
-
- .STACK 100h ; Allocate 256-byte stack
-
- .DATA
- message DB "Hello, world.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
- .CODE
- .STARTUP ; Use standard startup code
-
- mov bx,1 ; Load 1 - file handle
- ; for standard output
- mov cx,lmessage ; Load length of message
- mov dx,OFFSET message ; Load address of message
- mov ah,40h ; Load no. of DOS Write function
- int 21h ; Call interrupt 21H (DOS)
-
- mov ax,4c00h ; Load no. of DOS Exit function
- ; in AH, and 0 exit code in AL
- int 21h ; Call interrupt 21H (DOS)
-
- END
-
- The first statement determines the memory model of the program:
-
- .MODEL small ; Use small model
-
- This statement specifies small memory model, which places code and data in
- two separate segments, each of which cannot exceed 64K.
-
- The next few sections consider the rest of this program──stack, data, and
- code.
-
-
- 4.4 Inside the Stack Segment
-
- The stack segment is the easiest to create, because with simplified
- segment directives you enter only one statement:
-
- .STACK 100h ; Allocate 256-byte stack
-
- Each processor or interrupt call uses up stack space. The stack stores
- return addresses, parameters, and local variables for each procedure
- called. When a procedure or interrupt routine returns, the stack space it
- used is restored. The more procedure calls your program makes without
- returning, the more stack area it requires. Programs that nest many
- procedures or use recursion (in which a procedure calls itself repeatedly)
- may require large stacks. Unfortunately, there is no formula for
- determining how large a stack is needed.
-
- A 256-byte stack (100 hexadecimal) is adequate for most small programs.
- For this sample program, which makes one interrupt call but no procedure
- calls, 256 bytes provides an ample margin of error.
-
- You can also create a stack by using full segment definitions. See Section
- 5.2, "Full Segment Definitions," for more information.
-
-
- 4.5 Inside the Data Segment
-
- A single keyword declares the beginning of the segment:
-
- .DATA
-
- QuickAssembler considers all statements following this line to lie in the
- data segment, up until the next segment declaration or END directive. The
- END directive marks the end of the source file.
-
- The next two statements are directives that declare a string of characters
- and a symbolic constant:
-
- message DB "Hello, world.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
-
- The first statement above declares a series of bytes. The label message is
- a symbolic name that QuickAssembler associates with the string's starting
- address.
-
- The assembler allocates 15 bytes in the data segment, and initializes
- these bytes to the ASCII values for H, e, l, l, o, and so forth. The
- values 13 and 10 indicate a carriage return and line feed, respectively,
- causing the program to move the cursor to the beginning of the next line
- when it prints the string.
-
- The second directive in the data segment declares a symbolic constant
- equal to the length of the string:
-
- lmessage EQU $ - message ; Determine length of message
-
- Again, the item in the first column, lmessage, is the label of the
- statement. The EQU directive equates the label with the value of the
- operand itself. EQU does not allocate memory.
-
- The operand field contains $ - message, which in this case equals 15. We
- could just as easily have entered 15 in the operand field. However, the
- item $ - message is guaranteed to be equal to the length of the string,
- even if you later rewrite the initial string value.
-
- The dollar sign ($) is the "location counter," which represents the
- current address of the statement. QuickAssembler translates the full
- expression as "Take the current address ($) and subtract the address of
- message." The current address is one byte after the end of the string.
- Thus, $ - message is automatically equal to the length of the string.
-
-
- 4.6 Inside the Code Segment
-
- A single keyword declares the beginning of the code segment:
-
- .CODE
-
- The code segment consists of all statements between .CODE and the END
- statement, which marks the end of the source code. In this example, all
- the statements in the code segment, aside from .STARTUP, are instructions.
-
- The program has three basic tasks. Each instruction helps carry out one of
- these operations:
-
- 1. Initialize registers
-
- 2. Call a DOS function to print the message
-
- 3. Call a DOS function to exit the program gracefully
-
- The .STARTUP directive initializes registers. If you write a main module
- without this directive, you must explicitly initialize DS, CS, and IP.
- Furthermore, if you want SS to equal DS (which gives some programming
- advantages), you must adjust both SS and SP.
-
- To see how to initialize registers without the use of .STARTUP, see
- Chapter 5, "Defining Segment Structure."
-
- After registers are initialized, a series of five instructions makes the
- call to DOS that prints the message:
-
- mov bx,1 ; Load 1 - file handle for
- ; standard output
- mov cx,lmessage ; Load length of message
- mov dx,OFFSET message ; Load address of message
- mov ah,40h ; Load no. of DOS Write function
- int 21h ; Call interrupt 21H (DOS)
-
- The first four instructions prepare for the DOS call. Interrupt calls
- generally use registers to receive parameters. Unlike procedure calls,
- they do not reference the stack for this information. The DOS Write
- function uses the following registers to receive data:
-
- Register Data
- ──────────────────────────────────────────────────────────────────────────
- AH Selects the DOS function. 40H is the Write function.
-
- BX File handle to which to write. The number 1 is a
- reserved file handle that always corresponds to
- standard output. "Standard output" is normally
- synonymous with the computer screen, unless you
- redirect program output. If you were writing to a
- file, you would first open the file and use the file
- handle returned by the DOS open-file function.
-
- CX Length of the message. The second statement in the
- data segment determined this length.
-
- DS:DX The beginning address of the actual message text.
- Remember that DS was loaded earlier with the address
- of the data segment, so it does not need to be
- reloaded now.
-
-
- This procedure uses the OFFSET operator to load DX with the address of the
- message. Although variables are translated to addresses, the processor
- normally interprets a variable address as a memory operand──that is, the
- processor operates on the data at the address, not the address itself.
-
- The OFFSET operator extracts the offset portion of the address and turns
- it into an immediate operand. If the OFFSET operator was not used the DOS
- routine would not receive the address of message, but would instead
- receive the value of the first byte. The OFFSET operator is similar to the
- address operator (&) in C. Use it whenever you need to pass an address
- rather than a value.
-
- After the interrupt service returns, the AX register contains the number
- of bytes written. The programs in this chapter do not use this return
- value, but a more sophisticated program might. In particular, if AX
- (number of bytes written) is less than CX (number of bytes requested to be
- written), then an error has occurred.
-
- Each DOS function has its own conventions for receiving data in different
- registers. Consult the Microsoft MS-DOS Programmer's Reference for a
- complete description of each function. The Assembler Contents selection
- from the Help menu also describes the major DOS functions.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Each DOS function has conventions for getting and returning values
- in registers and flags. Bear in mind that values placed in any of these
- registers may change. If you need to preserve register values before
- making a DOS call, use the PUSH and POP instructions. See Section 13.4.1,
- "Pushing and Popping," for more information on how to preserve register
- values.
- ──────────────────────────────────────────────────────────────────────────
-
- The INT instruction makes the actual call to DOS. The interrupt number for
- the majority of DOS functions is 21H. You use different interrupt numbers
- to call ROM-BIOS services.
-
- The final two instructions cause the program to terminate operation and
- return control to DOS. High-level language programmers can ignore the need
- to exit a program explicitly, if they like. But when you write a
- stand-alone assembly program, you don't have this luxury. The program must
- exit explicitly. Otherwise, the processor continues to execute random
- instructions after the end of the program, making the system appear to
- crash.
-
- The DOS Exit function (service 4CH) is the preferred method for exiting
- back to DOS. This function uses two register values:
-
- Register Data
- ──────────────────────────────────────────────────────────────────────────
- AH Selects the DOS function. 4CH is the Exit function.
-
- AL Exit code. Batch files can use this exit code as an
- "errorlevel" indicator. An exit code of 0 usually
- indicates no error.
-
-
- A single instruction loads both registers:
-
- mov ax,4c00h ; Load number of DOS Exit func
- ; in AH, and 0 exit code in
-
- A single MOV instruction actually moves data into two registers──AH and
- AL. AH is loaded with 4CH, the function number for the DOS exit function,
- and AL is loaded with 0, an exit code indicating no error.
-
- Finally, another INT instruction calls DOS.
-
- int 21h ; Call interrupt 21H (DOS)
-
-
- 4.7 Making the Program Repeat Itself
-
- Once you understand the template for writing stand-alone programs, you can
- alter the sample program given above and generate your own code. This
- section alters the sample program so that it prints out a different
- message, and prints it ten times.
-
- The new sample program is listed below:
-
- .MODEL small ; Use small model
-
- .STACK 100h ; Allocate 256-byte stack
-
- .DATA
- message DB "Hello, ten times.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
- count DW 10
-
- .CODE
- .STARTUP ; Use standard startup code
-
- mov bx,1 ; Load 1 - file handle for
- ; standard output
- mov cx,lmessage ; Load length of message
- mov dx,OFFSET message ; Load address of message
- printit: mov ah,40h ; Load no. of DOS Write functi
- int 21h ; Call interrupt 21H (DOS)
-
- dec count ; count = count-1
- jnz printit ; if count > 0, print again
-
- mov ax,4c00h ; Load DOS 4C function number
- ; in AH, and 0 exit code in
- int 21h ; Call interrupt 21H (DOS)
-
- END
-
- Note the following changes:
-
- ■ The string data is different.
-
- ■ The data segment includes a new variable, count.
-
- ■ One of the instructions is now labeled printit.
-
- ■ Two additional instructions decrement count, then loop back to the
- label printit if count is greater than zero.
-
- The string data is longer than before, and QuickAssembler must allocate
- more bytes than in the previous version of the program. However, the EQU
- statement that follows guarantees that the assembler still calculates
- string length correctly:
-
- message DB "Hello, ten times.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
-
- The new variable is actually a memory location of word size (two bytes).
- QuickAssembler allocates another two bytes in the data segment, and
- initializes these bytes:
-
- count DW 10
-
- The label count becomes associated with the address of the data, and the
- number 10 is the initial value placed at this memory location. However,
- the value can change.
-
- The instruction mov ah,40h now has a label, because the program needs to
- return here to repeat the print operation. Not all instructions need a
- label──only those that the program may need to jump to directly.
-
- The two new instructions cause the program to repeat the print operation
- ten times:
-
- dec count ; count = count-1
- jnz printit ; if count > 0, print again
-
- The DEC instruction subtracts 1 from the memory location count, and sets
- processor flags according to the result of the operation. JNZ then jumps
- to the specified label if the result was not zero. The combined effect of
- these two instructions is to repeat the previous instructions (from
- printit onward) ten times. To change the number of repetitions, initialize
- count with a different value.
-
- Note that the DOS print function returns a value in the register
- AX──specifically, the number of bytes written. The program jumps back to
- printit so that AH is reloaded before the call to DOS.
-
- You can optimize this program further by using a register instead of the
- memory location count. For example, to use the register SI as the counter,
- follow these steps:
-
- ■ Remove the declaration of count.
-
- ■ Initialize SI to 10 at the beginning of the program with the
- instruction mov si,10.
-
- ■ Decrement SI instead of count near the bottom of the loop.
-
- With this program, it's safe to use SI as the counter, since SI is not
- needed for any other purpose. However, some programs make special use of
- SI. In these cases, it may be more efficient to place the count in a
- variable.
-
-
- 4.8 Creating .COM Files
-
- You can use QuickAssembler to produce .COM files as well as .EXE files.
- (However, these programs cannot contain any C modules.) Most of the memory
- models, ranging from small to large, produce a .EXE file. The tiny memory
- model is special because it alone supports creation of a .COM file.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE To produce a .COM file, you must not only use tiny memory model, but
- also select Generate COM File from the Linker Flags dialog box (choose
- Make from the Options menu), or else give the /TINY linker option on the
- QCL command line.
- ──────────────────────────────────────────────────────────────────────────
-
- Each .COM file has only one physical segment and is limited in size to a
- total of 64K. A .COM file has no executable-file header or
- relocation-table entries. Because DOS doesn't have to examine a file
- header or adjust relocatable segment addresses, it loads the .COM file
- slightly faster.
-
- DOS initializes all segment registers (including DS) to point to the first
- available memory address. The Stack Pointer, SP, is set to 64K above the
- start of the program. Unlike .EXE files, .COM files have no definite stack
- area. Instead, the stack starts at offset address FFFE hexadecimal and
- continues to grow downward until it overlaps code and data areas. At that
- point, program failure is likely.
-
- Simplified segment directives in QuickAssembler now provide direct support
- for .COM files. The template is, in fact, smaller than the template for a
- .EXE file.
-
- The code below shows the example in Section 4.3, "A Program That Says
- Hello," revised to produce a .COM file:
-
- .MODEL tiny ; Produce a .COM file
-
- .DATA
- message DB "Hello, world.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
- .CODE
- .STARTUP
- mov bx,1 ; Load 1 - file handle for
- ; standard output
- mov cx,lmessage ; Load length of message
- mov dx,OFFSET message ; Load address of message
- mov ah,40h ; Load no. of DOS Write function
- int 21h ; Call interrupt 21H (DOS)
-
- mov ax,4c00h ; Load no. of DOS Exit function
- ; in AH, and 0 exit code in AL
- int 21h ; Call interrupt 21H (DOS)
-
- END
-
- A tiny-model program could be produced by simply taking the small-model
- version from earlier in the chapter, and changing the first line to the
- following:
-
- .MODEL tiny
-
- The code would then run correctly. However, the sample code in this
- section takes advantage of tiny model by eliminating the stack segment.
- DOS initializes the SS (Stack Segment) register and SP (Stack Pointer)
- register for you, so you need not declare a stack. The assembler ignores
- stack segments in tiny model.
-
- The program still includes the .STARTUP directive. With tiny model, all
- this directive does is generate the statement ORG 100h.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The statement ORG 100h is necessary for programs in the .COM
- format, and must appear just before the first line of executable code. ORG
- 100h starts the location counter at 100 hexadecimal, reflecting the way
- that DOS loads .COM files into memory. (DOS reserves the first 256 bytes
- for the Program Segment Prefix (PSP).) See Section 6.6, "Setting the
- Location Counter," for more information on the ORG directive.
- ──────────────────────────────────────────────────────────────────────────
-
- With tiny-model programs, QuickAssembler lets you define separate code and
- data segments, but combines these segments into a single physical segment,
- called a "group." QuickAssembler places the code segment first regardless
- of how you write your source code. The resulting .COM file assumes a
- single segment address for the whole program (as required by the structure
- of a .COM file), and execution automatically begins at the proper address.
- Finally, Quick-Assembler directs the linker to output a file in the .COM
- format rather than the .EXE format.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE "Groups" are a standard concept in 8086 assembly language. You can
- place a series of segments into a group. The total size must not exceed
- 64K. The linker responds by combining all the segments into a single
- physical segment in which all addresses share the same segment address.
- For a fuller explanation of groups and segments, see Chapter 5.
- ──────────────────────────────────────────────────────────────────────────
-
- When you write .COM files, you must observe some important restrictions.
- You cannot use program-defined segment addresses. Similarly, you have no
- access to defined segment addresses, such as @data and @code.
-
- Because .COM files lack relocation-table entries, DOS cannot adjust
- segment addresses at load time. The program must use absolute segment
- addresses or else assume the loading segment address that DOS assigns. The
- principal restriction is that you cannot refer to program-defined segment
- addresses. Therefore, memory references can be of three kinds:
-
- 1. Any memory location within the 64K program area. For these memory
- references, you do not load a new value into any of the segment
- registers.
-
- 2. Hard-coded locations in memory that have special meaning at the system
- or hardware level. A video-page address, such as B800:0000, is such a
- special segment address.
-
- 3. An address returned to you by a DOS or ROM-BIOS function. For example,
- DOS function 48H, Allocate Memory, returns a pointer to a block of
- dynamically allocated memory.
-
-
- 4.9 Creating .COM Files with Full Segment Definitions
-
- You don't generally need to use full segment definitions to create .COM
- files. However, when you do use these directives with programs written in
- .COM format, you need to follow certain rules. The assembler automatically
- follows most of these rules when you use simplified segment directives.
-
- The guidelines for .COM format are listed below:
-
- ■ Place the entire program into one physical segment. It's possible to
- divide your program into separate logical segments, then group them
- into one physical segment with the GROUP directive. Simplified segment
- directives, in fact, use this technique with tiny model.
-
- However, you must ensure that code, not data, appears at the beginning
- of the .COM file. A number of different factors affect segment
- ordering, so it may be hard to ensure that the code segment appears
- first. Thus, creating just one segment is the more reliable method.
-
- In contrast, when you use simplified segment directives with tiny
- model, the assembler always places the code segment at the beginning of
- the .COM file.
-
- ■ Use the ASSUME directive to inform the assembler that all segment
- registers will point to the beginning of the segment. At load time, DOS
- sets all segment registers to this address. The ASSUME directive
- informs the assembler of this fact so that it can correctly calculate
- offset addresses. This directive is not necessary when you use
- simplified segment directives.
-
- ■ Use the ORG directive to set the location counter. At load time, DOS
- sets the starting address to 100H. The first 100H bytes are reserved
- for the Program Segment Prefix (PSP). The statement ORG 100h is
- necessary for the assembler to assign addresses in a way consistent
- with run-time conditions. Otherwise, jump instructions and data
- references will be wrong.
-
- When you use simplified segments directives with tiny model, the
- assembler automatically sets the location counter to 100H.
-
- ■ Use the END statement to take one argument: a starting address. This
- argument is not necessary if you use the .STARTUP simplified segment
- directive, because the program automatically begins execution wherever
- you place .STARTUP.
-
- The modified procedure is shown below:
-
- _TEXT SEGMENT 'CODE' ; Define code segment
- ASSUME cs:_TEXT,ds:_TEXT,ss:_TEXT
- ORG 100h
- start: jmp begin
-
- message DB "Hello, world.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
- begin: mov bx,1 ; Load 1 - file handle
- ; for standard output
- mov cx,lmessage ; Load length of message
- mov dx,OFFSET message ; Load address of message
- mov ah,40h ; Load no. of DOS Write function
- int 21h ; Call interrupt 21H (DOS)
-
- mov ax,4c00h ; Load no. of DOS Exit function
- ; in AH, and 0 exit code in AL
- int 21h ; Call interrupt 21H (DOS)
-
- _TEXT ENDS
- END start
-
- The first three statements are new. The SEGMENT statement defines the
- beginning of a segment named _TEXT. (Instead of using the name _TEXT, you
- can choose any other valid symbolic name.) The ASSUME statement then
- informs the assembler that the CS, DS, and SS segment registers will all
- point to the beginning of this segment at run time. Finally, the ORG
- statement informs the assembler that the instruction pointer will be set
- to 100H.
-
- _TEXT SEGMENT ; Define code segment
- ASSUME cs:_TEXT,ds:_TEXT,ss:_TEXT
- ORG 100h
-
- The body of the procedure now includes code and data together in the same
- segment. The first item in the segment must be an instruction, because
- .COM files always begin execution at the start of the file. Attempting to
- execute data would almost certainly cause program failure. Since there is
- no separate data segment, the first instruction jumps around the data
- declarations.
-
- start: jmp begin
-
- message DB "Hello, world.",13,10 ; Message to print
- lmessage EQU $ - message ; Determine length of message
- begin: mov bx,1 ; Load 1 - file handle for
- ; standard output
-
- Another way to write a program for .COM format is to place data
- declarations after the end of the instructions. However, the assembler
- often produces better results if you place data declarations early in the
- source file. That way, you avoid forward references to data.
-
- The source file ends by giving an argument to the END statement. This
- statement is necessary because the program does not use the .STARTUP
- directive. The argument to END must be the label of the first instruction
- executed:
-
- END start
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- PART 2: Using Directives
-
-
- Part 2 of the Programmer's Guide (comprising Chapters 5-12) describes
- the directives and operators recognized by the Microsoft QuickAssembler.
- Directives are nonexecutable statements that give general information to
- the assembler. Some of the more important directives declare program
- structure, define data, and create macros. Operators indicate calculations
- to be performed at assembly time.
-
- Chapters 5-8 present the basic directives you need to write a program,
- including segment, data, multimodule, and structure directives. Chapter
- 9 deals specifically with operators. Chapter 10 describes conditional
- assembly, and Chapter 11 presents macros, a technique for replacing a
- series of frequently used instructions with a single statement. The
- directives that control your output are covered in Chapter 12.
-
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 5: Defining Segment Structure
-
-
- A segment is an area in memory up to 64K in size, in which all locations
- share the same segment address. The 8086 assembly-language modules use
- segments for two reasons:
-
- ■ Segments provide a convenient means for dividing a program into its
- major divisions──code, data, constant data, and stack.
-
- ■ The architecture of the 8086 requires some use of segments. Every
- reference to memory must be relative to one of the four segment
- registers, as described in Section 2.7, "Segmented Addressing and
- Segment Registers." Segment definitions make it possible for
- QuickAssembler to assume the use of the same segment register for a
- large number of different addresses.
-
- You can define segments by using simplified segment directives or full
- segment definitions.
-
- In most cases, simplified segment directives are a better choice. They are
- easier to use and more consistent, yet you seldom sacrifice any
- functionality by using them. Simplified segment directives automatically
- define the segment structure required when combining assembler modules
- with modules prepared with Microsoft high-level languages.
-
- Although more difficult to use, full segment definitions give more
- complete control over segments. A few complex programs may require full
- segment definitions in order to get unusual segment orders and types.
-
- This chapter describes both methods. If you choose to use simplified
- segment directives, you will probably not need to read about full segment
- definitions.
-
-
- 5.1 Simplified Segment Directives
-
- Simplified segment directives provide an easy way to write
- assembly-language programs. They handle some of the difficult aspects of
- segment definition automatically, and assume the same conventions adopted
- by Microsoft high-level languages.
-
- When you write stand-alone assembler programs, the simplified segment
- directives make programming easier. The Microsoft conventions are flexible
- enough to work for most kinds of programs.
-
- When you write assembler routines to be linked with Microsoft high-level
- languages, the simplified segment directives ensure against mistakes that
- would make your modules incompatible. The names are automatically defined
- consistently and correctly.
-
- The simplified segment directives automatically generate the same ASSUME
- and GROUP statements used by Microsoft high-level languages. You can learn
- more about the ASSUME and GROUP directives in Sections 5.3 and 5.4.
-
- However, for most programs you do not need to understand these directives.
- Simply use the simplified segment directives in the format shown in the
- examples.
-
-
- 5.1.1 Understanding Memory Models
-
- To use simplified segment directives, you must declare a memory model for
- your program. The memory model specifies the default size of data and code
- used in a program.
-
- Microsoft high-level languages require that each program have a default
- size (or memory model). Any assembly-language routine called from a
- high-level language program should have the same memory model as the
- calling program. The C compiler provided with QuickAssembler supports all
- models except tiny. If you use assembly modules with a different compiler,
- the compiler documentation should tell what memory models are supported.
-
- The most commonly used memory models are described below:
-
- Model Description
- ──────────────────────────────────────────────────────────────────────────
- Tiny All data and code fit in a single physical segment
- (group). Tiny-model programs can be converted to
- .COM-file format with the Generate COM File option in
- the Linker Flags dialog box (or the linker /TINY
- option used with QCL). Tiny-model programs have
- restrictions described in Chapter 4, "Writing
- Stand-Alone Assembly Programs."
-
- Small All data fits within a single 64K segment, and all
- code fits within a 64K segment. Therefore, all code
- and data can be accessed as near. This is the most
- common model for stand-alone assembler programs. C is
- the only Microsoft language that supports this model.
-
- Medium All data fits within a single 64K segment, but code
- may be greater than 64K. Therefore, data is near, but
- code is far. Most recent versions of Microsoft
- high-level languages support this model.
-
- Compact All code fits within a single 64K segment, but the
- total amount of data may be greater than 64K (although
- no array can be larger than 64K). Therefore, code is
- near, but data is far. C is the only Microsoft
- high-level language that supports this model.
-
- Large Both code and data may be greater than 64K (although
- no array can be larger than 64K). Therefore, both code
- and data are far. All Microsoft high-level languages
- support this model.
-
- Huge Both code and data may be greater than 64K. In
- addition, any individual data array can be larger than
- 64K. From the standpoint of QuickAssembler, this
- memory model is almost equivalent to large model (the
- only exception is the meaning of the predefined equate
- @DataSize). If you want to support arrays larger than
- 64K, you must provide the program logic to support
- these arrays.
-
-
- Stand-alone assembler programs can have any model. Tiny and small model
- are adequate for most programs written entirely in assembly language.
- Since near data or code can be accessed more quickly, the smallest memory
- model that can accommodate your code and data is usually the most
- efficient.
-
- Mixed-model programs use the default size for most code and data but
- override the default for particular data items. Stand-alone assembler
- programs can be written as mixed-model programs by making specific
- procedures or variables near or far. Some Microsoft high-level languages
- have NEAR, FAR, and HUGE keywords that enable you to override the default
- size of individual data or code items.
-
-
- 5.1.2 Specifying DOS Segment Order
-
- The DOSSEG directive specifies that segments be ordered according to the
- DOS segment-order convention. This is the convention used by Microsoft
- high-level-language compilers.
-
- Syntax
-
- DOSSEG
-
- Using the DOSSEG directive enables you to maintain a consistent, logical
- segment order without actually defining segments in that order in your
- source file. Without this directive, the final segment order of the
- executable file depends on a variety of factors, such as segment order,
- class name, and order of linking. These factors are described in Section
- 5.2, "Full Segment Definitions."
-
- Since segment order is not crucial to the proper functioning of most
- stand-alone assembler programs, you can simply use the DOSSEG directive
- and ignore the whole issue of segment order.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Using the DOSSEG directive (or the /DOSSEG linker option) has two
- side effects. The linker generates symbols called _end and _edata. You
- should not use these names in programs that contain the DOSSEG directive.
- Also, the linker increases the offset of the first byte of the code
- segment by 16 bytes in small and compact models. This is to give proper
- alignment to executable files created with Microsoft compilers.
- ──────────────────────────────────────────────────────────────────────────
-
- If you want to use the DOS segment-order convention in stand-alone
- assembler programs, you should use the DOSSEG argument in the main module.
- Modules called from the main module need not use the DOSSEG directive.
-
- You do not need to use the DOSSEG directive for modules called from
- Microsoft high-level languages, since the compiler already defines DOS
- segment order.
-
- Under the DOS segment-order convention, segments have the following order:
-
- 1. All segment names having the class name 'CODE'
-
- 2. Any segments that do not have class name 'CODE' and are not part of the
- group DGROUP
-
- 3. Segments that are part of DGROUP, in the following order:
-
- a. Any segments of class BEGDATA (this class name is reserved for
- Microsoft use)
-
- b. Any segments not of class BEGDATA, BSS, or STACK
-
- c. Segments of class BSS
-
- d. Segments of class STACK
-
- Using the DOSSEG directive has the same effect as using the /DOSSEG linker
- option.
-
- The directive works by writing to the comment record of the object file.
- The Intel(R) title for this record is COMENT. If the linker detects a
- certain sequence of bytes in this record, it automatically puts segments
- in the DOS order.
-
-
- 5.1.3 Defining Basic Attributes of the Module
-
- The .MODEL directive defines attributes that affect the entire module:
- memory model, default calling and naming conventions, and stack type. This
- directive should appear before any other simplified segment directive.
-
- Syntax
-
- .MODEL memorymodel[[[[,language]],stacktype]]
-
- Each of the three fields defines a basic attribute. The memorymodel field
- defines the segment structure of the module. The language field defines
- the default calling and naming conventions assumed by PROC statements.
- These conventions correspond to the high-level language you specify. The
- stacktype field determines whether or not the assembler assumes that the
- SS register is equal to the DS register.
-
- The memorymodel field can be TINY, SMALL, MEDIUM, COMPACT, LARGE, or HUGE.
- The assembler defines segments the same way for large and huge models, but
- the @DataSize equate (explained in Section 5.1.5, "Using Predefined
- Segment Equates") gives a different value for these two models.
-
- If you write an assembler routine for a high-level language, the
- memorymodel field should match the memory model used by the compiler or
- interpreter. If you write a stand-alone assembler program, you can use any
- model. Section 5.1.1 describes each memory model.
-
- The optional language field tells the assembler to follow the naming,
- calling, and return conventions appropriate to the indicated language. In
- addition, if you use the language argument, the assembler automatically
- makes all procedure names public. You can use C, Pascal, FORTRAN, or BASIC
- as the language argument. The last three are equivalent, since these
- languages share the same naming and calling conventions.
-
- Note that although the language field is optional, you will not be able to
- use the high-level language features of the PROC directive if you do not
- give it. Normally, you should specify a language with .MODEL. If you use C
- for the language argument, all public and external names are by default
- prefixed with an underscore (_) in the .OBJ file. Specifying any other
- language has no effect on the names.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The assembler does not truncate names in order to match the
- conventions of specific languages, such as FORTRAN or Pascal. Moreover,
- using the C type specifier does not cause the assembler to preserve case.
- To preserve lowercase names in public symbols, choose one of the assembler
- flags that preserves case (Preserve Extrn or Preserve Case), or assemble
- with /Cx or /Cl on the QCL command line. Within the environment, the
- Preserve Extrn flag is on by default.
- ──────────────────────────────────────────────────────────────────────────
-
- See Appendix A for an explanation of how the different calling
- conventions are implemented. You should also note that each language has
- different defaults for passing parameters by value or by reference.
- Depending on which method is used, a high-level language passes a
- parameter either as a value or as a pointer to the value.
-
- The optional stacktype field determines whether or not the assembler
- assumes that SS is equal to DS. The default value is nearStack, which
- assumes that SS is part of the default data area, so that SS is equal to
- DS, and SP is set to the top of the data area. You can also use farStack,
- which assumes that the stack segment is in a separate physical segment
- from the default data area.
-
- If you write a module called from QuickC, you should always use the
- default (in other words, just leave the field blank), since QuickC always
- assumes DS equals SS. If you write modules for a compiler (such as the
- Microsoft Optimized C Compiler) that supports customized memory models,
- use farStack for models in which SS does not equal DS. If you write a
- stand-alone assembler program, you can choose either setting. If you use
- the .STARTUP directive, the assembler automatically generates the proper
- code for setting up the indicated stack type.
-
- If you write a stand-alone module without using .STARTUP, you should
- exercise caution. If you initialize DS but do not adjust SS and SP (as
- described in Section 5.5.3, "Initializing the SS and SP Registers), use
- the farStack keyword. If you do adjust SS and SP as described in Section
- 5.5.3, you can use the default value, nearStack.
-
- Example 1
-
- DOSSEG
- .MODEL small,c
-
- This statement defines default segments for small-model programs and
- creates the ASSUME and GROUP statements used by small-model programs. The
- segments are automatically ordered according to the Microsoft convention.
- The example statements might be used at the start of the main (or only)
- module of a stand-alone assembler program.
-
- Example 2
-
- .MODEL large,pascal
-
- This statement defines default segments for large-model programs and
- creates the ASSUME and GROUP statements used by large-model programs. It
- does not automatically order segments according to the Microsoft
- convention. The example statement might be used at the start of an
- assembly module that would be called from a large-model Pascal program or
- a C program in which the Pascal calling convention was specified.
-
- Example 3
-
- .MODEL small,c,farStack
-
- This statement defines default segments for a small-model program and
- creates the appropriate ASSUME and GROUP statements. In addition, this
- statement makes all procedures public, and directs the assembler to prefix
- an underscore to the beginning of each public name, so that the naming
- convention is compatible with C. If you later use the PROC statement to
- declare parameters, the assembler will assume that the parameters are
- placed on the stack in the order specified by the C calling convention. In
- addition, the statement uses farStack, indicating that SS is not equal to
- DS.
-
- The last example would be appropriate for a module called by a C module
- with a customized memory model, compiled with a setting that did not
- assume SS equal to DS. Note that QuickC does not support customized memory
- models.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The assembler does not normally display the code generated by the
- high-level-language support features. You can see the code produced by
- these features by using the .LALL directive or the /LA command-line
- option.
- ──────────────────────────────────────────────────────────────────────────
-
- To write procedures for use with more than one language and memory model,
- you can use text macros for the memory model and language arguments, and
- define the values from the command line or in the Assembler Flags dialog
- box. For example, the following .MODEL directive uses text macros for the
- memorymodel and language arguments:
-
- % .MODEL memmodel,lang ; Use % to evaluate memmodel, lang
-
- The values of the two text macros can be defined from the command line
- using the /D switch:
-
- QCL /Dmemmodel=MEDIUM /Dlang=C /AM /Cx main.c proc.asm
-
-
- 5.1.4 Defining Simplified Segments
-
- Each of the directives .CODE, .STACK, .DATA, .DATA?, .CONST, .FARDATA,
- .FARDATA?, and .STARTUP indicate the start of a segment. They also end the
- immediately preceding segment definition.
-
- Syntax
-
- .CODE [[name]] Code segment
- .STACK [[size]] Stack segment
- .DATA Initialized near-data segment
- .DATA? Uninitialized near-data segment
- .CONST Constant-data segment
- .FARDATA [[name]] Initialized far-data segment
- .FARDATA? [[name]] Uninitialized far-data segment
- .STARTUP Code to initialize segment registers
-
- For segments that take an optional name, the base file name of the source
- module is used if you do not specify a value yourself.
-
- Each new segment directive ends the previous segment. The END directive
- closes the last segment in the source file.
-
-
- 5.1.4.1 How to Use Simplified Segments
-
- The .CODE, .DATA, and .STACK directives create the three basic segments
- that programs generally need to have. Chapter 4, "Writing Stand-Alone
- Assembly Programs," demonstrates how to use these directives to write
- code, data, and stack segments. Chapter 4 also explains the purpose of
- each of these segments.
-
- The .STARTUP directive initializes segment registers to the appropriate
- segment values. Chapter 4 describes the use of .STARTUP, and Section
- 5.5 tells more about how .STARTUP works and what code it generates.
-
- When you write a mixed-language program, you generally don't need to
- declare a stack segment, because the start-up code in the C main module
- creates a stack for you. When you write a stand-alone program, you should
- declare a stack segment in the main module only.
-
- Your programs can also use the .DATA? and .CONST directives to create
- segments for uninitialized and constant data, respectively. With
- stand-alone assembler programs, the use of these directives is optional,
- because you can place all data in the segment defined by .DATA if you
- want. With mixed-language programs, use .DATA? and .CONST to ensure
- compatibility with the way C handles uninitialized and constant data. Once
- you define these segments, it is up to you to place the appropriate data
- in each segment.
-
- If your program is written in compact, large, or huge model, you can use
- the .FARDATA and .FARDATA? directives to define additional data segments.
- All the data in the other data segments (defined by .DATA, .DATA?, and
- .CONST) must not exceed a total of 64K across all modules. In addition,
- the stack segment is also placed into this 64K area unless you specify
- farStack with the .MODEL directive.
-
- Data in the .FARDATA and .FARDATA? segments takes slightly longer to
- access. However, there is generally much more room in these segments for
- data definitions. For each module, the .FARDATA and .FARDATA? directives
- each create a separate physical segment that can be up to 64K in size. The
- recommended procedure is to use .FARDATA for initialized data, and
- .FARDATA? for uninitialized data, although this is optional.
-
- With medium, large, and huge model, you can use the name attribute to
- create multiple code segments within a source module. With compact, large,
- and huge model, you can also use the name attribute to create multiple
- far-data segments.
-
- Example 1
-
- DOSSEG
- .MODEL small,c
- .STACK 100h
- .DATA
- ivariable DB 5
- iarray DW 50 DUP (5)
- string DB "This is a string"
- uarray DW 50 DUP (?)
- EXTRN xvariable:WORD
- .CODE
- .STARTUP
- EXTRN xprocedure:NEAR
- call xprocedure
- .
- .
- .
- END
-
- This code uses simplified segment directives for a small-model,
- stand-alone assembler program. Notice that initialized data, uninitialized
- data, and a string constant are all defined in the same data segment. See
- Section 5.1.7, "Default Segment Names," for an equivalent version that
- uses full segment definitions.
-
- Example 2
-
- .MODEL, large,c
- .FARDATA?
- fuarray DW 10 DUP (?) ; Far uninitialized data
- .CONST
- string DB "This is a string" ; String constant
- .DATA
- niarray DB 100 DUP (5) ; Near initialized data
- .FARDATA
- EXTRN xvariable:FAR
- fiarray DW 100 DUP (10) ; Far initialized data
- .CODE TASK
- EXTRN xprocedure:PROC
- task PROC
- .
- .
- .
- ret
- task ENDP
- END
-
- This example uses simplified segment directives to create a module that
- might be called from a large-model, high-level-language program. Notice
- that different types of data are put in different segments to conform to
- Microsoft compiler conventions. See Section 5.1.7, "Default Segment
- Names," for an equivalent version using full segment definitions.
-
-
- 5.1.4.2 How Simplified Segments Are Implemented
-
- When you use the simplified segment directives described above, the
- assembler defines segments in a way compatible with Microsoft high-level
- languages.
-
- This section makes a number of references to groups and ASSUME statements.
- Both of these concepts arise from the need to deal with the 8086 segmented
- architecture. A "group" consists of one or more segments, totaling no more
- than 64K. When multiple segments are placed into a group, the linker
- combines these segments into a single physical segment. All addresses in
- the physical segment are adjusted so that they share the same segment
- address. Use of groups is convenient because it removes the need to
- constantly reload the DS register.
-
- The ASSUME directive is described at greater length in Section 5.4,
- "Associating Segments with Registers." This directive informs the
- assembler where a segment register will point to at run time so that the
- assembler can correctly calculate offset addresses relative to the value
- in the appropriate segment register.
-
- Unless you use tiny model, the code segment (defined with .CODE) is placed
- in its own physical segment, separate from all the data and stack
- segments. With medium, large, or huge model, you can define multiple code
- segments within one source model by using .CODE repeatedly, each time with
- a different name attribute. When you use this technique, each .CODE
- directive generates a new ASSUME statement so that the assembler knows
- where CS points to at run time.
-
- Segments defined with the .STACK, .CONST, .DATA, or .DATA? directives are
- placed in a group called DGROUP. Segments defined with the .FARDATA or
- .FARDATA? directives are not placed in any group. See Section 5.3 for
- more information on segment groups. When initializing the DS register to
- access data in a group-associated segment, the value of DGROUP should be
- loaded into DS. The .STARTUP directive does this initialization
- automatically.
-
- The .MODEL directive generates ASSUME statements to inform the assembler
- that at run time, DS, SS, and ES will all point to the beginning of
- DGROUP. You don't need to write these ASSUME statements yourself.
-
- If you specify farStack with the .MODEL directive, the stack is placed in
- a separate physical segment and the .MODEL directive generates an ASSUME
- statement to inform the assembler that SS does not point to the same
- segment address that DS does.
-
-
- 5.1.5 Using Predefined Segment Equates
-
- Several equates are predefined for you. You can use the equate names at
- any point in your code to represent the equate values. You should not
- assign equates having these names. The predefined equates are listed
- below:
-
- Name Value
- ──────────────────────────────────────────────────────────────────────────
- @CodeSize and If the .MODEL directive has been used, the value of
- @DataSize @CodeSize is 0 for the models that use near-code
- labels (tiny, small, and compact) or 1 for models that
- use far-code labels (medium, large, and huge). The
- value of @DataSize is 0 for models that use near-data
- labels (tiny, small, and medium), 1 for compact and
- large models, and 2 for huge models. These values can
- be used in conditional-assembly statements.
-
- IF @DataSize
- les bx,pointer ; Load far pointer
- mov ax,es:WORD PTR [bx]
- ELSE
- mov bx,WORD PTR pointer ; Load near pointer
- mov ax,WORD PTR [bx]
- ENDIF
-
- @CurSeg This name has the segment name of the current segment.
- This value may be convenient for ASSUME statements,
- segment overrides, or other cases in which you need to
- access the current segment. It can also be used to end
- a segment.
-
- @FileName This value represents the base name of the current
- source file. For example, if the current source file
- is TASK.ASM, the value of @FileName is TASK. This
- value can be used in any name you would like to change
- if the file name changes. For example, it can be used
- as a procedure name:
-
-
- @FileName PROC
- .
- .
- .
- @FileName ENDP
-
- @Model As with the @CodeSize and @DataSize predefined
- equates, you must first use the .MODEL directive
- before using the @Model equate. The value of @Model is
- 1 for tiny model, 2 for small, 3 for compact, 4 for
- medium, 5 for large, and 6 for huge. @Model can be
- used in conditional-assembly statements.
-
- Segment equates For each of the primary segment directives, there is a
- corresponding equate with the same name, except that
- the equate starts with an "at sign" (@) instead of a
- period. For example, the @code equate represents the
- segment name defined by the .CODE directive.
- Similarly, @fardata represents the .FARDATA segment
- name and @fardata? represents the .FARDATA? segment
- name. The @data equate represents the group name
- shared by all the near-data segments. It can be used
- to access the segments created by the .DATA, .DATA?,
- .CONST, and .STACK segments.
-
- These equates can be used in ASSUME statements and at
- any other time a segment must be referred to by name.
-
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Although predefined equates are part of the simplified segment
- system, the @CurSeg and @FileName equates are also available when using
- full segment definitions. If you use the /Cl option or set Preserve Case
- in the Assembler Flags dialog box, predefined equates will be case
- sensitive with the exact names shown above.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 5.1.6 Simplified Segment Defaults
-
- Although your program can combine full segment definitions and simplified
- segment directives, the .MODEL directive enables certain features of
- simplified segment directives that change defaults. Defaults that change
- are listed below:
-
- ■ If you do not use the .MODEL directive, the default size for the PROC
- directive is always NEAR. If you use the .MODEL directive, the PROC
- directive is associated with the specified memory model: NEAR for tiny,
- small, and compact models and FAR for medium, large, and huge models.
- See Section 6.4.3, "Procedure Labels," for further discussion of the
- PROC directive.
-
- ■ If you use the .MODEL directive, the OFFSET operator returns an offset
- relative to the beginning of a group, whenever a data item is defined
- within a group. If you do not use the .MODEL directive, the OFFSET
- operator always returns an offset relative to the beginning of the
- segment. The simplified segment directives .DATA, .DATA?, and .STACK
- all create segments that are part of the group DGROUP.
-
- For example, assume the variable test1 was declared in a segment
- defined with the .DATA directive and test2 was declared in a segment
- defined with the .FARDATA directive. The statement
-
-
- mov ax,OFFSET test1
-
- loads the address of test1 relative to DGROUP. The statement
-
-
- mov ax,OFFSET test2
-
- loads the address of test2 relative to the segment defined by the
- .FARDATA directive. See Section 5.3 for more information on groups.
-
-
- 5.1.7 Default Segment Names
-
- If you use the simplified segment directives by themselves, you do not
- need to know the names assigned for each segment. However, it is possible
- to mix full segment definitions with simplified segment directives.
- Therefore, some programmers may wish to know the actual names assigned to
- all segments.
-
- Table 5.1 shows the default segment names created by each directive.
-
- Table 5.1 Default Segments and Types for Standard Memory Models
-
- Model Directive Name Align Combine Class Group
- ──────────────────────────────────────────────────────────────────────────
- Tiny .CODE _TEXT WORD PUBLIC 'CODE' DGROUP
-
- .DATA _DATA WORD PUBLIC 'DATA' DGROUP
-
- .CONST CONST WORD PUBLIC 'CONST' DGROUP
-
- .DATA? _BSS WORD PUBLIC 'BSS' DGROUP
-
- ──────────────────────────────────────────────────────────────────────────
- Small .CODE _TEXT WORD PUBLIC 'CODE'
-
- .DATA _DATA WORD PUBLIC 'DATA' DGROUP
-
- .CONST CONST WORD PUBLIC 'CONST' DGROUP
-
- .DATA? _BSS WORD PUBLIC 'BSS' DGROUP
-
- .STACK STACK PARA STACK 'STACK' DGROUP
-
- ──────────────────────────────────────────────────────────────────────────
- Medium .CODE name_TEXT WORD PUBLIC 'CODE'
-
- .DATA _DATA WORD PUBLIC 'DATA' DGROUP
-
- .CONST CONST WORD PUBLIC 'CONST' DGROUP
-
- .DATA? _BSS WORD PUBLIC 'BSS' DGROUP
-
- .STACK STACK PARA STACK 'STACK' DGROUP
-
- ──────────────────────────────────────────────────────────────────────────
- Compact .CODE _TEXT WORD PUBLIC 'CODE'
-
- .FARDATA FAR_DATA PARA private 'FAR_DATA'
-
- .FARDATA? FAR_BSS PARA private 'FAR_BSS'
-
- .DATA _DATA WORD PUBLIC 'DATA' DGROUP
-
- .CONST CONST WORD PUBLIC 'CONST' DGROUP
-
- .DATA? _BSS WORD PUBLIC 'BSS' DGROUP
-
- .STACK STACK PARA STACK 'STACK' DGROUP
-
- ──────────────────────────────────────────────────────────────────────────
- Large or .CODE name_TEXT WORD PUBLIC 'CODE'
-
- huge .FARDATA FAR_DATA PARA private 'FAR_DATA'
-
- .FARDATA? FAR_BSS PARA private 'FAR_BSS'
-
- .DATA _DATA WORD PUBLIC 'DATA' DGROUP
-
- .CONST CONST WORD PUBLIC 'CONST' DGROUP
-
- .DATA? _BSS WORD PUBLIC 'BSS' DGROUP
-
- .STACK STACK PARA STACK 'STACK' DGROUP
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- The name used as part of far-code segment names is the file name of the
- module. The default name associated with the .CODE directive can be
- overridden in medium and large models. The default names for the .FARDATA
- and .FARDATA? directives can always be overridden.
-
- The segment and group table at the end of listings always shows the actual
- segment names. However, the GROUP and ASSUME statements generated by the
- .MODEL directive are not shown in listing files. For a program that uses
- all possible segments, group statements equivalent to the following would
- be generated:
-
- DGROUP GROUP _DATA,CONST,_BSS,STACK
-
- For tiny model, the following would be generated:
-
- ASSUME cs:DGROUP,ds:DGROUP,ss:DGROUP
-
- For small and compact models, the following would be generated:
-
- ASSUME cs:_TEXT,ds:DGROUP,ss:DGROUP
-
- For medium, large, and huge models, the following statement is given:
-
- ASSUME cs: name_TEXT,ds:DGROUP,ss:DGROUP
-
- Example 1
-
- EXTRN xvariable:WORD
- EXTRN xprocedure:NEAR
- DGROUP GROUP _DATA,_BSS
- ASSUME cs:_TEXT,ds:DGROUP,ss:DGROUP
- _TEXT SEGMENT WORD PUBLIC 'CODE'
- start: mov ax,DGROUP ; Initialize data segment
- mov ds,ax
- cli
- mov ss,ax ; Move DGROUP into SS
-
- add sp,OFFSET STACK ; Adjust SP to top of stac
- sti
- .
- .
- .
- TEXT ENDS
- _DATA SEGMENT WORD PUBLIC 'DATA'
- ivariable DB 5
- iarray DW 50 DUP (5)
- string DB "This is a string"
- uarray DW 50 DUP (?)
- _DATA ENDS
- STACK SEGMENT PARA STACK 'STACK'
- DB 100h DUP (?)
- STACK ENDS
- END start
-
- This example is equivalent to Example 1 in Section 5.1.4, "Defining
- Simplified Segments." Notice that the segment order must be different in
- this version to achieve the segment order specified by using the DOSSEG
- directive in the first Section 5.1.4 example. The external variables are
- declared at the start of the source code in this example. With simplified
- segment directives, external variables can be declared in the segment in
- which they are used. The code generated by .STARTUP is discussed in more
- detail in Section 5.5.3.
-
- Example 2
-
- DGROUP GROUP _DATA,CONST,STACK
- ASSUME cs:TASK_TEXT,ds:FAR_DATA,ss:STACK
- EXTRN xprocedure:FAR
- EXTR xvariable:FAR
- FAR_BSS SEGMENT PARA 'FAR_DATA'
- fuarray DW 10 DUP (?) ; Far uninitialized data
- FAR_BSS ENDS
- CONST SEGMENT WORD PUBLIC 'CONST'
- string DB "This is a string" ; String constant
- CONST ENDS
- _DATA SEGMENT WORD PUBLIC 'DATA'
- niarray DB 100 DUP (5) ; Near initialized data
- _DATA ENDS
- FAR_DATA SEGMENT WORD 'FAR_DATA'
- fiarray DW 100 DUP (10)
- FAR_DATA ENDS
- TASK_TEXT SEGMENT WORD PUBLIC 'CODE'
- task PROC FAR
- .
- .
- .
- ret
- task ENDP
- TASK_TEXT ENDS
- END
-
- This example is equivalent to Example 2 in Section 5.1.4, "Defining
- Simplified Segments." Notice that the segment order is the same in both
- versions. The segment order shown here is written to the object file, but
- it is different in the executable file. The segment order specified by the
- compiler (the DOS segment order) overrides the segment order in the module
- object file.
-
-
- 5.2 Full Segment Definitions
-
- If you need complete control over segments, you may want to give complete
- segment definitions. The section below explains all aspects of segment
- definitions, including how to order segments and how to define all the
- segment types.
-
-
- 5.2.1 Setting the Segment-Order Method
-
- The order in which QuickAssembler writes segments to the object file can
- be either sequential or alphabetical. If the sequential method is
- specified, segments are written in the order in which they appear in the
- source code. If the alphabetical method is specified, segments are written
- in the alphabetical order of their segment names.
-
- The default is sequential. If no segment-order directive or option is
- given, segments are ordered sequentially. The segment-order method is only
- one factor in determining the final order of segments in memory. The
- DOSSEG directive (see Section 5.1.2, "Specifying DOS Segment Order") and
- class type (see Section 5.2.2.3, "Controlling Segment Structure with
- Class Type") can also affect segment order.
-
- The ordering method can be set by using the .ALPHA or .SEQ directive in
- the source code. The method can also be set using the /s (sequential) or
- /a (alphabetical) assembler options (see Appendix B, Section B.1,
- "Specifying the Segment-Order Method"). The directives have precedence
- over the options. For example, if the source code contains the .ALPHA
- directive, but the /s option is given on the command line, the segments
- are ordered alphabetically.
-
- Changing the segment order is an advanced technique. In most cases, you
- can simply leave the default sequential order in effect. If you are
- linking with high-level-language modules, the compiler automatically sets
- the segment order. The DOSSEG directive also overrides any segment-order
- directives or options.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Some previous versions of the IBM Macro Assembler ordered segments
- alphabetically by default. If you have trouble assembling and linking
- source-code listings from books or magazines, try using the /a option.
- Listings written for previous IBM versions of the assembler may not work
- without this option. The distinction between ENDS as the end of a segment
- and ENDS as the end of a structure is also made by the content of the
- program.
- ──────────────────────────────────────────────────────────────────────────
-
- Example 1
-
- .SEQ
- DATA SEGMENT WORD PUBLIC 'DATA'
- DATA ENDS
- CODE SEGMENT WORD PUBLIC 'CODE'
- CODE ENDS
-
- Example 2
-
- .ALPHA
- DATA SEGMENT WORD PUBLIC 'DATA'
- DATA ENDS
- CODE SEGMENT WORD PUBLIC 'CODE'
- CODE ENDS
-
- In Example 1, the DATA segment is written to the object file first because
- it appears first in the source code. In Example 2, the CODE segment is
- written to the object file first because its name comes first
- alphabetically.
-
-
- 5.2.2 Defining Full Segments
-
- The beginning of a program segment is defined with the SEGMENT directive,
- and the end of the segment is defined with the ENDS directive.
-
- Syntax
-
- name SEGMENT [[align]] [[combine]] [[use]] [['class']]
- statements
- name ENDS
-
- The name defines the name of the segment. This name can be unique, or it
- can be the same name given to other segments in the program. Segments with
- identical names are treated as the same segment. For example, if it is
- convenient to put different portions of a single segment in different
- source modules, the segment is given the same name in both modules.
-
- The optional align, combine, use, and 'class' types give the linker and
- the assembler instructions on how to set up and combine segments. Types
- can be specified in any order; it is not necessary to enter all types, or
- any type, for a given segment.
-
- Defining segment types is an advanced technique. Beginning
- assembly-language programmers might try using the simplified segment
- directives discussed in Section 5.1.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Don't confuse the PAGE align type and the PUBLIC combine type with
- the PAGE and PUBLIC directives. The distinction should be clear from
- context since the align and combine types are only used on the same line
- as the SEGMENT directive.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 5.2.2.1 Controlling Alignment with Align Type
-
- The optional align type defines the range of memory addresses from which a
- starting address for the segment can be selected. The align type can be
- any one of the following:
-
- Align Type Meaning
- ──────────────────────────────────────────────────────────────────────────
- BYTE Uses the next available byte address
-
- WORD Uses the next available word address (2 bytes per
- word)
-
- DWORD Uses the next available doubleword address (4 bytes
- per doubleword)
-
- PARA Uses the next available paragraph address (16 bytes
- per paragraph)
-
- PAGE Uses the next available page address (256 bytes per
- page)
-
-
- If no align type is given, PARA is used by default.
-
- The linker uses the alignment information to determine the relative start
- address for each segment. DOS uses the information to calculate the actual
- start address when the program is loaded.
-
- Align types are illustrated in Figure 5.1 in the next section.
-
-
- 5.2.2.2 Defining Segment Combinations with Combine Type
-
- The optional combine type defines how to combine segments having the same
- name. The combine type can be any one of the following:
-
- Combine Type Meaning
- ──────────────────────────────────────────────────────────────────────────
- PUBLIC Concatenates all segments having the same name to form
- a single, contiguous segment. The total size of the
- resulting segment is equal to the sum of all
- contributing segments.
-
- All instruction and data addresses in the new segment
- are relative to a single segment register, and all
- offsets are adjusted to represent the distance from
- the beginning of the segment.
-
- STACK Concatenates all segments having the same name to form
- a single, contiguous segment. This combine type is the
- same as the PUBLIC combine type, except that all
- addresses in the new segment are relative to the SS
- segment register. The total size of the resulting
- segment is equal to the sum of all contributing
- segments.
-
- The Stack Pointer (SP) register is initialized to the
- length of the segment. The stack segment of your
- program should normally use the STACK type, since this
- automatically initializes the SS register, as
- described in Section 5.5.3. If you create a stack
- segment and do not use the STACK type, you must give
- instructions to initialize the SS and SP registers.
-
- For each individual segment, all initialized data is
- placed at the high end of the resulting stack segment.
- Consequently, if more than one stack segment contains
- initialized data, the linker overwrites this data as
- it links in each segment. Note that stack data cannot
- be initialized with simplified segment directives.
-
- COMMON Creates overlapping segments by placing the start of
- all segments having the same name at the same address.
-
- The length of the resulting area is the length of the
- longest segment. All addresses in the segments are
- relative to the same base address. If variables are
- initialized in more than one segment having the same
- name and COMMON type, the most recently initialized
- data replaces any previously initialized data.
-
- MEMORY Concatenates all segments having the same name to form
- a single, contiguous segment.
-
- The Microsoft Overlay Linker treats MEMORY segments
- exactly the same as PUBLIC segments. QuickAssembler
- allows you to use MEMORY type even though LINK does
- not recognize a separate MEMORY type. This feature is
- compatible with other linkers that may support a
- combine type conforming to the Intel definition of
- MEMORY type.
-
- AT address Causes all label and variable addresses defined in the
- segment to be relative to address.
-
- The address can be any valid expression but must not
- contain a forward reference──that is, a reference to a
- symbol defined later in the source file. An AT segment
- typically contains no code or initialized data.
- Instead, it represents an address template that can be
- placed over code or data already in memory, such as a
- screen buffer or other absolute memory locations
- defined by hardware. The linker will not generate any
- code or data for AT segments, but existing code or
- data can be accessed by name if it is given a label in
- an AT segment. Section 6.6, "Setting the Location
- Counter," shows an example of a segment with AT
- combine type.
-
-
- If no combine type is given, the segment has private type. Segments having
- the same name are not combined. Instead, each segment receives its own
- physical segment when loaded into memory.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Although a given segment name can be used more than once in a
- source file, each segment definition using that name must have either
- exactly the same attributes, or attributes that do not conflict. If types
- are given for an initial segment definition, subsequent definitions for
- that segment need not specify any types.
-
- Normally, you should provide at least one stack segment (having STACK
- combine type) in a program. If no stack segment is declared, LINK displays
- a warning message. You can ignore this message if you have a specific
- reason for not declaring a stack segment. For example, you would not have
- a separate stack segment in a program in the .COM format.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- The following source-code shell illustrates one way in which the combine
- and align types can be used. Figure 5.1 shows the way LINK would load the
- sample program into memory.
-
- NAME module_1
-
- ASEG SEGMENT BYTE PUBLIC 'CODE'
- start: .
- .
- .
- ASEG ENDS
-
- BSEG SEGMENT WORD COMMON 'DATA'
- .
- .
- .
- BSEG ENDS
-
- CSEG SEGMENT PARA STACK 'STACK'
- .
- .
- .
- CSEG ENDS
-
- DSEG SEGMENT AT 0B800H
- .
- .
- .
- DSEG ENDS
- END start
-
- NAME module_2
-
- ASEG SEGMENT BYTE PUBLIC 'CODE'
- .
- .
- .
- ASEG ENDS
-
- BSEG SEGMENT WORD COMMON 'DATA'
- .
- .
- .
- BSEG ENDS
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 5.2.2.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 5.2.2.3 Controlling Segment Structure with Class Type
-
- Class type is a means of associating segments that have different names,
- but similar purposes. It can be used to control segment order and to
- identify the code segment.
-
- The class name must be enclosed in single quotation marks ('). Class names
- are not case sensitive unless the /Cl or /Cx option is used during
- assembly.
-
- All segments belong to a class. Segments for which no class name is
- explicitly stated have the null class name. LINK imposes no restriction on
- the number or size of segments in a class. The total size of all segments
- in a class can exceed 64K.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The names assigned for class types of segments should not be used
- for other symbol definitions in the source file. For example, if you give
- a segment the class name 'CONSTANT', you should not give the name constant
- to variables or labels in the source file.
- ──────────────────────────────────────────────────────────────────────────
-
- The linker expects segments having the class name CODE or a class name
- with the suffix CODE to contain program code. You should always assign
- this class name to segments containing code.
-
- Class type is one of two factors that control the final order of segments
- in an executable file. The other factor is the order of the segments in
- the source file (with the /s option or the .SEQ directive) or the
- alphabetical order of segments (with the /a option or the .ALPHA
- directive).
-
- These factors control different internal behavior, but both affect the
- final order of segments in the executable file. The sequential or
- alphabetical order of segments in the source file determines the order in
- which the assembler writes segments to the object file. The class type can
- affect the order in which the linker writes segments from object files to
- the executable file.
-
- Segments having the same class type are loaded into memory together,
- regardless of their sequential or alphabetical order in the source file.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The DOSSEG directive (see Section 5.1.2, "Specifying DOS Segment
- Order") overrides all other factors in determining segment order.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- A_SEG SEGMENT 'SEG_1'
- A_SEG ENDS
-
- B_SEG SEGMENT 'SEG_2'
- B_SEG ENDS
-
- C_SEG SEGMENT 'SEG_1'
- C_SEG ENDS
-
- When QuickAssembler assembles the preceding program fragment, it writes
- the segments to the object file in sequential or alphabetical order,
- depending on whether the /a option or the .ALPHA directive was used. In
- the example above, the sequential and alphabetical order are the same, so
- the order will be A_SEG, B_SEG, C_SEG in either case.
-
- When the linker writes the segments to the executable file, it first
- checks to see if any segments have the same class type. If they do, it
- writes them to the executable file together. Thus, A_SEG and C_SEG are
- placed together because they both have class type 'SEG_1'. The final order
- in memory is A_SEG, C_SEG, B_SEG.
-
- Since LINK processes modules in the order it receives them on the command
- line, you may not always be able to easily specify the order in which you
- want segments to be loaded. For example, assume your program has four
- segments that you want loaded in the following order: _TEXT, _DATA, CONST,
- and STACK.
-
- The _TEXT, CONST, and STACK segments are defined in the first module of
- your program, but the _DATA segment is defined in the second module. LINK
- will not put the segments in the proper order because it first loads the
- segments encountered in the first module.
-
- You can avoid this problem by starting your program with dummy segment
- definitions in the order you wish to load your real segments. The dummy
- segments can either go at the start of the first module, or they can be
- placed in a separate include file that is called at the start of the first
- module. You can then put the actual segment definitions in any order or
- any module you find convenient.
-
- For example, you might call the following include file at the start of the
- first module of your program:
-
- _TEXT SEGMENT WORD PUBLIC 'CODE'
- _TEXT ENDS
- _DATA SEGMENT WORD PUBLIC 'DATA'
- _DATA ENDS
- CONST SEGMENT WORD PUBLIC 'CONST'
- CONST ENDS
- STACK SEGMENT PARA STACK 'STACK'
- STACK ENDS
-
- The DOSSEG directive may be more convenient for defining segment order if
- you are willing to accept the DOS segment-order conventions.
-
- Once a segment has been defined, you do not need to specify the align,
- combine, use, and class types on subsequent definitions. For example, if
- your code defined dummy segments as shown above, you could define an
- actual data segment with the following statements:
-
- _DATA SEGMENT
- .
- .
- .
- _DATA ENDS
-
-
- 5.3 Defining Segment Groups
-
- A group is a collection of segments associated with the same starting
- address. You may wish to use a group if you want several types of data to
- be organized in separate segments in your source code, but want them all
- to be accessible from a single, common segment register at run time.
-
- Syntax
-
- name GROUP segment [[,segment]]...
-
- The name is the symbol assigned to the starting address of the group. All
- labels and variables defined within the segments of the group are relative
- to the start of the group, rather than to the start of the segments in
- which they are defined.
-
- The segment can be any previously defined segment or a SEG expression (see
- Section 9.2.4.5).
-
- Segments can be added to a group one at a time. For example, you can
- define and add segments to a group one by one.
-
- The GROUP directive does not affect the order in which segments of a group
- are loaded. Loading order depends on each segment's class, or on the order
- in which object modules are given to the linker.
-
- Segments in a group need not be contiguous. Segments that do not belong to
- the group can be loaded between segments that do. The only restriction is
- that the distance (in bytes) between the first byte in the first segment
- of the group and the last byte in the last segment must not exceed 65,535
- bytes.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE When the .MODEL directive is used, the offset of a group-relative
- segment refers to the ending address of the segment, not the beginning.
- For example, the expression OFFSET STACK evaluates to the end of the stack
- segment.
- ──────────────────────────────────────────────────────────────────────────
-
- Group names can be used with the ASSUME directive (discussed in Section
- 5.4, "Associating Segments with Registers") and as an operand prefix with
- the segment-override operator (discussed in Section 9.2.3).
-
- Example
-
- DGROUP GROUP ASEG,CSEG
- ASSUME ds:DGROUP
-
- ASEG SEGMENT WORD PUBLIC 'DATA'
- .
- asym .
- .
- ASEG ENDS
-
- BSEG SEGMENT WORD PUBLIC 'DATA'
- .
- bsym .
- .
- BSEG ENDS
-
- CSEG SEGMENT WORD PUBLIC 'DATA'
- .
- csym .
- .
- CSEG ENDS
- END
-
- Figure 5.2 shows the order of the example segments in memory. They are
- loaded in the order in which they appear in the source code (or in
- alphabetical order if the .ALPHA directive or /s option is specified).
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 5.3 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Since ASEG and CSEG are declared part of the same group, they have the
- same base despite their separation in memory. This means that the symbols
- asym and csym have offsets from the beginning of the group, which is also
- the beginning of ASEG. The offset of bsym is from the beginning of BSEG,
- since it is not part of the group. This sample illustrates the way LINK
- organizes segments in a group. It is not intended as a typical use of a
- group.
-
-
- 5.4 Associating Segments with Registers
-
- Many of the assembler instructions assume a default segment. For example,
- JMP instructions assume the segment associated with the CS register; PUSH
- and POP instructions assume the segment associated with the SS register;
- MOV instructions assume the segment associated with the DS register.
-
- When the assembler needs to reference an address, it must know what
- segment the address is in. It does this by using the default segment or
- group addresses assigned with the ASSUME directive.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Using the ASSUME directive to tell the assembler which segment to
- associate with a segment register is not the same as telling the
- processor. The ASSUME directive only affects assembly-time assumptions.
- You may need to use instructions to change run-time assumptions.
- Initializing segment registers at run time is discussed in Section 5.5.
- ──────────────────────────────────────────────────────────────────────────
-
- Syntax
-
- ASSUME segmentregister:name [[,segmentregister:name]]...
- ASSUME segmentregister:NOTHING
- ASSUME NOTHING
-
- The name must be the name of the segment or group that is to be associated
- with segmentregister. Subsequent instructions that assume a default
- register for referencing labels or variables automatically assume that if
- the default segment is segmentregister, the label or variable is in the
- name segment or group.
-
- The ASSUME directive can define a segment for each of the segment
- registers. The segmentregister can be CS, DS, ES, or SS. The name must be
- one of the following:
-
- ■ The name of a segment defined in the source file with the SEGMENT
- directive
-
- ■ The name of a group defined in the source file with the GROUP directive
-
- ■ The keyword NOTHING
-
- ■ A SEG expression (see Section 9.2.4.5, "SEG Operator")
-
- ■ A string equate that evaluates to a segment or group name (but not a
- string equate that evaluates to a SEG expression)
-
- The keyword NOTHING cancels the current segment selection. For example,
- the statement ASSUME NOTHING cancels all register selections made by
- previous ASSUME statements.
-
- Usually, a single ASSUME statement defines all four segment registers at
- the start of the source file. However, you can use the ASSUME directive at
- any point to change segment assumptions.
-
- Using the ASSUME directive to change segment assumptions is often
- equivalent to changing assumptions with the segment-override operator (:)
- (see Section 9.2.3). The segment-override operator is more convenient for
- one-time overrides, whereas the ASSUME directive may be more convenient if
- previous assumptions must be overridden for a sequence of instructions.
-
- Example
-
- DOSSEG
- .MODEL large ; DS automatically assumed to @data
- .STACK 100h
- .DATA
- d1 DW 7
- .FARDATA
- d2 DW 9
-
- .CODE
- start: mov ax,@data ; Initialize near data
- mov ds,ax
- mov ax,@fardata ; Initialize far data
- mov es,ax
- .
- .
- .
-
- ; Method 1 for series of instructions that need override
- ; Use segment override for each statement
-
- mov ax,es:d2
- .
- .
- .
- mov es:d2,bx
-
- ; Method 2 for series of instructions that need override
- ; Use ASSUME at beginning of series of instructions
-
- ASSUME es:@fardata
- mov cx,d2
- .
- .
- .
- mov d2,dx
-
-
- 5.5 Initializing Segment Registers
-
- Assembly-language programs must initialize segment values for each segment
- register before instructions that reference the segment register can be
- used in the source program.
-
- Initializing segment registers is different from assigning default values
- for segment registers with the ASSUME statement. The ASSUME directive
- tells the assembler what segments to use at assembly time. Initializing
- segments gives them an initial value that will be used at run time.
-
- The .STARTUP directive generates all the initialization code described in
- this section. This directive must be preceded by the .MODEL directive. If
- the .MODEL directive was followed by the farStack attribute, .STARTUP does
- not adjust SS and SP. Otherwise, it assumes the nearStack default, which
- sets SS equal to DS as described in Section 5.5.3, "Initializing the SS
- and SP Registers." When you use this default, the combined stack and near
- data must not exceed 64K.
-
- If you use .STARTUP, you don't need to enter any of the code in this
- section, except for the END statement. (However, if you use .STARTUP, you
- don't need to specify a starting address.) Make sure that you place the
- .STARTUP directive at the point you want your program to start executing,
- because the assembler automatically initializes CS:IP to point to the
- beginning of the code generated by .STARTUP.
-
-
- 5.5.1 Initializing the CS and IP Registers
-
- The CS and IP registers are initialized by specifying a starting address
- with the END directive.
-
- Syntax
-
- END [[startaddress]]
-
- The startaddress is a label or expression identifying the address where
- you want execution to begin when the program is loaded. Normally, a label
- for the start address should be placed at the address of the first
- instruction in the code segment.
-
- The CS segment is initialized to the value of startaddress. The IP
- register is normally initialized to 0. You can change the initial value of
- the IP register by using the ORG directive (see Section 6.6, "Setting the
- Location Counter") just before the startaddress label. For example,
- programs in the .COM format use ORG 100h to initialize the IP register to
- 256 (100 hexadecimal).
-
- If a program consists of a single source module, the start address is
- required for that module. If a program has several modules, all modules
- must terminate with an END directive, but only one of them can define a
- start address.
-
- ──────────────────────────────────────────────────────────────────────────
- WARNING One, and only one, module must define a start address. If you do
- not specify a start address, none is assumed. Neither QuickAssembler nor
- LINK will generate an error message, but your program will probably start
- execution at the wrong address.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- ; Module 1
- .CODE
- start: . ; First executable instruction
- .
- .
- EXTRN task:NEAR
- call task
- .
- .
- .
- END start ; Starting address defined in main module
-
- ; Module 2
- PUBLIC task
- .CODE
- task PROC
- .
- .
- .
- task ENDP
- END ; No starting address in secondary module
-
- If Module 1 and Module 2 are linked into a single program, it is essential
- that only the calling module define a starting address.
-
-
- 5.5.2 Initializing the DS Register
-
- The DS register must be initialized to the address of the segment that
- will be used for data.
-
- The address of the segment or group for the initial data segment must be
- loaded into the DS register. This is done in two statements because a
- memory value cannot be loaded directly into a segment register. The
- segment-setup lines typically appear at the start or very near the start
- of the code segment.
-
- Example 1
-
- _DATA SEGMENT WORD PUBLIC 'DATA'
- .
- .
- .
- _DATA ENDS
- _TEXT SEGMENT BYTE PUBLIC 'CODE'
- ASSUME cs:_TEXT,ds:_DATA
- start: mov ax,_DATA ; Load start of data segment
- mov ds,ax ; Transfer to DS register
- .
- .
- .
- _TEXT ENDS
- END start
-
- If you are using the Microsoft naming convention and segment order, the
- address loaded into the DS register is not a segment address but the
- address of DGROUP, as shown in Example 2. With simplified segment
- directives, the address of DGROUP is represented by the predefined equate
- @data.
-
- Example 2
-
- DOSSEG
- .MODEL SMALL
- .DATA
- .
- .
- .
- .CODE
- start: mov ax,@data ; Load start of DGROUP (@data)
- mov ds,ax ; Transfer to DS register
- .
- .
- .
- END start
-
-
- 5.5.3 Initializing the SS and SP Registers
-
- At load time, DOS sets SS to the segment address of the last segment
- having combine type STACK, and SP to the size of the stack. (The linker
- actually determines the value of SS:SP and places this value in the
- executable-file header. DOS sets SS and SP as indicated in the file
- header.)
-
- If you use a stack segment with combine type STACK or use the .STACK
- directive, the program automatically loads with SS and SP initialized, as
- described above.
-
- However, this basic initialization does not set SS equal to DS. If the
- program contains the statement ASSUME SS:DGROUP, it will be prone to
- errors. The following code resets SS and SP so that SS has the same value
- as DS. The code then adjusts SP upward so that SS:SP points to the
- same physical address it did before. Since hardware interrupts use
- the same stack as the program, you should turn off interrupts while
- changing the stack. Most 8086-family processors turn off interrupts
- automatically when you adjust SS or SP, but early versions of the 8088 do n
-
- Example 1
-
- .MODEL small
- .STACK 100h ; Initialize "STACK"
- .DATA
- .
- .
- .
- .CODE
- start: mov ax,@data ; Load segment location
- mov ds,ax ; into DS register
- cli ; Turn off interrupts
- mov ss,ax ; Load same value as DS into SS
- mov sp,OFFSET STACK ; Give SP new stack size
- sti ; Turn interrupts back on
- .
- .
- .
-
- This example reinitializes SS so that it has the same value as DS, and it
- adjusts SP to reflect the new stack offset. Microsoft high-level-language
- compilers do this so that stack variables in near procedures can be
- accessed relative to either SS or DS.
-
- However, this code only works correctly if you use .MODEL and you declare
- a stack segment in just one module. The following code handles the more
- general case. The .STARTUP directive generates this code:
-
- Example 2
-
- start_label:
- mov dx,DGROUP ; Move DGROUP into DS and DX
- mov ds,dx
- mov bx,ss ; BX = STACK - DGROUP
- sub bx,dx ;
- shl bx,1 ; Multiply difference by 16
- shl bx,1 ; and leave result in BX
- shl bx,1
- shl bx,1
- cli
- mov ss,dx ; Move DGROUP into SS
- add sp,bx ; Adjust SP upward by
- sti ; (STACK - DGROUP) * 16
-
- The code above sets SS and SP so that SS equals DS. This code works
- correctly no matter how many modules declare a stack segment.
-
-
- 5.5.4 Initializing the ES Register
-
- The ES register is not automatically initialized. If your program uses the
- ES register, you must initialize it by moving the appropriate segment
- value into the register.
-
- Example
-
- ASSUME es:@fardata ; Tell the assembler
- mov ax,@fardata ; Tell the processor
- mov es,ax
-
-
- 5.6 Nesting Segments
-
- Segments can be nested. When QuickAssembler encounters a nested segment,
- it temporarily suspends assembly of the enclosing segment and begins
- assembly of the nested segment. When the nested segment has been
- assembled, Quick-Assembler continues assembly of the enclosing segment.
-
- Nesting of segments makes it possible to mix segment definitions in
- programs that use simplified segment directives for most segment
- definitions. When a full segment definition is given, the new segment is
- nested in the simplified segment in which it is defined.
-
- Example 1
-
- ; Macro to print message on the screen
- ; Uses full segment definitions - segments nested
-
- message MACRO text
- LOCAL symbol
- _DATA SEGMENT WORD PUBLIC 'DATA'
- symbol DB &text
- DB 13,10,"$"
- _DATA ENDS
- mov ah,09h
- mov dx,OFFSET symbol
- int 21h
- ENDM
-
- _TEXT SEGMENT BYTE PUBLIC 'CODE'
- .
- .
- .
- message "Please insert disk"
-
- In the example above, a macro called from inside of the code segment
- (_TEXT) allocates a variable within a nested data segment (_DATA). This
- has the effect of allocating more data space on the end of the data
- segment each time the macro is called. The macro can be used for messages
- appearing only once in the source code.
-
- Example 2
-
- ; Macro to print message on the screen
- ; Uses simplified segment directives - segments not nested
-
- message MACRO text
- LOCAL symbol
- .DATA
- symbol DB &text
- DB 13,10,"$"
- .CODE
- mov ah,09h
- mov dx,OFFSET symbol
- int 21h
- ENDM
-
- .CODE
- .
- .
- .
- message "Please insert disk"
-
- Although Example 2 has the same practical effect as Example 1,
- Quick-Assembler handles the two macros differently. In Example 1, assembly
- of the outer (code) segment is suspended rather than terminated. In
- Example 2, assembly of the code segment terminates, assembly of the data
- segment starts and terminates, and then assembly of the code segment is
- restarted.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 6: Defining Constants, Labels, and Variables
-
-
- This chapter explains how to define constants, labels, variables, and
- other symbols that refer to instruction and data locations within
- segments.
-
- Constants are important in QuickAssembler, just as they are in other
- languages. You can use constants as immediate operands in instructions and
- as initial values in data declarations. QuickAssembler supports a number
- of useful radixes (including binary and hexadecimal), as described in
- Section 6.1.
-
- QuickAssembler lets you use symbols as well as constants. Sections 6.2,
- "Assigning Names to Symbols," and 6.3, "Using Type Specifiers," present
- the basic principles of generating symbolic names.
-
- Most symbols are either code labels or variable names. Section 6.4,
- "Defining Code Labels," and Section 6.5, "Defining and Initializing
- Data," describe how to define these symbols.
-
- This chapter tells you how to assign labels and most kinds of variables.
- (Multifield variables, such as structures and records, are discussed in
- Chapter 7, "Using Structures and Records.") Chapter 6 also discusses
- related directives, including those that control the location counter
- directly. The assembler uses the location counter to assign addresses to
- symbols.
-
-
- 6.1 Constants
-
- Constants can be used in source files to specify numbers or strings that
- are set or initialized at assembly time. The assembler recognizes four
- types of constant values:
-
- 1. Integers
-
- 2. Packed binary coded decimals
-
- 3. Real numbers
-
- 4. Strings
-
-
- 6.1.1 Integer Constants
-
- Integer constants represent integer values. They can be used in a variety
- of contexts in assembly-language source code. For example, they can be
- used in data declarations and equates, or as immediate operands.
-
- Packed decimal integers are a special kind of integer constant that can
- only be used to initialize binary coded decimal (BCD) variables. They are
- described in Sections 6.1.2, "Packed Binary Coded Decimal Constants," and
- 6.5.1.2, "Binary Coded Decimal Variables."
-
- Integer constants can be specified in binary, octal, decimal, or
- hexadecimal values. Table 6.1 shows the legal digits for each of these
- radixes. For hexadecimal radix, the digits can be either uppercase or
- lowercase letters.
-
- Table 6.1 Digits Used with Each Radix
-
- Radix Base Digits
- ──────────────────────────────────────────────────────────────────────────
- Binary 2 0 1
-
- Octal 8 0 1 2 3 4 5 6 7
-
- Decimal 10 0 1 2 3 4 5 6 7 8 9
-
- Hexadecimal 16 0 1 2 3 4 5 6 7 8 9 A B C D E F
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- The radix for an integer can be defined for a specific integer by using
- radix specifiers, or a default radix can be defined globally with the
- .RADIX directive.
-
-
- 6.1.1.1 Specifying Integers with Radix Specifiers
-
- The radix for an integer constant can be given by putting one of the
- following radix specifiers after the last digit of the number:
-
- Radix Specifier
- ──────────────────────────────────────────────────────────────────────────
- Binary B
-
- Octal Q or O
-
- Decimal D
-
- Hexadecimal H
-
-
- Radix specifiers can be given in either uppercase or lowercase letters;
- sample code in this manual uses lowercase letters.
-
- Hexadecimal numbers must always start with a decimal digit (0-9). If
- necessary, put a leading 0 at the left of the number to distinguish
- between symbols and hexadecimal numbers that start with a letter. For
- example, 0ABCh is interpreted as a hexadecimal number, but ABCh is
- interpreted as a symbol. The hexadecimal digits A through F can be either
- uppercase or lowercase letters. Sample code in this manual uses uppercase
- letters.
-
- If no radix is given, the assembler interprets the integer by using the
- current default radix. The initial default radix is decimal, but you can
- change the default with the .RADIX directive.
-
- Examples
-
- n360 EQU 01011010b + 132q + 5Ah + 90d ; 4 * 90
- n60 EQU 00001111b + 17o + 0Fh + 15d ; 4 * 15
-
-
- 6.1.1.2 Setting the Default Radix
-
- The .RADIX directive sets the default radix for integer constants in the
- source file.
-
- Syntax
-
- .RADIX expression
-
- The expression must evaluate to a number in the range 2-16. It defines
- whether the numbers are binary, octal, decimal, hexadecimal, or numbers of
- some other base.
-
- Numbers given in expression are always considered decimal, regardless of
- the current default radix. The initial default radix is decimal.
-
- Note that the .RADIX directive does not affect real numbers initialized as
- variables with the DD, DQ, or DT directive. Initial values for real-number
- variables declared with these directives are always evaluated as decimal
- unless a radix specifier is appended.
-
- Also, the .RADIX directive does not affect the optional radix specifiers,
- B and D, used with integer numbers. When the letters B or D appear at the
- end of any integer, they are always considered to be a radix specifier
- even if the current radix is 16.
-
- For example, if the input radix is 16, the number 0ABCD will be
- interpreted as 0ABC decimal, an illegal number, instead of as 0ABCD
- hexadecimal, as intended. Type 0ABCDh to specify 0ABCD in hexadecimal.
- Similarly, the number 11B will be treated as 11 binary, a legal number,
- but not as 11B hexadecimal as intended. Type 11Bh to specify 11B in
- hexadecimal.
-
- Examples
-
- .RADIX 16 ; Set default radix to hexadecimal
- .RADIX 2 ; Set default radix to binary
-
-
- 6.1.2 Packed Binary Coded Decimal Constants
-
- When an integer constant is used with the DT directive, the number is
- interpreted by default as a packed binary coded decimal (BCD) number. You
- can use the D radix specifier to override the default and initialize
- 10-byte integers as binary-format integers.
-
- The syntax for specifying binary coded decimals is exactly the same as for
- other integers. However, the assembler encodes binary coded decimals in a
- completely different way. See Section 6.5.1.2, "Binary Coded Decimal
- Variables," for complete information on storage of binary coded decimals.
-
- Examples
-
- positive DT 1234567890 ; Encoded as 00000000001234567890h
- negative DT -1234567890 ; Encoded as 80000000001234567890h
-
-
- 6.1.3 Real-Number Constants
-
- A real number is a number consisting of an integer part, a fractional
- part, and an exponent. Real numbers are usually represented in decimal
- format.
-
- Syntax
-
- [[+ | -]] integer.fraction[[E[[+ | -]]exponent]]
-
- The integer and fraction parts combine to form the value of the number.
- This value is stored internally as a unit and is called the mantissa. It
- may be signed. The optional exponent follows the exponent indicator (E).
- It represents the magnitude of the value and is stored internally as a
- unit. If no exponent is given, 1 is assumed. If an exponent is given, it
- may be signed.
-
- During assembly, the assembler converts real-number constants given in
- decimal format to a binary format. The sign, exponent, and mantissa of the
- real number are encoded as bit fields within the number. See Section
- 6.5.1.4, "Real-Number Variables," for an explanation of how real numbers
- are encoded.
-
- You can specify the encoded format directly using hexadecimal digits (0-9
- or A-F). The number must begin with a decimal digit (0-9) and cannot be
- signed. It must be followed by the real-number designator (R). This
- designator is used the same as a radix designator except it specifies that
- the given hexadecimal number should be interpreted as a real number.
-
- Real numbers can only be used to initialize variables with the DD, DQ, and
- DT directives. They cannot be used in expressions. The maximum number of
- digits in the number and the maximum range of exponent values depend on
- the directive. The number of digits for encoded numbers used with DD, DQ,
- and DT must be 8, 16, and 20 digits, respectively. (If a leading 0 is
- supplied, the number must be 9, 17, or 21 digits.) See Section 6.5.1.4,
- "Real-Number Variables," for an explanation of how real numbers are
- encoded.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Real numbers will be encoded differently depending upon whether you
- use the .MSFLOAT directive. By default, real numbers are encoded in the
- IEEE format. The .MSFLOAT directive overrides the default and specifies
- Microsoft Binary format. See Section 6.5.1.4, "Real-Number Variables,"
- for a description of these formats.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- ; Real numbers
- shrt DD 25.23
- long DQ 2.523E1
- ten_byte DT 2523.0E-2
-
- ; Assumes .MSFLOAT
- mbshort DD 81000000r ; 1.0 as Microsoft Binary short
- mblong DQ 8100000000000000r ; 1.0 as Microsoft Binary long
-
- ; Assumes default IEEE format
- ieeeshort DD 3F800000r ; 1.0 as IEEE short
- ieeelong DQ 3FF0000000000000r ; 1.0 as IEEE long
-
- ; The same regardless of processor directives
- temporary DT 3FFF8000000000000000r ; 1.0 as 10-byte temporary real
-
-
- 6.1.4 String Constants
-
- A string constant consists of one or more ASCII characters enclosed in
- single or double quotation marks. Strings are interpreted as lists of
- characters having the ASCII values of the characters in the string.
-
- Syntax
-
- 'characters'
- "characters"
-
- String constants are case sensitive. A string constant consisting of a
- single character is sometimes called a character constant.
-
- Single quotation marks must be encoded twice when used literally within
- string constants that are also enclosed by single quotation marks.
- Similarly, double quotation marks must be encoded twice when used in
- string constants that are also enclosed by double quotation marks.
-
- Examples
-
- char DB 'a'
- char2 DB "a"
- message DB "This is a message."
- warn DB 'Can"t find file.' ; Can't find file.
- warn2 DB "Can't find file." ; Can't find file.
- string DB "This ""value"" not found." ; This "value" not found.
- string2 DB 'This "value" not found.' ; This "value" not found.
-
-
- 6.1.5 Determining Floating-Point Format
-
- The .MSFLOAT directive disables all coprocessor instructions and specifies
- that initialized real-number variables be encoded in the Microsoft Binary
- format. Without this directive, initialized real-number variables are
- encoded in the IEEE format. This is a change from Versions 4.0 and earlier
- of the Microsoft Macro Assembler, which used Microsoft Binary format by
- default and required a coprocessor directive or the /R option to specify
- IEEE format. .MSFLOAT must be used for programs that require real-number
- data in the Microsoft Binary format. Section 6.5.1.4, "Real-Number
- Variables," describes real-number data formats and the factors to consider
- in choosing a format.
-
-
- 6.2 Assigning Names to Symbols
-
- A symbol is a name that represents a value. Symbols are one of the most
- important elements of assembly-language programs. Elements that must be
- represented symbolically in assembly-language source code include
- variables, address labels, macros, segments, procedures, records, and
- structures. Constants, expressions, and strings can also be represented
- symbolically.
-
- Symbol names are combinations of letters (both uppercase and lowercase),
- digits, and special characters. The QuickAssembler recognizes the
- following character set:
-
- A-Z a-z 0-9
-
- ? @ _ $ : . [ ] ( ) < > { } + - / *
-
- & % ! ' ~ | \ = # ^ ; , ` "
-
- Letters, digits, and some characters can be used in symbol names, but some
- restrictions on how certain characters can be used or combined are listed
- below:
-
- ■ A name can have any combination of uppercase and lowercase letters.
- Within the QC integrated environment, the default behavior (Preserve
- Extrn) is for the assembler to convert all symbol names to uppercase
- unless they are public or external. When you use simplified segment
- directives, all procedure labels declared with PROC are automatically
- public.
-
- When you use QCL, all lowercase letters are converted to uppercase by
- the assembler, unless you give the /Cl assembly option, or you declare
- the name with a PROC, PUBLIC, or EXTRN directive and you give the /Cx
- option. The /Cl and /Cx options correspond to the assembler flags
- Preserve Case and Preserve Extrn, respectively, within the QC
- environment.
-
- ■ Digits may be used within a name, but not as the first character.
-
- ■ A name can be given any number of characters, but only the first 31 are
- used. All other characters are ignored.
-
- ■ The following characters may be used at the beginning of a name or
- within a name: underscore (_), question mark (?), dollar sign ($), and
- at sign (@).
-
- ■ The period (.) is an operator and cannot be used within a name, but it
- can be used as the first character of a name.
-
- ■ A name may not be the same as any reserved name. Note that two special
- characters, the question mark (?) and the dollar sign ($), are reserved
- names and therefore can't stand alone as symbol names.
-
- A reserved name is any name with a special, predefined meaning to the
- assembler. Reserved names include instruction and directive mnemonics,
- register names, and operator names. All uppercase and lowercase letter
- combinations of these names are treated as the same name.
-
- The following is a list of names that are always reserved by the
- assembler. Using any of these names for a symbol results in an error.
-
- $ DWORD GE %OUT
- * ELSE GROUP PAGE
- + ELSEIF GT PROC
- - ELSEIF1 HIGH PTR
- . ELSEIF2 IF PUBLIC
- / ELSEIFB IF1 PURGE
- = ELSEIFDEF IF2 QWORD
- ? ELSEIFDIF IFB .RADIX
- [] ELSEIFDIFI IFDEF RECORD
- .186 ELSEIFE IFDIF REPT
- .286 ELSEIFIDN IFE .SALL
- .286P ELSEIFIDNI IFIDN SEG
- .287 ELSEIFNB IFNB SEGMENT
- .386 ELSEIFNDEF IFNDEF .SEQ
- .386P END INCLUDE .SFCOND
- .387 ENDIF INCLUDELIB SHL
- .8086 ENDM IRP SHORT
- .8087 ENDP IRPC SHR
- ALIGN ENDS LABEL SIZE
- .ALPHA EQ .LALL SIZESTR
- AND EQU LE .STACK
- ASSUME .ERR LENGTH .STARTUP
- BYTE .ERR1 .LFCOND STRUC
- CATSTR .ERR2 .LIST SUBSTR
- .CODE .ERRB LOCAL SUBTTL
- COMM .ERRDEF LOW TBYTE
- COMMENT .ERRDIF LT .TFCOND
- .CONST .ERRE MACRO THIS
- .CREF .ERRIDN MASK TITLE
- .DATA .ERRNB MOD TYPE
- .DATA? .ERRNDEF .MODEL .TYPE
- DB .ERRNZ NAME WIDTH
- DD EVEN NE WORD
- DOSSEG EXITM NEAR .XALL
- DQ EXTRN NOT .XCREF
- DS FAR OFFSET .XLIST
- DT .FARDATA OR XOR
-
- In addition to the names listed above, instruction mnemonics and register
- names are considered reserved names. Instructions can vary depending on
- the processor directives given in the source file. For example, ENTER is
- recognized as a reserved word if you have enabled 286 instructions with
- the .286 directive. Section 18.3 describes processor directives.
- Instruction mnemonics for each processor are listed in the on-line Help
- system. Register names are listed in Section 2.6.2, "Register Operands."
-
-
- 6.3 Using Type Specifiers
-
- Some statements require type specifiers to give the size or type of an
- operand. There are two kinds of type specifiers: those that specify the
- size of a variable or other memory operand, and those that specify the
- distance of a label.
-
- The type specifiers that give the size of a memory operand are listed
- below with the number of bytes specified by each:
-
- Specifier Number of Bytes
- ──────────────────────────────────────────────────────────────────────────
- BYTE 1
-
- WORD 2
-
- DWORD 4
-
- QWORD 8
-
- TBYTE 10
-
-
- In some contexts, ABS can also be used as a type specifier that indicates
- an operand is a constant rather than a memory operand.
-
- The type specifiers that give the distance of a label are listed below:
-
- Specifier Description
- ──────────────────────────────────────────────────────────────────────────
- FAR The label references both the segment and offset of
- the label.
-
- NEAR The label references only the offset of the label.
-
- PROC The label has the default type (NEAR or FAR) of the
- current memory model. The default size is always NEAR
- if you use full segment definitions. If you use
- simplified segment directives (see Section 5.1), the
- default type is NEAR for small and compact models or
- FAR for medium, large, and huge models.
-
-
- Directives that use type specifiers include LABEL, PROC, EXTRN, and COMM.
- Operators that use type specifiers include PTR and THIS.
-
-
- 6.4 Defining Code Labels
-
- Code labels give symbolic names to the addresses of instructions in the
- code segment. These labels can be used as the operands to jump, call, and
- loop instructions to transfer program control to a new instruction.
-
-
- 6.4.1 Near-Code Labels
-
- Near-label definitions create instruction labels that have NEAR type.
- These instruction labels can be used to access the address of the label
- from other statements.
-
- Syntax
-
- name:
-
- The name must be followed by a colon (:). The segment containing the
- definition must be the one that the assembler currently associates with
- the CS register. The ASSUME directive is used to associate a segment with
- a segment register (see Section 5.4, "Associating Segments with
- Registers"). A near label can appear on a line by itself or on a line with
- an instruction.
-
- Near-code labels have different behavior depending on whether they are
- used in a procedure with the extended PROC syntax. When the extended PROC
- feature is used (which requires that .MODEL and a language must be
- specified), near labels are local to the procedure. This functionality is
- explained in Section 15.3.7, "Variable Scope."
-
- If the full segments are used or if the language argument is not supplied
- to the .MODEL directive, near labels are known throughout the module in
- which they occur. The same label name can be used in different modules as
- long as each label is only referenced by instructions in its own module.
- If a label must be referenced by instructions in another module, it must
- be given a unique name and declared with the PUBLIC and EXTRN directives,
- as described in Chapter 8, "Creating Programs from Multiple Modules."
-
- Examples
-
- cmp ax,5 ; Compare with 5
- ja bigger
- jb smaller
- . ; Instructions if AX = 5
- .
- .
- jmp done
- bigger: . ; Instructions if AX > 5
- .
- .
- jmp done
- smaller: . ; Instructions if AX < 5
- .
- .
- done:
-
-
- 6.4.2 Anonymous Labels
-
- The assembler provides a way to generate automatic labels for jump
- instructions. To define a label, use two at signs (@@) followed by a colon
- (:). To jump to the nearest preceding anonymous label, use @B (back) in
- the jump instruction's operand field; to jump to the nearest following
- anonymous label, use @F (forward) in the operand field.
-
- You can use two at signs (@@) to define any number of anonymous labels in
- your program. The items @B and @F always refer to the nearest occurrences
- of @@, so there is never any conflict between different anonymous labels.
-
- Anonymous labels are best used for conditionally executing a few lines of
- code. The advantage is that you do not need to continually think up new
- label names. The disadvantage is that they do not provide a meaningful
- name. You should mark major divisions of a program with actual named
- labels.
-
- The following example shows a typical sequence of code with a
- jump-to-label instruction:
-
- ; DX is 20, unless CX is less than -20, then make DX 30
- mov dx,20
- cmp cx,-20
- jge greatequ
- mov dx,30
- greatequ:
-
- Here are the same lines rewritten to use an anonymous label:
-
- ; DX is 20, unless CX is less than -20, then make DX 30
- mov dx,20
- cmp cx,-20
- jge @F
- mov dx,30
- @@:
-
-
- 6.4.3 Procedure Labels
-
- The easiest way to declare a procedure is to use the PROC and ENDP
- directives. The former declares the beginning of the procedure, and the
- latter declares the end.
-
- The PROC directive has the following syntax:
-
- label PROC [[NEAR|FAR]]
- statements
- RET [[constant]]
- label ENDP
-
- The label assigns a symbol to the procedure. The distance can be NEAR or
- FAR. Any RET instructions within the procedure automatically have the same
- distance (NEAR or FAR) as the procedure.
-
- The syntax shown here is always available. In addition, there is an
- extended PROC syntax available if you use .MODEL and specify a language.
- The extended PROC syntax is explained in Section 15.3.4, "Declaring
- Parameters with the PROC Directive."
-
- The ENDP directive labels the address where the procedure ends. Every
- procedure label must have a matching ENDP label to mark the end of the
- procedure. QuickAssembler generates an error message if it does not find
- an ENDP directive to match each PROC directive.
-
- When the PROC label definition is encountered, the assembler sets the
- label's value to the current value of the location counter and sets its
- type to NEAR or FAR. If the label has FAR type, the assembler also sets
- its segment value to that of the enclosing segment. If you have specified
- full segment definitions, the default distance is NEAR. If you are using
- simplified segment directives, the default distance is the distance
- associated with the declared memory model──that is, NEAR for small and
- compact models or FAR for medium, large, and huge models.
-
- The procedure label can be used in a CALL instruction to direct execution
- control to the first instruction of the procedure. Control can be
- transferred to a NEAR procedure label from any address in the same segment
- as the label. Control can be transferred to a FAR procedure label from an
- address in any segment.
-
- Procedure labels must be declared with the PUBLIC and EXTRN directives if
- they are located in one module but called from another module, as
- described in Chapter 8, "Creating Programs from Multiple Modules."
-
- Example
-
- call task ; Call procedure
- .
- .
- .
- task PROC NEAR ; Start of procedure
- .
- .
- .
- ret
- task ENDP ; End of procedure
-
-
- 6.4.4 Code Labels Defined with the LABEL Directive
-
- The LABEL directive provides an alternative method of defining code
- labels.
-
- Syntax
-
- name LABEL distance
-
- The name is the symbol name assigned to the label. The distance can be a
- type specifier, such as NEAR, FAR, or PROC. PROC means NEAR or FAR,
- depending on the default memory model, as described in Section 5.1.3,
- "Defining Basic Attributes of the Module." You can use the LABEL directive
- to define a second entry point into a procedure. FAR code labels can also
- be the destination of far jumps or of far calls that use the RETF
- instruction (see Section 15.3.2, "Defining Procedures").
-
- Example
-
- task PROC FAR ; Main entry point
- .
- .
- .
- task1 LABEL FAR ; Secondary entry point
- .
- .
- .
- ret
- task ENDP ; End of procedure
-
-
- 6.5 Defining and Initializing Data
-
- The data-definition directives enable you to allocate memory for data. At
- the same time, you can specify the initial values for the allocated data.
- Data can be specified as numbers, strings, or expressions that evaluate to
- constants. The assembler translates these constant values into binary
- bytes, words, or other units of data. The encoded data is written to the
- object file at assembly time.
-
-
- 6.5.1 Variables
-
- Variables consist of one or more named data objects of a specified size.
-
- Syntax
-
- [[name]] directive initializer [[,initializer]]...
-
- The name is the symbol name assigned to the variable. If no name is
- assigned, the data is allocated; but the starting address of the variable
- has no symbolic name.
-
- The size of the variable is determined by directive. The directives that
- can be used to define single-item data objects are listed below:
-
- Directive Meaning
- ──────────────────────────────────────────────────────────────────────────
- DB Defines byte
-
- DW Defines word (2 bytes)
-
- DD Defines doubleword (4 bytes)
-
- DQ Defines quadword (8 bytes)
-
- DT Defines 10-byte variable
-
-
- The optional initializer can be a constant, an expression that evaluates
- to a constant, or a question mark (?). The question mark is the symbol
- indicating that the value of the variable is undefined. You can define
- multiple values by using multiple initializers separated by commas, or by
- using the DUP operator, as explained in Section 6.5.2, "Arrays and
- Buffers."
-
- Simple data types can allocate memory for integers, strings, addresses, or
- real numbers.
-
-
- 6.5.1.1 Integer Variables
-
- When defining an integer variable, you can specify an initial value as an
- integer constant or as a constant expression. QuickAssembler generates an
- error if you specify an initial value too large for the specified
- variable.
-
- Integer values for all sizes except 10-byte variables are stored in binary
- form. They can be interpreted as either signed or unsigned numbers. For
- instance, the hexadecimal value 0FFCD can be interpreted either as the
- signed number -51 or the unsigned number 65,485.
-
- The processor cannot tell the difference between signed and unsigned
- numbers. Some instructions are designed specifically for signed numbers.
- It is the programmer's responsibility to decide whether a value is to be
- interpreted as signed or unsigned, and then to use the appropriate
- instructions to handle the value correctly.
-
- Figure 6.1 shows various integer storage formats.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 6.5.1.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The directives for defining integer variables are listed below with the
- sizes of integer they can define:
-
- Directive Size of Directive
- ──────────────────────────────────────────────────────────────────────────
- DB (bytes) Allocates unsigned numbers from 0 to 255 or signed
- numbers from -128 to 127.
-
- DW (words) Allocates unsigned numbers from 0 to 65,535 or signed
- numbers from -32,768 to 32,767. The bytes of a word
- integer are stored in the format shown in Figure 6.1.
- Note that in assembler listings and in most debuggers
- the bytes of a word are shown in the opposite
- order──high byte first──since this is the way most
- people think of numbers.
-
- Word values can be used directly in 8086-family
- instructions. They can also be loaded, used in
- calculations, and stored with 8087-family
- instructions.
-
- DD (doublewords) Allocates unsigned numbers from 0 to 4,294,967,295 or
- signed numbers from -2,147,483,648 to 2,147,483,647.
- The words of a doubleword integer are stored in the
- format shown in Figure 6.1.
-
- These 32-bit values (called long integers) can be
- loaded, used in calculations, and stored with
- 8087-family instructions. Some calculations can be
- done on these numbers directly with 16-bit 8086-family
- processors; others involve an indirect method of doing
- calculations on each word separately (see Chapter 14,
- "Doing Arithmetic and Bit Calculations").
-
- DQ (quadwords) Allocates 64-bit integers. The doublewords of a
- quadword integer are stored in the format shown in
- Figure 6.1.
-
- These values can be loaded, used in calculations, and
- stored with 8087-family instructions. You must write
- your own routines to use them with 16-bit 8086-family
- processors.
-
- DT Allocates 10-byte (80-bit) integers if the D radix
- specifier is used.
-
- By default, DT allocates packed binary coded decimal
- (BCD) numbers, as described in Section 6.5.1.2,
- "Binary Coded Decimal Variables." If you define binary
- 10-byte integers, you must write your own routines to
- use routines in calculations.
-
-
- Example
-
- integer DB 16 ; Initialize byte to 16
- expression DW 4*3 ; Initialize word to 12
- empty DQ ? ; Allocate uninitialized long integer
- DB 1,2,3,4,5,6 ; Initialize six unnamed bytes
- long DD 4294967295 ; Initialize double word to 4,294,967,295
- tb DT 2345d ; Initialize 10-byte binary integer
-
-
- 6.5.1.2 Binary Coded Decimal Variables
-
- Binary coded decimal (BCD) numbers provide a method of doing calculations
- on large numbers without rounding errors. They are sometimes used in
- financial applications. There are two kinds: packed and unpacked.
-
- Unpacked BCD numbers are stored one digit to a byte, with the value in the
- lower four bits. They can be defined with the DB directive. For example,
- an unpacked BCD number could be defined and initialized as shown below:
-
- unpackedr DB 1,5,8,2,5,2,9 ; Initialized to 9,252,851
- unpackedf DB 9,2,5,2,8,5,1 ; Initialized to 9,252,851
-
- Whether least-significant digits can come either first or last depends on
- how you write the calculation routines that handle the numbers.
- Calculations with unpacked BCD numbers are discussed in Section 14.5.1.
-
- Packed BCD numbers are stored two digits to a byte, with one digit in the
- lower four bits and one in the upper four bits. The leftmost bit holds the
- sign (0 for positive or 1 for negative).
-
- Packed BCD variables can be defined with the DT directive as shown below:
-
- packed DT 9252851 ; Allocate 9,252,851
-
- The 8087-family processors can do fast calculations with packed BCD
- numbers, as described in Chapter 17, "Calculating with a Math
- Coprocessor." The 8086-family processors can also do some calculations
- with packed BCD numbers, but the process is slower and more complicated.
- See Section 14.5.2 for details.
-
-
- 6.5.1.3 String Variables
-
- Strings are normally initialized with the DB directive. The initializing
- value is specified as a string constant. Strings can also be initialized
- by specifying each value in the string. For example, the following
- definitions are equivalent:
-
- version1 DB 97,98,99 ; As ASCII values
- version2 DB 'a','b','c' ; As characters
- version3 DB "abc" ; As a string
-
- One- and two-character strings can also be initialized with any of the
- other data-definition directives. The last (or only) character in the
- string is placed in the byte with the lowest address. Either 0 or the
- first character is placed in the next byte. The unused portion of such
- variables is filled with zeros.
-
- Examples
-
- function9 DB 'Hello',13,10,'$' ; Use with DOS INT 21h
- ; function 9
- asciiz DB "\ASM\TEST.ASM",0 ; Use as ASCIIZ string
-
- message DB "Enter file name: " ; Use with DOS INT 21h
- l_message EQU $-message ; function 40h
- a_message EQU OFFSET message
-
- str1 DB "ab" ; Stored as 61 62
- str2 DD "ab" ; Stored as 62 61 00 00
- str3 DD "a" ; Stored as 61 00 00 00
-
-
- 6.5.1.4 Real-Number Variables
-
- Real numbers must be stored in binary format. However, when initializing
- variables, you can specify decimal or hexadecimal constants and let the
- assembler automatically encode them into their binary equivalents.
- QuickAssembler can use two different binary formats for real numbers: IEEE
- or Microsoft Binary. You can specify the format by using directives (IEEE
- is the default).
-
- This section tells you how to initialize real-number variables, describes
- the two binary formats, and explains real-number encoding.
-
- Initializing and Allocating Real-Number Variables
-
- Real numbers can be defined by initializing them either with real-number
- constants or with encoded hexadecimal constants. The real-number
- designator (R) must follow numbers specified in encoded format.
-
- The directives for defining real numbers are listed below with the sizes
- of the numbers they can allocate:
-
- Directive Size
- ──────────────────────────────────────────────────────────────────────────
- DD Allocates short (32-bit) real numbers in either the
- IEEE or Microsoft Binary format.
-
- DQ Allocates long (64-bit) real numbers in either the
- IEEE or Microsoft Binary format.
-
- DT Allocates temporary or 10-byte (80-bit) real numbers.
- The format of these numbers is similar to the IEEE
- format. They are always encoded the same regardless of
- the real-number format. Their size is nonstandard and
- incompatible with most Microsoft high-level languages.
- Temporary-real format is provided for those who want
- to initialize real numbers in the format used
- internally by 8087-family processors.
-
-
- The 8086-family microprocessors do not have any instructions for handling
- real numbers. You must write your own routines, use a library that
- includes real-number calculation routines, or use a coprocessor. The
- 8087-family coprocessors can load real numbers in the IEEE format; they
- can also use the values in calculations and store the results back to
- memory, as explained in Chapter 17, "Calculating with a Math
- Coprocessor."
-
- Examples
-
- shrt DD 98.6 ; QuickAsm automatically encodes
- long DQ 5.391E-4 ; in current format
- ten_byte DT -7.31E7
- eshrt DD 87453333r ; 98.6 encoded in Microsoft
- ; Binary format
- elong DQ 3F41AA4C6F445B7Ar ; 5.391E-4 encoded in IEEE format
-
- The real-number designator (R) used to specify encoded numbers is
- explained in Section 6.1.3, "Real-Number Constants."
-
- Selecting a Real-Number Format
-
- QuickAssembler can encode four-byte and eight-byte real numbers in two
- different formats: IEEE and Microsoft Binary. Your choice depends on the
- type of program you are writing. The four primary alternatives are listed
- below:
-
- 1. If your program requires a coprocessor for calculations, you must use
- the IEEE format.
-
- 2. Most high-level languages use the IEEE format. If you are writing
- modules that will be called from such a language, your program should
- use the IEEE format. All versions of the C, FORTRAN, and Pascal
- compilers sold by Microsoft and IBM use the IEEE format.
-
- 3. If you are writing a module that will be called from early versions of
- Microsoft or IBM BASIC, your program should use the Microsoft Binary
- format. Versions that support only the Microsoft Binary format include:
-
- ■ Microsoft QuickBASIC through Version 2.01
-
- ■ Microsoft BASIC Compiler through Version 5.3
-
- ■ IBM BASIC Compiler through Version 2.0
-
- ■ Microsoft GW-BASIC(R) interpreter (all versions)
-
- ■ IBM BASICA interpreter (all versions)
-
- Microsoft QuickBASIC Version 3.0 supported both the Microsoft Binary
- and IEEE formats as options. Current and future versions of Microsoft
- QuickBASIC and the Microsoft and IBM BASIC compilers support only the
- IEEE format.
-
- 4. If you are creating a stand-alone program that does not use a
- coprocessor, you can choose either format. The IEEE format is better
- for overall compatibility with high-level languages. The Microsoft
- Binary format may be necessary for compatibility with existing source
- code.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE When you interface assembly-language modules with high-level
- languages, the real-number format only matters if you initialize
- real-number variables in the assembly module. If your assembly module does
- not use real numbers, or if all real numbers are initialized in the
- high-level-language module, the real-number format does not make any
- difference.
- ──────────────────────────────────────────────────────────────────────────
-
- By default, QuickAssembler assembles real-number data in the IEEE format.
- If you wish to use the Microsoft Binary format, you must put the .MSFLOAT
- directive at the start of your source file before initializing any
- real-number variables.
-
- Real-Number Encoding
-
- The IEEE format for encoding four- and eight-byte real numbers is
- illustrated in Figure 6.2. Although this figure places the
- most-significant bit first for illustration, low bytes actually appear
- first in memory.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 6.5.1.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The parts of the real numbers are described below:
-
- 1. Sign bit (0 for positive or 1 for negative) in the upper bit of the
- first byte.
-
- 2. Exponent in the next bits in sequence (8 bits for short real number or
- 11 bits for long real number).
-
- 3. All except the first set bit of mantissa in the remaining bits of the
- variable. Since the first significant bit is known to be set, it need
- not be actually stored. The length is 23 bits for short real numbers
- and 52 bits for long real numbers.
-
- The Microsoft Binary format for encoding real numbers is illustrated in
- Figure 6.3.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 6.5.1.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The three parts of real numbers are described below:
-
- 1. Biased exponent (8 bits) in the high-address byte. The bias is 81H for
- short real numbers and 401H for long real numbers.
-
- 2. Sign bit (0 for positive or 1 for negative) in the upper bit of the
- second-highest byte.
-
- 3. All except the first set bit of mantissa in the remaining 7 bits of the
- second-highest byte and in the remaining bytes of the variable. Since
- the first significant bit is known to be set, it need not be actually
- stored. The length is 23 bits for short real numbers and 55 bits for
- long real numbers.
-
- QuickAssembler also supports the 10-byte temporary-real format used
- internally by 8087-family coprocessors. This format is similar to IEEE
- format. The size is nonstandard and is not used by Microsoft compilers or
- interpreters. Since the coprocessors can load and automatically convert
- numbers in the more standard 4-byte and 8-byte formats, the 10-byte format
- is seldom used in assembly-language programming.
-
- The temporary-real format for encoding real numbers is illustrated in
- Figure 6.4.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 6.5.1.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The four parts of the real numbers are described below:
-
- 1. Sign bit (0 for positive or 1 for negative) in the upper bit of the
- first byte.
-
- 2. Exponent in the next bits in sequence (15 bits for 10-byte real).
-
- 3. The integer part of mantissa in the next bit in sequence (bit 63).
-
- 4. Remaining bits of mantissa in the remaining bits of the variable. The
- length is 63 bits.
-
- Notice that the 10-byte temporary-real format stores the integer part of
- the mantissa. This differs from the 4-byte and 8-byte formats, in which
- the integer part is implicit.
-
-
- 6.5.2 Arrays and Buffers
-
- Arrays, buffers, and other data structures consisting of multiple data
- objects of the same size can be defined with the DUP operator. This
- operator can be used with any of the data-definition directives described
- in this chapter.
-
- Syntax
-
- count DUP (initialvalue[[,initialvalue]]...)
-
- The count sets the number of times to define initialvalue. The initial
- value can be any expression that evaluates to an integer value, a
- character constant, or another DUP operator. It can also be the undefined
- symbol (?) if there is no initial value.
-
- Multiple initial values must be separated by commas. If multiple values
- are specified within the parentheses, the sequence of values is allocated
- count times. For example, the statement
-
- DB 5 DUP ("Text ")
-
- allocates the string "Text " five times for a total of 25 bytes.
-
- DUP operators can be nested up to 17 levels. The initial value (or values)
- must always be placed within parentheses.
-
- Examples
-
- array DD 10 DUP (1) ; 10 doublewords
- ; initialized to 1
-
- buffer DB 256 DUP (?) ; 256 byte buffer
-
- masks DB 20 DUP (040h,020h,04h,02h) ; 80 byte buffer
- ; with bit masks
- DB 32 DUP ("I am here ") ; 320 byte buffer with
- ; signature for debugging
- three_d DD 5 DUP (5 DUP (5 DUP (0))) ; 125 doublewords
- ; initialized to 0
-
- Note that QuickAssembler sometimes generates different object code when
- the DUP operator is used rather than when multiple values are given. For
- example, the statement
-
- test1 DB ?,?,?,?,? ; Indeterminate
-
- is "indeterminate." It causes QuickAssembler to write five zero-value
- bytes to the object file. The statement
-
- test2 DB 5 DUP (?) ; Undefined
-
- is "undefined." It causes QuickAssembler to increase the offset of the
- next record in the object file by five bytes. Therefore, an object file
- created with the first statement will be larger than one created with the
- second statement.
-
- In most cases, the distinction between indeterminate and undefined
- definitions is trivial. The linker adjusts the offsets so that the same
- executable file is generated in either case. However, the difference is
- significant in segments with the COMMON combine type. If COMMON segments
- in two modules contain definitions for the same variable, one with an
- indeterminate value and one with an explicit value, the actual value in
- the executable file varies depending on link order. If the module with the
- indeterminate value is linked last, the 0 initialized for it overrides the
- explicit value. You can prevent this by always using undefined rather than
- indeterminate values in COMMON segments. For example, use the first of the
- following statements:
-
- test3 DB 1 DUP (?) ; Undefined - doesn't initialize
- test4 DB ? ; Indeterminate - initializes 0
-
- If you use the undefined definition, the explicit value is always used in
- the executable file regardless of link order.
-
-
- 6.5.3 Labeling Variables
-
- The LABEL directive can be used to define a variable of a given size at a
- specified location. It is useful if you want to refer to the same data as
- variables of different sizes.
-
- Syntax
-
- name LABEL type
-
- The name is the symbol assigned to the variable, and type is the variable
- size. The type can be any one of the following type specifiers: BYTE,
- WORD, DWORD, QWORD, or TBYTE. It can also be the name of a previously
- defined structure.
-
- Examples
-
- warray LABEL WORD ; Access array as 50 words
- darray LABEL DWORD ; Access same array as 25 doublewords
- barray DB 100 DUP(?) ; Access same array as 100 bytes
-
-
- 6.5.4 Pointer Variables
-
- The assembler lets you explicitly allocate pointers. A pointer (address)
- variable is either two or four bytes in size; consequently, any word or
- doubleword data definition can create a pointer variable. However,
- declaring pointer variables explicitly gives more accurate debugging
- information to the environment.
-
- Pointer-variable definitions have the following form:
-
- symbol [[DW | DD]] type PTR initialvalue
-
- The type can consist of the name of a record, structure, or one of the
- standard types described in Section 6.3, "Using Type Specifiers."
-
- Example
-
- string DB "from swerve of shore to bend of bay", 0
- pstring DW BYTE PTR string ; Declares a near pointer to string.
- fpstring DD BYTE PTR string ; Declares a far pointer to string.
-
- In this example, near (two-byte) and far (four-byte) pointers are declared
- and initialized to the value of a null terminated string. This is the
- format used in C language, and the pointer variables in the example could
- be used in C functions that process strings.
-
- Using an explicit pointer declaration generates debugging information,
- causing the variable to be viewed as a pointer during debugging.
- Consequently, the environment properly interprets the variable when you
- enter it as a Watch expression. No special syntax is required.
-
- This use of PTR is distinct from the use of PTR to alter the type of a
- variable during an instruction. The assembler uses the context of the
- program to determine which way you are using the PTR keyword.
-
-
- 6.6 Setting the Location Counter
-
- As the assembler encounters labels and data declarations, it needs to know
- what address to assign. This function is fulfilled by the location
- counter, which indicates the offset address corresponding to the current
- line of source code. This value is generally equal to the value that IP
- will have at run time.
-
- The assembler increments the location counter as it processes each
- statement. However, you can set the location counter directly by using the
- ORG directive.
-
- Syntax
-
- ORG expression
-
- Subsequent code and data offsets begin at the new offset specified set by
- expression. The expression must resolve to a constant number. In other
- words, all symbols used in the expression must be known on the first pass
- of the assembler.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The value of the location counter, represented by the dollar sign
- ($), can be used in the expression, as described in Section 9.3, "Using
- the Location Counter."
- ──────────────────────────────────────────────────────────────────────────
-
- Example 1
-
- ; Labeling absolute addresses
-
- STUFF SEGMENT AT 0 ; Segment has constant value 0
- ORG 410h ; Offset has constant value 410h
- equipment LABEL WORD ; Value at 0000:0410 labeled "equipment"
- ORG 417h ; Offset has constant value 417h
- keyboard LABEL WORD ; Value at 0000:0417 labeled "keyboard"
- STUFF ENDS
-
- .CODE
- .
- .
- .
- ASSUME ds:STUFF ; Tell the assembler
- mov ax,STUFF ; Tell the processor
- mov ds,ax
-
- mov dx,equipment
- mov keyboard,ax
-
- Example 1 illustrates one way of assigning symbolic names to absolute
- addresses.
-
- Example 2
-
- ; Format for .COM files
-
- _TEXT SEGMENT
- ASSUME cs:_TEXT,ds:_TEXT,ss:_TEXT,es:_TEXT
- ORG 100h ; Skip 100h bytes of DOS header
-
- entry: jmp begin ; Jump over data
- variable DW ? ; Put more data here
- .
- begin: . ; First line of code
- . ; Put more code here
- _TEXT ENDS
- END entry
-
- Example 2 illustrates how the ORG directive is used to initialize the
- starting execution point in .COM files.
-
-
- 6.7 Aligning Data
-
- Some operations are more efficient when the variable used in the operation
- is lined up on a boundary of a particular size. The EVEN and ALIGN
- directives can be used to pad the object file so that the next variable is
- aligned on a specified boundary.
-
- Syntax 1
-
- EVEN
-
- Syntax 2
-
- ALIGN number
-
- The EVEN directive always aligns on the next even byte. The ALIGN
- directive aligns on the next byte that is a multiple of number. The number
- must be a power of 2. For example, use ALIGN 2 or EVEN to align on word
- boundaries, or use ALIGN 4 to align on doubleword boundaries.
-
- If the value of the location counter is not on the specified boundary when
- an ALIGN directive is encountered, the location counter is incremented to
- a value on the boundary. If the location counter is already on the
- boundary, the directive has no effect.
-
- When the assembler increments the location counter, it also pads the
- skipped memory locations by inserting certain values. If the segment is a
- data segment, the assembler always pads these locations with zeros. If the
- segment is a code segment, the assembler pads skipped memory locations
- with a two-byte no-op instruction (instruction that performed no
- operation) where possible:
-
- xchg bx,bx
-
- This instruction, which assembles as 87D8 hexadecimal, does nothing, but
- it executes faster than two NOP instructions. Where there is no room for
- the two-byte no-op, the assembler inserts the one-byte NOP instruction.
-
- The ALIGN and EVEN directives give no efficiency improvements on
- processors that have an 8-bit data bus (such as the 8088). These
- processors always fetch data one byte at a time, regardless of the
- alignment. However, using EVEN can speed certain operation on processors
- that have a 16-bit data bus (such as the 8086), since the processor can
- fetch a word if the data is word aligned, but must do two memory fetches
- if the data is not word aligned.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The ALIGN directive is a new feature of recent versions of Microsoft
- assemblers, starting with 5.0. In previous versions, data could be word
- aligned by using the EVEN directive, but other alignments could not be
- specified.
-
- The EVEN directive should not be used in segments with BYTE align type.
- Similarly, the number specified with the ALIGN directive should be no
- greater than the size of the align type of the current segment (thus
- ensuring that number is a divisor of the align type of the segment).
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- DOSSEG
- .MODEL small,c
- .STACK 100h
- .DATA
- .
- .
- .
- ALIGN 2 ; For faster data access
- stuff DW 66,124,573,99,75
- .
- .
- .
- ALIGN 2 ; For faster data access
- evenstuff DW ?,?,?,?,?
- .CODE
- start: mov ax,@data ; Load segment location
- mov ds,ax ; into DS
- mov es,ax ; and ES registers
-
- mov cx,5 ; Load count
- mov si,OFFSET stuff ; Point to source
- mov di,OFFSET evenstuff ; and destination
- ALIGN 2 ; Align for faster loop access
- mloop: lodsw ; Load a word
- inc ax ; Make it even by incrementing
- and ax,NOT 1 ; and turning off first bit
- stosw ; Store
- loop mloop ; Again
- .
- .
- .
-
- In this example, the words at stuff and evenstuff are forced to word
- boundaries. This makes access to the data faster with processors that have
- a 16-bit data bus. Without this alignment, the initial data might start on
- an odd boundary and the processor would have to fetch half of each word at
- a time.
-
- Similarly, the alignment in the code segment speeds up repeated access to
- the code at the start of the loop. The sample code sacrifices program size
- in order to achieve moderate improvements on the 8086 and 80286. There is
- no speed advantage on the 8088.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 7: Using Structures and Records
-
-
- QuickAssembler can define and use two kinds of multifield variables:
- structures and records.
-
- "Structures" are templates for data objects made up of smaller data
- objects. A structure can be used to define structure variables, which are
- made up of smaller variables called fields. Fields within a structure can
- be different sizes, and each can be accessed individually.
-
- "Records" are templates for data objects whose bits can be described as
- groups of bits called fields. A record can be used to define record
- variables. Each bit field in a record variable can be used separately in
- constant operands or expressions. The processor cannot access bits
- individually at run time, but bit fields can be used with logical bit
- instructions to change bits indirectly.
-
- This chapter describes structures and records and tells how to use them.
-
-
- 7.1 Structures
-
- A structure variable is a collection of data objects that can be accessed
- symbolically as a single data object. Objects within the structure can
- have different sizes and can be accessed symbolically.
-
- There are two steps in using structure variables:
-
- 1. Declare a structure type. A structure type is a template for data. It
- declares the sizes and, optionally, the initial values for objects in
- the structure. By itself the structure type does not define any data.
- The structure type is used by QuickAssembler during assembly but is not
- saved as part of the object file.
-
- 2. Define one or more variables having the structure type. For each
- variable defined, memory is allocated to the object file in the format
- declared by the structure type.
-
- The structure variable can then be used as an operand in assembler
- statements. The structure variable can be accessed as a whole by using the
- structure name, or individual fields can be accessed by using structure
- and field names.
-
-
- 7.1.1 Declaring Structure Types
-
- The STRUC and ENDS directives mark the beginning and end of a type
- declaration for a structure.
-
- Syntax
-
- name STRUC
- fielddeclarations
- name ENDS
-
- The name declares the name of the structure type. It must be unique. The
- fielddeclarations declare the fields of the structure. Any number of field
- declarations may be given. They must follow the form of data definitions
- described in Section 6.5, "Defining and Initializing Data." Default
- initial values may be declared individually or with the DUP operator.
-
- The names given to fields must be unique within the source file where they
- are declared. When variables are defined, the field names will represent
- the offset from the beginning of the structure to the corresponding field.
-
- When declaring strings in a structure type, make sure the initial values
- are long enough to accommodate the largest possible string. Strings
- smaller than the field size can be placed in the structure variable, but
- larger strings will be truncated.
-
- A structure declaration can contain both field declarations and comments.
- Conditional-assembly statements are allowed in structure declarations; no
- other kinds of statements are allowed. Since the STRUC directive is not
- allowed inside structure declarations, structures cannot be nested.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The ENDS directive that marks the end of a structure has the same
- mnemonic as the ENDS directive that marks the end of a segment. The
- assembler recognizes the meaning of the directive from context. Make sure
- each SEGMENT directive and each STRUC directive has its own ENDS
- directive.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- student STRUC ; Structure for student records
- id DW ? ; Field for identification #
- sname DB "Last, First Middle "
- scores DB 10 DUP (100) ; Field for 10 scores
- student ENDS
-
- Within the sample structure student, the fields id, sname, and scores have
- the offset values 0, 2, and 24, respectively.
-
-
- 7.1.2 Defining Structure Variables
-
- A structure variable is a variable with one or more fields of different
- sizes. The sizes and initial values of the fields are determined by the
- structure type with which the variable is defined.
-
- Syntax
-
- [[name]] structurename <[[initialvalue [[,initialvalue]]...]]>
-
- The name is the name assigned to the variable. If no name is given, the
- assembler allocates space for the variable, but does not give it a
- symbolic name. The structurename is the name of a structure type
- previously declared by using the STRUC and ENDS directives.
-
- An initialvalue can be given for each field in the structure. Its type
- must not be incompatible with the type of the corresponding field. The
- angle brackets (< >) are required even if no initial value is given. If
- initial values are given for more than one field, the values must be
- separated by commas.
-
- If the DUP operator (see Section 6.5.2, "Arrays and Buffers") is used to
- initialize multiple structure variables, only the angle brackets and
- initial values, if given, need to be enclosed in parentheses. For example,
- you can define an array of structure variables as shown below:
-
- war date 365 DUP (<,,1940>)
-
- You need not initialize all fields in a structure. If an initial value is
- left blank, the assembler automatically uses the default initial value of
- the field, which was originally determined by the structure type. If there
- is no default value, the field is undefined.
-
- Examples
-
- The following examples use the student type declared in the example in
- Section 7.1.1, "Declaring Structure Types":
-
- s1 student <> ; Uses default values of type
-
-
- s2 student <1467,"White, Robert D.",>
- ; Override default values of first two
- ; fields--use default value of third
-
- sarray student 100 DUP (<>) ; Declare 100 student variables
- ; with default initial values
-
- Note that you cannot initialize any structure field that has multiple
- values if this field was given a default initial value when the structure
- was declared. For example, assume the following structure declaration:
-
- stuff STRUC
- buffer DB 100 DUP (?) ; Can't override
- crlf DB 13,10 ; Can't override
- query DB 'Filename: ' ; String <= can override
- endmark DB 36 ; Can override
- stuff ENDS
-
- The buffer and crlf fields cannot be overridden by initial values in the
- structure definition because they have multiple values. The query field
- can be overridden as long as the overriding string is no longer than query
- (10 bytes). A longer string would generate an error. The endmark field can
- be overridden by any byte value.
-
-
- 7.1.3 Using Structure Operands
-
- Like other variables, structure variables can be accessed by name. Fields
- within structure variables can also be accessed by using the syntax shown
- below:
-
- Syntax
-
- variable.field
-
- The variable must be the name of a structure (or an operand that resolves
- to the address of a structure). The field must be the name of a field
- within that structure. The variable is separated from the field by a
- period. The period is discussed as a structure-field-name operator in
- Section 9.2.1.2.
-
- The address of a structure operand is the sum of the offsets of variable
- and field. The address is relative to the segment or group in which the
- variable is declared.
-
- Examples
-
- date STRUC ; Declare structure
- month DB ?
- day DB ?
- year DW ?
- date ENDS
-
- .DATA
- yesterday date <9,30,1987> ; Declare structure
- today date <10,1,1987> ; variables
- tomorrow date <10,2,1987>
-
- .CODE
- .
- .
- .
- mov al,yesterday.day ; Use structure variables
- mov ah,today.month ; as operands
- mov tomorrow.year,dx
- mov bx,OFFSET yesterday ; Load structure address
- mov ax,[bx].month ; Use as indirect operand
- .
- .
- .
-
-
- 7.2 Records
-
- A record variable is a byte or word variable in which specific bit fields
- can be accessed symbolically. Bit fields within the record can have
- different sizes.
-
- There are two steps in declaring record variables:
-
- 1. Declare a record type. A record type is a template for data. It
- declares the sizes and, optionally, the initial values for bit fields
- in the record. By itself, the record type does not define any data. The
- record type is used by Quick-Assembler during assembly but is not saved
- as part of the object file.
-
- 2. Define one or more variables having the record type. For each variable
- defined, memory is allocated to the object file in the format declared
- by the type.
-
- The record variable can then be used as an operand in assembler
- statements. The record variable can be accessed as a whole by using the
- record name, or individual fields can be specified by using the record
- name and a field name combined with the field-name operator. A record type
- can also be used as a constant (immediate data).
-
-
- 7.2.1 Declaring Record Types
-
- The RECORD directive declares a record type for an 8-bit or 16-bit record
- that contains one or more bit fields.
-
- Syntax
-
- recordname RECORD field [[,field]]...
-
- The recordname is the name of the record type to be used when creating the
- record. The field declares the name, width, and initial value for the
- field.
-
- The syntax for each field is shown below:
-
- Syntax
-
- fieldname:width[[=expression]]
-
- The fieldname is the name of a field in the record, width is the number of
- bits in the field, and expression is the initial (or default) value for
- the field.
-
- Any number of field combinations can be given for a record, as long as
- each is separated from its predecessor by a comma. The sum of the widths
- for all fields must not exceed 16 bits.
-
- The width must be a constant. If the total width of all declared fields is
- larger than eight bits, the assembler uses two bytes. Otherwise, only one
- byte is used.
-
- If expression is given, it declares the initial value for the field. An
- error message is generated if an initial value is too large for the width
- of its field. If the field is at least seven bits wide, you can use an
- ASCII character for expression. The expression must not contain a forward
- reference to any symbol.
-
- In all cases, the first field you declare goes into the most significant
- bits of the record. Successively declared fields are placed in the
- succeeding bits to the right. If the fields you declare do not total
- exactly 8 bits or exactly 16 bits, the entire record is shifted right so
- that the last bit of the last field is the lowest bit of the record.
- Unused bits in the high end of the record are initialized to 0.
-
- Example 1
-
- color RECORD blink:1,back:3,intense:1,fore:3
-
- Example 1 creates a byte record type color having four fields: blink,
- back, intense, and fore. The contents of the record type are shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 7.2.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Since no initial values are given, all bits are set to 0. Note that this
- is only a template maintained by the assembler. No data is created.
-
- Example 2
-
- cw RECORD r1:3=0,ic:1=0,rc:2=0,pc:2=3,r2:2=1,masks:6=63
-
- Example 2 creates a record type cw having six fields. Each record declared
- by using this type occupies 16 bits of memory. The bit diagram below shows
- the contents of the record type:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 7.2.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Default values are given for each field. They can be used when data is
- declared for the record.
-
-
- 7.2.2 Defining Record Variables
-
- A record variable is an 8-bit or 16-bit variable whose bits are divided
- into one or more fields.
-
- Syntax
-
- [[name]] recordname <[[initialvalue [[,initialvalue]]...]]>
-
- The name is the symbolic name of the variable. If no name is given, the
- assembler allocates space for the variable, but does not give it a
- symbolic name. The recordname is the name of a record type that was
- previously declared by using the RECORD directive.
-
- An initialvalue for each field in the record can be given as an integer, a
- character constant, or an expression that resolves to a value compatible
- with the size of the field. Angle brackets (< >) are required even if no
- initial value is given. If initial values for more than one field are
- given, the values must be separated by commas.
-
- If the DUP operator (see Section 6.5.2, "Arrays and Buffers") is used to
- initialize multiple record variables, only the angle brackets and initial
- values, if given, need to be enclosed in parentheses. For example, you can
- define an array of record variables as shown below:
-
- xmas color 50 DUP (<1,2,0,4>)
-
- You do not have to initialize all fields in a record. If an initial value
- is left blank, the assembler automatically uses the default initial value
- of the field. This is declared by the record type. If there is no default
- value, each bit in the field is cleared.
-
- Sections 7.2.3, "Using Record Operands and Record Variables," and 7.2.4,
- "Record Operators," illustrate ways to use record data after it has been
- declared.
-
- Example 1
-
- color RECORD blink:1,back:3,intense:1,fore:3 ; Record declaration
- warning color <1,0,1,4> ; Record definition
-
- The definition above creates a variable named warning whose type is given
- by the record type color. The initial values of the fields in the variable
- are set to the values given in the record definition. The initial values
- would override the default record values, had any been given in the
- declaration. The contents of the record variable are shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 7.2.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Example 2
-
- color RECORD blink:1,back:3,intense:1,fore:3 ; Record declaration
- colors color 16 DUP (<>) ; Record declaration
-
- Example 2 creates an array named colors containing 16 variables of type
- color. Since no initial values are given in either the declaration or the
- definition, the variables have undefined (0) values.
-
- Example 3
-
- cw RECORD r1:3=0,ic:1=0,rc:2=0,pc:2=3,r2:2=1,masks:6=63
- newcw cw <,,2,,,>
-
- Example 3 creates a variable named newcw with type cw. The default values
- set in the type declaration are used for all fields except the rc field.
- This field is set to 2. The contents of the variable are shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 7.2.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 7.2.3 Using Record Operands and Record Variables
-
- A record operand refers to the value of a record type. It should not be
- confused with a record variable. A record operand is a constant; a record
- variable is a value stored in memory. A record operand can be used with
- the following syntax:
-
- Syntax
-
- recordname <[[value[[,value]]...]]>
-
- The recordname must be the name of a record type declared in the source
- file. The optional value is the value of a field in the record. If more
- than one value is given, each value must be separated by a comma. Values
- can include expressions or symbols that evaluate to constants. The
- enclosing angle brackets (<>) are required, even if no value is given. If
- no value for a field is given, the default value for that field is used.
-
- Example
-
- .DATA
- color RECORD blink:1,back:3,intense:1,fore:3 ; Record declaration
- window color <0,6,1,6> ; Record definition
-
- .CODE
- .
- .
- .
- mov ah,color <0,3,0,2> ; Load record operand
- ; (constant value 32h
- mov bh,window ; Load record variable
- ; (memory value 6Eh)
-
- In this example, the record operand color <0,3,0,2> and the record
- variable warning are loaded into registers. The contents of the values are
- shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 7.2.3 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 7.2.4 Record Operators
-
- The WIDTH and MASK operators are used exclusively with records to return
- constant values representing different aspects of previously declared
- records.
-
-
- 7.2.4.1 The MASK Operator
-
- The MASK operator returns a bit mask for the bit positions in a record
- occupied by the given record field. A bit in the mask contains a 1 if that
- bit corresponds to a field bit. All other bits contain 0.
-
- Syntax
-
- MASK {recordfieldname | record}
-
- The recordfieldname may be the name of any field in a previously defined
- record. The record may be the name of any previously defined record. The
- NOT operator is sometimes used with the MASK operator to reverse the bits
- of a mask.
-
- Example
-
- .DATA
- color RECORD blink:1,back:3,intense:1,fore:3
- message color <0,5,1,1>
- .CODE
- .
- .
- .
- mov ah,message ; Load initial 0101 1001
- and ah,NOT MASK back ; Turn off AND 1000 1111
- ; "back" ---------
- ; 0000 1001
- or ah,MASK blink ; Turn on OR 1000 0000
- ; "blink" ---------
- ; 1000 1001
- xor ah,MASK intense ; Toggle XOR 0000 1000
- ; "intense" ---------
- ; 1000 0001
-
-
- 7.2.4.2 The WIDTH Operator
-
- The WIDTH operator returns the width (in bits) of a record or record
- field.
-
- Syntax
-
- WIDTH {recordfieldname | record}
-
- The recordfieldname may be the name of any field defined in any record.
- The record may be the name of any defined record.
-
- Note that the width of a field is the number of bits assigned for that
- field; the value of the field is the starting position (from the right) of
- the field.
-
- Examples
-
- .DATA
- color RECORD blink:1,back:3,intense:1,fore:3
-
- wblink EQU WIDTH blink ; "wblink" = 1 "blink" = 7
- wback EQU WIDTH back ; "wback" = 3 "back" = 4
- wintense EQU WIDTH intense ; "wintense" = 1 "intense" = 3
- wfore EQU WIDTH fore ; "wfore" = 3 "fore" = 0
- wcolor EQU WIDTH color ; "wcolor" = 8
-
- prompt color <1,5,1,1>
-
- .CODE
- .
- .
- .
- IF (WIDTH color) GE 8 ; If color is 16 bit, load
- mov ax,prompt ; into 16-bit register
- ELSE ; else
- mov al,prompt ; load into low 8-bit register
- xor ah,ah ; and clear high 8-bit register
- ENDIF
-
-
- 7.2.5 Using Record-Field Operands
-
- Record-field operands represent the location of a field in its
- corresponding record. The operand evaluates to the bit position of the
- low-order bit in the field and can be used as a constant operand. The
- field name must be from a previously declared record.
-
- Record-field operands are often used with the WIDTH and MASK operators, as
- described in Sections 7.2.4.1 and 7.2.4.2.
-
- Example
-
- .DATA
- color RECORD blink:1,back:3,intense:1,fore:3 ; Record declaration
- cursor color <1,5,1,1> ; Record definition
- .CODE
- .
- .
- .
-
- ; Rotate "back" of "cursor" without changing other values
-
- mov al,cursor ; Load value from memory
- mov ah,al ; Save a copy for work 1101 1001=
- and al,NOT MASK back ; Mask out old bits AND 1000 1111=
- ; to save old cursor ---------
- ; 1000 1001=
- mov cl,back ; Load bit position
- shr ah,cl ; Shift to right 0000 1101=
- inc ah ; Increment 0000 1110=
-
- shl ah,cl ; Shift left again 1110 0000=
- and ah,MASK back ; Mask off extra bits AND 0111 0000=
- ; to get new cursor ---------
- ; 0110 0000
- or ah,al ; Combine old and new OR 1000 1001
- ; ---------
- mov cursor,ah ; Write back to memory 1110 1001
-
- This example illustrates several ways in which record fields can be used
- as operands and in expressions.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 8: Creating Programs from Multiple Modules
-
-
- Most medium and large assembly-language programs are created from several
- source files or modules. When several modules are used, the scope of
- symbols becomes important. This chapter discusses the scope of symbols and
- explains how to declare global symbols that can be accessed from any
- module. It also tells you how to specify a module that will be accessed
- from a library.
-
- Symbols, such as labels and variable names, can be either local or global
- in scope. By default, all symbols are local; they are specific to the
- source file in which they are defined. Symbols must be declared global if
- they must be accessed from modules other than the one in which they are
- defined.
-
- To declare symbols global, they must be declared public in the source
- module in which they are defined. They must also be declared external in
- any module that must access the symbol. If the symbol represents
- uninitialized data, it can be declared communal──meaning that the symbol
- is both public and external. The PUBLIC, EXTRN, and COMM directives are
- used to declare symbols public, external, and communal, respectively.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The term "local" often has a different meaning in assembly language
- than in many high-level languages. Local symbols in compiled languages are
- symbols that are known only within a procedure (called a function,
- routine, subprogram, or subroutine, depending on the language). You can
- use QuickAssembler to generate these kinds of variables, as explained in
- Section 15.3.6, "Creating Locals Automatically."
-
- By default, the assembler converts all lowercase letters in names declared
- with the PUBLIC, EXTRN, and COMM directives to uppercase letters before
- copying the name to the object file. To preserve lowercase names in public
- symbols, choose Preserve Case or Preserve Extrn from the Assembler Flags
- dialog box, or assemble with /Cx or /Cl on the QCL command line. This
- should be done when preparing assembler modules to be linked with modules
- from C and other case-sensitive languages.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 8.1 Declaring Symbols Public
-
- The PUBLIC directive is used to declare symbols public so that they can be
- accessed from other modules. If a symbol is not declared public, the
- symbol name is not written to the object file. The symbol has the value of
- its offset address during assembly, but the name and address are not
- available to the linker.
-
- If the symbol is declared public, its name is associated with its offset
- address in the object file. During linking, symbols in different
- modules──but with the same name──are resolved to a single address.
-
- Public symbol names are also used by some symbolic debuggers (such as
- SYMDEB) to associate addresses with symbols.
-
- Syntax
-
- PUBLIC declaration [[,declaration]]...
-
- Each declaration has the following syntax:
-
- [[lang]] name
-
- The optional lang field contains a language specifier that overrides the
- language specified by the .MODEL directive. With this statement, the
- language specifier determines naming conventions for the variable that it
- precedes. The specifier can be C, FORTRAN, Pascal, or BASIC. The C naming
- convention prefixes each variable with an underscore (_); the other
- conventions do not. If you specify lang with the .MODEL directive, all
- procedures are automatically public. However, you must use the PUBLIC
- directive for any data that you want to access from other modules.
-
- Note that using the C type specifier does not preserve case. You must
- choose one of the assembler flags or options that preserve case.
-
- The name must be the name of a variable, label, or numeric equate defined
- within the current source file. PUBLIC declarations can be placed anywhere
- in the source file. Equate names, if given, can only represent one-byte or
- two-byte integer or string values. Text macros (or text equates) cannot be
- declared public.
-
- Note that although absolute symbols can be declared public, aliases for
- public symbols may cause errors. For example, the following statements are
- illegal:
-
- PUBLIC lines ; Declare absolute symbol public
- lines EQU rows ; Declare alias for lines
- rows EQU 25 ; Illegal - Assign value to alias
-
- Example
-
- .MODEL small,c
- PUBLIC true,status,first,clear
- true EQU -1 ; Public constant
- .DATA
- status DB 1 ; Public variable
- .CODE
- .
- .
- .
- first LABEL FAR ; Public label
- clear PROC ; Procedure names are automatically public
- . ; with .MODEL model, lang
- .
- .
- clear ENDP
-
-
- 8.2 Declaring Symbols External
-
- If a symbol undeclared in a module must be accessed by instructions in
- that module, it must be declared with the EXTRN directive.
-
- This directive tells the assembler not to generate an error, even though
- the symbol is not in the current module. The assembler assumes that the
- symbol occurs in another module. However, the symbol must actually exist
- and must be declared public in some module. Otherwise, the linker
- generates an error.
-
- Syntax
-
- EXTRN declaration [[,declaration]]...
-
- Each declaration has the following syntax:
-
- [[lang]]name:type
-
- The optional lang field contains a language specifier that overrides the
- language specified by the .MODEL directive. With this statement, the
- language specifier determines naming conventions for the variable that it
- precedes. The specifier can be C, FORTRAN, Pascal, or BASIC. The C naming
- convention prefixes each variable with an underscore (_); the other
- conventions do not.
-
- Note that using the C type specifier does not preserve case. You must
- choose one of the assembler flags or options that preserve case.
-
- The EXTRN directive defines an external variable, label, or symbol of the
- specified name and type. The type must match the type given to the item in
- its actual definition in some other module. It can be any one of the
- following:
-
- Description Types
- ──────────────────────────────────────────────────────────────────────────
- Distance specifier NEAR, FAR, or PROC
-
- Size specifier BYTE, WORD, DWORD, QWORD, or TBYTE
-
- Absolute ABS
-
-
- The ABS type is for symbols that represent constant numbers, such as
- equates declared with the EQU and = directives (see Section 11.1, "Using
- Equates").
-
- The PROC type represents the default type for a procedure. For programs
- that use simplified segment directives, the type of an external symbol
- declared with PROC will be NEAR for small or compact model, or FAR for
- medium, large, or huge model. Section 5.1.3, "Defining Basic Attributes
- of the Model," tells you how to declare the memory model using the .MODEL
- directive. If full segment definitions are used, the default type
- represented by PROC is always NEAR.
-
- Although the actual address of an external symbol is not determined until
- link time, the assembler assumes a default segment for the item, based on
- where the EXTRN directive is placed in the source code. Placement of EXTRN
- directives should follow these rules:
-
- ■ NEAR code labels (such as procedures) must be declared in the code
- segment from which they are accessed.
-
- ■ FAR code labels can be declared anywhere in the source code. It may be
- convenient to declare them in the code segment from which they are
- accessed if the label may be FAR in one context or NEAR in another.
-
- ■ Data must be declared in the segment in which it occurs. This may
- require that you define a dummy data segment for the external
- declaration.
-
- ■ Absolute symbols can be declared anywhere in the source code.
-
- Example 1
-
- EXTRN max:ABS,act:FAR ; Constant or FAR label anywhere
- DOSSEG
- .MODEL small,c
- .STACK 100h
- .DATA
- EXTRN nvar:BYTE ; NEAR variable in near data
- .FARDATA
- EXTRN fvar:WORD ; FAR variable in far data
-
- .CODE
- .STARTUP
- EXTRN task:PROC ; PROC or NEAR in near code
- ASSUME es:SEG fvar ; Tell assembler
- mov ax,SEG fvar ; Tell processor that ES
- mov es,ax ; has far data segment
-
- .
- .
- .
- mov ah,nvar ; Load external NEAR variable
- mov bx,fvar ; Load external FAR variable
- mov cx,max ; Load external constant
- call task ; Call procedure (NEAR or FAR)
- jmp act ; Jump to FAR label
- END
-
- The example above shows how each type of external symbol could be declared
- and used in a small-model program that uses simplified segment directives.
- Notice the use of the PROC type specifier to make the external-procedure
- memory model independent. The jump and its external declaration are
- written so that they will be FAR regardless of the memory model. Using
- these techniques, you can change the memory model without breaking code.
-
- Example 2
-
- EXTRN max:ABS,act:FAR ; Constant or FAR label anywhere
- STACK SEGMENT PARA STACK 'STACK'
- DB 100h DUP (?)
- STACK ENDS
- _DATA SEGMENT WORD PUBLIC 'DATA'
- EXTRN nvar:BYTE ; NEAR variable in near data
- _DATA ENDS
- FAR_DATA SEGMENT PARA 'FAR_DATA'
- EXTRN fvar:WORD ; FAR variable in far data
- FAR_DATA ENDS
-
- DGROUP GROUP _DATA,STACK
- _TEXT SEGMENT BYTE PUBLIC 'CODE'
- EXTRN task:NEAR ; NEAR procedure in near code
- ASSUME cs:_TEXT,ds:DGROUP,ss:STACK
- start: mov ax,DGROUP ; Load segment
- mov ds,ax ; into DS
- ASSUME es:SEG fvar ; Tell assembler
- mov ax,SEG fvar ; Tell processor that ES
- mov es,ax ; has far data segment
- .
- .
- .
- mov ah,nvar ; Load external NEAR variable
- mov bx,fvar ; Load external FAR variable
- mov cx,max ; Load external constant
- call task ; Call NEAR procedure
- jmp act ; Jump to FAR label
- _TEXT ENDS
- END start
-
- Example 2 shows a fragment similar to the one in Example 1, but with full
- segment definitions. Notice that the types of code labels must be declared
- specifically. If you wanted to change the memory model, you would have to
- specifically change each external declaration and each call or jump.
-
-
- 8.3 Using Multiple Modules
-
- The following source files illustrate a program that uses public and
- external declarations to access instruction labels. The program consists
- of two modules called hello and display.
-
- The hello module is the program's initializing module. Execution starts at
- the instruction labeled start in the hello module. After initializing the
- data segment, the program calls the procedure display in the display
- module, where a DOS call is used to display a message on the screen.
- Execution then returns to the address after the call in the hello module.
-
- The hello module is shown below:
-
- TITLE hello
- DOSSEG
- .MODEL small,c
- .STACK 256
-
- .DATA
- PUBLIC message, lmessage
- message DB "Hello, world.",13,10
- lmessage EQU $ - message
-
- .CODE
- EXTRN display:PROC ; Declare in near code segment
- .STARTUP
- call display ; Call other module
- mov ax,04C00h ; Terminate with exit code 0
- int 21h ; Call DOS
- END
-
- The display module is shown below:
-
- TITLE display
- EXTRN lmessage:ABS ; Declare anywhere
- .MODEL small
- .DATA
- EXTRN message:BYTE ; Declare in near data segment
-
- .CODE
- display PROC
- mov bx,1 ; File handle for standard output
- mov cx,lmessage ; Message length
- mov dx,OFFSET message ; Message address
- mov ah,40h ; Write function
- int 21h ; Call DOS
- ret
-
- display ENDP
- END
-
- The sample program is a variation of the HELLO.ASM program used in the
- examples in Chapter 4, "Writing Stand-Alone Assembly Programs," except
- that it uses an external procedure to display to the screen. Notice that
- all symbols defined in one module but used in another are declared PUBLIC
- in the defining module and declared EXTRN in the using module.
-
- For instance, message and lmessage are declared PUBLIC in the program
- HELLO.ASM and declared EXTRN in DISPLAY.ASM. The procedure display is
- declared EXTRN in HELLO.ASM. The symbol display is automatically public in
- the simplified segment version, but you would have to specifically declare
- it PUBLIC if you used full segments.
-
- To create an executable file for these modules, you can add both files to
- the environment's Program List dialog box. You can also assemble the
- modules with the following command line:
-
- QCL hello.asm display.asm
-
- The output is placed in the executable file HELLO.EXE.
-
- For each source module, QuickAssembler writes a module name to the object
- file. The module name is used by some debuggers and by the linker when it
- displays error messages. With QuickAssembler, the module name is always
- the base name of the source module file.
-
- For compatibility, QuickAssembler recognizes the NAME directive. However,
- NAME has no effect. Arguments to the directive are ignored.
-
-
- 8.4 Declaring Symbols Communal
-
- Communal variables are uninitialized variables that are both public and
- external. They are often declared in include files.
-
- If a variable must be used by several assembly routines, you can declare
- the variable communal in an include file, and then include the file in
- each of the assembly routines. Although the variable is declared in each
- source module, it exists at only one address. Using a communal variable in
- an include file and including it in several source modules is an
- alternative to defining the variable and declaring it public in one source
- module and then declaring it external in other modules.
-
- If a variable is declared communal in one module and public in another,
- the public declaration takes precedence and the communal declaration has
- the same effect as an external declaration.
-
- Syntax
-
- COMM definition[[,definition]]...
-
- Each definition has the following syntax:
-
- [[NEAR | FAR]] [[lang]] label:size[[:count]]
-
- A communal variable can be NEAR or FAR. If neither is specified, the type
- will be that of the default memory model. If you use simplified segment
- directives, the default type is NEAR for small and medium models, or FAR
- for compact, large, and huge models. If you use full segment definitions,
- the default type is NEAR.
-
- The optional lang field can be C, BASIC, FORTRAN, or Pascal. The use of
- the C keyword turns on the C naming convention──the assembler prefixes the
- name of the variable with an underscore (_). The use of any of the other
- language types turns off the C naming convention, even if you specified C
- with the .MODEL directive. Note that the use of C does not preserve case.
- You must choose one of the assembler flags or options that preserve case.
-
- The label is the name of the variable. The size can be BYTE, WORD, DWORD,
- QWORD, TBYTE, or the name of a structure. The count is the number of
- elements. If no count is given, one element is assumed. Multiple variables
- can be defined with one COMM statement by separating each definition with
- a comma.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE C variables declared outside functions (except static variables) are
- communal unless explicitly initialized; they are the same as
- assembly-language communal variables. If you are writing assembly-language
- modules for C, you can declare the same communal variables in C include
- files and in QuickAssembler include files.
- ──────────────────────────────────────────────────────────────────────────
-
- QuickAssembler cannot tell whether a communal variable has been used in
- another module. Allocation of communal variables is handled by LINK. As a
- result, communal variables have the following limitations that other
- variables declared in assembly language do not have:
-
- ■ Communal variables cannot be initialized. Under DOS, initial values are
- not guaranteed to be 0 or any other value. The variables can be used
- for data, such as file buffers, that is not given a value until run
- time.
-
- ■ Communal variables are not guaranteed to be allocated in the sequence
- in which they are declared. Assembly-language techniques that depend on
- the sequence and position in which data is defined should not be used
- with communal variables. For example, the following statements do not
- work:
-
-
- COMM wbuffer:WORD:128
- lbuffer EQU $ - buffer ; "lbuffer" won't have desired val
-
- bbuffer LABEL BYTE ; "bbuffer" won't have desired add
- COMM wbuffer:WORD:40
-
- ■ If a communal variable references a variable that is allocated and
- declared public inside a module, the variable has the segment of the
- allocated instance. If all references to the variable are communal, the
- variable will be placed in one of the segments described below.
-
- Near communal variables are placed in a segment called c_common, which
- is part of DGROUP. This group is created and initialized automatically
- if you use simplified segment directives. If you use full segment
- definitions, you must create a group called DGROUP and use the ASSUME
- directive to associate it with the DS register.
-
- Far communal variables are placed in a segment called FAR_BSS. This
- segment has combine type private and class type 'FAR_BSS'. This means
- that multiple segments with the same name can be created. Such segments
- cannot be accessed by name. They must be initialized indirectly using
- the SEG operator. For example, if a far communal variable (with word
- size) is called comvar, its segment can be initialized with the
- following lines:
-
- ASSUME ds:SEG comvar ; Tell the assembler
- mov ax,SEG comvar ; Tell the processor
- mov ds,ax
- mov bx,comvar ; Use the variable
-
- Example 1
-
- .DATA
- COMM temp:BYTE:128
-
- ASCIIZ MACRO address ;; Name of address for string
- mov temp,128 ;; Insert maximum length
- mov dx,OFFSET temp ;; Address of string buffer
- mov ah,0Ah ;; Get string
- int 21h
- mov dl,temp[1] ;; Get length of string
- xor dh,dh
- mov bx,dx
- mov temp[bx+2],0 ;; Overwrite CR with null
- address EQU OFFSET temp+2
- ENDM
-
- Example 1 shows an include file that declares a buffer for temporary data.
- The buffer is then used in a macro in the same include file. An example of
- how the macro could be used in a source file is shown below:
-
- DOSSEG
- .MODEL small,c
- INCLUDE communal.inc
- .STACK
- .DATA
- message DB "Enter file name: $"
- .CODE
- .STARTUP
- .
- .
- .
-
- mov dx,OFFSET message ; Load offset of file prompt
- mov ah,09h ; Display prompt
- int 21h
-
- ASCIIZ place ; Get file name and
- ; return address as "place"
-
- mov al,00000010b ; Load access code
- mov dx,place ; Load address of ASCIIZ string
- mov ah,3Dh ; Open the file
- int 21h
- .
- .
- .
-
- Note that once the macro is written, the user does not need to know the
- name of the temporary buffer or how it is used in the macro.
-
- Example 2
-
- date STRUC
-
- month DB ?
- day DB ?
- year DB ?
-
- date ENDS
-
- .DATA
- COMM today:date
- .
- .
- .
-
- The example above uses the COMM directive to make the structure variable
- today a communal variable.
-
-
- 8.5 Specifying Library Files
-
- The INCLUDELIB directive instructs the linker to link with a specified
- library file. If you are writing a program that calls library routines,
- you can use this directive to specify the library file in the assembly
- source file rather than in the LINK command line.
-
- Syntax
-
- INCLUDELIB libraryname
-
- The libraryname is written to the comment record of the object file. The
- Intel title for this record is COMENT. At link time, the linker reads this
- record and links with the specified library file.
-
- The libraryname must be a file name rather than a complete file
- specification. If you do not specify an extension, the default extension
- .LIB is assumed. LINK searches directories for the library file in the
- following order:
-
- 1. The current directory
-
- 2. Any directories given in the library field of the LINK command line
-
- 3. Any directories listed in the LIB environment variable
-
- Example
-
- INCLUDELIB graphics
-
- This statement passes a message from QuickAssembler telling LINK to use
- library routines from the file GRAPHICS.LIB. If this statement is included
- in a source file called DRAW.ASM, the program might be linked with the
- following command line:
-
- LINK draw;
-
- Without the INCLUDELIB directive, the program would have to be linked with
- the following command line:
-
- LINK draw,,,graphics;
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 9: Using Operands and Expressions
-
-
- Operands are the arguments that define values to be acted on by
- instructions or directives. Operands can be constants, variables,
- expressions, or keywords, depending on the instruction or directive and
- the context of the statement.
-
- A common type of operand is an expression. An expression consists of
- several operands that are combined to describe a value or memory location.
- Operators indicate the operations to be performed when combining the
- operands of an expression.
-
- Expressions are evaluated at assembly time. By using expressions, you can
- instruct the assembler to calculate values that would be difficult or
- inconvenient to calculate when you are writing source code.
-
- This chapter discusses operands, expressions, and operators as they are
- evaluated at assembly time. See Section 2.6, "Addressing Modes," for a
- discussion of the addressing modes that can be used to calculate operand
- values at run time. This chapter also discusses the location-counter
- operand, forward references, and strong typing of operands.
-
-
- 9.1 Using Operands with Directives
-
- Each directive requires a specific type of operand. Most directives take
- string or numeric constants, or symbols or expressions that evaluate to
- such constants.
-
- The type of operand varies for each directive, but the operand must always
- evaluate to a value that is known at assembly time. This differs from
- instructions, whose operands may not be known at assembly time and may
- vary at run time. Operands used with instructions are discussed in Section
- 2.6, "Addressing Modes."
-
- Some directives, such as those used in data declarations, accept labels or
- variables as operands. When a symbol that refers to a memory location is
- used as an operand to a directive, the symbol represents the address of
- the symbol rather than its contents. This is because the contents may
- change at run time and are therefore not known at assembly time.
-
- Example 1
-
- ORG 100h ; Set address to 100h
- var DB 10h ; Address of "var" is 100h
- ; Value of "var" is 10h
- pvar DW var ; Address of "pvar" is 101h
- ; Value of "pvar" is
- ; address of "var" (100h)
-
- In Example 1, the operand of the DW directive in the third statement
- represents the address of var (100h) rather than its contents (10h). The
- address is relative to the start of the segment in which var is defined.
-
- Example 2
-
- TITLE doit ; String
- _TEXT SEGMENT BYTE PUBLIC 'CODE' ; Key words
- INCLUDE \include\bios.inc ; Pathname
- .RADIX 16 ; Numeric constant
- tst DW a / b ; Numeric expression
- PAGE + ; Special character
- sum EQU x * y ; Numeric expression
- here LABEL WORD ; Type specifier
-
- Example 2 illustrates the different kinds of values that can be used as
- directive operands.
-
-
- 9.2 Using Operators
-
- The assembler provides a variety of operators for combining, comparing,
- changing, or analyzing operands. Some operators work with integer
- constants, some with memory values, and some with both. Operators cannot
- be used with floating-point constants since QuickAssembler does not
- recognize real numbers in expressions.
-
- It is important to understand the difference between operators and
- instructions. Operators handle calculations of constant values that are
- known at assembly time. Instructions handle calculations of values that
- may not be known until run time. For example, the addition operator (+)
- handles assembly-time addition, while the ADD and ADC instructions handle
- run-time addition.
-
- This section describes the different kinds of operators used in
- assembly-language statements and gives examples of expressions formed with
- them. In addition to the operators described in this chapter, you can use
- the DUP operator (Section 6.5.2, "Arrays and Buffers"), the record
- operators (Section 7.2.4, "Record Operators"), and the macro operators
- (Section 11.4, "Using Macro Operators").
-
-
- 9.2.1 Calculation Operators
-
- QuickAssembler provides the common arithmetic operators as well as several
- other operators for adding, shifting, or doing bit manipulations. The
- sections below describe operators that can be used for doing numeric
- calculations.
-
-
- 9.2.1.1 Arithmetic Operators
-
- QuickAssembler recognizes a variety of arithmetic operators for common
- mathematical operations. Table 9.1 lists the arithmetic operators.
-
- Table 9.1 Arithmetic Operators
-
- Operator Syntax Meaning
- ──────────────────────────────────────────────────────────────────────────
- + +expression Positive (unary)
-
- - -expression Negative (unary)
-
- * expression1 * expression2 Multiplication
-
- / expression1 / expression2 Integer division
-
- MOD expression1 MOD expression2 Remainder (modulus)
-
- + expression1 + expression2 Addition
-
- - expression1 - expression2 Subtraction
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- For all arithmetic operators except the addition operator (+) and the
- subtraction operator (-), the expressions operated on must be integer
- constants.
-
- The addition and subtraction operators can be used to add or subtract an
- integer constant and a memory operand. The result can be used as a memory
- operand.
-
- The subtraction operator can also be used to subtract one memory operand
- from another, but only if the operands refer to locations within the same
- segment. The result will be a constant, not a memory operand.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The unary plus and minus operators (used to designate positive or
- negative numbers) are not the same as the binary plus and minus operators
- (used to designate addition or subtraction). The unary plus and minus
- operators have a higher level of precedence, as described in Section
- 9.2.5, "Operator Precedence."
- ──────────────────────────────────────────────────────────────────────────
-
- Example 1
-
- intgr = 14 * 3 ; = 42
- intgr = intgr / 4 ; 42 / 4 = 10
- intgr = intgr MOD 4 ; 10 mod 4 = 2
- intgr = intgr + 4 ; 2 + 4 = 6
- intgr = intgr - 3 ; 6 - 3 = 3
- intgr = -intgr - 8 ; -3 - 8 = -11
- intgr = -intgr - intgr ; 11 - -11 = 22
-
- Example 1 illustrates arithmetic operators used in integer expressions.
-
- Example 2
-
- ORG 100h
- a DB ? ; Address is 100h
- b DB ? ; Address is 101h
- mem1 EQU a + 5 ; mem1 = 100h + 5 = 105h
- mem2 EQU a - 5 ; mem2 = 100h - 5 = 0FBh
- const EQU b - a ; const = 101h - 100h = 1
-
- Example 2 illustrates arithmetic operators used in memory expressions.
-
-
- 9.2.1.2 Structure-Field-Name Operator
-
- The structure-field-name operator (.) indicates addition. It is used to
- designate a field within a structure.
-
- Syntax
-
- variable.field
-
- The variable is a memory operand (usually a previously declared structure
- variable), and field is the name of a field within the structure. See
- Section 7.1, "Structures," for more information.
-
- Example
-
- .DATA
- date STRUC ; Declare structure
- month DB ?
- day DB ?
- year DW ?
- date ENDS
- yesterday date <12,31,1987> ; Define structure variables
- today date <1,1,1988>
-
- .CODE
- .
- .
- .
- mov bh,yesterday.day ; Load structure variable
- mov bx,OFFSET today ; Load structure variable address
- inc [bx].year ; Use in indirect memory operand
-
-
- 9.2.1.3 Index Operator
-
- The index operator ([ ]) indicates addition. It is similar to the addition
- (+) operator. When used with a register, the index operator also indicates
- that the operand is an indirect memory operand rather than a
- register-direct operand.
-
- Syntax
-
- [[expression1]][expression2]
-
- In most cases expression1 is simply added to expression2. The limitations
- of the addition operator for adding memory operands also apply to the
- index operator. For example, two direct memory operands cannot be added.
- The expression label1[label2] is illegal if both are memory operands.
-
- The index operator has an extended function in specifying indirect memory
- operands. Section 2.6.4 explains the use of indirect memory operands. The
- index brackets must be outside the register or registers that specify the
- indirect displacement. However, any of the three operators that indicate
- addition (the addition operator, the index operator, or the
- structure-field-name operator) may be used for multiple additions within
- the expression.
-
- For example, the following statements are equivalent:
-
- mov ax,table[bx][di]
- mov ax,table[bx+di]
- mov ax,[table+bx+di]
- mov ax,[table][bx][di]
-
- The following statements are illegal because the index operator does not
- enclose the registers that specify indirect displacement:
-
- mov ax,table+bx+di ; Illegal - no index operator
- mov ax,[table]+bx+di ; Illegal - registers not
- ; inside index operator
-
- The index operator is typically used to index elements of a data object,
- such as variables in an array or characters in a string.
-
- Example 1
-
- mov al,string[3] ; Get 4th element of string
- add ax,array[4] ; Add 5th element of array
- mov string[7],al ; Load into 8th element of string
-
- Example 1 illustrates the index operator used with direct memory operands.
-
- Example 2
-
- mov ax,[bx] ; Get element BX points to
- add ax,array[si] ; Add element SI points to
- mov string[di],al ; Load element DI points to
- cmp cx,table[bx][di] ; Compare to element BX and DI point
-
- Example 2 illustrates the index operator used with indirect memory
- operands.
-
-
- 9.2.1.4 Shift Operators
-
- The SHR and SHL operators can be used to shift bits in constant values.
- Both perform logical shifts. Bits on the right for SHL and on the left for
- SHR are zero-filled as their contents are shifted out of position.
-
- Syntax
-
- expression SHR count
- expression SHL count
-
- The expression is shifted right or left by count number of bits. Bits
- shifted off either end of the expression are lost. If count is greater
- than or equal to 16, the result is 0.
-
- Do not confuse the SHR and SHL operators with the processor instructions
- having the same names. The operators work on integer constants only at
- assembly time. The processor instructions work on register or memory
- values at run time. The assembler can tell the difference between
- instructions and operands from context.
-
- Examples
-
- mov ax,01110111b SHL 3 ; Load 01110111000b
- mov ah,01110111b SHR 3 ; Load 01110b
-
-
- 9.2.1.5 Bitwise Logical Operators
-
- The bitwise operators perform logical operations on each bit of an
- expression. The expressions must resolve to constant values. Table 9.2
- lists the logical operators and their meanings.
-
- Table 9.2 Logical Operators
-
- Operator Syntax Meaning
- ──────────────────────────────────────────────────────────────────────────
- NOT NOT expression Bitwise complement
-
- AND expression1 AND expression2 Bitwise AND
-
- OR expression1 OR expression2 Bitwise inclusive OR
-
- XOR expression1 XOR expression2 Bitwise exclusive XOR
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- Do not confuse the NOT, AND, OR, and XOR operators with the processor
- instructions having the same names. The operators work on integer
- constants only at assembly time. The processor instructions work on
- register or memory values at run time. The assembler can tell the
- difference between instructions and operands from context.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Although calculations on expressions using the AND, OR, and XOR
- operators are done using 17-bit numbers, the results are truncated to 16
- bits.
- ──────────────────────────────────────────────────────────────────────────
-
- Examples
-
- mov ax,NOT 11110000b ; Load 1111111100001111b
- mov ah,NOT 11110000b ; Load 00001111b
- mov ah,01010101b AND 11110000b ; Load 01010000b
- mov ah,01010101b OR 11110000b ; Load 11110101b
- mov ah,01010101b XOR 11110000b ; Load 10100101b
-
-
- 9.2.2 Relational Operators
-
- The relational operators compare two expressions and return true (-1) if
- the condition specified by the operator is satisfied, or false (0) if it
- is not. The expressions must resolve to constant values. Relational
- operators are typically used with conditional directives. Table 9.3 lists
- the operators and the values they return if the specified condition is
- satisfied.
-
- Table 9.3 Relational Operators
-
- Operator Syntax Returned Value
- ──────────────────────────────────────────────────────────────────────────
- EQ expression1 EQ expression2 True if expressions are equal
-
- NE expression1 NE expression2 True if expressions are not
- equal
-
- LT expression1 LT expression2 True if left expression is
- less than right
-
- LE expression1 LE expression2 True if left expression is
- less than or equal to right
-
- GT expression1 GT expression2 True if left expression is
- greater than right
-
- GE expression1 GE expression2 True if left expression is
- greater than or equal to right
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- Note that the EQ and NE operators treat their arguments as 16-bit numbers.
- Numbers specified with the 16th bit set are considered negative. For
- example, the expression -1 EQ OFFFFh is true, but the expression -1 NE
- OFFFFh is false.
-
- The LT, LE, GT, and GE operators treat their arguments as 17-bit numbers,
- in which the 17th bit specifies the sign. For example, OFFFFh is 65,535,
- not -1. The expression 1 GT -1 is true, but the expression 1 GT OFFFFh is
- false.
-
- Examples
-
- mov ax,4 EQ 3 ; Load false( 0)
- mov ax,4 NE 3 ; Load true (-1)
- mov ax,4 LT 3 ; Load false( 0)
- mov ax,4 LE 3 ; Load false( 0)
- mov ax,4 GT 3 ; Load true (-1)
- mov ax,4 GE 3 ; Load true(-1)
-
-
- 9.2.3 Segment-Override Operator
-
- The segment-override operator (:) forces the address of a variable or
- label to be computed relative to a specific segment.
-
- Syntax
-
- segment:expression
-
- The segment can be specified in several ways. It can be one of the segment
- registers: CS, DS, SS, or ES. It can also be a segment or group name. In
- this case, the name must have been previously defined with a SEGMENT or
- GROUP directive and assigned to a segment register with an ASSUME
- directive. The expression can be a constant, expression, or a SEG
- expression. See Section 9.2.4.5 for more information on the SEG operator.
-
- Note that when a segment override is given with an indexed operand, the
- segment must be specified outside the index operators. For example,
- es:[di] is correct, but [es:di] generates an error.
-
- Examples
-
- mov ax,ss:[bx+4] ; Override default assume (DS)
- mov al,es:082h ; Load from ES
-
- ASSUME ds:FAR_DATA ; Tell the assembler and
- mov bx,FAR_DATA:count ; load from a far segment
-
- As shown in the last two statements, a segment override with a segment
- name is not enough if no segment register is assumed for the segment name.
- You must use the ASSUME directive to assign a segment register, as
- explained in Section 5.4, "Associating Segments with Registers."
-
-
- 9.2.4 Type Operators
-
- This section describes the assembler operators that specify or analyze the
- types of memory operands and other expressions.
-
-
- 9.2.4.1 PTR Operator
-
- The PTR operator specifies the type for a variable or label.
-
- Syntax
-
- type PTR expression
-
- The operator forces expression to be treated as having type. The
- expression can be any operand. The type can be BYTE, WORD, DWORD, QWORD,
- or TBYTE for memory operands. It can be NEAR, FAR, or PROC for labels.
-
- The PTR operator is typically used with forward references to define
- explicitly what size or distance a reference has. If it is not used, the
- assembler assumes a default size or distance for the reference. See
- Section 9.4 for more information on forward references.
-
- The PTR operator is also used to enable instructions to access variables
- in ways that would otherwise generate errors. For example, you could use
- the PTR operator to access the high-order byte of a WORD size variable.
- The PTR operator is required for FAR calls and jumps to forward-referenced
- labels.
-
- Example 1
-
- .DATA
- stuff DD ?
- buffer DB 20 DUP (?)
-
- .CODE
- .
- .
- .
- call FAR PTR task ; Call a far procedure
- jmp FAR PTR place ; Jump far
-
- mov bx,WORD PTR stuff[0] ; Load a word from a
- ; doubleword variable
- add ax,WORD PTR buffer[bx] ; Add a word from a byte variab
-
- The PTR operator can be used to specify the size of an indirect register
- operand for a CALL or JMP instruction. However, the size cannot be
- specified with NEAR or FAR. Use WORD or DWORD instead. Examples are shown
- below:
-
- Example 2
-
- jmp WORD PTR [bx] ; Legal near jump
- call NEAR PTR [bx] ; Illegal near call
- call DWORD PTR [bx] ; Legal far call
- jmp FAR PTR [bx] ; Illegal far jump
-
- This limitation only applies to indirect register operands. NEAR or FAR
- can be applied to operands associated with labels, as shown in Example 1.
- Furthermore, use NEAR or FAR with an indirect operand that combines a
- register with a label.
-
-
- 9.2.4.2 SHORT Operator
-
- The SHORT operator sets the type of a specified label to SHORT. Short
- labels can be used in JMP instructions whenever the distance from the
- label to the instruction is less than 128 bytes.
-
- Syntax
-
- SHORT label
-
- Instructions using short labels are a byte smaller than identical
- instructions using the default near labels. See Section 9.4.1, "Forward
- References to Labels," for information on using the SHORT operator with
- jump instructions.
-
- Example
-
- jmp again ; Jump 128 bytes or more
- .
- .
- .
- jmp SHORT again ; Jump less than 128 bytes
- .
- .
- .
- again:
-
-
- 9.2.4.3 THIS Operator
-
- The THIS operator creates an operand whose offset and segment values are
- equal to the current location-counter value and whose type is specified by
- the operator.
-
- Syntax
-
- THIS type
-
- The type can be BYTE, WORD, DWORD, QWORD, or TBYTE for memory operands. It
- can be NEAR, FAR, or PROC for labels.
-
- The THIS operator is typically used with the EQU or equal-sign (=)
- directive to create labels and variables. The result is similar to using
- the LABEL directive.
-
- Example
-
- tag1 EQU THIS BYTE ; Both represent the same variable
- tag2 LABEL BYTE
-
- check1 EQU THIS NEAR ; All represent the same address
- check2 LABEL NEAR
- check3:
- check4 PROC NEAR
- check4 ENDP
-
-
- 9.2.4.4 HIGH and LOW Operators
-
- The HIGH and LOW operators return the high and low bytes, respectively, of
- an expression.
-
- Syntax
-
- HIGH expression
- LOW expression
-
- The HIGH operator returns the high-order eight bits of expression; the LOW
- operator returns the low-order eight bits. The expression must evaluate to
- a constant. You cannot use the HIGH and LOW operators on the contents of a
- memory operand since the contents may change at run time.
-
- Examples
-
- stuff EQU 0ABCDh
- mov ah,HIGH stuff ; Load 0ABh
- mov al,LOW stuff ; Load 0CDh
-
- The HIGH and LOW operators work reliably only with constants and with
- offsets to external symbols. HIGH and LOW operations are not supported for
- offsets to local symbols.
-
-
- 9.2.4.5 SEG Operator
-
- The SEG operator returns the segment address of an expression.
-
- Syntax
-
- SEG expression
-
- The expression can be any label, variable, segment name, group name, or
- other memory operand. The SEG operator cannot be used with constant
- expressions. The returned value can be used as a memory operand.
-
- Example
-
- .DATA
- var DB ?
- .CODE
- .
- .
- .
- ASSUME ds:SEG var ; Assume segment of variable
- mov ax,SEG var ; Get address of segment
- mov ds,ax ; where variable is declared
-
-
- 9.2.4.6 OFFSET Operator
-
- The OFFSET operator returns the offset address of an expression.
-
- Syntax
-
- OFFSET expression
-
- The expression can be any label, variable, or other direct memory operand.
- Constant expressions return meaningless values. The value returned by the
- OFFSET operand is an immediate (constant) operand.
-
- If the MODEL directive is used, the value returned by the OFFSET operator
- is relative to a group, whenever the data item is declared in a segment
- that is part of a group. The OFFSET operator returns the number of bytes
- between the beginning of the group and the data object. If the object is
- declared in a segment not part of a group, OFFSET returns the number of
- bytes between the beginning of the segment and the data object.
-
- If the MODEL directive is not used, OFFSET returns a value relative to the
- beginning of the segment, regardless of whether the segment is part of a
- group.
-
- If full segment definitions are given, the returned value is a memory
- operand equal to the number of bytes between the item and the beginning of
- the segment in which it is defined.
-
- The segment-override operator (:) can be used to force OFFSET to return
- the number of bytes between the item in expression and the beginning of a
- named segment or group. This is the method used to generate valid offsets
- for items in a group when full segment definitions are used. For example,
- the statement
-
- mov bx,OFFSET DGROUP:array
-
- is not the same as
-
- mov bx,OFFSET array
-
- if array is not in the first segment in DGROUP.
-
- Example
-
- .DATA
- string DB "This is it."
- .CODE
- .
- .
- .
- mov dx,OFFSET string ; Load offset of variable
-
-
- 9.2.4.7 .TYPE Operator
-
- The .TYPE operator returns a byte that defines the mode and scope of an
- expression.
-
- Syntax
-
- .TYPE expression
-
- If expression is not valid, .TYPE returns 0. Otherwise, .TYPE returns a
- byte having the bit setting shown in Table 9.4. The .TYPE operator sets
- all bits except bit 6. Future versions of the assembler may reserve a use
- for this bit.
-
- Table 9.4 .TYPE Operator and Variable Attributes
-
- Bit Position If Bit = 0 If Bit = 1
- ──────────────────────────────────────────────────────────────────────────
- 0 Not program related Program related
-
- 1 Not data related Data related
-
- 2 Not a constant value Constant value
-
- 3 Addressing mode is not direct Addressing mode is direct
-
- 4 Not a register Expression is a register
-
- 5 Not defined Defined
-
- 7 Local or public scope External scope
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- The .TYPE operator is typically used in macros in which different kinds of
- arguments may need to be handled differently.
-
- Example
-
- display MACRO string
- IF ((.TYPE string) SHL 14) NE 8000h
- IF2
- %OUT Argument must be a variable
- ENDIF
- ENDIF
- mov dx,OFFSET string
- mov ah,09h
- int 21h
- ENDM
-
- This macro checks to see if the argument passed to it is data related (a
- variable). It does this by shifting all bits except the relevant bits (1
- and 0) to the left so that they can be checked. If the data bit is not
- set, an error message is generated.
-
-
- 9.2.4.8 TYPE Operator
-
- The TYPE operator returns a number that represents the type of an
- expression.
-
- Syntax
-
- TYPE expression
-
- If expression evaluates to a variable, the operator returns the number of
- bytes in each data object in the variable. Each byte in a string is
- considered a separate data object, so the TYPE operator returns 1 for
- strings.
-
- If expression evaluates to a structure or structure variable, the operator
- returns the number of bytes in the structure. If the expression is a
- label, the operator returns 0FFFFH for NEAR labels and 0FFFEH for FAR
- labels. If the expression is a constant, the operator returns 0.
-
-
- 9.2.4.9 LENGTH Operator
-
- The LENGTH operator returns the number of data elements in an array or
- other variable defined with the DUP operator.
-
- Syntax
-
- LENGTH variable
-
- The returned value is the number of elements of the declared size in
- variable. If the variable was declared with nested DUP operators, only the
- value given for the outer DUP operator is returned. If the variable was
- not declared with the DUP operator, the value returned is always 1.
-
- Example
-
- array DD 100 DUP(0FFFFFFh)
- table DW 100 DUP(1,10 DUP(?))
- string DB 'This is a string'
- var DT ?
- larray EQU LENGTH array ; 100 - number of elements
- ltable EQU LENGTH table ; 100 - inner DUP not counted
- lstring EQU LENGTH string ; 1 - string is one element
- lvar EQU LENGTH var ; 1
- .
- .
- .
- mov cx,LENGTH array ; Load number of elements
- again: . ; Perform some operation on
- . ; each element
- .
- loop again
-
-
- 9.2.4.10 SIZE Operator
-
- The SIZE operator returns the total number of bytes allocated for an array
- or other variable defined with the DUP operator.
-
- Syntax
-
- SIZE variable
-
- The returned value is equal to the value of LENGTH variable times the
- value of TYPE variable. If the variable was declared with nested DUP
- operators, only the value given for the outside DUP operator is
- considered. If the variable was not declared with the DUP operator, the
- value returned is always TYPE variable.
-
- Example
-
- array DD 100 DUP(1)
- table DW 100 DUP(1,10 DUP(?))
- string DB 'This is a string'
- var DT ?
- sarray EQU SIZE array ; 400 - elements times size
- stable EQU SIZE table ; 200 - inner DUP ignored
- sstring EQU SIZE string ; 1 - string is one element
- svar EQU SIZE var ; 10 - bytes in variable
- .
- .
- .
- mov cx,SIZE array ; Load number of bytes
- again: . ; Perform some operation on
- . ; each byte
- .
- loop again
-
-
- 9.2.5 Operator Precedence
-
- Expressions are evaluated according to the following rules:
-
- ■ Operations of highest precedence are performed first.
-
- ■ Operations of equal precedence are performed from left to right.
-
- ■ The order of evaluation can be overridden by using parentheses.
- Operations in parentheses are always performed before any adjacent
- operations.
-
- The order of precedence for all operators is listed in Table 9.5.
- Operators on the same line have equal precedence.
-
- Table 9.5 Operator Precedence
-
- Precedence Operators
- ──────────────────────────────────────────────────────────────────────────
- (Highest)
-
- 1 LENGTH, SIZE, WIDTH, MASK, (), [],<>
-
- 2 . (structure-field-name operator)
-
- 3 :
-
- 4 PTR, OFFSET, SEG, TYPE, THIS
-
- 5 HIGH, LOW
-
- 6 +,- (unary)
-
- 7 *,/, MOD, SHL, SHR
-
- 8 +, - (binary)
-
- 9 EQ, NE, LT, LE, GT, GE
-
- 10 NOT
-
- 11 AND
-
- 12 OR, XOR
-
- 13 SHORT, .TYPE
-
- (Lowest)
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- Examples
-
- a EQU 8 / 4 * 2 ; Equals 4
- b EQU 8 / (4 * 2) ; Equals 1
- c EQU 8 + 4 * 2 ; Equals 16
- d EQU (8 + 4) * 2 ; Equals 24
- e EQU 8 OR 4 AND 2 ; Equals 8
- f EQU (8 OR 4) AND 3 ; Equals 0
-
-
- 9.3 Using the Location Counter
-
- The location counter is a special operand that, during assembly,
- represents the address of the statement currently being assembled. At
- assembly time, the location counter keeps changing, but when used in
- source code, it resolves to a constant representing an address.
-
- The location counter has the same attributes as a near label. It
- represents an offset that is relative to the current segment and is equal
- to the number of bytes generated for the segment to that point.
-
- Example 1
-
- string DB "Who wants to count every byte in a string, "
- DB "especially if you might change it later."
- lstring EQU $-string ; Let the assembler do it
-
- Example 1 shows one way of using the location-counter operand in
- expressions relating to data.
-
- Example 2
-
- cmp ax,bx
- jl shortjump ; If ax < bx, go to "shortjump"
- . ; else if ax >= bx, continue
- .
- shortjump: .
-
- cmp ax,bx
- jge $+5 ; If ax >= bx, continue
- jmp longjump ; else if ax < bx, go to "longjump"
- . ; This is "$+5"
- .
- longjump: .
-
- Example 2 illustrates how you can use the location counter to do
- conditional jumps of more than 128 bytes. The first part shows the normal
- way of coding jumps of less than 128 bytes, and the second part shows how
- to code the same jump when the label is more than 128 bytes away.
-
-
- 9.4 Using Forward References
-
- The assembler permits you to refer to labels, variable names, segment
- names, and other symbols before they are declared in the source code. Such
- references are called forward references.
-
- The assembler handles forward references by making assumptions about them
- on the first pass and then attempting to correct the assumptions, if
- necessary, on the second pass. Checking and correcting assumptions on the
- second pass takes processing time, so source code with forward references
- assembles more slowly than source code with no forward references.
-
- In addition, the assembler may make incorrect assumptions that it cannot
- correct, or corrects at a cost in program efficiency.
-
-
- 9.4.1 Forward References to Labels
-
- Forward references to labels may result in incorrect or inefficient code.
-
- In the statement below, the label target is a forward reference:
-
- jmp target ; Generates 3 bytes in 16-bit segmen
- .
- .
- .
- target:
-
- Since the assembler processes source files sequentially, target is unknown
- when it is first encountered. It could be one of three types: short (-128
- to 127 bytes from the jump), near (-32,768 to 32,767 bytes from the jump),
- or far (in a different segment than the jump). QuickAssembler assumes that
- target is a near label, and assembles the number of bytes necessary to
- specify a near label: one byte for the instruction and two bytes for the
- operand.
-
- If, on the second pass, the assembler learns that target is a short label,
- it will need only two bytes: one for the instruction and one for the
- operand. However, it will not be able to change its previous assembly and
- the three-byte version of the assembly will stand. If the assembler learns
- that target is a far label, it will need five bytes. Since it can't make
- this adjustment, it will generate a phase error.
-
- You can override the assembler's assumptions by specifying the exact size
- of the jump. For example, if you know that a JMP instruction refers to a
- label less than 128 bytes from the jump, you can use the SHORT operator,
- as shown below:
-
- jmp SHORT target ; Generates 2 bytes
- .
- .
- .
- target:
-
- Using the SHORT operator makes the code smaller and slightly faster. If
- the assembler has to use the three-byte form when the two-byte form would
- be acceptable, it will generate a warning message if the warning level is
- 2. (The warning level can be set with the /W option, as described in
- Appendix B, Section B.16.) You can ignore the warning, or you can go
- back to the source code and change the code to eliminate the forward
- references.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The SHORT operator in the example above would not be needed if
- target were located before the jump. The assembler would have already
- processed target and would be able to make adjustments based on its
- distance.
- ──────────────────────────────────────────────────────────────────────────
-
- If you use the SHORT operator when the label being jumped to is more than
- 128 bytes away, QuickAssembler generates an error message. You can either
- remove the SHORT operator, or try to reorganize your program to reduce the
- distance.
-
- If a far jump to a forward-referenced label is required, you must override
- the assembler's assumptions with the FAR and PTR operators, as shown
- below:
-
- jmp FAR PTR target ; Generates 5 bytes
- .
- .
- .
- target: ; In different segment
-
- If the type of a label has been established earlier in the source code
- with an EXTRN directive, the type does not need to be specified in the
- jump statement.
-
-
- 9.4.2 Forward References to Variables
-
- When QuickAssembler encounters code referencing variables that have not
- yet been defined in pass 1, it makes assumptions about the segment where
- the variable will be defined. If on pass 2 the assumptions turn out to be
- wrong, an error will occur.
-
- These problems usually occur with complex segment structures that do not
- follow the Microsoft segment conventions. The problems never appear if
- simplified segment directives are used.
-
- By default, QuickAssembler assumes that variables are referenced to the DS
- register. If a statement must access a variable in a segment not
- associated with the DS register, and if the variable has not been defined
- earlier in the source code, you must use the segment-override operator to
- specify the segment.
-
- The situation is different if neither the variable nor the segment in
- which it is defined has been defined earlier in the source code. In this
- case, you must assign the segment to a group earlier in the source code.
- QuickAssembler will then know about the existence of the segment even
- though it has not yet been defined.
-
-
- 9.5 Strong Typing for Memory Operands
-
- The assembler carries out strict syntax checks for all instruction
- statements, including strong typing for operands that refer to memory
- locations. This means that when an instruction uses two operands with
- implied data types, the operand types must match. Warning messages are
- generated for nonmatching types.
-
- For example, in the following fragment, the variable string is incorrectly
- used in a move instruction:
-
- .DATA
- string DB "A message."
- .CODE
- .
- .
- .
- mov ax,string[1]
-
- The ax register has WORD type, but string has BYTE type. Therefore, the
- statement generates the following warning message:
-
- Operand types must match
-
- To avoid all ambiguity and prevent the warning error, use the PTR operator
- to override the variable's type, as shown below:
-
- mov ax,WORD PTR string[1]
-
- You can ignore the warnings if you are willing to trust the assembler's
- assumptions. When a register and memory operand are mixed, the assembler
- assumes that the register operand is always the correct size. For example,
- in the statement
-
- mov ax,string[1]
-
- the assembler assumes that the programmer wishes the word size of the
- register to override the byte size of the variable. A word starting at
- string[1] will be moved into AX. In the statement
-
- mov string[1],ax
-
- the assembler assumes that the programmer wishes to move the word value in
- AX into the word starting at string[1]. However, the assembler's
- assumptions are not always as clear as in these examples. You should not
- ignore warnings about type mismatches unless you are sure you understand
- how your code will be assembled.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Some assemblers (including early versions of the IBM Macro
- Assembler) do not do strict type checking. For compatibility with these
- assemblers, type errors are warnings rather than severe errors. Many
- assembly-language program listings in books and magazines are written for
- assemblers with weak type checking. Such programs may produce warning
- messages, but assemble correctly. You can use the /W option to turn off
- type warnings if you are sure the code is correct.
- ──────────────────────────────────────────────────────────────────────────
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 10: Assembling Conditionally
-
-
- QuickAssembler provides two types of conditional directives,
- conditional-assembly and conditional-error directives.
- Conditional-assembly directives test for a specified condition and
- assemble a block of statements if the condition is true. Conditional-error
- directives test for a specified condition and generate an assembly error
- if the condition is true.
-
- Both kinds of conditional directives test assembly-time conditions. They
- cannot test run-time conditions. Only expressions that evaluate to
- constants during assembly can be compared or tested.
-
- Since macros and conditional-assembly directives are often used together,
- you may need to refer to Chapter 11, "Using Equates, Macros, and Repeat
- Blocks," to understand some of the examples in this chapter. In
- particular, conditional directives are frequently used with the operators
- described in Section 11.5, "Using Macro Operators."
-
-
- 10.1 Using Conditional-Assembly Directives
-
- The conditional-assembly directives include the following:
-
- ELSE IFB IFIDN
- ENDIF IFDEF IFIDNI
- IF IFDIF IFNB
- IF1 IFDIFI IFNDEF
- IF2 IFE
-
- The IF directives and the ENDIF and ELSE directives can be used to enclose
- the statements to be considered for conditional assembly.
-
- Syntax
-
- IFcondition
- statements
- [[ELSEIFcondition
- statements]]
- .
- .
- .
- [[ELSE
- statements]]
- ENDIF
-
- The statements following the IF directive can be any valid statements,
- including other conditional blocks. The ELSEIF and ELSE blocks are
- optional. The conditional block can contain any number of ELSEIF blocks.
- (The ELSEIF directives are listed in Section 10.1.6.) ENDIF ends the
- block.
-
- The statements following the IF directive are assembled only if the
- corresponding condition is true. If the condition is not true and an
- ELSEIF directive is used, the assembler checks to see if the corresponding
- condition is true. If so, it assembles the statements following the ELSEIF
- directive. If no IF or ELSEIF conditions are satisifed, the statements
- following the ELSE directive are assembled.
-
- IF statements can be nested up to 20 levels. A nested ELSE or ELSEIF
- directive always belongs to the nearest preceding IF statement that does
- not have its own ELSE directive.
-
-
- 10.1.1 Testing Expressions with IF and IFE Directives
-
- The IF and IFE directives test the value of an expression and grant
- assembly based on the result.
-
- Syntax
-
- IF expression
- IFE expression
-
- The IF directive grants assembly if the value of expression is true
- (nonzero). The IFE directive grants assembly if the value of expression is
- false (0). The expression must evaluate to a constant value and must not
- contain forward references.
-
- Example
-
- IF debug GT 20
- push debug
- call adebug
- ELSEIF debug GT 10
- call bdebug
- ELSE
- call cdebug
- ENDIF
-
- In this example, a different debug routine will be called, depending on
- the value of debug.
-
-
- 10.1.2 Testing the Pass with IF1 and IF2 Directives
-
- The IF1 and IF2 directives test the current assembly pass and grant
- assembly only on the pass specified by the directive. Multiple passes of
- the assembler are discussed in Appendix C, Section C.7, "Reading a Pass
- 1 Listing."
-
- Syntax
-
- IF1
- IF2
-
- The IF1 directive grants assembly only on pass 1. The IF2 directive grants
- assembly only on pass 2. The directives take no arguments. If you turn on
- the One-Pass Assembly option, the IF2 directive produces an error.
-
- Macros usually only need to be processed once. You can enclose blocks of
- macros in IF1 blocks to prevent them from being reprocessed on the second
- pass.
-
- Example
-
- IF1 ; Define on first pass only
- dostuff MACRO argument
- .
- .
- .
- ENDM
- ENDIF
-
-
- 10.1.3 Testing Symbol Definition with IFDEF and IFNDEF Directives
-
- The IFDEF and IFNDEF directives test whether a symbol has been defined and
- grant assembly based on the result.
-
- Syntax
-
- IFDEF name
- IFNDEF name
-
- The IFDEF directive grants assembly only if name is a defined label,
- variable, or symbol. The IFNDEF directive grants assembly if name has not
- yet been defined.
-
- The name can be any valid name. Note that if name is a forward reference,
- it is considered undefined on pass 1, but defined on pass 2.
-
- Example
-
- IFDEF buffer
- buff DB buffer DUP(?)
- ENDIF
-
- In this example, buff is allocated only if buffer has been previously
- defined.
-
- One way to use this conditional block is to leave buffer undefined in the
- source file and define it if needed by using the /Dsymbol option (see
- Appendix B, Section B.4, "Defining Assembler Symbols") when you start
- QuickAssembler. For example, if the conditional block is in TEST.ASM, you
- could start the assembler with the following command line:
-
- QCL /Dbuffer=1024 test.asm
-
- You could also define the symbol buffer by entering buffer=1024 in the
- Defines field of the Assembler Flags dialog box.
-
- The command line would define the symbol buffer. As a result, the
- conditional-assembly block would allocate buff. However, if you didn't
- need buff, you could use the following command line:
-
- QCL test.asm
-
-
- 10.1.4 Verifying Macro Parameters with IFB and IFNB Directives
-
- The IFB and IFNB directives test to see if a specified argument was passed
- to a macro and grant assembly based on the result.
-
- Syntax
-
- IFB <argument >
- IFNB <argument>
-
- These directives are always used inside macros, and they always test
- whether a real argument was passed for a specified dummy argument. The IFB
- directive grants assembly if argument is blank. The IFNB directive grants
- assembly if argument is not blank. The arguments can be any name, number,
- or expression. Angle brackets (< >) are required.
-
- Example
-
- Write MACRO buffer,bytes,handle
- IFNB <handle>
- mov bx,handle ; (1=stdout,2=stderr,3=aux,4=printer)
- ELSE
- mov bx,1 ; Default standard out
- ENDIF
- mov dx,OFFSET buffer ; Address of buffer to write to
- mov cx,bytes ; Number of bytes to write
- mov ah,40h
- int 21h
- ENDM
-
- In this example, a default value is used if no value is specified for the
- third macro argument.
-
-
- 10.1.5 Comparing Macro Arguments with IFIDN and IFDIF Directives
-
- The IFIDN and IFDIF directives compare two macro arguments and grant
- assembly based on the result.
-
- Syntax
-
- IFIDN[[I]] <argument1>,<argument2>
- IFDIF[[I]] <argument1>,<argument2>
-
- These directives are always used inside macros, and they always test
- whether real arguments passed for two specified arguments are the same.
- The IFIDN directive grants assembly if argument1 and argument2 are
- identical. The IFDIF directive grants assembly if argument1 and argument2
- are different. The arguments can be names, numbers, or expressions. They
- must be enclosed in angle brackets and separated by a comma.
-
- The optional I at the end of the directive name specifies that the
- directive is case insensitive. Arguments that are spelled the same will be
- evaluated the same, regardless of case. If the I is not given, the
- directive is case sensitive.
-
- Example
-
- divide8 MACRO numerator,denominator
- IFDIFI <numerator>,<al> ;; If numerator isn't AL
- mov al,numerator ;; make it AL
- ENDIF
- xor ah,ah
- div denominator
- ENDM
-
- In this example, a macro uses the IFDIFI directive to check one of the
- arguments and take a different action, depending on the text of the
- string. The sample macro could be enhanced further by checking for other
- values that would require adjustment (such as a denominator passed in AL
- or passed in AH).
-
-
- 10.1.6 ELSEIF Directives
-
- The assembler includes an ELSEIF conditional-assembly directive
- corresponding to each of the IF directives. The ELSEIF directives provide
- a more compact and better structured way of writing some sequences of ELSE
- and IF directives. QuickAssembler supports the following directives:
-
- ELSEIF ELSEIFDEF ELSEIFIDN
- ELSEIF1 ELSEIFDIF ELSEIFIDNI
- ELSEIF2 ELSEIFDIFI ELSEIFNB
- ELSEIFB ELSEIFE ELSEIFNDEF
-
- The following macro contains nested IF and ELSE blocks:
-
- ; Macro to load register for high-level-language return
- FuncRet MACRO arg,length
- LOCAL tmploc
- IF length EQ 1
- mov al,arg
- ELSE
- IF length EQ 2
- mov ax,arg
- ELSE
- IF length EQ 4
- .DATA
- tmploc DW ?
- DW ?
- .CODE
- mov ax,WORD PTR arg
- mov tmploc,ax
- mov ax,WORD PTR arg+2
- mov tmploc+2,ax
- mov dx,SEG tmploc
- mov ax,OFFSET tmploc
- ELSE
- %OUT Error in FuncRet expansion
- .ERR
- ENDIF
- ENDIF
- ENDIF
- ENDM
-
- The macro can be rewritten as follows, using the ELSEIF directives:
-
- FuncRet MACRO arg,length
- LOCAL tmploc
- IF length EQ 1
- mov al,arg
- ELSEIF length EQ 2
- mov ax,arg
- ELSEIF length EQ 4
- .DATA
-
- tmploc DW ?
- DW ?
- .CODE
- mov ax,WORD PTR arg
- mov tmploc,ax
- mov ax,WORD PTR arg+2
- mov tmploc+2,ax
- mov dx,SEG tmploc
- mov ax,OFFSET tmploc
- ELSE
- %OUT Error in FuncRet expansion
- .ERR
- ENDIF
- ENDM
-
-
- 10.2 Using Conditional-Error Directives
-
- Conditional-error directives can be used to debug programs and check for
- assembly-time errors. By inserting a conditional-error directive at a key
- point in your code, you can test assembly-time conditions at that point.
- You can also use conditional-error directives to test for boundary
- conditions in macros.
-
- The conditional-error directives and the error messages they produce are
- listed in Table 10.1.
-
- Table 10.1 Conditional-Error Directives
-
- Directive Number Message
- ──────────────────────────────────────────────────────────────────────────
- .ERR1 2087 Forced error - pass1
-
- .ERR2 2088 Forced error - pass2
-
- .ERR 2089 Forced error
-
- .ERRE 2090 Forced error - expression equals 0
-
- .ERRNZ 2091 Forced error - expression not equal 0
-
- .ERRNDEF 2092 Forced error - symbol not defined
-
- .ERRDEF 2093 Forced error - symbol defined
-
- .ERRB 2094 Forced error - string blank
-
- .ERRNB 2095 Forced error - string not blank
-
- .ERRIDN [[I]] 2096 Forced error - strings identical
-
- .ERRDIF [[I]] 2097 Forced error - strings different
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- Like other severe errors, those generated by conditional-error directives
- cause the assembler to return exit code 7. If a severe error is
- encountered during assembly, QuickAssembler will delete the object module.
- All conditional-error directives except ERR1 generate severe errors.
-
-
- 10.2.1 Generating Unconditional Errors
- with .ERR, .ERR1, and .ERR2 Directives
-
- The .ERR, .ERR1, and .ERR2 directives force an error where the directives
- occur in the source file. The error is generated unconditionally when the
- directive is encountered, but the directives can be placed within
- conditional-assembly blocks to limit the errors to certain situations.
-
- Syntax
-
- .ERR
- .ERR1
- .ERR2
-
- The .ERR directive forces an error regardless of the pass. The .ERR1 and
- .ERR2 directives force the error only on their respective passes. The
- .ERR1 directive appears only on the screen or in the listing file if you
- use the /D option to request a pass 1 listing.
-
- You can place these directives within conditional-assembly blocks or
- macros to see which blocks are being expanded.
-
- Example
-
- IFDEF dos
- .
- .
- .
- ELSEIFDEF xenix
- .
- .
- .
- ELSE
- .ERR
- %OUT dos or xenix must be defined
- ENDIF
- ENDIF
-
- This example makes sure that either the symbol dos or the symbol xenix is
- defined. If neither is defined, the nested ELSE condition is assembled and
- an error message is generated. Since the .ERR directive is used, an error
- would be generated on each pass. You could use .ERR1 or .ERR2 to check if
- you want the error to be generated only on the corresponding pass.
-
-
- 10.2.2 Testing Expressions with .ERRE or .ERRNZ Directives
-
- The .ERRE and .ERRNZ directives test the value of an expression and
- conditionally generate an error based on the result.
-
- Syntax
-
- .ERRE expression
- .ERRNZ expression
-
- The .ERRE directive generates an error if expression is false (0). The
- .ERRNZ directive generates an error if expression is true (nonzero). The
- expression must evaluate to a constant value and must not contain forward
- references.
-
- Example
-
- buffer MACRO count,bname
- .ERRE count LE 128 ;; Allocate memory, but
- bname DB count DUP(0) ;; no more than 128 bytes
- ENDM
- .
- .
- .
- buffer 128,buf1 ; Data allocated - no error
- buffer 129,buf2 ; Error generated
-
- In this example, the .ERRE directive is used to check the boundaries of a
- parameter passed to the macro buffer. If count is less than or equal to
- 128, the expression being tested by the error directive will be true
- (nonzero) and no error will be generated. If count is greater than 128,
- the expression will be false (0) and the error will be generated.
-
-
- 10.2.3 Verifying Symbol Definition with .ERRDEF and .ERRNDEF Directives
-
- The .ERRDEF and .ERRNDEF directives test whether a symbol is defined and
- conditionally generate an error based on the result.
-
- Syntax
-
- .ERRDEF name
- .ERRNDEF name
-
- The .ERRDEF directive produces an error if name is defined as a label,
- variable, or symbol. The .ERRNDEF directive produces an error if name has
- not yet been defined. If name is a forward reference, it is considered
- undefined on pass 1, but defined on pass 2.
-
- Example
-
- .ERRNDEF publevel
- IF publevel LE 2
- PUBLIC var1, var2
- ELSE
- PUBLIC var1, var2, var3
- ENDIF
-
- In this example, the .ERRNDEF directive at the beginning of the
- conditional block makes sure that a symbol being tested in the block
- actually exists.
-
-
- 10.2.4 Testing for Macro Parameters with .ERRB and .ERRNB Directives
-
- The .ERRB and .ERRNB directives test whether a specified argument was
- passed to a macro and conditionally generate an error based on the result.
-
- Syntax
-
- .ERRB <argument>
- .ERRNB <argument>
-
- These directives are always used inside macros, and they always test
- whether a real argument was passed for a specified dummy argument. The
- .ERRB directive generates an error if argument is blank. The .ERRNB
- directive generates an error if argument is not blank. The argument can be
- any name, number, or expression. Angle brackets (<>) are required.
-
- Example
-
- work MACRO realarg,testarg
- .ERRB <realarg> ;; Error if no parameters
- .ERRNB <testarg> ;; Error if more than one parameter
- .
- .
- .
- ENDM
-
- In this example, error directives are used to make sure that one, and only
- one, argument is passed to the macro. The .ERRB directive generates an
- error if no argument is passed to the macro. The .ERRNB directive
- generates an error if more than one argument is passed to the macro.
-
-
- 10.2.5 Comparing Macro Arguments with .ERRIDN and .ERRDIF Directives
-
- The .ERRIDN and .ERRDIF directives compare two macro arguments and
- conditionally generate an error based on the result.
-
- Syntax
-
- .ERRIDN[[I]] <argument1>,<argument2>
- .ERRDIF[[I]] <argument1>,<argument2>
-
- These directives are always used inside macros, and they always compare
- the real arguments specified for two parameters. The .ERRIDN directive
- generates an error if the arguments are identical. The .ERRDIF directive
- generates an error if the arguments are different. The arguments can be
- names, numbers, or expressions. They must be enclosed in angle brackets
- and separated by a comma.
-
- The optional I at the end of the directive name specifies that the
- directive is case insensitive. Arguments that are spelled the same will be
- evaluated the same regardless of case. If the I is not given, the
- directive is case sensitive.
-
- Example
-
- addem MACRO ad1,ad2,sum
- .ERRIDNI <ax>,<ad2> ;; Error if ad2 is "ax"
- mov ax,ad1 ;; Would overwrite if ad2 were AX
- add ax,ad2
- mov sum,ax ;; Sum must be register or memory
- ENDM
-
- In this example, the .ERRIDNI directive is used to protect against passing
- the AX register as the second parameter, since this would cause the macro
- to fail.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 11: Using Equates, Macros, and Repeat Blocks
-
-
- This chapter explains how to use equates, macros, and repeat blocks.
- "Equates" are constant values assigned to symbols so that the symbol can
- be used in place of the value. "Macros" are a series of statements that
- are assigned a symbolic name (and, optionally, parameters) so that the
- symbol can be used in place of the statements. "Repeat blocks" are a
- special form of macro used to do repeated statements.
-
- Both equates and macros are processed at assembly time. They can simplify
- writing source code by allowing the user to substitute mnemonic names for
- constants and repetitive code. By changing a macro or equate, a programmer
- can change the effect of statements throughout the source code.
-
- In exchange for these conveniences, the programmer loses some
- assembly-time efficiency. Assembly may be slightly slower for a program
- that uses macros and equates extensively than for the same program written
- without them. However, the program without macros and equates usually
- takes longer to write and is more difficult to maintain.
-
-
- 11.1 Using Equates
-
- The equate directives enable you to use symbols that represent numeric or
- string constants. QuickAssembler recognizes three kinds of equates:
-
- 1. Redefinable numeric equates
-
- 2. Nonredefinable numeric equates
-
- 3. String equates (also called text macros)
-
-
- 11.1.1 Redefinable Numeric Equates
-
- Redefinable numeric equates are used to assign a numeric constant to a
- symbol. The value of the symbol can be redefined at any point during
- assembly time. Although the value of a redefinable equate may be different
- at different points in the source code, a constant value will be assigned
- for each use, and that value will not change at run time.
-
- Redefinable equates are often used for assembly-time calculations in
- macros and repeat blocks.
-
- Syntax
-
- name=expression
-
- The equal-sign (=) directive creates or redefines a constant symbol by
- assigning the numeric value of expression to name. No storage is allocated
- for the symbol. The symbol can be used in subsequent statements as an
- immediate operand having the assigned value. It can be redefined at any
- time.
-
- The expression can be an integer, a constant expression, a one- or
- two-character string constant, or an expression that evaluates to an
- address. The name must be either a unique name or a name previously
- defined by using the equal-sign (=) directive.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Redefinable equates must be assigned numeric values. String
- constants longer than two characters cannot be used.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- counter = 0 ; Initialize counter
- array LABEL BYTE ; Label array of increasing numbers
- REPT 100 ; Repeat 100 times
- DB counter ; Initialize number
- counter = counter + 1 ; Increment counter
- ENDM
-
- This example redefines equates inside a repeat block to declare an array
- initialized to increasing values from 0 to 100. The equal-sign directive
- is used to increment the counter symbol for each loop. See Section 11.4
- for more information on repeat blocks.
-
-
- 11.1.2 Nonredefinable Numeric Equates
-
- Nonredefinable numeric equates are used to assign a numeric constant to a
- symbol. The value of the symbol cannot be redefined.
-
- Nonredefinable numeric equates are often used for assigning mnemonic names
- to constant values. This can make the code more readable and easier to
- maintain. If a constant value used in numerous places in the source code
- needs to be changed, the equate can be changed in one place rather than
- throughout the source code.
-
- Syntax
-
- name EQU expression
-
- The EQU directive creates constant symbols by assigning expression to
- name. The assembler replaces each subsequent occurrence of name with the
- value of expression. Once a numeric equate has been defined with the EQU
- directive, it cannot be redefined. Attempting to do so generates an error.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE String constants can also be defined with the EQU directive, but the
- syntax is different, as described in Section 11.1.3, "String Equates."
- ──────────────────────────────────────────────────────────────────────────
-
- No storage is allocated for the symbol. Symbols defined with numeric
- values can be used in subsequent statements as immediate operands having
- the assigned value.
-
- Examples
-
- column EQU 80 ; Numeric constant 80
- row EQU 25 ; Numeric constant 25
- screenful EQU column * row ; Numeric constant 2000
- line EQU row ; Alias for "row"
-
- .DATA
- buffer DW screenful
-
- .CODE
- .
- .
- .
- mov cx,column
- mov bx,line
-
-
- 11.1.3 String Equates
-
- String equates (or text macros) are used to assign a string constant to a
- symbol. String equates can be used in a variety of contexts, including
- defining aliases and string constants.
-
- Syntax
-
- name EQU{string | <string>}
-
- The EQU directive creates constant symbols by assigning string to name.
- The assembler replaces each subsequent occurrence of name with string.
- Symbols defined to represent strings with the EQU directive can be
- redefined to new strings. Symbols cannot be defined to represent strings
- with the equal-sign (=) directive.
-
- An alias is a special kind of string equate. It is a symbol that is
- equated to another symbol or keyword.
-
- If you want an equate to be a string equate, you should use angle brackets
- to force the assembler to evaluate it as a string. If you do not use angle
- brackets, the assembler will try to guess from context whether a numric or
- string equate is appropriate. This can lead to unexpected results. For
- example, the statement
-
- rt EQU run-time
-
- would be evaluated as run minus time, even though the user might intend to
- define the string run-time. If run and time were not already defined as
- numeric equates, the statement would generate an error. Using angle
- brackets solves this problem. The statement
-
- rt EQU <run-time>
-
- is evaluated as the string run-time.
-
- Examples
-
- ;String equate definitions
- pi EQU <3.1415> ; String constant "3.1415"
- prompt EQU <'Type Name: '> ; String constant "'Type Name: '",
- WPT EQU <WORD PTR> ; String constant for "WORD PTR"
- argl EQU <[bp+4]> ; String constant for "[bp+4]"
-
- ; Use of string equates
- .DATA
- message DB prompt ; Allocate string "Type Name:"
- pie DQ pi ; Allocate real number 3.1415
-
- .CODE
- .
- .
- .
- inc WPT parm1 ; Increment word value of
- ; argument passed on stack
-
- Section 11.3, "Text-Macro String Directives," describes directives that
- enable you to manipulate strings. They are particularly powerful when you
- use them from within macros and repeat blocks, described later.
-
-
- 11.1.4 Predefined Equates
-
- The assembler includes several predefined equates. The ones related to
- segments are described in Section 5.1.5, "Using Predefined Segment
- Equates." In addition, the following equates are available: @WordSize,
- @Cpu, and @Version.
-
- The @WordSize equate returns the size of a word for the current segment.
- With QuickAssembler, this value is always equal to 2. However, other
- versions of the assembler can assign a different value to @WordSize when
- working with 80386 extended features.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE If you set the Preserve Case assembler flag or use the /Cl option,
- QuickAssembler considers predefined equates to be case-sensitive. The
- case-sensitive names of predefined equates are @WordSize, @Cpu, @Version,
- @CurSeg, @FileName, @CodeSize, @DataSize, @Model, @data, @data?, @fardata,
- @fardata?, and @code.
- ──────────────────────────────────────────────────────────────────────────
-
- The @Cpu equate returns a 16-bit value containing information about the
- selected processor. You select a processor by using one of the processor
- directives, such as the .286 directive. You can use the @Cpu text macro to
- control assembly of processor-specific code. Individual bits in the value
- returned by @Cpu indicate information about the selected processor.
-
- Bit If Bit = 1
- ──────────────────────────────────────────────────────────────────────────
- 0 8086 processor
-
- 1 80186 processor
-
- 2 80286 processor
-
- 8 8087 coprocessor instructions enabled
-
- 10 80287 coprocessor instructions enabled
-
-
- Because the processors are upwardly compatible, selecting a
- higher-numbered processor automatically sets the bits indicating
- lower-numbered processors. For example, selecting an 80286 processor
- automatically sets the 80186 and 8086 bits.
-
- Bits 4 through 6, 9, and 12 through 15 are reserved for future use and
- should be masked off when testing. Bits 3, 7, and 11 have special meaning
- to Versions 5.1 and later of the Microsoft Macro Assembler: bit 3
- indicates an 80386 processor, bit 7 indicates privilege mode enabled, and
- bit 11 indicates that 80387 coprocessor instructions are enabled.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The @Cpu equate only provides information about the processor
- selected during assembly by one of the processor directives. It does not
- provide information about the processor actually used when a program is
- run.
- ──────────────────────────────────────────────────────────────────────────
-
- The following example uses the @Cpu text macro to select more efficient
- instructions available only on the 80186 processor and above:
-
- ; Use the 186/286/386 pusha instruction if possible
- P186 EQU (@Cpu AND 0002h) ; Only test 186 bit--286 and
- ; 386 set 186 bit as well
- .
- .
- .
-
- IF P186 ; Non-zero if 186 processor
- pusha ; or above
- ELSE
- push ax ; Do what the single
- push cx ; pusha instruction does
- push dx
- push bx
- push sp
- push bp
- push si
- push di
- ENDIF
-
- The @Version equate returns the version of the assembler in use. With the
- @Version equate, you can write macros that take appropriate actions for
- different versions of the assembler. Currently, the @Version equate
- returns 520 as a string of three characters.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Although the version number of QuickAssembler is 2.01, the @Version
- equate returns 520 rather than 201. The number 520 was selected because
- QuickAssembler is an enhancement of Version 5.1 of the Microsoft Macro
- Assembler. The @Version equate was first assembled for Version 5.1.
- ──────────────────────────────────────────────────────────────────────────
-
- You can use the IF and IFE conditional assembly directives to test for
- different versions of the assembler and to assemble different code
- depending on the version.
-
- IFNDEF @Version
- %OUT MASM 5.0 or earlier has no extended PROC or .STARTUP
- ELSEIF @Version EQ 510
- %OUT MASM 5.1 has extended PROC, but not .STARTUP
- ELSEIF @Version EQ 520
- %OUT QuickAssembler 2.01 has extended PROC and .STARTUP
- ELSE
- %OUT Future assembler
- ENDIF
-
-
- 11.2 Using Macros
-
- Macros enable you to assign a symbolic name to a block of source
- statements and then to use that name in your source file to represent the
- statements. Parameters can also be defined to represent arguments passed
- to the macro.
-
- Macro expansion is a text-processing function that occurs at assembly
- time. Each time QuickAssembler encounters the text associated with a macro
- name, it replaces that text with the text of the statements in the macro
- definition. Similarly, the text of parameter names is replaced with the
- text of the corresponding actual arguments.
-
- A macro can be defined any place in the source file as long as the
- definition precedes the first source line that calls the macro. Macros and
- equates are often kept in a separate file and made available to the
- program through an INCLUDE directive (see Section 11.7.1, "Using Include
- Files") at the start of the source code.
-
- Often a task can be done by using either a macro or procedure. For
- example, the addup procedure shown in Section 15.3.3, "Passing Arguments
- on the Stack," does the same thing as the addup macro in Section 11.2.1,
- "Defining Macros." Macros are expanded on every occurrence of the macro
- name, so they can increase the length of the executable file if called
- repeatedly. Procedures are coded only once in the executable file, but the
- increased overhead of saving and restoring addresses and parameters can
- make them slower.
-
- The section below tells how to define and call macros. Repeat blocks, a
- special form of macro for doing repeated operations, are discussed
- separately in Section 11.4.
-
-
- 11.2.1 Defining Macros
-
- The MACRO and ENDM directives are used to define macros. MACRO designates
- the beginning of the macro block, and ENDM designates the end of the macro
- block.
-
- Syntax
-
- name MACRO [[parameter [[,parameter]]...]]
- statements
- ENDM
-
- The name must be unique and a valid symbol name. It can be used later in
- the source file to invoke the macro.
-
- The parameters (sometimes called dummy parameters) are names that act as
- placeholders for values to be passed as arguments to the macro when it is
- called. Any number of parameters can be specified, but they must all fit
- on one line. If you give more than one parameter, you must separate them
- with commas, spaces, or tabs. Commas can always be used as separators;
- spaces and tabs may cause ambiguity if the arguments are expressions.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE This manual uses the term "parameter" to refer to a placeholder for
- a value that will be passed to a macro or procedure. Parameters appear in
- macro or procedure definitions. The term "argument" is used to refer to an
- actual value passed to the macro or procedure when it is called.
- ──────────────────────────────────────────────────────────────────────────
-
- Any valid assembler statements may be placed within a macro, including
- statements that call or define other macros. Any number of statements can
- be used. The parameters can be used any number of times in the statements.
- Macros can be nested, redefined, or used recursively, as explained in
- Section 11.6, "Using Recursive, Nested, and Redefined Macros."
-
- QuickAssembler assembles the statements in a macro only if the macro is
- called and only at the point in the source file from which it is called.
- The macro definition itself is never assembled.
-
- A macro definition can include the LOCAL directive, which lets you define
- labels used only within a macro, or the EXITM directive, which allows you
- to exit from a macro before all the statements in the block are expanded.
- These directives are discussed in Sections 11.2.3, "Using Local Symbols,"
- and 11.2.4, "Exiting from a Macro." Macro operators can also be used in
- macro definitions, as described in Section 11.5, "Using Macro Operators."
-
- Example
-
- addup MACRO ad1,ad2,ad3
- mov ax,ad1 ;; First parameter in AX
- add ax,ad2 ;; Add next two parameters
- add ax,ad3 ;; and leave sum in AX
- ENDM
-
- This example defines a macro named addup, which uses three parameters to
- add three values and leave their sum in the AX register. The three
- parameters will be replaced with arguments when the macro is called.
-
-
- 11.2.2 Calling Macros
-
- A macro call directs QuickAssembler to copy the statements of the macro to
- the point of the call and to replace any parameters in the macro
- statements with the corresponding actual arguments.
-
- Syntax
-
- name [[argument [[,argument]]...]]
-
- The name must be the name of a macro defined earlier in the source file.
- The arguments can be any text. For example, symbols, constants, and
- registers are often given as arguments. Any number of arguments can be
- given, but they must all fit on one logical line. You can use the
- continuation character (\) to continue long macro calls on multiple
- physical lines. Multiple arguments must be separated by commas, spaces, or
- tabs.
-
- QuickAssembler replaces the first parameter with the first argument, the
- second parameter with the second argument, and so on. If a macro call has
- more arguments than the macro has parameters, the extra arguments are
- ignored. If a call has fewer arguments than the macro has parameters, any
- remaining parameters are replaced with a null (empty) string.
-
- You can use conditional statements to enable macros to check for null
- strings or other types of arguments. The macro can then take appropriate
- action to adjust to different kinds of arguments. See Chapter 10,
- "Assembling Conditionally," for more information on using
- conditional-assembly and conditional-error directives to test macro
- arguments.
-
- Example
-
- addup MACRO ad1,ad2,ad3 ; Macro definition
- mov ax,ad1 ;; First parameter in AX
- add ax,ad2 ;; Add next two parameters
- add ax,ad3 ;; and leave sum in AX
- ENDM
- .
- .
- .
- addup bx,2,count ; Macro call
-
- When the addup macro is called, QuickAssembler replaces the parameters
- with the actual parameters given in the macro call. In the example above,
- the assembler would expand the macro call to the following code:
-
- mov ax,bx
- add ax,2
- add ax,count
-
- This code could be shown in an assembler listing, depending on whether the
- .LALL, .XALL, or .SALL directive was in effect (see Section 12.3,
- "Controlling the Contents of Listings").
-
-
- 11.2.3 Using Local Symbols
-
- The LOCAL directive can be used within a macro to define symbols that are
- available only within the defined macro.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE In this context, the term "local" is not related to the public
- availability of a symbol, as described in Chapter 8, "Creating Programs
- from Multiple Modules," or to variables that are defined to be local to a
- procedure, as described in Section 15.3.5, "Using Local Variables." Local
- simply means that the symbol is not known outside the macro where it is
- defined.
- ──────────────────────────────────────────────────────────────────────────
-
- Syntax
-
- LOCAL localname [[,localname]]...
-
- The localname is a temporary symbol name that is to be replaced by a
- unique symbol name when the macro is expanded. At least one local name is
- required for each LOCAL directive. If more than one local symbol is given,
- the names must be separated with commas. Once declared, local name can be
- used in any statement within the macro definition.
-
- QuickAssembler creates a new actual name for localname each time the macro
- is expanded. The actual name has the following form:
-
- ??number
-
- The number is a hexadecimal number in the range 0000 to 0FFFF. You should
- not give other symbols names in this format, since doing so may produce a
- symbol with multiple definitions. In listings, the local name is shown in
- the macro definition, but the actual name is shown in expansions of macro
- calls.
-
- Nonlocal labels may be used in a macro; but if the macro is used more than
- once, the same label will appear in both expansions, and QuickAssembler
- will display an error message, indicating that the file contains a symbol
- with multiple definitions. To avoid this problem, use only local labels
- (or redefinable equates) in macros.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The LOCAL directive in macro definitions must precede all other
- statements in the definition. If you try another statement (such as a
- comment directive) before the LOCAL directive, an error will be generated.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- power MACRO factor,exponent ;; Use for unsigned only
- LOCAL again,gotzero ;; Declare symbols for macro
- xor dx,dx ;; Clear DX
- mov cx,exponent ;; Exponent is count for loop
- mov ax,1 ;; Multiply by 1 first time
- jcxz gotzero ;; Get out if exponent is zero
- mov bx,factor
- again: mul bx ;; Multiply until done
- loop again
- gotzero:
- ENDM
-
- In this example, the LOCAL directive defines the local names again and
- gotzero as labels to be used within the power macro. These local names
- will be replaced with unique names each time the macro is expanded. For
- example, the first time the macro is called, again will be assigned the
- name ??0000 and gotzero will be assigned ??0001. The second time through,
- again will be assigned ??0002 and gotzero will be assigned ??0003, and so
- on.
-
-
- 11.2.4 Exiting from a Macro
-
- Normally, QuickAssembler processes all the statements in a macro
- definition and then continues with the next statement after the macro
- call. However, you can use the EXITM directive to tell the assembler to
- terminate macro expansion before all the statements in the macro have been
- assembled.
-
- When the EXITM directive is encountered, the assembler exits the macro or
- repeat block immediately. Any remaining statements in the macro or repeat
- block are not processed. If EXITM is encountered in a nested macro or
- repeat block, QuickAssembler returns to expanding the outer block.
-
- The EXITM directive is typically used with conditional directives to skip
- the last statements in a macro under specified conditions. Often macros
- using the EXITM directive contain repeat blocks or are called recursively.
-
- Example
-
- allocate MACRO times ; Macro definition
- x = 0
- REPT times ;; Repeat up to 256 times
- IF x GT 0FFh ;; Is x > 255 yet?
- EXITM ;; If so, quit
- ELSE
- DB x ;; Else allocate x
- ENDIF
- x = x + 1 ;; Increment x
- ENDM
- ENDM
-
- This example defines a macro that allocates a variable amount of data, but
- no more than 255 bytes. The macro contains an IF directive that checks the
- expression x - 0FFh. When the value of this expression is true (x-255 =
- 0), the EXITM directive is processed and expansion of the macro stops.
-
-
- 11.3 Text-Macro String Directives
-
- The assembler includes four text-macro string directives that let you
- manipulate literal strings or text-macro values. You use the four
- directives in much the same way you use the equal-sign (=) directive. For
- example, the following line assigns the first three characters (abc) of
- the literal string to the label three by using the SUBSTR directive:
-
- three SUBSTR <abcdefghijklmnopqrstuvwxyz>,1,3
-
- Each of the directives assigns its value──depending on the directive──to a
- numeric label or a text macro. The following list summarizes the four
- directives and the type of label that the directives should be used with:
-
- Directive Description
- ──────────────────────────────────────────────────────────────────────────
- SUBSTR Returns a substring of its text macro or literal
- string argument. SUBSTR requires a text-macro label.
-
- CATSTR Concatenates a variable number of strings (text macros
- or literal strings) to form a single string. CATSTR
- requires a text-macro label.
-
- SIZESTR Returns the length, in characters, of its argument
- string. SIZESTR requires a numeric label.
-
- INSTR Returns an index indicating the starting position of a
- substring within another string. INSTR requires a
- numeric label.
-
-
- Strings used as arguments in the directives must be text enclosed in angle
- brackets (< >), previously defined text macros, or expressions starting
- with a percent sign (%). Numeric arguments can be numeric constants or
- expressions that evaluate to constants during assembly.
-
- The next four sections describe the directives in more detail.
-
-
- 11.3.1 The SUBSTR Directive
-
- The SUBSTR directive returns a substring from a given string.
-
- Syntax
-
- textlabel SUBSTR string,start[[, length]]
-
- The SUBSTR directive takes the following arguments:
-
- Argument Description
- ──────────────────────────────────────────────────────────────────────────
- textlabel The text label the result is assigned to.
-
- string The string the substring is extracted from.
-
- start The starting position of the substring. The first
- character in the string has a position of one.
-
- length The number of characters to extract. If omitted,
- SUBSTR returns all characters to the right of position
- start, including the character at position start.
-
-
- In the following lines, the text macro freg is assigned the first two
- characters of the text macro reglist:
-
- reglist EQU <ax,bx,cx,dx>
- .
- .
- .
- freg SUBSTR reglist,1,2 ; freg = ax
-
-
- 11.3.2 The CATSTR Directive
-
- The CATSTR directive concatenates a series of strings.
-
- Syntax
-
- textlabel CATSTR string[[, string]]...
-
- The CATSTR directive takes the following arguments:
-
- Argument Description
- ──────────────────────────────────────────────────────────────────────────
- textlabel The text label the result is assigned to
-
- string The string or strings concatenated and assigned to
- textlabel
-
-
- The following lines concatenate the two literal strings and assign the
- result to the text macro lstring:
-
- lstring CATSTR <a b c>, <d e f>, ; lstring = a b c d e f
-
-
- 11.3.3 The SIZESTR Directive
-
- The SIZESTR directive assigns the length of its argument string to a
- numeric label.
-
- Syntax
-
- numericlabel SIZESTR string
-
- The SIZESTR directive takes the following arguments:
-
- Argument Description
- ──────────────────────────────────────────────────────────────────────────
- numericlabel The numeric label that the assembler assigns the
- string length to
-
- string The string whose length is returned
-
-
- The following lines set slength to 8──the length of the text macro
- tstring:
-
- tstring EQU <ax bx cx>
- .
- .
- .
- slength SIZESTR tstring ; slength = 8
-
- A null string has a length of zero.
-
-
- 11.3.4 The INSTR Directive
-
- The INSTR directive returns the position of a string within another
- string. The directive returns 0 if the string is not found. The first
- character in a string has a position of one.
-
- Syntax
-
- numericlabel INSTR [[start,]]string1, string2
-
- The INSTR directive takes the following arguments:
-
- Argument Description
- ──────────────────────────────────────────────────────────────────────────
- numbericlabel The numeric label the substring's position is assigned
- to.
-
- start The starting position for the search. When omitted,
- the INSTR directive starts searching at the first
- character. The first character in the string has a
- position of one.
-
- string1 The string being searched.
-
- string2 The string to look for.
-
-
- The following lines set colpos to the character position of the first
- colon in segarg:
-
- segarg EQU <ES:AX>
- .
- .
- .
- colpos INSTR segarg,<:> ; colpos = 3
-
-
- 11.3.5 Using String Directives Inside Macros
-
- The following example uses the text-macro string directives CATSTR, INSTR,
- SIZESTR, and SUBSTR. It defines two macros, SaveRegs and RestRegs, that
- save and restore registers on the stack. The macros are written so that
- RestRegs restores only the most recently saved group of registers.
-
- The SaveRegs macro uses a text macro, regpushed, to keep track of the
- registers pushed onto the stack. The RestRegs macro uses this string to
- restore the proper registers. Each time the SaveRegs macro is invoked, it
- adds a pound sign (#) to the string to mark the start of a new group of
- registers. The RestRegs macro restores the most recently saved group by
- finding the first pound sign in the string, creating a substring
- containing the saved register names, and then looping and generating PUSH
- instructions.
-
- ; Initialize regpushed to the null string
- regpushed EQU <>
-
- ; SaveRegs
- ; Loops and generates a push for each argument register.
- ; Saves each register name in regpushed.
-
- SaveRegs MACRO r1,r2,r3,r4,r5,r6,r7,r8,r9
- regpushed CATSTR <#>,regpushed ;; Mark a new group of regs
- IRP reg,<r1,r2,r3,r4,r5,r6,r7,r8,r9>
- IFNB <reg>
- push reg ;; Push and record a register
- regpushed CATSTR <reg>,<,>,regpushed
- ELSE
- EXITM ;; Quit on blank argument
- ENDIF
- ENDM
- ENDM
-
- ; RestRegs
- ; Generates a pop for each register in the most recently saved groups
-
- RestRegs MACRO
- numloc INSTR regpushed,<#> ;; Find location of #
- reglist SUBSTR regpushed,1,numloc-1 ;; Get list of registers to pop
- reglen SIZESTR regpushed ;; Adjust numloc if # is notlast
- IF reglen GT numloc ;; item in the string
- numloc = numloc + 1
- ENDIF
- regpushed SUBSTR regpushed,numloc ;; Remove list from regpushed
- % IRP reg,<reglist> ;; Generate pop for each register
- IFNB <reg>
- pop reg
- ENDIF
- ENDM
- ENDM
-
- The following lines from a listing file show the sample code that the
- macros would generate (a 2 marks lines generated by the macros):
-
- SaveRegs ax,bx
- 2 push ax ;
- 2 push bx ;
- SaveRegs cx
- 2 push cx ;
- SaveRegs dx
- 2 push dx ;
- RestRegs
- 2 pop dx
- RestRegs
- 2 pop cx
- RestRegs
- 2 pop bx
- 2 pop ax
-
-
- 11.4 Defining Repeat Blocks
-
- Repeat blocks are a special form of macro that allows you to create blocks
- of repeated statements. They differ from macros in that they are not
- named, and thus cannot be called. However, like macros, they can have
- parameters that are replaced by actual arguments during assembly. Macro
- operators, symbols declared with the LOCAL directive, and the EXITM
- directive can be used in repeat blocks. Like macros, repeat blocks are
- always terminated by an ENDM directive.
-
- Repeat blocks are frequently placed in macros in order to repeat some of
- the statements in the macro. They can also be used independently, usually
- for declaring arrays with repeated data elements.
-
- Repeat blocks are processed at assembly time and should not be confused
- with the REP instruction, which causes string instructions to be repeated
- at run time, as explained in Chapter 16, "Processing Strings."
-
- Three different kinds of repeat blocks can be defined by using the REPT,
- IRP, and IRPC directives. The difference between them is in how the number
- of repetitions is specified.
-
-
- 11.4.1 The REPT Directive
-
- The REPT directive is used to create repeat blocks in which the number of
- repetitions is specified with a numeric argument.
-
- Syntax
-
- REPT expression
- statements
- ENDM
-
- The expression must evaluate to a numeric constant (a 16-bit unsigned
- number). It specifies the number of repetitions. Any valid assembler
- statements may be placed within the repeat block.
-
- Example
-
- alphabet LABEL BYTE
- x = 0 ;; Initialize
- REPT 26 ;; Specify 26 repetitions
- DB 'A' + x ;; Allocate ASCII code for letter
- x = x + 1 ;; Increment
- ENDM
-
- This example repeats the equal-sign (=) and DB directives to initialize
- ASCII values for each uppercase letter of the alphabet.
-
-
- 11.4.2 The IRP Directive
-
- The IRP directive is used to create repeat blocks in which the number of
- repetitions, as well as parameters for each repetition, is specified in a
- list of arguments.
-
- Syntax
-
- IRP parameter,<argument[[,argument]]...>
- statements
- ENDM
-
- The assembler statements inside the block are repeated once for each
- argument in the list enclosed by angle brackets (< >). The parameter is a
- name for a placeholder to be replaced by the current argument. Each
- argument can be text, such as a symbol, string, or numeric constant. Any
- number of arguments can be given. If multiple arguments are given, they
- must be separated by commas. The angle brackets (< >) around the argument
- list are required. The parameter can be used any number of times in the
- statements.
-
- When QuickAssembler encounters an IRP directive, it makes one copy of the
- statements for each argument in the enclosed list. While copying the
- statements, it substitutes the current argument for all occurrences of
- parameter in these statements. If a null argument (< >) is found in the
- list, the dummy name is replaced with a blank value. If the argument list
- is empty, the IRP directive is ignored and no statements are copied.
-
- Example
-
- numbers LABEL BYTE
- IRP x,<0,1,2,3,4,5,6,7,8,9>
- DB 10 DUP(x)
- ENDM
-
- This example repeats the DB directive 10 times, allocating 10 bytes for
- each number in the list. The resulting statements create 100 bytes of
- data, starting with 10 zeros, followed by 10 ones, and so on.
-
-
- 11.4.3 The IRPC Directive
-
- The IRPC directive is used to create repeat blocks in which the number of
- repetitions, as well as arguments for each repetition, is specified in a
- string.
-
- Syntax
-
- IRPC parameter,string
- statements
- ENDM
-
- The assembler statements inside the block are repeated as many times as
- there are characters in string. The parameter is a name for a placeholder
- to be replaced by the current character in the string. The string can be
- any combination of letters, digits, and other characters. It should be
- enclosed with angle brackets (< >) if it contains spaces, commas, or other
- separating characters. The parameter can be used any number of times in
- these statements.
-
- When QuickAssembler encounters an IRPC directive, it makes one copy of the
- statements for each character in the string. While copying the statements,
- it substitutes the current character for all occurrences of parameter in
- these statements.
-
- Example 1
-
- ten LABEL BYTE
- IRPC x,0123456789
- DB x
- ENDM
-
- Example 1 repeats the DB directive 10 times, once for each character in
- the string 0123456789. The resulting statements create 10 bytes of data
- having the values 0-9.
-
- Example 2
-
- IRPC letter,ABCDEFGHIJKLMNOPQRSTUVWXYZ
- DB '&letter' ; Allocate uppercase letter
- DB '&letter'+20h ; Allocate lowercase letter
- DB '&letter'-40h ; Allocate number of letter
- ENDM
-
- Example 2 allocates the ASCII codes for uppercase, lowercase, and numeric
- versions of each letter in the string. Notice that the substitute operator
- (&) is required so that letter will be treated as an argument rather than
- a string. See Section 11.5.1, "Substitute Operator," for more
- information.
-
-
- 11.5 Using Macro Operators
-
- Macro and conditional directives use the following special set of macro
- operators:
-
- Operator Definition
- ──────────────────────────────────────────────────────────────────────────
- & Substitute operator
-
- <> Literal-text operator
-
- ! Literal-character operator
-
- % Expression operator
-
- ;; Macro comment
-
-
- When used in a macro definition, a macro call, a repeat block, or as the
- argument of a conditional-assembly directive, these operators carry out
- special control operations, such as text substitution.
-
-
- 11.5.1 Substitute Operator
-
- The substitute operator (&) enables substitution of macro parameters to
- take place, even when the parameter occurs within a larger word or within
- a quoted string.
-
- Syntax
-
- You can use the substitute operator in any one of three different ways:
-
- name1&name2
- name&
- &name
-
- The assembler responds by analyzing name1 and name2 separately, then
- joining them together. If either name1 or name2 is a parameter, the
- assembler replaces each parameter by an actual argument before joining the
- names. You can join any number of names with the substitute operator, so
- that items such as a&b&c are valid.
-
- The last two forms are useful when a parameter name appears within a
- quoted string. The assembler responds by substituting the actual argument
- for the parameter; when not next to an ampersand (&), the assembler
- considers the parameter name just part of the string data.
-
- Example
-
- declare MACRO x,y
- xy DW 0
- x&y DW 0
- x&str DB 'x and y params are &x and &y'
- ENDM
-
- The example above demonstrates how the presence or absence of the
- substitute operator affects macro substitution. Given the macro definition
- above, the statement
-
- declare foot,ball
-
- is expanded to
-
- xy DW
- football DW 0
- footstr DB 'x and y params are foot and ball'
-
- In the first statement of the macro, xy is not identified with either of
- the parameters x or y; instead, xy forms a distinct name. No substitution
- takes place. In the second statement, the assembler interprets x&y by
- analyzing x and y as separate names, performing the appropriate parameter
- substitution in each case, and then joining the resulting names together.
-
- When you use the substitute operator with nested macros and repeat blocks,
- the assembler applies the following rules to expressions outside of
- quotes:
-
- 1. Perform parameter substitution as described above.
-
- 2. Remove exactly one of the substitute operators (&) from the group. The
- assembler strips off an operator whether or not a parameter was
- substituted.
-
- The number of ampersands (&) you use should never be greater than the
- number of levels of nesting. If you use too few ampersands, proper
- substitution will not take place. If you use too many ampersands, text
- such as x&y will remain after macro expansion is complete. Such
- expressions are invalid.
-
- When an ampersand appears inside quotes, the assembler removes ampersands
- on either side of a macro parameter when substitution is possible. When
- substitution is not possible (because the parameter name is not defined at
- the current level of nesting), the assembler leaves the ampersand as it
- is. With this method, parameter substitution automatically takes place at
- the appropriate level of nesting.
-
-
- 11.5.2 Literal-Text Operator
-
- The literal-text operator (< >) directs QuickAssembler to treat a list as
- a single string rather than as separate arguments.
-
- Syntax
-
- <text >
-
- The text is considered a single literal element even if it contains
- commas, spaces, or tabs. The literal-text operator is most often used in
- macro calls and with the IRP directive to ensure that values in a
- parameter list are treated as a single parameter.
-
- The literal-text operator can also be used to force QuickAssembler to
- treat special characters, such as the semicolon or the ampersand,
- literally. For example, the semicolon inside angle brackets <;> becomes a
- semicolon, not a comment indicator.
-
- QuickAssembler removes one set of angle brackets each time the parameter
- is used in a macro. When using nested macros, you will need to supply as
- many sets of angle brackets as there are levels of nesting.
-
- Example
-
- work 1,2,3,4,5 ; Passes five parameters to "work"
-
- work <1,2,3,4,5> ; Passes one five-element
- ; parameter to "work"
-
- When the IRP directive is used inside a macro definition and when the
- argument list of the IRP directive is also a parameter of the macro, you
- must use the literal-text operator (< >) to enclose the macro parameter.
-
- For example, in the following macro definition, the parameter x is used as
- the argument list for the IRP directive:
-
- init MACRO x
- IRP y,<x>
- DB y
- ENDM
- ENDM
-
- If this macro is called with
-
- init <0,1,2,3,4,5,6,7,8,9>
-
- the macro removes the angle brackets from the parameter so that it is
- expanded as 0,1,2,3,4,5,6,7,8,9. The brackets inside the repeat block are
- necessary to put the angle brackets back on. The repeat block is then
- expanded as shown below:
-
- IRP y,<0,1,2,3,4,5,6,7,8,9>
- DB y
- ENDM
-
-
- 11.5.3 Literal-Character Operator
-
- The literal-character operator (!) forces the assembler to treat a
- specified character literally rather than as a symbol.
-
- Syntax
-
- !character
-
- The literal-character operator is used with special characters, such as
- the semicolon or ampersand, when meaning of the special character must be
- suppressed. Using the literal-character operator is the same as enclosing
- a single character in brackets. For example, !! is the same as <!>.
-
- Example
-
- errgen MACRO y,x
- PUBLIC err&y
- err&y DB 'Error &y: &x'
- ENDMW
-
- .
- .
- .
- errgen 103,<Expression !> 255>
-
- The example macro call is expanded to allocate the string Error 103:
- Expression > 255. Without the literal-character operator, the greater-than
- symbol would be interpreted as the end of the argument and an error would
- result.
-
-
- 11.5.4 Expression Operator
-
- The expression operator (%) causes the assembler to treat the argument
- following the operator as an expression.
-
- Syntax
-
- %text
-
- QuickAssembler computes the expression's value and replaces text with the
- result. The expression can be either a numeric expression or a text
- equate. Additional arguments after an argument that uses the expression
- operator must be preceded by a comma, not a space or tab.
-
- The expression operator is typically used in macro calls when the
- programmer needs to pass the result of an expression rather than the
- actual expression to a macro.
-
- Example
-
- printe MACRO exp,val
- IF1 ;; On pass 1 only
- %OUT exp = val ;; Display expression and result
- ENDIF ;; to screen
- ENDM
-
- sym1 EQU 100
- sym2 EQU 200
- msg EQU <"Hello, World.">
-
- printe <sym1 + sym2>,%(sym1 + sym2)
- printe msg,%msg
-
- In the first macro call, the text literal sym1 + sym2 = is passed to the
- parameter exp, and the result of the expression is passed to the parameter
- val. In the second macro call, the equate name msg is passed to the
- parameter exp, and the text of the equate is passed to the parameter val.
- As a result, Quick-Assembler displays the following messages:
-
- sym1 + sym2 = 300
- msg = "Hello, World."
-
- The %OUT directive, which sends a message to the screen, is described in
- Section 12.1, "Sending Messages to the Standard Output Device"; the IF1
- directive is described in Section 10.1.2, "Testing the Pass with IF1 and
- IF2 Directives."
-
- You can also use the expression operator (%) to substitute the values of
- text macros for the macro names anywhere a text-macro name appears. When
- the expression operator is the first item on a line and is followed by one
- or more blanks or tabs, the line is scanned for text macros and the values
- of the macros are substituted. Using the expression operator, you can
- force substitution of text macros wherever they appear in a line. The
- assembler re-scans the line until all substitutions have been made.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Text macros are always evaluated when they appear in the name or
- operation fields. The expression operator is required to evaluate a text
- macro only when the macro appears in the operand field.
- ──────────────────────────────────────────────────────────────────────────
-
- This use of the expression operator eliminates the need to do a macro call
- in order to evaluate a text macro. For example, the following macro uses a
- separate macro, popregs, to evaluate the text macro regpushed:
-
- regpushed EQU <ax,bx,cx>
- .
- .
- .
- RestRegs MACRO
- popregs %regpushed
- ENDM
-
- popregs MACRO reglist
- IRP reg,<reglist>
- pop reg
- ENDM
- ENDM
-
- The use of the expression operator to evaluate text macros in a line makes
- the popregs macro unnecessary:
-
- regpushed EQU <ax,bx,cx>
- .
- .
- .
- RestRegs MACRO
- % IRP reg,<regpushed> ;; % operator makes
- ;; separate macro unnecessary
- pop reg
- ENDM
- ENDM
-
- You cannot use the EQU directive to assign a value to a text macro in a
- line evaluated with the expression operator. For example, the following
- lines generate an error:
-
- strpos EQU <[si]+12>
- .
- .
- .
- % wpstrpos EQU <WORD PTR strpos>
-
- On pass 1, wpstrpos is defined as a text macro that is expanded on pass 2.
- Thus, on pass 2 the second EQU directive becomes
-
- WORD PTR [si]+12 EQU <WORD PTR [si]+12>
-
- and generates an error.
-
- Instead, use the CATSTR directive to assign values to text macros (see
- Section 11.3, "Text-Macro String Directives," for more information about
- CATSTR and other text-macro string directives). The previous example
- should be rewritten as follows:
-
- strpos EQU <[si]+12>
- .
- .
- .
- wpstrpos CATSTR <WORD PTR >, strpos
-
- If the text macro evaluates to a valid name, there is no error when you
- use EQU. The following lines do not generate an error, but define two
- names, one (numlabel) with the value 5, the other (tmacro) with the value
- <numlabel>:
-
- tmacro EQU <numlabel>
- % tmacro EQU 5
-
- You can also use the substitution operator (&) with text macros just as
- you would inside a macro:
-
- SegName EQU <MySeg>
- .
- .
- .
- % SegName&_text SEGMENT PUBLIC 'CODE'
-
- The final line, after expanding the text macro, becomes:
-
- MySeg_text SEGMENT PUBLIC 'CODE'
-
- The substitution operator separates the text-macro name from the text that
- immediately follows it. The name appears to the assembler as segName_text
- without the substitution operator, and the assembler fails to recognize
- the text macro.
-
-
- 11.5.5 Macro Comments
-
- A macro comment is any text in a macro definition that does not need to be
- copied in the macro expansion. A double semicolon (;;) is used to start a
- macro comment.
-
- Syntax
-
- ;;text
-
- All text following the double semicolon (;;) is ignored by the assembler
- and will appear only in the macro definition when the source listing is
- created.
-
- The regular comment operator (;) can also be used in macros. However,
- regular comments may appear in listings when the macro is expanded. Macro
- comments will appear in the macro definition, but not in macro expansions.
- Whether or not regular comments are listed in macro expansions depends on
- the use of the .LALL, .XALL, and .SALL directives, as described in Section
- 12.2.3, "Controlling Page Breaks."
-
-
- 11.6 Using Recursive, Nested, and Redefined Macros
-
- The concept of replacing macro names with predefined macro text is simple,
- but in practice it has many implications and potentially unexpected side
- effects. The following sections discuss advanced macro features (such as
- nesting, recursion, and redefinition) and point out some side effects of
- macros.
-
-
- 11.6.1 Using Recursion
-
- Macro definitions can be recursive: that is, they can call themselves.
- Using recursive macros is one way of doing repeated operations. The macro
- does a task, and then calls itself to do the task again. The recursion is
- repeated until a specified condition is met.
-
- Example
-
- pushall MACRO reg1,reg2,reg3,reg4,reg5,reg6
- IFNB <reg1> ;; If parameter not blank
- push reg1 ;; push one register and repeat
- pushall reg2,reg3,reg4,reg5,reg6
- ENDIF
- ENDM
- .
- .
- .
- pushall ax,bx,si,ds
- pushall cs,es
-
- In this example, the pushall macro repeatedly calls itself to push a
- register given in a parameter until no parameters are left to push. A
- variable number of parameters (up to six) can be given.
-
-
- 11.6.2 Nesting Macro Definitions
-
- One macro can define another. QuickAssembler does not process nested
- definitions until the outer macro has been called. Therefore, nested
- macros cannot be called until the outer macro has been called at least
- once. Macro definitions can be nested to any depth. Nesting is limited
- only by the amount of memory available when the source file is assembled.
- Using a macro to create similar macros can make maintenance easier. If you
- want to change all the macros, change the outer macro and it automatically
- changes the others.
-
- Example
-
- shifts MACRO opname ; Define macro that defines macros
- opname&s MACRO operand,rotates
- IF rotates LE 4
- REPT rotates
- opname operand,1 ;; One at a time is faster
- ENDM ;; for 4 or less on 8088/8086
- ELSE
- mov cl,rotates ;; Using CL is faster
- opname operand,cl ;; for more than 4 on 8088/8086
- ENDIF
- ENDM
- ENDM
-
- shifts ror ; Call macro to new macros
- shifts rol
- shifts shr
- shifts shl
- shifts rcl
- shifts rcr
- shifts sal
- shifts sar
- .
- .
- .
- shrs ax,5 ; Call defined macros
- rols bx,3
-
- This macro, when called as shown, creates macros for multiple shifts with
- each of the shift and rotate instructions. All the macro names are
- identical except for the instruction. For example, the macro for the SHR
- instruction is called shrs; the macro for the ROL instruction is called
- rols. If you want to enhance the macros by doing more parameter checking,
- you can modify the original macro. Doing so will change the created macros
- automatically. This macro uses the substitute operator, as described in
- Section 11.5.1.
-
-
- 11.6.3 Nesting Macro Calls
-
- Macro definitions can contain calls to other macros. Nested macro calls
- are expanded like any other macro call, but only when the outer macro is
- called.
-
- Example
-
- ex MACRO text,val ; Inner macro definition
- IF2
- %OUT The expression (text) has the value: &val
- ENDIF
- ENDM
-
- express MACRO expression ; Outer macro definition
- ex <expression>,%(expression)
- ENDM
- .
- .
- .
- express <4 + 2 * 7 - 3 MOD 4>
-
- The two sample macros enable you to print the result of a complex
- expression to the screen by using the %OUT directive, even though that
- directive expects text rather than an expression (see Section 12.1,
- "Sending Messages to the Standard Output Device"). Being able to see the
- value of an expression is convenient during debugging.
-
- Both macros are necessary. The express macro calls the ex macro, using
- operators to pass the expression both as text and as the value of the
- expression. With the call in the example, the assembler sends the
- following line to the standard output:
-
- The expression (4 + 2 * 7 - 3 MOD 4) has the value: 15
-
- You could get the same output by using only the ex macro, but you would
- have to type the expression twice and supply the macro operators in the
- correct places yourself. The express macro does this for you
- automatically. Notice that expressions containing spaces must still be
- enclosed in angle brackets. Section 11.5.2, "Literal-Text Operator,"
- explains why.
-
-
- 11.6.4 Redefining Macros
-
- Macros can be redefined. You do not need to purge the macro before
- redefining it. The new definition automatically replaces the old
- definition. If you redefine a macro from within the macro itself, make
- sure there are no statements or comments between the ENDM directive of the
- nested redefinition and the ENDM directive of the original macro.
-
- Example
-
- getasciiz MACRO
- .DATA
- max DB 80
- actual DB ?
- tmpstr DB 80 DUP (?)
- .CODE
- mov ah,0Ah
- mov dx,OFFSET max
- int 21h
- mov bl,actual
- xor bh,bh
- mov tmpstr[bx],0
- getasciiz MACRO
- mov ah,0Ah
- mov dx,OFFSET max
- int 21h
- mov bl,actual
- xor bh,bh
- mov tmpstr[bx],0
- ENDM
- ENDM
-
- This macro allocates data space the first time it is called, and then
- redefines itself so that it doesn't try to reallocate the data on
- subsequent calls.
-
-
- 11.6.5 Avoiding Inadvertent Substitutions
-
- QuickAssembler replaces all parameters when they occur with the
- corresponding argument, even if the substitution is inappropriate. For
- example, if you use a register name, such as AX or BH, as a parameter,
- QuickAssembler replaces all occurrences of that name when it expands the
- macro. If the macro definition contains statements that use the register,
- not the parameter, the macro will be incorrectly expanded. QuickAssembler
- will not warn you about using reserved names as macro parameters.
-
- QuickAssembler does give a warning if you use a reserved name as a macro
- name. You can ignore the warning, but be aware that the reserved name will
- no longer have its original meaning. For example, if you define a macro
- called ADD, the ADD instruction will no longer be available. Your ADD
- macro takes its place.
-
-
- 11.7 Managing Macros and Equates
-
- Macros and equates are often kept in a separate file and read into the
- assembler source file at assembly time. In this way, libraries of related
- macros and equates can be used by many different source files.
-
- The INCLUDE directive is used to read an include file into a source file.
- Memory can be saved by using the PURGE directive to delete the unneeded
- macros from memory.
-
-
- 11.7.1 Using Include Files
-
- The INCLUDE directive inserts source code from a specified file into the
- source file from which the directive is given.
-
- Syntax
-
- INCLUDE filespec
-
- The filespec must specify an existing file containing valid assembler
- statements. When the assembler encounters an INCLUDE directive, it opens
- the specified source file and begins processing its statements. When all
- statements have been read, QuickAssembler continues with the statement
- immediately following the INCLUDE directive.
-
- The filespec can be given either as a file name, or as a complete or
- relative file specification, including drive or directory name.
-
- If a complete or relative file specification is given, QuickAssembler
- looks for the include file only in the specified directory. If a file name
- is given without a directory or drive name, QuickAssembler looks for the
- file in the following order:
-
- 1. If paths are specified with the /I option, QuickAssembler looks for the
- include file in the specified directory or directories.
-
- 2. QuickAssembler looks for the include file in the current directory.
-
- 3. If an INCLUDE environment variable is defined, QuickAssembler looks for
- the include file in the directory or directories specified in the
- environment variable.
-
- Nested INCLUDE directives are allowed. QuickAssembler marks included
- statements with the letter "C" in assembly listings.
-
- Directories can be specified in INCLUDE path names with either the
- backslash (\) or the forward slash (/). This is for XENIX(R)
- compatibility.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Any standard code can be placed in an include file. However, include
- files are usually used only for macros, equates, and standard segment
- definitions. Standard procedures are usually assembled into separate
- object files and linked with the main source modules.
- ──────────────────────────────────────────────────────────────────────────
-
- Examples
-
- INCLUDE fileio.mac ; File name only; use with
- ; /I or environment
- INCLUDE b:\include\keybd.inc ; Complete file specification
-
- INCLUDE /usr/jons/include/stdio.mac ; Path name in XENIX format
-
- INCLUDE masm_inc\define.inc ; Partial path name in DOS format
- ; (relative to current directory)
-
-
- 11.7.2 Purging Macros from Memory
-
- The PURGE directive can be used to delete a currently defined macro from
- memory.
-
- Syntax
-
- PURGE macroname[[,macroname]]...
-
- Each macroname is deleted from memory when the directive is encountered at
- assembly time. Any subsequent call to that macro causes the assembler to
- generate an error.
-
- The PURGE directive is intended to clear memory space no longer needed by
- a macro. If a macro has been used to redefine a reserved name, the
- reserved name is restored to its previous meaning.
-
- The PURGE directive can be used to clear memory if a macro or group of
- macros is needed only for part of a source file.
-
- It is not necessary to purge a macro before redefining it. Any
- redefinition of a macro automatically purges the previous definition.
- Also, a macro can purge itself as long as the PURGE directive is on the
- last line of the macro.
-
- The PURGE directive works by redefining the macro to a null string.
- Therefore, calling a purged macro does not cause an error. The macro name
- is simply ignored.
-
- Examples
-
- GetStuff
- PURGE GetStuff
-
- These examples call a macro and then purge it. You might need to purge
- macros in this way if your system does not have enough memory to keep all
- of the macros needed for a source file in memory at the same time.
-
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 12: Controlling Assembly Output
-
-
- QuickAssembler has two ways of communicating results. It can write
- information to a listing or object file, or it can display messages to the
- standard output device (normally the screen).
-
- Both kinds of output can be controlled by menu commands and by source-file
- statements. This chapter explains the directives that directly control
- output through source-file statements.
-
-
- 12.1 Sending Messages to the Standard Output Device
-
- The %OUT directive instructs the assembler to display text to the program
- output window.
-
- Syntax
-
- %OUT text
-
- The text can be any line of ASCII characters. If you want to display
- multiple lines, you must use a separate %OUT directive for each line.
-
- The directive is useful for displaying messages at specific points of a
- long assembly. It can be used inside conditional-assembly blocks to
- display messages when certain conditions are met.
-
- The %OUT directive generates output for both assembly passes. The IF1 and
- IF2 directives can be used for control when the directive is processed.
- Macros that enable you to output the value of expressions are shown in
- Section 11.6.3, "Nesting Macro Calls."
-
- Example
-
- IF1
- %OUT First Pass - OK
- ENDIF
-
- This sample block could be placed at the end of a source file so that the
- message First Pass - OK would be displayed at the end of the first pass,
- but ignored on the second pass.
-
-
- 12.2 Controlling Page Format in Listings
-
- QuickAssembler provides several directives for controlling the page format
- of listings. These directives include the following:
-
- Directive Action
- ──────────────────────────────────────────────────────────────────────────
- TITLE Sets title for listings
-
- SUBTTL Sets title for sections in listings
-
- PAGE Sets page length and width, and controls page and
- section breaks
-
-
-
- 12.2.1 Setting the Listing Title
-
- The TITLE directive specifies a title to be used on each page of assembly
- listings. In editor-based listing files (the default inside the QC
- environment), the title is only printed once, at the top of the file.
-
- Syntax
-
- TITLE text
-
- The text can be any combination of characters up to 60 in length. The
- title is printed flush left on the second line of each page of the
- listing.
-
- If no TITLE directive is given, the title will be blank. No more than one
- TITLE directive per module is allowed.
-
- Example
-
- TITLE Graphics Routines
-
- This example sets the listing title. A page heading that reflects this
- title is shown below:
-
- Microsoft (R) QuickC with QuickAssembler Version 2.01 9/25/89 12:00:00
- Graphics Routines
- Page 1-2
-
-
- 12.2.2 Setting the Listing Subtitle
-
- The SUBTTL directive specifies the subtitle used on each page of assembly
- listings. In editor-based listing files (the default inside the QC
- environment), the subtitle is ignored.
-
- Syntax
-
- SUBTTL text
-
- The text can be any combination of characters up to 60 in length. The
- subtitle is printed flush left on the third line of the listing pages.
-
- If no SUBTTL directive is used, or if no text is given for a SUBTTL
- directive, the subtitle line is left blank.
-
- Any number of SUBTTL directives can be given in a program. Each new
- directive replaces the current subtitle with the new text. SUBTTL
- directives are often used just before a PAGE + statement, which creates a
- new section (see Section 12.2.3, "Controlling Page Breaks").
-
- Example
-
- SUBTTL Point Plotting Procedure
- PAGE +
-
- The example above creates a section title and then creates a page break
- and a new section. A page heading that reflects this title is shown below:
-
- Microsoft (R) QuickC with QuickAssembler Version 2.01 9/25/89 12:00:00
- Graphics Routines
- Page 3-1
- Point Plotting Procedure
-
-
- 12.2.3 Controlling Page Breaks
-
- The PAGE directive can be used to designate the line length and width for
- the program listing, to increment the section and adjust the section
- number accordingly, or to generate a page break in the listing. In
- editor-based listing files (the default inside the QC environment),
- page-break directives are ignored, except for the page width specifier.
-
- Syntax
-
- PAGE [[[[length]],width]]
- PAGE +
-
- If length and width are specified, the PAGE directive sets the maximum
- number of lines per page to length and the maximum number of characters
- per line to width. The length must be in the range of 10-255 lines. The
- default page length is 50 lines. The width must be in the range of 60-132
- characters. The default page width is 80 characters. With editor-based
- listing files, the default page width is 255. To specify the width without
- changing the default length, use a comma before width.
-
- If no argument is given, PAGE starts a new page in the program listing by
- copying a form-feed character to the file and generating new title and
- subtitle lines.
-
- If a plus sign follows PAGE, a page break occurs, the section number is
- incremented, and the page number is reset to 1. Program-listing page
- numbers have the following format:
-
- section-page
-
- The section is the section number within the module, and page is the page
- number within the section. By default, section and page numbers begin with
- 1-1. The SUBTTL directive and the PAGE directive can be used together to
- start a new section with a new subtitle. See Section 12.2.2, "Setting the
- Listing Subtitle," for an example.
-
- Example 1
-
- PAGE
-
- Example 1 creates a page break.
-
- Example 2
-
- PAGE 58,90
-
- Example 2 sets the maximum page length to 58 lines and the maximum width
- to 90 characters.
-
- Example 3
-
- PAGE ,132
-
- Example 3 sets the maximum width to 132 characters. The current page
- length (either the default of 50 or a previously set value) remains
- unchanged.
-
- Example 4
-
- PAGE +
-
- Example 4 creates a page break, increments the current section number, and
- sets the page number to 1. For example, if the preceding page was 3-6, the
- new page would be 4-1.
-
-
- 12.2.4 Naming the Module
-
- The assembler automatically uses the base file name of the source file as
- the name of the module. This name is used to identify error messages when
- you run the assembler from the command line.
-
-
- 12.3 Controlling the Contents of Listings
-
- QuickAssembler provides several directives for controlling what text will
- be shown in listings. The directives that control the contents of listings
- are shown below:
-
- Directive Action
- ──────────────────────────────────────────────────────────────────────────
- .LIST Lists statements in program listing
-
- .XLIST Suppresses listing of statements
-
- .LFCOND Lists false-conditional blocks in program listing
-
- .SFCOND Suppresses false-conditional listing
-
- .TFCOND Toggles false-conditional listing
-
- .LALL Includes macro expansions in program listing
-
- .SALL Suppresses listing of macro expansions
-
- .XALL Excludes comments from macro listing
-
-
-
- 12.3.1 Suppressing and Restoring Listing Output
-
- The .LIST and .XLIST directives specify which source lines are included in
- the program listing.
-
- Syntax
-
- .LIST
- .XLIST
-
- The .XLIST directive suppresses copying of subsequent source lines to the
- program listing. The .LIST directive restores copying. The directives are
- typically used in pairs to prevent a particular section of a source file
- from being copied to the program listing.
-
- The .XLIST directive overrides other listing directives, such as .SFCOND
- or .LALL.
-
- Example
-
- .XLIST ; Listing suspended here
- .
- .
- .
- .LIST ; Listing resumes here
- .
- .
- .
-
-
- 12.3.2 Controlling Listing of Conditional Blocks
-
- The .SFCOND, .LFCOND, and .TFCOND directives control whether
- false-conditional blocks should be included in assembly listings.
-
- Syntax
-
- .SFCOND
- .LFCOND
- .TFCOND
-
- The .SFCOND directive suppresses the listing of any subsequent conditional
- blocks whose condition is false. The .LFCOND directive restores the
- listing of these blocks. Like .LIST and .XLIST, conditional-listing
- directives can be used to suppress listing of conditional blocks in
- sections of a program.
-
- The .TFCOND directive toggles the current status of listing of conditional
- blocks. This directive can be used in conjunction with the /Sx option of
- the assembler. By default, conditional blocks are not listed on start-up.
- However, they will be listed on start-up if the /Sx option is given. This
- means that using /Sx reverses the meaning of the first .TFCOND directive
- in the source file. The /Sx option is discussed in Appendix B, Section
- B.14, "Listing False Conditionals."
-
- Example
-
- test1 EQU 0 ; Defined to make all conditionals false
-
- ; /Sx not used /Sx used
- .TFCOND
- IFNDEF test1 ; Listed Not listed
- test2 DB 128
- ENDIF
- .TFCOND
- IFNDEF test1 ; Not listed Listed
- test3 DB 128
- ENDIF
- .SFCOND
- IFNDEF test1 ; Not listed Not listed
-
- test4 DB 128
- ENDIF
- .LFCOND
- IFNDEF test1 ; Listed Listed
- test5 DB 128
- ENDIF
-
- In the example above, the listing status for the first two conditional
- blocks would be different, depending on whether the /X option was used.
- The blocks with .SFCOND and .LFCOND would not be affected by the /X
- option.
-
-
- 12.3.3 Controlling Listing of Macros
-
- The .LALL, .XALL, and .SALL directives control the listing of the expanded
- macro calls. The assembler always lists the full macro definition. The
- directives only affect expansion of macro calls.
-
- Syntax
-
- .LALL
- .XALL
- .SALL
-
- The .LALL directive causes QuickAssembler to list all the source
- statements in a macro expansion, including normal comments (preceded by a
- single semicolon) but not macro comments (preceded by a double semicolon).
-
- The .XALL directive causes QuickAssembler to list only those source
- statements in a macro expansion that generate code or data. For instance,
- comments, equates, and segment definitions are ignored.
-
- The .SALL directive causes QuickAssembler to suppress listing of all macro
- expansions. The listing shows the macro call, but not the source lines
- generated by the call.
-
- The .XALL directive is in effect when QuickAssembler first begins
- execution.
-
- Example
-
- tryout MACRO param
- ;; Macro comment
- ; Normal comment
- it EQU 3 ; No code or data
- ASSUME es:_DATA ; No code or data
- DW param ; Generates data
- mov ax,it ; Generates code
- ENDM
- .
- .
- .
- .LALL
- tryout 6 ; Call with .LALL
-
- .XALL
- tryout 6 ; Call with .XALL
-
- .SALL
- tryout 6 ; Call with .SALL
-
- The macro calls in the example generate the following listing lines:
-
- .LALL
- tryout 6 ; Call with .LALL
- 1 ; Normal comment
- = 0003 1 it EQU 3 ; No code or data
- 1 ASSUME es:_TEXT ; No code or data
- 0015 0006 1 DW 6 ; Generates data
- 0017 B8 0003 1 mov ax,it ; Generates code
-
-
- .XALL
- tryout 6 ; Call with .XALL
- 001A 0006 1 DW 6 ; Generates data
- 001C B8 0003 1 mov ax,it ; Generates code
-
-
- .SALL
- tryout 6 ; Call with .SALL
-
- Notice that the macro comment is never listed in macro expansions. Normal
- comments are listed only with the .LALL directive.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- PART 3: Using Instructions
-
-
- Part 3 of the Programmer's Guide (comprising Chapters 13-18) explains
- how to use instructions in assembly-language source code. Instructions
- constitute the actual steps of your program and are translated into
- machine-code statements that the processor executes at run time.
-
- Part 3 is organized topically, with related instructions discussed
- together. Chapter 13 explains the instructions for moving data from one
- location to another. The instructions for performing calculations on
- numbers and bits are covered in Chapter 14.
-
- The 8086-family processors provide four major types of instructions for
- controlling program flow, as described in Chapter 15. Chapter 16
- explains the instructions and techniques for processing strings. The
- 8087-family coprocessors and their instructions are explained in Chapter
- 17. Finally, Chapter 18 summarizes the instructions available for
- processor control.
-
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 13: Loading, Storing, and Moving Data
-
-
- The 8086-family processors provide several instructions for loading,
- storing, or moving various kinds of data. Among the types of transferable
- data are variables, pointers, and flags. Data can be moved to and from
- registers, memory, ports, and the stack. This chapter explains the
- instructions for moving data from one location to another.
-
-
- 13.1 Transferring Data
-
- Moving data is one of the most common tasks in assembly-language
- programming. Data can be moved between registers or between memory and
- registers. Immediate data can be loaded into registers or into memory.
-
- Furthermore, all memory-to-memory operations are illegal. To move data
- from one memory location to another, you must first move the data to an
- intermediate register.
-
-
- 13.1.1 Copying Data
-
- The MOV instruction is the most common method of moving data. This
- instruction can be thought of as a copy instruction, since it always
- copies the source operand to the destination operand. Immediately after a
- MOV instruction, the source and destination operands both contain the same
- value. The old value in the destination operand is destroyed.
-
- Syntax
-
- MOV {register | memory},{register | memory | immediate}
-
- Example 1
-
- mov ax,7 ; Immediate to register
- mov mem,7 ; Immediate to memory direct
- mov mem[bx],7 ; Immediate to memory indirect
-
- mov mem,ds ; Segment register to memory
- mov mem,ax ; Register to memory direct
- mov mem[bx],ax ; Register to memory indirect
-
- mov ax,mem ; Memory direct to register
- mov ax,mem[bx] ; Memory indirect to register
- mov ds,mem ; Memory to segment register
-
- mov ax,bx ; Register to register
- mov ds,ax ; General register to segment register
- mov ax,ds ; Segment register to general register
-
- The statements in Example 1 illustrate each type of memory move that can
- be done with a single instruction. Example 2 illustrates several common
- types of moves that require two instructions.
-
- Example 2
-
- ; Move immediate to segment register
- mov ax,DGROUP ; Load immediate to general register
- mov ds,ax ; Store general register to segment register
-
- ; Move memory to memory
- mov ax,mem1 ; Load memory to general register
- mov mem2,ax ; Store general register to memory
-
- ; Move segment register to segment register
- mov ax,ds ; Load segment register to general register
- mov es,ax ; Store general register to segment register
-
-
- 13.1.2 Exchanging Data
-
- The XCHG (Exchange) instruction exchanges the data in the source and
- destination operands. Data can be exchanged between registers or between
- registers and memory.
-
- Syntax
-
- XCHG {register | memory},{register | memory}
-
- Examples
-
- xchg ax,bx ; Put AX in BX and BX in AX
- xchg memory,ax ; Put "memory" in AX and AX in "memory"
-
-
- 13.1.3 Looking Up Data
-
- The XLAT (Translate) instruction is used to load data from a table in
- memory. The instruction is useful for translating bytes from one coding
- system to another.
-
- Syntax
-
- XLAT[[B]] [[[[segment:]]memory]]
-
- The BX register must contain the address of the start of the table. By
- default, the DS register contains the segment of the table, but a segment
- override can be used to specify a different segment. The operand need not
- be given except when specifying a segment override.
-
- Before the XLAT instruction is called, the AL register should contain a
- value that points into the table (the start of the table is considered 0).
- After the instruction is called, AL will contain the table value pointed
- to. For example, if AL contains 7, the 8th byte of the table will be
- placed in the AL register.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE For compatibility with Intel 80386 mnemonics, QuickAssembler
- recognizes XLATB as a synonym for XLAT. In the Intel syntax, XLAT requires
- an operand; XLATB does not allow one. Quick-Assembler never requires an
- operand, but always allows one.
- ──────────────────────────────────────────────────────────────────────────
-
- Example
-
- ; Table of Hexadecimal digits
- hex DB "0123456789ABCDEF"
- convert DB "You pressed the key with ASCII code "
- key DB ?,?,"h",13,10,"$"
- .CODE
- .
- .
- .
- mov ah,8 ; Get a key in AL
- int 21h ; Call DOS
- mov bx,OFFSET hex ; Load table address
- mov ah,al ; Save a copy in high byte
- and al,00001111b ; Mask out top character
- xlat ; Translate
- mov key[1],al ; Store the character
- mov cl,12 ; Load shift count
- shr ax,cl ; Shift high character into position
- xlat ; Translate
- mov key,al ; Store the character
- mov dx,OFFSET convert ; Load message
- mov ah,9 ; Display it
- int 21h ; Call DOS
-
- This example looks up hexadecimal characters in a table in order to
- convert an eight-bit binary number to a string representing a hexadecimal
- number.
-
-
- 13.1.4 Transferring Flags
-
- The 8086-family processors provide instructions for loading and storing
- flags in the AH register.
-
- Syntax
-
- LAHF
- SAHF
-
- The status of the lower byte of the flags register can be saved to the AH
- register with LAHF and then later restored with SAHF. If you need to save
- and restore the entire flags register, use PUSHF and POPF, as described in
- Section 13.4.3, "Saving Flags on the Stack."
-
- SAHF is often used with a coprocessor to transfer coprocessor control
- flags to processor control flags. Section 17.7, "Controlling Program
- Flow," explains and illustrates this technique.
-
-
- 13.2 Converting between Data Sizes
-
- Since moving data between registers of different sizes is illegal, you
- must take special steps if you need to extend a register value to a larger
- register or register pair.
-
- The procedure is different for signed and unsigned values. The processor
- cannot tell the difference between signed and unsigned numbers; the
- programmer has to understand this difference and program accordingly.
-
-
- 13.2.1 Extending Signed Values
-
- The CBW (Convert Byte to Word) and CWD (Convert Word to Doubleword)
- instructions are provided to sign-extend values. Sign-extending means
- copying the sign bit of the unextended operand to all bits of the extended
- operand.
-
- Syntax
-
- CBW
- CWD
-
- The CBW instruction converts an 8-bit signed value in AL to a 16-bit
- signed value in AX. The CWD instruction is similar except that it
- sign-extends a 16-bit value in AX to a 32-bit value in the DX:AX register
- pair. Both instructions work only on values in the accumulator register.
-
- Example
-
- .DATA
- mem8 DB -5
- mem16 DW -5
- .CODE
- .
- .
- .
- mov al,mem8 ; Load 8-bit -5 (FBh)
- cbw ; Convert to 16-bit -5 (FFFBh) in AX
-
- mov ax,mem16 ; Load 16-bit -5 (FFFBh)
- cwd ; Convert to 32-bit -5 (FFFF:FFFBh)
- ; in DX:AX
-
-
- 13.2.2 Extending Unsigned Values
-
- To extend unsigned numbers, set the value of the upper register to 0.
-
- Example
-
- .DATA
- mem8 DB 251
- mem16 DW 251
- .CODE
- .
- .
- .
- mov al,mem8 ; Load 251 (FBh) from 8-bit memory
- xor ah,ah ; Zero upper half (AH)
-
- mov ax,mem16 ; Load 251 (FBh) from 16-bit memory
- xor dx,dx ; Zero upper half (DX)
-
-
- 13.3 Loading Pointers
-
- The 8086-family processors provide several instructions for loading
- pointer values into registers or register pairs. They can be used to load
- either near or far pointers.
-
-
- 13.3.1 Loading Near Pointers
-
- The LEA instruction loads a near pointer into a specified register.
-
- Syntax
-
- LEA register,memory
-
- The destination register may be any general-purpose register. The source
- operand may be any memory operand. The effective address of the source
- operand is placed in the destination register.
-
- The LEA instruction can be used to calculate the effective address of a
- direct memory operand, but this is usually not efficient, since the
- address of a direct memory operand is a constant known at assembly time.
- For example, the following statements have the same effect, but the second
- version is faster:
-
- lea dx,string ; Load effective address - slow
- mov dx,OFFSET string ; Load offset - fast
-
- The LEA instruction is more useful for calculating the address of indirect
- memory operands:
-
- lea dx,string[si] ; Load effective address
-
-
- 13.3.2 Loading Far Pointers
-
- The LDS and LES instructions load far pointers.
-
- Syntax
-
- LDS register,memory
- LES register,memory
-
- The memory address being pointed to is specified in the source operand,
- and the register where the offset will be stored is specified in the
- destination operand.
-
- The address must be stored in memory with the segment in the upper word
- and the offset in the lower word. The segment register where the segment
- will be stored is specified in the instruction name. For example, LDS puts
- the segment in DS, and LES puts the segment in ES. The instructions are
- often used with string instructions, as explained in Chapter 16,
- "Processing Strings."
-
- Example
-
- .DATA
- string DB "This is a string."
- fpstring DD string ; Far pointer to string
- pointers DD 100 DUP (?)
- .CODE
- .
- .
- .
- les di,fpstring ; Put address in ES:DI pair
- lds si,pointers[bx] ; Put address in DS:SI pair
-
-
- 13.4 Transferring Data to and from the Stack
-
- A "stack" is an area of memory for storing temporary data. Unlike other
- segments in which data is stored starting from low memory, data on the
- stack is stored in reverse order starting from high memory.
-
- Initially, the stack is an uninitialized segment of a finite size. As data
- is added to the stack at run time, the stack grows downward from high
- memory to low memory. When items are removed from the stack, it shrinks
- upward from low memory to high memory.
-
- The stack has several purposes in the 8086-family processors. The CALL,
- INT, RET, and IRET instructions automatically use the stack to store the
- calling addresses of procedures and interrupts (see Sections 15.3, "Using
- Procedures," and 15.4, "Using Interrupts"). You can also use the PUSH and
- POP instructions and their variations to store values on the stack.
-
-
- 13.4.1 Pushing and Popping
-
- In 8086-family processors, the SP (Stack Pointer) register always points
- to the current location in the stack. The PUSH and POP instructions use
- the SP register to keep track of the current position in the stack.
-
- The values pointed to by the BP and SP registers are relative to the SS
- (Stack Segment) register. The BP register is often used to point to the
- base of a frame of reference (a stack frame) within the stack.
-
- Syntax
-
- PUSH {register | memory}
- POP {register | memory}
- PUSH immediate (80186-80386 only)
-
- The PUSH instruction is used to store a two-byte operand on the stack. The
- POP instruction is used to retrieve a previously pushed value. When a
- value is pushed onto the stack, the SP register is decreased by 2. When a
- value is popped off the stack, the SP register is increased by 2. Although
- the stack always contains word values, the SP register points to bytes.
- Thus, SP changes in multiples of 2.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The 8088 and 8086 processors differ from later Intel processors in
- how they push and pop the SP register. If you give the statement push sp
- with the 8088 or 8086, the word pushed will be the word in SP after the
- push operation.
- ──────────────────────────────────────────────────────────────────────────
-
- Figure 13.1 illustrates how pushes and pops change the SP register.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 13.4.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The PUSH and POP instructions are almost always used in pairs. Words are
- popped off the stack in reverse order from the order in which they are
- pushed onto the stack. You should normally do the same number of pops as
- pushes to return the stack to its original status. However, it is possible
- to return the stack to its original status by subtracting the correct
- number of words from the SP register.
-
- Values on the stack can be accessed by using indirect memory operands with
- BP as the base register.
-
- Example
-
- mov bp,sp ; Set stack frame
- push ax ; Push first; SP = BP + 2
- push bx ; Push second; SP = BP + 4
- push cx ; Push third; SP = BP + 6
- .
- .
- .
- mov ax,[bp+6] ; Put third in AX
- mov bx,[bp+4] ; Put second in BX
- mov cx,[bp+2] ; Put first in CX
- .
- .
- .
- sub sp,6 ; Restore stack pointer
- ; two bytes per push
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, the PUSH instruction can be given with
- an immediate operand. For example, the following statement is legal on the
- 80186, 80286, and 80386 processors:
-
- push 7 ; 3 clocks on 80286
-
- This statement is faster than the following equivalent statements, which
- are required on the 8088 or 8086:
-
- mov ax,7 ; 2 clocks on 80286
- push ax ; 3 clocks on 80286
-
-
- 13.4.2 Using the Stack
-
- The stack can be used to store temporary data. For example, in the
- Microsoft calling convention, the stack is used to pass arguments to a
- procedure. The arguments are pushed onto the stack before the call. The
- procedure retrieves and uses them. Then the stack is restored to its
- original position at the end of the procedure. The stack can also be used
- to store variables that are local to a procedure. Both of these techniques
- are discussed in Section 15.3, "Using Procedures."
-
- Another common use of the stack is to store temporary data when there are
- no free registers available or when a particular register must hold more
- than one value. For example, the CX register usually holds the count for
- loops. If two loops are nested, the outer count is loaded into CX at the
- start. When the inner loop starts, the outer count is pushed onto the
- stack and the inner count loaded into CX. When the inner loop finishes,
- the original count is popped back into CX.
-
- Example
-
- mov cx,10 ; Load outer loop counter
- outer: .
- . ; Start outer loop task
- .
- push cx ; Save outer loop value
- mov cx,20 ; Load inner loop counter
- inner: .
- . ; Do inner loop task
- .
- loop inner
- pop cx ; Restore outer loop counter
- .
- . ; Continue outer loop task
- .
- loop outer
-
-
- 13.4.3 Saving Flags on the Stack
-
- Flags can be pushed and popped onto the stack using the PUSHF and POPF
- instructions.
-
- Syntax
-
- PUSHF
- POPF
-
- These instructions are sometimes used to save the status of flags before a
- procedure call and then to restore the same status after the procedure.
- They can also be used within a procedure to save and restore the flag
- status of the caller.
-
- Example
-
- pushf
- call systask
- popf
-
-
- 13.4.4 Saving All Registers on the Stack
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, the PUSHA and POPA instructions were
- implemented to push or pop all the general-purpose registers with one
- instruction.
-
- Syntax
-
- PUSHA
- POPA
-
- These instructions can be used to save the status of all registers before
- a procedure call and then to restore them after the return. Using PUSHA
- and POPA instructions is significantly faster and takes fewer bytes of
- code than pushing and popping each register individually.
-
- The registers are pushed in the following order: AX, CX, DX, BX, SP, BP,
- SI, and DI. The SP word pushed is the value before the first register is
- pushed. The registers are popped in the opposite order.
-
- Example
-
- pusha
- call systask
- popa
-
-
- 13.5 Transferring Data to and from Ports
-
- "Ports" are the gateways between hardware devices and the processor. Each
- port has a unique number through which it can be accessed. Ports can be
- used for low-level communication with devices, such as disks, the video
- display, or the keyboard. The OUT instruction is used to send data to a
- port; the IN instruction receives data from a port.
-
- Syntax
-
- IN accumulator,{portnumber | DX}
- OUT {portnumber | DX},accumulator
-
- When using the IN and OUT instructions, the number of the port can either
- be an eight-bit immediate value or the DX register. You must use DX for
- ports with a number higher than 256. The value to be received from the
- port must be in the accumulator register (AX for word values or AL for
- byte values).
-
- When using the IN instruction, the number of the port is given as the
- source operand and the value to be sent to the port is the destination
- operand. When using the OUT instruction, the number of the port is given
- as the destination operand and the value to be sent to the port is the
- source operand.
-
- In applications programming, most communication with hardware is done with
- DOS or ROM-BIOS calls. Ports are more often used in systems programming.
- Since systems programming is beyond the scope of this manual and since
- ports differ depending on hardware, the IN and OUT instructions are not
- explained in detail here.
-
- Example
-
- ; Actual values are hardware dependent
- sound EQU 61h ; Port to chip that controls speaker
- timer EQU 42h ; Port to chip that pulses speaker
- on EQU 00000011b ; Bits 0 and 1 turn on speaker
-
- in al,sound ; Get current port setting
- or al,on ; Turn on speaker and connect timer
- out sound,al ; Put value back in port
-
- mov al,50 ; Start at 50
- sounder: out timer,al ; Send byte to timer port...
-
- mov cx,2000 ; Loop 2000 times to delay
- hold: loop hold
-
- dec al ; Go down one step
- jnz sounder ; Repeat for each step
-
- in al,sound ; Get port value
- and al,NOT on ; Turn it back off
- out sound,al ; Put it back in port
-
- This example creates a sound of ascending frequency on the IBM PC and
- IBM-compatible computers. The technique of making sound or the port values
- used may be different on other hardware.
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, instructions were implemented to send
- strings of data to and from ports. The instructions are INS, INSB, INSW,
- OUTS, OUTSB, and OUTSW. The operation of these instructions is much like
- the operation of other string instructions. They are discussed in Section
- 16.7, "Transferring Strings to and from Ports."
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 14: Doing Arithmetic and Bit Manipulations
-
-
- The 8086-family processors provide instructions for doing calculations on
- byte, word, and doubleword values. Operations include addition,
- subtraction, multiplication, and division. You can also do calculations at
- the bit level. This includes the AND, OR, XOR, and NOT logical operations.
- Bits can also be shifted or rotated to the right or left.
-
- This chapter tells you how to use the instructions that do calculations on
- numbers and bits.
-
-
- 14.1 Adding
-
- The ADD, ADC, and INC instructions are used for adding and incrementing
- values.
-
- Syntax
-
- ADD {register | memory},{register | memory | immediate}
- ADC {register | memory},{register | memory | immediate}
- INC {register | memory}
-
- These instructions can work directly on 8-bit or 16-bit values. They can
- also be used in combination to do calculations on values that are too
- large to be held in a single register (such as 32-bit values). When used
- with AAA and DAA, they can be used to do calculations on binary coded
- decimal (BCD) numbers, as described in Section 14.5.
-
-
- 14.1.1 Adding Values Directly
-
- The ADD and INC instructions are used for adding to values in registers or
- memory.
-
- The INC instruction takes a single register or memory operand. The value
- of the operand is incremented. The value is treated as an unsigned
- integer, so the carry flag is not updated for signed carries.
-
- The ADD instruction adds values given in source and destination operands.
- The destination can be either a register or a memory operand. Its contents
- will be destroyed by the operation. The source operand can be an
- immediate, memory, or register operand. Since memory-to-memory operations
- are never allowed, the source and destination operands can never both be
- memory operands.
-
- The result of the operation is stored in the source operand. The operands
- can be either 8-bit or 16-bit, but both must be the same size.
-
- An addition operation can be interpreted as addition of either signed
- numbers or unsigned numbers. It is the programmer's responsibility to
- decide how the addition should be interpreted and to take appropriate
- action if the sum is too large for the destination operand. When an
- addition overflows the possible range for signed numbers, the overflow
- flag is set. When an addition overflows the range for unsigned numbers,
- the carry flag is set.
-
- There are two ways to take action on an overflow: you can use the JO or
- JNO instruction to direct program flow to or around instructions that
- handle the overflow (see Section 15.1.2.3, "Testing Bits and Jumping").
- You can also use the INTO instruction to trigger the overflow interrupt
- (interrupt 4) if the overflow flag is set. This requires writing an
- interrupt handler for interrupt 4, since the DOS overflow routine simply
- returns without taking any action. Section 15.4.2, "Defining and
- Redefining Interrupt Routines," gives a sample of an overflow interrupt
- handler.
-
- Example
-
- .DATA
- mem8 DB 39
- .CODE
- .
- .
- . ; unsigned signed
- mov al,26 ; Start with register 26 26
- inc al ; Increment 1 1
- add al,76 ; Add immediate + 76 76
- ; ---- ----
- ; 103 103
- add al,mem8 ; Add memory + 39 39
- ; ---- ----
- mov ah,al ; Copy to AH 142 -114+overflow
- add al,ah ; Add register 142
- ; ----
- ; 28+carry
-
- This example shows 8-bit addition. When the sum exceeds 127, the overflow
- flag is set. A JO (Jump On Overflow) or INTO (Interrupt On Overflow)
- instruction at this point could transfer control to error-recovery
- statements. When the sum exceeds 255, the carry flag is set. A JC (Jump On
- Carry) instruction at this point could transfer control to error-recovery
- statements.
-
-
- 14.1.2 Adding Values in Multiple Registers
-
- The ADC (Add with Carry) instruction makes it possible to add numbers
- larger than can be held in a single register.
-
- The ADC instruction adds two numbers in the same fashion as the ADD
- instruction, except that the value of the carry flag is included in the
- addition. If a previous calculation has set the carry flag, then 1 will be
- added to the sum of the numbers. If the carry flag is not set, the ADC
- instruction has the same effect as the ADD instruction.
-
- When adding numbers in multiple registers, the carry flag should be
- ignored for the least-significant portion, but taken into account for the
- most-significant portion. This can be done by using the ADD instruction
- for the least-significant portion and the ADC instruction for
- most-significant portion.
-
- You can add and carry repeatedly inside a loop for calculations that
- require more than two registers. Use the ADC instruction in each
- iteration, but turn off the carry flag with the CLC (Clear Carry Flag)
- instruction before entering the loop so that it will not be used for the
- first iteration. You could also do the first add outside the loop.
-
- Example
-
- .DATA
- mem32 DD 316423
- .CODE
- .
- .
- .
- mov ax,43981 ; Load immediate 43981
- sub dx,dx ; into DX:AX
- add ax,WORD PTR mem32[0] ; Add to both + 316423
- adc dx,WORD PTR mem32[2] ; memory words ------
- ; Result in DX:AX 360404
-
-
- 14.2 Subtracting
-
- The SUB, SBB, DEC, and NEG instructions are used for subtracting and
- decrementing values.
-
- Syntax
-
- SUB {register | memory},{register | memory | immediate}
- SBB {register | memory},{register | memory | immediate}
- DEC {register | memory}
- NEG {register | memory}
-
- These instructions can work directly on 8-bit or 16-bit values. They can
- also be used in combination to do calculations on values too large to be
- held in a single register. When used with AAA and DAA, they can be used to
- do calculations on BCD numbers, as described in Section 14.5,
- "Calculating with Binary Coded Decimals."
-
-
- 14.2.1 Subtracting Values Directly
-
- The SUB and DEC instructions are used for subtracting from values in
- registers or memory. A related instruction, NEG (Negate), reverses the
- sign of a number.
-
- The DEC instruction takes a single register or memory operand. The value
- of the operand is decremented. The value is treated as an unsigned
- integer, so the carry flag is not updated for signed borrows.
-
- The NEG instruction takes a single register or memory operand. The sign of
- the value of the operand is reversed. The NEG instruction should only be
- used on signed numbers.
-
- The SUB instruction subtracts the values given in the source operand from
- the value of the destination operand. The destination can be either a
- register or a memory operand. It will be destroyed by the operation. The
- source operand can be an immediate, memory, or register operand. It will
- not be destroyed by the operation. Since memory-to-memory operations are
- never allowed, the source and destination operands cannot both be memory
- operands.
-
- The result of the operation is stored in the source operand. The operands
- can be either 8-bit or 16-bit, but both must be the same size.
-
- A subtraction operation can be interpreted as subtraction of either signed
- numbers or unsigned numbers. It is the programmer's responsibility to
- decide how the subtraction should be interpreted and to take appropriate
- action if the result is too small for the destination operand. When a
- subtraction overflows the possible range for signed numbers, the carry
- flag is set. When a subtraction underflows the range for unsigned numbers
- (becomes negative), the sign flag is set.
-
- Example
-
- .DATA
- mem8 DB 122
- .CODE
- .
- .
- . ; signed unsigned
- mov al,95 ; Load register 95 95
- dec al ; Decrement - 1 - 1
- sub al,23 ; Subtract immediate - 23 - 23
- ; ---- ----
- ; 71 71
- sub al,mem8 ; Subtract memory - 122 - 122
- ; ---- ----
- ; - 51 205+sign
-
- mov ah,119 ; Load register 119
- sub al,ah ; and subtract - 51
- ; ----
- ; 86+overflow
-
- This example shows 8-bit subtraction. When the result goes below 0, the
- sign flag is set. A JS (Jump On Sign) instruction at this point could
- transfer control to error-recovery statements. When the result goes below
- -128, the carry flag is set. A JC (Jump On Carry) instruction at this
- point could transfer control to error-recovery statements.
-
-
- 14.2.2 Subtracting with Values in Multiple Registers
-
- The SBB (Subtract with Borrow) instruction makes it possible to subtract
- from numbers larger than can be held in a single register.
-
- The SBB instruction subtracts two numbers in the same fashion as the SUB
- instruction except that the value of the carry flag is included in the
- subtraction. If a previous calculation has set the carry flag, then 1 will
- be subtracted from the result. If the carry flag is not set, the SBB
- instruction has the same effect as the SUB instruction.
-
- When subtracting numbers in multiple registers, the carry flag should be
- ignored for the least-significant portion, but taken into account for the
- most-significant portion. This can be done by using the SUB instruction
- for the least-significant portion and the SBB instruction for
- most-significant portions.
-
- You can subtract and borrow repeatedly inside a loop for calculations that
- require more than two registers. Use the SBB instruction in each
- iteration, but turn off the carry flag with the CLC (Clear Carry Flag)
- instruction before entering the loop so that it will not be used for the
- first iteration. You could also do the first subtraction outside the loop.
-
- Example
-
- .DATA
- mem32a DD 316423
- mem32b DD 156739
- .CODE
- .
- .
- .
- mov ax,WORD PTR mem32a[0] ; Load mem32 316423
- mov dx,WORD PTR mem32a[2] ; into DX:AX
- sub ax,WORD PTR mem32b[0] ; Subtract low 156739
- sbb dx,WORD PTR mem32b[2] ; then high ------
- ; Result in DX:AX 159684
-
-
- 14.3 Multiplying
-
- The MUL and IMUL instructions are used to multiply numbers. The MUL
- instruction should be used for unsigned numbers; the IMUL instruction
- should be used for signed numbers. This is the only difference between the
- two.
-
- Syntax
-
- MUL {register | memory}
- IMUL {register | memory}
-
- The multiply instructions require that one of the factors be in the
- accumulator register (AL for 8-bit numbers or AX for 16-bit numbers). This
- register is implied; it should not be specified in the source code. Its
- contents will be destroyed by the operation.
-
- The other factor to be multiplied must be specified in a single register
- or memory operand. The operand will not be destroyed by the operation,
- unless it is DX, AX, AH, or AL. A number may be squared by loading it into
- the accumulator, and then executing a multiplication instruction with the
- accumulator as the operand.
-
- Note that multiplying two 8-bit numbers will produce a 16-bit number. If
- the product is a 16-bit number, it will be placed in AX and the overflow
- and carry flags will be set.
-
- Similarly, multiplying two 16-bit numbers will produce a 32-bit number in
- the DX:AX register pair. If the product is a 32-bit number, the
- least-significant bits will be in AX, the most-significant bits will be in
- DX, and the overflow and carry flags will be set.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Multiplication is one of the slower operations on 8086-family
- processors (especially the 8086 and 8088). Multiplying by certain common
- constants is often faster when done by shifting bits (see Section 14.7.1,
- "Multiplying and Dividing by Constants").
- ──────────────────────────────────────────────────────────────────────────
-
- Examples
-
- .DATA
- mem16 DW -30000
- .CODE
- .
- .
- . ; 8-bit unsigned multiply
- mov al,23 ; Load AL 23
- mov bl,24 ; Load BL * 24
- mul bl ; Multiply BL -----
- ; 16-bit signed multiply
- mov ax,50 ; Load AX 50
- ; -30000
- imul mem16 ; Multiply memory -----
- ; Product in DX:AX -1500000
- ; overflow and carry set
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, the IMUL instruction has two additional
- syntaxes that allow for 16-bit multiples that produce a 16-bit product.
-
- Syntax
-
- IMUL register16, immediate
- IMUL register16, memory16, immediate
-
- You can specify a 16-bit immediate value as the source instruction and a
- word register as the destination operand. The product appears in the
- destination operand. The 16-bit result will be placed in the destination
- operand. If the product is too large to fit in 16 bits, the carry and
- overflow flags will be set. In this context, IMUL can be used for either
- signed or unsigned multiplication, since the 16-bit product is the same.
-
- You can also specify three operands for IMUL. The first operand must be a
- 16-bit register operand, the second a 16-bit memory operand, and the third
- a 16-bit immediate operand. The second and third operands are multiplied
- and the product stored in the first operand.
-
- With both of these syntaxes, the carry and overflow flags will be set if
- the product is too large to fit in 16 bits. The IMUL instruction with
- multiple operands can be used for either signed or unsigned
- multiplication, since the 16-bit product is the same in either case. If
- you need to get a 32-bit result, you must use the single-operand version
- of MUL or IMUL.
-
- Examples
-
- imul dx,456 ; Multiply DX times 456
- imul ax,[bx],6 ; Multiply the value pointed to by BX
- ; times 6 and put the result in AX
-
-
- 14.4 Dividing
-
- The DIV and IDIV instructions are used to divide integers. Both a quotient
- and a remainder are returned. The DIV instruction should be used for
- unsigned integers; the IDIV instruction should be used for signed
- integers. This is the only difference between the two.
-
- Syntax
-
- DIV {register | memory}
- IDIV {register | memory}
-
- To divide a 16-bit number by an 8-bit number, put the number to be divided
- (the dividend) in the AX register. The contents of this register will be
- destroyed by the operation. Specify the dividing number (the divisor) in
- any 8-bit memory or register operand (except AL or AH). This operand will
- not be changed by the operation. After the multiplication, the result
- (quotient) will be in AL and the remainder will be in AH.
-
- To divide a 32-bit number by a 16-bit number, put the dividend in the
- DX:AX register pair. The least-significant bits go in AX. The contents of
- these registers will be destroyed by the operation. Specify the divisor in
- any 16-bit memory or register operand (except AX or DX). This operand will
- not be changed by the operation. After the division, the quotient will be
- in AX and the remainder will be in DX.
-
- To divide a 16-bit number by a 16-bit number, you must first sign-extend
- or zero-extend (see Section 13.2, "Converting between Data Sizes") the
- dividend to 32 bits; then divide as described above. You cannot divide a
- 32-bit number by another 32-bit number.
-
- If division by zero is specified, or if the quotient exceeds the capacity
- of its register (AL or AX), the processor automatically generates an
- interrupt 0. By default, the program terminates and returns to DOS. This
- problem can be handled in two ways: check the divisor before division and
- go to an error routine if you can determine it to be invalid, or write
- your own interrupt routine to replace the processor's interrupt 0 routine.
- See Section 15.4 for more information on interrupts.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Division is one of the slower operations on 8086-family processors
- (especially the 8086 and 8088). Dividing by common constants that are
- powers of two is often faster when done by shifting bits, as described in
- Section 14.7.1, "Multiplying and Dividing by Constants."
- ──────────────────────────────────────────────────────────────────────────
-
- Examples
-
- .DATA
-
- mem16 DW -2000
- mem32 DD 500000
- .CODE
- .
- . ; Divide 16-bit unsigned by 8-bit
- .
- mov ax,700 ; Load dividend 700
- mov bl,36 ; Load divisor DIV 36
- div bl ; Divide BL -----
- ; Quotient in AL 19
- ; Remainder in AH 16
-
- ; Divide 32-bit signed by 16-bit
-
- mov ax,WORD PTR mem32[0] ; Load into DX:AX
- mov dx,WORD PTR mem32[2] ; 500000
- idiv mem16 ; DIV -2000
- ; Divide memory ------
- ; Quotient in AX -250
- ; Remainder in DX 0
-
- ; Divide 16-bit signed by 16-bit
-
- mov ax,WORD PTR mem16 ; Load into AX -2000
- cwd ; Extend to DX:AX
- mov bx,-421 ; DIV -421
- idiv bx ; Divide by BX -----
- ; Quotient in AX 4
- ; Remainder in DX -316
-
-
- 14.5 Calculating with Binary Coded Decimals
-
- The 8086-family processors provide several instructions for adjusting BCD
- numbers. The BCD format is seldom used for applications programming in
- assembly language. Programmers who wish to use BCD numbers usually use a
- high-level language. However, BCD instructions are used to develop
- compilers, function libraries, and other systems tools.
-
- Since systems programming is beyond the scope of this manual, this section
- provides only a brief overview of calculations on the two kinds of BCD
- numbers, unpacked and packed.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Intel mnemonics use the term "ASCII" to refer to unpacked BCD
- numbers and "decimal" to refer to packed BCD numbers. Thus AAA (ASCII
- Adjust After Addition) adjusts unpacked numbers, while DAA (Decimal Adjust
- After Addition) adjusts packed numbers.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 14.5.1 Unpacked BCD Numbers
-
- Unpacked BCD numbers are made up of bytes containing a single decimal
- digit in the lower four bits of each byte. The 8086-family processors
- provide instructions for adjusting unpacked values with the four
- arithmetic operations──addition, subtraction, multiplication, and
- division.
-
- To do arithmetic on unpacked BCD numbers, you must do the eight-bit
- arithmetic calculations on each digit separately. The result should always
- be in the AL register. After each operation, use the corresponding BCD
- instruction to adjust the result. The ASCII-adjust instructions do not
- take an operand. They always work on the value in the AL register.
-
- When a calculation using two one-digit values produces a two-digit result,
- the ASCII-adjust instructions put the first digit in AL and the second in
- AH. If the digit in AL needs to carry to or borrow from the digit in AH,
- the carry and auxiliary carry flags are set.
-
- The four ASCII-adjust instructions are described below:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- AAA Adjusts after an addition operation. For example, to
- add 9 and 3, use the following lines:
-
-
- mov ax,9 ; Load 9
- mov bx,3 ; and 3 as unpacked BCD
- add al,bl ; Add 09h and 03h to get 0Ch
- aaa ; Adjust 0Ch in AL to 02h,
- ; increment AH to 01h, set carry
- ; Result 12 unpacked BCD in AX
-
- AAS Adjusts after a subtraction operation. For example, to
- subtract 4 from 13, use the following lines:
-
-
- mov ax,103h ; Load 13
- mov bx,4 ; and 4 as unpacked BCD
- sub al,bl ; Subtract 4 from 3 to get FFh (-1)
- aas ; Adjust 0FFh in AL to 9,
- ; decrement AH to 0, set carry
- ; Result 9 unpacked BCD in AX
-
- AAM Adjusts after a multiplication operation. Always use
- MUL, not IMUL. For example, to multiply 9 times 3, use
- the following lines:
-
-
- mov ax,903h ; Load 9 and 3 as unpacked BCD
- mul ah ; Multiply 9 and 3 to get 1Bh
- aam ; Adjust 1Bh in AL
- ; to get 27 unpacked BCD in AX
-
- AAD Adjusts before a division operation. Unlike other BCD
- instructions, this one converts a BCD value to a
- binary value before the operation. After the
- operation, the quotient must still be adjusted by
- using AAM. For example, to divide 25 by 2, use the
- following lines:
-
-
- mov ax,205h ; Load 25
- mov bl,2 ; and 2 as unpacked BCD
- aad ; Adjust 0205h in AX
- ; to get 19h in AX
- div bl ; Divide by 2 to get
- ; quotient 0Ch in AL
- ; remainder 1 in AH
- aam ; Adjust 0Ch in AL
- ; to 12 unpacked BCD in AX
- ; (remainder destroyed)
-
- Notice that the remainder is lost. If you need the remainder, save it in
- another register before adjusting the quotient. Then move it back to AL
- and adjust if necessary.
-
-
- Multidigit BCD numbers are usually processed in loops. Each digit is
- processed and adjusted in turn. In addition to their use for processing
- unpacked BCD numbers, the ASCII-adjust instructions can be used in
- routines that convert between different number bases.
-
- Example
-
- mov al,79 ; Load 79 (04Fh)
- aam ; Adjust to BCD (0709h)
- add ah,'0' ; Adjust to ASCII characters
- add al,'0' ; (3739h)
- mov dx,ax ; Copy to DX
- xchg dl,dh ; Trade for most significant digit
- mov ah,2 ; DOS display character function
- int 21h ; Call DOS
- mov dl,dh ; Load least significant digit
- int 21h ; Call DOS
-
- The example converts an eight-bit binary number to hexadecimal and
- displays it on the screen. The routine could be enhanced to handle large
- numbers.
-
-
- 14.5.2 Packed BCD Numbers
-
- Packed BCD numbers are made up of bytes containing two decimal digits: one
- in the upper four bits and one in the lower four bits. The 8086-family
- processors provide instructions for adjusting packed BCD numbers after
- addition and subtraction. You must write your own routines to adjust for
- multiplication and division.
-
- To do arithmetic on packed BCD numbers, you must do the eight-bit
- arithmetic calculations on each byte separately. The result should always
- be in the AL register. After each operation, use the corresponding BCD
- instruction to adjust the result. The decimal-adjust instructions do not
- take an operand. They always work on the value in the AL register.
-
- Unlike the ASCII-adjust instructions, the decimal-adjust instructions
- never affect AH. The auxiliary carry flag is set if the digit in the lower
- four bits carries to or borrows from the digit in the upper four bits. The
- carry flag is set if the digit in the upper four bits needs to carry to or
- borrow from another byte.
-
- The decimal-adjust instructions are described below:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- DAA Adjusts after an addition operation. For example, to
- add 88 and 33, use the following lines:
-
-
- mov ax,8833h ; Load 88 and 33 as packed BCD
- add al,ah ; Add 88 and 33 to get 0BBh
- daa ; Adjust 0BBh to 121 packed BCD:
- ; 1 in carry and 21 in AL
-
- DAS Adjusts after a subtraction operation. For example, to
- subtract 38 from 83, put 83 in AL and 38 in AH in
- packed BCD format. Then use the following lines to
- subtract them:
-
-
- mov ax,3883h ; Load 83 and 38 as packed BCD
- sub al,ah ; Subtract 38 from 83 to get 04Bh
- das ; Adjust 04Bh to 45 packed BCD:
- ; 0 in carry and 45 in AL
-
-
- Multidigit BCD numbers are usually processed in loops. Each byte is
- processed and adjusted in turn.
-
-
- 14.6 Doing Logical Bit Manipulations
-
- The logical instructions do Boolean operations on individual bits. The
- AND, OR, XOR, and NOT operations are supported by the 8086-family
- instructions.
-
- AND compares two bits and sets the result if both bits are set. OR
- compares two bits and sets the result if either bit is set. XOR compares
- two bits and sets the result if the bits are different. NOT reverses a
- single bit. Table 14.1 shows a truth table for the logical operations.
-
- Table 14.1 Values Returned by Logical Operations
-
- X Y NOT X X AND Y X OR Y X XOR Y
- ──────────────────────────────────────────────────────────────────────────
- 1 1 0 1 1 0
-
- 1 0 0 0 1 1
-
- 0 1 1 0 1 1
-
- 0 0 1 0 0 0
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- The syntax of the AND, OR, and XOR instructions is the same. The only
- difference is the operation performed. For all instructions, the target
- value to be changed by the operation is placed in one operand. A mask
- showing the positions of bits to be changed is placed in the other
- operand. The format of the mask differs for each logical instruction. The
- destination operand can be register or memory. The source operand can be
- register, memory, or immediate. However, the source and destination
- operands cannot both be memory operands.
-
- Either of the values can be in either operand. However, the source operand
- will be unchanged by the operation, while the destination operand will be
- destroyed by it. Your choice of operands depends on whether you want to
- save a copy of the mask or of the target value.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE The logical instructions should not be confused with the logical
- operators. They specify completely different behavior. The instructions
- control run-time bit calculations. The operators control assembly-time bit
- calculations. Although the instructions and operators have the same name,
- the assembler can distinguish them from context.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 14.6.1 AND Operations
-
- The AND instruction does an AND operation on the bits of the source and
- destination operands. The original destination operand is replaced by the
- resulting bits.
-
- Syntax
-
- AND {register | memory},{register | memory | immediate}
-
- The AND instruction can be used to clear the value of specific bits
- regardless of their current settings. To do this, put the target value in
- one operand and a mask of the bits you want to clear in the other. The
- bits of the mask should be 0 for any bit positions you want to clear and 1
- for any bit positions you want to remain unchanged.
-
- Example 1
-
- mov ax,035h ; Load value 00110101
- and ax,0FBh ; Mask off bit 2 AND 11111011
- ; --------
- ; Value is now 31h 00110001
- and ax,0F8h ; Mask off bits 2,1,0 AND 11111000
- ; --------
- ; Value is now 30h 00110000
-
- Example 2
-
- mov ah,7 ; Get character without echo
- int 21h
- and al,11011111b ; Convert to uppercase by clearing bit 5
- cmp al,'Y' ; Is it Y?
- je yes ; If so, do Yes stuff
- . ; else do No stuff
- .
- yes: .
-
- Example 2 illustrates how to use the AND instruction to convert a
- character to uppercase. If the character is already uppercase, the AND
- instruction has no effect, since bit 5 is always clear in uppercase
- letters. If the character is lowercase, clearing bit 5 converts it to
- uppercase.
-
-
- 14.6.2 OR Operations
-
- The OR instruction does an OR operation on the bits of the source and
- destination operands. The original destination operand is replaced by the
- resulting bits.
-
- Syntax
-
- OR {register | memory},{register | memory | immediate}
-
- The OR instruction can be used to set the value of specific bits
- regardless of their current settings. To do this, put the target value in
- one operand and a mask of the bits you want to clear in the other. The
- bits of the mask should be 1 for any bit positions you want to set and 0
- for any bit positions you want to remain unchanged.
-
- Example
-
- mov ax,035h ; Move value to register 00110101
- or ax,08h ; Mask on bit 3 OR 00001000
- ; --------
- ; Value is now 3Dh 00111101
- or ax,07h ; Mask on bits 2,1,0 OR 00000111
- ; --------
- ; Value is now 3Fh 00111111
-
- Another common use for OR is to compare an operand to 0:
-
- or bx,bx ; Compare to 0
- ; 2 bytes, 2 clocks on 8088
- jg positive ; BX is positive
- jl negative ; BX is negative
- ; BX is zero
-
- The first statement has the same effect as the following statement, but is
- faster and smaller:
-
- cmp bx,0 ; 3 bytes, 3 clocks on 8088
-
-
- 14.6.3 XOR Operations
-
- The XOR (Exclusive OR) instruction does an XOR operation on the bits of
- the source and destination operands. The original destination operand is
- replaced by the resulting bits.
-
- Syntax
-
- XOR {register | memory},{register | memory | immediate}
-
- The XOR instruction can be used to toggle the value of specific bits
- (reverse them from their current settings). To do this, put the target
- value in one operand and a mask of the bits you want to toggle in the
- other. The bits of the mask should be 1 for any bit positions you want to
- toggle and 0 for any bit positions you want to remain unchanged.
-
- Example
-
- mov ax,035h ; Move value to register 00110101
- xor ax,08h ; Mask on bit 3 XOR 00001000
- ; --------
- ; Value is now 3Dh 00111101
- xor ax,07h ; Mask on bits 2,1,0 XOR 00000111
- ; --------
- ; Value is now 3Ah 00111010
-
- Another common use for the XOR instruction is to set a register to 0:
-
- xor cx,cx ; 2 bytes, 3 clocks on 8088
-
- This sets the CX register to 0. When the XOR instruction takes identical
- operands, each bit cancels itself, producing 0. The statement
-
- mov cx,0 ; 3 bytes, 4 clocks on 8088
-
- is the obvious way of doing this, but it is larger and slower. The
- statement
-
- sub cx,cx ; 2 bytes, 3 clocks on 8088
-
- is also smaller than the MOV version. The only advantage of using MOV is
- that it does not affect any flags.
-
-
- 14.6.4 NOT Operations
-
- The NOT instruction does a NOT operation on the bits of a single operand.
- It is used to toggle the value of all bits at once.
-
- Syntax
-
- NOT {register | memory}
-
- The NOT instruction is often used to reverse the sense of a bit mask from
- masking certain bits on to masking them off. Use the NOT instruction if
- the value of the mask is not known until run time; use the NOT operator
- (see Section 9.2.1.5, "Bitwise Logical Operators") if the mask is a
- constant.
-
- Example
-
- .DATA
- masker DB 00010000b ; Value may change at run time
- .CODE
- .
- .
- .
- mov ax,0D743h ; Load 0D7h to AH, 43h to AL 01000011
- or al,masker ; Turn on bit 4 in AL OR 00010000
- ; --------
- ; Result is 53h 01010011
-
- not masker ; Reverse sense of mask 11101111
- and ah,masker ; Turn off bit 4 in AH AND 11010111
- ; --------
- ; Result is 0C7h 11000111
-
-
- 14.7 Shifting and Rotating Bits
-
- The 8086-family processors provide a complete set of instructions for
- shifting and rotating bits. Bits can be moved right (toward the
- most-significant bits) or left (toward the 0 bit). Values shifted off the
- end of the operand go into the carry flag.
-
- Shift instructions move bits a specified number of places to the right or
- left. The last bit in the direction of the shift goes into the carry flag,
- and the first bit is filled with 0 or with the previous value of the first
- bit.
-
- Rotate instructions move bits a specified number of places to the right or
- left. For each bit rotated, the last bit in the direction of the rotate
- operation is moved into the first bit position at the other end of the
- operand. With some variations, the carry bit is used as an additional bit
- of the operand. Figure 14.1 illustrates the eight variations of shift and
- rotate instructions for eight-bit operands. Notice that SHL and SAL are
- identical.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 14.7 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Syntax
-
- SHL {register | memory},{CL | 1} SHR {register | memory},{CL | 1} SAL
- {register | memory},{CL | 1} SAR {register | memory},{CL | 1} ROL
- {register | memory},{CL | 1} ROR {register | memory},{CL | 1} RCL
- {register | memory},{CL | 1} RCR {register | memory},{CL | 1}
-
- The format of all the shift instructions is the same. The destination
- operand should contain the value to be shifted. It will contain the
- shifted operand after the instruction. The source operand should contain
- the number of bits to shift or rotate. It can be the immediate value 1 or
- the CL register. No other value or register is accepted on the 8088 and
- 8086 processors.
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, eight-bit immediate values larger than
- 1 can be given as the source operand for shift or rotate instructions, as
- shown below:
-
- shr bx,4 ; 9 clocks, 3 bytes on 80286
-
- The following statements are equivalent if the program must run the 8088
- or 8086:
-
- mov cl,4 ; 2 clocks, 3 bytes on 80286
- shr bx,cl ; 9 clocks, 2 bytes on 80286
- ; 11 clocks, 5 bytes
-
-
- 14.7.1 Multiplying and Dividing by Constants
-
- Shifting right by one has the effect of dividing by two; shifting left by
- one has the effect of multiplying by two. You can take advantage of this
- to do fast multiplication and division by common constants. The easiest
- constants are the powers of two. Shifting left twice multiplies by four,
- shifting left three times multiplies by eight, and so on.
-
- SHR is used to divide unsigned numbers. SAR can be used to divide signed
- numbers, but SAR rounds negative numbers down──IDIV always rounds up. Code
- that divides by using SAR must adjust for this difference. Multiplication
- by shifting is the same for signed and unsigned numbers, so either SAL or
- SHL can be used. Both instructions do the same operation.
-
- Since the multiply and divide instructions are the slowest on the 8088 and
- 8086 processors, using shifts instead can often speed operations by a
- factor of 10 or more. For example, on the 8088 or 8086 processor, the
- following statements take four clocks:
-
- xor ah,ah ; Clear AH
- shl ax,1 ; Multiply byte in AL by 2
-
- The following statements have the same effect, but take between 74 and 81
- clocks on the 8088 or 8086:
-
- mov bl,2 ; Multiply byte in AL by 2
- mul bl
-
- The same statements take 15 clocks on the 80286. See the on-line Help
- system for complete information on timing of instructions.
-
- Shift instructions can be combined with add or subtract instructions to do
- multiplication by common constants. These operations are best put in
- macros so that they can be changed if the constants in a program change.
-
- Example 1
-
- mul_10 MACRO factor ; Factor must be unsigned
- mov ax,factor ; Load into AX
- shl ax,1 ; AX = factor * 2
- mov bx,ax ; Save copy in BX
- shl ax,1 ; AX = factor * 4
- shl ax,1 ; AX = factor * 8
- add ax,bx ; AX = (factor * 8) + (factor * 2)
- ENDM ; AX = factor * 10
-
- Example 2
-
- div_u512 MACRO dividend ; Dividend must be unsigned
- mov ax,dividend ; Load into AX
- shr ax,1 ; AX = dividend / 2 (unsigned)
- xchg al,ah ; xchg is like rotate right 8
- ; AL = (dividend / 2) / 256
- cbw ; Clear upper byte
- ENDM ; AX = (dividend / 512
-
-
- 14.7.2 Moving Bits to the Least-Significant Position
-
- Sometimes a group of bits within an operand needs to be treated as a
- single unit──for example, to do an arithmetic operation on those bits
- without affecting other bits. This can be done by masking off the bits and
- then shifting them into the least-significant positions. After the
- arithmetic operation is done, the bits are shifted back to the original
- position and merged with the original bits by using OR. See Section
- 7.2.5, "Using Record-Field Operands," for an example of this operation.
-
-
- 14.7.3 Adjusting Masks
-
- Masks for logical instructions can be shifted to new bit positions. For
- example, an operand that masks off a bit or group of bits can be shifted
- to move the mask to a different position.
-
- Example
-
- .DATA
- masker DB 00000010b ; Mask that may change at run time
- .CODE
- .
- .
- .
- mov cl,2 ; Rotate two at a time
- mov bl,57h ; Load value to be changed 01010111b
- rol masker,cl ; Rotate two to left 00001000b
- or bl,masker ; Turn on masked values ---------
- ; New value is 05Fh 01011111b
- rol masker,cl ; Rotate two more 00100000b
- or bl,masker ; Turn on masked values ---------
- ; New value is 07Fh 01111111b
-
- This technique is useful only if the mask value is unknown until run time.
-
-
- 14.7.4 Shifting Multiword Values
-
- Sometimes it is necessary to shift a value that is too large to fit in a
- register. In this case, you can shift each part separately, passing the
- shifted bits through the carry flag. The RCR or RCL instructions must be
- used to move the carry value from the first register to the second.
-
- RCR and RCL can also be used to initialize the high or low bit of an
- operand. Since the carry flag is treated as part of the operand (like
- using a nine-bit operand), the flag value before the operation is crucial.
- The carry flag may be set by a previous instruction, or you can set it
- directly using the CLC (Clear Carry Flag), CMC (Complement Carry Flag),
- and STC (Set Carry Flag) instructions.
-
- Example
-
- .DATA
- mem32 DD 500000
- .CODE
- .
- . ; Divide 32-bit unsigned by 16
- .
- mov cx,4 ; Shift right 4 500000
- again: shr WORD PTR mem32[2],1 ; Shift into carry DIV 16
- rcr WORD PTR mem32[0],1 ; Rotate carry in ------
- loop again ; 31250
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 15: Controlling Program Flow
-
-
- The 8086-family processors provide a variety of instructions for
- controlling the flow of a program. The four major types of program-flow
- instructions are jumps, loops, procedure calls, and interrupts.
-
- This chapter tells you how to use these instructions and how to test
- conditions for the instructions that change program flow conditionally.
-
-
- 15.1 Jumping
-
- Jumps are the most direct method of changing program control from one
- location to another. At the internal level, jumps work by changing the
- value of the IP (Instruction Pointer) register from the address of the
- current instruction to a target address.
-
- Jumps can be short, near, or far. QuickAssembler automatically handles
- near and short jumps, although it may not always generate the most
- efficient code if the label being jumped to is a forward reference. The
- size and control of jumps are discussed in Section 9.4.1, "Forward
- References to Labels."
-
-
- 15.1.1 Jumping Unconditionally
-
- The JMP instruction is used to jump unconditionally to a specified
- address.
-
- Syntax
-
- JMP {register | memory}
-
- The operand should contain the address to be jumped to. Unlike conditional
- jumps, whose target address must be short (within 128 bytes), the target
- address for unconditional jumps can be short, near, or far. See Section
- 9.4.1 for more information on specifying the distance for conditional
- jumps.
-
- If a conditional jump must be greater than 128 bytes, the construction
- must be reorganized. This can be done by reversing the sense of the
- conditional jump and adding an unconditional jump, as shown in Example 1.
-
- Example 1
-
- cmp ax,7 ; If AX is 7 and jump is short
- je close ; then jump close
-
- cmp ax,6 ; If AX is 6 and jump is near
- jne close ; then test opposite and skip over
- jmp distant ; Now jump
- .
- .
- .
- close: ; Less than 128 bytes from jump
- .
- .
- .
- distant: ; More than 128 bytes from jump
-
- An unconditional jump can be used as a form of conditional jump by
- specifying the address in a register or indirect memory operand. The value
- of the operand can be calculated at run time, based on user interaction or
- other factors. You can use indirect memory operands to construct jump
- tables that work like C switch statements, BASIC ON GOTO statements, or
- Pascal case statements.
-
- Example 2
-
- .CODE
- .
- .
- .
- jmp process ; Jump over data
- ctl_tbl LABEL WORD ; (required in overlay procedures)
- DW extended ; Null key (extended code)
- DW ctrla ; Address of CONTROL-A key routine
- DW ctrlb ; Address of CONTROL-B key routine
- process: mov ah,8h ; Get a key
- int 21h
- cbw ; Convert AL to AX
- mov bx,ax ; Copy
- shl bx,1 ; Convert to address
-
- jmp ctl_tbl[bx] ; Jump to key routine
-
- extended: mov ah,8h ; Get second key of extended
- int 21h
- . ; Use another jump table
- . ; for extended keys
- .
- ctrla: . ; CONTROL-A routine here
- .
- .
- jmp next
-
- ctrlb: . ; CONTROL-B routine here
- .
- .
- jmp next
- .
- .
- next: . ; Continue
-
- In Example 2, an indirect memory operand points to addresses of routines
- for handling different keystrokes. Notice that the jump table is placed in
- the code segment. This technique is optional in stand-alone assembler
- programs, but it may be required for procedures called from some
- languages.
-
-
- 15.1.2 Jumping Conditionally
-
- The most common way of transferring control in assembly language is with
- conditional jumps. This is a two-step process: first test the condition,
- and then jump if the condition is true or continue if it is false.
-
- Syntax
-
- Jcondition label
-
- Conditional-jump instructions take a single operand containing the address
- to be jumped to. The distance from the jump instruction to the specified
- address must be short (less than 128 bytes). If a longer distance is
- specified, an error will be generated telling the distance of the jump in
- bytes. See Section 15.1.1, "Jumping Unconditionally," for information on
- arranging longer conditional jumps.
-
- Conditional-jump instructions (except JCXZ) use the status of one or more
- flags as their condition. Thus, any statement that sets a flag under
- specified conditions can be the test statement. The most common test
- statements use the CMP or TEST instructions. The jump statement can be any
- one of 31 conditional-jump instructions.
-
- Because conditional jumps cannot refer to labels more than 128 bytes away,
- they are often used in combination with unconditional jumps, which have no
- such limitation. For example, the following statement is valid as long as
- target is not far away:
-
- jz target ; If previous operation resulted in
- ; zero, jump to target
-
- Once target becomes too distant, the following sequence must be used to
- enable a longer jump. Note that this sequence is logically equivalent to
- the example above:
-
- jnz skip ; If previous operation resulted in NOT zero
- ; jump to "skip"
- jmp target ; Otherwise, jump to target
- skip:
-
- The instructions above first test for the logical inverse of the desired
- condition. If the test condition (in this case, equality to zero) is not
- true, the jump to target is avoided. Yet if a zero condition is true, the
- program falls through to the instruction jmp target, which can jump any
- distance. The effect, of course, is to jump to target if the previous
- operation resulted in zero.
-
- The problem with this technique is that if used often, you may have to
- think up a label name just to jump around one instruction. Anonymous
- labels, described in Section 6.4.2, let you avoid having to invent so
- many label names. For example, you could use an anonymous label to rewrite
- the example above:
-
- jnz @F ; If previous operation resulted in NOT zero,
- ; jump forward to next @ label
- jmp target ; Otherwise, jump to target
- @:
-
-
- 15.1.2.1 Comparing and Jumping
-
- The CMP instruction is specifically designed to test for conditional
- jumps. It does not change the destination operand, so it can be used to
- compare two values without changing either of them. Instructions that
- change operands (such as SUB or AND) can also be used to test conditions.
-
- The CMP instruction compares two operands and sets flags based on the
- result. It is used to test the following relationships: equal; not equal;
- greater than; less than; greater than or equal; or less than or equal.
-
- Syntax
-
- CMP {register | memory},{register | memory | immediate}
-
- The destination operand can be memory or register. The source operand can
- be immediate, memory, or register. However, they cannot both be memory
- operands.
-
- The jump instructions that can be used with CMP are made up of mnemonic
- letters combined to indicate the type of jump. The letters are shown
- below:
-
- Letter Meaning
- ──────────────────────────────────────────────────────────────────────────
- J Jump
-
- G Greater than (for unsigned comparisons)
-
- L Less than (for unsigned comparisons)
-
- A Above (for signed comparisons)
-
- B Below (for signed comparisons)
-
- E Equal
-
- N Not
-
-
- The mnemonic names always refer to the relationship that the first operand
- of the CMP instruction has to the second operand of the CMP instruction.
- For instance, JG tests whether the first operand is greater than the
- second. Several conditional instructions have two names. You can use
- whichever name seems more mnemonic in context.
-
- Comparisons and conditional jumps can be thought of as statements in the
- following format:
-
- IF (value1 relationship value2) THEN GOTO truelabel:
-
- Statements of this type can be coded in assembly language by using the
- following syntax:
-
- CMP value1,value2
- Jrelationship truelabel
- .
- .
- .
- truelabel:
-
- Table 15.1 lists conditional-jump instructions for each relationship and
- shows the flags that are tested in order to see if relationship is true.
-
- Table 15.1 Conditional-Jump Instructions Used after Compare
-
- Jump Condition Signed Jump if: Unsigned Jump if:
- Compare Compare
- ──────────────────────────────────────────────────────────────────────────
- = Equal JE ZF = 1 JE ZF = 1
-
- ╪ Not equal JNE ZF = 1 JNE ZF = 1
-
- > Greater than JG or JNLE ZF = 0 and JA or JNBE CF = 0 and ZF = 0
- SF = OF
- <= Less than or JLE or JNG ZF = 1 and JBE or JNA CF = 1 or ZF = 1
- equal SF ╪ OF
- < Less than JL or JNGE SF ╪ OF JB or JNAE CF = 1
-
- >= Greater than JGE or JNL SF = OF JAE or JNB CF = 0
- or equal
- ──────────────────────────────────────────────────────────────────────────
-
-
- Internally, the CMP instruction is exactly the same as the SUB
- instruction, except that the destination operand is not changed. The flags
- are set according to the result that would have been generated by a
- subtraction.
-
- Example 1
-
- ; If CX is less than -20, then make DX 30, else make DX 20
-
- cmp cx,-20 ; If signed CX is smaller than -20
- jl less ; then do stuff at "less"
- mov dx,20 ; Else set DX to 20
- jmp skip ; Finished
- less: mov dx,30 ; Then set DX to 30
- skip:
-
- Example 1 shows the basic form of conditional jumps. Notice that in
- assembly language, if-then-else constructions are usually written in the
- form if-else-then.
-
- This theme has many variations. For example, you may find it more mnemonic
- to code in the if-then-else format. However, you must then use the
- opposite jump condition, as shown in Example 2.
-
- Example 2
-
- ; If CX is greater than or equal to -20, then make DX 20, else make DX 30
-
- cmp cx,-20 ; If signed CX is smaller than -20
- jnl notless ; else do stuff at "notless"
- mov dx,30 ; Then set DX to 30
- jmp continue ; Finished
- notless: mov dx,20 ; Else set DX to 20
- continue:
-
- The then-if-else format shown in Example 3 is often more efficient. Do the
- work for the most likely case, and then compare for the opposite
- condition. If the condition is true, you are finished.
-
- Example 3
-
- ; DX is 20, unless CX is less than -20, then make DX 30
-
- mov dx,20 ; DX is 20
- cmp cx,-20 ; If signed CX is greater than -20
- jge greatequ ; then done
- mov dx,30 ; Else set DX to 30
- greatequ:
-
- This example avoids the unconditional jump used in Examples 1 and 2 and
- thus is faster even if the less likely condition is true.
-
-
- 15.1.2.2 Jumping Based on Flag Status
-
- The CMP instruction is the most mnemonic way to set the flags for
- conditional jumps, but any instruction that changes flags can be used as
- the test condition. The conditional-jump instructions listed below enable
- you to jump based on the condition of flags rather than on relationships
- of operands. Some of these instructions have the same effect as
- instructions listed in Table 15.1.
-
- Instruction Action
- ──────────────────────────────────────────────────────────────────────────
- JO Jumps if the overflow flag is set
-
- JNO Jumps if the overflow flag is clear
-
- JC Jumps if the carry flag is set (same as JB)
-
- JNC Jumps if the carry flag is clear (same as JAE)
-
- JZ Jumps if the zero flag is set (same as JE)
-
- JNZ Jumps if the zero flag is clear (same as JNE)
-
- JS Jumps if the sign flag is set
-
- JNS Jumps if the sign flag is clear
-
- JP Jumps if the parity flag is set
-
- JNP Jumps if the parity flag is clear
-
- JPE Jumps if parity is even (parity flag set)
-
- JPO Jumps if parity is odd (parity flag clear)
-
- JCXZ Jumps if CX is 0
-
-
- Notice that JCXZ is the only conditional jump based on the condition of a
- register (CX) rather than flags. Since JCXZ is usually used with loop
- instructions, it is discussed in more detail in Section 15.2, "Looping."
-
- Example 1
-
- add ax,bx ; Add two values
- jo overflow ; If value too large, adjust
- .
- .
- .
- overflow: ; Adjustment routine here
-
- Example 2
-
- sub ax,dx ; Subtract
- jnz skip ; If the result is not zero, continue
- call zhandler ; else do special case
- skip:
-
-
- 15.1.2.3 Testing Bits and Jumping
-
- Like the CMP instruction, the TEST instruction is designed to test for
- conditional jumps. However, specific bits are compared rather than entire
- operands.
-
- Syntax
-
- TEST {register | memory},{register | memory | immediate}
-
- The destination operand can be memory or register. The source operand can
- be immediate, memory, or register. However, they cannot both be memory
- operands.
-
- Normally, one of the operands is a mask in which the bits to be tested are
- the only bits set. The other operand contains the value to be tested. If
- all the bits set in the mask are clear in the operand being tested, the
- zero flag will be set. If any of the flags set in the mask are also set in
- the operand, the zero flag will be cleared.
-
- The TEST instruction is actually the same as the AND instruction, except
- that neither operand is changed. If the result of the operation is 0, the
- zero flag is set, but the 0 is not actually written to the destination
- operand.
-
- You can use the JZ and JNZ instructions to jump after the test. JE and JNE
- are the same and can be used if you find them more mnemonic.
-
- Example
-
- .DATA
- bits DB ?
- .CODE
- .
- .
- .
- ; If bit 2 or bit 4 is set, then call taska
-
- ; Assume "bits" is 0D3h 11010011
- test bits,10100b ; If 2 or 4 is set AND 00010100
- jz skip1 ; Else continue --------
- call taska ; Then call taska 00010000
- skip1: ; Jump not taken
- .
- .
- .
- ; If bits 2 and 4 are clear, then call taskb
-
- ; Assume "bits" is 0E9h 11101001
- test bits,10100b ; If 2 and 4 are clear AND 00010100
- jnz skip2 ; Else continue --------
- call taskb ; Then call taskb 00000000
- skip2: ; Jump not taken
-
-
- 15.2 Looping
-
- The 8086-family processors have several instructions specifically designed
- for creating loops of repeated instructions. In addition, you can create
- loops using conditional jumps.
-
- Syntax
-
- LOOP label
- LOOPE label
- LOOPZ label
- LOOPNE label
- LOOPNZ label
- JCXZ label
-
- The LOOP instruction is used for loops with a set number of iterations.
- For example, it can be used in constructions similar to the "for" loops of
- BASIC, C, and Pascal, and the "do" loops of FORTRAN.
-
- A single operand specifies the address to jump to each time through the
- loop. The CX register is used as a counter for the number of times to
- loop. On each iteration, CX is decremented. When CX reaches 0, control
- passes to the instruction after the loop.
-
- The LOOPE, LOOPZ, LOOPNE, and LOOPNZ instructions are used in loops that
- check for a condition. For example, they can be used in constructions
- similar to the "while" loops of BASIC, C, and Pascal; the "repeat" loops
- of Pascal; and the "do" loops of C.
-
- The LOOPE (also called LOOPZ) instruction can be thought of as meaning
- "loop while equal." Similarly, the LOOPNE (also called LOOPNZ) instruction
- can be thought of as meaning "loop while not equal." A single short memory
- operand specifies the address to loop to each time through. The CX
- register can specify a maximum number of times to go through the loop. The
- CX register can be set to a number that is out of range if you do not want
- a maximum count.
-
- The JCXZ instruction is often used in loop structures. For example, it may
- be used in loops that check a condition at the start of the loop rather
- than at the end. Unlike the loop instruction, JCXZ does not decrement CX,
- so the programmer must use another statement to decrement the count. You
- can also use JCX2 with string instructions, as described in Chapter 16,
- "Processing Strings."
-
- Example 1
-
- ; For 0 to 200 do task
-
- mov cx,200 ; Set counter
- next: . ; Do the task here
- .
- .
- loop next ; Do again
- ; Continue after loop
-
- This loop has the same effect as the following statements:
-
- ; For 0 to 200, do task
-
- mov cx,200 ; Set counter
- next: .
- . ; Do the task here
- .
- dec cx
- cmp cx,0
- jne next ; Do again
- ; Continue after loop
-
- The first version is more efficient as well as easier to understand.
- However, there are situations in which you must use conditional-jump
- instructions rather than loop instructions. For example, conditional jumps
- are often required for loops that test several conditions.
-
- If the counter in CX is variable because of previous instructions, you
- should use the JCXZ instruction to check for 0, as shown in Example 2.
- Otherwise, if CX is 0, it will be decremented to -1 in the first iteration
- and will continue through 65,535 iterations before it reaches 0 again.
-
- Example 2
-
- ; For 0 to CX do task
-
- ; CX counter set previously
- jcxz done ; Check for 0
- next: . ; Do the task here
- .
- .
- loop next ; Do again
- done: ; Continue after loop
-
-
- 15.3 Using Procedures
-
- A "procedure" is a program subdivision that typically executes a specific
- task. Once you write a procedure, you can execute it from anywhere in the
- program. This technique lets you avoid writing the same block of code over
- and over, thus saving space.
-
- Even if you execute it only once, writing a procedure can be a useful way
- of dividing a large program into manageable units. You can place a
- procedure in its own source module and test it separately.
- Assembly-language procedures are comparable to functions in C;
- subprograms, functions, and subroutines in BASIC; procedures and functions
- in Pascal; or routines and functions in FORTRAN.
-
- Two instructions control the use of assembly-language procedures. The CALL
- instruction can appear anywhere in a program. It temporarily transfers
- program control to a specified procedure. The RET instruction appears at
- the end of a procedure. It returns control back to the location that
- issued the call.
-
- These instructions use the stack to properly return from each call. The
- instruction immediately following the CALL instruction is called the
- "return address," and the procedure should return to this location when
- done. CALL pushes the return address onto the stack; RET pops this address
- off the stack and transfers program control there.
-
- Along with the RET instruction (which terminates a procedure), two
- directives help define a procedure. The PROC and ENDP directives normally
- mark the beginning and end of a procedure definition, as described in
- Section 15.3.2, "Defining Procedures."
-
- In addition, the PROC directive can save you time and effort by automating
- the following tasks:
-
- ■ Preserving register values that should not change, but that the
- procedure might otherwise alter
-
- ■ Setting up a framepointer, so that you can access parameters placed on
- the stack
-
- ■ Creating text macros, so that your source code can refer to each
- parameter by a meaningful name
-
- Section 15.3.4, "Declaring Parameters with the PROC Directive," describes
- how to use these features. Section 15.3.3, "Passing Arguments on the
- Stack," gives background information on the technique for accessing
- parameters.
-
- When you write procedures, you can create local variables, which exist
- only during execution of the procedure. The advantage of these variables
- is that they use memory dynamically, taking up space only in the procedure
- that uses them. Section 15.3.5, "Using Local Variables," describes the
- basic technique for allocating and accessing local variables. Section
- 15.3.6, "Creating Locals Automatically," describes how to make the
- assembler generate the necessary code for you.
-
-
- 15.3.1 Calling Procedures
-
- The CALL instruction saves the address following the instruction on the
- stack and passes control to a specified address.
-
- Syntax
-
- CALL {register | memory}
-
- The address is usually specified as a direct memory operand. However, the
- operand can also be a register or indirect memory operand containing a
- value calculated at run time. This enables you to write call tables
- similar to the jump table illustrated in Section 15.1.2.1, "Comparing and
- Jumping."
-
- Calls can be near or far. Near calls push only the offset portion of the
- calling address. Far calls push both the segment and offset. You must give
- the type of far calls to forward-referenced labels using the FAR type
- specifier and the PTR operator. For example, use the following statement
- to make a far call to a label that has not been earlier defined or
- declared external in the source code:
-
- call FAR PTR task
-
-
- 15.3.2 Defining Procedures
-
- Procedures are defined by labeling the start of the procedure and placing
- an ENDP directive at the end. The code should not fall through past the
- end of the procedure. Exit the procedure with a RET, RETF, RETN, or IRET
- instruction. There are several variations of this syntax.
-
- Syntax 1
-
- label PROC [[NEAR|FAR]] RET [[constant]] label ENDP
-
- Procedures are normally defined by using the PROC directive at the start
- of the procedure and the ENDP directive at the end. The RET instruction is
- normally placed immediately before the ENDP directive. The size of the RET
- instruction automatically matches the size defined by the PROC directive.
-
- The syntax shown is always available. In addition, there is an extended
- PROC syntax available if you use .MODEL and specify a language. The
- extended PROC syntax is explained in Section 15.3.4, "Declaring
- Parameters with the PROC Directive." These language features automate many
- of the details of accessing parameters and saving registers.
-
- Syntax 2
-
- label:
- statements
- RETN [[constant]]
-
- Syntax 3
-
- label LABEL FAR
- statements
- RETF [[constant]]
-
- The RET instruction can be extended to RETN (Return Near) or RETF (Return
- Far) to override the default size. This enables you to define and use
- procedures without the PROC and ENDP directives, as shown in Syntax 2 and
- Syntax 3, above. However, with this method, the programmer is responsible
- for making sure the size of the CALL matches the size of the RET.
-
- The RET instruction (and its RETF and RETN variations) allows a constant
- operand that specifies a number of bytes to be added to the value of the
- SP register after the return. This operand can be used to adjust for
- arguments passed to the procedure before the call, as shown in the example
- in Section 15.3.5, "Using Local Variables."
-
- Example 1
-
- call task ; Call is near because procedure is near
- . ; Return comes to here
- .
- .
- task PROC NEAR ; Define "task" to be near
- .
- . ; Instructions of "task" go here
- .
- ret ; Return to instruction after call
- task ENDP ; End "task" definition
-
- Example 1 shows the recommended way of making calls with QuickAssembler.
- Example 2 shows another method that programmers who are used to other
- assemblers may find more familiar.
-
- Example 2
-
- call NEAR PTR task ; Call is declared near
- . ; Return comes to here
- .
- .
- task: ; Procedure begins with near label
- .
- . ; Instructions go here
- .
- retn ; Return declared near
-
- This method gives more direct control over procedures, but the programmer
- must make sure that calls have the same size as corresponding returns.
-
- For example, if a call is made with the statement
-
- call NEAR PTR task
-
- the assembler does a near call. This means that one word (the offset
- following the calling address) is pushed onto the stack. If the return is
- made with the statement
-
- retf
-
- two words are popped off the stack. The first will be the offset, but the
- second will be whatever happened to be on the stack before the call. Not
- only will the popped value be meaningless, but the stack status will be
- incorrect, causing the program to fail.
-
-
- 15.3.3 Passing Arguments on the Stack
-
- Procedure arguments can be passed in various ways. For example, values can
- be passed to a procedure in registers or in variables. However, the most
- common method of passing arguments is to use the stack. Microsoft
- languages have a specific convention for doing this.
-
- This section describes how a procedure accesses the parameters passed to
- it on the stack. Each parameter is accessed as an offset from BP, and you
- must calculate this offset. However, if you use the PROC directive to
- declare parameters, the assembler calculates these offsets for you and
- lets you refer to parameters by name. The next section explains how to use
- PROC this way.
-
- The arguments are pushed onto the stack before the call. After the call,
- the procedure retrieves and processes them. At the end of the procedure,
- the stack is adjusted to account for the arguments.
-
- Although the same basic method is used for all Microsoft high-level
- languages, the details vary. For instance, in some languages, pointers to
- the arguments are passed to the procedure; in others, the arguments
- themselves are passed. The order in which arguments are passed (whether
- the first argument is pushed first or last) also varies according to the
- language. Finally, in some languages, the stack is adjusted by the RET
- instruction in the called procedure; in others, the code immediately
- following the CALL instruction adjusts the stack. See Appendix A,
- "Mixed-Language Mechanics," for details on calling conventions.
-
- Example
-
- ; C-style procedure call and definition
-
- mov ax,10 ; Load and
- push ax ; push constant as third argument
- push arg2 ; Push memory as second argument
- push cx ; Push register as first argument
- call addup ; Call the procedure
- add sp,6 ; Destroy the pushed arguments
- . ; (equivalent to three pops)
- .
- .
- addup PROC NEAR ; Return address for near call
- ; takes two bytes
- push bp ; Save base pointer - takes two bytes
- ; so arguments start at 4th byte
- mov bp,sp ; Load stack into base pointer
- mov ax,[bp+4] ; Get first argument from
- ; 4th byte above pointer
- add ax,[bp+6] ; Add second argument from
- ; 6th byte above pointer
- add ax,[bp+8] ; Add third argument from
- ; 8th byte above pointer
- pop bp ; Restore BP
- ret ; Return result in AX
- addup ENDP
-
- The example shows one method of passing arguments to a procedure. This
- method is similar to the way procedures are called in the C language.
- Figure 15.1 shows the stack condition at key points in the process.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 15.3.3 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Arguments passed on the stack in assembler routines cannot be
- accessed by name in debugging commands, unless you declare parameters with
- the PROC directive, as explained in the next section.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 15.3.4 Declaring Parameters with the PROC Directive
-
- This section describes how to use the PROC directive in order to automate
- the parameter-accessing techniques described in the last section.
-
- The PROC directive lets you specify registers to be saved, define
- arguments to the procedure, and set up text macros so that you can refer
- to parameters by name (rather than as an offset to BP). For example, the
- following PROC directive could be placed at the beginning of a procedure
- called from BASIC that takes a single argument passed by value and that
- uses (and must save) the DI and SI registers:
-
- myproc PROC FAR BASIC USES DI SI, arg1:WORD
-
- Note that you must use the .MODEL directive and specify a language in
- order to use the extended features of PROC, including the lang type,
- reglist, and arguments.
-
- Syntax
-
- label PROC [[NEAR|FAR]] [[lang]] [[USES reglist]] [[arguments]]
-
- The NEAR and FAR keywords indicate whether you invoke the procedure with a
- near call or a far call, as described in Section 15.3.2, "Defining
- Procedures."
-
- The following list describes the other parts of the PROC directive:
-
- Argument Description
- ──────────────────────────────────────────────────────────────────────────
- label The name of the procedure. The assembler automatically
- adds an underscore to the beginning of the name if you
- specify C as the language in the .MODEL directive or
- if you specify C as the lang.
-
- lang An optional language specifier that overrides language
- conventions specified by the .MODEL directive. The
- language type may be C, Pascal, FORTRAN, or BASIC.
-
- The language type determines the calling convention
- used to access parameters and restore the stack. It
- also determines whether an underscore is prefixed to
- the procedure name, as required by the C naming
- convention. Note that use of the C specifier does not
- preserve lowercase letters in the procedure name. To
- guarantee compatibility with C naming conventions,
- choose Preserve Case or Preserve Extrn from the
- Assembler Flags dialog box, or assemble with /Cl or
- /Cx from the QCL command line.
-
- reglist A list of registers that the procedure uses and that
- should be saved on entry. Registers in the list must
- be separated by blanks or tabs. The assembler
- generates code to push these registers on the stack.
- When you exit, the assembler generates code to pop the
- saved register values off the stack.
-
- arguments The list of arguments passed to the procedure on the
- stack. See the discussion below for the syntax of the
- argument.
-
-
- The arguments indicate each of the procedure's arguments and are separated
- from the reglist argument by a comma if there is a list of registers. Each
- argument has the following syntax:
-
- argname [[ :[[[[NEAR|FAR]]PTR]]type]]
-
- If you have more than one argument, separate each by a comma.
-
- The argname is the name of the argument. The type is the type of the
- argument and may be WORD, DWORD, QWORD, TBYTE, or the name of a structure
- defined by a STRUC structure declaration (see Chapter 6, "Defining
- Labels, Constants, and Variables" for more information about types). If
- you omit type, the default is the WORD type.
-
- The FAR, NEAR, PTR, and type arguments are all optional. If you omit all
- of them, the assembler assumes the variable is a WORD type. If you use
- only the type argument, the assembler assumes the variable has the
- indicated type.
-
- ──────────────────────────────────────────────────────────────────────────
- Note If you are writing a routine to be called from BASIC, FORTRAN, or
- Pascal, and the routine returns a function value, you must declare an
- additional parameter if you return anything other than a two- or four-byte
- integer. See Appendix A, "Mixed-Language Mechanics," for more
- information.
- ──────────────────────────────────────────────────────────────────────────
-
- The PTR type generates debugging information so that the variable is
- treated as a pointer during debugging. The assembler assumes specific
- sizes for the variable, depending on the combination of NEAR, FAR, and PTR
- arguments you specify. The lines below show some example combinations of
- NEAR, FAR, PTR, and type:
-
- myproc PROC var1:PTR WORD, var2:PTR DWORD
- .
- .
- .
- myproc ENDP
-
- proc2 PROC var3:FAR PTR WORD, var4:NEAR PTR BYTE
- .
- .
- .
- proc2 ENDP
-
- If you omit NEAR or FAR, the default data size established by .MODEL is
- used. All PTR declarations are translated into a word-size variable if the
- data size is near or a doubleword variable if the data size is far.
-
- For example, the following declarations of procvar produce the same code
- for the variable name, although they generate different debugging
- information:
-
- aproc PROC procvar:PTR WORD
-
- aproc PROC procvar:PTR DWORD
-
- aproc PROC procvar:PTR BYTE
-
- Specifying a particular type changes only the debugging information, not
- the code produced for accessing the argument.
-
- If you specify a NEAR PTR or FAR PTR argument, as in the declarations of
- var3 and var4, the assembler ignores the memory model you selected and
- assigns a WORD type for a NEAR PTR argument and a DWORD type for a FAR PTR
- argument.
-
- The assembler does not generate any code to get the value or values the
- pointer references; your program must still explicitly treat the argument
- as a pointer. For example, the procedure in Section 5.1 can be rewritten
- for use with BASIC so that it gets its argument by near reference (the
- BASIC default):
-
- ; Call from BASIC as a FUNCTION returning an integer
-
- .MODEL medium, basic
- .CODE
- myadd PROC arg1:NEAR PTR WORD, arg2:NEAR PTR WORD
-
- mov bx,arg1 ; Load first argument
- mov ax,[bx]
- mov bx,arg2 ; Add second argument
- add ax,[bx]
-
- ret
-
- myadd ENDP
- END
-
- In the example above, even though the arguments are declared as near
- pointers, you still must code two move instructions in order to get the
- values of the arguments──the first move gets the address of the argument;
- the second move gets the argument.
-
- You can use conditional-assembly directives to make sure that your pointer
- arguments are loaded correctly for the memory model. For example, the
- following version of myadd treats the arguments as far arguments if
- necessary:
-
- .MODEL medium,c ;Could be any model
- .CODE
- myadd PROC arg1:PTR WORD, arf2:PTR WORD
-
- IF @DataSize
- les bx,arg1 ;Far arguments
- mov ax,es:[bx]
- les bx,arg2
- add ax,es:[bx]
- ELSE
- mov bx,arg1 ;Near arguments
- mov ax,[bx]
- mov bx,arg2
- add ax,[bx]
- ENDIF
-
- ret
- myadd ENDP
-
- END
-
- ──────────────────────────────────────────────────────────────────────────
- Note When you use the high-level-language features and the assembler
- encounters a RET instruction, it automatically generates instructions to
- pop saved registers, remove local variables from the stack, and, if
- necessary, remove arguments. The assembler does not generate this code if
- you use a RETF or RETN instruction. It generates this code for each RET
- instruction it encounters. You can save code by having only one exit and
- jumping to it from various points.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 15.3.5 Using Local Variables
-
- In high-level languages, local variables are known only within a
- procedure. In Microsoft languages, these variables are usually stored on
- the stack. Assembly-language programs can use the same technique. These
- variables should not be confused with labels or variable names that are
- local to a module, as described in Chapter 8, "Creating Programs from
- Multiple Modules."
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE If your procedure has relatively few variables, you can usually
- write the most efficient code by placing these values in registers. Local
- (stack) data is efficient when you have a large amount of local data for
- the procedure.
- ──────────────────────────────────────────────────────────────────────────
-
- This section outlines the standard methods for creating local variables.
- The next section shows how to use the LOCAL directive to make the
- assembler generate local variables for you automatically. When you use
- this directive, the assembler generates the same instructions as those
- used in this section, but hides some of the details from you.
-
- If you want to use LOCAL right away, you may want to skip directly to the
- next section. However, this section gives useful background.
-
- Local variables are created by saving stack space for the variable at the
- start of the procedure. The variable can then be accessed by its position
- in the stack. At the end of the procedure, the stack pointer is restored
- to restore the memory used by local variables.
-
- Example
-
- push ax ; Push one argument
- call task ; Call
- .
- .
- .
- arg EQU <[bp+4]> ; Name for argument
- loc EQU <[bp-2]> ; Name for local variable
-
- task PROC NEAR
- push bp ; Save base pointer
- mov bp,sp ; Load stack into base pointer
- sub sp,2 ; Save two bytes for local variable
- .
- .
- .
- mov loc,3 ; Initialize local variable
- add ax,loc ; Add local variable to AX
- sub arg,ax ; Subtract local from argument
- . ; Use "loc" and "arg" in other operations
- .
- .
- mov sp,bp ; Adjust for stack variable
- pop bp ; Restore base
- ret 2 ; Return result in AX and pop
- task ENDP ; two bytes to adjust stack
-
- In this example, two bytes are subtracted from the SP register to make
- room for a local word variable. This variable can then be accessed as
- [bp-2]. In the example, this value is given the name loc with a text
- equate. Notice that the instruction mov sp,bp is given at the end to
- restore the original value of SP. The statement is only required if the
- value of SP is changed inside the procedure (usually by allocating local
- variables). The argument passed to the procedure is returned with the
- RET instruction. Contrast this to the example in Section 15.3.3,
- "Passing Arguments on the Stack," in which the calling code adjusts for
- the argument. Figure 15.2 shows the state of the stack at key points in
- the process.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 15.3.5 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Local variables created in assembler routines cannot be accessed by
- name with debugging commands, unless you declare local variables with the
- LOCAL directive, as explained in the next section.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 15.3.6 Creating Locals Automatically
-
- This section describes how to automate the techniques for local-variable
- creation described in the last section.
-
- You can use the LOCAL directive to save time and effort when working with
- local variables. When you use this directive, simply list the variables
- you want to create, giving a type for each one. The assembler calculates
- how much space is required on the stack. It also generates instructions to
- properly decrement SP (as described in the previous section) and to later
- reset SP when you return from the procedure.
-
- The LOCAL directive can only be used inside procedures created with the
- extended PROC directive. This means that you must first use .MODEL and
- specify a language.
-
- When you create local variables this way, your source code can then refer
- to each local variable by name rather than as an offset. Moreover, the
- assembler generates debugging information for each local variable, so that
- you can enter the name of the local variable as part of a Watch
- expression.
-
- The procedure in Section 15.3.5 can be generated more simply with the
- following code:
-
- task PROC NEAR arg:WORD
- LOCAL loc:WORD
- .
- .
- .
- mov loc,3 ; Initialize local variable
- add ax,loc ; Add local variable to AX
- sub arg,ax ; Subtract local from argument
- . ; Use "Loc" and "arg" in other operat
- .
- .
- ret
- task ENDP
-
- The LOCAL directive has the following syntax:
-
- LOCAL vardef [[,vardef]]...
-
- Each vardef has the form:
-
- label[[[count]]][[:[[[[NEAR | FAR]]PTR]]type]]]]...
-
- The LOCAL directive arguments are as follows:
-
- Argument Description
- ──────────────────────────────────────────────────────────────────────────
- label The name given to the local variable. The assembler
- automatically defines a text macro you may use to
- access the variable.
-
- count The number of elements of this name and type to
- allocate on the stack. Using count allows you to
- allocate a simple array on the stack. The brackets
- around count are required. If this field is omitted,
- one data object is assumed.
-
- type The type of variable to allocate. The type argument
- may be one of the following: WORD, DWORD, QWORD,
- TBYTE, or the name of a structure defined by a STRUC
- structure declaration.
-
-
- The assembler sets aside space on the stack, following the same rules as
- for procedure arguments.
-
- The assembler does not initialize local variables. Your program must
- include code to perform any necessary initializations. For example, the
- following code fragment sets up a local array and initializes it to zero:
-
- arraysz EQU 20
-
- aproc PROC
- LOCAL var1[arraysz]:WORD, var2:WORD
- .
- .
- .
- ; Initialize local array to zero
- mov cx,arraysz
- xor ax,ax
- xor di,di ; Use di as array index
- repeat: mov var1[di],ax
- inc di
- inc di
- loop repeat
- ; Use the array...
- .
- .
- .
- ret
- aproc
-
-
- 15.3.7 Variable Scope
-
- When you use the extended form of the .MODEL directive, the assembler
- makes all identifiers inside a procedure local to the procedure. Labels
- ending with a colon (:), procedure arguments, and local variables declared
- in a LOCAL directive are undefined outside of the procedure. Variables
- defined outside of any procedure are available inside a procedure. For
- example, in the following fragment, var1 can be used in proc1 and proc2,
- while var2──because it is defined in proc2──is not available to proc1:
-
- .MODEL medium,c
- .DATA
- var1 DW 256 ; Available to proc1 and proc2
-
- .CODE
- proc1 PROC
- .
- .
- .
- exit: ret
- proc1 ENDP
-
- proc2 PROC
- LOCAL var2:WORD ; This var2 only available in proc2
- .
- .
- .
- exit: ret
- proc2 ENDP
-
- If proc1 contained a LOCAL directive defining var2, that var2 would be a
- completely different variable than the var2 in proc2.
-
- Notice that both procedures contain the label exit. Because labels are
- local when you use the language option on the .MODEL directive, you may
- use the same labels in different procedures. You can make a label in a
- procedure global (make it available outside the procedure) by ending it
- with two colons:
-
- proc3 PROC
- .
- .
- .
- label1::
- .
- .
- .
- proc3 ENDP
-
- In the preceding example, label1 is available throughout the file
- containing proc3.
-
-
- 15.3.8 Setting Up Stack Frames
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, the ENTER and LEAVE instructions are
- provided for setting up a stack frame. These instructions do the same
- thing as the multiple instructions at the start and end of procedures in
- the Microsoft calling conventions (see the examples in Section 15.3.3,
- "Passing Arguments on the Stack").
-
- The PROC statement takes advantage of these instructions if you enable the
- extended instruction set with the .186 or .286 directive.
-
- Syntax
-
- ENTER framesize, nestinglevel
- statements
- LEAVE
-
- The ENTER instruction takes two constant operands. The framesize (a 16-bit
- constant) specifies the number of bytes to reserve for local variables.
- The nestinglevel (an 8-bit constant) specifies the level at which the
- procedure is nested. This operand should always be 0 when writing
- procedures for BASIC, C, and FORTRAN. The nestinglevel can be greater than
- 0 with Pascal and other languages that enable procedures to access the
- local variables of calling procedures.
-
- The LEAVE instruction reverses the effect of the last ENTER instruction by
- restoring BP and SP to their values before the procedure call.
-
- Example 1
-
- task PROC NEAR
- enter 6,0 ; Set stack frame and reserve 6
- . ; bytes for local variables
- . ; Do task here
- .
- leave ; Restore stack frame
- ret ; Return
- task ENDP
-
- Example 1 has the same effect as the code in Example 2.
-
- Example 2
-
- task PROC NEAR
- push bp ; Save base pointer
- mov bp,sp ; Load stack into base pointer
- sub sp,6 ; Reserve 6 bytes for local variables
- .
- . ; Do task here
- .
-
- mov sp,bp ; Restore stack pointer
- pop bp ; Restore base
- ret ; Return
- task ENDP
-
- The code in Example 1 takes fewer bytes, but is slightly slower. See
- on-line Help on instructions for exact comparisons of size and timing.
-
-
- 15.4 Using Interrupts
-
- "Interrupts" are a special form of routines that are called by number
- instead of by address. They can be initiated by hardware devices as well
- as by software. Hardware interrupts are called automatically whenever
- certain events occur in the hardware.
-
- Interrupts can have any number from 0 to 255. Most of the interrupts with
- lower numbers are reserved for use by the processor, DOS, or the ROM BIOS.
-
- The programmer can call existing interrupts with the INT instruction.
- Interrupt routines can also be defined or redefined to be called later.
- For example, an interrupt routine that is called automatically by a
- hardware device can be redefined so that its action is different.
-
- DOS defines several interrupt handlers. Two that are sometimes used by
- applications programmers are listed below:
-
- Interrupt Description
- ──────────────────────────────────────────────────────────────────────────
- 0 Divide overflow. Called automatically when the
- quotient of a divide operation is too large for the
- source operand or when a divide by zero is attempted.
-
- 4 Overflow. Called by the INTO instruction if the
- overflow flag is set.
-
-
- Interrupt 21H is the normal method of using DOS functions. To call a
- function, place the function number in AH, put arguments in registers as
- appropriate, then call the interrupt. For complete documentation of DOS
- functions, see the Microsoft MS-DOS Programmer's Reference, one of the
- many other books on DOS functions, or the on-line Help system.
-
- DOS has several other interrupts, but they should not normally be called.
- Some (such as 20H and 27H) have been replaced by DOS functions. Others are
- used internally by DOS.
-
- You can also access ROM-BIOS services through interrupt calls. See the
- on-line Help system for a description of all these services.
-
-
- 15.4.1 Calling Interrupts
-
- Interrupts are called with the INT instruction.
-
- Syntax
-
- INT interruptnumber
- INTO
-
- The INT instruction takes an immediate operand with a value between 0 and
- 255.
-
- When calling DOS and ROM-BIOS interrupts, a function number is usually
- placed in the AH register. Other registers may be used to pass arguments
- to functions. Some interrupts and functions return values in certain
- registers. Register use varies for each interrupt.
-
- When the instruction is called, the processor takes the following six
- steps:
-
- 1. Looks up the address of the interrupt routine in the interrupt
- descriptor table. In real mode, this table starts at the lowest point
- in memory (segment 0, offset 0) and consists of four bytes (two segment
- and two offset) for each interrupt. Thus, the address of an interrupt
- routine can be found by multiplying the number of the interrupt by 4.
-
- 2. Pushes the flags register, the current code segment (CS), and the
- current instruction pointer (IP).
-
- 3. Clears the trap (TF) and interrupt enable (IF) flags.
-
- 4. Jumps to the address of the interrupt routine, as specified in the
- interrupt description table.
-
- 5. Executes the code of the interrupt routine until it encounters an IRET
- instruction.
-
- 6. Pops the instruction pointer, code segment, and flags.
-
- Figure 15.3 illustrates how interrupts work.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 15.4.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The INTO (Interrupt On Overflow) instruction is a variation of the INT
- instruction. It calls interrupt 04H if called when the overflow flag is
- set. By default, the routine for interrupt 4 simply consists of an IRET so
- that it returns without doing anything. However, you can write your own
- overflow interrupt routine. Using INTO is an alternative to using JO (Jump
- On Overflow) to jump to an overflow routine. Section 15.4.2, "Defining
- and Redefining Interrupt Routines," gives an example of this.
-
- The CLI (Clear Interrupt Flag) and STI (Set Interrupt Flag) instructions
- can be used to turn interrupts on or off. You can use CLI to turn
- interrupt processing off so that an important routine cannot be stopped by
- a hardware interrupt. After the routine has finished, use STI to turn
- interrupt processing back on. Interrupts received while interrupt
- processing was turned off by CLI are saved and executed when STI turns
- interrupts back on.
-
- Example 1
-
- ; DOS call (Display String)
-
- mov ah,09h ; Load function number
- mov dx,OFFSET string ; Load argument
- int 21h ; Call DOS
-
- Example 2
-
- ; BIOS call (Read Character from Keyboard)
-
- xor ah,ah ; Load function number 0 in AH
- int 16h ; Call BIOS
- ; Return scan code in AH
- ; Return ascii code in AL
-
- Example 1 is a call to a DOS function.
-
- Example 2 is a ROM-BIOS call that works on IBM Personal Computers and
- IBM-compatible computers. See the on-line Help system for complete
- information on DOS and BIOS calls.
-
-
- 15.4.2 Defining and Redefining Interrupt Routines
-
- You can write your own interrupt routines, either to replace an existing
- routine or to use an undefined interrupt number.
-
- Syntax
-
- label PROC FAR[[USES reglist]]
- statements
- IRET
- label ENDP
-
- An interrupt routine can be written like a procedure by using the PROC and
- ENDP directives. The only differences are that the routine should always
- be defined as far and the routine should be terminated by an IRET
- instruction instead of a RET instruction.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Since the assembler doesn't know whether you are going to terminate
- with a RET or an IRET, it is possible to use the full extended PROC syntax
- (described in Section 15.3.4) for interrupt procedures. However, making
- interrupt procedures NEAR or specifying arguments for them makes no sense.
- The USES keyword does correctly generate code to save and restore a
- register list in interrupt procedures.
- ──────────────────────────────────────────────────────────────────────────
-
- Your program should replace the address in the interrupt descriptor table
- with the address of your routine. DOS calls are provided for this task.
- Another common technique is to jump to the old interrupt routine and let
- it do the IRET instruction.
-
- It is usually a good idea to save the old address and restore it before
- your program ends.
-
- Interrupt routines you may want to replace include the processor's
- divide-overflow (0H) and overflow (04H) interrupts. You can also replace
- DOS interrupts, such as the critical-error (24H) and CONTROL+C (23H)
- handlers. Interrupt routines can be part of device drivers. Writing
- interrupt routines is usually a systems task. The example below
- illustrates a simple routine. For complete information, see the Microsoft
- MS-DOS Programmer's Reference or one of the other reference books on DOS.
-
- Example
-
- .DATA
- message DB "Overflow - result set to 0",13,10,"$"
- vector DD ?
- .CODE
- .STARTUP
-
- mov ax,3504h ; Load interrupt 4 and call DOS
- int 21h ; get interrupt vector function
- mov WORD PTR vector[2],es ; Save segment
- mov WORD PTR vector[0],bx ; and offset
-
- push ds ; Save DS
- mov ax,cs ; Load segment of new routine
- mov ds,ax
- mov dx,OFFSET overflow ; Load offset of new routine
- mov ax,2504h ; Load interrupt 4 and call DOS
- int 21h ; set interrupt vector function
- pop ds ; Restore
- .
- .
- .
- add ax,bx ; Do addition (or multiplication)
- into ; Call interrupt 4 if overflow
- .
- .
- .
- lds dx,vector ; Load original interrupt address
- mov ax,2504h ; Restore interrupt number 4
- int 21h ; with DOS set vector function
- mov ax,4C00h ; Terminate function
- int 21h
-
- overflow PROC FAR
- sti ; Enable interrupts
- ; (turned off by INT)
- mov ah,09h ; Display string function
- mov dx,OFFSET message ; Load address
- int 21h ; Call DOS
- xor ax,ax ; Set AX to 0
- xor dx,dx ; Set DX to 0
- iret ; Return
- overflow ENDP
- END start
-
- In this example, DOS functions are used to save the address of the initial
- interrupt routine in a variable and to put the address of the new
- interrupt routine in the interrupt table. Once the new address has been
- set, the new routine is called any time the interrupt is called. The
- sample interrupt handler sets the result of a calculation that causes an
- overflow (either in AX or AX:DX) to 0. It is good practice to restore the
- original interrupt address before terminating the program.
-
-
- 15.5 Checking Memory Ranges
-
- 80186/286/386 Only
-
- Starting with the 80186 processor, the BOUND instruction can check to see
- if a value is within a specified range. This instruction is usually used
- to check a signed index value to see if it is within the range of an
- array. BOUND is a conditional interrupt instruction like INTO. If the
- condition is not met (the index is out of range), an interrupt 5 is
- executed.
-
- Syntax
-
- BOUND register16, memory32
-
- To use it for this purpose, the starting and ending values of the array mu
- st be stored as 16-bit values in the low and high words of a doubleword me
- mory operand. This operand is given as the source operand. The index value
- to be checked is given as the destination operand. If the index value is
- out of range, the instruction issues interrupt 5. This means that the oper
- ating system or the program must provide an interrupt routine for interrup
- t 5. DOS does not provide such a routine, so you must write your own. See
- Section 15.4, "Using Interrupts," for more information.
-
- Example
-
- .DATA
- bottom EQU 0
- top EQU 19
- dbounds LABEL DWORD ; Allocate boundaries
- wbounds DW bottom,top ; initialized to bounds
- array DB top+1 DUP (?) ; Allocate array
- .CODE
- .
- .
- . ; Assume index in DI
- bound di,dbounds ; Check to see if it is in range
- ; if out of range, interrupt 5
- mov dx,array[di] ; If in range, use it
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 16: Processing Strings
-
-
- The 8086-family processors have a full set of instructions for
- manipulating strings. In the discussion of these instructions, the term
- "string" refers not only to the common definition of a string──a sequence
- of bytes containing characters──but to any sequence of bytes or words
-
- The following instructions are provided for 8086-family string functions:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- MOVS Moves string from one location to another
-
- SCAS Scans string for specified values
-
- CMPS Compares values in one string with values in another
-
- LODS Loads values from a string to accumulator register
-
- STOS Stores values from accumulator register to a string
-
- INS Transfers values from a port to memory
-
- OUTS Transfers values from memory to a port
-
-
- All these instructions use registers in the same way and have a similar
- syntax. Most are used with the repeat instruction prefixes: REP, REPE,
- REPNE, REPZ, and REPNZ.
-
- This chapter first explains the general format for string instructions and
- then tells you how to use each instruction.
-
-
- 16.1 Setting Up String Operations
-
- The string instructions all work in a similar way. Once you understand the
- gen-eral procedure, it is easy to adapt the format for a particular string
- operation. The five steps are listed below:
-
- 1. Make sure the direction flag indicates the direction in which you want
- the string to be processed. If the direction flag is clear, the string
- will be pro-cessed up (from low addresses to high addresses). If the
- direction flag is set, the string will be processed down (from high
- addresses to low addresses). The CLD instruction clears the flag, while
- STD sets it. Under DOS, the direction flag will normally be cleared if
- your program has not changed it.
-
- 2. Load the number of iterations for the string instruction into the CX
- register. For instance, if you want to process a 100-byte string, load
- 100. If a string instruction will be terminated conditionally, load the
- maximum number of iterations that can be done without an error.
-
- 3. Load the starting offset address of the source string into DS:SI and
- the starting address of the destination string into ES:DI. Some string
- instructions take only a destination or source (shown in Table 16.1
- below). Normally, the segment address of the source string should be
- DS, but you can use a segment override with the string instruction to
- specify a different segment. You cannot override the segment address
- for the destination string. Therefore, you may need to change the value
- of ES.
-
- 4. Choose the appropriate repeat-prefix instruction. Table 16.1 shows the
- repeat prefixes that can be used with each instruction.
-
- 5. Put the appropriate string instruction immediately after the repeat
- prefix (on the same line).
-
- String instructions have two basic forms, as shown below:
-
- Syntax 1
-
- [[repeatprefix]] stringinstruction[[ES:[[destination,]]]]
- [[[[segmentregister:]]source]]
-
- The string instruction can be given with the source and/or destination as
- operands. The size of the operand or operands indicates the size of the
- objects to be processed by the string. Note that the operands only specify
- the size. The actual values to be worked on are the ones pointed to by
- DS:SI and/or ES:DI. No error is generated if the operand is not the same
- as the actual source or destination. One important advantage of this
- syntax is that the source operand can have a segment override. The
- destination operand is always relative to ES and cannot be overridden.
-
- Syntax 2
-
- [[repeatprefix]] stringinstructionB
- [[repeatprefix]] stringinstructionW
-
- The letter B or W appended to stringinstruction indicates bytes or words.
- With a letter appended to a string instruction, no operand is allowed.
-
- For instance, MOVS can be given with byte operands to move bytes or with
- word operands to move words. As an alternative, MOVSB can be given with no
- operands to move bytes, or MOVSW can be given with no operands to move
- words.
-
- Note that instructions that specify the size in the name never accept
- operands. Therefore, the following statement is illegal:
-
- lodsb es:0 ; Illegal - no operand allowed
-
- Instead, the statement must be coded as shown below:
-
- lods BYTE PTR es:0 ; Legal - use type specifier
-
- If a repeat prefix is used, it can be one of the following instructions:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- REP Repeats for a specified number of iterations. The
- number is given in CX.
-
- REPE or REPZ Repeats while equal. The maximum number of iterations
- should be specified in CX.
-
- REPNE or REPNZ Repeats while not equal. The maximum number of
- iterations should be specified in CX.
-
-
- REPE is the same as REPZ, and REPNE is the same as REPNZ. You can use
- whichever name you find more mnemonic. The prefixes ending with E are used
- in syntax listings and tables in the rest of this chapter.
-
- Table 16.1 lists each string instruction with the type of repeat prefix it
- uses and whether the instruction works on a source, a destination, or
- both.
-
- Table 16.1 Requirements for String Instructions
-
- Instruction Repeat Prefix Source/Destination Register Pair
- ──────────────────────────────────────────────────────────────────────────
- MOVS REP Both DS:SI, ES:DI
-
- SCAS REPE/REPNE Destination ES:DI
-
- CMPS REPE/REPNE Both ES:DI, DS:SI
-
- LODS None Source DS:SI
-
- STOS REP Destination ES:DI
-
- INS REP Destination ES:DI
-
- OUTS REP Source DS:SI
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- At run time, a string instruction preceded by a repeat sequence causes the
- processor to take the following steps:
-
- 1. Checks the CX registers and exits from the string instruction if CX is
- 0.
-
- 2. Performs the string operation once.
-
- 3. Increases SI and/or DI if the direction flag is cleared. Decreases SI
- and/or DI if the direction flag is set. The amount of increase or
- decrease is 1 for byte operations, 2 for word operations.
-
- 4. Decrements CX (no flags are modified).
-
- 5. If the string instruction is SCAS or CMPS, checks the zero flag and
- exits if the repeat condition is false──that is, if the flag is set
- with REPE or REPZ or if it is clear with REPNE or REPNZ.
-
- 6. Goes to the next iteration (step 1).
-
- Although string instructions (except LODS) are most often used with repeat
- prefixes, they can also be used by themselves. In this case, the SI and/or
- DI registers are adjusted as specified by the direction flag and the size
- of operands. However, you must decrement the CX register and set up a loop
- for the repeated action.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Although you can use a segment override on the source operand, a
- segment override combined with a repeat prefix can cause problems in
- certain situations. If an interrupt occurs during the string operation,
- the segment override is lost and the rest of the string operation
- processes incorrectly. Segment overrides can be used safely when
- interrupts are turned off.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 16.2 Moving Strings
-
- The MOVS instruction is used to move data from one area of memory to
- another.
-
- Syntax
-
- [[REP]] MOVS [[ES:]]destination,[[segmentregister:]]source
- [[REP]] MOVSB
- [[REP]] MOVSW
-
- To move the data, load the count and the source and destination addresses
- into the appropriate registers. Then use REP with the MOVS instruction.
-
- Example 1
-
- .MODEL small
- .DATA
- source DB 10 DUP ('0123456789')
- destin DB 100 DUP (?)
- .CODE
- mov ax,@data ; Load same segment
- mov ds,ax ; to both DS
- mov es,ax ; and ES
- .
- .
- .
- cld ; Work upward
- mov cx,100 ; Set iteration count to 100
- mov si,OFFSET source ; Load address of source
- mov di,OFFSET destin ; Load address of destination
- rep movsb ; Move 100 bytes
-
- Example 1 shows how to move a string by using string instructions. For
- comparison, Example 2 shows a much less efficient way of doing the same
- operation without string instructions.
-
- Example 2
-
- .MODEL small
- .DATA
- source DB 10 DUP ('0123456789')
- destin DB 100 DUP (?)
- .CODE
- . ; Assume ES = DS
- .
- .
- mov cx,100 ; Set iteration count to 100
- mov si,OFFSET source ; Load offset of source
- mov di,OFFSET destin ; Load offset of destination
- repeat: mov al,[si] ; Get a byte from source
- mov [di],al ; Put it in destination
- inc si ; Increment source pointer
- inc di ; Increment destination pointer
- loop repeat ; Do it again
-
- Both examples illustrate how to move byte strings in a small-model program
- in which DS already points to the segment containing the variables. In
- such programs, ES can be set to the same value as DS.
-
- There are several variations on this. If the source string was not in the
- current data segment, you could load the starting address of its segment
- into ES. Another option would be to use the MOVS instruction with operands
- and give a segment override on the source operand. For example, you could
- use the following statement if ES pointed to both the source and the
- destination strings:
-
- rep movs destin,es:source
-
- It is sometimes faster to move a string of bytes as words. You must adjust
- for any odd bytes, as shown in Example 3. Assume the source and
- destination are already loaded.
-
- Example 3
-
- mov cx,count ; Load count
- shr cx,1 ; Divide by 2 (carry will be set
- ; if count is odd)
- rep movsw ; Move words
- rcl cx,1 ; If odd, make CX 1
- rep movsb ; Move odd byte if there is one
-
-
- 16.3 Searching Strings
-
- The SCAS instruction is used to scan a string for a specified value.
-
- Syntax
-
- [[REPE | REPNE]] SCAS [[ES:]]destination
- [[REPE | REPNE]] SCASB
- [[REPE | REPNE]] SCASW
-
- SCAS and its variations work only on a destination string, which must be
- pointed to by ES:DI. The value to scan for must be in the accumulator
- register──AL for bytes, AX for words.
-
- The SCAS instruction works by comparing the value pointed to by DI with
- the value in the accumulator. If the values are the same, the zero flag is
- set. Thus, the instruction only makes sense when used with one of the
- repeat prefixes that checks the zero flag.
-
- If you want to search for the first occurrence of a specified value, use
- the REPNE or REPNZ instruction. If the value is found, ES:DI will point to
- the value immediately after the first occurrence. You can decrement DI to
- make it point to the first matching value.
-
- If you want to search for the first value that does not have a specified
- value, use REPE or REPZ. If the value is found, ES:DI will point to the
- position after the first nonmatching value. You can decrement DI to make
- it point to the first non-matching value.
-
- After a REPNE SCAS, the zero flag will be cleared if no match was found.
- After a REPE SCAS, the zero flag will be set if no nonmatch was found.
-
- Example
-
- .DATA
- string DB "The quick brown fox jumps over the lazy dog"
- lstring EQU $-string ; Length of string
- pstring DD string ; Far pointer to string
- .CODE
- .
- .
- .
- cld ; Work upward
- mov cx,lstring ; Load length of string
- les di,pstring ; Load address of string
- mov al,'z' ; Load character to find
- repne scasb ; Search
- jnz notfound ; CX is 0 if not found
- . ; ES:DI points to character
- . ; after first 'z'
- .
- notfound: ; Special case for not found
-
- This example assumes that ES is not the same as DS, but that the address
- of the string is stored in a pointer variable. The LES instruction is used
- to load the far address of the string into ES:DI.
-
-
- 16.4 Comparing Strings
-
- The CMPS instruction is used to compare two strings and point to the
- address where a match or nonmatch occurs.
-
- Syntax
-
- [[REPE | REPNE]] CMPS [[segmentregister:]]source,[[ES:]],destination
- [[REPE | REPNE]] CMPSB
- [[REPE | REPNE]] CMPSW
-
- The count and the addresses of the strings are loaded into registers, as
- described in Section 16.1, "Setting Up String Operations." Either string
- can be considered the destination or source string unless a segment
- override is used. Notice that unlike other instructions, CMPS requires
- that the source be on the left.
-
- The CMPS instruction works by comparing, in turn, each value pointed to by
- DI with the value pointed to by SI. If the values are the same, the zero
- flag is set. Thus, the instruction makes sense only when used with one of
- the repeat prefixes that checks the zero flag.
-
- If you want to search for the first match between the strings, use the
- REPNE or REPNZ instruction. If a match is found, ES:DI and DS:SI will
- point to the position after the first match in the respective strings. You
- can decrement DI or SI to point to the match. (Conversely, you would
- increment DI or SI if the direction flag was set.)
-
- If you want to search for a nonmatch, use REPE or REPZ. If a nonmatch is
- found, ES:DI and DS:SI will point to the position after the first nonmatch
- in the respective strings. You can decrement DI or SI to point to the
- nonmatch.
-
- After a REPNE CMPS, the zero flag will be cleared if no match was found.
- After a REPE CMPS, the zero flag will be set if no nonmatch was found.
-
- Example
-
- .MODEL large
- .DATA
- string1 DB "The quick brown fox jumps over the lazy dog"
- .FARDATA
- string2 DB "The quick brown dog jumps over the lazy fox"
- lstring EQU $-string2
- .CODE
- mov ax,@data ; Load data segment
- mov ds,ax ; into DS
- mov ax,@fardata ; Load far data segment
- mov es,ax ; into ES
- .
- .
- .
- cld ; Work upward
- mov cx,lstring ; Load length of string
- mov si,OFFSET string1 ; Load offset of string1
- mov di,OFFSET string2 ; Load offset of string2
- repe cmpsb ; Compare
- jnz allmatch ; CX is 0 if no nonmatch
- dec si ; Adjust to point to nonmatch
- dec di ; in each string
- .
- .
- allmatch: . ; Special case for all match
-
- This example assumes that the strings are in different segments. Both
- segments must be initialized to the appropriate segment register.
-
-
- 16.5 Filling Strings
-
- The STOS instruction is used to store a specified value in each position
- of a string.
-
- Syntax
-
- [[REP]] STOS [[ES:]]destination
- [[REP]] STOSB
- [[REP]] STOSW
-
- The string is considered the destination, so it must be pointed to by
- ES:DI. The length and address of the string must be loaded into registers,
- as described in Section 16.1, "Setting Up String Operations." The value
- to store must be in the accumulator register──AL for bytes, AX for words.
-
- For each iteration specified by the REP instruction prefix, the value in
- the accumulator is loaded into the string.
-
- Example
-
- .MODEL small
- .DATA
- destin DB 100 DUP ?
- .CODE
- . ; Assume ES = DS
- .
- .
- cld ; Work upward
- mov ax,'aa' ; Load character to fill
- mov cx,50 ; Load length of string
- mov di,OFFSET destin ; Load address of destination
- rep stosw ; Store 'a' into array
-
- This example loads 100 bytes containing the character 'a'. Notice that
- this is done by storing 50 words rather than 100 bytes. This makes the
- code faster by reducing the number of iterations. You would have to adjust
- for the last byte if you wanted to fill an odd number of bytes.
-
-
- 16.6 Loading Values from Strings
-
- The LODS instruction is used to load a value from a string into a
- register.
-
- Syntax
-
- LODS [[segmentregister:]]source
- LODSB
- LODSW
-
- The string is considered the source, so it must be pointed to by DS:SI.
- The value is always loaded from the string into the accumulator
- register──AL for bytes, AX for words.
-
- Unlike other string instructions, LODS is not normally used with a repeat
- prefix since there is no reason to move a value repeatedly to a register.
- However, LODS does adjust the SI register as specified by the direction
- flag and the size of operands. The programmer must code the instructions
- to use the value after it is loaded.
-
- Example 1
-
- .DATA
- stuff DB 0,1,2,3,4,5,6,7,8,9
- .CODE
- .
- .
- .
- cld ; Work upward
- mov cx,10 ; Load length
- mov si,OFFSET stuff ; Load offset of source
- mov ah,2 ; Display character function
- get: lodsb ; Get a character
- add al,'0' ; Convert to ASCII
- mov dl,al ; Move to DL
- int 21h ; Call DOS to display character
- loop get ; Repeat
-
- Example 1 loads, processes, and displays each byte in a string of bytes.
-
- Example 2
-
- .DATA
- buffer DB 80 DUP(?) ; Create buffer for argument strin
- .CODE
- start: mov ax,@data ; Initialize DS
- mov ds,ax
- ; On start-up ES points to PSP
- cld ; Work upward
- mov cl,BYTE PTR es:[80h] ; Load length of arguments
- xor ch,ch
- mov di,OFFSET buffer ; Load offset of buffer
- mov si,82h ; Load position of argument string
- mov dx,es ; Exchange ES and DS
- mov ax,ds
- mov es,ax
- mov ds,dx
- another: lodsb ; Get a character
- cmp al,'a' ; Is it high enough to be upper?
- jb noway ; No? Check
- cmp al,'z' ; Is it low enough to be letter?
- ja noway
- sub al,32 ; Yes? Convert to uppercase
-
- noway: stosb
- loop another ; Repeat
- mov dx,es ; Restore ES and DS
- mov ax,ds
- mov es,ax
- mov ds,dx
-
- Example 2 copies the command arguments from position 82H in the DOS
- Program Segment Prefix (PSP) while converting them to uppercase. See the
- Microsoft MS-DOS Programmer's Reference or one of the many other books on
- DOS for information about the PSP. Notice that both LODSB and STOSB are
- used without repeat prefixes.
-
-
- 16.7 Transferring Strings to and from Ports
-
- 80186/286/386 Only
-
- The INS instruction reads a string from a port to memory, and the OUTS
- instruction writes a string from memory to a port.
-
- Syntax
-
- OUTS DX,[[segmentregister:]]source
- OUTSB
- OUTSW
-
- INS [[ES:]]destination,DX
- INSB
- INSW
-
- The INS and OUTS instructions require that the number of the port be in
- DX. The port cannot be specified as an immediate value, as it can be with
- IN and OUT.
-
- To move the data, load the count into CX. The string to be transferred by
- INS is considered the destination string, so it must be pointed to by
- ES:DI. The string to be transferred by OUTS is considered the source
- string, so it must be pointed to by DS:SI.
-
- If you specify the source or destination as an operand, DX must be
- specified. Otherwise, DX is assumed and should be omitted.
-
- If you need to process the string as it is transferred (for instance, to
- check for the end of a null-terminated string), you must set up the loop
- yourself instead of using the REP instruction prefix.
-
- Example
-
- .DATA
- count EQU 100
- buffer DB count DUP (?)
- inport DW ?
- .CODE
- . ; Assume ES = DS
- .
- .
- cld ; Work upward
- mov cx,count ; Load length to transfer
- mov di,OFFSET buffer ; Load address of destination
- mov dx,inport ; Load port number
- rep insb ; Transfer the string
- ; from port to buffer
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 17: Calculating with a Math Coprocessor
-
-
- The 8087-family coprocessors are used to do fast mathematical
- calculations. When used with real numbers, packed binary coded decimal
- (BCD) numbers, or long integers, they do calculations many times faster
- than the same operations done with 8086-family processors.
-
- This chapter explains how to use the 8087-family processors to transfer
- and process data. The approach taken is from an applications standpoint.
- Features that would be used by systems programmers (such the flags used
- when writing exception handlers) are not explained. This chapter is
- intended as a reference, not a tutorial.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE This manual does not attempt to explain the mathematical concepts
- involved in using certain coprocessor features. It assumes that you will
- not need to use a feature unless you understand the mathematics involved.
- For example, you need to understand logarithms to use the FYL2X and
- FYL2XP1 instructions.
- ──────────────────────────────────────────────────────────────────────────
-
-
- 17.1 Coprocessor Architecture
-
- The math coprocessor works simultaneously with the main processor.
- However, since the coprocessor cannot handle device input or output, most
- data originates in the main processor.
-
- The main processor and the coprocessor have their own registers, which are
- completely separate and inaccessible to the other. They exchange data
- through memory, since memory is available to both.
-
- Ordinarily you follow these three steps when using the coprocessor:
-
- 1. Load data from memory to coprocessor registers.
-
- 2. Process the data.
-
- 3. Store the data from coprocessor registers back to memory.
-
- Step 2, processing the data, can occur while the main processor is
- handling other tasks. Steps 1 and 3 must be coordinated with the main
- processor so that the processor and coprocessor do not try to access the
- same memory at the same time, as explained in Section 17.5, "Transferring
- Data."
-
-
- 17.1.1 Coprocessor Data Registers
-
- The 8087-family coprocessors have eight 80-bit data registers. Unlike
- 8086-family registers, the coprocessor data registers are organized as a
- stack. As data is pushed into the top register, previous data items move
- into higher-numbered registers. Register 0 is the top of the stack;
- register 7 is the bottom. The syntax for specifying registers is shown
- below:
-
- ST[[(number)]]
-
- The number must be a digit between 0 and 7, or a constant expression that
- evaluates to a number from 0 to 7. If number is omitted, register 0 (top
- of stack) is assumed.
-
- All coprocessor data is stored in registers in the temporary-real format.
- This is the 10-byte IEEE format described in Section 6.5.1.4,
- "Real-Number Variables." The registers and the register format are shown
- in Figure 17.1.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.1.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Internally, all calculations are done on numbers of the same type. Since
- temporary-real numbers have the greatest precision, lower-precision
- numbers are guaranteed not to lose precision as a result of calculations.
- The instructions that transfer values between the main processor and the
- coprocessor automatically convert numbers to and from the temporary-real
- format.
-
-
- 17.1.2 Coprocessor Control Registers
-
- The 8087-family coprocessors have seven 16-bit control registers. The most
- useful control registers are made up of bit fields or flags. Some flags
- control coprocessor operations, while others maintain the current status
- of the coprocessor. In this sense, they are much like the 8086-family
- flags registers.
-
- You do not need to understand these registers to do most coprocessor
- operations. Control flags are set by default to the values appropriate for
- most programs. Errors and exceptions are reported in the status-word
- register. However, the coprocessor already has a default system for
- handling exceptions. Applications programmers can usually accept the
- defaults. Systems programmers may want to use the status-word and
- control-word registers when writing exception handlers, but such problems
- are beyond the scope of this manual.
-
- Figure 17.2 shows the overall layout of the control registers, including
- the control word, status word, tag word, instruction pointer, and operand
- pointer. The format of each of the registers is not shown, since these
- registers are generally of use only to systems programmers. The exception
- is the condition-code bits of the status-word register. These bits are
- explained in Section 17.7, "Controlling Program Flow."
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.1.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The control registers are explained in more detail in the on-line Help
- system.
-
-
- 17.2 Emulation
-
- You can write assembly-language procedures that use the emulator library
- when called from QuickC. First write the procedure by using coprocessor
- instructions, then assemble it using the /E option, and finally link it
- with your high-level-language modules. When compiling modules, use the
- compiler options that specify emulation.
-
- Some coprocessor instructions are not emulated by Microsoft emulation
- libraries. Which instructions are emulated varies depending on the
- language and version. If you use a coprocessor instruction that is not
- emulated, the program will generate a run-time error when it tries to
- execute the unemulated instruction. You cannot use a Microsoft emulation
- library with stand-alone assembler programs, since the library depends on
- the compiler start-up code.
-
- See Appendix B, Section B.6, "Creating Code for a Floating-Point
- Emulator," for information on the /E option. See Appendix A,
- "Mixed-Language Mechanics," for information on writing assembly-language
- procedures for high-level languages.
-
-
- 17.3 Using Coprocessor Instructions
-
- Coprocessor instructions are readily recognizable because, unlike all
- 8086-family instruction mnemonics, they start with the letter F.
-
- Most coprocessor instructions have two operands, but in many cases, one or
- both operands are implied. Often, one operand can be a memory operand; in
- this case, the other operand is always implied as the stack-top register.
- Coprocessor instructions can never have immediate operands, and with the
- exception of the FSTSW instruction (see Section 17.5.2, "Loading
- Constants"), they cannot have processor registers as operands. As with
- 8086-family instructions, memory-to-memory operations are never allowed.
- One operand must be a coprocessor register.
-
- Instructions usually have a source and a destination operand. The source
- specifies one of the values to be processed. It is never changed by the
- operation. The destination specifies the value to be operated on and
- replaced with the result of the operation. If two operands are specified,
- the first is the destination and the second is the source.
-
- The stack organization of registers gives the programmer flexibility to
- think of registers either as elements on a stack or as registers much like
- 8086-family registers. Table 17.1 lists the variations of coprocessor
- instructions along with the syntax for each.
-
- Table 17.1 Coprocessor Operand Forms
-
- Instruction Form Implied Syntax Operands Example
- ──────────────────────────────────────────────────────────────────────────
- Classical-stack Faction ST(1), ST fadd
-
- Memory Faction memory ST fadd memloc
-
- Register Faction ST(num), fadd st(5),st Faction ST, ST(num)
- ST fadd st,st(3)
-
- Register pop FactionP ST(num), faddp st(4),st
- ST
- ──────────────────────────────────────────────────────────────────────────
-
-
- Not all instructions accept all operand variations. For example, load and
- store instructions always require the memory form. Load-constant
- instructions always take the classical-stack form. Arithmetic instructions
- can usually take any form.
-
- Some instructions that accept the memory form can have the letter I
- (integer) or B (BCD) following the initial F to specify how a memory
- operand is to be interpreted. For example, FILD interprets its operand as
- an integer and FBLD interprets its operand as a BCD number. If no type
- letter is included in the instruction name, the instruction works on real
- numbers.
-
-
- 17.3.1 Using Implied Operands in the Classical-Stack Form
-
- The classical-stack form treats coprocessor registers like items on a
- stack. Items are pushed onto or popped off the top elements of the stack.
- Since only the top item can be accessed on a traditional stack, there is
- no need to specify operands. The first register (and the second if there
- are two operands) is always assumed.
-
- In arithmetic operations (see Section 17.6), the top of the stack (ST) is
- the source operand, and the second register (ST(1)) is the destination.
- The result of the operation goes into the destination operand, and the
- source is popped off the stack. The effect is that both of the values used
- in the operation are destroyed and the result is left at the top of the
- stack.
-
- Instructions that load constants always use the stack form (see Section
- 17.5.1, "Transferring Data to and from Registers"). In this case, the
- constant created by the instruction is the implied source, and the top of
- the stack (ST) is the destination. The source is pushed into the
- destination.
-
- Note that the classical-stack form with its implied operands is similar to
- the register-pop form, not to the register form. For example, fadd, with
- the implied operands ST(1),ST, is equivalent to faddp st(1),st, rather
- than to fadd st(1),st.
-
- Example
-
- fld1 ; Push 1 into first position
- fldpi ; Push pi into first position
- fadd ; Add pi and 1 and pop
-
- The status of the register stack after each instruction is shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.3.1 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 17.3.2 Using Memory Operands
-
- The memory form treats coprocessor registers like items on a stack. Items
- are pushed from memory onto the top element of the stack, or popped from
- the top element to memory. Since only the top item can be accessed on a
- traditional stack, there is no need to specify the stack operand. The top
- register (ST) is always assumed. However, the memory operand must be
- specified.
-
- Memory operands can be used in load and store instructions (see Section
- 17.5.1, "Transferring Data to and from Registers"). Load instructions
- push source values from memory to an implied destination register (ST).
- Store instructions pop source values from an implied source register (ST)
- to the destination in memory. Some versions of store instructions pop the
- register stack so that the source is destroyed. Others simply copy the
- source without changing the stack.
-
- Memory operands can also be used in calculation instructions that operate
- on two values (see Section 17.6, "Doing Arithmetic Calculations"). The
- memory operand is always the source. The stack top (ST) is always the
- implied destination. The result of the operation replaces the destination
- without changing its stack position.
-
- Example
-
- .DATA
- m1 DD 1.0
- m2 DD 2.0
- .CODE
- .
- .
- .
- fld m1 ; Push m1 into first position
- fld m2 ; Push m2 into first position
- fadd m1 ; Add m2 to first position
- fstp m1 ; Pop first position into m1
- fst m2 ; Copy first position to m2
-
- The status of the register stack and the memory locations used in the
- instructions is shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.3.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 17.3.3 Specifying Operands in the Register Form
-
- The register form treats coprocessor registers as traditional registers.
- Registers are specified the same as 8086-family instructions with two
- register operands. The only limitation is that one of the two registers
- must be the stack top (ST).
-
- In the register form, operands are specified by name. The second operand
- is the source; it is not affected by the operation. The first operand is
- the destination; its value is replaced with the result of the operation.
- The stack position of the operands does not change.
-
- The register form can only be used with the FXCH instruction and with
- arithmetic instructions that do calculations on two values. With the FXCH
- instruction, the stack top is implied and need not be specified.
-
- Example
-
- fadd st(1),st ; Add second position to first -
- ; result goes in second position
- fadd st,st(2) ; Add first position to second -
- ; result goes in first position
- fxch st(1) ; Exchange first and second positions
-
- The status of the register stack if the registers were previously
- initialized to 1.0, 2.0, and 3.0 is shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.3.3 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 17.3.4 Specifying Operands in the Register-Pop Form
-
- The register-pop form treats coprocessor registers as a modified stack.
- This form has some of the aspects of both a stack and registers. The
- destination register can be specified by name, but the source register
- must always be the stack top.
-
- The result of the operation will be placed in the destination operand, and
- the stack top will be popped off the stack. The effect is that both values
- being operated on will be destroyed and the result of the operation will
- be saved in the specified destination register. The register-pop form is
- only used for instructions that do calculations on two values.
-
- Example
-
- faddp st(2),st ; Add first and third positions and pop -
- ; first position destroyed
- ; third moves to second and holds result
-
- The status of the register stack if the registers were already initialized
- to 1.0, 2.0, and 3.0 is shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.3.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
-
- 17.4 Coordinating Memory Access
-
- Problems of coordinating memory access can occur when the coprocessor and
- the main processor both try to access a memory location at the same time.
- Since the processor and coprocessor work independently, they may not
- finish working on memory in the order in which you give instructions.
- There are two separate cases, and they are handled in different ways.
-
- In the first case, if a processor instruction is given and then followed
- by a coprocessor instruction, the coprocessor must wait until the
- processor is finished before it can start the next instruction. This is
- handled automatically by Quick-Assembler for the 8088 and 8086 or by the
- processor for the 80186 and 80286.
-
- ──────────────────────────────────────────────────────────────────────────
- Coprocessor Differences To synchronize operations between the 8088 or
- 8086 processor and the 8087 coprocessor, each 8087 instruction must be
- preceded by a WAIT instruction. This is not necessary for the 80287. If
- you use the .8087 directive, QuickAssembler inserts WAIT instructions
- automatically. However, if you use the .286 directive, QuickAssembler
- assumes the instructions are for the 80287 or 80387 and does not insert
- the WAIT instructions. If your code will never need to run on an 8086 or
- 8088 processor, you can make your programs shorter and more efficient by
- using the .286 directive.
- ──────────────────────────────────────────────────────────────────────────
-
- In the second case, if a coprocessor instruction that accesses memory is
- followed by a processor instruction attempting to access the same memory
- location, memory access is not automatically synchronized. For instance,
- if you store a coprocessor register to a variable and then try to load
- that variable into a processor register, the coprocessor may not be
- finished. Thus, the processor gets the value that was in memory before the
- coprocessor finished, rather than the value stored by the coprocessor. Use
- the WAIT or FWAIT instruction (they are mnemonics for the same
- instruction) to ensure that the coprocessor finishes before the processor
- begins.
-
- Example
-
- ; Coprocessor instruction first - Wait needed
-
- fist mem32 ; Store to memory
- fwait ; Wait until coprocessor is done
- mov ax,WORD PTR mem32 ; Move to register
- mov dx,WORD PTR mem32[2]
-
- ; Processor instruction first - No wait needed
-
- mov WORD PTR mem32,ax ; Load memory
- mov WORD PTR mem32[2],dx
- fild mem32 ; Load to register
-
-
- 17.5 Transferring Data
-
- The 8087-family coprocessors have separate instructions for each of the
- following types of transfers:
-
- 1. Transferring data between memory and registers, or between different
- registers
-
- 2. Loading certain common constants into registers
-
- 3. Transferring control data to and from memory
-
-
- 17.5.1 Transferring Data to and from Registers
-
- Data-transfer instructions transfer data between main memory and the
- coprocessor registers, or between different coprocessor registers. Two
- basic principles govern data transfers:
-
- ■ The instruction determines whether a value in memory will be considered
- an integer, a BCD number, or a real number. The value is always
- considered a temporary-real number once it is transferred to the
- coprocessor.
-
- ■ The size of the operand determines the size of a value in memory.
- Values in the coprocessor always take up 10 bytes.
-
- The adjustments between formats are made automatically. Notice that
- floating-point numbers must be stored in the IEEE format, not in the
- Microsoft Binary format. Data is automatically stored correctly by
- default. It is stored incorrectly and the coprocessor instructions
- disabled if you use the .MSFLOAT directive. Data formats for real numbers
- are explained in Section 6.5.1.4, "Real-Number Variables."
-
- Data is transferred to stack registers by using load commands. These
- commands push data onto the stack from memory or coprocessor registers.
- Data is removed by using store commands. Some store commands pop data off
- the register stack into memory or coprocessor registers, whereas others
- simply copy the data without changing it on the stack.
-
-
- 17.5.1.1 Real Transfers
-
- The following instructions are available for transferring real numbers:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FLD mem Pushes a copy of mem into ST. The source must be a 4-,
- 8-, or 10-byte memory operand. It is automatically
- converted to the temporary-real format.
-
- FLD ST(num) Pushes a copy of the specified register into ST.
-
- FST mem Copies ST to mem without affecting the register stack.
- The destination can be a 4- or 8-byte memory operand.
- It is automatically converted from temporary-real
- format to short real or long real format, depending on
- the size of the operand. It cannot be stored in the
- 10-byte-real format.
-
- FST ST(num) Copies ST to the specified register. The current value
- of the specified register is replaced.
-
- FSTP mem Pops a copy of ST into mem. The destination can be a
- 4-, 8-, or 10-byte memory operand. It is automatically
- converted from temporary-real format to the
- appropriate real-number format, depending on the size
- of the operand.
-
- FSTP ST(num) Pops ST into the specified register. The current value
- of the specified register is replaced.
-
- FXCH [[ST(num)]] Exchanges the value in ST with the value in ST(num).
- If no operand is specified, ST(0) and ST(1) are
- exchanged.
-
-
-
- 17.5.1.2 Integer Transfers
-
- The following instructions are available for transferring binary integers:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FILD mem The source must be a 2-, 4-, or 8-byte integer memory
- operand. It is interpreted as an integer and converted
- to temporary-real format.
-
- FIST mem Copies ST to mem. The destination must be a 2- or
- 4-byte memory operand. It is automatically converted
- from temporary-real format to a word or a doubleword,
- depending on the size of the operand. It cannot be
- converted to a quadword integer.
-
- FISTP mem Pops ST into mem. The destination must be a 2-, 4-, or
- 8-byte memory operand. It is automatically converted
- from temporary-real format to a word, doubleword, or
- quadword integer, depending on the size of the
- operand.
-
-
-
- 17.5.1.3 Packed BCD Transfers
-
- The following instructions are available for transferring BCD integers:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FBLD mem Pushes a copy of mem into ST. The source must be a
- 10-byte memory operand. It should contain a packed BCD
- value, although no check is made to see that the data
- is valid.
-
- FBSTP mem Pops ST into mem. The destination must be a 10-byte
- memory operand. The value is rounded to an integer if
- necessary and converted to a packed BCD value.
-
-
- The following examples illustrate instructions described throughout this
- section:
-
- Example 1
-
- fld m1 ; Push m1 into first item
- fld st(2) ; Push third item into first
- fst m2 ; Copy first item to m2
- fxch st(2) ; Exchange first and third items
- fstp m1 ; Pop first item into m1
-
- Assuming that registers ST and ST(1) were previously initialized to 3.0
- and 4.0, the status of the register stack is shown below:
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.5.1.3 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Example 2
-
- .DATA
- shortreal DD 100 DUP (?)
- longreal DQ 100 DUP (?)
- .CODE
- . ; Assume array shortreal has been
- . ; filled by previous code
- .
- mov cx,100 ; Initialize loop
- xor si,si ; Clear pointer into shortreal
- xor di,di ; Clear pointer into longreal
- again: fld shortreal[si] ; Push shortreal
- fstp longreal[di] ; Pop longreal
- add si,4 ; Increment source pointer
- add di,8 ; Increment destination pointer
- loop again ; Do it again
-
- Example 2 illustrates one way of doing run-time type conversions.
-
-
- 17.5.2 Loading Constants
-
- Constants cannot be given as operands and loaded directly into coprocessor
- registers. You must allocate memory and initialize the variable to a
- constant value. The variable can then be loaded by using one of the load
- instructions described in Section 17.5.1, "Transferring Data to and from
- Registers."
-
- However, special instructions are provided for loading certain constants.
- You can load 0, 1, pi, and several common logarithmic values directly.
- Using these instructions is faster and often more precise than loading the
- values from initialized variables.
-
- The instructions that load constants all have the stack top as the implied
- destination operand. The constant to be loaded is the implied source
- operand. The instructions are listed below:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FLDZ Pushes 0 into ST
-
- FLD1 Pushes 1 into ST
-
- FLDPI Pushes the value of pi into ST
-
- FLDL2E Pushes the value of log2^e into ST
-
- FLDL2T Pushes log2^10 into ST
-
- FLDLG2 Pushes log10^2 into ST
-
- FLDLN2 Pushes loge^2 ST
-
-
-
- 17.5.3 Transferring Control Data
-
- The coprocessor data area, or parts of it, can be stored to memory and
- later loaded back. One reason for doing this is to save a snapshot of the
- coprocessor state before going into a procedure, and restore the same
- status after the procedure. Another reason is to modify coprocessor
- behavior by storing certain data to main memory, operating on the data
- with 8086-family instructions, and then loading it back to the coprocessor
- data area.
-
- You can choose to transfer the entire coprocessor data area, the control
- registers, or just the status or control word. Applications programmers
- seldom need to load anything other than the status word.
-
- All the control-transfer instructions take a single memory operand. Load
- instructions use the memory operand as the destination; store instructions
- use it as the source. The coprocessor data area is the implied source for
- load instructions and the implied destination for store instructions.
-
- Each store instruction has two forms. The "wait form" checks for unmasked
- numeric-error exceptions and waits until they have been handled. The
- "no-wait" form (which always begins with FN) ignores unmasked exceptions.
- The instructions are listed below:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FLDCW mem2byte Loads control word
-
- F[[N]]STCW mem2byte Stores control word
-
- F[[N]]STSW mem2byte Stores status word
-
- FLENV mem14byte Loads environment
-
- F[[N]]STENV mem14byte Stores environment
-
- FRSTOR mem94byte Restores state
-
- F[[N]]SAVE mem94byte Saves state
-
-
- 80287/80387 Only
-
- Starting with the 80287 coprocessor, the FSTSW and FNSTSW instructions can
- store data directly to the AX register. This is the only case in which
- data can be transferred directly between processor and coprocessor
- registers, as shown below:
-
- fstsw ax
-
-
- 17.6 Doing Arithmetic Calculations
-
- The math coprocessors offer a rich set of instructions for doing
- arithmetic. Most arithmetic instructions accept operands in any of the
- formats discussed in Section 17.3, "Using Coprocessor Instructions."
-
- When using memory operands with an arithmetic instruction, make sure you
- indicate in the name whether you want the memory operand to be treated as
- a real number or an integer. For example, use FADD to add a real number to
- the stack top or FIADD to add an integer to the stack top. You do not need
- to specify the operand type in the instruction if both operands are stack
- registers, since register values are always real numbers. You cannot do
- arithmetic on BCD numbers in memory. You must use FBLD to load the numbers
- into stack registers.
-
- The arithmetic instructions are listed below.
-
- Addition
-
- The following instructions add the source and destination and put the
- result in the destination:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FADD Classical-stack form. Adds ST and ST(1) and pops the
- result into ST. Both operands are destroyed.
-
- FADD ST(num),ST Register form with stack top as source. Adds the two
- register values and replaces ST(num) with the result.
-
- FADD ST,ST(num) Register form with stack top as destination. Adds the
- two register values and replaces ST with the result.
-
- FADD mem Real-memory form. Adds a real number in mem to ST. The
- result replaces ST.
-
- FIADD mem Integer-memory form. Adds an integer in mem to ST. The
- result replaces ST.
-
- FADDP ST(num),ST Register-pop form. Adds the two register values and
- pops the result into ST(num).
-
-
- Normal Subtraction
-
- The following instructions subtract the source from the destination and
- put the difference in the destination. Thus, the number being subtracted
- from is replaced by the result.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FSUB Classical-stack form. Subtracts ST from ST(1) and pops
- the result into ST. Both operands are destroyed.
-
- FSUB ST(num),ST Register form with stack top as source. Subtracts ST
- from ST(num) and replaces ST(num) with the result.
-
- FSUB ST,ST(num) Register form with stack top as destination. Subtracts
- ST(num) from ST and replaces ST with the result.
-
- FSUB mem Real-memory form. Subtracts the real number in mem
- from ST. The result replaces ST.
-
- FISUB mem Integer-memory form. Subtracts the integer in mem from
- ST. The result replaces ST.
-
- FSUBP ST(num),ST Register-pop form. Subtracts ST from ST(num) and pops
- the result into ST(num). Both operands are destroyed.
-
-
- Reversed Subtraction
-
- The following instructions subtract the destination from the source and
- put the difference in the destination. Thus, the number subtracted is
- replaced by the result.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FSUBR Classical-stack form. Subtracts ST(1) from ST and pops
- the result into ST. Both operands are destroyed.
-
- FSUBR ST(num),ST Register form with stack top as source. Subtracts
- ST(num) from ST and replaces ST(num) with the result.
-
- FSUBR ST,ST(num) Register form with stack top as destination. Subtracts
- ST from ST(num) and replaces ST with the result.
-
- FSUBR mem Real-memory form. Subtracts ST from the real number in
- mem. The result replaces ST.
-
- FISUBR mem Integer-memory form. Subtracts ST from the integer in
- mem. The result replaces ST.
-
- FSUBRP ST(num),ST Register-pop form. Subtracts ST(num) from ST and pops
- the result into ST(num). Both operands are destroyed.
-
-
- Multiplication
-
- The following instructions multiply the source and destination and put the
- product in the destination:
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FMUL Classical-stack form. Multiplies ST by ST(1) and pops
- the result into ST. Both operands are destroyed.
-
- FMUL ST(num),ST Register form with stack top as source. Multiplies the
- two register values and replaces ST(num) with the
- result.
-
- FMUL ST,ST(num) Register form with stack top as destination.
- Multiplies the two register values and replaces ST
- with the result.
-
- FMUL mem Real-memory form. Multiplies a real number in mem by
- ST. The result replaces ST.
-
- FIMUL mem Integer-memory form. Multiplies an integer in mem by
- ST. The result replaces ST.
-
- FMULP ST(num),ST Register-pop form. Multiplies the two register values
- and pops the result into ST(num). Both operands are
- destroyed.
-
-
- Normal Division
-
- The following instructions divide the destination by the source and put
- the quotient in the destination. Thus, the dividend is replaced by the
- quotient.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FDIV Classical-stack form. Divides ST(1) by ST and pops the
- result into ST. Both operands are destroyed.
-
- FDIV ST(num),ST Register form with stack top as source. Divides
- ST(num) by ST and replaces ST(num) with the result.
-
- FDIV ST,ST(num) Register form with stack top as destination. Divides
- ST by ST(num) and replaces ST with the result.
-
- FDIV mem Real-memory form. Divides ST by the real number in
- mem. The result replaces ST.
-
- FIDIV mem Integer-memory form. Divides ST by the integer in mem.
- The result replaces ST.
-
- FDIVP ST(num),ST Register-pop form. Divides ST(num) by ST and pops the
- result into ST(num). Both operands are destroyed.
-
-
- Reversed Division
-
- The following instructions divide the source by the destination and put
- the quotient in the destination. Thus, the divisor is replaced by the
- quotient.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FDIVR Classical-stack form. Divides ST by ST(1) and pops the
- result into ST. Both operands are destroyed.
-
- FDIVR ST(num),ST Register form with stack top as source. Divides ST by
- ST(num) and replaces ST(num) with the result.
-
- FDIVR ST,ST(num) Register form with stack top as destination. Divides
- ST(num) by ST and replaces ST with the result.
-
- FDIVR mem Real-memory form. Divides the real number in mem by
- ST. The result replaces ST.
-
- FIDIVR mem Integer-memory form. Divides the integer in mem by ST.
- The result replaces ST.
-
- FDIVRP ST(num),ST Register-pop form. Divides ST by ST(num) and pops the
- result into ST(num). Both operands are destroyed.
-
-
- Other Operations
-
- The following instructions all use the stack top (ST) as an implied
- destination operand. The result of the operation replaces the value in the
- stack top. No operand should be given.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FABS Sets the sign of ST to positive.
-
- FCHS Reverses the sign of ST.
-
- FRNDINT Rounds ST to an integer.
-
- FSQRT Replaces the contents of ST with its square root.
-
- FSCALE Scales by powers of 2 by adding the value of ST(1) to
- the exponent of the value in ST. This effectively
- multiplies the stack-top value by 2 to the power
- contained in ST(1). Since the exponent field is an
- integer, the value in ST(1) should normally be an
- integer.
-
- FPREM Calculates the partial remainder by performing modulo
- division on the top two stack registers. The value in
- ST is divided by the value in ST(1). The remainder
- replaces the value in ST. The value in ST(1) is
- unchanged. Since this instruction works by repeated
- subtractions, it can take a lot of execution time if
- the operands are very different in magnitude. FRPEM is
- sometimes used with trigonometric functions.
-
- FXTRACT Breaks a number down into its exponent and mantissa
- and pushes the mantissa onto the register stack.
- Following the operation, ST contains the value of the
- original mantissa and ST(1) contains the value of the
- unbiased exponent.
-
-
- Example
-
- .DATA
- a DD 3.0
- b DD 7.0
- c DD 2.0
- posx DD 0.0
- negx DD 0.0
-
- .CODE
- .
- .
- .
- ; Solve quadratic equation - no error checking
- fld1 ; Get constants 2 and 4
- fadd st,st ; 2 at bottom
- fld st ; Copy it
- fmul a ; = 2a
-
- fmul st(1),st ; = 4a
- fxch ; Exchange
- fmul c ; = 4ac
-
- fld b ; Load b
- fmul st,st ; = b^2
- fsubr ; = b^2 - 4ac
- ; Negative value here produces error
- fsqrt ; = square root(b^2 - 4ac)
- fld b ; Load b
- fchs ; Make it negative
- fxch ; Exchange
-
- fld st ; Copy square root
- fadd st,st(2) ; Plus version = -b + root((b^2 - 4ac)
- fxch ; Exchange
- fsubp st(2),st ; Minus version = -b - root((b^2 - 4ac)
-
- fdiv st,st(2) ; Divide plus version
- fstp posx ; Store it
- fdivr ; Divide minus version
- fstp negx ; Store it
-
- This example solves quadratic equations. It does no error checking and
- fails for some values because it attempts to find the square root of a
- negative number. You could enhance the code by using the FTST instruction
- (see Section 17.7.1, "Comparing Operands to Control Program Flow") to
- check for a negative number or 0 just before the square root is
- calculated. If b^2 - 4ac is negative or 0, the code can jump to routines
- that handle special cases for no solution or one solution, respectively.
-
-
- 17.7 Controlling Program Flow
-
- The math coprocessors have several instructions that set control flags in
- the status word. The 8087-family control flags can be used with
- conditional jumps to direct program flow in the same way that 8086-family
- flags are used. Since the coprocessor does not have jump instructions, you
- must transfer the status word to memory so that the flags can be used by
- 8086-family instructions.
-
- An easy way to use the status word with conditional jumps is to move its
- upper byte into the lower byte of the processor flags. For example, use
- the following statements:
-
- fstsw mem16 ; Store status word in memory
- fwait ; Make sure coprocessor is done
- mov ax,mem16 ; Move to AX
- sahf ; Store upper word in flags
-
- As noted in Section 17.5.3, "Transferring Control Data," you can save
- several steps by loading the status word directly to AX on the 80287.
-
- Figure 17.3 shows how the coprocessor control flags line up with the
- processor flags. C3 overwrites the zero flag, C2 overwrites the parity
- flag, and C0 overwrites the carry flag. C1 overwrites an undefined bit, so
- it cannot be used directly with conditional jumps, although you can use
- the TEST instruction to check C1 in memory or in a register. The sign and
- auxiliary-carry flags are also overwritten, so you cannot count on them
- being unchanged after the operation.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section 17.7 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- See Section 15.1.2 for more information on using conditional-jump
- instructions based on flag status.
-
-
- 17.7.1 Comparing Operands to Control Program Flow
-
- The 8087-family coprocessors provide several instructions for comparing
- operands. All these instructions compare the stack top (ST) to a source
- operand, which may either be specified or implied as ST(1).
-
- The compare instructions affect the C3, C2, and C0 control flags. The C1
- flag is not affected. Table 17.2 shows the flags set for each possible
- result of a comparison or test.
-
- Variations on the compare instructions allow you to pop the stack once or
- twice, and to compare integers and zero. For each instruction, the stack
- top is always the implied destination operand. If you do not give an
- operand, ST(1) is the implied source. Some compare instructions allow you
- to specify the source as a memory or register operand.
-
- Table 17.2 Control-Flag Settings after Compare or Test
-
- After FCOM After FTEST C3 C2 C0
- ──────────────────────────────────────────────────────────────────────────
- ST > source ST is positive 0 0 0
-
- ST < source ST is negative 0 0 1
-
- ST = source ST is 0 1 0 0
-
- Not comparable ST is NAN or 1 1 1
- projective
- infinity
- ──────────────────────────────────────────────────────────────────────────
-
-
- The compare instructions are listed below.
-
-
- 17.7.1.1 Compare
-
- These instructions compare the stack top to the source. The source and
- destination are unaffected by the comparison.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FCOM Compares ST to ST(1).
-
- FCOM ST(num) Compares ST to ST(num).
-
- FCOM mem Compares ST to mem. The memory operand can be a four-
- or eight-byte real number.
-
- FICOM mem Compares ST to mem. The memory operand can be a two-
- or four-byte integer.
-
- FTST Compares ST to 0. The control registers will be
- affected as if ST had been compared to 0 in ST(1).
- Table 17.2 above shows the possible results.
-
-
-
- 17.7.1.2 Compare and Pop
-
- These instructions compare the stack top to the source and then pop the
- stack. Thus, the destination is destroyed by the comparison.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- FCOMP Compares ST to ST(1) and pops ST off the register
- stack.
-
- FCOMP ST(num) Compares ST to ST(num) and pops ST off the register
- stack.
-
- FCOMP mem Compares ST to mem and pops ST off the register stack.
- The operand can be a four- or eight-byte real number.
-
- FICOMP mem Compares ST to mem and pops ST off the register stack.
- The operand can be a two- or four-byte integer.
-
- FCOMPP Compares ST to ST(1) and then pops the stack twice.
- Both the source and destination are destroyed by the
- comparison.
-
-
- Example
-
- IFDEF c287
- .287
- ENDIF
- .DATA
- down DD 10.35 ; Sides of a rectangle
- across DD 13.07
- diameter DD 12.93 ; Diameter of a circle
- status DW ?
- .CODE
- .
- .
- .
- ; Get area of rectangle
- fld across ; Load one side
- fmul down ; Multiply by the other
-
- ; Get area of circle
- fld1 ; Load one and
- fadd st,st ; double it to get constant 2
- fdivr diameter ; Divide diameter to get radius
- fmul st,st ; Square radius
- fldpi ; Load pi
- fmul ; Multiply it
-
- ; Compare area of circle and rectangle
- fcompp ; Compare and throw both away
- IFNDEF c287
- fstsw status ; Load from coprocessor to memory
- fwait ; Wait for coprocessor
- mov ax,status ; Memory to register
- ELSE
- fstsw ax ; (for 287+, skip memory)
- ENDIF
- sahf ; to flags
- jp nocomp ; If parity set, can't compare
- jz same ; If zero set, they're the same
- jc rectangle ; If carry set, rectangle is bigger
- jmp circle ; else circle is bigger
-
- nocomp: . ; Error handler
- .
- same: . ; Both equal
- .
- rectangle: . ; Rectangle bigger
- .
- circle: . ; Circle bigger
-
- Notice how conditional blocks are used to enhance 80287 code. If you
- define the symbol c287 from the command line by using the /D symbol option
- (see Section B.4, "Defining Assembler Symbols"), the code is smaller and
- faster, but does not run on an 8087.
-
-
- 17.7.2 Testing Control Flags after Other Instructions
-
- In addition to the compare instructions, the FXAM and FPREM instructions
- affect coprocessor control flags.
-
- The FXAM instruction sets the value of the control flags based on the type
- of the number in the stack top (ST). This instruction is used to identify
- and handle special values, such as infinity, zero, unnormal numbers,
- denormal numbers, and NANs (not a number). Certain math operations are
- capable of producing these special-format numbers. A description of them
- is beyond the scope of this manual. The possible settings of the flags are
- shown in the on-line Help system.
-
- FPREM also sets control flags. Since this instruction must sometimes be
- repeated to get a correct remainder for large operands, it uses the C2
- flag to indicate whether the remainder returned is partial (C2 is set) or
- complete (C2 is clear). If the bit is set, the operation should be
- repeated.
-
- FPREM also returns the least-significant three bits of the quotient in C0,
- C3, and C1. These bits are useful for reducing operands of periodic
- transcendental functions, such as sine and cosine, to an acceptable range.
- The technique is not explained here. The possible settings for each flag
- are shown in the on-line Help system.
-
-
- 17.8 Using Transcendental Instructions
-
- The 8087-family coprocessors provide a variety of instructions for doing
- transcendental calculations, including exponentiation, logarithmic
- calculations, and some trigonometric functions.
-
- Use of these advanced instructions is beyond the scope of this manual.
- However, the instructions are listed below for reference. All
- transcendental instructions have implied operands──either ST as a
- single-destination operand, or ST as the destination and ST(1) as the
- source.
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- F2XM1 Calculates 2^x-1, where x is the value of the stack
- top. The value x must be between 0 and .5, inclusive.
- Returning 2^x-1 instead of 2^x allows the instruction
- to return the value with greater accuracy. The
- programmer can adjust the result to get 2^x.
-
- FYL2X Calculates Y times log2 X, where X is in ST and Y is
- in ST(1). The stack is popped, so both X and Y are
- destroyed, leaving the result in ST. The value of X
- must be positive.
-
- FYL2XP1 Calculates Y times log2 (X+1), where X is in ST and Y
- is in ST(1). The stack is popped, so both X and Y are
- destroyed, leaving the result in ST. The absolute
- value of X must be between 0 and the square root of 2
- divided by 2. This instruction is more accurate than
- FYL2X when computing the log of a number close to 1.
-
- FPTAN Calculates the tangent of the value in ST. The result
- is a ratio Y/X, with Y replacing the value in ST and X
- pushed onto the stack so that after the instruction,
- ST contains Y and ST(1) contains X. The value being
- calculated must be a positive number less than pi/4.
- The result of the FPTAN instruction can be used to
- calculate other trigonometric functions, including
- sine and cosine.
-
- FPATAN Calculates the arctangent of the ratio Y/X, where X is
- in ST and Y is in ST(1). The stack is popped, so both
- X and Y are destroyed, leaving the result in ST. Both
- X and Y must be positive numbers less than infinity,
- and Y must be less than X. The result of the FPATAN
- instruction can be used to calculate other inverse
- trigonometric functions, including arcsine and
- arccosine.
-
-
-
- 17.9 Controlling the Coprocessor
-
- Additional instructions are available for controlling various aspects of
- the coprocessor. With the exception of FINIT, these instructions are
- generally used only by systems programmers. They are summarized below, but
- not fully explained or illustrated. Some instructions have a wait version
- and a no-wait version. The no-wait versions have N as the second letter.
-
- Syntax Description
- ──────────────────────────────────────────────────────────────────────────
- F[[N]]INIT Resets the coprocessor and restores all the default
- conditions in the control and status words. It is a
- good idea to use this instruction at the start and end
- of your program. Placing it at the start ensures that
- no register values from previous programs affect your
- program. Placing it at the end ensures that register
- values from your program will not affect later
- programs.
-
- F[[N]]CLEX Clears all exception flags and the busy flag of the
- status word. It also clears the error-status flag on
- the 80287, or the interrupt-request flag on the 8087.
-
- FINCSTP Adds 1 to the stack pointer in the status word. Do not
- use to pop the register stack. No tags or registers
- are altered.
-
- FDECSTP Subtracts 1 from the stack pointer in the status word.
- No tags or registers are altered.
-
- FREE ST(num) Marks the specified register as empty.
-
- FNOP Copies the stack top to itself, thus padding the
- executable file and taking up processing time without
- having any effect on registers or memory.
-
-
- 8087 Only
-
- The 8087 has the instructions FDISI, FNDISI, FENI, and FNENI. These
- instructions can be used to enable or disable interrupts. The 80287
- coprocessor permits these instructions, but ignores them. Applications
- programmers will not normally need these instructions. Systems programmers
- should avoid using them so that their programs are portable to all
- coprocessors.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Chapter 18: Controlling the Processor
-
-
- The 8086-family processors provide instructions for processor control.
- These instructions are available on all 8086-family processors.
-
- System-control instructions have limited use in applications programming.
- They are primarily used by systems programmers who write operating systems
- and other control software. Since systems programming is beyond the scope
- of this manual, the systems-control instructions are summarized, but not
- explained in detail, in the sections below.
-
- This chapter ends with a description of all the directives that enable the
- instruction sets for the various processors in the 8086 family.
-
-
- 18.1 Controlling Timing and Alignment
-
- The NOP instruction takes up one byte of memory but does not have any
- effect when executed. The purpose of this instruction is generally to fill
- up space in the code segment; primarily, it is used to pad executable code
- for alignment.
-
- Although NOP has no effect, it does take a few clock cycles to execute. In
- a sense, NOP does do something──it is exactly equivalent to the following
- instruction:
-
- xchg ax,ax ; Exchange AX with itself
-
- Because NOP does use up some clock time, you can use it in timing loops by
- executing it many times. However, when writing a program for use on
- different machines, avoid using this technique. Timing loops that use NOP
- take different lengths of time on different machines. A better way to
- control timing is to use the DOS Get Time function, since it is based on
- the computer's internal clock rather than on the speed of the processor.
-
- QuickAssembler automatically inserts NOP instructions for padding when you
- use the ALIGN or EVEN directive (see Section 6.7, "Aligning Data") to
- align data or code on a given boundary.
-
-
- 18.2 Controlling the Processor
-
- The LOCK, WAIT, ESC, and HLT instructions control different aspects of the
- processor.
-
- These instructions can be used to control processes handled by external
- coprocessors. The 8087-family coprocessors are the coprocessors most
- commonly used with 8086-family processors, but 8086-based machines can
- work with other coprocessors if they have the proper hardware and control
- software.
-
- These instructions are summarized below:
-
- Instruction Description
- ──────────────────────────────────────────────────────────────────────────
- LOCK Locks out other processors until a specified
- instruction is finished. This is a prefix that
- precedes the instruction. It can be used to make sure
- that a coprocessor does not change data being worked
- on by the processor.
-
- WAIT Instructs the processor to do nothing until it
- receives a signal that a coprocessor has finished with
- a task being performed at the same time. See Section
- 17.4, "Coordinating Memory Access," for information
- on using WAIT or its coprocessor equivalent, FWAIT,
- with the 8087-family coprocessors.
-
- ESC Provides an instruction and possibly a memory operand
- for use by a coprocessor. QuickAssembler automatically
- inserts ESC instructions when required for use with
- 8087-family coprocessors.
-
- HLT Stops the processor until an interrupt is received. It
- can be used in place of an endless loop if a program
- needs to wait for an interrupt.
-
-
-
- 18.3 Processor Directives
-
- Processor and coprocessor directives define the instruction set that is
- recognized by QuickAssembler. They are listed and explained below:
-
- Directive Description
- ──────────────────────────────────────────────────────────────────────────
- .8086 The .8086 directive enables assembly of instructions
- for the 8086 and 8088 processors and the 8087
- coprocessor. It disables assembly of the instructions
- unique to the 80186, 80286, and 80386 processors.
-
- This is the default mode and is used if no instruction
- set directive is specified. Using the default
- instruction set ensures that your program can be used
- on all 8086-family processors. However, if you choose
- this directive, your program will not take advantage
- of the more powerful instructions available on more
- advanced processors.
-
- .186 The .186 directive enables assembly of the 8086
- processor instructions, 8087 coprocessor instructions,
- and the additional instructions for the 80186
- processor.
-
- .286 The .286 directive enables assembly of the 8086
- instructions plus the additional nonprivileged
- instructions of the 80286 processor. It also enables
- 80287 coprocessor instructions. If privileged
- instructions were previously enabled, the .286
- directive disables them.
-
- This directive should be used for programs that will
- be executed only by an 80186, 80286, or 80386
- processor. For compatibility with early versions of
- the Macro Assembler, the .286C directive is also
- available. It is equivalent to the .286 directive.
-
- .8087 The .8087 directive enables assembly of instructions
- for the 8087 math coprocessor and disables assem-bly
- of instructions unique to the 80287 coprocessor. It
- also specifies the IEEE format for encoding
- floating-point variables.
-
- This is the default mode and is used if no coprocessor
- directive is specified. This directive should be used
- for programs that must run with either the 8087,
- 80287, or 80387 coprocessors.
-
- .287 The .287 directive enables assembly of instructions
- for the 8087 floating-point coprocessor and the
- additional instructions for the 80287. It also
- specifies the IEEE format for encoding floating-point
- variables.
-
- Coprocessor instructions are optimized if you use this
- directive rather than the .8087 directive. Therefore,
- you should use it if you know your program will never
- need to run under an 8087 coprocessor. See Section
- 17.4, "Coordinating Memory Access," for an
- explanation.
-
-
- If you do not specify any processor directives, QuickAssembler uses the
- following defaults:
-
- 1. 8086/8088 processor instruction set
-
- 2. 8087 coprocessor instruction set
-
- 3. IEEE format for floating-point variables
-
- Normally, the processor and coprocessor directives can be used at the
- start of the source file to define the instruction sets for the entire
- assembly. However, it is possible to use different processor directives at
- different points in the source file to change assumptions for a section of
- code. For instance, you might have processor-specific code in different
- parts of the same source file. You can also turn privileged instructions
- on and off or allow unusual combinations of the processor and coprocessor.
-
- There are two limitations on changing the processor or coprocessor:
-
- 1. The directives must be given outside segments. You must end the current
- segment, give the processor directive, and then open another segment.
- See Section 5.1.5, "Using Predefined Segment Equates," for an example
- of changing the processor directives with simplified segment
- directives.
-
- 2. You can specify a lower-level coprocessor with a higher-level
- coprocessor, but an error message will be generated if you try to
- specify a lower-level processor with a higher-level coprocessor.
-
- The coprocessor directives have the opposite effect of the .MSFLOAT
- directive. .MSFLOAT turns off coprocessor instruction sets and enables the
- Microsoft Binary format for floating-point variables. Any coprocessor
- instruction turns on the specified coprocessor instruction set and enables
- IEEE format for floating-point variables.
-
- Examples
-
- ; .MSFLOAT affects the source file until turned off
- .MSFLOAT
- .8087 ; Ignored
-
- ; Illegal - can't use 8086 with 80287
- .8086
- .287
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Appendix A: Mixed-Language Mechanics
-
-
- The QuickAssembler PROC statement automates most details of interfacing to
- QuickC, as well as to other Microsoft high-level languages. When you use
- PROC with a parameter list or USES clause as described in Section 15.3.4,
- or when you use the LOCAL directive, the assembler generates code that
- properly enters and exits the procedure. The assembler also determines the
- location of each parameter on the stack for you. You refer to each
- parameter by a meaningful name, and the assembler translates each
- parameter name into the actual memory reference.
-
- The main purpose of this appendix is to show you what code the assembler
- generates when you use the LOCAL directive or expanded features of the
- PROC directive. However, you can write this code yourself rather than
- letting the assembler generate it. Doing so requires significant extra
- work, but it does give you complete control over your procedure.
-
- Using simplified segment directives is the easiest way to interface with a
- Microsoft high-level language (including QuickC). The simplified segment
- directives generate segment definitions similar to the ones generated by
- high-level languages and guarantee compatibility in the use of segment
- names and conventions. If you want to use full segment definitions, see
- Chapter 5, "Defining Segment Structure," for a description of the
- segments used in Microsoft languages.
-
-
- A.1 Writing the Assembly Procedure
-
- The Microsoft BASIC, C, FORTRAN, and Pascal compilers use roughly the same
- interface for procedure calls. This section describes the interface, so
- that you can call assembly procedures using essentially the same methods
- as Microsoft compiler-generated code. Procedures written with these
- methods can be called recursively.
-
- The standard assembly-interface method consists of these steps:
-
- 1. Setting up the procedure
-
- 2. Entering the procedure
-
- 3. Allocating local data (optional)
-
- 4. Preserving register values
-
- 5. Accessing parameters
-
- 6. Returning a value (optional)
-
- 7. Exiting the procedure
-
- The PROC statement, when used with a parameter list or USES clause,
- automates steps 1 and 2, and simplifies step 5 for you. The LOCAL
- directive automates step 3, and the USES clause automates step 4. Finally,
- if you use any of these features, the assembler automatically generates
- all the proper code to exit (step 7) wherever it encounters a RET
- directive. (However, the RETF and RETN statements never generate automatic
- code.)
-
- Sections A.1.1-A.1.7 describe these steps.
-
-
- A.1.1 Setting Up the Procedure
-
- The linker cannot combine the assembly procedure with the calling program
- unless compatible segments are used and unless the procedure itself is
- declared properly. The following points may be helpful:
-
- 1. Use the .MODEL directive at the beginning of the source file; this
- directive automatically causes the appropriate kind of returns to be
- generated (NEAR for small or compact model, FAR otherwise). Modules
- called from Pascal should be declared as .MODEL LARGE; modules called
- from BASIC should be .MODEL MEDIUM.
-
- 2. Use the simplified segment directives: .CODE to declare the code
- segment and .DATA to declare the data segment. (Having a code segment
- is sufficient if you do not have data declarations.)
-
- 3. Tie procedure label must be public. This makes the procedure available
- to be called by other modules. If you specify a language type with the
- .MODEL directive, the assembler automatically makes all procedure names
- public, but you must use the PUBLIC directive if you don't specify the
- language. Also, any data you want to make public to other modules must
- be declared as PUBLIC.
-
- 4. Global data or procedures accessed by the routine (but defined in other
- modules) must be declared EXTRN.
-
-
- A.1.2 Entering the Procedure
-
- Two instructions begin the procedure:
-
- push bp
- mov bp,sp
-
- This sequence establishes BP as the framepointer. The framepointer is used
- to access parameters and local data, which are located on the stack. SP
- cannot be used for this purpose because it is not an index or base
- register. Also, the value of SP may change as more data is pushed onto the
- stack. However, the value of the base register (BP) will remain constant
- throughout the procedure, so that each parameter can be addressed as a
- fixed displacement from the location pointed to by BP.
-
- The instruction sequence above first saves the value of BP, since it will
- be needed by the calling procedure as soon as the current procedure
- terminates. Then BP is loaded with the value of SP in order to capture the
- value of the stack pointer at the time of entry to the procedure.
-
- The PROC statement generates these two lines of code automatically if you
- use a parameter list, LOCAL directive, or USES clause.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE If you alter the direction flag with the STD instruction, make sure
- you reset this flag with the CLD instruction before you exit.
- ──────────────────────────────────────────────────────────────────────────
-
-
- A.1.3 Allocating Local Data (Optional)
-
- Local variables are also called dynamic, stack, or automatic variables.
-
- An assembly procedure can use the same technique for implementing local
- data used by high-level languages. To set up local data space, decrease
- the contents of SP in the third instruction of the procedure. (To ensure
- correct execution, you should always increase or decrease SP by an even
- amount.) Decreasing SP reserves space on the stack for the local data. The
- space must be restored at the end of the procedure.
-
- push bp
- mov bp,sp
- sub sp,space
-
- In the code above, space is the total size in bytes of the local data.
- Local variables are then accessed as fixed, negative displacements from
- the location pointed to by BP.
-
- Example
-
- push bp
- mov bp,sp
- sub sp,4
- .
- .
- .
- mov WORD PTR [bp-2],0
- mov WORD PTR [bp-4],0
-
- The example above uses two local variables, each of which is two bytes in
- size. SP is decreased by 4, since there are four bytes total of local
- data. Later, each of the variables is initialized to 0. These variables
- are never formally declared with any assembler directive; the programmer
- must keep track of them manually.
-
- The LOCAL directive uses this same method for creating local variables.
- However, when you use LOCAL, you can refer to a local variable by a
- symbolic name rather than by a reference, such as WORD PTR [bp-2].
-
-
- A.1.4 Preserving Register Values
-
- A procedure called from any of the Microsoft high-level languages should
- preserve the values of SI, DI, SS, and DS (in addition to BP, which is
- already saved). Therefore, push any of these register values that the
- procedure alters. If the procedure does not change the value of any of
- these registers, the registers do not need to be pushed.
-
- The recommended method (used by high-level languages) is to save registers
- after the framepointer is set and local data (if any) is allocated.
-
- push bp ; Save old framepointer
- mov bp,sp ; Establish current framepointer
- sub sp,4 ; Allocate local data space
- push si ; Save SI and DI
- push di
- .
- .
- .
-
- In the example above, DI and SI (in that order) must be popped from the
- stack before the end of the procedure.
-
- The USES clause in a PROC statement causes the assembler to generate this
- same code.
-
-
- A.1.5 Accessing Parameters
-
- When you use PROC with a parameter list, the assembler calculates the
- location of each parameter on the stack. This section shows how the
- assembler determines these locations. If you do not use a parameter list,
- you must calculate parameter locations yourself and refer to them
- explicitly by their offsets from BP. Otherwise, you can refer to each
- parameter by the name you gave it in the parameter list.
-
- To write instructions that can access parameters, consider the general
- picture of the stack frame after a procedure call, as illustrated in
- Figure A.1.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section A.1.5 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- When determining the order of parameters on the stack, note that the C
- calling convention (the default for QuickC) specifies that parameters are
- passed in the reverse order they appear in source code. The non-C calling
- convention (which you can specify in QuickC with the pascal or fortran
- keyword) specifies that parameters are passed in the same order they
- appear in source code.
-
- The stack frame for the procedure is established by the following
- sequence:
-
- 1. The calling program pushes each of the parameters on the stack, after
- which SP points to the last parameter pushed.
-
- 2. The calling program issues a CALL instruction, which causes the return
- address (the place in the calling program to which control will
- ultimately return) to be placed on the stack. This address may be
- either two bytes long (for near calls) or four bytes long (for far
- calls). SP now points to this address.
-
- 3. The first instruction of the called procedure saves the old value of
- BP, with the instruction push bp. SP now points to the saved copy of
- BP.
-
- 4. BP is used to capture the current value of SP, with the instruction mov
- bp,sp. BP therefore now points to the old value of BP.
-
- 5. Whereas BP remains constant throughout the procedure, SP may be
- decreased to provide room on the stack for local data or saved
- registers.
-
- In general, the displacement (from the location pointed to by BP) for a
- parameter X is equal to:
-
- 2 + size of return address + total size of parameters between X and BP
-
- For example, consider a FAR procedure that has received one parameter, a
- two-byte address. The displacement of the parameter would be:
-
- Argument's displacement = 2 + size of return address
- = 2 + 4
- = 6
-
- The argument can thus be loaded into BX with the following instruction:
-
- mov bx,[bp+6]
-
- Once you determine the displacement of each parameter, you may want to use
- string equates or structures so that the parameter can be referenced with
- a single identifier name in your assembly source code. For example, the
- parameter above at BP+6 can be conveniently accessed if you put the
- following statement at the beginning of the assembly source file:
-
- Arg1 EQU <[bp+6]>
-
- You could then refer to this parameter as Arg1 in any instruction. Use of
- this feature is optional.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Microsoft high-level languages always push segment addresses before
- pushing offset addresses. Furthermore, when pushing arguments larger than
- two bytes, high-order words are always pushed before low-order words.
-
- This standard for pushing segment addresses before pushing offset
- addresses facilitates the use of the LES and LDS instructions, as
- described in Chapter 3, "Writing Assembly Modules for C Programs."
- ──────────────────────────────────────────────────────────────────────────
-
-
- A.1.6 Returning a Value (Optional)
-
- The assembler does not generate code to return a value. If you want your
- procedure to return a value, you must take care of the details yourself.
-
- Microsoft BASIC, C, FORTRAN, and Pascal share similar conventions for
- receiving return values. The conventions are the same when the data type
- to be returned is simple (that is, not an array or structured type) and is
- no more than four bytes long. This includes all NEAR and FAR address types
- (in other words, all pointers and all parameters passed by reference).
-
- Data Size Returned in Register
- ──────────────────────────────────────────────────────────────────────────
- 1 byte AL
-
- 2 bytes AX
-
- 4 bytes High-order portion (or segment address) in DX;
- low-order portion (or offset address) in AX
-
-
- When the return value is larger than four bytes, a procedure called by C
- must allocate space for the return value and then place its address in
- DX:AX. You can create space for the return value by simply declaring it in
- a data segment.
-
- If your assembly procedure uses the non-C calling convention, it must use
- a special convention in order to return floating-point values, records,
- user-defined types and arrays, and values larger than four bytes. This
- convention is presented below.
-
- BASIC/FORTRAN/Pascal Long Return Values
-
- To create an interface for long return values, modules using the non-C
- calling convention take the following actions before they call your
- procedure:
-
- 1. The calling modules create space, somewhere in the stack segment, to
- hold the actual return value.
-
- 2. When the call to your procedure is made, an extra parameter is passed
- containing the offset address of the actual return value. This
- parameter is placed immediately above the return address. (In other
- words, this parameter is the last one pushed.)
-
- 3. The segment address of the return value is contained in both SS and DS.
-
- The extra parameter (containing the offset address of the return value) is
- always located at BP+6. Furthermore, its presence automatically increases
- the displacement of all other parameters by 2, as shown in Figure A.2.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section A.1.6 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- Your assembly procedure will successfully return a long value if you
- follow these steps:
-
- 1. Put the data for the return value at the location pointed to by the
- return value offset.
-
- 2. Copy the return-value offset (located at BP+6) to AX, and copy SS to
- DX. This is necessary because the calling module expects DX:AX to point
- to the return value.
-
- 3. Exit the procedure as described in the next section.
-
-
- A.1.7 Exiting the Procedure
-
- Several steps may be involved in terminating the procedure:
-
- 1. If any of the registers SS, DS, SI, or DI have been saved, these must
- be popped off the stack in the reverse order that they were saved.
-
- 2. If local data space was allocated at the beginning of the procedure, SP
- must be restored with the instruction mov sp,bp.
-
- 3. Restore BP with pop bp. This step is always necessary.
-
- 4. Finally, return to the calling program with ret. If the BASIC, FORTRAN,
- or Pascal calling convention is in use, you can use the ret n form of
- the instruction to adjust the stack with respect to the parameters that
- were pushed by the caller. (If the procedure is called by a C module,
- the calling module will perform this adjustment.)
-
- Examples
-
- pop bp
- ret
-
- The example above shows the simplest possible exit sequence. No registers
- were saved, no local data space was allocated, and the C calling
- convention is in use.
-
- pop di ; Pop saved regs
- pop si
- mov sp,bp ; Remove local data space
- pop bp ; Restore old framepointer
- ret 6 ; Exit, and restore 6 byte of args
-
- The example above shows an exit sequence for a procedure that has
- previously saved SI and DI, allocated local data space, and uses a non-C
- calling convention. The procedure must therefore use ret 6 (where n is 6)
- to restore the six bytes of parameters on the stack.
-
- Assuming you use one of the automated features described above (such as a
- parameter list or LOCAL directive), the assembler generates all the code
- to properly exit from a procedure whenever it encounters a RET
- instruction. However, the assembler does not generate any exit code when
- you use the directives RETN or RETF.
-
-
- A.2 Calls from Modules Using C Conventions
-
- Most of the details below are automated when you use simplified segment
- directives and the expanded features of the PROC directive. Make sure to
- declare both a language type and a memory model with the .MODEL directive.
-
- This section reviews all the steps taken when you use the C language type.
- In addition to the steps outlined in Section A.1, the assembler observes
- the following rules to set up an interface to C.
-
- Follow these rules if you want to manually establish this interface:
-
- 1. Declare procedures called from C as FAR if the C module is compiled in
- large, huge, or medium model, and NEAR if the C module is compiled in
- small or compact model (although the near and far keywords can override
- these defaults). The correct declaration for the procedure is made
- implicitly when you use the .MODEL directive. Note that tiny memory
- model is not supported by QuickC 2.0.
-
- 2. Observe the C calling convention.
-
- a. Return with a simple ret instruction. Do not restore the stack with
- ret size, since the calling C routine will restore the stack itself
- as soon as it resumes control.
-
- b. Parameters are placed on the stack in the reverse order that they
- appear in the C source code. The first parameter will be lowest in
- memory (because it is placed on the stack last and the stack grows
- downward).
-
- c. By default, C parameters are passed by value, except for arrays,
- which are passed by reference. As a rule, do not expect an address
- to be placed on the stack, unless the C code specifically refers to
- a pointer or array in the function call or prototype.
-
- 3. Observe the C naming convention.
-
- Include an underscore (_) in front of any name that will be shared
- publicly with C. C recognizes only the first eight characters of any
- name, so do not make names shared with C longer than eight characters.
- Also, if you plan to link with the /NOIGNORECASE option, remember that
- C is case sensitive and does not convert names to uppercase. To
- preserve lowercase names in public symbols, choose Preserve Case or
- Preserve Extrn from the Assembler Flags dialog box, or assemble with
- the /Cl or /Cx option on the QCL command line.
-
- In the example program below, C calls an assembly procedure that
- calculates "A x 2^B," where A and B are the first and second parameters,
- respectively. The calculation is performed by shifting the bits in A to
- the left, B times.
-
- extern int power2( int, int ),
-
- main ()
- {
- printf( "3 times 2 to the power of 5 is %d\n",
- power2( 3, 5 ) );
- }
-
- The C program uses an extern declaration to create an interface with the
- assembly procedure. No special keywords are required because the assembly
- procedure will use the C calling convention.
-
- To understand how to write the assembly procedure, consider how the
- parameters are placed on the stack, as illustrated in Figure A.3.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section A.2 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The return address is two bytes long, assuming that the C module is
- compiled in small or compact model. If the C module is compiled in large,
- huge, or medium model, the addresses of Arg 1 and Arg 2 are each increased
- by 2, to BP+6 and BP+8, respectively, because the return address will be
- four bytes long.
-
- Arg 1 (parameter 1) is lower in memory than Arg 2, because C pushes
- arguments in the reverse order that they appear. Each argument is passed
- by value.
-
- The assembly procedure can be written as follows:
-
- .MODEL small
- .CODE
- PUBLIC _power2
- _power2 PROC
- push bp ; Entry sequence - save old BP
- mov bp,sp ; Set stack framepointer
-
- mov ax,[bp+4] ; Load Arg1 into AX
- mov cx,[bp+6] ; Load Arg2 into CX
- shl ax,cl ; AX = AX * (2 to power of CX)
- ; Leave return value in AX
-
- pop bp ; Exit sequence - restore old BP
- ret ; Return
- _power2 ENDP
- END
-
- The example above assumes that the C module is compiled in small model.
- The parameter offsets and the .MODEL directive will change for different
- models.
-
- Note that ret without a size variable is used, since the caller will
- adjust the stack upon return from the call.
-
-
- A.3 Calls from Non-C Modules
-
- In your C programs you can specify the pascal or fortran function type.
- These keywords are equivalent: they both specify use of non-C calling and
- naming conventions. Furthermore, you may want to interface to languages
- other than C (which you can do by linking .OBJ files together outside the
- environment). In all these cases, make sure you specify BASIC, FORTRAN, or
- Pascal as the language type with the .MODEL directive. Alternately, you
- can specify the language as part of the procedure if you are using the
- extended PROC directive.
-
- This section reviews all the steps taken when you use a non-C language
- type. In addition to the steps outlined in Section A.1, the assembler
- observes the following rules to set up an interface to a language using a
- non-C calling convention.
-
- Follow these rules if you want to manually establish an interface to a
- high-level language:
-
- 1. If the procedure is called from Microsoft BASIC, Pascal, or FORTRAN,
- make sure to declare the procedure as FAR, or use the .MODEL directive
- to specify medium or large memory model. BASIC always uses medium
- memory model; Pascal uses large memory model.
-
- 2. Observe the non-C calling convention.
-
- a. Upon exit, the procedure must reset SP to the value it had before
- the parameters were placed on the stack. This is accomplished with
- the instruction ret size, where size is the total size in bytes of
- all the parameters.
-
- b. Parameters are placed on the stack in the same order in which they
- appear in the high-level language source code. The first parameter
- will be highest in memory (because it is placed on the stack first
- and the stack grows downward).
-
- c. Each language has different defaults for passing parameters by value
- or reference. When a language passes by reference, it places a data
- pointer on the stack. When it passes by value, it places a complete
- copy of the parameter on the stack. Consult your language
- documentation for the details of when the language passes by value
- or reference. (In C, the default is by value except for arrays.)
-
- 3. Observe the language naming convention.
-
- Microsoft BASIC, FORTRAN, and Pascal output symbolic names in uppercase
- characters, which is also the default behavior of the assembler. Each
- language recognizes a different number of characters in a name. For
- example, BASIC recognizes up to 40 characters of a name, whereas the
- assembler recognizes only the first 31.
-
- In the following example program, QuickBASIC 4.0 calls an assembly
- procedure that calculates "A x 2B," where A and B are the first and second
- parameters, respectively. The calculation is performed by shifting the
- bits in A to the left, B times. (Note: with earlier versions of BASIC, you
- need to rewrite the example so that it calls a subprogram, not a
- function.)
-
- ' BASIC program
- DEFINT A-Z
- PRINT "3 times 2 to the power of 5 is ";
- PRINT Power2(3,5)
- END
-
- To understand how to write the assembly procedure, consider how the
- parameters are placed on the stack, as illustrated in Figure A.4.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section A.3 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- The return address is four bytes long because procedures called from BASIC
- must be FAR. Arg 1 (parameter 1) is higher in memory than Arg 2 because
- BASIC pushes arguments (parameters) in the same order in which they
- appear. Also, each argument is passed as a two-byte offset address, the
- BASIC default.
-
- The assembly procedure can be written as follows:
-
- .MODEL medium
- .CODE
- PUBLIC Power2
- Power2 PROC
- push bp ; Entry sequence - save old BP
- mov bpsp ; Set stack framepointer
-
- mov bx,[bp+8] ; Load Arg1 into
- mov ax,[bx] ; AX
- mov bx,[bp] ; Load Arg2 into
- mov cx,[bx] ; CX
- shl ax,cl ; AX = AX * (2 to power of CX)
- ; Leave return value in AX
-
- pop bp ; Exit sequence - restore old BP
- ret 4 ; Return, and restore 4 bytes
- Power2 ENDP
- END
-
- Note that each parameter must be loaded in a two-step process because the
- address of each is passed rather than the value. Also, note that the stack
- is restored with the instruction ret 4, since the total size of the
- parameters is four bytes.
-
-
- A.4 Calling High-Level Languages from Assembly Language
-
- Many high-level-language routines assume that certain initialization code
- has previously been executed; you can ensure that the proper
- initialization is performed by starting in a high-level-language module,
- and then calling an assembly procedure. The assembly procedure can then
- call high-level-language routines as needed, as shown in Figure A.5.
-
- ┌────────────────────────────────────────────────────────────────────────┐
- │ This figure can be found in Section A.4 of the manual │
- └────────────────────────────────────────────────────────────────────────┘
-
- To execute an assembly call to a high-level language, you need to observe
- the following guidelines:
-
- 1. Push each parameter onto the stack, observing the calling convention of
- the high-level language. Constants, such as offset addresses, must
- first be loaded into a register before being pushed.
-
- 2. With long parameters, always push the segment or high-order portion of
- the parameter first, regardless of the calling convention.
-
- 3. If you are using the BASIC/FORTRAN/Pascal calling convention with a
- function that returns a noninteger value, allocate an additional
- two-byte parameter. This additional parameter should contain the offset
- of the location where you want the value returned and must be pushed on
- the stack last.
-
- 4. Execute a call. The call must be far unless the high-level-language
- routine is small model.
-
- 5. If the routine used the C calling convention, after the call you must
- immediately clear the stack of parameters with the instruction add sp,
- size, where size is the total size in bytes of all parameters that were
- pushed.
-
-
- A.5 Using Full Segment Definitions
-
- If you use the simplified segment directives by themselves, you do not
- need to know the names assigned for each segment. However, if you choose
- to use full segment definitions, you should use the SEGMENT, GROUP,
- ASSUME, and ENDS directives equivalent to the simplified segment
- directives.
-
- The following example shows the C-assembly program from Section A.3,
- without the simplified segment directives:
-
- _TEXT SEGMENT WORD PUBLIC 'CODE'
- ASSUME cs:_TEXT
- PUBLIC _Power2
- _Power2 PROC
- push bp ; Entry sequence - save BP
- mov bp,sp ; Set stack frame
-
- mov ax,[bp+4] ; Load Arg1 into AX
- mov cx,[bp+6] ; Load Arg2 into CX
- shl ax,cl ; AX = AX * (2 to power of CX)
- ; Leave return value in AX
-
- pop bp ; Exit sequence - restore BP
- ret ; Return
- _Power2 ENDP
- _TEXT ENDS
- END
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Appendix B: Using Assembler Options with QCL
-
-
- You can use the QCL driver for both compiling and assembling. The driver
- compiles .C files and assembles .ASM files. Unless the /c option is given,
- the QCL driver then links together all resulting .OBJ files, as well as
- any .OBJ files specified on the command line. The default file extension
- is .OBJ.
-
- If you acquired QuickAssembler as an upgrade, make sure you use the
- version of QCL that came with the QuickAssembler package. This driver
- program is an updated and expanded version, and it supports assembly
- options in addition to all the compile options listed in the QuickC Tool
- Kit.
-
- The following options may affect work with .ASM files, but are not
- described here because they work precisely the same way as described in
- the QuickC Tool Kit:
-
- Option Action
- ──────────────────────────────────────────────────────────────────────────
- /help Print help listing for QCL
-
- /link flags Specify linker flags
-
- /Fefile Specify output file
-
- /Fofile Name object file
-
- /Z{d|i} Generate debugging information.
-
-
- The /c, /D, and /W options are documented in the QuickC Tool Kit, but are
- also documented here because their meaning and usage change somewhat for
- assembly-language files.
-
- In addition to the linker options documented in the QuickC Tool Kit, QCL
- supports one other option, /TINY. This option causes the linker to output
- a .COM file, if possible. The linker can only create a .COM file if the
- program is entirely written in assembly language, and all the modules
- observe the rules for the .COM format. (The easiest way to do this is to
- use tiny memory model as described in Chapter 5.) The following example
- generates a .COM file:
-
- QCL /AT TINYPROG.ASM /link /TINY
-
- The /AT option causes the assembler to check the assembly code for
- adherence to the .COM format. The /TINY linker option causes the linker to
- generate a .COM file.
-
- The QuickAssembler version of QCL supports the following options in
- addition to the ones supported for use with C-language modules:
-
- Option Action
- ──────────────────────────────────────────────────────────────────────────
- /a Writes segments in alphabetical order
-
- /AT Requires program to use tiny memory model; gives error
- messages for code that violates requirements of .COM
- format
-
- /C{l|u|x} Determines case sensitivity (l=preserve case,
- u=convert to upper, x=preserve case of external and
- public symbols)
-
- /D Defines symbols
-
- /Ez Displays error lines on screen
-
- /Flfile Generates an assembly-listing file with given file
- name
-
- /FPi Creates code for emulated floating-point instructions
-
- /l Generates an assembly-listing file
-
- /P1 Enables one-pass assembly
-
- /s Writes segments in source-code order (reverses effect
- of /a)
-
- /Sa Lists all lines of macro expansions (assumes /Flfile
- or /l is given)
-
- /Sd Adds pass 1 information to listing (assumes /Flfile or
- /l is given)
-
- /Se Creates editor-oriented listing file; the resulting
- listing has no page breaks or page headings (assumes
- /Flfile or /l is given)
-
- /Sn Suppresses symbol-table in listing (assumes /Flfile or
- /l is given)
-
- /Sq Generates an editor-based listing file with a
- source-line index at the end (assumes Flfile or /l is
- given)
-
- /Sx Suppresses listing of false conditionals (assumes
- Flfile or /l is given)
-
- /t Suppresses messages if assembly is successful
-
- /v Displays extra statistics during assembly
-
- /w Equivalent to /W0
-
- /W{0|1|2} Sets warning-message level
-
-
-
- B.1 Specifying the Segment-Order Method
-
- Syntax
-
- /s Default
- /a
-
- The /a option directs QuickAssembler to place the assembled segments in
- alphabetical order before copying them to the object file. The /s option
- directs the assembler to write segments in the order in which they appear
- in the source code.
-
- Source-code order is the default. If no option is given, QuickAssembler
- copies the segments in the order encountered in the source file. The /s
- option is provided for compatibility with the XENIX(R) operating system
- and for overriding a default option in the QuickAssembler environment
- variable.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE Some previous versions of the IBM Macro Assembler ordered segments
- alphabetically by default. Listings in some books and magazines have been
- written with these early versions in mind. If you have trouble assembling
- and linking a listing taken from a book or magazine, try using the /a
- option.
- ──────────────────────────────────────────────────────────────────────────
-
- The order in which segments are written to the object file is only one
- factor in determining the order in which they will appear in the
- executable file. The significance of segment order and ways to control it
- are discussed in Sections 5.2.1, "Setting the Segment-Order Method" and
- 5.2.2.2, "Defining Segment Combinations with Combine Type."
-
- Example
-
- QCL /a file.asm
-
- The example above creates an object file, FILE.OBJ, whose segments are
- arranged in alphabetical order. If the /s option were used instead, or if
- no option were specified, the segments would be arranged in sequential
- order.
-
-
- B.2 Checking Code for Tiny Model
-
- Syntax
-
- /AT
-
- The /AT option causes the assembler to enforce the requirements of .COM
- format. If the .MODEL directive is used, /AT generates an error unless the
- directive specifies tiny memory model. If the .MODEL directive is not
- used, the /AT option generates an error if any program-defined segments
- are referenced (since these references violate conditions of .COM format).
-
- The use of /AT alone does not generate a .COM file. You must also use the
- /TINY linker option, as in the following example:
-
- QCL /AT TINYPROG.ASM /link /TINY
-
-
- B.3 Selecting Case Sensitivity
-
- Syntax
-
- /Cu Default
- /Cl
- /Cx
-
- The /Cl option directs the assembler to make all names case sensitive. The
- /Cx option directs the assembler to make public and external names case
- sensitive. The /Cu option directs the assembler to convert all names to
- uppercase.
-
- By default, QuickAssembler converts all names to uppercase (/Cu).
-
- If case sensitivity is turned on, all names that have the same spelling
- but use letters of different cases are considered distinct. For example,
- with the /Cl option, DATA and data are different. They would also be
- different with the /Cx option if they were declared external or public.
- Public and external names include any label, variable, or symbol names
- defined by using the EXTRN, PUBLIC, or COMM directives (see Chapter 8,
- "Creating Programs from Multiple Modules").
-
- If you use the /Zi or /Zd option (these cause QCL to include debugging
- information), the /Cx, /Cl, and /Cu options affect the case of the
- symbolic data that will be available to a symbolic debugger.
-
- The /Cl and /Cx options are typically used when object modules created
- with QuickAssembler are to be linked with object modules created by a
- case-sensitive compiler such as the Microsoft C compiler. If case
- sensitivity is important, you should also use the linker /NOI option.
-
- Example
-
- QCL /Cx module.asm
-
- This example shows how to use the /Cx option with QuickAssembler to
- assemble a file with case-sensitive public symbols.
-
-
- B.4 Defining Assembler Symbols
-
- Syntax
-
- /Dsymbol[[=value]]
-
- The /D option, when given with a symbol argument, directs QuickAssembler
- to define a symbol that can be used during the assembly as if it were
- defined as a text equate in the source file. Multiple symbols can be
- defined in a single command line.
-
- The value can be any text string that does not include a space, comma, or
- semicolon. If value is not given, the symbol is assigned a null string.
-
- Example
-
- QCL /Dwide /Dmode=3 file,,;
-
- This example defines the symbol wide and gives it a null value. The symbol
- could then be used in the following conditional-assembly block:
-
- IFDEF wide
- PAGE 50,132
- ENDIF
-
- When the symbol is defined in the command line, the listing file is
- formatted for a 132-column printer. When the symbol is not defined in the
- command line, the listing file is given the default width of 80 (see the
- description of the PAGE directive in Section 12.2, "Controlling Page
- Format in Listings").
-
- The example also defines the symbol mode and gives it the value 3. The
- symbol could then be used in a variety of contexts, as shown below:
-
- IF mode LT 15 ; Use in expression
- scrmode DB mode ; Initialize to mode
- ELSE
- scrmode DB 15 ; Initialize to 15
- ENDIF
-
-
- B.5 Displaying Error Lines on the Screen
-
- Syntax
-
- /Ez
-
- The /Ez option directs QuickAssembler to display lines containing errors
- on the screen. Normally, when the assembler encounters an error, it
- displays only an error message describing the problem. When you use the
- /Ez option in the command line, the assembler displays the source line
- that produced the error in addition to the error message. QuickAssembler
- assembles faster without the /Ez option, but you may find the convenience
- of seeing the incorrect source lines worth the slight cost in processing
- speed.
-
- Example
-
- QCL /Ez file.asm
-
-
- B.6 Creating Code for a Floating-Point Emulator
-
- Syntax
-
- /FPi 87 /FPi
-
- The /FPi and /FPi87 options control how instructions for a math
- coprocessor (such as the 8087, 80287, or 80387) are assembled. The /FPi
- option tells the assembler to generate code for a coprocessor emulator
- library. The /FPi87 option tells the assembler to generate code for a
- coprocessor. These options are different than most other QuickAssembler
- options in that the default for C files is /FPi, but the default for
- assembler files is /FPi87. They are also different in that the options
- must be specified separately for each file.
-
- An emulator library uses the instructions of a coprocessor if one is
- present; otherwise, the library executes interrupts that emulate
- coprocessor instructions. Emulator libraries are available for QuickC and
- other high-level language compilers, including Microsoft Pascal, BASIC,
- and FORTRAN compilers.
-
- With QuickAssembler, you should specify /FPi only for assembly modules
- that will be linked with a main C module, since the emulator code requires
- the start-up code generated by the C compiler. A stand-alone assembler
- program generated with /FPi will execute emulator interrupts, but the
- program will not work because the interrupts will not be initialized. If
- you are programming in the QC environment and you want the emulator
- library to be used with an assembler module, you must specify /FPi in the
- Global Custom Flags field of the Assembler Flags dialog box (reached from
- the Options menu). This will affect all assembly modules in the program
- list.
-
- To the applications programmer, writing code for the emulator is like
- writing code for a coprocessor. The instruction sets are the same (except
- as noted in Chapter 17, "Calculating with a Math Coprocessor"). However,
- at run time the coprocessor instructions are used only if there is a
- coprocessor available on the machine. If there is no coprocessor, the
- slower code from the emulator library is used instead.
-
- The /FPi87 option specifies that coprocessor instructions should be
- generated directly. It does not need to be given directly for assembly
- modules, since it is the default, but it must be specified for C modules.
- Programs that use this option can be run only on a system that has a
- coprocessor. If the program contains a main C module, it will fail with a
- warning if the system has no coprocessor. If the program is a stand-alone
- assembler program, you should write the code to check for a coprocessor
- and terminate with an error message if no coprocessor exists.
-
- Example
-
- QCL calc.c /FPi /Cx math.asm
-
- The example above assembles MATH.ASM with the /FPi option and compiles the
- C source file CALC.C. The resulting object files are then linked together
- to produce the file CALC.EXE. The C compiler generates emulator code for
- floating-point instructions. The FORTRAN, BASIC, and Pascal compilers
- generate similar code.
-
-
- B.7 Creating Listing Files
-
- Syntax
-
- /l
- /Flfile
-
- The /l option directs QuickAssembler to create a listing file. Files
- specified with this option always have the base name of the source file
- plus a .LST extension. You cannot specify any other file name. The /Fl
- option has the same purpose as /l, but lets you specify any file name as
- the listing file. The default file name is the base file name plus a .LST
- extension.
-
- Example
-
- QCL /l prog.asm
-
- This example causes the assembler to generate the file PROG.LST during
- assembly.
-
-
- B.8 Enabling One-Pass Assembly
-
- Syntax
-
- /P1
-
- The /P1 option causes the assembler to attempt translation of source code
- in one pass. If successful, the translation is significantly faster than
- the default two-pass assembly. Assembly modules cannot be successfully
- assembled with this option if they contain conditional-assembly directives
- that make references to pass 1 or pass 2.
-
- ──────────────────────────────────────────────────────────────────────────
- NOTE One-pass assembly is not compatible with the generation of listing
- files or the /a option for specifying alphabetical segment order.
- ──────────────────────────────────────────────────────────────────────────
-
- If the assembler generates a message reporting that one-pass assembly is
- not possible, simply assemble the file again without using this option.
-
- Example
-
- QCL /P1 file.asm
-
-
- B.9 Listing All Lines of Macro Expansions
-
- Syntax
-
- /Sa
-
- The /Sa option causes the listing file to contain all statements generated
- by the assembler. It overrides directives that limit listings such as
- .XLIST, .XALL, and .SFCOND. It forces display of all statements generated
- automatically by simplified segment directives and the extended PROC
- syntax. The /Sa option has no effect unless /l or /Fl is also specified.
-
- Example
-
- QCL /l /Sa file.asm
-
-
- B.10 Creating a Pass 1 Listing
-
- Syntax
-
- /Sd
-
- The /Sd option causes the listing file to contain the results of both
- assembler passes. A pass 1 listing is typically used to locate phase
- errors. Phase errors occur when the assembler makes assumptions about the
- program in pass 1 that are not valid in pass 2. The /Sd option has no
- effect unless /l or /Fl is also specified.
-
- Example
-
- QCL /l /Sd file.asm
-
-
- B.11 Specifying an Editor-Oriented Listing
-
- Syntax
-
- /Se
-
- The /Se option causes the assembler to generate the listing file in a
- format suited to text editors. This format does not contain page breaks or
- page headings. The default behavior, which is designed for files output to
- a printer, assumes a page break and heading at periodic intervals. The /Se
- option has no effect unless /l or /Fl is also specified.
-
- Example
-
- QCL /l /Se file.asm
-
-
- B.12 Suppressing Tables in the Listing File
-
- Syntax
-
- /Sn
-
- The /Sn option tells the assembler to omit all tables from the end of the
- listing file. If this option is not chosen, QuickAssembler includes tables
- of macros, structures, records, segments and groups, and symbols. The code
- portion of the listing file is not changed by the /Sn option. The /Sn
- option has no effect unless /l or /Fl is also specified.
-
- Example
-
- QCL /l /Sn file.asm
-
-
- B.13 Adding a Line-Number Index to the Listing
-
- Syntax
-
- /Sq
-
- The /Sq option generates an editor-based listing file just as the /Se
- option does, but it also adds a source-line index to the end of the
- listing file. This index contains pairs of corresponding line numbers for
- the listing file and appropriate source files. The QuickC/QuickAssembler
- environment uses this information to let you move from a source file to
- the corresponding position in a listing file.
-
- When you create a listing file from within the QuickC/QuickAssembler
- environment, QC.EXE automatically passes this option to the assembler. The
- /Sq option has no effect unless /l or /Fl is also specified.
-
- Example
-
- QCL /l /Sq file.asm
-
-
- B.14 Listing False Conditionals
-
- Syntax
-
- /Sx
-
- The /Sx option directs QuickAssembler to copy to the assembly listing all
- statements forming the body of conditional-assembly blocks whose condition
- is false. If you do not give the /Sx option in the command line,
- QuickAssembler suppresses all such statements. The /Sx option lets you
- display conditionals that do not generate code. Conditional-assembly
- directives are explained in Chapter 12, "Controlling Assembly Output."
-
- The .LFCOND, .SFCOND, and .TFCOND directives can override the effect of
- the /Sx option, as described in Section 12.3.2, "Controlling Listing of
- Conditional Blocks." The /Sx option does not affect the assembly listing
- unless you direct the assembler to create an assembly-listing file.
-
- Example
-
- QCL /Sx file,,;
-
- Listing of false conditionals is turned on when FILE.ASM is assembled.
- Directives in the source file can override the /Sx option to change the
- status of false-conditional listing.
-
-
- B.15 Controlling Display of Assembly Statistics
-
- Syntax
-
- /v
- /t
-
- The /v and /t options specify the level of information displayed to the
- screen at the end of assembly (/v is a mnemonic for verbose; /t is a
- mnemonic for terse).
-
- If neither option is given, QuickAssembler outputs a line telling the
- amount of symbol space free and the number of warnings and errors.
-
- If the /v option is given, QuickAssembler also reports the number of lines
- and symbols processed.
-
- If the /t option is given, QuickAssembler does not output anything to the
- screen unless errors are encountered. This option may be useful in batch
- or make files if you do not want the output cluttered with unnecessary
- messages.
-
- If errors are encountered, they will be displayed whether these options
- are given or not.
-
-
- B.16 Setting the Warning Level
-
- Syntax
-
- /W{0 | 1 | 2}
- /w
-
- The /W option sets the assembler warning level. QuickAssembler gives
- warning messages for assembly statements that are ambiguous or
- questionable but not necessarily illegal. Some programmers purposely use
- practices that generate warnings. By setting the appropriate warning
- level, they can turn off warnings if they are aware of the problem and do
- not wish to take action to remedy it. The /w option is equivalent to /W0.
-
- QuickAssembler has three levels of errors, as shown in Table B.1.
-
- Table B.1 Warning Levels
-
- Level Type Description
- ──────────────────────────────────────────────────────────────────────────
- 0 Severe errors Illegal statements
-
- 1 Serious Ambiguous statements or questionable
- warnings programming practices
-
- 2 Advisory Statements that may produce inefficient
- warnings code
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- The default warning level is 1. A higher warning level includes all of the
- messages reported by a lower level. Level 2 includes severe errors,
- serious warnings, and advisory warnings. If severe errors are encountered,
- no object file is produced.
-
- Warning level 0 reports error messages in the range 1000-2999. Warning
- level 1 reports warning and error messages in the ranges 1000-2999 and
- 4000-4999. Warning level 2 reports all warning and error messages,
- including those numbered 5000 and above.
-
-
-
- ────────────────────────────────────────────────────────────────────────────
- Appendix C: Reading Assembly Listings
-
-
- QuickAssembler creates an assembly listing of your source file whenever
- you give an assembly-listing option on the QCL command line or select a
- listing file option in the Assembler Flags dialog box. The assembly
- listing contains both the statements in the source file and the object
- code (if any) generated for each statement. The listing also shows the
- names and values of all labels, variables, and symbols in your source
- file.
-
- The assembler creates tables for macros, structures, records, segments,
- groups, and other symbols. These tables are placed at the end of the
- assembly listing (unless you suppress them with the QCL /Sn option).
- QuickAssembler lists only the types of symbols encountered in the program.
- All symbol names will be shown in uppercase letters unless you choose
- Preserve Case or Preserve Extrn from the Assembler Flags dialog box or use
- a QCL option (/Cx or /Cl) that supports case sensitivity.
-
-
- C.1 Reading Code in a Listing
-
- The assembler lists the code generated from the statements of a source
- file. Each line has the syntax shown below:
-
- offset [[code]] statement
-
- The offset is the offset from the beginning of the current segment to the
- code. If the statement generates code or data, code shows the numeric
- value in hexadecimal if the value is known at assembly time. If the value
- is calculated at run time, the assembler indicates what action is
- necessary to compute the value. The statement is the source statement
- shown exactly as it appears in the source file, or as expanded by a macro.
-
- If any errors occur during assembly, each error message and error number
- will appear directly below the statement where the error occurred. An
- example of an error line and message is shown below:
-
- 0012 E8 001C R call doit
- test.ASM(46): error A2071: Forward needs override or FAR
-
- The assembler uses the symbols and abbreviations in Table C.1 to indicate
- addresses that need to be resolved by the linker or values that were
- generated in a special way.
-
- Table C.1 Symbols and Abbreviations in Listings
-
- Character Meaning
- ──────────────────────────────────────────────────────────────────────────
- R Relocatable address (linker must resolve)
-
- E External address (linker must resolve)
-
- ---- Segment/group address (linker must resolve)
-
- = EQU or equal-sign (=) directive
-
- nn: Segment override in statement
-
- nn/ REP or LOCK prefix instruction
-
- nn[xx] DUP expression: nn copies of the value xx
-
- n Macro-expansion nesting level (+ if more than nine)
-
- C Line from include file
-
- ──────────────────────────────────────────────────────────────────────────
-
-
- Example
-
- The sample listing shown in this section is produced using the /Se option,
- which produces an editor-oriented listing. The QuickC/QuickAssembler
- environment always produces this kind of listing. The editor-oriented
- environment produces no page headings and is thus ideal for viewing within
- the environment or another editor. If you are using QCL to generate a
- listing file that you intend to print, you may want to generate a
- printer-oriented listing file by giving the /Sp option:
-
- QCL /l /Sp listdemo.asm
-
- The code portion of the resulting listing is shown below. The tables
- normally seen at the end of the listing are explained later, in Sections
- C.2-C.7.
-
- Microsoft(R) QuickC with QuickAssembler Version 2.01 Listing features demo
-
- PAGE 65,132
- TITLE Listing features demo
- C INCLUDE dos.mac
- C StrAlloc MACRO name,text
- C name DB &text
- C DB 13d,10d
- C l&name EQU $-name
- C ENDM
-
- = 0080 larg EQU 80h
-
- DOSSEG
- .MODEL small
-
- 0100 .STACK 256
-
- color RECORD b:1,r:3=1,i:1=1,f:3=7
-
- date STRUC
- 0000 05 month DB 5
- 0001 07 day DB 7
- 0002 07C3 year DW 1987
- 0004 date ENDS
-
- 0000 .DATA
- 0000 1F text color <>
- 0001 09 today date <9,22,1987>
- 0002 16
- 0003 07C3
-
- 0005 0064[ buffer DW 100 DUP(?)
- ????
-
-
-
- StrAlloc ending,"Finished."
- 00CD 46 69 6E 69 73 68 65 1 ending DB "Finished."
- 00D6 0D 0A 1 DB 13d,10d
-
- 0000 .CODE
-
- 0000 B8 ---- R start: mov ax,@DATA
- 0003 8E D8 mov ds,ax
-
- 0005 B8 0063 mov ax,'c'
- 0008 26: 8B 0E 0080 mov cx,es:larg
- 000D BF 0052 mov di,82
- 0010 F2/ AE repne scasb
- 0012 57 push di
-
- EXTRN work:NEAR
- 0013 E8 0000 E call work
-
- 0016 B8 170C mov ax,4C00
- listdemo.ASM(40): error A2107: Non-digit in number
- 0019 CD 21 int 21h
-
- 001B END start
-
-
- C.2 Reading a Macro Table
-
- A macro table at the end of a listing file gives the names and sizes (in
- lines) of all macros called or defined in the source file. The macros
- appear in alphabetical order.
-
- Example
-
- Macros:
-
- N a m e Lines
-
- STRALLOC . . . . . . . . . . . . 3
-
-
- C.3 Reading a Structure and Record Table
-
- All structures and records declared in the source file are given at the
- end of the listing file. The names are listed in alphabetical order. Each
- name is followed by the fields in the order in which they are declared.
-
- Example
-
- Structures and Records:
-
- N a m e Width # fields
- Shift Width Mask Initial
-
- COLOR . . . . . . . . . . . . . 0008 0004
- B . . . . . . . . . . . . . . 0007 0001 0080 0000
- R . . . . . . . . . . . . . . 0004 0003 0070 0010
- I . . . . . . . . . . . . . . 0003 0001 0008 0008
- F . . . . . . . . . . . . . . 0000 0003 0007 0007
- DATE . . . . . . . . . . . . . . 0004 0003
- MONTH . . . . . . . . . . . . 0000
- DAY . . . . . . . . . . . . . 0001
- YEAR . . . . . . . . . . . . . 0002
-
- The first row of headings only applies to the record or structure itself.
- For a record, the "Width" column shows the width in bits while the "#
- fields" column tells the total number of fields.
-
- The second row of headings applies only to fields of the record or
- structure. For records, the "Shift" column lists the offset (in bits) from
- the low-order bit of the record to the low-order bit in the field. The
- "Width" column lists the number of bits in the field. The "Mask" column
- lists the maximum value of the field, expressed in hexadecimal. The
- "Initial" column lists the initial value of the field, if any. For each
- field, the table shows the mask and initial values as if they were placed
- in the record and all other fields were set to 0.
-
- For a structure, the "Width" column lists the size of the structure in
- bytes. The "# fields" column lists the number of fields in the structure.
- Both values are in hexadecimal.
-
- For structure fields, the "Shift" column lists the offset in bytes from
- the beginning of the structure to the field. This value is in hexadecimal.
- The other columns are not used.
-
-
- C.4 Reading a Segment and Group Table
-
- Segments and groups used in the source file are listed at the end of the
- program with their size, align type, combine type, and class. If you used
- simplified segment directives in the source file, the actual segment names
- generated by QuickAssembler will be listed in the table.
-
- Example
-
- Segments and Groups:
-
- N a m e Size Align Combine Class
-
- DGROUP . . . . . . . . . . . . . GROUP
- _DATA . . . . . . . . . . . . 00D8 WORD PUBLIC 'DATA'
- STACK . . . . . . . . . . . . 0800 PARA STACK 'STACK'
- _TEXT . . . . . . . . . . . . . 0018 BYTE PUBLIC 'CODE'
-
- The "Name" column lists the names of all segments and groups. Segment and
- group names are given in alphabetical order, except that the names of
- segments belonging to a group are placed under the group name in the order
- in which they were added to the group.
-
- The "Size" column lists the byte size (in hexadecimal) of each segment.
- The size of groups is not shown.
-
- The "Align" column lists the align type of the segment.
-
- The "Combine" column lists the combine type of the segment. If no explicit
- combine type is defined for the segment, the listing shows NONE,
- representing the private combine type. If the "Align" column contains AT,
- the "Combine" column contains the hexadecimal address of the beginning of
- the segment.
-
- The "Class" column lists the class name of the segment. For a complete
- explanation of the align, combine, and class types, see Section 5.2.2,
- "Defining Full Segments."
-
-
- C.5 Reading a Symbol Table
-
- All symbols (except names for macros, structures, records, and segments)
- are listed in a symbol table at the end of the listing.
-
- Example
-
- Symbols:
-
- N a m e Type Value Attr
-
- BUFFER . . . . . . . . . . . . . L WORD 0005 _DATA Length = 0064
-
- ENDING . . . . . . . . . . . . . L BYTE 00CD _DATA
-
- LARG . . . . . . . . . . . . . . NUMBER 0080
- LENDING . . . . . . . . . . . . NUMBER 000B
-
- START . . . . . . . . . . . . . L NEAR 0000 _TEXT
-
- TEXT . . . . . . . . . . . . . . L BYTE 0000 _DATA
- TODAY . . . . . . . . . . . . . L DWORD 0001 _DATA
-
- WORK . . . . . . . . . . . . . . L NEAR 0000 _TEXT External
-
- @CODE . . . . . . . . . . . . . TEXT _TEXT
- @CODESIZE . . . . . . . . . . . TEXT 0
- @DATA . . . . . . . . . . . . . TEXT DGROUP
- @DATASIZE . . . . . . . . . . . TEXT 0
- @FARDATA . . . . . . . . . . . . TEXT FAR_DATA
- @FARDATA? . . . . . . . . . . . TEXT FAR_BSSk
- @FILENAME . . . . . . . . . . . TEXT listdemo
- @MODEL . . . . . . . . . . . . . TEXT 1
- @VERSION . . . . . . . . . . . . TEXT 520
-
- The "Name" column lists the names in alphabetical order. The "Type" column
- lists each symbol's type. A type is given as one of the following:
-
- Type Definition
- ──────────────────────────────────────────────────────────────────────────
- L NEAR A near label
-
- L FAR A far label
-
- N PROC A near procedure label
-
- F PROC A far procedure label
-
- NUMBER An absolute label
-
- ALIAS An alias for another symbol
-
- TEXT A text equate
-
- BYTE One byte
-
- WORD One word (two bytes)
-
- DWORD Doubleword (four bytes)
-
- QWORD Quadword (eight bytes)
-
- TBYTE Ten bytes
-
- number Length in bytes of a structure variable
-
-
- The length of a multiple-element variable, such as an array or string, is
- the length of a single element, not the length of the entire variable. For
- example, string variables are always shown as L BYTE.
-
- If the symbol represents an absolute value defined with an EQU or
- equal-sign (=) directive, the "Value" column shows the symbol's value. The
- value may be another symbol, a string, or a constant numeric value (in
- hexadecimal), depending on whether the type is ALIAS, TEXT, or NUMBER. If
- the symbol represents a variable, label, or procedure, the "Value" column
- shows the symbol's hexadecimal offset from the beginning of the segment in
- which it is defined.
-
- The "Attr" column shows the attributes of the symbol. The attributes
- include the name of the segment (if any) in which the symbol is defined,
- the scope of the symbol, and the code length. A symbol's scope is given
- only if the symbol is defined using the EXTRN and PUBLIC directives. The
- scope can be external, global, or communal. The code length (in
- hexadecimal) is given only for procedures. The "Attr" column is blank if
- the symbol has no attribute.
-
- The text equates shown at the end of the sample table are the ones defined
- automatically when you use simplified segment directives (see Section
- 5.1.1, "Understanding Memory Models").
-
-
- C.6 Reading Assembly Statistics
-
- Data on the assembly, including the number of lines and symbols processed
- and the errors or warnings encountered, is shown at the end of the
- listing.
-
- Example
-
- 48 Source Lines
- 52 Total Lines
- 53 Symbols
-
- 45570 + 310654 Bytes symbol space free
-
- 0 Warning Errors
- 1 Severe Errors
-
-
- C.7 Reading a Pass 1 Listing
-
- When you specify the /Sd option in the QCL command line or select Pass One
- Information from the Assembler Flags dialog box, the assembler puts a pass
- 1 listing in the assembly-listing file. The listing file shows the results
- of both assembler passes. Pass 1 listings are useful in analyzing phase
- errors.
-
- The following example illustrates a pass 1 listing for a source file that
- assembled without error on the second pass.
-
- 0017 7E 00 jle label1
- PASS_CMP.ASM(20) : error 9 : Symbol not defined LABEL1
- 0019 BB 1000 mov bx,4096
- 001C label1:
-
- During pass 1, the JLE instruction to a forward reference produces an
- error message and the value 0 is encoded as the operand. QuickAssembler
- displays this error because it has not yet encountered the symbol label1.
-
- Later in pass 1, label1 is defined. Therefore, the assembler knows about
- label1 on pass 2 and can fix the pass 1 error. The pass 2 listing is shown
- below:
-
- 0017 7E 03 jle label1
- 0019 BB 1000 mov bx,4096
- 001C label1:
-
- The operand for the JLE instruction is now coded as 3 instead of 0 to
- indicate that the distance of the jump to label1 is three bytes.
-
- Since QuickAssembler generated the same number of bytes for both passes,
- there was no error. Phase errors occur if the assembler makes an
- assumption on pass 1 that it cannot change on pass 2. If you get a phase
- error, you can examine the pass 1 listing to see what assumptions the
- assembler made.
-
-
-
- ═══════════════════════════════════════════════════════════════════════════
- Index
-
-
- Symbols
-
- (brackets)
- with arrays
- index operator
- with registers
- + (plus sign), to separate registers
- _ (underscore)
-
- Numbers
-
- 10-byte temporary-real format
- .186 directive
- .286 directive
- .287 directive
- .8086 directive
- 8086-family processors
- assembly language
- calculating physical addresses (figure)
- flags (figure)
- instructions
- registers (figure)
- .8087 directive
- 8087-family registers, modifying
-
- A
-
- /a option
- AAA instruction
- AAD instruction
- AAM instruction
- AAS instruction
- ABS type
- Absolute segments
- Accumulator
- ADC instruction
- ADD instruction
- Adding
- Addressing modes
- contrasted with C
- defined
- direct memory
- immediate
- indirect memory
- listed
- register
- Advisor, Quick
- Advisory warnings
- AH register
- AL register
- Aliases
- ALIGN directive
- Align type
- Alignment, of segments
- .ALPHA directive
- & (ampersand), operator
- Ampersand (&), operator
- AND instruction
- AND operator
- \la (angle brackets), operator
- Angle brackets (\la), operator
- Animate command
- Arguments
- macros
- passing on stack
- repeat blocks
- Arithmetic operators (table)
- Arrays
- accessing elements of
- boundary checking
- defining
- specifying elements
- ASCII character set
- converting numerals to face value
- name for unpacked BCD numbers
- Assembler display mode
- Assembler Flags dialog box (figure)
- Assembler options, summary
- Assembly
- calling from C
- parameters, accessing
- passing by
- near reference
- value
- warning levels (table)
- Assembly listing
- addresses
- false conditionals
- macros
- page breaks
- page length
- page width
- reading
- subtitle
- suppressing
- title
- Assembly-language books
- ASSUME directive
- (at sign)
- symbol names, used in
- /AT option
- AT combine type
- Attributes of variables
- Auto display mode
- Automatic variables
- Auxiliary-carry flag
- AX register
-
- B
-
- Base register
- Based operands
- Based-indexed operands
- BASIC
- calling convention
- for loops
- nestinglevel
- ON GOTO statements
- stack
- frame (figure)
- long return values (figure)
- BCD (binary coded decimal) numbers
- calculations with
- constants
- defining of
- transfer instructions (list)
- variables initialized
- BH register
- Binary integers, transfer instructions (list)
- Binary radix
- Binary to decimal conversion
- BIOS functions
- calling with interrupts
- on-line help
- Bit fields
- Bit mask
- Bits, rotating and shifting (figure)
- Bitwise operators
- BL register
- Boolean bit operations
- BOUND instruction
- Boundary-checking array
- BP register
- Brackets ()
- with arrays
- index operator
- with registers
- Buffers
- Bugs, reporting
- Building programs
- within environment
- QCL driver
- BY memory operator
- BYTE align type
- BYTE type specifier
-
- C
-
- C compiler
- C display mode
- C language
- assembly call (figure)
- calling convention
- if statement
- interfacing with
- loops
- memory models and
- nestinglevel value
- passing by value
- return value
- stack frame (figure)
- switch statements
- C register variables
- CALL instruction
- Call tables
- Calling convention
- Calls command
- Carry flag
- Case sensitivity
- compilers
- lack of
- options
- preserving
- CATSTR directive
- CBW instruction
- CH register
- Character constant
- Character set
- /Cl option
- /Cl option
- CL register
- defined
- use in shifting bits
- Class type
- Classical-stack operands, coprocessor
- CLC instruction
- CLD instruction
- CLI instruction
- CMP instruction
- CMPS instruction
- .CODE directive
- CODE class name
- Code equate
- Code Segment register
- Code segments
- developing programs with
- initializing
- (colon), operator
- definition
- (colon), operator
- in Debug expressions
- .COM file, assembling
- .COM format
- debugging
- example
- with full segment definitions
- initializing
- with linker
- restrictions on
- tiny memory model
- Combine type
- COMENT object record
- COMM directive
- Command line, QC
- COMMENT directive
- COMMON combine type
- Communal symbols
- Compact memory model
- Compare instructions
- Comparing
- register to zero
- strings
- Concatenating strings
- Conditional assembly
- Conditional directives
- assembly directives
- assembly passes
- error directives
- macro arguments
- nesting
- operators
- symbol definition
- value of true and false
- Conditional-error directives (table)
- Conditional-jump instructions
- based on flag status (table)
- defined
- logic
- used after Compare (table)
- .CONST directive
- Constants
- as direct memory operands
- integer
- loading instructions (list)
- multiplying and dividing by
- packed binary coded decimal
- real number
- string
- use of
- Control data, coprocessor
- Control registers, coprocessor (figure)
- Control-flag settings (table)
- Control-flow instructions
- Controlling program flow
- Converting
- binary to decimal
- data sizes
- Coprocessors
- architecture
- control data
- control flags (figure)
- control instructions (list)
- control registers (figure)
- data registers (figure)
- directives (list)
- emulator
- loading data
- loading pi
- no-wait instructions
- operand forms (table)
- storing data
- Copying data
- Count register
- Cpu equate
- CS register
- /Cu option
- Current-line highlighting
- Custom flags
- Customer support
- CWD instruction
- /Cx option
- /Cx option
- CX register
-
- D
-
- D
- DAA instruction
- DAS instruction
- .DATA directive
- .DATA? directive
- Data conversion
- Data equate
- Data pointer, using far
- Data registers, coprocessor (figure)
- Data segments
- defining
- developing programs with
- initializing
- registers
- Data-definition directives
- Data-manipulation instructions
- DataSize equate
- DB directive
- .DBG files
- DD directive
- Debug expression operators
- Debug flags
- Debugging
- commands
- local variables
- DEC instruction
- Decimal conversion example
- Decimal, packed BCD numbers
- Decimal radix
- Declaring
- far pointers
- strings
- Decrementing
- Defaults
- file extension
- QCL driver
- radix
- segment names
- segment registers
- simplified segment
- stack size
- types
- Defining symbols from command line
- Dereferencing, pointer
- Destination operand
- Destination string
- DGROUP group name
- COMM directive, with
- DOSSEG, with
- simplified segments, with
- DH register
- DI register
- DIF
- Direction flag
- Directives
- .186
- .286
- .287
- .8086
- .8087
- ALIGN
- .ALPHA
- ASSUME
- CATSTR
- .CODE
- COMM
- COMMENT
- .CONST
- .DATA
- .DATA?
- data definition (list)
- data (list)
- DB
- DD
- defined
- DOSSEG
- DQ
- DT
- DW
- ELSE
- ELSEIF
- EQU
- equal sign (=)
- .ERR
- .ERR1
- .ERR2
- .ERRB
- .ERRDEF
- .ERRDIF
- .ERRE
- .ERRIDN
- .ERRNB
- .ERRNDEF
- .ERRNZ
- EVEN
- EXITM
- EXTRN
- .FARDATA
- .FARDATA?
- full segment
- global
- GROUP
- IF
- IF1
- IF2
- IFB
- IFDEF
- IFDIF
- IFE
- IFIDN
- IFNB
- IFNDEF
- INCLUDE
- INCLUDELIB
- INSTR
- instruction set
- IRP
- IRPC
- LABEL
- .LALL
- .LFCOND
- .LIST
- LOCAL
- MACRO
- .MODEL
- .MSFLOAT
- NAME
- on-line help
- ORG
- %OUT
- PAGE
- PROC
- PUBLIC
- PURGE
- .RADIX
- RECORD
- REPT
- .SALL
- SEGMENT
- .SEQ
- .SFCOND
- simplified segment
- SIZESTR
- .STACK
- .STARTUP
- string-manipulation (list)
- STRUC
- SUBSTR
- SUBTTL
- .TFCOND
- TITLE
- .XALL
- .XLIST
- Directives
- Displacement
- Display dialog box (figure)
- Display mode
- DIV instruction
- Divide overflow interrupt
- Dividing
- by constants
- integers
- DL register
- DM
- Document conventions
- Documentation feedback card
- $ (dollar sign)
- location counter symbol
- symbol names, used in
- Dollar sign ($)
- location counter symbol
- symbol names, used in
- DOS
- returning to
- version requirements
- DOS functions
- calling with interrupts
- Exit, registers used (list)
- on-line help
- segment-order convention
- Write, registers used (list)
- /DOSSEG linker option
- DOSSEG directive
- DP
- DQ directive
- DS
- DS register
- /Dsymbol option
- DT directive
- Dummy parameters
- macros
- repeat blocks
- Dummy segment definitions
- DUP operator
- DW directive
- DW memory operator
- DWORD type specifier
- DX register
- Dynamic variables
-
- E
-
- Effective address
- Elements, array
- ELSE directive
- ELSEIF directives
- Emulator, coprocessor
- Encoded real numbers
- END directive
- ENDIF directive
- ENDM directive
- ENDP directive
- ENDS directive
- ENTER instruction
- Environment variable, INCLUDE
- EQ operator
- EQU directive
- = (equal sign), directive
- Equal sign (=), directive
- Equates
- defined
- nonredefinable
- predefined. See predefined equates
- redefinable
- string
- .ERR directive
- .ERR1 directive
- .ERR2 directive
- .ERRB directive
- .ERRDEF directive
- .ERRDIF directive
- .ERRE directive
- .ERRIDN directive
- .ERRNB directive
- .ERRNDEF directive
- .ERRNZ directive
- Error lines, displaying
- ES register
- ESC instruction
- EVEN directive
- ! (exclamation point), operator
- Exclamation point (!), operator
- Execution, tracing
- Exit function, DOS
- Exiting a program
- EXITM directive
- Exponent, part of real-number constant
- Exponentiation, with 8087-family coprocessors
- Expression operator (%)
- Expressions
- in Debug commands
- defined
- External names
- External symbols
- Extra segment
- EXTRN directive
- /Ez option
-
- F
-
- F2XM1 instruction
- FABS instruction
- FADD instruction
- FADDP instruction
- False conditionals, listing
- Far data pointers
- decimal conversion with
- loading
- FAR type specifier
- .FARDATA? directive
- .FARDATA directive
- Fardata equate
- Fardata? equate
- farStack
- farStack keyword
- Fatal errors
- FBLD instruction
- FBSTP instruction
- FCHS instruction
- FCOM instruction
- FCOMP instruction
- FCOMPP instruction
- FDIV instruction
- FDIVP instruction
- FDIVR instruction
- FDIVRP instruction
- FIADD instruction
- FICOM instruction
- FICOMP instruction
- FIDIV instruction
- FIDIVR instruction
- Fields
- assembler statements
- bit
- records
- structures
- File extensions, default
- File menu, defaults
- Filename equate
- Files
- .COM
- .DBG
- include
- listing
- specifications
- FIMUL instruction
- Finishing execution
- FINIT instruction
- First-in-first-out (FIFO)
- FIST instruction
- FISTP instruction
- FISUB instruction
- FISUBR instruction
- /Fl option
- Flags
- 8086-family processors (figure)
- altering within environment
- build
- control, settings after compare or test (table)
- coprocessor, processor control (figure)
- loading and storing
- register, summary (table)
- FLD instruction
- FLD1 instruction
- FLDCW instruction
- FLDL2E instruction
- FLDL2T instruction
- FLDLG2 instruction
- FLDLN2 instruction
- FLDPI instruction
- FLDZ instruction
- Floating-point numbers
- FMUL instruction
- FMULP instruction
- For loops, emulating high-level-language statement
- FORTRAN
- compiler
- do loops, emulating
- nestinglevel
- return value (figure)
- Forward references
- defined
- during a pass
- labels
- variables
- FPATAN instruction
- /FPi option
- FPREM instruction
- FPTAN instruction
- Fraction
- Framepointer
- FRNDINT instruction
- FSCALE instruction
- FSQRT instruction
- FST instruction
- FSTCW instruction
- FSTP instruction
- FSTSW instruction
- FSUB instruction
- FSUBP instruction
- FSUBR instruction
- FSUBRP instruction
- FTST instruction
- Full segment definitions
- Function return values
- FXAM instruction
- FXCH instruction
- FXTRACT instruction
- FYL2X instruction
- FYL2XP1 instruction
-
- G
-
- GE operator
- General-purpose registers
- Global directives
- defined
- illustrated
- Global flags
- Global scope
- Global symbols
- GROUP directive
- Group-relative segments
- Groups
- assembly listing
- defined
- illustrated
- size restriction
- GT operator
-
- H
-
- Hardware interrupts
- Help menu
- Help on DOS and BIOS functions
- Help topics
- Hexadecimal conversion example
- Hexadecimal radix
- in Debug expressions
- specifier
- HIGH operator
- High-level languages
- interfacing with
- memory model
- HLT instruction
- Huge memory model
-
- I
-
- /I option
- IDIV instruction
- IEEE format
- If blocks, run-time
- IF directives
- IF1 directive
- IF2 directive
- IFB directive
- IFDEF directive
- IFDIF directive
- IFE directive
- IFIDN directive
- IFNB directive
- IFNDEF directive
- Immediate operands
- Implied operands
- IMUL instruction
- IN instruction
- INC instruction
- INCLUDE directive
- INCLUDE environment variable
- Include files
- assembly listings
- using
- View menu command
- INCLUDELIB directive
- Incrementing
- Indeterminate operand
- Index checking
- Index, Help menu selection
- Index operator
- Index registers
- Indexed operands
- Indirect addressing modes (table)
- Indirection, pointer
- Initializing
- segment registers
- variables
- INS instruction
- INSTR directive
- Instruction-pointer register (IP)
- Instructions
- AAA
- AAD
- AAM
- AAS
- ADC
- ADD
- addition (list)
- AND
- bit test
- BOUND
- CALL
- CBW
- CLC
- CLD
- CLI
- CMP
- CMPS
- compare
- conditional-jump
- control-flow
- CWD
- DAA
- DAS
- data-manipulation
- DEC
- defined
- DIV
- ESC
- F2XM1
- FABS
- FADD
- FADDP
- FBLD
- FBSTP
- FCHS
- FCOM
- FCOMP
- FCOMPP
- FDIV
- FDIVP
- FDIVR
- FDIVRP
- FIADD
- FICOM
- FICOMP
- FIDIV
- FIDIVR
- FILD
- FIMUL
- FINIT
- FIST
- FISTP
- FISUB
- FISUBR
- FLD
- FLD1
- FLDCW
- FLDL2E
- FLDL2T
- FLDLG2
- FLDLN2
- FLDPI
- FLDZ
- FMUL
- FMULP
- FPATAN
- FPREM
- FPTAN
- FRNDINT
- FSCALE
- FSQRT
- FST
- FSTCW
- FSTP
- FSTSW
- FSUB
- FSUBP
- FSUBR
- FSUBRP
- FTST
- FXAM
- FXCH
- FXTRACT
- FYL2X
- FYL2XP1
- HLT
- IDIV
- IMUL
- IN
- INC
- INS
- INT
- INTO
- IRET
- JC
- Jcondition
- JCXZ
- JMP
- LAHF
- LDS
- LEA
- LEAVE
- LES
- LOCK
- LODS
- logical
- LOOP
- LOOPNE
- LOOPNZ
- MOV
- MOVS
- MUL
- multiplication (list)
- NEG
- NOP
- normal division (list)
- normal subtraction (list)
- NOT
- on-line help
- OR
- OUT
- OUTS
- POP
- POPA
- POPF
- program-flow
- PUSH
- PUSHA
- PUSHF
- REP
- REPE
- REPNE
- REPNZ
- REPZ
- RET
- RETF
- RETN
- reversed division (list)
- reversed subtraction (list)
- SAHF
- SBB
- SCAS
- STD
- STI
- STOS
- SUB
- TEST
- timing of
- WAIT
- XCHG
- XLAT
- XOR
- Instructions
- instructions
- AND
- LDS
- LES
- SHL
- Instruction-set directives
- INT instruction
- Integer formats (figure)
- Integers
- Interrupt-enable flag
- Interrupts
- defined
- operation of (figure)
- using
- INTO instruction
- IP register
- IRET instruction
- IRP directive
- IRPC directive
-
- J
-
- JC instruction
- Jcondition instruction
- JCXZ instruction
- JMP instruction
- JO instruction
- Jump tables
- Jumping conditionally
-
- K
-
- Keywords, on-line help for
-
- L
-
- /l option
- LABEL directive
- Labels
- macros, in
- near-code
- procedures
- LAHF instruction
- .LALL directive
- Language type
- COMM directive
- EXTRN directive
- .MODEL directive
- PROC statement
- PUBLIC directive
- Large memory model
- LDS instruction
- LE operator
- LEA instruction
- Learning assembly language
- LEAVE instruction
- LENGTH operator
- LES instruction
- .LFCOND directive
- Line-continuation character
- .LIST directive
- Listing
- controlling contents of
- false conditionals
- format
- addresses
- described
- EQU directive
- errors
- groups
- include files
- LOCK directive
- macro expansions
- macros
- Pass 1, reading
- records
- REP directive
- segment override
- segments
- structures
- symbols
- macros
- subtitles in
- suppressing output
- symbols and abbreviations in (table)
- Listing files
- creating
- editor-oriented
- example
- index to source code
- macro expansion
- Pass 1
- setting title
- suppressing tables
- View menu
- viewing
- Literal-character operator (!)
- Literal-text operator (\la)
- Loading
- constants to coprocessor
- coprocessor data
- far pointers
- values from strings
- LOCAL directive
- declaring stack variables
- symbols declared with
- using
- Local symbols
- defined
- in macros
- Local variables
- in procedures
- on stack (figure)
- Location counter ($)
- LOCK directive, assembly listing
- LOCK instruction
- LODS instruction
- Logarithms
- Logical bit operations (table of values)
- Logical instructions
- Logical operators
- vs. logical instructions
- (table)
- LOOP instruction
- Looping
- overview
- without use of CX
- LOOPNE instruction
- LOOPNZ instruction
- LOW operator
- LT operator
-
- M
-
- Machine code
- Macro comment operator (\sc\sc)
- MACRO directive
- Macro expansions, assembly listings
- Macros
- argument testing
- arguments
- assembly listing
- calling
- compared to procedures
- defined
- efficiency penalty
- exiting early
- expansions in listing
- local symbols
- nested
- operators
- parameters
- recursive
- redefining
- removing from memory
- string-manipulation directives (list)
- text
- viewing listing of
- Make dialog box (figure)
- MASK operator
- Masking bits
- Masking out a bit
- Masks, adjusting
- Math coprocessors
- Medium memory model
- Memory access, coordinating
- MEMORY combine type
- Memory models
- assembly procedure with
- default segments, types (table)
- described (list)
- Memory operands
- coprocessor
- defined
- Memory requirements
- Memory-model-independent procedures
- Messages
- to screen
- suppressing
- Microsoft Binary format
- Microsoft Binary Real format
- Microsoft segment model
- Mixed-language interface
- C
- entry sequences
- exit sequences
- local data
- register considerations
- return value
- Mixed-language programs
- building
- C and assembler
- program list (figure)
- .MODEL directive
- Model equate
- Modular programming
- Modulo division
- MOV instruction
- MOVS instruction
- .MSFLOAT directive
- MUL instruction
- Multiple modules
- Multiplying
- by 16
- by constants
- instructions
- Multiword values, shifting
-
- N
-
- NAME directive
- Names
- assigning
- external
- public
- reserved
- Naming convention
- NE operator
- Near pointers
- Near reference parameters, assembly
- NEAR type specifier
- nearStack
- nearStack keyword
- NEG instruction
- Negating
- Nesting
- conditional
- DUP operators
- include files
- macros
- procedures for Pascal
- segments
- Nonredefinable equates
- NOP instruction
- NOT instruction
- NOT operator
- No-wait coprocessor instructions
- Null class type
- Null string
-
- O
-
- Object records
- Octal radix
- OFFSET operator
- with group-relative segments
- with .MODEL directive
- overview
- ON GOSUB, emulating BASIC statement
- One-pass assembly option
- Operands
- based
- based indexed
- classical stack
- coprocessor
- defined
- destination
- direct memory
- immediate
- implied
- indeterminate
- indexed
- indirect memory
- location counter
- memory
- record field
- records
- register
- register indirect
- relocatable
- source
- strong typing
- structures
- types (list)
- undefined
- Operators
- AND
- arithmetic
- bitwise
- calculation
- defined
- DUP
- EQ
- expression (%)
- GE
- GT
- HIGH
- index
- LE
- LENGTH
- literal character (!)
- literal text
- logical (table)
- LOW
- LT
- macro comment (\sc\sc)
- macro (list)
- MASK
- NE
- NOT
- OFFSET
- OR
- precedence (table)
- PTR
- relational (table)
- SEG
- segment override (:). See
- (segment-override operator)
- shift
- SHL
- SHORT
- SHR
- SIZE
- structure-field name
- substitute (&)
- THIS
- TYPE
- .TYPE
- WIDTH
- XOR
- Options
- /a
- /AT
- /Cl
- /Cu
- /Cx
- /DOSSEG linker
- /Dsymbol
- /Ez
- /Fl
- /FPi
- /I
- /l
- /P1
- /s
- /Sa
- /Sd
- /Se
- setting inside environment
- /Sn
- /Sq
- summary
- /Sx
- /t
- /v
- /W
- /w
- Options menu
- OR instruction
- OR operator
- ORG directive
- %OUT directive
- OUT instruction
- Output messages to screen
- OUTS instruction
- Overflow flag
- Overflow interrupt
-
- P
-
- /P1 option
- Packed BCD numbers
- Packed decimal integers
- Packed decimal numbers
- PAGE align type
- Page breaks in assembly listings
- PAGE directive
- Page format, in listings
- PARA align type
- Parameter list, in PROC statement
- Parameters
- assembly, accessing from
- defining in procedures
- macros
- repeat blocks
- types, common (list)
- Parity flag
- Partial remainder
- Pascal
- case statements, emulating
- compiler
- for loops
- nestinglevel
- repeat loops, emulating
- return value (figure)
- Pass 1 listing
- Passing by
- near reference, assembly
- value
- assembly
- C
- % (percent sign), expression operator
- Percent sign (%), expression operator
- . (period)
- Period (.)
- Phase errors
- Pi, loading to coprocessor
- Plus sign (+), to separate registers
- Pointer indirection
- Pointer registers
- Pointers
- defining
- loading
- POP instruction
- POPA instruction
- POPF instruction
- Ports
- defined
- getting strings from
- sending strings to
- Precedence of operators
- Predefined equates
- CodeSize
- Cpu
- CurSeg
- DataSize
- FileName
- Model
- segment
- Version
- WordSize
- Preserving register values
- PROC directive
- PROC type specifier
- Procedure arguments, on stack (figure)
- Procedures
- compared to macros
- defining labels
- using
- Processor directives
- Product assistance report
- Program list (figure)
- Program-flow instructions
- PTR operator
- declaring parameters with
- declaring pointers with
- syntax
- used with data types
- PUBLIC combine type
- PUBLIC directive
- Public names
- Public symbols
- PURGE directive
- PUSH instruction
- PUSHA instruction
- PUSHF instruction
-
- Q
-
- QC environment
- building programs
- debugging commands
- described
- on-line help
- starting
- QC register variables
- QCL driver
- introduction
- options (list)
- ? (question mark)
- Question mark (?)
- Quick Advisor
- QuickAssembler
- assembly interfaces, writing
- environment
- See QC environment
- QuickBASIC compiler
- QuickC, interfacing with
- QWORD type specifier
-
- R
-
- .RADIX directive
- Radixes
- binary
- default
- specifiers (table)
- Real numbers
- arithmetic calculations
- designator (R)
- directives for defining (list)
- encoding
- format
- transfer instructions (list)
- RECORD directive
- Record operands
- compared to variables (figure)
- using
- Record type
- contents (figure)
- declaring
- Record variables
- compared to operands (figure)
- contents (figure)
- defining
- Records
- assembly listing
- declarations
- defining
- field operands
- fields
- initializing
- MASK operator
- object
- WIDTH operator
- Recursive macros
- Redefinable equates
- Redefining
- interrupts
- macros
- Register stacks
- classical-stack form (figure)
- data transfer (figure)
- memory form (figure)
- register form (figure)
- register-pop form (figure)
- Register variables, in C
- Registers
- accumulator
- AH
- AL
- altering within environment
- AX
- BH
- BL
- BP
- BX
- CH
- CL
- coprocessor
- CS
- CX
- defined
- DH
- DI
- DL
- DS
- DX
- ES
- flags
- general purpose
- index
- IP
- operands
- operands, coprocessor
- pointer
- preserving value of
- register-pop operands, coprocessor
- segment
- setting to zero
- SI
- SP
- SS
- strategy for using
- types of
- uses of
- watching contents of
- Registers window
- Relational operators (table)
- REP directive, assembly listing
- REP instruction
- REPE instruction
- Repeat blocks
- arguments
- defined
- parameters
- repeat for each argument
- repeat for each character of string
- repeat for specified count
- Repeating
- instructions, execution of
- using 8086-family string functions
- REPNE instruction
- REPNZ instruction
- Reporting problems
- REPT directive
- REPZ instruction
- Reserved names
- RET instruction
- RETF instruction
- RETN instruction
- Return value, offset (figure)
- Rotating bits (figure)
-
- S
-
- /s option
- /Sa option
- SAHF instruction
- .SALL directive
- SBB instruction
- Scaling by powers of two
- Scaling factor
- SCAS instruction
- Screen swapping
- \sc\sc (semicolons), operator
- /Sd option
- /Se option
- Search paths, include files
- Searching strings
- Sections in assembly listings
- SEG operator
- SEGMENT directive
- Segment register
- Segmented addressing
- Segment-order method
- (Segment-override operator)
- definition
- OFFSET operator, with
- string instructions, with
- XLAT instructions, with
- (segment-override operator)
- used with ES
- Segments
- absolute
- align type
- align type (figure)
- assembly listing
- combine type
- (figure)
- (list)
- defined
- extra
- group-relative offset
- groups
- defining
- structure (figure)
- MEMORY
- nesting
- ordering
- override, assembly listings
- override operator (:). See
- (segment-override operator)
- registers
- types
- Semicolons (\sc\sc), operator
- .SEQ directive
- Serious warnings
- Severe errors
- .SFCOND directive
- Shift operators
- Shifting
- bits
- multiword values
- SHL instruction
- SHL operator
- SHORT operator
- SHR operator
- SI register
- Sign flag
- Signed numbers
- Simplified segment defaults
- SIZE operator
- SIZESTR directive
- Small memory model
- defining attributes
- described
- setting up procedures
- Smart help
- /Sn option
- Source files, include
- Source modules
- Source operand
- Source string
- SP register
- Specifying calling convention
- /Sq option
- Square root
- SS register
- .STACK directive
- Stack
- allocating
- defined
- frame
- local variables on (figure)
- near and far types
- operands, coprocessor
- procedure arguments on (figure)
- registers
- segment
- status, after pushes and pops (figure)
- trace. See Calls command
- type
- use of
- variables
- STACK combine type
- Stand-alone programs
- .STARTUP directive
- Statement fields
- Statistics
- STD instruction
- Step Over command
- STI instruction
- STOS instruction
- String directives (list)
- Strings
- comparing
- constants
- declaring
- defined
- destination strings
- equates
- filling
- instructions, requirements for (table)
- loading values from
- moving
- null
- ports, transfer from and to
- searching
- source
- structures, in
- variables
- Strong typing
- STRUC directive
- Structure type
- Structure-field-name operator
- Structures
- assembly listing
- declarations
- definitions
- fields
- initializing
- operands
- overview
- variables
- SUB instruction
- Substitute operator (&)
- SUBSTR directive
- Substring directive
- Subtitles in listings
- Subtracting values
- SUBTTL directive
- /Sx option
- Symbols
- assembly listing
- communal
- defined
- defining from command line
- external
- global
- location counter
- public
- relocatable operands
- scope
- SYMDEB
- System requirements
-
- T
-
- /t option
- TBYTE type specifier
- Temporary real format (figure)
- TER
- Terminating execution
- TEST instruction
- Text macros
- .TFCOND directive
- THIS operator
- /TINY linker option
- Tiny memory model
- Tiny model option
- TITLE directive
- Trace Into command
- Tracing execution
- Transcendental calculations
- Transferring
- BCD integers
- binary integers
- real numbers
- Trap flag
- Trigonometric functions
- Tutorial books, assembly language
- Type
- ABS
- align
- class
- combine
- null class
- operand matching
- operators, described
- PROC
- record
- structure
- TYPE operator
- Type specifiers (list)
- .TYPE operator
-
- U
-
- Undefined operand
- Underscore (_)
- Unpacked BCD numbers
- Unsigned numbers
- Updates
- USES, in PROC statement
-
- V
-
- /v option
- Value parameters, C
- Variables
- automatic
- communal
- defined
- dynamic
- external
- floating point
- initializing
- integer
- local
- public
- real number
- record
- string
- structure
- watching value of
- Version equate
- View menu Include command
-
- W
-
- /W option
- /w option
- WAIT instruction
- Warning levels
- Watch Value command
- Watch Value dialog box (figure)
- Watchpoint command
- Weak typing in other assemblers
- While, emulating high-level-language statement
- WIDTH operator
- Width, structures
- WO memory operator
- WORD align type
- WORD PTR, in example
- WORD type specifier
- WordSize equate
- Write function, DOS
-
- X
-
- .XALL directive
- XCHG instruction
- XENIX compatibility
- pathnames, with / (forward slash)
- /sI
- XLAT instruction
- .XLIST directive
- XOR instruction
- XOR operator
-
- Z
-
- Zero flag
-