main.java revision 96b803c8821bac22418e48f976adf0132e3d9b24
1/*
2 * [The "BSD licence"]
3 * Copyright (c) 2010 Ben Gruver (JesusFreke)
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 *    notice, this list of conditions and the following disclaimer in the
13 *    documentation and/or other materials provided with the distribution.
14 * 3. The name of the author may not be used to endorse or promote products
15 *    derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29package org.jf.smali;
30
31import org.antlr.runtime.*;
32import org.antlr.runtime.tree.CommonTree;
33import org.antlr.runtime.tree.CommonTreeNodeStream;
34import org.apache.commons.cli.*;
35import org.jf.dexlib.Code.Opcode;
36import org.jf.dexlib.CodeItem;
37import org.jf.dexlib.DexFile;
38import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
39import org.jf.util.ConsoleUtil;
40import org.jf.util.smaliHelpFormatter;
41
42import java.io.*;
43import java.nio.CharBuffer;
44import java.nio.MappedByteBuffer;
45import java.nio.channels.FileChannel;
46import java.nio.charset.Charset;
47import java.nio.charset.CharsetDecoder;
48import java.util.LinkedHashSet;
49import java.util.Properties;
50import java.util.Set;
51
52/**
53 * Main class for smali. It recognizes enough options to be able to dispatch
54 * to the right "actual" main.
55 */
56public class main {
57
58    public static final String VERSION;
59
60    private final static Options basicOptions;
61    private final static Options debugOptions;
62    private final static Options options;
63
64    static {
65        basicOptions = new Options();
66        debugOptions = new Options();
67        options = new Options();
68        buildOptions();
69
70        InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
71        Properties properties = new Properties();
72        String version = "(unknown)";
73        try {
74            properties.load(templateStream);
75            version = properties.getProperty("application.version");
76        } catch (IOException ex) {
77        }
78        VERSION = version;
79    }
80
81
82    /**
83     * This class is uninstantiable.
84     */
85    private main() {
86    }
87
88    /**
89     * Run!
90     */
91    public static void main(String[] args) {
92        CommandLineParser parser = new PosixParser();
93        CommandLine commandLine;
94
95        try {
96            commandLine = parser.parse(options, args);
97        } catch (ParseException ex) {
98            usage();
99            return;
100        }
101
102        boolean allowOdex = false;
103        boolean sort = false;
104        boolean fixJumbo = true;
105        boolean fixGoto = true;
106        boolean verboseErrors = false;
107        boolean oldLexer = false;
108        boolean printTokens = false;
109
110        boolean apiSet = false;
111        int apiLevel = 14;
112
113        String outputDexFile = "out.dex";
114        String dumpFileName = null;
115
116        String[] remainingArgs = commandLine.getArgs();
117
118        Option[] options = commandLine.getOptions();
119
120        for (int i=0; i<options.length; i++) {
121            Option option = options[i];
122            String opt = option.getOpt();
123
124            switch (opt.charAt(0)) {
125                case 'v':
126                    version();
127                    return;
128                case '?':
129                    while (++i < options.length) {
130                        if (options[i].getOpt().charAt(0) == '?') {
131                            usage(true);
132                            return;
133                        }
134                    }
135                    usage(false);
136                    return;
137                case 'o':
138                    outputDexFile = commandLine.getOptionValue("o");
139                    break;
140                case 'x':
141                    allowOdex = true;
142                    break;
143                case 'a':
144                    apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
145                    apiSet = true;
146                    break;
147                case 'D':
148                    dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump");
149                    break;
150                case 'S':
151                    sort = true;
152                    break;
153                case 'J':
154                    fixJumbo = false;
155                    break;
156                case 'G':
157                    fixGoto = false;
158                    break;
159                case 'V':
160                    verboseErrors = true;
161                    break;
162                case 'L':
163                    oldLexer = true;
164                    break;
165                case 'T':
166                    printTokens = true;
167                    break;
168                default:
169                    assert false;
170            }
171        }
172
173        if (remainingArgs.length == 0) {
174            usage();
175            return;
176        }
177
178        try {
179            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
180
181            for (String arg: remainingArgs) {
182                    File argFile = new File(arg);
183
184                    if (!argFile.exists()) {
185                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
186                    }
187
188                    if (argFile.isDirectory()) {
189                        getSmaliFilesInDir(argFile, filesToProcess);
190                    } else if (argFile.isFile()) {
191                        filesToProcess.add(argFile);
192                    }
193            }
194
195            Opcode.updateMapsForApiLevel(apiLevel);
196
197            DexFile dexFile = new DexFile();
198
199            if (apiSet && apiLevel >= 14) {
200                dexFile.HeaderItem.setVersion(36);
201            }
202
203            boolean errors = false;
204
205            for (File file: filesToProcess) {
206                if (!assembleSmaliFile(file, dexFile, verboseErrors, oldLexer, printTokens, allowOdex, apiLevel)) {
207                    errors = true;
208                }
209            }
210
211            if (errors) {
212                System.exit(1);
213            }
214
215
216            if (sort) {
217                dexFile.setSortAllItems(true);
218            }
219
220            if (fixJumbo || fixGoto) {
221                fixInstructions(dexFile, fixJumbo, fixGoto);
222            }
223
224            dexFile.place();
225
226            ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
227
228            if (dumpFileName != null) {
229                out.enableAnnotations(120, true);
230            }
231
232            dexFile.writeTo(out);
233
234            byte[] bytes = out.toByteArray();
235
236            DexFile.calcSignature(bytes);
237            DexFile.calcChecksum(bytes);
238
239            if (dumpFileName != null) {
240                out.finishAnnotating();
241
242                FileWriter fileWriter = new FileWriter(dumpFileName);
243                out.writeAnnotationsTo(fileWriter);
244                fileWriter.close();
245            }
246
247            FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
248
249            fileOutputStream.write(bytes);
250            fileOutputStream.close();
251        } catch (RuntimeException ex) {
252            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
253            ex.printStackTrace();
254            System.exit(2);
255        } catch (Throwable ex) {
256            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
257            ex.printStackTrace();
258            System.exit(3);
259        }
260    }
261
262    private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) {
263        for(File file: dir.listFiles()) {
264            if (file.isDirectory()) {
265                getSmaliFilesInDir(file, smaliFiles);
266            } else if (file.getName().endsWith(".smali")) {
267                smaliFiles.add(file);
268            }
269        }
270    }
271
272    private static void fixInstructions(DexFile dexFile, boolean fixJumbo, boolean fixGoto) {
273        dexFile.place();
274
275        for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) {
276            codeItem.fixInstructions(fixJumbo, fixGoto);
277        }
278    }
279
280    private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors, boolean oldLexer,
281                                             boolean printTokens, boolean allowOdex, int apiLevel)
282            throws Exception {
283        CommonTokenStream tokens;
284
285
286        boolean lexerErrors = false;
287        LexerErrorInterface lexer;
288
289        if (oldLexer) {
290            ANTLRFileStream input = new ANTLRFileStream(smaliFile.getAbsolutePath(), "UTF-8");
291            input.name = smaliFile.getAbsolutePath();
292
293            lexer = new smaliLexer(input);
294            tokens = new CommonTokenStream((TokenSource)lexer);
295        } else {
296            FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath());
297            InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
298
299            lexer = new smaliFlexLexer(reader);
300            ((smaliFlexLexer)lexer).setSourceFile(smaliFile);
301            tokens = new CommonTokenStream((TokenSource)lexer);
302        }
303
304        if (printTokens) {
305            tokens.getTokens();
306
307            for (int i=0; i<tokens.size(); i++) {
308                Token token = tokens.get(i);
309                if (token.getChannel() == smaliLexer.HIDDEN) {
310                    continue;
311                }
312
313                System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
314            }
315        }
316
317        smaliParser parser = new smaliParser(tokens);
318        parser.setVerboseErrors(verboseErrors);
319        parser.setAllowOdex(allowOdex);
320        parser.setApiLevel(apiLevel);
321
322        smaliParser.smali_file_return result = parser.smali_file();
323
324        if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
325            return false;
326        }
327
328        CommonTree t = (CommonTree) result.getTree();
329
330        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
331        treeStream.setTokenStream(tokens);
332
333        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
334
335        dexGen.dexFile = dexFile;
336        dexGen.smali_file();
337
338        if (dexGen.getNumberOfSyntaxErrors() > 0) {
339            return false;
340        }
341
342        return true;
343    }
344
345
346    /**
347     * Prints the usage message.
348     */
349    private static void usage(boolean printDebugOptions) {
350        smaliHelpFormatter formatter = new smaliHelpFormatter();
351        formatter.setWidth(ConsoleUtil.getConsoleWidth());
352
353        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
354                "assembles a set of smali files into a dex file", basicOptions, "");
355
356        if (printDebugOptions) {
357            System.out.println();
358            System.out.println("Debug Options:");
359
360            StringBuffer sb = new StringBuffer();
361            formatter.renderOptions(sb, debugOptions);
362            System.out.println(sb.toString());
363        }
364    }
365
366    private static void usage() {
367        usage(false);
368    }
369
370    /**
371     * Prints the version message.
372     */
373    private static void version() {
374        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
375        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
376        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
377        System.exit(0);
378    }
379
380    private static void buildOptions() {
381        Option versionOption = OptionBuilder.withLongOpt("version")
382                .withDescription("prints the version then exits")
383                .create("v");
384
385        Option helpOption = OptionBuilder.withLongOpt("help")
386                .withDescription("prints the help message then exits. Specify twice for debug options")
387                .create("?");
388
389        Option outputOption = OptionBuilder.withLongOpt("output")
390                .withDescription("the name of the dex file that will be written. The default is out.dex")
391                .hasArg()
392                .withArgName("FILE")
393                .create("o");
394
395        Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions")
396                .withDescription("allow odex instructions to be compiled into the dex file. Only a few" +
397                        " instructions are supported - the ones that can exist in a dead code path and not" +
398                        " cause dalvik to reject the class")
399                .create("x");
400
401        Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
402                .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " +
403                        "specified, it defaults to 14 (ICS).")
404                .hasArg()
405                .withArgName("API_LEVEL")
406                .create("a");
407
408        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
409                .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)")
410                .hasOptionalArg()
411                .withArgName("FILE")
412                .create("D");
413
414        Option sortOption = OptionBuilder.withLongOpt("sort")
415                .withDescription("sort the items in the dex file into a canonical order before writing")
416                .create("S");
417
418        Option noFixJumboOption = OptionBuilder.withLongOpt("no-fix-jumbo")
419                .withDescription("Don't automatically instructions with the /jumbo variant where appropriate")
420                .create("J");
421
422        Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto")
423                .withDescription("Don't replace goto type instructions with a larger version where appropriate")
424                .create("G");
425
426        Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
427                .withDescription("Generate verbose error messages")
428                .create("V");
429
430        Option oldLexerOption = OptionBuilder.withLongOpt("old-lexer")
431                .withDescription("Use the old lexer")
432                .create("L");
433
434        Option printTokensOption = OptionBuilder.withLongOpt("print-tokens")
435                .withDescription("Print the name and text of each token")
436                .create("T");
437
438        basicOptions.addOption(versionOption);
439        basicOptions.addOption(helpOption);
440        basicOptions.addOption(outputOption);
441        basicOptions.addOption(allowOdexOption);
442        basicOptions.addOption(apiLevelOption);
443
444        debugOptions.addOption(dumpOption);
445        debugOptions.addOption(sortOption);
446        debugOptions.addOption(noFixJumboOption);
447        debugOptions.addOption(noFixGotoOption);
448        debugOptions.addOption(verboseErrorsOption);
449        debugOptions.addOption(oldLexerOption);
450        debugOptions.addOption(printTokensOption);
451
452        for (Object option: basicOptions.getOptions()) {
453            options.addOption((Option)option);
454        }
455
456        for (Object option: debugOptions.getOptions()) {
457            options.addOption((Option)option);
458        }
459    }
460}