【问题标题】:Is this a defect in the `/SUBSYSTEM (Specify subsystem)`, a MS document?这是 MS 文档“/SUBSYSTEM(指定子系统)”中的缺陷吗?
【发布时间】:2019-12-07 20:28:01
【问题描述】:

/SUBSYSTEM = CONSOLE 链接器选项在此 MS documentCONSOLE 部分下进行了描述,如下所示:

Win32 字符模式应用程序。操作系统提供了一个 控制台应用程序的控制台。如果 mainwmain 被定义为 本机代码,int main(array<String ^> ^) 是为托管定义的 代码,或者您使用/clr:safe 完全构建应用程序, CONSOLE 是默认值。

因此,我得出结论,默认情况下使用CONSOLE 构建不提供/SUBSYSTEM,但提供/ENTRY = mainwmain 的程序集(本机代码)。我可以通过下面的代码确认这一点,

.386
.MODEL flat, stdcall
.stack 4096

ExitProcess PROTO STDCALL, dwExitCode: DWORD

.CODE
main PROC
    mov eax, -1
    invoke ExitProcess, 0
main ENDP

END main

根据 PE 格式文档中的this table,通过对相应的可执行文件运行 DUMPBIN 来获得此输出,这证实了上面的段落。

同样适用于 /SUBSYSTEM = WINDOWS,如 WINDOWS 部分所述,如下所示:

应用程序不需要控制台,可能是因为它创建 它自己的窗口用于与用户交互。如果WinMainwWinMain 定义为本机代码,或 WinMain(HISTANCE *, HINSTANCE *, char *, int)wWinMain(HINSTANCE *, HINSTANCE *, wchar_t *, int) 是为托管代码定义的,WINDOWS 是 默认。

但是,当我尝试在没有 /SUBSYSTEM 选项但使用 /ENTRY = WinMain 的情况下构建下面的汇编代码时,链接器会发出以下错误:LNK1221: a subsystem can't be inferred and must be defined

.386
.MODEL flat, stdcall
.stack 4096

ExitProcess PROTO STDCALL, dwExitCode: DWORD

.CODE
WinMain PROC
    mov eax, -1
    invoke ExitProcess, 0
WinMain ENDP

END WinMain

然后我必须得出结论,暗示的 Microsoft 文档中存在与 /SUBSYSTEM = Windows 相关的缺陷。还是我错过了什么?

编辑

我介绍了这个编辑部分,以回应下面罗斯里奇给出的答案。

这些是我为所有可能的场景所做的构建模拟

表明/SUBSYSTEM 中以下句子中使用的术语已定义 表示在 /ENTRY 选项中定义,而不是在源文件或 .obj 文件中。

  1. 控制台小节中的“如果 main or wmain 被定义为本机代码...”
  2. 如果 WinMainwWinMain 定义为本机代码 ..." 在 Windows 小节

提到的 MS 文档。

因此,在我看来,我的第一个示例没有任何问题,只是因为其他案例甚至没有构建。

EDIT1

我仍然不明白为什么我需要擦除 END 指令的入口点。你能解释一下吗?尽管如此,在进行此擦除后,我与您的结果仍有一些差异,如下所示。

令人惊讶的是,您能够构建上面的最后一个案例,我继续遇到链接器错误。

【问题讨论】:

  • 整个入口点名称可能只是用于编译器,然后相应地调用链接器。它记录在链接器中,因为我猜它具有这些选项。
  • 如果您指定/SUBSYSTEM=Windows 选项,第二个示例是否链接并正确运行?
  • @prl 当然可以,

标签: visual-studio assembly linker


【解决方案1】:

您的第一个推断并不完全正确。这不是入口点是否命名为mainwmain,而是这些符号中的任何一个是否由任何被链接的目标文件定义。通常这些符号不用作控制台应用程序的入口点,控制台应用程序的默认入口点实际上是mainCRTStartupwmainCRTStartup。这些入口点由 Microsoft C 运行时定义,在初始化 C 运行时库后会分别调用mainwmain

第二个例子的问题是你没有正确声明WinMain。此函数是 Windows API 的一部分,因此使用与大多数其他 32-bt x86 Windows API 函数相同的“stdcall”调用约定。在此调用约定中,符号名称以下划线 _ 为前缀,并以 @# 为后缀,其中 # 是传递给函数的参数的大小(以字节为单位)。由于 Windows API 将 WinMain 定义为一个接受 4 个参数的函数,这些参数占用堆栈上的 16 个字节,因此链接器正在查看符号 _WinMain@16 是否已定义。由于您对 WinMain 的定义没有任何参数,因此您的代码最终会定义符号 _WinMain@0

注意,严格来说,第一个示例中的 main 应该使用“cdecl”调用约定而不是 stdcall 定义。这将导致汇编器使用符号_main 而不是符号_main@0。在 cdecl 调用约定中,符号名称以 _ 为前缀,并且没有添加后缀。因为您将main 作为入口点,所以链接器能够解决这个问题。在创建 32 位 x86 图像时,这个下划线 _ 前缀,但不是任何 @# 后缀,对于我在第一段中提到的所有其他符号也是必需的:_main_mainCRTStartup_wmainCRTStartup

对于 64 位 x86 目标,mainWinMain 都使用相同的 x64 调用约定,并且该约定不会为符号添加前缀或后缀,因此它只是 mainWinMain、@987654349 @等

就个人而言,我建议使用/SUBSYSTEM: 选项明确指定您想要的子系统。我强烈建议不要使用名为 mainWinMain 的入口点,因为这意味着这些函数应该是同名的标准 C 或 Windows API 函数,而实际上它们不是。


为了证明我在第一段中所说的话,我运行了与您声称运行的相同的测试。这是我用来执行测试的程序集文件:

_TEXT   SEGMENT
M   PROC
    int 3
M   ENDP
_TEXT   ENDS

    END

这是我运行的命令:

ml /DM=main t450.asm /link /entry:main
ml /DM=main t450.asm
ml /DM=_main t450.asm /link /entry:_main
ml /DM=_main t450.asm
ml /DM=main t450.asm /link /entry:_main
ml /DM=_main t450.asm /link /entry:main

这是我得到的结果:

> ml /DM=main t450.asm /link /entry:main 
Microsoft (R) Macro Assembler Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

/OUT:t450.exe 
t450.obj 
/entry:main 
 Assembling: t450.asm

> ml /DM=main t450.asm 
Microsoft (R) Macro Assembler Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

/OUT:t450.exe 
t450.obj 
LINK : fatal error LNK1561: entry point must be defined
 Assembling: t450.asm

> ml /DM=_main t450.asm /link /entry:_main 
Microsoft (R) Macro Assembler Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

/OUT:t450.exe 
t450.obj 
/entry:_main 
LINK : error LNK2001: unresolved external symbol __main
t450.exe : fatal error LNK1120: 1 unresolved externals
 Assembling: t450.asm

> ml /DM=_main t450.asm 
Microsoft (R) Macro Assembler Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

/OUT:t450.exe 
t450.obj 
LINK : error LNK2001: unresolved external symbol _mainCRTStartup
t450.exe : fatal error LNK1120: 1 unresolved externals
 Assembling: t450.asm

> ml /DM=main t450.asm /link /entry:_main 
Microsoft (R) Macro Assembler Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

/OUT:t450.exe 
t450.obj 
/entry:_main 
LINK : fatal error LNK1221: a subsystem can't be inferred and must be defined
 Assembling: t450.asm

> ml /DM=_main t450.asm /link /entry:main 
Microsoft (R) Macro Assembler Version 14.00.24210.0
Copyright (C) Microsoft Corporation.  All rights reserved.

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

/OUT:t450.exe 
t450.obj 
/entry:main 
 Assembling: t450.asm

总结结果:

 /ENTRY    Defined   Result
 main      main      No error
 Not set   main      LNK1561: entry point must be defined
 _main     _main     LNK2001: unresolved external symbol __main
 Not set   _main     LNK2001: unresolved external symbol _mainCRTStartup
 _main     main      LNK1221: a subsystem can't be inferred and must be defined
 main      _main     No error

这些结果都与我上面所说的不一致,尽管我们确实了解到链接器会自动在 /ENTRY 命令行选项传递的符号前加上下划线 _

我省略了与 WinMain 相关的测试,因为我不知道您想用它来证明什么。可以说,尽管我还验证了定义 _WinMain@16 如上所述确实允许链接器推断子系统。

【讨论】:

  • 参见我上面的编辑部分。
  • @Belloc 如果您的编辑是为了反驳我的回答,那么恐怕您错了。我不知道您是如何获得结果的,但我无法重现它们。我和你做了同样的测试,得到了不同的结果。我的猜测是问题的一部分是您仍在使用 END 指令定义一个入口点,该指令的功能与 /ENTRY 开关相同。
  • 见我上面的Edit1
  • @Belloc 你没有展示你是如何产生你得到的结果的,所以我只能猜测为什么它们仍然不同。这一次,我的猜测是您使用的是.model stdcall,这会导致汇编程序自动将_ 前缀和@# 后缀添加到函数名称中,因此您定义的真正符号是_main@0 和@987654364 @。链接器有时能够进行一些模糊匹配,忽略_ 前缀和@# 后缀,以便匹配符号,因​​此“链接器能够解决这个问题”并匹配/entry:main,与_main@0 .
  • @Belloc 我已经更新了我的答案,以说明您在示例中使用了.model flat, stdcall,这是我之前没有注意到的。然而,这并没有改变我的任何结论。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-12-29
  • 2014-03-28
  • 2012-05-22
  • 1970-01-01
  • 2021-10-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多