2014-10-31 4 views
1

Я очень новичок в Java байткоде. По моему мнению, при дизассемблировании JAR-файла результатом будет байт-код, интерпретируемый JVM напрямую (числа). Каждый байт или 2 байта чисел ассоциируется с методом Java в фактическом исходном файле Java. Где я могу найти их отображение?Является ли Java Bytecode последовательно выполненным JVM?

Кроме того, скажем, я хочу узнать, была ли инициализирована переменная в классе, но она больше никогда не использовалась. Могу ли я просто проверить, когда он был создан, а затем считать, что он никогда не использовался, если он никогда не появляется снова в байтекоде после его инициализации? Для того, чтобы эта логика работала, JVM пришлось бы последовательно выполнять байт-код, чтобы эта переменная не могла перейти к другой функции и т. Д. Определены границы функций в отличие от общего кода сборки (intel, MIPS).

Заранее спасибо.

+0

Попробуйте выполнить команду 'javap -c'. Некоторые из них не имеют смысла. Переменные не «прыгают» нигде, и для этого не требуется последовательное выполнение. – EJP

+1

Возможно, вы хотите начать чтение [Спецификация виртуальной машины Java®] (http://docs.oracle.com/javase/specs/jvms/se8/html/index.html), особенно [§4. Формат файла класса] (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html) и [§6. Набор инструкций виртуальной машины Java] (http://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html) – Holger

ответ

2

Требуется некоторое время, чтобы понять байт-код JVM.Для того, чтобы вы начали здесь две вещи, которые вы должны знать:

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

  • Все параметры и локальные переменные хранятся в локальном массиве переменных.

Давайте посмотрим, что на практике. Вот исходный код:

package p1; 

public class Movie { 
    public void setPrice(int price) { 
    this.price = price; 
    } 
} 

Как сказал EJP, вы должны запустить javap -c, чтобы увидеть байткод: javap -c bin/p1/Movie.class. Это выход:

public class p1.Movie { 
    public p1.Movie(); 
    Code: 
     0: aload_0  
     1: invokespecial #10 // Method java/lang/Object."<init>":()V 
     4: return   

    public void setPrice(int); 
    Code: 
     0: aload_0  
     1: iload_1  
     2: putfield  #18 // Field price:I 
     5: return   
} 

Глядя на выходе вы можете увидеть, что в байт-код, мы видим, конструктор по умолчанию, а метод setPrice.

Первая инструкция, aload_0 принимает значение локальной переменной 0 и толкает ее в стек (complete list of instructions). В не статический метод, локальная переменная 0, всегда параметр this так после инструктажа 0 наш стек

| this | 
+------+ 

Следующая инструкция aload_1 которая принимает значение локальной переменной 1 и толкает его в стек. В нашей локальной переменной 1 указан параметр метода (цена). Наш стек теперь выглядит следующим образом:

| price | 
| this | 
+-------+ 

Следующая команда putfield #18 это один делает назначение this.price = price. Эта команда извлекает два значения стека. Первое всплывающее значение - это новое значение полей. Второе всплывающее значение - это указатель на объект, которому присвоено поле. Имя назначаемого поля закодировано в инструкции (поэтому инструкция берет три байта: она начинается в позиции 2, но следующая инструкция начинается в позиции 5). Дополнительное значение, закодированное в инструкции, равно «# 18». Это индекс в постоянный пул. Чтобы увидеть постоянный пул, вы должны запустить: javap -v bin/p1/Movie.class:

Classfile /home/imaman/workspace/Movie-shop/bin/p1/Movie.class 
... 
Constant pool: 
    #1 = Class    #2    // p1/Movie 
    ... 
    #5 = Utf8    price 
    #6 = Utf8    I 
    ... 
    #18 = Fieldref   #1.#19   // p1/Movie.price:I 
    #19 = NameAndType  #5:#6   // price:I 
    ... 

Так #18 указывает, что поле для назначения является price поле p1.Movie класса (как вы можете видеть, # 18 делает ссылки на # 1, # 19 которые, в свою очередь, ссылаются на № 5 и № 6. Фактическое имя назначенного поля отображается в пуле констант)

назад к нашему исполнению команды putfield: выставив два значения из стека, JVM теперь назначает сначала всплыло в поле price (указано #18) this (второе значение).

Оценочный стек теперь пуст.

Последняя инструкция просто возвращается.

3

Это не совсем понятно, что вы просите здесь, так что позвольте мне ответить на некоторые вещи:

границы Метод хорошо определены, в отличии от «нормального» ассемблера. Типы хорошо определены повсюду. Поля хорошо определены. Классы четко определены. Границы инструкции четко определены (незаконно переходить к середине команды). Код и данные легко различаются. Методы не могут обращаться к переменным друг друга; только поля. Эти вещи делают это много проще анализировать байт-код Java, чем машинный код.

Чтобы читать и писать файлы классов из программы Java, я рекомендую ASM library. Он позаботится о понимании формата файла класса и переведет его в более простой в использовании формат (либо дерево объектов Java, либо последовательность вызовов методов). Существуют и другие библиотеки с аналогичными целями, такие как BCEL, cgLib и Javassist. Я не достаточно хорошо разбираюсь в этих библиотеках, чтобы сравнить их.

Bytecode внутри метода выполняется последовательно, для «большинства» инструкций. Существует несколько инструкций, которые могут привести к тому, что выполнение не будет последовательным - обычно это является целью инструкции (например, условные переходы, используемые для реализации if/while/и т. Д.). Многие инструкции также могут генерировать исключения, которые заставляют выполнение либо перейти к обработчику исключений, либо выйти из текущего метода.

Следующие инструкции влияют на поток управления:

  • areturn, dreturn, freturn, ireturn, lreturn - при выполнении, вызвать метод для возврата в обычном режиме. Также можно бросить IllegalMonitorStateException, в редких условиях (неверно генерируемый байт-код).
  • athrow - при исполнении исключает исключение.
  • if_icmpne, if_icmpeq, if_icmplt, if_icmpge, if_icmpgt, if_icmple, ifne, ifeq, iflt, ifge, ifgt, ifle, ifnonnull, ifnull - условные инструкции скачка
  • goto, goto_w - безусловные инструкций перехода
  • aaload, aastore , anewarray, arraylength, baload, bastore, caload, castore, checkcast, daload, dastore, faload, fastore, getfield, getstatic, iaload, iastore, idiv, instanceof, irem, laload, lastore, ldc, ldc_w, ldc2_w, ldiv, lrem, monitorenter, monitorexit, multianewarray, newarray, putfield, putstatic, saload, sastore - может исключить исключения при некоторых обстоятельствах.
  • invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual, new - вызвать другие методы, чтобы назвать, что может привести к исключению броска.
  • jsr, jsr_w, ret - позволяют методам содержать подпрограммы, вызываемые из нескольких мест. К счастью для вас, современные компиляторы, похоже, не создают их.
0

JVM не обязательно исполняет байт-код последовательно, но он ведет себя так, как если бы он делал (что-то вроде выполнения вне порядка на процессорах, но с гораздо большей глубиной оптимизации).

Главное, что вы, похоже, беспокоитесь о структурных проработках платформы байт-кода.

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

  • Кроме того, все байт-коды имеют принудительную проверку типов, хотя проверка типов несколько слабее, чем на уровне языка Java. Вы не можете взять float и interpert его как int, например, а тем более указатель. Весь доступ к памяти отвлечен виртуальной машиной, невозможно выполнить необработанный доступ к памяти, как в собственном коде.

  • Инструкции могут быть более 2 байтов (на самом деле инструкции коммутатора могут быть произвольными длинными). Но подавляющее большинство инструкций составляют 1 или 3 байта. Они не обязательно соответствуют 1 к 1 с элементами источника Java, хотя сопоставление обычно является простым. В общем, более поздние версии Java добавляют больше синтаксического сахара, что уменьшает сходство байт-кода с исходным кодом при использовании этих функций (заметным случаем является включение строки).

1

Как указано в комментарии EJP, вы можете декомпилировать класс с именем example.Main, используя команду javap -c example.Main.class.Как видно, байт-код Java гораздо более структурирован, чем IA32. Фактические инструкции байткода содержатся в методах, как и в самой Java.

Вы можете найти информацию о каждой из инструкций байт-кода в:

  1. JVM Specification
  2. Wikipedia

инструкции виртуальной машины Java манипулировать стек операндов. Например:

LDC 10 // Push the constant 10 onto the stack. 
LDC 20 // Push the constant 20 onto the stack. 
IADD  // Pop two numbers off the stack, add them, push the result. 
ISTORE 5 // Pop an integer (in this case 30) off the stack and put it in variable #5. 

Как можно заметить, локальные переменные фактически хранятся в пронумерованных слотов в стеке-кадра. Задача компилятора Java связывать локальную переменную с пронумерованным слотом. Важно отметить, что переменные типа (boolean, char, byte, short, int или reference-type) будут храниться в одном слоте. Однако для переменных типа long или double требуется два слота для их хранения. Кроме того, в нестатических методах слот # 0 всегда используется для хранения this. Кроме того, параметры всегда связаны с младшими пронумерованными слотами. Таким образом, в нестатический методmoo(String message, int times), this будет в слоте # 0, переменная message будет в слоте №1, а переменная times будет в слоте # 2.

Для того, чтобы определить, где в байткод метода локальной переменной является живой, вам нужно будет использовать Live Variable Analysis на байткод метода, потому что инструкции байткодом выполняются последовательно (т.е. не все сразу), но не обязательно линейно.

С другой стороны, поля не сохраняются в вышеупомянутой последовательности кадров. Я думаю, вы можете ссылаться на поля, поскольку они могут быть «инициализированы в классе», а затем использоваться в разных методах, тогда как локальные переменные являются локальными для метода, который их объявляет.

Смежные вопросы