main.java revision 4ba8cebf012c7b3f67d99be22283141d4cdd2216
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.CodeItem;
36import org.jf.dexlib.DexFile;
37import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
38import org.jf.util.ConsoleUtil;
39import org.jf.util.smaliHelpFormatter;
40
41import java.io.*;
42import java.nio.CharBuffer;
43import java.nio.MappedByteBuffer;
44import java.nio.channels.FileChannel;
45import java.nio.charset.Charset;
46import java.nio.charset.CharsetDecoder;
47import java.util.LinkedHashSet;
48import java.util.Properties;
49import java.util.Set;
50
51/**
52 * Main class for smali. It recognizes enough options to be able to dispatch
53 * to the right "actual" main.
54 */
55public class main {
56
57    public static final String VERSION;
58
59    private final static Options basicOptions;
60    private final static Options debugOptions;
61    private final static Options options;
62
63    static {
64        basicOptions = new Options();
65        debugOptions = new Options();
66        options = new Options();
67        buildOptions();
68
69        InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
70        Properties properties = new Properties();
71        String version = "(unknown)";
72        try {
73            properties.load(templateStream);
74            version = properties.getProperty("application.version");
75        } catch (IOException ex) {
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        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 fixStringConst = true;
104        boolean fixGoto = true;
105        boolean verboseErrors = false;
106        boolean oldLexer = false;
107        boolean printTokens = false;
108
109        String outputDexFile = "out.dex";
110        String dumpFileName = null;
111
112        String[] remainingArgs = commandLine.getArgs();
113
114        Option[] options = commandLine.getOptions();
115
116        for (int i=0; i<options.length; i++) {
117            Option option = options[i];
118            String opt = option.getOpt();
119
120            switch (opt.charAt(0)) {
121                case 'v':
122                    version();
123                    return;
124                case '?':
125                    while (++i < options.length) {
126                        if (options[i].getOpt().charAt(0) == '?') {
127                            usage(true);
128                            return;
129                        }
130                    }
131                    usage(false);
132                    return;
133                case 'o':
134                    outputDexFile = commandLine.getOptionValue("o");
135                    break;
136                case 'x':
137                    allowOdex = true;
138                    break;
139                case 'D':
140                    dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump");
141                    break;
142                case 'S':
143                    sort = true;
144                    break;
145                case 'C':
146                    fixStringConst = false;
147                    break;
148                case 'G':
149                    fixGoto = false;
150                    break;
151                case 'V':
152                    verboseErrors = true;
153                    break;
154                case 'L':
155                    oldLexer = true;
156                    break;
157                case 'T':
158                    printTokens = true;
159                    break;
160                default:
161                    assert false;
162            }
163        }
164
165        if (remainingArgs.length == 0) {
166            usage();
167            return;
168        }
169
170        try {
171            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
172
173            for (String arg: remainingArgs) {
174                    File argFile = new File(arg);
175
176                    if (!argFile.exists()) {
177                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
178                    }
179
180                    if (argFile.isDirectory()) {
181                        getSmaliFilesInDir(argFile, filesToProcess);
182                    } else if (argFile.isFile()) {
183                        filesToProcess.add(argFile);
184                    }
185            }
186
187            DexFile dexFile = new DexFile();
188
189            boolean errors = false;
190
191            for (File file: filesToProcess) {
192                if (!assembleSmaliFile(file, dexFile, verboseErrors, oldLexer, printTokens, allowOdex)) {
193                    errors = true;
194                }
195            }
196
197            if (errors) {
198                System.exit(1);
199            }
200
201
202            if (sort) {
203                dexFile.setSortAllItems(true);
204            }
205
206            if (fixStringConst || fixGoto) {
207                fixInstructions(dexFile, fixStringConst, fixGoto);
208            }
209
210            dexFile.place();
211
212            ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
213
214            if (dumpFileName != null) {
215                out.enableAnnotations(120, true);
216            }
217
218            dexFile.writeTo(out);
219
220            byte[] bytes = out.toByteArray();
221
222            DexFile.calcSignature(bytes);
223            DexFile.calcChecksum(bytes);
224
225            if (dumpFileName != null) {
226                out.finishAnnotating();
227
228                FileWriter fileWriter = new FileWriter(dumpFileName);
229                out.writeAnnotationsTo(fileWriter);
230                fileWriter.close();
231            }
232
233            FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
234
235            fileOutputStream.write(bytes);
236            fileOutputStream.close();
237        } catch (RuntimeException ex) {
238            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
239            ex.printStackTrace();
240            System.exit(2);
241        } catch (Throwable ex) {
242            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
243            ex.printStackTrace();
244            System.exit(3);
245        }
246    }
247
248    private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) {
249        for(File file: dir.listFiles()) {
250            if (file.isDirectory()) {
251                getSmaliFilesInDir(file, smaliFiles);
252            } else if (file.getName().endsWith(".smali")) {
253                smaliFiles.add(file);
254            }
255        }
256    }
257
258    private static void fixInstructions(DexFile dexFile, boolean fixStringConst, boolean fixGoto) {
259        dexFile.place();
260
261        byte[] newInsns = null;
262
263        for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) {
264            codeItem.fixInstructions(fixStringConst, fixGoto);
265        }
266    }
267
268    private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors, boolean oldLexer,
269                                             boolean printTokens, boolean allowOdex)
270            throws Exception {
271        CommonTokenStream tokens;
272
273
274        boolean lexerErrors = false;
275        LexerErrorInterface lexer;
276
277        if (oldLexer) {
278            ANTLRFileStream input = new ANTLRFileStream(smaliFile.getAbsolutePath(), "UTF-8");
279            input.name = smaliFile.getAbsolutePath();
280
281            lexer = new smaliLexer(input);
282            tokens = new CommonTokenStream((TokenSource)lexer);
283        } else {
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
292        if (printTokens) {
293            tokens.getTokens();
294
295            for (int i=0; i<tokens.size(); i++) {
296                Token token = tokens.get(i);
297                if (token.getChannel() == smaliLexer.HIDDEN) {
298                    continue;
299                }
300
301                System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText());
302            }
303        }
304
305        smaliParser parser = new smaliParser(tokens);
306        parser.setVerboseErrors(verboseErrors);
307        parser.setAllowOdex(allowOdex);
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
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        formatter.setWidth(ConsoleUtil.getConsoleWidth());
339
340        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
341                "assembles a set of smali files into a dex file", basicOptions, "");
342
343        if (printDebugOptions) {
344            System.out.println();
345            System.out.println("Debug Options:");
346
347            StringBuffer sb = new StringBuffer();
348            formatter.renderOptions(sb, debugOptions);
349            System.out.println(sb.toString());
350        }
351    }
352
353    private static void usage() {
354        usage(false);
355    }
356
357    /**
358     * Prints the version message.
359     */
360    private static void version() {
361        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
362        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
363        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
364        System.exit(0);
365    }
366
367    private static void buildOptions() {
368        Option versionOption = OptionBuilder.withLongOpt("version")
369                .withDescription("prints the version then exits")
370                .create("v");
371
372        Option helpOption = OptionBuilder.withLongOpt("help")
373                .withDescription("prints the help message then exits. Specify twice for debug options")
374                .create("?");
375
376        Option outputOption = OptionBuilder.withLongOpt("output")
377                .withDescription("the name of the dex file that will be written. The default is out.dex")
378                .hasArg()
379                .withArgName("FILE")
380                .create("o");
381
382        Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions")
383                .withDescription("allow odex instructions to be compiled into the dex file. Only a few" +
384                        " instructions are supported - the ones that can exist in a dead code path and not" +
385                        " cause dalvik to reject the class")
386                .create("x");
387
388        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
389                .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)")
390                .hasOptionalArg()
391                .withArgName("FILE")
392                .create("D");
393
394        Option sortOption = OptionBuilder.withLongOpt("sort")
395                .withDescription("sort the items in the dex file into a canonical order before writing")
396                .create("S");
397
398        Option noFixStringConstOption = OptionBuilder.withLongOpt("no-fix-string-const")
399                .withDescription("Don't replace string-const instructions with string-const/jumbo where appropriate")
400                .create("C");
401
402        Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto")
403                .withDescription("Don't replace goto type instructions with a larger version where appropriate")
404                .create("G");
405
406        Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
407                .withDescription("Generate verbose error messages")
408                .create("V");
409
410        Option oldLexerOption = OptionBuilder.withLongOpt("old-lexer")
411                .withDescription("Use the old lexer")
412                .create("L");
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
423        debugOptions.addOption(dumpOption);
424        debugOptions.addOption(sortOption);
425        debugOptions.addOption(noFixStringConstOption);
426        debugOptions.addOption(noFixGotoOption);
427        debugOptions.addOption(verboseErrorsOption);
428        debugOptions.addOption(oldLexerOption);
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}