main.java revision 6786055f3566c4fcafd352329662b6b8b223580c
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.apache.commons.cli.*;
32import org.jf.dexlib.DexFile;
33import org.jf.dexlib.CodeItem;
34import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
35import org.antlr.runtime.ANTLRInputStream;
36import org.antlr.runtime.CommonTokenStream;
37import org.antlr.runtime.tree.CommonTree;
38import org.antlr.runtime.tree.CommonTreeNodeStream;
39import org.jf.util.*;
40
41import java.io.*;
42import java.util.Set;
43import java.util.LinkedHashSet;
44import java.util.Properties;
45
46/**
47 * Main class for smali. It recognizes enough options to be able to dispatch
48 * to the right "actual" main.
49 */
50public class main {
51
52    public static final String VERSION;
53
54    private final static Options basicOptions;
55    private final static Options debugOptions;
56    private final static Options options;
57
58    static {
59        basicOptions = new Options();
60        debugOptions = new Options();
61        options = new Options();
62        buildOptions();
63
64        InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
65        Properties properties = new Properties();
66        String version = "(unknown)";
67        try {
68            properties.load(templateStream);
69            version = properties.getProperty("application.version");
70        } catch (IOException ex) {
71        }
72        VERSION = version;
73    }
74
75
76    /**
77     * This class is uninstantiable.
78     */
79    private main() {
80    }
81
82    /**
83     * Run!
84     */
85    public static void main(String[] args) {
86        CommandLineParser parser = new PosixParser();
87        CommandLine commandLine;
88
89        try {
90            commandLine = parser.parse(options, args);
91        } catch (ParseException ex) {
92            usage();
93            return;
94        }
95
96        boolean sort = false;
97        boolean fixStringConst = true;
98        boolean fixGoto = true;
99
100        String outputDexFile = "out.dex";
101        String dumpFileName = null;
102
103        String[] remainingArgs = commandLine.getArgs();
104
105        Option[] options = commandLine.getOptions();
106
107        for (int i=0; i<options.length; i++) {
108            Option option = options[i];
109            String opt = option.getOpt();
110
111            switch (opt.charAt(0)) {
112                case 'v':
113                    version();
114                    return;
115                case '?':
116                    while (++i < options.length) {
117                        if (options[i].getOpt().charAt(0) == '?') {
118                            usage(true);
119                            return;
120                        }
121                    }
122                    usage(false);
123                    return;
124                case 'o':
125                    outputDexFile = commandLine.getOptionValue("o");
126                    break;
127                case 'D':
128                    dumpFileName = commandLine.getOptionValue("D", outputDexFile + ".dump");
129                    break;
130                case 'S':
131                    sort = true;
132                    break;
133                case 'C':
134                    fixStringConst = false;
135                    break;
136                case 'G':
137                    fixGoto = false;
138                    break;
139                default:
140                    assert false;
141            }
142        }
143
144        if (remainingArgs.length == 0) {
145            usage();
146            return;
147        }
148
149        try {
150            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
151
152            for (String arg: remainingArgs) {
153                    File argFile = new File(arg);
154
155                    if (!argFile.exists()) {
156                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
157                    }
158
159                    if (argFile.isDirectory()) {
160                        getSmaliFilesInDir(argFile, filesToProcess);
161                    } else if (argFile.isFile()) {
162                        filesToProcess.add(argFile);
163                    }
164            }
165
166            DexFile dexFile = new DexFile();
167
168            boolean errors = false;
169
170            for (File file: filesToProcess) {
171                if (!assembleSmaliFile(file, dexFile)) {
172                    errors = true;
173                }
174            }
175
176            if (errors) {
177                System.exit(1);
178            }
179
180
181            if (sort) {
182                dexFile.setSortAllItems(true);
183            }
184
185            if (fixStringConst || fixGoto) {
186                fixInstructions(dexFile, fixStringConst, fixGoto);
187            }
188
189            dexFile.place();
190
191            ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
192
193            if (dumpFileName != null) {
194                out.enableAnnotations(120, true);
195            }
196
197            dexFile.writeTo(out);
198
199            byte[] bytes = out.toByteArray();
200
201            DexFile.calcSignature(bytes);
202            DexFile.calcChecksum(bytes);
203
204            if (dumpFileName != null) {
205                out.finishAnnotating();
206
207                FileWriter fileWriter = new FileWriter(dumpFileName);
208                out.writeAnnotationsTo(fileWriter);
209                fileWriter.close();
210            }
211
212            FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
213
214            fileOutputStream.write(bytes);
215            fileOutputStream.close();
216        } catch (RuntimeException ex) {
217            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
218            ex.printStackTrace();
219            System.exit(2);
220        } catch (Throwable ex) {
221            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
222            ex.printStackTrace();
223            System.exit(3);
224        }
225    }
226
227    private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) {
228        for(File file: dir.listFiles()) {
229            if (file.isDirectory()) {
230                getSmaliFilesInDir(file, smaliFiles);
231            } else if (file.getName().endsWith(".smali")) {
232                smaliFiles.add(file);
233            }
234        }
235    }
236
237    private static void fixInstructions(DexFile dexFile, boolean fixStringConst, boolean fixGoto) {
238        dexFile.place();
239
240        byte[] newInsns = null;
241
242        for (CodeItem codeItem: dexFile.CodeItemsSection.getItems()) {
243            codeItem.fixInstructions(fixStringConst, fixGoto);
244        }
245    }
246
247    private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile)
248            throws Exception {
249        ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile));
250        input.name = smaliFile.getAbsolutePath();
251
252        smaliLexer lexer = new smaliLexer(input);
253
254        CommonTokenStream tokens = new CommonTokenStream(lexer);
255        smaliParser parser = new smaliParser(tokens);
256
257        smaliParser.smali_file_return result = parser.smali_file();
258
259        if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) {
260            return false;
261        }
262
263        CommonTree t = (CommonTree) result.getTree();
264
265        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
266        treeStream.setTokenStream(tokens);
267
268        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
269
270        dexGen.dexFile = dexFile;
271        dexGen.smali_file();
272
273        if (dexGen.getNumberOfSyntaxErrors() > 0) {
274            return false;
275        }
276
277        return true;
278    }
279
280
281    /**
282     * Prints the usage message.
283     */
284    private static void usage(boolean printDebugOptions) {
285        smaliHelpFormatter formatter = new smaliHelpFormatter();
286        formatter.setWidth(ConsoleUtil.getConsoleWidth());
287
288        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
289                "assembles a set of smali files into a dex file", basicOptions, "");
290
291        if (printDebugOptions) {
292            System.out.println();
293            System.out.println("Debug Options:");
294
295            StringBuffer sb = new StringBuffer();
296            formatter.renderOptions(sb, debugOptions);
297            System.out.println(sb.toString());
298        }
299    }
300
301    private static void usage() {
302        usage(false);
303    }
304
305    /**
306     * Prints the version message.
307     */
308    private static void version() {
309        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
310        System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)");
311        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
312        System.exit(0);
313    }
314
315    private static void buildOptions() {
316        Option versionOption = OptionBuilder.withLongOpt("version")
317                .withDescription("prints the version then exits")
318                .create("v");
319
320        Option helpOption = OptionBuilder.withLongOpt("help")
321                .withDescription("prints the help message then exits. Specify twice for debug options")
322                .create("?");
323
324        Option outputOption = OptionBuilder.withLongOpt("output")
325                .withDescription("the name of the dex file that will be written. The default is out.dex")
326                .hasArg()
327                .withArgName("FILE")
328                .create("o");
329
330        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
331                .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)")
332                .hasOptionalArg()
333                .withArgName("FILE")
334                .create("D");
335
336        Option sortOption = OptionBuilder.withLongOpt("sort")
337                .withDescription("sort the items in the dex file into a canonical order before writing")
338                .create("S");
339
340        Option noFixStringConstOption = OptionBuilder.withLongOpt("no-fix-string-const")
341                .withDescription("Don't replace string-const instructions with string-const/jumbo where appropriate")
342                .create("C");
343
344        Option noFixGotoOption = OptionBuilder.withLongOpt("no-fix-goto")
345                .withDescription("Don't replace goto type instructions with a larger version where appropriate")
346                .create("G");
347
348        basicOptions.addOption(versionOption);
349        basicOptions.addOption(helpOption);
350        basicOptions.addOption(outputOption);
351
352        debugOptions.addOption(dumpOption);
353        debugOptions.addOption(sortOption);
354        debugOptions.addOption(noFixStringConstOption);
355        debugOptions.addOption(noFixGotoOption);
356
357        for (Object option: basicOptions.getOptions()) {
358            options.addOption((Option)option);
359        }
360
361        for (Object option: debugOptions.getOptions()) {
362            options.addOption((Option)option);
363        }
364    }
365}