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

Why compiler use a 32bit register to pass a pointer to a function on a amd64 linux in this situation

我正在调试修改后的 chromium boringssl.It 总是出现 SegmentFault。 我发现问题是

EC_GROUP_get_curve_name(EC_KEY_get0_group(ec));

反汇编代码:

callq EC_KEY_get0_group
mov %eax,%edi
callq EC_GROUP_get_curve_name

EC_KEY_get0_group的return类型是一个EC_GROUP*指针,但是被32位寄存器传给了EC_GROUP_get_curve_name

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

我可以提供追踪问题的指导,但不能对你的问题给出具体的答案,因为我没有使用修改后的 BoringSSL 版本。


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

首先让我印象深刻的是每个函数调用之前的 mov [=17=], %al。这向我暗示这些函数要么是可变的要么是无原型的。 64位Linux使用的AMD64 System V ABI是这样描述AL寄存器的:

For calls that may call functions that use varargs or stdargs (prototype-less calls or calls to functions containing ellipsis (. . . ) in the declaration) %al is used as hidden argument to specify the number of vector registers used.

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

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

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


我们可以通过这些简单的 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));
}

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

0000000000000000 <testfun>:
   0:   48 83 ec 08             sub    [=12=]x8,%rsp
   4:   31 ff                   xor    %edi,%edi
   6:   e8 00 00 00 00          callq  b <testfun+0xb>
   b:   48 83 c4 08             add    [=12=]x8,%rsp
   f:   48 89 c7                mov    %rax,%rdi
  12:   e9 00 00 00 00          jmpq   17 <testfun+0x17>

上面的代码似乎是正确的,因为来自第一个函数调用的 64 位 return 值(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));
}

我得到了这个生成的代码:

0000000000000000 <testfun>:
   0:   48 83 ec 08             sub    [=14=]x8,%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    [=14=]x8,%rsp
  11:   89 c7                   mov    %eax,%edi
  13:   31 c0                   xor    %eax,%eax
  15:   e9 00 00 00 00          jmpq   1a <testfun+0x1a>

现在我们有 RAXALRAX 的低 8 位)设置为零,第一个函数调用的 return 值被视为 32 位 int,这与您看到的行为相似。我建议至少使用 -Wall -Wextra 构建 C 文件以查看更多种类的警告。对于没有原型的代码,我的编译器会抛出这些警告:

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));
                                    ^~~~~~~~~~~~~~~~~

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