Using stackmap frames and how does this help in checking byte code?

I try to plunge into an obscure frame of a stack map, and it plays a role in checking a dynamically loaded class in one pass.

A few responses to stack overflow and other resources that I found very useful,

I understand the following:

  • Each base unit must begin with a stack map frame.
  • Each instruction, immediately following the unconditional branch (this is the beginning of the base block), must have a frame of the stack map.
  • Algorithm for creating a stack map frame using ASM. Section 3.5 ASM Document

The disadvantage of all of these articles is that it does not describe how exactly the frame of the stack map is used in validation.

More specifically - Let's say we have a bytecode, as indicated below. At the current location of the operand stack, it will be empty, and the type for local variable 1 will be B. Place L0 has a stack map frame associated with it. How does the verifier use this information?

<initial frame for method> GETSTATIC B.VALUE ASTORE 1 GOTO L0 <- Current location <stack map frame> L1 GETSTATIC A.VALUE ASTORE 1 <stack map frame> L0 ILOAD 0 IFNE L1 <stack map frame> ALOAD 1 ARETURN 

Note: Please note that I read the JVM specification and failed miserably to understand the frame of the stack map. Any help would be very helpful.

+5
source share
1 answer

At each point of the bytecode, each element in the locales and the operand stack has an implicit type. In the old system, the verifier calculated these types as they arrived, but if the control flow goes back, which can change the types in the target value, which means that it had to iterate before approaching.

Types are now explicitly specified at such transition points. The verifier makes a single, linear pass through the bytecode. Whenever it hits the stack frame, it claims that the current put types are compatible with the explicit types in the stack stack, and then it continues using the types of stack frames. Whenever he jumps to a jump, he claims that the stack frame at the target of the jump has types compatible with the current supposed types.

In fact, the frames of the stack clearly preserve the results of "iteration to convergence", which means that instead of calculating them, the verifier simply checks the correctness of the results, which can be done in one pass.

In addition, new class classes are not allowed to use the jsr and ret commands, which greatly simplifies validation.

As a specific example, suppose you have code like the following

 .method static foo : ()V L0: aconst_null L1: astore_0 L2: new Foo L3: dup L4: invokespecial Method Foo <init> ()V L5: astore_0 L6: goto L2 .end method 

When checking the output, verfier initially prints the type var 0 as NULL on L2. Once he reaches L6, he should go back and change the type to Foo.

When checking the stack map, the verifier again initially displays the type var 0 as NULL on L2. However, he sees that there is a stack stack on L2 and checks that type 0 is in the stack frame. Whatever it is, it sets 0 for this type and continues checking. When he gets to L6, he looks at the stack stack of the jump target (L2) and claims that type 0 on L6 (which is Foo) can be assigned to type 0 on L2 (indicated on the L2 frame stack).

Suppose the stack stack in L2 declares that 0 is of type Object. The stack verifier then displays the following types at each step.

 L0: INVALID (unset) L1: INVALID (unset) L2: NULL (checks stack frame at L2) (assert that NULL is assignable to Object) L2: Object L3: Object L4: Object L5: Object L6: Foo (check stack frame at L2) (assert that Foo is assignable to Object) 
+7
source

Source: https://habr.com/ru/post/1265264/


All Articles