在C ++ Builder中使用GCC生成的汇编程序

时间:2013-04-07 13:35:34

标签: gcc assembly x86 c++builder calling-convention

我在Win32上使用C ++ builder来实现GUI应用程序。 Borland编译器优化非常糟糕,不知道如何使用SSE。 使用mingw gcc 4.7编译时,我的函数快5倍。 我想要求gcc生成汇编代码,然后在我的C函数中使用这个cod,因为Borland编译器允许内联汇编程序。

C中的函数如下所示:

void Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)
{
double s = 77.777;
size_t m = mA[NT-3];
AV[2]=x[n-4]+m*s;
}

为了简化我的问题,我使功能代码变得非常简单。我的真实函数包含许多循环。

Borland C ++编译器生成了这个汇编代码:

  ;
  ; void Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)
  ;
  @1:
push      ebp
mov       ebp,esp
add       esp,-16
push      ebx
 ;
 ;  {
 ;      double s = 77.777;
 ;
mov       dword ptr [ebp-8],1580547965
mov       dword ptr [ebp-4],1079210426
 ;
 ;      size_t m = mA[NT-3];
 ;
mov       edx,dword ptr [ebp+20]
mov       ecx,dword ptr [ebp+24]
mov       eax,dword ptr [edx+4*ecx-12]
 ;
 ;      AV[2]=x[n-4]+m*s;
 ;
 ?live16385@48: ; EAX = m
xor       edx,edx
mov       dword ptr [ebp-16],eax
mov       dword ptr [ebp-12],edx
fild      qword ptr [ebp-16]
mov       ecx,dword ptr [ebp+8]
mov       ebx,dword ptr [ebp+12]
mov       eax,dword ptr [ebp+16]
fmul      qword ptr [ebp-8]
fadd      qword ptr [ecx+8*ebx-32]
fstp      qword ptr [eax+16]
 ;
 ;  }
 ;
 ?live16385@64: ;
 @2:
pop       ebx
mov       esp,ebp
pop       ebp
ret

虽然gcc生成的汇编代码是:

 _Test_Fn:
mov edx, DWORD PTR [esp+20]
mov eax, DWORD PTR [esp+16]
mov eax, DWORD PTR [eax-12+edx*4]
mov edx, DWORD PTR [esp+8]
add eax, -2147483648
cvtsi2sd    xmm0, eax
mov eax, DWORD PTR [esp+4]
addsd   xmm0, QWORD PTR LC0
mulsd   xmm0, QWORD PTR LC1
addsd   xmm0, QWORD PTR [eax-32+edx*8]
mov eax, DWORD PTR [esp+12]
movsd   QWORD PTR [eax+16], xmm0
ret
 LC0:
   .long    0
   .long    1105199104
   .align 8
 LC1:
   .long    1580547965
   .long    1079210426
   .align 8

我想获得有关如何在gcc和Borland C ++中完成函数参数访问的帮助。 我在Borland的C ++中的功能类似于:

 void Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)
 {
__asm
  {
  put gcc generated assembler here
  }
 }

当gcc使用ebp寄存器时,Borland开始使用esp寄存器。 我是否可以使用某些调用约定(如cdecl ou stdcall?)强制其中一个编译器生成用于访问参数的兼容代码?

3 个答案:

答案 0 :(得分:2)

在两种情况下都会以类似的方式传递参数。区别在于Borland生成的代码表示相对于EBP寄存器和GCC相对于ESP的参数位置,但它们都指向相同的地址。

Borlands将EBP设置为指向函数堆栈帧的开头并表示相对于该帧的位置,而GCC不设置新的堆栈帧,而是表示相对于ESP的位置,调用者已将其指向结尾调用者的堆栈框架。

Borland生成的代码在函数开头设置了一个堆栈帧,导致Borland代码中的EBP等于GCC代码中的ESP减少了4.这可以通过查看前两个Borland看到行:

push      ebp     ; decrease esp by 4
mov       ebp,esp ; ebp = the original esp decreased by 4

GCC代码不会改变ESP,Borland代码在程序结束前不会改变EBP,因此在访问参数时关系会保持不变。

在两种情况下,调用约定似乎都是cdecl,并且调用函数的方式没有区别。您可以向两者添加关键字__cdecl,以便明确这一点。

 void __cdecl Test_Fn(double *x, size_t n,double *AV, size_t *mA, size_t NT)

然而,将使用GCC编译的内联汇编添加到使用Borland编译的函数并不简单,因为即使函数体仅包含内联汇编,Borland也可能设置堆栈帧,导致ESP寄存器的值与使用的值不同在GCC代码中。我看到了三种可能的解决方法:

  1. 使用选项"Standard stack frames"与Borland编译。如果编译器发现不需要堆栈帧,则可能会有效。
  2. 使用选项-fomit-frame-pointer与GCC编译。这应该确保至少EBP的值在两者中是相同的。该选项在-O,-O2,-O3和-Os。
  3. 级别启用
  4. 手动编辑GCC生成的程序集,将对ESP的引用更改为EBP,并将4添加到偏移量。

答案 1 :(得分:1)

我建议你阅读应用程序二进制接口。 这是一个相关的链接,可以帮助您找出什么编译器生成什么样的代码: https://en.wikipedia.org/wiki/X86_calling_conventions

答案 2 :(得分:1)

我会尝试使用GCC编译所有内容,或者查看是否只使用GCC编译关键文件,其余部分与Borland编译并链接在一起工作。您解释的内容可以起作用,但这将是一项艰苦的工作,可能不值得您投入时间(除非它会在很多很多机器上频繁运行)。