【发布时间】:2021-06-19 11:50:36
【问题描述】:
我正在将代码移植到现代编译器,不幸的是,当从 Rust 调用 FFI 函数到 C++ 时,遇到了一个微妙的分段错误。
堆栈跟踪显示,在转换到 C++ 后,Rust 提供的第一个参数神奇地消失了,这误导了 C++ 使用错误的参数。
代码有点私密,所以我不能在这里发布,但程序集显示了一些有趣的东西:
在 GCC-7 中(代码运行没有问题),汇编的前几行如下所示:
0x0000000000001119 <+0>: push rbp
0x000000000000111a <+1>: mov rbp,rsp
0x000000000000111d <+4>: push r13
0x000000000000111f <+6>: push r12
0x0000000000001121 <+8>: push rbx
0x0000000000001122 <+9>: sub rsp,0x128
0x0000000000001129 <+16>: mov QWORD PTR [rbp-0x118],rdi
0x0000000000001130 <+23>: mov rax,rsi
0x0000000000001133 <+26>: mov rsi,rdx
0x0000000000001136 <+29>: mov rdx,rsi
0x0000000000001139 <+32>: mov QWORD PTR [rbp-0x130],rax
0x0000000000001140 <+39>: mov QWORD PTR [rbp-0x128],rdx
0x0000000000001147 <+46>: mov QWORD PTR [rbp-0x120],rcx
0x000000000000114e <+53>: mov QWORD PTR [rbp-0x140],r8
0x0000000000001155 <+60>: mov QWORD PTR [rbp-0x138],r9
0x000000000000115c <+67>: lea rax,[rbp-0x110]
0x0000000000001163 <+74>: mov rdi,rax
0x0000000000001166 <+77>: call 0x116b
0x000000000000116b <+82>: mov rax,QWORD PTR [rbp-0x128]
0x0000000000001172 <+89>: mov edx,eax
0x0000000000001174 <+91>: mov rcx,QWORD PTR [rbp-0x130]
0x000000000000117b <+98>: lea rax,[rbp-0x110]
0x0000000000001182 <+105>: mov rsi,rcx
0x0000000000001185 <+108>: mov rdi,rax
0x0000000000001188 <+111>: call 0x118d
但是,使用 GCC-8/9/10,程序集变成了
0x0000000000001125 <+0>: push rbp
0x0000000000001126 <+1>: mov rbp,rsp
0x0000000000001129 <+4>: push r12
0x000000000000112b <+6>: push rbx
0x000000000000112c <+7>: sub rsp,0x120
0x0000000000001133 <+14>: mov QWORD PTR [rbp-0x108],rdi
0x000000000000113a <+21>: mov QWORD PTR [rbp-0x110],rsi
0x0000000000001141 <+28>: mov QWORD PTR [rbp-0x120],rdx
0x0000000000001148 <+35>: mov QWORD PTR [rbp-0x118],rcx
0x000000000000114f <+42>: mov QWORD PTR [rbp-0x128],r8
0x0000000000001156 <+49>: mov QWORD PTR [rbp-0x130],r9
0x000000000000115d <+56>: lea rax,[rbp-0x100]
0x0000000000001164 <+63>: mov rdi,rax
0x0000000000001167 <+66>: call 0x116c
0x000000000000116c <+71>: mov rax,QWORD PTR [rbp-0x118]
0x0000000000001173 <+78>: mov edx,eax
0x0000000000001175 <+80>: mov rcx,QWORD PTR [rbp-0x120]
0x000000000000117c <+87>: lea rax,[rbp-0x100]
0x0000000000001183 <+94>: mov rsi,rcx
0x0000000000001186 <+97>: mov rdi,rax
0x0000000000001189 <+100>: call 0x118e
所以逻辑几乎相同移位量改变?我觉得这种行为很奇怪。
参数列表类似于:
struct Opaque {
char _inner[0];
}
struct View {
char * base;
size_t len;
}
ReturnType ffi_function(Opaque*, View, uint64_t, View, uint64_t, uint64_t);
我还通过x/-100xg $rbp倾倒了堆栈
0x7fe7a67d41c0: 0x00007fe7a67d41f0 0x0000000014c1b1dc
0x7fe7a67d41d0: 0x000000000000003e 0x0000000000000006
0x7fe7a67d41e0: 0x00007fe7a67d4330 0x00007fe88714b540
0x7fe7a67d41f0: 0x0000000000201000 0x0000000013bd0df1
0x7fe7a67d4200: 0x0000000000000001 0x00007fe7a5e2f4e0
0x7fe7a67d4210: 0x000000000000003e 0x000000000000003b
0x7fe7a67d4220: 0x00007fe7a5e01080 0x00007ffebbd1ddb0
0x7fe7a67d4230: 0x000000001602ff80 0x0000000000000000
0x7fe7a67d4240: 0x0000000000000000 0x0000000000000000
0x7fe7a67d4250: 0x0000000000000000 0x0000000018d59680
0x7fe7a67d4260: 0x0000000018d59680 0x0000000000000000
0x7fe7a67d4270: 0x0000000000000000 0x0000000000000000
0x7fe7a67d4280: 0x00007fe700000000 0x0000000000000000
0x7fe7a67d4290: 0x00007fe7a5e2f4e0 0x00007fe88f1630ed
0x7fe7a67d42a0: 0x00007fe7a67d42f8 0x00007fe7a5e2f4e0
0x7fe7a67d42b0: 0x00007fe7a5e2f4e0 0x00007fe88f0d5f42
0x7fe7a67d42c0: 0x00007fe7a5e2f4e0 0x00007fe7a67d43f0
0x7fe7a67d42d0: 0x00007fe7a67d43f0 0x00007fe88f0ffa04
0x7fe7a67d42e0: 0x0000000000000001 0x0000000000000001
0x7fe7a67d42f0: 0x00007fe7a67d43f0 0x00007fe7a5e2f4e0
0x7fe7a67d4300: 0x00007fe7a5e2f4e0 0x0000000000000001
0x7fe7a67d4310: 0x00007fe7a67d43f0 0x00007fe88f0cf1cf
0x7fe7a67d4320: 0x0000000000000006 0x00007fe88714b540
现在,0x00007ffebbd1ddb0 似乎是第一个参数的正确值,但 C++ 将其读取为 0x00007fe7a5e01080
更新:
就在callq之后,在执行push %rbp之前:
这是我从x/-100xg $rsp ($rsp = 0x7fff0b7d4338) 那里得到的信息
0x7fff0b7d4108: 0x00007fff0ae01080 0x000000000000003e
0x7fff0b7d4118: 0x0000003e00000060 0x00007fff0b7d4ab8
0x7fff0b7d4128: 0x00007fff0b7d4310 0x00007fff0b7d4310
0x7fff0b7d4138: 0x00007fff00000004 0x00007fff0b7d41b8
0x7fff0b7d4148: 0x00007fff0ae2f480 0x00007fff00000004
0x7fff0b7d4158: 0x00007fff0b7d41b8 0x00007fff0ae2f480
0x7fff0b7d4168: 0x0000000000000004 0x0000000000000000
0x7fff0b7d4178: 0x00007fff0b7d41b8 0x00007fff0ae2f498
0x7fff0b7d4188: 0x0000000000000000 0x00007fff0ae2f498
0x7fff0b7d4198: 0x00007ffff3d8219e 0x00007fff0b7d41b8
0x7fff0b7d41a8: 0x00007ffff3da157f 0x00007fff0ae01080
0x7fff0b7d41b8: 0x000000000000003e 0x000000000000003e
0x7fff0b7d41c8: 0x0000000000000002 0x000000000000003e
0x7fff0b7d41d8: 0x00007ffff5d4326d 0x00007fff0ae01080
0x7fff0b7d41e8: 0x000000000000003e 0x00007fff0b7d41b0
0x7fff0b7d41f8: 0x00007fff0ae01080 0x000000000000003e
0x7fff0b7d4208: 0x000000000000003e 0x0000000000000004
0x7fff0b7d4218: 0x00007fff0ae2f480 0x00007fff0ae2f498
0x7fff0b7d4228: 0x0000000000000004 0x00007fff0ae2f480
0x7fff0b7d4238: 0x00007fff0ae2f498 0x00007fff0ae2f480
0x7fff0b7d4248: 0x0000000000000004 0x0000000000000001
0x7fff0b7d4258: 0x00007fff0b7fe2a8 0x00007fff0ae2f480
0x7fff0b7d4268: 0x0000000000000004 0x00007fff0ae2f498
0x7fff0b7d4278: 0x00007fff0ae2f498 0x00007fff0ae2f4e0
0x7fff0b7d4288: 0x0000000000000000 0x00007fff0ae2f4e0
0x7fff0b7d4298: 0x00007ffff3e270ed 0x00007fff0b7d42f8
0x7fff0b7d42a8: 0x00007fff0ae2f4e0 0x00007fff0ae2f4e0
0x7fff0b7d42b8: 0x00007ffff3d99f42 0x00007fff0ae2f4e0
0x7fff0b7d42c8: 0x00007fff0b7d43f0 0x00007fff0b7d43f0
0x7fff0b7d42d8: 0x00007ffff3dc3a04 0x0000000000000001
0x7fff0b7d42e8: 0x0000000000000001 0x00007fff0b7d43f0
0x7fff0b7d42f8: 0x00007fff0ae2f4e0 0x00007fff0ae2f4e0
0x7fff0b7d4308: 0x0000000000000001 0x00007fff0b7d43f0
0x7fff0b7d4318: 0x00007ffff3d931cf 0x00007fff0ae2f4e0
0x7fff0b7d4328: 0x0000000000000001 0x00007fff0b7d43f0
和100xg:
0x7fff0b7d4338: 0x00007ffff3dc42fe 0x0000000000000007
0x7fff0b7d4348: 0x0000000000000006 0x00007ffff637393e
0x7fff0b7d4358: 0x00007fff0ae2f480 0x00007fff0b7d4388
0x7fff0b7d4368: 0x00007fff0ae2f480 0x0000000000000060
0x7fff0b7d4378: 0x00007fff0ae01080 0x000000000000003e
0x7fff0b7d4388: 0x0000000000000001 0x00007fff0ae2f4e0
0x7fff0b7d4398: 0x000000000000003e 0x00007fff0ae01080
0x7fff0b7d43a8: 0x0000000013bd0d88 0x00007fffffffbfe0
0x7fff0b7d43b8: 0x00007fff0b7d4420 0x00007fffffffbfa8
0x7fff0b7d43c8: 0x00007fffffffbf50 0x00007fff0b7d4ab8
0x7fff0b7d43d8: 0x000000000000003b 0x0000000000000007
0x7fff0b7d43e8: 0x0000000000000006 0x00007fff0ae2f4e0
0x7fff0b7d43f8: 0x0000000000000004 0x0000000000000001
0x7fff0b7d4408: 0x00007fff0ae2f480 0x0000000000000004
0x7fff0b7d4418: 0x0000000000000001 0x00007fff0ae01080
0x7fff0b7d4428: 0x000000000000003e 0x000000000000003e
0x7fff0b7d4438: 0x00007fffffffbf50 0x00007fff0b7d4ab8
0x7fff0b7d4448: 0x000000000000003b 0x0000000000000007
0x7fff0b7d4458: 0x0000000000000006 0x00007fff0ae2f2a0
0x7fff0b7d4468: 0x000000000000003e 0x00007fff0ae01080
0x7fff0b7d4478: 0x000000000000003e 0x00007fff0ae2f4e0
0x7fff0b7d4488: 0x0000000000000001 0x00007fff0b7d45b0
0x7fff0b7d4498: 0x000001ff0ae2f2a0 0x00007fff0ae2f480
0x7fff0b7d44a8: 0x0000000000000000 0x00007ffff7bb2c60
0x7fff0b7d44b8: 0x00007ffff3daebc6 0x0000000000201000
0x7fff0b7d44c8: 0x0000000000000000 0x00007fffebd4f080
0x7fff0b7d44d8: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d44e8: 0x0100000000010301 0x00007fff0ae2f2a0
0x7fff0b7d44f8: 0x000000000000003e 0x000000000000003e
0x7fff0b7d4508: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d4518: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d4528: 0x00007fff0ae2f2a0 0x0000000060cc0baf
0x7fff0b7d4538: 0x00007fff0ae32180 0x00007fffffffbf50
0x7fff0b7d4548: 0x0000000000000000 0x00007fff0ae32240
0x7fff0b7d4558: 0x00007fff0ae32000 0x0000000000000006
0x7fff0b7d4568: 0x0000000000000007 0x000000000000003b
0x7fff0b7d4578: 0x00007fff0ae3e000 0x00007fff0b7d50b0
0x7fff0b7d4588: 0x00007fff0b7d50b0 0x00007fff0b7d4ab8
0x7fff0b7d4598: 0x00007fff0ae2f480 0x0000000000000004
0x7fff0b7d45a8: 0x0000000000000001 0x00007fff0ae32240
0x7fff0b7d45b8: 0x00007fff0ae32240 0x0000000000000000
0x7fff0b7d45c8: 0x00007fff0ae2f2a0 0x000000000000003e
0x7fff0b7d45d8: 0x0000000000000001 0x00007fff0ae2f480
0x7fff0b7d45e8: 0x0000000000000004 0x0000000000000001
0x7fff0b7d45f8: 0x0000000000000000 0x00007fff0ae3e000
0x7fff0b7d4608: 0x000000000000003b 0x0000000000000007
0x7fff0b7d4618: 0x0000000000000006 0x00007fffebda59d0
0x7fff0b7d4628: 0x00007ffff21bf5cd 0x00007fff0ae32180
0x7fff0b7d4638: 0x00007fff0ae32180 0x00007fff0ae32180
0x7fff0b7d4648: 0x0000000000000000 0x00007fff0b7d4858
C++的帧信息:
called by frame at 0x7fff0b7d44c0
source language c++.
Arglist at 0x7fff0b7d4330, args: server=0x7fff0ae2f498, region_buff=..., peer_id=62, snaps=..., index=62, term=140737324202302
Locals at 0x7fff0b7d4330, Previous frame's sp is 0x7fff0b7d4340
Saved registers:
rip at 0x7fff0b7d4338
预期的第一个参数:0x7fffffffbfe0
预期的第二个参数:(0x7fff0ae01080, 0x3e)
预期的第三个参数:0x3b
预期的第四个参数:(0x7fff0ae2f4e0, 1)
预计第 5、6 个参数:7、6
【问题讨论】:
-
为什么您认为您的论点缺失?参数可能通过寄存器传入(如果它是堆栈,您会看到 rbp 的正偏移量)。编译器通常会在版本之间生成不同的汇编指令,这并不意味着编译器存在问题。很可能,问题出在 c++ 代码中。
-
第一个参数(
Opaque*,在从 rust 转换到 c++ 之后)变为arg2.base等等。参数列表似乎发生了变化。 -
其实我怀疑 C++ 代码中存在 UB,因为使用相同的 rust 共享库,gcc-7 编译的代码运行流畅,但没有其他编译器的可执行文件。但是在段错误的位置(C++ 使用
arg2.len作为地址,应该是base字段),我的观察是参数转移错误。 -
我期待
extern "C"所以编译器使用 C ABI。 -
我怀疑 gcc 8/9/10 都有一个导致此问题的重大错误。更有可能您的 c++ 代码不符合正确创建 c API 的要求,而对于 gcc 7,您很幸运它创建了一个可以工作的二进制文件。