大端字节序码流中取出2字节

Java语言由Java语言规范(JLS)定义。 但是,Java虚拟机的可执行字节码由单独的标准Java虚拟规范(通常称为VMSpec)定义。

JVM字节码由javac从Java源代码文件生成,并且该字节码与该语言有很大不同。 例如,一些熟悉的高级Java语言功能已被编译掉,根本没有出现在字节码中。

最明显的例子之一就是Java的循环关键字(for,while等),它们被编译掉并替换为字节码分支指令。 这意味着方法内部的字节码流控制仅由if语句和跳转(用于循环)组成。

在本文中,我们将假定读者具有字节码基础。 如果需要一些背景知识,请参阅The Well-Grounded Java Developer(Evans和Verburg,Manning 2012)或RebelLabs的此报告 (PDF需注册)。

让我们看一个示例,该示例经常使对JVM字节码不熟悉的开发人员感到困惑。 它使用JDK或JRE附带的javap工具,它实际上是Java字节码反汇编程序。 在我们的示例中,我们将讨论一个实现Callable接口的简单类:

public class ExampleCallable implements Callable<Double> {
public Double call() {
return 3.1415;
}
}

我们可以使用最简单的javap工具形式将其分解,如图所示:

$ javap kathik/java/bytecode_examples/ExampleCallable.class
Compiled from "ExampleCallable.java"
public class kathik.java.bytecode_examples.ExampleCallable
implements java.util.concurrent.Callable<java.lang.Double> {
public kathik.java.bytecode_examples.ExampleCallable();
public java.lang.Double call();
public java.lang.Object call() throws java.lang.Exception;
}

这种反汇编看起来是错误的-毕竟,我们编写了一个调用方法,而不是两个; 即使我们试图这样写,javac也会抱怨有两个具有相同名称和签名的方法,只是返回类型不同,因此该代码将无法编译。 但是,该类是从上面显示的真实有效的Java源文件中生成的。

这清楚地表明Java熟悉的模糊返回类型限制是Java语言约束,而不是JVM字节码要求。 如果javac插入了您没有写到类文件中的代码的想法令人不安,那不应该; 我们每天都看到它! Java程序员学习的第一个经验教训是“如果不提供构造函数,编译器会为您添加一个简单的构造函数”。 在javap的输出中,即使我们没有编写它,您甚至可以看到已经提供的构造函数。

这些额外的方法提供了一个语言规范要求比VM规范更为严格的示例。 如果我们直接编写字节码,那么有许多“不可能”的事情可以做-没有Java编译器会发出的合法字节码。

例如,我们可以创建完全没有构造函数的类。 Java语言规范要求每个类至少具有一个构造函数,如果我们未能提供一个简单的void构造函数,则javac会自动插入该构造函数。 但是,如果我们直接编写字节码,则可以随意省略一个。 即使通过反射,也无法实例化此类。

我们的最后一个例子是几乎可行的例子,但是效果并不理想。 在字节码中,我们可以编写一个方法来尝试调用属于另一个类的私有方法。 这是有效的字节码,但是如果有任何程序尝试加载它,它将无法正确链接。 这是因为对调用的访问控制限制将由类加载器的验证程序检测到,并且非法访问将被拒绝。

ASM简介

如果我们想创建可以实现某些非Java行为的代码,那么我们将需要从头开始生成一个类文件。 由于类文件格式是二进制的,因此使用使我们能够处理抽象数据结构,然后将其转换为字节码并将其流式传输到磁盘的库是有意义的。

有几种此类库可供选择,但是在本文中,我们将重点介绍ASM。 这是一个非常常见的库,在Java 8发行版中以内部API的形式出现(经过稍微修改)。 对于用户代码,我们希望使用通用的开源库而不是JDK的版本,因为我们不应该依赖内部API。

ASM的核心重点是提供一种API,该API虽然有些不可思议(有时是很笨拙),但以相当直接的方式对应于字节码数据结构。

Java运行时是经过多年设计决策的结果,并且在类文件格式的后续版本中可以清楚地看到所产生的积聚。

ASM试图相当紧密地对类文件进行建模-因此,基本API分为许多相当简单的方法部分(尽管这些部分模拟了二进制问题)。

希望从头开始创建类文件的程序员需要了解类文件的整体结构,并且随着时间的推移,这种情况确实会发生变化。 幸运的是,ASM处理了Java版本之间在类文件格式上的细微差异,并且Java平台的强兼容性要求也有帮助。

按顺序,一个类文件包含:

  • 幻数(在传统的Unix上-Java的幻数是过时的性别歧视0xCAFEBABE)
  • 使用的类文件格式的版本号
  • 不变
  • 访问控制标志(例如,公共类,受保护类,程序包访问类)
  • 此类的名称
  • 该类的超类
  • 此类实现的接口
  • 此类拥有的字段(超出超类的字段)
  • 此类拥有的方法(超越父类的方法)
  • 属性(类级注释)

可以使用以下助记符来调用JVM类文件的主要部分:

ASM提供了两个API,最容易使用的API在很大程度上取决于Visitor模式。 通常,ASM是从空白开始的,使用ClassWriter(当习惯于使用ASM和直接字节码操作时,许多开发人员发现CheckClassAdapter是有用的起点-这是ClassVisitor,它以相似的方式检查其方法到出现在Java的类加载子系统中的验证程序。)

让我们看一些遵循通用模式的简单类生成示例:

  • 启动ClassVisitor(在我们的示例中为ClassWriter)
  • 写标题
  • 根据需要生成方法和构造函数
  • 将ClassVisitor转换为字节数组并将其写出

例子

public class Simple implements ClassGenerator {
// Helpful constants
private static final String GEN_CLASS_NAME = "GetterSetter";
private static final String GEN_CLASS_STR = PKG_STR + GEN_CLASS_NAME;
@Override
public byte[] generateClass() {
ClassWriter cw = new ClassWriter(0);
CheckClassAdapter cv = new CheckClassAdapter(cw);
// Visit the class header
cv.visit(V1_7, ACC_PUBLIC, GEN_CLASS_STR, null, J_L_O, new String[0]);
generateGetterSetter(cv);
generateCtor(cv);
cv.visitEnd();
return cw.toByteArray();
}
private void generateGetterSetter(ClassVisitor cv) {
// Create the private field myInt of type int. Effectively:
// private int myInt;
cv.visitField(ACC_PRIVATE, "myInt", "I", null, 1).visitEnd();
// Create a public getter method
// public int getMyInt();
MethodVisitor getterVisitor =
cv.visitMethod(ACC_PUBLIC, "getMyInt", "()I", null, null);
// Get ready to start writing out the bytecode for the method
getterVisitor.visitCode();
// Write ALOAD_0 bytecode (push the this reference onto stack)
getterVisitor.visitVarInsn(ALOAD, 0);
// Write the GETFIELD instruction, which uses the instance on
// the stack (& consumes it) and puts the current value of the
// field onto the top of the stack
getterVisitor.visitFieldInsn(GETFIELD, GEN_CLASS_STR, "myInt", "I");
// Write IRETURN instruction - this returns an int to caller.
// To be valid bytecode, stack must have only one thing on it
// (which must be an int) when the method returns
getterVisitor.visitInsn(IRETURN);
// Indicate the maximum stack depth and local variables this
// method requires
getterVisitor.visitMaxs(1, 1);
// Mark that we've reached the end of writing out the method
getterVisitor.visitEnd();
// Create a setter
// public void setMyInt(int i);
MethodVisitor setterVisitor =
cv.visitMethod(ACC_PUBLIC, "setMyInt", "(I)V", null, null);
setterVisitor.visitCode();
// Load this onto the stack
setterVisitor.visitVarInsn(ALOAD, 0);
// Load the method parameter (which is an int) onto the stack
setterVisitor.visitVarInsn(ILOAD, 1);
// Write the PUTFIELD instruction, which takes the top two
// entries on the execution stack (the object instance and
// the int that was passed as a parameter) and set the field
// myInt to be the value of the int on top of the stack.
// Consumes the top two entries from the stack
setterVisitor.visitFieldInsn(PUTFIELD, GEN_CLASS_STR, "myInt", "I");
setterVisitor.visitInsn(RETURN);
setterVisitor.visitMaxs(2, 2);
setterVisitor.visitEnd();
}
private void generateCtor(ClassVisitor cv) {
// Constructor bodies are methods with special name <init>
MethodVisitor mv =
cv.visitMethod(ACC_PUBLIC, INST_CTOR, VOID_SIG, null, null);
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
// Invoke the superclass constructor (we are basically
// mimicing the behaviour of the default constructor
// inserted by javac)
// Invoking the superclass constructor consumes the entry on the top
// of the stack.
mv.visitMethodInsn(INVOKESPECIAL, J_L_O, INST_CTOR, VOID_SIG);
// The void return instruction
mv.visitInsn(RETURN);
mv.visitMaxs(2, 2);
mv.visitEnd();
}
@Override
public String getGenClassName() {
return GEN_CLASS_NAME;
}
}

它使用一个具有单个方法的简单接口来生成类的字节,使用一个辅助方法来返回所生成类的名称以及一些有用的常量:

interface ClassGenerator {
public byte[] generateClass();
public String getGenClassName();
// Helpful constants
public static final String PKG_STR = "kathik/java/bytecode_examples/";
public static final String INST_CTOR = "<init>";
public static final String CL_INST_CTOR = "<clinit>";
public static final String J_L_O = "java/lang/Object";
public static final String VOID_SIG = "()V";
}

为了驱动我们将生成的类,我们使用一种称为Main的线束。 这提供了一个简单的类加载器,并提供了一种反映生成的类的方法的反射方式。 为了简单起见,我们还将生成的类写到Maven目标目录中的正确位置,以在IDE的类路径中进行选择:

public class Main {
public static void main(String[] args) {
Main m = new Main();
ClassGenerator cg = new Simple();
byte[] b = cg.generateClass();
try {
Files.write(Paths.get("target/classes/" + PKG_STR +
cg.getGenClassName() + ".class"), b, StandardOpenOption.CREATE);
} catch (IOException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
m.callReflexive(cg.getGenClassName(), "getMyInt");
}

下面的类只是提供一种访问受保护的defineClass()方法的方法,因此我们可以将byte []转换为类对象以进行反射使用

private static class SimpleClassLoader extends ClassLoader {
public Class<?> simpleDefineClass(byte[] clazzBytes) {
return defineClass(null, clazzBytes, 0, clazzBytes.length);
}
}
private void callReflexive(String typeName, String methodName) {
byte[] buffy = null;
try {
buffy = Files.readAllBytes(Paths.get("target/classes/" + PKG_STR +
typeName + ".class"));
if (buffy != null) {
SimpleClassLoader myCl = new SimpleClassLoader();
Class<?> newClz = myCl.simpleDefineClass(buffy);
Object o = newClz.newInstance();
Method m = newClz.getMethod(methodName, new Class[0]);
if (o != null && m != null) {
Object res = m.invoke(o, new Object[0]);
System.out.println("Result: " + res);
}
}
} catch (IOException | InstantiationException | IllegalAccessException |
NoSuchMethodException | SecurityException |
IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(Simple.class.getName()).log(Level.SEVERE, null, ex);
}
}

通过这种设置,我们只需进行少量修改即可轻松测试不同的类生成器,以探索字节码生成的不同方面。

非构造函数类非常相似。 例如,以下是如何生成具有单个静态字段的类的类,该类具有为其获取和设置的对象(此生成器未调用generateCtor()):

private void generateStaticGetterSetter(ClassVisitor cv) {
// Generate the static field
cv.visitField(ACC_PRIVATE | ACC_STATIC, "myStaticInt", "I", null,
1).visitEnd();
MethodVisitor getterVisitor = cv.visitMethod(ACC_PUBLIC | ACC_STATIC,
"getMyInt", "()I", null, null);
getterVisitor.visitCode();
getterVisitor.visitFieldInsn(GETSTATIC, GEN_CLASS_STR, "myStaticInt", "I");
getterVisitor.visitInsn(IRETURN);
getterVisitor.visitMaxs(1, 1);
getterVisitor.visitEnd();
MethodVisitor setterVisitor = cv.visitMethod(ACC_PUBLIC | ACC_STATIC, "setMyInt",
"(I)V", null, null);
setterVisitor.visitCode();
setterVisitor.visitVarInsn(ILOAD, 0);
setterVisitor.visitFieldInsn(PUTSTATIC, GEN_CLASS_STR, "myStaticInt", "I");
}
setterVisitor.visitInsn(RETURN);setterVisitor.visitMaxs(2,2);setterVisitor.visitEnd();

请注意如何使用ACC_STATIC标志集生成方法,以及方法参数如何在本地变量列表中首先出现(如ILOAD 0模式所隐含-在实例方法中,这将是ILOAD 1,作为“ this”引用)将存储在局部变量表中的0偏移处)。

使用javap,我们可以确认该类确实没有构造函数:

$ javap -c kathik/java/bytecode_examples/StaticOnly.class
public class kathik.StaticOnly {
public static int getMyInt(); Code:
0: getstatic    #11                // Field myStaticInt:I
3: ireturn
public static void setMyInt(int); Code:
0: iload_0
1: putstatic    #11                // Field myStaticInt:I
4: return
}

使用生成的类

到目前为止,我们已经通过ASM生成的类进行了自反的工作。 这有助于保持
这些示例是独立的,但是在许多情况下,我们希望将生成的代码与常规Java文件一起使用。 这很容易做到。 这些示例有助于将生成的类放入Maven目标
目录,因此很简单:

$ cd target/classes
$ jar cvf gen-asm.jar kathik/java/bytecode_examples/GetterSetter.class kathik/java/bytecode_examples/StaticOnly.class
$ mv gen-asm.jar ../../lib/gen-asm.jar

现在,我们有了一个JAR文件,可以将其用作其他代码中的依赖项。 例如,我们可以使用GetterSetter类:

import kathik.java.bytecode_examples.GetterSetter;
public class UseGenCodeExamples {
public static void main(String[] args) {
UseGenCodeExamples ugcx = new UseGenCodeExamples();
ugcx.run();
}
private void run() {
GetterSetter gs = new GetterSetter();
gs.setMyInt(42);
System.out.println(gs.getMyInt());
}
}

这不会在IDE中编译(因为GetterSetter类不在类路径中)。 但是,如果我们进入命令行并提供对类路径的适当依赖关系,则一切工作正常:

$ cd ../../src/main/java/
$ javac -cp ../../../lib/gen-asm.jar kathik/java/bytecode_examples/withgen/UseGenCodeExamples.java
$ java -cp .:../../../lib/gen-asm.jar kathik.java.bytecode_examples.withgen.UseGenCodeExamples
42

结论

在本文中,我们研究了使用ASM库中的简单API从头开始生成类文件的基础。 我们已经展示了Java语言和字节码要求之间的一些差异,并且Java的某些规则实际上只是该语言的约定,而不是由运行时强制执行的。 我们还展示了可以从该语言直接使用编写正确的类文件,就像它是由javac生成的一样。 这是Java与非Java语言(例如Groovy或Scala)的互操作性的基础。

有许多更高级的技术可用,但是本文应该为开始深入研究JVM运行时及其运行方式提供一个好地方。

翻译自: https://www.infoq.com/articles/Secrets-of-the-Bytecode-Ninjas/?topicPageSponsorship=c1246725-b0a7-43a6-9ef9-68102c8d48e1

大端字节序码流中取出2字节

大端字节序码流中取出2字节_字节码忍者的秘密相关推荐

  1. 大端字节序码流中取出2字节_产生字节码

    大端字节序码流中取出2字节 在这篇文章中,我们将看到如何为我们的语言生成字节码. 到目前为止,我们已经看到了如何构建一种语言来表达我们想要的东西,如何验证该语言,如何为该语言构建编辑器,但实际上我们还 ...

  2. 大端字节序码流中取出2字节_graalvm字节码到位码

    大端字节序码流中取出2字节 GraalVM & Micronauts - frenzy way to MicroServices, Serverless: Part1 GraalVM和Micr ...

  3. tcp码流中查找rtp头_跟踪数据流中的时间以查找性能问题

    tcp码流中查找rtp头 We're facing a challenge with several of our data flows that use more time than they ha ...

  4. 【音视频数据数据处理 12】【H.264篇】解析H.264原始码流中的I帧 / P帧 / B帧数据(暂未解决,本文先放着,来日更新)

    [音视频数据数据处理 12][H.264篇]解析H.264原始码流中的I帧 / P帧 / B帧数据 一.如何判断是 I帧 / P帧 / B帧 1.1 slice_type 1.2 slice_head ...

  5. H264码流中SPS、PPS详解

    1 SPS和PPS从何处而来? 2 SPS和PPS中的每个参数起什么作用? 3 如何解析SDP中包含的H.264的SPS和PPS串? 1 客户端抓包 在做客户端视频解码时,一般都会使用Wireshar ...

  6. 对h.264压缩视频码流中i帧的提取(firstime)

    这个问题要说清楚还是有点复杂:首先判断 NALU 类型是否是 5,如果是,那么以后连续出现的 NALU 类型为 5 的 NALU 就属于 IDR 帧(一种特殊的 I 帧):如果 NALU 不是 5,则 ...

  7. 从h264码流中获取图像的宽高---版本2(简洁版)

    从264码流中获取图像的宽高,代码如下,注意代码文件应该为cpp文件 #include <stdio.h> #include <stdlib.h> #include <s ...

  8. H264码流中SPS的获取

    The h.264 Sequence Parameter Set April 20th, 2011 by Ben Mesander 此文对于想要了解如何获取h264码流中SPS参数的过程,但是又不是很 ...

  9. sps和pps一篇好的解释 H264码流中SPS PPS详解<转>

    https://blog.csdn.net/luzubodfgs/article/details/86775940 H264码流中NALU sps pps IDR帧的理解 https://blog.c ...

最新文章

  1. 存储安全 系统的最后一道防线
  2. 在WinRT中读取资源文件
  3. @RequestBody注解失效?从前端传来的数据到底需不需要@RequestBody注解?前端传输数据解析的问题?
  4. 如何正确的在项目中接入微信JS-SDK
  5. 基于.net平台remoting、DB2技术的大型分布式HIS系统架构及开发(项目架构师方向)...
  6. JsonMappingException: (was java.lang.NullPointerException)
  7. Cookie和Session实现保存登录状态免登录
  8. 平面上有两个圆相交,求两个圆相交部分的面积
  9. Spring : Spring profile 实现多环境支持
  10. 20141113--SQL 事务
  11. [SHOI2008]堵塞的交通traffic
  12. python 条件判断和循环
  13. day-15 磁盘挂载 磁盘格式化
  14. curl和libcurl的区别简介
  15. 如何制作统计报表(盈帆报表: efreprt.com)
  16. W25Q64 Flash芯片原理与应用方案(含W25Q64中文数据手册)
  17. jQuery练习_狂拍灰太狼
  18. 5G 产业链:基站天线和小基站爆发潜力大
  19. 【回归预测-ELM预测】基于遗传算法优化极限学习机实现风电数据回归预测附matlab代码
  20. C语言中char和char *a[]和char (*a)[]

热门文章

  1. 计算机概念:抽象和具象的关系和联系
  2. 利用计算机解决问题过程中找出已知,用计算机解决问题的过程.ppt
  3. 洛谷 P1219八皇后
  4. SSL协议解析及SSL虚拟专用网
  5. 安全管家安卓_【沙发管家】电视盒子Root是什么意思?Root有风险吗?
  6. 【微软算法面试高频题】俄罗斯套娃信封
  7. 【CSS】单边阴影、双边阴影、三边阴影
  8. 2.说明计算机环境下会计循环的八个流程,2020年云南自考《会计信息系统》要点复习(二)...
  9. 梅科尔工作室-“SQL语句”
  10. wap2app ios首页侧滑关闭页面出现空白 解决方法