Android proguard 混淆代码实际上不应该导致 NullPointerException

Posted

技术标签:

【中文标题】Android proguard 混淆代码实际上不应该导致 NullPointerException【英文标题】:Android proguard obfuscated code is causing NullPointerException when it really shouldn't be 【发布时间】:2011-11-29 12:41:02 【问题描述】:

我在 android Marketplace 上分发了一个应用程序。我收到了一小部分用户(可能是 2%)的错误报告,他们收到了 NullPointerExceptions,而这在逻辑上没有意义。

我自己从来没有复制过这个。该代码相对简单,是每个用户都必须遵循的通用代码路径。实际上,我已经获取了可能创建 NPE 的每一行单独的代码,并将其包装在一个 try-catch 块中并抛出一个自定义运行时异常,但我仍然没有捕获到 NullPointerException 错误。

在这一点上,我唯一能想象的就是与我的 Proguard 混淆有关的事情。如果您发现奇怪的行为,我已经看到其他一些文章谈到取出 -overloadaggressively 选项,但据我所知,我没有使用该选项。

有没有其他人使用 android 和 proguard 体验过神秘的 NPE。是否有任何其他设置可供人们推荐以降低可能导致此问题的优化?

还有其他想法吗?

作为参考,这里是获取 NPE 的未混淆函数:

public MainMenuScreen(final HauntedCarnival game) 
    super(game);

    game.startMusic("data/music/intro.mp3");

    stage = new Stage(Screen.SCREEN_WIDTH, Screen.SCREEN_HEIGHT,true);
    stage.addActor(new Image("background", Assets.mainMenuBackground));
    Image title = new Image("title", Assets.mainMenuTitle);
    title.x = 0;
    title.y = 340;
    resetEyeBlink();
    stage.addActor(title);
    dispatcher.registerInputProcessor(stage);
    settings = game.getSettings();

    eyeBlinkImage = new Image("eyeBlink", Assets.eyeBlink);
    if (settings.getPlayers().isEmpty()) 
        settings.addPlayer("Player One");
        settings.save(game);
    
    setupContinue();



所以我能看到的唯一可能性是游戏、调度程序和设置。

通过此代码在另一个类中设置游戏。 game 是另一个类中的最终变量:

game.setScreen(new MainMenuScreen(game));

dispatcher 在上面对 super 的调用中被设置。

getSettings() 返回一个设置对象,该对象在应用程序的一开始就设置,是私有的并且永远不会取消设置。在此方法之前也使用过几次。

没有自动装箱原语。

这里是 proguard 配置:

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

-keepattributes Signature

-keep public class com.alkilabs.hauntedcarnival.settings.Settings
-keep public class com.alkilabs.hauntedcarnival.settings.Settings 
    *;

-keep public class com.alkilabs.hauntedcarnival.settings.Player
-keep public class com.alkilabs.hauntedcarnival.settings.Player 
    *;

-keepnames public class com.alkilabs.hauntedcarnival.world.World
-keepnames public class * extends com.alkilabs.hauntedcarnival.world.upgrades.Upgrade
-keepnames public class * extends com.alkilabs.hauntedcarnival.world.achievments.Achievement

-keepnames public class com.alkilabs.hauntedcarnival.world.monsters.MonsterType
-keepclassmembers class * extends com.alkilabs.hauntedcarnival.world.monsters.Monster 
    public <init>(com.alkilabs.hauntedcarnival.world.monsters.MonsterType, java.lang.Integer, com.alkilabs.hauntedcarnival.world.World);


-keepnames public class com.alkilabs.hauntedcarnival.world.items.ItemType
-keepclassmembers class * extends com.alkilabs.hauntedcarnival.world.items.Item 
    public <init>(com.alkilabs.hauntedcarnival.world.World, java.lang.Integer, java.lang.Integer);



-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference

-dontwarn com.badlogic.gdx.scenes.scene2d.ui.utils.DesktopClipboard
-dontwarn com.badlogic.gdx.utils.JsonWriter
-dontwarn com.badlogic.gdx.utils.XmlWriter

-keepclasseswithmembernames class * 
    native <methods>;


-keepclasseswithmembers class * 
    public <init>(android.content.Context, android.util.AttributeSet);


-keepclasseswithmembers class * 
    public <init>(android.content.Context, android.util.AttributeSet, int);


-keepclassmembers class * extends android.app.Activity 
   public void *(android.view.View);


-keepclassmembers enum * 
    public static **[] values();
    public static ** valueOf(java.lang.String);


-keep class * implements android.os.Parcelable 
  public static final android.os.Parcelable$Creator *;

【问题讨论】:

您能否发布导致您使用的 NPE 和 ProGuard 设置的代码? 我添加了有关特定代码和我的 proguard 配置的更多详细信息 【参考方案1】:

好的,我想我找到了问题/困惑的根源。

proguard 所做的其中一件事是内联一些方法。因此,我的构造函数底部的 setupContinue() 函数的全部内容被直接添加到我的构造函数的内容中。所以现在我有更多代码要查看,而且我确实看到了 NPE 的更多可能性。我很确定我会深入了解我的问题。

我通过使用 proguard 生成的 obfuscated.jar 并通过反编译器运行它来解决这个问题。这是一个有趣的练习,因为您对 proguard 的内部工作有了更多的了解。对于希望更好地了解 proguard 对其代码的影响的人们,我强烈推荐它。

【讨论】:

查看已处理的代码可能非常有用。您应该确保您使用的是最新版本的 ProGuard(目前为 4.6 或 4.7 beta)。值得注意的是,4.6 版包含一个与静态初始化程序相关的修复,该修复程序由于方法被移动(内联)而无法运行。不过,令人惊讶的是,问题并没有持续发生。 Proguard 内联代码的部分帮助很大。现在我意识到为什么我的用户提交的崩溃/堆栈跟踪似乎以方法签名而不是导致 NPE 的实际代码行结束。太糟糕了,它没有专门指向导致问题的行。如果该方法包含大量代码(就像大海捞针一样),那真的很糟糕。【参考方案2】:

最好的办法是使用 mapping.txt 文件和回溯工具来查找错误的确切位置。 从那里可以更容易理解它是否确实是 Proguard 或您没有想到的其他最终情况。

为此,您需要将堆栈跟踪从开发人员控制台复制到另一个文件,假设它被称为

c:\trace.txt

现在,在您的项目中,您会找到一个包含 4 个文件的 Proguard 文件夹。 假设您的项目在

c:\项目

您需要做的是运行位于(更改为您的 Android Sdk 文件夹的位置)的回溯工具(使用批处理文件以便于使用):

c:\android-sdk-windows\tools\proguard\bin\retrace.bat

转到该文件夹​​并运行:

回溯 c:\project\proguard\mapping.txt c:\trace.txt

从那里开始,找出异常的确切行并可能找到错误会容易得多。

根据我的经验,唯一可能会搞砸的是第三方库。对于我所有的项目,普通的 Android 代码从未受到混淆的伤害。

【讨论】:

是的,我已经这样做了。我知道确切的方法和调用该方法的整个堆栈跟踪。我唯一不知道的是行号,但这是因为这是一个没有行号的生产版本。另外,据我了解,Proguard 实际上会在优化阶段重写您的一些代码(这是我怀疑 proguard 的原因之一)。所以我被降级为每条可以产生 NPE 的线的老式调试,但可惜,没有骰子。 所以我对你的唯一建议是将一个未混淆的版本放置几天,看看你是否收到额外的错误报告......我从未在我的代码中遇到过 Proguard 错误(来自最复杂的应用程序到最简单的应用程序),我总是混淆我的项目。 是的,我担心它可能会出现这种情况,但不幸的是,我只是不打算让我的代码公之于众。我看到其他一些帖子谈论手机上的proguard代码问题:***.com/questions/93290/…所以我认为这不是前所未有的。我只是希望其他人可能有类似的情况。 这是从 2008 年开始的。现在 Proguard 是具有预定义设置的 Android Sdk 的一部分。 @IncrediApp 我在 2020 年也有同样的问题【参考方案3】:

抱歉,我还不能发布 cmets(我是 SO 新手)。

这可能是我自己遇到的另一个问题。在某些手机上,有时会出现缺少 Android 库(如 JSon 库)的问题。

我建议您仔细看看哪些手机实际上获得了 NPE - 可能有一些相似之处。

在我的情况下,它是 HTC Desire Z,它缺少 JSon 库,因此每次调用 JSon 部分时应用程序强制关闭。 HTC 稍后通过对 Desire Z 的 rom 的修补程序修复了该问题。

【讨论】:

以上是关于Android proguard 混淆代码实际上不应该导致 NullPointerException的主要内容,如果未能解决你的问题,请参考以下文章

Android逆向笔记-Proguard混淆Android代码以及去打印日志信息

Android ProGuard - 只有混淆

Android ProGuard - 只有混淆

Android proguard代码混淆

Android:proguard 不会混淆源代码

如何在 Android 中使用 Proguard 不混淆接口方法及其参数?