调试是什么意思(产品调试是什么意思)

1955年,一家名为计算机使用公司(CUC)的公司诞生了。它是世界上第一家专门从事软件开发和服务的公司。CU公司的创始人是埃尔默·库比(Elmer Kubie)

1955年,一家名为计算机使用公司(CUC)的公司诞生了。它是世界上第一家专门从事软件开发和服务的公司。CU公司的创始人是埃尔默·库比(Elmer Kubie)和约翰·w·谢尔登(John W. Sheldon),两人都曾供职于IBM。他们从当时计算机硬件的快速发展中看到了软件方面的潜在机会。CUC的诞生标志着一个新行业的正式开始。

与其他行业相比,软件行业的发展速度是惊人的。仅仅过了60多年,我们已经很难数清世界上有多少家软件公司了。我们只知道那一定是一个巨大的数字,而且这个数字还在增加。与此同时,软件产品的数量已经到了难以统计的程度。各类软件已经渗透到人类生产生活的各个领域,越来越多的人开始依赖软件进行工作和生活。

软件产品与传统产品相比有着本质的不同,其生产流程也有着本质的不同。在软件开发的整个过程中,有很多不确定的因素。在软件真正完成之前,很难预测它的完成日期。很多软件项目都经历过多次延期,很多中途夭折。​

直到今天,人们还没有找到一种有效的方法来控制软件的生产过程。软件生产不可控的根本原因是软件本身的复杂性。软件的规模越大,复杂程度越高。​

调试是什么意思(产品调试是什么意思)插图

简单来说,软件是程序和文档的集合,程序的核心内容是按照一定顺序排列的一系列指令。如果把每一条指令看成一个积木,那么软件开发就是用这些积木搭建一个CPU(中央处理器)运行的交通系统。这个系统中有很多各具特色的道路(功能)。

如果说软件的执行过程就像CPU驰骋在无数条道路(指令流)上,那么开发软件的过程就是设计和构建这个交通网络的过程。

它的基本目标是使CPU在这个网络中运行时能够完成需求中定义的功能。对该网络的其他要求通常是可靠性、灵活性、健壮性和易维护性。开发者可以通过简单的修改让其他类型的车辆(CPU)在上面运行...

开发一个满足上述要求的软件系统并不是一件简单的事情。通常需要经过分析、设计、编码、测试等多个步骤。通过测试和发布后,需要进行维护和支持工作。在以上环节中,每一步都可能遇到这样那样的技术问题。

在软件的世界里,传统的螺丝刀、万用表等检测工具和修复工具已经不再适用,取而代之的是以调试器为核心的各种软件调试工具。软件调试的基本手段有断点、单步执行、堆栈回溯等。它的初衷是跟踪记录CPU执行软件的过程,将动态的瞬间“定格”,以供检查分析。

软件调试的基本目标是定位软件中的设计错误。但除此之外,软件调试技术和工具还有很多其他用途,比如分析软件的工作原理,分析系统崩溃,辅助解决系统和硬件问题等。

综上所述,软件通过指令的组合来指挥硬件,既简单又复杂,是一个充满神秘和挑战的世界。软件调试是帮助人们探索和征服这个神秘世界的有力工具。

简介

本文首先给出了软件调试的解释性定义,然后介绍了软件调试的基本过程。

定义

什么是软件调试?先说英文单词软件调试的原话。Debug就是在bug这个词前面加上前缀de,意思是把bug分离出来,去掉。

Bug的意思是“昆虫”,但早在19世纪,人们就开始用这个词来形容电子设备中的设计缺陷。著名发明家托马斯·阿尔瓦·爱迪生(1847-1931)曾用这个词来形容电路中的设计错误。

1947年9月9日,当人们测试马克2号计算机时,它突然出故障了。工作人员经过几个小时的检查,发现f盘70号继电器里死了一只蛀虫,取出蛀虫后,电脑恢复正常。当时在马克2号计算机公司工作的著名女科学家格蕾丝·赫柏将这只蛾子粘贴到了当天的工作簿上(见图1-1),并在上面加了一行注释——“首次发现实际的臭虫案例”。当时的时间是15:45。

随着这个故事的流传,越来越多的人开始用bug这个词来指代计算机中的设计错误,并将格蕾丝·赫柏注册的飞蛾视为计算机历史上有文件记载的第一个bug。

调试是什么意思(产品调试是什么意思)插图(1)

图1-1计算机历史上记录在文档中的第一个bug

bug这个词被广泛使用后,人们自然开始用debug这个词来指代调试错误的过程。目前,关于谁首先创造并使用了这个词,还没有公认的说法,但可以肯定的是,格蕾丝·赫柏在20世纪50年代发表的许多论文中频繁使用这个词。

所以可以肯定的说,在20世纪50年代,人们就开始用这个词来表达软件调试的意思,一直延续至今。

虽然从字面上来看,debug直接的意思是去除bug,但实际上也涉及到查找和定位bug。因为清除bug的前提是找到bug,如何找到bug比找到之后再清除要困难得多。而且随着计算机系统的发展,软件调试已经变得越来越不像继电器间的“捉虫”那么容易了。

所以在台湾省,人们把软件调试翻译成“软件调试”。这种翻译并没有遵循英语原词的直译,超出了“去除”的本义,融入了“调查”的意思。是非常好的意译。

在国内,我们通常将软件调试翻译为“软件调试”,一般指再现软件故障,定位故障根源,最终解决软件问题的过程。

软件调试的另一种更广义的解释是指利用调试工具解决各种软件问题的过程,如跟踪软件的执行过程,探索软件本身或其他支撑软件,或硬件系统的工作原理等。这些过程的目的可能是也可能不是消除软件缺陷。

基本流程

虽然取出飞蛾非常容易,但还是花了几个小时才找到。所以,从一开始,软件调试其实就包括两个基本步骤:定位错误和排除错误。此外,一个完整的软件调试过程是如图1-2所示的循环过程,包括以下步骤。

调试是什么意思(产品调试是什么意思)插图(2)

图1-2软件调试过程

第一,重现故障,通常是在用于调试的系统上重复导致故障的步骤,使要解决的问题出现在被调试的系统中。

其次是定位根源,即综合利用各种调试工具,运用各种调试手段,寻找软件故障的根源。通常,测试人员报告和描述软件故障的外部症状,如界面或执行结果的异常;或者是不符合软件需求和功能规范,也就是所谓的软件缺陷。

然而,这些表面的缺陷总是由一个或多个内部因素造成的,这些内部因素要么是代码行为错误,要么是“不作为”错误。根本原因是要找到外虚的内因。

第三,探索并实现解决方案,即根据找到的故障根源、资源和紧急程度,设计并实现解决方案。

第四,验证方案,在目标环境下测试方案的有效性,也称为回归测试。如果问题已经解决,那么你可以关闭问题;如果没有,请返回步骤3调整和修改解决方案。

在上述步骤中,定位根本原因往往是最困难也是最关键的一步,是软件调试过程的核心。如果找不到问题的根本原因,那么解决方案很可能是抓一抓,或者治一治头疼。有时候看似缓解了问题,实际上并没有完全解决问题,甚至是浪费时间。

分类

根据被调试软件的特点、使用的调试工具以及软件的运行环境,软件调试可以分为很多子类。本节将介绍几种常见的分类方法,并介绍每种分类方法中的典型调试任务。

1.2.1根据系统环境对调试目标进行分类

软件调试中使用的工具和方法与操作系统密切相关。例如,许多调试器是为操作系统设计的,只能在一个或几个操作系统上运行。软件调试的一个基本分类标准是被调试程序(调试目标)运行的系统环境(操作系统)。

根据这个标准,我们可以将调试分为Windows、Linux、DOS等下的软件调试。

这种分类方法主要是针对编译成机器码的原生程序。对于用Java和。NET,它们具有良好的跨平台特性,与操作系统的相关性较低,因此不适合这种分类方法(见下文)。

1.2.2根据目标代码的执行方式分类

脚本语言简单易学,不需要编译,比如在web开发中广泛使用的JavaScript和VBScript。脚本由专门的解释器解释执行,不需要生成目标代码,与编译执行的程序差别很大。调试用脚本语言编写的脚本程序的过程称为脚本调试。使用的调试器称为脚本调试器。

编译后的程序主要分为两类:一类是先编译成中间代码,然后动态编译成当前CPU运行时可以执行的目标代码。典型的代表是。使用C#开发的. NET程序。另一类是直接编译链接成目标代码的程序,比如传统的C/C++程序。

为了便于区分,对前一类代码的调试一般称为托管调试,对后一类程序的调试称为本地调试。如果希望在同一个调试会话中同时调试托管代码和本机代码,这种调试方法称为混合调试。

图1-3总结了按照执行和编译的方式对软件调试进行分类的判断方法和步骤。

调试是什么意思(产品调试是什么意思)插图(3)

图1-3软件调试按执行和编译分类的判断方法和步骤

1.2.3根据目标代码的执行方式分类

在Windows这样的多任务操作系统中,作为保证安全和秩序的根本措施,系统定义了两种执行模式,即低权限级别的用户模式和高权限级别的内核模式。

应用程序代码运行在用户态,而操作系统的内核、执行器和大部分设备驱动程序运行在内核态。因此,根据被调试程序的执行方式,我们可以将软件调试分为用户态调试和内核态调试。

因为内核态运行的代码主要是本地代码和少数脚本,比如用ASL语言写的ACPI脚本,所以内核态调试主要是调试本地代码。用户模式调试包括调试本地应用程序和调试托管应用程序。1.2.4根据调试器和调试对象的相对位置分类。

如果被调试的程序(调试目标)和调试器在同一个计算机系统中,那么这种调试称为本机调试。这里的同一台计算机系统是指同一台计算机上的同一操作系统,不包括运行在同一台物理计算机上的多个虚拟机。

如果调试器和被调试的程序位于不同的计算机系统中,并且它们通过以太网或其他网络进行通信,那么这种调试方法称为远程调试。远程调试通常需要在被调试程序所在的系统中运行调试服务器程序。这个服务器程序与远程调试器交互,向调试器报告调试事件,并执行调试器发出的命令。

用Windows内核调试引擎进行主动内核调试需要两台机器,通过串口、1394接口或USB 2.0连接。虽然这种调试的调试器和调试目标也在两台机器上,但通常不归为远程调试。

1.2.5根据调试工具分类

软件调试也可以根据使用的工具进行分类。最简单的就是根据调试时是否使用调试器,分为有调试器的软件调试和没有调试器的软件调试。

用调试器调试可以使用断点、单步执行、跟踪执行等强大的调试功能。无调试器调试主要依靠调试信息输出、日志文件、观察内存和文件等。后者的优点是简单,适合调试简单的问题或者调试器不能用的时候。

以上介绍了几种常见的软件调试分类方法,旨在让读者对典型的软件调试任务有一个大致的了解。一些分类方法是交叉的。比如在浏览器中调试JavaScript,就属于脚本调试和用户态调试。

调试技术概述

深入介绍各种软件调试技术是本书的主题。基于循序渐进的原则,在本文中,我们首先概述了各种常用的软件调试技术,以帮助您建立一个总体印象。

1.3.1断点

使用调试器进行调试时,断点是最常用的调试技术之一。基本思想是在某个位置设置一个“陷阱”,当CPU在这个位置执行时,就会“落入陷阱”,即停止执行被调试的程序,并中断给调试器,供调试器分析调试。在调试器的分析之后,被调试程序可以继续执行。

根据断点空的设置,断点可以分为以下几类。

(1)代码断点:设置在内存空中的断点,其地址通常是某段代码的开头。当CPU执行指定内存地址的代码(指令)时,断点命中并中断调试器。使用调试器的图形界面或快捷键在源代码或汇编代码行设置的断点是代码断点。

(2)数据断点:在内存空中设置的断点,其地址一般是被监控变量(数据)的起始地址。当被调试程序访问指定内存地址的数据时,断点就会命中。根据需要,测试人员可以定义访问模式(读/写)和宽度(字节、字、双字等。)的触发器断点。

(3)I/O断点:设置在I/O空室的断点,其地址为I/O地址。当程序使用指定的I/O地址访问端口时,中断调试器。与数据断点类似,测试人员也可以根据需要设置触发的断点的访问宽度。

根据断点的设置方法,我们可以将断点分为软件断点和硬件断点。软件断点通常通过在指定的代码位置插入特殊的断点指令来实现。比如IA32 CPU的INT 3指令(机器码为0xCC)就是断点指令。

硬件断点通常通过设置CPU的调试寄存器来设置。IA32 CPU定义了8个调试寄存器:DR0~DR7,最多可以同时设置4个硬件断点(针对一个调试会话)。上述三个断点中的任何一个都可以通过调试寄存器设置,但是只有代码断点可以通过断点指令设置。

当调试器中断时,系统或调试器会将被调试程序的状态保存到一个数据结构中——通常称为执行上下文。调试器中断后,被调试程序处于静态,直到用户输入恢复执行命令。

踪迹是断点的派生物。基本思想是,当一个跟踪点被设置时,调试器将把它当作一个特殊的断点。当跟踪点被执行时,系统将向调试器报告断点事件。调试器收到它后,将检查内部维护的断点列表。当发现跟踪点当前正在发生时,就会执行这个跟踪点定义的行为,通常是打印提示信息和变量值,然后直接恢复被调试程序的执行。

因为调试器在执行跟踪操作后立即恢复被调试程序的执行,所以调试器不会感觉到被调试程序中断了调试器,尽管它实际上发生了。

条件断点以类似的方式工作。当用户设置条件断点时,调试器实际上插入了一个无条件断点。在断点命中并且调试器接收到调试事件后,它将检查此断点的附加条件。如果满足条件,中断给用户,让用户开始交互调试;如果没有,则立即恢复被调试程序的执行。

单步执行

单步调试是最早的调试方法之一。简单来说就是让应用按照一定的步骤单元一步步执行。

根据每次要执行的步骤单位,可以分为以下几类。

(1)一次执行一条汇编指令,在汇编语言层次上称为单步跟踪。一般实现方法是设置CPU的单步执行标志。以IA32 CPU为例,在CPU标志寄存器中设置陷阱标志(TF)位,可以使CPU在每次执行一条指令时产生一个调试异常(INT 1),并中断给调试器。

(2)每次执行一条源代码(比汇编语言更高级的编程语言,如C/C++)的语句,也叫源代码级单步跟踪。高级语言的单步执行一般是通过汇编级的多个单步执行来实现的。

调试器每收到一个调试事件,就会判断程序指针(IP)是否还属于当前的高级语言语句。如果是,它将再次设置单步执行标志并立即恢复执行,并让CPU执行另一条汇编指令,直到程序指针指向的汇编指令已经属于其他语句。调试器通常通过符号文件中的源代码行信息来判断程序指针所属的源代码行。

(3)每次执行一个程序分支,也叫分支到分支单步跟踪。在IA32 CPU的DbgCtl MSR寄存器中设置BTF(分支陷阱标志)标志,然后设置TF标志,可以在CPU执行下一条分支指令时触发调试异常。WinDBG的tb命令用于执行到下一个分支。

(4)一次执行一个任务(线程),即当指定的任务被调度执行时,中断调试器。当IA32 CPU切换到新任务时,它将检查任务状态部分(TSS)的T标志。如果标志为1,将生成调试异常。然而,目前大多数调试器都没有提供相应的功能。

单步执行可以跟踪程序执行的每一步,观察代码的执行路线和数据的变化过程,是诊断软件动态特性的有效方法。然而,随着大型软件的发展,从头到尾跟踪和执行一个软件甚至一个模块一般已经不再可行。一般的做法是使用断点功能,先将程序中断到感兴趣的位置,再单步执行关键代码。我们将在第四章详细介绍CPU的单步调试。

1.3.3输出调试信息

打印输出调试信息是一种简单而“古老”的软件调试方法。其基本思想是在程序中编写一个输出调试信息的专用语句,将程序的运行位置、状态、变量值等信息以文本的形式输出到一些可观察的地方,如控制台、窗口、文件或调试器。

比如在Windows平台上,驱动程序可以使用DbgPrint/DbgPrintEx输出调试信息,应用程序可以调用OutputDebugString,控制台程序可以直接使用printf系列函数打印信息。在Linux平台上,驱动可以使用printk输出调试信息,应用可以使用printf系列函数。

上述方法的优点是简单方便,不依赖于调试器和复杂的工具,所以在很多场合仍然被广泛使用。

但是,这种简单的方法也有一些明显的缺点,比如需要给被调试的程序添加代码。如果被调试程序的某个位置没有打印的语句,就无法观察到那里的信息。如果要添加打印的语句,需要重新编译和更新程序。

另外,这种方法容易影响程序的执行效率。打印文本包含的信息有限,很容易泄露程序的技术细节。通常不能动态打开,信息没有结构化,很难分析整理。我们将在16.5.2节介绍使用这种方法时应该注意的一些细节。

1.3.4日志

类似于调试信息的输出,写日志是被调试程序的另一种自发辅助调试方式。基本思想是在编写程序时添加特定的代码,将程序的运行状态信息写入日志文件或数据库。

日志通常按照时间自动命名,每条记录也有详细的时间信息,适合长期保存和事后检查分析。所以很多需要长时间在后台运行的服务器程序都有日志机制。

Windows操作系统提供基本的日志记录、观察和管理(删除和备份)功能。Windows Vista引入了一个名为通用日志文件系统(CLFS)的新内核模块。SYS)进一步加强日志功能。Syslog是Linux系统中常见的日志记录工具。

事件跟踪

调试信息和日志都是以文本的形式输出和记录的,不适合处理巨大的数据和高速度的要求。事件跟踪机制就是为了满足这种需求而设计的。它采用结构化的二进制形式记录数据,然后在观测时根据格式文件将信息格式转换成文本形式。因此,它适用于监控频繁和复杂的软件进程,如监控文件访问和网络通信。

ETW(Event Trace for Windows)是Windows操作系统内置的一种事件追踪机制,Windows内核本身和Windows下的很多软件工具(如Bootvis、TCP/IP View)都使用这种机制。我们将在第16章详细介绍事件跟踪机制及其应用。

堆栈回溯

目前主流的CPU架构都是用栈来进行函数调用,函数的返回地址都记录在栈上。因此,通过递归查找放在堆栈上的函数的返回地址,可以回溯到当前线程的函数调用序列,这就是堆栈回溯的基本原理。栈回溯产生的函数调用信息称为调用栈。

堆栈回溯是记录和探索程序执行轨迹的一种极好的方法。利用这种方法,可以快速知道一个程序的运行轨迹,看它从哪里来,到哪里去。

因为从堆栈中得到的只是函数返回地址(数值),而不是函数名,所以为了便于理解,可以使用调试符号文件将返回地址翻译成函数名。大多数编译器支持在编译时生成调试符号。Microsoft的调试符号服务器包含系统文件的多个Windows版本的调试符号。我们将在本书的后续章节中深入讨论符号调试。

大多数调试器都提供了栈回溯的功能,比如WinDBG的K命令和GDB的bt命令,用来观察栈回溯信息。一些非调试器工具也可以记录和显示堆栈回溯信息。

意义

从软件工程的角度来看,软件调试是软件工程的重要组成部分,它出现在软件工程的各个阶段。从最初的可行性分析和原型验证到开发测试阶段,再到发布后的维护和支持,都需要软件调试技术。

在几乎所有的软件项目中,定位和修复bug都是一个重要的问题。离发布越近,这个问题越重要!许多软件项目由于无法在原定期限内修复bug而延期。

为了解决这个问题,整个软件团队要重视软件调试,重视软件调试风险的评估和预测,预留时间。

1.4.1调试和编码的关系

调试和编码是软件开发中两个不同但密切相关的过程。在软件的开发阶段,对于一个模块(一段代码),它的编写者通常也是它的调试器。或者说,程序员负责调试自己写的代码。这有两个非常大的优势。

(1)调试过程可以让程序员了解程序的实际执行过程,检查执行效果是否与自己的设计预期一致。如果不一致,很可能说明代码有问题,需要注意。

(2)调试过程可以让程序员更好地认识到提高代码可调试性和代码质量的重要性,进而使他们有意识地改进编码方法,合理地添加可以用来支持调试的代码。

编码和调试是程序员日常工作中最重要的两项任务。这两项任务相辅相成。编写高质量的可调试代码,可以明显提高调试效率,节省调试时间。此外,调试可以让程序员真切感受到程序的实际执行过程,反思编码和设计中存在的问题,加深对软件和系统的理解,提高对代码的感知和控制。

软件发布后,有些调试任务是由技术支持人员完成的,但当他们将错误定位到某个模块而无法解决时,有时不得不去找它的原设计者。

很多有经验的程序员都是把调试放在第一位的,他们会用各种调试方法去观察、跟踪、了解代码的执行过程。通过调试,他们可以发现编码和设计中的问题,并在将它们发布给测试人员之前纠正它们。

结果就是人们认为他们的编码水平很高,没有或者很少bug,在团队中口碑非常好。对于测试人员发现的问题,他们似乎也是千里眼。看了测试人员的描述,他们一般都能很快意识到问题所在,因为他们已经通过调试把代码的静态和动态特性放在脑子里了,并且了然于胸。

但是有些程序员很少跟踪调试自己写的代码,不知道代码什么时候执行了多少次。对于测试人员报告的很多问题,他们往往很困惑,无所适从。

毫无疑问,忽视调试对于提高程序员的编程水平和综合能力是非常不利的。因此,《通过思考进行调试》一书的作者罗伯特·查尔斯·梅茨格(Robert Charles Metzger)说:“今天的软件有这么多缺陷,原因是多方面的,其中之一就是很多程序员不擅长调试,有些程序员对待软件调试就像对待个人所得税申报一样消极。”

1.4.2调试和测试的关系

简单来说,测试的目的是在不知道问题存在的情况下发现问题,而调试是在问题已经存在的情况下定位问题的根源。从因果关系的角度来看,测试是为了发现软件“表面”的不当行为和属性,而调试是寻找这个表面之下的内因。所以两者有明显的区别,虽然有些人经常混淆。

符合测试调试的目的,即软件的及时交付。为了实现这个共同的目标,测试人员和调试器应该相互尊重,密切合作。例如,测试人员应该尽可能准确和详细地描述缺陷,陈述错误的症状、实际和预期的结果、发现问题的硬件和软件环境、重现问题的方法以及需要注意的细节。

测试人员应该在软件中加入错误检查和调试的手段,以便更快地定位问题。

软件的调试版本应该包含更多的错误检查链接,这样更容易检测出错误。因此,除了测试软件的发布版本之外,测试调试版本是提高测试效率和加快整个项目进度的有效措施。

1.4.3调试与逆向工程的关系

典型的软件开发过程是设计、编码然后编译成可执行文件(目标程序)的过程。所以所谓逆向工程,就是根据可执行文件逆向推导编码方式和设计方法的过程。

调试器是逆向工程中的主要工具。符号、跟踪执行、变量监控和观察、断点等软件调试技术在逆向工程中经常用到。

反向工程的合法性取决于许多因素,如软件的许可协议、所在国的法律以及反向工程的目的。它的细节超出了本书的范围。​

调试是什么意思(产品调试是什么意思)插图

软件调试(第二版)第1卷:硬件基础

作者:张

这本书堪称软件调试的“百科全书”。围绕软件调试的三条主线,即“生态”系统、异常和调试器,介绍了软件调试的相关原理和机制,论述了可调试性的内涵和意义,以及实现软件调试的原则和方法,总结了软件调试的方法和技巧。

第1卷侧重于硬件技术。全书分为4章,共16章。第一篇“绪论”(第1章)介绍了软件调试的概念、基本过程、分类和简史,并对本书后面将要详细介绍的主要调试技术进行了总结。第二章“CPU及其调试工具”(第2 ~ 7章),以基于Intel和ARM架构的CPU为例,系统阐述CPU的调试支持。第三章“GPU及其调试设施”(第8 ~ 14章),深入探讨了Nvidia、AMD、Intel、ARM、Imagination的GPU。第四章“调试”(第15 ~ 16章),介绍了提高软件调试的意义、基本原则、实例和注意的问题,并讨论了如何在软件开发实践中实现调试。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。

作者:美站资讯,如若转载,请注明出处:https://www.meizw.com/n/190881.html

发表回复

登录后才能评论