/*
* 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 .dex
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 .dex
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);
}
}
}