这个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: xOld value = 1
New value = 0
set_zero (t=PARAM) at main.cpp:21
21      }
(gdb) disas
……0x0000555555554a64 <+234>:   mov    -0xd8(%rbp),%rax0x0000555555554a6b <+241>:   movq   $0x0,(%rax)
=> 0x0000555555554a72 <+248>:   jmp    0x555555554acb <set_zero(type, ...)+337>
……0x0000555555554acb <+337>:   nop0x0000555555554acc <+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);

而使用可变长参数则正好掩盖了该问题。

bug诞生记——不定长参数隐藏的类型问题相关推荐

  1. bug诞生记——临时变量、栈变量导致的双杀

    这是<bug诞生记>的第一篇文章.本来想起个文艺点的名字,比如<Satan(撒旦)来了>,但是最后还是想让这系列的重心放在"bug的产生过程"和" ...

  2. python3 函数 不定长参数 不定参

    第一种不定长参数*args *args 称为不定长参数,只能放在形参的最后位置,返回的是一个元组 def num(a,b,*args):print(a)print(b)print(args)num(1 ...

  3. 不定长参数的装包与拆包

    #转载请联系 def task(a,b,c,*args,**kwargs):print(a)print(b)print(c)print(args)print(kwargs)task(1,2,3,4,5 ...

  4. python不定长参数怎么相加_python函数不定长参数使用方法解析

    这篇文章主要介绍了python函数不定长参数使用方法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 pathon中的函数可以使用不定长参数,可以 ...

  5. python不定长参数详解

    不定长参数 如果想要一个函数能不固定接收任意多个参数,可以使用不定长参数. 1.不定长参数两种基本形式: python自定义函数中有两种不定长参数,第一种是*XXX,在传入额外的参数时可以不用指明参数 ...

  6. python笔记之函数参数(缺省参数,命名参数,不定长参数)

    缺省参数 函数中定义带有初始值的形参 参数调用时,缺省参数可传,可不传 缺省参数一定在参数列表的最后面 缺省参数的数量没有限制 def x_y_sum(x,y=20): #缺省参数要在参数列表的最后p ...

  7. java 不定参数方法_java中不定长参数的使用方法

    java中不定长参数的使用方法 不定长参数方法的语法如下:返回值 方法名(参数类型...参数名称) 在参数列表中使用"..."形式定义不定长参数,其实这个不定长参数a就是一个数组, ...

  8. C语言 函数不定长参数 ##__VA_ARGS__经典案例 - C语言零基础入门教程

    目录 一.##__VA_ARGS__简介 二.##__VA_ARGS__经典案例 三.猜你喜欢 零基础 C/C++ 学习路线推荐 : C/C++ 学习目录 >> C 语言基础入门 一.## ...

  9. C语言 函数不定长参数 - C语言零基础入门教程

    目录 一.前言 二.函数不定长参数简介 1.va_start 2.va_arg 3.va_end 三.自定义不定长参数的函数 1.va_start/va_arg/va_end 案例一 2.va_sta ...

最新文章

  1. ASP.NET 父页面取子页面的值
  2. 鸿蒙开发-从搭建todolist待办事项来学习组件与js之间的交互
  3. layui 行悬停显示工具_Minitab | 工具栏和状态栏
  4. 算法测试—机器学习算法评价指标
  5. 小程序WXML基本使用
  6. PrimeFaces Mobile入门
  7. Leetcode836.Rectangle Overlap矩阵重叠
  8. SQL语法集锦一:SQL语句实现表的横向聚合
  9. centos7 mysql 数据库备份与还原
  10. php 调试环境配置
  11. 中国公用计算机互联网网络简称为什么,中国公用计算机互联网国际联网管理办法...
  12. Pwned Vulnhub
  13. CSharpGL(13)用GLSL实现点光源(point light)和平行光源(directional light)的漫反射(diffuse reflection)...
  14. Application provided invalid, non monotonically increasing dts to muxer in stream
  15. Python实现二维码、条形码识别
  16. java异常之-ClassNotFoundException: .......web.context.ContextLoaderServlet
  17. 启发式算法,元启发式算法,超启发式算法
  18. POJ3984迷宫问题
  19. vr分类及其常见类型
  20. 测试之道——阿里巴巴八年测试专家倾情奉献

热门文章

  1. java上传加密_Java上传下载文件并实现加密解密
  2. 缓存成神路:Redis读写分离难以理解?一文解析Redis读写分离技术
  3. 力扣(LeetCode)刷题,简单题(第16期)
  4. 【图像分类案例】(2) DenseNet 天气图片四分类(权重迁移学习),附Tensorflow完整代码
  5. mysql通过参数文件启动_mysql启动的时参数文件中的[mysql]下的参数没有生效
  6. 【小心勿喷,吃饭中的小朋友最好别看】史上最搞笑的前端vue文件命名,没有之一,呵呵哒
  7. 用Python和项目进行机器学习(初学者) Machine Learning A-Z with Python with Project (Beginner)
  8. visual-reasoning 笔记
  9. 开源分布式Job系统,调度与业务分离-如何创建一个计划HttpJob任务
  10. Sql语法---DDL