CPython bytecode 구현체 보는 법

eunjo·2022년 7월 23일
0

Python을 사용하다 보면 자신이 작성한 코드가 내부적으로 어떻게 동작하는지 궁금할 때가 있습니다. dis 라이브러리는 소스코드를 바이트코드로 변환할 수 있도록 기능을 제공합니다. 기본 내장된 라이브러리이기에 따로 설치할 필요는 없습니다.

dis.dis() 함수를 사용해서 소스코드를 바이트코드로 변환해보고, Python VM(Virtual Machine) 내부에서 어떻게 동작하는지 알기 위해 해당 바이트코드의 구현체를 확인해보겠습니다.

1. 소스코드를 바이트코드로 변환하기

import dis

def foo():
    print("hello world")
    return True

dis.dis(foo)
  4           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('hello world')
              4 CALL_FUNCTION            1
              6 POP_TOP

  5           8 LOAD_CONST               2 (True)
             10 RETURN_VALUE

foo() 함수를 바이트코드로 변환한 모습입니다. 디스어셈블 결과에는 바이트코드 뿐만 아니라 소스코드의 줄 번호, 바이트코드가 위치한 오프셋, 오퍼랜드 등등 여러 정보가 함께 표시됩니다.

예시로 함수를 디스어셈블했지만, dis.dis()는 함수 뿐만 아니라 module, class, generator, coroutine, source code 등등 범용적으로 사용할 수 있습니다.

2. CPython 소스코드 다운받기

CPython 소스코드는 공식 홈페이지나 깃에서 다운받을 수 있습니다. 공식 홈페이지는 최신 버전 뿐만 아니라 이전 버전도 다운로드가 가능합니다.

3. 바이트코드 구현체 확인하기

  • Include/opcode.h: 바이트코드 목록이 있는 헤더 파일
  • Python/ceval.c: 바이트코드 구현체가 있는 소스 파일
    /* Instruction opcodes for compiled code */
#define POP_TOP                   1
#define ROT_TWO                   2
#define ROT_THREE                 3
#define DUP_TOP                   4
#define DUP_TOP_TWO               5
#define ROT_FOUR                  6
#define NOP                       9
...

Include/opcode.h 파일에는 현재 최신 버전인 3.10.4를 기준으로 총 128개의 바이트코드가 정의되어 있습니다.

...
switch (opcode) {

    /* BEWARE!
       It is essential that any operation that fails must goto error
       and that all operation that succeed call DISPATCH() ! */

    case TARGET(NOP): {
        DISPATCH();
    }

    case TARGET(LOAD_FAST): {
        PyObject *value = GETLOCAL(oparg);
        if (value == NULL) {
            format_exc_check_arg(tstate, PyExc_UnboundLocalError,
                                 UNBOUNDLOCAL_ERROR_MSG,
                                 PyTuple_GetItem(co->co_varnames, oparg));
            goto error;
        }
        Py_INCREF(value);
        PUSH(value);
        DISPATCH();
    }
...

Python/ceval.c 파일에는 바이트코드의 구현체가 작성되어 있습니다. Include/opcode.h에서 봤던 바이트코드의 상수 값에 따라 분기하여 알맞은 구현체를 실행하게 됩니다.

profile
바이너리 분석이 좋아요

0개의 댓글