Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >写给小白:浏览器断点调试是怎么实现的?

写给小白:浏览器断点调试是怎么实现的?

作者头像
公众号@魔术师卡颂
发布于 2021-05-27 10:12:11
发布于 2021-05-27 10:12:11
1.6K00
代码可运行
举报
文章被收录于专栏:魔术师卡颂魔术师卡颂
运行总次数:0
代码可运行

点击上方“魔术师卡颂”,选择“设为星标”

专注React,学不会你打我!

代码写完会运行一下看下效果,开发的时候我们更多都是通过 dubugger 来单步或断点运行。我们整天在用 debugger,可是你有想过它的实现原理么。

本文会解答以下问题:

  • 代码运行的底层原理是什么
  • 为什么需要 debugger
  • debugger 实现原理是什么
  • 如何实现 debugger 客户端

代码运行的原理是什么

代码的运行方式可以分为直接执行和解释执行两类。

不知道平时你有没有注意,可执行文件直接 ./xxx 就可以执行,而执行 js 文件需要 node ./xxx,执行 python 文件需要 python ./xxx,这就是编译执行(直接执行)和解释执行的区别。

直接执行

cpu 提供了一套指令集,基于这套指令集就可以控制整个计算机的运转,机器语言的代码就是由这些指令和对应的操作数构成的,这些机器码可以直接跑在计算机上,也就是可直接执行。由它们构成的文件叫做可执行文件。

不同操作系统可执行文件的格式不同,在 windows 上是 pe(Portable Executable) 格式,在 linux、unix 系统上是 elf(Executable Linkable Format) 格式,在 mac 上是 mash-o 格式。它们规定了不同的内容(.text 是代码、.data .bass 等是数据)放在文件中的什么位置。但其中真正可执行的部分还是由 cpu 提供的机器指令构成的。

编译型语言会经过编译、汇编、链接的阶段,编译是把源代码转成汇编语言构成的中间代码,汇编是把中间代码变成目标代码,链接会把目标代码组合成可执行文件。这个可执行文件是可以在操作系统上直接执行的。就因为它是由 cpu 的机器指令构成的,可以直接控制 cpu。所以可以直接 ./xxx 就可以执行。

解释执行

编译型语言都是生成可执行文件直接在操作系统上来执行的,不需要安装解释器,而 js、python 等解释型语言的代码需要用解释器来跑。

为什么有了解释器就不需要生成机器码了,cpu 仍然不认识这些代码啊?

那是因为解释器是需要编译成机器码的,cpu 知道怎么执行解释器,而解释器知道怎么执行更上层的脚本代码,就这样,由机器码解释执行解释器,再由解释器解释执行上层代码,这就是脚本语言的原理。 包括 js、python 等都是这样。

但是解释器毕竟多了一层,所以有的时候会把它编译成机器码来直接执行,这就是 JIT 编译器。比如 js 引擎一般就是由 parser、解释器、JIT 编译器、GC 构成,大部分代码是由解释器解释执行的,而热点代码会经过 JIT 编译器编译成由机器码,直接在操作系统上执行以提高性能。

编译成机器码直接执行,或者是从源码解释执行,代码就这两种执行方式。两者各有各的好处,编译型速度快,解释型跨平台。这就是代码运行的原理。

王垠说过,计算机的本质就是解释器。就是说 cpu 用电路解释机器码,解释器用机器码解释更上层的脚本代码,所以计算机的本质是解释器。

为什么需要 debugger

我们知道,图灵完备的语言可以解释任何可计算问题,所以不管是编译型还是解释型都能够描述所有可计算的业务逻辑。

我们利用不同的语言描述业务逻辑,然后运行它看效果,当代码的逻辑比较复杂的时候,难免会出错,我们希望能够一步步运行或是运行到某个点停下来,然后看一下当时的环境中的变量,执行某个脚本。完成这个功能的就是 debugger。

也许还有很多初级程序员只会用 console.log 打日志,但是日志不能完全展现当时的环境,最好的方式还是 debugger。

狼叔说过,是否会用 debugger 是 nodejs 水平的一个明显的区分

debugger 的原理

我们知道了 debugger 是调试程序必不可少的,那么它是怎么实现的呢?

可执行文件的 debugger

其实 cpu、操作系统在设计的时候就支持了 debugger 的能力(可见 debugger 的重要性),cpu 里面有 4 个寄存器可以做硬中断,操作系统提供了系统调用来做软中断。这是编译型语言的 debugger 实现的基础。

中断

cpu 只会不断的执行下一条指令,但程序运行过程中难免要处理一些外部的消息,比如 io、网络、异常等等,所以设计了中断的机制,cpu 每执行完一条指令,就会去看下中断标记,是否需要中断了。就像 event loop 每次 loop 完都要检查下是否需要渲染一样。

INT 指令

cpu 支持 INT 指令来触发中断,中断有编号,不同的编号有不同的处理程序,记录编号和中断处理程序的表叫做中断向量表。其中 INT 3 (3 号中断)可以触发 debugger,这是一种约定。

那么可执行文件是怎么利用这个 3 号中断来 debugger 的呢?其实就是运行时替换执行的内容,debugger 程序会在需要设置断点的位置把指令内容换成 INT 3,也就是 0xCC,这就断住了。就可以获取这时候的环境数据来做调试。

通过机器码替换成 0xcc (INT 3)是把程序断住了,可是怎么恢复执行呢?其实也比较简单,把当时替换的机器码记录下来,需要释放断点的时候再换回去就行了。

这就是可执行文件的 debugger 的原理了,最终还是靠 cpu 支持的中断机制来实现的。

中断寄存器

上面说的 debugger 实现方式是修改内存中的机器码的方式,但有的时候修改不了代码,比如 ROM,这种情况就要通过 cpu 提供的 4 个中断寄存器(DR0 - DR3)来做了。这种叫做硬中断。

总之,INT 3 的软中断,还有中断寄存器的硬中断,是可执行文件实现 debugger 的两种方式。

解释型语言的 debugger

编译型语言因为直接在操作系统之上执行,所以要利用 cpu 和操作系统的中断机制和系统调用来实现 debugger。但是解释型语言是自己实现代码的解释执行的,所以不需要那一套,但是实现思路还是一样的,就是插入一段代码来断住,支持环境数据的查看和代码的执行,当释放断点的时候就继续往下执行。

比如 javascript 中支持 debugger 语句,当解释器执行到这一条语句的时候就会断住。

解释型语言的 debugger 相对简单一些,不需要了解 cpu 的 INT 3 中断。

debugger 客户端

上面我们了解了直接执行和解释执行的代码的 debugger 分别是怎么实现的。我们知道了代码是怎么断住的,那么断住之后呢?怎么把环境数据暴露出去,怎么执行外部代码?

这就需要 debugger 客户端了。

比如 v8 引擎会把设置断点、获取环境信息、执行脚本的能力通过 socket 暴露出去,socket 传递的信息格式就是 v8 debug protocol 。

比如:

设置断点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "seq":117,
    "type":"request",
    "command":"setbreakpoint",
    "arguments":{
        "type":"function",
        "target":"f"
    }

去掉断点:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "seq":117,
    "type":"request",
    "command":"clearbreakpoint",
    "arguments": {
        "type":"function",
        "breakpoint":1
     }
}

继续:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "seq":117,
    "type":"request",
    "command":"continue"
}

执行代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
{
    "seq":117,
    "type":"request",
    "command":"evaluate",
    "arguments":{
        "expression":"1+2"
    }
}

感兴趣的同学可以去 v8 debug protocol 的文档中去查看全部的协议。

基于这些协议就可以控制 v8 的 debugger 了,所有的能够实现 debugger 的都是对接了这个协议,比如 chrome devtools、vscode debugger 还有其他各种 ide 的 debugger。

nodejs 代码的调试

nodejs 可以通过添加 --inspect 的 option 来做调试(也可以是 --inspect-brk,这个会在首行就断住)。

它会起一个 debugger 的 websocket 服务端,我们可以用 vscode 来调试 nodejs 代码,也可以用 chrome devtools 来调试(见 nodejs debugger 文档)。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
➜ node --inspect test.js
Debugger listening on ws://127.0.0.1:9229/db309268-623a-4abe-b19a-c4407ed8998d
For help see https://nodejs.org/en/docs/inspector

原理就是实现了 v8 debug protocol。

我们如果自己做调试工具、做 ide,那就要对接这个协议。

debugger adaptor protocol

上面介绍的 v8 debug protocol 可以实现 js 代码的调试,那么 python、c# 等肯定也有自己的调试协议,如果要实现 ide,都要对接一遍太过麻烦。所以后来出现了一个中间层协议,DAP(debugger adaptor protocol)。

debugger adaptor protocol, 顾名思义,就是适配的,一端适配各种 debugger 协议,一端提供给客户端统一的协议。这是适配器模式的一个很好的应用。

总结

本文我们学习了 debugger 的实现原理和暴露出的调试协议。

首先我们了解了代码两种运行方式:直接执行和解释执行,然后分析了下为什么需要 debugger。

之后探索了直接执行的代码通过 INT 3 的中断的方式来实现 debugger 和解释型语言自己实现的 debugger。

然后 debugger 的能力会通过 socket 暴露给客户端,提供调试协议,比如 v8 debug protocol,各种客户端包括 chrome devtools、ide 等都实现了这个协议。

但是每种语言都要实现一次的话太过麻烦,所以后来出现了一个适配层协议,屏蔽了不同协议的区别,提供统一的协议接口给客户端用。

希望这篇文章能够让你理解 debugger 的原理,如果要实现调试工具也知道怎么该怎么去对接协议。能够知道 chrome devtools、vscode 为啥都可以调试 nodejs 代码。


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 魔术师卡颂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
小白了解浏览器V8
计算机只能识别二进制的机器语言,高级语言要想识别需要先翻译成机器语言,这种翻译有两种方式:
一起重学前端
2024/09/26
1500
python运行机制,java 运行机制,throw new RuntimeException(Stub!) 是什么
不明白:解释型(虚拟机)语言(js,python),编译型语言(c++),混合型语言(java)
zhangjiqun
2024/12/16
570
python运行机制,java 运行机制,throw new RuntimeException(Stub!) 是什么
重学JS-1.3-知识点:V8引擎
V8是一个由Google开发的开源JavaScript引擎,用于Chrome、Node.js等环境中,作用是将JS代码编译为不同CPU(Intel, ARM以及MIPS等)对应的汇编代码。
luciozhang
2023/04/22
7240
重学JS-1.3-知识点:V8引擎
计算机语言&Python解释器
由于计算机内部只能接受二进制代码,因此,用二进制代码0和1描述的指令称为机器指令,全部机器指令的集合构成计算的机器语言 机器语言属于低级语言
py3study
2020/01/09
8650
计算机语言&Python解释器
计算机基础系列:源代码如何被计算机执行
现在各行各业的朋友都开始使用计算机解决自己的业务问题,网络上有大量的免费公开课,教我们处理数据并数学建模。Python等编程语言上手快,开源软件多,足以应付绝大多数的需求。在计算机软硬件体系中,上述工作都是在最顶层,用户执行程序需要依赖于计算机硬件和系统软件。聊天用的微信、娱乐玩的农药、上网打开的浏览器、还有我们自己写的程序…这些程序是如何从源代码,变成计算机芯片可以执行的程序呢?
PP鲁
2019/12/25
1.5K0
编译型语言和解释型语言,动态结构语言和静态结构语言
计算机不能直接的理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言的编写的程序。翻译的方式有两种,一个是编译,一个是解释。两种方式只是翻译的时间不同。 编译型语言:
zhangjiqun
2024/12/16
1230
编译型语言和解释型语言,动态结构语言和静态结构语言
LLVM(一)——编译流程
我们程序员编写的源代码是人类语言,我们可以很轻松得理解;但是对于计算机硬件(CPU)而言,这些源代码就好比是天书,它根本无法理解,更无法直接执行。计算机只能够识别某些特定的二进制指令,所以在程序真正运行之前,必须要把源代码转换成计算机可以识别的二进制指令。
拉维
2021/04/16
2.5K0
3分钟搞懂什么是编译执行和解释执行《轻松搞定大厂面试》
在讲"Java是解释执行还是编译执行?"前,先理解一下什么是解释执行,什么又是编译执行。
谙忆
2021/10/28
7K1
3分钟搞懂什么是编译执行和解释执行《轻松搞定大厂面试》
浅析V8引擎,让你更懂JavaScript!
导语 | 本文介绍了编译、解释、动静态语言等基本概念,以及V8引擎的基本流程。本文将对其进行详细阐述,希望为更多的开发者提供经验和帮助。 一、编译与解释 二进制指令就是机器码: 编译:将源代码一次性转换成目标代码的过程。执行编译过程的程序叫编译器(Compiler)。 解释:将源代码逐条转换成目标代码,同时逐条运行的过程。执行解释过程的程序叫解释器(Interpreter)。解释器一般来说就是vm,vm有两种,一种是基于堆栈,一种是基于寄存器。 编译过程大致包括词法分析、语法分析、语义分析、性能优化、生成
腾讯云开发者
2022/03/17
8430
计算机基础------计算机语言分类(脚本语言引发的思考)
开始只是对于脚本语言理解不到位,通过查阅感觉了解脚本语言只是冰山一角(可能对脚本语言的介绍会多一些),有必要对计算机语言的分类做进一步了解,做一下总结。 以下内容有多处参考。
鲲志说
2025/04/07
660
计算机基础------计算机语言分类(脚本语言引发的思考)
Python第一节
什么是编程?       个人理解编程的意思就是:编程就是使用一种程序设计语言编写程序代码,让计算机解决某个问题的过程。 编程语言的种类 1、机器语言:机器语言是一种指令集的体系。这种指令集,称机器码(machine code),是电脑的CPU可直接解读的数据 2、汇编语言:汇编语言是一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。 3、高级语言:高级语言相对于机器语言(machine language,是一种指令集的体系。这种指令集,称机器码(machine code),是电脑的CPU可直接解读的数据)而言。
py3study
2020/01/15
4080
【JavaScript】JavaScript 简介 ④ ( 解释型语言 和 编译型语言 | 计算机程序本质 | 编译器 和 解释器 )
计算机 的 程序 是在 CPU 上执行的 , CPU 上执行的只有匹配该 CPU 的机器码指令 , 不同类型的 CPU 执行的 机器码指令 格式不同 , X86 和 ARM 执行的 机器码 指令格式是不同的 ;
韩曙亮
2024/03/18
1600
【JavaScript】JavaScript 简介 ④ ( 解释型语言 和 编译型语言 | 计算机程序本质 | 编译器 和 解释器 )
iOS编译原理
LLVM的编译过程相当复杂,iOS代码运行需要经过:预处理、编译、汇编、链接四个关键阶段,具体的流程如下图:
梧雨北辰
2021/11/24
1.7K0
iOS编译原理
编译型语言 VS 解释型语言
在详述前,先记录一下自己的经历:之前写过一段时间Java代码,现在主要写前端,在这个过程中有一个场景让我印象深刻,但原理还有待深究: 之前写Java代码的时候记得修改一些内容的时候必须要重启Tomcat服务器,才能看到修改代码过后的运行结果,但是有些又不用重启。而在写前端语言的时候,抛去缓存,代码修改后可以马上运行显示,当时根本就没去想这个问题,我能简单的想起编译型语言与解释型语言的区别也就是这个了。
六个周
2022/10/28
1.4K0
计算机语言的分类
 到目前为止,我自己学过或者说碰过的语言有 C、Java、JavaScript、Python、Go。最近在学的是 Go,看到 Go meta描述是:静态、编译型。但是突然发现自己对于编译型的理解就是:该种语言若要执行,则需要从源码转换为二进制,而语言的静态和动态却摸不着头脑。看来自己基础不牢,需要总结了。
Fisherman渔夫
2020/02/18
7850
阶段三:V8工作原理
12 | 栈空间和堆空间:数据是如何存储的? 这节讲解的是JavaScript的内存机制。 首先,我们知道JavaScript是弱类型动态语言。 接着,JavaScript的数据类型一共是八种:Boolean| String | Number | Undefined | Null | BigInt | Symbol | Object 前七种为基本数据类型,他们存在栈中,后一种为引用数据类型,它存在堆中。 13 | 垃圾回收:垃圾数据是如何自动回收的? 不同语言的垃圾回收策略 通常情况下,垃圾
六个周
2022/10/28
4980
Java到底是编译还是解释型语言?编译和解释型语言有什么区别?
7.java语言执行过程与方式: 编译型语言: 是指使用专门的编译器、针对特定平台(操作系统)将某种高级语言源程序一次性“翻译”成可被该平台硬件运行的机器码(包括指令和数据),并包装成该平台的操作系统所能识别和运行的格式。这一过程称为“编译”。最后生成的程序(可执行文件)可以脱离开发环境在特定平台上独立执行。比如c,优点快,缺点,移植性差。 解释型语言: 是指使用专门的解释器将某种高级语言源程序逐条解释成特定平台的机器码指令并立即执行,解释一句执行一句,这类似于会场中的“同声翻译”,而不进行整体性的编译和链接处理。解释型语言相当于把编译型语言相对独立的编译和执行过程混合到一起,而且每一次执行时都要重复进行“编译”,因而执行的效率较低。且不能脱离解释器独自执行。比如javascript,优点:移植性强。缺点:慢。
马克java社区
2021/02/09
6030
Java到底是编译还是解释型语言?编译和解释型语言有什么区别?
程序的基本概念
程序的基本概念 1.1. 程序和编程语言 程序(Program)告诉计算机应如何完成一个计算任务,这里的计算可以是数学运算,比如解方程,也可以是符号运算,比如查找和替换文档中的某个单词。从根本上说,计算机是由数字电路组成的运算机器,只能对数字做运算,程序之所以能做符号运算,是因为符号在计算机内部也是用数字表示的。此外,程序还可以处理声音和图像,声音和图像在计算机内部必然也是用数字表示的,这些数字经过专门的硬件设备转换成人可以听到、看到的声音和图像。 程序由一系列基本操作组成,基本操作有以下几类: 输入(Input) 从键盘、文件或者其他设备获取数据。
一个会写诗的程序员
2018/08/17
1.2K0
编译型语言、解释型语言、静态类型语言、动态类型语言、强类型语言、弱类型语言概念与区别
编译型语言和解释型语言 1、编译型语言 需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言。一般需经过编译(compile)、链接(linker)这两个步骤。编译是把源代码编译成机器码,链接是把各个模块的机器码和依赖库串连起来生成可执行文件。 优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高。可以脱离语言环境独立运行。 缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的
Albert陈凯
2018/04/04
3.3K0
Java Review (一、Java开发环境)
有些程序编译结束后,还可能需要对其他编译好的目标代码进行链接,即组装两个以上的目标代码 模块生成最终的可执行性程序,通过这种方式实现低层次的代码复用。 因为编译型语言是一次性地编译成机器码,所以可以脱离开发环境独立运行,而且通常运行效率较 高;但因为编译型语言的程序被编译成特定平台上的机器码,因此编译生成的可执行性程序通常无法移植到其他平台上运行;如果需要移植,则必须将源代码复制到特定平台上,针对特定平台进行修改,至 少也需要采用特定平台上的编译器重新编译。 现有的C、C++、Objective-C、Pascal等高级语言都属于编译型语言。
三分恶
2020/07/16
8450
推荐阅读
相关推荐
小白了解浏览器V8
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验