进程挂死问题记录
背景
进程处于开发设备中中运行良好,输入指令无异常,近期部门为了调试方便,在编译环境中加入了x86_64环境,用于方便调试,同样指令x86_64中出现挂死现象。
现象
输入指令进程挂死
输入同样指令进程正常
分析
进程挂死后,查看日志文件中保存的挂死堆栈信息时发现,在执行strncmp过程中,进程挂掉。
查看命令对应的代码发现
分析函数入参发现第二个参数为固定字符串大小,第一个参数为外部传参,大致可定位是第一个参数导致挂死,对cipher地址进行打印出现0xffffffff9338c560,高8位地址出现全f,访问地址处于内核地址,导致挂死,至此问题定位于在编译执行过程中变量cipher被意外改变导致进程挂死。
定位
追踪cipher从申请到使用时地址变化,加%p打印发现调用函数内部定义变量申请内存后地址为0x55919338c560,函数返回后返回的函数地址为0xffffffff9338c560,高8位被截断且进行补全1操作,至此问题定位。
原因
平台架构不同,原先的设备环境mips为32位架构,现用于调试的x86为64位架构,函数指针在返回时进行了符号扩展操作,但为什么会出现符号扩展呢?
对编译过程中对于此文件的编译进行分析,发现在编译过程中出现告警提示
编译过程中未找到函数的定义,会发生隐式的整型向指针的转换,在x86_64中编译器会帮我们隐式定义一个int返回,即是有符号的32位给无符号的64位进行赋值,此时产生符号扩展。
在于C89有一个隐式声明规则(implicit declaration),当需要调用一个函数但找不到函数原型时,编译器会提供一个隐式声明,该隐式声明会假定函数返回值类型为int,C99已经去掉了这一规则,要求函数调用必须有函数声明,但gcc可能为了兼容老代码,并没有强制执行C99,只是给出了一个警告。在我们这个例子里,gcc找不到函数salt的原型,于是假定函数salt的返回值类型是int。
验证
为了验证我们的猜想,对编译出的文件使用objdump进行反汇编操作,查看salt函数返回过程中是否发生4字节向8字节的符号扩展。
如图在赋值时出现了movslq操作,证明此时有4字节向8字节的扩展操作。
解决方法
在文件中加入被调用函数的额外申明,或者加入头文件的引用,使编译器能够正确找到函数的定义,消除掉告警中的隐式转换。
总结
1.编译的时候最好加上 -Werror 参数,它要求GCC将所有的警告当成错误进行处理
2.灵活使用日志文件,打印等工具对问题进行分析定位
3.写代码时考虑应当跨平台产生的影响