WriteConsole returns false in MASM - windows

I am trying to make an array, and access it's values and print them out
After calling the WriteConsole subroutine, it is returning false, however, all the values are supplied. Here we can see that - https://imgur.com/a/vUfwOo6
Eax register is 0 after calling WriteConsole. Here you can see the register values, that are being pushed to the stack. https://imgur.com/a/gv6s4uG
Considering, that WriteConsole is WINAPI subroutine, that means it's stdcall. So, I am passing values right to left.
lpReserved -> 0
lpNumberOfCharsWritten -> offset to 00403028 (CharsWritten variable)
nNumberOfCharsToWrite -> Just 2, because in array only ints are present of length 2
*lpBuffer -> ebx register, which contains array lvalue
hConsoleOutput -> Output from GetStdHandle (In this case -> edx register -> A0)
My MASM code:
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
include C:\masm32\include\masm32.inc
includelib C:\masm32\lib\masm32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\msvcrt.lib ; Some default includes :P
.data
dArray dd 10 dup (?) ; Main array
CharsWritten dd ?
LoopCounter dd 0
StdHandle dd ?
.code
PrintArrayToScreen proc
mov eax, STD_OUTPUT_HANDLE
push eax
call GetStdHandle
mov StdHandle,eax
mov eax,[LoopCounter]
innerPrintLoop:
mov ecx,offset dArray
mov eax, [LoopCounter]
mov ebx,[ecx + eax * 4]
mov esi,offset CharsWritten
push 0
push esi
push 2
push ebx
mov edx,StdHandle
push edx
call WriteConsole
mov eax,[LoopCounter]
inc eax
mov LoopCounter,eax ; Storing the Loop Counter in the variable
cmp eax,11 ; +1 because of loop counter increment
jnz innerPrintLoop
ret
PrintArrayToScreen endp
arrayLoop proc ; Subroutine for the array filling
mov eax,offset dArray
mov bx,10
mov ecx,0
innerLoop:
mov [eax + ecx * 4],bx ; ecx * 4 => counter * 4 bytes
inc bx
add ecx,1
cmp ecx,10
jne innerLoop
mov eax,offset dArray
ret
arrayLoop endp
start:
call arrayLoop
call PrintArrayToScreen
mov eax,0
push eax
call ExitProcess
end start

From the documentation for WriteConsole:
lpBuffer [in]
A pointer to a buffer that contains characters to be written to the console screen buffer.
So you should be passing the address of the data to be written, but you're actually passing the data itself.
You could "fix" that by changing the line mov ebx,[ecx + eax * 4] to lea ebx,[ecx + eax * 4]. But note that WriteConsole doesn't do any integer-to-string conversion for you, so you still probably wouldn't get the result you expected. If you want that sort of functionality, use printf.

Related

Sorting a list through a procedure in flat assembly

I'm pretty new to fasm and I just recently started learning about procedures. My problem is that I have a proc and I want it to sort my list in a certain way. But when I run my code it just seems to sort some random numbers from memory. I don't quite know why it happens and I would appreciate any help.
Here's the code:
format PE gui 5.0
include 'D:\Flat Assembler\INCLUDE\win32a.inc'
entry start
section '.data' data readable writable
mas1 dw 2, -3, 1, -1, 3, -2, 5, -5, -4, 4
N = ($ - mas1) / 2
numStr db N dup('%d '), 0
strStr db '%s', 0
undefStr db 'undefined', 0
buff db 50 dup(?)
Caption db 'Result', 0
section '.code' code readable executable
start:
stdcall bubble, mas1
cinvoke wsprintf, buff, numStr
invoke MessageBox, 0, buff, Caption, MB_OK + MB_ICONINFORMATION
invoke ExitProcess, 0
proc bubble, mas:word
mov ecx, 0
mov ebx, 0
outerLoop:
cmp ecx, 10
je done
mov ebx, 2
innerLoop:
mov eax, 0
mov edx, 0
cmp [mas+ebx], 0 ;if(mas[j] > 0)
jge continue ;continue
mov ax, [mas+ebx-2]
cmp ax, [mas+ebx]
jle continue
mov dx, [mas+ebx]
mov [mas+ebx-2], dx
mov [mas+ebx], ax
continue:
cmp ebx, 18 ;10
je innerDone
add ebx, 2 ;inc ebx
jmp innerLoop
innerDone:
inc ecx
jmp outerLoop
done:
mov ecx, 0
mov ebx, 0
mov ebx, 18
mov ecx, N
print:
mov eax, 0
mov ax, [mas+ebx]
cwde
push eax
sub ebx, 2
loop print
ret
endp
section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'
include 'D:\Flat Assembler\INCLUDE\API\kernel32.inc'
include 'D:\Flat Assembler\INCLUDE\API\user32.inc'
Error 1
stdcall bubble, mas1
...
proc bubble, mas:word
The parameter mas1 is an address and is pushed to the stack as a dword. Therefore you should not limit the argument mas to a word.
What your bubble procedure needs is the full address of the array. You get this via mov esi, [mas] that FASM will encode as if you would have written mov esi, [ebp+8]. EBP+8 is where the first argument (and in your program the only argument) resides, when the standard prologue push ebp mov ebp, esp is used.
Error 2
In your bubble procedure you push the resulting array to the stack hoping to have wsprintf use it from there, but once the bubble procedure executes its ret instruction, the epilogue code as well as the ret instruction itself will start eating your array and even return to the wrong address in memory!
If you're going to return an array via the stack, then store it above the return address and the argument(s). That's why I wrote in my program below:
sub esp, N*4 ; Space for N dwords on the stack
stdcall bubble, mas1
Error 3
cmp [mas+ebx], 0 ;if(mas[j] > 0)
jge continue ;continue
Your BubbleSort is wrong because you don't allow positive numbers to get compared!
Furthermore you make too many iterations that also continu for too long.
I tested below program on FASM 1.71.22 Don't forget to change the paths!
format PE gui 5.0
include 'C:\FASM\INCLUDE\win32a.inc'
entry start
section '.data' data readable writable
mas1 dw 2, -3, 1, -1, 3, -2, 5, -5, -4, 4
N = ($ - mas1) / 2
numStr db N-1 dup('%d, '), '%d', 0
;strStr db '%s', 0
;undefStr db 'undefined', 0
buff db 50 dup(?)
Caption db 'Result', 0
section '.code' code readable executable
start:
sub esp, N*4 ; Space for N dwords on the stack
stdcall bubble, mas1
cinvoke wsprintf, buff, numStr
invoke MessageBox, 0, buff, Caption, MB_OK + MB_ICONINFORMATION
invoke ExitProcess, 0
proc bubble uses ebx esi, mas
mov esi, [mas] ; Address of the array
mov ecx, (N-1)*2 ; Offset to the last item; Max (N-1) compares
outerLoop:
xor ebx, ebx
innerLoop:
mov ax, [esi+ebx]
mov dx, [esi+ebx+2]
cmp ax, dx
jle continue
mov [esi+ebx+2], ax
mov [esi+ebx], dx
continue:
add ebx, 2
cmp ebx, ecx
jb innerLoop
sub ecx, 2
jnz outerLoop
mov ebx, (N-1)*2
toStack:
movsx eax, word [esi+ebx]
mov [ebp+12+ebx*2], eax
sub ebx, 2
jnb toStack
ret
endp
section '.idata' import data readable writeable
library kernel32,'KERNEL32.DLL',\
user32,'USER32.DLL'
include 'C:\FASM\INCLUDE\API\kernel32.inc'
include 'C:\FASM\INCLUDE\API\user32.inc'
Error 2 revisited
IMO returning the resulting array through the stack would make better sense if your bubble procedure didn't modify the original array.
But in your present code you do, so...
Once you strike the toStack snippet from the bubble procedure, you can simply (after returning from the bubble procedure) push the word-sized elements of the array to the stack as dwords followed by using wsprintf.
...
start:
stdcall bubble, mas1
mov ebx, (N-1)*2
toStack:
movsx eax, word [mas1+ebx]
push eax
sub ebx, 2
jnb toStack
cinvoke wsprintf, buff, numStr
...
sub ecx, 2
jnz outerLoop
; See no more toStack here!
ret
endp
...

Passing string parameter to a PROC

I want to call a function that will perform upper to lower case conversion to a user typed string, preserving the especial characters. This part works, but only for the first 4 characters, everything after that just gets truncated. I believe it is because I have defined the parameters as DWORD:
I have tried using PAGE, PARA and BYTE. The first two don't work and with byte says type missmatch.
upperToLower proc, source:dword, auxtarget:dword
mov eax, source ;Point to string
mov ebx, auxtarget ; point to destination
L1:
mov dl, [eax] ; Get a character from buffer
cmp byte ptr [eax], 0 ; End of string? (not counters)
je printString ; if true, jump to printString
cmp dl, 65 ; 65 == 'A'
jl notUpper ; if less, it's not uppercase
cmp dl, 90 ; 90 == 'Z'
jg notUpper ; if greater, it's not uppercase
xor dl, 100000b ; XOR to change upper to lower
mov [ebx], dl ; add char to target
inc eax ; Move counter up
inc ebx ; move counter up
jmp L1 ; loop
notUpper: ; not uppercase
mov [ebx], dl ; copy the letter
inc eax ;next letter
inc ebx
jmp L1
printString:
invoke WriteConsoleA, consoleOutHandle, auxtarget, sizeof auxtarget, bytesWritten, 0
ret
upperToLower endp
The PROTO:
upperToLower PROTO,
source: dword,
auxtarget: dword
Invoke:
invoke upperToLower, offset buffer, offset target
The buffer parameter is: buffer db 128 DUP(?)
How can I get printed the whole string, and not just the first 4 characters?
Why are only 4 characters being printed? You write the string to the console with:
invoke WriteConsoleA, consoleOutHandle, auxtarget, sizeof auxtarget, bytesWritten, 0
The sizeof auxtarget parameter is the size of auxtarget which is a DWORD (4 bytes) thus you are asking to only print 4 bytes. You need to pass the length of the string. You can easily do so by taking the ending address in EAX and subtracting the source pointer from it. The result would be the length of the string you traversed.
Modify the code to be:
printString:
sub eax, source
invoke WriteConsoleA, consoleOutHandle, auxtarget, eax, bytesWritten, 0
A version of your code that follows the C call convention, uses both a source and destination buffer, tests for the pointers to make sure they aren't NULL, does the conversion using a similar method described by Peter Cordes is as follows:
upperToLower proc uses edi esi, source:dword, dest:dword
; uses ESI EDI is used to tell assembler we are clobbering two of
; the cdecl calling convetions non-volatile registers. See:
; https://en.wikipedia.org/wiki/X86_calling_conventions#cdecl
mov esi, source ; ESI = Pointer to string
test esi, esi ; Is source a NULL pointer?
jz done ; If it is then we are done
mov edi, dest ; EDI = Pointer to string
test edi, edi ; Is dest a NULL pointer?
jz done ; If it is then we are done
xor edx, edx ; EDX = 0 = current character index into the strings
jmp getnextchar ; Jump into loop at point of getting next character
charloop:
lea ecx, [eax - 'A'] ; cl = al-'A', and we do not care about the rest
; of the register
cmp cl, 25 ; if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 20h] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the
; uppercase range to start with
mov [edi + edx], al ; Update character in destination string
inc edx ; Go to next character
getnextchar:
movzx eax, byte ptr [esi + edx]
; mov al, [esi + edx] leaving high garbage in EAX is ok
; too, but this avoids a partial-register stall
; when doing the mov+sub
; in one instruction with LEA
test eax, eax ; Is the character NUL(0) terminator?
jnz charloop ; If not go back and process character
printString:
; EDI = source, EDX = length of string
invoke WriteConsoleA, consoleOutHandle, edi, edx, bytesWritten, 0
mov edx, sizeof buffer
done:
ret
upperToLower endp
A version that takes one parameter and changes the source string to upper case could be done this way:
upperToLower proc, source:dword
mov edx, source ; EDX = Pointer to string
test edx, edx ; Is it a NULL pointer?
jz done ; If it is then we are done
jmp getnextchar ; Jump into loop at point of getting next character
charloop:
lea ecx, [eax - 'A'] ; cl = al-'A', and we do not care about the rest
; of the register
cmp cl, 25 ; if(c >= 'A' && c <= 'Z') c += 0x20;
lea ecx, [eax + 20h] ; without affecting flags
cmovna eax, ecx ; take the +0x20 version if it was in the
; uppercase range to start with
mov [edx], al ; Update character in string
inc edx ; Go to next character
getnextchar:
movzx eax, byte ptr [edx] ; mov al, [edx] leaving high garbage in EAX is ok, too,
; but this avoids a partial-register stall
; when doing the mov+sub in one instruction with LEA
test eax, eax ; Is the character NUL(0) terminator?
jnz charloop ; If not go back and process character
printString:
sub edx, source ; EDX-source=length
invoke WriteConsoleA, consoleOutHandle, source, edx, bytesWritten, 0
done:
ret
upperToLower endp
Observations
A generic upperToLower function that does the string conversion would normally not do the printing itself. You'd normally call upperToLower to do the conversion only, then you'd output the string to the display in a separate call.

InitializeCriticalSection fails in NASM

UPDATE: based on comments below, I revised the code below to add a struc and a pointer (new or revised code has "THIS IS NEW" or "THIS IS UPDATED" beside the code). Now the program does not crash, so the pointer is initialized, but the programs hangs at EnterCriticalSection. I suspect that in translating the sample MASM code below into NASM syntax, I did not declare the struc correctly. Any ideas? Thanks very much.
ORIGINAL QUESTION:
Below is a simple test program in 64-bit NASM, to test a critical section in
Windows. This is a dll and the entry point is Main_Entry_fn, which calls Init_Cores_fn, where we initialize four threads (cores) to call Test_fn.
I suspect that the problem is the pointer to the critical section. None of the online resources specifies what that pointer is. The doc "Using Critical Section Objects" at https://learn.microsoft.com/en-us/windows/desktop/sync/using-critical-section-objects shows a C++ example where the pointer appears to be relevant only to EnterCriticalSection and LeaveCriticalSection, but it's not a pointer to an independent object.
For those not familiar with NASM, the first parameter in a C++ signature goes into rcx and the second parameter goes into rds, but otherwise it should function the same as in C or C++. It's the same thing as InitializeCriticalSectionAndSpinCount(&CriticalSection,0x00000400) in C++.
Here's the entire program:
; Header Section
[BITS 64]
[default rel]
extern malloc, calloc, realloc, free
global Main_Entry_fn
export Main_Entry_fn
extern CreateThread, CloseHandle, ExitThread
extern WaitForMultipleObjects, GetCurrentThreadId
extern InitializeCriticalSectionAndSpinCount, EnterCriticalSection
extern LeaveCriticalSection, DeleteCriticalSection, InitializeCriticalSection
struc CRITICAL_SECTION ; THIS IS NEW
.cs_quad: resq 5
endstruc
section .data align=16
const_1000000000: dq 1000000000
ThreadID: dq 0
TestInfo: times 20 dq 0
ThreadInfo: times 3 dq 0
ThreadInfo2: times 3 dq 0
ThreadInfo3: times 3 dq 0
ThreadInfo4: times 3 dq 0
ThreadHandles: times 4 dq 0
Division_Size: dq 0
Start_Byte: dq 0
End_Byte: dq 0
Return_Data_Array: times 4 dq 0
Core_Number: dq 0
const_inf: dq 0xFFFFFFFF
SpinCount: dq 0x00000400
CriticalSection: ; THIS IS NEW
istruc CRITICAL_SECTION
iend
section .text
; ______________________________________
Init_Cores_fn:
; Calculate the data divisions
mov rax,[const_1000000000]
mov rbx,4 ;cores
xor rdx,rdx
div rbx
mov [End_Byte],rax
mov [Division_Size],rax
mov rax,0
mov [Start_Byte],rax
; Populate the ThreadInfo arrays to pass for each core
; ThreadInfo: (1) startbyte; (2) endbyte; (3) Core_Number
mov rdi,ThreadInfo
mov rax,[Start_Byte]
mov [rdi],rax
mov rax,[End_Byte]
mov [rdi+8],rax
mov rax,[Core_Number]
mov [rdi+16],rax
call DupThreadInfo ; Create ThreadInfo arrays for cores 2-4
mov rbp,rsp ; preserve caller's stack frame
sub rsp,56 ; Shadow space (was 32)
; _____
; Create four threads
label_0:
mov rax,[Core_Number]
cmp rax,0
jne sb2
mov rdi,ThreadInfo
jmp sb5
sb2:cmp rax,8
jne sb3
mov rdi,ThreadInfo2
jmp sb5
sb3:cmp rax,16
jne sb4
mov rdi,ThreadInfo3
jmp sb5
sb4:cmp rax,24
jne sb5
mov rdi,ThreadInfo4
sb5:
; _____
; Create Threads
mov rcx,0 ; lpThreadAttributes (Security Attributes)
mov rdx,0 ; dwStackSize
mov r8,Test_fn ; lpStartAddress (function pointer)
mov r9,rdi ; lpParameter (array of data passed to each core)
mov rax,0
mov [rsp+32],rax ; use default creation flags
mov rdi,ThreadID
mov [rsp+40],rdi ; ThreadID
call CreateThread
; Move the handle into ThreadHandles array (returned in rax)
mov rdi,ThreadHandles
mov rcx,[Core_Number]
mov [rdi+rcx],rax
mov rdi,TestInfo
mov [rdi+rcx],rax
mov rax,[Core_Number]
add rax,8
mov [Core_Number],rax
mov rbx,32 ; Four cores
cmp rax,rbx
jl label_0
mov rcx,CriticalSection ; THIS IS REVISED
mov rdx,[SpinCount]
call InitializeCriticalSectionAndSpinCount
; _____
; Wait
mov rcx,4 ;rax ; number of handles
mov rdx,ThreadHandles ; pointer to handles array
mov r8,1 ; wait for all threads to complete
mov r9,[const_inf] ;4294967295 ;0xFFFFFFFF
call WaitForMultipleObjects
; _____
mov rsp,rbp ; can we push rbp so we can use it internally?
jmp label_900
; ______________________________________
Test_fn:
mov rdi,rcx
mov r14,[rdi] ; Start_Byte
mov r15,[rdi+8] ; End_Byte
mov r13,[rdi+16] ; Core_Number
;______
; while(n < 1000000000)
label_401:
cmp r14,r15
jge label_899
mov rcx,CriticalSection
call EnterCriticalSection
; n += 1
add r14,1
mov rcx,CriticalSection
call LeaveCriticalSection
jmp label_401
;______
label_899:
mov rdi,Return_Data_Array
mov [rdi+r13],r14
mov rbp,ThreadHandles
mov rax,[rbp+r13]
call ExitThread
ret
; __________
label_900:
mov rcx,CriticalSection
call DeleteCriticalSection
mov rdi,Return_Data_Array
mov rax,rdi
ret
; __________
; Main Entry
Main_Entry_fn:
push rdi
push rbp
call Init_Cores_fn
pop rbp
pop rdi
ret
DupThreadInfo:
mov rdi,ThreadInfo2
mov rax,8
mov [rdi+16],rax ; Core Number
mov rax,[Start_Byte]
add rax,[Division_Size]
mov [rdi],rax
mov rax,[End_Byte]
add rax,[Division_Size]
mov [rdi+8],rax
mov [Start_Byte],rax
mov rdi,ThreadInfo3
mov rax,16
mov [rdi+16],rax ; Core Number
mov rax,[Start_Byte]
mov [rdi],rax
add rax,[Division_Size]
mov [rdi+8],rax
mov [Start_Byte],rax
mov rdi,ThreadInfo4
mov rax,24
mov [rdi+16],rax ; Core Number
mov rax,[Start_Byte]
mov [rdi],rax
add rax,[Division_Size]
mov [rdi+8],rax
mov [Start_Byte],rax
ret
The code above shows the functions in three separate places, but of course we test them one at a time (but they all fail).
To summarize, my question is why do InitializeCriticalSection and InitializeCriticalSectionAndSpinCount both fail in the code above? The inputs are dead simple, so I don't understand why it should not work.
InitializeCriticalSection take pointer to critical section object
The process is responsible for allocating the memory used by a
critical section object, which it can do by declaring a variable of
type CRITICAL_SECTION.
so code can be something like (i use masm syntax)
CRITICAL_SECTION STRUCT
DQ 5 DUP(?)
CRITICAL_SECTION ends
extern __imp_InitializeCriticalSection:QWORD
extern __imp_InitializeCriticalSectionAndSpinCount:QWORD
.DATA?
CriticalSection CRITICAL_SECTION {}
.CODE
lea rcx,CriticalSection
;mov edx,400h
;call __imp_InitializeCriticalSectionAndSpinCount
call __imp_InitializeCriticalSection
also you need declare all imported functions as
extern __imp_funcname:QWORD
instead
extern funcname

ESI and EDI change values after function call

I'm trying to convert some strings representing binary numbers into their actual values, using a conversion function defined in a different file.
Here's my code:
main.asm
bits 32
global start
%include 'convert.asm'
extern exit, scanf, printf
import exit msvcrt.dll
import scanf msvcrt.dll
import printf msvcrt.dll
section data use32 class=data
s DB '10100111b', '01100011b', '110b', '101011b'
len EQU $ - s
res times len DB 0
segment code use32 class=code
start:
mov ESI, s ; move source string
mov EDI, res ; move destination string
mov ECX, len ; length of the string
mov EBX, 0
repeat:
lodsb ; load current byte into AL
inc BL
cmp AL, 'b' ; check if its equal to the character b
jne end ; if its not, we need to keep parsing
push dword ESI ; push the position of the current character in the source string to the stack
push dword EDI ; push the position of the current character in the destination string to the stack
push dword EBX ; push the current length to the stack
call func1 ; call the function
end:
loop repeat
push dword 0
call [exit]
convert.asm
func1:
mov ECX, [ESP] ; first parameter is the current parsed length
mov EDI, [ESP + 4] ; then EDI
mov ESI, [ESP + 8] ; and ESI
sub ESI, ECX
parse:
mov EDX, [ESI]
sub EDX, '0'
mov [EDI], EDX
shl dword [EDI], 1
inc ESI
loop parse
ret 4 * 3
I noticed that I keep getting access violation errors after the function call though. ESI has some random value after the call. Am I doing something wrong? I think the parameter pushing part should be alright. Inside the conversion function, the parameters should be accessed in the reverse order. But that's not happening for some reason.
I'm also pretty sure that I did the compiling/linking part alright using nasm and alink.
nasm -fobj main.asm
nasm -fobj convert.asm
alink main.obj convert.obj -oPE -subsys console -entry start

fatal error LNK1120: 1 unresolved externals for assembly language using VS2010

Comment #
Using Programming Exercise 6 in Chapter 4 as a starting point,
write a program that generates the first 47 values in the Fibonacci
series, stores them in an array of doublewords, and writes the
doubleword array to a disk file.
#
INCLUDE c:\Irvine\Irvine32.inc
FIB_COUNT = 47 ; number of values to generate
.data
fileHandle DWORD ?
filename BYTE "fibonacci.bin",0
array DWORD FIB_COUNT DUP(?)
.code
main2sub PROC
; Generate the array of values
mov esi, OFFSET array
mov ecx, LENGTHOF array
call generate_fibonacci
; Create the file, call CreateOutputFile
mov edx,OFFSET filename
call CreateOutputFile
mov fileHandle, eax
; Write the array to the file, call WriteToFile
mov eax, fileHandle
mov edx, OFFSET array
mov ecx, FIB_COUNT * 4
call WriteToFile
; Close the file, call CloseFile
mov eax, fileHandle
call CloseFile
exit
main2sub ENDP
;------------------------------------------------------------
generate_fibonacci PROC USES eax ebx ecx edx
;
; Generates fibonacci values and stores in an array.
; Receives: ESI points to the array,
; ECX = count
; Returns: nothing
;------------------------------------------------------------
mov ebx, 1
mov ecx, 0
L1:
add ebx, ecx
mov [esi], eax
call WriteDec
call Crlf
inc esi
xchg ebx, eax
loop L1 ;end of looping
ret
generate_fibonacci ENDP
END main2sub
It is my first semester learning about Assembly Language. Im not sure what to do with this error message "fatal error LNK1120: 1 unresolved externals". Can anyone help me out?
The linker couldn't find the library-files Irvine32.lib, Kernel32.Lib and/or User32.Lib. They are in the same folder as Irvine32.inc
The easiest way is to inform ML about those libraries with a MASM-directive. Insert following three lines right behind the INCLUDE c:\Irvine\Irvine32.inc:
INCLUDELIB c:\Irvine\Irvine32.lib
INCLUDELIB c:\Irvine\Kernel32.lib
INCLUDELIB c:\Irvine\User32.lib

Resources