main.java revision dd77ba20f44e0c40a603a28bd0a9bcb0b6f43dfc
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 com.google.common.collect.Lists;
32import org.antlr.runtime.CommonTokenStream;
33import org.antlr.runtime.Token;
34import org.antlr.runtime.TokenSource;
35import org.antlr.runtime.tree.CommonTree;
36import org.antlr.runtime.tree.CommonTreeNodeStream;
37import org.apache.commons.cli.*;
38import org.jf.dexlib2.writer.builder.DexBuilder;
39import org.jf.util.ConsoleUtil;
40import org.jf.util.SmaliHelpFormatter;
41
42import javax.annotation.Nonnull;
43import java.io.*;
44import java.util.*;
45import java.util.concurrent.Callable;
46import java.util.concurrent.ExecutorService;
47import java.util.concurrent.Executors;
48import java.util.concurrent.Future;
49
50/**
51 * Main class for smali. It recognizes enough options to be able to dispatch
52 * to the right "actual" main.
53 */
54public class main {
55
56    public static final String VERSION;
57
58    private final static Options basicOptions;
59    private final static Options debugOptions;
60    private final static Options options;
61
62    static {
63        basicOptions = new Options();
64        debugOptions = new Options();
65        options = new Options();
66        buildOptions();
67
68        InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
69        Properties properties = new Properties();
70        String version = "(unknown)";
71        try {
72            properties.load(templateStream);
73            version = properties.getProperty("application.version");
74        } catch (IOException ex) {
75            // just eat it
76        }
77        VERSION = version;
78    }
79
80
81    /**
82     * This class is uninstantiable.
83     */
84    private main() {
85    }
86
87    /**
88     * Run!
89     */
90    public static void main(String[] args) {
91        Locale locale = new Locale("en", "US");
92        Locale.setDefault(locale);
93
94        CommandLineParser parser = new PosixParser();
95        CommandLine commandLine;
96
97        try {
98            commandLine = parser.parse(options, args);
99        } catch (ParseException ex) {
100            usage();
101            return;
102        }
103
104        int jobs = -1;
105        boolean allowOdex = false;
106        boolean verboseErrors = false;
107        boolean printTokens = false;
108
109        int apiLevel = 15;
110
111        String outputDexFile = "out.dex";
112
113        String[] remainingArgs = commandLine.getArgs();
114
115        Option[] options = commandLine.getOptions();
116
117        for (int i=0; i<options.length; i++) {
118            Option option = options[i];
119            String opt = option.getOpt();
120
121            switch (opt.charAt(0)) {
122                case 'v':
123                    version();
124                    return;
125                case '?':
126                    while (++i < options.length) {
127                        if (options[i].getOpt().charAt(0) == '?') {
128                            usage(true);
129                            return;
130                        }
131                    }
132                    usage(false);
133                    return;
134                case 'o':
135                    outputDexFile = commandLine.getOptionValue("o");
136                    break;
137                case 'x':
138                    allowOdex = true;
139                    break;
140                case 'a':
141                    apiLevel = Integer.parseInt(commandLine.getOptionValue("a"));
142                    break;
143                case 'j':
144                    jobs = Integer.parseInt(commandLine.getOptionValue("j"));
145                    break;
146                case 'V':
147                    verboseErrors = true;
148                    break;
149                case 'T':
150                    printTokens = true;
151                    break;
152                default:
153                    assert false;
154            }
155        }
156
157        if (remainingArgs.length == 0) {
158            usage();
159            return;
160        }
161
162        try {
163            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
164
165            for (String arg: remainingArgs) {
166                    File argFile = new File(arg);
167
168                    if (!argFile.exists()) {
169                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
170                    }
171
172                    if (argFile.isDirectory()) {
173                        getSmaliFilesInDir(argFile, filesToProcess);
174                    } else if (argFile.isFile()) {
175                        filesToProcess.add(argFile);
176                    }
177            }
178
179            if (jobs <= 0) {
180                jobs = Runtime.getRuntime().availableProcessors();
181                if (jobs > 6) {
182                    jobs = 6;
183                }
184            }
185
186            boolean errors = false;
187
188            final DexBuilder dexBuilder = DexBuilder.makeDexBuilder(apiLevel);
189            ExecutorService executor = Executors.newFixedThreadPool(jobs);
190            List<Future<Boolean>> tasks = Lists.newArrayList();
191
192            final boolean finalVerboseErrors = verboseErrors;
193            final boolean finalPrintTokens = printTokens;
194            final boolean finalAllowOdex = allowOdex;
195            final int finalApiLevel = apiLevel;
196            for (final File file: filesToProcess) {
197                tasks.add(executor.submit(new Callable<Boolean>() {
198                    @Override public Boolean call() throws Exception {
199                        return assembleSmaliFile(file, dexBuilder, finalVerboseErrors, finalPrintTokens,
200                                finalAllowOdex, finalApiLevel);
201                    }
202                }));
203            }
204
205            for (Future<Boolean> task: tasks) {
206                while(true) {
207                    try {
208                        if (!task.get()) {
209                            errors = true;
210                        }
211                    } catch (InterruptedException ex) {
212                        continue;
213                    }
214                    break;
215                }
216            }
217
218            executor.shutdown();
219
220            if (errors) {
221                System.exit(1);
222            }
223
224            dexBuilder.writeTo(outputDexFile);
225        } catch (RuntimeException ex) {
226            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
227            ex.printStackTrace();
228            System.exit(2);
229        } catch (Throwable ex) {
230            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
231            ex.printStackTrace();
232            System.exit(3);
233        }
234    }
235
236    private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) {
237        File[] files = dir.listFiles();
238        if (files != null) {
239            for(File file: files) {
240            if (file.isDirectory()) {
241                getSmaliFilesInDir(file, smaliFiles);
242            } else if (file.getName().endsWith(".smali")) {
243                smaliFiles.add(file);
244            }
245        }
246    }
247        }
248
249    private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors,
250                                             boolean printTokens, boolean allowOdex, int apiLevel)
251            throws Exception {
252        CommonTokenStream tokens;
253
254        LexerErrorInterface lexer;
255
256        FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath());
257        InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
258
259        lexer = new smaliFlexLexer(reader);
260        ((smaliFlexLexer)lexer).setSourceFile(smaliFile);
261        tokens = new CommonTokenStream((TokenSource)lexer);
262
263        if (printTokens) {
264            tokens.getTokens();
265
266            for (int i=0; i<tokens.size(); i++) {
267                Token token = tokens.get(i);
268                if (token.getChannel() == smaliParser.HIDDEN) {
269                    continue;
270                }
271
272                System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
273            }
274        }
275
276        smaliParser parser = new smaliParser(tokens);
277        parser.setVerboseErrors(verboseErrors);
278        parser.setAllowOdex(allowOdex);
279        parser.setApiLevel(apiLevel);
280
281        smaliParser.smali_file_return result = parser.smali_file();
282
283        if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) {
284            return false;
285        }
286
287        CommonTree t = result.getTree();
288
289        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
290        treeStream.setTokenStream(tokens);
291
292        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
293        dexGen.setVerboseErrors(verboseErrors);
294        dexGen.setDexBuilder(dexBuilder);
295        dexGen.smali_file();
296
297        return dexGen.getNumberOfSyntaxErrors() == 0;
298    }
299
300
301    /**
302     * Prints the usage message.
303     */
304    private static void usage(boolean printDebugOptions) {
305        SmaliHelpFormatter formatter = new SmaliHelpFormatter();
306
307        int consoleWidth = ConsoleUtil.getConsoleWidth();
308        if (consoleWidth <= 0) {
309            consoleWidth = 80;
310        }
311
312        formatter.setWidth(consoleWidth);
313
314        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
315                "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null);
316    }
317
318    private static void usage() {
319        usage(false);
320    }
321
322    /**
323     * Prints the version message.
324     */
325    private static void version() {
326        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
327        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
328        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
329        System.exit(0);
330    }
331
332    @SuppressWarnings("AccessStaticViaInstance")
333    private static void buildOptions() {
334        Option versionOption = OptionBuilder.withLongOpt("version")
335                .withDescription("prints the version then exits")
336                .create("v");
337
338        Option helpOption = OptionBuilder.withLongOpt("help")
339                .withDescription("prints the help message then exits. Specify twice for debug options")
340                .create("?");
341
342        Option outputOption = OptionBuilder.withLongOpt("output")
343                .withDescription("the name of the dex file that will be written. The default is out.dex")
344                .hasArg()
345                .withArgName("FILE")
346                .create("o");
347
348        Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions")
349                .withDescription("allow odex instructions to be compiled into the dex file. Only a few" +
350                        " instructions are supported - the ones that can exist in a dead code path and not" +
351                        " cause dalvik to reject the class")
352                .create("x");
353
354        Option apiLevelOption = OptionBuilder.withLongOpt("api-level")
355                .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " +
356                        "specified, it defaults to 15 (ICS).")
357                .hasArg()
358                .withArgName("API_LEVEL")
359                .create("a");
360
361        Option jobsOption = OptionBuilder.withLongOpt("jobs")
362                .withDescription("The number of threads to use. Defaults to the number of cores available, up to a " +
363                        "maximum of 6")
364                .hasArg()
365                .withArgName("NUM_THREADS")
366                .create("j");
367
368        Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
369                .withDescription("Generate verbose error messages")
370                .create("V");
371
372        Option printTokensOption = OptionBuilder.withLongOpt("print-tokens")
373                .withDescription("Print the name and text of each token")
374                .create("T");
375
376        basicOptions.addOption(versionOption);
377        basicOptions.addOption(helpOption);
378        basicOptions.addOption(outputOption);
379        basicOptions.addOption(allowOdexOption);
380        basicOptions.addOption(apiLevelOption);
381        basicOptions.addOption(jobsOption);
382
383        debugOptions.addOption(verboseErrorsOption);
384        debugOptions.addOption(printTokensOption);
385
386        for (Object option: basicOptions.getOptions()) {
387            options.addOption((Option)option);
388        }
389
390        for (Object option: debugOptions.getOptions()) {
391            options.addOption((Option)option);
392        }
393    }
394}