嵌入式软件调试

嵌入式软件调试

2025-06-01 19:50:16

嵌入式软件调试理论基础

• 什么是软件调试?

• 英文software debug,又译软件侦错

• 软件调试过程,就是发现软件失效,定位软件错误并将其修复的过程

软件调试的重要性

• 据统计:软件调试、 debug时间一般占软件开发周期的50%以上,是软件开发中耗时最多的一项活动

• 很多项目延期,往往就栽在不能定位的bug上。

• 随着软件、系统越来越复杂,软件调试技术需要与软件工程、开发技术同步升级

• 软件调试理论和知识尚未系统化,很多开发者对其基本原理知之甚少,不能根据实际情况融会贯通地去使用各种调试技巧,对于复杂问题、BUG往往一筹莫展、无能为力

软件调试的特点

• 是一项具有挑战性、很强技巧性的工作

• 复杂度高、难度大,必须通过现象,大量的分析,才能逐步接

近真相,犹如福尔摩斯探案,抓住蛛丝马迹,层层推理。

• 需要知识面广:设计到硬件、软件、操作系统、编译器等。

• 有些bug极难复现,定位困难。

• 是一项不受欢迎的工作

• 对心理影响:

– 打击人的自信、消磨工作热情、考验人的耐心和抗压能力、

怀疑人生、怀疑自己是不是这块料…

• 对生理影响:

– 脾气暴躁、上火、口腔溃疡、失眠…

嵌入式软件调试特点

• 调试环境和运行环境不在一个平台上,增

加了调试的难度

• 嵌入式设备往往没有输出、打印终端,遇

到问题,只能看日志

• 考虑问题还要考虑硬件问题、时序问题,

需要软硬件结合去思考问题,增加了调试

的难度

软件调试一些参考经验

• 寻找类似的bug,一些常见的出错场景

• 使用版本管理工具查看最近代码的变化

• 打印有时候会失效:比如在打印之前程序已经crash

• 尽可能深入准确理解系统、不适当的改动可能使事情更糟

• 体力不支时、没思路时可以稍事休息,保持充沛精力

• 对于多模块系统,多沟通、多交流,而不是相互推诿

常用的嵌入式软件调试工具

• 软件

• IAR、 ADS/AXD、 Keil、 MDK、 RVDS、 Eclipse、 H-JTAG、 Trace32

• GCC、 GDB、 KGDB、 JDB、

• 性能分析工具、内存分析工具

• 硬件

• 万用表、示波器、逻辑分析仪、仿真器

• Jlink、 JTAG

• 常用的软件调试技术

软件调试技术分类

• 按目标代码的执行方式

• 脚本调试、托管调试、本地调试、混合调试

• 按目标代码的执行模式

• 用户态调试、内核态调试

• 按软件所处的阶段

• 开发期调试、产品期调试

• 按调试器和调试目标的相对位置

• 本地调试、远程调试

打印

• 输出调试信息

• 打印函数栈、变量值

• 日志、文件转储

• 应用场合

• 错误简单,直接打印比使用调试器方便

• 难以使用调试器的环境、或者使用调试器无法重现

调试器

• 断点

• 单步执行

• 事件追踪

• 栈回溯

• 反汇编

• 观察和修改寄存器、内存数据

• 控制被调试的进程或线程

使用调试器有哪些优点

• 不需要预知错误在哪里

• 支持在线检查错误,不需要改代码、重新编译

• 可以看到运行时的各种数据:变量值、寄存器、内存数据…

• 单步

调试器调试一般步骤

• 定位出现错误的场景

• 分析错误、粗略定位可能出错的代码

• 设置初始断点

• 开始调试程序或者attach一个已经运行的

程序进程

• 在断点上观察数据各种数据:变量、寄存

器、调用栈、反汇编, dump有用的数据

• 单步执行、更新断点

• 结束调试器

• 常见的错误类型

编译型错误

• 语法规则检查

• C语言的基本语法、关键字、运算符、表达式

• 中英文符号

• 全角、半角的问题

• 函数问题

• 函数声明与函数定义不匹配:函数参数、返回类型

• 误认为形参改变会影响实参的值

• 函数的实参和形参类型不一致

• 函数未声明

• 有时候一个warning也是引起软件失效的诱因

运行时错误

• 对异常未做处理

• 打开的文件未找到

• 磁盘空间不足

• 内存不足

• 网络异常

• Scanf输入格式、忘记地址符

• 堆栈溢出

• 空指针的引用

• 未初始化局部变量

• 数组问题

• 数组越界, C语言并不会对数组做边界检查

• 数组下标

• 混淆数组名与指针的区别、数组作为参数的无用

逻辑错误

• 运算符

• =和==、 &和&&、 |和||混用

• 运算符的优先级和结合性

• 循环条件设置问题

• 未注意int、 char类型的数值范围,导致死循环

• 循环边界控制

• 链表的头尾判断、空链表处理

• 业务逻辑错误

• 死锁

• 不良的编程习惯和代码风格

• 不该加分号的地方加了分号或者少加分号

• 花括号忘记使用,导致错误的逻辑分支

内存错误

• 内存溢出

• 内存泄露

• 内存踩踏

• Debug文件和release文件的区别

目标文件的链接过程

可执行文件的运行

• 调试符号

什么是调试符号

• 二进制代码与源程序联系的桥梁

• 很多调试必须依赖调试符号才能工作

• 如:源代码级调试、 栈回溯、按名称显示变量等

• 生成过程

• 在编译过程中,编译器从源文件收集调试信息供开发者调试使用

• 这些信息以表格形式记录在符号表中,是对源程序的概括

• 包括变量、类型、函数、标号和源代码行等。

• 存储方式

• 由编译器收集和提炼后,再由链接器或者专门工具保存到调试符

号文件中。

• 调试符号可存储在单独的文件,也可与目标文件共享一个文件

• 调试信息的存储格式

• COFF格式

• 二进制格式,用来存储可执行映像文件、目标文件、库文件

• CodeView格式

• 与MSC编译器一起使用的调试器

• CV格式的调试信息可以与映像文件保存在一起,也可单独存放

• PDB格式

• PDB格式的调试信息需单独存储在一个文件中

• 如VC++6.0中的.pdb文件

• DWARF格式

• 公开的调试信息格式规范,主要用在Unix、 Linux发行系统中

• GCC和GDB都支持这种格式

• 目标文件中的调试信息

image-20250602172333724

• 目标文件

• 编译器用来存放目标代码的文件

• VC使用COFF格式来存储目标文件

• 目标文件格式

• 文件头结构

• 节头部数据结构:

• 三种数据: 原始数据、 重定位信息、 行

号信息

• 节数据之后是调试符号表和字符串表

• 重定位信息和行号信息

• 重定位信息:链接和加载映像文件时应

如何修改节数据、重定位的地址和方法

• 行号信息: 用来描述源代码行和目标代

码的对应关系

• 使用GCC编译debug目标文件

• 断点和单步是怎么实现的?

• CPU对调试的支持

• CPU指令和指令集

• 为某一类CPU所支持的指令集被简称为指令集,根据指令集特征,

CPU可划分为两大阵营: RISC和CISC

• RISC:通过减少指令集数量和简化指令格式来提高和优化CPU执行

指令效率。例如: ARM、 MIPS、 Alpha、 SPARC、 PowerPC

• CISC: X86系列处理器

• CPU对断点调试的支持

• 支持断点调试指令: INT 3指令

• 标志寄存器: EFLAGS寄存器中的TF标志位

• 1: CPU执行完一条指令都会产生调试异常, CPU转到

ISR中去,在该ISR中可以很多调试操作

• 该标志是实现单步调试的基础

• 调试寄存器: DR0~DR7

• JTAG支持:单独靠软件无法调试的裸板、系统bringup调试

• 操作系统对调试的支持

• 在内核层面提供支持

• 提供支持远程调试协议的通信模块

• 提供断点设置函数

• 提供软陷异常处理:调试功能

• 对用户态调试器的支持

• 创建调试目标的系统函数

• 在调试循环中处理调试事件的系统函数

• 查看和修改调试目标的系统函数,这些系统函数用于调试事件

的处理过程中

• 用户态调试器将这些系统函数与其它函数结合起来,从而提供

强大的功能

• 在调试器中加入断点

.

• 单步

• 获取变量值

进程中可以以地址来标识变量、函数,但不知

道每个地址的含义、地址对应的名称

• 调试器使用符号表从地址获得变量名

• 调试信息的内容

• 地址对应的变量名、函数名

• 指令对应的源文件及其行号

• 数据结构的信息等

• 调试信息的保存

• Debug版本程序:添加到目标文件中

• Release版本:单独的符号文件

• DBG文件

• PDB文件

• MAP文件

• 读写寄存器

• CPU对调试的支持

• CPU自身提供的机制

• ARM结构CPU寄存器

• JTAG扫描链电路+ARM 寄存器

• 仿真器调试原理

• 嵌入式常用调试手段

• 软件模拟器

• 在PC上模拟目标CPU并执行用户目标代码。如ARM仿真器: ARM

armulator,可以模拟运行ARM指令系统

• 目标Monitor

• 将目标代码下载到用户目标板的存储器重,并增加一个monitor软

件,用来监听用户目标代码的执行。用户通过串口等调试端口,

通过PC进行调试。如ARM基于调试代理angel的调试

• 缺点:耗费MCU、 CPU资源、目标系统必须是一个完整的系统、

无法在ROM区设置断点、对于存储受限的单片机等并不适用

• 仿真器

• 一般会有一个仿真头、 代替目标系统中的MCU、 CPU, 并仿真其

运行,可以连接目标板,甚至不连接都可以。

• 仿真器运行起来跟实际的目标处理器一样,增加了调试功能、支

持在线调试

• 为什么要使用仿真器调试

• 在前期硬件验证上必须使用仿真器(目标系

统硬件不完整也可以运行)

• 硬件实际性能测试(电路、电容、电感等)

• 额外的优点

• Monitor要占用额外的存储和通信端口,仿真器不需要目标系

统或CPU资源

• 硬件断点:调试ROM或者NOR 存储模式的目标系统毫无压力

• 跟踪功能:能够记录所有的取指操作

• 条件触发

• 实时显示存储器、内存和I/O内容

• 硬件性能分析

Trace32仿真器介绍

• 德国Lauterbach公司研发

• 通用性:更换仿真头子可以调试不同芯片

• 仿真器调试的缺点

仿真器的缺点

• 贵

• 仿真器有自己的目标CPU、 RAM甚至ROM以及软件

• 通用性差

• 随着CPU不断发布、升级,需要同步升级。

• 硬件仿真效果跟实际处理器还是有差异

• 仿真器中的CPU跟目标系统中的CPU电气特性不同

• 不能反映实际时序

• JTAG和JLINK调试原理及区别

• JTAG调试原理

• 测试接口标准化

• 在芯片内部定义一个标准的测试访问接口(TAP ),通过专用的

JTAG测试工具对芯片内部节点进行测试和调试

• JTAG(Joint Test Action Group)

• 一种国际标准测试协议,主要用于芯片内部测试

• 目前大多数芯片都支持JTAG协议: ARM、 DSP、 FPGA等

• JTAG接口

• TMS:测试模式选择

• TCK:测试时钟输入

• TDI: 测试数据输入,数据通过TDI引脚输入JTAG接口

• TDO:测试数据输出,数据通过TDO引脚从JTAG接口输出

• JTAG接口

image-20250602174440435

image-20250602174445412

image-20250602174447903

JTAG实物图

• 边界扫描电路

• 测试访问口TAP

• TAP

• Test Access Port

• 是一个通用端口,通过该端口可以访问数据寄存器和指令寄存

• 通过TAP控制器来对整个TAP端口的控制

• 总结

• PC机对目标板的调试就是通过TAP接口完成对数据寄存器DR和

指令寄存器IR的访问。

• Jlink调试原理

• RDI调试接口

• ARM公司提出的调试接口标准,实现跨平台的硬件调试

• 各大调试工具Keil、 ADS、 IAR等都使用RDI公共调试接口

• RDI命令到JTAG协议的转换

– 软件实现

• 在PC写一个程序,将ADS/Keil的RDI命令解析成JTAG协议,然后

通过物理转换接口(仅仅是电气物理层上的转换,像RS232),发

送到目标板。如H-JTAG工具

– 硬件实现

• 做一个电路板,直接接收来自Keil、 ADS等软件的调试命令,硬

件实现RDI到JTAG协议的转换,与目标板通信。如Jlink类似工具

• 相对于软件转换,硬件速度快,而且不需要装jtag解析软件

Jlink工具实物图

printf函数打印高阶技巧

• 输出重定向

• 流的概念

• 程序输入或输出的一个连续的字节序列

• 设备(鼠标、键盘、打印机、屏幕…)的输入输出使用流来处理

• 在C语言中,所有流均以文件的形式出现

• 统一了各种硬件操作接口带来的差异

• C语言中提供的5中标准流

名称
stdin
stdout
stderr
stdprn
stdaux

• 利用shell的I/O进行输出重定向

• 在Linux下,文本流和二进制流是相同的

• 文本流是由文本行组成的序列,以’\n’结尾(或有回车符换行符

代替),二进制流由未经处理的字节组成

• 流与文件的连接

• 打开一个流,该流将与一个文件连接起来,关闭流则断开连接

• 指向该打开文件的指针记录了控制该流的信息

• 程序执行时, 默认会打开stdin、 stdout和stderr三个文件

• 重定向符

• 输出重定向: >、 >>、 >!

• 输入重定向: <

  • >:将输出写入文件,覆盖原有内容。
  • >>:将输出追加到文件,不覆盖原有内容。
  • <:将文件内容作为程序的输入。
  • >!:在某些shell中用于强制覆盖文件(但现代shell通常不需要)。

• 标准错误重定向

• 2>:标准错误重定向到一个文件,并覆盖原来的文件(b-shell)

• 2>>:标准错误重定向到一个文件(追加)。 1>默认为>

• 2>&1:将标准错误重定向到标准输出

• >&:将一个输出重定向到另一个句柄的输入中

• 为什么要进行重定向

• 将屏幕输出的重要信息保存下来

• 有时候不希望打印干扰屏幕

• 正确和错误的信息需要分别输出时

• 使用freopen重定向输入输出流

• 输出重定向

• 错误重定向

• 打印文件名、函数名、行号

• 打印缓冲问题

• 打印开关控制

• 打印等级控制

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值