main.java revision 894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cd
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.CommonTokenStream;
32import org.antlr.runtime.Token;
33import org.antlr.runtime.TokenSource;
34import org.antlr.runtime.tree.CommonTree;
35import org.antlr.runtime.tree.CommonTreeNodeStream;
36import org.apache.commons.cli.*;
37import org.jf.dexlib2.writer.builder.DexBuilder;
38import org.jf.util.ConsoleUtil;
39import org.jf.util.SmaliHelpFormatter;
40
41import javax.annotation.Nonnull;
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            // just eat it
74        }
75        VERSION = version;
76    }
77
78
79    /**
80     * This class is uninstantiable.
81     */
82    private main() {
83    }
84
85    /**
86     * Run!
87     */
88    public static void main(String[] args) {
89        Locale locale = new Locale("en", "US");
90        Locale.setDefault(locale);
91
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 verboseErrors = false;
104        boolean printTokens = false;
105
106        int apiLevel = 15;
107
108        String outputDexFile = "out.dex";
109
110        String[] remainingArgs = commandLine.getArgs();
111
112        Option[] options = commandLine.getOptions();
113
114        for (int i=0; i<options.length; i++) {
115            Option option = options[i];
116            String opt = option.getOpt();
117
118            switch (opt.charAt(0)) {
119                case 'v':
120                    version();
121                    return;
122                case '?':
123                    while (++i < options.length) {
124                        if (options[i].getOpt().charAt(0) == '?') {
125                            usage(true);
126                            return;
127                        }
128                    }
129                    usage(false);
130                    return;
131                case 'o':
132                    outputDexFile = commandLine.getOptionValue("o");
133                    break;
134                case 'x':
135                    allowOdex = true;
136                    break;
137                case 'a':
138                    apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
139                    break;
140                case 'V':
141                    verboseErrors = true;
142                    break;
143                case 'T':
144                    printTokens = true;
145                    break;
146                default:
147                    assert false;
148            }
149        }
150
151        if (remainingArgs.length == 0) {
152            usage();
153            return;
154        }
155
156        try {
157            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
158
159            for (String arg: remainingArgs) {
160                    File argFile = new File(arg);
161
162                    if (!argFile.exists()) {
163                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
164                    }
165
166                    if (argFile.isDirectory()) {
167                        getSmaliFilesInDir(argFile, filesToProcess);
168                    } else if (argFile.isFile()) {
169                        filesToProcess.add(argFile);
170                    }
171            }
172
173            boolean errors = false;
174
175            DexBuilder dexBuilder = DexBuilder.makeDexBuilder();
176
177            for (File file: filesToProcess) {
178                if (!assembleSmaliFile(file, dexBuilder, verboseErrors, printTokens, allowOdex, apiLevel)) {
179                    errors = true;
180                }
181            }
182
183            if (errors) {
184                System.exit(1);
185            }
186
187            dexBuilder.writeTo(outputDexFile);
188        } catch (RuntimeException ex) {
189            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
190            ex.printStackTrace();
191            System.exit(2);
192        } catch (Throwable ex) {
193            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
194            ex.printStackTrace();
195            System.exit(3);
196        }
197    }
198
199    private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) {
200        File[] files = dir.listFiles();
201        if (files != null) {
202            for(File file: files) {
203            if (file.isDirectory()) {
204                getSmaliFilesInDir(file, smaliFiles);
205            } else if (file.getName().endsWith(".smali")) {
206                smaliFiles.add(file);
207            }
208        }
209    }
210        }
211
212    private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors,
213                                             boolean printTokens, boolean allowOdex, int apiLevel)
214            throws Exception {
215        CommonTokenStream tokens;
216
217        LexerErrorInterface lexer;
218
219        FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath());
220        InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
221
222        lexer = new smaliFlexLexer(reader);
223        ((smaliFlexLexer)lexer).setSourceFile(smaliFile);
224        tokens = new CommonTokenStream((TokenSource)lexer);
225
226        if (printTokens) {
227            tokens.getTokens();
228
229            for (int i=0; i<tokens.size(); i++) {
230                Token token = tokens.get(i);
231                if (token.getChannel() == smaliParser.HIDDEN) {
232                    continue;
233                }
234
235                System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
236            }
237        }
238
239        smaliParser parser = new smaliParser(tokens);
240        parser.setVerboseErrors(verboseErrors);
241        parser.setAllowOdex(allowOdex);
242        parser.setApiLevel(apiLevel);
243
244        smaliParser.smali_file_return result = parser.smali_file();
245
246        if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
247            return false;
248        }
249
250        CommonTree t = result.getTree();
251
252        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
253        treeStream.setTokenStream(tokens);
254
255        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
256        dexGen.setVerboseErrors(verboseErrors);
257        dexGen.setDexBuilder(dexBuilder);
258        dexGen.smali_file();
259
260        return dexGen.getNumberOfSyntaxErrors() == 0;
261    }
262
263
264    /**
265     * Prints the usage message.
266     */
267    private static void usage(boolean printDebugOptions) {
268        SmaliHelpFormatter formatter = new SmaliHelpFormatter();
269
270        int consoleWidth = ConsoleUtil.getConsoleWidth();
271        if (consoleWidth <= 0) {
272            consoleWidth = 80;
273        }
274
275        formatter.setWidth(consoleWidth);
276
277        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
278                "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null);
279    }
280
281    private static void usage() {
282        usage(false);
283    }
284
285    /**
286     * Prints the version message.
287     */
288    private static void version() {
289        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
290        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
291        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
292        System.exit(0);
293    }
294
295    private static void buildOptions() {
296        Option versionOption = OptionBuilder.withLongOpt("version")
297                .withDescription("prints the version then exits")
298                .create("v");
299
300        Option helpOption = OptionBuilder.withLongOpt("help")
301                .withDescription("prints the help message then exits. Specify twice for debug options")
302                .create("?");
303
304        Option outputOption = OptionBuilder.withLongOpt("output")
305                .withDescription("the name of the dex file that will be written. The default is out.dex")
306                .hasArg()
307                .withArgName("FILE")
308                .create("o");
309
310        Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions")
311                .withDescription("allow odex instructions to be compiled into the dex file. Only a few" +
312                        " instructions are supported - the ones that can exist in a dead code path and not" +
313                        " cause dalvik to reject the class")
314                .create("x");
315
316        Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
317                .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " +
318                        "specified, it defaults to 15 (ICS).")
319                .hasArg()
320                .withArgName("API_LEVEL")
321                .create("a");
322
323        Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
324                .withDescription("Generate verbose error messages")
325                .create("V");
326
327        Option printTokensOption = OptionBuilder.withLongOpt("print-tokens")
328                .withDescription("Print the name and text of each token")
329                .create("T");
330
331        basicOptions.addOption(versionOption);
332        basicOptions.addOption(helpOption);
333        basicOptions.addOption(outputOption);
334        basicOptions.addOption(allowOdexOption);
335        basicOptions.addOption(apiLevelOption);
336
337        debugOptions.addOption(verboseErrorsOption);
338        debugOptions.addOption(printTokensOption);
339
340        for (Object option: basicOptions.getOptions()) {
341            options.addOption((Option)option);
342        }
343
344        for (Object option: debugOptions.getOptions()) {
345            options.addOption((Option)option);
346        }
347    }
348}