【问题标题】:How do I link with FreePascal a NASM program calling a DLL?如何将调用 DLL 的 NASM 程序与 FreePascal 链接?
【发布时间】:2013-02-06 10:09:13
【问题描述】:

问题

我有一个用汇编程序 (nasm) 编写的函数“bob”,它使用 kernel32.dll 中的函数。我在 FreePascal 中有一个名为“bob”的程序。

我将 nasm 用于:

nasm -fwin32 bob.asm

我在 FreePascal 中声明:

{$link bob.obj}

function bob(s:pchar):longint; stdcall; external name 'bob';

但是当我使用 fpc 编译时出现错误,告诉它没有找到在 bob.asm 中声明为 extern 的 GetStdHandle 和 WriteConsoleA(没有 @n 后缀)。我想告诉 fpc 在 kernel32.dll 或适当的导入库中查找它们。

但是,当我在纯汇编程序中使用相同的函数时,它与 nasm 和 golink 一起工作得很好。并且当我不调用 DLL 函数时,我可以毫无问题地与 FreePascal 链接。

如何将 kernel32 函数与 FreePascal 链接,以便汇编函数“看到”它们?


解决方案

由贝尼贝拉提供。我更改名称以便于事情容易理解。

program dlltest;

function WindowsGetStdHandle(n: longint): longint; stdcall;
   external 'kernel32.dll' name 'GetStdHandle';

{$asmmode intel}
procedure WrapperGetStdHandle; assembler; public name 'AliasGetStdHandle';
asm
   jmp WindowsGetStdHandle
end;


{$link myget.obj}

function AsmGetStdHandle(n: longint): longint; stdcall;
   external name 'gethandle';

const STDOUT = -11;

begin
   writeln(AsmGetStdHandle(STDOUT));
   writeln(WindowsGetStdHandle(STDOUT));
end.

在汇编中,在 myget.asm 中:

section .text

extern AliasGetStdHandle

global gethandle

gethandle:
   mov   eax, [esp+4]
   push  eax
   call  AliasGetStdHandle
   ret   4

WindowsGetStdHandle是kernel32.dll中GetStdHandle的别称。

WrapperGetStdHandle 只跳转到前面,这里是 aliaspublic name 能力:我们给它命名 AliasGetStdHandle对于外部对象。这是重要的部分,函数对汇编程序可见。

AsmGetStdHandle 是汇编函数 gethandle 在 FreePascal 中的名称。它调用 WrapperStdHandle(昵称 AliasGetStdHandle),跳转到 DLL 函数 WindowsGetStdHandle。

我们完成了,现在可以链接汇编程序,而无需更改其中的任何内容。所有的重命名机制都是在调用它的 pascal 程序中完成的。

唯一的缺点:需要一个包装函数,但对于名称的精细控制来说,它的价格并不高。


另一种解决方案

如果在 WindowsGetStdHandle 的声明中未指定 kernel32.dll,但使用 {$linklib kernel32},则该符号在 pascal 程序中链接的目标文件中可见。但是,仅 $linklib 指令似乎是不够的,仍然需要在 pascal 中声明一些引用它的函数

program dlltest;

{$linklib kernel32}

function WindowsGetStdHandle(n: longint): longint; stdcall;
   external name 'GetStdHandle';

{$link myget.obj}

function AsmGetStdHandle(n: longint): longint; stdcall;
   external name 'gethandle';

const STDOUT = -11;

begin
   writeln(AsmGetStdHandle(STDOUT));
   writeln(WindowsGetStdHandle(STDOUT));
end.

使用以下汇编程序。 AliasGetStdHandle 被 GetStdHandle 取代,现在直接指向 kernel32 函数。

section .text

extern GetStdHandle

global gethandle

gethandle:
         mov   eax, [esp+4]
         push  eax
         call  GetStdHandle
         ret   4

但这仅在使用外部链接器(gnu ld)时有效,带有命令

fpc -Xe dlltest.pas

当省略 opton '-Xe' 时,fpc 给出以下错误

Free Pascal Compiler version 2.6.0 [2011/12/25] for i386
Copyright (c) 1993-2011 by Florian Klaempfl and others
Target OS: Win32 for i386
Compiling dlltest.pas
Linking dlltest.exe
dlltest.pas(17,1) Error: Asm: Duplicate label __imp_dir_kernel32.dll
dlltest.pas(17,1) Error: Asm: Duplicate label __imp_names_kernel32.dll
dlltest.pas(17,1) Error: Asm: Duplicate label __imp_fixup_kernel32.dll
dlltest.pas(17,1) Error: Asm: Duplicate label __imp_dll_kernel32.dll
dlltest.pas(17,1) Error: Asm: Duplicate label __imp_names_end_kernel32.dll
dlltest.pas(17,1) Error: Asm: Duplicate label __imp_fixup_end_kernel32.dll
dlltest.pas(17,1) Fatal: There were 6 errors compiling module, stopping
Fatal: Compilation aborted

【问题讨论】:

  • @ 编辑:因为您从我的示例中删除了别名。如果添加`别名:'GetStdHandle';它应该工作。 (虽然我在帕斯卡和汇编中使用了 _GetStdHandle@4。你也可以确定两个别名)
  • 傻我!我不太了解这个“别名”的东西,但现在我发现它对您的解决方案至关重要。我马上改一下。
  • 我修改了我的答案。试图评论贝尼贝拉的发现。但它似乎与导入库有关。显然 FPC 链接器只为 FPC 拥有的符号生成那些存根。
  • @Marco 看起来你是对的:单独使用 $linklib 时,它不起作用,但如果我在 Pascal 中声明对 GetStdHandle 的引用,我可以在汇编中使用该符号。我想知道是否可以直接使用 Windows 单元,但我无法获得正确的名称(我认为类似于 _WINDOWS$$_GetStdHandle,但在将 $$ 替换为 PATH 时出现奇怪的错误)。另请参阅 freepascal.org/docs-html/prog/progsu125.html 了解名称修改。

标签: dll nasm freepascal


【解决方案1】:

我不知道如何直接解决链接问题,但您可以声明从 Pascal 源导出这些函数的公共包装函数。

例如:

{$ASMMODE INTEL}
procedure WrapperGetStdHandle; assembler; public; alias: '_GetStdHandle@4';
asm  jmp GetStdHandle end;
procedure WrapperWriteConsoleA; assembler; public; alias: '_WriteConsoleA@20';
asm  jmp WriteConsoleA end;

【讨论】:

  • 现在我用别名指令做对了,我不太理解。我会再等一段时间,看看是否有人提出了不涉及包装器的解决方案,但是这个已经很好了。非常感谢!
  • 顺便说一句,根据 FP 文档,“别名”已被弃用,应替换为“公共名称”(请参阅​​问题中的链接)。这不会降低您答案的价值。
  • 我什至没有意识到这一点(别名弃用,所以不要针对 BeniBela;-)
  • 顺便说一句:那些不应该是标准调用吗?
  • stdcall 与否并不重要,因为函数没有堆栈帧
【解决方案2】:

我怀疑有一些导入库被 nasm 自动链接以用于 nasm 代码,并且您可能还需要链接该库中的相关存根。

修改:

这可能是智能链接的问题。如前所述,FPC 会即时生成导入存根,但仅在需要时才会生成。因为 Windows 单元(包含所有核心 WINAPI 调用)非常大,所以为它激活了智能链接(仅添加您使用的内容)。 (还有其他原因)

NASM 起源的 obj 不在 FPC 的控制范围内,因此不会为其生成相关函数。

如果是这种情况,BeniBela 的代码可能会起作用,因为它强制从 FPC 代码引用,并在符号中链接。不过这是推测,它也可能是带有装饰的东西,或者带有前导下划线的东西。

测试很简单,使用 pascal 代码中的函数,无需 Benibela 的声明。

顺便说一句,FPC 的默认值不是 stdcall,所以 BenBela 的函数可能应该得到一个 stdcall 修饰符

【讨论】:

  • Nasm 不是链接器。我用nasm的链接器golink,可以直接读取DLL文件,不需要导入库(命令就是:golink /console bob.obj kernel32.dll)。因此,我遇到的问题是向 freepascal 显示我的链接对象文件具有对 dll 的外部依赖项(golink 会自动看到),并解决这些依赖项。
  • 这就是我的观点。 FPC 的内部链接器也可以这样做,但可能只针对其本机代码,而不是外部 .objs。
  • 我之前试过了,还是不行,除非用public再次导出函数。将导入的函数本身也公开,没有用。
  • 我没有找到一种方法来为链接器指定 something 以强制将目标文件与给定库链接。对我来说不是很清楚它是否需要一个导入库,或者它是否可以直接将 ld 与 dll 一起使用,无论如何,它似乎不起作用(可能我错过了一个决定性的命令行选项或其他东西)。
猜你喜欢
  • 1970-01-01
  • 2011-09-14
  • 2014-09-19
  • 1970-01-01
  • 1970-01-01
  • 2016-05-18
  • 1970-01-01
  • 2023-01-26
  • 1970-01-01
相关资源
最近更新 更多