敞开成长之旅!这是我参与「日新方案 12 月更文挑战」的第4天,点击检查活动概况
作者简介
架构师李肯(全网同名)
一个专注于嵌入式IoT范畴的架构师。有着近10年的嵌入式一线开发经历,深耕IoT范畴多年,熟知IoT范畴的事务开展,深度掌握IoT范畴的相关技能栈,包含但不限于干流RTOS内核的完结及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其完结、底层汇编及编译原理、编译优化及代码重构、干流IoT云渠道的对接、嵌入式IoT体系的架构规划等等。具有多项IoT范畴的发明专利,热衷于技能共享,有多年编撰技能博客的经历堆集,接连多月取得RT-Thread官方技能社区原创技能博文优秀奖,荣获CSDN博客专家、CSDN物联网范畴优质创作者、2021年度CSDN&RT-Thread技能社区之星、RT-Thread官方嵌入式开源社区认证专家、RT-Thread 2021年度论坛之星TOP4、华为如此享专家(嵌入式物联网架构规划师)等荣誉。深信【常识改动命运,技能改动国际】!
【经历共享】根据Linux指令行编程环境的栈追溯和反汇编技能实践共享
本文结合一线工程实例,简要介绍了在非IDE且无仿真器环境下怎样高效运用指令行快速进行栈帧追溯和反汇编的原理和办法,希望能给咱们在debug反常代码的时分供给多一些思路和办法。
1 写在前面
在嵌入式开发中,往往受限于体系(芯片)本身的资源环境,无法像根据PC主机开发那样方便地进行程序debug,尤其当程序呈现一些反常的时分,找到有用的办法进行debug,对排查问题非常有帮助。
在实践编程调试环境中,甚至于底子不可能拿到仿真器或者IDE环境来辅助你做反常剖析,这个时分你的排查手法更多的是依赖一些指令行东西,结合编译输出的各种文件,配合代码逻辑做反常剖析。其间,比较常用的办法有栈帧追溯和反汇编技能。
本文将简要介绍一下这两种办法的根底常识,需求对咱们有所帮助;经过本文的阅览,你讲会了解到以下常识点:
- 代码构建时可能输出的各种文件介绍
- N种常见常用的指令行东西
- 栈帧追溯的根本原理和操作办法
- 反汇编的根本操作办法
- 汇编代码的根本阅览技巧
PS:本次共享源自团队neibu的现场共享,所以部分深入的内容不便揭露,部分文字也不可能百分百复原现场的讲解,感兴趣的能够私下交流。
2 代码构建体系简介
2.1 常见的构建体系
有传统的Make、CMake,也有新干流的scons、xmake的,我个人观点,构建功能无所谓肯定的好坏,首要看各自团队的掌握情况以及需求程序,大可不必迫切追新,根底原理都是一样,万变不离其间,学精通一个就差不多了,其他的只是语法、指令、函数不一样罢了。
2.2 编译构建生成的文件简介
以 GCC 编译器为例,
下面是业内常见叫法的文件:
- .d文件 :.d文件的内容是参与编译的C源码文件
- .i文件 : .i 文件的内容是C源码文件被履行 预处理 之后的代码文本(可读的文本文件)
- .s文件 : .s 文件的内容是预处理之后的文件(.i文件)经过 编译 之后,生成的 汇编代码 文件 (可读的文本文件)
- .o文件 : .o 文件的内容是编译器对汇编文件进行汇编处理后,生成的 方针文件,(不可读的二进制文件);在 GCC 编译器中,.o文件实践也是一个 elf 文件;
- .map文件:map文件便是经过编译器编译之后,生成的程序、数据及IO空间信息的一种映射文件,里边包含函数巨细,入口地址等一些重要信息;
- .elf文件 :.elf文件(Executable and Linkable Format),是Linux渠道是的可履行文件,里边包含代码段和数据段,以及可能包含的调试信息等等;
- .hex文件 :Intel HEX文件是由一行行契合Intel HEX文件格局的文本所构成的ASCII文本文件;
- .bin文件 : 纯二进制文件,一般用做烧录flash的固件文件;
- .a文件 :静态库文件,通常是原厂把不希望揭露的代码打包成静态库的方式开放给第三方运用;
- .mk文件:即为Makefile文件,里边遵从Makefile的标准语法。
下面几个文件后缀,是咱们自己约好的文件:
.cmd文件 :.cmd是指令文件,里边保存的是一些流程需求用到文本指令;(在构建时直接把相关指令打包好,放在这个文件里)
.dmp文件:.dmp是反汇编的输出文件,用于保存反汇编得到的汇编的代码。
2.3 几个常用的指令行东西
以下几个Linux 指令行东西,常常用于剖析上述文件,用好它们一定能让你的作业事半功倍。
- vim : Linux下文本编辑神器
- grep :关键字检索东西,搜索log,搜索代码的神器
- find :查找指定类似文件的神器
- file :剖析文件类型的神器
- size :剖析程序巨细的神器
- readelf :剖析elf文件的神器(object文件、so文件、elf可履行文件,本质都是elf文件)
- nm : 剖析静态库(.a)文件的神器
- objdump :反汇编神器 (下文详讲)
- addr2line :栈追溯神器 (下文详讲)
3 根据指令行东西的栈追溯
3.1 栈追溯技能简介
栈追溯技能,一般来说,便是利用 处理器反常 时的现场信息(首要是寄存器信息),把函数的调用栈联系捋出来,然后复原出代码调用的反常途径,辅助排查反常问题的一种技能。
3.2 栈追溯技能的根底常识
3.2.1 ARM寄存器简介
ARM有37个寄存器,31个通用寄存器,6个状况寄存器:
首要介绍如下:
不分组通用寄存器R0-R7:这意味着在所有处理器模式下,拜访的都是同一个物理寄存器。不分组寄存器没有被体系用于特别的用处,任何可采用通用寄存器的应用场合都能够运用未分组寄存器;
- R0-R3:函数调用的入参和出参;
- R4-R7:函数履行的中心变量;
- 规则由 ATPCS(即ARM-THUMB procedure call standard(ARM-Thumb过程调用标准))描绘。
分组寄存器R8-R12:处理器保留做特殊通途;
寄存器R13通常做堆栈指针SP;
寄存器R14用作子程序链接寄存器(Link Register-LR),也称为LR,指向函数的返回地址;
寄存器R15被用作 程序计数器,也称为PC。
3.2.2 RISC-V寄存器简介
以32位RISC-V处理器为例,它的寄存器描绘如下表所示:
3.2.3 函数调用的栈描绘示意图及栈追溯的根本原理
函数调用的示意图如下所示:
在排查栈追溯相关的问题中,咱们首要重视的寄存器是:PC寄存器和LR寄存器,核心思路便是把LR地址串起来,本质上就复原了函数调用的层次联系。
3.3 根据指令行addr2line的栈追溯实践
以 某RISC-V芯片渠道 为例,实践一下:
recan@ubuntu:~/build$ ./build.sh backtrace 23037250 2303660e 23038c2a 2300e45a 2300e070 2300e344
elf file : /home/recan/ccc.elf
================ Backtrace =================
| backtrace[05] 2300e344 protocol_handle_task at protocol.c:416 (discriminator 1)
| backtrace[04] 2300e070 protocol_parse at protocol.c:102
| backtrace[03] 2300e45a read_round_buf at roundbuf.c:131
| backtrace[02] 23038c2a mutex_lock at osal.c:91
| backtrace[01] 2303660e xQueueSemaphoreTake at queue.c:1517
V backtrace[00] 23037250 xTaskGetSchedulerState at tasks.c:4039
============================================
这个build.sh是咱们的构建编译的前端shell脚本,用于接收参数输入,内容不方便揭露(你懂的),但是怎样调起addr2line能够简略说一下,里边的脚本核心便是调用如下:
addr2line_cmd="$backtrace_cmd 0x$addr -e $backtrace_elf_file -f -C -s -p"
其间addr就填脚本传入的那些栈帧地址,比方23037250,backtrace_elf_file就填你编译固件输出的elf文件。 还有便是这个 addr2line,一般交叉编译东西链里边都有,跟xxx-gcc是在一起的,比方我这儿是:riscv64_unkown_elf_gcc10.2.0/Linux64/bin/riscv64-unknown-elf-addr2line
得到的输出应该是类似于这样:23037250 xTaskGetSchedulerState at tasks.c:4039
然后把多个地址的输出组起来,把调用层次联系捋一下,便是是从上往下调用了。
比方有一段反常代码如下:
运转之后,反常现场呈现了:
Exception Entry--->>>
mcause 38000005, mepc 23002880, mtval 00000008, tasksp 420242d0
Current task sp data:
RA:2300c8b2, mstatus:80007880
A0:00000000 A1:4201d7c0 A2:4201d810 A3:230b1580 A4:4201d811 A5:65707974 A6:65707974 A7:4201d811
T0:00000020 T1:4200f000 T2:00002d25 T3:4200f000 T4:4200f000 T5:00000000 T6:5baba208
S0:42024358 S1:0000000e S2:230b06cc S3:230b1000 S4:42010254 S5:00004007 S6:00002710 S7:230b1000
S8:230b1000 S9:230b1000 S10:4201cd00 S11:230b1000
Exception code: 5
msg: Load access fault
!!!maybe crash task name:x-task
==========
Task name:x-task
Backtrace: 23037e54 23037f0e 42023f88
==========
咱们先试下x-task任务的 栈追溯:
咱们回过头来,单传入 PC 指针 进行栈追溯看看:(mepc 23002880)
看样子,有点东西,咱们找到这个C文件的对应行数:
公然跟咱们上面特意写的反常代码是符合的。
所以啊,也不能太一味地信赖栈帧,有时直接对PC指针进行addr2line也很好使。
4 根据指令行objdump的反汇编
4.1 反汇编技能简介
所谓的反汇编技能 便是履行与编译器编译C源码代码相关的流程,归于逆向剖析的一种。
简略来说,如下:
程序汇编:将C源代码翻译成汇编代码,从而翻译成适用于处理器运转的二进制指令;
程序反汇编:将方针文件换成会汇编代码,再由汇编代码尽可能地复原出具有可读性的高级言语代码(比方C代码)。
- 根据elf文件的反汇编:假如elf文件中带有调试信息,根本能够做到 汇编代码与C代码一一对应;
- 根据bin文件的反汇编:一般来说,不太可能之间翻译成C代码,但能够直接翻译成汇编代码;汇编代码到C代码,需求借助一些东西,比方IDA东西。
4.2 反汇编技能的根底常识
要想熟练掌握反汇编技能,除了根本的反汇编操作办法外,剩余的便是了解对应处理器渠道的汇编指令集。
因为不同的处理器的汇编指令集都不一样,甚至于一种处理器的不同版别对应的指令集也不尽相同(比方ARM处理器),所以只能是学以致用,一边学习,一边应用。
比方开源的 RISC-V指令集 :
一大堆指令规范文档,慢慢啃吧。
4.3 根据objdump指令行的反汇编实践
以 某ARM架构的处理器 为例:
recan@ubuntu:~/build$ ./build.sh objdump
elf file : ./binary/bbb.elf
objdump ... please wait some time ...
objdump done, please check dump file with the following cmd:
[ vi ./binary/bbb.dmp ]
查找一个函数:自己完结的函数 softap_decrypt_password 剖析C代码和汇编代码。
查找一个 原厂供给的接口函数(打包在静态库里边的函数)rwip_aes_encrypt 剖析其汇编代码:
假如我的程序不是根据我的构建流程编译出来的,能进行反汇编吗? 答案是肯定能够的,只需芯片渠道和编译东西用的是同一个。
下面我把objdump的指令参数揭露一下:
arm-none-eabi-objdump -l -d -x -s -S xxx.elf > xxx.dmp
其间xxx.elf为编译输出的elf文件,注意,假如想在反汇编的代码中看到C代码与汇编代码的对应联系,记得把GCC的-g选项翻开;
因为objdump是直接输出到控制台的,所以需求重界说输出到某个文件中,随意取一个姓名即可。
看固件的代码巨细,反汇编的时分可长可短,完结之后,翻开xxx.dmp文件就能够看到反汇编之后的汇编代码了。
5 Q & A
-
map文件中能够看到局部变量的地址吗? 【答案:看不到】
-
履行backtrace操作的时分,有时分会下面的提示,是不是出什么问题了?
backtrace[03] 01000000 ?? ??:0
-
已然能反汇编,那岂不是没有源码隐秘可言?那我不是就能够看到原厂封装的那些库文件的源代码了? 【你觉得呢?】
-
静态库里边能看到结构体是怎样界说的吗? 【答案:看不到】
-
快速阅览汇编代码及了解其大致的完结逻辑,有什么技巧可言? 【你觉得呢?】
6 参考链接
- RISC-V的汇编
- ARM的汇编
7 更多共享
架构师李肯
一个专注于嵌入式IoT范畴的架构师。有着近10年的嵌入式一线开发经历,深耕IoT范畴多年,熟知IoT范畴的事务开展,深度掌握IoT范畴的相关技能栈,包含但不限于干流RTOS内核的完结及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其完结、底层汇编及编译原理、编译优化及代码重构、干流IoT云渠道的对接、嵌入式IoT体系的架构规划等等。具有多项IoT范畴的发明专利,热衷于技能共享,有多年编撰技能博客的经历堆集,接连多月取得RT-Thread官方技能社区原创技能博文优秀奖,荣获CSDN博客专家、CSDN物联网范畴优质创作者、2021年度CSDN&RT-Thread技能社区之星、RT-Thread官方嵌入式开源社区认证专家、RT-Thread 2021年度论坛之星TOP4、华为如此享专家(嵌入式物联网架构规划师)等荣誉。深信【常识改动命运,技能改动国际】!
欢迎重视我的github仓库01workstation,日常共享一些开发笔记和项目实战,欢迎纠正问题。
同时也非常欢迎重视我的CSDN主页和专栏:
【CSDN主页:架构师李肯】
【RT-Thread主页:架构师李肯】
【C/C++言语编程专栏】
【GCC专栏】
【信息安全专栏】
【RT-Thread开发笔记】
【freeRTOS开发笔记】
有问题的话,能够跟我评论,知无不答,谢谢咱们。