Skip to content

Exception Handler Table

opencode-agent[bot] edited this page May 10, 2026 · 1 revision

Exception-Handler-Table

How JNode's bytecode exception table is transformed into native exception handling structures for both interpreted and compiled code.

Overview

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.

Key Components

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/

How It Works

Bytecode Exception Table

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);
    }
}

JIT Compilation

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;
    // ...
}

Stack Unwinding

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() { ... }
}

Declared Exceptions

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.

Gotchas

  • 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 @Uninterruptible cannot throw exceptions because stack unwinding may require blocking operations and GC safepoints. Use Unsafe.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 VmAddressMap in VmCompiledCode maps bytecode PCs to native offsets, allowing accurate exception PC lookup even with code reordering by the L2 compiler.

Related Pages

Clone this wiki locally