33

我有一段 C 代码调用程序集中定义的函数。例如,假设 foo.c 包含:

int bar(int x);  /* returns 2x */
int main(int argc, char *argv[]) { return bar(7); }

bar.s 包含了 bar() 在 x86 程序集中的实现:

.global bar
bar:    movl 4(%esp), %eax
        addl %eax, %eax
        ret

在 Linux 上,我可以使用 GCC 轻松编译和链接这些源代码,如下所示:

% gcc -o test foo.c bar.s
% ./test; echo $?
14

在带有 MinGW 的 Windows 上,这会失败,并出现“未定义对‘bar’的引用”的错误。事实证明,造成这种情况的原因是在 Windows 上,所有具有 C 调用约定的函数标识符都带有下划线前缀,但由于“bar”是在程序集中定义的,它没有得到这个前缀并且链接失败。(因此错误消息实际上是在抱怨缺少符号 _bar,而不是 bar。)

总结一下:

% gcc -c foo.c bar.s
% nm foo.o bar.o
foo.o:
00000000 b .bss
00000000 d .data
00000000 t .text
         U ___main
         U _bar
00000000 T _main

bar.o:
00000000 b .bss
00000000 d .data
00000000 t .text
00000000 T bar

现在的问题是:我怎样才能很好地解决这个问题?如果我只为 Windows 编写代码,我可以将下划线添加到 bar.s 中的标识符,但随后代码在 Linux 上会中断。我查看了 gcc-fleading-underscore-fno-leading-underscore选项,但似乎都没有做任何事情(至少在 Windows 上)。

我现在看到的唯一选择是通过 C 预处理器传递程序集文件,如果定义了 WIN32,则手动重新定义所有声明的符号,但这也不是很漂亮。

有没有人有一个干净的解决方案?也许是我监督的编译器选项?也许 GNU 汇编器支持一种方法来指定这个特定的符号是指使用 C 调用约定的函数并且应该像这样被修改?还有其他想法吗?

4

4 回答 4

32

一种选择,虽然很危险,是说服 GCC 省略 ABI 要求的前导下划线。

  • -fleading-underscore

    此选项及其对应选项-fno-leading-underscore强制更改 C 符号在目标文件中的表示方式。一种用途是帮助链接旧的汇编代码。

    警告:-fleading-underscore开关导致 GCC 生成的代码与没有该开关生成的代码二进制不兼容。使用它来符合非默认应用程序二进制接口。并非所有目标都为此开关提供完全支持。

另一个更安全的选择是明确告诉 GCC 要使用的名称。

5.39 控制汇编代码中使用的名称

asm您可以通过在声明符后写(or ) 关键字来指定要在 C 函数或变量的汇编代码中使用的名称,__asm__如下所示:

     int foo asm ("myfoo") = 2;

这指定在汇编代码中用于变量的名称foo应该是“myfoo ' rather than the usual \``_foo”。

在通常在 C 函数或变量名称前附加下划线的系统上,此功能允许您为不以下划线开头的链接器定义名称。

将此功能与非静态局部变量一起使用是没有意义的,因为此类变量没有汇编程序名称。如果您尝试将变量放在特定寄存器中,请参阅Explicit Reg Vars。GCC 目前接受带有警告的此类代码,但将来可能会更改为发出错误而不是警告。

您不能在函数定义asm中以这种方式使用;但是您可以通过在定义之前为函数编写声明并将其放在那里来获得相同的效果,如下所示:asm

 extern func () asm ("FUNC");

 func (x, y)
      int x, y;
 /* ... */

确保您选择的汇编器名称不会与任何其他汇编器符号冲突,这取决于您。此外,您不得使用注册名称;这将产生完全无效的汇编代码。GCC 还没有将静态变量存储在寄存器中的能力。也许会添加。

在你的情况下,

extern int bar(int x) asm("bar");

应该告诉 GCC “bar使用 asm 名称 ``bar`',即使它是一个 ccall 函数”。

于 2009-06-24T00:23:20.017 回答
9

您可以使用 C 预处理器对程序集进行预处理,并使用宏在 Windows 上添加缺少的下划线。首先,您需要将程序集文件从 bar.s 重命名为 bar.S(大写“S”)。这告诉 gcc 使用 cpp 来预处理文件。

要添加缺少的下划线,您可以定义一个宏“cdecl”,如下所示:

#if defined(__WIN32__)
# define cdecl(s) _##s
#else
# define cdecl(s) s
#endif

然后像这样使用它:

.global cdecl(bar)
cdecl(bar):
    movl 4(%esp), %eax
    addl %eax, %eax
    ret

请注意,Mac OSX 还需要前导下划线,因此您可以像这样更新宏的第一行:

#if defined(__WIN32__) || defined(__APPLE__)
于 2012-04-13T17:19:58.910 回答
5

你能声明两次吗?

.global bar
.global _bar

我已经有一段时间没有编写程序集了,但是 .global 标识符是否就像一个标签?

于 2009-06-23T20:45:09.567 回答
4

默认情况下,ELF 目标的编译器不添加前导下划线。您可以-fleading-underscore在编译为 ELF 格式时添加(在 Linux 下)。在生成文件中使用条件。

参考: http: //opencores.org/openrisc,gnu_toolchain(在页面上搜索“保持全局名称不变”)

于 2011-10-19T20:09:19.110 回答