Next Chapter | Previous Chapter | Contents | Index
This chapter attempts to cover some of the common issues encountered
when writing 16-bit code to run under 
.EXE Any large program written under DOS needs to be built as a
In general, you generate 
NASM may also support 
obj .EXE This section describes the usual method of generating
Most 16-bit programming language packages come with a suitable linker;
if you have none of these, there is a free linker called VAL, available in
When linking several 
An example of a NASM source file which can be assembled to a
segment code 
..start: 
        mov     ax,data 
        mov     ds,ax 
        mov     ax,stack 
        mov     ss,ax 
        mov     sp,stacktop
This initial piece of code sets up 
Note also that the special symbol 
        mov     dx,hello 
        mov     ah,9 
        int     0x21
The above is the main program: load 
        mov     ax,0x4c00 
        int     0x21
This terminates the program using another DOS system call.
segment data hello: db 'hello, world', 13, 10, '$'
The data segment contains the string we want to display.
segment stack stack 
        resb 64 
stacktop:
The above code declares a stack segment containing 64 bytes of
uninitialized stack space, and points 
The above file, when assembled into a 
bin .EXE The 
Included in the NASM archives, in the 
To produce a 
In this model, the code you end up writing starts at
You can't directly refer to your segment base value, unfortunately,
since this would require a relocation in the header, and things would get a
lot more complicated. So you should get your segment base by copying it out
of 
On entry to your 
A sample program which generates a 
.COM While large DOS programs must be written as
bin .COM 
        org 100h 
section .text 
start: 
        ; put your code here 
section .data 
        ; put data items here 
section .bss 
        ; put uninitialized data here
The 
The BSS (uninitialized data) section does not take up space in the
To assemble the above program, you should use a command line like
nasm myprog.asm -fbin -o myprog.com
The 
obj .COM If you are writing a 
If you do this, you need to take care of several things:
RESB 100h 100h .COM ORG ORG bin .COM .SYS MS-DOS device drivers - 
For more information on the format of 
This section covers the basics of writing assembly routines that call,
or are called from, C programs. To do this, you would typically write an
assembly module as a 
C compilers have the convention that the names of all global symbols
(functions or data) they define are formed by prefixing an underscore to
the name as it appears in the C program. So, for example, the function a C
programmer thinks of as 
If you find the underscores inconvenient, you can define macros to
replace the 
%macro cglobal 1 global _%1 %define %1 _%1 %endmacro %macro cextern 1 extern _%1 %define %1 _%1 %endmacro
(These forms of the macros only take one argument at a time; a
If you then declare an external like this:
cextern printf
then the macro will expand it as
extern _printf %define printf _printf
Thereafter, you can reference 
The 
Also see section 2.1.27.
NASM contains no mechanism to support the various C memory models directly; you have to keep track yourself of which one you are writing for. This means you have to keep track of the following things:
CS CALL RETN RET RETN CALL CALL FAR CALL seg:offset RETF RETF CALL FAR DS DS ES DS SS DS SS DS In models with a single code segment, the segment is called
The C calling convention in 16-bit programs is as follows. In the following description, the words caller and callee are used to denote the function doing the calling and the function which gets called.
CALL CALL SP BP BP BP BP BP [BP] BP [BP+2] CALL [BP+4] [BP+4] [BP+6] BP printf SP BP AL AX DX:AX ST0 SP BP BP RETN RETF SP POP It is instructive to compare this calling convention with that for
Pascal programs (described in section 8.5.1).
Pascal has a simpler convention, since no functions have variable numbers
of parameters. Therefore the callee knows how many parameters it should
have been passed, and is able to deallocate them from the stack itself by
passing an immediate argument to the 
Thus, you would define a function in C style in the following way. The following example is for small model:
global  _myfunc 
_myfunc: 
        push    bp 
        mov     bp,sp 
        sub     sp,0x40         ; 64 bytes of local stack space 
        mov     bx,[bp+4]       ; first parameter to function 
        ; some more code 
        mov     sp,bp           ; undo "sub sp,0x40" above 
        pop     bp 
        ret
For a large-model function, you would replace
At the other end of the process, to call a C function from your assembly code, you would do something like this:
extern  _printf 
      ; and then, further down... 
      push    word [myint]        ; one of my integer variables 
      push    word mystring       ; pointer into my data segment 
      call    _printf 
      add     sp,byte 4           ; `byte' saves space 
      ; then those data items... 
segment _DATA 
myint         dw    1234 
mystring      db    'This number -> %d <- should be 1234',10,0
This piece of code is the small-model assembly equivalent of the C code
    int myint = 1234; 
    printf("This number -> %d <- should be 1234\n", myint);
In large model, the function-call code might look more like this. In
this example, it is assumed that 
      push    word [myint] 
      push    word seg mystring   ; Now push the segment, and... 
      push    word mystring       ; ... offset of "mystring" 
      call    far _printf 
      add    sp,byte 6
The integer value still takes up one word on the stack, since large
model does not affect the size of the 
To get at the contents of C variables, or to declare variables which C
can access, you need only declare the names as
extern _i 
        mov ax,[_i]
And to declare your own integer variable which C programs can access as
global _j _j dw 0
To access a C array, you need to know the size of the components of the
array. For example, 
To access a C data structure, you need to know the offset from the base
of the structure to the field you are interested in. You can either do this
by converting the C structure definition into a NASM structure definition
(using 
To do either of these, you should read your C compiler's manual to find
out how it organizes data structures. NASM gives no special alignment to
structure members in its own 
struct { 
    char c; 
    int i; 
} foo;
might be four bytes long rather than three, since the
c16.mac Included in the NASM archives, in the 
(An alternative, TASM compatible form of 
An example of an assembly function using the macro set is given here:
proc    _nearproc 
%$i     arg 
%$j     arg 
        mov     ax,[bp + %$i] 
        mov     bx,[bp + %$j] 
        add     ax,[bx] 
endproc
This defines 
Note that the 
The macro set produces code for near functions (tiny, small and
compact-model code) by default. You can have it generate far functions
(medium, large and huge-model code) by means of coding
The large-model equivalent of the above function would look like this:
%define FARCODE 
proc    _farproc 
%$i     arg 
%$j     arg     4 
        mov     ax,[bp + %$i] 
        mov     bx,[bp + %$j] 
        mov     es,[bp + %$j + 2] 
        add     ax,[bx] 
endproc
This makes use of the argument to the 
Interfacing to Borland Pascal programs is similar in concept to interfacing to 16-bit C programs. The differences are:
DS The 16-bit Pascal calling convention is as follows. In the following description, the words caller and callee are used to denote the function doing the calling and the function which gets called.
CALL SP BP BP BP BP BP [BP] BP [BP+2] [BP+4] [BP+6] BP SP BP AL AX DX:AX ST0 Real DX:BX:AX String RETF SP BP BP RETF RETF Thus, you would define a function in Pascal style, taking two
global  myfunc 
myfunc: push    bp 
        mov     bp,sp 
        sub     sp,0x40         ; 64 bytes of local stack space 
        mov     bx,[bp+8]       ; first parameter to function 
        mov     bx,[bp+6]       ; second parameter to function 
        ; some more code 
        mov     sp,bp           ; undo "sub sp,0x40" above 
        pop     bp 
        retf    4               ; total size of params is 4
At the other end of the process, to call a Pascal function from your assembly code, you would do something like this:
extern  SomeFunc 
       ; and then, further down... 
       push   word seg mystring   ; Now push the segment, and... 
       push   word mystring       ; ... offset of "mystring" 
       push   word [myint]        ; one of my variables 
       call   far SomeFunc
This is equivalent to the Pascal code
procedure SomeFunc(String: PChar; Int: Integer); 
    SomeFunc(@mystring, myint);
Since Borland Pascal's internal unit file format is completely different
from 
CODE CSEG _TEXT CONST _DATA DATA DSEG _BSS GROUP c16.mac The 
Defining 
%define PASCAL 
proc    _pascalproc 
%$j     arg 4 
%$i     arg 
        mov     ax,[bp + %$i] 
        mov     bx,[bp + %$j] 
        mov     es,[bp + %$j + 2] 
        add     ax,[bx] 
endproc
This defines the same routine, conceptually, as the example in
section 8.4.5: it defines a function taking
two arguments, an integer and a pointer to an integer, which returns the
sum of the integer and the contents of the pointer. The only difference
between this code and the large-model C version is that