亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

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

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

讓程序停下來

如果我們需要對(duì)一個(gè)程序進(jìn)行調(diào)試最重要的一個(gè)點(diǎn)就是如果讓程序停下來,只有讓程序的執(zhí)行停下來我們才能夠觀察程序執(zhí)行的狀態(tài),比如我們需要調(diào)試 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 就可以對(duì)上面的程序進(jìn)行調(diào)試:

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

當(dāng)然你也可以在 IDE 當(dāng)中進(jìn)行調(diào)試:

根據(jù)我們的調(diào)試經(jīng)歷容易知道,要想調(diào)試一個(gè)程序首先最重要的一點(diǎn)就是程序需要在我們?cè)O(shè)置斷點(diǎn)的位置要能夠停下來

cpython 王炸機(jī)制 —— tracing

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

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

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

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

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

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

事件的含義如下:

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

自己動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)單的調(diào)試器

在本小節(jié)當(dāng)中我們將實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的調(diào)試器幫助大家理解調(diào)試器的實(shí)現(xiàn)原理。調(diào)試器的實(shí)現(xiàn)代碼如下所示,只有短短幾十行卻可以幫助我們深入去理解調(diào)試器的原理,我們先看一下實(shí)現(xiàn)的效果在后文當(dāng)中再去分析具體的實(shí)現(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)

在上面的程序當(dāng)中使用如下:

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

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

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

可以看到我們的程序真正的被調(diào)試起來了。

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

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

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

python 官方調(diào)試器源碼分析

python 官方的調(diào)試器為 pdb 這個(gè)是 python 標(biāo)準(zhǔn)庫(kù)自帶的,我們可以通過 python -m pdb xx.py 去調(diào)試文件 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ù)進(jìn)行 tracing 操作,當(dāng)有事件發(fā)生的時(shí)候就能夠捕捉了。在上面的代碼當(dāng)中 tracing 函數(shù)為 self.trace_dispatch 我們?cè)賮砜催@個(gè)函數(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

從上面的代碼當(dāng)中可以看到每一種事件都有一個(gè)對(duì)應(yīng)的處理函數(shù),在本文當(dāng)中我們主要分析 函數(shù) dispatch_line,這個(gè)處理 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

這個(gè)函數(shù)首先會(huì)判斷是否需要在當(dāng)前行停下來,如果需要停下來就需要進(jìn)入 user_line 這個(gè)函數(shù),后面的調(diào)用鏈函數(shù)比較長(zhǎng),我們直接看最后執(zhí)行的函數(shù),根據(jù)我們使用 pdb 的經(jīng)驗(yàn)來看,最終肯定是一個(gè) while 循環(huán)讓我們可以不斷的輸入指令進(jì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:
                            # 核心邏輯就在這里 不斷的要求輸入然后進(jìn)行處理
                            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) # 這個(gè)函數(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)在我們?cè)賮砜匆幌?do_p 打印一個(gè)表達(dá)式是如何實(shí)現(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:
            # 看到這里就破案了這不是和我們自己實(shí)現(xiàn)的 pdb 獲取變量的方式一樣嘛 都是
            # 使用當(dāng)前執(zhí)行棧幀的全局和局部變量交給 eval 函數(shù)處理 并且將它的返回值輸出
            return eval(arg, self.curframe.f_globals, self.curframe_locals)
        except:
            self._error_exc()
            raise

總結(jié)

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

還記得我們?cè)谟懻?frameobject 的時(shí)候有一個(gè)字段 f_trace 嘛,這個(gè)字段就是指向我們傳遞給 sys.settrace 的函數(shù),當(dāng)發(fā)生事件的時(shí)候虛擬機(jī)就會(huì)調(diào)用這個(gè)函數(shù)。

以上就是深入理解Python虛擬機(jī)中調(diào)試器實(shí)現(xiàn)原理與源碼分析的詳細(xì)內(nèi)容,更多關(guān)于Python虛擬機(jī)調(diào)試器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • python中try的使用方法詳解

    python中try的使用方法詳解

    這篇文章主要給大家介紹了關(guān)于python中try使用的相關(guān)資料,try語(yǔ)句用于包含一段可能引發(fā)異常的代碼塊,并允許我們定義如何處理這些異常,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-10-10
  • Python基于Tkinter模塊實(shí)現(xiàn)的彈球小游戲

    Python基于Tkinter模塊實(shí)現(xiàn)的彈球小游戲

    這篇文章主要介紹了Python基于Tkinter模塊實(shí)現(xiàn)的彈球小游戲,涉及Python圖形繪制、數(shù)值計(jì)算、判斷等相關(guān)操作技巧,需要的朋友可以參考下
    2018-12-12
  • python字符串常用方法

    python字符串常用方法

    這篇文章主要介紹了python字符串常用方法,find、count、replace、split、startswith、endswith等多種方法,需要的朋友可以參考一下文章得具體內(nèi)容,希望對(duì)你有所幫助
    2021-10-10
  • 詳解Python中is和==的區(qū)別

    詳解Python中is和==的區(qū)別

    這篇文章主要介紹了Python中is和==的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • Pygame Rect區(qū)域位置的使用(圖文)

    Pygame Rect區(qū)域位置的使用(圖文)

    在 Pygame 中我們使用 Rect() 方法來創(chuàng)建一個(gè)指定位置,大小的矩形區(qū)域。本文主要就來介紹一下如何使用,具有一定的參考價(jià)值,感興趣的可以了解一下
    2021-11-11
  • 15行Python代碼實(shí)現(xiàn)網(wǎng)易云熱門歌單實(shí)例教程

    15行Python代碼實(shí)現(xiàn)網(wǎng)易云熱門歌單實(shí)例教程

    這篇文章主要給大家介紹了關(guān)于利用15行Python代碼實(shí)現(xiàn)網(wǎng)易云熱門歌單的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用python具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • python入門學(xué)習(xí)關(guān)于for else的特殊特性講解

    python入門學(xué)習(xí)關(guān)于for else的特殊特性講解

    本文將介紹 Python 中的" for-else"特性,并通過簡(jiǎn)單的示例說明如何正確使用它,有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2021-11-11
  • python跨文件夾調(diào)用別的文件夾下py文件或參數(shù)方式詳解

    python跨文件夾調(diào)用別的文件夾下py文件或參數(shù)方式詳解

    這篇文章主要給大家介紹了關(guān)于python跨文件夾調(diào)用別的文件夾下py文件或參數(shù)方式的相關(guān)資料,在python中有時(shí)候我們需要調(diào)用另一.py文件中的方法或者類,需要的朋友可以參考下
    2023-08-08
  • python使用rsa加密算法模塊模擬新浪微博登錄

    python使用rsa加密算法模塊模擬新浪微博登錄

    這篇文章主要介紹了python使用rsa加密算法模塊模擬新浪微博登錄的示例,大家參考使用吧
    2014-01-01
  • django框架使用方法詳解

    django框架使用方法詳解

    這篇文章主要介紹了django框架使用方法詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-07-07

最新評(píng)論