gcc 无法使用link.exe链接ml64编译的程序集文件

bt1cpqcv  于 2023-05-07  发布在  其他
关注(0)|答案(1)|浏览(168)

问题!

我已经开始学习汇编语言,在编译程序时遇到了一些困难.

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

我已经成功地使用以下命令将我的程序集文件编译为目标文件。

ml64 /c main.asm

当我尝试使用以下命令编译结果main.obj时,出现了问题。

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

printf符号丢失。所以我做了一些研究,发现我需要链接kernel32.liblegacy_stdio_definitions.libmsvcrt.lib才能让这个工作。所以我也链接了这些文件,但随后得到了以下错误。

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

所以我也包含了这个文件,并运行了以下命令。

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"

现在我得到了以下错误,我目前的理论是,我需要链接更多的文件,但不能找出哪些。

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

在这一点上,我放弃了link.exe,并使用gcc来编译我的代码,它成功编译!

gcc main.obj -o main.exe

我的问题

  • 如何使用link.exe链接目标文件。

1.为了找出解决方案,我遇到了以下编译器和链接器,其中一些我有点熟悉,其中一些对我来说是新发现。

  1. ml64
  2. link
  3. gcc
  4. ld
  5. cl
    其中一些是微软的编译器和链接器,一些是gnu编译器和链接器。这些编译器和链接器之间有什么区别?我应该在什么时候使用哪些?(如果需要,我使用Windows AMD机器)
    1.我在这里提供的汇编代码是我从一本书中复制的,但做了一些修改。在那本书中,作者链接了一个汇编编译的.obj文件和一个C编译的.obj文件,并调用了assembly中定义的C中的函数来在控制台中编写Hello, World!。我在这里做了一些修改,使其成为一个独立的assembly程序,但仍然不理解该程序,例如,subadd指令的用途是什么。因此,对该计划的简要说明将不胜感激。
    1.我注意到在link中有一个entry标志。我现在的假设是,它指定了程序/指令开始的位置。因此,如果我将汇编代码中的所有main关键字替换为start,并使用/entry:start作为其中一个标志,程序是否会编译并按预期工作?(假设我首先设法使用link编译了程序)
js4nwp54

js4nwp541#

printf / wprintf-这是CRT的一部分-为了使用它-需要做一些额外的步骤。这里可以有3个解决方案:
1.完全不使用printf/wprintf和CRT。只使用win32 API使用WriteConsoleW。ASM代码可以是

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

编译它:

ml64 /c /Cp /Zp16 main.asm

并构建:

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

注意这里只使用了2个库-- kernel32.Lib和ucrt.lib(对于_getch)。最终的exe大小接近2.5Kb调用ExitProcess是强制性的-否则您的exe根本不会终止或在30秒后终止。exe入口点可以具有任何名称。
2.
如果我们想使用CRT -它必须被初始化。在这种情况下,exe入口点必须是[w]mainCRTStartup,或者代码必须具有proc [w]main。它将从[w]mainCRTStartup调用
asm码

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

注意,这里我们不能调用ExitProcess-我们返回到CRT代码,它自己调用它。我们现在需要设置的链接

/entry:wmainCRTStartup

和库列表:

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

源代码变得越来越简单,与第一个变体相比,但二进制exe的大小不超过9 Kb(与第一个变体相比为2.5Kb)
3.部分使用CRT(最复杂的变体)。使用wprintf等CRT函数,但可自行初始化CRT

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

这里发生了什么变化?首先,我们再次将入口点设置为self code:

/entry:AnyName

我们再次需要直接调用ExitProcess。库列表变小了

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

我们可以删除vcruntime.libmsvcrt.lib-它们在这里未使用
需要为链接器添加选项

/merge:.CRT=.rdata

问题-此选项的用途以及对_initterm.CRT$XIA.CRT$XIZ等的调用?
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(通常此文件位于 * \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")

当链接器查看.CRT*部分时,它总是生成警告:

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

当我们像往常一样使用CRT时-事实上包含了initializers.cpp(通过ob/lib),并且#pragma comment(linker, "/merge:.CRT=.rdata")删除了.CRT部分。如果我们不使用CRT -需要自己做。不仅合并节,而且调用所有函数指针-通过调用_initterm
如果使用/map选项,则可以查看符号
__PLEASE_LINK_WITH_legacy_stdio_wide_specifiers.lib(是的,就是这个名字)。它来自legacy_stdio_wide_specifiers.lib,位于.CRT$XIC截面。所以这是函数指针,需要调用它。如果你感兴趣的是这个符号是如何生成的,以及哪个代码将被调用,请查找\Community\VC\Tools\MSVC\<version>\crt\src\linkopts\legacy_stdio_wide_specifiers.cpp
它片段:

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;

3种情况下的exe最终大小将接近3.5Kb

相关问题