代码之家  ›  专栏  ›  技术社区  ›  Iter Ator

在windows(MinGW)上编译Nasm程序时对“WinMain”的未定义引用

  •  6
  • Iter Ator  · 技术社区  · 6 年前

    我想编译 Hello World NASM example 在窗户上。

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

    nasm -fwin32 .\main.asm -o main.obj
    

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

    g++ .\main.obj -o main.exe -m32
    

    但我有一个错误:

    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'
    

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

    1 回复  |  直到 6 年前
        1
  •  8
  •   Community Heathro    4 年前

    Hello World程序正在尝试手动创建PE导入表。为了让它工作,你需要仔细地指导链接器(PE部分没有绑定到PE目录, idata 只是一个名字)。
    在该来源中进行了进一步的假设(例如,图像的基址和CRT的需要)。

    老实说,这只是胡说八道。正确使用链接器,比如 Jester shown .
    说实话,整个维基百科部分充其量只是提供信息。
    长话短说:永远不要将维基百科用作编程教程。

    编辑 :维基百科页面上的x86-64 Linux示例已由Peter Cordes更新;其他人可能仍然具有误导性。

    一点简单的理论

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

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

      1. 静态地
        CRT源代码编译产生的目标文件与源代码编译/汇编产生的目标文件相链接。
        CRT代码完全嵌入到应用程序中。
        在这个场景中,您的主要功能( main / WinMain / DllMain 和unicode变体)由CRT调用,CRT首先由正确设置的PE入口点运行)。
        为了使用这种方法,您需要CRT对象文件,这些文件可以在Visual Studio或MinGW中找到。
        执行顺序是:Windows加载程序调用您的PE入口点,设置为 _mainCRTStartup 初始化CRT,CRT调用主函数。
      2. 动态地
        CRT主dll是 msvcrt.dll 适用于Windows安装附带的版本,或 msvcrtXX0.dll 对于随Visual Studio安装附带的版本(其中 XX 取决于VS版本)。
        CRT dll在dll入口点中有初始化和分解代码,因此只需将其放入PE导入表中,CRT就会自动管理。 执行顺序是:Windows加载程序加载您的PE依赖项,包括CRT DLL(按照上述初始化),然后调用您的PE入口点。
    2. 仅使用Windows API

      您可以使用Windows API和CRT(常见的情况是图形应用程序静态链接并使用CRT) 程序进入点 作为入口点——Windows API与C实用程序函数混合使用)或单独使用Windows API。
      当单独使用它们时,你会得到一个更小、更快、更容易执行的文件。

    要使用1.1,您需要CRT对象文件,这些文件通常随编译器一起提供(它们曾经随Windows SDK一起提供,但现在VS是免费的,Microsoft将它们移动到VS包中,这很公平,但VS比SDK重几个数量级)。
    1.2和2不需要这些对象文件。
    但是请注意,编译器/汇编程序/链接器的兼容性可能是一个令人讨厌的问题,尤其是 .lib 链接外部API的机制(基本上,libs文件是一种让链接器找到加载程序在运行时解析的函数的方法,即在外部DLL中定义的函数)。

    你好,世界!

    方法2

    首先,写你好,世界!使用方法2。,看见 this other answer of mine .
    它是在Windows SDK中有链接器时编写的,现在我使用 GoLink .
    这是一个极简主义的、非常容易使用的链接器。
    其中一个关键点是 不需要这个。库文件目录 ,您可以将其传递给外部函数所在的DLL路径。

    NASM命令与链接使用相同:

     golink /console /entry main c:\windows\system32\kernel32.dll hello.obj -fo hello.exe
    

    未测试-可选添加 /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
    

    与维基相比,这非常简单。
    要生成可执行文件,请执行以下操作:

    nasm -fwin32 helloworld.asm -o helloworld.obj
    golink /console /entry _main c:\windows\system32\msvcrt.dll helloworld.obj -fo helloworld.exe
    

    维基百科的代码正在创建一个 .idata 存储PE导入地址表的节。
    这是一个愚蠢的举动,链接器用于根据对象文件的动态依赖关系生成该表。
    要将该计划链接起来,我们需要:

    1. 告诉链接器基址是 0x400000 .这可以通过任何链接器完成(用于golink) /base 0x400000 ).
    2. 告诉链接器入口点是 .text 第一节开始。我不知道 link.exe 可以接受 文本 作为有效的符号名,或者如果允许指定相对于 文本 但这似乎不太可能。戈林克不会允许的。简而言之,标签可能不见了。
    3. 告诉链接器进行导入 目录 指向 伊达塔先生 部分我不知道任何链接器会允许这样做(尽管它可能存在)。

    简而言之,忘掉它吧。

    方法1.1

    这就是 link Jester pointed out 正在使用。
    汇编代码与1.2相同,但使用MinGW进行链接。