diff --git a/README.md b/README.md index 8e979e0a1b6e276412530cacfecef7b9ecb567f6..de7e6cdff8e0b11372a819b818a2cc2045b757d3 100644 --- a/README.md +++ b/README.md @@ -71,12 +71,12 @@ options: -d, --output-dir - output directory -ds, --output-dir-src - output directory for sources -dr, --output-dir-res - output directory for resources - -j, --threads-count - processing threads count -r, --no-res - do not decode resources -s, --no-src - do not decompile source code --single-class - decompile a single class - --output-format - can be 'java' or 'json' (default: java) + --output-format - can be 'java' or 'json', default: java -e, --export-gradle - save as android gradle project + -j, --threads-count - processing threads count, default: 4 --show-bad-code - show inconsistent code (incorrectly decompiled) --no-imports - disable use of imports, always write entire package name --no-debug-info - disable debug info @@ -85,22 +85,25 @@ options: --escape-unicode - escape non latin characters in strings (with \u) --respect-bytecode-access-modifiers - don't change original access modifiers --deobf - activate deobfuscation - --deobf-min - min length of name, renamed if shorter (default: 3) - --deobf-max - max length of name, renamed if longer (default: 64) + --deobf-min - min length of name, renamed if shorter, default: 3 + --deobf-max - max length of name, renamed if longer, default: 64 --deobf-rewrite-cfg - force to save deobfuscation map --deobf-use-sourcename - use source file name as class name alias - --rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' + --rename-flags - what to rename, comma-separated, 'case' for system case sensitivity, 'valid' for java identifiers, 'printable' characters, 'none' or 'all' (default) --fs-case-sensitive - treat filesystem as case sensitive, false by default --cfg - save methods control flow graph to dot file --raw-cfg - save methods control flow graph (use raw instructions) -f, --fallback - make simple dump (using goto instead of 'if', 'for', etc) - -v, --verbose - verbose output + -v, --verbose - verbose output (set --log-level to DEBUG) + -q, --quiet - turn off output (set --log-level to QUIET) + --log-level - set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG, default: PROGRESS --version - print jadx version -h, --help - print this help Example: jadx -d out classes.dex jadx --rename-flags "none" classes.dex jadx --rename-flags "valid,printable" classes.dex + jadx --log-level error app.apk ``` These options also worked on jadx-gui running from command line and override options from preferences dialog diff --git a/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java b/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java index f87f6a9bf861a9d5c733620ce72818f719fa0022..f51ef60de2c0c9d77f48902b7c67b756cb654932 100644 --- a/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java +++ b/jadx-cli/src/main/java/jadx/cli/JCommanderWrapper.java @@ -9,6 +9,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.jetbrains.annotations.Nullable; + import com.beust.jcommander.JCommander; import com.beust.jcommander.ParameterDescription; import com.beust.jcommander.ParameterException; @@ -80,7 +82,10 @@ public class JCommanderWrapper { opt.append(" ").append(p.getNames()); addSpaces(opt, maxNamesLen - opt.length() + 3); opt.append("- ").append(p.getDescription()); - addDefaultValue(args, f, opt); + String defaultValue = getDefaultValue(args, f, opt); + if (defaultValue != null) { + opt.append(", default: ").append(defaultValue); + } out.println(opt); } out.println("Example:"); @@ -102,26 +107,26 @@ public class JCommanderWrapper { return fieldList; } - private void addDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) { - Class fieldType = f.getType(); - if (fieldType == int.class) { - try { - int val = f.getInt(args); - opt.append(" (default: ").append(val).append(')'); - } catch (Exception e) { - // ignore + @Nullable + private String getDefaultValue(JadxCLIArgs args, Field f, StringBuilder opt) { + try { + Class fieldType = f.getType(); + if (fieldType == int.class) { + return Integer.toString(f.getInt(args)); } - } - if (fieldType == String.class) { - try { - String val = (String) f.get(args); + if (fieldType == String.class) { + return (String) f.get(args); + } + if (Enum.class.isAssignableFrom(fieldType)) { + Enum val = (Enum) f.get(args); if (val != null) { - opt.append(" (default: ").append(val).append(')'); + return val.name(); } - } catch (Exception e) { - // ignore } + } catch (Exception e) { + // ignore } + return null; } private static void addSpaces(StringBuilder str, int count) { diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java index 9f4cf9b5270b42f5d0fcb1c681e4c1f6bba1f2ab..dd7c3031a8858d1d4581e03af274b4a469957a2b 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLI.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLI.java @@ -40,10 +40,10 @@ public class JadxCLI { int errorsCount = jadx.getErrorsCount(); if (errorsCount != 0) { jadx.printErrorsReport(); - LOG.error("finished with errors"); + LOG.error("finished with errors, count: {}", errorsCount); } else { LOG.info("done"); } - return errorsCount; + return 0; } } diff --git a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java index 1b798f23d010f4748863f5228aad61b02ec79d18..56ecf59729cd413c6d81cb140aefce5f5c25ac2c 100644 --- a/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java +++ b/jadx-cli/src/main/java/jadx/cli/JadxCLIArgs.java @@ -8,15 +8,9 @@ import java.util.Locale; import java.util.Set; import java.util.stream.Collectors; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.beust.jcommander.IStringConverter; import com.beust.jcommander.Parameter; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.Appender; - import jadx.api.JadxArgs; import jadx.api.JadxArgs.RenameEnum; import jadx.api.JadxDecompiler; @@ -114,9 +108,19 @@ public class JadxCLIArgs { @Parameter(names = { "-f", "--fallback" }, description = "make simple dump (using goto instead of 'if', 'for', etc)") protected boolean fallbackMode = false; - @Parameter(names = { "-v", "--verbose" }, description = "verbose output") + @Parameter(names = { "-v", "--verbose" }, description = "verbose output (set --log-level to DEBUG)") protected boolean verbose = false; + @Parameter(names = { "-q", "--quiet" }, description = "turn off output (set --log-level to QUIET)") + protected boolean quiet = false; + + @Parameter( + names = { "--log-level" }, + description = "set log level, values: QUIET, PROGRESS, ERROR, WARN, INFO, DEBUG", + converter = LogHelper.LogLevelConverter.class + ) + protected LogHelper.LogLevelEnum logLevel = LogHelper.LogLevelEnum.PROGRESS; + @Parameter(names = { "--version" }, description = "print jadx version") protected boolean printVersion = false; @@ -158,15 +162,7 @@ public class JadxCLIArgs { if (threadsCount <= 0) { throw new JadxException("Threads count must be positive, got: " + threadsCount); } - if (verbose) { - ch.qos.logback.classic.Logger rootLogger = - (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); - // remove INFO ThresholdFilter - Appender appender = rootLogger.getAppender("STDOUT"); - if (appender != null) { - appender.clearAllFilters(); - } - } + LogHelper.setLogLevelFromArgs(this); } catch (JadxException e) { System.err.println("ERROR: " + e.getMessage()); jcw.printUsage(); @@ -339,15 +335,18 @@ public class JadxCLIArgs { try { set.add(RenameEnum.valueOf(s.toUpperCase(Locale.ROOT))); } catch (IllegalArgumentException e) { - String values = Arrays.stream(RenameEnum.values()) - .map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'') - .collect(Collectors.joining(", ")); throw new IllegalArgumentException( '\'' + s + "' is unknown for parameter " + paramName - + ", possible values are " + values); + + ", possible values are " + enumValuesString(RenameEnum.values())); } } return set; } } + + public static String enumValuesString(Enum[] values) { + return Arrays.stream(values) + .map(v -> '\'' + v.name().toLowerCase(Locale.ROOT) + '\'') + .collect(Collectors.joining(", ")); + } } diff --git a/jadx-cli/src/main/java/jadx/cli/LogHelper.java b/jadx-cli/src/main/java/jadx/cli/LogHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..e92c488412ac5a5693fd97b7f195dc5f53ae627a --- /dev/null +++ b/jadx-cli/src/main/java/jadx/cli/LogHelper.java @@ -0,0 +1,93 @@ +package jadx.cli; + +import org.slf4j.LoggerFactory; + +import com.beust.jcommander.IStringConverter; + +import ch.qos.logback.classic.Level; +import ch.qos.logback.classic.Logger; + +import jadx.api.JadxDecompiler; + +public class LogHelper { + private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(LogHelper.class); + + public enum LogLevelEnum { + QUIET(Level.OFF), + PROGRESS(Level.OFF), + ERROR(Level.ERROR), + WARN(Level.WARN), + INFO(Level.INFO), + DEBUG(Level.DEBUG); + + private final Level level; + + LogLevelEnum(Level level) { + this.level = level; + } + + public Level getLevel() { + return level; + } + } + + public static void setLogLevelFromArgs(JadxCLIArgs args) { + if (isCustomLogConfig()) { + return; + } + LogLevelEnum logLevel = args.logLevel; + if (args.quiet) { + logLevel = LogLevelEnum.QUIET; + } else if (args.verbose) { + logLevel = LogLevelEnum.DEBUG; + } + + applyLogLevel(logLevel); + } + + public static void applyLogLevel(LogLevelEnum logLevel) { + Logger rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + rootLogger.setLevel(logLevel.getLevel()); + + if (logLevel != LogLevelEnum.QUIET) { + // show progress for all levels except quiet + setLevelForClass(JadxCLI.class, Level.INFO); + setLevelForClass(JadxDecompiler.class, Level.INFO); + } + } + + private static void setLevelForClass(Class cls, Level level) { + ((Logger) LoggerFactory.getLogger(cls)).setLevel(level); + } + + /** + * Try to detect if user provide custom logback config via -Dlogback.configurationFile= + */ + private static boolean isCustomLogConfig() { + try { + String logbackConfig = System.getProperty("logback.configurationFile"); + if (logbackConfig == null) { + return false; + } + LOG.debug("Use custom log config: {}", logbackConfig); + return true; + } catch (Exception e) { + LOG.error("Failed to detect custom log config", e); + } + return false; + } + + public static class LogLevelConverter implements IStringConverter { + + @Override + public LogLevelEnum convert(String value) { + try { + return LogLevelEnum.valueOf(value.toUpperCase()); + } catch (Exception e) { + throw new IllegalArgumentException( + '\'' + value + "' is unknown log level, possible values are " + + JadxCLIArgs.enumValuesString(LogLevelEnum.values())); + } + } + } +} diff --git a/jadx-cli/src/main/resources/logback.xml b/jadx-cli/src/main/resources/logback.xml index 1b666db4aabfb35a452df4a91d5ae96604cd3769..294e272c5eb1a874cd71d177b03349f1d94aad88 100644 --- a/jadx-cli/src/main/resources/logback.xml +++ b/jadx-cli/src/main/resources/logback.xml @@ -1,14 +1,11 @@ - - INFO - %-5level - %msg%n - +