• 검색 결과가 없습니다.

Declaring procedures

문서에서 Paradigm Assembler User's Guide (페이지 129-141)

C h a p t e r

10

You can override the default distance of a procedure by specifying the desired distance in the procedure definition. To do this, use the NEAR or FAR keywords. These

keywords override the default procedure distance, but only for the current procedure.

For example,

MODEL TINY;default distance near

;test1 is a far procedure test1 PROC PAR

;body of procedure

RET ;this will be a far return ENDP

;test2 is by default a near procedure test2 PROC

;body of procedure

RET ;this will be a near return ENDP

The same RET mnemonic is used in both NEAR and FAR procedures; Paradigm Assembler uses the distance of the procedure to determine whether a near or far return is required. Similarly, Paradigm Assembler uses the procedure distance to determine whether a near or far call is required to reference the procedure:

CALL test1;this is a far call CALL test2;this is a near call

When you make a call to a forward referenced procedure, Paradigm Assembler might have to make multiple passes to determine the distance of the procedure. For example,

test1 PROC NEAR MOV ax,10 CALL test2 RET

test1 ENDP test2 PROC FAR ADD ax, ax RET

test2 ENDP

When Paradigm Assembler reaches the ''call test2" instruction during the first pass, it has not yet encountered test2, and therefore doesn't know the distance. It assumes a distance of NEAR, and presumes it can use a near call.

When it discovers that test2 is in fact a FAR procedure, Paradigm Assembler determines that it needs a second pass to correctly generate the call. If you enable multiple passes (with the /m command-line switch), a second pass will be made. If you don't enable multiple passes, Paradigm Assembler will report a "forward

reference needs override" error.

You can specify the distance of forward referenced procedures as NEAR PTR or FAR PTR in the call to avoid this situation (and to reduce the number of passes)..

test1 PROC NEAR MOV ax, 10

CALL FAR PTR test2 RET

test1 ENDP

The previous example tells Paradigm Assembler to use a far call, so that multiple assembler passes aren't necessary.

Declaring a procedure language

You can easily define procedures that use high-level language interfacing conventions in Paradigm Assembler. Interfacing conventions are supported for the NOLANGUAGE (Assembler), BASIC, FORTRAN, PROLOG, C, CPP (C++), SYSCALL, STDCALL, and PASCAL languages.

Paradigm Assembler does all the work of generating the correct prolog (procedure entry) and epilog (procedure exit) code necessary to adhere to the specified language convention.

You can specify a default language as a parameter of the MODEL directive. See Chapter 7 for further details. If a default language is present, all procedures that don't otherwise specify a language use the conventions of the default language.

To override the default language for an individual procedure, include the language name in the procedure definition. You can specify a procedure language by including a language identifier keyword in its declaration. For example, a definition in MASM mode for a PASCAL procedure would be

pascalproc PROC PASCAL FAR ;procedure body

pascalproc ENDP

Paradigm Assembler uses the language of the procedure to determine what prolog and epilog code is automatically included in the procedure's body. The prolog code sets up the stack frame for passed arguments and local variables in the procedure; the epilog code restores the stack frame before returning from the procedure.

Paradigm Assembler automatically inserts prolog code into the procedure before the first instruction of the procedure, or before the first "label:" tag.

Prolog code does the following:

l Saves the current BP on the stack.

l Sets BP to the current stack pointer.

l Adjusts the stack pointer to allocate local variables.

l Saves the registers specified by USES on the stack.

Paradigm Assembler automatically inserts epilog code into the procedure at each RET instruction in the procedure (if there are multiple RETS, the epilog code will be inserted multiple times). Paradigm Assembler also inserts epilog code before any object-oriented method jump (see Chapter 4).

Epilog code reverses the effects of prolog code in the following ways:

l Pops the registers specified by USES off the stack.

l Adjusts the stack pointer to discard local arguments.

l Pops, the stored BP off the stack.

l Adjusts the stack to discard passed arguments (if the language requires it) and returns.

The last part of the epilog code, discarding passed arguments, is performed only for those languages requiring the procedure to discard arguments (for example, BASIC, FORTRAN, PASCAL). The convention for other languages (C, C++, PROLOG) is to leave the arguments on the stack and let the caller discard them. SYSCALL behaves like C++. For the STDCALL language specification, C++ calling conventions are used if the procedure has variable arguments. Otherwise, PASCAL calling conventions are used.

Paradigm Assembler always implements the prolog and epilog code using the most efficient instructions for the language and the current processor selected. Paradigm Assembler doesn't generate prolog or epilog code for NOLANGUAGE procedures. If such procedures expect arguments on the stack, you must specifically include the prolog and epilog code yourself.

In general, the language of a procedure affects the procedure in the manner shown in the following figure.

Language: None Basic Fortran Pascal C CPP Prolog Argument

ordering (left-to-right, right-to-left)

L-R L-R L-R L-R R-L R-L R-L

Who cleans up stack (caller, procedure)

PROC PROC PROC PROC CALLER CALLER CALLER

You can use the /la command-line switch to include procedure prolog and epilog code in your listing file. This lets you see the differences between the languages. See Chapter 13 for further information.

Specifying a language modifier

Language modifiers tell Paradigm Assembler to include special prolog and epilog code in procedures that interface with Windows. To use them, specify one before the

procedure language in the model directive, or in the procedure header. Valid modifiers are NORMAL, WINDOWS, ODDNEAR, and ODDFAR.

Additionally, you can specify a default language modifier as a parameter of the MODEL directive. If a default language modifier exists, all procedures that don't otherwise specify a language modifier will use the conventions of the default. See Chapter 7 for more information.

Include the modifier in the procedure definition to specify the language modifier for an individual procedure. For example,

+

Figure 10-1 How language affects procedures

sample PROC WINDOWS PASCAL FAR ;prodedure body

ENDP

If you don't specify a language modifier, Paradigm Assembler uses the language modifier specified in the MODEL statement. Paradigm Assembler will generate the standard prolog or epilog code for the procedure if there isn't a MODEL statement, or if NORMAL is specified.

If you've selected the WINDOWS language modifier, Paradigm Assembler generates prolog and epilog code that lets you call the procedure from Windows. Paradigm Assembler generates special prolog and epilog code only for FAR Windows

procedures. You can t call NEAR procedures from Windows, so they don't need special prolog or epilog code. Procedures called by Windows typically use PASCAL calling conventions. For example,

winproc PROC WINDOWS PASCAL PAR

ARG @@hwnd:WORD,@@mess.WORD,@@wparam:WORD,@@lparam:DWORD ;body of procedure

ENDP

Refer to your Windows documentation for more information on Windows procedures.

Defining arguments and local variables

Paradigm Assembler passes arguments to higher-level language procedures in stack frames by pushing the arguments onto the stack before the procedure is called. A language procedure reads the arguments off the stack when it needs them. When the procedure returns, it either removes the arguments from the stack at that point (the Pascal calling convention), or relies on the caller to remove the arguments (the C calling convention).

The ARG directive specifies, in the procedure declaration, the stack frame arguments passed to procedures. Arguments are defined internally as positive offsets from the BP or EBP registers.

The procedure's language convention determines whether or not the arguments will be assigned in reverse order on the stack. You should always list arguments in the ARG statement in the same order they would appear in a high-level declaration of the procedure.

The LOCAL directive specifies, in the procedure declaration, the stack frame variables local to procedures. Arguments are defined internally as negative offsets from the BP or EBP register.

Allocate space for local stack frame variables on the stack frame by including procedure prolog code, which adjusts the stack pointer downward by the amount of space

required. The procedure's epilog code must discard this extra space by restoring the stack pointer. (Paradigm Assembler automatically generates this prolog code when the procedure obeys any language convention other than NOLANGUAGE.)

Remember that Paradigm Assembler assumes that any procedure using stack frame arguments will include proper prolog code in it to set up the BP or EBP register.

(Paradigm Assembler automatically generates prolog code when the procedure obeys

+

+

any language convention other than NOLANGUAGE). Define arguments and local variables with the ARG and LOCAL directives even if the language interfacing convention for the procedure is NOLANGUAGE. No prolog or epilog code win automatically be generated, however, in this case.

ARG and LOCAL syntax

Here's the syntax for defining the arguments passed to the procedure.

ARG argument [,argument] ..,. [=symbol]

[RETURNS argument [,argument]]

To define the local variables for the procedure, use the following:

LOCAL argument [,argument] ... [=symbol]

An individual argument has the following syntax:

argname [[count1_expression]] [: complex_type [:count2_expression]]

complex_type is the data type of the argument. It can be either a simple type, or a complex pointer expression. See Chapter 5 for more information about the syntax of complex types.

If you don't specify a complex_type field, Paradigm Assembler assumes WORD. It assumes DWORD if the selected model is a 32-bit model.

count2_expression specifies how many items of this type the argument defines. An argument definition of

ARG tmp:DWORD:4

defines an argument called tmp, consisting of 4 double words.

The default value for count2_expression is 1, except for arguments of type BYTE.

Since you can't push a byte value, BYTE arguments have a default count of 2 to make them word-sized on the stack. This corresponds with the way high-level languages treat character variables passed as parameters. If you really want to specify an argument as a single byte m the stack, you must explicitly supply a count2_expression field of 1, such as

ARG realbyte:BYTE:1

count1_expression is an array element size multiplier. The total space reserved for the argument on the stack is count2_expression times the length specified by the argtype field, times countl_expression. The default value for count1_expression is 1 if it is not specified. count1_expression times count2_expression specifies the total count of the argument.

For Paradigm Assembler, you can specify count2_expression using the ? keyword to indicate that a variable number of arguments are passed to the procedure. For example, an argument definition of

ARG tmp:WORD:?

defines an argument called tmp, consisting of a variable number of words.

? must be used as the last item in the argument list. Also, you can use ? only in procedures that support variable-length arguments (such as procedures that use the C calling conventions).

If you end the argument list with an equal sign (=) and a symbol, Paradigm Assembler will equate that symbol to the total size of the argument block in bytes. If you are not

using Paradigm Assembler's automatic handling of high level language interfacing conventions, you can use this value at the end of the procedure as an argument to the RET instruction. Notice that this causes a stack cleanup of any pushed arguments before returning (this is the Pascal calling convention).

The arguments and variables are defined within the procedure as BP-relative memory operands. Passed arguments defined with ARG are positive offset from BP; local variables defined with LOCAL are negative offset from BP. For example,

func1 PROC NEAR

ARG a:WORD,b:DWORD:4,c:BYTE=d LOCAL x:DWORD,y:WORD:2=z

defines a as [bp+4], b as [bp+6], c as [bp+14], and d as 20; x is [bp-2], y is [bp=6], and z is 8.

The scope of ARG and LOCAL variable names

All argument names specified in the procedure header, whether ARGs (passed

arguments), RETURNs (return arguments), or LOCALs (local variables), are global in scope unless you give them names prepended with the local symbol prefix.

The LOCALS directive enables locally scoped symbols. For example,

LOCALS

test1 PROC PASCAL FAR

ARG @@a:WORD,@@b:DWORD,@@c:BYTE LOCAL @@x:WORD,@@y:DWORD

MOV ax,@@a MOV @@x,ax LES di, @@b

MOV WORD ptr @@y,di MOV WORD ptr @@y+2,es MOV @@c, 'a'

RET ENDP

test2 PROC PASCAL FAR ARG @@a:DWORD,@@b:BYTE LOCAL @@x:WORD

LES di, @@a MOV ax,es:[di]

MOV @@x,ax CMP al, @@b jz @@dn mov a@x, 0

@@dn: MOV ax, @@x RET

ENDP

Since this example uses locally scoped variables, the names exist only within the body of the procedure. Thus, test2 can reuse the argument names @@a, @@b, and @@x.

See Chapter 11 for more information about controlling the scope of symbols.

Preserving registers

Most higher-level languages require that called procedures preserve certain registers.

You can do this by pushing them at the start of the procedure, and popping them again at the end of it.

Paradigm Assembler can automatically generate code to save and restore these registers as part of a procedure's prolog and epilog code. You can specify these registers with the USES statement. Here's its syntax:

USES item [,item] ...

item can be any register or single-token data item that can legally be pushed or popped.

There is a limit of eight items per procedure. For example,

myproc PROC PASCAL NEAR

ARG @@source:DWORD,@@dest:DWORD,@@count:WORD USES cx,,si, di, foo

MOV cx,i@count MOV foo,@@count LES di, @@dest LDS si,@@source REP MOVSB

RET ENDP

See Chapter 18 for information about what registers are normally preserved.

USES is only available when used with procedures that have a language interfacing convention other than NOLANGUAGE. Defining procedures using procedure types You can use a procedure type (defined with PROCTYPE) as a template for the procedure declaration itself. For example,

Defining procedures using procedure types

You can use a procedure type (defined with PROCTYPE) as a template for the procedure declaration itself. For example,

footype PROCTYPE pascal near :word, :dword,:word

foo PROC footype ;pascal near procedure arg al:word,a2:dword,a3:word ;an error would occur if ;arguments did not match ;those of footype

When you declare a procedure using a named procedure description, the number and types of the arguments declared for PROC are checked against those declared by PROCTYPE. The procedure description supplies the language and distance of procedure declaration.

Nested procedures and scope rules

All procedures have global scope, even if you nest them within another procedure. For example,

test1 PROC FAR ;some code here CALL test2

;some more code here RET

test2 PROC NEAR ;some code here RET ;near return test2 ENDP

test1 ENDP

In this example, it's legal to call test1 or test2 from outside the outer procedure.

If you want to have localized subprocedures, use a locally scoped name. For example,

LOCALS

testl PROC FAR ;some code here RET

@@test2 PROC NEAR ;some code here RET

@@test2 ENDP test1 ENDP

In this case, you can only access the procedure @@test2 from within the procedure test1. n fact, there can be multiple procedures named @@test2 as long as no two are within the same procedure. For example, the following is legal:

LOCALS

test1 PROC FAR

MOV si,OFFSET Buffer CALL @@test2

RET

@@test2 PROC NEAR ;some code here RET

@@test2 ENDP test1 ENDP test2 PROC PAR

MOV si,OFFSET Buffer2 CALL @@test2

RET

@@test2 PROC NEAR ;some code here RET

@@test2 ENDP test2 ENDP

The following code is not legal:

LOCALS

test1 PROC FAR

mov si,OFFSET,Buffer CALL @@test2

RET test1 ENDP

@@test2 PROC NEAR ;some code here RET

@@test2 ENDP

since the CALL to @@test2 specifies a symbol local to the procedure testl, and no such symbol exists.

The LOCALS directive enables locally scoped symbols. See Chapter 11 for further information.

Declaring method procedures for objects

Some special considerations apply when you create method procedures for objects.

Object method procedures must be able to access the object that they are operating on, and thus require a pointer to that object as a parameter to the procedure.

Paradigm Assembler's treatment of objects is flexible enough to allow a wide range of conventions for passing arguments to method procedures. The conventions are

constrained only by the need to interface with objects created by a high-level language.

If you are writing a native assembly-language object method procedure, you might want to use register argument passing conventions. In this case, you should write a method procedure to expect a pointer to the object in a register or register pair (such as ES:DI).

If you are writing a method procedure that uses high-level language interfacing

conventions, your procedure should expect the object pointer to be one of the arguments passed to the procedure. The object pointer passed from high-level OOP languages like C++ is an implicit argument usually placed at the start of the list of arguments. A method procedure written in assembly language must include the object pointer explicitly in its list of arguments, or unexpected results will occur. Remember that the object pointer can be either a WORD or DWORD quantity, depending on whether the object is NEAR or FAR.

Other complexities arise when you write constructor or destructor procedures in assembly language. C++ uses other implicit arguments (under some circumstances) to indicate to the constructor or destructor that certain actions must be taken. Constructors written for an application using native assembly language do not necessarily need a pointer to the object passed to them. If an object is never statically allocated, the object's constructor will always allocate the object from the heap.

You can find information about the calling conventions of Paradigm C++ in Chapter 18.

Using procedure prototypes

Paradigm Assembler lets you declare procedure prototypes much like procedure prototypes in C. To do so, use the PROCDESC directive.

The Ideal mode syntax of PROCDESC is:

PROCDESC name [procedure_description]

Use the following syntax in MASM mode:

name PROCDESC [procedure_description]

procedure_description is similar to the language and argument specification used in the PROC directive. Its syntax is:

[[language_modifier] language] [distance] [argument_list]

+

+

language_modifier, language, and distance have the same syntax as in the PROC directive. argument_list has the form:

argument [,argument] ...

For more information about PROC, see the beginning of this chapter.

An individual argument has the following syntax:

[argname] [[count1_expression]]:complex_type [:count2_expression]

complex_type is the data type of the argument, and can be either a simple type or a pointer expression. count2_expression specifies how many items of this type the argument defines. The default value of count2_expression is 1, except for arguments of BYTE, which have a default count of 2 (since you can't PUSH a byte value onto the 80x86 stack). See Chapter 5 for further information about the syntax of complex types.

For the last argument, in procedure types whose calling convention allows

variable_length arguments (like C), count2_expression can be ? to indicate that the procedure caller will determine the size of the array.

Note that the name of each argument (argname) is optional, but complex_type is required for each argument because procedure types are used mainly for type checking purposes. The names of the arguments do not have to agree, but the types must.

Here's an example:

test PROCDESC pascal near a:word,b:dword,c:word

This example defines a prototype for the procedure test as a PASCAL procedure taking three arguments (WORD, DWORD, WORD). Argument names are ignored, and you can omit them in the PROCDESC directive, as follows:

test PROCDESC pascal near :word,:dword,:word

The procedure prototype is used to check calls to the procedure, and to check the PROC declaration against the language, number of arguments, and argument types in the prototype. For example,

test PROC pascal near.

ARG a1:word,a2:dword,a3:word ;matches PROCDESC for test

PROCDESC also globally publishes the name of the procedure. Procedures that are not defined in a module are published as externals, while procedures that are defined are published as public. Be sure that PROCDESC precedes the PROC declaration, and any use of the procedure name.

Procedure prototypes can also use procedure types (defined with PROCTYPE). For example,

footype PROCTYRE pascal near :word,:dword,:word foo PROCDESC footype

문서에서 Paradigm Assembler User's Guide (페이지 129-141)