ย
ย
๋ง์ฝ ์ด๋ฏธ ๋ฐฐํฌํด๋ฒ๋ฆฐ ์๋ฐ ์ฝ๋. ์ฆ,
.class
ํ์ผ์ ์์ ํ๊ณ ์ถ๋ค๋ฉด ์ด๋ป๊ฒ ํด์ผ ํ ๊น?ย
Decompile๋ ํ๋์ ๋ฐฉ๋ฒ์ด๊ฒ ์ง๋ง... ์ด๋ณด๋ค๋ BCI(Byte-code Injection)๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์กฐ๊ธ ๋ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ด๋ค.
ย
Java๋ ๊ฐ๊ฐ์ ํด๋์ค ํ์ผ์ ๋ฉ๋ชจ๋ฆฌ์ Loadํ ๋
java.lang.ClassLoader
๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด Dynamicํ๊ฒ, ํ์ํ ๋ ๋ถ๋ฌ์จ๋ค.ย
์ด๋ฅผ ์ด์ฉํ์ฌ ํด๋์ค ํ์ผ์ Modify ํ ์ ์๋๋ฐ, ์ด๋ฅผ BCI๋ผ๊ณ ํ๋ค.
ย
์ฌ๋ฌ๊ฐ์ง ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์ง๋ง, ์ฌ๊ธฐ์์๋ ๊ฐ์ฅ ๋น ๋ฅด๊ฒ ํ์ตํ๊ธฐ ์ฌ์ด Javassist ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ BCI๋ฅผ ์ด๋ป๊ฒ ํ๋์ง ๋ณด๋๋ก ํ๊ฒ ๋ค.
ย
๋ฐฉ๋ฒ์ ํฌ๊ฒ ์ด๋ ต์ง ์๋ค. ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ๊ฐ๋ ฅํ๊ณ ๋ค์ํ API๋ฅผ ์ ๊ณตํ๊ธฐ์ ์ด๋ฅผ ์ด์ฉํ๋ฉด ๋๋ค. ๊ฑฐ์ ๊ธฐ์กด์ Java ์ฝ๋ฉ๊ณผ ๋ค๋ฅผ ๊ฒ์ด ์๋ ์์ค.
ย
์์ ๋ฅผ ํ๋ ๋ณด์.
ย
import javassist.ClassPool; import javassist.CtClass; ClassPool cp = ClassPool.getDefault(); CtClass cc = cp.get("test.Rectangle");
ย
์ ์ฝ๋๋
javassist
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ์ฌ test.Rectangle
์ด๋ผ๋ ํด๋์ค๋ฅผ Loadํ ๊ฒ์ด๋ค.ย
์ฌ๊ธฐ์ ๋ค์๊ณผ ๊ฐ์ ์ฝ๋๋ก
test.Rectangle
ํด๋์ค์ Superclass๋ฅผ ์ง์ ํ๋๋ก ํ ์ ์๋ค.ย
// cc := test.Rectangle class file (CtClass type) cc.setSuperclass(cp.get("test.Point")); cc.writeFile();
ย
๊ฐ๋จํ์ง ์์๊ฐ? ๋จ ๋ค ์ค์ ์ฝ๋๋ก ์ด๋ฏธ ์ปดํ์ผ๊น์ง ๋ง์น
test.Rectangle
ํด๋์ค์ ๋ํด, test.Point
๋ผ๋ ํด๋์ค๋ฅผ Loadํด test.Rectangle
ํด๋์ค์ Superclass๋ก ๋ง๋ค์ด์ฃผ๊ฒ ๋์๋ค.ย
์ฐธ๊ณ ๋ก ๋ง์ง๋ง
writeFile()
์ ์์ ์ ๋ง์น test.Rectangle
ํด๋์ค๋ฅผ ๋ค์ ์๋ณธ ํด๋์ค ํ์ผ์ Writeํ๋ค๋ ์๋ฏธ. ๋ฐ๋ผ์ ์์ ์ฐ์ฐ์ ์งํํ ์ดํ test.Rectangle
ํด๋์ค๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด, Superclass๋ก test.Point
ํด๋์ค๋ฅผ ๊ฐ์ง test.Rectangle
ํด๋์ค๊ฐ Load๋ ๊ฒ์ด๋ค.ย
Reading and Writing Bytecode
ย
์ด๋ ๊ฒ
javassist.CtClass
๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํด ํด๋์ค ํ์ผ์ ๋ํ๋ด๊ฒ ๋๊ณ , ์ด ๊ฐ์ฒด๋ javassist.ClassPool
์ด๋ผ๋, ํด๋์ค ํ์ผ๋ค์ ๊ด๋ฆฌํ๋ Class Pool ๊ฐ์ฒด๋ฅผ ์ด์ฉํด์ ๊ฐ์ ธ์ฌ ์ ์๊ฒ ๋๋ค.ย
Class Pool. ๋ค์๋งํด ์์ Loadํ ํด๋์ค ํ์ผ(
CtClass
)๋ค์ ์บ์ฑ ํ๋ค๋ ๋ง.ย
// cc := CtClass type Loaded Class file byte[] buf = cc.toBytecode(); Class clazz = cc.toClass();
ย
์ด์ ๊ฐ์ด
CtClass
๋ ํด๋์ค ํ์ผ์ ๋ํด ์์ ํ ๋ค Bytecode ๋๋ java.lang.Class
ํ์
์ ๊ฐ์ฒด๋ก Convert ๋ํ ๊ฐ๋ฅํ๋ค.ย
์ฐธ๊ณ ๋ก ์์ ํด๋์ค๋ ์ธํฐํ์ด์ค ์์ฒด๋ฅผ ๋ง๋ค์๋ ์๋๋ฐ ์ด๊ฑด ๋
ผ์ธ. ์ฌ๊ธฐ์๋ BCI์ ๋ํด์๋ง ๋ค๋ฃจ๋๋ก ํ๊ฒ ๋ค.
ย
Frozen Class
ย
์์ ๊ฐ์ด Converting ๊ณผ์ ์ ๊ฑฐ์น
CtClass
๊ฐ์ฒด๋ Frozen Class๋ผ๊ณ ๋ถ๋ฆฌ๋ฉฐ, ์ดํ ํด๋น ํด๋์ค ํ์ผ์ ์์ ํ ์ ์๊ฒ ๋๋ค.ย
๋ฌผ๋ก ์์ํ ๋ถ๊ฐ๋ฅํ ๊ฒ์ ์๋๊ณ ,
defrost()
๋ฉ์๋๋ฅผ ์ด์ฉํด ๋ค์ ์์ ํ ์ ์๋๋ก ๋ง๋ค์ด ์ค ์ ์๋ค.ย
CtClass cc = cp.get("test.Rectangle"); cc.writeFile(); // frozen cc.setSuperclass(/* ... */); // ERROR cc.defrost(); // defrost cc.setSuperclass(/* ... */); // OK
ย
Class Search Path
ย
์ง๊ธ๊น์ง
ClassPool.getDefault()
๋ฉ์๋๋ฅผ ์ด์ฉํด ClassPool
์ธ์คํด์ค๋ฅผ ๋ฐ์์จ ๋ค, ClassPool.get()
๋ฉ์๋๋ฅผ ์ด์ฉํด ํด๋์ค ํ์ผ์ Loadํ๋ ๊ณผ์ ์ ๋ณด์๋ค. ๊ทธ๋ผ ์ด ํด๋์ค ํ์ผ์ ์ด๋์ ๊ฐ์ ธ์ค๋ ๊ฒ์ผ๊น?ย
๊ธฐ๋ณธ์ ์ผ๋ก
ClassPool.getDefault()
๋ฉ์๋๋ JVM๊ณผ ๋์ผํ ๊ฒฝ๋ก์ ์ํ ํ์ผ์ ๊ธฐ์ค์ผ๋ก ํด๋์ค ํ์ผ์ ํ์ํ๊ฒ ๋๋ค. ๋ฐ๋ผ์, Tomcat๊ณผ ๊ฐ์ WAS(Web App Server)๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ์์ํ์ง ๋ชปํ ํด๋์ค ํ์ผ์ ๊ฐ์ ธ์ค๊ฑฐ๋ ์์ ๊ฐ์ ธ์ค์ง ๋ชปํ๋ ๊ฒฝ์ฐ๊ฐ ๋ฐ์ํ๊ฒ ๋๋ค.ย
pool.insertClassPath("/usr/local/javalib"); pool.insertClassPath(new URLClassPath( "www.javassist.org", 80, "/java/", "org.javassist." ); // www.javassist.org:80/java/org/javassist pool.insertClassPath(new ByteArrayClassPath("ClassName", buf));
ย
๋ฐ๋ผ์ ํ์ํ ๊ฒฝ์ฐ ์์ ๊ฐ์ด Classpath๋ฅผ Insertํ๋ ๋ฐฉ๋ฒ์ผ๋ก ์ํ๋ ํด๋์ค ํ์ผ์ Loadํ ์ ์๋๋ก ์ง์ ํด์ฃผ๋๋ก ํ์.
ย
Class Loader
ย
Java๋
java.lang.ClassLoader
๋ฅผ ์ด์ฉํด ํด๋์ค ํ์ผ์ ๊ฐ์ ธ์จ๋ค๊ณ ํ๋ค.ย
์ด๋ฅผ ์ด์ฉํด ํด๋์ค ํ์ผ์ Loadํ ๋ ์์ (BCI)ํ ์ ์์ผ๋, ์ฌ๊ธฐ์๋ ์ด๋ณด๋ค๋ ์กฐ๊ธ ๋ ์ฌ์ด ๋ฐฉ๋ฒ์
javassist.Loader
๋ฅผ ์ด์ฉํด BCI๋ฅผ ํ๋ ๋ฐฉ๋ฒ์ ๋ณด๋๋ก ํ๊ฒ ๋ค.ย
์ฐธ๊ณ ๋ก ์ง๊ธ๊น์ง ์งํํ๋ ๋ฐฉ๋ฒ์ ํด๋์ค ํ์ผ์ ์ง์ ์ง์ ํด์ Loadํ ๋ค BCI๋ฅผ ํ๋ ๊ฒ์ด๊ณ , ์ง๊ธ ๋ณผ ๋ฐฉ๋ฒ์ Class Loader๋ฅผ ํตํด Load๋๋ ํด๋์ค ํ์ผ์ ๋ํด BCI๋ฅผ ํ๋ ๊ฒ์ด๋ค. ์ด ์ฐจ์ด๋ฅผ ์์๋์.
ย
public class Main { public static void main(String[] args) throws Throwable { ClassPool cp = ClassPool.getDefault(); Loader cl = new Loader(cp); // javassist.Loader // ์ง๊ธ๊น์ง ๋ณด์๋ ๋ฐฉ๋ฒ CtClass ct = cp.get("test.Rectangle"); ct.setSuperclass(cp.get("test.Point")); // Class Loader๋ฅผ ์ด์ฉํด ํด๋์ค๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ Class c = cl.loadClass("test.Rectangle"); Object rect = c.getDeclaredConstructor().newInstance(); } }
ย
๋ฐฉ๋ฒ์ ํฌ๊ฒ ๋ค๋ฅด์ง ์๋ค. ๋จ, ์์ ๊ฐ์ด
javassist.Loader
๋ฅผ ์ด์ฉํ๋ ๊ฒฝ์ฐ ํด๋์ค ํ์ผ์ Loadํ ๋(loadClass()
) BCI๋ฅผ ํ๋๋ก ์ผ์ข
์ ์ด๋ฒคํธ๋ฅผ ๋ฑ๋กํด๋์ ์ ์๋ค.ย
public interface Translator { public void start(ClassPool cp) throws NotFoundException, CannotCompileException; public void onLoad(ClassPool cp, String classname) throws NotFoundException, CannotCompileException; }
ย
์ด๋ฒคํธ ๋ฆฌ์ค๋์ ์ธํฐํ์ด์ค๋ ์์ ๊ฐ์ผ๋ฉฐ, ์ด๋ฅผ ๊ตฌํํ๋ ๋ฐฉ์์ผ๋ก ์ด๋ฒคํธ์ ๋ฑ๋ก์ด ๊ฐ๋ฅํ๋ค.
ย
public class MyTranslator implements Translator { @Override public void start(ClassPool cp) throws NotFoundException, CannotCompileException { } @Override public void onLoad(ClassPool cp, String classname) throws NotFoundException, CannotCompileException { CtClass cc = cp.get(classname); cc.setModifiers(Modifier.PUBLIC); // modified class modifier // ... } }
ย
์ด๋ฐ ์์ด๋ค. ๊ฐ ๋ฉ์๋์ ์๋ฏธ๋ ๋ค์์ ์ฐธ๊ณ .
ย
start()
:Translator
๊ฐ ๋ฑ๋ก๋ ๋ ํธ์ถ
onLoad()
:javassist.Loader
๊ฐ ํด๋์ค๋ฅผ Loadํ๊ธฐ ์ ํธ์ถ
ย
๋ฐ๋ผ์ ์ผ๋ฐ์ ์ผ๋ก
onLoad()
๋ฉ์๋๋ฅผ ์ค๋ฒ๋ผ์ด๋ ํ๋ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ค.ย
public class Main { public static void main(String[] args) throws Throwable { ClassPool cp = ClassPool.getDefault(); Loader cl = new Loader(cp); Translator t = new MyTranslator(); cl.addTranslator(cp, t); cl.loadClass("test.Rectangle"); // emit => run MyTranslator } }
ย
์ฌ์ฉ์ ์์ ๊ฐ์ด
Loader.addTranslator()
๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ๋๋ค.ย
Introspection and Customization
ย
์ง๊ธ๊น์ง๋ ํด๋์ค์ ๋ํด์๋ง ๋ค๋ฃจ์์ผ๋, ๋ฉ์๋ ์ญ์ ์์ ์ด ๊ฐ๋ฅํ๋ค.
ย
๋ฉ์๋๋ฅผ ํํํ๋
javassist.CtMethod
๋ผ๋ ๊ฐ์ฒด๋ฅผ ์ด์ฉํ๋ฉฐ, insertBefore()
, insertAfter()
, insertAt()
๊ณผ ๊ฐ์ API๋ฅผ ์ด์ฉํด ๋ฉ์๋์ ๋ํ BCI๊ฐ ๊ฐ๋ฅํ๋ค. ๋จ, ๋ค์ด๊ฐ๋ ๋งค๊ฐ๋ณ์๋ String
์ด๋, ๋ค์ ์ธ ๊ฐ์ง ํํ๋ง์ด ๊ฐ๋ฅํ๋ค.ย
- Inline:
System.outPrintln("Hello");
- Block:
{ System.out.println("Hello"); }
- Statement:
if (i < 0) { i = -1; }
ย
๋ํ ๋ค์์ Special Character๋ฅผ ์ด์ฉํด BCI์ ํ๊น์ด ๋๋ ๋ฉ์๋์ Parameters์ ๋ํ ์ฐธ์กฐ ์ญ์ ๊ฐ์ ธ์ฌ ์ ์๋ค.
ย
$0
:this
Context
$1
,$2
, ... : Parameters
$_
: Return Value
ย
๋ค๋ฅธ ๊ฒ๋ค๋ ๋ง์ผ๋, ์ฃผ๋ก ์ฌ์ฉ๋๋ ๊ฒ์ ์์ ๊ฐ๋ค. ์ค์ ์ฌ์ฉ ์๋ ๋ค์ ์ฝ๋๋ฅผ ์ฐธ๊ณ .
ย
// cm := CtMethod type object cm.insertAfter(String.join("\n", "System.out.println(\"Hello!\");", "if ($1 == true) {", " System.out.println(\"Hello2\");", "}" );
ย
Field ์ญ์ ์์ฑ์ด ๊ฐ๋ฅ. ์ด ๋๋
CtField.make()
๋ฉ์๋๋ฅผ ์ด์ฉํ๋ค.ย
// cc := CtClass type Object cc.addField(CtField.make("private boolean isValid = true;", cc));
ย
์ค์ ์์ ๊ฐ์ ์ฝ๋๊ฐ ํด๋์ค ํ์ผ ๋ด์ BCI ๋๋ ๊ฒ์ด๋ค.
ย
์ฌ๊ธฐ๊น์ง๊ฐ
javassist
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ด์ฉํ ํํ ๋ฆฌ์ผ์ด๋ฉฐ, ๊ต์ฅํ ๋ค์ํ ๋ฐฉ๋ฒ๊ณผ API๋ฅผ ์ ๊ณตํ๋ ํ์ํ๋ค๋ฉด ๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ๋๋ก ํ์.