blob: 09eec27a1ce484dff2f8f19f8c33d16af4a419e3 [file] [log] [blame]
Brian Muramatsu0748ed52011-11-29 14:43:10 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package dxconvext;
18
19import dxconvext.util.FileUtils;
20
21import java.io.BufferedOutputStream;
22import java.io.BufferedReader;
23import java.io.ByteArrayInputStream;
24import java.io.ByteArrayOutputStream;
25import java.io.File;
26import java.io.FileInputStream;
27import java.io.FileNotFoundException;
28import java.io.FileOutputStream;
29import java.io.IOException;
30import java.io.InputStreamReader;
31import java.io.OutputStream;
32import java.io.Reader;
33import java.io.UnsupportedEncodingException;
34import java.security.DigestException;
35import java.security.MessageDigest;
36import java.security.NoSuchAlgorithmException;
37import java.util.zip.Adler32;
38
39public class ClassFileAssembler {
40
41 /**
42 * @param args
43 */
44 public static void main(String[] args) {
45 ClassFileAssembler cfa = new ClassFileAssembler();
46 cfa.run(args);
47 }
48
49 private void run(String[] args) {
50 // this class can be used to generate .class files that are somehow
51 // damaged in order to test the dalvik vm verifier.
52 // The input is a .cfh (class file hex) file.
53 // The output is a java vm .class file.
54 // The .cfh files can be generated as follows:
55 // 1. create the initial .cfh file from an existing .class files by using
56 // the ClassFileParser
57 // 2. modify some bytes to damage the structure of the .class file in a
58 // way that would not be possible with e.g. jasmin (otherwise you are
59 // better off using jasmin).
60 // Uncomment the original bytes, and write "MOD:" meaning a modified
61 // entry (with the original commented out)
62 //
63 // Use the ClassFileAssembler to generate the .class file.
64 // this class here simply takes all non-comment lines from the .cfh
65 // file, parses them as hex values and writes the bytes to the class file
66 File cfhF = new File(args[0]);
67 if (!cfhF.getName().endsWith(".cfh") &&
68 !cfhF.getName().endsWith(".dfh")) {
69 System.out.println("file must be a .cfh or .dfh file, and its filename end with .cfh or .dfh");
70 return;
71 }
72
73 String outBase = args[1];
74
75 boolean isDex = cfhF.getName().endsWith(".dfh");
76
77 byte[] cfhbytes = FileUtils.readFile(cfhF);
78 ByteArrayInputStream bais = new ByteArrayInputStream(cfhbytes);
79 // encoding should not matter, since we are skipping comment lines and parsing
80 try {
81 // get the package name
82 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfhF)));
83 String firstLine = br.readLine();
84 br.close();
85 String classHdr = "//@class:";
86 String dexHdr = "// Processing '";
87 String hdr;
88 if(isDex)
89 hdr = dexHdr;
90 else
91 hdr = classHdr;
92
93 if (!firstLine.startsWith(hdr)) throw new RuntimeException("wrong format:"+firstLine +" isDex=" + isDex);
94 String tFile;
95 if(isDex) {
96 tFile = outBase + "/classes.dex";
97 } else {
98 String classO = firstLine.substring(hdr.length()).trim();
99 tFile = outBase +"/"+classO+".class";
100 }
101 File outFile = new File(tFile);
102 System.out.println("outfile:" + outFile);
103 String mkdir = tFile.substring(0, tFile.lastIndexOf("/"));
104 new File(mkdir).mkdirs();
105
106 Reader r = new InputStreamReader(bais,"utf-8");
107 OutputStream os = new FileOutputStream(outFile);
108 BufferedOutputStream bos = new BufferedOutputStream(os);
109 writeClassFile(r, bos, isDex);
110 bos.close();
111 } catch (UnsupportedEncodingException e) {
112 throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
113 } catch (FileNotFoundException e) {
114 throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
115 } catch (IOException e) {
116 throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e);
117 }
118 }
119
120 /**
121 * Calculates the signature for the <code>.dex</code> file in the
122 * given array, and modify the array to contain it.
123 *
124 * Originally from com.android.dx.dex.file.DexFile.
125 *
126 * @param bytes non-null; the bytes of the file
127 */
128 private void calcSignature(byte[] bytes) {
129 MessageDigest md;
130
131 try {
132 md = MessageDigest.getInstance("SHA-1");
133 } catch (NoSuchAlgorithmException ex) {
134 throw new RuntimeException(ex);
135 }
136
137 md.update(bytes, 32, bytes.length - 32);
138
139 try {
140 int amt = md.digest(bytes, 12, 20);
141 if (amt != 20) {
142 throw new RuntimeException("unexpected digest write: " + amt +
143 " bytes");
144 }
145 } catch (DigestException ex) {
146 throw new RuntimeException(ex);
147 }
148 }
149
150 /**
151 * Calculates the checksum for the <code>.dex</code> file in the
152 * given array, and modify the array to contain it.
153 *
154 * Originally from com.android.dx.dex.file.DexFile.
155 *
156 * @param bytes non-null; the bytes of the file
157 */
158 private void calcChecksum(byte[] bytes) {
159 Adler32 a32 = new Adler32();
160
161 a32.update(bytes, 12, bytes.length - 12);
162
163 int sum = (int) a32.getValue();
164
165 bytes[8] = (byte) sum;
166 bytes[9] = (byte) (sum >> 8);
167 bytes[10] = (byte) (sum >> 16);
168 bytes[11] = (byte) (sum >> 24);
169 }
170
171 public void writeClassFile(Reader r, OutputStream rOs, boolean isDex) {
172 ByteArrayOutputStream baos = new ByteArrayOutputStream(8192);
173 BufferedReader br = new BufferedReader(r);
174 String line;
175 String secondLine = null;
176 int lineCnt = 0;
177 try {
178 while ((line = br.readLine()) != null) {
179 if (isDex && lineCnt++ == 1) {
180 secondLine = line;
181 }
182 // skip it if it is a comment
183 if (!line.trim().startsWith("//")) {
184 // we have a row like " ae 08 21 ff" etc.
185 String[] parts = line.split("\\s+");
186 for (int i = 0; i < parts.length; i++) {
187 String part = parts[i].trim();
188 if (!part.equals("")) {
189 int res = Integer.parseInt(part, 16);
190 baos.write(res);
191 }
192 }
193 }
194 }
195
196 // now for dex, update the checksum and the signature.
197 // special case:
198 // for two tests (currently T_f1_9.dfh and T_f1_10.dfh), we need
199 // to keep the checksum or the signature, respectively.
200 byte[] outBytes = baos.toByteArray();
201 if (isDex) {
202 boolean leaveChecksum = secondLine.contains("//@leaveChecksum");
203 boolean leaveSignature= secondLine.contains("//@leaveSignature");
204 // update checksum and signature for dex file
205 if(!leaveSignature)
206 calcSignature(outBytes);
207 if(!leaveChecksum)
208 calcChecksum(outBytes);
209 }
210 rOs.write(outBytes);
211 rOs.close();
212 } catch (IOException e) {
213 throw new RuntimeException("problem while writing file",e);
214 }
215 }
216
217}