본문 바로가기

프로젝트/APM prototype 개발

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