91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Python虛擬機中調試器的實現原理是什么

發布時間:2023-04-26 11:13:07 來源:億速云 閱讀:141 作者:iii 欄目:開發技術

這篇文章主要講解了“Python虛擬機中調試器的實現原理是什么”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“Python虛擬機中調試器的實現原理是什么”吧!

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

讓程序停下來

如果我們需要對一個程序進行調試最重要的一個點就是如果讓程序停下來,只有讓程序的執行停下來我們才能夠觀察程序執行的狀態,比如我們需要調試 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()

現在執行命令 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 當中進行調試:

Python虛擬機中調試器的實現原理是什么

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

cpython 王炸機制 &mdash;&mdash; tracing

現在的問題是,上面的程序是怎么在程序執行時停下來的呢?

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

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

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

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

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

事件的含義如下:

  • call,調用了一個函數(或者進入了其他代碼塊)。調用全局跟蹤函數;arg 為 None;返回值指定了本地跟蹤函數。

  • line,將要執行一行新的代碼,參數 arg 的值為 None 。

  • return,函數(或其他代碼塊)即將返回。調用本地跟蹤函數;arg 是將要返回的值,如果事件是由引發的異常引起的,則arg為None。跟蹤函數的返回值將被忽略。

  • exception,發生了異常。調用本地跟蹤函數;arg是一個元組(exception,value,traceback);返回值指定了新的本地跟蹤函數。

  • opcode,解釋器即將執行新的字節碼指令。調用本地跟蹤函數;arg 為 None;返回值指定了新的本地跟蹤函數。默認情況下,不會發出每個操作碼的事件:必須通過在幀上設置 f_trace_opcodes 為 True 來顯式請求。

  • c_call,一個 c 函數將要被調用。

  • c_exception,調用 c 函數的時候產生了異常。

自己動手實現一個簡單的調試器

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

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 執行一行代碼。

  • p name 打印變量 name 。

  • q 退出調試。

現在我們執行上面的程序,進行程序調試:

(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) ? 

Python虛擬機中調試器的實現原理是什么

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

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

  • frame,當前正在執行的棧幀。

  • event,事件的類別,這一點在前面的文件當中已經提到了。

  • arg,參數這一點在前面也已經提到了。

  • 同時需要注意的是這個函數也需要有一個返回值,python 虛擬機在下一次事件發生的時候會調用返回的這個函數,如果返回 None 那么就不會在發生事件的時候調用 tracing 函數了,這是代碼當中為什么在 debug 返回 debug 的原因。

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

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)

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

    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

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

    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

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

    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) # 這個函數就是處理我們輸入的字符串的比如 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:
                # 根據下面的代碼可以分析了解到如果我們執行命令 p 執行的函數為 do_p
                func = getattr(self, 'do_' + cmd)
            except AttributeError:
                return self.default(line)
            return func(arg)

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

    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:
            # 看到這里就破案了這不是和我們自己實現的 pdb 獲取變量的方式一樣嘛 都是
            # 使用當前執行棧幀的全局和局部變量交給 eval 函數處理 并且將它的返回值輸出
            return eval(arg, self.curframe.f_globals, self.curframe_locals)
        except:
            self._error_exc()
            raise

感謝各位的閱讀,以上就是“Python虛擬機中調試器的實現原理是什么”的內容了,經過本文的學習后,相信大家對Python虛擬機中調試器的實現原理是什么這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

黄大仙区| 金华市| 枣强县| 丰镇市| 繁峙县| 漳平市| 泌阳县| 吉首市| 乐清市| 沧州市| 鄂伦春自治旗| 本溪市| 台中县| 渝北区| 长垣县| 溧阳市| 运城市| 新宁县| 扎赉特旗| 万宁市| 泸定县| 青河县| 桦川县| 集贤县| 莲花县| 浦江县| 永春县| 铅山县| 牡丹江市| 东山县| 朝阳市| 兰西县| 通辽市| 广河县| 曲靖市| 贺州市| 淮安市| 永胜县| 襄垣县| 共和县| 甘南县|