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