GNU Info

Info Node: (nasm.info)Section 7.4.3

(nasm.info)Section 7.4.3


Next: Section 7.4.4 Prev: Section 7.4.2 Up: Section 7.4
Enter node , (file) or (file)node

7.4.3. Function Definitions and Function Calls
----------------------------------------------

   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.

   * The caller pushes the function's parameters on the stack, one after
     another, in reverse order (right to left, so that the first
     argument specified to the function is pushed last).

   * The caller then executes a `CALL' instruction to pass control to
     the callee. This `CALL' is either near or far depending on the
     memory model.

   * The callee receives control, and typically (although this is not
     actually necessary, in functions which do not need to access their
     parameters) starts by saving the value of `SP' in `BP' so as to be
     able to use `BP' as a base pointer to find its parameters on the
     stack.  However, the caller was probably doing this too, so part
     of the calling convention states that `BP' must be preserved by
     any C function. Hence the callee, if it is going to set up `BP' as
     a _frame pointer_, must push the previous value first.

   * The callee may then access its parameters relative to `BP'. The
     word at `[BP]' holds the previous value of `BP' as it was pushed;
     the next word, at `[BP+2]', holds the offset part of the return
     address, pushed implicitly by `CALL'. In a small-model (near)
     function, the parameters start after that, at `[BP+4]'; in a
     large-model (far) function, the segment part of the return address
     lives at `[BP+4]', and the parameters begin at `[BP+6]'. The
     leftmost parameter of the function, since it was pushed last, is
     accessible at this offset from `BP'; the others follow, at
     successively greater offsets. Thus, in a function such as `printf'
     which takes a variable number of parameters, the pushing of the
     parameters in reverse order means that the function knows where to
     find its first parameter, which tells it the number and type of
     the remaining ones.

   * The callee may also wish to decrease `SP' further, so as to
     allocate space on the stack for local variables, which will then
     be accessible at negative offsets from `BP'.

   * The callee, if it wishes to return a value to the caller, should
     leave the value in `AL', `AX' or `DX:AX' depending on the size of
     the value. Floating-point results are sometimes (depending on the
     compiler) returned in `ST0'.

   * Once the callee has finished processing, it restores `SP' from
     `BP' if it had allocated local stack space, then pops the previous
     value of `BP', and returns via `RETN' or `RETF' depending on
     memory model.

   * When the caller regains control from the callee, the function
     parameters are still on the stack, so it typically adds an
     immediate constant to `SP' to remove them (instead of executing a
     number of slow `POP' instructions). Thus, if a function is
     accidentally called with the wrong number of parameters due to a
     prototype mismatch, the stack will still be returned to a sensible
     state since the caller, which _knows_ how many parameters it
     pushed, does the removing.

   It is instructive to compare this calling convention with that for
Pascal programs (described in *Note Section 7.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 `RET' or `RETF' instruction, so
the caller does not have to do it. Also, the parameters are pushed in
left-to- right order, not right-to-left, which means that a compiler
can give better guarantees about sequence points without performance
suffering.

   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 `RET' by `RETF', and
look for the first parameter at `[BP+6]' instead of `[BP+4]'.  Of
course, if one of the parameters is a pointer, then the offsets of
_subsequent_ parameters will change depending on the memory model as
well: far pointers take up four bytes on the stack when passed as a
parameter, whereas near pointers take up two.

   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 `DS' already holds the segment base of
the segment `_DATA'. If not, you would have to initialise it first.

           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 `int' data type. The first
argument (pushed last) to `printf', however, is a data pointer, and
therefore has to contain a segment and offset part. The segment should
be stored second in memory, and therefore must be pushed first. (Of
course, `PUSH DS' would have been a shorter instruction than `PUSH WORD
SEG mystring', if `DS' was set up as the above example assumed.) Then
the actual call becomes a far call, since functions expect far calls in
large model; and `SP' has to be increased by 6 rather than 4 afterwards
to make up for the extra word of parameters.


automatically generated by info2www version 1.2.2.9