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

问题描述

enter image description here

我正在使用改良的boringssl调试铬。它总是出现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的返回类型是EC_GROUP*指针,但是它是由32位寄存器传递给EC_GROUP_get_curve_name的。

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

解决方法

由于我没有使用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);

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


我们可以通过以下简单的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    $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>

上面的代码似乎正确无误,因为第一个函数调用的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));
}

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

0000000000000000 <testfun>:
   0:   48 83 ec 08             sub    $0x8,%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>

现在我们将 RAX AL RAX 的低8位)设置为零,并且从第一个函数返回值呼叫被视为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));
                                    ^~~~~~~~~~~~~~~~~

我会在生成的输出中寻找有关隐式声明的类似警告,并在代码中验证是否正确包含了函数原型。