2019独角兽企业重金招聘Python工程师标准>>>

BUG产生背景

项目开发中的在对网络的IP等地址进行协议封装的处理过程中,我使用了如下的一段代码:

buff[0] = content->res_network_params.up_res_status;
sscanf(content->res_network_params.ip,"%d.%d.%d.%d",buff+1,buff+2,buff+3,buff+4);
sscanf(content->res_network_params.mask,"%d.%d.%d.%d",buff+5,buff+6,buff+7,buff+8);
sscanf(content->res_network_params.gateway,"%d.%d.%d.%d",buff+9,buff+10,buff+11,buff+12);
sscanf(content->res_network_params.dns1,"%d.%d.%d.%d",buff+13,buff+14,buff+15,buff+16);
sscanf(content->res_network_params.dns2,"%d.%d.%d.%d",buff+17,buff+18,buff+19,buff+20);buff[21] = content->res_network_params.web_port&0xff;buff[22] = (content->res_network_params.web_port>>8)&0xff;buff[23] = content->res_network_params.video_port&0xff;buff[24] = (content->res_network_params.video_port>>8)&0xff;buff[25] = content->res_network_params.rtsp_port&0xff;buff[26] = (content->res_network_params.rtsp_port>>8)&0xff;buff[27] = content->res_network_params.upnp_flag;buff[28] = content->res_network_params.dhcp_flag;

其中buff的数据类型为unsigned char *.这段代码在编译时会产生如下一串的警告:

warning: format ‘%d’ expects argument of type ‘int *’, but argument 3 has type ‘unsigned char *’ [-Wformat]
......

而一开始我因为没有找到合适的格式符就把这些警告给忽略了,同时也因为当我在X86机器上编写代码进行接口测试时他们工作正常,所以没有给予更多的关注。测试结果之一为:

[RE_NET]res = 1,ip:192.168.0.178,mask:255.255.255.0,gw:192.168.0.1,fdns:202.112.20.131,sdns:192.168.0.1
[buff]01 c0 a8 00 b2 ff ff ff 00 c0 a8 00 01 ca 70 14 83 c0 a8 00 01 50 00 54 24 6a 21 00 01

可是当我在开发板(ARM)上运行时,却得到了完全出乎意料的结果,buff完全不对,结果如下,而且每次都是这个结果:

[RE_NET]res = 1,ip:192.168.0.178,mask:255.255.255.0,gw:192.168.0.1,fdns:202.112.20.131,sdns:192.168.0.1
[buff] 00 00 00 ff 00 00 00 a8 00 00 00 70 00 00 00 a8 00 00 00 01 00 50 00 54 24 6a 21 00 01

此为BUG所在,我对这个初看毫无规律的结果几乎毫无头绪,让我头疼了一天多。

BUG定位与解决

现象分析

开发板的运行结果奇怪在不仅没有给buff[1]到buff[20]赋予正确的值,还在于它把buff[0]的值给覆盖掉了,可是buff[21]往后的端口值却仍然是正确的。

问题定位

在花了一天的时间check了整个执行流程没有找到问题后,我才回到上面那段代码,把5句sscanf调用屏蔽,直接用memset(buff+1,0xfe,20);强制赋值,结果发现在开发板上的结果与x86的测试结果一致,正确了。直到此时我才再次想起那串警告,才猜到是sscanf的格式符类型不匹配导致的内存写覆盖问题,于是我写了一个测试程序来验证猜测和分析具体的问题产生过程,最终解决了问题。

测试程序的代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main(int argc, char **argv)
{unsigned char buff[32]={0};int ret = 0,i;char *ip = "192.168.0.178";printf("buff addr[0-31] = %p,%p...%p(30),%p(31)\n",buff,buff+1,buff+30,buff+31);/**< 4,12,20,28 */sscanf(ip,"%d.%d.%d.%d",buff+4,buff+12,buff+20,buff+28);printf("buff value[4,12,20,28]:0x%0x,0x%0x,0x%0x,0x%0x\n",buff[4],buff[12],buff[20],buff[28]);printf("buff value[0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n\n");/**< 4,5,6,7*/memset(buff,0,sizeof(buff));sscanf(ip,"%d.%d.%d.%d",buff+4,buff+5,buff+6,buff+7);printf("buff value[4,5,6,7]:0x%0x,0x%0x,0x%0x,0x%0x\n",buff[4],buff[5],buff[6],buff[7]);printf("buff value[0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n\n");/**< 192.168*/memset(buff,0,sizeof(buff));sscanf("192.","%d.",buff+4);printf("buff value[4(192),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("168.","%d.",buff+5);printf("buff value[5(168),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("1.","%d.",buff+6);printf("buff value[6(1),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("178.","%d.",buff+7);printf("buff value[7(178),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n");sscanf("255.","%d.",buff+8);printf("buff value[8(255),0-32]:\n");sscanf("255.","%d.",buff+8);printf("buff value[8(255),0-32]:\n");for(i=0;i<32;i++){printf("0x%0x ",buff[i]);}printf("\n\n");/**< use inet_addr/inet_network interface convert */in_addr_t addr = inet_addr(ip);printf("addr by inet_addr = [addr]%p,[value]0x%0x, %u\n",&addr, addr, addr);// cast convert address typeunsigned char *paddr = (unsigned char *)&addr;printf("addr by char address: %p,%p,%p,%p\n",paddr,paddr+1,paddr+2,paddr+3);printf("addr by char value: 0x%0x,0x%0x,0x%0x,0x%0x\n",paddr[0],paddr[1],paddr[2],paddr[3]);printf("\n");addr = inet_network(ip);printf("addr by inet_network = [addr]%p,[value]0x%0x, %u\n",&addr, addr, addr);// cast convert address typepaddr = (unsigned char *)&addr;printf("addr by char address: %p,%p,%p,%p\n",paddr,paddr+1,paddr+2,paddr+3);printf("addr by char value: 0x%0x,0x%0x,0x%0x,0x%0x\n",paddr[0],paddr[1],paddr[2],paddr[3]);return 0;
}

在PC上和开发板上的测试结果出来后,问题就一目了然了,PC上的结果都符合预期,这里就不粘贴了,开发板上的测试结果为:

[root@anyka /mnt]$ ./ram_test_arm
buff addr[0-31] = 0xbe82ac08,0xbe82ac09...0xbe82ac26(30),0xbe82ac27(31)
buff value[4,12,20,28]:0xc0,0xa8,0x0,0xb2
buff value[0-32]:
0x0 0x0 0x0 0x0 0xc0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xa8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0buff value[4,5,6,7]:0xb2,0x0,0x0,0x0
buff value[0-32]:
0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0buff value[4(192),0-32]:
0x0 0x0 0x0 0x0 0xc0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
buff value[5(168),0-32]:
0x0 0x0 0x0 0x0 0xa8 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
buff value[6(1),0-32]:
0x0 0x0 0x0 0x0 0x1 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
buff value[7(178),0-32]:
0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
buff value[8(255),0-32]:
0x0 0x0 0x0 0x0 0xb2 0x0 0x0 0x0 0xff 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0addr by inet_addr = [addr]0xbe82ac04,[value]0xb200a8c0, 2986387648
addr by char address: 0xbe82ac04,0xbe82ac05,0xbe82ac06,0xbe82ac07
addr by char value: 0xc0,0xa8,0x0,0xb2addr by inet_network = [addr]0xbe82ac04,[value]0xc0a800b2, 3232235698
addr by char address: 0xbe82ac04,0xbe82ac05,0xbe82ac06,0xbe82ac07
addr by char value: 0xb2,0x0,0xa8,0xc0

问题解决

分析上面的测试结果可以得出问题产生的原因(PC平台和开发板ARM平台均为小端类型):

在开发板上,sscanf函数中的%d格式符导致对它相应的指针变量内存地址写时按照整型(int)的4字节对齐进行,如果sscanf中的地址不是4字节对齐,如buff+5=0xbe82ac0d时,程序会取比该地址小的4字节对齐字节进行写内存,就是上面的buff+4地址,从而导致内存覆盖;而buff+8,buff+12,buff+20,buff+28地址刚好都是4字节对齐地址,所以没有产生内存覆盖现象。

进一步进行分析可以得出根本的原因在于编译器的处理。在PC上使用gcc编译器构建程序可以OK,但是使用交叉编译器arm-none-linux-gnueabi-gcc构建的程序在开发板上却不OK,不同编译器对这样的问题的内存处理是不同的,我们没法做任何假设。

问题原因找到了,解决就简单了,其实解决方法已经在测试代码中展示了,利用unix的网络地址转换接口可以方便地实现字符串地址和二进制地址的转换,同时还能轻松地判断地址是否有效,而没必要自己去实现判断和字符串解析工作。最后的处理代码如下面这样:

struct in_addr ip_addr;
......
if((ret=inet_aton(content->res_network_params.ip,&ip_addr))!=0)
{//inet_aton() returns nonzero if the address is valid, zero if notptmp = (unsigned char *)&ip_addr.s_addr;memcpy(buff+1,ptmp,4);// binary form in network byte order
}
if((ret!=0)&&(ret=inet_aton(content->res_network_params.mask,&ip_addr))!=0)
{ptmp = (unsigned char *)&ip_addr.s_addr;memcpy(buff+5,ptmp,4);
}
......

这里之所以没使用inet_addr接口,是因为它失败时返回的INADDR_NONE (usually -1)值有可能也是有效地址255.255.255.255,即可能会误判。注意inet_addr和inet_aton接口均是产生网络字节序(大端)的二进制,即字符串的高字节放在起始地址,而inet_network接口是产生主机字节序的二进制,主机字节序取决于平台。

思考与学习

从这个bug中,我认识到编译器产生的任何一个警告都不应该轻易或者想当然的去忽略,如果可能则尽量消除警告,否则必须有足够的验证表明该警告不会在实际环境中产生问题才能忽略。

此外,这个案例中我再一次深刻地认识到嵌入式与PC软件开发的区别。嵌入式软件的开发必须时刻注意到平台、编译器和操作系统接口的不同可能导致的问题,开发好的软件在PC平台上验证OK并不能表示在目标平台也一定OK,完整可靠的测试必须基于和生成部署环境真是一致的环境下进行,其结果才可靠和有说服力。

另一种解决方法

既然是由sscanf函数中使用不匹配的格式符引起的问题,那是不是可以通过使用匹配的格式符解决呢?答案是肯定的,只是我之前一致想当然的认为不存在对应与unsigned char或者char变量的输入格式符。通过man sscanf查看手册有如下两个格式修饰符可用:

ConversionsThe following type modifier characters can appear in a conversion specification:h      Indicates  that  the  conversion  will  be one of d, i, o, u, x, X, or n and the next pointer is a pointer to a short int or unsigned short int (rather than int).hh     As for h, but the next pointer is a pointer to a signed char or unsigned char.

因此另一种更简单的解决方法就是将一开始代码中的%d格式符换成%hhu,这样既不会有警告,程序在开发板上也运行正确,代码改动量也小。而我之所以没使用这种方法,是觉得有必要判断一下字符串地址的有效性。

转载于:https://my.oschina.net/shelllife/blog/172273

sscanf函数中类型不匹配警告引发的BUG和思考相关推荐

  1. pack()函数中类型对应的符号标记

    pack (PHP 4, PHP 5) pack - Pack data into binary string Report a bug 说明 string pack ( string $format ...

  2. Windows图形编程 中的一个例程所引发的收获和思考

    作者:朱金灿 来源:http://blog.csdn.net/clever101/ 袁峰大侠著的<Windows图形编程>是一本学习<Windows图形编程>的好书.书中的第二 ...

  3. 一次关于使用status作为变量引发的bug及思考

    这个bug出现在一年前,当时自己大学还没毕业,刚刚进入一家公司实习.那个时候还没有用seajs或者requirejs那样的模块化管理的库,也没有用一个自执行的函数将要执行的代码包裹起来,于是bug就在 ...

  4. 第八章 函数中的类型提示

    应该强调的是,Python 仍将是一种动态类型的语言,即使按照惯例,作者也不希望强制类型提示 --Guido van Rossum, Jukka Lehtosalo, and Łukasz Langa ...

  5. VBA中byref类型不匹配 ByRef Argument Type Mismatch错误

    VBA中子函数调用时出现如下错误:"ByRef Argument Type Mismatch",(参数类型不匹配)代码如下: Function MainFunc()Dim a, b ...

  6. html绑定带有形参的函数,Python中函数参数类型和参数绑定

    参数类型 Python函数的参数类型一共有五种,分别是: POSITIONAL_OR_KEYWORD(位置参数或关键字参数) VAR_POSITIONAL(可变参数) KEYWORD_ONLY(关键字 ...

  7. matlab ubound,关于VB调用MATLAB函数,出现类型不匹配

    我在matlab中建立了一个函数 function   dblRho=FunSatuVaporRho(dblt,dblp) dblt0=[0,0,0,0,0,0,0,0,0,0,... 10,10,1 ...

  8. 《流畅的Python第二版》读书笔记——函数中的类型注解

    引言 这是<流畅的Python第二版>抢先版的读书笔记.Python版本暂时用的是python3.10.为了使开发更简单.快捷,本文使用了JupyterLab. 本章关注于Python在函 ...

  9. R语言str_subset函数和str_which函数:str_subset函数提取字符串向量中所有包含匹配字符的字符串、str_which函数返回字符串向量中所有包含匹配字符的位置(索引)

    R语言str_subset函数和str_which函数:str_subset函数提取字符串向量中所有包含匹配字符的字符串.str_which函数返回字符串向量中所有包含匹配字符的位置(索引) 目录

最新文章

  1. 轻松易懂的缓存雪崩、穿透、击穿以及解决方案
  2. 信息系统项目管理师:论项目的质量管理
  3. Could not find a file system implementation for scheme ‘hdfs‘.
  4. 树的存储结构-孩子兄弟表示法
  5. wget在线扒站程序php源码
  6. sql 注入防护与xss攻击防护
  7. Radware LP 增加线路接口操作
  8. pku1177 Picture(矩形外围总周长)
  9. PF粒子滤波算法理解
  10. Objective-C延迟执行方法总结
  11. [Mysql]WARN: Establishing SSL connection without server's identity verificatio
  12. 用Python搞出自己的云词图 | 【带你装起来】
  13. 《新参者-加贺恭一郎》、《麦田里的守望者》杂记
  14. 浏览器报错:net::ERR_CONTENT_LENGTH_MISMATCH 200 (OK)
  15. 手机电源键失灵无法开机的有效土方法
  16. 快速实现B站(B ili b ili)手机缓存m4s文件转mp4(批量升级版)
  17. 解决谷歌浏览器提示Google账号无法登录提示浏览器或应用不安全问题
  18. C++复习炒剩饭(1)心一意
  19. 计算机是怎么跑起来的——简记
  20. python django考勤签到系统

热门文章

  1. java头像交互式差分演变_一种基于交互式差分进化计算的用户知识需求获取方法与流程...
  2. native字体尺寸自适应 react_react-native中 屏幕以及字体 大小适配
  3. 运营管理最新版史蒂文森_运营管理-史蒂文森stevenson各章课后习题答案
  4. linux 修改时区_【003】一文全面掌握Linux初始化进程(超详细)
  5. vue中 .sync 修饰符 个人理解
  6. 初中计算机知识点 考题,初中信息技术考题规律及趋势_教师资格面试初中信息技术...
  7. 观察者模式重复调用mysql问题,2、观察者模式
  8. Tesseract使用日记
  9. 第九天2017/04/18(2、类的继承、面试题:继承访问修饰符、组合、static、构造、多态)
  10. Batch Normalization导读