上一篇随笔中提到了,rapidxml在每个xml对象中维护了一个内存池,自己管理变量的生存周期。看起来很好,但我们在实际使用中还是出现了问题。

项目中我们的模块很快写好了,在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. Android手机端使用Zipalign优化apk应用程序
  2. Android的无边界程序设计理念
  3. 管理Android应用程序的资源
  4. Android 学习 之 Activity 堆栈信息
  5. Android列表实现(1)_数组列表实例介绍
  6. android恶意程序分析 (二)
  7. Android之调用其他程序
  8. android不同应用程序之间启动Activity

随机推荐

  1. Android开发设置Activity全屏与不全屏的
  2. 基于Android的Linux内核的电源管理
  3. Android程序入口ActivityThread和Android
  4. Android 绘制2D图形
  5. ActivityThread 源码笔记(1)
  6. android机型适配终极篇
  7. java android 将小数度数转换为度分秒格
  8. (转载)Instrumentation 框架简介
  9. Android NDK开发环境搭建及案例
  10. Android内存管理策略的优化