Типы в байт-коде - PullRequest
       4

Типы в байт-коде

12 голосов
/ 14 апреля 2010

Я некоторое время работал над (Java) байт-кодом, однако мне никогда не приходило в голову спросить, почему напечатаны некоторые инструкции? Я понимаю, что в операции ADD нам нужно различать целочисленное и FP-добавление (поэтому у нас есть IADD и FADD). Однако почему мы должны различать ISTORE и FSTORE? Они оба включают в себя одну и ту же операцию, которая перемещает 32 бита из стека в позицию локальной переменной?

Единственный ответ, который я могу придумать, - это безопасность типов, чтобы предотвратить это: (ILOAD, ILOAD, FADD). Тем не менее, я считаю, что безопасность типов уже применяется на уровне языка Java. Хорошо, формат файла Class напрямую не связан с Java, поэтому это способ обеспечения безопасности типов для языков, которые его не поддерживают? Есть мысли? Спасибо.

РЕДАКТИРОВАТЬ: , чтобы следить за ответом Риди. Я написал эту минимальную программу:

public static void main(String args[])
{
    int x = 1;
}

скомпилировано в:

iconst_1
istore_1
return

используя редактор байт-кода, я изменил вторую инструкцию:

iconst_1
fstore_1
return

и он вернул java.lang.VerifyError: Ожидается найти float в стеке.

Интересно, если в стеке нет информации о типе, только биты, как команда FSTORE узнала, что она имеет дело с int, а не с плавающей точкой?

Примечание: Не могу найти лучшего заголовка для этого вопроса. Не стесняйтесь улучшать это.

Ответы [ 4 ]

17 голосов
/ 14 апреля 2010

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

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

Дело 1:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
istore_1                OK              []                     1: int
return                  OK              []                     1: int

Дело 2:

Instruction             Verification    Stack Types            Local Variable Types 
----------------------- --------------- ---------------------- ----------------------- 
<method entry>          OK              []                     1: none
iconst_1                OK              [int]                  1: none
fstore_1                Error: Expecting to find float on stack

Ошибка возникает из-за того, что верификатор знает, что fstore_1 ожидает float в стеке, но результат выполнения предыдущих инструкций оставляет int в стеке.

Эта проверка выполняется без выполнения кодов операций, скорее, она выполняется путем просмотра типов инструкции, так же, как компилятор java выдает ошибку, когда вы пишете (Integer)"abcd". Компилятору не нужно запускать программу, чтобы знать, что "abcd" является строкой и не может быть приведен к Integer.

4 голосов
/ 21 апреля 2010

Джефф Риди объяснил в своем ответе, что делает верификатор при загрузке класса. Я просто хочу добавить, что вы можете отключить верификатор с помощью параметра JVM. Это не рекомендуется!

Для вашей программы-примера (с iconst и fstore) результатом работы с отключенной проверкой является ошибка VM, которая останавливает JVM со следующим сообщением:

=============== DEBUG MESSAGE: illegal bytecode sequence - method not verified ================

#
# An unexpected error has been detected by HotSpot Virtual Machine:
#
#  EXCEPTION_PRIV_INSTRUCTION (0xc0000096) at pc=0x00a82571, pid=2496, tid=3408
#
# Java VM: Java HotSpot(TM) Client VM (1.5.0_15-b04 mixed mode, sharing)
# Problematic frame:
# j  BytecodeMismatch.main([Ljava/lang/String;)V+0
#
...
3 голосов
/ 14 апреля 2010

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

Чтобы ответить на ваш второй вопрос, VerifyError выбрасывается при загрузке класса, а не при его выполнении. Процесс проверки описан здесь ; Сноска № 3.

2 голосов
/ 16 июня 2012

Весь байт-код должен быть гарантированно безопасным при статическом анализе потока данных, как упомянуто выше. Однако это на самом деле не объясняет, почему такие команды, как _store, имеют разные типы, поскольку тип может быть выведен из типа значения в стеке. На самом деле, есть некоторые инструкции, такие как pop, dup и swap, которые делают именно это и работают с несколькими типами. Почему некоторые инструкции напечатаны, а другие нет - это то, что могут объяснить только первоначальные разработчики Java.

...