警惕rapidxml的陷阱(二):在Android上默认内存池分配数组过大,容易导致栈溢出

项目中我们的模块很快写好了,在windows和linux上测试都工作的很好,但在Android上有时候却会崩溃。

背景:我们的模块是c++写的,编译成so动态库在不同的平台(linux,windows,Android)上运行;Android上我们包装了一个service,通过jni加载so动态库运行的。

 

解决程序崩溃问题,首先要找到崩溃点。但我们的程序是service+jni的形式,直接调试比较困难,都没有经验;只能想其他办法了。

(1)通过logcat打印的falut addr以及back trace来确定崩溃点。

通常程序都会打印这些信息的,但是很不幸我们程序啥都没有,只打印了两行:

08-07 20:27:57.840: I/ActivityManager(2793): Process com.sec.android.psfcore (pid 18686) (adj 1) has died.
08-07 20:27:57.850: D/Zygote(1993): Process 18686 terminated by signal (11)

看来此路不通。

(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, 0);
        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(0)
            , m_first_attribute(0)
        {
        }

        ...
        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(0)
            , m_free_func(0)
        {
            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的堆栈。

这也算是为了性能而牺牲了可用性吧!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值