用cl.exe编译asm文件为dll

Posted

技术标签:

【中文标题】用cl.exe编译asm文件为dll【英文标题】:Compile asm file with cl.exe to dll 【发布时间】:2016-12-06 23:52:00 【问题描述】:

给定:prog.c 有一个入口点prog

我通常会这样做

cl.exe /MD /LD /Fe"prog.dll" /Fo"prog" "prog.c" /link ext.lib

cl.exe /MD /Fo"prog.obj"
cl.exe /MD /LD /Fe"prog.dll" "prog.obj" /link ext.lib

在这两种情况下,生成的 prog.dll 都可以正常工作。

现在我做了以下操作来获取一个 asm 文件而不是 obj 文件:

cl.exe /c /MD /Fa"prog"

到目前为止,这也“有效”。但我不知道如何制作这个文件的 dll。

试过了:

ml.exe /c /Cx /coff prog.asm
cl.exe /MD /LD /Fe"prog.dll" "prog.obj" /link ext.lib

结果:prog.dll 没有入口点prog

再试一次:

ml.exe /c /Cx /coff prog.asm
cl.exe /MD /LD /Fe"prog.dll" "prog.obj" /link /entry:prog ext.lib

结果:关于错误入口点 _prog 不是带有 12 字节参数的 stdcall 的编译器警告以及关于未解析符号 _memcpy 的编译器错误。

问题:有没有办法将cl.exe通过/Fa生成的asm文件编译成dll(最好通过cl.exe,如果用ml.exe不行的话)?

【问题讨论】:

一般情况下,您无法组装 Microsoft 编译器的输出并获得可以工作的东西。 MASM 不支持编译器使用的所有功能,因此汇编输出仅代表编译器创建的目标文件。 @RossRidge “程序集输出仅代表编译器创建的目标文件” - 这应该没问题,因为我可以直接编译编译器创建的目标文件 - 我只是不'不知道如何编译/链接它以使其从 asm 文件中工作。 不,您能够将编译器创建的程序集文件组装到目标文件中这一事实并不一定意味着您得到了有效的东西。 Microsoft 的编译器不支持您尝试执行的操作。您应该只使用编译器直接创建的目标文件。 LLVM 工具链支持先创建一个 .asm 然后再组装它。现在甚至有一个工具和测试集可以确保这相当于直接产生输出(检查 CFC),请参阅snsystems.com/tech-blog/2015/04/22/… Nvidia nvcc CUDA 编译器也可以作为中间步骤编译成他们的汇编语言 ptx,然后继续编译,而不是直接给出目标文件。我认为看这个很有教育意义。 【参考方案1】:

有什么方法可以将 cl.exe 由/Fa 生成的 asm 文件编译成 dll(最好通过 cl.exe,如果用 ml.exe 无法实现)?

没有:

    C/C++ 编译器 (cl.exe) 无法汇编汇编代码输入。它只需要 C 或 C++ 源代码作为输入。汇编程序是 MASM (ml.exe)。 cl.exe 的汇编代码输出通常不能直接输入 MASM。在某些情况下,它甚至不是有效的汇编代码。在其他情况下,MASM 不直接支持代码中发出的指令、关键字和其他内容。如果 C/C++ 源代码使用异常,事情就会变得特别棘手。列表文件仅用于信息目的。

我很不清楚你为什么一开始就想这样做。如果您的源代码是 C 或 C++,并且可以通过 MSVC 编译和链接,那么引入额外的将其转换为汇编语言和从汇编语言转换的中间步骤有什么意义呢?直接用cl.exe做个DLL就好了。

如果您绝对必须执行此操作,则必须获取由 MSVC 生成的 ASM 列表文件并在通过 MASM 运行之前手动清理它。您可以通过关闭整个程序优化、关闭异常处理、关闭安全检查/cookie 以及向链接器指示图像确实包含安全的 SEH 处理程序,从而使此清理任务更容易。请注意,其中一些可能会破坏或改变代码的行为!您还需要为从运行时库调用的函数添加 EXTERN 定义。

【讨论】:

我已经更正了问题中的错字并删除了您答案中的解释。这就是这样做的原因(以及错字):我正在为使用底层 C 编译器的 GnuCOBOL 构建一些测试(主要是 gcc,然后是 cl.exe,然后是 clang,然后是 icc)。这样它也可以创建一个程序集文件。为了测试这是否真的有效并且 GnuCOBOL 知道如何使用 C 编译器,我将生成的程序集文件反馈给 C 编译器(对 gcc 工作正常,对 cl 显然不行)。只有在使用cl.exe时asm文件存在时我才会调整测试检查。 如果这个问题得到了一些人的支持,因此留在了这里(顺便说一句,很好的答案)我可能会在此处链接,原因... 仍然不确定我是否理解这是一项有价值的测试。它不测试输出的正确性,它与 GnuCOBOL 无关。它只是测试 C 编译器的内部一致性。除非您说 GnuCOBOL 的目的是将 COBOL 代码转换为 C 或 x86 程序集?与直接编译为目标代码相比,这样做有什么意义?所以你可以用 COBOL 写一些东西,把它翻译成 C 或程序集,然后将它与现有的 C/程序集项目集成? 您可以在不了解系统细节的情况下将 C/asm 编译为模块/对象/可执行文件直接使用 GnuCOBOL 编译器 cobc 将其链接到 GnuCOBOL 运行时库。您可以(并且人们这样做...)从 COBOL 模块调用 C / asm 文件的函数条目并将它们组合起来(cobc 首先从 C / asm 源创建一个目标文件,然后将其链接到生成的COBOL 模块)。当前测试的重点是:cobc 知道如何调用 C 编译器来执行此操作,cobc 知道 s/asm 是汇编程序。首先创建它只是为了获得一个“有效”的 asm 文件。 我找不到关于列表仅供参考的“官方”声明,但这篇文章声称相同:***.com/a/7495413/524504【参考方案2】:

虽然由 Microsoft C 编译器生成的 ASM 源代码可能不是返回 MASM 的最佳输入,但这并不意味着它不会工作,至少在某些情况下(可能并不复杂)。如果您查看由 C 编译器生成的 ASM 文件,您会发现 Microsoft 的某个人费了很大的力气来插入各种“hacky”包含、指令、手动段定义和其他 MASM 细节以至少提供源文件被反馈到 MASM 并获得组装结果的可能性很小。只要您将期望值设置得低,我猜一个简单的 C 源文件,转换为 ASM,然后反馈到 MASM 应该可以工作,如果您按顺序获得命令行选项。

您需要记住的一个警告是,如果您像现在一样使用 CRT(即使用 memcmp),您需要允许从适当的 CRT 中选择默认入口点 ___DllMainCRTStartup@12 。 LIB 文件,而不是指定您自己的。这允许在调用 DllMain 之前初始化 CRT,从而防止在调用依赖于此初始化的某些 CRT 函数时发生崩溃。话虽如此,旧版本的 Visual Studio,例如 7.1 (2003),您可以不初始化 CRT,具体取决于您使用的函数,而不会有崩溃的风险。如果进程之前未调用过 mainCRTStarttup 或 DllMainCRTStartup,则无论调用哪个 CRT 函数,较新版本的 C 运行时都会引发异常。

出于教育目的,让我们使用 MSVC 7.1 (2003) 解决上述入口点问题,我们无需担心初始化 CRT,因此您可以明确指定自己的入口点。我认为您遇到了以下链接器警告:

warning LNK4086: entrypoint '_prog@XX' is not __stdcall with 12 bytes of arguments; image may not run

当指定您自己的 DLL 入口点时,链接器需要一个 DllMain 签名(这是 12 个参数字节和 stdcall,因此函数会自行清除参数);官方是:

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

您可以实现这个版本的 prog.c 中所示的入口点函数:

#include <Windows.h>
#include <stdio.h>

#pragma warning (disable:4100) //Warning Level 4: unreferenced formal parameter

int __stdcall prog(DWORD hInst, DWORD dwReason, DWORD dwReserved)

    printf("Result of memcmp: %d\n",memcmp("foo","bar",3));
    return(1);

假设您的 LIB 和 INCLUDE 环境变量设置正确,您可以使用以下命令构建上面的源代码:

cl.exe /nologo /c /MD /Fa./prog.asm prog.c
link.exe /nologo /dll /subsystem:console /entry:prog prog.obj kernel32.lib

您已经知道可以使用 C 编译器构建原始 C 源代码。 着眼于从 /Fa 选项生成的 prog.asm 输出文件,您可以从生成的 ASM 源构建 DLL,如下所示:

ml.exe /c /coff /Cx prog.asm
link.exe /nologo /dll /subsystem:console /entry:prog prog.obj kernel32.lib

使用简单的控制台加载程序测试您的 DLL,例如:

#include <Windows.h>
int __cdecl main(void)

    HMODULE hLib = LoadLibrary("prog.dll");
    printf("LoadLibrary result: 0x%X / code=0x%X\n",hLib,GetLastError());

在我的机器上,C 和 MASM 生成的 DLL 都产生了以下输出:

Result of memcmp: 1
LoadLibrary result: 0x10000000 / code=0x0
Result of memcmp: 1

下面列出了编译器生成的 MSVC 7.1 ASM 文件以供参考。注意文件如何将自己称为“列表”:)

; Listing generated by Microsoft (R) Optimizing Compiler Version 13.10.6030 

    TITLE   prog.c
    .386P
include listing.inc
if @Version gt 510
.model FLAT
else
_TEXT   SEGMENT PARA USE32 PUBLIC 'CODE'
_TEXT   ENDS
_DATA   SEGMENT DWORD USE32 PUBLIC 'DATA'
_DATA   ENDS
CONST   SEGMENT DWORD USE32 PUBLIC 'CONST'
CONST   ENDS
_BSS    SEGMENT DWORD USE32 PUBLIC 'BSS'
_BSS    ENDS
$$SYMBOLS   SEGMENT BYTE USE32 'DEBSYM'
$$SYMBOLS   ENDS
_TLS    SEGMENT DWORD USE32 PUBLIC 'TLS'
_TLS    ENDS
FLAT    GROUP _DATA, CONST, _BSS
    ASSUME  CS: FLAT, DS: FLAT, SS: FLAT
endif

INCLUDELIB MSVCRT
INCLUDELIB OLDNAMES

_DATA   SEGMENT
$SG74617 DB 'bar', 00H
$SG74618 DB 'foo', 00H
$SG74619 DB 'Result of memcmp: %d', 0aH, 00H
_DATA   ENDS
PUBLIC  _prog@12
EXTRN   __imp__printf:NEAR
EXTRN   _memcmp:NEAR
; Function compile flags: /Odt
_TEXT   SEGMENT
_hInst$ = 8                     ; size = 4
_dwReason$ = 12                     ; size = 4
_dwReserved$ = 16                   ; size = 4
_prog@12 PROC NEAR
; File prog.c
; Line 10
    push    ebp
    mov ebp, esp
; Line 11
    push    3
    push    OFFSET FLAT:$SG74617
    push    OFFSET FLAT:$SG74618
    call    _memcmp
    add esp, 12                 ; 0000000cH
    push    eax
    push    OFFSET FLAT:$SG74619
    call    DWORD PTR __imp__printf
    add esp, 8
; Line 12
    mov eax, 1
; Line 13
    pop ebp
    ret 12                  ; 0000000cH
_prog@12 ENDP
_TEXT   ENDS
END

【讨论】:

很多有用的信息。有没有办法不手动添加BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) 并且仍然让 dll 工作?直接编译时也不这样做。 Windows 加载程序会将 DLL 的任何入口点视为具有 DllMain 签名,该签名接受 3 个 DWORD 参数并调整堆栈,以便在返回时将其删除。您不必使用它们或引用它们,但无论如何它们都会在堆栈中。在汇编级别,您的入口点函数在返回之前唯一必须做的就是从堆栈中清除 3 个 DWORD,在这种情况下,最后的“RET 12”说明了这一点。如果您不这样做,该过程可能会崩溃。 我差点忘了提到,如果你在第一次调用 DLL 入口点函数时(即 RET 指令之前的 EAX 中的值)返回零(即 DLL_PROCESS_ATTACH == dwReason),你是在告诉系统初始化失败,DLL 将被卸载。您应该始终返回 1。请记住,Windows 将使用不同的 dwReason 值多次调用您的入口点。此外,根据您在此入口点调用的 API,可能会导致死锁。看看这里:msdn.microsoft.com/en-us/library/windows/desktop/…

以上是关于用cl.exe编译asm文件为dll的主要内容,如果未能解决你的问题,请参考以下文章

c怎么生成dll文件?

链接器link.exe 编译器cl.exe 资源编译器rc.exe

Microsoft C 编译器 (cl.exe):可以限制每个文件 (/Wall) 的警告范围吗?

cl.exe 不会在第二个子进程中编译

Visual Studio 2015 没有 cl.exe

Clang如何处理MSVC的编译参数