偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

JavaScript Debugger 原理揭秘

開發(fā) 前端
代碼寫完會(huì)運(yùn)行一下看下效果,開發(fā)的時(shí)候我們更多都是通過 dubugger 來單步或斷點(diǎn)運(yùn)行。我們整天在用 debugger,可是你有想過它的實(shí)現(xiàn)原理么。

[[399391]]

本文轉(zhuǎn)載自微信公眾號(hào)「神光的編程秘籍」,作者神說要有光zxg。轉(zhuǎn)載本文請(qǐng)聯(lián)系神光的編程秘籍公眾號(hào)。

代碼寫完會(huì)運(yùn)行一下看下效果,開發(fā)的時(shí)候我們更多都是通過 dubugger 來單步或斷點(diǎn)運(yùn)行。我們整天在用 debugger,可是你有想過它的實(shí)現(xiàn)原理么。

本文會(huì)解答以下問題:

  • 代碼運(yùn)行的底層原理是什么
  • 為什么需要 debugger
  • debugger 實(shí)現(xiàn)原理是什么
  • 如何實(shí)現(xiàn) debugger 客戶端

代碼運(yùn)行的原理是什么

代碼的運(yùn)行方式可以分為直接執(zhí)行和解釋執(zhí)行兩類。

不知道平時(shí)你有沒有注意,可執(zhí)行文件直接 ./xxx 就可以執(zhí)行,而執(zhí)行 js 文件需要 node ./xxx,執(zhí)行 python 文件需要 python ./xxx,這就是編譯執(zhí)行(直接執(zhí)行)和解釋執(zhí)行的區(qū)別。

直接執(zhí)行

cpu 提供了一套指令集,基于這套指令集就可以控制整個(gè)計(jì)算機(jī)的運(yùn)轉(zhuǎn),機(jī)器語言的代碼就是由這些指令和對(duì)應(yīng)的操作數(shù)構(gòu)成的,這些機(jī)器碼可以直接跑在計(jì)算機(jī)上,也就是可直接執(zhí)行。由它們構(gòu)成的文件叫做可執(zhí)行文件。

不同操作系統(tǒng)可執(zhí)行文件的格式不同,在 windows 上是 pe(Portable Executable) 格式,在 linux、unix 系統(tǒng)上是 elf(Executable Linkable Format) 格式,在 mac 上是 mash-o 格式。它們規(guī)定了不同的內(nèi)容(.text 是代碼、.data .bass 等是數(shù)據(jù))放在文件中的什么位置。但其中真正可執(zhí)行的部分還是由 cpu 提供的機(jī)器指令構(gòu)成的。

編譯型語言會(huì)經(jīng)過編譯、匯編、鏈接的階段,編譯是把源代碼轉(zhuǎn)成匯編語言構(gòu)成的中間代碼,匯編是把中間代碼變成目標(biāo)代碼,鏈接會(huì)把目標(biāo)代碼組合成可執(zhí)行文件。這個(gè)可執(zhí)行文件是可以在操作系統(tǒng)上直接執(zhí)行的。就因?yàn)樗怯? cpu 的機(jī)器指令構(gòu)成的,可以直接控制 cpu。所以可以直接 ./xxx 就可以執(zhí)行。

解釋執(zhí)行

編譯型語言都是生成可執(zhí)行文件直接在操作系統(tǒng)上來執(zhí)行的,不需要安裝解釋器,而 js、python 等解釋型語言的代碼需要用解釋器來跑。

為什么有了解釋器就不需要生成機(jī)器碼了,cpu 仍然不認(rèn)識(shí)這些代碼啊?

那是因?yàn)榻忉屍魇切枰幾g成機(jī)器碼的,cpu 知道怎么執(zhí)行解釋器,而解釋器知道怎么執(zhí)行更上層的腳本代碼,就這樣,由機(jī)器碼解釋執(zhí)行解釋器,再由解釋器解釋執(zhí)行上層代碼,這就是腳本語言的原理。 包括 js、python 等都是這樣。

但是解釋器畢竟多了一層,所以有的時(shí)候會(huì)把它編譯成機(jī)器碼來直接執(zhí)行,這就是 JIT 編譯器。比如 js 引擎一般就是由 parser、解釋器、JIT 編譯器、GC 構(gòu)成,大部分代碼是由解釋器解釋執(zhí)行的,而熱點(diǎn)代碼會(huì)經(jīng)過 JIT 編譯器編譯成由機(jī)器碼,直接在操作系統(tǒng)上執(zhí)行以提高性能。

編譯成機(jī)器碼直接執(zhí)行,或者是從源碼解釋執(zhí)行,代碼就這兩種執(zhí)行方式。兩者各有各的好處,編譯型速度快,解釋型跨平臺(tái)。這就是代碼運(yùn)行的原理。

王垠說過,計(jì)算機(jī)的本質(zhì)就是解釋器。就是說 cpu 用電路解釋機(jī)器碼,解釋器用機(jī)器碼解釋更上層的腳本代碼,所以計(jì)算機(jī)的本質(zhì)是解釋器。

為什么需要 debugger

我們知道,圖靈完備的語言可以解釋任何可計(jì)算問題,所以不管是編譯型還是解釋型都能夠描述所有可計(jì)算的業(yè)務(wù)邏輯。

我們利用不同的語言描述業(yè)務(wù)邏輯,然后運(yùn)行它看效果,當(dāng)代碼的邏輯比較復(fù)雜的時(shí)候,難免會(huì)出錯(cuò),我們希望能夠一步步運(yùn)行或是運(yùn)行到某個(gè)點(diǎn)停下來,然后看一下當(dāng)時(shí)的環(huán)境中的變量,執(zhí)行某個(gè)腳本。完成這個(gè)功能的就是 debugger。

也許還有很多初級(jí)程序員只會(huì)用 console.log 打日志,但是日志不能完全展現(xiàn)當(dāng)時(shí)的環(huán)境,最好的方式還是 debugger。

狼叔說過,是否會(huì)用 debugger 是 nodejs 水平的一個(gè)明顯的區(qū)分。

debugger 的原理

我們知道了 debugger 是調(diào)試程序必不可少的,那么它是怎么實(shí)現(xiàn)的呢?

可執(zhí)行文件的 debugger

其實(shí) cpu、操作系統(tǒng)在設(shè)計(jì)的時(shí)候就支持了 debugger 的能力(可見 debugger 的重要性),cpu 里面有 4 個(gè)寄存器可以做硬中斷,操作系統(tǒng)提供了系統(tǒng)調(diào)用來做軟中斷。這是編譯型語言的 debugger 實(shí)現(xiàn)的基礎(chǔ)。

中斷

cpu 只會(huì)不斷的執(zhí)行下一條指令,但程序運(yùn)行過程中難免要處理一些外部的消息,比如 io、網(wǎng)絡(luò)、異常等等,所以設(shè)計(jì)了中斷的機(jī)制,cpu 每執(zhí)行完一條指令,就會(huì)去看下中斷標(biāo)記,是否需要中斷了。就像 event loop 每次 loop 完都要檢查下是否需要渲染一樣。

INT 指令

cpu 支持 INT 指令來觸發(fā)中斷,中斷有編號(hào),不同的編號(hào)有不同的處理程序,記錄編號(hào)和中斷處理程序的表叫做中斷向量表。其中 INT 3 (3 號(hào)中斷)可以觸發(fā) debugger,這是一種約定。

那么可執(zhí)行文件是怎么利用這個(gè) 3 號(hào)中斷來 debugger 的呢?其實(shí)就是運(yùn)行時(shí)替換執(zhí)行的內(nèi)容,debugger 程序會(huì)在需要設(shè)置斷點(diǎn)的位置把指令內(nèi)容換成 INT 3,也就是 0xCC,這就斷住了。就可以獲取這時(shí)候的環(huán)境數(shù)據(jù)來做調(diào)試。

通過機(jī)器碼替換成 0xcc (INT 3)是把程序斷住了,可是怎么恢復(fù)執(zhí)行呢?其實(shí)也比較簡單,把當(dāng)時(shí)替換的機(jī)器碼記錄下來,需要釋放斷點(diǎn)的時(shí)候再換回去就行了。

這就是可執(zhí)行文件的 debugger 的原理了,最終還是靠 cpu 支持的中斷機(jī)制來實(shí)現(xiàn)的。

中斷寄存器

上面說的 debugger 實(shí)現(xiàn)方式是修改內(nèi)存中的機(jī)器碼的方式,但有的時(shí)候修改不了代碼,比如 ROM,這種情況就要通過 cpu 提供的 4 個(gè)中斷寄存器(DR0 - DR3)來做了。這種叫做硬中斷。

總之,INT 3 的軟中斷,還有中斷寄存器的硬中斷,是可執(zhí)行文件實(shí)現(xiàn) debugger 的兩種方式。

解釋型語言的 debugger

編譯型語言因?yàn)橹苯釉诓僮飨到y(tǒng)之上執(zhí)行,所以要利用 cpu 和操作系統(tǒng)的中斷機(jī)制和系統(tǒng)調(diào)用來實(shí)現(xiàn) debugger。但是解釋型語言是自己實(shí)現(xiàn)代碼的解釋執(zhí)行的,所以不需要那一套,但是實(shí)現(xiàn)思路還是一樣的,就是插入一段代碼來斷住,支持環(huán)境數(shù)據(jù)的查看和代碼的執(zhí)行,當(dāng)釋放斷點(diǎn)的時(shí)候就繼續(xù)往下執(zhí)行。

比如 javascript 中支持 debugger 語句,當(dāng)解釋器執(zhí)行到這一條語句的時(shí)候就會(huì)斷住。

解釋型語言的 debugger 相對(duì)簡單一些,不需要了解 cpu 的 INT 3 中斷。

debugger 客戶端

上面我們了解了直接執(zhí)行和解釋執(zhí)行的代碼的 debugger 分別是怎么實(shí)現(xiàn)的。我們知道了代碼是怎么斷住的,那么斷住之后呢?怎么把環(huán)境數(shù)據(jù)暴露出去,怎么執(zhí)行外部代碼?

這就需要 debugger 客戶端了。

比如 v8 引擎會(huì)把設(shè)置斷點(diǎn)、獲取環(huán)境信息、執(zhí)行腳本的能力通過 socket 暴露出去,socket 傳遞的信息格式就是 v8 debug protocol 。

比如:

設(shè)置斷點(diǎn):

  1.     "seq":117, 
  2.     "type":"request"
  3.     "command":"setbreakpoint"
  4.     "arguments":{ 
  5.         "type":"function"
  6.         "target":"f" 
  7.     } 

去掉斷點(diǎn):

  1.     "seq":117, 
  2.     "type":"request"
  3.     "command":"clearbreakpoint"
  4.     "arguments": { 
  5.         "type":"function"
  6.         "breakpoint":1 
  7.      } 

繼續(xù):

  1.     "seq":117, 
  2.     "type":"request"
  3.     "command":"continue" 

執(zhí)行代碼:

  1.     "seq":117, 
  2.     "type":"request"
  3.     "command":"evaluate"
  4.     "arguments":{ 
  5.         "expression":"1+2" 
  6.     } 

感興趣的同學(xué)可以去 v8 debug protocol 的文檔中去查看全部的協(xié)議。

基于這些協(xié)議就可以控制 v8 的 debugger 了,所有的能夠?qū)崿F(xiàn) debugger 的都是對(duì)接了這個(gè)協(xié)議,比如 chrome devtools、vscode debugger 還有其他各種 ide 的 debugger。

nodejs 代碼的調(diào)試

nodejs 可以通過添加 --inspect 的 option 來做調(diào)試(也可以是 --inspect-brk,這個(gè)會(huì)在首行就斷住)。

它會(huì)起一個(gè) debugger 的 websocket 服務(wù)端,我們可以用 vscode 來調(diào)試 nodejs 代碼,也可以用 chrome devtools 來調(diào)試(見 nodejs debugger 文檔)。

  1. ➜ node --inspect test.js 
  2. Debugger listening on ws://127.0.0.1:9229/db309268-623a-4abe-b19a-c4407ed8998d 
  3. For help see https://nodejs.org/en/docs/inspector 

原理就是實(shí)現(xiàn)了 v8 debug protocol。

我們?nèi)绻约鹤稣{(diào)試工具、做 ide,那就要對(duì)接這個(gè)協(xié)議。

debugger adaptor protocol

上面介紹的 v8 debug protocol 可以實(shí)現(xiàn) js 代碼的調(diào)試,那么 python、c# 等肯定也有自己的調(diào)試協(xié)議,如果要實(shí)現(xiàn) ide,都要對(duì)接一遍太過麻煩。所以后來出現(xiàn)了一個(gè)中間層協(xié)議,DAP(debugger adaptor protocol)。

debugger adaptor protocol, 顧名思義,就是適配的,一端適配各種 debugger 協(xié)議,一端提供給客戶端統(tǒng)一的協(xié)議。這是適配器模式的一個(gè)很好的應(yīng)用。

總結(jié)

本文我們學(xué)習(xí)了 debugger 的實(shí)現(xiàn)原理和暴露出的調(diào)試協(xié)議。

首先我們了解了代碼兩種運(yùn)行方式:直接執(zhí)行和解釋執(zhí)行,然后分析了下為什么需要 debugger。

之后探索了直接執(zhí)行的代碼通過 INT 3 的中斷的方式來實(shí)現(xiàn) debugger 和解釋型語言自己實(shí)現(xiàn)的 debugger。

然后 debugger 的能力會(huì)通過 socket 暴露給客戶端,提供調(diào)試協(xié)議,比如 v8 debug protocol,各種客戶端包括 chrome devtools、ide 等都實(shí)現(xiàn)了這個(gè)協(xié)議。

但是每種語言都要實(shí)現(xiàn)一次的話太過麻煩,所以后來出現(xiàn)了一個(gè)適配層協(xié)議,屏蔽了不同協(xié)議的區(qū)別,提供統(tǒng)一的協(xié)議接口給客戶端用。

希望這篇文章能夠讓你理解 debugger 的原理,如果要實(shí)現(xiàn)調(diào)試工具也知道怎么該怎么去對(duì)接協(xié)議。能夠知道 chrome devtools、vscode 為啥都可以調(diào)試 nodejs 代碼。

 

責(zé)任編輯:武曉燕 來源: 神光的編程秘籍
相關(guān)推薦

2019-11-15 15:12:19

Windows激活KMS

2023-04-11 16:13:44

JavaScripSymbol前端

2011-04-08 16:26:14

JavaScript

2010-10-09 15:31:51

JavaScriptCookie

2010-09-17 15:25:03

JAVAJVM

2021-12-12 21:01:12

CSS 技巧PurgeCss

2009-12-04 18:09:56

2010-07-12 22:19:27

UDP服務(wù)

2020-09-22 08:52:50

平臺(tái)實(shí)現(xiàn)消息

2024-08-07 09:22:57

2009-10-23 13:32:34

.NET CRL

2009-11-04 13:51:46

ADO.NET性能

2021-07-19 09:25:19

數(shù)據(jù)庫MySQL技術(shù)

2022-04-08 08:32:40

mobx狀態(tài)管理庫redux

2023-06-26 18:03:26

btrace 2.0開源

2025-06-12 05:00:00

@Autowired自動(dòng)裝配實(shí)現(xiàn)機(jī)制

2022-10-13 13:21:58

系統(tǒng)httpsCharles

2024-10-10 17:46:06

2023-03-06 07:43:05

JavaScripDebugger

2021-06-21 17:00:05

云計(jì)算Hologres云原生
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)