1

The issue!

I have started learning assembly language and am facing some difficulty compiling my program.

option casemap:none
.data
fmtStr byte 'Hello, world!', 10, 0

.code
externdef printf:proc

main proc
    sub rsp, 56
    lea rcx, fmtStr
    call printf
    add rsp, 56

    ret
main endp
    end

I have successfully compiled my assembly file to object file using the following command.

ml64 /c main.asm

The problem arises when I try to compile the resulting main.obj using the following command.

link main.obj /subsystem:console /entry:main /out:main.exe

The printf symbol was missing. So I did some research and found that I need to link kernel32.lib, legacy_stdio_definitions.lib and msvcrt.lib to get this to work. So I linked those files as well but then got the following error.

LINK : fatal error LNK1104: cannot open file 'legacy_stdio_wide_specifiers.lib'

So I included that file as well and ran the following command.

link main.obj /subsystem:console /entry:main /out:main.exe "C:\Program Files (x86)\Windows Kits\10\Lib\10.0.19041.0\um\x64\kernel32.Lib" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64\legacy_stdio_definitions.lib" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64\legacy_stdio_wide_specifiers.lib" "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\lib\x64\msvcrt.lib"

Now I am getting the following errors and my current theory is that I need to link more files but can't figure out which ones.

Microsoft (R) Incremental Linker Version 14.29.30148.0
Copyright (C) Microsoft Corporation.  All rights reserved.

legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __acrt_iob_func referenced in function _vwprintf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfwprintf referenced in function _vfwprintf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfwprintf_s referenced in function _vfwprintf_s_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfwprintf_p referenced in function _vfwprintf_p_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfwscanf referenced in function _vfwscanf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vswprintf referenced in function _vsnwprintf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vswprintf_s referenced in function _vswprintf_s_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vsnwprintf_s referenced in function _vsnwprintf_s_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vswprintf_p referenced in function _vswprintf_p_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vswscanf referenced in function _vswscanf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfprintf referenced in function _vfprintf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfprintf_s referenced in function _vfprintf_s_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfprintf_p referenced in function _vfprintf_p_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vfscanf referenced in function _vfscanf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vsprintf referenced in function _vsnprintf_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vsprintf_s referenced in function _vsprintf_s_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vsnprintf_s referenced in function _vsnprintf_s_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vsprintf_p referenced in function _vsprintf_p_l
legacy_stdio_definitions.lib(legacy_stdio_definitions.obj) : error LNK2019: unresolved external symbol __stdio_common_vsscanf referenced in function _vsscanf_l
main.exe : fatal error LNK1120: 19 unresolved externals

At this point I ditched link.exe and use gcc to compile my code and it successfully compiled!

gcc main.obj -o main.exe

My questions

  • What can I do to link my object files using link.exe.

  • Trying to figure out a solution I came across the following compilers and linkers, some of them I was a little familiar with, and some of them were a new discovery to me.

    1. ml64
    2. link
    3. gcc
    4. ld
    5. cl

    Some of them are microsoft compilers and linkers some are gnu compilers and linkers. What are the differences between these compilers and linkers and when should I use which ones? (I use a windows amd machine if that's needed)

  • The assembly code that I have provided here is the one that I copied from a book with some changes. In that book the author was linking an assembly compiled .obj file and a C compiled .obj and was calling the function in C defined in assembly to write Hello, World! in the console. I made some changes here to make this a standalone assembly program but still don't understand the program properly, e.g., what is the use of sub and add instructions. So a brief explanation of the program will be appreciated.

  • I noticed that there is an entry flag available to us in link. My assumption for now is that is specifies where the program/instructions begin. So, if I replaced all the main keywords in the assembly code with let's say start and used /entry:start as one of the flags, will the program compile and work as intended? (assuming that I managed to compile the program using link in the first place)

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Sanmay Kant
  • 149
  • 8
  • you need add *ucrt.lib* – RbMm May 05 '23 at 06:36
  • It worked but now is giving the following warning `legacy_stdio_wide_specifiers.lib(legacy_stdio_wide_specifiers.obj) : warning LNK4210: .CRT section exists; there may be unhandled static initializers or terminators` – Sanmay Kant May 05 '23 at 09:56
  • yes, correct. because you need `/merge:.CRT=.rdata` and call `_initterm` – RbMm May 05 '23 at 11:54
  • adding the merge flag gets rid of compile warning, but when I call `_initterm` in the program, `"Hello, World!"` stops printing. Just using the `merge:.CRT=.rdata` works. Can you explain what exactly the flag does? – Sanmay Kant May 06 '23 at 00:11

1 Answers1

1

the printf / wprintf - this is part of CRT - and for use it - need do some additional steps. here can be 3 solutions:

1. not use printf / wprintf and CRT at all. use only win32 api. use WriteConsoleW. the asm code can be

extern __imp__getch:qword
extern __imp_ExitProcess:qword
extern __imp_GetStdHandle:qword
extern __imp_WriteConsoleW:qword
    
.const

;; Hello, world!

msg@@Len = 0eH
ALIGN 2
msg:
DW 0048h, 0065h, 006Ch, 006Ch, 006Fh, 002Ch, 0020h, 0077h
DW 006Fh, 0072h, 006Ch, 0064h, 0021h, 000Ah

.code
STD_OUTPUT_HANDLE = -11

AnyName proc
    sub rsp, 28h
    
    mov rcx, STD_OUTPUT_HANDLE 
    call __imp_GetStdHandle
    mov rcx, rax
    lea rdx, msg
    mov r8, msg@@Len
    lea r9, [rsp + 20h]
    mov qword ptr[rsp + 20h], 0
    call __imp_WriteConsoleW
   
    call __imp__getch
    
    call __imp_ExitProcess
    
    add rsp, 28h
    ret
AnyName endp

end

compile it:

ml64 /c /Cp /Zp16 main.asm

and build:

link main.obj 
/subsystem:console 
/entry:AnyName 
/out:main.exe 
/map 
/MANIFEST:NO 
/NOLOGO 
/NODEFAULTLIB 
/DEBUG 
/OPT:REF 
/OPT:ICF 
/MACHINE:X64 
/EMITPOGOPHASEINFO 
/LIBPATH:"..." 
kernel32.Lib ucrt.lib > build_log.txt

note that here used only 2 lib - kernel32.Lib and ucrt.lib (for _getch ). final exe size is near 2.5Kb call ExitProcess is mandatory - otherwise your exe not terminate at all or terminate say after 30 seconds. the exe entry point can have anyname.

2.

if we want use CRT - it must be initialized. the exe entry point must be [w]mainCRTStartup in this case and or code must have proc [w]main. it will be called from [w]mainCRTStartup

asm code

extern __imp__getch:qword
extern __imp_wprintf:qword
    
.const

;; Hello, world!

msg@@Len = 0eH
ALIGN 2
msg:
DW 0048h, 0065h, 006Ch, 006Ch, 006Fh, 002Ch, 0020h, 0077h
DW 006Fh, 0072h, 006Ch, 0064h, 0021h, 000Ah

.code

wmain proc
    sub rsp, 28h
    
    lea rcx, msg
    call __imp_wprintf
   
    call __imp__getch
        
    add rsp, 28h
    ret
wmain endp

end

note that here we must not call ExitProcess - we return to CRT code, which by self call it. for link we need now set

/entry:wmainCRTStartup

and list of libs:

kernel32.Lib 
ucrt.lib 
vcruntime.lib 
msvcrt.lib 
legacy_stdio_definitions.lib 
legacy_stdio_wide_specifiers.lib

source code became less and more simply compare first variant, but binary exe not take 9Kb+ in size (compare 2.5Kb infirst variant)

3. partialy use CRT (most complex variant). use CRT functions like wprintf but initialize CRT by self

OPTION DOTNAME

extern __imp__getch:qword
extern __imp_wprintf:qword
extern __imp__initterm:qword
extern __imp_ExitProcess:qword
    
.CRT$XIA SEGMENT READONLY PARA 'CONST'
__xi_a DQ 0
.CRT$XIA ENDS

.CRT$XIZ SEGMENT READONLY PARA 'CONST'
__xi_z DQ 0
.CRT$XIZ ENDS

.const

;; Hello, world!

msg@@Len = 0eH
ALIGN 2
msg:
DW 0048h, 0065h, 006Ch, 006Ch, 006Fh, 002Ch, 0020h, 0077h
DW 006Fh, 0072h, 006Ch, 0064h, 0021h, 000Ah

.code

AnyName proc
    sub rsp, 28h
    
    lea rcx, __xi_a
    lea rdx, __xi_z
    call __imp__initterm
    
    lea rcx, msg
    call __imp_wprintf
   
    call __imp__getch
    
    call __imp_ExitProcess
        
    add rsp, 28h
    ret
AnyName endp

end

what here is changed ? at first we again set entry point to self code:

/entry:AnyName

we again need direct call ExitProcess. the list of libs became smaller

kernel32.Lib 
ucrt.lib 
legacy_stdio_definitions.lib 
legacy_stdio_wide_specifiers.lib 

we can remove vcruntime.lib and msvcrt.lib - they unused here

and need add option for linker

/merge:.CRT=.rdata

question - for what this option and for what call to _initterm , .CRT$XIA, .CRT$XIZ, etc ?

When Microsoft C/C++ compiler sees a global initializer, it generates a dynamic initializer and place function pointers to it in special sections. names of this section you can view in initializers.cpp (usually this file is located in \Community\VC\Tools\MSVC\<version>\crt\src\vcruntime\initializers.cpp ) fragment fom it:

extern "C" _CRTALLOC(".CRT$XIA") _PIFV __xi_a[] = { nullptr }; // C initializers (first)
extern "C" _CRTALLOC(".CRT$XIZ") _PIFV __xi_z[] = { nullptr }; // C initializers (last)
extern "C" _CRTALLOC(".CRT$XCA") _PVFV __xc_a[] = { nullptr }; // C++ initializers (first)
extern "C" _CRTALLOC(".CRT$XCZ") _PVFV __xc_z[] = { nullptr }; // C++ initializers (last)
extern "C" _CRTALLOC(".CRT$XPA") _PVFV __xp_a[] = { nullptr }; // C pre-terminators (first)
extern "C" _CRTALLOC(".CRT$XPZ") _PVFV __xp_z[] = { nullptr }; // C pre-terminators (last)
extern "C" _CRTALLOC(".CRT$XTA") _PVFV __xt_a[] = { nullptr }; // C terminators (first)
extern "C" _CRTALLOC(".CRT$XTZ") _PVFV __xt_z[] = { nullptr }; // C terminators (last)

#pragma comment(linker, "/merge:.CRT=.rdata")

when linker view .CRT* section it always generate warning:

LNK4210: .CRT section exists; there may be unhandled static initializers or terminators

when we use CRT as usual - the initializers.cpp by fact is included (via ob/lib) and #pragma comment(linker, "/merge:.CRT=.rdata") remove .CRT section. if we not use CRT - need by self do this. not only merge sections, but call all function pointers - by call _initterm

if we use /map option we can view symbol

__PLEASE_LINK_WITH_legacy_stdio_wide_specifiers.lib (yes, exactly with such name). it come from legacy_stdio_wide_specifiers.lib and located in .CRT$XIC section. so this is function pointer and need call it. if interesting how this symbol is generated and which code will be called - look for \Community\VC\Tools\MSVC\<version>\crt\src\linkopts\legacy_stdio_wide_specifiers.cpp

it fragment:

static int __CRTDECL initialize_legacy_wide_specifiers() noexcept
{
    _CRT_INTERNAL_LOCAL_PRINTF_OPTIONS |= _CRT_INTERNAL_PRINTF_LEGACY_WIDE_SPECIFIERS;
    _CRT_INTERNAL_LOCAL_SCANF_OPTIONS  |= _CRT_INTERNAL_SCANF_LEGACY_WIDE_SPECIFIERS;
    return 0;
}

    #pragma warning(disable: 4483) // Allow use of __identifier
    extern "C" _CRTALLOC(".CRT$XIC") _PIFV __identifier("__PLEASE_LINK_WITH_legacy_stdio_wide_specifiers.lib") = initialize_legacy_wide_specifiers;

final size of exe in 3 case will be near 3.5Kb

RbMm
  • 31,280
  • 3
  • 35
  • 56