allasm.ru

    Меню

 

Декомпиляция Java-программ

В данной статье я собираюсь рассказать об основах дизассемблирования программ, написанных на Java.

Что такое Java?

Термин "Java" включает в себя несколько различных концепций:

  • Язык программирования
  • Спецификации языка
  • Среда выполнения и API

Вот небольшое пояснение к вышесказанному: после многочисленных экспериментов по созданию языка программирования для своих инженеров SUN решила изобрести современный (по ее утверждению) язык, основанный на принципах объектно-ориентированного программирования, очень модному в то время. В архитектуру ООП входят (среди прочих) такие концепции как наследование и полиморфизм.

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

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

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

Что представляет из себя JAVA?

Каждый объект в JAVA находится в отдельном файле. Один или более объектов можно сгруппировать в пакет (pakage'и). Файлы группируются в Java-архив (JAR) или в архив, сходный по формату с ZIP.

Каждый класс объявляется определенным образом. Например:

public class MyClase
{
}

У каждого класса может быть (а может и не быть) один или несколько конструкторов, которые отличаются друг от друга количеством или типом аргументов:

public class MiClase
{
        public MiClase()
        {
        }

        public MiClase(int i)
        {
        }
}

У каждого класса может быть (а может и не быть) один или больше методов.

Также классы бывают разных типов: публичными, частными, финальными и виртуальными... Методы могут быть публичными, частными, статическими, финальными, ...

Класс может расширять функциональность другого.

Базовые типы аргументов методов и переменных могут быть следующие: boolean, char, byte, short, int, long, float, double. Эти типы являются системными. Им соответствуют следующие классы: java.lang.Integer, java.lang.String, java.net.ContentHandler, java.rmi.NotBoundException, ...

Важным классом является java.lang.Class. Экземпляры класса Class представляют классы и интерфейсов Java-приложения. JVM предоставляет механизм установки каждого объекта, как экземпляра класса Class.

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

Первые шаги

Предположим, что у нас есть программа, написанная на Java, которую необходимо скомпилировать. Для этого нам необходимо иметь компилятор Java. В частности, SUN бесплатно распространяет JDK, который включает в себя компилятор javac.

Вызываем

javac  MiClase.java

Он запускается следующим образом:

javac  MiClase.java

На выходе получаем файл 'MiClase.class', который содержит Java-байткод.

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

Запустить программу можно следующим образом:

java MiClase

Введение в суть дела

Всю необходимую информацию относительно спецификации JavaTM Virtual Machine, можно получить на сайте java.sun.com.

Архив каждого класса начинается со структуры типа ClassFile

    {
        u4 magic;
        u2 minor_version;
        u2 major_version;
        u2 constant_pool_count;
        cp_info constant_pool[constant_pool_count-1];
        u2 access_flags;
        u2 this_class;
        u2 super_class;
        u2 interfaces_count;
        u2 interfaces[interfaces_count];
        u2 fields_count;
        field_info fields[fields_count];
        u2 methods_count;
        method_info methods[methods_count];
        u2 attributes_count;
        attribute_info attributes[attributes_count];
    }

Например, поле 'magic' занимает 4 байта и всегда равно 0xCAFEBABE. В общем случае данные этой структуры представляют массивы элементов размером в слово, при этом каждому массиву предшествует поле, указывающее размер этого массива. Например, methods_count содержит в себе количество методов (функций или подпрограмм), которые содержит класс, в то время как methods[methods_count] является массивом структур типа method_info. Эта информация генерируется компилятором и предназначается для JVM.

Поскольку эта информация показывает, сколько класс занимает места в памяти, JVM имеет механизм верификации классов.

Этот процесс выполняется следующим образом:

Фаза 1:
- поле magic верно

Фаза 2:
- у класса с типом final (финальный) не может быть наследников
- класс наследуется от другого, хотя бы java.lang.Class или java.lang.Object
- область констант непрерывна
- у всех поля и методы принадлежат верным классам, имена и типы полей и методов также верны.

Фаза 3:

- запук программы возможно только, если:

  • присутствуют все необходимые операторы
  • куча содержит данные верных типов
  • переменные имеют определенное значение (включая void и null)
  • вызовы методов используют правильные аргументы
  • у всех опкодов верные аргументы

Фаза 4:

- некоторые проверки фазы 3 выполняются во время запуска класса.

Как и все языки, основывающиеся на байткодах, JVM имеет ряд регистров и структур для внутреннего использования. Например, программный счетчик (pc) содержит адрес выполняющейся JVM инструкции для каждого треда. Помните, что по определению при выполнении JAVA-программы создается несколько тредов, выполняющихся одновременно. Также существуют различные кучи данных (стек) по одной для каждого из выполняющихся тредов. Существует одна уникальная куча, использующаяся для хранения объектов.

Другой важной зоной является область методов, в которой хранятся байткоды классов. И еще одна зона с константами. И еще одна с "родными"(native)-методами (процедуры, написанные не на JAVA, а на другом языке, как правило C, и предназначающиеся для вызовов в Java и вызовов методов DLL; соответственно, эти процедуры хранятся не в байткодах).

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

Более подробное рассмотрение

Опкоды организованы по типам:

  • Присвоение и чтение
  • Арифметические инструкции
  • Конвертирование типов
  • Создание объектов и манипулирование ими
  • Управление кучей
  • Контроль передачи данных
  • Вызов методов
  • Исключения

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

Чтобы байткоды могли быть интерпретированы какой-либо JVA, они должны соответствовать определенным спецификациям (также установленным SUN'ом), в которых задаются возможные инструкции и их операнды.

Вот таблица возможных опкодов:

<++>java/opcodes.txt
00 (0x00) nop
01 (0x01) aconst_null
02 (0x02) iconst_m1
03 (0x03) iconst_0
04 (0x04) iconst_1
05 (0x05) iconst_2
06 (0x06) iconst_3
07 (0x07) iconst_4
08 (0x08) iconst_5
09 (0x09) lconst_0
10 (0x0a) lconst_1
11 (0x0b) fconst_0
12 (0x0c) fconst_1
13 (0x0d) fconst_2
14 (0x0e) dconst_0
15 (0x0f) dconst_1
16 (0x10) bipush
17 (0x11) sipush
18 (0x12) ldc
19 (0x13) ldc_w
20 (0x14) ldc2_w
21 (0x15) iload
22 (0x16) lload
23 (0x17) fload
24 (0x18) dload
25 (0x19) aload
26 (0x1a) iload_0
27 (0x1b) iload_1
28 (0x1c) iload_2
29 (0x1d) iload_3
30 (0x1e) lload_0
31 (0x1f) lload_1
32 (0x20) lload_2
33 (0x21) lload_3
34 (0x22) fload_0
35 (0x23) fload_1
36 (0x24) fload_2
37 (0x25) fload_3
38 (0x26) dload_0
39 (0x27) dload_1
40 (0x28) dload_2
41 (0x29) dload_3
42 (0x2a) aload_0
43 (0x2b) aload_1
44 (0x2c) aload_2
45 (0x2d) aload_3
46 (0x2e) iaload
47 (0x2f) laload
48 (0x30) faload
49 (0x31) daload
50 (0x32) aaload
51 (0x33) baload
52 (0x34) caload
53 (0x35) saload
54 (0x36) istore
55 (0x37) lstore
56 (0x38) fstore
57 (0x39) dstore
58 (0x3a) astore
59 (0x3b) istore_0
60 (0x3c) istore_1
61 (0x3d) istore_2
62 (0x3e) istore_3
63 (0x3f) lstore_0
64 (0x40) lstore_1
65 (0x41) lstore_2
66 (0x42) lstore_3
67 (0x43) fstore_0
68 (0x44) fstore_1
69 (0x45) fstore_2
70 (0x46) fstore_3
71 (0x47) dstore_0
72 (0x48) dstore_1
73 (0x49) dstore_2
74 (0x4a) dstore_3
75 (0x4b) astore_0
76 (0x4c) astore_1
77 (0x4d) astore_2
78 (0x4e) astore_3
79 (0x4f) iastore
80 (0x50) lastore
81 (0x51) fastore
82 (0x52) dastore
83 (0x53) aastore
84 (0x54) bastore
85 (0x55) castore
86 (0x56) sastore
87 (0x57) pop
88 (0x58) pop2
89 (0x59) dup
90 (0x5a) dup_x1
91 (0x5b) dup_x2
92 (0x5c) dup2
93 (0x5d) dup2_x1
94 (0x5e) dup2_x2
95 (0x5f) swap
96 (0x60) iadd
97 (0x61) ladd
98 (0x62) fadd
99 (0x63) dadd
100 (0x64) isub
101 (0x65) lsub
102 (0x66) fsub
103 (0x67) dsub
104 (0x68) imul
105 (0x69) lmul
106 (0x6a) fmul
107 (0x6b) dmul
108 (0x6c) idiv
109 (0x6d) ldiv
110 (0x6e) fdiv
111 (0x6f) ddiv
112 (0x70) irem
113 (0x71) lrem
114 (0x72) frem
115 (0x73) drem
116 (0x74) ineg
117 (0x75) lneg
118 (0x76) fneg
119 (0x77) dneg
120 (0x78) ishl
121 (0x79) lshl
122 (0x7a) ishr
123 (0x7b) lshr
124 (0x7c) iushr
125 (0x7d) lushr
126 (0x7e) iand
127 (0x7f) land
128 (0x80) ior
129 (0x81) lor
130 (0x82) ixor
131 (0x83) lxor
132 (0x84) iinc
133 (0x85) i2l
134 (0x86) i2f
135 (0x87) i2d
136 (0x88) l2i
137 (0x89) l2f
138 (0x8a) l2d
139 (0x8b) f2i
140 (0x8c) f2l
141 (0x8d) f2d
142 (0x8e) d2i
143 (0x8f) d2l
144 (0x90) d2f
145 (0x91) i2b
146 (0x92) i2c
147 (0x93) i2s
148 (0x94) lcmp
149 (0x95) fcmpl
150 (0x96) fcmpg
151 (0x97) dcmpl
152 (0x98) dcmpg
153 (0x99) ifeq
154 (0x9a) ifne
155 (0x9b) iflt
156 (0x9c) ifge
157 (0x9d) ifgt
158 (0x9e) ifle
159 (0x9f) if_icmpeq
160 (0xa0) if_icmpne
161 (0xa1) if_icmplt
162 (0xa2) if_icmpge
163 (0xa3) if_icmpgt
164 (0xa4) if_icmple
165 (0xa5) if_acmpeq
166 (0xa6) if_acmpne
167 (0xa7) goto
168 (0xa8) jsr
169 (0xa9) ret
170 (0xaa) tableswitch
171 (0xab) lookupswitch
172 (0xac) ireturn
173 (0xad) lreturn
174 (0xae) freturn
175 (0xaf) dreturn
176 (0xb0) areturn
177 (0xb1) return
178 (0xb2) getstatic
179 (0xb3) putstatic
180 (0xb4) getfield
181 (0xb5) putfield
182 (0xb6) invokevirtual
183 (0xb7) invokespecial
184 (0xb8) invokestatic
185 (0xb9) invokeinterface
186 (0xba) xxxunusedxxx1
187 (0xbb) new
188 (0xbc) newarray
189 (0xbd) anewarray
190 (0xbe) arraylength
191 (0xbf) athrow
192 (0xc0) checkcast
193 (0xc1) instanceof
194 (0xc2) monitorenter
195 (0xc3) monitorexit
196 (0xc4) wide
197 (0xc5) multianewarray
198 (0xc6) ifnull
199 (0xc7) ifnonnull
200 (0xc8) goto_w
201 (0xc9) jsr_w
зарезервированные опкоды:
202 (0xca) breakpoint
254 (0xfe) impdep1
255 (0xff) impdep2
<-->

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

Установленные факты:

- Некоторые опкоды не используются. Их обработка, если они встретятся в каком-то классе, будет зависеть от реализации JVM.

- В общем случае каждый опкод имеет форму ixxxx, dxxxx, lxxxx, fxxxx, в зависимости от типа данных, с которым он работает (целые числа, двойные, длинные или плавающие).

- Для сохранения значения в куче используется aload_n, где 0 <= n <= 3. Хочу сказатель, что хотя существует опкод для сохранения числа 2, но для сохранения номера отдельный опкод aload отсутствует. Отдельные опкоды были выделены для наиболее часто используемых значений: 0, 1, 2, 3.

Инструменты

Первым и основным является JAVA-компилятор. Легче всего достать JDK от SUN и, хотя у него не слишком много возможностей и сервисов по сравнению с другими компиляторами, генерируемый им код более-менее приемлем.

Где: java.sun.com

Для того, чтобы совершать путешествия внутрь ява-программ, следует ознакомиться с соответствующими спецификацими на http://java.sun.com/docs/books/vmspec/2nd-edition/html/.

Поскольку мы предполагаем, что у вас установлен JAVA-компилятор, по-видимому, у вас установлен и декомпилятор. Он называется javap и вызывается с помощью команды 'javap -c MiClase', что отобразит на экране дизассемблированный код.

Единственное, что плохо - если встречается ссылка на класс, местонахождение которого невозможно обнаружить, процесс дизассемблирования прерывается.

И, поскольку мы находимся в андерграундном мире, необходимо найти инструмент, умеющий декомпилировать Java-программы. Наиболее часто используемый - это JAD, а лично мне нравится DJ Java Decompiler, который можно найти на http://members.fortunecity.com/neshkov/dj.html.

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

Задачу декомпиляции затрудняет то, что существуют программы для усложнения скомпилированного кода насколько это возможно. Найти такие программы можно, например, на www.zelix.com или www.condensity.com.

Jasmin - компилятор опкодов. Он трансформирует опкоды в классы. На вход он принимает то, что мы получили с помощью javap.

jas - другой JAVA-компилятор. Он позволяет генерировать динамический код и модифицировать программу на лету, что является полезной техникой для создания мутирующих программ, чтобы спрятать реальный код.

D-java - это еще один JAVA-дизассемблер, написанный на языке C (доступны исходники). Он так же прост, как и javac, но он несколько более устойчив.

Больше информации вы можете получить здесь: http://www.meurrens.org/ip-Links/java/codeEngineering/#tocDecompilersToJava

Практика

Теперь, когда вам известна основные теоретические сведения, мы можем начать с простого примера. Жертвой будет программа под названием BEA-WebLogic. Это приложение работает в Windows так же как и в UNIX, так как полностью написано на JAVA, и представляет собой JAR-архив, в котором упакованны все соответствующие классы. В своей основе это сервер веб-приложений, поэтому в него включен веб-сервер, сервлеты и EJB. Загрузить BEA-WebLogic можно с www.bea.com или www.beasys.com. Использовалась версия 5.1. Уже вышла 6.0, но для того, чтобы понять технику, хватит и 5.1.

Будучи проинсталлированной в c:\weblogic, система лицензий устанавливает в c:\weblogic\WebLogicLicense.xml типа XML (т.е. текстовый) со примерно следующими строками

<LICENSE PRODUCT="WebLogic" IP="127.0.0.1"
      UNITS="1" EXPIRATION="never" KEY="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" />

Игра с приложением

Модифицирование KEY приводит к LicenseKeyInvalidException, которое находится в классе, располагающемся в файле c:\weblogic\classes\weblogic\common\LicenseKeyInvalidException.class

Сейчас мы должны найти все файлы, которые обращаются к этому классу, то есть содержать строку LicenseKeyInvalidException, что дает нам c:\weblogic\classes\weblogic\t3\svr\T3Srvr.class.

Декомпилируем его с помощью DJ Java Decompiler и получаем

private void handleLicenseException(LicenseException licenseexception)
{
.......
if(licenseexception.getClass().isInstance(new LicenseKeyInvalidException()))
 {
 if(licenseExceptions == null)
  {
  licenseExceptions = new StringBuffer("");
  licenseExceptions.append("\n" + licenseexception.getMessage());
  return;
  }
 licenseExceptions.append("\nAnd also: " + licenseexception.getMessage());
 }
 .......
}

после чего вызывается

private boolean checkAccess()
{
.......
Object obj = null;
try
        {
        FileInfo fileinfo = ServerUtil.findFile("WebLogic" + logSuffix());
        fileinfo.checkAccess(0, logVal());
        x86 = true;
        }
catch(LicenseException licenseexception)
        {
        handleLicenseException(licenseexception);
        }
.......
}

после чего вызывается

public static void main(String args[], String s, String s1)
{
.......
t3srvr.checkAccess();
.......
}

Также в c:\weblogic\classes\weblogic\common\internal\FileInfo.class встречаем

public void checkAccess(int i, String s) вызывается исключение LicenseException
{
.......
if(!getFullName(s).equals(getPermissions()))
 throw new LicenseKeyInvalidException("License key incorrect for "
 + toString());
.......
else
        return;
}

Пачкаем руки

Хоть и не очень сильно.

Один из наиболее часто применяемых методов - это не вызывать checkAccess в main, хотя также можно не вызывать исключение LicenseException в fileinfo.checkAccess или узнать, какой метод сохраняет KEY.

Благодаря JAD'у мы можем отредактировать полученный исходный код: удаляем вызов checkAccess и компилируем новый T3Srvr.java.

Делайте так:

jad -d T3Srvr.class > T3Srvr.java
комментируем строку 't3srvr.checkAccess();'
javac T3Srvr.java
запускаем программу

И вуаля! Код работает.

Если, в силу какой-либо причины, сгенерированный JAD'ом код нельзя скомпилировать, остается другой способ: дизассемблирование вручную. Нам нужно избежать вызова checkAccess.

Дизассемблирование с помощью D-java или javap дает:

Method public static void main(String[],String,String)
 0 iconst_0
 1 istore_3
 2 invokestatic #595 <Method weblogic.kernel.Kernel.setIsServer():void>
 5 new #353 <Class weblogic.t3.srvr.T3Srvr>
 8 dup
 9 invokespecial #678 <Method weblogic.t3.srvr.T3Srvr.<init>():void>
12 putstatic #732 <Field weblogic.t3.srvr.T3Srvr.theT3Server:
                         weblogic.t3.srvr.T3Srvr>
15 sipush 26160
18 invokestatic #491 <Method weblogic.html.HtmlElement.setAnchorMode(int):
                             void>
21 invokestatic #525 <Method weblogic.t3.srvr.T3Srvr.getT3Srvr():
                             weblogic.t3.srvr.T3Srvr>
24 astore local4
26 aload local4
28 aload_1
29 putfield #435 <Field weblogic.t3.srvr.T3Srvr.logval:String>
32 aload local4
34 aload_2
35 putfield #805 <Field weblogic.t3.srvr.T3Srvr.logsuffix:String>
38 invokestatic #804 <Method weblogic.t3.srvr.T3Srvr.configure():
                             weblogic.t3.services.Config>
41 pop
42 aload local4
44 invokespecial #485 <Method weblogic.t3.srvr.T3Srvr.checkAccess():boolean>
47 pop
48 aload local4
50 invokevirtual #650 <Method weblogic.t3.srvr.T3Srvr.start():void>
53 goto 70
.......

Вызов, который нам нужно избежать, находится в строке 44. У инструкции invokespecial опкод 0xB7, а 485=0x01E5. Посему загружаем T3Srvr.class в наш любимый шестнадцатиричный редактор, ищем последовательность B701E5, которая оказывается по смещению 8B27 и заменяем ее на 000000 (три NOP'а). И остается перекомпилировать.

Другой пример

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

Одной из таких программ является Jindent, который можно получить на www.jindent.de. Шароварная версия неудобна, так как не форматирует файлы объемом более 400 строк. Мы попытаемся убрать это ограничение.

Программа вызывается следующим образом:

java -jar Jindent.jar Jindent MiClase

но так как в MiClase.java больше 400 строк, то получаем ошибку

Error: ".\MiClase.java" exceeds 400 lines of code.
Parsing terminated.

Это весьма огорчительно.

Код находится в файле Jindent.jar, который, как нам хорошо известно, является не более чем Java ARchive, поэтому мы можем использовать jar -xvf Jindent.jar. Переходив в директорию jindent\, где находятся все архивы. Ищем строку "exceeds 400 lines of code", но не находим. Ищем "exceeds" и снова не находим.

В чем же дело? По-видимому, код был прогнан через обфускатор. У нас есть архивы под названием a.class, b.class, c.class, ..., чье название нам никак не помогает, а просмотр каждого класса будет весьма утомительным.

Распаковываем файлы и пишем

java Jindent MiClase.java

Чтобы получить немного больше информации пишем

java -verbose:class Jindent MiClase.java

.......
[Loaded java.awt.LightweightDispatcher$2 from c:\jdk1.3\JAVA2\lib\rt.jar]
[Loaded sun.awt.ScreenUpdater from c:\jdk1.3\JAVA2\lib\rt.jar]
[Loaded sun.awt.ScreenUpdater$1 from c:\jdk1.3\JAVA2\lib\rt.jar]

Parsing from file  ".\MiClase.java".
[Loaded jindent.m]
[Loaded java.awt.geom.Rectangle2D$Double from c:\jdk1.3\JAVA2\lib\rt.jar]
[Loaded java.awt.geom.GeneralPath from c:\jdk1.3\JAVA2\lib\rt.jar]

[Loaded jindent.y]

Error: ".\MiClase.java" exceeds 400 lines of code.
Parsing terminated.

Похоже, что m.class отвечает за загрузку файла, а класс y.class жалуется на превышение лимита. С помощью нашего друга JAD'а декомпилируем m.class. У нас есть некоторое количество функций с разными именами. Например, у нас есть 15 функций с именем a, но с различным количеством и типом аргументов. Это так называемые перегруженные методы, что для нас не слишком хорошо. В частности, последняя функция:

private static String a(String s1)
{
        char ac[];
        int i1;
        int j1;
        ac = s1.toCharArray();
        i1 = ac.length;
        j1 = 0;
        goto _L1
_L9:
        ac;
        j1;
        JVM INSTR dup2 ;
        JVM INSTR caload ;
        j1 % 5;
        JVM INSTR tableswitch 0 3: default 76
    //          0 52
    //          1 58
    //          2 64
    //          3 70;
        goto _L2 _L3 _L4 _L5 _L6
_L3:
        0x30;
        goto _L7
_L4:
        102;
        goto _L7
_L5:
        9;
        goto _L7
_L6:
        80;
        goto _L7
_L2:
        44;
_L7:
        JVM INSTR ixor ;
        (char);
        JVM INSTR castore ;
        j1++;
_L1:
        if(j1 < i1) goto _L9; else goto _L8
_L8:
        return new String(ac);
}

Как видим, опкоды невозможно перекомпилировать с помощью простого вызова javac. Но так как они закоментированы, это не важно. Просто изменяем последнюю функцию в файле m.java следующим образом:

private static String a(String s1)
{
        return new String("funcion   a   ha sido llamada "+s1);
}

Теперь, поскольку теперь не вызывается ничего из того, что вызывалось раньше, нам не о чем беспокоиться.

И помещаем в каждый методод следующую строку

System.out.println("estoy en la funcion xxxx ");

в каждый метод.

Запускаем программу и видим, что последние строки выполняются вместе с методом o() и после метода b().

    public void b()
    {
        System.out.println("estoy en la funcion b() ");
        r = null;
        b = null;
        k = null;
        l = null;
    }

Похоже, что очищаются какие-то указатели. Ничего особенного.

С другой стороны

    public int o()
    {
        System.out.println("estoy en la funcion o() ");
        int i1 = 0;
        for(int j1 = 0; j1 < s; j1++)
            if(r[j1] == '\n')
                i1++;
        System.out.println("estoy en la funcion o() . Retorno i1="+i1);
        return i1;
    }

Запускаем программу и в результатет получаем

        "estoy en la funcion o() . Retorno i1=2000"

Случайно в нашем файле оказывается 2000 строк. Все становится ясным: функция o() возвращает количество строк в файле.

В редких случаях эта функция не вызывается из этого модуля. Тогда делаем printStackTrace(), пока не будет возвращен i1, получаем:

        at jindent.m.o(m.java, Compiled Code)
        at jindent.JindentParser.a(JindentParser.java)
        at jindent.JindentParser.c(JindentParser.java, Compiled Code)
        at jindent.JindentParser.d(JindentParser.java)
        at jindent.JindentParser.invoke(JindentParser.java, Compiled Code)
        at Jindent.main(Jindent.java)

До того, как пропатчить, исследуем программу еще немного.

Дизассемблируем JindentParser.class и ищем, где происходит первый вызов o().

    void i(k k1)
    {
        if(k1.n())
        {
            int i1 = k1.o();
            C.setVariable(E("p\016]xvl?P}a"), k1.p());
            for(int j1 = 0; j1 < i1; j1++)
            {
                String s1 = k1.i(j1);
                s1 = e(s1);
                g(s1);
            }
        }
    }

В цикле переменной i1 присваивается результат o(). Пока лучше не патчить o() (В любом случае, мы несколько смущены. Откуда мы знаем, что k1 - это объект типа m?).

второй вызов:

строка 11255
void a(Reader reader, Writer writer) throws JindentException
{
mW();
cA = new m(reader, 1, 1, e, u);
.........
if(cA.o() > bi - 512)
 {
 bY();
 throw new JindentException(E("K\005Yxp\"\016Qnag\017Z-02[\taml\016Z-kdKJb`gE"));
 }
.........

Это очень интересно. cA - это один экземпляр объекта типа m, и где-то в конце вызывается m.o().

        if(cA.o() > bi - 512)

где bi равно ?

Переходим к определению переменных и (нам везет) находим константу bi:

  bi = 912;

Ах, теперь все понятно: bi - 512 = 912 - 512 = 400, все просто!

третий вызов (строка 8158):

void a(String s1, String s2, String s3) throws JindentException
{
Object obj = null;
.........
cA = new m(filereader, 1, 1, e, u);
if(cA.o() > bi - 512)
 {
 bY();
 throw new JindentException("\"" + s2 + E(" KLugg\016M~$6[\031-hk\005L~$m\r\tnkf\016\007"));
 }
.........

Теперь просто необходимо пропатчить значение bi JindentParset.class, чтобы утилита принимала файлы размером до 65535-512.

Другая техника состоит в использовании отладчика. В JDK входит один, хотя он предоставляет большого количества сервисов. Большую часть информации он выдает, если класс был скомпилирован с опцией отладки.

С другой стороны, две наиболее используемые среды разработки, VisualAge и VisualCafe имеют возможность посмотреть, что делает класс без необходимости иметь исходный код.

Стоит упомянуть о SUN'овском Hotspot'е, прекрасой JVM с JIT'овским компилятором с прилагающимся исходным кодом. Он может не просто выполнять инструкции, но создавать файл, который затем запустит.

Туда включен графический пошаговый отладчик.

Есть еще много тем, касающихся Java, которых было бы интересно коснуться.

Например:

  • реализация JVM для новичков
  • функционирование Sandbox и как подгружать классы с помощью другого ClassLoader'а
  • генерация java-кода на лету для повышения безопасности
  • вызовы DLL и библиотек S.O. с помощью jni
  • сервлеты, RMI, EJB

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

Если предсказания известных аналитиков софтверного рынка оправдаются, 90% процентов приложений будут интегрированы с Web, а это значит, что они будут переведены на Java (автор статьи не подозревал, что Miscrosoft замутит .Net и связанную с ней ересь :) - прим.пер.). Как бы то ни было, можно назвать такие примеры как Oracle, SmartCard, Vantive, Lotus и IBM.

  [C] FCA00000 / SET#25, пер. Aquila