Related
I wanted to write something basic in assembly under Windows. I'm using NASM, but I can't get anything working.
How do I write and compile a hello world program without the help of C functions on Windows?
This example shows how to go directly to the Windows API and not link in the C Standard Library.
global _main
extern _GetStdHandle#4
extern _WriteFile#20
extern _ExitProcess#4
section .text
_main:
; DWORD bytes;
mov ebp, esp
sub esp, 4
; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
push -11
call _GetStdHandle#4
mov ebx, eax
; WriteFile( hstdOut, message, length(message), &bytes, 0);
push 0
lea eax, [ebp-4]
push eax
push (message_end - message)
push message
push ebx
call _WriteFile#20
; ExitProcess(0)
push 0
call _ExitProcess#4
; never here
hlt
message:
db 'Hello, World', 10
message_end:
To compile, you'll need NASM and LINK.EXE (from Visual studio Standard Edition)
nasm -fwin32 hello.asm
link /subsystem:console /nodefaultlib /entry:main hello.obj
NASM examples.
Calling libc stdio printf, implementing int main(){ return printf(message); }
; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits. It needs to be linked with a C library.
; ----------------------------------------------------------------------------
global _main
extern _printf
section .text
_main:
push message
call _printf
add esp, 4
ret
message:
db 'Hello, World', 10, 0
Then run
nasm -fwin32 helloworld.asm
gcc helloworld.obj
a
There's also The Clueless Newbies Guide to Hello World in Nasm without the use of a C library. Then the code would look like this.
16-bit code with MS-DOS system calls: works in DOS emulators or in 32-bit Windows with NTVDM support. Can't be run "directly" (transparently) under any 64-bit Windows, because an x86-64 kernel can't use vm86 mode.
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
Build this into a .com executable so it will be loaded at cs:100h with all segment registers equal to each other (tiny memory model).
Good luck.
These are Win32 and Win64 examples using Windows API calls. They are for MASM rather than NASM, but have a look at them. You can find more details in this article.
This uses MessageBox instead of printing to stdout.
Win32 MASM
;---ASM Hello World Win32 MessageBox
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
.data
title db 'Win32', 0
msg db 'Hello World', 0
.code
Main:
push 0 ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg ; LPCSTR lpText
push 0 ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax ; uExitCode = MessageBox(...)
call ExitProcess
End Main
Win64 MASM
;---ASM Hello World Win64 MessageBox
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
.data
title db 'Win64', 0
msg db 'Hello World!', 0
.code
main proc
sub rsp, 28h
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx, msg ; LPCSTR lpText
lea r8, title ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
add rsp, 28h
mov ecx, eax ; uExitCode = MessageBox(...)
call ExitProcess
main endp
End
To assemble and link these using MASM, use this for 32-bit executable:
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
or this for 64-bit executable:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
Why does x64 Windows need to reserve 28h bytes of stack space before a call? That's 32 bytes (0x20) of shadow space aka home space, as required by the calling convention. And another 8 bytes to re-align the stack by 16, because the calling convention requires RSP be 16-byte aligned before a call. (Our main's caller (in the CRT startup code) did that. The 8-byte return address means that RSP is 8 bytes away from a 16-byte boundary on entry to a function.)
Shadow space can be used by a function to dump its register args next to where any stack args (if any) would be. A system call requires 30h (48 bytes) to also reserve space for r10 and r11 in addition to the previously mentioned 4 registers. But DLL calls are just function calls, even if they're wrappers around syscall instructions.
Fun fact: non-Windows, i.e. the x86-64 System V calling convention (e.g. on Linux) doesn't use shadow space at all, and uses up to 6 integer/pointer register args, and up to 8 FP args in XMM registers.
Using MASM's invoke directive (which knows the calling convention), you can use one ifdef to make a version of this which can be built as 32-bit or 64-bit.
ifdef rax
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
else
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text db 'Hello World', 0
.code
main proc
invoke MessageBoxA, 0, offset text, offset caption, 0
invoke ExitProcess, eax
main endp
end
The macro variant is the same for both, but you won't learn assembly this way. You'll learn C-style asm instead. invoke is for stdcall or fastcall while cinvoke is for cdecl or variable argument fastcall. The assembler knows which to use.
You can disassemble the output to see how invoke expanded.
To get an .exe with NASM as the assembler and Visual Studio's linker this code works fine:
default rel ; Use RIP-relative addressing like [rel msg] by default
global WinMain
extern ExitProcess ; external functions in system libraries
extern MessageBoxA
section .data
title: db 'Win64', 0
msg: db 'Hello world!', 0
section .text
WinMain:
sub rsp, 28h ; reserve shadow space and make RSP%16 == 0
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx,[msg] ; LPCSTR lpText
lea r8,[title] ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
mov ecx,eax ; exit status = return value of MessageBoxA
call ExitProcess
add rsp, 28h ; if you were going to ret, restore RSP
hlt ; privileged instruction that crashes if ever reached.
If this code is saved as test64.asm, then to assemble:
nasm -f win64 test64.asm
Produces test64.obj
Then to link from command prompt:
path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no
where path_to_link could be C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin or wherever is your link.exe program in your machine,
path_to_libs could be C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x64 or wherever are your libraries (in this case both kernel32.lib and user32.lib are on the same place, otherwise use one option for each path you need) and the /largeaddressaware:no option is necessary to avoid linker's complain about addresses to long (for user32.lib in this case).
Also, as it is done here, if Visual's linker is invoked from command prompt, it is necessary to setup the environment previously (run once vcvarsall.bat and/or see MS C++ 2010 and mspdb100.dll).
(Using default rel makes the lea instructions work from anywhere, including outside the low 2GiB of virtual address space. But the call MessageBoxA is still a direct call rel32 that can only reach instructions +-2GiB away from itself.)
Flat Assembler does not need an extra linker. This makes assembler programming quite easy. It is also available for Linux.
This is hello.asm from the Fasm examples:
include 'win32ax.inc'
.code
start:
invoke MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
invoke ExitProcess,0
.end start
Fasm creates an executable:
>fasm hello.asm
flat assembler version 1.70.03 (1048575 kilobytes memory)
4 passes, 1536 bytes.
And this is the program in IDA:
You can see the three calls: GetCommandLine, MessageBox and ExitProcess.
If you want to use NASM and Visual Studio's linker (link.exe) with anderstornvig's Hello World example you will have to manually link with the C Runtime Libary that contains the printf() function.
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
Hope this helps someone.
Unless you call some function this is not at all trivial. (And, seriously, there's no real difference in complexity between calling printf and calling a win32 api function.)
Even DOS int 21h is really just a function call, even if its a different API.
If you want to do it without help you need to talk to your video hardware directly, likely writing bitmaps of the letters of "Hello world" into a framebuffer. Even then the video card is doing the work of translating those memory values into DisplayPort/HDMI/DVI/VGA signals.
Note that, really, none of this stuff all the way down to the hardware is any more interesting in ASM than in C. A "hello world" program boils down to a function call. One nice thing about ASM is that you can use any ABI you want fairly easily; you just need to know what that ABI is.
The best examples are those with fasm, because fasm doesn't use a linker, which hides the complexity of windows programming by another opaque layer of complexity.
If you're content with a program that writes into a gui window, then there is an example for that in fasm's example directory.
If you want a console program, that allows redirection of standard in and standard out that is also possible.
There is a (helas highly non-trivial) example program available that doesn't use a gui, and works strictly with the console, that is fasm itself. This can be thinned out to the essentials. (I've written a forth compiler which is another non-gui example, but it is also non-trivial).
Such a program has the following command to generate a proper header for 32-bit executable, normally done by a linker.
FORMAT PE CONSOLE
A section called '.idata' contains a table that helps windows during startup to couple names of functions to the runtimes addresses. It also contains a reference to KERNEL.DLL which is the Windows Operating System.
section '.idata' import data readable writeable
dd 0,0,0,rva kernel_name,rva kernel_table
dd 0,0,0,0,0
kernel_table:
_ExitProcess#4 DD rva _ExitProcess
CreateFile DD rva _CreateFileA
...
...
_GetStdHandle#4 DD rva _GetStdHandle
DD 0
The table format is imposed by windows and contains names that are looked up in system files, when the program is started. FASM hides some of the
complexity behind the rva keyword. So _ExitProcess#4 is a fasm label and _exitProcess is a string that is looked up by Windows.
Your program is in section '.text'. If you declare that section readable writeable and executable, it is the only section you need to add.
section '.text' code executable readable writable
You can call all the facilities you declared in the .idata section. For a console program you need _GetStdHandle to find he filedescriptors for standard in and standardout (using symbolic names like STD_INPUT_HANDLE which fasm finds in the include file win32a.inc).
Once you have the file descriptors you can do WriteFile and ReadFile.
All functions are described in the kernel32 documentation. You are probably aware of that or you wouldn't try assembler programming.
In summary: There is a table with asci names that couple to the windows OS.
During startup this is transformed into a table of callable addresses, which you use in your program.
For ARM Windows:
AREA data, DATA
Text DCB "Hello world(text)", 0x0
Caption DCB "Hello world(caption)", 0x0
EXPORT WinMainCRTStartup
IMPORT __imp_MessageBoxA
IMPORT __imp_ExitProcess
AREA text, CODE
WinMainCRTStartup PROC
movs r3,#0
ldr r2,Caption_ptr
ldr r1,Text_ptr
movs r0,#0
ldr r4,MessageBoxA_ptr # nearby, reachable with PC-relative
ldr r4,[r4]
blx r4
movs r0,#0
ldr r4,ExitProcess_ptr
ldr r4,[r4]
blx r4
MessageBoxA_ptr DCD __imp_MessageBoxA # literal pool (constants near code)
ExitProcess_ptr DCD __imp_ExitProcess
Text_ptr DCD Text
Caption_ptr DCD Caption
ENDP
END
I wanted to write something basic in assembly under Windows. I'm using NASM, but I can't get anything working.
How do I write and compile a hello world program without the help of C functions on Windows?
This example shows how to go directly to the Windows API and not link in the C Standard Library.
global _main
extern _GetStdHandle#4
extern _WriteFile#20
extern _ExitProcess#4
section .text
_main:
; DWORD bytes;
mov ebp, esp
sub esp, 4
; hStdOut = GetstdHandle( STD_OUTPUT_HANDLE)
push -11
call _GetStdHandle#4
mov ebx, eax
; WriteFile( hstdOut, message, length(message), &bytes, 0);
push 0
lea eax, [ebp-4]
push eax
push (message_end - message)
push message
push ebx
call _WriteFile#20
; ExitProcess(0)
push 0
call _ExitProcess#4
; never here
hlt
message:
db 'Hello, World', 10
message_end:
To compile, you'll need NASM and LINK.EXE (from Visual studio Standard Edition)
nasm -fwin32 hello.asm
link /subsystem:console /nodefaultlib /entry:main hello.obj
NASM examples.
Calling libc stdio printf, implementing int main(){ return printf(message); }
; ----------------------------------------------------------------------------
; helloworld.asm
;
; This is a Win32 console program that writes "Hello, World" on one line and
; then exits. It needs to be linked with a C library.
; ----------------------------------------------------------------------------
global _main
extern _printf
section .text
_main:
push message
call _printf
add esp, 4
ret
message:
db 'Hello, World', 10, 0
Then run
nasm -fwin32 helloworld.asm
gcc helloworld.obj
a
There's also The Clueless Newbies Guide to Hello World in Nasm without the use of a C library. Then the code would look like this.
16-bit code with MS-DOS system calls: works in DOS emulators or in 32-bit Windows with NTVDM support. Can't be run "directly" (transparently) under any 64-bit Windows, because an x86-64 kernel can't use vm86 mode.
org 100h
mov dx,msg
mov ah,9
int 21h
mov ah,4Ch
int 21h
msg db 'Hello, World!',0Dh,0Ah,'$'
Build this into a .com executable so it will be loaded at cs:100h with all segment registers equal to each other (tiny memory model).
Good luck.
These are Win32 and Win64 examples using Windows API calls. They are for MASM rather than NASM, but have a look at them. You can find more details in this article.
This uses MessageBox instead of printing to stdout.
Win32 MASM
;---ASM Hello World Win32 MessageBox
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
.data
title db 'Win32', 0
msg db 'Hello World', 0
.code
Main:
push 0 ; uType = MB_OK
push offset title ; LPCSTR lpCaption
push offset msg ; LPCSTR lpText
push 0 ; hWnd = HWND_DESKTOP
call MessageBoxA
push eax ; uExitCode = MessageBox(...)
call ExitProcess
End Main
Win64 MASM
;---ASM Hello World Win64 MessageBox
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
.data
title db 'Win64', 0
msg db 'Hello World!', 0
.code
main proc
sub rsp, 28h
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx, msg ; LPCSTR lpText
lea r8, title ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
add rsp, 28h
mov ecx, eax ; uExitCode = MessageBox(...)
call ExitProcess
main endp
End
To assemble and link these using MASM, use this for 32-bit executable:
ml.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:Main
or this for 64-bit executable:
ml64.exe [filename] /link /subsystem:windows
/defaultlib:kernel32.lib /defaultlib:user32.lib /entry:main
Why does x64 Windows need to reserve 28h bytes of stack space before a call? That's 32 bytes (0x20) of shadow space aka home space, as required by the calling convention. And another 8 bytes to re-align the stack by 16, because the calling convention requires RSP be 16-byte aligned before a call. (Our main's caller (in the CRT startup code) did that. The 8-byte return address means that RSP is 8 bytes away from a 16-byte boundary on entry to a function.)
Shadow space can be used by a function to dump its register args next to where any stack args (if any) would be. A system call requires 30h (48 bytes) to also reserve space for r10 and r11 in addition to the previously mentioned 4 registers. But DLL calls are just function calls, even if they're wrappers around syscall instructions.
Fun fact: non-Windows, i.e. the x86-64 System V calling convention (e.g. on Linux) doesn't use shadow space at all, and uses up to 6 integer/pointer register args, and up to 8 FP args in XMM registers.
Using MASM's invoke directive (which knows the calling convention), you can use one ifdef to make a version of this which can be built as 32-bit or 64-bit.
ifdef rax
extrn MessageBoxA: PROC
extrn ExitProcess: PROC
else
.386
.model flat, stdcall
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
endif
.data
caption db 'WinAPI', 0
text db 'Hello World', 0
.code
main proc
invoke MessageBoxA, 0, offset text, offset caption, 0
invoke ExitProcess, eax
main endp
end
The macro variant is the same for both, but you won't learn assembly this way. You'll learn C-style asm instead. invoke is for stdcall or fastcall while cinvoke is for cdecl or variable argument fastcall. The assembler knows which to use.
You can disassemble the output to see how invoke expanded.
To get an .exe with NASM as the assembler and Visual Studio's linker this code works fine:
default rel ; Use RIP-relative addressing like [rel msg] by default
global WinMain
extern ExitProcess ; external functions in system libraries
extern MessageBoxA
section .data
title: db 'Win64', 0
msg: db 'Hello world!', 0
section .text
WinMain:
sub rsp, 28h ; reserve shadow space and make RSP%16 == 0
mov rcx, 0 ; hWnd = HWND_DESKTOP
lea rdx,[msg] ; LPCSTR lpText
lea r8,[title] ; LPCSTR lpCaption
mov r9d, 0 ; uType = MB_OK
call MessageBoxA
mov ecx,eax ; exit status = return value of MessageBoxA
call ExitProcess
add rsp, 28h ; if you were going to ret, restore RSP
hlt ; privileged instruction that crashes if ever reached.
If this code is saved as test64.asm, then to assemble:
nasm -f win64 test64.asm
Produces test64.obj
Then to link from command prompt:
path_to_link\link.exe test64.obj /subsystem:windows /entry:WinMain /libpath:path_to_libs /nodefaultlib kernel32.lib user32.lib /largeaddressaware:no
where path_to_link could be C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\bin or wherever is your link.exe program in your machine,
path_to_libs could be C:\Program Files (x86)\Windows Kits\8.1\Lib\winv6.3\um\x64 or wherever are your libraries (in this case both kernel32.lib and user32.lib are on the same place, otherwise use one option for each path you need) and the /largeaddressaware:no option is necessary to avoid linker's complain about addresses to long (for user32.lib in this case).
Also, as it is done here, if Visual's linker is invoked from command prompt, it is necessary to setup the environment previously (run once vcvarsall.bat and/or see MS C++ 2010 and mspdb100.dll).
(Using default rel makes the lea instructions work from anywhere, including outside the low 2GiB of virtual address space. But the call MessageBoxA is still a direct call rel32 that can only reach instructions +-2GiB away from itself.)
Flat Assembler does not need an extra linker. This makes assembler programming quite easy. It is also available for Linux.
This is hello.asm from the Fasm examples:
include 'win32ax.inc'
.code
start:
invoke MessageBox,HWND_DESKTOP,"Hi! I'm the example program!",invoke GetCommandLine,MB_OK
invoke ExitProcess,0
.end start
Fasm creates an executable:
>fasm hello.asm
flat assembler version 1.70.03 (1048575 kilobytes memory)
4 passes, 1536 bytes.
And this is the program in IDA:
You can see the three calls: GetCommandLine, MessageBox and ExitProcess.
If you want to use NASM and Visual Studio's linker (link.exe) with anderstornvig's Hello World example you will have to manually link with the C Runtime Libary that contains the printf() function.
nasm -fwin32 helloworld.asm
link.exe helloworld.obj libcmt.lib
Hope this helps someone.
Unless you call some function this is not at all trivial. (And, seriously, there's no real difference in complexity between calling printf and calling a win32 api function.)
Even DOS int 21h is really just a function call, even if its a different API.
If you want to do it without help you need to talk to your video hardware directly, likely writing bitmaps of the letters of "Hello world" into a framebuffer. Even then the video card is doing the work of translating those memory values into DisplayPort/HDMI/DVI/VGA signals.
Note that, really, none of this stuff all the way down to the hardware is any more interesting in ASM than in C. A "hello world" program boils down to a function call. One nice thing about ASM is that you can use any ABI you want fairly easily; you just need to know what that ABI is.
The best examples are those with fasm, because fasm doesn't use a linker, which hides the complexity of windows programming by another opaque layer of complexity.
If you're content with a program that writes into a gui window, then there is an example for that in fasm's example directory.
If you want a console program, that allows redirection of standard in and standard out that is also possible.
There is a (helas highly non-trivial) example program available that doesn't use a gui, and works strictly with the console, that is fasm itself. This can be thinned out to the essentials. (I've written a forth compiler which is another non-gui example, but it is also non-trivial).
Such a program has the following command to generate a proper header for 32-bit executable, normally done by a linker.
FORMAT PE CONSOLE
A section called '.idata' contains a table that helps windows during startup to couple names of functions to the runtimes addresses. It also contains a reference to KERNEL.DLL which is the Windows Operating System.
section '.idata' import data readable writeable
dd 0,0,0,rva kernel_name,rva kernel_table
dd 0,0,0,0,0
kernel_table:
_ExitProcess#4 DD rva _ExitProcess
CreateFile DD rva _CreateFileA
...
...
_GetStdHandle#4 DD rva _GetStdHandle
DD 0
The table format is imposed by windows and contains names that are looked up in system files, when the program is started. FASM hides some of the
complexity behind the rva keyword. So _ExitProcess#4 is a fasm label and _exitProcess is a string that is looked up by Windows.
Your program is in section '.text'. If you declare that section readable writeable and executable, it is the only section you need to add.
section '.text' code executable readable writable
You can call all the facilities you declared in the .idata section. For a console program you need _GetStdHandle to find he filedescriptors for standard in and standardout (using symbolic names like STD_INPUT_HANDLE which fasm finds in the include file win32a.inc).
Once you have the file descriptors you can do WriteFile and ReadFile.
All functions are described in the kernel32 documentation. You are probably aware of that or you wouldn't try assembler programming.
In summary: There is a table with asci names that couple to the windows OS.
During startup this is transformed into a table of callable addresses, which you use in your program.
For ARM Windows:
AREA data, DATA
Text DCB "Hello world(text)", 0x0
Caption DCB "Hello world(caption)", 0x0
EXPORT WinMainCRTStartup
IMPORT __imp_MessageBoxA
IMPORT __imp_ExitProcess
AREA text, CODE
WinMainCRTStartup PROC
movs r3,#0
ldr r2,Caption_ptr
ldr r1,Text_ptr
movs r0,#0
ldr r4,MessageBoxA_ptr # nearby, reachable with PC-relative
ldr r4,[r4]
blx r4
movs r0,#0
ldr r4,ExitProcess_ptr
ldr r4,[r4]
blx r4
MessageBoxA_ptr DCD __imp_MessageBoxA # literal pool (constants near code)
ExitProcess_ptr DCD __imp_ExitProcess
Text_ptr DCD Text
Caption_ptr DCD Caption
ENDP
END
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 . . .
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
Below you'll find three very simple assembly codes, one for platform x64, one for platform x86 (Win32) with the END directive containing an entry point to the code, and the third, with the same Win32 code, but without the entry point in the END directive.
x64:
EXTERN ExitProcess:PROC
.CODE
main PROC
mov eax, -1
exit:
xor ecx, ecx
call ExitProcess
main ENDP
END
x86 (Win32) with entry point at the ENDdirective:
.386
.MODEL flat, stdcall
.stack 4096
ExitProcess PROTO STDCALL, dwExitCode: DWORD
.CODE
main PROC
mov eax, -1
invoke ExitProcess, 0
main ENDP
END main
x86 (Win32) without an entry point at the ENDdirective:
.386
.MODEL flat, stdcall
.stack 4096
ExitProcess PROTO STDCALL, dwExitCode: DWORD
.CODE
main PROC
mov eax, -1
invoke ExitProcess, 0
main ENDP
END ;main
I present below a table with the error messages produced by VS2019 (the same error messages are produced by VS2017) when I try to build these codes with the following linker option: /SUBSYSTEM = Not set and /ENTRY = empty.
I have a problem with the highlighted error message above. It doesn't make any sense to me, as it misleads one to assume that the /SUBSYSTEM option was Console, instead of Not set, for the Debug/x64 build.
i would like to write a string in console output using writeconsole API but it doesn't work
i link and build it using console in masm
here is the code
.386
.MODEL Flat,STDCALL
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
STD_OUTPUT_HANDLE EQU -11
.DATA
Msg db "Hello World",13,10,0
lmessage dd 13
.DATA?
consoleOutHandle dd ?
bytesWritten dd ?
.code
start:
INVOKE GetStdHandle, STD_OUTPUT_HANDLE
mov [consoleOutHandle],eax
invoke WriteConsole, consoleOutHandle,offset Msg,offset lmessage,offset bytesWritten,0
INVOKE ExitProcess,0
end start
when i run the exe output
i got the following
C:\masm32>18.exe
C:\masm32>
empty ouput
so any advice
The third parameter is the number of characters to be written, not the address of the number of characters to be written. Fortunately for you the address turned out to be over 64K which caused the call to fail with the error code ERROR_NOT_ENOUGH_MEMORY.
One obvious problem is that you haven't defined a stack:
.stack 8192
That needs to go after the .MODEL directive, but otherwise the location doesn't matter a lot. As little stack space as you're using, you could probably make it only 4096 bytes, but it won't make a lot of difference either way.
When you call WriteFile, you also want to pass the actual size of the data to be written. I usually compute it, something like:
message db "Hello World!", 13, 10
msg_size equ $ - offset message
; ...
invoke WriteFile, \
eax, \
offset message, \
msg_size, \
offset written, \
0
Note that instead of saving the standard output handle into memory, I've just passed it directly from EAX where GetStdHandle returns it. For a non-trivial program, saving it in memory is normally the right thing to do though.