1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//! HotSpot's self-description: the VMStructs table.
//!
//! Every HotSpot ships an exported table describing the layout of its own C++
//! structures — the same table `jhsdb` uses to debug a live JVM from outside.
//! Because we read it at runtime instead of hardcoding offsets, this works
//! across JDK builds and versions without a single magic number.
//!
//! jvm.dll exports:
//! gHotSpotVMStructs -> VMStructEntry[] (type, field, offset)
//! gHotSpotVMTypes -> VMTypeEntry[] (type, super, size)
//! gHotSpotVMIntConstants -> VMIntConstantEntry[]
//! gHotSpotVMLongConstants -> VMLongConstantEntry[]
//! gHotSpotVM*EntryArrayStride -> element size of each array
//! gHotSpotVM*Entry*Offset -> field offsets inside each element
use std::collections::HashMap;
use crate::win::{Module, Process, Result};
#[derive(Debug, Clone)]
pub struct FieldEntry {
pub type_name: String,
pub field_name: String,
/// C++ type of the field, verbatim ("Symbol*", "jint", "OopHandle", ...).
pub type_string: String,
pub is_static: bool,
/// Byte offset within the struct (instance fields).
pub offset: u64,
/// Absolute address in the target (static fields).
pub address: u64,
}
#[derive(Debug, Clone)]
pub struct TypeInfo {
pub name: String,
pub superclass: String,
pub is_oop_type: bool,
pub is_integer: bool,
pub is_unsigned: bool,
pub size: u64,
}
pub struct VmDb {
pub types: HashMap<String, TypeInfo>,
pub fields: HashMap<(String, String), FieldEntry>,
pub int_consts: HashMap<String, i32>,
pub long_consts: HashMap<String, u64>,
}
/// Read one exported `uint64_t` global.
fn export_u64(p: &Process, exports: &HashMap<String, u64>, name: &str) -> Result<u64> {
let a = *exports
.get(name)
.ok_or_else(|| format!("jvm.dll does not export {name}"))?;
p.u64(a)
}
impl VmDb {
pub fn load(p: &Process, jvm: &Module) -> Result<Self> {
let exports: HashMap<String, u64> = p.exports(jvm)?.into_iter().collect();
// --- struct entries -------------------------------------------------
let structs = export_u64(p, &exports, "gHotSpotVMStructs")?;
let s_stride = export_u64(p, &exports, "gHotSpotVMStructEntryArrayStride")?;
let o_type = export_u64(p, &exports, "gHotSpotVMStructEntryTypeNameOffset")?;
let o_field = export_u64(p, &exports, "gHotSpotVMStructEntryFieldNameOffset")?;
let o_tstr = export_u64(p, &exports, "gHotSpotVMStructEntryTypeStringOffset")?;
let o_static = export_u64(p, &exports, "gHotSpotVMStructEntryIsStaticOffset")?;
let o_off = export_u64(p, &exports, "gHotSpotVMStructEntryOffsetOffset")?;
let o_addr = export_u64(p, &exports, "gHotSpotVMStructEntryAddressOffset")?;
let mut fields = HashMap::new();
let mut e = structs;
loop {
// The table terminates on the first entry with a null fieldName.
let field_ptr = p.u64(e + o_field)?;
if field_ptr == 0 {
break;
}
let type_name = p.cstring(p.u64(e + o_type)?, 256)?;
let field_name = p.cstring(field_ptr, 256)?;
let type_string_ptr = p.u64(e + o_tstr)?;
let type_string = if type_string_ptr == 0 {
String::new()
} else {
p.cstring(type_string_ptr, 256)?
};
let is_static = p.i32(e + o_static)? != 0;
let entry = FieldEntry {
type_string,
is_static,
offset: if is_static { 0 } else { p.u64(e + o_off)? },
address: if is_static { p.u64(e + o_addr)? } else { 0 },
type_name: type_name.clone(),
field_name: field_name.clone(),
};
fields.insert((type_name, field_name), entry);
e += s_stride;
}
// --- type entries ---------------------------------------------------
let types_base = export_u64(p, &exports, "gHotSpotVMTypes")?;
let t_stride = export_u64(p, &exports, "gHotSpotVMTypeEntryArrayStride")?;
let t_name = export_u64(p, &exports, "gHotSpotVMTypeEntryTypeNameOffset")?;
let t_super = export_u64(p, &exports, "gHotSpotVMTypeEntrySuperclassNameOffset")?;
let t_oop = export_u64(p, &exports, "gHotSpotVMTypeEntryIsOopTypeOffset")?;
let t_int = export_u64(p, &exports, "gHotSpotVMTypeEntryIsIntegerTypeOffset")?;
let t_uns = export_u64(p, &exports, "gHotSpotVMTypeEntryIsUnsignedOffset")?;
let t_size = export_u64(p, &exports, "gHotSpotVMTypeEntrySizeOffset")?;
let mut types = HashMap::new();
let mut e = types_base;
loop {
let name_ptr = p.u64(e + t_name)?;
if name_ptr == 0 {
break;
}
let name = p.cstring(name_ptr, 256)?;
let sup_ptr = p.u64(e + t_super)?;
let superclass = if sup_ptr == 0 {
String::new()
} else {
p.cstring(sup_ptr, 256)?
};
types.insert(
name.clone(),
TypeInfo {
name,
superclass,
is_oop_type: p.i32(e + t_oop)? != 0,
is_integer: p.i32(e + t_int)? != 0,
is_unsigned: p.i32(e + t_uns)? != 0,
size: p.u64(e + t_size)?,
},
);
e += t_stride;
}
// --- constants ------------------------------------------------------
let mut int_consts = HashMap::new();
let ic = export_u64(p, &exports, "gHotSpotVMIntConstants")?;
let ic_stride = export_u64(p, &exports, "gHotSpotVMIntConstantEntryArrayStride")?;
let ic_name = export_u64(p, &exports, "gHotSpotVMIntConstantEntryNameOffset")?;
let ic_val = export_u64(p, &exports, "gHotSpotVMIntConstantEntryValueOffset")?;
let mut e = ic;
loop {
let np = p.u64(e + ic_name)?;
if np == 0 {
break;
}
int_consts.insert(p.cstring(np, 256)?, p.i32(e + ic_val)?);
e += ic_stride;
}
let mut long_consts = HashMap::new();
let lc = export_u64(p, &exports, "gHotSpotVMLongConstants")?;
let lc_stride = export_u64(p, &exports, "gHotSpotVMLongConstantEntryArrayStride")?;
let lc_name = export_u64(p, &exports, "gHotSpotVMLongConstantEntryNameOffset")?;
let lc_val = export_u64(p, &exports, "gHotSpotVMLongConstantEntryValueOffset")?;
let mut e = lc;
loop {
let np = p.u64(e + lc_name)?;
if np == 0 {
break;
}
long_consts.insert(p.cstring(np, 256)?, p.u64(e + lc_val)?);
e += lc_stride;
}
Ok(Self { types, fields, int_consts, long_consts })
}
pub fn field(&self, ty: &str, field: &str) -> Result<&FieldEntry> {
self.fields
.get(&(ty.to_string(), field.to_string()))
.ok_or_else(|| format!("VMStructs has no {ty}::{field}"))
}
/// Byte offset of an instance field within its struct.
pub fn off(&self, ty: &str, field: &str) -> Result<u64> {
let f = self.field(ty, field)?;
if f.is_static {
return Err(format!("{ty}::{field} is static, not an offset"));
}
Ok(f.offset)
}
/// Absolute address of a static field in the target process.
pub fn static_addr(&self, ty: &str, field: &str) -> Result<u64> {
let f = self.field(ty, field)?;
if !f.is_static {
return Err(format!("{ty}::{field} is an instance field"));
}
Ok(f.address)
}
pub fn type_size(&self, ty: &str) -> Result<u64> {
self.types
.get(ty)
.map(|t| t.size)
.ok_or_else(|| format!("VMStructs has no type {ty}"))
}
pub fn int_const(&self, name: &str) -> Result<i32> {
self.int_consts
.get(name)
.copied()
.ok_or_else(|| format!("no int constant {name}"))
}
/// All fields declared on one type, sorted by offset.
pub fn fields_of(&self, ty: &str) -> Vec<&FieldEntry> {
let mut v: Vec<&FieldEntry> = self.fields.values().filter(|f| f.type_name == ty).collect();
v.sort_by_key(|f| (f.is_static, f.offset));
v
}
}