草庐IT

应用程序调试原理浅析

温少雄 2023-03-28 原文

一、Bug和Debug

说起“Debug”,就不得不提及“Bug”这个程序猿和游戏玩家耳熟能详的词,它由美国格蕾丝·赫柏博士第一次提出,当时运行研究数据的Harvard Mark II计算机突然不能正常工作,经赫柏和团队的反复排查,发现是一只飞蛾飞入了电脑的内部继电器中造成短路而引起的故障。修复故障后,赫柏在日记中诙谐地记录下了这件事(图1), “Bug”一词(原意为“虫子”)也逐渐被广泛用于形容计算机程序中隐藏的错误,同时,受到从电脑中驱除飞蛾虫子的启发,计算机术语“Debug”(调试排错)开始使用。

图1

Debug调试覆盖了整个计算机领域,包括不限于数字电路、模拟仿真、嵌入式软硬件以及应用软件,是技术研发人员必须熟练掌握的重要技能,对于产品研发过程的代码纠错和产品质量把控有重要影响,本文主要探讨分析主流硬件平台和操作系统的软件程序Debug原理。

二、调试原理-断点

对于如C、C++等编译运行的可执行程序,其Debug断点调试需要硬件和操作系统的支持,主要依赖以下两点:

(1) 硬件平台和操作系统提供设置断点的方法。

(2) 断点触发系统中断通知到调试器的功能。

对于第一点断点的实现,从计算机体系角度看分为软件断点和硬件断点。软件断点是指向指定的代码位置插入专用的断点指令实现(插桩)。而硬件断点则是通过直接利用CPU核心的调试寄存器实现,此场景主要针对不允许写入操作的ROM只读内存和软件断点无法处理的情况,如中断向量表被破坏等。

图2

不同的硬件架构对应断点实现指令也不相同,如果我们的硬件处理器基于X86系列,其软件断点工作原理是调试器将代码对应位置的原指令的首个字节保存起来,然后写入一条INT3指令(图2)。因为INT3指令的二进制码为11001100b(0xCC),仅有一个字节,所以设置和取消断点时也只需要保存和恢复一个字节。当CPU执行到INT3指令时,将会触操作系统软中断并停止运行当前进程,转而执行内核定义好的中断处理函数。X86的硬件断点使用DR0-DR7调试地址寄存器,但是由于存储断点地址的寄存器数量有限(DR0-DR3),只能设置4个断点。基于ARM系列的断点实现与X86平台类似, 软件断点的工作原理是用HLT或BRK指令的操作码进行指令替换,硬件断点使用内置在core中的比较器,并在执行到达指定地址时停止执行并触发相应中断,和X86一样,由于只提供有限数量的硬件断点单元也存在断点设置数量限制。

对于第二点操作系统的中断通知,以X86平台为例,Windows平台由操作系统软中断触发的对应函数为KiTrap03(),Linux平台则是do_int3()函数,这些函数均为操作系统内核预先定义好的中断处理例程。KiTrap03()会将断点异常通过调试子系统以调试事件的形式分发给用户模式的调试器,并等待调试器的回复,只有调试器确认该异常为“自己”设置的断点后,才会允许挂起被调试进程进行交互性调试。do_int3()例程则是向被调试进程发送一个SIGTRAP信号,当进程接收到SIGTRAP信号后,当前进程让出CPU暂停运行。

三、调试原理-进程交互模型

调试器和被调试进程的如果都位于同一台物理机,即为跨进程调试,反之为远程调试,远程调试是在跨进程调试的基础上增加了一层网络协议交互。由于Windows和Linux的进程描述模型存在一定差异,我们分别介绍这两种平台的调试器进程交互原理。

3.1 Windows

WIN32内核提供了一组系统Api用于支持调试器与被调试进程交互,这里挑几个重要函数进行介绍。

图3

基于WIN32的调试器交互就是通过上述所示的调试函数和一系列调试事件[1]相结合实现。调试器启动后首先通过CreateProcess函数创建待调试进程,或者通过调用DebugActiveProcess函数捆绑到正在运行的进程,在一系列准备操作后就会进入调试循环阶段,调试器会阻塞调用WaitForDebugEvent函数来等待调试事件通知,当有诸如异常事件或dll文件装卸载事件通知到来时,此函数立即返回,返回的事件信息被封装在DEBUG_EVENT结构中,这个结构包含事件的类型、相关进程描述信息和文件句柄等。此时调试器就进入了命令交互阶段,调试器将在自定义的事件处理函数ProcessEvent匹配事件并执行对应事件的回调代码,如果是断点触发这类型操作,被调试目标进程的所有线程都会被操作系统挂起,此时调试器可以调用相关函数如GetThreadContext来获取指定线程的上下文信息。调试器和目标进程地调试信息交互基于Windows进程间同步机制,相关信息可参阅微软相关开发文档[2]


图4

3.2 Linux

相比Windows,Linux作为开源系统可以透过源码更深入地窥探调试器原理,这里以GDB调试为例。

当我们从shell终端对某个已编译C程序文件进行GDB命令调试时,系统首先会创建GDB进程(调试器进程),该进程会fork出一个子进程(调试目标进程),子进程初始化后首先调用关键系统函数ptrace(PTRACE_TRACEME…),使自身进入被追踪模式;同时调用execv函数执行待调试的C程序文件,此时会暂停当前进程的运行,并且发送一个SIGCHLD信号给父进程,父进程接收到SIGCHLD信号后就可以对被调试的进程进行调试。GDB也支持对已存在的进程进行调试,此时将由GDB进程调用ptrace(PTRACE_ATTACH, pid, ...)对被调试进程进入被追踪模式。

图5

ptrace系统函数[3]是GDB交互调试的核心依赖函数,该函数的第一个参数request确定要执行的操作模式,这些操作模式定义了调试器控制读写被调试进程的行为,具体支持的操作模式如下:

图6

借助ptrace函数的强大功能,GDB调试器进程可以对调试目标进程的指令空间、数据空间、堆栈和寄存器的值进行读写,如堆栈打印、变量展示修改等。GDB同时会截获内核通知到被调试进程的几乎所有信号,通过对这些信号的拦截和判定,调试器进程就可以对程序进行断点匹配和单步调试等操作[4]

4、调试器的未来发展

Windows平台的Windbg、Linux的GDB调试器都是功能全面、具有复杂逻辑实现的软件工具,这些debugger调试器因为根植于不同硬件平台和操作系统,存在着底层功能实现和交互模型的显著差异,很明显不适合跨平台发展,而随着Java、Js、python等解释型语言的兴起和云平台的发展,虚拟机调试体系(JDPA、v8 debug protocol)被提出和广泛应用,这种百花齐放的局面让IDE厂家面临着一个非常棘手的问题——调试器交互规范不统一带来的巨大开发难度,微软针对此问题率先提出了DAP(Debug Adapter Protocol)协议,让各厂家IDE(主要是还是服务自家的VsCode)通过相同的协议基于适配器模式与不同语言的debugger通信,力图屏蔽软硬件底层的差异性,降低IDE调试器的开发难度。DAP协议凭借着专业性和普适性得到了业界的一定认可,不过Eclipse和IDEA等JAVA编辑器仍然是直接适配JDPA调试体系的,毕竟软件行业统一规范的背后仍然是各家科技公司行业话语权的争夺。

有关应用程序调试原理浅析的更多相关文章

  1. ruby - 在 Ruby 程序执行时阻止 Windows 7 PC 进入休眠状态 - 2

    我需要在客户计算机上运行Ruby应用程序。通常需要几天才能完成(复制大备份文件)。问题是如果启用sleep,它会中断应用程序。否则,计算机将持续运行数周,直到我下次访问为止。有什么方法可以防止执行期间休眠并让Windows在执行后休眠吗?欢迎任何疯狂的想法;-) 最佳答案 Here建议使用SetThreadExecutionStateWinAPI函数,使应用程序能够通知系统它正在使用中,从而防止系统在应用程序运行时进入休眠状态或关闭显示。像这样的东西:require'Win32API'ES_AWAYMODE_REQUIRED=0x0

  2. ruby - 将差异补丁应用于字符串/文件 - 2

    对于具有离线功能的智能手机应用程序,我正在为Xml文件创建单向文本同步。我希望我的服务器将增量/差异(例如GNU差异补丁)发送到目标设备。这是计划:Time=0Server:hasversion_1ofXmlfile(~800kiB)Client:hasversion_1ofXmlfile(~800kiB)Time=1Server:hasversion_1andversion_2ofXmlfile(each~800kiB)computesdeltaoftheseversions(=patch)(~10kiB)sendspatchtoClient(~10kiBtransferred)Cl

  3. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  4. ruby - 在 Ruby 中编写命令行实用程序 - 2

    我想用ruby​​编写一个小的命令行实用程序并将其作为gem分发。我知道安装后,Guard、Sass和Thor等某些gem可以从命令行自行运行。为了让gem像二进制文件一样可用,我需要在我的gemspec中指定什么。 最佳答案 Gem::Specification.newdo|s|...s.executable='name_of_executable'...endhttp://docs.rubygems.org/read/chapter/20 关于ruby-在Ruby中编写命令行实用程序

  5. ruby-on-rails - Rails 应用程序之间的通信 - 2

    我构建了两个需要相互通信和发送文件的Rails应用程序。例如,一个Rails应用程序会发送请求以查看其他应用程序数据库中的表。然后另一个应用程序将呈现该表的json并将其发回。我还希望一个应用程序将存储在其公共(public)目录中的文本文件发送到另一个应用程序的公共(public)目录。我从来没有做过这样的事情,所以我什至不知道从哪里开始。任何帮助,将不胜感激。谢谢! 最佳答案 无论Rails是什么,几乎所有Web应用程序都有您的要求,大多数现代Web应用程序都需要相互通信。但是有一个小小的理解需要你坚持下去,网站不应直接访问彼此

  6. ruby - 无法运行 Rails 2.x 应用程序 - 2

    我尝试运行2.x应用程序。我使用rvm并为此应用程序设置其他版本的ruby​​:$rvmuseree-1.8.7-head我尝试运行服务器,然后出现很多错误:$script/serverNOTE:Gem.source_indexisdeprecated,useSpecification.Itwillberemovedonorafter2011-11-01.Gem.source_indexcalledfrom/Users/serg/rails_projects_terminal/work_proj/spohelp/config/../vendor/rails/railties/lib/r

  7. ruby-on-rails - Rails 应用程序中的 Rails : How are you using application_controller. rb 是新手吗? - 2

    刚入门rails,开始慢慢理解。有人可以解释或给我一些关于在application_controller中编码的好处或时间和原因的想法吗?有哪些用例。您如何为Rails应用程序使用应用程序Controller?我不想在那里放太多代码,因为据我了解,每个请求都会调用此Controller。这是真的? 最佳答案 ApplicationController实际上是您应用程序中的每个其他Controller都将从中继承的类(尽管这不是强制性的)。我同意不要用太多代码弄乱它并保持干净整洁的态度,尽管在某些情况下ApplicationContr

  8. ruby-on-rails - 如何在我的 Rails 应用程序 View 中打印 ruby​​ 变量的内容? - 2

    我是一个Rails初学者,但我想从我的RailsView(html.haml文件)中查看Ruby变量的内容。我试图在ruby​​中打印出变量(认为它会在终端中出现),但没有得到任何结果。有什么建议吗?我知道Rails调试器,但更喜欢使用inspect来打印我的变量。 最佳答案 您可以在View中使用puts方法将信息输出到服务器控制台。您应该能够在View中的任何位置使用Haml执行以下操作:-puts@my_variable.inspect 关于ruby-on-rails-如何在我的R

  9. ruby-on-rails - 无法让 rspec、spork 和调试器正常运行 - 2

    GivenIamadumbprogrammerandIamusingrspecandIamusingsporkandIwanttodebug...mmm...let'ssaaay,aspecforPhone.那么,我应该把“require'ruby-debug'”行放在哪里,以便在phone_spec.rb的特定点停止处理?(我所要求的只是一个大而粗的箭头,即使是一个有挑战性的程序员也能看到:-3)我已经尝试了很多位置,除非我没有正确测试它们,否则会发生一些奇怪的事情:在spec_helper.rb中的以下位置:require'rubygems'require'spork'

  10. ruby - JetBrains RubyMine 3.2.4 调试器不工作 - 2

    使用Ruby1.9.2运行IDE提示说需要gemruby​​-debug-base19x并提供安装它。但是,在尝试安装它时会显示消息Failedtoinstallgems.Followinggemswerenotinstalled:C:/ProgramFiles(x86)/JetBrains/RubyMine3.2.4/rb/gems/ruby-debug-base19x-0.11.30.pre2.gem:Errorinstallingruby-debug-base19x-0.11.30.pre2.gem:The'linecache19'nativegemrequiresinstall

随机推荐