I am struggling to get printf to work for me in NASM while linking to ucrt.dll, It works 0 problems when I link to msvcrt.dll for printf, but I am trying to practice with something a little newer. I have tried linking to ucrtbase.dll, made no difference
This is my prompts: (again, linking to msvcrt.dll works perfectly, so im not sure why this wont work, should be more or less the same thing, no?)
nasm -f win64 src.asm -o src.obj
golink /console src.obj /entry main ucrt.dll stdio_definitions.dll kernel32.dll
and this my assembly right now:
bits 64
default rel
segment .rdata
str_0 db `Hello world!\n`, 0
segment .data
num dd 0
segment .bss
segment .text
global main
extern ExitProcess
extern printf
main:
push rbp
mov rbp, rsp
sub rsp, 32
mov RCX, str_0
call printf
mov RCX, [num]
call ExitProcess
mov rsp, rbp
pop rbp
ret
The primary issue here is the same as encountered in this posting:
Unresolved external symbol printf in Windows x64 Assembly Programming with NASM
[ Aside for someone with more SO experience: I do not know how to mark as potential duplicate ]
Take a look at the comments and accepted answer (from the poster) as the combination of them provides the relevant information.
However, there is an issue in the accepted answer in that the code contains:
xor rax, rax
call ExitProcess
Presumably this is intending to return 0 (zero) to Windows. However, the x64 calling convention dictates that the first 4 parameters are passed in RCX, RDX, R8, and R9 (amongst other requirements). In addition, the Windows ExitProcess function takes a UINT value which is a 32-bit unsigned value (see: UINT / INT).
So, the above should more properly be:
xor ecx, xcx
call ExitProcess
As part of the Microsoft C/C++ change history 2003 - 2015 you can see that printf along with other functions was made inline:
<stdio.h>and<conio.h>
The printf and scanf family of functions are now defined inline.
The definitions of all of the
printfandscanffunctions have been moved inline into<stdio.h>,<conio.h>, and other CRT headers. This breaking change leads to a linker error (LNK2019, unresolved external symbol) for any programs that declared these functions locally without including the appropriate CRT headers. If possible, you should update the code to include the CRT headers (that is, add#include <stdio.h>) and the inline functions, but if you do not want to modify your code to include these header files, an alternative solution is to addlegacy_stdio_definitions.libto your linker input.
As noted in the Microsoft change documentation as well as the above linked StackOverflow question you should link against legacy_stdio_definitions.lib for printf.
However, it looks like you are using a linker called GoLink. I am not familiar with that linker but a quick review of the linked paged and I see:
"I've kept files to a minimum and abolished LIB files."
Given that, I do not know if you will be able to use that linker with legacy_stdio_definitions.lib for printf.
Two comments about your code snippet which may be worthwhile:
As I noted above in reference to the sample code from the accepted answer in the potential duplicate, ExitProcess takes a UINT parameter so you can use the 32-bit ECX register rather than the 64-bit RCX register. The xor ecx, ecx is a common way to "zero a register" rather than using a (less efficient) move instruction. Though if you are just experimenting with different options and exploring it is not a critical issue.
Because no code following the ExitProcess call will execute you do not need the epilogue and ret code.
Edit to add a bit more information:
The point noted by "ecm" in the comments about zeroing the entire register is a good one and I failed to mention it above.
I did some slight reworking to your original:
bits 64
default rel
extern printf
extern ExitProcess
segment .rdata
str_0 db "Hello world!", 0x0D, 0x0A, 0x00
num dd 46
segment .text
global main
main:
push rbp
mov rbp, rsp
sub rsp, 32
lea rcx, [str_0]
call printf
mov ecx, [num]
call ExitProcess
You'll see that I folded "num" into the .rdata segment and changed the value to non-zero to easily see that it is the value being returned to Windows (see more below).
I changed "str_0" to include the Windows CRLF sequence. A small aside: in this case rather than using printf, puts would be satisfactory (and likely what a C compiler would substitute given the current code).
I also changed the move of "str_0" to an lea (load effective address) for a bit more efficiency.
Again, this is likely experimental on your part so things are not required to be ideal or perfect. But a couple of small tweaks might be beneficial without clouding the issue (too much).
For reference here is how I compiled, linked, and executed the code:
nasm -g -f win64 src.asm -o src.obj
link /debug /subsystem:console /machine:x64 /out:hello.exe src.obj msvcrt.lib legacy_stdio_definitions.lib kernel32.lib
$ .\hello.exe
Hello world!
$ echo %errorlevel%
46
$
NOTE: I have set my Windows command prompt value to be a dollar sign ($) for conciseness (this is all done in a "x64 Native Tools Command Prompt for VS 2022" window - I have Visual Studio 2022 installed in my environment and am using "Microsoft (R) Incremental Linker Version 14.44.35219.0" here).
As you can see in the above the expected return value (46 in my sample) was returned from the executable to Windows (i.e., from the echo %errorlevel%).
So, let's try to walk how we go from extern printf in the assembly source to what eventually is invoked in UCRT (which was one of your goals) even though I have not directly linked against ucrt.lib in my sample.
As mentioned earlier Microsoft have refactored things from Visual Studio 2015 and while it may be typical for C code to have printf (and related items) inline, we clearly are not doing that here. Again, based on my Visual Studio 2022 installation.
In stdio.h we see where "plain" printf is defined:
_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL printf(
_In_z_ _Printf_format_string_ char const* const _Format,
...)
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
int _Result;
va_list _ArgList;
__crt_va_start(_ArgList, _Format);
_Result = _vfprintf_l(stdout, _Format, NULL, _ArgList);
__crt_va_end(_ArgList);
return _Result;
}
#endif
From this we can gather that our next stop is _vfprintf_l and that is defined thusly:
_Check_return_opt_
_CRT_STDIO_INLINE int __CRTDECL _vfprintf_l(
_Inout_ FILE* const _Stream,
_In_z_ char const* const _Format,
_In_opt_ _locale_t const _Locale,
va_list _ArgList
)
#if defined _NO_CRT_STDIO_INLINE
;
#else
{
return __stdio_common_vfprintf(_CRT_INTERNAL_LOCAL_PRINTF_OPTIONS, _Stream, _Format, _Locale, _ArgList);
}
#endif
From here we see the next stop __stdio_common_vfprintf which has the following prototype:
_ACRTIMP int __cdecl __stdio_common_vfprintf(
_In_ unsigned __int64 _Options,
_Inout_ FILE* _Stream,
_In_z_ _Printf_format_string_params_(2) char const* _Format,
_In_opt_ _locale_t _Locale,
va_list _ArgList
);
So, from starting with the common printf we've now landed on the function that is included in the little test executable (__stdio_common_vfprintf) and we can see it is part of the imports:
$ link /dump /imports hello.exe
[ snip ]
api-ms-win-crt-stdio-l1-1-0.dll
1400123B8 Import Address Table
140012930 Import Name Table
0 time date stamp
0 Index of first forwarder reference
8 __stdio_common_vfwprintf_p
A __stdio_common_vfwscanf
11 __stdio_common_vswprintf
13 __stdio_common_vswprintf_s
C __stdio_common_vsnwprintf_s
12 __stdio_common_vswprintf_p
14 __stdio_common_vswscanf
9 __stdio_common_vfwprintf_s
5 __stdio_common_vfprintf_s
4 __stdio_common_vfprintf_p
6 __stdio_common_vfscanf
D __stdio_common_vsprintf
F __stdio_common_vsprintf_s
B __stdio_common_vsnprintf_s
E __stdio_common_vsprintf_p
10 __stdio_common_vsscanf
3 __stdio_common_vfprintf <== here it is
0 __acrt_iob_func
54 _set_fmode
1 __p__commode
7 __stdio_common_vfwprintf
[ snip ]
As part of the Visual Studio 2015 / UCRT refactor Microsoft introduced "API Sets" and, as I understand it, api-ms-win-crt-stdio-l1-1-0.dll is acting in the capacity of a bridge / forwarder between legacy routines like "plain" printf and the ultimate implementation.
For example, looking at api-ms-win-crt-stdio-l1-1-0.dll we see:
$ link /dump /exports api-ms-win-crt-stdio-l1-1-0.dll
[ snip / extract ]
__stdio_common_vfprintf (forwarded to ucrtbase.__stdio_common_vfprintf)
[ snip ]
aha! finally! here we can see that this is forwarding to a destination within the UCRT DLL:
__stdio_common_vfprintf in ucrtbase as you were hoping to achieve.
By linking against legacy_stdio_definitions.lib (as well as msvcrt.lib and kernel32.lib) we've achieved the goal of calling printf and, ultimately, using UCRT.