main.java revision 1c56c7e7507dc24ae1ed2f693c793d94df814c76
1/*
2 * Copyright (C) 2007 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 org.jf.baksmali;
18
19import org.apache.commons.cli.*;
20import org.jf.baksmali.Deodex.Deodexerant;
21import org.jf.dexlib.DexFile;
22
23import java.io.File;
24import java.io.InputStream;
25import java.io.IOException;
26import java.util.Properties;
27
28public class main {
29
30    public static final String VERSION;
31
32    private static final Options options;
33
34    public static final int ALL = 1;
35    public static final int ALLPRE = 2;
36    public static final int ALLPOST = 4;
37    public static final int ARGS = 8;
38    public static final int DEST = 16;
39    public static final int MERGE = 32;
40    public static final int FULLMERGE = 64;
41
42    static {
43        options = new Options();
44        buildOptions();
45
46        InputStream templateStream = baksmali.class.getClassLoader().getResourceAsStream("baksmali.properties");
47        Properties properties = new Properties();
48        String version = "(unknown)";
49        try {
50            properties.load(templateStream);
51            version = properties.getProperty("application.version");
52        } catch (IOException ex) {
53        }
54        VERSION = version;
55    }
56
57    /**
58     * This class is uninstantiable.
59     */
60    private main() {
61    }
62
63    /**
64     * Run!
65     */
66    public static void main(String[] args) {
67        CommandLineParser parser = new PosixParser();
68        CommandLine commandLine;
69
70        try {
71            commandLine = parser.parse(options, args);
72        } catch (ParseException ex) {
73            usage();
74            return;
75        }
76
77        boolean disassemble = true;
78        boolean doDump = false;
79        boolean write = false;
80        boolean sort = false;
81        boolean fixRegisters = false;
82        boolean noParameterRegisters = false;
83        boolean useLocalsDirective = false;
84        boolean useSequentialLabels = false;
85        boolean outputDebugInfo = true;
86
87        int registerInfo = 0;
88
89        String outputDirectory = "out";
90        String dumpFileName = null;
91        String outputDexFileName = null;
92        String inputDexFileName = null;
93        String deodexerantHost = null;
94        String bootClassPath = "core.jar:ext.jar:framework.jar:android.policy.jar:services.jar";
95        int deodexerantPort = 0;
96
97        String[] remainingArgs = commandLine.getArgs();
98
99        if (commandLine.hasOption("v")) {
100            version();
101            return;
102        }
103
104        if (commandLine.hasOption("?")) {
105            usage();
106            return;
107        }
108
109        if (remainingArgs.length != 1) {
110            usage();
111            return;
112        }
113
114        inputDexFileName = remainingArgs[0];
115
116        if (commandLine.hasOption("n")) {
117            disassemble = false;
118        }
119
120        if (commandLine.hasOption("d")) {
121            doDump = true;
122            dumpFileName = commandLine.getOptionValue("d", inputDexFileName + ".dump");
123        }
124
125        if (commandLine.hasOption("w")) {
126            write = true;
127            outputDexFileName = commandLine.getOptionValue("w");
128        }
129
130        if (commandLine.hasOption("o")) {
131            outputDirectory = commandLine.getOptionValue("o");
132        }
133
134        if (commandLine.hasOption("s")) {
135            sort = true;
136        }
137
138        if (commandLine.hasOption("f")) {
139            fixRegisters = true;
140        }
141
142        if (commandLine.hasOption("p")) {
143            noParameterRegisters = true;
144        }
145
146        if (commandLine.hasOption("l")) {
147            useLocalsDirective = true;
148        }
149
150        if (commandLine.hasOption("i")) {
151            useSequentialLabels = true;
152        }
153
154        if (commandLine.hasOption("b")) {
155            outputDebugInfo = false;
156        }
157
158        if (commandLine.hasOption("r")) {
159            String[] values = commandLine.getOptionValues('r');
160
161            if (values == null || values.length == 0) {
162                registerInfo = ARGS | DEST | MERGE;
163            } else {
164                for (String value: values) {
165                    if (value.equalsIgnoreCase("ALL")) {
166                        registerInfo |= ALL;
167                    } else if (value.equalsIgnoreCase("ALLPRE")) {
168                        registerInfo |= ALLPRE;
169                    } else if (value.equalsIgnoreCase("ALLPOST")) {
170                        registerInfo |= ALLPOST;
171                    } else if (value.equalsIgnoreCase("ARGS")) {
172                        registerInfo |= ARGS;
173                    } else if (value.equalsIgnoreCase("DEST")) {
174                        registerInfo |= DEST;
175                    } else if (value.equalsIgnoreCase("MERGE")) {
176                        registerInfo |= MERGE;
177                    } else if (value.equalsIgnoreCase("FULLMERGE")) {
178                        registerInfo |= FULLMERGE;
179                    } else {
180                        usage();
181                        return;
182                    }
183                }
184
185                if ((registerInfo & FULLMERGE) != 0) {
186                    registerInfo &= ~MERGE;
187                }
188            }
189        }
190
191        if (commandLine.hasOption("c")) {
192            bootClassPath = commandLine.getOptionValue("c");
193        }
194
195        if (commandLine.hasOption("x")) {
196            String deodexerantAddress = commandLine.getOptionValue("x");
197            String[] parts = deodexerantAddress.split(":");
198            if (parts.length != 2) {
199                System.err.println("Invalid deodexerant address. Expecting :<port> or <host>:<port>");
200                System.exit(1);
201            }
202
203            deodexerantHost = parts[0];
204            if (deodexerantHost.length() == 0) {
205                deodexerantHost = "localhost";
206            }
207            try {
208                deodexerantPort = Integer.parseInt(parts[1]);
209            } catch (NumberFormatException ex) {
210                System.err.println("Invalid port \"" + deodexerantPort + "\" for deodexerant address");
211                System.exit(1);
212            }
213        }
214
215        try {
216            File dexFileFile = new File(inputDexFileName);
217            if (!dexFileFile.exists()) {
218                System.err.println("Can't find the file " + inputDexFileName);
219                System.exit(1);
220            }
221
222            //Read in and parse the dex file
223            DexFile dexFile = new DexFile(dexFileFile, !fixRegisters, false);
224
225            Deodexerant deodexerant = null;
226
227
228            if (deodexerantHost != null) {
229                if (!dexFile.isOdex()) {
230                    System.err.println("-x cannot be used with a normal dex file. Ignoring -x");
231                }
232                deodexerant = new Deodexerant(dexFile, deodexerantHost, deodexerantPort);
233            }
234
235            if (dexFile.isOdex()) {
236                if (doDump) {
237                    System.err.println("-d cannot be used with on odex file. Ignoring -d");
238                }
239                if (write) {
240                    System.err.println("-w cannot be used with an odex file. Ignoring -w");
241                }
242                if (deodexerant == null) {
243                    System.err.println("Warning: You are disassembling an odex file without deodexing it. You");
244                    System.err.println("won't be able to re-assemble the results unless you use deodexerant, and");
245                    System.err.println("the -x option for baksmali");
246                }
247            }
248
249            if (disassemble) {
250                baksmali.disassembleDexFile(dexFile, deodexerant, outputDirectory, bootClassPath, noParameterRegisters,
251                        useLocalsDirective, useSequentialLabels, outputDebugInfo, registerInfo);
252            }
253
254            if ((doDump || write) && !dexFile.isOdex()) {
255                try
256                {
257                    dump.dump(dexFile, dumpFileName, outputDexFileName, sort);
258                }catch (IOException ex) {
259                    System.err.println("Error occured while writing dump file");
260                    ex.printStackTrace();
261                }
262            }
263        } catch (RuntimeException ex) {
264            System.err.println("\n\nUNEXPECTED TOP-LEVEL EXCEPTION:");
265            ex.printStackTrace();
266            System.exit(1);
267        } catch (Throwable ex) {
268            System.err.println("\n\nUNEXPECTED TOP-LEVEL ERROR:");
269            ex.printStackTrace();
270            System.exit(1);
271        }
272    }
273
274    /**
275     * Prints the usage message.
276     */
277    private static void usage() {
278        HelpFormatter formatter = new HelpFormatter();
279        formatter.setWidth(100);
280        formatter.printHelp("java -jar baksmali.jar [options] <dex-file>",
281                "disassembles and/or dumps a dex file", options, "");
282    }
283
284    /**
285     * Prints the version message.
286     */
287    private static void version() {
288        System.out.println("baksmali " + VERSION + " (http://smali.googlecode.com)");
289        System.out.println("Copyright (C) 2009 Ben Gruver");
290        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
291        System.exit(0);
292    }
293
294    private static void buildOptions() {
295        Option versionOption = OptionBuilder.withLongOpt("version")
296                .withDescription("prints the version then exits")
297                .create("v");
298
299        Option helpOption = OptionBuilder.withLongOpt("help")
300                .withDescription("prints the help message then exits")
301                .create("?");
302
303        Option noDisassemblyOption = OptionBuilder.withLongOpt("no-disassembly")
304                .withDescription("suppresses the output of the disassembly")
305                .create("n");
306
307        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
308                .withDescription("dumps the given dex file into a single annotated dump file named FILE" +
309                        " (<dexfile>.dump by default), along with the normal disassembly.")
310                .hasOptionalArg()
311                .withArgName("FILE")
312                .create("d");
313
314        Option writeDexOption = OptionBuilder.withLongOpt("write-dex")
315                .withDescription("additionally rewrites the input dex file to FILE")
316                .hasArg()
317                .withArgName("FILE")
318                .create("w");
319
320        Option outputDirOption = OptionBuilder.withLongOpt("output")
321                .withDescription("the directory where the disassembled files will be placed. The default is out")
322                .hasArg()
323                .withArgName("DIR")
324                .create("o");
325
326        Option sortOption = OptionBuilder.withLongOpt("sort")
327                .withDescription("sort the items in the dex file into a canonical order before dumping/writing")
328                .create("s");
329
330        Option fixSignedRegisterOption = OptionBuilder.withLongOpt("fix-signed-registers")
331                .withDescription("when dumping or rewriting, fix any registers in the debug info that are encoded as" +
332                        " a signed value")
333                .create("f");
334
335        Option noParameterRegistersOption = OptionBuilder.withLongOpt("no-parameter-registers")
336                .withDescription("use the v<n> syntax instead of the p<n> syntax for registers mapped to method" +
337                        " parameters")
338                .create("p");
339
340        Option deodexerantOption = OptionBuilder.withLongOpt("deodexerant")
341                .withDescription("connect to deodexerant on the specified HOST:PORT, and deodex the input odex"
342                        + " file. This option is ignored if the input file is a dex file instead of an odex file")
343                .hasArg()
344                .withArgName("HOST:PORT")
345                .create("x");
346
347        Option useLocalsOption = OptionBuilder.withLongOpt("use-locals")
348                .withDescription("output the .locals directive with the number of non-parameter registers, rather" +
349                        " than the .register directive with the total number of register")
350                .create("l");
351
352        Option sequentialLabelsOption = OptionBuilder.withLongOpt("sequential-labels")
353                .withDescription("create label names using a sequential numbering scheme per label type, rather than " +
354                        "using the bytecode address")
355                .create("q");
356
357        Option noDebugInfoOption = OptionBuilder.withLongOpt("no-debug-info")
358                .withDescription("don't write out debug info (.local, .param, .line, etc.)")
359                .create("b");
360
361        Option registerInfoOption = OptionBuilder.withLongOpt("register-info")
362                .hasOptionalArgs()
363                .withArgName("REGISTER_INFO_TYPES")
364                .withValueSeparator(',')
365                .withDescription("print the specificed type(s) of register information for each instruction. " +
366                        "\"ARGS,DEST,MERGE\" is the default if no types are specified.\nValid values are:\nALL: all " +
367                        "pre- and post-instruction registers.\nALLPRE: all pre-instruction registers\nALLPOST: all " +
368                        "post-instruction registers\nARGS: any pre-instruction registers used as arguments to the " +
369                        "instruction\nDEST: the post-instruction destination register, if any\nMERGE: Any " +
370                        "pre-instruction register has been merged from more than 1 different post-instruction " +
371                        "register from its predecessors\nFULLMERGE: For each register that would be printed by " +
372                        "MERGE, also show the incoming register types that were merged")
373                .create("r");
374
375        Option classPathOption = OptionBuilder.withLongOpt("bootclasspath")
376                .withDescription("the bootclasspath jars to use, for analysis")
377                .hasOptionalArg()
378                .withArgName("BOOTCLASSPATH")
379                .create("c");
380
381        options.addOption(versionOption);
382        options.addOption(helpOption);
383        options.addOption(dumpOption);
384        options.addOption(noDisassemblyOption);
385        options.addOption(writeDexOption);
386        options.addOption(outputDirOption);
387        options.addOption(sortOption);
388        options.addOption(fixSignedRegisterOption);
389        options.addOption(noParameterRegistersOption);
390        options.addOption(deodexerantOption);
391        options.addOption(useLocalsOption);
392        options.addOption(sequentialLabelsOption);
393        options.addOption(noDebugInfoOption);
394        options.addOption(registerInfoOption);
395        options.addOption(classPathOption);
396    }
397}