Большинство основных языков, включая Java, .NET, такие как C #, Python, компилируются в промежуточный язык. У этих языков есть интерпретаторы (например, интерпретатор Python) или виртуальные машины (например, виртуальная машина Java), которые выполняют байт-коды. Эти байт-коды либо генерируются на лету (как в случае с Python), либо сохраняются в формате файла (формат файла класса Java в Java и CIL или Common Intermediate Language для .NET).
Изучение того, как читать байт-коды, может быть на удивление полезным, хотя лишь немногие разработчики когда-либо пробуют это делать (даже разработчики экспертного уровня). Основное преимущество - понимание того, как язык работает «изнутри», то есть как язык и его функции работают. В более практическом плане понимание байт-кодов может помочь вам отладить сложные сценарии, дать представление о проблемах производительности и помочь написать лучший код, который использует возможности языка.
В этом кратком сообщении блога давайте начнем с дизассемблирования байт-кода Python.
Что такое «разборка»? Это противоположность сборки - процесс преобразования кода сборки в машинный код (для реальной машины или виртуальной машины). Дизассемблирование будет означать обратное - процесс преобразования машинного кода в ассемблерный код.
Начнем с примера. Запустите свой интерпретатор Python (я использую Python 3.7 в этом сообщении в блоге) и попробуйте прочитать фактические байт-коды:
>>> def some_fun(): ... a = 10 ... b = 20 ... c = 30 ... return a * b + c ... >>> some_fun.__code__ <code object some_fun at 0x1075e9420, file "<stdin>", line 1> >>> some_fun.__code__.co_code b'd\x01}\x00d\x02}\x01d\x03}\x02|\x00|\x01\x14\x00|\x02\x17\x00S\x00'
Мы определили простую функцию с именем some_fun, которая оценивает выражение a * b + c и возвращает результат.
__Code__ показывает «объект кода, представляющий тело скомпилированной функции». Затем мы вызываем co_code
, который является «строкой, представляющей последовательность инструкций байт-кода». Это определенно не читается человеком, и поэтому мы собираемся использовать модуль dis, чтобы увидеть версию байт-кода, читаемую человеком.
>>> import dis >>> dis.dis(some_fun) 2 0 LOAD_CONST 1 (10) 2 STORE_FAST 1 (a) 3 4 LOAD_CONST 2 (20) 6 STORE_FAST 2 (b) 4 8 LOAD_CONST 3 (30) 10 STORE_FAST 3 (c) 5 12 LOAD_FAST 1 (a) 14 LOAD_FAST 2 (b) 16 BINARY_MULTIPLY 18 LOAD_FAST 3 (c) 20 BINARY_ADD 22 RETURN_VALUE >>>
Сначала импортируйте модуль «dis». Выражение dis.dis (some_fun) разбирает some_fun и показывает байт-код. Давайте внимательнее посмотрим, что он показывает.
В первом столбце показаны номера строк исходного кода и соответствующие записи в дизассемблированном байт-коде. Второй столбец показывает индекс байт-кода. Третий столбец - это фактические байт-коды, такие как LOAD_CONST, BINARY_MULTIPLY и т. Д.
Python - это стековый язык. Чтобы понять этот промежуточный формат, подумайте о пост-исправлении эквивалента данного выражения «a * b + c» после исправления - это будет «a b * c +», и это то, что достигают следующие байт-коды:
12 LOAD_FAST 1 (a) 14 LOAD_FAST 2 (b) 16 BINARY_MULTIPLY 18 LOAD_FAST 3 (c) 20 BINARY_ADD 22 RETURN_VALUE
Байт-коды могут принимать аргументы. Например, LOAD_FAST принимает значение «a» (с индексом «1») в качестве аргумента. Сам байт-код - это один байт (поскольку это «байтовый» код, как указывает имя), а аргумент - 1 байт в этом случае, то есть всего 2 байта для «LOAD_FAST 1 (a)». Текущий индекс байт-кода равен 12, и добавление 2 дает следующий индекс байт-кода, начиная с 14, который равен «LOAD_FAST 2 (b)».
Все программы на Python конвертируются в байт-код. Глядя на байт-коды, можно «прояснить» то, как работают некоторые функции. Вот пример:
>>> def square_fun(a): ... return a * a ... >>> dis.dis(square_fun) 2 0 LOAD_FAST 0 (a) 2 LOAD_FAST 0 (a) 4 BINARY_MULTIPLY 6 RETURN_VALUE >>> square_fun <function square_fun at 0x1076d8598> >>> square_lambda = lambda a: a * a >>> dis.dis(square_lambda) 1 0 LOAD_FAST 0 (a) 2 LOAD_FAST 0 (a) 4 BINARY_MULTIPLY 6 RETURN_VALUE >>> square_lambda <function <lambda> at 0x1076141e0>
Этот пример короткий, приятный и передает мощный посыл: несмотря на различия в синтаксисе, функция и лямбда-код компилируются в один и тот же код! Как я и обещал ранее, теперь вы знаете, что я имел в виду под этим, может дать вам «понимание того, как язык работает« изнутри », то есть как язык и его функции работают!»
Так чего же вы ждете: просто запустите свой интерпретатор, поиграйте и исследуйте байт-коды Python - поверьте, это приключение, полное веселья!
Ссылки / Дополнительная литература:
- Модель данных Python (например, __code__): https://docs.python.org/3/reference/datamodel.html
- Список байт-кодов Python: https://docs.python.org/2/library/dis.html#bytecodes
- Дизассемблер для байт-кодов Python: https://docs.python.org/2/library/dis.html
- Понимание байт-кодов Python: https://www.synopsys.com/blogs/software-security/understanding-python-bytecode/
- Видео по байт-кодам: https://www.youtube.com/watch?v=cSSpnq362Bk и https://www.youtube.com/watch?v=GNPKBICTF2w
(Автор: Ганеш Самартям, соучредитель, KonfHub Technologies LLP)