为什么编译器在这种情况下使用 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>
现在我们有 RAX(AL 是 RAX 的低 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));
^~~~~~~~~~~~~~~~~
我会在您的构建输出中寻找关于隐式声明的类似警告,并在您的代码中验证是否已正确包含函数原型。
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>
现在我们有 RAX(AL 是 RAX 的低 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));
^~~~~~~~~~~~~~~~~
我会在您的构建输出中寻找关于隐式声明的类似警告,并在您的代码中验证是否已正确包含函数原型。