sscanf函数中类型不匹配警告引发的BUG和思考
2019獨角獸企業(yè)重金招聘Python工程師標準>>>
BUG產(chǎn)生背景
項目開發(fā)中的在對網(wǎng)絡的IP等地址進行協(xié)議封裝的處理過程中,我使用了如下的一段代碼:
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的數(shù)據(jù)類型為unsigned char *.這段代碼在編譯時會產(chǎn)生如下一串的警告:
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可是當我在開發(fā)板(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所在,我對這個初看毫無規(guī)律的結果幾乎毫無頭緒,讓我頭疼了一天多。BUG定位與解決
現(xiàn)象分析
開發(fā)板的運行結果奇怪在不僅沒有給buff[1]到buff[20]賦予正確的值,還在于它把buff[0]的值給覆蓋掉了,可是buff[21]往后的端口值卻仍然是正確的。
問題定位
在花了一天的時間check了整個執(zhí)行流程沒有找到問題后,我才回到上面那段代碼,把5句sscanf調用屏蔽,直接用memset(buff+1,0xfe,20);強制賦值,結果發(fā)現(xiàn)在開發(fā)板上的結果與x86的測試結果一致,正確了。直到此時我才再次想起那串警告,才猜到是sscanf的格式符類型不匹配導致的內存寫覆蓋問題,于是我寫了一個測試程序來驗證猜測和分析具體的問題產(chǎn)生過程,最終解決了問題。
測試程序的代碼如下:
#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上和開發(fā)板上的測試結果出來后,問題就一目了然了,PC上的結果都符合預期,這里就不粘貼了,開發(fā)板上的測試結果為: [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問題解決
分析上面的測試結果可以得出問題產(chǎn)生的原因(PC平臺和開發(fā)板ARM平臺均為小端類型):
在開發(fā)板上,sscanf函數(shù)中的%d格式符導致對它相應的指針變量內存地址寫時按照整型(int)的4字節(jié)對齊進行,如果sscanf中的地址不是4字節(jié)對齊,如buff+5=0xbe82ac0d時,程序會取比該地址小的4字節(jié)對齊字節(jié)進行寫內存,就是上面的buff+4地址,從而導致內存覆蓋;而buff+8,buff+12,buff+20,buff+28地址剛好都是4字節(jié)對齊地址,所以沒有產(chǎn)生內存覆蓋現(xiàn)象。
進一步進行分析可以得出根本的原因在于編譯器的處理。在PC上使用gcc編譯器構建程序可以OK,但是使用交叉編譯器arm-none-linux-gnueabi-gcc構建的程序在開發(fā)板上卻不OK,不同編譯器對這樣的問題的內存處理是不同的,我們沒法做任何假設。
問題原因找到了,解決就簡單了,其實解決方法已經(jīng)在測試代碼中展示了,利用unix的網(wǎng)絡地址轉換接口可以方便地實現(xiàn)字符串地址和二進制地址的轉換,同時還能輕松地判斷地址是否有效,而沒必要自己去實現(xiàn)判斷和字符串解析工作。最后的處理代碼如下面這樣:
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接口均是產(chǎn)生網(wǎng)絡字節(jié)序(大端)的二進制,即字符串的高字節(jié)放在起始地址,而inet_network接口是產(chǎn)生主機字節(jié)序的二進制,主機字節(jié)序取決于平臺。
思考與學習
從這個bug中,我認識到編譯器產(chǎn)生的任何一個警告都不應該輕易或者想當然的去忽略,如果可能則盡量消除警告,否則必須有足夠的驗證表明該警告不會在實際環(huán)境中產(chǎn)生問題才能忽略。
此外,這個案例中我再一次深刻地認識到嵌入式與PC軟件開發(fā)的區(qū)別。嵌入式軟件的開發(fā)必須時刻注意到平臺、編譯器和操作系統(tǒng)接口的不同可能導致的問題,開發(fā)好的軟件在PC平臺上驗證OK并不能表示在目標平臺也一定OK,完整可靠的測試必須基于和生成部署環(huán)境真是一致的環(huán)境下進行,其結果才可靠和有說服力。
另一種解決方法
既然是由sscanf函數(shù)中使用不匹配的格式符引起的問題,那是不是可以通過使用匹配的格式符解決呢?答案是肯定的,只是我之前一致想當然的認為不存在對應與unsigned char或者char變量的輸入格式符。通過man sscanf查看手冊有如下兩個格式修飾符可用:
因此另一種更簡單的解決方法就是將一開始代碼中的%d格式符換成%hhu,這樣既不會有警告,程序在開發(fā)板上也運行正確,代碼改動量也小。而我之所以沒使用這種方法,是覺得有必要判斷一下字符串地址的有效性。
轉載于:https://my.oschina.net/shelllife/blog/172273
總結
以上是生活随笔為你收集整理的sscanf函数中类型不匹配警告引发的BUG和思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: centos下 keepalived1.
- 下一篇: 用scheme重写Python的三大函数