Понимаем байт-код EVM: Часть 2
В первой части мы изучили часть создания контракта в байт-коде виртуальной машины. В этом разделе мы проанализируем рантайм байт-кода виртуальной машины. Мы по-прежнему будем использовать пример кода из предыдущей статьи.
Весь байткод выглядит так:
Эта часть байт-кода кажется намного длиннее, чем часть создания. Но давайте разделим это на части и проанализируем их одну за другой.
Эти инструкции фактически сохраняют адрес 0x80 со смещением 0x40 в памяти в качестве указателя свободной памяти для использования в будущем.
Существует новый код операции CALLDATASIZE, который мы раньше не встречали в 0x07. Он получит размер полезной нагрузки данных из этой транзакции. LT — это код операции для сравнения двух элементов в стеке, он вернет значение TRUE, если сравнение выполнено.
Итак, собрав все части вместе, мы можем получить эквивалентный ассемблерный код Solidity следующим образом:
mstore(0x40,0x80);
Мы можем видеть, что в основном эти инструкции инициализируют указатели памяти и проверяют, составляет ли размер полезной нагрузки данных не менее 4 байт. Причина этого заключается в том, что при обычном внешнем вызове смарт-контракта первые 4 байта в полезной нагрузке данных являются хэш-значением для подписи функции.
Это 4-байтовое значение будет использоваться контрактом для выбора функции для доставки остальных данных, которые являются параметрами для этой функции. Например, если вы вызовете функцию withdraw(0xABCD) в смарт-контракте, полезная нагрузка данных для этого вызова будет выглядеть следующим образом:
0x3823D66C000000000000000000000000000000000000000000000000000000000000ABCD
В этом примере первое 4-байтовое значение равно 0x3823D66C, что является хэш-значением SHA3 для “withdrawn(bytes32)”. Следующее 32-байтовое целое число является параметром вызова функции 0xABCD. Это простой пример параметров целочисленной функции. При обработке параметров переменного размера все усложнится. Мы поговорим о них позже.
А пока давайте вернемся к инструкциям, которые мы обсуждали. Размер полезной нагрузки данных должен составлять не менее 4 байт. Если нет, то все вернется на круги своя. Но будет ли это справедливо для всех смарт-контрактов? Что, если мы просто отправим эфир в этот смарт-контракт без вызова какой-либо функции?
Возможно, вы уже помните некоторые функциональные возможности в программах Solidity. Да, именно там реализована функция fallback. Чтобы подтвердить это, вы можете получить образец с реализованной функцией fallback, а затем проверить ветвь инструкции после кода проверки msg.data.length.
Продолжая разбор кода, мы видим следующие команды:
Если вы посмотрите больше байт-кода виртуальной машины из разных смарт-контрактов, вы всегда найдете один и тот же фрагмент кода или функционально эквивалентный фрагмент кода. Этот фрагмент кода не из того кода, который вы поместили в свой исходный код, и он был введен компилятором. Давайте пройдемся по каждой строчке.
Сначала код помещает 0xFFFFFFFF в стек. Это значение будет использоваться в качестве одного из операндов кода операции AND в 0x33. Затем код добавит еще одно огромное постоянное значение, которое будет использоваться в качестве разделителя DIV в 0x32. Для инструкций в 0x2F, 0x31 код получит первое 32-байтовое значение из полезной нагрузки данных, используя CALLDATALOAD(0x0).
В сочетании с набором команд более поздних DIV и AND мы можем видеть, что эти инструкции фактически получают первое 4-байтовое значение полезной нагрузки данных, которое является хэш-значением подписи функции. Вычисленный результат помещается в стек. Затем значение сравнивается с 0x1003e2d2, используя код операции EQ в 0x3A. Если это было правдой, выполнение будет переведено на адрес 0x4D с помощью JUMPI. В противном случае код продолжит работу, и результат будет сравнен с другим хэш-значением 0xb69ef8a8.
Теперь логика этого фрагмента кода довольно ясна. Он получает первое 4-байтовое значение из полезной нагрузки данных и решает, какая функция будет выбрана для запуска. Для кодовых адресов 0x4D и 0x74 они являются входом для каждой public функции, к которой вызывающий может получить доступ к смарт-контрактам. Если ни одно из хэш-значений в коде не удовлетворяется, то это приведет к срабатыванию fallback функции смарт-контрактов. Если он не был определен, он просто вернется.
До сих пор мы знали, как public функции могут получить доступ к внешним функциям. Теперь давайте подробнее рассмотрим конкретные функции. Адрес 0x4D является входом функции add():
На входе в функцию add() находится код операции JUMPDEST. Это специальный код операции, который помечает только адрес, на который можно перейти. Похоже, что это не играет важной роли для реализации EVM. Однако вы увидите, что это действительно помогает определить график потока управления (CFG) для байт-кода. Мы обсудим это в следующем разделе.
Инструкции, установленные в 0x4F-0x57, были просмотрены в предыдущем разделе. Они были введены компилятором для функции, не принимающей средства. После этого кода проверки код операции PUSH1 по адресу 0x5A легко проигнорировать. Однако это PUSH1 очень важно для того, чтобы код мог вернуться позже.
Давайте просто запомним, что адрес 0x62 пока помещен в стек. Затем вызывается CALLDATALOAD(0x04) для загрузки параметра из полезной нагрузки данных, который расположен со смещением 0x04. После получения параметра код перейдет к 0x86 для выполнения:
В приведенном выше фрагменте кода значение 0x0 в хранилище загружается с помощью SLOAD(0x0). Затем это значение будет добавлено к параметру, загруженному из полезной нагрузки данных, и сохранено обратно в то же место 0x0 в хранилище. Наконец, мы видим код, который мы поместили внутри функции add():
balance = balance + value;
Мы использовали только одну целочисленную переменную balance внутри смарт-контракта. Компилятор присваивает этой переменной смещение 0x0. Таким образом, любая операция чтения или записи с этой переменной баланса будет помещена в смещение 0x0 в хранилище.
В конце фрагмента кода используется JUMP для возврата к 0x62. Если вы все еще помните, где это значение 0x62 было помещено в стек. Эта операция может напомнить вам что-то об архитектуре X86. Да, это на самом деле call и ret для вызовов функций. Поскольку EVM не поддерживает вызовы функций на уровне байт-кода, он может использовать только коды операций PUSH и JUMP для вызовов функций. Таким образом, возникнет много проблем с созданием CFG из байт-кода.
Давайте вернемся к адресу 0x62, чтобы посмотреть, что произойдет дальше:
Этот фрагмент кода содержит несколько настроек элементов в стеке с помощью кодов операций DUP и SWAP. Для эквивалентного кода на ассемблере приведенный выше код выглядит следующим образом:
mstore(mload(0x40), value);
return(mload(0x40), 0x20);
Value внутри кода было вычисленным результатом предыдущей операции add(), которое является новым значением balance. Наконец, мы просмотрели весь байт-код внутри функции add().
Теперь давайте вернемся к фрагменту кода отправки функции для второй функции HASH 0xb69ef8a8. Вход для этой функции находится в 0x74:
Мы видим, что первая часть кода действительно похожа на предыдущую функцию, за исключением того, что параметр не был загружен. Затем функция вызовет фрагмент кода с 0x95. Инструкции в 0x95-0x98 просто загружают значение в 0x0 в хранилище и возвращают. Мы отметили, что фрагмент кода в 0x62 был повторно использован для обеих функций. Это связано с тем, что обе функции вернут переменную balance обратно.
Вы можете задаться вопросом, почему внутри кода отправки функции присутствует функция HASH 0xb69ef8a8? Разве внутри смарт-контракта нет только одной функции add()? Если вы используете базу данных в 4 байта для проверки этого хэша, вы получите balance(). По-видимому, переменная хранилища распознается компилятором как public функция без параметров.
Подводя итог этому разделу, мы обсудили всю структуру байт-кода, относящегося к среде выполнения. Как внешние вызывающие пользователи получают доступ к функциям, как передаются параметры. Но для этого демонстрационного примера мы ввели только некоторые целочисленные переменные хранилища.
Как это будет выглядеть для сопоставлений или массивов переменной длины? Как будут представлены параметры в полезной нагрузке данных для строк? Мы поговорим обо всем этом в следующем разделе.
Как объяснить, что такое байткод? [закрыт]
Закрыт. На этот вопрос невозможно дать объективный ответ. Ответы на него в данный момент не принимаются.
Хотите улучшить этот вопрос? Переформулируйте вопрос так, чтобы на него можно было дать ответ, основанный на фактах и цитатах.
Закрыт 3 года назад .
Пытался объяснить начинающим инженерам, что такое байткод (в рамках пояснения принципов выполнения Java программ). Так вот, я пытаюсь стандартно объяснить, мол это что то между компиляцией и интерпретацией, типа каждая команда джавовского байт кода, это как несколько ассемблеровских комманд, но они смотрят на меня и говорят — не понятноооо! Они не программисты, не знают что такое ни компиляция, ни интерпретация, ни ассемблер, это я попытался на пальцах им объяснить, и вроде до них что-то дошло, а как можно на пальцах используя жизненные примеры объяснит принцип работы байткода?
Отслеживать
23.4k 3 3 золотых знака 49 49 серебряных знаков 70 70 бронзовых знаков
задан 18 мая 2013 в 20:37
vanyamelikov vanyamelikov
3,113 3 3 золотых знака 30 30 серебряных знаков 59 59 бронзовых знаков
Если они не программисты, то им это не нужно. Не тратьте время.
18 мая 2013 в 20:44
18 мая 2013 в 20:45
Я сам не программист(по образованию), сейчас стоит вопрос проведения расчета охлаждения камеры сгорания ракетного двигателя. Так вот я хочу дать троим студентам эту задачу, на трех разных языках, и посмотреть у кого лучше получиться. Языки: java-среда эклипс, Си++ — Visual Studio и Visual Basic да еще и 6. Сам я его накидал в MathCAD и MathLab. Думаю должен получиться хороший эксперимент!
18 мая 2013 в 20:51
Извините за прямоту — но складывается ощушение, будто отвечающие, сами до конца не понимают, кроме @KoVadim что такое байт код. Не надо мразу минусовать! Вдумайтесь в вопросе было, что необходимо обяснить байт код, при условии понимания небольшого понятий компиляции и интерпретации! А у многих определения можно отнести как к первому, так ко второму и третьему!
19 мая 2013 в 6:05
Хотел было уже написать, но заглянул в Википедию и понял, что ничего добавить не смогу. @vanyamelikov, а Ваши коллеги эту статью читали?
19 мая 2013 в 19:50
7 ответов 7
Сортировка: Сброс на вариант по умолчанию
А нужно объяснять очень просто. Используя их положение. Для начала показываем этим инженерам программу на Java. Например, классический HelloWorld. И спрашиваем — понятно ли? Скорее всего они скажут нет. Объясняем, что и специальной программе, которая исполняет жава код, тоже не понятно. Для этого нужно «разобрать по косточкам».
Теперь делаем «псевдотрасляцию» — как для машинистки (секретарши). Для HelloWorld’а она будет такая.
- настроить окружение (в коде этого нет, но это автоматом) — приготовить бумагу, проверить катриджи.
- взять из памяти строку «привет мир».
- нижимая кнопки, побуквенно ввести сроку (здесь появился цикл:) ).
- почистить все за собой и отнести бумагу заказчику.
Формально — это и есть простой байткод. Только это человеческий байткод. А если в нем стандартизировать все операции и занумеровать, то все может быть сведено к набору чисел. Теперь к реальному байткоду перейти просто.
Легко будет объяснить и переносимость. Если человек (секретарша) выучит все коды операций, то она сможет выполнить любую работу, главное, что бы была последовательность кодов. А инженеры могут попробовать спаять-сконструировать устройство, которое будет это исполнять.
Машинный код и байт код: на каком языке говорит ваша программа?
У тех, кто только начинает знакомиться с Java, довольно часто возникает путаница в понятиях машинный и байт код. Что они собой представляют? В чём различия? В короткой заметке мы постараемся максимально просто и понятно расписать их особенности, чтоб раз и навсегда закрыть этот вопрос.

Машинный код
Процессор — это, по сути, очень сложный и продвинутый калькулятор. У него есть множество ячеек памяти (называемых регистрами) с которыми и между которыми проводятся различные математические и байтовые операции. Машинный код как раз и представляет собой описание последовательности выполнения операций и набора участвующих данных. По сути, это единственный язык, который понимает процессор вашего компьютера.
Врожденная несовместимость
При этом далеко не все процессоры «говорят» на одном языке. Различия есть не только между архитектурами CISC и RISC, но и внутри этих «лагерей».
CISC (англ. Complex Instruction Set Computing) — концепция проектирования процессоров, которая характеризуется следующим набором свойств:
- много команд, разных по длине;
- много режимов адресации;
- сложная кодировка инструкции.
В новых поколениях процессоров внедряют дополнительные наборы инструкций, которые моделям старшего поколения попросту неизвестны. Из-за этого программы, скомпилированные для одной архитектуры (или одного поколения процессоров) не могут работать на другом аппаратном обеспечении. Все это вынуждает заниматься перекомпиляцией программ для обеспечения их работы на других компьютерах. Впрочем, заново компилировать приходится не только из-за процессоров, но и еще из-за различий во взаимодействии программ и операционной системы. Именно из-за них невозможно запустить «виндовую» программу под Linux, а «линуксовую» под Windows.
Байт-код
Байт-код во многом похож на машинный код, только он использует набор инструкций не реального процессора, а виртуального. При этом он может включать в себя участки, ориентированные на использование JIT-компилятора, оптимизирующего выполнение команд под реальный процессор, на котором запущена программа.
| JIT-компиляция (англ. Just-in-time compilation, компиляция «на лету») или динамическая компиляция (англ. dynamic translation) — это технология увеличения производительности программных систем, использующих байт-код, путём компиляции байт-кода в машинный код или в другой формат непосредственно во время работы программы. «Официально» в Java до 9-й версии был только JIT-компилятор. В Java 9 появился ещё один компилятор, причём компилирует он с опережением (AoT). Эта возможность позволяет компилировать классы Java в нативный код перед запуском на виртуальной машине. Данная функция предназначена для улучшения времени запуска и малых, и больших приложений, с ограниченным влиянием на максимальную производительность. |
Для CISC процессоров некоторые инструкции могут объединяться в более сложные конструкции, поддерживаемые процессором, а для RISC – наоборот разбиваться на более простые последовательности команд.
Еще и виртуальная ОС
Впрочем, байт код содержит не только процессорные инструкции. В нем также содержится логика взаимодействия с виртуальной операционной системой, которая делает поведение приложения независящим от используемой на компьютере операционной системы. Это отлично видно в JVM, где работа с системными вызовами и GUI зачастую не зависят от ОС, на которой запущена программа. По большому счету JVM эмулирует запуск процесса программы, в отличие от решений вроде Virtual Box, которые создают только виртуальную систему/железо.
JVM одна такая?
Определенно нет. Тот же DotNet CLI это тоже виртуальная машина, которую чаще всего используют на компьютерах, работающих под Windows с x86 совместимыми процессорами. Впрочем существует ее реализация и под другие системы: приложения под него должны работать в Windows RT запущенной на ARM (RISC) совместимых процессорах, или можно запустить их на Linux/OSX в среде Mono, являющей сторонней (и потому не полностью совместимой) реализацией DotNet для этих платформ. Так что эта платформа, как и JVM, работает на разных процессорах и разных ОС. Существует еще множество похожих решений (как старых, так и новых): LLVM, Flash SWF, и другие. У некоторых языков программирования есть собственные виртуальные машины. К примеру, CPython компилирует исходники из PY в файлы PYC – скомпилированный (compiled) байт код который подготовлен к запуску в PVM. Или есть намного более древний пример — Lisp можно компилировать в файлы FASL (Fast Load). Фактически они содержат AST дерево, построенное генератором из исходного кода. Эти файлы могут быть прочитаны и запущены интерпретатором Lisp на разных платформах, или использованы для создания машинного кода для используемой на данный момент аппаратной архитектуры.
Hello World из байт-кода для JVM
Не думаю, что статья будет достаточно информативной для тех, кто поверхностно не знает как выглядит байт-код и как с ним работает JVM (например, хотя бы простейшие инструкции (знание об их существовании)).
На самом деле, это не так сложно. Достаточно использовать инструмент javap из JDK и рассмотреть дизассемблированный код.
А мы приступим к разбору самой структуры байт-кода для JVM
Очень полезной книгой для этого стала официальная спецификация JVM — The Java Virtual Machine Specification на сайте oracle
Для начала создадим простенькую программу:
public class Main < public static void main(String . args) < System.out.println("Hello World"); >>
Скомпилируем её командой javac Main.java и собственно сделаем дизассемблинг
javap -c -v Main
Classfile /C:/Users/Arthur/playground/java/jvm/Main.class Last modified 26.10.2019; size 413 bytes MD5 checksum 6449121a3bb611fee394e4f322401ee1 Compiled from "Main.java" public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#15 // java/lang/Object."":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Main.java #15 = NameAndType #7:#8 // "":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello World #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Main #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V < public Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String. ); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC, ACC_VARARGS Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello World 5: invokevirtual #4// Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 4: 0 line 5: 8 > SourceFile: "Main.java"
Это просто представление байт-кода, которое человеку видеть легче, чем оригинальный байт-код, но сам он выглядит иначе:
cafe babe 0000 0034 001d 0a00 0600 0f09 0010 0011 0800 120a 0013 0014 0700 1507 0016 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0a53 6f75 7263 6546 696c 6501 0009 4d61 696e 2e6a 6176 610c 0007 0008 0700 170c 0018 0019 0100 0b48 656c 6c6f 2057 6f72 6c64 0700 1a0c 001b 001c 0100 044d 6169 6e01 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 1d00 0100 0100 0000 052a b700 01b1 0000 0001 000a 0000 0006 0001 0000 0001 0089 000b 000c 0001 0009 0000 0025 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0100 0a00 0000 0a00 0200 0000 0400 0800 0500 0100 0d00 0000 0200 0e
(Можете открыть ваш .class файл через Sublime Text указав при этом File-> Save with Encoding -> Hexademical)
С этим кодом мы и будем работать.
Но для начала нам нужно его отформатировать, чтобы не путаться что где находится, а байт-код, на самом деле, имеет вполне жесткую структуру:
ClassFile
Её вы можете найти в спецификации JVM Chapter 4.1 The ClassFile Structure
Тут все просто — слева указана размерность в байтах, а справа описание.
Разбирать байт-код мы будем в hexadecimal, где каждая цифра занимает 4 бита, а следовательно, на два байта — 4 цифры и на четыре байта — 8 цифр.
magic
magic — это значение, которое идентифицирует формат нашего класса. Он равен 0xCAFEBABE , который имеет свою историю создания.
minor_version, major_version
Это версии вашего class файла. Если мы назовем major_version M и minor_version m, то получаем версию нашего class файла как M.m
Сейчас я сразу буду приводить примеры из нашей программы «Hello World», чтобы посмотреть как они используются:
cafe babe -- magic 0000 -- minor_version 0034 -- major_version
Его же мы можем видеть в дизассемблированном коде, но уже в десятичной системе счисления:
. public class Main minor version: 0 major version: 52 flags: ACC_PUBLIC, .
constant_pool_count
Здесь указывается количество переменных в пуле констант. При этом, если вы решили писать код на чистом байт-коде, то вам обязательно нужно следить за его значением, так как если вы укажете не то значение, то вся программа полетит к чертям (проверено!).
Также следует не забывать, что вы должны писать туда количество_переменных_в_пуле + 1
cafe babe -- magic 0000 0034 -- version 001d -- constant_pool_count
constant_pool[]
Каждый тип переменной в пуле констант имеет свою структуру:
cp_info
Здесь все нужно делать последовательно. Сначала считываем tag , чтобы узнать тип переменной и по типу этой переменной смотрим какую структуру имеет последующее его значение info[]
Таблица с тэгами можно найти в спецификации Table 4.3 Constant pool tags
Собственно, вот табличка:
| Constant Type | Value |
|---|---|
| CONSTANT_Class | 7 |
| CONSTANT_Fieldref | 9 |
| CONSTANT_Methodref | 10 |
| CONSTANT_InterfaceMethodref | 11 |
| CONSTANT_String | 8 |
| CONSTANT_Integer | 3 |
| CONSTANT_Float | 4 |
| CONSTANT_Long | 5 |
| CONSTANT_Double | 6 |
| CONSTANT_NameAndType | 12 |
| CONSTANT_Utf8 | 1 |
| CONSTANT_MethodHandle | 15 |
| CONSTANT_MethodType | 16 |
| CONSTANT_InvokeDynamic | 18 |
Как ранее уже говорилось, каждый тип константы имеет свою структуру.
Вот, например, структура CONSTANT_Class :
CONSTANT_Class_info
Структура поля и метода:
CONSTANT_Fieldref_info < u1 tag; u2 class_index; u2 name_and_type_index; >CONSTANT_Methodref_info
Тут важно заметить, что разные структуры, могут иметь разную длину.
Рассмотрим часть нашего кода:
cafe babe 0000 0034 001d -- constant_pool_count 0a00 0600 0f09 0010 0011 0800 12 .
Итак, смотрим на структуру константы и узнаем, что первый байт отведен под тип константы. Здесь мы видим 0a (10) — а, следовательно, это CONSTANT_Methodref
Смотрим его структуру:
CONSTANT_Methodref_info
После одного байта для тэга, нам нужно еще 4 байта для class_index и name_and_type_index
cafe babe 0000 0034 001d -- constant_pool_count 0a 0006 000f -- CONSTANT_Methodref 0900 1000 1108 0012 .
Отлично, мы нашли одну из значений пула констант. Идем дальше. Смотрим, 09 — значит тип CONSTANT_Fieldref
cafe babe 0000 0034 001d -- constant_pool_count 0a 0006 000f -- CONSTANT_Methodref 09 0010 0011 -- CONSTANT_Fieldref 08 0012 .
Вам может показаться, что большинство типов имеет одинаковую форму, но это не так.
Например, структура следующего типа выглядит так, CONSTANT_String :
CONSTANT_String_info
Все эти структуры можно посмотреть в Chapter 4.4 The Constant Pool
Теперь разберем, что значат типы внутри самого info
Методы, которые попадают под паттерн *_index обычно содержат адрес из таблицы пула констант. Например, class_index на значение с типом CONSTANT_Class_info , а string_index на CONSTANT_Utf8_info
Это же мы можем видеть в дизассемблированном коде:
#1 = Methodref #6.#15 // java/lang/Object."":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18
0a 0006 000f -- CONSTANT_Methodref 09 0010 0011 -- CONSTANT_Fieldref 08 0012 -- CONSTANT_String
Также можно выделить представление чисел и строк.
Про представление чисел можно прочитать начиная с главы 4.4.4, а мы пока разберем лишь строки, так как числа пока не входят в программу Hello World
Собственно, вот так представляется строка:
CONSTANT_Utf8_info
Например, наш Hello World:
01 -- tag 000b -- length 48 65 6c 6c 6f 20 57 6f 72 6c 64 -- bytes[length] // H e l l o W o r l d
Разбирая весь пул констант байт-кода, получим:
Весь пул констант
-- [Constant Pool] -- methodref 0a 0006 000f -- fieldref 09 0010 0011 -- string 08 0012 -- methodref 0a 0013 0014 -- Class 07 0015 -- Class 07 0016 -- Utf8 01 0006 3c 69 6e 69 74 3e -- Utf8 01 0003 28 29 56 -- Utf8 01 0004 43 6f 64 65 -- Utf8 01 000f 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 -- Utf8 01 0004 6d 61 69 6e -- Utf8 01 0016 28 5b 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 -- Utf8 01 000a 53 6f 75 72 63 65 46 69 6c 65 -- Utf8 01 0009 4d 61 69 6e 2e 6a 61 76 61 -- NameAndType 0c 0007 0008 -- Class 07 0017 -- NameAndType 0c 0018 0019 -- Utf8 01 000b 48 65 6c 6c 6f 20 57 6f 72 6c 64 -- Class 07 001a -- NameAndType 0c 001b 001c -- Utf8 01 0004 4d 61 69 6e -- Utf8 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 -- Utf8 01 0010 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d -- Utf8 01 0003 6f 75 74 -- Utf8 01 0015 4c 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d 3b -- Utf8 01 0013 6a 61 76 61 2f 69 6f 2f 50 72 69 6e 74 53 74 72 65 61 6d -- Utf8 01 0007 70 72 69 6e 74 6c 6e -- Utf8 01 0015 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 29 56 -- [Constant Pool END]
Также, мы можем сравнить его с дизассемблированным кодом:
Constant pool: #1 = Methodref #6.#15 // java/lang/Object."":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 main #12 = Utf8 ([Ljava/lang/String;)V #13 = Utf8 SourceFile #14 = Utf8 Main.java #15 = NameAndType #7:#8 // "":()V #16 = Class #23 // java/lang/System #17 = NameAndType #24:#25 // out:Ljava/io/PrintStream; #18 = Utf8 Hello World #19 = Class #26 // java/io/PrintStream #20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V #21 = Utf8 Main #22 = Utf8 java/lang/Object #23 = Utf8 java/lang/System #24 = Utf8 out #25 = Utf8 Ljava/io/PrintStream; #26 = Utf8 java/io/PrintStream #27 = Utf8 println #28 = Utf8 (Ljava/lang/String;)V
Тем самым проверив, что все совпадает, ведь по сути javap просто обрабатывает этот байт-код и показывает нам его в форматированном виде.
Пул констант нужен для инструкций. Например:
public Main(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // ссылается на адрес 1 в пуле констант 4: return
Подробнее обо всех типах в пуле констант можно узнать в Chapter 4.4 The Constant Pool
Идем дальше по структуре ClassFile
access_flags
Это битовая маска для свойств модификаторов
| Flag Name | Value | Interpretation |
|---|---|---|
| ACC_PUBLIC | 0x0001 | Declared public ; may be accessed from outside its package. |
| ACC_FINAL | 0x0010 | Declared final ; no subclasses allowed. |
| ACC_SUPER | 0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. |
| ACC_INTERFACE | 0x0200 | Is an interface, not a class. |
| ACC_ABSTRACT | 0x0400 | Declared abstract ; must not be instantiated. |
| ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
| ACC_ANNOTATION | 0x2000 | Declared as an annotation type. |
| ACC_ENUM | 0x4000 | Declared as an enum type. |
this_class
Должна содержать адрес на this класса. В нашем случае, она находится по адресу 5:
Constant pool: #1 = Methodref #6.#15 // java/lang/Object."":()V #2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #18 // Hello World #4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #21 // Main #6 = Class #22 // java/lang/Object .
Следует заметить, что структуру этой переменной должна соответствовать CONSTANT_Class_info
super_class
Адрес предка класса. В нашем случае, значение по адресу #6 . Ну, и также обязательным является структура значения CONSTANT_Class_info
Имена этих классов заданы в структуре константы CONSTANT_Utf8_info . Если мы посмотрим ячейки #21 и #22 , то увидим:
. #21 = Utf8 Main #22 = Utf8 java/lang/Object .
То есть в этих ячейках указан name_index из структуры:
CONSTANT_Class_info
interfaces_count, fields_count
Их в нашей программе нет, поэтому их значения будут равны 0000, а последующих значений fields[] , interfaces[] просто не будет.
methods_count
Количество методов. Хоть и в коде мы видим один метод в классе, но, на самом деле, их два. Кроме main метода еще есть конструктор по умолчанию. Поэтому их количество равно двум, в нашем случае.
methods[]
Каждый элемент должен соответствовать структуре method_info описанной в Chapter 4.6 Methods
method_info
В нашем байт-коде (отформатированном, с комментариями) выглядит это так:
-- [methods] -- public Main(); 0001 --access_flags 0007 -- name_index 0008 -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 001d - attribute_length 0001 -- max_stack 0001 -- max_locals 0000 0005 -- code_length 2a b7 00 01 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count; 000a -- attribute_name_index 0000 0006 -- attribute_length 00 01 00 00 00 01 -- public static void main(java.lang.String. ); 0089 --access_flags 000b -- name_index 000c -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 0025 -- attribute_length 0002 -- max_stack 0001 -- max_locals 0000 0009 -- code_length b2 00 02 12 03 b6 00 04 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count 000a -- attribute_name_index 0000 000a -- attribute_length 00 02 00 00 00 04 00 08 00 05 -- [methods END]
Разберем по-подробнее структуру методов:
access_flags
| Flag Name | Value | Interpretation |
|---|---|---|
| ACC_PUBLIC | 0x0001 | Declared public ; may be accessed from outside its package. |
| ACC_PRIVATE | 0x0002 | Declared private ; accessible only within the defining class. |
| ACC_PROTECTED | 0x0004 | Declared protected ; may be accessed within subclasses. |
| ACC_STATIC | 0x0008 | Declared static . |
| ACC_FINAL | 0x0010 | Declared final ; must not be overridden (§5.4.5). |
| ACC_SYNCHRONIZED | 0x0020 | Declared synchronized ; invocation is wrapped by a monitor use. |
| ACC_BRIDGE | 0x0040 | A bridge method, generated by the compiler. |
| ACC_VARARGS | 0x0080 | Declared with variable number of arguments. |
| ACC_NATIVE | 0x0100 | Declared native ; implemented in a language other than Java. |
| ACC_ABSTRACT | 0x0400 | Declared abstract ; no implementation is provided. |
| ACC_STRICT | 0x0800 | Declared strictfp ; floating-point mode is FP-strict. |
| ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
Как мы можем видеть из байт-кода, в методе public Main(); (конструктор) стоит маска 0001 , который означает ACC_PUBLIC .
А теперь сами попробуем собрать метод main . Вот, что у него есть:
- public — ACC_PUBLIC — 0x0001
- static — ACC_STATIC — 0x0008
- String… args — ACC_VARARGS — 0x0080
Собираем маску: 0x0001 + 0x0008 + 0x0080 = 0x0089 . Итак, мы получили access_flag
К слову, ACC_VARARGS здесь необязательный, в том плане, что, если бы мы
использовали String[] args вместо String… args, то этого флага бы не было
name_index
Адрес имени метода ( CONSTANT_Utf8_info ) в пуле констант. Здесь важно заметить, что имя конструктора это не Main, а , расположенная в ячейке #7.
descriptor_index
Грубо говоря, это адрес указывающий на дескриптор метода. Этот дескриптор содержит тип возвращаемого значения и тип его сигнатуры.
Также, в JVM используются интерпретируемые сокращения:
| BaseType Character | Type | Interpretation |
|---|---|---|
| B | byte | signed byte |
| C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
| D | double | double-precision floating-point value |
| F | float | single-precision floating-point value |
| I | int | integer |
| J | long | long integer |
| L ClassName ; | reference | an instance of class ClassName |
| S | short | signed short |
| Z | boolean | true or false |
| [ | reference | one array dimension |
В общем случае это выглядит так:
( ParameterDescriptor* ) ReturnDescriptor
Например, следующий метод:
Object method(int i, double d, Thread t)
Можно представить в виде
(IDLjava/lang/Thread;)Ljava/lang/Object
Собственно, I — это int , D — это double , а Ljava/lang/Thread; класс Thread из стандартной библиотеки java.lang .
Далее, идут атрибуты, которые также имеют свою структуру.
Но сначала, как и всегда, идет его количество attributes_count
Затем сами атрибуты со структурой описанной в Chapter 4.7 Attributes
attribute_info
attribute_name_index
Указание имени атрибута. В нашем случае, у обоих методов это Code . Атрибуты это отдельная большая тема, в котором можно по спецификации создавать даже свои атрибуты. Но нам пока следует знать, что attribute_name_index просто указывает на адрес в пуле констант со структурой CONSTANT_Utf8_info
attribute_length
Содержит длину атрибута, не включая attribute_name_index и attribute_length
info
Далее, мы будем использовать структуру Code , так как в значении attribute_name_index мы указали на значение в пуле констант Code .
Вот его структура:
Code_attribute < u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; < u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; >exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; >
max_stack
Мне кажется, что имя этого атрибута может ввести в заблуждение из-за приставки max. На самом деле, это минимальный размер стека нужный для выполнения операции. Ну, это имя приобретает логику, если сказать, максимальный размер стека, который будет достигнут во время выполнения операции.
Упрощенно говоря, JVM выделит место для стека операндов. Там можно указывать значение, которое больше, чем нужно, но определение в этом атрибуте значения меньше, чем нужно приведет к ошибке.
max_locals
Максимальный размер локальных переменных
Ознакомится с локальными переменными можно либо в Mastering Java Bytecode at the Core of the JVM или в том же JVM Internals
code_length
Размер кода, который будет исполнятся внутри метода
code[]
Каждый код указывает на какую-то инструкцию. Таблицу соотношения optcode и команды с мнемоникой можно найти в википедии — Java bytecode instruction listings или в самой спецификации в конце книги
Для примера, возьмем наш конструктор:
-- public Main(); 0001 --access_flags 0007 -- name_index 0008 -- descriptor_index 0001 -- attributes_count -- attribute_info 0009 -- attribute_name_index (Code) 0000 001d - attribute_length 00 01 -- max_stack 00 01 -- max_locals 00 00 00 05 -- code_length 2a b7 00 01 b1 -- code[] 0000 -- exception_table_length 0001 -- attributes_count; 00 0a -- attribute_name_index 0000 0006 -- attribute_length 00 01 00 00 00 01
Здесь мы можем найти наш код:
2a b7 00 01 b1
Ищем в таблице команды и сопоставляем:
2a - aload_0 b7 0001 - invokespecial #1 b1 - return
Также описания этих команд можно найти здесь: Chapter 4.10.1.9. Type Checking Instructions
exception_table_length
Задает число элементов в таблице exception_table. У нас пока нет перехватов исключений поэтому разбирать его не будем. Но дополнительно можно почитать Chapter 4.7.3 The Code Attribute
exception_table[]
Имеет вот такую структуру:
Если упрощать, то нужно указать начало, конец ( start_pc , end_pc ) кода, который будет обрабатывать handler_pc и тип исключения catch_type
attributes_count
Количество атрибутов в Code
attributes[]
Атрибуты, часто используются анализаторами или отладчиками.
Средства для работы с байт-кодом
Это немного не та тема, которая относится к данной статье, но все же косвенно связанная с ней.
Средств для работы с байт-кодом, на самом деле, достаточно много. Здесь я бы хотел рассмотреть Byte Code Engineering Library (BCEL) от Apache Commons.
Для начала, с помощью него мы может получить некоторые атрибуты байт-кода:
// read file from resources/complied/ClassA.class InputStream inputStream = ClassParserExample.class.getResourceAsStream("/compiled/ClassA.class"); if (inputStream == null) throw new FileNotFoundException(); ClassParser parser = new ClassParser(inputStream, "ClassA.class"); JavaClass clazz = parser.parse(); final String HEX_BYTECODE = getHex(clazz.getBytes()); System.out.println("Hex bytecode: "); System.out.println(HEX_BYTECODE); System.out.println(); final String MINOR_VER = getHex(clazz.getMinor()); final String MAJOR_VER = getHex(clazz.getMajor()); final String CONSTANT_POOL = getHex(clazz.getConstantPool().getConstantPool()); final String ACCESS_FLAGS = getHex(clazz.getAccessFlags()); final String THIS_CLASS = getHex(clazz.getClassName().getBytes()); final String SUPER_CLASS = getHex(clazz.getSuperClass().getBytes()); final String INTERFACES = getHex(clazz.getInterfaces()); final String FIELDS = getHex(clazz.getFields()); final String METHODS = getHex(clazz.getMethods()); final String ATTRIBUTES = getHex(clazz.getAttributes());
Полный листинг кода
import org.apache.bcel.classfile.*; import org.apache.commons.codec.binary.Hex; import java.io.*; public class ClassParserExample < public static void main(String. args) throws IOException, ClassNotFoundException < // read file from resources/complied/ClassA.class InputStream inputStream = ClassParserExample.class.getResourceAsStream("/compiled/ClassA.class"); if (inputStream == null) throw new FileNotFoundException(); ClassParser parser = new ClassParser(inputStream, "ClassA.class"); JavaClass clazz = parser.parse(); final String HEX_BYTECODE = getHex(clazz.getBytes()); System.out.println("Hex bytecode: "); System.out.println(HEX_BYTECODE); System.out.println(); final String MINOR_VER = getHex(clazz.getMinor()); final String MAJOR_VER = getHex(clazz.getMajor()); final String CONSTANT_POOL = getHex(clazz.getConstantPool().getConstantPool()); final String ACCESS_FLAGS = getHex(clazz.getAccessFlags()); final String THIS_CLASS = getHex(clazz.getClassName().getBytes()); final String SUPER_CLASS = getHex(clazz.getSuperClass().getBytes()); final String INTERFACES = getHex(clazz.getInterfaces()); final String FIELDS = getHex(clazz.getFields()); final String METHODS = getHex(clazz.getMethods()); final String ATTRIBUTES = getHex(clazz.getAttributes()); System.out.println( "minor: " + MINOR_VER ); // 0 System.out.println( "major: " + MAJOR_VER ); // 34 System.out.println( "constant pool: " + CONSTANT_POOL); // not correctly System.out.println( "access flags: " + ACCESS_FLAGS ); // 21 System.out.println( "this class: " + THIS_CLASS ); System.out.println( "super class: " + SUPER_CLASS ); // Object System.out.println( "interfaces: " + INTERFACES ); // System.out.println( "fields: " + FIELDS ); // System.out.println( "methods: " + METHODS ); // one method: psvm hello world System.out.println( "attributes: " + ATTRIBUTES ); // 536f7572636546696c65 - I think it's instructions for Java tools > private static String getHex(byte[] bytes) < return Hex.encodeHexString(bytes); >private static String getHex(int intNum) < return Integer.toHexString(intNum); >private static String getHex(Constant[] constants) < if (constants == null) return null; StringBuilder sb = new StringBuilder(); for (Constant c : constants)< if (c == null) continue; sb.append(getHex(c.getTag())).append(" "); >return sb.toString(); > private static String getHex(JavaClass[] clazzes) < if (clazzes == null) return null; StringBuilder sb = new StringBuilder(); for (JavaClass c : clazzes)< sb.append(getHex(c.getClassName().getBytes())).append(" "); >return sb.toString(); > private static String getHex(Field[] fields) < if (fields == null) return null; StringBuilder sb = new StringBuilder(); for (Field c : fields)< sb.append(getHex(c.getName().getBytes())).append(" "); >return sb.toString(); > private static String getHex(Method[] methods) < if (methods == null) return null; StringBuilder sb = new StringBuilder(); for (Method c : methods)< sb.append(getHex(c.getName().getBytes())).append(" "); >return sb.toString(); > private static String getHex(Attribute[] attributes) < if (attributes == null) return null; StringBuilder sb = new StringBuilder(); for (Attribute c : attributes)< sb.append(getHex(c.getName().getBytes())).append(" "); >return sb.toString(); > > /* Class A: public class ClassA < public static void main(String[] args) < System.out.println("Hello world"); >> */ /* Class A bytecode: cafe babe 0000 0034 0022 0a00 0600 1409 0015 0016 0800 170a 0018 0019 0700 1a07 001b 0100 063c 696e 6974 3e01 0003 2829 5601 0004 436f 6465 0100 0f4c 696e 654e 756d 6265 7254 6162 6c65 0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 6501 0004 7468 6973 0100 1d4c 636f 6d2f 6170 706c 6f69 6478 7878 2f70 6172 7365 2f43 6c61 7373 413b 0100 046d 6169 6e01 0016 285b 4c6a 6176 612f 6c61 6e67 2f53 7472 696e 673b 2956 0100 0461 7267 7301 0013 5b4c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b01 000a 536f 7572 6365 4669 6c65 0100 0b43 6c61 7373 412e 6a61 7661 0c00 0700 0807 001c 0c00 1d00 1e01 000b 4865 6c6c 6f20 776f 726c 6407 001f 0c00 2000 2101 001b 636f 6d2f 6170 706c 6f69 6478 7878 2f70 6172 7365 2f43 6c61 7373 4101 0010 6a61 7661 2f6c 616e 672f 4f62 6a65 6374 0100 106a 6176 612f 6c61 6e67 2f53 7973 7465 6d01 0003 6f75 7401 0015 4c6a 6176 612f 696f 2f50 7269 6e74 5374 7265 616d 3b01 0013 6a61 7661 2f69 6f2f 5072 696e 7453 7472 6561 6d01 0007 7072 696e 746c 6e01 0015 284c 6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b29 5600 2100 0500 0600 0000 0000 0200 0100 0700 0800 0100 0900 0000 2f00 0100 0100 0000 052a b700 01b1 0000 0002 000a 0000 0006 0001 0000 0006 000b 0000 000c 0001 0000 0005 000c 000d 0000 0009 000e 000f 0001 0009 0000 0037 0002 0001 0000 0009 b200 0212 03b6 0004 b100 0000 0200 0a00 0000 0a00 0200 0000 0800 0800 0900 0b00 0000 0c00 0100 0000 0900 1000 1100 0000 0100 1200 0000 0200 13 */ /* Assembled code: Classfile /C:/java/BCEL/src/main/resources/compiled/ClassA.class Last modified 08.12.2019; size 563 bytes MD5 checksum bcd0198f6764a1dc2f3967fef701452e Compiled from "ClassA.java" public class com.apploidxxx.parse.ClassA minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #6.#20 // java/lang/Object."":()V #2 = Fieldref #21.#22 // java/lang/System.out:Ljava/io/PrintStream; #3 = String #23 // Hello world #4 = Methodref #24.#25 // java/io/PrintStream.println:(Ljava/lang/String;)V #5 = Class #26 // com/apploidxxx/parse/ClassA #6 = Class #27 // java/lang/Object #7 = Utf8 #8 = Utf8 ()V #9 = Utf8 Code #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 Lcom/apploidxxx/parse/ClassA; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 ClassA.java #20 = NameAndType #7:#8 // "":()V #21 = Class #28 // java/lang/System #22 = NameAndType #29:#30 // out:Ljava/io/PrintStream; #23 = Utf8 Hello world #24 = Class #31 // java/io/PrintStream #25 = NameAndType #32:#33 // println:(Ljava/lang/String;)V #26 = Utf8 com/apploidxxx/parse/ClassA #27 = Utf8 java/lang/Object #28 = Utf8 java/lang/System #29 = Utf8 out #30 = Utf8 Ljava/io/PrintStream; #31 = Utf8 java/io/PrintStream #32 = Utf8 println #33 = Utf8 (Ljava/lang/String;)V < public com.apploidxxx.parse.ClassA(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."":()V 4: return LineNumberTable: line 6: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcom/apploidxxx/parse/ClassA; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 3: ldc #3 // String Hello world 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 8: return LineNumberTable: line 8: 0 line 9: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 args [Ljava/lang/String; > SourceFile: "ClassA.java */
Помимо этого мы можем сгенерировать, изменить или дизассемблировать (например, в Jasmin) байт-код.
Парочку примеров можно найти в моем репозитории или в официальных примерах
Также, я уделил внимание и Jasmin. На самом деле, я не знаю, чем оно может быть полезно, но я её использовал при изучении механизма работы JVM с байт-кодом.
С помощью неё можно писать на упрощенном ассемблерном коде:
Hello World
.bytecode 52.0 .source Main.j .class public Main .super java/lang/Object .method public static main([Ljava/lang/String;)V .limit stack 2 .limit locals 2 getstatic java/lang/System/out Ljava/io/PrintStream; ldc "Hello world!" invokevirtual java/io/PrintStream.println(Ljava/lang/String;)V return .end method
Создание объекта
; ClassCreating.j .bytecode 52.0 .source ClassCreating.java .class public ClassCreating .super java/lang/Object .method public ()V .limit stack 1 .limit locals 1 .line 1 0: aload_0 1: invokespecial java/lang/Object/()V 4: return .end method .method public static main([Ljava/lang/String;)V ; Flag ACC_VARARGS set, see JVM spec .limit stack 2 .limit locals 3 .line 3 0: new java/lang/String 3: dup 4: invokespecial java/lang/String/()V 7: astore_1 .line 4 8: new ClassCreating 11: dup 12: invokespecial ClassCreating/()V 15: astore_2 .line 5 16: aload_2 17: invokevirtual ClassCreating/sayHello()V .line 6 20: return .end method .method public sayHello()V .limit stack 2 .limit locals 1 .line 9 0: getstatic java/lang/System/out Ljava/io/PrintStream; 3: ldc "Hello, User!" 5: invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V .line 10 8: return .end method
Вот мы и разобрали простую программку Hello World
Листинг байт-кода с комментариями можно найти на моем гисте: gist.github
Если есть ошибки прошу писать в комментариях или в сообщениях.
Использованная литература
- The Java Virtual Machine Specification — docs.oracle
