内容简介:这个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);
而使用可变长参数则正好掩盖了该问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。