-
Notifications
You must be signed in to change notification settings - Fork 0
Exception Handler Table
How JNode's bytecode exception table is transformed into native exception handling structures for both interpreted and compiled code.
The Exception Handler Table bridges Java bytecode exception handling and the VM's native exception dispatch. In bytecode, each method can declare try-catch blocks in an exception table (read from the Code attribute). JNode transforms this table into two parallel hierarchies at compile time: VmInterpretedExceptionHandler for the interpreter and VmCompiledExceptionHandler for JIT-compiled code. The VM runtime (VmSystem.findThrowableHandler()) uses these structures to unwind the stack and locate the appropriate handler when an exception is thrown.
| Class | Role |
|---|---|
core/src/core/org/jnode/vm/classmgr/AbstractExceptionHandler.java |
Base class storing catchType (VmConstClass) |
core/src/core/org/jnode/vm/classmgr/VmInterpretedExceptionHandler.java |
Bytecode exception entry with PC ranges for interpreted mode |
core/src/core/org/jnode/vm/classmgr/VmCompiledExceptionHandler.java |
Native exception entry with addresses for compiled mode |
core/src/core/org/jnode/vm/classmgr/VmCompiledCode.java |
Holds VmCompiledExceptionHandler[] table and default handler address |
core/src/core/org/jnode/vm/classmgr/VmExceptions.java |
Declared exceptions from method's Exceptions attribute |
core/src/core/org/jnode/vm/VmSystem.java |
findThrowableHandler() stack unwinding logic |
core/src/core/org/jnode/vm/VmStackFrameEnumerator.java |
Iterates stack frames during unwinding |
core/src/core/org/jnode/vm/VmStackReader.java |
Reads method/compiled code from frame pointer |
core/src/core/org/jnode/vm/VmStackFrame.java |
Stack frame metadata (method, program counter) |
core/src/core/org/jnode/vm/x86/compiler/l1a/X86StackFrame.java |
L1A exception handling code generation |
core/src/core/org/jnode/vm/x86/compiler/l1b/X86StackFrame.java |
L1B exception handling code generation |
core/src/core/org/jnode/vm/x86/compiler/l2/X86StackFrame.java |
L2 exception handling code generation |
Source path: core/src/core/org/jnode/vm/classmgr/, core/src/core/org/jnode/vm/
The JVM bytecode format stores exception handling information in the Code attribute's exception_table:
exception_table[entry]:
start_pc (2 bytes) - start of protected range
end_pc (2 bytes) - end of protected range
handler_pc (2 bytes) - bytecode offset of handler code
catch_type (2 bytes) - constant pool index (0 = finally)
JNode decodes these entries into VmInterpretedExceptionHandler at class loading time:
public final class VmInterpretedExceptionHandler extends AbstractExceptionHandler {
private final char startPC;
private final char endPC;
private final char handlerPC;
public boolean isInScope(int pc) {
return (pc >= startPC) && (pc <= endPC);
}
}When the JIT compiler generates native code, it transforms the bytecode exception table into VmCompiledExceptionHandler objects with native addresses:
@MagicPermission
public final class VmCompiledExceptionHandler extends AbstractExceptionHandler {
private final VmAddress handler; // Native address of catch block
private final VmAddress startPtr; // Native code range start
private final VmAddress endPtr; // Native code range end
public boolean isInScope(Address address) {
final Address start = Address.fromAddress(startPtr);
final Address end = Address.fromAddress(endPtr);
return address.GE(start) && address.LT(end);
}
}VmCompiledCode holds the handler table:
public final class VmCompiledCode extends AbstractCode {
private final VmCompiledExceptionHandler[] eTable;
private final VmAddress defaultExceptionHandler;
// ...
}When an exception is thrown, VmSystem.findThrowableHandler() walks the call stack:
1. Get current frame pointer and instruction pointer
2. Look up VmMethod and VmCompiledCode from the frame
3. Iterate eTable to find a handler where isInScope(ip) == true
4. Check catchType assignability (null = catch-all / finally)
5. If matched: return handler native address
6. If PC in compiled code but no handler matched: return defaultExceptionHandler
7. Walk to previous frame and repeat until handler found or stack exhausted
The VmStackFrameEnumerator manages the frame chain traversal:
public final class VmStackFrameEnumerator {
public VmStackFrameEnumerator(VmStackReader reader, Address framePtr, Address instrPtr) { ... }
public VmStackFrameEnumerator(VmStackReader reader) { ... } // for exception frames
public VmMethod getMethod() { ... }
public Address getProgramCounter() { ... }
public boolean hasMoreFrames() { ... }
public void next() { ... }
}The Exceptions attribute (separate from the exception table) lists checked exceptions a method declares via throws:
public final class VmExceptions extends VmSystemObject {
private final VmConstClass[] exceptions;
private final char pragmaFlags;
public final int getLength() { ... }
public final VmConstClass getException(int index) { ... }
}Used by the compiler and bytecode verifier for compile-time checking.
-
Interpreted vs Compiled representation: Interpreted handlers use bytecode PC offsets (char), compiled handlers use native addresses (VmAddress). The conversion happens at JIT compile time in
NativeCodeCompiler. - finally blocks: Represented as catch_type = 0 (null catchType). The handler must check exception type to distinguish finally from actual catches.
-
@Uninterruptible incompatibility: Methods annotated
@Uninterruptiblecannot throw exceptions because stack unwinding may require blocking operations and GC safepoints. UseUnsafe.die()for fatal errors instead. - Default exception handler: Invoked when the exception PC is within compiled code but no explicit handler matched. Typically points to an interpreter fallback or fatal error dump.
- Multiple catch blocks: Handlers are stored as an array; iteration order matters for multi-catch (Java 7+) where the first matching handler wins. JNode compiles to the most specific applicable handler at bytecode level.
-
Handler PC to native address mapping: The
VmAddressMapinVmCompiledCodemaps bytecode PCs to native offsets, allowing accurate exception PC lookup even with code reordering by the L2 compiler.
- Exceptions-Implementation — Higher-level exception handling overview
- JIT-Compilers — L1/L2 compiler pipeline
- L1-Compiler — L1 compiler exception handling integration
- L2-Compiler-Deep-Dive — L2 compiler stack frame and exception generation
- Stack-Frame-Layout — x86 stack frame structure during unwinding
- Yieldpoint — Thread scheduling integration with exception handling
- Core-Thread-Scheduling — Thread state during exception dispatch