虚拟机指令集&栈与函数调用(上)

简介: 虚拟机指令集&栈与函数调用(上)

指令集


save&load

  • IMM

全称load immidiatily

立即加载数据到寄存器

  • LEA

load effective address

加载地址

  • LC/LI/SC/SI

load char/load int:将char和int加载到寄存器

save char/save int:将char和int从寄存器加载到内存

  • PUSH

将寄存器的数据推到栈顶stack peek

举例

image.png

  • ax是通用寄存器
  • pc是代码区指针program counter

指向当前正在执行的指令

这些指令背后的直接操作数也会存在代码区

比如IMM 2 把2这个数字这个常量(字面量)加载到ax寄存器

解析完这个指令放入code区

image.png

当读到IMM之后 PC就会下移指向2的位置

代码中的op变量就会装刚刚读到的指令IMM

ax =(dereference)实际地址所在的内容即2

把它放在ax寄存器中

会有一个后置的pc++操作即继续移到下一步

LEA指令在x86里的作用,举例说明:

有一个寄存器bx 里面装了一个内存的地址

bx=430430

想算这个地址往后移8位的地址

bx+8的地址存入ax中

如果没有LEA的话 需要这么实现:

ADD bx 8 mov bx ax 然后再把bx数据转移动ax

这样操作会有一个问题:

1、改变了bx原本的值

2、加法操作有可能会导致溢出

有可能相加的结果会大于32位的值

又会改变CF OF这些标识位的数据

这样会对后续的计算带来更多的复杂性

所以通过LEA这样的操作来简化

LEA ax (bx+8)

在我们的实现下LEA 它不是基于任何地方去算地址的

因为实现也没有必要

主要是想基于栈里面去计算地址

最多是用来读取函数的参数和局部变量的参数

它们都是会基于栈的bp所在的位置

所以我们的LEA是基于bp来加减

可能会得到这个函数的第几个参数或第几个局部变量

比如LEA-1 就是取bp在栈里面的位置 减1其实就是加1 因为栈是从大到小的 减1其实就是bp的下一个位置 这个位置可能就是这个函数的第一个局部变量 比如它是35 把这个位置的地址拿出来 取里面的值就是35

LEA其实就是基于bp的地址来计算它的相对位置

继续分析代码

接下来就是load操作

ax本身装了一个地址

把这个地址所对应的内存的值加载到ax中去

即ax=*ax

由于不同的类型 则会使用不同的指针来做强制转换

sc就会把数据存在栈顶 sp指向栈顶 里面存了一个地址

把这个地址转换成相应的指针

比方说char指针(char*)

然后对这个地址取deference

就拿到了这个地址所在的空间

把这个ax的数据存在这个空间里

用完栈顶之后sp会加加

栈就会往回退一格

即这个栈用完了 就回到上一位

假设这个栈里存的地址是430430

假设说它是data区的一个位置

sp指向这个地址 先找到430430

把它取出来 转化成一个int的指针

指向了data区的一个位置

再对它取一个dereference 那就拿到了这块空间

假设ax=1 这个时候就会把430430这个地址的数据设置为1

就完成了把ax里面的数据存到了内存空间的data区的430430这个位置

再继续看代码

push指令 比如ax里面load了一个数据2,将该数据压栈:sp指针先-- 再把数据2放入栈中


运算相关指令


image.png

算数运算

四则运算

ADD/SUB/MUL/DIV

MOD取模


位运算

OR

XOR 异或

AND

SHL/SHR左移/右移


逻辑运算

EQ相等

NQ不等

LT/LE/GT/GE小于/小于等于/大于/大于等于


分支跳转指令

JMP相关指令

  • JMP 将pc指针跳到一个指定的代码区域

假设430430是代码区的某个地址

jmp 430430 ,pc指针就会直接移动到430430的地址处 跳过中间的指令

  • JZ/JNZ 基于寄存器当前的值去判断是否要jump以及jump到什么位置

JZ 就是判断 ax是否等于0 如果等于0 就做JMP 如果不等于0 接着执行下一条指令 pc就直接加1了

JNX就相反

举例说明

while(a>b){
...
}

这样一个while循环 怎么用最简单的vm命令实现?

假设已经实现了a>b表达式的值 要么是0要么是1 并且将结果保存在ax中

在代码区定义2个位置 一个是loop point,一个是end point

然后JZ指令判断 ax是否等于0 如果等于0的话 就JMP到end point

如果不等于0 直接执行下一条语句

循环体代码执行完了 要循环 回到开始的位置 有一个JMP loop point

image.png

再举一个例子

if(a>b){
...
}else{
...
}

在代码区有3个锚点 true point,false point,end point

如果是true 则从true point执行

如果是false 则从false point执行

true执行到false point位置 也需要跳过false point后面的指令

跳转到end point

首先需要计算a>b的值存到ax中去

当为true的时候 直接执行 不需要跳转

当不为true的时候 即JZ false point 跳转到false point

执行完就结束了

如果执行ture的逻辑 执行完了之后 走到 JMP end point

直接跳转到end point

然后执行if之后的语句

image.png

有了while和if这两个分支判断指令 不涉及函数的情况下 可以实现

图灵完备的计算了

为什么要有statck(code和data空间)

假如没有栈 如何实现函数调用

int add(int a,int b){
   int ret;
   ret = a+b;
   reuturn ret;
}
int main(){
   int a=3;
   int b=4;
   int ret=add(a,b);
   return 0;
}

main函数调用add函数 代码从main函数开始执行

需要知道这几个信息

a、要调用的函数的地址

函数在翻译成汇编的时候 在代码区的位置

b、在执行的过程中需要知道传递的参数的值

add函数内的局部变量和参数值在函数执行完后就没用了 然后找到返回地址 返回到调用处就可以了

关键是在函数执行的过程中要把局部变量a、b和返回地址存起来

add函数在data区先存3再存4再存返回地址(比如是代码区的430430位置)

image.png

函数调用结束之后 把这个data区删除 把430430地址数据清空

data区在不同的地方就会有不同函数的空间

得有一个统一的地方去管理这些空间

这个地方逻辑上的概念可以定义成一个hash散列表

能够让每次函数调用的时候 能够快速的定位到一个地方

比如说add方法调用完了 我要回到main方法的时候

mian的这些参数值返回值等得恢复

得找到这个空间 去hash散列表中查

这种方案也是可以的 但会导致data区的维护很复杂

计算机中任何复杂的东西都可以通过增加一个中间层或者说抽象层来解决

函数在调用的过程中 最后调用的也就是最近调用的

正在执行的这个函数是最先释放的

我执行完了 就回到调用我的地方

是一个后进先出的过程

那么就会想到stack

所以一般会用stack去描述函数的局部空间


相关文章
|
7月前
|
存储 Java 数据安全/隐私保护
【JVM】Java虚拟机栈(Java Virtual Machine Stacks)
【JVM】Java虚拟机栈(Java Virtual Machine Stacks)
116 0
|
7月前
|
存储 安全 Java
【数据结构】栈的使用|模拟实现|应用|栈与虚拟机栈和栈帧的区别
【数据结构】栈的使用|模拟实现|应用|栈与虚拟机栈和栈帧的区别
66 0
|
4月前
|
Java
程序计数器和虚拟机栈
这篇文章介绍了Java虚拟机(JVM)的内存结构,特别解释了程序计数器(Program Counter Register)的作用,即用来记录下一条JVM指令的执行地址和行号。
程序计数器和虚拟机栈
|
4月前
|
存储 安全 Java
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程是什么,JDK、JRE、JVM的联系与区别;什么是程序计数器,堆,虚拟机栈,栈内存溢出,堆栈的区别是什么,方法区,直接内存
JVM常见面试题(二):JVM是什么、由哪些部分组成、运行流程,JDK、JRE、JVM关系;程序计数器,堆,虚拟机栈,堆栈的区别是什么,方法区,直接内存
|
5月前
|
云计算
云计算问题之线程请求的栈深度大于虚拟机所允许的深度如何解决
云计算问题之线程请求的栈深度大于虚拟机所允许的深度如何解决
32 1
|
4月前
|
安全 Java
虚拟机栈的五道面试题
这篇文章提供了关于Java虚拟机栈的五个面试问题,涉及栈溢出的情况、栈大小调整、栈内存的分配、垃圾回收与虚拟机栈的关系以及局部变量的线程安全性。
|
5月前
|
存储 Java 对象存储
Java虚拟机(JVM)中的栈(Stack)和堆(Heap)
在Java虚拟机(JVM)中,栈(Stack)和堆(Heap)是存储数据的两个关键区域。它们在内存管理中扮演着非常重要的角色,但各自的用途和特点有所不同。
55 0
|
6月前
|
存储 Java C++
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据
Java虚拟机(JVM)管理内存划分为多个区域:程序计数器记录线程执行位置;虚拟机栈存储线程私有数据,如局部变量和操作数;本地方法栈支持native方法;堆存放所有线程的对象实例,由垃圾回收管理;方法区(在Java 8后变为元空间)存储类信息和常量;运行时常量池是方法区一部分,保存符号引用和常量;直接内存非JVM规范定义,手动管理,通过Buffer类使用。Java 8后,永久代被元空间取代,G1成为默认GC。
67 2
|
7月前
|
存储 监控 安全
JVM工作原理与实战(十六):运行时数据区-Java虚拟机栈
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了运行时数据区、Java虚拟机栈等内容。
47 0
|
7月前
|
存储 缓存 Java
JVM【带着问题去学习 02】数据结构栈+本地方法栈+虚拟机栈+JVM栈运行原理
JVM【带着问题去学习 02】数据结构栈+本地方法栈+虚拟机栈+JVM栈运行原理
87 0