How to write and combine multiple source files for a project in MASM?

Viewed 618

Pretty new to assembly, having fun poking at it. I am wanting to split the functionality of my program across multiple files, specifically by grouping similar functions together for organization. These other files would be called by the main file (and hopefully even other non-main files). I haven't yet managed to do so, and would like help.

I am not using an IDE, preferring to use notepad++, ml.exe, and link.exe (from MASM folder) to write, assemble, and link the program myself. Most online resources I have looked at assume Visual Studio, and give code that doesn't work for me, or maybe is incomplete b/c the IDE does something else. I do not intend to start using an IDE.

I would like to learn the "best" way, meaning, the way that is most useful for future projects. Can I set it up in such a way that I can just copy the file and write a couple lines of code to use it in a different project in the future? Or maybe that's bad practice and I should learn a more standard method instead? I understand this platform is not for opinionated questions, and I'm hoping this question is more factually based than opinion.

All useful info I can think of:
Language: Masm assembly x86
Computer: 64 bit Windows

Code:

RUN.bat

@echo off

ml /c /coff /Zi /Fl Driver.asm
ml /c /coff /Zi /Fl Utils.asm

link /debug /subsystem:console /entry:start /out:Driver.exe Utils.obj Driver.obj \masm32\lib\kernel32.lib

Driver.exe

pause

Driver.asm

.386
.model flat
.stack 100h

ExitProcess PROTO Near32 STDCALL, dwExitCode:DWORD
ClearRegs PROTO

.DATA
.CODE

PUBLIC _start
_start:

    Main PROC
        MOV EAX, 0
        INVOKE ClearRegs
        INVOKE ExitProcess, 0
    Main ENDP

END

Utils.asm

.386
.model flat
.stack 100h

OPTION PROC:PRIVATE ; Set procedures to private by default

PUBLIC ClearRegs

.DATA
.CODE
    
    ClearRegs PROC C
        XOR EAX, EAX
        XOR EBX, EBX
        XOR ECX, ECX
        XOR EDX, EDX
        XOR ESI, ESI
        XOR EDI, EDI
        RET
    ClearRegs ENDP

END

Terminal output

Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: Driver.asm
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: Utils.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

Driver.obj : error LNK2001: unresolved external symbol ClearRegs
Driver.exe : fatal error LNK1120: 1 unresolved externals
'Driver.exe' is not recognized as an internal or external command,
operable program or batch file.
Press any key to continue . . .
1 Answers

Now that your question has been updated with a minimal, complete, verifiable example some specific problems can be identified. When you declare a function with PROC there is a language naming and calling convention applied to each function. Not specifying one associates no special processing.

You can specify a default language with the model directive as a second parameter. In both your files you have used:

.model flat

So you haven't associated a default language. You have defined ClearRegs as:

ClearRegs PROC C
[snip]
ClearRegs ENDP

The problem here is that PROC C specifies the C language calling convention and naming convention. With COFF format (32-bit) the C naming convention requires an underscore (_) to be prepended to the beginning of the function name. If you were to generate a MAP file you'd discover that the Function name exported from utils.asm is actually _ClearRegs and not ClearRegs.

There are a number of ways to fix this. You can choose not to add a default language to the .model directive and tell Driver.asm that ClearRegs is defined as a C PROTOtype by changing:

ClearRegs PROTO

to

ClearRegs PROTO C

So now utils.asm is exporting _ClearRegs and Driver.asm is importing _ClearRegs as both sides match and MASM will handle adding the extra underscore. INVOKE ClearRegs will use the naming convention associated with the PROTO statement which says the language is C so it will add the extra _ for you.

This brings up an additional change you can make. An END directive can be used to specify the entry point to your program rather than using /entry:<name> on the linker command line. The entry point has to have a name that starts with an _ to satisfy the linker.

You currently use this in Driver.asm:

PUBLIC _start
_start:

Main PROC
    [snip]
Main ENDP

END

And you use /entry:start when linking. You could change this to be:

_Main PROC
    [snip]
_Main ENDP

END _Main   ; END with a function name tells linker to use _Main as program entry point

When linking you can now remove the /entry option altogether and you don't need the _start label anymore. We can do better though. The entry point called by the MS C Runtime startup assumes the function is following the C language naming and calling convention. What is preferable is to do this:

Main PROC C
    [snip]
Main ENDP

END Main   ; END with a function name tells linker to use _Main as program entry point

If you intend to make all your functions PROC C then you can avoid specifying C in most places by changing the default language in both Utils.asm and Driver.asm by changing:

.model flat

to:

.model flat, C

This will change the default for PROTO statements, PUBLIC statements specifying a function defined with PROC and PROC statements themselves. Your code in Driver.asm could look like:

.386
.model flat, C
.stack 100h

ExitProcess PROTO Near32 STDCALL, dwExitCode:DWORD
ClearRegs PROTO

.DATA
.CODE

Main PROC
    MOV EAX, 0
    INVOKE ClearRegs
    INVOKE ExitProcess, 0
Main ENDP

END Main

Utils.asm could look like:

.386
.model flat, C
.stack 100h

OPTION PROC:PRIVATE ; Set procedures to private by default

PUBLIC ClearRegs

.DATA
.CODE
    
ClearRegs PROC
    XOR EAX, EAX
    XOR EBX, EBX
    XOR ECX, ECX
    XOR EDX, EDX
    XOR ESI, ESI
    XOR EDI, EDI
    RET
ClearRegs ENDP

END

And you'd link with:

link /debug /subsystem:console /out:Driver.exe Utils.obj Driver.obj \masm32\lib\kernel32.lib
Related