关于JVM中重定位的问题
1 何为重定位
我认为重定位概念应该分为2种类型
第一种 整片代码重定位
假设原来有一个变量保存着一片内存的首地址,而该片内存保存着指令机器码,当这片代码被整体移动到内存中的另一个位置后,这时需要将移动后的首地址替换到变量中。
上述情况还可分为第二种情况,假设有一个变量保存着一片内存的首地址,而该片内存保存着指令机器码,当这片内存的实现逻辑被另一片内存替换之后,这时需要更新变量中的内存地址。
第二种 单个指令的重定位
假设代码逻辑中创建了一个Object对象,之后需要对该对象进行若干操作。在汇编层面则需加载该对象,即ldr(load from memory),然后将该对象的值放入寄存器中进行操作,然后写入内存。这里存在一个问题。对象Object只有在运行时才会在内存中创建,那么其加载对象的操作,如ldr,如何提前知道其对象在内存中的位置呢?其实是不知道的,只能先将内存地址的值用一个假设值填写进ldr的操作数,待对象创建之后,将ldr的内存地址操作数进行替换。诸如此类的典型操作还有call,jmp等。比如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.c和b.c通过GCC编译为对象文件a.o和b.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即可获得内存中重定位的地址

最低0.47元/天 解锁文章
86万+

被折叠的 条评论
为什么被折叠?



