main.java revision dee6ba748e748f1c870cf25f551f3892f867a041
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.jf.smali;
18
19import org.apache.commons.cli.*;
20import org.jf.dexlib.DexFile;
21import org.jf.dexlib.Util.ByteArrayAnnotatedOutput;
22import org.jf.dexlib.Util.FileUtils;
23import org.antlr.runtime.ANTLRInputStream;
24import org.antlr.runtime.CommonTokenStream;
25import org.antlr.runtime.TokenRewriteStream;
26import org.antlr.runtime.Lexer;
27import org.antlr.runtime.tree.CommonTree;
28import org.antlr.runtime.tree.CommonTreeNodeStream;
29
30import java.io.*;
31import java.util.Set;
32import java.util.LinkedHashSet;
33import java.util.Properties;
34
35/**
36 * Main class for smali. It recognizes enough options to be able to dispatch
37 * to the right "actual" main.
38 */
39public class main {
40
41    public static final String VERSION;
42
43    private final static Options options;
44
45    static {
46        options = new Options();
47        buildOptions();
48
49        InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties");
50        Properties properties = new Properties();
51        String version = "(unknown)";
52        try {
53            properties.load(templateStream);
54            version = properties.getProperty("application.version");
55        } catch (IOException ex) {
56        }
57        VERSION = version;
58    }
59
60
61    /**
62     * This class is uninstantiable.
63     */
64    private main() {
65    }
66
67    /**
68     * Run!
69     */
70    public static void main(String[] args) {
71        CommandLineParser parser = new PosixParser();
72        CommandLine commandLine;
73
74        try {
75            commandLine = parser.parse(options, args);
76        } catch (ParseException ex) {
77            usage();
78            return;
79        }
80
81        boolean sort = false;
82        boolean rewriteLabels = false;
83
84        String outputDexFile = "out.dex";
85        String dumpFileName = null;
86
87        String[] remainingArgs = commandLine.getArgs();
88
89        if (commandLine.hasOption("v")) {
90            version();
91            return;
92        }
93
94        if (commandLine.hasOption("?")) {
95            usage();
96            return;
97        }
98
99        if (remainingArgs.length == 0) {
100            usage();
101            return;
102        }
103
104        if (commandLine.hasOption("r")) {
105            rewriteLabels = true;
106        }
107
108        if (commandLine.hasOption("o")) {
109            if (rewriteLabels) {
110                System.err.println("The --output/-o option is not compatible with the --rewrite-labels/-r option. Ignoring");
111            } else {
112                outputDexFile = commandLine.getOptionValue("o");
113            }
114        }
115
116        if (commandLine.hasOption("d")) {
117            if (rewriteLabels) {
118                System.err.println("The --dump/-d option is not compatible with the --rewrite-labels/-r option. Ignoring");
119            } else {
120                dumpFileName = commandLine.getOptionValue("d", outputDexFile + ".dump");
121            }
122        }
123
124        if (commandLine.hasOption("s")) {
125            if (rewriteLabels) {
126                System.err.println("The --sort/-s option is not compatible with the --rewrite-labels/-r option. Ignoring");
127            } else {
128                sort = true;
129            }
130        }
131
132
133        try {
134
135            LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>();
136
137            for (String arg: remainingArgs) {
138                    File argFile = new File(arg);
139
140                    if (!argFile.exists()) {
141                        throw new RuntimeException("Cannot find file or directory \"" + arg + "\"");
142                    }
143
144                    if (argFile.isDirectory()) {
145                        getSmaliFilesInDir(argFile, filesToProcess);
146                    } else if (argFile.isFile()) {
147                        filesToProcess.add(argFile);
148                    }
149            }
150
151            if (rewriteLabels) {
152                if (doRewriteLabels(filesToProcess)) {
153                    System.exit(1);
154                }
155                System.exit(0);
156            }
157
158            DexFile dexFile = new DexFile();
159
160            boolean errors = false;
161
162            for (File file: filesToProcess) {
163                if (!assembleSmaliFile(file, dexFile)) {
164                    errors = true;
165                }
166            }
167
168            if (errors) {
169                System.exit(1);
170            }
171
172
173            if (sort) {
174                dexFile.setSortAllItems(true);
175            }
176
177            dexFile.place();
178
179            ByteArrayAnnotatedOutput out = new ByteArrayAnnotatedOutput();
180
181            if (dumpFileName != null) {
182                out.enableAnnotations(120, true);
183            }
184
185            dexFile.writeTo(out);
186
187            byte[] bytes = out.toByteArray();
188
189            DexFile.calcSignature(bytes);
190            DexFile.calcChecksum(bytes);
191
192            if (dumpFileName != null) {
193                out.finishAnnotating();
194
195                FileWriter fileWriter = new FileWriter(dumpFileName);
196                out.writeAnnotationsTo(fileWriter);
197                fileWriter.close();
198            }
199
200            FileOutputStream fileOutputStream = new FileOutputStream(outputDexFile);
201
202            fileOutputStream.write(bytes);
203            fileOutputStream.close();
204        } catch (RuntimeException ex) {
205            System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:");
206            ex.printStackTrace();
207            System.exit(2);
208        } catch (Throwable ex) {
209            System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:");
210            ex.printStackTrace();
211            System.exit(3);
212        }
213    }
214
215    private static void getSmaliFilesInDir(File dir, Set<File> smaliFiles) {
216        for(File file: dir.listFiles()) {
217            if (file.isDirectory()) {
218                getSmaliFilesInDir(file, smaliFiles);
219            } else if (file.getName().endsWith(".smali")) {
220                smaliFiles.add(file);
221            }
222        }
223    }
224
225    private static boolean doRewriteLabels(Set<File> files)
226        throws Exception {
227        boolean errorOccurred = false;
228
229        for (File file: files) {
230            ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(file));
231            input.name = file.getAbsolutePath();
232
233            smaliLexer_old lexer = new smaliLexer_old(input);
234
235            if (lexer.getNumberOfLexerErrors() > 0) {
236                errorOccurred = true;
237                continue;
238            }
239
240            TokenRewriteStream tokens = new TokenRewriteStream(lexer);
241            labelConverter parser = new labelConverter(tokens);
242            parser.top();
243
244            if (parser.getNumberOfSyntaxErrors() > 0) {
245                errorOccurred = true;
246                continue;
247            }
248
249            FileWriter writer = null;
250            try
251            {
252                writer = new FileWriter(file);
253                writer.write(tokens.toString());
254            } finally {
255                if (writer != null) {
256                    writer.close();
257                }
258            }
259        }
260
261        return !errorOccurred;
262    }
263
264    private static boolean assembleSmaliFile(File smaliFile, DexFile dexFile)
265            throws Exception {
266        ANTLRInputStream input = new ANTLRInputStream(new FileInputStream(smaliFile));
267        input.name = smaliFile.getAbsolutePath();
268
269        smaliLexer lexer = new smaliLexer(input);
270
271        CommonTokenStream tokens = new CommonTokenStream(lexer);
272        smaliParser parser = new smaliParser(tokens);
273
274        smaliParser.smali_file_return result = parser.smali_file();
275
276        if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfLexerErrors() > 0) {
277            return false;
278        }
279
280        CommonTree t = (CommonTree) result.getTree();
281
282        CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t);
283        treeStream.setTokenStream(tokens);
284
285        smaliTreeWalker dexGen = new smaliTreeWalker(treeStream);
286
287        dexGen.dexFile = dexFile;
288        dexGen.smali_file();
289
290        if (dexGen.getNumberOfSyntaxErrors() > 0) {
291            return false;
292        }
293
294        return true;
295    }
296
297
298    /**
299     * Prints the usage message.
300     */
301    private static void usage() {
302        HelpFormatter formatter = new HelpFormatter();
303        formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*",
304                "assembles a set of smali files into a dex file, and optionally generats an annotated dump of the output file", options, "");
305    }
306
307    /**
308     * Prints the version message.
309     */
310    private static void version() {
311        System.out.println("smali " + VERSION + " (http://smali.googlecode.com)");
312        System.out.println("Copyright (C) 2009 Ben Gruver");
313        System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)");
314        System.exit(0);
315    }
316
317
318
319    private static void buildOptions() {
320        Option versionOption = OptionBuilder.withLongOpt("version")
321                .withDescription("prints the version then exits")
322                .create("v");
323
324        Option helpOption = OptionBuilder.withLongOpt("help")
325                .withDescription("prints the help message then exits")
326                .create("?");
327
328        Option dumpOption = OptionBuilder.withLongOpt("dump-to")
329                .withDescription("additionally writes a dump of written dex file to FILE (<dexfile>.dump by default)")
330                .hasOptionalArg()
331                .withArgName("FILE")
332                .create("d");
333
334        Option outputOption = OptionBuilder.withLongOpt("output")
335                .withDescription("the name of the dex file that will be written. The default is out.dex")
336                .hasArg()
337                .withArgName("FILE")
338                .create("o");
339
340        Option sortOption = OptionBuilder.withLongOpt("sort")
341                .withDescription("sort the items in the dex file into a canonical order before writing")
342                .create("s");
343
344        Option rewriteLabelOption = OptionBuilder.withLongOpt("rewrite-labels")
345                .withDescription("rewrite the input smali files using the old label format to the new label format")
346                .create("r");
347
348        options.addOption(versionOption);
349        options.addOption(helpOption);
350        options.addOption(dumpOption);
351        options.addOption(outputOption);
352        options.addOption(sortOption);
353        options.addOption(rewriteLabelOption);
354    }
355}