Programs written in assembly language are inherently more difficult to understand than those written in a high-level language like Pascal or C. A thought that can be expressed in one Pascal statement is spread over ten or so statements in assembly language, and a Pascal construct like IF-THEN-ELSE or WHILE-ENDWHILE has to be simulated using comparisons and jumps. Techniques such as dividing the program into logical modules, commenting, and the use of blank lines and indentations to visually tie together blocks of statements that logically belong together, tend to make a program written in any language more readable but are especially helpful in assembly-language programming.
Sets of equates, macros, structure definitions, blocks of assembly language statements, and complete subprocedures pertaining to a particular task or device can often become tools useful in other programs. The use of such tools, provided they are tested, validated, and properly documented, tends to make program development easier and faster.
There are two ways to keep such tools in separate files and combine them with the current program as needed: EQUates, macros, and STRUCture definitions are kept in an .ASM or .INC source file and copied into another .ASM source file with the %include directive; complete subprocedures are best assembled separately and kept in an .OBJ file from which the linker (TLINK) can extract the required subprocedures and combine them with the program into one .EXE file--that is in fact a linker's main raison d'etre.
An .ASM (or other) file consisting of EQUates, macros, and/or STRUCture definitions may be inserted into another assembly language program with the directive
%include "[path]filename.ext"
The entire file specified is inserted immediately after the %include directive and is assembled together with the rest of the program. Note that the file to be %included may consist of any collection of statements acceptable to NASM. If the file contains macro definitions it must of course be %included before any of the macros are invoked.
%include is particularly well suited for a library of EQUates, macros, and/or STRUCture definitions, since they add assembly code to the program only when they are invoked; for a file of subroutines, on the other hand, %include lengthens the program by adding to it the assembly code of all subroutines in the file, even those never invoked by the program, and slows down the assembly. A library of subroutines should instead be supplied as a file of separately assembled subroutines (although an include file may be supplied to define the routines as EXTERN).
The division of a programming task into several logical modules, or subprocedures, where each module's task and interface is defined carefully before detailed programming is started, makes it possible to identify subtasks for which existing subprocedures can be used, leads to faster program development, and makes it easier to test, validate, and verify the program.
The interface specification refers primarily to the calling convention or protocol, i.e., the method that is used to pass arguments to a subprocedure and pass results back to the calling program; restricting all interactions between modules to the well-defined interfaces makes it possible to use separately-developed modules from a tool kit, avoids "side effects," and minimizes incompatibility problems. In high-level languages the parameter passing conventions are pre-defined; in assembly language programs several different methods may be used but some preferred protocols are usually established to simplify the problem.
Arguments may be passed to a subprocedure, and results may be passed back to the calling program, in several different ways. If the subprocedure is recursive, the only practical way to pass arguments if via the stack; if the subprocedure is not recursive arguments may be passed via registers, via the stack, or via global variables (or a combination of these approaches). An argument may be passed "by value," i.e., as a signed or unsigned integer, ASCII character, or other code, or "by reference," i.e., as an address pointing to a variable, list, table, array, or structure. Commonly, argument passing by value is unidirectional, i.e., the values may be used and modified by the subprocedure with the changes not visible to the calling program. Argument passing by reference, however, is bidirectional, since the contents of the addresses given as arguments may be modified by the subprocedure, thus implicitly passing results back to the calling program.
An arbitrarily large number of arguments may be pushed onto the stack before the subprocedure is called; these arguments must be removed from the stack again upon return from the subprocedure. (Passing arguments via the stack is the only practical method for recursive subprocedures).
Registers are commonly used when few arguments are passed. (Note that one address argument is sufficient to point to an entire array, parameter list, etc).
Passing arguments via global variables is the least-general method, since specific global variables must be associated with specific subprocedures--the same effect could be achieved by passing the variables by reference.
Results computed by the subprocedure may be passed back to the calling program in a similar way, but note that results for arguments passed by reference are passed back implicitly.
It is frequently convenient to use procedures that have been assembled separately, such as library routines, and let the linker combine the separately assembled program with the library routines. Procedures in separately assembled modules are considered "external" to each other. The effective address of a name that is defined in an external procedure cannot be computed by NASM; it will have to be filled in later by the linker. However, such names have to be identified to NASM with the directive
EXTERN name[, ...]
where name is the symbol defined in the external module.
Similarly, NASM must be able to tell the linker all names in a module that can be referenced by external procedures. This is done with the directive
GLOBAL name[, ...]
Note that the names appearing in the EXTERN directive of this module are listed in the PUBLIC directive of the external module, and vice versa.
Lastly, the code segment in this module should have the same name as the code segment in the external procedure (which cannot be changed in the case of library procedures) and have the "combine-type" PUBLIC so that the two logical code segments are combined into one physical segment.
The module containing the main program is, strictly speaking, a separately assembled module. It differs from other separately assembled modules, however, in the following points:
It is most likely the module in which the stack is declared.
It is most likely the module containing the starting point of the program (..start).
Other separately assembled modules, whether parts of the program or library routines, are written following the rules given above but:
Need not have a stack specified--if a stack is specified it will be concatenated with the stack specified in preceding modules
Should not define a ..start label.
TLINK is designed to link separately assembled procedures as well as extract the library routines used by the program from a library file. The general form for invoking TLINK is:
tlink [/c] [/v] {objfile ...} , [exefile] , [mapfile] , [libfile ...]
Where objfile (there may be more than one) are the separately assembled modules of the program and libfiles are the files to be searched for library routines. See Section 2.2 for a more detailed description of the TLINK options.