[关闭]
@qidiandasheng 2021-04-22T22:49:55.000000Z 字数 16364 阅读 1218

Python 实现 Python 解释器(😁)

Python&Ruby


入门解释器

最简单的入门解释器仅实现加法这一个功能。它也只认得三条指令,所以它可以运行的程序也只有这三个指令的排列组合而已。

入门解释器最基本的三个指令:

这里我们先关心编译后字节码,以7+5的源码为例:

  1. what_to_execute = {
  2. "instructions": [("LOAD_VALUE", 0), # 第一个数
  3. ("LOAD_VALUE", 1), # 第二个数
  4. ("ADD_TWO_VALUES", None),
  5. ("PRINT_ANSWER", None)],
  6. "numbers": [7, 5] }

其中what_to_execute就是我们的字节码对象(code object), instructions相当于字节码。

我们的解释器是一个堆栈机器,所以是使用栈来完成加法的。首先执行第一个指令LOAD_VALUE,将第一个数压入栈中,第二个指令同样将第二个数压入栈中。第三个指令ADD_TWO_VALUES弹出栈中的两个数,将它们相加并将结果压入栈中,最后一个指令弹出栈中的答案并打印。

导出图片Sun Apr 04 2021 17_20_52 GMT+0800 (中国标准时间).png-35.4kB

LOAD_VALUE 指令需要找到参数指定的数据进行压栈,那么数据哪里来的呢?可以发现我们的指令集包含两部分:指令自身与一个常量列表。数据来自常量列表。

我们可以看到("LOAD_VALUE", 0)("LOAD_VALUE", 1)传的参数0和1表示分别从常量列表读取第一个和第二个值压如栈中,("ADD_TWO_VALUES", None)并没有传参,它只需要从栈中弹出两个数相加再把结果压如栈中即可。

真实的Python字节码

我们先定义一个函数,然后输出一些信息:

  1. import dis
  2. def cond():
  3. x = 3
  4. if x < 5:
  5. return 'yes'
  6. else:
  7. return 'no'
  8. //输出字节码
  9. print(cond.__code__.co_code)
  10. //输出字节码对应的数字数组
  11. print(list(bytearray(cond.__code__.co_code)))
  12. //输出反编译代码
  13. dis.dis(cond)
  14. //输出100对应的指令
  15. print(dis.opname[100])
  16. //输出常数列表
  17. print(cond.__code__.co_consts)

拿定义的 cond 方法举例,cond.__code__是其code objectcond.__code__.co_code 是其字节码。以下内容为对应的输出:

  1. b'd\x01}\x00|\x00d\x02k\x00r\x10d\x03S\x00d\x04S\x00d\x00S\x00'
  2. [100, 1, 125, 0, 124, 0, 100, 2, 107, 0, 114, 16, 100, 3, 83, 0, 100, 4, 83, 0, 100, 0, 83, 0]
  3. 4 0 LOAD_CONST 1 (3)
  4. 2 STORE_FAST 0 (x)
  5. 5 4 LOAD_FAST 0 (x)
  6. 6 LOAD_CONST 2 (5)
  7. 8 COMPARE_OP 0 (<)
  8. 10 POP_JUMP_IF_FALSE 16
  9. 6 12 LOAD_CONST 3 ('yes')
  10. 14 RETURN_VALUE
  11. 8 >> 16 LOAD_CONST 4 ('no')
  12. 18 RETURN_VALUE
  13. 20 LOAD_CONST 0 (None)
  14. 22 RETURN_VALUE
  15. LOAD_CONST
  16. (None, 3, 5, 'yes', 'no')

字节码对象的属性

  1. print(dir(code_obj))
  1. ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_kwonlyargcount', 'co_lnotab', 'co_name', 'co_names', 'co_nlocals', 'co_posonlyargcount', 'co_stacksize', 'co_varnames', 'replace']

字节码表示

b'd\x01}\x00|\x00d\x02k\x00r\x10d\x03S\x00d\x04S\x00d\x00S\x00'为字节码的输出,一个字节表示一个数字。

[100, 1, 125, 0, 124, 0, 100, 2, 107, 0, 114, 16, 100, 3, 83, 0, 100, 4, 83, 0, 100, 0, 83, 0]就是每个字节转换为数字之后排列的数组。

每个字节可能是使用的ASCII表示(1个ASCII占用一个字节),也可能使用的是2个16进制数表示(1个16进制数4位,两个8位一个字节)

以下为前面几个字节对应的翻译:

  1. d100 ASCII
  2. 011 16进制)
  3. }:125 ASCII
  4. 000 16进制)
  5. |:124 ASCII
  6. 000 16进制)
  7. d100 ASCII
  8. 022 16进制)
  9. k107 ASCII

反编译

当然我们直接看字节码并不直观,这里我们使用dis.dis(cond)反编译,翻译成人能读懂的指令:

  1. 4 0 LOAD_CONST 1 (3)
  2. 2 STORE_FAST 0 (x)
  3. 5 4 LOAD_FAST 0 (x)
  4. 6 LOAD_CONST 2 (5)
  5. 8 COMPARE_OP 0 (<)
  6. 10 POP_JUMP_IF_FALSE 16
  7. 6 12 LOAD_CONST 3 ('yes')
  8. 14 RETURN_VALUE
  9. 8 >> 16 LOAD_CONST 4 ('no')
  10. 18 RETURN_VALUE
  11. 20 LOAD_CONST 0 (None)
  12. 22 RETURN_VALUE
  13. LOAD_CONST

这里一共分4列

第4行指令翻译:

LOAD_CONST读取3作为参数,压如栈中,STORE_FAST读取x变量,从栈中弹出3赋值给x。

读取源代码编译成字节码对象

  1. code = """
  2. def loop():
  3. x = 1
  4. while x < 5:
  5. if x==3:
  6. break
  7. x = x + 1
  8. print(x)
  9. return x
  10. def add():
  11. x = 1 + 1
  12. """
  13. # compile 能够将源代码编译成字节码对象
  14. code_obj = compile(code, "tmp", "exec")
  1. print(code_obj.co_code)
  2. dis.dis(code_obj)
  1. b'd\x00d\x01\x84\x00Z\x00d\x02d\x03\x84\x00Z\x01d\x04S\x00'
  2. 2 0 LOAD_CONST 0 (<code object loop at 0x109bcdb30, file "tmp", line 2>)
  3. 2 LOAD_CONST 1 ('loop')
  4. 4 MAKE_FUNCTION 0
  5. 6 STORE_NAME 0 (loop)
  6. 10 8 LOAD_CONST 2 (<code object add at 0x109bcdbe0, file "tmp", line 10>)
  7. 10 LOAD_CONST 3 ('add')
  8. 12 MAKE_FUNCTION 0
  9. 14 STORE_NAME 1 (add)
  10. 16 LOAD_CONST 4 (None)
  11. 18 RETURN_VALUE
  12. Disassembly of <code object loop at 0x109bcdb30, file "tmp", line 2>:
  13. 3 0 LOAD_CONST 1 (1)
  14. 2 STORE_FAST 0 (x)
  15. 4 >> 4 LOAD_FAST 0 (x)
  16. 6 LOAD_CONST 2 (5)
  17. 8 COMPARE_OP 0 (<)
  18. 10 POP_JUMP_IF_FALSE 40
  19. 5 12 LOAD_FAST 0 (x)
  20. 14 LOAD_CONST 3 (3)
  21. 16 COMPARE_OP 2 (==)
  22. 18 POP_JUMP_IF_FALSE 22
  23. 6 20 JUMP_ABSOLUTE 40
  24. 7 >> 22 LOAD_FAST 0 (x)
  25. 24 LOAD_CONST 1 (1)
  26. 26 BINARY_ADD
  27. 28 STORE_FAST 0 (x)
  28. 8 30 LOAD_GLOBAL 0 (print)
  29. 32 LOAD_FAST 0 (x)
  30. 34 CALL_FUNCTION 1
  31. 36 POP_TOP
  32. 38 JUMP_ABSOLUTE 4
  33. 9 >> 40 LOAD_FAST 0 (x)
  34. 42 RETURN_VALUE
  35. Disassembly of <code object add at 0x109bcdbe0, file "tmp", line 10>:
  36. 11 0 LOAD_CONST 1 (2)
  37. 2 STORE_FAST 0 (x)
  38. 4 LOAD_CONST 0 (None)
  39. 6 RETURN_VALUE

我们可以看到输出的这个字节码为python对象的字节码,loop函数和add函数都是它的常量,比如这里LOAD_CONST取常量列表中第0个值为loop函数的字节码对象,压如栈中。

  1. (<code object loop at 0x10d9f7b30, file "tmp", line 2>, 'loop', <code object add at 0x10d9f7be0, file "tmp", line 10>, 'add', None)
  1. loopbytes = code_obj.co_consts[0].co_code
  2. print(loopbytes)
  3. print(list(bytearray(loopbytes)))
  1. b'd\x01}\x00|\x00d\x02k\x00r(|\x00d\x03k\x02r\x16q(|\x00d\x01\x17\x00}\x00t\x00|\x00\x83\x01\x01\x00q\x04|\x00S\x00'
  2. [100, 1, 125, 0, 124, 0, 100, 2, 107, 0, 114, 40, 124, 0, 100, 3, 107, 2, 114, 22, 113, 40, 124, 0, 100, 1, 23, 0, 125, 0, 116, 0, 124, 0, 131, 1, 1, 0, 113, 4, 124, 0, 83, 0]

帧的概念

我们知道了一个函数内的代码是如何被编译运行的,那函数外呢,RETURN_VALUE之后又是什么呢?为了回答这个问题,需要再引入一个概念:帧。帧包含了一段代码运行所需要的信息与上下文环境。帧在代码执行时被动态地创建与销毁,每一个帧的创建对应一次函数调用,所以每一个帧都有一个 code object与其关联,同时一个 code object 可以拥有多个帧,因为一个函数可以递归调用自己多次。

帧存在于调用栈中,你在程序异常时所看到的Traceback就是调用栈中的信息。调用栈顾名思义就是每当你在当前函数内调用一次函数就在当前调用栈上压入所调用的函数的帧,在所调用函数返回时再将该帧弹出。这里再说下解释器用到的另外两个栈,第一个我们已经接触过了,就是数据栈,执行字节码操作时使用的栈。还有一个叫作块栈,用于特定的控制流,比如循环与异常处理。每一个帧都拥有自己的数据栈与块栈。

举个具体例子来说明一下,以下代码的调用顺序是 main(模块) --> foo() --> bar()

  1. >>> def bar(y):
  2. ... z = y + 3 # <--- (3) ... 现在解释器执行到这里
  3. ... return z
  4. ...
  5. >>> def foo():
  6. ... a = 1
  7. ... b = 2
  8. ... return a + bar(b) # <--- (2) ... 调用 bar 函数
  9. ...
  10. >>> foo() # <--- (1) 我们在 foo() 中

下图显示当前调用栈中的内容:

document-uid8834labid1877timestamp1465072092637.png-68.2kB

我们现在在 bar() 中,在该函数返回后,bar Frame 就会被弹出,foo Frame 也是同理。

RETURN_VALUE 令解释器在帧之间传递一个值,首先调用栈的栈顶帧的数据栈的栈顶值会被弹出,之后丢弃栈顶帧,将之前弹出的值压到下一个帧的数据栈中。这样就完成了一次 RETURN_VALUE

实现Python解释器

Python环境:3.8.6

创建帧类

  1. class Frame(object):
  2. def __init__(self, code_obj, global_names, local_names, prev_frame):
  3. # 当前帧的字节码对象
  4. self.code_obj = code_obj
  5. # 全局变量
  6. self.f_globals = global_names
  7. # 局部变量
  8. self.f_locals = local_names
  9. # 上一帧
  10. self.prev_frame = prev_frame
  11. # 数据栈
  12. self.stack = []
  13. # block 栈
  14. self.block_stack = []
  15. # 如果前一帧存在,则把前一帧的内建模块赋值给当前帧
  16. # 否则取本地的内建模块给当前帧
  17. if prev_frame:
  18. self.f_builtins = prev_frame.f_builtins
  19. else:
  20. self.f_builtins = local_names['__builtins__']
  21. if hasattr(self.f_builtins, '__dict__'):
  22. self.f_builtins = self.f_builtins.__dict__
  23. # 最后运行的指令,初始为 0
  24. self.last_instruction = 0

创建函数类

  1. class Function(object):
  2. # __slots__ 会固定对象的属性,无法再动态增加新的属性,这可以节省内存空间
  3. __slots__ = [
  4. 'func_code', 'func_name', 'func_defaults', 'func_globals',
  5. 'func_locals', 'func_dict', 'func_closure',
  6. '__name__', '__dict__', '__doc__',
  7. '_vm', '_func',
  8. ]
  9. def __init__(self, name, code, globs, defaults, closure, vm):
  10. """这部分不需要去深究,但是代码会尽量注释说明"""
  11. self._vm = vm
  12. # 这里的 code 即所调用函数的 code_obj
  13. self.func_code = code
  14. # 函数名会存在 code.co_name 中
  15. self.func_name = self.__name__ = name or code.co_name
  16. # 函数参数的默认值,如 func(a=5,b=3) ,则 func_defaults 为 (5,3)
  17. self.func_defaults = tuple(defaults)
  18. self.func_globals = globs
  19. self.func_locals = self._vm.frame.f_locals
  20. self.__dict__ = {}
  21. # 函数的闭包信息
  22. self.func_closure = closure
  23. self.__doc__ = code.co_consts[0] if code.co_consts else None
  24. # 有时我们需要用到真实 Python 的函数,下面的代码是为它准备的
  25. kw = {
  26. 'argdefs': self.func_defaults,
  27. }
  28. # 为闭包创建 cell 对象
  29. if closure:
  30. kw['closure'] = tuple(make_cell(0) for _ in closure)
  31. self._func = types.FunctionType(code, globs, **kw)
  32. def __call__(self, *args, **kwargs):
  33. """每当调用一次函数,会创建一个新 frame 并运行"""
  34. # 通过 inspect 获得函数的参数
  35. callargs = inspect.getcallargs(self._func, *args, **kwargs)
  36. # 创建函数的帧
  37. frame = self._vm.make_frame(
  38. self.func_code, callargs, self.func_globals, {}
  39. )
  40. return self._vm.run_frame(frame)
  41. def make_cell(value):
  42. """创建一个真实的 cell 对象"""
  43. # Thanks to Alex Gaynor for help with this bit of twistiness.
  44. fn = (lambda x: lambda: x)(value)
  45. return fn.__closure__[0]

虚拟机类

  1. class VirtualMachine(object):
  2. def __init__(self):
  3. # 调用栈
  4. self.frames = []
  5. # 当前运行的帧
  6. self.frame = None
  7. # frame 返回时的返回值
  8. self.return_value = None
  9. self.last_exception = None
  10. def run_code(self, code, global_names=None, local_names=None):
  11. """ 运行 Python 程序的入口,程序编译后生成 code_obj
  12. 这里 code_obj 在参数 code 中
  13. run_code 根据输入的 code_obj 新建一个 frame 并开始运行
  14. """
  15. frame = self.make_frame(code, global_names=global_names,
  16. local_names=local_names)
  17. self.run_frame(frame)
  18. # 新建一个帧,code 为 code_obj ;callargs 为函数调用时的参数
  19. def make_frame(self, code, callargs={}, global_names=None, local_names=None):
  20. if global_names is not None:
  21. global_names = global_names
  22. if local_names is None:
  23. local_names = global_names
  24. elif self.frames:
  25. global_names = self.frame.global_names
  26. local_names = {}
  27. else:
  28. # __builtins__内建函数
  29. global_names = local_names = {
  30. '__builtins__':__builtins__,
  31. '__name__': '__main__',
  32. '__doc__': None,
  33. '__package__':None,
  34. }
  35. # 将函数调用时的参数更新到局部变量空间中
  36. local_names.update(callargs)
  37. frame = Frame(code, global_names, local_names, self.frame)
  38. return frame
  39. # 运行 frame
  40. def run_frame(self, frame):
  41. """运行帧直至它返回"""
  42. self.push_frame(frame)
  43. while True:
  44. byte_name, arguments = self.parse_byte_and_args()
  45. why = self.dispatch(byte_name, arguments)
  46. while why and frame.block_stack:
  47. why = self.manage_block_stack(why)
  48. if why:
  49. break
  50. self.pop_frame()
  51. if why == 'exception':
  52. exc, val, tb = self.last_exception
  53. e = exc(val)
  54. e.__traceback__ = tb
  55. raise e
  56. return self.return_value
  1. class VirtualMachine(object):
  2. # [... 跳过 ...]
  3. # 调用栈压入 frame
  4. def push_frame(self, frame):
  5. self.frames.append(frame)
  6. self.frame = frame
  7. # 调用栈弹出 frame
  8. def pop_frame(self):
  9. self.frames.pop()
  10. if self.frames:
  11. self.frame = self.frames[-1]
  12. else:
  13. self.frame = None
  14. # 数据栈操作
  15. def top(self):
  16. return self.frame.stack[-1]
  17. def pop(self):
  18. return self.frame.stack.pop()
  19. def push(self, *vals):
  20. self.frame.stack.extend(vals)
  21. def popn(self, n):
  22. """弹出多个值"""
  23. if n:
  24. ret = self.frame.stack[-n:]
  25. self.frame.stack[-n:] = []
  26. return ret
  27. else:
  28. return []
  29. # block栈操作
  30. def push_block(self, b_type, handler=None):
  31. stack_height = len(self.frame.stack)
  32. self.frame.block_stack.append(Block(b_type, handler, stack_height))
  33. def pop_block(self):
  34. return self.frame.block_stack.pop()
  35. def unwind_block(self, block):
  36. """Unwind the values on the data stack corresponding to a given block."""
  37. if block.type == 'except-handler':
  38. # The exception itself is on the stack as type, value, and traceback.
  39. offset = 3
  40. else:
  41. offset = 0
  42. while len(self.frame.stack) > block.stack_height + offset:
  43. self.pop()
  44. if block.type == 'except-handler':
  45. traceback, value, exctype = self.popn(3)
  46. self.last_exception = exctype, value, traceback
  47. def manage_block_stack(self, why):
  48. """管理一个 frame 的 block 栈
  49. 在循环、异常处理、返回这几个方面操作 block 栈与数据栈
  50. """
  51. frame = self.frame
  52. block = frame.block_stack[-1]
  53. if block.type == 'loop' and why == 'continue':
  54. self.jump(self.return_value)
  55. why = None
  56. return why
  57. self.pop_block()
  58. self.unwind_block(block)
  59. if block.type == 'loop' and why == 'break':
  60. why = None
  61. self.jump(block.handler)
  62. return why
  63. if (block.type in ['setup-except', 'finally'] and why == 'exception'):
  64. self.push_block('except-handler')
  65. exctype, value, tb = self.last_exception
  66. self.push(tb, value, exctype)
  67. self.push(tb, value, exctype) # yes, twice
  68. why = None
  69. self.jump(block.handler)
  70. return why
  71. elif block.type == 'finally':
  72. if why in ('return', 'continue'):
  73. self.push(self.return_value)
  74. self.push(why)
  75. why = None
  76. self.jump(block.handler)
  77. return why
  78. return why
  1. class VirtualMachine(object):
  2. # [... 跳过 ...]
  3. # 解析当前帧的指令名称和参数
  4. def parse_byte_and_args(self):
  5. f = self.frame
  6. opoffset = f.last_instruction
  7. # 取得要运行的指令
  8. byteCode = f.code_obj.co_code[opoffset]
  9. f.last_instruction += 1
  10. # 指令名称
  11. byte_name = dis.opname[byteCode]
  12. # 指令码 <dis.HAVE_ARGUMENT 的都是无参数指令,其它则是有参数指令
  13. if byteCode >= dis.HAVE_ARGUMENT:
  14. # 取得后一字节的参数
  15. arg = f.code_obj.co_code[f.last_instruction:f.last_instruction+1]
  16. f.last_instruction += 1
  17. # 参数第一个字节为参数
  18. arg_val = arg[0]
  19. if byteCode in dis.hasconst: # 查找常量
  20. arg = f.code_obj.co_consts[arg_val]
  21. elif byteCode in dis.hasname: # 查找变量名
  22. arg = f.code_obj.co_names[arg_val]
  23. elif byteCode in dis.haslocal: # 查找局部变量名
  24. arg = f.code_obj.co_varnames[arg_val]
  25. elif byteCode in dis.hasjrel: # 计算跳转位置
  26. arg = f.last_instruction + arg_val
  27. else:
  28. arg = arg_val
  29. argument = [arg]
  30. else:
  31. f.last_instruction += 1
  32. argument = []
  33. return byte_name, argument
  34. # 获取到指令对应的函数进行分发调用
  35. def dispatch(self, byte_name, argument):
  36. why = None
  37. try:
  38. # 通过指令名得到对应的方法函数
  39. bytecode_fn = getattr(self, 'byte_%s' % byte_name, None)
  40. if bytecode_fn is None:
  41. # 这里对一元操作、二元操作和其它操作做了区分
  42. if byte_name.startswith('UNARY_'):
  43. self.unaryOperator(byte_name[6:])
  44. elif byte_name.startswith('BINARY_'):
  45. self.binaryOperator(byte_name[7:])
  46. else:
  47. raise VirtualMachineError(
  48. "unsupported bytecode type: %s" % byte_name
  49. )
  50. else:
  51. why = bytecode_fn(*argument)
  52. except:
  53. # 存储运行指令时的异常信息
  54. self.last_exception = sys.exc_info()[:2] + (None,)
  55. why = 'exception'
  56. return why
  1. class VirtualMachine(object):
  2. # [... 跳过 ...]
  3. def byte_LOAD_CONST(self, const):
  4. self.push(const)
  5. def byte_POP_TOP(self):
  6. self.pop()
  7. ## Names,读取变量对应的值
  8. def byte_LOAD_NAME(self, name):
  9. frame = self.frame
  10. # 局部变量>全局变量>内建变量
  11. if name in frame.f_locals:
  12. val = frame.f_locals[name]
  13. elif name in frame.f_globals:
  14. val = frame.f_globals[name]
  15. elif name in frame.f_builtins:
  16. val = frame.f_builtins[name]
  17. else:
  18. raise NameError("name '%s' is not defined" % name)
  19. self.push(val)
  20. def byte_STORE_NAME(self, name):
  21. self.frame.f_locals[name] = self.pop()
  22. def byte_LOAD_FAST(self, name):
  23. if name in self.frame.f_locals:
  24. val = self.frame.f_locals[name]
  25. else:
  26. raise UnboundLocalError(
  27. "local variable '%s' referenced before assignment" % name
  28. )
  29. self.push(val)
  30. def byte_STORE_FAST(self, name):
  31. self.frame.f_locals[name] = self.pop()
  32. def byte_LOAD_GLOBAL(self, name):
  33. f = self.frame
  34. if name in f.f_globals:
  35. val = f.f_globals[name]
  36. elif name in f.f_builtins:
  37. val = f.f_builtins[name]
  38. else:
  39. raise NameError("global name '%s' is not defined" % name)
  40. self.push(val)
  41. BINARY_OPERATORS = {
  42. 'POWER': pow,
  43. 'MULTIPLY': operator.mul,
  44. 'FLOOR_DIVIDE': operator.floordiv,
  45. 'TRUE_DIVIDE': operator.truediv,
  46. 'MODULO': operator.mod,
  47. 'ADD': operator.add,
  48. 'SUBTRACT': operator.sub,
  49. 'SUBSCR': operator.getitem,
  50. 'LSHIFT': operator.lshift,
  51. 'RSHIFT': operator.rshift,
  52. 'AND': operator.and_,
  53. 'XOR': operator.xor,
  54. 'OR': operator.or_,
  55. }
  56. def binaryOperator(self, op):
  57. x, y = self.popn(2)
  58. self.push(self.BINARY_OPERATORS[op](x, y))
  59. COMPARE_OPERATORS = [
  60. operator.lt,
  61. operator.le,
  62. operator.eq,
  63. operator.ne,
  64. operator.gt,
  65. operator.ge,
  66. lambda x, y: x in y,
  67. lambda x, y: x not in y,
  68. lambda x, y: x is y,
  69. lambda x, y: x is not y,
  70. lambda x, y: issubclass(x, Exception) and issubclass(x, y),
  71. ]
  72. def byte_COMPARE_OP(self, opnum):
  73. x, y = self.popn(2)
  74. self.push(self.COMPARE_OPERATORS[opnum](x, y))
  75. ## Attributes and indexing
  76. def byte_LOAD_ATTR(self, attr):
  77. obj = self.pop()
  78. val = getattr(obj, attr)
  79. self.push(val)
  80. def byte_STORE_ATTR(self, name):
  81. val, obj = self.popn(2)
  82. setattr(obj, name, val)
  83. ## Building
  84. def byte_BUILD_LIST(self, count):
  85. elts = self.popn(count)
  86. self.push(elts)
  87. def byte_BUILD_MAP(self, size):
  88. self.push({})
  89. def byte_STORE_MAP(self):
  90. the_map, val, key = self.popn(3)
  91. the_map[key] = val
  92. self.push(the_map)
  93. def byte_LIST_APPEND(self, count):
  94. val = self.pop()
  95. the_list = self.frame.stack[-count] # peek
  96. the_list.append(val)
  97. ## Jumps
  98. def byte_JUMP_FORWARD(self, jump):
  99. self.jump(jump)
  100. def byte_JUMP_ABSOLUTE(self, jump):
  101. self.jump(jump)
  102. def byte_POP_JUMP_IF_TRUE(self, jump):
  103. val = self.pop()
  104. if val:
  105. self.jump(jump)
  106. def byte_POP_JUMP_IF_FALSE(self, jump):
  107. val = self.pop()
  108. if not val:
  109. self.jump(jump)
  110. def jump(self, jump):
  111. self.frame.last_instruction = jump
  112. ## Blocks
  113. def byte_SETUP_LOOP(self, dest):
  114. self.push_block('loop', dest)
  115. def byte_GET_ITER(self):
  116. self.push(iter(self.pop()))
  117. def byte_FOR_ITER(self, jump):
  118. iterobj = self.top()
  119. try:
  120. v = next(iterobj)
  121. self.push(v)
  122. except StopIteration:
  123. self.pop()
  124. self.jump(jump)
  125. def byte_BREAK_LOOP(self):
  126. return 'break'
  127. def byte_POP_BLOCK(self):
  128. self.pop_block()
  129. ## Functions
  130. def byte_MAKE_FUNCTION(self, argc):
  131. name = self.pop()
  132. # 从数据栈中取出数据
  133. code = self.pop()
  134. defaults = self.popn(argc)
  135. globs = self.frame.f_globals
  136. fn = Function(name, code, globs, defaults, None, self)
  137. self.push(fn)
  138. def byte_CALL_FUNCTION(self, arg):
  139. lenKw, lenPos = divmod(arg, 256) # KWargs not supported here
  140. posargs = self.popn(lenPos)
  141. func = self.pop()
  142. frame = self.frame
  143. retval = func(*posargs)
  144. self.push(retval)
  145. def byte_RETURN_VALUE(self):
  146. self.return_value = self.pop()
  147. return "return"
  148. ## Prints
  149. def byte_PRINT_ITEM(self):
  150. item = self.pop()
  151. sys.stdout.write(str(item))
  152. def byte_PRINT_NEWLINE(self):
  153. print("")

源码

byterun.py15.4kB

运行时结构

我们这里用以一段C程序源程序为例:

  1. int fun(int a,int b);
  2. int m = 10;
  3. int main()
  4. {
  5. int i = 4;
  6. int j = 5;
  7. m = fun(i,j);
  8. return 0;
  9. }
  10. int fun(int a,int b)
  11. {
  12. int c = 0;
  13. c = a + b;
  14. return c;
  15. }

eip & ebp & esp

eip永远指向代码区将要执行的下一条指令,它的管控方式有两种,一种是“顺序执行”,即程序执行完一条指令后自动指向下一条执行;另一种是跳转,也就是执行完一条跳转指令后跳转到指定的位置。

ebp和esp用来管控栈空间,ebp指向栈底,esp指向栈顶,在代码区中,函数调用、返回和执行伴随着不断压栈和清栈,栈中数据存储和释放的原则是后进先出。

截屏2021-04-20 上午8.13.31.png-169.5kB

运行时初始状态

eip指向main函数的第一条指令,此时程序还没有运行,栈空间里还没有数据,ebp和esp指向的位置是程序加载时内核设置的。

截屏2021-04-20 上午8.16.51.png-223kB

保存ebp

程序开始执行main函数第一条指令,eip自动指向下一条指令。第一条指令的执行,致使ebp的地址值被保存在栈中,保存的目的是本程序执行完毕后,ebp还能返回现在的位置,复原现在的栈。随着ebp地址值的压栈,esp自动向栈顶方向移动,它将永远指向栈顶。

截屏2021-04-20 上午8.18.45.png-425.4kB

构建main函数栈

程序继续执行,开始构建main函数自己的栈,ebp原来指向的地址值已经被保存了,它被腾出来了,用来看管main函数的栈底,此时它和esp是重叠的。

截屏2021-04-20 上午8.21.23.png-397.4kB

变量i和j压栈并初始化

程序继续执行,eip指向下一条指令,此次执行的是局部变量i的初始化,初始值4被存储在栈中,esp自动向栈顶方向移动。j同样流程。

截屏2021-04-20 上午8.23.00.png-415.2kB

传参

上面两个局部数据都是供main函数自己用的,接下来调用fun函数时压栈的数据虽然也保存在main函数的栈中,但它们都是供fun函数用的。可以说fun函数的数据,一半在fun函数中,一半在主调函数中。

先执行传参的指令,此时参数入栈的顺序和代码中传参的书写顺序正好相反,参数b先入栈,数值是main函数中局部变量j的数值5。

截屏2021-04-20 上午8.25.15.png-407.2kB

程序继续执行,参数a被压入栈中,数值是局部变量i的数值4

截屏2021-04-20 上午8.25.57.png-431.7kB

设置fun函数返回值位置

程序继续执行,此次压入的是fun函数返回值,将来fun函数返回之后,这里的值会传递给m

截屏2021-04-20 上午8.29.53.png-453.2kB

fun函数执行后的返回地址压栈

跳转到fun函数去执行,这一步分为两部分动作,一部分是把fun函数执行后的返回地址压入栈中,以便fun函数执行完毕后能返回到main函数中继续执行。

截屏2021-04-20 上午8.31.17.png-460.5kB

另一部分就是跳转到被调用的函数的第一条指令去执行

截屏2021-04-20 上午8.32.18.png-454.8kB

fun函数开始执行

第一件事就是保存ebp指向的地址值,此时ebp指向的是main函数的栈底,保存的目的是在返回时恢复main函数栈底的位置,这和前面main函数刚开始执行时第一步就保存ebp的地址值的目的是一样的。

截屏2021-04-20 上午8.33.51.png-472.8kB

构建fun函数栈

程序继续执行,仍然使用腾出来的ebp看管栈底,ebp和esp此时指向相同的位置

截屏2021-04-20 上午8.34.52.png-457.1kB

局部变量c压栈

局部变量c开始初始化,入栈,数值为0,这个c就是fun函数的数据,存在于fun函数的栈中

截屏2021-04-20 上午8.35.47.png-486.1kB

加法运算

截屏2021-04-20 上午8.36.52.png-476.8kB

返回值返回

程序继续执行,fun函数中局部变量c的数据当成返回值返回

截屏2021-04-20 上午8.37.54.png-458kB

恢复main函数栈

现在fun函数已经执行完毕,要恢复main函数调用fun函数的现场,这一现场包括两个部分,一部分是main函数的栈要恢复,包括栈顶和栈底,另一部分是要找到fun函数执行后的返回地址,然后再跳转到那里继续执行。

我们来看ebp的恢复。前面存储了ebp的地址值,现在可以把存储的地址值赋值给ebp,使之指向main函数的栈底。

截屏2021-04-20 上午8.39.24.png-465.1kB

ebp地址值出栈后,esp自动退栈,指向fun函数执行后的返回地址,之后执行ret指令,即返回指令,把地址值传给eip,使之指向fun函数执行后的返回地址。

截屏2021-04-20 上午8.40.10.png-469.5kB

返回值赋值给m

恢复现场以后,把fun函数返回值传递给m

截屏2021-04-20 上午8.41.16.png-457.5kB

清栈

参数和返回值清栈:

截屏2021-04-20 上午8.42.07.png-392.6kB

main函数清栈:

截屏2021-04-20 上午8.42.16.png-377.9kB

参考

Python 实现 Python 解释器
编译系统透视:图解编译原理

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注