最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解Python虛擬機中調試器實現(xiàn)原理與源碼分析

 更新時間:2023年04月26日 09:06:49   作者:一無是處的研究僧  
本文主要給大家介紹python中調試器的實現(xiàn)原理,通過了解一個語言的調試器的實現(xiàn)原理我們可以更加深入的理解整個語言的運行機制,可以幫助我們更好的理解程序的執(zhí)行,感興趣的可以了解一下

調試器是一個編程語言非常重要的部分,調試器是一種用于診斷和修復代碼錯誤(或稱為 bug)的工具,它允許開發(fā)者在程序執(zhí)行時逐步查看和分析代碼的狀態(tài)和行為,它可以幫助開發(fā)者診斷和修復代碼錯誤,理解程序的行為,優(yōu)化性能。無論在哪種編程語言中,調試器都是一個強大的工具,對于提高開發(fā)效率和代碼質量都起著積極的作用。

在本篇文章當中主要給大家介紹 python 語言當中調試器的實現(xiàn)原理,通過了解一個語言的調試器的實現(xiàn)原理我們可以更加深入的理解整個語言的運行機制,可以幫助我們更好的理解程序的執(zhí)行。

讓程序停下來

如果我們需要對一個程序進行調試最重要的一個點就是如果讓程序停下來,只有讓程序的執(zhí)行停下來我們才能夠觀察程序執(zhí)行的狀態(tài),比如我們需要調試 99 乘法表:

def m99():
    for i in range(1, 10):
        for j in range(1, i + 1):
            print(f"{i}x{j}={i*j}", end='\t')
        print()


if __name__ == '__main__':
    m99()

現(xiàn)在執(zhí)行命令 python -m pdb pdbusage.py 就可以對上面的程序進行調試:

(py3.8) ?  pdb_test git:(master) ? python -m pdb pdbusage.py
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(3)<module>()
-> def m99():
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(10)<module>()
-> if __name__ == '__main__':
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(11)<module>()
-> m99()
(Pdb) s
--Call--
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(3)m99()
-> def m99():
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(4)m99()
-> for i in range(1, 10):
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(5)m99()
-> for j in range(1, i + 1):
(Pdb) s
> /Users/xxxx/Desktop/workdir/dive-into-cpython/code/pdb_test/pdbusage.py(6)m99()
-> print(f"{i}x{j}={i*j}", end='\t')
(Pdb) p i
1
(Pdb) 

當然你也可以在 IDE 當中進行調試:

根據(jù)我們的調試經歷容易知道,要想調試一個程序首先最重要的一點就是程序需要在我們設置斷點的位置要能夠停下來

cpython 王炸機制 —— tracing

現(xiàn)在的問題是,上面的程序是怎么在程序執(zhí)行時停下來的呢?

根據(jù)前面的學習我們可以了解到,一個 python 程序的執(zhí)行首先需要經過 python 編譯器編譯成 python 字節(jié)碼,然后交給 python 虛擬機進行執(zhí)行,如果需要程序停下來就一定需要虛擬機給上層的 python 程序提供接口,讓程序在執(zhí)行的時候可以知道現(xiàn)在執(zhí)行到什么位置了。這個神秘的機制就隱藏在 sys 這個模塊當中,事實上這個模塊幾乎承擔了所有我們與 python 解釋器交互的接口。實現(xiàn)調試器一個非常重要的函數(shù)就是 sys.settrace 函數(shù),這個函數(shù)將為線程設置一個追蹤函數(shù),當虛擬機有函數(shù)調用,執(zhí)行完一行代碼的時候、甚至執(zhí)行完一條字節(jié)碼之后就會執(zhí)行這個函數(shù)。

設置系統(tǒng)的跟蹤函數(shù),允許在 Python 中實現(xiàn)一個 Python 源代碼調試器。該函數(shù)是線程特定的;為了支持多線程調試,必須對每個正在調試的線程注冊一個跟蹤函數(shù),使用 settrace() 或者使用 threading.settrace() 。

跟蹤函數(shù)應該有三個參數(shù):frame、event 和 arg。frame 是當前的棧幀。event 是一個字符串:'call'、'line'、'return'、'exception'、 'opcode' 、'c_call' 或者 'c_exception'。arg 取決于事件類型。

跟蹤函數(shù)在每次進入新的局部作用域時被調用(事件設置為'call');它應該返回一個引用,用于新作用域的本地跟蹤函數(shù),或者如果不想在該作用域中進行跟蹤,則返回None。

如果在跟蹤函數(shù)中發(fā)生任何錯誤,它將被取消設置,就像調用settrace(None)一樣。

事件的含義如下:

  • call,調用了一個函數(shù)(或者進入了其他代碼塊)。調用全局跟蹤函數(shù);arg 為 None;返回值指定了本地跟蹤函數(shù)。
  • line,將要執(zhí)行一行新的代碼,參數(shù) arg 的值為 None 。
  • return,函數(shù)(或其他代碼塊)即將返回。調用本地跟蹤函數(shù);arg 是將要返回的值,如果事件是由引發(fā)的異常引起的,則arg為None。跟蹤函數(shù)的返回值將被忽略。
  • exception,發(fā)生了異常。調用本地跟蹤函數(shù);arg是一個元組(exception,value,traceback);返回值指定了新的本地跟蹤函數(shù)。
  • opcode,解釋器即將執(zhí)行新的字節(jié)碼指令。調用本地跟蹤函數(shù);arg 為 None;返回值指定了新的本地跟蹤函數(shù)。默認情況下,不會發(fā)出每個操作碼的事件:必須通過在幀上設置 f_trace_opcodes 為 True 來顯式請求。
  • c_call,一個 c 函數(shù)將要被調用。
  • c_exception,調用 c 函數(shù)的時候產生了異常。

自己動手實現(xiàn)一個簡單的調試器

在本小節(jié)當中我們將實現(xiàn)一個非常簡單的調試器幫助大家理解調試器的實現(xiàn)原理。調試器的實現(xiàn)代碼如下所示,只有短短幾十行卻可以幫助我們深入去理解調試器的原理,我們先看一下實現(xiàn)的效果在后文當中再去分析具體的實現(xiàn):

import sys

file = sys.argv[1]
with open(file, "r+") as fp:
    code = fp.read()
lines = code.split("\n")


def do_line(frame, event, arg):
    print("debugging line:", lines[frame.f_lineno - 1])
    return debug


def debug(frame, event, arg):
    if event == "line":
        while True:
            _ = input("(Pdb)")
            if _ == 'n':
                return do_line(frame, event, arg)
            elif _.startswith('p'):
                _, v = _.split()
                v = eval(v, frame.f_globals, frame.f_locals)
                print(v)
            elif _ == 'q':
                sys.exit(0)
    return debug


if __name__ == '__main__':
    sys.settrace(debug)
    exec(code, None, None)
    sys.settrace(None)

在上面的程序當中使用如下:

  • 輸入 n 執(zhí)行一行代碼。
  • p name 打印變量 name 。
  • q 退出調試。

現(xiàn)在我們執(zhí)行上面的程序,進行程序調試:

(py3.10) ?  pdb_test git:(master) ? python mydebugger.py pdbusage.py
(Pdb)n
debugging line: def m99():
(Pdb)n
debugging line: if __name__ == '__main__':
(Pdb)n
debugging line:     m99()
(Pdb)n
debugging line:     for i in range(1, 10):
(Pdb)n
debugging line:         for j in range(1, i + 1):
(Pdb)n
debugging line:             print(f"{i}x{j}={i*j}", end='\t')
1x1=1   (Pdb)n
debugging line:         for j in range(1, i + 1):
(Pdb)p i
1
(Pdb)p j
1
(Pdb)q
(py3.10) ?  pdb_test git:(master) ? 

可以看到我們的程序真正的被調試起來了。

現(xiàn)在我們來分析一下我們自己實現(xiàn)的簡易版本的調試器,在前文當中我們已經提到了 sys.settrace 函數(shù),調用這個函數(shù)時需要傳遞一個函數(shù)作為參數(shù),被傳入的函數(shù)需要接受三個參數(shù):

  • frame,當前正在執(zhí)行的棧幀。
  • event,事件的類別,這一點在前面的文件當中已經提到了。
  • arg,參數(shù)這一點在前面也已經提到了。
  • 同時需要注意的是這個函數(shù)也需要有一個返回值,python 虛擬機在下一次事件發(fā)生的時候會調用返回的這個函數(shù),如果返回 None 那么就不會在發(fā)生事件的時候調用 tracing 函數(shù)了,這是代碼當中為什么在 debug 返回 debug 的原因。

我們只對 line 這個事件進行處理,然后進行死循環(huán),只有輸入 n 指令的時候才會執(zhí)行下一行,然后打印正在執(zhí)行的行,這個時候就會退出函數(shù) debug ,程序就會繼續(xù)執(zhí)行了。python 內置的 eval 函數(shù)可以獲取變量的值。

python 官方調試器源碼分析

python 官方的調試器為 pdb 這個是 python 標準庫自帶的,我們可以通過 python -m pdb xx.py 去調試文件 xx.py 。這里我們只分析核心代碼:

代碼位置:bdp.py 下面的 Bdb 類

    def run(self, cmd, globals=None, locals=None):
        """Debug a statement executed via the exec() function.

        globals defaults to __main__.dict; locals defaults to globals.
        """
        if globals is None:
            import __main__
            globals = __main__.__dict__
        if locals is None:
            locals = globals
        self.reset()
        if isinstance(cmd, str):
            cmd = compile(cmd, "<string>", "exec")
        sys.settrace(self.trace_dispatch)
        try:
            exec(cmd, globals, locals)
        except BdbQuit:
            pass
        finally:
            self.quitting = True
            sys.settrace(None)

上面的函數(shù)主要是使用 sys.settrace 函數(shù)進行 tracing 操作,當有事件發(fā)生的時候就能夠捕捉了。在上面的代碼當中 tracing 函數(shù)為 self.trace_dispatch 我們再來看這個函數(shù)的代碼:

    def trace_dispatch(self, frame, event, arg):
        """Dispatch a trace function for debugged frames based on the event.

        This function is installed as the trace function for debugged
        frames. Its return value is the new trace function, which is
        usually itself. The default implementation decides how to
        dispatch a frame, depending on the type of event (passed in as a
        string) that is about to be executed.

        The event can be one of the following:
            line: A new line of code is going to be executed.
            call: A function is about to be called or another code block
                  is entered.
            return: A function or other code block is about to return.
            exception: An exception has occurred.
            c_call: A C function is about to be called.
            c_return: A C function has returned.
            c_exception: A C function has raised an exception.

        For the Python events, specialized functions (see the dispatch_*()
        methods) are called.  For the C events, no action is taken.

        The arg parameter depends on the previous event.
        """
        if self.quitting:
            return # None
        if event == 'line':
            print("In line")
            return self.dispatch_line(frame)
        if event == 'call':
            print("In call")
            return self.dispatch_call(frame, arg)
        if event == 'return':
            print("In return")
            return self.dispatch_return(frame, arg)
        if event == 'exception':
            print("In execption")
            return self.dispatch_exception(frame, arg)
        if event == 'c_call':
            print("In c_call")
            return self.trace_dispatch
        if event == 'c_exception':
            print("In c_exception")
            return self.trace_dispatch
        if event == 'c_return':
            print("In c_return")
            return self.trace_dispatch
        print('bdb.Bdb.dispatch: unknown debugging event:', repr(event))
        return self.trace_dispatch

從上面的代碼當中可以看到每一種事件都有一個對應的處理函數(shù),在本文當中我們主要分析 函數(shù) dispatch_line,這個處理 line 事件的函數(shù)。

    def dispatch_line(self, frame):
        """Invoke user function and return trace function for line event.

        If the debugger stops on the current line, invoke
        self.user_line(). Raise BdbQuit if self.quitting is set.
        Return self.trace_dispatch to continue tracing in this scope.
        """
        if self.stop_here(frame) or self.break_here(frame):
            self.user_line(frame)
            if self.quitting: raise BdbQuit
        return self.trace_dispatch

這個函數(shù)首先會判斷是否需要在當前行停下來,如果需要停下來就需要進入 user_line 這個函數(shù),后面的調用鏈函數(shù)比較長,我們直接看最后執(zhí)行的函數(shù),根據(jù)我們使用 pdb 的經驗來看,最終肯定是一個 while 循環(huán)讓我們可以不斷的輸入指令進行處理:

    def cmdloop(self, intro=None):
        """Repeatedly issue a prompt, accept input, parse an initial prefix
        off the received input, and dispatch to action methods, passing them
        the remainder of the line as argument.

        """
        print("In cmdloop")
        self.preloop()
        if self.use_rawinput and self.completekey:
            try:
                import readline
                self.old_completer = readline.get_completer()
                readline.set_completer(self.complete)
                readline.parse_and_bind(self.completekey+": complete")
            except ImportError:
                pass
        try:
            if intro is not None:
                self.intro = intro
            print(f"{self.intro = }")
            if self.intro:
                self.stdout.write(str(self.intro)+"\n")
            stop = None
            while not stop:
                print(f"{self.cmdqueue = }")
                if self.cmdqueue:
                    line = self.cmdqueue.pop(0)
                else:
                    print(f"{self.prompt = } {self.use_rawinput}")
                    if self.use_rawinput:
                        try:
                            # 核心邏輯就在這里 不斷的要求輸入然后進行處理
                            line = input(self.prompt) # self.prompt = '(Pdb)'
                        except EOFError:
                            line = 'EOF'
                    else:
                        self.stdout.write(self.prompt)
                        self.stdout.flush()
                        line = self.stdin.readline()
                        if not len(line):
                            line = 'EOF'
                        else:
                            line = line.rstrip('\r\n')

                line = self.precmd(line)
                stop = self.onecmd(line) # 這個函數(shù)就是處理我們輸入的字符串的比如 p n 等等
                stop = self.postcmd(stop, line)
            self.postloop()
        finally:
            if self.use_rawinput and self.completekey:
                try:
                    import readline
                    readline.set_completer(self.old_completer)
                except ImportError:
                    pass
    def onecmd(self, line):
        """Interpret the argument as though it had been typed in response
        to the prompt.

        This may be overridden, but should not normally need to be;
        see the precmd() and postcmd() methods for useful execution hooks.
        The return value is a flag indicating whether interpretation of
        commands by the interpreter should stop.

        """
        cmd, arg, line = self.parseline(line)
        if not line:
            return self.emptyline()
        if cmd is None:
            return self.default(line)
        self.lastcmd = line
        if line == 'EOF' :
            self.lastcmd = ''
        if cmd == '':
            return self.default(line)
        else:
            try:
                # 根據(jù)下面的代碼可以分析了解到如果我們執(zhí)行命令 p 執(zhí)行的函數(shù)為 do_p
                func = getattr(self, 'do_' + cmd)
            except AttributeError:
                return self.default(line)
            return func(arg)

現(xiàn)在我們再來看一下 do_p 打印一個表達式是如何實現(xiàn)的:

    def do_p(self, arg):
        """p expression
        Print the value of the expression.
        """
        self._msg_val_func(arg, repr)

    def _msg_val_func(self, arg, func):
        try:
            val = self._getval(arg)
        except:
            return  # _getval() has displayed the error
        try:
            self.message(func(val))
        except:
            self._error_exc()

    def _getval(self, arg):
        try:
            # 看到這里就破案了這不是和我們自己實現(xiàn)的 pdb 獲取變量的方式一樣嘛 都是
            # 使用當前執(zhí)行棧幀的全局和局部變量交給 eval 函數(shù)處理 并且將它的返回值輸出
            return eval(arg, self.curframe.f_globals, self.curframe_locals)
        except:
            self._error_exc()
            raise

總結

在本篇文章當中我們主要分析 python 當中實現(xiàn)調試器的原理,并且通過一個幾十行的代碼實現(xiàn)了一個非常簡單的調試器,這可以深入幫助我們理解調試器實現(xiàn)的細節(jié),這讓我們對于程序設計語言的認識又加深了一點。最后簡單的介紹了一下 python 自己的調試器 pdb,但是有一點遺憾的目前 pdb 還不能夠支持直接調試 python 字節(jié)碼,但是在 python 虛擬機當中已經有調試字節(jié)碼的事件了,相信在未來應該可以直接調試字節(jié)碼了。

還記得我們在討論 frameobject 的時候有一個字段 f_trace 嘛,這個字段就是指向我們傳遞給 sys.settrace 的函數(shù),當發(fā)生事件的時候虛擬機就會調用這個函數(shù)。

以上就是深入理解Python虛擬機中調試器實現(xiàn)原理與源碼分析的詳細內容,更多關于Python虛擬機調試器的資料請關注腳本之家其它相關文章!

相關文章

  • Python正則表達式指南 推薦

    Python正則表達式指南 推薦

    本文介紹了Python對于正則表達式的支持,包括正則表達式基礎以及Python正則表達式標準庫的完整介紹及使用示例。本文的內容不包括如何編寫高效的正則表達式、如何優(yōu)化正則表達式,這些主題請查看其他教程。
    2018-10-10
  • 使用python3 實現(xiàn)插入數(shù)據(jù)到mysql

    使用python3 實現(xiàn)插入數(shù)據(jù)到mysql

    今天小編就為大家分享一篇使用python3 實現(xiàn)插入數(shù)據(jù)到mysql,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-03-03
  • python的數(shù)學算法函數(shù)及公式用法

    python的數(shù)學算法函數(shù)及公式用法

    在本篇內容里小編給大家分享了一篇關于python的數(shù)學算法函數(shù)及公式用法,有興趣的朋友們可以參考下。
    2020-11-11
  • Python DBM模塊輕松使用小型數(shù)據(jù)庫存儲管理數(shù)據(jù)

    Python DBM模塊輕松使用小型數(shù)據(jù)庫存儲管理數(shù)據(jù)

    這篇文章主要介紹了Python DBM模塊輕松使用小型數(shù)據(jù)庫存儲管理數(shù)據(jù),它可以讓你輕松地存儲和管理鍵值對數(shù)據(jù),可以使用 dbm 模塊來操作 DBM 文件,或者使用 shelve 模塊來存儲任意類型的 Python 對象
    2024-01-01
  • Python網(wǎng)絡編程詳解

    Python網(wǎng)絡編程詳解

    這篇文章主要介紹了Python網(wǎng)絡編程詳解,涉及具體代碼示例,還是挺不錯的,這里分享給大家,供需要的朋友參考。
    2017-10-10
  • Python import與from import使用和區(qū)別解讀

    Python import與from import使用和區(qū)別解讀

    Python程序可以調用一組基本的函數(shù)(即內建函數(shù)),比如print()、input()和len()等函數(shù)。接下來通過本文給大家介紹Python import與from import使用及區(qū)別介紹,感興趣的朋友一起看看吧
    2021-09-09
  • Pygame如何使用精靈和碰撞檢測

    Pygame如何使用精靈和碰撞檢測

    本文主要介紹了Pygame如何使用精靈和碰撞檢測,它們能夠幫助我們跟蹤屏幕上移動的大量圖像。我們還會了解如何檢測兩個圖像相互重疊或者碰撞的方法。
    2021-11-11
  • Python+PyQt5自制監(jiān)控小工具

    Python+PyQt5自制監(jiān)控小工具

    這篇文章主要為大家詳細介紹了如何通過使用python實現(xiàn)對計算機攝像頭的調用從而實現(xiàn)攝像監(jiān)控的功能,文中的示例代碼講解詳細,需要的可以參考一下
    2023-03-03
  • 分享3個簡單的Python代碼高效運行技巧

    分享3個簡單的Python代碼高效運行技巧

    這篇文章主要介紹了分享3個簡單的Python代碼高效運行技巧,下面主要分享三個有效的,方便理解的,執(zhí)行高效的實用技巧,需要的朋友可以參考一下
    2022-03-03
  • 深入理解Python虛擬機中常見魔術方法的使用

    深入理解Python虛擬機中常見魔術方法的使用

    本文主要給大家介紹在 python 當中與數(shù)學計算相關的一些常見的魔術方法,是在很多科學計算的包當中都使用到的魔術方法,感興趣的小伙伴可以了解一下
    2023-05-05

最新評論

福建省| 巴彦淖尔市| 黄骅市| 安仁县| 肇东市| 德江县| 兰溪市| 巫溪县| 体育| 广宗县| 历史| 荆门市| 邯郸县| 唐河县| 塔河县| 彭泽县| 东莞市| 新邵县| 永仁县| 泊头市| 曲麻莱县| 鄂伦春自治旗| 大同市| 桦川县| 什邡市| 新兴县| 宜都市| 昭平县| 金乡县| 锦屏县| 莱西市| 永川市| 赫章县| 浦东新区| 巢湖市| 漳浦县| 邛崃市| 施秉县| 吉林省| 鹿泉市| 息烽县|