main.java revision 4c872e9a7cc5f389449c35f6aea49c8e4ed632d4
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 sort = false;
102        boolean fixStringConst = true;
103        boolean fixGoto = true;
104        boolean verboseErrors = false;
105        boolean oldLexer = false;
106
107        String outputDexFile = "out.dex";
108        String dumpFileName = null;
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 'D':
135                    dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump");
136                    break;
137                case 'S':
138                    sort = true;
139                    break;
140                case 'C':
141                    fixStringConst = false;
142                    break;
143                case 'G':
144                    fixGoto = false;
145                    break;
146                case 'V':
147                    verboseErrors = true;
148                    break;
149                case 'L':
150                    oldLexer = 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            DexFile dexFile = new DexFile();
180
181            boolean errors = false;
182
183            for (File file: filesToProcess) {
184                if (!assembleSmaliFile(file, dexFile, verboseErrors, oldLexer)) {
185                    errors = true;
186                }
187            }
188
189            if (errors) {
190                System.exit(1);
191            }
192
193
194            if (sort) {
195                dexFile.setSortAllItems(true);
196            }
197
198            if (fixStringConst || fixGoto) {
199                fixInstructions(dexFile, fixStringConst, fixGoto);
200            }
201
202            dexFile.place();
203
204            ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
205
206            if (dumpFileName != null) {
207                out.enableAnnotations(120, true);
208            }
209
210            dexFile.writeTo(out);
211
212            byte[] bytes = out.toByteArray();
213
214            DexFile.calcSignature(bytes);
215            DexFile.calcChecksum(bytes);
216
217            if (dumpFileName != null) {
218                out.finishAnnotating();
219
220                FileWriter fileWriter = new FileWriter(dumpFileName);
221                out.writeAnnotationsTo(fileWriter);
222                fileWriter.close();
223            }
224
225            FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
226
227            fileOutputStream.write(bytes);
228            fileOutputStream.close();
229        } catch (RuntimeException ex) {
230            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
231            ex.printStackTrace();
232            System.exit(2);
233        } catch (Throwable ex) {
234            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
235            ex.printStackTrace();
236            System.exit(3);
237        }
238    }
239
240    private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) {
241        for(File file: dir.listFiles()) {
242            if (file.isDirectory()) {
243                getSmaliFilesInDir(file, smaliFiles);
244            } else if (file.getName().endsWith(".smali")) {
245                smaliFiles.add(file);
246            }
247        }
248    }
249
250    private static void fixInstructions(DexFile dexFile, boolean fixStringConst, boolean fixGoto) {
251        dexFile.place();
252
253        byte[] newInsns = null;
254
255        for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) {
256            codeItem.fixInstructions(fixStringConst, fixGoto);
257        }
258    }
259
260    private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile, boolean verboseErrors, boolean oldLexer)
261            throws Exception {
262        CommonTokenStream tokens;
263
264        if (oldLexer) {
265            ANTLRFileStream input = new ANTLRFileStream(smaliFile.getAbsolutePath(), "UTF-8");
266            input.name = smaliFile.getAbsolutePath();
267
268            smaliLexer lexer = new smaliLexer(input);
269            tokens = new CommonTokenStream(lexer);
270        } else {
271            FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath());
272            InputStreamReader reader = new InputStreamReader(fis, "UTF-8");
273
274            smaliFlexLexer lexer = new smaliFlexLexer(reader);
275
276            tokens = new CommonTokenStream(lexer);
277        }
278
279        smaliParser parser = new smaliParser(tokens);
280        parser.setVerboseErrors(verboseErrors);
281
282        smaliParser.smali_file_return result = parser.smali_file();
283
284        //TODO: check for lexer errors
285        if (parser.getNumberOfSyntaxErrors() > 0 /*|| lexer.getNumberOfSyntaxErrors() > 0*/) {
286            return false;
287        }
288
289        CommonTree t = (CommonTree) result.getTree();
290
291        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
292        treeStream.setTokenStream(tokens);
293
294        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
295
296        dexGen.dexFile = dexFile;
297        dexGen.smali_file();
298
299        if (dexGen.getNumberOfSyntaxErrors() > 0) {
300            return false;
301        }
302
303        return true;
304    }
305
306
307    /**
308     * Prints the usage message.
309     */
310    private static void usage(boolean printDebugOptions) {
311        smaliHelpFormatter formatter = new smaliHelpFormatter();
312        formatter.setWidth(ConsoleUtil.getConsoleWidth());
313
314        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
315                "assembles a set of smali files into a dex file", basicOptions, "");
316
317        if (printDebugOptions) {
318            System.out.println();
319            System.out.println("Debug Options:");
320
321            StringBuffer sb = new StringBuffer();
322            formatter.renderOptions(sb, debugOptions);
323            System.out.println(sb.toString());
324        }
325    }
326
327    private static void usage() {
328        usage(false);
329    }
330
331    /**
332     * Prints the version message.
333     */
334    private static void version() {
335        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
336        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
337        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
338        System.exit(0);
339    }
340
341    private static void buildOptions() {
342        Option versionOption = OptionBuilder.withLongOpt("version")
343                .withDescription("prints the version then exits")
344                .create("v");
345
346        Option helpOption = OptionBuilder.withLongOpt("help")
347                .withDescription("prints the help message then exits. Specify twice for debug options")
348                .create("?");
349
350        Option outputOption = OptionBuilder.withLongOpt("output")
351                .withDescription("the name of the dex file that will be written. The default is out.dex")
352                .hasArg()
353                .withArgName("FILE")
354                .create("o");
355
356        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
357                .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)")
358                .hasOptionalArg()
359                .withArgName("FILE")
360                .create("D");
361
362        Option sortOption = OptionBuilder.withLongOpt("sort")
363                .withDescription("sort the items in the dex file into a canonical order before writing")
364                .create("S");
365
366        Option noFixStringConstOption = OptionBuilder.withLongOpt("no-fix-string-const")
367                .withDescription("Don't replace string-const instructions with string-const/jumbo where appropriate")
368                .create("C");
369
370        Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto")
371                .withDescription("Don't replace goto type instructions with a larger version where appropriate")
372                .create("G");
373
374        Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors")
375                .withDescription("Generate verbose error messages")
376                .create("V");
377
378        Option oldLexerOption = OptionBuilder.withLongOpt("old-lexer")
379                .withDescription("Use the old lexer")
380                .create("L");
381
382
383        basicOptions.addOption(versionOption);
384        basicOptions.addOption(helpOption);
385        basicOptions.addOption(outputOption);
386
387        debugOptions.addOption(dumpOption);
388        debugOptions.addOption(sortOption);
389        debugOptions.addOption(noFixStringConstOption);
390        debugOptions.addOption(noFixGotoOption);
391        debugOptions.addOption(verboseErrorsOption);
392        debugOptions.addOption(oldLexerOption);
393
394        for (Object option: basicOptions.getOptions()) {
395            options.addOption((Option)option);
396        }
397
398        for (Object option: debugOptions.getOptions()) {
399            options.addOption((Option)option);
400        }
401    }
402}