调试Android应用

背景

需要针对已经发布的Release包进行调试,而要调试应用程序,需要满足:

  1. 应用Manifest文件中Application标签包含属性android:debuggable="true"
  2. /default.propro.debuggable值为1

针对上面限制,可选方案有:

  1. 反编译应用修改Manifest,插入android:debuggable="true"
  2. 修改boot.img,将ro.debuggable修改为1
  3. 利用Xposed进行hook并debug应用

实践

使用Debuggable属性

从官方文档可以看出(application):

android:debuggable
Whether or not the application can be debugged, even when running on a device in user mode — “true” if it can be, and “false” if not. The default value is “false”.

1
2
3
4
5
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme"
android:debuggable="true">

正常情况下,IDE会根据自身的打包来配置debug属性,因此在Manifest中最好设置debuggable属性,而由打包方式来决定。查看具体的APK的信息:

1
2
# 查看应用信息,可查看android:debuggable,0x0为false,0xffffffff为true
aapt list -v -a apk.apk

在应用中也可判断是否debug状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 自身应用
public static boolean isDebuggable(Context context) {
try {
ApplicationInfo info = context.getApplicationInfo();
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Exception e) {
}
return false;
}

# 其他应用
public static boolean isDebuggable(Context context,String packageName) {
try {
PackageInfo pkgInfo = context.getPackageManager().getPackageInfo(packageName, 1);
if (pkgInfo != null ) {
ApplicationInfo info = pkgInfo.applicationInfo;
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
} catch (Exception e) {
}
return false;
}

有的时候,我们还需要一些其他的方式或手段:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 反编译apk,可修改smali及资源等
apktool d apk.apk
# 修改Manifest,新增debuggable
vim apk/AndroidManifest.xml
# 重新打包,apk文件在dist目录中,也可指定apk文件
apktool b apk
# 更多信息
apktool -h
# debug签名,也可自行指定签名,密码android
jarsigner -verbose -keystore ~/.android/debug.keystore -signedjar new.apk apk.apk androiddebugkey
# 查看签名
keytool -list -alias androiddebugkey -keystore <path_to_debug_keystore>.keystore -storepass android -keypass android
# 有时需要系统签名,系统签名官方git有提供([security at master · android](https://github.com/android/platform_build/tree/master/target/product/security))
java -jar signapk.jar platform.x509.pem platform.pk8 apk/dist/apk.apk new_sys.apk

使用build.prop属性

有一种是重新打包boot.img,简单思路就是解包boot.img,修改ramdisk中的default.prop,再打包并刷机。因为boot.img会在开机自动覆盖,所以只能通过刷boot来搞定。具体可以搜索相关解包教程。

另外一种要涉及重新编译boot.img,不同的版本源码编译不一样。更多可以参考:Android Open Source Project
ADB权限管理:

  1. 4.2.2版本,针对连接设备需要使用者进行确认,且分设备与MAC进行管理
  2. 4.2等设备有针对ADB管理的开发者模式,通过该模式对ADB的DEBUG进行权限管理
  3. 开源版本,针对eng/userdebug/user进行管理与开放adb权限

从系统编译和编译环境控制来说,通过property进行管理,主要为ro.debuggablesystem/core/rootdir/init.usb.rcsys.usb.config里包含对adb选项。

简单编译可通过查找:

  1. ro.debuggable相关的环境变量,比如enable_target_debugging
  2. 查找init.rc中权限改变
  3. 添加开发者模式进行管理

在系统编译时,有几个编译选项:

  1. eng:编译时默认选项
  2. user:产品生成发布版
  3. userdebug:与user版本相同,添加adb服务

在4.4源码上进行相关参数的搜索(ro.securero.debuggablero.kernel.android.checkjni):
ro.secure

1
2
3
4
5
6
7
8
9
10
11
./frameworks/base/services/java/com/android/server/wm/WindowManagerService.java:    private static final String SYSTEM_SECURE = "ro.secure";
./frameworks/base/core/java/android/net/http/AndroidHttpClient.java: // Never print auth token -- we used to check ro.secure=0 to
./bionic/libc/bionic/system_properties.c: * binary tree. For instance, "ro.secure"="1" could be stored as follows:
./bionic/libc/bionic/system_properties.c: * v v v +-------->| ro.secure |
./build/core/main.mk: ADDITIONAL_DEFAULT_PROPERTIES += ro.secure=1
./build/core/main.mk: ADDITIONAL_DEFAULT_PROPERTIES += ro.secure=0
./build/core/build-system.html: <li><code>ro.secure=0</code>
./build/core/build-system.html: <li><code>ro.secure=1</code>
./system/core/adb/adb.c: /* run adbd in secure mode if ro.secure is set and
./system/core/adb/adb.c: property_get("ro.secure", value, "1");
./system/core/adb/adb.c: // don't run as root if ro.secure is set...

  • build/xxx:环境设置
  • system/xxx/adb.c:如果=1,adb root将无法成功. 还有其它的附加条件除外.
  • WindowManagerService:用于管理viewserver(Hierarchy Viewer)
  • AndroidHttpClient:在=1时,不会在log中dump auth token
  • bionic/xxx/system_properties.c:介绍properties存储结构时用的例子

ro.debuggable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
./bootable/recovery/adb_install.cpp:    int len = property_get("ro.debuggable", value, NULL);
./bootable/recovery/etc/init.rc:on property:ro.debuggable=1
./build/core/build-system.html: <li><code>ro.debuggable=0</code>
./build/core/build-system.html: <li><code>ro.debuggable=1</code>
./build/core/build-system.html: <li><code>ro.debuggable=1</code>
./build/core/main.mk: ADDITIONAL_DEFAULT_PROPERTIES += ro.debuggable=0
./build/core/main.mk: ADDITIONAL_DEFAULT_PROPERTIES += ro.debuggable=1
./build/tools/post_process_props.py: # If ro.debuggable is 1, then enable adb on USB by default
./build/tools/post_process_props.py: if prop.get("ro.debuggable") == "1":
./dalvik/docs/debugger.html:for all applications when the system property <code>ro.debuggable</code>
./dalvik/docs/debugger.html:is set to </code>1</code> (use <code>adb shell getprop ro.debuggable</code>
./device/samsung/manta/init.manta.rc:on property:ro.debuggable=1
./external/libnfc-nxp/Linux_x86/phDal4Nfc.c: property_get("ro.debuggable", value, "");
./external/openssh/servconf.c: /* Allow root login if ro.debuggable is set */
./external/openssh/servconf.c: property_get("ro.debuggable", value, "");
./frameworks/av/services/audioflinger/AudioFlinger.cpp: (void) property_get("ro.debuggable", value, "0");
./frameworks/base/core/java/android/net/SSLCertificateSocketFactory.java: return "1".equals(SystemProperties.get("ro.debuggable")) &&
./frameworks/base/core/java/android/os/Build.java: SystemProperties.getInt("ro.debuggable", 0) == 1;
./frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java: if ("1".equals(SystemProperties.get("ro.debuggable"))) {
./frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java: * If "ro.debuggable" is "1", all apps are debuggable. Otherwise,
./frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java: mEnableShiftMenuBugReports = "1".equals(SystemProperties.get("ro.debuggable"));
./frameworks/base/services/java/com/android/server/BootReceiver.java: SystemProperties.getInt("ro.debuggable", 0) == 1 ? 98304 : 65536;
./frameworks/base/services/java/com/android/server/am/ActivityManagerService.java: private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
./frameworks/base/services/java/com/android/server/wm/WindowManagerService.java: private static final String SYSTEM_DEBUGGABLE = "ro.debuggable";
./frameworks/native/opengl/libs/EGL/egl.cpp: property_get("ro.debuggable", value, "0");
./packages/services/Telephony/src/com/android/phone/AudioRouter.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/BluetoothManager.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/BluetoothPhoneService.java: && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallCommandService.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallController.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallHandlerServiceProxy.java: "ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallLogger.java: (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallModeler.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallNotifier.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallStateMonitor.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallerInfoCache.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CallerInfoCacheUpdateReceiver.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/CdmaDisplayInfo.java: private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/InCallScreenShowActivation.java: && (SystemProperties.getInt("ro.debuggable", 0) == 1)) {
./packages/services/Telephony/src/com/android/phone/InCallScreenShowActivation.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/NotificationMgr.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/OutgoingCallBroadcaster.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/PhoneGlobals.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/PhoneGlobals.java: * (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1)
./packages/services/Telephony/src/com/android/phone/PhoneGlobals.java: * 1 - normal debug logging if ro.debuggable is set (which is true in
./packages/services/Telephony/src/com/android/phone/RespondViaSmsManager.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/Ringer.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/SipCallOptionHandler.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./packages/services/Telephony/src/com/android/phone/WiredHeadsetManager.java: (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
./sdk/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java: // because am -D does not check for ro.debuggable and the
./system/core/adb/adb.c: property_get("ro.debuggable", value, "");
./system/core/adb/adb.c: property_get("ro.debuggable", value, "");
./system/core/adb/services.c: property_get("ro.debuggable", value, "");
./system/core/debuggerd/tombstone.c: property_get("ro.debuggable", value, "0");
./system/core/include/cutils/trace.h: * level tracing is not allowed unless the ro.debuggable system property is
./system/core/init/property_service.c: ret = property_get("ro.debuggable", debuggable);
./system/core/libcutils/trace.c: property_get("ro.debuggable", value, "0");
./system/core/libcutils/trace.c:// application-level tracing is allowed when the ro.debuggable system property
./system/core/rootdir/init.rc:on property:ro.debuggable=1

  • ./bootable/recovery/adb_install.cpp:debuggable 开启时,才可以成功重启adb
  • ./build/xxxx:debuggable 数据设置
  • ./dalvik/xxx:在dalvik中,如果debuggable为0, 仅AndroidManifest.xml中含有debuggable 才会支持jdwp调试
  • ./external/libnfc-nxp/Linux_x86/phDal4Nfc.c:NFC调试支持开关
  • ./external/openssh/servconf.c:openssh 允许root访问开关
  • ./frameworks/av/services/audioflinger/AudioFlinger.cpp:Audio Debug 开关
  • ./frameworks/base/core/java/android/net/SSLCertificateSocketFactory.java:SSL check 开关
  • ./frameworks/base/core/java/android/os/Build.java:IS_DEBUGGABLE环境变量控制
  • ./frameworks/base/core/java/com/android/internal/os/ZygoteConnection.java:App debuggable 开关, 如果=1, 所有应用都将进行debug支持
  • ./frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java:mEnableShiftMenuBugReports 支持
  • ./frameworks/base/services/java/com/android/server/BootReceiver.java:logged event size 控制, =1 存储量大
  • ./frameworks/base/services/java/com/android/server/am/ActivityManagerService.java:系统DEBUG状态,包括do bug report, OpenGLTrace, App Profile, App Heap Dump
  • ./frameworks/base/services/java/com/android/server/wm/WindowManagerService.java:系统DEBUG状态,与ro.secure 一起管理viewserver
  • ./frameworks/native/opengl/libs/EGL/egl.cpp:EGL debug
  • LOGD 开关:
    ./packages/services/Telephony/src/com/android/phone/AudioRouter.java:
    ./packages/services/Telephony/src/com/android/phone/BluetoothManager.java:
    ./packages/services/Telephony/src/com/android/phone/BluetoothPhoneService.java
    ./packages/services/Telephony/src/com/android/phone/CallCommandService.java
    ./packages/services/Telephony/src/com/android/phone/CallController.java
    ./packages/services/Telephony/src/com/android/phone/CallHandlerServiceProxy.java
    ./packages/services/Telephony/src/com/android/phone/CallLogger.java
    ./packages/services/Telephony/src/com/android/phone/CallModeler.java
    ./packages/services/Telephony/src/com/android/phone/CallNotifier.java
    ./packages/services/Telephony/src/com/android/phone/CallStateMonitor.java
    ./packages/services/Telephony/src/com/android/phone/CallerInfoCache.java
    ./packages/services/Telephony/src/com/android/phone/CallerInfoCacheUpdateReceiver.java
    ./packages/services/Telephony/src/com/android/phone/CdmaDisplayInfo.java
    ./packages/services/Telephony/src/com/android/phone/InCallScreenShowActivation.java
    ./packages/services/Telephony/src/com/android/phone/InCallScreenShowActivation.java
    ./packages/services/Telephony/src/com/android/phone/NotificationMgr.java
    ./packages/services/Telephony/src/com/android/phone/OutgoingCallBroadcaster.java
    ./packages/services/Telephony/src/com/android/phone/PhoneGlobals.java
    ./packages/services/Telephony/src/com/android/phone/PhoneGlobals.java
    ./packages/services/Telephony/src/com/android/phone/PhoneGlobals.java
    ./packages/services/Telephony/src/com/android/phone/RespondViaSmsManager.java
    ./packages/services/Telephony/src/com/android/phone/Ringer.java
    ./packages/services/Telephony/src/com/android/phone/SipCallOptionHandler.java
    ./packages/services/Telephony/src/com/android/phone/WiredHeadsetManager.java
  • ./sdk/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/launch/AndroidLaunchController.java:ADT app debug launcher 支持
  • ./system/core/adb/adb.c:adb root permission
  • ./system/core/adb/services.c:adb root permission
  • ./system/core/debuggerd/tombstone.c:dump_crash want log if =1
  • ./system/core/init/property_service.c:Allow local property overwrite ro.debuggerd value
  • ./system/core/libcutils/trace.c:app trace on/off
  • ./system/core/rootdir/init.rc:adbd服务开启控制

ro.kernel.android.checkjni

1
2
3
4
5
./frameworks/base/core/jni/AndroidRuntime.cpp:        property_get("ro.kernel.android.checkjni", propBuf, "");
./dalvik/docs/embedded-vm-control.html:first is <code>ro.kernel.android.checkjni</code>. This is set by the
./dalvik/docs/embedded-vm-control.html:of this overrides the value from <code>ro.kernel.android.checkjni</code>.
./build/core/main.mk: ADDITIONAL_BUILD_PROPERTIES += ro.kernel.android.checkjni=1
./build/core/build-system.html: <li><code>ro.kernel.android.checkjni=1</code>

  • ./frameworks/base/core/jni/AndroidRuntime.cpp:checkJNI value load
  • dalvik/xxx:Java VM CheckJNI on/off
  • build/xxx:checkJNI 状态设置

基本来说,ro.secure主要是系统安全相关属性开关,包括:

  • adb的root权限
  • view server(Hierarchy Viewer)的开关
  • 系统各个模块的敏感信息输出

ro.debuggable主要是系统debug信息开关,包括:

  • adb管理,是否可root,是否开机运行
  • view server(Hierarchy Viewer)的开关
  • 系统debug信息开关,包括应用内部log、audio debug, ssl check, nfc, opengl elg等信息打印、系统各应用级别状态,包括profile, jdwp-debug, heap dump, opengl trace等

ro.kernel.android.checkjni主要是Java VM checkJNI的开关

Xposed进行HOOK

从上面的两个方案来看,debug都太过麻烦,还有一个简单的方式,那就是利用开源的Xposed框架进行hook。使用这一利器之前需要先root系统。用一句话来解释Xposed:

利用JNI作为一个中转,将所有对于被hook的方法统一进入Xposed管理器,管理器再进行hook的前后调用。Xposed 调用非JVM JNI标准接口,需要准确调用dalvik/art中的方法调用,完成对原函数的调用。

当我们设置android:debuggable="true"时候,应用即可进行调试,而且可以进行JDWP连接进行调试。开启新应用时,Dalvik VM会forked/started新的线程,并开启debug,这时JDWP线程知道是否开启。我们跟踪android.os.Process.start()方法会发现,这是一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public ProcessStartResult start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags, int mountExternal,
int targetSdkVersion,
String seInfo,
String[] zygoteArgs) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
throw new RuntimeException(
"Starting VM process through Zygote failed", ex);
}
}

继续跟踪会发现,在startViaZygote方法中对DEBUG_ENABLE_DEBUGGER进行判断:

1
2
3
if ((debugFlags & Zygote.DEBUG_ENABLE_DEBUGGER) != 0) {
argsForZygote.add("--enable-debugger");
}

接下来就简单了,利用反射获得这个start的方法,并进行hook即可,代码为(完整项目查看AndroidKnife/XposedDebug):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 这里演示了两个hook的方式,更多可以参阅文档
public class PhoneDebug implements IXposedHookLoadPackage {
public boolean debugApps = true;
public static final int DEBUG_ENABLE_DEBUGGER = 0x1;

@Override
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (BuildConfig.DEBUG) {
XposedBridge.log("-- handle package: " + loadPackageParam.packageName + " process: " + loadPackageParam.processName);
}

if (loadPackageParam.appInfo == null ||
(loadPackageParam.appInfo.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) != 0) {
XposedBridge.log("-- appInfo: " + loadPackageParam.appInfo);
return;
}

try {
Method start = Process.class.getMethod(
"start", String.class, String.class, Integer.TYPE, Integer.TYPE, int[].class,
Integer.TYPE, Integer.TYPE, Integer.TYPE, String.class, String[].class);
XposedBridge.log("-- start hook, appInfo: " + loadPackageParam.appInfo);
XposedBridge.hookMethod(start, new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam methodHookParam) throws Throwable {
if (debugApps) {
int id = 5;
int flags = (Integer) methodHookParam.args[id];
if ((flags & DEBUG_ENABLE_DEBUGGER) == 0) {
flags |= DEBUG_ENABLE_DEBUGGER;
}
methodHookParam.args[id] = flags;
if (BuildConfig.DEBUG) {
XposedBridge.log("set debuggable flags to: " + flags);
}
}
}
});
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
/*
XposedBridge.hookAllMethods(Process.class, "start", new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (debugApps) {
int id = 5;
int flags = (Integer) param.args[id];
if ((flags & DEBUG_ENABLE_DEBUGGER) == 0) {
flags |= DEBUG_ENABLE_DEBUGGER;
}
param.args[id] = flags;
if (BuildConfig.DEBUG) {
XposedBridge.log("set debuggable flags to: " + flags);
}
}
}
});*/

}
}

简要介绍Xposed插件开发流程

在Android Studio上大概过程为(完整代码:AndroidKnife/XposedDebug):

  1. 创建工程,可以无Activity
  2. 下载Xposed API XposedBridgeApi-xx.jar,并将其放入libs目录
  3. 修改Manifest,新增三个meta-data标签(详细看下面代码)
  4. 实现IXposedHookLoadPackage接口及具体hook逻辑
  5. assets/xposed_init配置声明需要加载到XposedInstaller的入口
  6. ALL DONE。
    更多可以查看Development tutorial

运行Xposed插件,需要:

  1. 安装Xposed Installer
  2. 手机root

AndroidManifest.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hwangjr.xposed.mods.phonedebug">


<application
android:allowBackup="true"
android:supportsRtl="true">

<meta-data
android:name="xposedmodule"
android:value="true" />

<meta-data
android:name="xposeddescription"
android:value="Phone Debugger! (ro.debuggable or android:debuggable)" />

<meta-data
android:name="xposedminversion"
android:value="54" />

</application>

</manifest>

assets/xposed_init

1
com.hwangjr.xposed.mods.phonedebug.PhoneDebug

Ref

Xposed Module Repository
Debug All the Android Things - mwrlabs
Android USER 版本与ENG 版本的差异–MTK官方解释 - hunanwy的专栏 - 博客频道 - CSDN.NET
Xposed插件开发基础篇|码源
Android Forum for Mobile Phones, Tablets, Watches & Android App Development - XDA Forums