main.java revision 894327c7ed6c4ffc3a7b9fe61849a878b9a1e8cd
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.CommonTokenStream; 32import org.antlr.runtime.Token; 33import org.antlr.runtime.TokenSource; 34import org.antlr.runtime.tree.CommonTree; 35import org.antlr.runtime.tree.CommonTreeNodeStream; 36import org.apache.commons.cli.*; 37import org.jf.dexlib2.writer.builder.DexBuilder; 38import org.jf.util.ConsoleUtil; 39import org.jf.util.SmaliHelpFormatter; 40 41import javax.annotation.Nonnull; 42import java.io.*; 43import java.util.LinkedHashSet; 44import java.util.Locale; 45import java.util.Properties; 46import java.util.Set; 47 48/** 49 * Main class for smali. It recognizes enough options to be able to dispatch 50 * to the right "actual" main. 51 */ 52public class main { 53 54 public static final String VERSION; 55 56 private final static Options basicOptions; 57 private final static Options debugOptions; 58 private final static Options options; 59 60 static { 61 basicOptions = new Options(); 62 debugOptions = new Options(); 63 options = new Options(); 64 buildOptions(); 65 66 InputStream templateStream = main.class.getClassLoader().getResourceAsStream("smali.properties"); 67 Properties properties = new Properties(); 68 String version = "(unknown)"; 69 try { 70 properties.load(templateStream); 71 version = properties.getProperty("application.version"); 72 } catch (IOException ex) { 73 // just eat it 74 } 75 VERSION = version; 76 } 77 78 79 /** 80 * This class is uninstantiable. 81 */ 82 private main() { 83 } 84 85 /** 86 * Run! 87 */ 88 public static void main(String[] args) { 89 Locale locale = new Locale("en", "US"); 90 Locale.setDefault(locale); 91 92 CommandLineParser parser = new PosixParser(); 93 CommandLine commandLine; 94 95 try { 96 commandLine = parser.parse(options, args); 97 } catch (ParseException ex) { 98 usage(); 99 return; 100 } 101 102 boolean allowOdex = false; 103 boolean verboseErrors = false; 104 boolean printTokens = false; 105 106 int apiLevel = 15; 107 108 String outputDexFile = "out.dex"; 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 'x': 135 allowOdex = true; 136 break; 137 case 'a': 138 apiLevel = Integer.parseInt(commandLine.getOptionValue("a")); 139 break; 140 case 'V': 141 verboseErrors = true; 142 break; 143 case 'T': 144 printTokens = true; 145 break; 146 default: 147 assert false; 148 } 149 } 150 151 if (remainingArgs.length == 0) { 152 usage(); 153 return; 154 } 155 156 try { 157 LinkedHashSet<File> filesToProcess = new LinkedHashSet<File>(); 158 159 for (String arg: remainingArgs) { 160 File argFile = new File(arg); 161 162 if (!argFile.exists()) { 163 throw new RuntimeException("Cannot find file or directory \"" + arg + "\""); 164 } 165 166 if (argFile.isDirectory()) { 167 getSmaliFilesInDir(argFile, filesToProcess); 168 } else if (argFile.isFile()) { 169 filesToProcess.add(argFile); 170 } 171 } 172 173 boolean errors = false; 174 175 DexBuilder dexBuilder = DexBuilder.makeDexBuilder(); 176 177 for (File file: filesToProcess) { 178 if (!assembleSmaliFile(file, dexBuilder, verboseErrors, printTokens, allowOdex, apiLevel)) { 179 errors = true; 180 } 181 } 182 183 if (errors) { 184 System.exit(1); 185 } 186 187 dexBuilder.writeTo(outputDexFile); 188 } catch (RuntimeException ex) { 189 System.err.println("\nUNEXPECTED TOP-LEVEL EXCEPTION:"); 190 ex.printStackTrace(); 191 System.exit(2); 192 } catch (Throwable ex) { 193 System.err.println("\nUNEXPECTED TOP-LEVEL ERROR:"); 194 ex.printStackTrace(); 195 System.exit(3); 196 } 197 } 198 199 private static void getSmaliFilesInDir(@Nonnull File dir, @Nonnull Set<File> smaliFiles) { 200 File[] files = dir.listFiles(); 201 if (files != null) { 202 for(File file: files) { 203 if (file.isDirectory()) { 204 getSmaliFilesInDir(file, smaliFiles); 205 } else if (file.getName().endsWith(".smali")) { 206 smaliFiles.add(file); 207 } 208 } 209 } 210 } 211 212 private static boolean assembleSmaliFile(File smaliFile, DexBuilder dexBuilder, boolean verboseErrors, 213 boolean printTokens, boolean allowOdex, int apiLevel) 214 throws Exception { 215 CommonTokenStream tokens; 216 217 LexerErrorInterface lexer; 218 219 FileInputStream fis = new FileInputStream(smaliFile.getAbsolutePath()); 220 InputStreamReader reader = new InputStreamReader(fis, "UTF-8"); 221 222 lexer = new smaliFlexLexer(reader); 223 ((smaliFlexLexer)lexer).setSourceFile(smaliFile); 224 tokens = new CommonTokenStream((TokenSource)lexer); 225 226 if (printTokens) { 227 tokens.getTokens(); 228 229 for (int i=0; i<tokens.size(); i++) { 230 Token token = tokens.get(i); 231 if (token.getChannel() == smaliParser.HIDDEN) { 232 continue; 233 } 234 235 System.out.println(smaliParser.tokenNames[token.getType()] + ": " + token.getText()); 236 } 237 } 238 239 smaliParser parser = new smaliParser(tokens); 240 parser.setVerboseErrors(verboseErrors); 241 parser.setAllowOdex(allowOdex); 242 parser.setApiLevel(apiLevel); 243 244 smaliParser.smali_file_return result = parser.smali_file(); 245 246 if (parser.getNumberOfSyntaxErrors() > 0 || lexer.getNumberOfSyntaxErrors() > 0) { 247 return false; 248 } 249 250 CommonTree t = result.getTree(); 251 252 CommonTreeNodeStream treeStream = new CommonTreeNodeStream(t); 253 treeStream.setTokenStream(tokens); 254 255 smaliTreeWalker dexGen = new smaliTreeWalker(treeStream); 256 dexGen.setVerboseErrors(verboseErrors); 257 dexGen.setDexBuilder(dexBuilder); 258 dexGen.smali_file(); 259 260 return dexGen.getNumberOfSyntaxErrors() == 0; 261 } 262 263 264 /** 265 * Prints the usage message. 266 */ 267 private static void usage(boolean printDebugOptions) { 268 SmaliHelpFormatter formatter = new SmaliHelpFormatter(); 269 270 int consoleWidth = ConsoleUtil.getConsoleWidth(); 271 if (consoleWidth <= 0) { 272 consoleWidth = 80; 273 } 274 275 formatter.setWidth(consoleWidth); 276 277 formatter.printHelp("java -jar smali.jar [options] [--] [<smali-file>|folder]*", 278 "assembles a set of smali files into a dex file", basicOptions, printDebugOptions?debugOptions:null); 279 } 280 281 private static void usage() { 282 usage(false); 283 } 284 285 /** 286 * Prints the version message. 287 */ 288 private static void version() { 289 System.out.println("smali " + VERSION + " (http://smali.googlecode.com)"); 290 System.out.println("Copyright (C) 2010 Ben Gruver (JesusFreke@JesusFreke.com)"); 291 System.out.println("BSD license (http://www.opensource.org/licenses/bsd-license.php)"); 292 System.exit(0); 293 } 294 295 private static void buildOptions() { 296 Option versionOption = OptionBuilder.withLongOpt("version") 297 .withDescription("prints the version then exits") 298 .create("v"); 299 300 Option helpOption = OptionBuilder.withLongOpt("help") 301 .withDescription("prints the help message then exits. Specify twice for debug options") 302 .create("?"); 303 304 Option outputOption = OptionBuilder.withLongOpt("output") 305 .withDescription("the name of the dex file that will be written. The default is out.dex") 306 .hasArg() 307 .withArgName("FILE") 308 .create("o"); 309 310 Option allowOdexOption = OptionBuilder.withLongOpt("allow-odex-instructions") 311 .withDescription("allow odex instructions to be compiled into the dex file. Only a few" + 312 " instructions are supported - the ones that can exist in a dead code path and not" + 313 " cause dalvik to reject the class") 314 .create("x"); 315 316 Option apiLevelOption = OptionBuilder.withLongOpt("api-level") 317 .withDescription("The numeric api-level of the file to generate, e.g. 14 for ICS. If not " + 318 "specified, it defaults to 15 (ICS).") 319 .hasArg() 320 .withArgName("API_LEVEL") 321 .create("a"); 322 323 Option verboseErrorsOption = OptionBuilder.withLongOpt("verbose-errors") 324 .withDescription("Generate verbose error messages") 325 .create("V"); 326 327 Option printTokensOption = OptionBuilder.withLongOpt("print-tokens") 328 .withDescription("Print the name and text of each token") 329 .create("T"); 330 331 basicOptions.addOption(versionOption); 332 basicOptions.addOption(helpOption); 333 basicOptions.addOption(outputOption); 334 basicOptions.addOption(allowOdexOption); 335 basicOptions.addOption(apiLevelOption); 336 337 debugOptions.addOption(verboseErrorsOption); 338 debugOptions.addOption(printTokensOption); 339 340 for (Object option: basicOptions.getOptions()) { 341 options.addOption((Option)option); 342 } 343 344 for (Object option: debugOptions.getOptions()) { 345 options.addOption((Option)option); 346 } 347 } 348}