异常打印
Java 如果发生异常,通常会调用 Throwable.printStackTrace 去打印堆栈信息。
堆栈信息包括完整类名,方法名,java 文件名,行号
而这样的信息根据发生 Crash 线程所经历的n个方法会打印出n行。
整个过程被称为栈回朔
栈回朔
栈回朔的过程发生于异常被 New 出来的时候
Throwable.backtrace 这个 Throwbale 的成员变量就是用来保存栈回朔链表的
/**
* WARNING: this must be the second variable. Native code saves some
* indication of the stack backtrace in this slot.
*/
private transient Object backtrace = buildStackElement();//
private native StackTraceElement buildStackElement();
可见 Throwable 初始化的时候会调用 native 方法 buildStackElement()
StackTraceElement
public class StackTraceElement {
private String declaringClass;
private String methodName;
private String fileName;
private int lineNumber;
StackTraceElement parent;
.......
}
buildStackElement()
s32 java_io_Throwable_buildStackElement(Runtime *runtime, JClass *clazz) {
RuntimeStack *stack = runtime->stack;
Instance *tmps = (Instance *) localvar_getRefer(runtime->localvar, 0);
#if _JVM_DEBUG_BYTECODE_DETAIL > 5
invoke_deepth(runtime);
jvm_printf("java_io_Throwable_buildStackElement %s \n", utf8_cstr(tmps->mb.clazz->name));
#endif
Instance *ins = buildStackElement(runtime, runtime->parent);
push_ref(stack, ins);
return 0;
}
这里从操作数栈中取出当前 Throwbale 对象,runtime 是当前栈帧即调用 buildStackElement() 的栈帧,所以回朔开始应该是上一个栈帧。
//生成堆栈元素对象 StackTraceElement
Instance *buildStackElement(Runtime *runtime, Runtime *target) {
JClass *clazz = classes_load_get_c(STR_CLASS_JAVA_LANG_STACKTRACE, target);
if (clazz) {
Instance *ins = instance_create(runtime, clazz);
gc_refer_hold(ins);
instance_init(ins, runtime);
c8 *ptr;
//方法所在类
ptr = getFieldPtr_byName_c(ins, STR_CLASS_JAVA_LANG_STACKTRACE, "declaringClass", STR_INS_JAVA_LANG_STRING, runtime);
if (ptr) {
Instance *name = jstring_create(target->clazz->name, runtime);
setFieldRefer(ptr, name);
}
//调用的方法名
ptr = getFieldPtr_byName_c(ins, STR_CLASS_JAVA_LANG_STACKTRACE, "methodName", STR_INS_JAVA_LANG_STRING, runtime);
if (ptr) {
Instance *name = jstring_create(target->method->name, runtime);
setFieldRefer(ptr, name);
}
//java 源文件名
ptr = getFieldPtr_byName_c(ins, STR_CLASS_JAVA_LANG_STACKTRACE, "fileName", STR_INS_JAVA_LANG_STRING, runtime);
if (ptr) {
Instance *name = jstring_create(target->clazz->source, runtime);
setFieldRefer(ptr, name);
}
//代码所在行号
ptr = getFieldPtr_byName_c(ins, STR_CLASS_JAVA_LANG_STACKTRACE, "lineNumber", "I", runtime);
if (ptr) {
if (target->method->access_flags & ACC_NATIVE) {
setFieldInt(ptr, -1);
} else {
setFieldInt(ptr, getLineNumByIndex(target->ca, (s32) (target->pc - target->ca->code)));
}
}
//递归,如果还有父方法栈则递归生成父方法栈信息
if (target->parent && target->parent->parent) {
ptr = getFieldPtr_byName_c(ins, STR_CLASS_JAVA_LANG_STACKTRACE, "parent", "Ljava/lang/StackTraceElement;", runtime);
if (ptr) {
Instance *parent = buildStackElement(runtime, target->parent);
setFieldRefer(ptr, parent);
}
}
gc_refer_release(ins);
return ins;
}
return NULL;
}
- new StackTraceElement 并且向内部填充栈帧信息(类名,源码名,方法名,行号)
- 递归生成夫栈帧的 StackTraceElement
打印栈回朔信息
那么当调用 printStacktrace 的时候就很简单了。
- Java 层实现
public void printStackTrace(Writer writer) {
try {
writer.write(getCodeStack());
} catch (IOException ex) {
}
}
public String getCodeStack() {
StringBuilder stack = new StringBuilder();
String msg = getMessage();
stack.append(this.getClass().getName()).append(": ").append(msg == null ? "" : msg).append("\n");
if (backtrace != null) {
StackTraceElement sf = (StackTraceElement) backtrace;
while (sf != null) {
try {
Class clazz = Class.forName(sf.getDeclaringClass());
if (!clazz.isAssignableFrom(Throwable.class)) {
stack.append(" at ").append(sf.getDeclaringClass());
stack.append(".").append(sf.getMethodName());
stack.append("(").append(sf.getFileName());
stack.append(":").append(sf.getLineNumber());
stack.append(")\n");
}
sf = sf.parent;
} catch (Exception e) {
}
}
}
return stack.toString();
}
- Native
//打印异常
void print_exception(Runtime *runtime) {
__refer ref = pop_ref(runtime->stack);
Instance *ins = (Instance *) ref;
Utf8String *getStackFrame_name = utf8_create_c("getCodeStack");
Utf8String *getStackFrame_type = utf8_create_c("()Ljava/lang/String;");
MethodInfo *getStackFrame = find_methodInfo_by_name(ins->mb.clazz->name, getStackFrame_name,
getStackFrame_type, runtime);
utf8_destory(getStackFrame_name);
utf8_destory(getStackFrame_type);
if (getStackFrame) {
push_ref(runtime->stack, ins);
s32 ret = execute_method_impl(getStackFrame, runtime, getStackFrame->_this_class);
if (ret != RUNTIME_STATUS_NORMAL) {
ins = pop_ref(runtime->stack);
return;
}
ins = (Instance *) pop_ref(runtime->stack);
Utf8String *str = utf8_create();
jstring_2_utf8(ins, str);
printf("%s\n", utf8_cstr(str));
utf8_destory(str);
} else {
printf("ERROR: %s\n", utf8_cstr(ins->mb.clazz->name));
}
}
异常抛出
异常产生
- 虚拟机运行时,内部发出的异常,如没有搜寻到某方法,称为虚拟机内部异常
- 在 Java 代码中使用 throw 抛出的异常
虚拟机内部异常
如下 Class.forName 如果没有找到对应的 Class 则从虚拟机内部抛出 ClassNotFoundException
抛出流程一般是:
- New 出内部异常的实例
- 返回值设为 RUNTIME_STATUS_EXCEPTION,通知解释器下一步跳转到异常处理 Handler
s32 java_lang_Class_forName(Runtime *runtime, JClass *clazz) {
RuntimeStack *stack = runtime->stack;
Instance *jstr = (Instance *) localvar_getRefer(runtime->localvar, 0);
JClass *cl = NULL;
s32 ret = RUNTIME_STATUS_NORMAL;
if (jstr) {
......
cl = classes_load_get(ustr, runtime);
if (!cl) {
Instance *exception = exception_create(JVM_EXCEPTION_CLASSNOTFOUND, runtime);
push_ref(stack, (__refer) exception);
ret = RUNTIME_STATUS_EXCEPTION;
} else {
.....
}
} else {
Instance *exception = exception_create(JVM_EXCEPTION_NULLPOINTER, runtime);
push_ref(stack, (__refer) exception);
ret = RUNTIME_STATUS_EXCEPTION;
}
.....
return ret;
}
Java 中抛出异常
- 操作数栈中取出目标异常的对象并推入本地变量
- 返回值设为 RUNTIME_STATUS_EXCEPTION,通知解释器下一步跳转到异常处理 Handler
case op_athrow: {
Instance *ins = (Instance *) pop_ref(stack);
push_ref(stack, (__refer) ins);
#if _JVM_DEBUG_BYTECODE_DETAIL > 5
invoke_deepth(runtime);
jvm_printf("athrow [%llx].exception throws \n", (s64)(intptr_t)ins);
#endif
//opCode += 1;
ret = RUNTIME_STATUS_EXCEPTION;
break;
}
异常处理
在 Switch 解释器取指令循环体的末尾,如果判断到上一次指令执行的返回值 ret == RUNTIME_STATUS_EXCEPTION,则代表现在需要进入异常分发的流程。
- 循环对比 Code 属性中的异常向量表,如果 catch 的类型符合抛出异常的类型的话,则进入该分支
else if (ret == RUNTIME_STATUS_EXCEPTION) {
//取出目标异常对象
Instance *ins = pop_ref(stack);
//jvm_printf("stack size:%d , enter size:%d\n", stack->size, stackSize);
//restore stack enter method size, must pop for garbage
while (stack->size > stackSize)pop_empty(stack);
push_ref(stack, ins);
// if (utf8_equals_c(ins->mb.clazz->name, "espresso/util/NotConstant")) {
// int debug = 1;
// }
#if _JVM_DEBUG_BYTECODE_DETAIL > 3
s32 lineNum = getLineNumByIndex(ca, runtime->pc - ca->code);
printf(" at %s.%s(%s.java:%d)\n",
utf8_cstr(clazz->name), utf8_cstr(method->name),
utf8_cstr(clazz->name),
lineNum
);
#endif
//从异常向量表中找到合适的异常 Handler,即对应的 catch 分支
ExceptionTable *et = _find_exception_handler(runtime, ins, ca, (s32) (opCode - ca->code), ins);
if (et == NULL) {
break;
} else {
#if _JVM_DEBUG_BYTECODE_DETAIL > 3
jvm_printf("Exception : %s\n", utf8_cstr(ins->mb.clazz->name));
#endif
//跳转到合适的分支
opCode = (ca->code + et->handler_pc);
ret = RUNTIME_STATUS_NORMAL;
}
}
static ExceptionTable *
_find_exception_handler(Runtime *runtime, Instance *exception, CodeAttribute *ca, s32 offset, __refer exception_ref) {
Instance *ins = (Instance *) exception_ref;
s32 i;
ExceptionTable *e = ca->exception_table;
for (i = 0; i < ca->exception_table_length; i++) {
if (offset >= (e + i)->start_pc
&& offset <= (e + i)->end_pc) {
if (!(e + i)->catch_type) {
return e + i;
}
ConstantClassRef *ccr = class_get_constant_classref(runtime->clazz, (e + i)->catch_type);
JClass *catchClass = classes_load_get(ccr->name, runtime);
//catch 类型和抛出类型对比
if (instance_of(catchClass, exception, runtime))
return e + i;
}
}
return NULL;
}
关于 finally 块
finally 块在字节码中并没有什么特殊的标志,正常来说它会紧跟在 try 块之后, try 完,catch 块处理完就会走到 finally 块。如果 try 块中含有 return,则 return 指令会被编译器放到 finally 后面,需要注意的是 retrun 的返回值,在 try 块末尾就回被保存起来准备返回,finally 块的修改不会改变返回值