概述
openssl支持async mode. 在定位越界问题时,我使用了libasan, 之后就OOM了, 能够看见在这个地方:
原因是因为, memset的size参数特别大, (程序本身的虚拟内存就占了很多, 随着memset的写入,会不断申请更多的真实内存,最后OOM ?? )
接下来将重点分析为什么size会这么大
使用
openssl 使用libasan的方法
config时,使用选项 enable-asan
./Configure --prefix=/root/debug/ shared enable-asan LDFLAGS="-Wl,-rpath,/root/debug/lib" linux-x86_64
用户程序使用libasan的方法:
因为我的程序是用来了openssl, 所以两边都要同时加, 实践过程中, 如果用户程序不加只在openssl上加是不行的.
LDFLAGS也要叫两个-f参数, 不然会coredump, 我也不知道为什么.
CFLAGS+=-fsanitize=address -fno-omit-frame-pointer LDFLAGS+= -lssl -lcrypto LDFLAGS+=-fsanitize=address -fno-omit-frame-pointer LDFLAGS+= -lasan
分析
一 libasan
libasan怎么检测longjmp API, 以下是gcc4.8.5里边的代码
大概意思是, gcc使用libasan之后, hock了longjmp, 在真正进入longjmp之前调用函数__asan_handle_no_return() 进行内存检测.
在该函数里, top是之前保存的调用栈的栈顶. &local_stack是取了临时变量的地址也就是当前使用的调用栈的栈底地址.
然后会memset, 把整个调用栈(maybe?) 写0, 而我当前出现的问题就是临时变量的地址没有取到pthread的调用栈的地址上.
所以两个一减, size就特别大.
我们知道调用栈的地址是0x7fff是正常的, 这个local_stack的0x609200值,很明显是不正常的, 从而导致了size不正常, 接下来继续分析
这个值为什么是这样的.
二 async
上文中调用栈的值的变化,主要是由api setcontext 导致的, 见如下:
上文中提到的OOM复现过程是这样的, openssl进入SSL_accept函数之后, 在61行进行了第一次上下文切换, 之后在第59行进行了如上文调用栈图片所示的,
有qatengine触发的第二次上下文切换. 于是进入了libasan的__asan_handle_no_return() 并触发OOM.
setcontext函数切换了整个调用栈的栈指针, 所以会有上文0x60开头的栈地址,而不是0x7f, 如下图的registers的rsp可见:
函数调用前:
函数调用后:
扩展
longjmp与setcontext都是用来做上下文切换的API, 功能可以相互替换.
setcontext之所以使用了新的地址,而不是就得0x7f地址, 推测是为了提高效率防止拷贝. 带来的一个隐形坏处是gdb不能继续单步执行, 单步执行的下一步就是它
切回来的时候, 继续使用gdb的方法是, 在即将切换的那个函数上加一个断点.
longjmp没有这个gdb的困扰问题, 推测它每次切换都会做一次栈拷贝.
此类API的主要用途: 实现协程, 实现信号处理, 实现异常处理.