最初,第一次接触到栈回溯是由于在追查不同的业务场景问题时,通常对方仅仅给你一个接口,而为了弄清楚场景的调用方向,就需要问不同的人,尝试不同的方法,自己想尝试通过一种方法能够加速对繁杂业务代码的阅读和理解。
而最近越来越觉得,在面对崩溃问题时,大家的无措,更是应该用signal捕捉结合栈回溯来完成的。

栈回溯的选择

最初在做时,查了一些的方法,大致如下:(欢迎大家提意见补充)

  1. __builtin_return_address:在当前代码中最常见的方式,获取上层的调用者地址,但是缺点在许多ARM平台上或者说看网上默认gcc都只能回溯一层,即只支持__builtin_return_address(0)
  2. -mapcs/-mapcs-frame:原理和之前学习ARM栈帧关系一样,而该选项则是告知编译器遵循APCS(ARM Procedure Call Standard)规范,APCS规范了arm寄存器的使用、函数调用过程出栈和入栈的约定,但是缺点是在复杂的代码结构下,会造成编译器内部错误而导致的编译不过问题;
  3. -funwind-tables:也是最终采用的方式,也是抱着尝试的心态,发现最终即使在复杂的代码结构下,也能够正常通过,其原理为链接器实际保存了帧的解压缩信息放置在专用链接器部分,而帧展开后的信息允许程序在任何点进行“窥视”上下文;

引用栈回溯的过程

  1. 初探:
    在决定并尝试-funwind-tables可以编译通过后,使用__Unwind_Backtrace__Unwind_GetIP完成了栈回溯,但是由于只能打印地址,每次需要使用nostrip的文件进行gdb查找函数名。
  2. 完善:
    在对这些不满足的条件下,发现了dladdr函数的妙用,但是却发现只有动态库中的符号可以被准确的查找到,因此将可执行文件的部分相关库改成了动态库,这无非是最直接的方法了,而后接触到的-rdynamic编译选项再次让我叹为观止,是为了-funwind-tables而生的没错了。
  3. 补充尝试:
    在顺利将unwind应用到工程中的我又想将其应用于崩溃问题的查看,而真正让我付诸实践的是新同事的一句“怎么我们现在还没有一套完整查看崩溃问题的工具”,而这就是个契机。真正实现起来并不难,但是实际发现,崩溃时的调用栈只能打印signal捕捉函数本身,这才是问题的关键。但是demo明明可用,一定是有哪个地方会影响到-funwind-tables,将工程的所有gcc编译选项逐一比对发现在-O1/-O2/-O3/-Os的情况下会导致无法正常使用栈回溯(虽然后来发现即使加上-Os也能正常了,但是希望读者可以在出现问题时往这方面去查)。最终将该工具部署后,signal捕捉常见的SIGSEGV/SIGABRT/SIGFPE后发现,还是存在部分崩溃无法栈回溯:
  • abort()
  • 0异常
  • 部分signal11的情况:空函数指针(unwind的固有缺陷)/mmap环形缓冲的memset空指针
    而如上问题,通过查资料和demo尝试,pc下是可以正常回溯abort和除0的,因此一定还有哪里不同于pc。

glibc交叉编译

除了怀疑架构上的不同貌似已经没有怀疑点了,但是之前的unwind原理上可以实现并且是架构无关的,应该还存在可以尝试的点——glibc库的重新编译-funwind-tables,如下是参照网上的方法依然遇到的几个坑:

  • 编译方式1:
callon@callon-virtual-machine:~/Documents/glibc-2.16.0$ ./configure --prefix=/home/callon/Documents/out --host=arm-linux --enable-add-on=nptl CC=arm-hisiv400-linux-gcc CXX=arm-hisiv400-linux-g++

报错1:

configure: error: you must configure in a separate build directory

解决1:

mkdir build/ out/;cd build/;../configure --prefix=/home/callon/Documents/glibc-2.16.0/out --host=arm-linux --enable-add-on=nptl CC=arm-hisiv400-linux-gcc CXX=arm-hisiv400-linux-g++
  • 报错2:
checking sysdep dirs... configure: error: The arm is not supported.

解决2:网上的方法,下载glibc-ports-2.16.0,并解压到glibc-2.16.0目录下重命名为ports目录

  • 报错3:
checking add-on ports for preconfigure fragments... alpha am33 arm Old ABI no longer supported

解决3:通过找no longer supported报错的具体位置,找到是变量的值不对导致,

callon@callon-virtual-machine:~/Documents/glibc-2.16.0$ grep -nr "no longer supported" .
./ports/sysdeps/arm/preconfigure:45:        echo "Old ABI no longer supported" 2>&1
./README:25:Linux kernels is no longer supported, and we are not distributing it

最后修改--host为海思编译器原始的前缀后正常

callon@callon-virtual-machine:~/Documents/glibc-2.16.0/build$ ../configure --prefix=/home/callon/Documents/glibc-2.16.0/out --host=arm-hisiv400-linux-gnueabi --enable-add-on=nptl CC=arm-hisiv400-linux-gcc CXX=arm-hisiv400-linux-g++

而我们是为了编译出正常可回溯的glibc,因此,参考网上的方式,编译选项整体为

callon@callon-virtual-machine:~/Documents/glibc-2.16.0/build$ ../configure --prefix=/home/callon/Documents/glibc-2.16.0/out --host=arm-hisiv400-linux-gnueabi --enable-add-on=nptl CC=arm-hisiv400-linux-gcc CXX=arm-hisiv400-linux-g++ CFLAGS="-g -O2 -U_FORTIFY_SOURCE" libc_cv_forced_unwind=yes libc_cv_c_cleanup=yes

而最终在设备上:

mkdir /mnt/nfs;mount -t nfs -o nolock xxx.xx.xx.xx:/home/callon/nfs/test /mnt/nfs;cd /mnt/nfs
mkdir /libtmp;cp -d ./lib/* /libtmp
export LD_LIBRARY_PATH=/libtmp:$LD_LIBRARY_PATH
./test

发现abort依然无法回溯
在即将放弃时,使用

callon@callon-virtual-machine:~/Documents/glibc-2.16.0/build$ ../configure --prefix=/home/callon/Documents/glibc-2.16.0/out --host=arm-hisiv400-linux-gnueabi --enable-add-on=nptl CC=arm-hisiv400_v2-linux-gcc CXX=arm-hisiv400_v2-linux-g++ CFLAGS="-g -Os -funwind-tables"

最终成功回溯signal 6

signal 8多次尝试都不行,再次反汇编深入追究其原因发现:

void test_func()
{841c:  e92d4800    push    {fp, lr}8420:   e28db004    add fp, sp, #48424: e24dd008    sub sp, sp, #8int a = 0, b = 1;8428:  e3a03000    mov r3, #0842c: e50b3008    str r3, [fp, #-8]8430:  e3a03001    mov r3, #18434: e50b300c    str r3, [fp, #-12]b /= a;8438: e51b000c    ldr r0, [fp, #-12]843c: e51b1008    ldr r1, [fp, #-8]8440:  eb000016    bl  84a0 <__aeabi_idiv>8444:  e1a03000    mov r3, r08448: e50b300c    str r3, [fp, #-12]b += a;844c:    e51b200c    ldr r2, [fp, #-12]8450: e51b3008    ldr r3, [fp, #-8]8454:  e0823003    add r3, r2, r38458: e50b300c    str r3, [fp, #-12]
}

结合网上查阅的资料发现实际上,对于满足eabi(嵌入式arm应用程序二进制接口)的arm工具链,编译时编译器将编译对象的’/'操作替换为调用__aeabi_idiv函数,__aeabi_idiv是由libgcc.so或gcc.a库提供的。
所以编译glibc是不够的,最好整个工具链的gcc库都更新才行,而除0异常本身出现较少,因此不再深究。

但在多次的demo尝试中,发现memcpy/memset/memmovesignal 11崩溃居然无法追溯,但是简单的空指针赋值/strncpy等是正常的,而且更奇怪的是,默认的libc.so.6居然可以回溯memcpy,但是不能回溯memset,这样就更加奇怪了,通过grep不断的找memcpystrncpy这些函数到底有什么不同时,发现glibc-ports中存在memset.S/memcpy.S/memmove.S,正好没有strncpy的汇编实现函数,并且在string/memcpy.c中加上了printf的打印没有打出来,而strncpy的是可以的,因此问题变成了memcpy如何使用.c的而不是.S的实现,中途尝试过:

  1. 删除.S的实现(编译不过);
  2. Makefile中的相关编译文件删除(无影响);
  3. preconfigure文件中的-fno-unwind-tables选项删除(…/configure运行不过),或者说执行后手动删除所有-fno-unwind-tables的地方(无影响);
  4. 改变memcpy.SENTRY的名称为asm_memcpy(编译错误很多)
    等等,以及网上查了好几天方案也都没有。

不过还好,经过不懈的怀疑到尝试到反思,
最终的方案是,将string/中的memset.c/memcpy.c/memmove.c替换掉memset.S/memcpy.S/memmove.S
再进行编译,此时编译通过,尝试原来的demo,果然都能正常回溯了!

最终集成工程时,可以

arm-hisiv400_v2-linux-strip *.so*

将库进行strip减少内存使用,并在进程启动时加上

LD_PRELOAD=/home/debug_lib/libc.so.6 ./my_program

保证对其他进程影响最小,且backtrace的封装也使用了glibc的自带源码参考,自己做了一些修改,主要是backtrace_symbols的实现,因为发现在堆越界时,再次调用malloc,此时出现malloc内部的assert,然后系统死锁无法恢复,这个非常严重,所以写了一种不再使用malloc的实现方式,通过局部变量的数组传入,只要限制栈回溯的层数和合理限制result数组的大小,是不会有任何问题的:

/* Return backtrace of current program state.Copyright (C) 2008, 2009 Free Software Foundation, Inc.This file is part of the GNU C Library.Contributed by Kazu Hirata <kazu@codesourcery.com>, 2008.The GNU C Library is free software; you can redistribute it and/ormodify it under the terms of the GNU Lesser General PublicLicense as published by the Free Software Foundation; eitherversion 2.1 of the License, or (at your option) any later version.The GNU C Library is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; without even the implied warranty ofMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNULesser General Public License for more details.You should have received a copy of the GNU Lesser General PublicLicense along with the GNU C Library.  If not, see<http://www.gnu.org/licenses/>.  */#include "unwind_backtrace.h"struct trace_arg
{void **array;int cnt, size;
};#ifdef SHARED
static _Unwind_Reason_Code (*unwind_backtrace) (_Unwind_Trace_Fn, void *);
static _Unwind_VRS_Result (*unwind_vrs_get) (_Unwind_Context *,_Unwind_VRS_RegClass,_uw,_Unwind_VRS_DataRepresentation,void *);static void *libgcc_handle;static void
init (void)
{libgcc_handle = __libc_dlopen ("libgcc_s.so.1");if (libgcc_handle == NULL)return;unwind_backtrace = __libc_dlsym (libgcc_handle, "_Unwind_Backtrace");unwind_vrs_get = __libc_dlsym (libgcc_handle, "_Unwind_VRS_Get");if (unwind_vrs_get == NULL)unwind_backtrace = NULL;
}/* This function is identical to "_Unwind_GetGR", except that it uses"unwind_vrs_get" instead of "_Unwind_VRS_Get".  */
static inline _Unwind_Word
unwind_getgr (_Unwind_Context *context, int regno)
{_uw val;unwind_vrs_get (context, _UVRSC_CORE, regno, _UVRSD_UINT32, &val);return val;
}/* This macro is identical to the _Unwind_GetIP macro, except that ituses "unwind_getgr" instead of "_Unwind_GetGR".  */
# define unwind_getip(context) \(unwind_getgr (context, 15) & ~(_Unwind_Word)1)
#else
# define unwind_backtrace _Unwind_Backtrace
# define unwind_getip _Unwind_GetIP
#endifstatic _Unwind_Reason_Code
backtrace_helper (struct _Unwind_Context *ctx, void *a)
{struct trace_arg *arg = a;/* We are first called with address in the __backtrace function.Skip it.  */if (arg->cnt != -1)arg->array[arg->cnt] = (void *) unwind_getip (ctx);if (++arg->cnt == arg->size)return _URC_END_OF_STACK;return _URC_NO_REASON;
}int
backtrace (array, size)void **array;int size;
{struct trace_arg arg = { .array = array, .size = size, .cnt = -1 };
#ifdef SHARED__libc_once_define (static, once);__libc_once (once, init);if (unwind_backtrace == NULL)return 0;
#endifif (size >= 1)unwind_backtrace (backtrace_helper, &arg);if (arg.cnt > 1 && arg.array[arg.cnt - 1] == NULL)--arg.cnt;return arg.cnt != -1 ? arg.cnt : 0;
}void
backtrace_symbols (array, size, result, max_len)void *const *array;int size;char **result;int max_len;
{Dl_info info[size];int status[size];int cnt;size_t total = 0;/* Fill in the information we can get from `dladdr'.  */for (cnt = 0; cnt < size; ++cnt){struct link_map *map;status[cnt] = _dl_addr (array[cnt], &info[cnt], &map, NULL);if (status[cnt] && info[cnt].dli_fname && info[cnt].dli_fname[0] != '\0'){/* We have some info, compute the length of the string which will be"<file-name>(<sym-name>+offset) [address].  */total += (strlen (info[cnt].dli_fname ?: "")+ strlen (info[cnt].dli_sname ?: "")+ 3 + WORD_WIDTH + 3 + WORD_WIDTH + 5);/* The load bias is more useful to the user than the loadaddress.  The use of these addresses is to calculate anaddress in the ELF file, so its prelinked bias is notsomething we want to subtract out.  */info[cnt].dli_fbase = (void *) map->l_addr;}elsetotal += 5 + WORD_WIDTH;}if (result != NULL){char *last = (char *) (result + size);for (cnt = 0; cnt < size; ++cnt){result[cnt] = last;if (status[cnt]&& info[cnt].dli_fname != NULL && info[cnt].dli_fname[0] != '\0'){if (info[cnt].dli_sname == NULL)/* We found no symbol name to use, so describe it asrelative to the file.  */info[cnt].dli_saddr = info[cnt].dli_fbase;if (info[cnt].dli_sname == NULL && info[cnt].dli_saddr == 0)last += 1 + sprintf (last, "%s(%s) [%p]",info[cnt].dli_fname ?: "",info[cnt].dli_sname ?: "",array[cnt]);else{char sign;long int offset;if (array[cnt] >= (void *) info[cnt].dli_saddr){sign = '+';offset = array[cnt] - info[cnt].dli_saddr;}else{sign = '-';offset = info[cnt].dli_saddr - array[cnt];}last += 1 + sprintf (last, "%s(%s%c%#tx) [%p]",info[cnt].dli_fname ?: "",info[cnt].dli_sname ?: "",sign, offset, array[cnt]);}}elselast += 1 + sprintf (last, "[%p]", array[cnt]);}assert (last <= (char *) result + max_len);}return;
}#ifdef SHARED
/* Free all resources if necessary.  */
libc_freeres_fn (free_mem)
{unwind_backtrace = NULL;if (libgcc_handle != NULL){__libc_dlclose (libgcc_handle);libgcc_handle = NULL;}
}
#endif

ARM平台(海思)unwind栈回溯的实现相关推荐

  1. [PCIE703]FPGA实时处理器-XCKU060+ARM(华为海思视频处理器-HI3531DV200)高性能综合视频图像处理平台设计资料及原理图分享

    板卡概述 PCIE703是自主研制的一款基于PCIE总线架构的高性能综合视频图像处理平台,该平台采用Xilinx的高性能Kintex UltraScale系列FPGA加上华为海思的高性能视频处理器来实 ...

  2. Unwind 栈回溯详解:libunwind

    目录 1. 历史背景 1.1 frame pointers 1.2 .debug_frame (DWARF) 1.3 .eh_frame (LSB) 1.4 CFI directives 2. .de ...

  3. Unwind 栈回溯详解

    文章目录 1. 历史背景 1.1 frame pointers 1.2 .debug_frame (DWARF) 1.3 .eh_frame (LSB) 1.4 CFI directives 2. . ...

  4. Unwind 栈回溯 issue

    1..eh_frame信息中cie的Augmentation字段为空: Oracle Linux 6.5 # readelf -wf oracle | more Contents of the .eh ...

  5. 【交叉编译】海思平台和安霸平台交叉编译

    海思平台 板端编译首先需要配置系统的NFS,挂载NFS服务器:然后安装ubuntu下的交叉编译环境(3516A和3518为例):登录板端:板端编译(需要修改相关的makefile文件):板端运行: 一 ...

  6. c++ opencv mat_海思平台OpenCV编译与进一步裁剪

    OpenCV是计算机视觉算法开发常用的工具.如果我们需要在嵌入式设备上运行opencv,那么就需要交叉编译,将它移植到对应平台上.但是有些嵌入式平台的存储空间有限,能节省1MB也有相当大的作用.Ope ...

  7. Android STB 海思平台调试

    文章目录 一.产品参数 1.1 Hi3798MV100 1.2 Hi3798MV300 二.网络类&播放类 三.系统类 3.1 输出相关 3.2 CEC 3.3 杜比 3.4 IGMP 3.5 ...

  8. 音视频数据采集及单向传输的实现(海思3516EV200平台)

    目录 2.硬件选型 2.1远端无线装置 2.1.1音视频采集模块 2.1.2光电转换模块 2.2本地接受设备 2.2.1光电转换模块 2.2.2音视频还原模块 3.串口配置 4.音视频采集 5.音视频 ...

  9. usb_modeswitch移植到海思3531D平台(华为E8372h-155)

    usb_modeswitch移植到Hi3531D平台 PC宿主机:ubuntu 16.04LTS 板子处理器:Hi3531DV100 4G Dongle:华为随行WiFi 2mini (E8372h- ...

最新文章

  1. python使用复合语句def创建函数对象_【收藏】Python实用技巧-成为Pythoner必经之路...
  2. Java源码:java.lang.reflect反射之AccessibleObject、ReflectionFactory、Filed、Method、Constructor类
  3. 如何计算一只鸡的表面积?各大专业的奇葩解法
  4. c语言单选题大全,C语言单选题(五)
  5. 玩转 SpringBoot 2.x 之 快速集成 Jedis客户端(普通版)
  6. 什么是hypernetworks? hypernetworks简单介绍
  7. 中国 AI 开发者真实现状:人才依赖海外引进,本科 AI 教育盛行
  8. [转]一张图帮你搞定职业规划
  9. Android Revolution
  10. 《Java游戏编程原理与实践教程》读书笔记(第4章——Java游戏程序的基本框架)
  11. 机器学习项目 - ctr 电商点击率预估
  12. java小球落体问题_小球落体 -- 算法Java
  13. linux格式化硬盘 中断,linux格式化硬盘【调解方案】
  14. ASP.NET 4.0 尚未在WEB服务器上注册
  15. Visio 下载,及密钥
  16. Elastic:集群相关知识点总结(一)数据流 Data Stream、索引生命周期 ILM、可搜索快照 searchable snapshots、跨集群搜索 CCS、跨集群复制 CCR
  17. Web 开发项目的6个最佳Java框架
  18. Dell T40和Dell T140有啥区别?
  19. windows7系统损坏修复_利用老毛桃系统修复Windows引导故障
  20. Linux 探索之旅 | 第五部分第八课:用 Shell 做统计练习

热门文章

  1. unity 2D人物精灵动画控制
  2. OCR文字识别:Tesseract-4.00训练字库
  3. 【VR】虚拟现实相关硬件设备
  4. HMI-63-【多媒体】空调部分 4
  5. IDEA看源码的几个小技巧
  6. 公积金查询测试接口+对接库的使用
  7. python语言FCM(模糊C均值聚类)第三方包介绍
  8. AcceptChanges()和RejectChanges()原理
  9. php 7编译 phar,解决PHP 7编译安装错误:cannot stat ‘phar.phar’: No such file or directory...
  10. Arduino遇上机智云4.0,岂止是送50个Arduino板卡!