main.java revision d317a0679d983819f91994b19828e4072918ac4c
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.util.LinkedHashSet;
44import java.util.Locale;
45import java.util.Properties;
46import java.util.Set;
47
48/**
49 * Main class for smali. It recognizes enough options to be able to dispatch
50 * to the right "actual" main.
51 */
52public class main {
53
54    public static final String VERSION;
55
56    private final static Options basicOptions;
57    private final static Options debugOptions;
58    private final static Options options;
59
60    static {
61        basicOptions = new Options();
62        debugOptions = new Options();
63        options = new Options();
64        buildOptions();
65
66        InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
67        Properties properties = new Properties();
68        String version = "(unknown)";
69        try {
70            properties.load(templateStream);
71            version = properties.getProperty("application.version");
72        } catch (IOException ex) {
73        }
74        VERSION = version;
75    }
76
77
78    /**
79     * This class is uninstantiable.
80     */
81    private main() {
82    }
83
84    /**
85     * Run!
86     */
87    public static void main(String[] args) {
88        Locale locale = new Locale("en", "US");
89        Locale.setDefault(locale);
90
91        CommandLineParser parser = new PosixParser();
92        CommandLine commandLine;
93
94        try {
95            commandLine = parser.parse(options, args);
96        } catch (ParseException ex) {
97            usage();
98            return;
99        }
100
101        boolean allowOdex = false;
102        boolean sort = false;
103        boolean fixJumbo = true;
104        boolean fixGoto = true;
105        boolean verboseErrors = false;
106        boolean printTokens = false;
107
108        boolean apiSet = false;
109        int apiLevel = 14;
110
111        String outputDexFile = "out.dex";
112        String dumpFileName = null;
113
114        String[] remainingArgs = commandLine.getArgs();
115
116        Option[] options = commandLine.getOptions();
117
118        for (int i=0; i<options.length; i++) {
119            Option option = options[i];
120            String opt = option.getOpt();
121
122            switch (opt.charAt(0)) {
123                case 'v':
124                    version();
125                    return;
126                case '?':
127                    while (++i < options.length) {
128                        if (options[i].getOpt().charAt(0) == '?') {
129                            usage(true);
130                            return;
131                        }
132                    }
133                    usage(false);
134                    return;
135                case 'o':
136                    outputDexFile = commandLine.getOptionValue("o");
137                    break;
138                case 'x':
139                    allowOdex = true;
140                    break;
141                case 'a':
142                    apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
143                    apiSet = true;
144                    break;
145                case 'D':
146                    dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump");
147                    break;
148                case 'S':
149                    sort = true;
150                    break;
151                case 'J':
152                    fixJumbo = false;
153                    break;
154                case 'G':
155                    fixGoto = false;
156                    break;
157                case 'V':
158                    verboseErrors = true;
159                    break;
160                case 'T':
161                    printTokens = true;
162                    break;
163                default:
164                    assert false;
165            }
166        }
167
168        if (remainingArgs.length == 0) {
169            usage();
170            return;
171        }
172
173        try {
174            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
175
176            for (String arg: remainingArgs) {
177                    File argFile = new File(arg);
178
179                    if (!argFile.exists()) {
180                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
181                    }
182
183                    if (argFile.isDirectory()) {
184                        getSmaliFilesInDir(argFile, filesToProcess);
185                    } else if (argFile.isFile()) {
186                        filesToProcess.add(argFile);
187                    }
188            }
189
190            Opcode.updateMapsForApiLevel(apiLevel);
191
192            DexFile dexFile = new DexFile();
193
194            if (apiSet && apiLevel >= 14) {
195                dexFile.HeaderItem.setVersion(36);
196            }
197
198            boolean errors = false;
199
200            for (File file: filesToProcess) {
201                if (!assembleSmaliFile(file, dexFile, verboseErrors, printTokens, allowOdex, apiLevel)) {
202                    errors = true;
203                }
204            }
205
206            if (errors) {
207                System.exit(1);
208            }
209
210
211            if (sort) {
212                dexFile.setSortAllItems(true);
213            }
214
215            if (fixJumbo || fixGoto) {
216                fixInstructions(dexFile, fixJumbo, fixGoto);
217            }
218
219            dexFile.place();
220
221            ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
222
223            if (dumpFileName != null) {
224                out.enableAnnotations(120, true);
225            }
226
227            dexFile.writeTo(out);
228
229            byte[] bytes = out.toByteArray();
230
231            DexFile.calcSignature(bytes);
232            DexFile.calcChecksum(bytes);
233
234            if (dumpFileName != null) {
235                out.finishAnnotating();
236
237                FileWriter fileWriter = new FileWriter(dumpFileName);
238                out.writeAnnotationsTo(fileWriter);
239                fileWriter.close();
240            }
241
242            FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
243
244            fileOutputStream.write(bytes);
245            fileOutputStream.close();
246        } catch (RuntimeException ex) {
247            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
248            ex.printStackTrace();
249            System.exit(2);
250        } catch (Throwable ex) {
251            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
252            ex.printStackTrace();
253            System.exit(3);
254        }
255    }
256
257    private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) {
258        for(File file: dir.listFiles()) {
259            if (file.isDirectory()) {
260                getSmaliFilesInDir(file, smaliFiles);
261            } else if (file.getName().endsWith(".smali")) {
262                smaliFiles.add(file);
263            }
264        }
265    }
266
267    private static void fixInstructions(DexFile dexFile, boolean fixJumbo, boolean fixGoto) {
268        dexFile.place();
269
270        for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) {
271            codeItem.fixInstructions(fixJumbo, fixGoto);
272        }
273    }
274
275    private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors,
276                                             boolean printTokens, boolean allowOdex, int apiLevel)
277            throws Exception {
278        CommonTokenStream tokens;
279
280
281        boolean lexerErrors = false;
282        LexerErrorInterface lexer;
283
284        FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath());
285        InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
286
287        lexer = new smaliFlexLexer(reader);
288        ((smaliFlexLexer)lexer).setSourceFile(smaliFile);
289        tokens = new CommonTokenStream((TokenSource)lexer);
290
291        if (printTokens) {
292            tokens.getTokens();
293
294            for (int i=0; i<tokens.size(); i++) {
295                Token token = tokens.get(i);
296                if (token.getChannel() == smaliParser.HIDDEN) {
297                    continue;
298                }
299
300                System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
301            }
302        }
303
304        smaliParser parser = new smaliParser(tokens);
305        parser.setVerboseErrors(verboseErrors);
306        parser.setAllowOdex(allowOdex);
307        parser.setApiLevel(apiLevel);
308
309        smaliParser.smali_file_return result = parser.smali_file();
310
311        if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
312            return false;
313        }
314
315        CommonTree t = (CommonTree) result.getTree();
316
317        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
318        treeStream.setTokenStream(tokens);
319
320        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
321        dexGen.setVerboseErrors(verboseErrors);
322        dexGen.dexFile = dexFile;
323        dexGen.smali_file();
324
325        if (dexGen.getNumberOfSyntaxErrors() > 0) {
326            return false;
327        }
328
329        return true;
330    }
331
332
333    /**
334     * Prints the usage message.
335     */
336    private static void usage(boolean printDebugOptions) {
337        SmaliHelpFormatter formatter = new SmaliHelpFormatter();
338
339        int consoleWidth = ConsoleUtil.getConsoleWidth();
340        if (consoleWidth <= 0) {
341            consoleWidth = 80;
342        }
343
344        formatter.setWidth(consoleWidth);
345
346        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
347                "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null);
348    }
349
350    private static void usage() {
351        usage(false);
352    }
353
354    /**
355     * Prints the version message.
356     */
357    private static void version() {
358        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
359        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
360        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
361        System.exit(0);
362    }
363
364    private static void buildOptions() {
365        Option versionOption = OptionBuilder.withLongOpt("version")
366                .withDescription("prints the version then exits")
367                .create("v");
368
369        Option helpOption = OptionBuilder.withLongOpt("help")
370                .withDescription("prints the help message then exits. Specify twice for debug options")
371                .create("?");
372
373        Option outputOption = OptionBuilder.withLongOpt("output")
374                .withDescription("the name of the dex file that will be written. The default is out.dex")
375                .hasArg()
376                .withArgName("FILE")
377                .create("o");
378
379        Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions")
380                .withDescription("allow odex instructions to be compiled into the dex file. Only a few" +
381                        " instructions are supported - the ones that can exist in a dead code path and not" +
382                        " cause dalvik to reject the class")
383                .create("x");
384
385        Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
386                .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " +
387                        "specified, it defaults to 14 (ICS).")
388                .hasArg()
389                .withArgName("API_LEVEL")
390                .create("a");
391
392        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
393                .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)")
394                .hasOptionalArg()
395                .withArgName("FILE")
396                .create("D");
397
398        Option sortOption = OptionBuilder.withLongOpt("sort")
399                .withDescription("sort the items in the dex file into a canonical order before writing")
400                .create("S");
401
402        Option noFixJumboOption = OptionBuilder.withLongOpt("no-fix-jumbo")
403                .withDescription("Don't automatically instructions with the /jumbo variant where appropriate")
404                .create("J");
405
406        Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto")
407                .withDescription("Don't replace goto type instructions with a larger version where appropriate")
408                .create("G");
409
410        Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
411                .withDescription("Generate verbose error messages")
412                .create("V");
413
414        Option printTokensOption = OptionBuilder.withLongOpt("print-tokens")
415                .withDescription("Print the name and text of each token")
416                .create("T");
417
418        basicOptions.addOption(versionOption);
419        basicOptions.addOption(helpOption);
420        basicOptions.addOption(outputOption);
421        basicOptions.addOption(allowOdexOption);
422        basicOptions.addOption(apiLevelOption);
423
424        debugOptions.addOption(dumpOption);
425        debugOptions.addOption(sortOption);
426        debugOptions.addOption(noFixJumboOption);
427        debugOptions.addOption(noFixGotoOption);
428        debugOptions.addOption(verboseErrorsOption);
429        debugOptions.addOption(printTokensOption);
430
431        for (Object option: basicOptions.getOptions()) {
432            options.addOption((Option)option);
433        }
434
435        for (Object option: debugOptions.getOptions()) {
436            options.addOption((Option)option);
437        }
438    }
439}