我正在尝试在java字节码中做一些错误处理。我首先尝试实现一些类似catch的子例程,在那里我将检查错误条件,并跳转到适当的子例程,有点像:
iconst_1
iconst_0
dup
ifeq calldiverr
goto enddivtest
calldiverr:
jsr divError
enddivtest:
idiv
...More instructions...
divError:
getstatic java/lang/System/out Ljava/io/PrintStream;
ldc "Oh dear you divided by 0!"
invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V
上面的问题是,当我有多条指令跳转到这个子例程时,在运行字节码时得到一条错误消息,说堆栈高度不一致。
也许使用异常是解决这个问题的最好方法?
从一些谷歌我发现你可以创建例外类的实例,并初始化他们的东西,如:
new java/lang/Exception
dup
ldc "exception message!"
invokespecial java/lang/Exception/<init>(Ljava/lang/String;)V
我也发现你可以把它们和athrow
一起扔,这似乎没问题。
让我困惑的是异常是如何被捕获的。似乎有一个神奇的"异常表",它将异常的抛出和捕获粘合在一起,但我不知道如何在从头编写字节码(并使用Jasmin进行组装)时定义其中一个。有人能告诉我创建异常表的秘密吗?可能给我一个异常处理的例子,将与jasmin组装?
最后我想出了一个比jsr
更好的解决方案——在Jasmin中使用.method
定义一个方法。我只是使用invokestatic
调用我的错误处理程序一旦我检测到错误。
对于那些寻找实际异常处理的人-我认为在Jasmin中定义异常表可以使用.catch
完成,但我还没有研究它作为方法定义解决了我的问题。
我确实不得不看看.catch
在最后,发现它真的很容易使用。
首先值得指出的是,版本51.0的类文件可能不包含jsr指令。重复代码或使用方法。
在字节码的每一点,帧中每个元素的静态类型必须是已知的。每一帧不是一个调用栈。
一般来说,你不会想要玩巨大的堆栈。将临时变量存储在局部变量中以保持简单。
如果抛出异常,则很明显该框架可能具有异常可能抛出的任何位置的内容。因此,内容将被丢弃并由异常替换。无论如何,您都无法返回并继续使用框架内容。
验证jsr
的规则非常复杂,正如Tom指出的那样,不赞成使用操作码。所以最好避免。
我对jsr
的记忆有点模糊,但是…
(已更新)在Java字节码验证中有一个规则,无论两个控制流在哪里连接在一起,堆栈深度必须沿着两个分支相同。jsr
子程序在一定程度上不受此规则的约束——具有不同堆栈深度的多个异常点可以"到达"同一个jsr
例程,但是从jsr
例程进入到随后的ret
例程的堆栈深度净变化必须为零(或者实际上是负1,因为异常原因总是在进入例程时被推入)。
此外,虽然jsr
例程可以"逃逸"并分支回常规控制流,但如果它这样做了,jsr
就不能免除连接点的堆栈深度规则。这严重限制了您可以这样做的情况,因为jsr
例程可能会以不同的堆栈深度进入。
(毫无疑问,我仍然有一些错误,但这是我能做的最好的。)
(我不太明白你是如何计划"绕过"你的jsr
异常问题的。)
(同样,Sun使字节码的编写变得更加复杂,有4或5(不记得是哪个了),使得手工编写字节码几乎不可能。他们这样做是因为他们不知道如何快速地进行验证以击败IBM的验证器,但这是另一回事。