在Win32上使用GCC将前导下划线添加到装配符号?

时间:2009-06-23 20:05:23

标签: c winapi gcc assembly mingw

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

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

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

.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上,此操作失败并显示“未定义引用”栏'的错误。事实证明,在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 个答案:

答案 0 :(得分:27)

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

  
      
  • -fleading-underscore

         

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

         

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

  •   

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

  

5.39 Controlling Names Used in Assembler Code

     

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

     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 name``bar`',即使它是一个ccall函数”。

答案 1 :(得分:8)

您可以使用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__)

答案 2 :(得分:5)

你可以宣布两次吗?

.global bar
.global _bar

我有一段时间没有编写汇编,但.global标识符的行为有点像标签吗?

答案 3 :(得分:4)

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

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