【问题标题】:How to configure Proguard to remove non-trivial log messages?如何配置 Proguard 以删除重要的日志消息?
【发布时间】:2014-11-17 04:22:53
【问题描述】:

Proguard 很好地删除了琐碎的日志调用。 (使用 assumenosideeffects 关键字)
但它处理非平凡日志调用很差。

我所说的“非平凡”指的不仅仅是一个字符串。
例如:Log.i(TAG,"velocity="+velocity)"。
Proguard 保留new StringBuilder("velocity="),以及附加的变量来保留值,并没有太多地掩盖该变量。它仅删除对 Log 的最终调用。
字符串将保留在那里,浪费内存、cpu 周期并帮助破解者理解代码。


所以为了解决这个问题,我用if(BuildConfig.DEBUG){...} 包装了我的应用程序中的每个非平凡 调试日志调用。 但是用if(..){..} 包装每个日志很乏味且容易出错。
它肯定不是 DRY(不要重复自己)。

有没有办法标记 Proguard(或任何其他方式)完全删除的方法,包括所有调用方法?

类似:

@proguard_purge
public static void vanishingDebug(String whatever) {
  Log.i(TAG,whatever);
}

所以该方法会被混淆器消失,并且所有对该方法的调用也会递归消失?

详细说明

混淆将优化代码并删除未使用或排除的方法。
但是代码编译会在方法调用之前生成额外的字节码之前,这些字节码将被删除,并且即使在混淆之后,前面的代码保留下来,并留下如下代码:

new StringBuilder("velocity=").append(a)

(假设在编译时无法确定a。要测试,请使用velocity=Math.random();

这使得混淆代码很容易理解。
要重现此问题,您需要安装 dex2jar 将 apk 转换为 jar,并安装 JAD 将 jar 转换为 java 代码。
您会看到真正留下的东西,并感到恐惧。

示例

@Override
protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    Log.i("TAG", "Simple Comment"); // << Disappears well!

    double index = Math.random();
    index++;

    Log.i("TAG2", "log_index=" + index); // << hmm... (!)

    // class with only log calls inside
    new ReferencedClass();

    // simple method call
    MyLogger.notLog("no_log" + index); // << stays, as expected

    // simple method call with only Log call inside (aka "Log Wrapper")
    MyLogger.log("log" + index);  // << stays, as expected

    Log.i("TAG2", "This is random:" + Math.random()); // << stays, same as above

    setContentView(R.layout.activity_main);
}

使用这个混淆配置:

-assumenosideeffects class android.util.Log {
    public static *** isLoggable(java.lang.String, int);
    public static *** d(...);
    public static *** v(...);
    public static *** i(...);
    public static *** w(...);
    public static *** e(...);
}

它将被混淆为可以反编译和反混淆的状态:

  protected void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    double d = 1.0D + Math.random();
    new StringBuilder("log_index=").append(d).toString();
    new b();
    a.a("no_log" + d);
    new StringBuilder("log").append(d).toString();
    a.a();
    new StringBuilder("This is random:").append(Math.random()).toString();
    setContentView(2130903064);
  }

【问题讨论】:

  • 你用过assumenosideeffects关键字吗?
  • @NickT 当然,这就是我使用 trivial 的意思,我编辑了我的问题以澄清这一点。

标签: android proguard


【解决方案1】:

在我的研究中,proguard 根本无法做到这一点。但是,您可以做的是设置您的 ant 构建脚本。

<target name="-commentoutlogs">
    <replaceregexp match="(Log\..*?;\s*\n)" replace="/*\1*/" flags="gs" byline="false">
        <fileset dir="src">
            <include name="**/*.java"/>
        </fileset>
    </replaceregexp>
</target>


<target name="-uncommentlogs">
    <replaceregexp match="\/\*(Log\..*?;\s*\n)\*\/" replace="\1" flags="gs" byline="false">
        <fileset dir="src">
            <include name="**/*.java"/>
        </fileset>
    </replaceregexp>
</target>

这是一个简单的基于正则表达式的脚本,您可以在 build.xml 文件中使用,将其添加到 ant 发布目标,如下所示:

<target name="release"
            depends="-uncommentlogsbefore, -commentoutlogs, -set-release-mode, -release-obfuscation-check, -package, -post-package, -release-prompt-for-password, -release-nosign, -release-sign, -uncommentlogsafter, -post-build"
            description="Builds the application in release mode.">
</target>

当然,您还需要创建名为 uncommentlogs 的目标,与 uncommentlogs 具有相同的主体

这基本上将 /* 放在任何日志之前。和 */ 在最近的 );

【讨论】:

  • 谢谢!就我而言,问题是我正在使用调用 proguard 的 3rd 方构建工具(Crashlytics),所以我不确定是否可以在他们的脚本中插入 Ant 脚本。但它可能会解决其他开发人员的问题,希望有人在这里发表评论,如果它有效
【解决方案2】:

我得出的结论是,ProGuard 不可能知道StringBuilder 与他被要求删除的日志有关。
因此,不能有 ProGuard 规则来删除该类型的复杂 Log 项:

Log.i("TAG2", "This is random:" + Math.random());

而且它总是会产生带有剩余的混淆代码,可以解码为:

new StringBuilder("This is random:").append(Math.random()).toString();

消除剩余的唯一方法是将每个非平凡的日志调用包装为:

if (BuildConfig.DEBUG) Log.i(...)

【讨论】:

    【解决方案3】:

    您可以将以下规则添加到您的 proguard 文件中,以删除对 debug 日志打印输出的所有调用:

    -assumenosideeffects class android.util.Log {
      public static *** d(...);  }
    

    例如,以下代码中的所有行都将从您的混淆 jar 文件中消失:

    String logevent = "log event";
    android.util.Log.d("Tag", "This is my ");
    android.util.Log.d("Tag", "This is my " + logevent);
    

    附言
    如果您有此行,请不要忘记删除或注释掉它:-dontoptimize

    【讨论】:

    • 感谢您的回答。不过,有一个问题,您是否检查了(去)混淆代码,在使用 MyLog("text"+var) 时确实没有 StringBuilder 留在调用方?我不明白包装器将如何发挥作用。
    • 包装器没有区别,您可以在 android Log 类上使用它(对答案进行编辑)。只是我在我的代码中使用了一个包装器。如果您在混淆后打开 jar 文件,您将看不到日志行或保存日志硬编码字符串的变量。
    • 问题不在于混淆后的日志调用仍然存在。问题是当在日志中使用 ("x="+x) 时,javac 会生成一个带有 "x=" 的 StringBuilder 并连接 x 的值。只有这样,它才会将结果放入日志调用中。 Proguard 删除 only 日志调用,但保留 StringBuilder 的东西。你具体检查了吗?
    • 您在使用 Log.d("tag", "x=" + var) 时在哪里找到 StringBuilder 的东西?在混淆的类文件中?如果是这样,我在我的混淆类文件中找不到任何东西,我用上面的三行代码从答案中重新检查了它。我错过了什么吗?
    • 4.7 也在这里。我以前尝试过类似的方法,但还没有尝试过您的解决方案,因为我看不出它有什么不同。使用 dex2jar 去混淆和 JAD 打开它,你可能也会看到它。问题发生在调用代码中
    猜你喜欢
    • 2012-09-05
    • 1970-01-01
    • 1970-01-01
    • 2013-03-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-09
    相关资源
    最近更新 更多