在Windows上编译Nasm程序时未定义的对"WinMain"的引用(MinGW)

Ite*_*tor 6 windows assembly mingw nasm

我想在Windows上编译Hello World NASM示例.

我已将上面的代码粘贴到一个main.asm文件中,并使用以下命令将其编译为obj文件:

nasm -fwin32 .\main.asm -o main.obj
Run Code Online (Sandbox Code Playgroud)

之后我想将这个obj文件编译成一个exe,如下所示:

g++ .\main.obj -o main.exe -m32
Run Code Online (Sandbox Code Playgroud)

但我得到这个错误:

C:/Program Files (x86)/mingw-w64/i686-8.1.0-posix-dwarf-rt_v6-rev0/mingw32/bin/../lib/gcc/i686-w64-mingw32/8.1.0/../../../../i686-w64-mingw32/lib/../lib/libmingw32.a(lib32_libmingw32_a-crt0_c.o):crt0_c.c:(.text.startup+0x39): undefined reference to `WinMain@16'
Run Code Online (Sandbox Code Playgroud)

我错过了什么?如何修复此错误?

Mar*_*oom 5

Hello World程序正在尝试手动创建PE导入表.为了使其工作,您需要仔细指示链接器(PE部分不依赖于PE目录,idata只是一个名称).
在该源中进行进一步的假设(例如图像的基地址和CRT的需要).

老实说,这只是胡说八道.正确使用链接器,如Jester所示.
说实话,整个Wikipedia部分充其量只是提供信息.
长话短说:从不使用维基百科作为编程教程.

一点简短的理论

您可以主要以两种方式创建32位Windows控制台程序:

  1. 使用C运行时(CRT)
    这使您可以使用常用的C函数(最重要的是printf).
    使用CRT有两种方法:

    1. 静态
      编译CRT源代码产生的目标文件与源代码的编译/汇编产生的目标文件链接.
      CRT代码完全嵌入您的应用程序中.
      在这种情况下您的主要功能(main/ WinMain/ DllMain和Unicode变种)正在由一个正确设置PE的入口点先运行)的CRT调用.
      要使用此方法,您需要CRT对象文件,可以使用Visual Studio或MinGW(命名为twos)找到这些文件.
      执行顺序是:Windows加载程序调用您的PE入口点,这被设置为_mainCRTStartup初始化CRT并且CRT调用您的主要功能.
    2. 动态
      CRT主dll msvcrt.dll适用于Windows安装附带的版本或msvcrtXX0.dllVisual Studio安装附带的版本(XX取决于VS版本).
      CRT dll在DLL入口点中具有初始化和拆除代码,因此只需将其放入PE导入表中即可自动管理CRT.执行顺序是:Windows加载程序加载您的PE依赖项,包括CRT DLL(按上面的方式初始化),然后调用您的PE入口点.
  2. 仅使用Windows API
    Windows API是操作系统公开的功能,这些是CRT实现最终调用的功能.

    您可以使用Windows API和CRT(常见的场景是图形应用程序将CRT静态链接并WinMain用作入口点 - Windows API与C实用程序功能混合使用)或单独使用Windows API.
    单独使用它们时,您可以获得更小,更快,更容易制作的可执行文件.

要使用1.1,你需要CRT目标文件,这些文件通常附带一个编译器(它们曾经随Windows SDK一起提供,但现在VS是免费的,微软将它们移到了VS软件包中 - 公平,但VS的数量级比SDK).
1.2和2不需要这些目标文件.
但请注意,编译器/汇编器/链接器兼容性可能是一个令人讨厌的野兽,尤其是.lib用于链接外部API 的机器(基本上libs文件是一种使链接器找到将在运行时由加载器解析的函数的方法 - 即在外部DLL).

你好,世界!

方法2

首先,写下Hello,World!使用方法2.,请参阅我的另一个答案.
它是在Windows SDK中提供链接器时编写的,今天我使用的是GoLink.
它是一个极简主义,非常易于使用的链接器.
它的一个关键点是它不需要.lib文件,而是可以将它传递给外部函数所在的DLL的路径.

NASM命令是相同的,链接使用:

 golink /console /entry main c:\windows\system32\kernel32.dll hello.obj -fo hello.exe
Run Code Online (Sandbox Code Playgroud)

未经测试 - /largeaddressaware如果您的代码可以处理,则可选择添加

这个例子适用于64位编程,它比32位编程更复杂,但无论如何都可能有用.

方法1.2

这就是维基百科文章试图使用的内容.
在分析特定代码之前,让我展示一下我是如何编写它的:

BITS 32

GLOBAL _main

EXTERN printf
EXTERN exit

SECTION .text

_main:
 push strHelloWorld 
 call printf 
 add esp, 04h

 push 0
 call exit 


SECTION .data

 strHelloWorld db "Hello, world!", 13, 10, 0
Run Code Online (Sandbox Code Playgroud)

与Wiki的相比,这非常简单.
要制作可执行文件:

nasm -fwin32 helloworld.asm -o helloworld.obj
golink /console /entry _main c:\windows\system32\msvcrt.dll helloworld.obj -fo helloworld.exe
Run Code Online (Sandbox Code Playgroud)

维基百科的代码创建了一个.idata存储PE导入地址表的部分.
这是一个愚蠢的举动,链接器用于根据目标文件的动态依赖关系生成该表.
要建立该程序链接,我们需要:

  1. 告诉链接器基址是0x400000.这可以通过任何链接器完成(用于golink /base 0x400000).
  2. 告诉链接器入口点是.text节开始的位置.我不知道是否link.exe可以.text作为有效的符号名称,或者是否允许指定相对于的入口点,.text但这似乎不太可能.Golink不会允许这样做.简而言之,标签可能缺失.
  3. 告诉链接器使Import 目录指向该.idata部分.我不知道任何允许这样做的链接器(虽然它可能存在).

简而言之,忘了它.

方法1.1

这就是Jester指出链接.
汇编代码与1.2相同,但您使用MinGW进行链接.