Home | History | Annotate | Download | only in dxconvext
      1 /*
      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 
     17 package dxconvext;
     18 
     19 import dxconvext.util.FileUtils;
     20 
     21 import java.io.BufferedOutputStream;
     22 import java.io.BufferedReader;
     23 import java.io.ByteArrayInputStream;
     24 import java.io.ByteArrayOutputStream;
     25 import java.io.File;
     26 import java.io.FileInputStream;
     27 import java.io.FileNotFoundException;
     28 import java.io.FileOutputStream;
     29 import java.io.IOException;
     30 import java.io.InputStreamReader;
     31 import java.io.OutputStream;
     32 import java.io.Reader;
     33 import java.io.UnsupportedEncodingException;
     34 import java.security.DigestException;
     35 import java.security.MessageDigest;
     36 import java.security.NoSuchAlgorithmException;
     37 import java.util.zip.Adler32;
     38 
     39 public 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 }
    218