개요
Label의 기본 개념
- Label 객체는 코드 내 특정 위치를 가리키는 마커 역할을 한다. 이 위치는 다른 지점에서 점프하거나 분기할 때 참조될 수 있다.
- ASM에서는 메서드의 바이트 코드를 생성하거나 수정할 때, Label을 사용하여 조건문, 반복문, try-catch 블록 등의 흐름 제어 구조를 구현한다.
- Label은 MethodVisitor 인터페이스를 통해 메서드의 코드를 방문할 때 사용되며, visitLabel(Label label) 메소드를 사용하여 코드 상의 특정 위치를 정의한다.
Label 사용 방법
Label 객체 생성: 먼저, Label 객체를 생성한다. 이 객체는 코드 내의 특정 위치를 나타낸다.
Label start = new Label();
Label 위치 정의: 메서드 바이트 코드를 생성하거나 수정하는 과정에서, MethodVisitor의 visitLabel 메소드를 호출하여 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);
}
}
전부 실행했을 경우의 고수준 코드
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;
}
}
'프로젝트 > APM MiddleWare' 카테고리의 다른 글
[TroubleShooting][ASM][HikariCP] HikariCP 기반 Springboot 트랜잭션 로그 수집 - 1 (2) | 2024.02.27 |
---|---|
[ASM][Util]TraceClassVisitor/고수준 코드 출력하기[InteliJ] (0) | 2024.02.23 |
[A Java bytecode engineering library] - [Tree API] 9.Metadata (0) | 2024.02.23 |
[A Java bytecode engineering library] - [Tree API] 8. Method Analysis (0) | 2024.02.22 |
개발 진행 : Springboot DataSource 동적 Proxy 테스트 (0) | 2024.02.06 |