问题描述
我正在使用改良的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));
^~~~~~~~~~~~~~~~~
我会在生成的输出中寻找有关隐式声明的类似警告,并在代码中验证是否正确包含了函数原型。