字节码原理浅析 —— 基于栈的执行引擎

浅析一下V8引擎的原理到底是什么呢? 因为无法确定这个代码会运行在怎样的环境上(windows,mac,linux),不同环境的cpu架构不同,不同cpu架构能执行的机器指令不同,所以无法确定机器指令,所以才转化为字节码。字节码可以跨平台,转化为机器指令后就可以运行了。因为lgnition解释器并不能直接认识s代码,需要先经过parse模块解析成抽象语法树。追问这里抛出一个问题,为什么不通过lgnition直接编译成字节码呢?去执行js代码,可以独立运行,也可以嵌入在C++应用程序中。简单说一下就是V8引擎是C++编写的开源引擎,用于。... 阅读详情

字节码是运行在 JVM 上的,为了能弄懂字节码,需要对 JVM 的运行原理有所了解。这篇文章将以栈帧为切入点理解字节码在 JVM 上执行的细节。

虚拟机

虚拟机常见的实现方式有两种:Stack based 的和 Register based。比如基于 Stack 的虚拟机有Hotspot JVM、.net CLR,这种基于 Stack 实现虚拟机是一种广泛的实现方法。而基于 Register 的虚拟机有 Lua 语言虚拟机 LuaVM 和 Google 开发的安卓虚拟机 DalvikVM。

两者有什么不同呢?举一个计算两数相加的例子:c = a + b 基于 HotSpot JVM 的源码和字节码如下

源码
void bar(int a, int b) {
    int c =  a + b;
}

对应字节码
0: iload_1 // 将 a 压入操作数栈
1: iload_2 // 将 b 压入操作数栈
2: iadd    // 将栈顶两个值出栈,相加,然后将结果放回栈顶
3: istore_3 // 将栈顶值存入局部变量表中第 3 个 slot 中

基于寄存器的 LuaVM 的 lua 源码和字节码如下,查看字节码使用luac -l -l -v -s test.lua 命令

源码
local function my_add(a, b)
	return a + b;
end

对应字节码
1	[3]	ADD      	2 0 1

基于寄存器的 add 指令直接把寄存器 R0 和 R1 相加,结果保存在寄存器 R2 中。

基于栈和基于寄存器的过程对比如下:


基于栈和寄存器的指令集各有优缺点,基于栈的指令集移植性更好,代码更加紧凑、编译器实现更加简单,但完成相同功能所需的指令数一般比寄存器架构多,需要频繁的入栈出栈,栈架构指令集的执行速度会相对而言慢一些。

为了理解字节码的细节,我们需要详细了解字节码的执行过程。众所周知,Hotspot JVM 是一个基于栈的虚拟机,每个线程都有一个虚拟机栈,存储了「栈帧」。每次方法调用都伴随着栈帧的创建销毁。

栈帧

栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构 栈帧随着方法调用而创建,随着方法结束而销毁,栈帧的存储空间分配在 Java 虚拟机栈中,每个栈帧拥有自己的局部变量表(Local Variables)、操作数栈(Operand Stack) 和 指向运行时常量池的引用

局部变量表

每个栈帧内部都包含一组称为局部变量表(Local Variables)的变量列表,局部变量表的大小在编译期间就已经确定。Java 虚拟机使用局部变量表来完成方法调用时的参数传递,当一个方法被调用时,它的参数会被传递到从 0 开始的连续局部变量列表位置上。当一个实例方法(非静态方法)被调用时,第 0 个局部变量是调用这个实例方法的对象的引用(也就是我们所说的 this )

操作数栈

每个栈帧内部都包含了一个称为操作数栈的后进先出(LIFO)栈,栈的大小同样也是在编译期间确定。Java 虚拟机提供的一些字节码指令用来从局部变量表或者对象实例的字段中复制常量或者变量到操作数栈,也有一些指令用于从操作数栈取走数据、操作数据和把操作结果重新入栈。在方法调用时,操作数栈也用来准备调用方法的参数和接收方法返回的结果。

比如 iadd 指令用来将两个 int 类型的数值相加,它要求执行之前操作数栈已经存在两个由前面其它指令放入的 int 型数值,在 iadd 指令执行时,两个 int 值从操作数栈中出栈,相加求和,然后将求和的结果重新入栈。

比如 1 + 2 这样的指令执行过程如下


整个 JVM 指令执行的过程就是局部变量表与操作数栈之间不断 load、store 的过程


我们再来看一个稍微复杂一点的例子

public class ScoreCalculator {
    public void record(double score) {
    }

    public double getAverage() {
        return 0;
    }
}
public static void main(String[] args) {
    ScoreCalculator calculator = new ScoreCalculator();

    int score1 = 1;
    int score2 = 2;

    calculator.record(score1);
    calculator.record(score2);

    double avg = calculator.getAverage();
} 

javap 查看字节码输出如下

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
  stack=3, locals=6, args_size=1
     0: new           #2                  // class ScoreCalculator
     3: dup
     4: invokespecial #3                  // Method ScoreCalculator."<init>":()V
     7: astore_1
     
     8: iconst_1
     9: istore_2
     
    10: iconst_2
    11: istore_3
    
    12: aload_1
    13: iload_2
    14: i2d
    15: invokevirtual #4                  // Method ScoreCalculator.record:(D)V
    
    18: aload_1
    19: iload_3
    20: i2d
    21: invokevirtual #4                  // Method ScoreCalculator.record:(D)V
    
    24: aload_1
    25: invokevirtual #5                  // Method ScoreCalculator.getAverage:()D
    28: dstore        4
    
    30: return

  • 0 ~ 7:新建了一个 ScoreCalculator 对象,使用 astore_1 存储在局部变量 calculator 中:astore_1 的含义是把栈顶的值存储到局部变量表下标为 1 的位置上,这里为什么会有一个 dup,我们后面会讲到
  • 8 ~ 11:iconst_1 和 iconst_2 用来将整数 1 和 2 加载到栈顶,istore_2 和 istore_3 用来将栈顶的元素存储到局部变量表 2 和 3 的位置上
  • 12 ~ 15:可以看到 store 指令会把栈顶元素移除,所以下次我们要用到这些局部变量时,需要使用 load 命令重新把它加载到栈顶。比如我们要执行calculator.record(score1),对应的字节码如下
12: aload_1
13: iload_2
14: i2d
15: invokevirtual #4 // Method ScoreCalculator.record:(D)V

可以看到 aload_1 先从局部变量表中 1 的位置加载 calculator 对象,iload_2 从 局部变量表中 2 的位置加载一个整型值,i2d 这个指令用来将整型值转为 double 并将新的值重新入栈,到目前为止参数全部就绪,可以用 invokevirtual 执行方法调用了

  • 24 ~ 28:同样是一个普通的方法调用,流程还是先 aload_1 加载 calculator 对象,invokevirtual 调用 getAverage 方法,并将 栈顶元素存储到局部变量表下标为 4 的位置上
    有一点需要注意的是 javap 输出的locals=6,但是我们目前看到的局部变量只有args、calculator、score1、score2、avg这 5 个,为什么这里等于 6 呢?这是因为 avg 为 double 型变量,需要两个槽位(slot) 整个过程局部变量表如下图所示


其实局部变量表可以通过 javap 用 -l 参数直接输出,但是我们用 javap -v -p -l MyLocalVariableTest 并没有输出任何局部变量表相关的信息。这是因为默认情况下局部变量表属于调试级别的信息,javac 编译的时候并没有编译进字节码,我们可以加上 javac -g 生成字节码的时候同时生成所有的调试信息,如下所示

javac -g  MyLocalVariableTest.java 
javap  -v -p -l   MyLocalVariableTest
LocalVariableTable:
Start  Length  Slot  Name   Signature
    0      31     0  args   [Ljava/lang/String;
    8      23     1 calculator   LScoreCalculator;
   10      21     2 score1   I
   12      19     3 score2   I
   30       1     4   avg   D

从二进制看 class 文件和字节码

public class Get {
    String name;

    public String getName() {
        return name;
    }
}

javap 查看字节码如下

public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
  stack=1, locals=1, args_size=1
     0: aload_0
     1: getfield      #2 / Field name:Ljava/lang/String;
     4: areturn


直接从二进制来看下这个 class 文件 xxd Get.class


我们可以手动用 16 进制编辑器去修改这些字节码文件,只是比较容易出错,所以产生了一些字节码操作的工具,最出名的莫过于 ASM 和 Javassist。我们后面讲到软件破解的时候,会介绍直接修改字节码和通过 ASM 动态修改字节码这两种方式

小结

一起来回顾一下这篇文章的要点:

  • 第一,基于栈和基于寄存器指令集的优劣势;
  • 第二,讲解了 JVM 栈帧的构成(局部变量表、操作数栈、指向运行时常量池的引用),顺带讲解了 javap -l 参数和其在局部变量表中的应用;
  • 第三,从类文件二进制角度看字节码的实现,并引出 ASM 字节码改写技术。
JVM指令手册 比如对应int型才该方式只能把-1,0,1,2,3,4,5(分别采用iconst_m1,iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5) 送到栈顶。对于int型,其他的数值请使用push系列命令(比如bipush)。 立即下载

相关推荐

私募pp网-私募排行js逆向

在数据采集的学习过程当中,发现了一个有意思的网站,所以记录一下,仅供学习交流,用于其他用途与作者无关。目标:私募基金当中的列表页数据。

zzp28218的博客 1895

java 字节码指令集

1.将局部变量表中的变量压入操作数栈中 xload_n or xload n 将数据压入栈 iload_1:将局部变量表中下标为 1 的 int 变量压入操作数栈中。 aload_2:将局部变量表中下标为 2 的引用数据类型变量(此时为 String)压入操作数栈中。 lload_3:将局部变量表中下标为 3 的 long 型变量压入操作数栈中。 iload 5:将局部变量表中下标为 5 的 int 变量(实际为 boolean)压入操作数栈中。 2.将常量池中的常量压入操作数栈中 根据数据类型

liuzipeizliuziyu的博客 1659

Deepracer 智能无人车-绿洲科学云平台介绍

Deepracer 智能无人车 绿洲科学云

woaixiyue19831的博客 1945

java istore_怎么理解JVM中的iload和istore指令

我最近在学习JVM,被istore和iload两条指令困扰了。以下是我查看《Java虚拟机规范》得到的解释将一个局部变量加载到操纵栈的指令包括:iload、iload_、lload…将一个数值从操作数栈存储到局部变量表的指令包括:istore、istore_、lstore…下面是我的java代码publicstaticintadd(inta,intb){intc=0;c=a+b;ret...

weixin_39608398的博客 699

霍尔安装歪了也能用?揭秘无感FOC中的‘霍尔自学习’黑科技,实现信号线自适应

本文揭秘了无感FOC中的‘霍尔自学习’黑科技,即使霍尔安装歪了或信号线接错,电机仍能平稳运转。该技术通过算法创新解决了传统霍尔方案的安装公差、接线容错和校准成本问题,显著提升了电机控制的可靠性和效率。文章详细解析了自学习算法的实现架构、关键技术及工程实践中的优化方案,展示了其在工业应用中的卓越性能。

weixin_28309571的博客 462

【以太网硬件九】1000base-X是什么?

简要描述1000base-X物理层规范

highman110的博客 1万+

局部变量压栈指令

一 局部变量压栈指令 iload 从局部变量中装载int类型值 lload 从局部变量中装载long类型值 fload 从局部变量中装载float类型值 dload 从局部变量中装载double类型值 aload 从局部变量中装载引用类型值(refernce) iload_0 从局部变量0中装载int类型值 iload_1 从局部变量1中装载int类型值 iload_2 从局部变量2中装载int类型值 iload_3 从局部变量3中装载int类型值 lload_0 从局部变量0中装载.

实践求真知 989

ETAS工具导入DBC生成Com协议栈

ETAS工具导入DBC主要也是生成arxml用的,ETAS推荐使用Cobra导入,本文介绍导入过程及注意事项不管是导入DBC还是arxml,本质上都是为了生成对应的com协议栈,目的是为了减少重复的工作量,所以,前期定义好收发的矩阵很重要,一般导入一次之后,后面就可以按变更点手动更改了。

赞的博客 1710

JVM——Java字节码基础

JVM——Java字节码基础:从栈模型到指令集的全面剖析

从知识输入到知识输出 1976

Mac版 Android Studio自带模拟器无法联网解决方案

前言 作为一枚安卓开发者,免不了使用模拟器来开发调试(毕竟它比手机方便太多)。但是因为适配需要,我们会更多的使用真机开发调试,那么当模拟器遇到这样那样的问题,我们往往会无从下手,其中最烦的就是网络问题。 我最近图方便,又重新用起了模拟器,但是发现联网失败,用模拟器内置的浏览器也打不开网页,于是百度上一通找,毫无效果,各种说用cmd命令行设置模拟器dns的都没用,显示没有什么dns文件啥的,后来...

陈万洲的专栏 1万+

【Unity】Gif图片播放插件(支持UI及模型纹理渲染两种模式)

unity本身不支持GIF格式文件直接渲染, 因此一般的做法是通过加载图片,通过代码控制其循环播放、停止等,模拟动画效果,本文简单介绍一下某个现有开源工具,感谢开源作者。

灵境引路人 4036

从iload_1 iload_2 iadd字节码角度看jvm字节码执行

ubuntu22 环境openjdk17。

u014200244的专栏 612

2024-江苏科技大学-模拟-动态接触角,模拟液滴撞击,comsol软件模拟。

相反,其对液滴粘度和平衡接触角的变化基本不敏感。重力势能的影响显著受过程参数调节,随着液滴直径和密度的增加而增加,而随着初始液滴速度和表面张力系数的增加而减小。通过考虑形状因子和粘度修正因子,可以准确定义表面能和粘性耗散能,使得所提出的半经验修正勺状模型表现出良好的性能,在所有65个研究案例中,液滴最大铺展长度因子(βmax)和中心厚度(h)的相对误差基本分别落在5%和10%的区间内。重力势能的影响受过程参数显著调节,随液滴直径和密度的增大而增大,随液滴初始速度和表面张力系数的增大而减小。

qq_15963745的博客 511

字节码原理浅析 —— 基于栈的执行引擎

本文主要讲了以下几个要点:第一,基于栈和基于寄存器指令集的优劣势;第二,讲解了 JVM 栈帧的构成(局部变量表、操作数栈、指向运行时常量池的引用),顺带讲解了 javap -l 参数和其在局部变量表中的应用;第三,从类文件二进制角度看字节码的实现,并引出 ASM 字节码改写技术。

Rocky006的博客 239

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

Java基于栈的字节码执行引擎

文章目录编译执行和解释执行基于栈的指令集基于栈的解释执行过程 编译执行和解释执行 许多Java虚拟机的执行引擎在执行Java代码的时候都有解释执行(通过解释器执行)和编译执行(通过即时编译器产生本地代码执行)两种选择。Java语言经常被人定义为“解释执行的语言”,在Java初生的JDK 1.0时代,这种定义还算准确,但当前主流的虚拟机当中都包含了及时编译器后,Class文件中的代码到底会被解释执行还是编译执行,就成了只有虚拟机自己才能知道的事。 大部分程序代码转化成物理机目标代码或虚拟机能执行的指令集之前,

记得我雨的博客 323

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

自娱自乐,浅析执行引擎如何运作

8.1 概述 从外观看,所有的Java虚拟机的执行引擎都是一致的:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。 8.2 运行时栈帧结构 栈帧是用于支持虚拟机进行方法调用和方法执行的数据结构,它是虚拟机运行时数据区中的虚拟机栈的栈元素。栈帧存储了方法的局部变量表、操作数栈、动态链接和方法返回地址等信息。 每一个栈帧都包括了局部变量表、操作数栈、动态链接、方法返回地址和一些额...

weixin_43259821的博客 485

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

java中 u0038_【0038】浅析Java虚拟机字节码执行引擎

前言执行引擎是Java虚拟机的核心组成部分之一,其模型使用IPO概念解读为:输入的是字节码文件、处理过程是等效字节码解析过程,输出的是执行结果。既然是执行字节码,必然有一个入口,对于程序而言那就是方法的调用执行,而方法调用和运行的基本单位是栈帧,所以下文会先介绍栈帧结构和方法调用,最后在分析虚拟机的字节码执行引擎。本文大纲:1、运行时栈帧结构2、方法调用3、基于栈的字节码解释执行引擎4、总结一、运...

weixin_36021461的博客 242

Java虚拟机字节码执行引擎浅析

执行引擎是Java虚拟机的核心组成部分之一。     我们知道,javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树、再遍历语法树生成线性的字节码指令流的过程。而字节码文件再经过加载、验证、准备、解析、初始化等阶段才能被使用。字节码执行引擎正是执行了这样的过程:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。 运行时栈帧结构: 栈帧(

kobejayandy的专栏 3903

java虚拟机字节码执行引擎浅析

执行引擎是java虚拟机的核心组成部分之一。     我们知道,javac编译器完成了程序代码经过词法分析、语法分析到抽象语法树、再遍历语法树生成线性的字节码指令流的过程。而字节码文件再经过加载、验证、准备、解析、初始化等阶段才能被使用。字节码执行引擎正是执行了这样的过程:输入的是字节码文件,处理过程是字节码解析的等效过程,输出的是执行结果。 运行时栈帧结构: 栈帧(

RowandJJ 3227

【JavaScript执行原理浅析】什么是JavaScript解析引擎?

什么是JavaScript解析引擎? 文章目录什么是JavaScript解析引擎?什么是JavaScript解析引擎?JavaScript解析引擎到底是干什么的? 本人对于JavaScript有一点认识,但是认识也不深,突然发现使用了那么长时间的Javascript,但是对其运行原理还是不清晰,于是就想写一个系列的文章希望能在这个过程中加深自己对于JavaScript执行原理的更深层次的理解。 什...

kiku 582

深入理解Java虚拟机-- java虚拟机字节码执行引擎浅析

深入理解Java虚拟机-- java虚拟机字节码执行引擎浅析

mine_song的博客 651

随笔-使用d8将JS源码编译为字节码,及字节码指令解读

什么是 D8 d8 是一个非常有用的调试工具,你可以把它看成是 debug for V8 的缩写。我们可以使用 d8 来查看 V8 在执行 JavaScript 过程中的各种中间数据,比如作用域、AST、字节码、优化的二进制代码、垃圾回收的状态,还可以使用 d8 提供的私有 API 查看一些内部信息。 下载D8 win64 平台: https://storage.googleapis.com/chromium-v8/official/canary/v8-win64-dbg-8.4.109.zip

优快云 2202

Google V8引擎浅析

前端开发人员都会遇到一个流行词:V8。它的流行程度很大一部分是因为它将JavaScript的性能提升到了一个新的水平。是的,V8很快。但它是如何发挥它的魔力?前言源代码:https://s...

奇舞周刊 3750

浅析Javascript运行原理

Javascript是一种广泛应用于网页前端开发的脚本语言。本篇博客将深入探讨Javascript的运行原理,包括Javascript引擎、解释器和编译器、执行上下文、作用域和作用域链、事件循环、内存管理和异步编程等方面。

Jerry_zpon的博客 1726

如何保护Python源码?从打包、混淆到加密字节码执行

在软件开发中,代码安全是一个重要的课题。Python 代码由于以源码形式直接部署到服务器并通过解释器执行,容易暴露其中的敏感信息,如 API 密钥、加密盐等。为了解决这一问题,开发者需要采取有效的措施对代码进行加密或保护。本文将详细探讨几种常见的方案,包括编译为可执行文件、生成字节码文件、代码混淆以及基于动态解密的自定义解释器等。这些方法各有特点,可根据实际需求选择适合的方案。第一种方法是编译为可执行文件。

General_zy的博客 3902
上一篇: 基于Redis的消息发布和订阅(广播模式)
下一篇: 字节码初体验-从HelloWorld开始
yue_stack
博客等级 码龄3年 6粉丝 43原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值