Байт-код

Байт- (байтко́д; англ. bytecode, также иногда p-, p-code от portable code) — стандартное промежуточное представление[en], в которое может быть переведена компьютерная программа автоматическими средствами. По сравнению с исходным ом, удобным для создания и чтения человеком, байт- — это компактное представление программы, уже прошедшей синтаксический и семантический анализ. В нём в явном виде заированы типы, области видимости и другие конструкции. С технической точки зрения байт- представляет собой машинно-независимый низкого уровня, генерируемый транслятором из исходного а.

Многие современные языки программирования, особенно интерпретируемые, используют байт- для облегчения и ускорения работы интерпретатора. Трансляция в байт- является методом, промежуточным по эффективности между прямой интерпретацией и компиляцией в машинный .

По форме байт- похож на машинный , но предназначен для исполнения не реальным процессором, а виртуальной машиной. В качестве виртуальной машины обычно выступает интерпретатор соответствующего языка программирования (иногда дополненный JIT- или AOT-компилятором). Спецификации байт-а и исполняющих его виртуальных машин могут сильно различаться для разных языков: часто байт- состоит из инструкций для стековой[en] виртуальной машины[1], однако могут использоваться и регистровые[en] машины[2][3]. Тем не менее, большинство инструкций байт-а обычно эквивалентны одной или нескольким командам ассемблера.

Байт- называется так, потому что длина каждого а операции традиционно составляет один байт. Каждая инструкция обычно представляет собой однобайтовый операции (от 0 до 255), за которым могут следовать различные параметры, например, номер регистра или адрес в памяти.


Исполнение[ | ]

Программа на байт-е обычно выполняется интерпретатором байт-а. Преимущество байт-а в большей эффективности и портируемости, то есть один и тот же байт- может исполняться на разных платформах и архитектурах, для которых реализован интерпретатор. То же самое преимущество дают непосредственно интерпретируемые языки, однако, поскольку байт- обычно менее абстрактен и более компактен, чем исходный , эффективность интерпретации байт-а обычно выше, чем чистая интерпретация исходного а или интерпретация АСД. Кроме того, интерпретатор байт-а зачастую проще интерпретатора исходного а и его проще перенести (портировать) на другую аппаратную платформу.

В высокопроизводительных реализациях виртуальных машин может применяться комбинация интерпретатора и JIT-компилятора, который во время исполнения программы транслирует часто используемые фрагменты байт-а в машинный , применяя при этом различные оптимизации. Вместо JIT-компиляции может применяться AOT-компилятор, транслирующий байт- в машинный предварительно, до исполнения.

В то же время возможно создание процессоров, для которых данный байт- является непосредственно машинным ом (такие экспериментальные процессоры создавались, например, для языков Java и Форт).

История[ | ]

Среди первых систем, использовавших байт-, были O-code для BCPL (1960-е), Smalltalk (1976)[4], SIL (System Implementation Language) для языка Snobol-4 (1967), p- (p-code, 1970-е, при участии Никлауса Вирта) для переносимых компиляторов языка программирования Pascal[5][6][7].

Варианты p-а широко использовались в различных реализациях языка Pascal, например, в UCSD p-System (UCSD Pascal).[8]

Применение[ | ]

К интерпретируемым языкам, использующим байт-, относятся Perl, PHP (например Zend Engine), Ruby (начиная с версии 1.9), Python, Erlang и многие другие.

Широко распространённые платформы, использующие байт-[9]:

  • Байт- Java (стековая виртуальная машина), исполняемый различными виртуальными машинами Java[10][11]. Платформа была создана компанией Sun для языка Java, но стала использоваться и для других языков; существуют десятки высокопроизводительных реализаций JVM, использующих JIT-компиляторы.
    • Существуют варианты трансляции Java в байт- регистровых машин, например, в виртуальной машине Dalvik (с JIT-компиляцией) или при AOT-компиляции в ART
  • Платформа Microsoft .NET использует стековый байт- Intermediate Language (CIL, MSIL)[8], исполняемый с помощью Common Language Runtime (CLR), создана Microsoft для C# и некоторых других языков.
  • Сценарный язык JavaScript выполняется различными высокопроизводительными «движками», в основном, встроенными в веб-браузеры, часто с возможностью JIT-оптимизации. Многие интерпретаторы построены с применением байт-а, однако программы на Javascript распространяются в виде исходных ов.
  • Сценарный язык ActionScript транслируется в стековый байт-, распространяется в составе swf- и pdf-файлов, и выполняется виртуальными машинами в Adobe Flash и Adobe Acrobat.

Компилятор Clipper создает исполняемый файл, в который включен байт-, транслированный из исходного текста программы, и виртуальная машина, исполняющая этот байт-.

Программы на Java обычно компилируются в class-файлы (англ.), содержащие байт- Java. Эти универсальные файлы передаются на различные целевые машины.

В ранних реализациях Visual Basic (до версии 6) использовался высокоуровневый Microsoft p-code[9]

Высокоуровневые p-ы и байт ы применялись в СУБД, некоторых реализациях Бейсика и Паскаля.

В стандарте открытых загрузчиков Open Firmware фирмы Sun Microsystems байт- представляет операторы языка Форт.

Примеры[ | ]

Python[ | ]

:

>>> print("Hello, World!")
Hello, World!

Байт-:

>>> import dis #импортируем модуль "dis" - Disassembler of Python byte code into mnemonics.
>>> dis.dis('print("Hello, World!")')
  1           0 LOAD_NAME                0 (print)
              2 LOAD_CONST               0 ('Hello, World!')
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

Java[ | ]

:

outer:
for (int i = 2; i < 1000; i++) {
    for (int j = 2; j < i; j++) {
        if (i % j == 0)
            continue outer;
    }
    System.out.println(i);
}

Байт-:

  0:   iconst_2
  1:   istore_1
  2:   iload_1
  3:   sipush  1000
  6:   if_icmpge       44
  9:   iconst_2
  10:  istore_2
  11:  iload_2
  12:  iload_1
  13:  if_icmpge       31
  16:  iload_1
  17:  iload_2
  18:  irem
  19:  ifne    25
  22:  goto    38
  25:  iinc    2, 1
  28:  goto    11
  31:  getstatic       #84; //Field java/lang/System.out:Ljava/io/PrintStream;
  34:  iload_1
  35:  invokevirtual   #85; //Method java/io/PrintStream.println:(I)V
  38:  iinc    1, 1
  41:  goto    2
  44:  return

Критика[ | ]

Традиционно байт- проектируется в стиле стековых виртуальных машин, что упрощает генерацию из AST, позволяет использовать более простую и компактную ировку байт-а, упростить интерпретатор и уменьшить количество машинного а, требуемого для исполнения одной инструкции байт-а. С другой стороны, такие варианты байт-а для заданной программы содержат большее количество инструкций, чем байт-ы регистровых виртуальных машин, из-за чего интерпретатор должен совершить больше непрямых переходов, для которых плохо работает предсказание переходов[3]. Байт- для регистровых виртуальных машин имеет немного больший размер машинных ов, однако количество инструкций по сравнению со стековым байт ом примерно в два раза меньше, а интерпретатор — быстрее на десятки процентов[3]. Также байт- стековых машин сложнее для проведения оптимизаций (выражения становятся неявными, связанные инструкции не сгруппированы, выражения распределены по нескольким базовым блокам)[12] и требует верификации корректности использования стека[13].

Ошибки верификации байт-а стековых машин приводили к появлению множества экстремально опасных уязвимостей, в частности десятков в виртуальной машине AVM2, используемой в Adobe Flash для исполнения скриптов ActionScript[14][15][16] и нескольких в ранних популярных системах исполнения Java (JVM)[17][18]

В конце 2000-х — начале 2010-х авторы компиляторов V8 (для языка JavaScript, часто реализуемого через байт-)[19] и Dart[20] усомнились в том, что промежуточные байты обязательны для быстрых и эффективных виртуальных машин. В этих проектах была реализована непосредственная JIT-компиляция (компиляция во время исполнения) из исходных ов сразу в машинный .[21]

Примечания[ | ]

  1. Terence Parr. Language Implementation Patterns — Pragmatic Bookshelf, December 2009, ISBN 978-1-934356-45-6 «Part 3: Building Interpreters. Pattern 27 Stack-Based Bytecode Interpreter»
  2. Terence Parr. Language Implementation Patterns — Pragmatic Bookshelf, December 2009, ISBN 978-1-934356-45-6 «Part 3: Building Interpreters. Pattern 28 Register-Based Bytecode Interpreter»
  3. 1 2 3 Yunhe Shi, David Gregg, Andrew Beatty, M. Anton Ertl. Virtual Machine Showdown: Stack Versus Registers (англ.) // VEE '05: Proceedings of the 1st ACM/USENIX international conference on Virtual execution environments. — Chicago, Illinois, USA: ACM, 2005. — P. 153 - 163. — ISBN 1-59593-047-7. — doi:10.1145/1064979.1065001.
  4. Bringing Performanceand Scalability toDynamic Languages (недоступная ссылка) // Mario Wolczko, Oracle 2012 слайд 7
  5. Руслан Богатырев. Летопись языков Паскаль, Мир ПК, № 04/2001
  6. Компиляторы: принципы, технологии и инструментарий — Вильямс, ISBN 9785845901897, стр 517 «12.2 Компиляторы Pascal»
  7. THE UCSD P-SYSTEM MUSEUM, 2004
  8. 1 2 Understanding .NET: A Tutorial and Analysis, David Chappell, David Wayne Chappell, 2002, ISBN 9780201741629 page 92
  9. 1 2 C# Versus Java / Dr. Dobb’s Journal February 2001
  10. http://www.javaworld.com/article/2077233/core-java/bytecode-basics.html 1996
  11. Алан Джок. Компиляторы, интерпретаторы и байт-. «Computerworld Россия», № 06, 2001. Дата обращения: 18 мая 2015.
  12. Ando Saabas, Tarmo Uustalu. Type systems for optimizing stack-based code // Electronic Notes in Theoretical Computer Science. — 2007. — Вып. 190.1. — С. 103-119.. — doi:10.1016/j.entcs.2007.02.063. Архивировано 26 мая 2016 года.: «virtual stack or virtual register VMs can be executed more efficiently using an interpreter. Virtual register machines can be an attractive alternative to stack architectures because they allow the number of executed VM instructions to be substantially reduced.»
  13. Gerwin Klein and Martin Wildmoser, Verified Bytecode Subroutines Архивная копия от 10 августа 2017 на Wayback Machine // Journal of Automated Reasoning 30.3-4 (2003): 363—398. «Bytecode verification is a static check for bytecode safety. Its purpose is to ensure that the JVM only executes safe code: no operand stack over- or underflows, no ill-formed instructions, no type errors»
  14. Mark Dowd (X-Force Researcher IBM Internet Security Systems), Leveraging the ActionScript Virtual Machine (недоступная ссылка), IBM 2008 «if there was a way to execute AS3 instructions that had never been verified, it would be quite dangerous. Unverified instructions would be able to manipulate the native runtime stack … The attack works by manipulating a data structure used by the AVM2 verifier such that it doesn’t correctly verify the ActionScript instructions for a given method»
  15. Haifei Li, Understanding and Exploiting Flash ActionScript Vulnerabilities Архивировано 26 ноября 2013 года., 2011 «Bytecode -> Verification process … ActionScript Vulnerabilities are due to various program flow calculating errors in the Verification/Generation Process (the Verification Flow and the Execution Flow are not the same)»
  16. Haifei Li (Microsoft), Inside AVM // REcon 2012, Montreal «Most Flash vulnerabilities are ActionScript-related … Faults on verification cause highly-dangerous JIT type confusion vulnerabilities. • highly-dangerous means perfect exploitation: bypassing ASLR+DEP, with %100 reliability, no heapSpray, no JITSpray. • JIT type confusion bugs are due to faults in the verification of AVM!»
  17. The last stage of delirium research group, Java and Java Virtual Machine security vulnerabilities and their exploitation techniques, BlackHat 2002: «The flaw stemmed from the fact that Bytecode Verifier did not properly perform the bytecode flow analysis»
  18. Verification of Bytecode in a Virtual machine Архивировано 30 апреля 2013 года. // International Journal of Advanced Research in Computer Science and Software Engineering Vol.3 Issue 3 March 2013, ISSN 2277-128X: «Java byte code verification has been studied extensively from a correctness perspective, and several vulnerabilities have been found and eliminated in this process»
  19. Dynamic Machine Code Generation. Google.
  20. Loitsch, Florian Why Not a Bytecode VM?. Google.
  21. Dr. Axel Rauschmayer. JavaScript myth: JavaScript needs a standard bytecode (англ.).