asm示例

本文介绍了一种利用ASM框架修改Java字节码的方法。通过一个简单示例,展示了如何在不修改源代码的情况下,为现有类添加计时功能。这种方法能够绕过编译阶段的检查,直接修改已编译好的类文件。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前些天看aop就看到了cglib,看cglib又看到了asm,模仿着做了个示例。利用asm修改字节码,能实现编译不通过执行通的过的效果,挺有意思。

一个简单的待修改类:

package com.asm.zjc;
public class C {
	public void m() throws InterruptedException{
		Thread.sleep(300);
	}
}
对其进行修改的类:

package com.asm.zjc;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.ClassAdapter;

public class Generator {
	public static void main(String[] arg){
		try{
			ClassReader cr = new ClassReader("com/asm/zjc/C");
			ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
			ClassAdapter classAdapter = new AddTimeClassAdapter(cw);
			
			//使给定的访问者访问Java类的ClassReader
			cr.accept(classAdapter, ClassReader.SKIP_DEBUG);
			
			byte[] data = cw.toByteArray();
			File file = new File(System.getProperty("user.dir") + "\\bin\\com\\asm\\zjc\\C.class");
			FileOutputStream fout = new FileOutputStream(file);
			fout.write(data);
			fout.close();
			System.out.println("success!");
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}catch(IOException e){
			e.printStackTrace();
		}
	}
}

其中用到了类适配器:

package com.asm.zjc;
import org.objectweb.asm.*;

public class AddTimeClassAdapter extends ClassAdapter {
	private String owner;
	private boolean isInterface;
	
	public AddTimeClassAdapter(ClassVisitor cv) {
		super(cv);
	}
	
	@Override
	public void visit(int version, int access, String name, String signature,
			String superName, String[] interfaces){
		cv.visit(version, access, name, signature, superName, interfaces);
		owner = name;
		isInterface = (access & Opcodes.ACC_INTERFACE) != 0;
	}
	
	@Override
	public MethodVisitor visitMethod(int access, String name, String desc,
			String signature, String[] exceptions){
		MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions);
		if(!name.equals("<init>") && !isInterface && mv!=null){
			mv=new AddTimeMethodAdapter(mv);
		}
		return mv;
	}
	
	@Override
	public void visitEnd(){
		if(!isInterface){
			FieldVisitor fv = cv.visitField(Opcodes.ACC_PUBLIC+Opcodes.ACC_STATIC, "timer", "J", null, null);
			if(fv!=null){
				fv.visitEnd();
			}
		}
		cv.visitEnd();
	}
	
	
	class AddTimeMethodAdapter extends MethodAdapter{
		public AddTimeMethodAdapter(MethodVisitor mv){
			super(mv);
		}
		
		@Override
		public void visitCode(){
			mv.visitCode();
			mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
			mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
			mv.visitInsn(Opcodes.LSUB);
			mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
		}
		
		@Override
		public void visitInsn(int opcode){
			if((opcode>=Opcodes.IRETURN && opcode<=Opcodes.RETURN) || opcode==Opcodes.ATHROW){
				mv.visitFieldInsn(Opcodes.GETSTATIC, owner, "timer", "J");
				mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/System", "currentTimeMillis", "()J");
				mv.visitInsn(Opcodes.LADD);
				mv.visitFieldInsn(Opcodes.PUTSTATIC, owner, "timer", "J");
			}
			mv.visitInsn(opcode);
		}
		
		@Override
		public void visitMaxs(int maxStack, int maxLocal){
			mv.visitMaxs(maxStack+4, maxLocal);
		}
	}
}

最后写一个测试程序验证:

package com.asm.zjc;

public class Test {
	@SuppressWarnings("static-access")
	public static void main(String[] args)throws InterruptedException{
		C c1=new C();
		c1.m();
		System.out.println(C.timer);
	}
}

注意第8行是不能通过编译的,但可以执行成功。

其实我们用下面的命令行反编译class文件,也可以了解字节码的结构:
D:\software\eclipse\workspace\study_asm\bin>javap -c -verbose com.asm.zjc.C >aaa.java
2.8】存储器与寄存器间数据传送。 MOV AX,BUF ;BUF是变量,源操作数为直接寻址 MOV BH,[DI] ;源操作数为寄存器间接寻址 MOV DI,ES:3[SI] ;源操作数为变址寻址,使用跨段前缀 MOV BP,3[BX+SI] ;源操作数为基址加变址寻址 MOV BUFA,DL ;BUFA是一字节变量 MOV [BP],AX ;使用SS段寄存器 MOV DS:[BP],DL ;使用跨段前缀 MOV BUF,DS ;BUF是个字变量 MOV ES ,BUF 【2.10】将一种代码转换成另一种代码。 【2.16】更改数据段段首址。 【2.23】带借位减运算。 【2.48】比较数据中STR1字符串和附加段中STR2字符串是否相同。 假设两个字符串长度一,为COUNT个字节。比较的结果存入RESULT单元, 结果为0表示相等,为-1(即FFH)表示不等。 【2.51】用重复前缀比较两个字符串相等。 【2.55】把数据区的数据按正、负数分开,并分别送至两个缓冲区。 【2.56】利用子程序完成将AL低4位中的一位16进制数转换成对应的ASCII码 【3.4】用算术运算符进行数值表达式运算。 【3.7】用属性运算符表示类型属性。 【3.8】下面程序段的某些语句是错误的。 【3.9】用属性运算符定义新变量 【3.11】分离变量类型。 【6.5】用软中断INT 60H、发声中断服务程序INT 61H以及软中断INT 62H、 INT 63H , 实现字符串“intel 80486 DX2/66 CPU”显示、扬声器发声和变色三角形的显示功能, 定时器ICH中断作为计数器使用。 ................................................................ ................................................................ 7.1 实验步骤 在PC机上运行汇编程序必须经过以下几个步骤: (1)编辑源程序。利用文本编辑工具编辑源程序, 生成一个汇编语言源程序的纯文本文件?惚嘤镅栽闯绦虻睦┱姑?签qASM。 (2)汇编源程序。用汇编器汇编源程序生成目标代码文件,目标代码文件的扩展名 是?OBJ,汇编器还可以生成列表文件和交叉参考文件?如果源程序有语法错误行, 汇编器就不生成目标代码文件。这时,必须重新编辑源程序,修改语法错误的行。 当发现源程序中的某些行含不确定因素时,汇编器会给出警告信息,但仍按缺省处 理办法生成目标代码文件。此时,可以重新编辑源程序,消除不确定因素。 (3)连接目标程序。利用连接器连接目标代码程序和库函数代码生成可执行程序文件。 通常DOS平台上的可执行程序文件的扩展名是?EXE。一般单个? 或者与库函数连接时,如果在目标代码文件或者库中找不到所需的连接信息, 连接器就会发生错误提示信息,而不生成可执行程序文件。这时,就要重新编辑源程序, 并汇编源程序。 (4)调试可执行程序。程序的动态调试是在形成可执行程序文件后,针对可执行程序进行的。 DEBUG是简单而有效的动态调试工具,利用调试工具动态地调试程序,找出程序中的问题。 如果发现程序中有问题,那么必须重新编辑、汇编源程序。 下面以一个简单的子说明汇编源程序的上机过程。 假定要在显示器显示如下一行信息: I am a student! 那么,其汇编源程序的过程如下: STACK SEGMENT STACK DB 200 DUP(0) STACK ENDS DATA SEGM0ENT BUF DB I am a student!$ DATA ENDS CODE SEBMENT ASSUME CS:CODE,DS:DATA,SS:STACK START: MOV AX,DATA MOV DS,AX LEA DX,BUF MOV AH,9 INT 21H MOV AH,4CH INT 21H CODE ENDS END START 第一步:编辑源程序。假定源文件名为EXAM?ASM。 第二步:汇编源程序EXAM?ASM。 A>MASM EXAM ; 此命令是调用宏汇编程序MASM对源文件EXAM?ASM进行汇编,生成目标文件EXAM?OBJ. 若汇编无错误,则进入第三步。 第三步:连接目标程序文件EXAM?OBJ。 A>LINK EXAM ; 若连接成功,则进入下一步。 第四步:运行可执行文件EXAM?EXE A>EXAM 运行结果如下: I am a student! 若未得到预期的结果,可检查EXAM?ASM文件内容,修改错误,再次汇编、连接、运行,直到满意为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值