在不同版本上,该对象的数据类型都不一样。
4.0(华为畅玩4C,版本4.4.4)
5.0(华为P8 Lite,版本5.0.2)
6.0(三星GALAXY S7,版本6.0.1)
7.0+(三星GALAXY C7,版本7.0)
这里是第一个比较坑的地方,有的是int数组,有的是long数组,有的是Object数组的第一/最后一项,而且指令集位置有的在一起,有的是间隔的,确实比较坑,需要适配兼容。
8.0
8.0有一个问题,异常处理系统初始化时会执行如下逻辑:
// 代码版本:Android8.0,文件名称:RuntimeInit.java
protected static final void commonInit() {
if (DEBUG) Slog.d(TAG, “Entered RuntimeInit!”);
/*
* set handlers; these apply to all threads in the VM. Apps can replace
* the default handler, but not the pre handler.
*/
Thread.setUncaughtExceptionPreHandler(new LoggingHandler());
Thread.setDefaultUncaughtExceptionHandler(new KillApplicationHandler());
…
}
其中 Thread.setUncaughtExceptionPreHandler(new LoggingHandler()); 是该版本新增的,会在uncaughtException 之前调用,LoggingHandler 会导致Throwable#getInternalStackTrace被调用,该方法逻辑如下:
/**
* Returns an array of StackTraceElement. Each StackTraceElement
* represents a entry on the stack.
*/
private StackTraceElement[] getInternalStackTrace() {
if (stackTrace == EmptyArray.STACK_TRACE_ELEMENT) {
stackTrace = nativeGetStackTrace(stackState);
stackState = null; // Let go of intermediate representation.
return stackTrace;
} else if (stackTrace == null) {
return EmptyArray.STACK_TRACE_ELEMENT;
} else {
return stackTrace;
}
}
因此,8.0以上版本在Hook默认的 UncaughtExceptionHandler 时,stackState信息**已经丢失了!!**我的解决办法是 反射Hook掉Thread#uncaughtExceptionPreHandler 字段,使 LoggingHandler 被覆盖
但是在9.0会有以下错误:
Accessing hidden field Ljava/lang/Thread;->uncaughtExceptionPreHandler:Ljava/lang/Thread$UncaughtExceptionHandler; (dark greylist, reflection)
java.lang.NoSuchFieldException: No field uncaughtExceptionPreHandler in class Ljava/lang/Thread; (declaration of ‘java.lang.Thread’ appears in /system/framework/core-oj.jar)
at java.lang.Class.getDeclaredField(Native Method)
at top.vimerzhao.testremovelineinfo.ExceptionHookUtils.init(ExceptionHookUtils.java:18)
…
通过类似FreeReflection目前可以突破这个限制,因此Android 9+ 的机型依然可以使用这个方案。
## 深入
这里再详细介绍下底层获取行号的逻辑,首先Throwable会调用到一个native方法(这里的注释信息讲的很清楚,注意看):
//http://androidxref.com/4.4_r1/xref/dalvik/vm/native/dalvik_system_VMStack.cpp
/*
* public static int fillStackTraceElements(Thread t, StackTraceElement[] stackTraceElements)
* Retrieve a partial stack trace of the specified thread and return
* the number of frames filled. Returns 0 on failure.
*/
static void Dalvik_dalvik_system_VMStack_fillStackTraceElements(const u4* args,
JValue* pResult) {
Object* targetThreadObj = (Object*) args[0];
ArrayObject* steArray = (ArrayObject*) args[1];
size_t stackDepth;
int* traceBuf = getTraceBuf(targetThreadObj, &stackDepth);
if (traceBuf == NULL)
RETURN_PTR(NULL);
/*
* Set the raw buffer into an array of StackTraceElement.
*/
if (stackDepth > steArray->length) {
stackDepth = steArray->length;
}
dvmFillStackTraceElements(traceBuf, stackDepth, steArray);
free(traceBuf);
RETURN_INT(stackDepth);
}
该方法计算行信息的是dvmFillStackTraceElements:
// http://androidxref.com/4.4_r1/xref/dalvik/vm/Exception.cpp
/*
* Fills the StackTraceElement array elements from the raw integer
* data encoded by dvmFillInStackTrace().
* “intVals” points to the first {method,pc} pair.
*/
void dvmFillStackTraceElements(const int* intVals, size_t stackDepth, ArrayObject* steArray) {
unsigned int i;
/* init this if we haven’t yet */
if (!dvmIsClassInitialized(gDvm.classJavaLangStackTraceElement))
dvmInitClass(gDvm.classJavaLangStackTraceElement);
/*
* Allocate and initialize a StackTraceElement for each stack frame.
* We use the standard constructor to configure the object.
*/
for (i = 0; i < stackDepth; i++) {
Object* ste = dvmAllocObject(gDvm.classJavaLangStackTraceElement,ALLOC_DEFAULT);
if (ste == NULL) {
return;
}
Method* meth = (Method*) *intVals++;
int pc = *intVals++;
int lineNumber;
if (pc == -1) // broken top frame?
lineNumber = 0;
else
lineNumber = dvmLineNumFromPC(meth, pc);
…
/*
* Invoke:
* public StackTraceElement(String declaringClass, String methodName,
* String fileName, int lineNumber)
* (where lineNumber==-2 means “native”)
*/
JValue unused;
dvmCallMethod(dvmThreadSelf(), gDvm.methJavaLangStackTraceElement_init,
ste, &unused, className, methodName, fileName, lineNumber);
…
dvmSetObjectArrayElement(steArray, i, ste);
}
}
由此可知,默认行号可能是0,否则通过dvmLineNumFromPC获取具体信息:
//http://androidxref.com/4.4_r1/xref/dalvik/vm/interp/Stack.cpp
/*
* Determine the source file line number based on the program counter.
* “pc” is an offset, in 16-bit units, from the start of the method’s code.
* Returns -1 if no match was found (possibly because the source files were
* compiled without “-g”, so no line number information is present).
* Returns -2 for native methods (as expected in exception traces).
*/
int dvmLineNumFromPC(const Method* method, u4 relPc) {
const DexCode* pDexCode = dvmGetMethodCode(method);
if (pDexCode == NULL) {
if (dvmIsNativeMethod(method) && !dvmIsAbstractMethod(method))
return -2;
return -1; /* can happen for abstract method stub */
}
LineNumFromPcContext context;
memset(&context, 0, sizeof(context));
context.address = relPc;
// A method with no line number info should return -1
context.lineNum = -1;
dexDecodeDebugInfo(method->clazz->pDvmDex->pDexFile, pDexCode,
method->clazz->descriptor,
method->prototype.protoIdx,
method->accessFlags,
lineNumForPcCb, NULL, &context);
return context.lineNum;
}
由此可知,默认行号还可能是-2/-1,而dexDecodeDebugInfo里面就是具体的解析信息了,不做深入分析(太复杂了,给看懵逼了~)。
## 效果
以一台Android6.0的魅族为例,我的Demo部分日志如下:
01-14 10:17:42.525 845-868/? I/ExceptionHookUtils: succeed [28, 12, 12, 5, 6]
01-14 10:17:42.525 845-868/? I/ExceptionHookUtils: set top.vimerzhao.testremovelineinfo.a from -1 to 28
01-14 10:17:42.526 845-868/? I/ExceptionHookUtils: set top.vimerzhao.testremovelineinfo.a from -1 to 12
01-14 10:17:42.526 845-868/? I/ExceptionHookUtils: set top.vimerzhao.testremovelineinfo.a from -1 to 12
01-14 10:17:42.526 845-868/? I/ExceptionHookUtils: set top.vimerzhao.testremovelineinfo.MainActivity$a from -1 to 5
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!**
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-3iGOjnKN-1710932830661)]
[外链图片转存中…(img-yNp57gS3-1710932830662)]
[外链图片转存中…(img-9ADbyjEu-1710932830662)]
[外链图片转存中…(img-19GNdFBr-1710932830662)]
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
[外链图片转存中…(img-OPtMKsao-1710932830663)]