一个误用snprintf的bug分析

2024-02-06 12:38
文章标签 分析 bug 误用 snprintf

本文主要是介绍一个误用snprintf的bug分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

         转载地址: http://blog.csdn.net/wuchun/article/details/38455609


         

前言

snprintf函数的功能是格式化输出到字符串中,函数原型为:

int snprintf(char *str, size_t size, const char *fomat, ...)

正常来说,只要会用printf函数写输出语句,应该都能会用该函数。但是,稍有不留神,很可能踩到其中的坑。现将前段时间遇到的一个问题进行简单地分析。

问题

以下是抽取出来的问题代码段:

[cpp]  view plain copy
  1. #include <stdio.h>  
  2. #include <iostream>  
  3.   
  4. class A  
  5. {  
  6.     public:  
  7.         int64_t b;  
  8.         char* pstr;  
  9.         int a;  
  10.   
  11. };  
  12.   
  13. int main()  
  14. {  
  15.     char source[128] = "hello world";  
  16.     char tstr[128] = "\0";  
  17.     A a;  
  18.     a.a=0x0001;  
  19.     a.b=0x00030004;  
  20.     a.pstr = source;  
  21.   
  22.     snprintf(tstr, sizeof(tstr), "b=%ld,str=%s,a=%d\n", a.b, a.pstr, a.a);  
  23.     printf("%s",tstr);  
  24.     //printf("%s\nsource address:%ld\n",tstr, source); //查找问题时增加  
  25.   
  26.     return 0;  
  27. }  
如上所示,功能比较简单,就是使用snprintf函数将类A中的变量值格式化输出到tstr字符串中。

首先将其编译成64位程序:(注:本文中所用gcc版本为4.1.2)

g++ -g -m64 -o tsn tsn.cpp

运行tsn,结果为:

b=196612,str=hello world,a=1

一切正常。

但当用g++ -g -m32 -o tsn tsn.cpp将其编译为32位程序后,问题出现了,运行tsn后,结果为:

b=196612,str=(null),a=-2270288

从结果来看,应该是a.pstr和a.a引用的地址出了问题,于是在代码中增加了一行语句,打印a.pstr所指向的source地址,修改后,编译成32位,运行结果如下:

b=196612,str=(null),a=-2270288

source address:-2270288

从结果发现a的值变成了source的地址,这时突然意识到snprintf是一个可变参数的函数,其应该也是使用va_arg来读取堆栈中可变参数,而va_arg来读取参数时,是按照指定大小来访问,很可能是指定的访问大小出现了问题所导致(注:snprintf函数内部是否通过va_arg来实现,本人并未进行研究,只是在找问题时猜测其可能性),果然,发现在A中的变量b定义的是int64_t,而在格式化输出时用的却是%ld,正确地应当使用%lld。

分析

问题看似找到了,而且也解决了,但是为什么将其编译64位时不会出现问题呢?于是决定查看二者的汇编指令差别在哪里。

首先,使用objdump -S tsn,生成64位程序汇编指令,去掉不相关内容后,如下所示

[cpp]  view plain copy
  1. 4007f3: c7 45 f0 01 00 00 00    movl   $0x1,0xfffffffffffffff0(%rbp)  
  2. 4007fa: 48 c7 45 e0 04 00 03    movq   $0x30004,0xffffffffffffffe0(%rbp)  
  3. 400801: 00   
  4. 400802: 48 8d 85 60 ff ff ff    lea    0xffffffffffffff60(%rbp),%rax  
  5. 400809: 48 89 45 e8             mov    %rax,0xffffffffffffffe8(%rbp)  
  6. 40080d: 8b 45 f0                mov    0xfffffffffffffff0(%rbp),%eax  
  7. 400810: 48 8b 55 e8             mov    0xffffffffffffffe8(%rbp),%rdx  
  8. 400814: 48 8b 4d e0             mov    0xffffffffffffffe0(%rbp),%rcx  
  9. 400818: 48 8d bd e0 fe ff ff    lea    0xfffffffffffffee0(%rbp),%rdi  
  10. 40081f: 41 89 c1                mov    %eax,%r9d  
  11. 400822: 49 89 d0                mov    %rdx,%r8  
  12. 400825: ba 58 09 40 00          mov    $0x400958,%edx  
  13. 40082a: be 80 00 00 00          mov    $0x80,%esi  
  14. 40082f: b8 00 00 00 00          mov    $0x0,%eax  
  15. 400834: e8 af fd ff ff          callq  4005e8 <snprintf@plt>   

生成32位汇编指令如下所示:

[cpp]  view plain copy
  1. 80486a8:    c7 45 f4 01 00 00 00    movl   $0x1,0xfffffff4(%ebp)  
  2. 80486af:    c7 45 e8 04 00 03 00    movl   $0x30004,0xffffffe8(%ebp)  
  3. 80486b6:    c7 45 ec 00 00 00 00    movl   $0x0,0xffffffec(%ebp)  
  4. 80486bd:    8d 85 68 ff ff ff       lea    0xffffff68(%ebp),%eax  
  5. 80486c3:    89 45 f0                mov    %eax,0xfffffff0(%ebp)  
  6. 80486c6:    8b 4d f4                mov    0xfffffff4(%ebp),%ecx  
  7. 80486c9:    8b 5d f0                mov    0xfffffff0(%ebp),%ebx  
  8. 80486cc:    8b 45 e8                mov    0xffffffe8(%ebp),%eax  
  9. 80486cf:    8b 55 ec                mov    0xffffffec(%ebp),%edx  
  10. 80486d2:    89 4c 24 18             mov    %ecx,0x18(%esp)  
  11. 80486d6:    89 5c 24 14             mov    %ebx,0x14(%esp)  
  12. 80486da:    89 44 24 0c             mov    %eax,0xc(%esp)  
  13. 80486de:    89 54 24 10             mov    %edx,0x10(%esp)  
  14. 80486e2:    c7 44 24 08 10 88 04    movl   $0x8048810,0x8(%esp)  
  15. 80486e9:    08   
  16. 80486ea:    c7 44 24 04 80 00 00    movl   $0x80,0x4(%esp)  
  17. 80486f1:    00   
  18. 80486f2:    8d 85 e8 fe ff ff       lea    0xfffffee8(%ebp),%eax  
  19. 80486f8:    89 04 24                mov    %eax,(%esp)  
  20. 80486fb:    e8 b4 fd ff ff          call   80484b4 <snprintf@plt>  

通过以上两段汇编代码发现,在调用snprintf前,64位下并没有将参数push到栈中,而只是将参数存入了寄存器中,由于各个寄存器是相互独立的,所以即使在格式化输出时,指定的参数大小不一致,也能“正常”输出(其实看到的“正常”,是因为b的值较小,仍能使用低32位表示),而在32位下,由于参数进行的是堆栈操作,所以当指定大小有误时,输出也就会存在问题:由于b在栈中分配了8个字节,而格式化时输出时,却认为其只有4个字节,从而导致接下来,在取pstr值时,实际上取到的是b值中全为0的高32位,而取a值时,取到的则是pstr的值。

想到这里时,想起以前看过一篇文章《X86-64寄存器和栈帧》,以及在《深入理解计算机系统》的部分章节中均有相关介绍。因此,也进一步佐证了前面的分析应该是正确的。

总结

1、本文中所提到的问题,在所有通过va_start函数实现的可变参数函数中,应该都会存在。因为在含有可变参数的函数缺少对类型安全性的检查。

2、体系结构的不一样,对同一个函数的行为,会产生较大地区别。


这篇关于一个误用snprintf的bug分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/684342

相关文章

Redis连接失败:客户端IP不在白名单中的问题分析与解决方案

《Redis连接失败:客户端IP不在白名单中的问题分析与解决方案》在现代分布式系统中,Redis作为一种高性能的内存数据库,被广泛应用于缓存、消息队列、会话存储等场景,然而,在实际使用过程中,我们可能... 目录一、问题背景二、错误分析1. 错误信息解读2. 根本原因三、解决方案1. 将客户端IP添加到Re

Redis主从复制实现原理分析

《Redis主从复制实现原理分析》Redis主从复制通过Sync和CommandPropagate阶段实现数据同步,2.8版本后引入Psync指令,根据复制偏移量进行全量或部分同步,优化了数据传输效率... 目录Redis主DodMIK从复制实现原理实现原理Psync: 2.8版本后总结Redis主从复制实

锐捷和腾达哪个好? 两个品牌路由器对比分析

《锐捷和腾达哪个好?两个品牌路由器对比分析》在选择路由器时,Tenda和锐捷都是备受关注的品牌,各自有独特的产品特点和市场定位,选择哪个品牌的路由器更合适,实际上取决于你的具体需求和使用场景,我们从... 在选购路由器时,锐捷和腾达都是市场上备受关注的品牌,但它们的定位和特点却有所不同。锐捷更偏向企业级和专

Spring中Bean有关NullPointerException异常的原因分析

《Spring中Bean有关NullPointerException异常的原因分析》在Spring中使用@Autowired注解注入的bean不能在静态上下文中访问,否则会导致NullPointerE... 目录Spring中Bean有关NullPointerException异常的原因问题描述解决方案总结

python中的与时间相关的模块应用场景分析

《python中的与时间相关的模块应用场景分析》本文介绍了Python中与时间相关的几个重要模块:`time`、`datetime`、`calendar`、`timeit`、`pytz`和`dateu... 目录1. time 模块2. datetime 模块3. calendar 模块4. timeit

python-nmap实现python利用nmap进行扫描分析

《python-nmap实现python利用nmap进行扫描分析》Nmap是一个非常用的网络/端口扫描工具,如果想将nmap集成进你的工具里,可以使用python-nmap这个python库,它提供了... 目录前言python-nmap的基本使用PortScanner扫描PortScannerAsync异

Oracle数据库执行计划的查看与分析技巧

《Oracle数据库执行计划的查看与分析技巧》在Oracle数据库中,执行计划能够帮助我们深入了解SQL语句在数据库内部的执行细节,进而优化查询性能、提升系统效率,执行计划是Oracle数据库优化器为... 目录一、什么是执行计划二、查看执行计划的方法(一)使用 EXPLAIN PLAN 命令(二)通过 S

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57