如何调试程序中存在错误或CPU内部发生的错误?

商务办公
如果把程序(program)中的每一条指令看作电影胶片的一帧,那么执行程序的CPU就像一台飞速运转的放映机。

 如果把程序(program)中的每一条指令看作电影胶片的一帧,那么执行程序的CPU就像一台飞速运转的放映机。以英特尔P6系列CPU为例,其处理能力大约在300(一代产品Pentium Pro)~3000(奔腾III)MIPS。MIPS的含义是CPU每秒钟能执行的指令数(以百万指令为单位)。如果按3000MIPS计算,那么意味着每秒钟大约有30亿条指令“流”过这台高速的“放映机”。这大约是电影胶片放映速度(24帧每秒)的1.25亿倍。如此高的执行速度,如果在程序中存在错误或CPU内部发生了错误,该如何调试呢?

[[258659]]

CPU的设计者们一开始就考虑到了这个问题—— 如何在CPU中包含对调试的支持。就像在制作电影过程中人们可以慢速放映或停下来分析每一帧一样,CPU也提供了一系列机制,允许一条一条地执行指令,或者使其停在指定的位置。

以英特尔的IA结构CPU为例,其提供的调试支持如下。

  • INT 3指令:又叫断点指令,当CPU执行到该指令时便会产生断点异常,以便中断到调试器程序。INT 3指令是软件断点的实现基础。
  • 标志寄存器(EFLAGS)中的TF标志:陷阱标志位,当该标志为1时,CPU每执行完一条指令就产生调试异常。陷阱标志位是单步执行的实现基础。
  • 调试寄存器DR0~DR7:用于设置硬件断点和报告调试异常的细节。
  • 断点异常(#BP):INT 3指令执行时会导致此异常,CPU转到该异常的处理例程。异常处理例程会进一步将异常分发给调试器软件。
  • 调试异常(#DB):当除INT 3指令以外的调试事件发生时,会导致此异常。
  • 任务状态段(TSS)的T标志:任务陷阱标志,当切换到设置了T标志的任务时,CPU会产生调试异常,中断到调试器。
  • 分支记录机制:用来记录上一个分支、中断和异常的地址等信息。
  • 性能监视:用于监视和优化CPU及软件的执行效率。
  • JTAG支持:可以与JTAG调试器一起工作来调试单独靠软件调试器无法调试的问题。

除了对调试功能的直接支持,CPU的很多核心机制也为实现调试功能提供了硬件基础,比如异常机制、保护模式和性能监视功能等。

CPU是Central Processing Unit的缩写,即中央处理单元,或者叫中央处理器,有时也简称为处理器(processor)。头一款集成在单一芯片上的CPU是英特尔公司于1969年开始设计并于1971年推出的4004,与当时的其他CPU相比,它的体积可算是微乎其微,因此,人们把这种实现在单一芯片上的CPU(Single-chip CPU)称为微处理器(microprocessor)。目前,绝大多数(即使不是全部)CPU都是集成在单一芯片上的,甚至多核技术还把多个CPU内核(core)集成在一块芯片上,因此微处理器和处理器这两个术语也几乎被等同起来了。

尽管现代CPU的集成度不断提高,其结构也变得越来越复杂,但是它在计算机系统中的角色仍然非常简单,那就是从内存中读取指令(fetch instruction),然后解码(decode)和执行(execute)。指令是CPU可以理解并执行的操作(operation),它是CPU能够“看懂”的语言。本文将以这一核心任务为线索,介绍关于CPU的基本知识和概念。

指令和指令集

某一类CPU所支持的指令集合简称为指令集(Instruction Set)。根据指令集的特征,CPU可以划分为两大阵营,即RISC和CISC。

精简指令集计算机(Reduced Instruction Set Computer,RISC)是IBM研究中心的John Cocke博士于1974年提出的。其基本思想是通过减少指令的数量和简化指令的格式来优化和提高CPU执行指令的效率。RISC出现后,人们很自然地把与RISC相对的另一类指令集称为复杂指令集计算机(Complex Instruction Set Computer,CISC)。

RISC处理器的典型代表有SPARC处理器、PowerPC处理器、惠普公司的PA-RISC处理器、MIPS处理器、Alpha处理器和ARM处理器等。

CISC处理器的典型代表有x86处理器和DEC VAX-11处理器等。头一款x86处理器是英特尔公司于1978年推出的8086,其后的8088、80286、80386、80486、奔腾处理器及AMD等公司的兼容处理器都是兼容8086的,因此人们把基于该架构的处理器统称为x86处理器。

基本特征

下面将以比较的方式来介绍RISC处理器和CISC处理器的基本特征和主要差别。除非特别说明,我们用ARM处理器代表RISC处理器,用x86处理器代表CISC处理器。

一,大多数RISC处理器的指令都是等长的(通常为4个字节,即32比特),而CISC处理器的指令长度是不确定的,最短的指令是1个字节,有些长的指令有十几个字节(x86)甚至几十个字节(VAX-11)。定长的指令有利于解码和优化,其缺点是目标代码占用的空间比较大(因为有些指令没必要用4字节)。对于软件调试而言,定长的指令有利于实现反汇编和软件断点,我们将在4.1节详细介绍软件断点。这里简要介绍一下反汇编。对于x86这样不定长的指令集,反汇编时一定要从一条有效指令的字节开始,依次进行,比如下面3条指令是某个函数的序言。

  1. 0:000> u 47f000 
  2. image00400000+0x7f000: 
  3. 0047f000 55 push ebp 
  4. 0047f001 8bec mov ebp,esp 
  5. 0047f003 6aff push 0FFFFFFFFh 

上面是从正确的起始位置开始反汇编,结果是正确的,但是如果把反汇编的起点向前调整两个字节,那么结果就会出现很大变化。

  1. 0:000> u 47effd 
  2. image00400000+0x7effd: 
  3. 0047effd 0000 add byte ptr [eax],al 
  4. 0047efff 00558b add byte ptr [ebp-75h],dl 
  5. 0047f002 ec in al,dx 
  6. 0047f003 6aff push 0FFFFFFFFh 

这就是所谓的指令错位。为了减少这样的问题,编译器在编译时,会在函数的间隙填充nop或者int 3等单字节指令,这样即使反汇编时误从函数的间隙开始,也不会错位,可以帮助反汇编器顺利“上手”。而上面的例子来自某个做过加壳保护的软件,这样的软件不愿意被反汇编,所以故意在函数的间隙或者某些位置加上0来迷惑反汇编器。

二,RISC处理器的寻址方式(addressing mode)比CISC要少很多,我们稍后将单独介绍。

三,与RISC相比,CISC处理器的通用寄存器(general register)数量较少。例如16位和32位的x86处理器都只有8个通用寄存器:AX/EAX、BX/EBX、CX/ECX、DX/EDX、SI/ESI、DI/EDI、BP/EBP、SP/ESP(E开头为32位,为Extended之缩写),而且其中的BP/EBP和SP/ESP常常被固定用来维护栈,失去通用性。64位的x86处理器增加了8个通用寄存器(R8~R15),但是总量仍然远远小于RISC处理器(通常多达32个)。寄存器位于CPU内部,可供CPU直接使用,与访问内存相比,其效率更高。

四,RISC的指令数量也相对较少。就以跳转指令为例,8086有32条跳转指令(JA、JAE、JB、JPO、JS、JZ等),而ARM处理器只有两条跳转指令(BLNV和BLEQ)。跳转指令对流水线执行很不利,因为一旦遇到跳转指令,CPU就需要做分支预测(branch prediction),而一旦预测失败,就要把已经执行的错误分支结果清理掉,这会降低CPU的执行效率。但是丰富的跳转指令为编程提供了很多方便,这是CISC处理器的优势。

五,从函数(或子程序)调用(function/procedure call)来看,二者也有所不同。RISC处理器因具有较多的寄存器,通常就有足够多的寄存器来传递函数的参数。而在CISC中,即使用所谓的快速调用(fast call)协定,也只能将两个参数用寄存器来传递,其他参数仍然需要用栈来传递。从执行速度看,使用寄存器的速度更快。我们将在后面关于调用协定的内容中进一步讨论函数调用的细节。

鉴于以上特征,RISC处理器的实现相对来说简单一些,这也是很多低成本的供嵌入式系统使用的处理器大多采用RISC架构的一个原因。关于RISC和CISC的优劣,一直存在着很多争论,采用两种技术的处理器也在相互借鉴对方的优点。比如从P6系列处理器的一代产品Pentium Pro开始,英特尔的x86处理器就开始将CISC指令先翻译成等长的微操作(micro-ops或µops),然后再执行。微操作与RISC指令很类似,因此很多时候又被称为微指令。因此可以说今天的主流x86处理器(不包括那些用于嵌入式系统的x86处理器)的内部已经具有了RISC的特征。此外,ARM架构的v4版本引入了Thumb指令集,允许混合使用16位指令和32指令,指令的长度由单一一种变为两种,程序员可以根据需要选择短指令和长指令,不必再拘泥于一种长度,这样可使编译好的目标程序更加紧凑。

 

责任编辑:武晓燕 来源: 今日头条
相关推荐

2017-03-03 10:47:57

TensorFlowtfdbg机器学习

2010-08-06 13:43:45

Flex调试

2010-01-26 14:59:29

Android调试程序

2024-10-12 16:48:48

2011-07-19 10:33:53

Mac OS X Li

2024-10-16 08:23:15

大型语言模型LLM机器学习

2015-07-28 14:45:31

ubuntu修复系统程序

2010-01-20 10:39:52

Linuxcore

2010-01-25 18:15:52

2018-03-05 19:20:49

LinuxWordPressHTTP

2011-06-16 17:05:54

CSS

2010-11-08 14:45:44

SQL Server连

2021-01-14 21:37:01

JavaScript开发代码

2018-08-30 10:28:05

修复Windows 10IntcOED

2021-03-02 07:31:26

WebApiweb

2023-05-04 12:39:27

GDB命令程序

2019-06-12 10:15:17

Windows 10 错误系统配置信息

2012-09-24 09:29:11

云应用部署云计算模式应用性能监控

2022-09-16 15:10:12

模型AI

2011-03-21 15:25:52

MySQL系统错误
点赞
收藏

51CTO技术栈公众号