diff --git a/src/brut/androlib/src/DebugInjector.java b/src/brut/androlib/src/DebugInjector.java new file mode 100644 index 00000000..4dd08bb4 --- /dev/null +++ b/src/brut/androlib/src/DebugInjector.java @@ -0,0 +1,225 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * 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. + * under the License. + */ +package brut.androlib.src; + +import brut.androlib.AndrolibException; +import java.util.ListIterator; +import org.jf.dexlib.Code.Opcode; + +/** + * @author Ryszard Wiśniewski + */ +public class DebugInjector { + + public static void inject(ListIterator it, StringBuilder out) + throws AndrolibException { + new DebugInjector(it, out).inject(); + } + + private DebugInjector(ListIterator it, StringBuilder out) { + mIt = it; + mOut = out; + } + + private void inject() throws AndrolibException { + String definition = nextAndAppend(); + if (definition.contains(" abstract ")) { + nextAndAppend(); + return; + } + injectParameters(definition); + + boolean end = false; + while (!end) { + end = step(); + } + } + + private void injectParameters(String definition) throws AndrolibException { + int pos = definition.indexOf('('); + if (pos == -1) { + throw new AndrolibException(); + } + int pos2 = definition.indexOf(')', pos); + if (pos2 == -1) { + throw new AndrolibException(); + } + String params = definition.substring(pos + 1, pos2); + + int i = definition.contains(" static ") ? 0 : 1; + int argc = TypeName.listFromInternalName(params).size() + i; + while(i < argc) { + mOut.append(".parameter \"p").append(i).append("\"\n"); + i++; + } + } + + private boolean step() { + String line = next(); + if (line.isEmpty()) { + return false; + } + + switch (line.charAt(0)) { + case '#': + return false; + case ':': + append(line); + return false; + case '.': + return processDirective(line); + default: + return processInstruction(line); + } + } + + private boolean processDirective(String line) { + String line2 = line.substring(1); + if ( + line2.startsWith("line ") || + line2.equals("prologue") || + line2.startsWith("local ") || + line2.startsWith("parameter") + ) { + return false; + } + + append(line); + if (line2.equals("end method")) { + return true; + } + if ( + line2.startsWith("annotation ") || + line2.equals("sparse-switch") || + line2.startsWith("packed-switch ") || + line2.startsWith("array-data ") + ) { + while(true) { + line2 = nextAndAppend(); + if (line2.startsWith(".end ")) { + break; + } + } + } + return false; + } + + private boolean processInstruction(String line) { + if (mFirstInstruction) { + mOut.append(".prologue\n"); + mFirstInstruction = false; + } + mOut.append(".line ").append(mIt.nextIndex()).append('\n') + .append(line).append('\n'); + + int pos = line.indexOf(' '); + if (pos == -1) { + return false; + } + Opcode opcode = Opcode.getOpcodeByName(line.substring(0, pos)); + if (! opcode.setsRegister()) { + return false; + } + + int pos2 = line.indexOf(',', pos); + String register = pos2 == -1 ? line.substring(pos + 1) : + line.substring(pos + 1, pos2); + + mOut.append(".local ").append(register).append(", ").append(register) + .append(':').append(getRegisterTypeForOpcode(opcode)).append('\n'); + + return false; + } + + private String getRegisterTypeForOpcode(Opcode opcode) { + switch (opcode.value) { + case (byte)0x0d: // ? + case (byte)0x1a: + case (byte)0x1b: + case (byte)0x1c: // ? + case (byte)0x22: + case (byte)0x23: // ? + case (byte)0xf4: // ? + return "Ljava/lang/Object;"; + case (byte)0x1f: // ? + case (byte)0x20: // ? + return "Z"; + case (byte)0x21: // ? + return "I"; + } + + String name = opcode.name; + int pos = name.lastIndexOf('-'); + if (pos != -1) { + int pos2 = name.indexOf('/'); + String type = pos2 == -1 ? name.substring(pos + 1) : + name.substring(pos + 1, pos2); + + if (type.equals("object")) { + return "Ljava/lang/Object;"; + } + if (type.equals("int")) { + return "I"; + } + if (type.equals("boolean")) { + return "Z"; + } + if (type.equals("float")) { + return "F"; + } + if (type.equals("double")) { + return "D"; + } + if (type.equals("long")) { + return "J"; + } + if (type.equals("byte")) { + return "B"; + } + if (type.equals("char")) { + return "C"; + } + if (type.equals("short")) { + return "S"; + } + if (type.equals("long")) { + return "J"; + } + } + + return "I"; + } + + private String next() { + return mIt.next().trim(); + } + + private String nextAndAppend() { + String line = next(); + append(line); + return line; + } + + private void append(String append) { + mOut.append(append).append('\n'); + } + + private final ListIterator mIt; + private final StringBuilder mOut; + + private boolean mFirstInstruction = true; +} diff --git a/src/brut/androlib/src/SmaliBuilder.java b/src/brut/androlib/src/SmaliBuilder.java new file mode 100644 index 00000000..beee1ed8 --- /dev/null +++ b/src/brut/androlib/src/SmaliBuilder.java @@ -0,0 +1,109 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * 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. + * under the License. + */ +package brut.androlib.src; + +import brut.androlib.AndrolibException; +import brut.androlib.res.util.ExtFile; +import brut.androlib.util.DexFileBuilder; +import brut.directory.DirectoryException; +import java.io.*; +import java.util.List; +import java.util.ListIterator; +import org.apache.commons.io.IOUtils; + +/** + * @author Ryszard Wiśniewski + */ +public class SmaliBuilder { + + public static void build(ExtFile smaliDir, File dexFile, boolean debug) + throws AndrolibException { + new SmaliBuilder(smaliDir, dexFile, debug).build(); + } + + private SmaliBuilder(ExtFile smaliDir, File dexFile, boolean debug) { + mSmaliDir = smaliDir; + mDexFile = dexFile; + mDebug = debug; + } + + private void build() throws AndrolibException { + try { + mDexBuilder = new DexFileBuilder(); + for (String fileName : mSmaliDir.getDirectory().getFiles(true)) { + buildFile(fileName); + } + mDexBuilder.writeTo(mDexFile); + } catch (IOException ex) { + throw new AndrolibException(ex); + } catch (DirectoryException ex) { + throw new AndrolibException(ex); + } + } + + private void buildFile(String fileName) throws AndrolibException, + IOException { + File inFile = new File(mSmaliDir, fileName); + InputStream inStream = new FileInputStream(inFile); + + if (fileName.endsWith(".smali")) { + mDexBuilder.addSmaliFile(inFile); + return; + } + if (! fileName.endsWith(".java")) { + throw new AndrolibException("Unknown file type: " + inFile); + } + + StringBuilder out = new StringBuilder(); + List lines = IOUtils.readLines(inStream); + + if (!mDebug) { + final String[] linesArray = lines.toArray(new String[0]); + for (int i = 2; i < linesArray.length - 2; i++) { + out.append(linesArray[i]).append('\n'); + } + } else { + lines.remove(lines.size() - 1); + lines.remove(lines.size() - 1); + ListIterator it = lines.listIterator(2); + + out.append(".source \"").append(inFile.getName()).append("\"\n"); + while (it.hasNext()) { + String line = it.next().trim(); + if (line.isEmpty() || line.charAt(0) == '#' || + line.startsWith(".source")) { + continue; + } + if (line.startsWith(".method ")) { + it.previous(); + DebugInjector.inject(it, out); + continue; + } + + out.append(line).append('\n'); + } + } + mDexBuilder.addSmaliFile( + IOUtils.toInputStream(out.toString()), fileName); + } + + private final ExtFile mSmaliDir; + private final File mDexFile; + private final boolean mDebug; + + private DexFileBuilder mDexBuilder; +} diff --git a/src/brut/androlib/src/SmaliDecoder.java b/src/brut/androlib/src/SmaliDecoder.java new file mode 100644 index 00000000..22e1493e --- /dev/null +++ b/src/brut/androlib/src/SmaliDecoder.java @@ -0,0 +1,88 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * 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. + * under the License. + */ +package brut.androlib.src; + +import brut.androlib.AndrolibException; +import brut.androlib.src.mod.IndentingWriter; +import java.io.*; +import org.jf.baksmali.Adaptors.ClassDefinition; +import org.jf.baksmali.baksmali; +import org.jf.dexlib.ClassDefItem; +import org.jf.dexlib.DexFile; + +/** + * @author Ryszard Wiśniewski + */ +public class SmaliDecoder { + + public static void decode(File apkFile, File outDir, boolean debug) + throws AndrolibException { + new SmaliDecoder(apkFile, outDir, debug).decode(); + } + + private SmaliDecoder(File apkFile, File outDir, boolean debug) { + mApkFile = apkFile; + mOutDir = outDir; + mDebug = debug; + } + + private void decode() throws AndrolibException { + try { + baksmali.useLocalsDirective = true; + baksmali.useSequentialLabels = true; + + DexFile dexFile = new DexFile(mApkFile); + for (ClassDefItem classDefItem : + dexFile.ClassDefsSection.getItems()) { + decodeClassDefItem(classDefItem); + } + } catch (IOException ex) { + throw new AndrolibException(ex); + } + } + + private void decodeClassDefItem(ClassDefItem classDefItem) + throws AndrolibException, IOException { + TypeName name = TypeName.fromInternalName( + classDefItem.getClassType().getTypeDescriptor()); + File outFile = new File(mOutDir, name.getFilePath(true) + + (mDebug ? ".java" : ".smali")); + + if (outFile.exists()) { + throw new AndrolibException( + "File already exists: " + outFile); + } + outFile.getParentFile().mkdirs(); + + IndentingWriter indentWriter = + new IndentingWriter(new FileWriter(outFile)); + + if (mDebug) { + indentWriter.write("package " + name.package_ + "; class " + + name.getName(true, true) + " {/*\n\n"); + } + new ClassDefinition(classDefItem).writeTo(indentWriter); + if (mDebug) { + indentWriter.write("\n*/}\n"); + } + indentWriter.close(); + } + + private final File mApkFile; + private final File mOutDir; + private final boolean mDebug; +} diff --git a/src/brut/androlib/src/TypeName.java b/src/brut/androlib/src/TypeName.java new file mode 100644 index 00000000..dbce8e1e --- /dev/null +++ b/src/brut/androlib/src/TypeName.java @@ -0,0 +1,205 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * 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. + * under the License. + */ + +package brut.androlib.src; + +import brut.androlib.AndrolibException; +import brut.util.Duo; +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Ryszard Wiśniewski + */ +public class TypeName { + public final String package_; + public final String type; + public final String innerType; + public final int array; + + public TypeName(String type, int array) { + this(null, type, null, array); + } + + public TypeName(String package_, String type, String innerType, int array) { + this.package_ = package_; + this.type = type; + this.innerType = innerType; + this.array = array; + } + + public String getShortenedName() { + return getName("java.lang".equals(package_), isFileOwner()); + } + + public String getName() { + return getName(false, false); + } + + public String getName(boolean excludePackage, boolean separateInner) { + String name = + (package_ == null || excludePackage ? "" : package_ + '.') + + type + + (innerType != null ? (separateInner ? '$' : '.') + innerType : ""); + for (int i = 0; i < array; i++) { + name += "[]"; + } + return name; + } + + public String getJavaFilePath() { + return getFilePath(isFileOwner()) + ".java"; + } + + public String getSmaliFilePath() { + return getFilePath(true) + ".smali"; + } + + public String getFilePath(boolean separateInner) { + return package_.replace('.', File.separatorChar) + File.separatorChar + + type + (separateInner && isInner() ? "$" + innerType : ""); + } + + public boolean isInner() { + return innerType != null; + } + + public boolean isArray() { + return array != 0; + } + + public boolean isFileOwner() { + if (mIsFileOwner == null) { + mIsFileOwner = true; + if (isInner()) { + char c = innerType.charAt(0); + if (c < '0' || c > '9') { + mIsFileOwner = false; + } + } + } + return mIsFileOwner; + } + + @Override + public String toString() { + return getName(); + } + + public static TypeName fromInternalName(String internal) + throws AndrolibException { + Duo duo = fetchFromInternalName(internal); + if (duo.m2 != internal.length()) { + throw new AndrolibException( + "Invalid internal name: " + internal); + } + return duo.m1; + } + + public static List listFromInternalName(String internal) + throws AndrolibException { + List types = new ArrayList(); + while (! internal.isEmpty()) { + Duo duo = fetchFromInternalName(internal); + types.add(duo.m1); + internal = internal.substring(duo.m2); + } + return types; + } + + public static Duo fetchFromInternalName(String internal) + throws AndrolibException { + String origInternal = internal; + int array = 0; + + boolean isArray = false; + do { + if (internal.isEmpty()) { + throw new AndrolibException( + "Invalid internal name: " + origInternal); + } + isArray = internal.charAt(0) == '['; + if (isArray) { + array++; + internal = internal.substring(1); + } + } while (isArray); + + int length = array + 1; + String package_ = null; + String type = null; + String innerType = null; + switch (internal.charAt(0)) { + case 'B': + type = "byte"; + break; + case 'C': + type = "char"; + break; + case 'D': + type = "double"; + break; + case 'F': + type = "float"; + break; + case 'I': + type = "int"; + break; + case 'J': + type = "long"; + break; + case 'S': + type = "short"; + break; + case 'Z': + type = "boolean"; + break; + case 'V': + type = "void"; + break; + case 'L': + int pos = internal.indexOf(';'); + if (pos == -1) { + throw new AndrolibException( + "Invalid internal name: " + origInternal); + } + length += pos; + internal = internal.substring(1, pos); + + pos = internal.lastIndexOf('/'); + package_ = internal.substring(0, pos).replace('/', '.'); + type = internal.substring(pos + 1); + + pos = type.indexOf('$'); + if (pos != -1) { + innerType = type.substring(pos + 1); + type = type.substring(0, pos); + } + break; + default: + throw new AndrolibException( + "Invalid internal name: " + origInternal); + } + + return new Duo( + new TypeName(package_, type, innerType, array), length); + } + + + private Boolean mIsFileOwner; +} diff --git a/src/brut/androlib/src/mod/IndentingWriter.java b/src/brut/androlib/src/mod/IndentingWriter.java new file mode 100644 index 00000000..7b61d9fc --- /dev/null +++ b/src/brut/androlib/src/mod/IndentingWriter.java @@ -0,0 +1,29 @@ +/* + * Copyright 2010 Ryszard Wiśniewski . + * + * 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. + * under the License. + */ +package brut.androlib.src.mod; + +import java.io.Writer; + +/** + * @author Ryszard Wiśniewski + */ +public class IndentingWriter extends org.jf.baksmali.IndentingWriter { + + public IndentingWriter(Writer writer) { + super(writer); + } +}