/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dxconvext; import dxconvext.util.FileUtils; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.security.DigestException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.zip.Adler32; public class ClassFileAssembler { /** * @param args */ public static void main(String[] args) { ClassFileAssembler cfa = new ClassFileAssembler(); cfa.run(args); } private void run(String[] args) { // this class can be used to generate .class files that are somehow // damaged in order to test the dalvik vm verifier. // The input is a .cfh (class file hex) file. // The output is a java vm .class file. // The .cfh files can be generated as follows: // 1. create the initial .cfh file from an existing .class files by using // the ClassFileParser // 2. modify some bytes to damage the structure of the .class file in a // way that would not be possible with e.g. jasmin (otherwise you are // better off using jasmin). // Uncomment the original bytes, and write "MOD:" meaning a modified // entry (with the original commented out) // // Use the ClassFileAssembler to generate the .class file. // this class here simply takes all non-comment lines from the .cfh // file, parses them as hex values and writes the bytes to the class file File cfhF = new File(args[0]); if (!cfhF.getName().endsWith(".cfh") && !cfhF.getName().endsWith(".dfh")) { System.out.println("file must be a .cfh or .dfh file, and its filename end with .cfh or .dfh"); return; } String outBase = args[1]; boolean isDex = cfhF.getName().endsWith(".dfh"); byte[] cfhbytes = FileUtils.readFile(cfhF); ByteArrayInputStream bais = new ByteArrayInputStream(cfhbytes); // encoding should not matter, since we are skipping comment lines and parsing try { // get the package name BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(cfhF))); String firstLine = br.readLine(); br.close(); String classHdr = "//@class:"; String dexHdr = "// Processing '"; String hdr; if(isDex) hdr = dexHdr; else hdr = classHdr; if (!firstLine.startsWith(hdr)) throw new RuntimeException("wrong format:"+firstLine +" isDex=" + isDex); String tFile; if(isDex) { tFile = outBase + "/classes.dex"; } else { String classO = firstLine.substring(hdr.length()).trim(); tFile = outBase +"/"+classO+".class"; } File outFile = new File(tFile); System.out.println("outfile:" + outFile); String mkdir = tFile.substring(0, tFile.lastIndexOf("/")); new File(mkdir).mkdirs(); Reader r = new InputStreamReader(bais,"utf-8"); OutputStream os = new FileOutputStream(outFile); BufferedOutputStream bos = new BufferedOutputStream(os); writeClassFile(r, bos, isDex); bos.close(); } catch (UnsupportedEncodingException e) { throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e); } catch (FileNotFoundException e) { throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e); } catch (IOException e) { throw new RuntimeException("problem while parsing .dfh or .cfh file: "+cfhF.getAbsolutePath(), e); } } /** * Calculates the signature for the <code>.dex</code> file in the * given array, and modify the array to contain it. * * Originally from com.android.dx.dex.file.DexFile. * * @param bytes non-null; the bytes of the file */ private void calcSignature(byte[] bytes) { MessageDigest md; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException ex) { throw new RuntimeException(ex); } md.update(bytes, 32, bytes.length - 32); try { int amt = md.digest(bytes, 12, 20); if (amt != 20) { throw new RuntimeException("unexpected digest write: " + amt + " bytes"); } } catch (DigestException ex) { throw new RuntimeException(ex); } } /** * Calculates the checksum for the <code>.dex</code> file in the * given array, and modify the array to contain it. * * Originally from com.android.dx.dex.file.DexFile. * * @param bytes non-null; the bytes of the file */ private void calcChecksum(byte[] bytes) { Adler32 a32 = new Adler32(); a32.update(bytes, 12, bytes.length - 12); int sum = (int) a32.getValue(); bytes[8] = (byte) sum; bytes[9] = (byte) (sum >> 8); bytes[10] = (byte) (sum >> 16); bytes[11] = (byte) (sum >> 24); } public void writeClassFile(Reader r, OutputStream rOs, boolean isDex) { ByteArrayOutputStream baos = new ByteArrayOutputStream(8192); BufferedReader br = new BufferedReader(r); String line; String secondLine = null; int lineCnt = 0; try { while ((line = br.readLine()) != null) { if (isDex && lineCnt++ == 1) { secondLine = line; } // skip it if it is a comment if (!line.trim().startsWith("//")) { // we have a row like " ae 08 21 ff" etc. String[] parts = line.split("\\s+"); for (int i = 0; i < parts.length; i++) { String part = parts[i].trim(); if (!part.equals("")) { int res = Integer.parseInt(part, 16); baos.write(res); } } } } // now for dex, update the checksum and the signature. // special case: // for two tests (currently T_f1_9.dfh and T_f1_10.dfh), we need // to keep the checksum or the signature, respectively. byte[] outBytes = baos.toByteArray(); if (isDex) { boolean leaveChecksum = secondLine.contains("//@leaveChecksum"); boolean leaveSignature= secondLine.contains("//@leaveSignature"); // update checksum and signature for dex file if(!leaveSignature) calcSignature(outBytes); if(!leaveChecksum) calcChecksum(outBytes); } rOs.write(outBytes); rOs.close(); } catch (IOException e) { throw new RuntimeException("problem while writing file",e); } } }