본문 바로가기

프로젝트/APM MiddleWare

[ASM] Label 기본 개념 및 if/for/while 제어문 바이트코드 조작

개요

Label의 기본 개념

  • Label 객체는 코드 내 특정 위치를 가리키는 마커 역할을 한다. 이 위치는 다른 지점에서 점프하거나 분기할 때 참조될 수 있다.
  • ASM에서는 메서드의 바이트 코드를 생성하거나 수정할 때, Label을 사용하여 조건문, 반복문, try-catch 블록 등의 흐름 제어 구조를 구현한다.
  • Label은 MethodVisitor 인터페이스를 통해 메서드의 코드를 방문할 때 사용되며, visitLabel(Label label) 메소드를 사용하여 코드 상의 특정 위치를 정의한다.

Label 사용 방법

Label 객체 생성: 먼저, Label 객체를 생성한다. 이 객체는 코드 내의 특정 위치를 나타낸다.

Label start = new Label();

 

Label 위치 정의: 메서드 바이트 코드를 생성하거나 수정하는 과정에서, MethodVisitorvisitLabel 메소드를 호출하여 Label이 가리키는 위치를 정의한다.

methodVisitor.visitLabel(start);

 

Label을 참조하는 지시어 사용: 조건문, 반복문, 점프 명령 등에서 해당 Label을 참조하여 흐름 제어를 구현한다. 예를 들어, if 조건이 거짓일 때 점프하는 경우, visitJumpInsn 메소드와 함께 Label 객체를 사용한다.

methodVisitor.visitJumpInsn(IF_ICMPEQ, start);

 

예외 처리: 예외 처리를 위한 try-catch 블록에서도 Label을 사용하여 시작점, 종료점, 처리점을 정의할 수 있다.

Label startTry = new Label();
Label endTry = new Label();
Label catchBlock = new Label();

methodVisitor.visitTryCatchBlock(startTry, endTry, catchBlock, "java/lang/Exception");
methodVisitor.visitLabel(startTry);
// try 블록 코드
methodVisitor.visitLabel(endTry);
// 다른 명령어
methodVisitor.visitLabel(catchBlock);
// catch 블록 코드

 

Label을 사용하는 것은 저수준 바이트 코드 조작에서 매우 중요하다. 이를 통해 복잡한 흐름 제어 로직을 구현할 수 있으며, ASM을 사용한 고급 바이트 코드 조작 기술의 핵심 요소 중 하나다.

 

예제

Lable을 통한 지역 변수 사용하기


public class LocalValueExample {
    public static void run(ClassNode cn) {
        //1. MethodNode 설정 및 지시문 List, Label, 지역변수
        MethodNode mn = new MethodNode();
        mn.name = "localMethod";
        mn.desc = "(I)V";
        mn.access = ACC_PUBLIC;
        InsnList il = new InsnList();
        LabelNode startLabel = new LabelNode(); //if문
        LabelNode endLabel = new LabelNode();

        // 로컬 변수 추가
        // localVariables 리스트 초기화
        mn.localVariables = new ArrayList<>();
        mn.localVariables.add(new LocalVariableNode("param", "I", null, startLabel, endLabel, 1)); // 메서드 입력 파라미터
        mn.localVariables.add(new LocalVariableNode("localVar", "I", null, startLabel, endLabel, 2)); // 추가한 로컬 변수

        il.add(startLabel); //local Start
        // 매개변수 값을 로컬 변수에 저장 (예를 들어, 매개변수의 인덱스가 1인 경우)
        il.add(new InsnNode(ICONST_5)); // 상수 5를 로드
        il.add(new VarInsnNode(ISTORE, 2)); // 로컬 변수 인덱스 2에 저장


        //출력
        il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); // System.out 객체를 스택에 푸시
        il.add(new VarInsnNode(ILOAD, 1)); // 저장된 로컬 변수 값을 스택에 푸시
        il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false)); // println 메소드 호출

        //출력
        il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); // System.out 객체를 스택에 푸시
        il.add(new VarInsnNode(ILOAD, 2)); // 저장된 로컬 변수 값을 스택에 푸시
        il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false)); // println 메소드 호출



        il.add(endLabel); //local End
        il.add(new InsnNode(RETURN));


        mn.instructions.add(il);
        cn.methods.add(mn);

    }
}

 

while문 구현

package org.agent.asm_tree_api.testcode.control_statement;

import org.objectweb.asm.tree.*;
import static org.objectweb.asm.Opcodes.*;

/**
 * while 레퍼런스
 */
public class WhileExampleTree {
    public static void run(ClassNode cn) {
        MethodNode mn = new MethodNode(ACC_PRIVATE, "whileMethod", "(I)I", null, null);
        InsnList il = new InsnList();

        //label
        LabelNode localValStart = new LabelNode();
        LabelNode localValEnd = new LabelNode();
        LabelNode startWhile = new LabelNode();
        LabelNode endWhile = new LabelNode();

        //Method
        il.add(localValStart); //method start

        //지역변수 값 할당
        il.add(new LdcInsnNode(10));
        il.add(new VarInsnNode(ISTORE, 2));

        //sysout : 이 구문이 없다면 while문이 for문으로 변경됨 : for(trig = 10; var1 <= trig; ++trig) 
        //.. 동일한 로직이기는 하다. 알아서 최적화 되는 듯
        il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); // System.out 객체를 스택에 푸시
        il.add(new VarInsnNode(ILOAD, 2));
        il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false)); // println 메소드 호출


        //while문 start
        il.add(startWhile); //start
        il.add(new VarInsnNode(ILOAD, 1)); //입력 param 저장
        il.add(new VarInsnNode(ILOAD, 2)); //trig 지역 변수 저장
        il.add(new JumpInsnNode(IF_ICMPGT, endWhile)); //비교 후 점프
        il.add(new IincInsnNode(1, 1)); // 입력 파라미터 값 증가
        il.add(new JumpInsnNode(GOTO, startWhile));
        il.add(endWhile); //end while

        il.add(new VarInsnNode(ILOAD, 1));  //메서드 입력 파라미터 1의 값을 스택에 저장
        il.add(new InsnNode(IRETURN));              //이 값을 return
        il.add(localValEnd); //method end

        //localVariables
        mn.localVariables.add(new LocalVariableNode("trig", "I", null, localValStart, localValEnd, 2));

        mn.instructions.add(il);
        cn.methods.add(mn);
    }
}

 

if문 구현


public class IfExampleTree {
    public static void run(ClassNode cn) {
        //1. MethodNode 설정 및 지시문 List, Label, 지역변수
        MethodNode mn = new MethodNode();
        mn.name = "ifMethod";
        mn.desc = "(I)V";
        mn.access = ACC_PUBLIC;
        InsnList il = new InsnList();
        LabelNode elseLabel = new LabelNode(); //if문
        LabelNode endLabel = new LabelNode();

        //if문 체크

        il.add(new VarInsnNode(ILOAD, 1)); //지역변수 idx1 : 입력파라미터 = 스택에 저장
        il.add(new JumpInsnNode(IFGE, elseLabel)); //if문 비교에 사용
        //if-true
        // "true" 출력
        il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); // System.out 객체를 스택에 푸시
        il.add(new LdcInsnNode("true")); // "true" 문자열을 스택에 푸시
        il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false)); // println 메소드 호출


        //else에 가지 않도록 end로 jump 처리
        il.add(new JumpInsnNode(GOTO, endLabel));
        //else 위치
        il.add(elseLabel);
        // "false" 출력
        il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); // System.out 객체를 스택에 푸시
        il.add(new LdcInsnNode("false")); // "false" 문자열을 스택에 푸시
        il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false)); // println 메소드 호출


        //end 위치 - if문 끝
        il.add(endLabel);

        il.add(new InsnNode(RETURN));

        mn.instructions.add(il);
//        mn.maxStack = 3; frame 계산 생략(Compute_max 사용)
//        mn.maxLocals = 2;
        cn.methods.add(mn);
    }
}
    public void ifMethod(int var1) {
        if (var1 < 0) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }

 

for문 구현

/**
for문 구현
 */
public class ForExampleTree {
    public static void run(ClassNode cn) {

        MethodNode mn = new MethodNode(ACC_PROTECTED, "forMethod", "(I)V", null, null);
        InsnList il = new InsnList();

        //label 목록
        LabelNode forStart = new LabelNode(); //label
        LabelNode forEnd = new LabelNode();
        LabelNode localStart = new LabelNode(); //localA
        LabelNode localEnd = new LabelNode();

        //지역변수들 목록
        mn.localVariables.add(new LocalVariableNode("localA", "I", null, localStart, localEnd, 2));
        mn.localVariables.add(new LocalVariableNode("i", "I", null, forStart, forEnd, 3)); //for문 내부 I 생성

        //+=====세부 로직=====+
        il.add(localStart);//local start

        //1. method 지역변수(idx 2) 추가. 0 = this, 1 = inputParam
        il.add(new VarInsnNode(ILOAD, 1));//stack에 입력 파라미터(idx1) 대입
        il.add(new LdcInsnNode(1));//stack에 상수 1 대입
        il.add(new InsnNode(IADD)); //두 상수를 더하기
        il.add(new VarInsnNode(ISTORE, 2)); //localA 변수에 더한 상수 대입

        //2. for문
        il.add(new LdcInsnNode(0)); //Stack에 0 저장
        il.add(new VarInsnNode(ISTORE, 3)); //지역변수 I에 0 대입
        il.add(forStart); //for 시작
        //for 내부 구문 비교 (for(int i = 0;i < localA; i++)
        //I값이랑 localA값 비교 조건
        il.add(new VarInsnNode(ILOAD, 3)); //i 로드
        il.add(new VarInsnNode(ILOAD, 2)); //localA 로드
        il.add(new JumpInsnNode(IF_ICMPGE, forEnd)); //로드한 값 비교하여 클 경우 forEnd로 이동

        //sysout
        il.add(new FieldInsnNode(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;")); // System.out 객체를 스택에 푸시
        il.add(new VarInsnNode(ILOAD, 2));
        il.add(new MethodInsnNode(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(I)V", false)); // println 메소드 호출

        //i++
        il.add(new IincInsnNode(3, 1)); // i 값을 1 증가시킴

        il.add(new JumpInsnNode(GOTO, forStart)); //반복 시행
        il.add(forEnd);   //for 루프 종료
        il.add(localEnd); //local End

        //return
        il.add(new InsnNode(RETURN)); //return;
        mn.instructions.add(il);
        cn.methods.add(mn);

    }
}

for문 한줄이 실제 label로 구현하면 이리도 긴 것이다.

 

 

전부 실행했을 경우의 고수준 코드

 

public class Main {

    public static void main(String[] args) {
        //ClassNode
        ClassNode cn = new ClassNode();
        cn.version = V21; //java 21
        cn.access = ACC_PRIVATE;
        cn.name = "Class0223";
        cn.superName = "java/lang/Object"; //객체 타입

        //Method
        MethodNode mn = new MethodNode(ACC_PUBLIC, "method", "()I", null, null);
        InsnList il = new InsnList();
        il.add(new InsnNode(ICONST_5));
        il.add(new InsnNode(IRETURN));
        mn.instructions.add(il);
        cn.methods.add(mn);
        cn.fields.add(new FieldNode(ACC_PRIVATE, "field", "F", null, -1F));

        //Logic
        IfExampleTree.run(cn);
        ForExampleTree.run(cn);
        LocalValueExample.run(cn);
        WhileExampleTree.run(cn);

        //Accept
        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
        TraceClassVisitor tcv = new TraceClassVisitor(cw, new PrintWriter(System.out));
        cn.accept(tcv);

        CodePrinter.printClass(cw.toByteArray(), "TEMP", true);
        System.out.println(cw.toByteArray());
    }
}

 

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

private class Class0223 {
    private float field;

    public int method() {
        return 5;
    }

    public void ifMethod(int var1) {
        if (var1 < 0) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }

    }

    protected void forMethod(int var1) {
        int localA = var1 + 1;

        for(int i = 0; i < localA; ++i) {
            System.out.println(localA);
        }

    }

    public void localMethod(int param) {
        int localVar = 5;
        System.out.println(param);
        System.out.println(localVar);
    }

    private int whileMethod(int var1) {
        int trig = 10;
        System.out.println(trig);

        while(var1 <= trig) {
            ++var1;
        }

        return var1;
    }
}