关于JVM中重定位的问题

关于JVM中重定位的问题

1 何为重定位

我认为重定位概念应该分为2种类型

第一种 整片代码重定位

假设原来有一个变量保存着一片内存的首地址,而该片内存保存着指令机器码,当这片代码被整体移动到内存中的另一个位置后,这时需要将移动后的首地址替换到变量中。

上述情况还可分为第二种情况,假设有一个变量保存着一片内存的首地址,而该片内存保存着指令机器码,当这片内存的实现逻辑被另一片内存替换之后,这时需要更新变量中的内存地址。

第二种 单个指令的重定位

假设代码逻辑中创建了一个Object对象,之后需要对该对象进行若干操作。在汇编层面则需加载该对象,即ldr(load from memory),然后将该对象的值放入寄存器中进行操作,然后写入内存。这里存在一个问题。对象Object只有在运行时才会在内存中创建,那么其加载对象的操作,如ldr,如何提前知道其对象在内存中的位置呢?其实是不知道的,只能先将内存地址的值用一个假设值填写进ldr的操作数,待对象创建之后,将ldr的内存地址操作数进行替换。诸如此类的典型操作还有calljmp等。比如call指令跳转的地址在进入运行时之前是未知的,只有完全进入运行时才可确定最后跳转的地址。

2 hotspot中的重定位

第一种

这种是最简单的,比如在sharedRuntime.cpp文件中

void AdapterHandlerEntry::relocate(address new_base) {
  address old_base = base_address();
  assert(old_base != NULL, "");
  ptrdiff_t delta = new_base - old_base;
  if (_i2c_entry != NULL)
    _i2c_entry += delta;
  if (_c2i_entry != NULL)
    _c2i_entry += delta;
  if (_c2i_unverified_entry != NULL)
    _c2i_unverified_entry += delta;
  assert(base_address() == new_base, "");
}

hotspot中,_i2c_entry表示函数从解释器到JIT的入口地址,这里可以看到_i2c_entry的值根据if的条件进行了更改。也就是说,同一个函数的执行逻辑,从一片内存转移到了另一片内存。

第二种

2.1 虚拟值代替操作数

首先了解一下纯C语言条件下重定位的过程,假设有以下C语言调用

在这里插入图片描述

通过下面的命令进行编译

在这里插入图片描述

这里可以看到,a.cb.c通过GCC编译为对象文件a.ob.o,这两个对象文件内部保存的是C文件的汇编逻辑。

通过使用objdump工具可以反汇编出a.o中的内容

在这里插入图片描述

分析代码如下

push
mov
sub
#上述代码为建立函数栈
movl $0x64, -0x4(%rbp) #  100 -> 内存
lea -0x4(%rbp), %rax   #  内存(100)-> rax
#上述代码执行 int a = 100
lea 0x0(%rip), %rsi

其中rip表示程序计数器寄存器,指向的是下一条即将执行的指令位置。由于当前a.o是一个独立的文件,无法知道具体的调用,因此假设lea的操作数为下一条指令的0x0偏移。这里可以看到

在这里插入图片描述

同理可以看到call指令

在这里插入图片描述

很明显,call的下一条指令位置是2b,也就是后面一条mov指令。

由于编译器生成的汇编指令条数是固定的,因此每条指令相对于第一条的位置是固定的,比如`2b`表示相对于第一条指令的偏移是`0x2b`

实际这就是把call指令的返回地址作为跳转地址操作数。

如果当这些对象文件通过链接器链接在一起,然后统一放入内存的某个位置,此时只需要将call或者lea的地址操作数进行修改就可以了

在这里插入图片描述

这时,我们来看看call指令的操作数值发生了怎样的改变

e8 00 00 00 00 --> e8 07 00 00 00
小端编码,需要从左往右读,其操作数实际就是0x00000007,也就是说从call指令的下一条指令,即原来2b的位置开始往高地址处偏移7个字节处,即swap函数的开始

这里可以看出在

call指令跳转地址 = 下一个指令地址 + 偏移

在C语言中如何计算替换的地址值不属于当前的主题。我们来看一下hotspot中如何进行重定位值的计算。

2.2 重定位值的计算

首先要确定一个大的重定位计算原则,即在固定的指令位置基础上,通过偏移值进行重定位。

比如,对于某条指令,例如call的位置在函数中的位置是固定的。在编译器将程序进入运行时之前,被调用函数foo的位置未知,但当程序在运行时确定了foo的位置之后,只需要将foo的位置减去call的下一条指令(返回地址)位置获得偏移值,即可实现call指令操作数的重定位。

在这里插入图片描述

JVM中实现上述逻辑涉及几个类。

1 relocInfo

该类用于记录重定位的信息。其中最重要的2个属性是relocType_value

relocType用于描述重定位的类型。可以假设不同的重定位类型所涉及的算法或者逻辑不一致。

enum relocType {
    none                    =  0, // Used when no relocation should be generated
    oop_type                =  1, // embedded oop
    virtual_call_type       =  2, // a standard inline cache call for a virtual send
    opt_virtual_call_type   =  3, // a virtual call that has been statically bound (i.e., no IC cache)
    static_call_type        =  4, // a static send
    ...
};

_value其声明如下

unsigned short _value

hotspot中使用一个16位数表示重定位地址的计算。该值分为2个部分,

在这里插入图片描述

通过_value即可获得内存中重定位的地址

2 RelocationHolder类和与之相关的类</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值