在这种情况下,为什么编译器使用 32 位寄存器来传递指向 amd64 linux 上的函数的指针

Kbd*_*man 1 c ssl assembly x86-64 chromium

在此处输入图片说明我正在调试一个带有修改过的boringssl 的chrome。它总是有一个SegmentFault。我发现问题是

EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
Run Code Online (Sandbox Code Playgroud)

反汇编代码:

callq EC_KEY_get0_group
mov %eax,%edi
callq EC_GROUP_get_curve_name
Run Code Online (Sandbox Code Playgroud)

的返回类型EC_KEY_get0_group是一个EC_GROUP*指针,但它是EC_GROUP_get_curve_name由一个 32 位寄存器传递给的。

指针被截断并导致 SegmentFault。为什么编译器会生成这样的代码?是否有任何编译器选项可以避免这种情况?

Mic*_*tch 6

我可以提供跟踪问题的指导,但不能为您的问题提供具体答案,因为我没有使用 BoringSSL 的修改版本。


如果您没有C函数的原型,那么所有参数和返回值都将默认为一个int类型。该函数将被视为有未指定数量的参数。

让我印象深刻的第一件事是mov $0, %al在每个函数调用之前。这向我表明这些函数要么是可变参数的,要么是无原型的。64 位 Linux 使用的AMD64 System V ABI是这样描述AL寄存器的:

对于可能调用使用 varargs 或 stdargs 的函数的调用(无原型调用或调用在声明中包含省略号 (. . ) 的函数), %al 用作隐藏参数来指定使用的向量寄存器的数量。

我们可以排除它们是可变参数,因为它们的原型应该是这样的:

int EC_GROUP_get_curve_name(const EC_GROUP *group);
const EC_GROUP *EC_KEY_get0_group(const EC_KEY *key);
Run Code Online (Sandbox Code Playgroud)

由于这些函数不是可变参数(不要使用...),因此您的代码中可能有某些内容没有使这些函数的原型可用。


我们可以通过这些简单的 C 函数调用看到相同的行为:

testfunc.c :

#include <stdlib.h>

typedef struct ec_group_st EC_GROUP;
typedef struct ec_key_st EC_KEY;

int EC_GROUP_get_curve_name(const EC_GROUP *group);
const EC_GROUP *EC_KEY_get0_group(const EC_KEY *key);

int testfun()
{
    EC_KEY *ec = NULL;
    return EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
}
Run Code Online (Sandbox Code Playgroud)

如果我使用 GCC 编译gcc -Wall -Wextra -c -O3 testfunc.c -o testfunc.o并使用objdump -D testfunc.o它查看生成的代码,它看起来像:

0000000000000000 <testfun>:
   0:   48 83 ec 08             sub    $0x8,%rsp
   4:   31 ff                   xor    %edi,%edi
   6:   e8 00 00 00 00          callq  b <testfun+0xb>
   b:   48 83 c4 08             add    $0x8,%rsp
   f:   48 89 c7                mov    %rax,%rdi
  12:   e9 00 00 00 00          jmpq   17 <testfun+0x17>
Run Code Online (Sandbox Code Playgroud)

上面的代码似乎是正确的,因为第一个函数调用的 64 位返回值(RAX 中的指针)按预期传递给第二个函数调用。该代码也没有将AL设置为零。

但是,如果我采用相同的代码并注释掉函数的原型,如下所示:

#include <stdlib.h>

typedef struct ec_group_st EC_GROUP;
typedef struct ec_key_st EC_KEY;

/*int EC_GROUP_get_curve_name(const EC_GROUP *group);
const EC_GROUP *EC_KEY_get0_group(const EC_KEY *key);*/

int testfun()
{
    EC_KEY *ec = NULL;
    return EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
}
Run Code Online (Sandbox Code Playgroud)

我得到这个生成的代码:

0000000000000000 <testfun>:
   0:   48 83 ec 08             sub    $0x8,%rsp
   4:   31 ff                   xor    %edi,%edi
   6:   31 c0                   xor    %eax,%eax
   8:   e8 00 00 00 00          callq  d <testfun+0xd>
   d:   48 83 c4 08             add    $0x8,%rsp
  11:   89 c7                   mov    %eax,%edi
  13:   31 c0                   xor    %eax,%eax
  15:   e9 00 00 00 00          jmpq   1a <testfun+0x1a>
Run Code Online (Sandbox Code Playgroud)

现在我们有RAXAL是的低8位RAX)被设为零,并从第一个函数调用的返回值被视为32位int,类似于您所看到的行为。我建议至少构建您的C文件-Wall -Wextra以查看更多种类的警告。在没有原型的代码的情况下,我的编译器抛出了这些警告:

testfunc.c: In function ‘testfun’:
testfunc.c:12:12: warning: implicit declaration of function ‘EC_GROUP_get_curve_name’ [-Wimplicit-function-declaratio
]
     return EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
            ^~~~~~~~~~~~~~~~~~~~~~~
testfunc.c:12:36: warning: implicit declaration of function ‘EC_KEY_get0_group’ [-Wimplicit-function-declaration]
     return EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));
                                    ^~~~~~~~~~~~~~~~~
Run Code Online (Sandbox Code Playgroud)

我会在您的构建输出中寻找关于隐式声明的类似警告,并在您的代码中验证函数原型是否已正确包含。