Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions src/game_engine/unity/mono/attachment_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
//! Attachment to an older Linux player whose engine is in the executable,
//! rather than a separate `UnityPlayer.so` module.

use super::{profiles, Module};
use crate::runtime::mock::{poll_once, with_modules};
use std::{task::Poll, vec, vec::Vec};

const RUNTIME: u64 = 0x7F00_0000_0000;
#[cfg(feature = "alloc")]
const EXECUTABLE: u64 = 0x7F00_0010_0000;

// The real Unity 5.6.7f1 Linux legacy-Mono fixture ships this libmono.so ID
// and no UnityPlayer.so. The process memory below is synthetic: it provides
// a GNU note, the assembly export and its getter, but does not run Unity.
const BUILD_ID: [u8; 20] = [
0xC1, 0xA5, 0x3E, 0xA7, 0x10, 0x9A, 0x2D, 0xA5, 0x82, 0x20, 0xAB, 0x30, 0xF4, 0xCA, 0xB7, 0xC8,
0xCE, 0x8F, 0x38, 0x13,
];

fn put(image: &mut [u8], at: usize, bytes: &[u8]) {
image[at..at + bytes.len()].copy_from_slice(bytes);
}

fn runtime() -> Vec<u8> {
let mut image = vec![0; 0x2000];
put(&mut image, 0, b"\x7fELF");
image[4..7].copy_from_slice(&[2, 1, 1]);
put(&mut image, 0x10, &3_u16.to_le_bytes());
put(&mut image, 0x12, &0x3E_u16.to_le_bytes());
put(&mut image, 0x20, &0x40_u64.to_le_bytes());
put(&mut image, 0x36, &56_u16.to_le_bytes());
put(&mut image, 0x38, &3_u16.to_le_bytes());
put(&mut image, 0x40, &1_u32.to_le_bytes()); // PT_LOAD
put(&mut image, 0x60, &0x2000_u64.to_le_bytes());
put(&mut image, 0x68, &0x2000_u64.to_le_bytes());
put(&mut image, 0x78, &4_u32.to_le_bytes()); // PT_NOTE
put(&mut image, 0x88, &0x200_u64.to_le_bytes());
put(&mut image, 0x98, &0x24_u64.to_le_bytes());
put(&mut image, 0xA0, &0x24_u64.to_le_bytes());
put(&mut image, 0xB0, &2_u32.to_le_bytes()); // PT_DYNAMIC
put(&mut image, 0xC0, &0x300_u64.to_le_bytes());
put(&mut image, 0xD0, &0x30_u64.to_le_bytes());
put(&mut image, 0xD8, &0x30_u64.to_le_bytes());
put(&mut image, 0x200, &4_u32.to_le_bytes());
put(&mut image, 0x204, &20_u32.to_le_bytes());
put(&mut image, 0x208, &3_u32.to_le_bytes());
put(&mut image, 0x20C, b"GNU\0");
put(&mut image, 0x210, &BUILD_ID);
for (slot, (tag, value)) in [(6_u64, RUNTIME + 0x400), (5, RUNTIME + 0x500), (10, 0x80)]
.into_iter()
.enumerate()
{
put(&mut image, 0x300 + slot * 16, &tag.to_le_bytes());
put(&mut image, 0x308 + slot * 16, &value.to_le_bytes());
}
put(&mut image, 0x418, &1_u32.to_le_bytes()); // second Elf64_Sym name
put(&mut image, 0x420, &0x800_u64.to_le_bytes()); // symbol value
put(&mut image, 0x428, &0x100_u64.to_le_bytes()); // symbol size
put(&mut image, 0x430, &0x80_u32.to_le_bytes()); // terminate symbol walk
put(&mut image, 0x501, b"mono_assembly_foreach\0");
put(&mut image, 0x800, &[0x48, 0x8B, 0x3D]);
put(&mut image, 0x803, &0x1F9_i32.to_le_bytes()); // global at 0xA00
image
}

#[test]
fn an_explicit_profile_needs_neither_a_player_nor_alloc() {
let runtime = runtime();
with_modules(
&[(RUNTIME, &runtime)],
&[("libmono.so", RUNTIME, runtime.len() as u64)],
|process| {
let profile = profiles::UNITY_5_6_0F1_LINUX_MONO_X86_64;
let attached = Module::attach(process, profile).unwrap();
assert_eq!(attached.profile, profile);
let Poll::Ready(attached) = poll_once(Module::wait_attach(process, profile)) else {
panic!("the explicit profile should attach immediately");
};
assert_eq!(attached.profile, profile);
},
);
}

#[cfg(feature = "alloc")]
#[test]
fn an_unknown_legacy_runtime_uses_the_executables_version() {
let runtime = runtime();
let mut executable = vec![0; 0x2000];
let version = b"\x005.6.7f1\0";
put(&mut executable, 0x100, version);
with_modules(
&[(EXECUTABLE, &executable), (RUNTIME, &runtime)],
&[
("fixture", EXECUTABLE, executable.len() as u64),
("libmono.so", RUNTIME, runtime.len() as u64),
],
|process| {
// Ensure this goes through version detection, not a known-ID match.
assert!(super::linux_builds::find(&BUILD_ID).is_none());
assert_eq!(
Module::unity_version(process, super::BinaryFormat::ELF),
Some((5, 6, 7, 0))
);
let profile = profiles::UNITY_5_6_0F1_LINUX_MONO_X86_64;
let attached = Module::attach_auto_detect(process).unwrap();
assert_eq!(attached.profile, profile);
let Poll::Ready(attached) = poll_once(Module::wait_attach_auto_detect(process)) else {
panic!("the executable's version should allow automatic attachment");
};
assert_eq!(attached.profile, profile);
},
);
}
183 changes: 167 additions & 16 deletions src/game_engine/unity/mono/linux_builds.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ const fn id<const N: usize>(written: &str) -> [u8; N] {
}

pub(super) const BUILDS: &[Build] = &[
// 5.6.7f1, libmono.so
// 5.6.0f1, libmono.so
Build {
build_id: &id::<20>("c1a53ea7109a2da58220ab30f4cab7c8ce8f3813"),
unity: (5, 6, 7, 3267),
build_id: &id::<20>("83bc621e6ccf123cafbc71c0894207071968f29e"),
Comment thread
CryZe marked this conversation as resolved.
unity: (5, 6, 0, 23754),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::Mono,
Expand Down Expand Up @@ -124,10 +124,59 @@ pub(super) const BUILDS: &[Build] = &[
v_table: MonoVTableOffsets { vtable: 0x48 },
},
},
// 2017.3.0f3, libmonobdwgc-2.0.so
// 2017.1.0f1, libmonobdwgc-2.0.so
Build {
build_id: &id::<20>("3cf4e78c4ef6b520fb777738602ac56c15f8ff0f"),
unity: (2017, 3, 0, 63597),
build_id: &id::<20>("488594493d54ab4951119e260d8d039d0fb94b53"),
unity: (2017, 1, 0, 32737),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::MonoBdwgc,
assembly: AssemblyOffsets {
aname: Some(0x10),
image: 0x60,
},
image: ImageOffsets {
assembly_name: None,
class_cache: 0x4A8,
},
hash_table: HashTableOffsets {
size: 0x18,
table: 0x20,
},
class: ClassOffsets {
class_kind: None,
instance_size: Some(0x1C),
parent: 0x28,
nested_in: Some(0x30),
name: 0x40,
namespace: 0x48,
vtable_size: 0x54,
fields: 0xA8,
runtime_info: 0xF0,
field_count: 0x8C,
next_class_cache: 0xF8,
},
generic: GenericOffsets {
generic_class: Some(0xD8),
container_class: Some(0x0),
},
type_words: TypeOffsets {
data: Some(0x0),
kind: Some(0xA),
},
field: FieldInfoOffsets {
type_: Some(0x0),
name: 0x8,
offset: 0x18,
stride: 0x20,
},
v_table: MonoVTableOffsets { vtable: 0x40 },
},
},
// 2017.2.0f1, libmonobdwgc-2.0.so
Build {
build_id: &id::<20>("a276fd2ef4fd4fe54270bf3a1d8cc294f32c8684"),
unity: (2017, 2, 0, 58714),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::MonoBdwgc,
Expand Down Expand Up @@ -173,10 +222,112 @@ pub(super) const BUILDS: &[Build] = &[
v_table: MonoVTableOffsets { vtable: 0x40 },
},
},
// 2017.4.40f1, libmono.so
// 2017.4.6f1, libmono.so
Build {
build_id: &id::<20>("806bb9c8b6bbb0b9aa964d423694bdd5d29aee4c"),
unity: (2017, 4, 6, 20272),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::Mono,
assembly: AssemblyOffsets {
aname: Some(0x10),
image: 0x58,
},
image: ImageOffsets {
assembly_name: None,
class_cache: 0x3D0,
},
hash_table: HashTableOffsets {
size: 0x18,
table: 0x20,
},
class: ClassOffsets {
class_kind: None,
instance_size: Some(0x1C),
parent: 0x28,
nested_in: Some(0x30),
name: 0x48,
namespace: 0x50,
vtable_size: 0x18,
fields: 0xA8,
runtime_info: 0xF8,
field_count: 0x94,
next_class_cache: 0x100,
},
generic: GenericOffsets {
generic_class: Some(0xD8),
container_class: Some(0x0),
},
type_words: TypeOffsets {
data: Some(0x0),
kind: Some(0xA),
},
field: FieldInfoOffsets {
type_: Some(0x0),
name: 0x8,
offset: 0x18,
stride: 0x20,
},
// Nothing reads this: these builds keep their statics in the slot
// `vtable_size` points at.
v_table: MonoVTableOffsets { vtable: 0x48 },
},
},
// 2018.1.0f1, libmono.so
Build {
build_id: &id::<20>("8297a83ffb74d772baf82ebd05fa663e0bf9feda"),
unity: (2018, 1, 0, 30795),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::Mono,
assembly: AssemblyOffsets {
aname: Some(0x10),
image: 0x58,
},
image: ImageOffsets {
assembly_name: None,
class_cache: 0x3D0,
},
hash_table: HashTableOffsets {
size: 0x18,
table: 0x20,
},
class: ClassOffsets {
class_kind: None,
instance_size: Some(0x1C),
parent: 0x28,
nested_in: Some(0x30),
name: 0x40,
namespace: 0x48,
vtable_size: 0x18,
fields: 0xA0,
runtime_info: 0xF0,
field_count: 0x8C,
next_class_cache: 0xF8,
},
generic: GenericOffsets {
generic_class: Some(0xD0),
container_class: Some(0x0),
},
type_words: TypeOffsets {
data: Some(0x0),
kind: Some(0xA),
},
field: FieldInfoOffsets {
type_: Some(0x0),
name: 0x8,
offset: 0x18,
stride: 0x20,
},
// Nothing reads this: these builds keep their statics in the slot
// `vtable_size` points at.
v_table: MonoVTableOffsets { vtable: 0x48 },
},
},
// 2018.1.7f1, libmono.so
Build {
build_id: &id::<20>("93b5b95d7a6112b3f7d53b40e79a663cbcd62b14"),
unity: (2017, 4, 40, 5126),
build_id: &id::<20>("488c9cc9d9ed712a104549ba008c4dfc8504886e"),
unity: (2018, 1, 7, 46210),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::Mono,
Expand Down Expand Up @@ -224,10 +375,10 @@ pub(super) const BUILDS: &[Build] = &[
v_table: MonoVTableOffsets { vtable: 0x48 },
},
},
// 2021.2.20f1, UnityPlayer.so
// 2021.2.0f1, UnityPlayer.so
Build {
build_id: &id::<8>("8efc4cbe377f5467"),
unity: (2021, 2, 20, 62729),
build_id: &id::<8>("18a01e8f0aa6d238"),
unity: (2021, 2, 0, 61932),
profile: Profile {
pointer_size: PointerSize::Bit64,
library: Library::MonoBdwgc,
Expand Down Expand Up @@ -280,15 +431,15 @@ mod tests {
use super::{find, id, BUILDS};
use crate::PointerSize;

// The build ID of 2017.3's Mono library, as it is stored.
// The build ID of 2017.2's Mono library, as it is stored.
const STORED: [u8; 20] = [
0x3C, 0xF4, 0xE7, 0x8C, 0x4E, 0xF6, 0xB5, 0x20, 0xFB, 0x77, 0x77, 0x38, 0x60, 0x2A, 0xC5,
0x6C, 0x15, 0xF8, 0xFF, 0x0F,
0xA2, 0x76, 0xFD, 0x2E, 0xF4, 0xFD, 0x4F, 0xE5, 0x42, 0x70, 0xBF, 0x3A, 0x1D, 0x8C, 0xC2,
0x94, 0xF3, 0x2C, 0x86, 0x84,
];

#[test]
fn parses_written_build_ids_into_stored_bytes() {
assert_eq!(id::<20>("3cf4e78c4ef6b520fb777738602ac56c15f8ff0f"), STORED);
assert_eq!(id::<20>("a276fd2ef4fd4fe54270bf3a1d8cc294f32c8684"), STORED);
}

#[test]
Expand Down
Loading
Loading