Computer >> 컴퓨터 >  >> 프로그래밍 >> Python

Python 바이트코드 디스어셈블러: dis 모듈 완벽 가이드

Python 표준 라이브러리의 dis 모듈은 파이썬 바이트코드를 사람이 읽기 쉬운 형태로 역어셈블(disassemble)하여 분석할 수 있도록 도와주는 다양한 함수를 제공합니다. 이를 활용하면 코드의 동작 원리를 깊이 이해하고 성능 최적화 작업을 수행할 수 있습니다. 다만 바이트코드는 인터프리터의 버전별 구현 세부 사항이므로, Python 버전에 따라 출력 결과가 달라질 수 있다는 점에 유의해야 합니다.

dis() 함수

dis() 함수는 모듈, 클래스, 메서드, 함수 또는 코드 객체 등 어떤 종류의 Python 코드 소스든 역어셈블된 표현을 생성합니다.

>>> def hello():
    print("hello world")

>>> import dis
>>> dis.dis(hello)
  2           0 LOAD_GLOBAL              0 (print)
              3 LOAD_CONST               1 ('hello world')
              6 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
              9 POP_TOP
             10 LOAD_CONST               0 (None)
             13 RETURN_VALUE

Bytecode() 클래스

바이트코드 분석 API는 Bytecode 클래스에 정의되어 있습니다. 생성자는 Bytecode 객체를 반환하며, 이 객체는 바이트코드를 분석할 수 있는 여러 메서드를 제공합니다.

Bytecode()는 생성자로서, 함수, 제너레이터, 메서드, 소스 코드 문자열 또는 코드 객체에 해당하는 바이트코드를 분석합니다. 여러 내부 함수들을 편리하게 감싸는 래퍼(wrapper) 역할을 합니다.

>>> string = dis.Bytecode(hello)
>>> for x in string:
        print(x)
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=0, argval='print', argrepr='print', offset=0, starts_line=2, is_jump_target=False)
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval='hello world', argrepr="'hello world'", offset=3, starts_line=None, is_jump_target=False)
Instruction(opname='CALL_FUNCTION', opcode=131, arg=1, argval=1, argrepr='1 positional, 0 keyword pair', offset=6, starts_line=None, is_jump_target=False)
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=9, starts_line=None, is_jump_target=False)
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=None, argrepr='None', offset=10, starts_line=None, is_jump_target=False)
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=13, starts_line=None, is_jump_target=False)

code_info() 함수

code_info() 함수는 Python 코드 객체에 대한 상세 정보를 문자열 형태로 반환합니다.

>>> dis.code_info(hello)
"Name: hello\nFilename: <pyshell#2>\nArgument count: 0\nKw-only arguments: 0\nNumber of locals: 0\nStack size: 2\nFlags: OPTIMIZED, NEWLOCALS, NOFREE\nConstants:\n 0: None\n 1: 'hello world'\nNames:\n 0: print"

show_code() 함수

show_code() 함수는 Python 모듈, 함수 또는 클래스의 상세한 코드 정보를 화면에 출력합니다.

>>> dis.show_code(hello)
Name:              hello
Filename:          <pyshell#2>
Argument count:    0
Kw-only arguments: 0
Number of locals:  0
Stack size:        2
Flags:             OPTIMIZED, NEWLOCALS, NOFREE
Constants:
    0: None
    1: 'hello world'
Names:
    0: print

disassemble() 함수

disassemble() 함수는 코드 객체를 역어셈블하여 다음과 같은 열(column)로 구분된 결과를 출력합니다.

  • 각 줄의 첫 번째 명령어에 해당하는 줄 번호(line number)
  • 현재 실행 중인 명령어를 나타내는 --> 표시
  • 분기 대상인 명령어를 나타내는 >> 레이블 표시
  • 명령어의 주소(offset)
  • 연산 코드(opcode) 이름
  • 연산 매개변수
  • 괄호 안에 표시되는 매개변수의 해석
>>> codeInString = 'a = 5\nb = 6\nsum = a + b \nprint("sum = ", sum)'
>>> codeObject = compile(codeInString, 'sumstring', 'exec')
>>> dis.disassemble(codeObject)

출력 결과

  1           0 LOAD_CONST               0 (5)
              3 STORE_NAME               0 (a)
  2           6 LOAD_CONST               1 (6)
              9 STORE_NAME               1 (b)
  3          12 LOAD_NAME                0 (a)
             15 LOAD_NAME                1 (b)
             18 BINARY_ADD
             19 STORE_NAME               2 (sum)
  4          22 LOAD_NAME                3 (print)
             25 LOAD_CONST               2 ('sum =')
             28 LOAD_NAME                2 (sum)
             31 CALL_FUNCTION            2 (2 positional, 0 keyword pair)
             34 POP_TOP
             35 LOAD_CONST               3 (None)
             38 RETURN_VALUE

get_instructions() 함수

get_instructions() 함수는 지정된 함수, 메서드, 소스 코드 문자열 또는 코드 객체에 포함된 명령어들에 대한 반복자(iterator)를 반환합니다. 이 반복자는 코드 내 각 연산의 세부 정보를 담고 있는 Instruction 네임드 튜플(named tuple) 시리즈를 생성합니다.

>>> it = dis.get_instructions(code)
>>> for i in it:
        print(i)
Instruction(opname='LOAD_CONST', opcode=100, arg=0, argval=<code object hello at 0x02A9BA70, file "<disassembly>", line 2>, argrepr='<code object hello at 0x02A9BA70, file "<disassembly>", line 2>', offset=0, starts_line=2, is_jump_target=False)
Instruction(opname='LOAD_CONST', opcode=100, arg=1, argval='hello', argrepr="'hello'", offset=3, starts_line=None, is_jump_target=False)
Instruction(opname='MAKE_FUNCTION', opcode=132, arg=0, argval=0, argrepr='', offset=6, starts_line=None, is_jump_target=False)
Instruction(opname='STORE_NAME', opcode=90, arg=0, argval='hello', argrepr='hello', offset=9, starts_line=None, is_jump_target=False)
Instruction(opname='LOAD_CONST', opcode=100, arg=2, argval=None, argrepr='None', offset=12, starts_line=None, is_jump_target=False)
Instruction(opname='RETURN_VALUE', opcode=83, arg=None, argval=None, argrepr='', offset=15, starts_line=None, is_jump_target=False)

각 명령어 정보는 아래 표와 같은 필드를 가진 튜플 형태의 객체로 제공됩니다.

필드명설명
opcode연산에 해당하는 숫자 코드로, opcode 목록 및 Opcode 컬렉션의 바이트코드 값과 일치합니다.
opname연산의 사람이 읽을 수 있는 이름입니다.
arg연산의 숫자 인자(있는 경우). 없으면 None입니다.
argval해석된 실제 인자 값(알려진 경우). 알 수 없으면 arg와 동일한 값입니다.
argrepr연산 인자에 대한 사람이 읽을 수 있는 설명입니다.
offset바이트코드 시퀀스 내에서 해당 연산이 시작되는 인덱스입니다.
starts_line이 opcode가 시작하는 줄 번호(있는 경우). 없으면 None입니다.
is_jump_target다른 코드가 이 위치로 점프(jump)하면 True, 그렇지 않으면 False입니다.