警惕rapidxml的陷阱(二):在Android上默认内存池分配数组过大,容易导致栈溢出
上一篇随笔中提到了,rapidxml在每个xml对象中维护了一个内存池,自己管理变量的生存周期。看起来很好,但我们在实际使用中还是出现了问题。
项目中我们的模块很快写好了,在windows和linux上测试都工作的很好,但在Android上有时候却会崩溃。
背景:我们的模块是c++写的,编译成so动态库在不同的平台(linux,windows,Android)上运行;Android上我们包装了一个service,通过jni加载so动态库运行的。
解决程序崩溃问题,首先要找到崩溃点。但我们的程序是service+jni的形式,直接调试比较困难,都没有经验;只能想其他办法了。
(1)通过logcat打印的falut addr以及back trace来确定崩溃点。
通常程序都会打印这些信息的,但是很不幸我们程序啥都没有,只打印了两行:
- ::57.840: I/ActivityManager(): Process com.sec.android.psfcore (pid ) (adj ) has died.
- ::57.850: D/Zygote(): Process terminated by signal ()
看来此路不通。
(2)崩溃一般是由SEGSEV引起的,可以尝试在程序中捕捉该信号,然后打印堆栈信息。但仅仅是理论上行得通而已。查了一堆资料,发现巨麻烦,咱们时间不够直接放弃这条路了。
(3)通过logcat日志的方式跟踪,找到崩溃点。
但在实际测试中发现,logcat不是实时的,有些时候进程崩溃了日志也随即丢失了,导致我们很难追踪到真正的崩溃点。
(4)终极大法:二分查找法。依次注释掉每个功能、函数、代码块,通过逐次测试程序是否运行正常来确定这些代码块有没有问题。这是个体力活没什么技术含量,但好歹咱们通过这种办法确定了崩溃点。
最终确定程序的崩溃点是如下函数:
std::string pack_send_local_context_list_command(const std::string& unique,
std::vector<PSFResource>& key, int count, int state)
{
rapidxml::xml_document<> doc;
rapidxml::xml_node<>* root = doc.allocate_node(rapidxml::node_element, "set_list", NULL);
doc.append_node(root); root->append_node(doc.allocate_node(rapidxml::node_element, "unique_name", APPEND_STRING(unique, &doc)));
root->append_node(doc.allocate_node(rapidxml::node_element, "state", APPEND_STRING(cs::int2string(state), &doc)));
root->append_node(doc.allocate_node(rapidxml::node_element, "count", APPEND_STRING(cs::int2string(count), &doc))); rapidxml::xml_node<>* context_node = doc.allocate_node(rapidxml::node_element, "psfresources", NULL);
root->append_node(context_node); append(&doc, context_node, key);
std::string text;
rapidxml::print(std::back_inserter(text), doc, );
return text;
}
现象:
不是每次都崩溃;
如果崩溃,就是在执行第一条语句的时候崩溃。
第一行啥都没干,就是声明了一个xml_document对象而已啊,为什么会崩溃呢?
好在rapidxml库代码量比较少,直接查看源代码。
template<class Ch = char>
class xml_document: public xml_node<Ch>, public memory_pool<Ch>
{ public: //! Constructs empty XML document
xml_document()
: xml_node<Ch>(node_document)
{
}
...
}
可以看到,xml_document类本身没有成员变量,构造函数啥都没干。
但他继承了xml_node和memory_pool类,看看他们都干了些啥。
enum node_type
{
node_document, //!< A document node. Name and value are empty.
node_element, //!< An element node. Name contains element name. Value contains text of first data node.
node_data, //!< A data node. Name is empty. Value contains data text.
node_cdata, //!< A CDATA node. Name is empty. Value contains data text.
node_comment, //!< A comment node. Name is empty. Value contains comment text.
node_declaration, //!< A declaration node. Name and value are empty. Declaration parameters (version, encoding and standalone) are in node attributes.
node_doctype, //!< A DOCTYPE node. Name is empty. Value contains DOCTYPE text.
node_pi //!< A PI node. Name contains target. Value contains instructions.
}; template<class Ch = char>
class xml_node: public xml_base<Ch>
{ public: ///////////////////////////////////////////////////////////////////////////
// Construction & destruction //! Constructs an empty node with the specified type.
//! Consider using memory_pool of appropriate document to allocate nodes manually.
//! \param type Type of node to construct.
xml_node(node_type type)
: m_type(type)
, m_first_node()
, m_first_attribute()
{
} ...
node_type m_type; // Type of node; always valid
xml_node<Ch> *m_first_node; // Pointer to first child node, or 0 if none; always valid
xml_node<Ch> *m_last_node; // Pointer to last child node, or 0 if none; this value is only valid if m_first_node is non-zero
xml_attribute<Ch> *m_first_attribute; // Pointer to first attribute of node, or 0 if none; always valid
xml_attribute<Ch> *m_last_attribute; // Pointer to last attribute of node, or 0 if none; this value is only valid if m_first_attribute is non-zero
xml_node<Ch> *m_prev_sibling; // Pointer to previous sibling of node, or 0 if none; this value is only valid if m_parent is non-zero
xml_node<Ch> *m_next_sibling; // Pointer to next sibling of node, or 0 if none; this value is only valid if m_parent is non-zero }
xml_node类似乎也没什么特别的。
template<class Ch = char>
class memory_pool
{ public:
//! Constructs empty pool with default allocator functions.
memory_pool()
: m_alloc_func()
, m_free_func()
{
init();
}
void init()
{
m_begin = m_static_memory;
m_ptr = align(m_begin);
m_end = m_static_memory + sizeof(m_static_memory);
}
char *m_begin; // Start of raw memory making up current pool
char *m_ptr; // First free byte in current pool
char *m_end; // One past last available byte in current pool
char m_static_memory[RAPIDXML_STATIC_POOL_SIZE]; // Static raw memory
alloc_func *m_alloc_func; // Allocator function, or 0 if default is to be used
free_func *m_free_func; // Free function, or 0 if default is to be used
}
memory_pool类的构造函数也没啥特别的,但注意其成员变量:
char m_static_memory[RAPIDXML_STATIC_POOL_SIZE];
这里申请了一个大小为RAPIDXML_STATIC_POOL_SIZE的数组,而RAPIDXML_STATIC_POOL_SIZE的值是多少呢?
#ifndef RAPIDXML_STATIC_POOL_SIZE
// Size of static memory block of memory_pool.
// Define RAPIDXML_STATIC_POOL_SIZE before including rapidxml.hpp if you want to override the default value.
// No dynamic memory allocations are performed by memory_pool until static memory is exhausted.
#define RAPIDXML_STATIC_POOL_SIZE (64 * 1024)
#endif
看到问题没有?
memory_pool默认申请了一个64k的数组!而且是在栈上面的!
我们知道,linux下线程默认的堆栈大小是8M(Android上是多少,也许会更小?),而我们当前的线程里还用到了另一个开源库AllJoyn,该库也会使用比较多的堆栈空间。
因此我们推测,很有可能是当前线程的堆栈空间不足,导致memory_pool声明变量时申请64k的堆栈空间失败,最后进程崩溃。
我们做了一个测试,将memory_pool申请的数组大小改为4k,只需要在程序中增加如下代码:
#define RAPIDXML_STATIC_POOL_SIZE 4*1024
果然问题解决了!
这也解释了此前的几个现象:
(1)为什么不是每次都崩溃?
每次运行时线程的堆栈使用情况都不一样,如果运气好的话堆栈足够,即使申请64k数组也有空间。
(2)HQ写的模块也调用了rapidxml库,但他们的代码为什么没崩溃呢?
经过分析,发现他们的代码是另外启动了一个线程,相对来说堆栈空间是足够的,即使申请64k的数组也没有问题。
(3)为什么在windows,linux下都没崩溃?
因为windows和linux默认的堆栈足够大。
linux:8M
windows:1M(但我们使用的是pthread库,也许跟Linux相同都是8M?)
Android:根据这里的回答,似乎是16K?--如果真的是16k的话,那每次调用必然会崩溃,而不是偶尔。存疑。没时间去查以后再看吧。
从网上的资料看,Android的堆栈不够大,申请比较大的数组时容易崩溃,已经引起过不少血案了,比如这里:http://blog.csdn.net/win2k3net/article/details/6718591
总结:
归根结底是rapidxml没有考虑到Android等移动设备,认为所有设备都能申请到64k的堆栈。
这也算是为了性能而牺牲了可用性吧!
警惕rapidxml的陷阱(二):在Android上默认内存池分配数组过大,容易导致栈溢出的更多相关文章
- 警惕rapidxml的陷阱:添加节点时,请保证变量的生命周期
http://www.cnblogs.com/chutianyao/p/3246592.html 项目中要使用xml打包.解析协议,HQ指定了使用rapidxml--号称是最快的xml解析器. 功能很 ...
- Android学习十二---在android上实现图像匹配
一.效果图及功能描述 效果图 点击ShowImg后 点击match,然后点击showmatch,可以不断点击showmatch. 主要功能描述:显示在SD卡上已经存在的图片test.jpg,根据图片在 ...
- C++ Primer : 第十二章 : 动态内存之动态数组
动态数组的分配和释放 new和数组 C++语言和标准库提供了一次分配一个对象数组的方法,定义了另一种new表达式语法.我们需要在类型名后跟一对方括号,在其中指明要分配的对象的数目. int* arr ...
- 最牛逼android上的图表库MpChart(二) 折线图
最牛逼android上的图表库MpChart二 折线图 MpChart折线图介绍 MpChart折线图实例 MpChart效果 最牛逼android上的图表库MpChart(二) 折线图 最近工作中, ...
- Android开发(二十五)——Android上传文件至七牛
设置头像: Drawable drawable = new BitmapDrawable(dBitmap); //Drawable drawable = Drawable.createFromPath ...
- 二十一、Android上常见度量单位【xdpi、hdpi、mdpi、ldpi】解读
术语和概念 屏幕尺寸 屏幕的物理尺寸,以屏幕的对角线长度作为依据(比如 2.8寸, 3.5寸). 简而言之, Android把所有的屏幕尺寸简化为三大类:大,正常,和小. 程序可以针对这三种尺寸的屏幕 ...
- 在Android上使用qemu-user运行可执行文件
在Android上使用qemu-user运行可执行文件 作者:寻禹@阿里聚安全 前言 QEMU简要介绍: QEMU可以解释执行可执行程序.既然QEMU可以解释执行可执行程序,那么QEMU就能够知道执行 ...
- 系列篇|编译可在Android上运行的依赖库(一):glib库
前言 这是系列文章,它们由<编译可在Android上运行的glib库>及其他4篇文章组成,这4篇文章在“编译依赖库”一节中列出.由于glib库依赖于其他第三方库,所以需要先将依赖的第三方库 ...
- TensorFlow 在android上的Demo(1)
转载时请注明出处: 修雨轩陈 系统环境说明: ------------------------------------ 操作系统 : ubunt 14.03 _ x86_64 操作系统 内存: 8GB ...
随机推荐
- [ 手记 ] 联想rd650服务器整列及系统安装
联想 RD650服务器 磁盘阵列:http://wenku.baidu.com/view/b364c2db5f0e7cd185253644.html?from=search 该服务器安装系统需要BIO ...
- Java 5大内存区域和对象的创建过程
1.Java运行时数据区 方法区,堆线程共享.虚拟机栈,本地方法栈和程序计数器线程私有. 2.程序计数器(PC计数器) 占用较小的一块内存空间,当执行Java方法时记录正在执行的虚拟机字节码指令地址, ...
- 关于 svn: E155004 is already locked 出现原因和解决办法
1.出错提示: svn: E155004 is already locked,please clean up ......... 2.出错原因: SVN 本地更新时,由于一些操作中断更新,如磁盘空间 ...
- Eclipse svn代码提交冲突
Eclipse svn代码提交冲突(转) 1.Synchronize视图下查看代码冲突 1.Incoming Mode 全部update,更新到本地2.Outgoing Mode 全部commit,提 ...
- 区块链开发(五)git、truffle安装
truffle是以太坊最受欢迎的一个开发框架,本篇博客介绍truffle的下载安装过程. git安装 在安装truffle之前需要核实一下本机是否安装git程序.后面的程序安装需要依赖git. 输入以 ...
- nginx部署vue工程和反向代理nodejs工程
前端是vue,后端是nodejs 前端打包成dist目录,后端接口是localhost:4000/api server { listen 80; #listen [::]:80; server_nam ...
- AC日记——codeforces Ancient Berland Circus 1c
1C - Ancient Berland Circus 思路: 求出三角形外接圆: 然后找出三角形三条边在小数意义下的最大公约数; 然后n=pi*2/fgcd; 求出面积即可: 代码: #includ ...
- 【转】jenkins插件pipeline使用介绍
摘要: pipeline字面意思就是流水线,将很多步骤按顺序排列好,做完一个执行下一个.下面简单介绍下如何使用该插件帮我们完成一些流水线型的任务 pipeline字面意思就是流水线,将很多步骤按顺序排 ...
- CF 1005B Delete from the Left 【模拟数组操作/正难则反】
You are given two strings s and t. In a single move, you can choose any of two strings and delete th ...
- rsync用于同步目录
rsync是unix/linux下同步文件的一个高效算法,它能同步更新两处计算机的文件与目录,并适当利用查找文件中的不同块以减少数据传输.rsync中一项与其他大部分类似程序或协定中所未见的重要特性是 ...