内容简介:这个bug的诞生源于项目中使用了一个开源C库。由于对该C库API不熟悉,一个不起眼的错误调用,导致一系列诡异的问题。最终经过调试,我们发现发生了内存覆盖问题。为了直达问题根节,我将问题代码简化如下如果只是简单看一下main函数,可以认为输出是然而实际输出是
这个bug的诞生源于项目中使用了一个开源C库。由于对该C库API不熟悉,一个不起眼的错误调用,导致一系列诡异的问题。最终经过调试,我们发现发生了内存覆盖问题。为了直达问题根节,我将问题代码简化如下 (转载请指明出于breaksoftware的csdn博客)
#include <iostream>
#include <stdarg.h>
enum type {
PARAM,
RESULT
};
void set_zero(type t, ...) {
va_list arg;
va_start(arg, t);
if (PARAM == t) {
long* param_longp = va_arg(arg, long *);
*param_longp = 0;
} else {
int* param_intp = va_arg(arg, int *);
*param_intp = 0;
}
va_end(arg);
}
int main() {
int x = 1;
int y = 2;
set_zero(PARAM, &y);
std::cout << "x = " << x << "; y = " << y << std::endl;
return 0;
}
如果只是简单看一下main函数,可以认为输出是
x = 1; y = 0
然而实际输出是
x = 0; y = 0
是不是很诡异?我们在main函数中只是把y的值从2修改成0,根本没有“动”过x变量。但是最终x的值变成了0。
由于示例足够简单,我们可以通过阅读源码来定位问题。第26行传递的参数y是4个字节的int类型。而在第13行,发现参数被当成8个字节的long类型设置为0,这样就覆盖了y空间之后的4个字节。而x变量正好在内存上位于y变量之后,这样x的值也会被改成0。
现实中,我们的场景比较复杂,最终我们通过GDB来确定该问题。其过程大致如下
Reading symbols from ./test...done. (gdb) b 26 Breakpoint 1 at 0xb0a: file main.cpp, line 26. (gdb) r Starting program: /home/fangliang/projects/test_cover/test Breakpoint 1, main () at main.cpp:26 26 set_zero(PARAM, &y); (gdb) p &x $1 = (int *) 0x7fffffffe434 (gdb) p x $2 = 1 (gdb) p &y $3 = (int *) 0x7fffffffe430 (gdb) p y $4 = 2 (gdb) x/2x &y 0x7fffffffe430: 0x00000002 0x00000001 (gdb) awatch x Hardware access (read/write) watchpoint 2: x (gdb) c Continuing. Hardware access (read/write) watchpoint 2: x Old value = 1 New value = 0 set_zero (t=PARAM) at main.cpp:21 21 } (gdb) disas …… 0x0000555555554a64 <+234>: mov -0xd8(%rbp),%rax 0x0000555555554a6b <+241>: movq $0x0,(%rax) => 0x0000555555554a72 <+248>: jmp 0x555555554acb <set_zero(type, ...)+337> …… 0x0000555555554acb <+337>: nop 0x0000555555554acc <+338>: mov -0xb8(%rbp),%rax ---Type <return> to continue, or q <return> to quit---q Quit (gdb) i r rax rax 0x7fffffffe430 140737488348208 (gdb) x/2x 0x7fffffffe430 0x7fffffffe430: 0x00000000 0x00000000
第2行在代码第26行下了断点,为了让我们可以在main函数中查看x、y变量的地址和值。
第10,14和18行可以看出x和y变量的内存空间是连续的。
第19行我们给“莫名”被修改的变量x下了内存读写断点。执行continue后,由于x的值被从1改成0,从而触发了断点。
第30行,我们查看当前代码处的汇编指令。
第33行,是触发内存断点,即x的值被修改的位置。movq是给8个字节赋值,于是我们只要验证rax地址是否就是y变量的地址。
第41行验证了rax地址就是y变量地址,从而可以证明就是movq $0x0,(%rax)导致x变量值被改变。
第43行,我们查看此时x和y的内存空间的值,它们已经都是0了。
如果我们把set_zero方法改成针对y变量的函数
void set_param(long* param_longp) {
*param_longp = 0;
}
这样如果我们给其传递int型变量,编译器就会报错
main.cpp: In function ‘int main()’: main.cpp:30:14: error: cannot convert ‘int*’ to ‘long int*’ for argument ‘1’ to ‘void set_param(long int*)’ set_param(&y);
而使用可变长参数则正好掩盖了该问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Rationality for Mortals
Gerd Gigerenzer / Oxford University Press, USA / 2008-05-02 / USD 65.00
Gerd Gigerenzer's influential work examines the rationality of individuals not from the perspective of logic or probability, but from the point of view of adaptation to the real world of human behavio......一起来看看 《Rationality for Mortals》 这本书的介绍吧!