mirror of
https://github.com/touchHLE/touchHLE.git
synced 2026-01-31 01:25:24 +01:00
Allocate guest memory using platform specific calls
Creating Mem using the host allocator allows use to explicitly drop the previous allocation and freely allocate a new instance without running into crashes on Android. As such this commit removes the previous refurbishing mechanism, in favor of the simpler drop -> reallocate. Change-Id: Ic9fd3b790640bb0525c2ae425c26cc9fb922a843
This commit is contained in:
committed by
acieslewicz
parent
261f0b7bf9
commit
e8e8711290
21
Cargo.lock
generated
21
Cargo.lock
generated
@@ -385,9 +385,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.137"
|
||||
version = "0.2.175"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
@@ -860,6 +860,7 @@ dependencies = [
|
||||
"encoding_rs",
|
||||
"gl_generator",
|
||||
"hound",
|
||||
"libc",
|
||||
"mach_object",
|
||||
"md5",
|
||||
"plist",
|
||||
@@ -874,6 +875,7 @@ dependencies = [
|
||||
"touchHLE_pvrt_decompress_wrapper",
|
||||
"touchHLE_stb_image_wrapper",
|
||||
"touchHLE_version",
|
||||
"windows-sys",
|
||||
"yore",
|
||||
"zip",
|
||||
]
|
||||
@@ -985,6 +987,21 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.61.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "xml-rs"
|
||||
version = "0.8.4"
|
||||
|
||||
@@ -68,6 +68,8 @@ touchHLE_openal_soft_wrapper = { path = "src/audio/openal_soft_wrapper" }
|
||||
touchHLE_pvrt_decompress_wrapper = { path = "src/image/pvrt_decompress_wrapper" }
|
||||
touchHLE_stb_image_wrapper = { path = "src/image/stb_image_wrapper" }
|
||||
touchHLE_version = { path = "src/version" }
|
||||
windows-sys = { version = "0.61.0", features = ["Win32_System_Memory"] }
|
||||
libc = "0.2.175"
|
||||
|
||||
[build-dependencies]
|
||||
cargo-license = "0.5.1"
|
||||
|
||||
@@ -49,10 +49,7 @@ struct AppInfo {
|
||||
icon_ui_image: Option<id>,
|
||||
}
|
||||
|
||||
pub fn app_picker(
|
||||
options: Options,
|
||||
option_args: &mut Vec<String>,
|
||||
) -> Result<(PathBuf, Environment), String> {
|
||||
pub fn app_picker(options: Options, option_args: &mut Vec<String>) -> Result<PathBuf, String> {
|
||||
let apps_dir = paths::user_data_base_path().join(paths::APPS_DIR);
|
||||
|
||||
let apps: Result<Vec<AppInfo>, String> = if !apps_dir.is_dir() {
|
||||
@@ -274,7 +271,7 @@ fn show_app_picker_gui(
|
||||
options: Options,
|
||||
option_args: &mut Vec<String>,
|
||||
mut apps: Result<Vec<AppInfo>, String>,
|
||||
) -> Result<(PathBuf, Environment), String> {
|
||||
) -> Result<PathBuf, String> {
|
||||
let icon = {
|
||||
let bytes: &[u8] = match super::branding() {
|
||||
"" => include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/res/icon.png")),
|
||||
@@ -732,8 +729,7 @@ fn show_app_picker_gui(
|
||||
option_args.push("--allow-network-access".to_string());
|
||||
}
|
||||
|
||||
// Return the environment so some parts of it can be salvaged.
|
||||
Ok((app_path, environment))
|
||||
Ok(app_path)
|
||||
}
|
||||
|
||||
const ICON_SIZE: CGSize = CGSize {
|
||||
|
||||
@@ -234,33 +234,14 @@ fn generate_binary_load_order(graph: &[BinaryDependencyNode]) -> Result<Vec<usiz
|
||||
|
||||
impl Environment {
|
||||
/// Loads the binary and sets up the emulator.
|
||||
///
|
||||
/// `env_for_salvage` can be used to provide an existing environment (in
|
||||
/// practice, the app picker's, created with [Environment::new_without_app])
|
||||
/// that is to be destroyed. Certain components may be salvaged from the
|
||||
/// old environment, but their states will be reset, so the result should be
|
||||
/// "like new". This option exists because touchHLE on Android would crash
|
||||
/// when allocating a second [mem::Mem] instance.
|
||||
pub fn new(
|
||||
bundle: bundle::Bundle,
|
||||
fs: fs::Fs,
|
||||
mut options: options::Options,
|
||||
app_args: Vec<String>,
|
||||
env_for_salvage: Option<Environment>,
|
||||
) -> Result<Environment, String> {
|
||||
let startup_time = Instant::now();
|
||||
|
||||
// Extract things to salvage from the old environment, and then drop it.
|
||||
// This needs to be done before creating a new window, because SDL2 only
|
||||
// allows one window at once.
|
||||
let mem_for_salvage = if let Some(env_for_salvage) = env_for_salvage {
|
||||
let Environment { mem, .. } = env_for_salvage;
|
||||
// Everything other than the memory is now dropped.
|
||||
Some(mem)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Certain apps need to launch in a non-portrait orientation, and this
|
||||
// should be handled before creating the window because handling of
|
||||
// window rotation after-the-fact is somewhat glitchy.
|
||||
@@ -336,11 +317,7 @@ impl Environment {
|
||||
))
|
||||
};
|
||||
|
||||
let mut mem = if let Some(mem) = mem_for_salvage {
|
||||
mem::Mem::refurbish(mem)
|
||||
} else {
|
||||
mem::Mem::new()
|
||||
};
|
||||
let mut mem = mem::Mem::new();
|
||||
|
||||
let is_spore = bundle.bundle_identifier().starts_with("com.ea.spore");
|
||||
// We always reset this flag depending on which game is launched.
|
||||
|
||||
15
src/lib.rs
15
src/lib.rs
@@ -182,8 +182,8 @@ pub fn main<T: Iterator<Item = String>>(mut args: T) -> Result<(), String> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let (bundle_path, env_for_salvage) = if let Some(bundle_path) = bundle_path {
|
||||
(bundle_path, None)
|
||||
let bundle_path = if let Some(bundle_path) = bundle_path {
|
||||
bundle_path
|
||||
} else {
|
||||
let mut options = options::Options::default();
|
||||
// Apply command-line options only (no app-specific options apply)
|
||||
@@ -199,8 +199,7 @@ pub fn main<T: Iterator<Item = String>>(mut args: T) -> Result<(), String> {
|
||||
echo!(
|
||||
"No app specified, opening app picker. Use the --help flag to see command-line usage."
|
||||
);
|
||||
let (bundle_path, env_for_salvage) = app_picker::app_picker(options, &mut option_args)?;
|
||||
(bundle_path, Some(env_for_salvage))
|
||||
app_picker::app_picker(options, &mut option_args)?
|
||||
};
|
||||
|
||||
// When PowerShell does tab-completion on a directory, for some reason it
|
||||
@@ -311,13 +310,7 @@ pub fn main<T: Iterator<Item = String>>(mut args: T) -> Result<(), String> {
|
||||
}
|
||||
|
||||
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
Environment::new(
|
||||
bundle,
|
||||
fs,
|
||||
options.clone(),
|
||||
app_args.unwrap_or_default(),
|
||||
env_for_salvage,
|
||||
)
|
||||
Environment::new(bundle, fs, options.clone(), app_args.unwrap_or_default())
|
||||
}));
|
||||
let mut env = match res {
|
||||
Ok(ret) => match ret {
|
||||
|
||||
42
src/mem.rs
42
src/mem.rs
@@ -17,6 +17,7 @@
|
||||
use crate::libc::wchar::wchar_t;
|
||||
|
||||
mod allocator;
|
||||
mod host;
|
||||
|
||||
/// Equivalent of `usize` for guest memory.
|
||||
pub type GuestUSize = u32;
|
||||
@@ -252,9 +253,8 @@ pub struct Mem {
|
||||
|
||||
impl Drop for Mem {
|
||||
fn drop(&mut self) {
|
||||
let layout = std::alloc::Layout::new::<Bytes>();
|
||||
unsafe {
|
||||
std::alloc::dealloc(self.bytes as *mut _, layout);
|
||||
crate::mem::host::free_memory(self.bytes.cast(), std::mem::size_of::<Bytes>()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -275,13 +275,17 @@ impl Mem {
|
||||
|
||||
/// Create a fresh instance of guest memory.
|
||||
pub fn new() -> Mem {
|
||||
// This will hopefully get the host OS to lazily allocate the memory.
|
||||
let layout = std::alloc::Layout::new::<Bytes>();
|
||||
// TODO: align memory to guest page size
|
||||
// Right now, if aligned, will cause OOM on low end Android devices.
|
||||
// See relevant [Github's issue](https://github.com/touchHLE/touchHLE/issues/498)
|
||||
let bytes = unsafe { std::alloc::alloc_zeroed(layout) as *mut Bytes };
|
||||
assert!(!bytes.is_null());
|
||||
let size = std::mem::size_of::<Bytes>();
|
||||
|
||||
let ptr = unsafe { crate::mem::host::allocate_memory(size).unwrap() };
|
||||
|
||||
assert_eq!(
|
||||
ptr as usize & PAGE_SIZE_ALIGN_MASK as usize,
|
||||
0,
|
||||
"Failed to align host memory with guest memory"
|
||||
);
|
||||
|
||||
let bytes = ptr as *mut Bytes;
|
||||
|
||||
let allocator = allocator::Allocator::new();
|
||||
|
||||
@@ -293,26 +297,6 @@ impl Mem {
|
||||
}
|
||||
}
|
||||
|
||||
/// Take an existing instance of [Mem], but free and zero all the
|
||||
/// allocations so it's "like new".
|
||||
///
|
||||
/// Note that, since there is no protection against writing outside an
|
||||
/// allocation, there might be stray bytes preserved in the result.
|
||||
pub fn refurbish(mut mem: Mem) -> Mem {
|
||||
let Mem {
|
||||
bytes: _,
|
||||
null_segment_size: _,
|
||||
ref mut allocator,
|
||||
..
|
||||
} = mem;
|
||||
let used_chunks = allocator.reset_and_drain_used_chunks();
|
||||
for allocator::Chunk { base, size } in used_chunks {
|
||||
mem.bytes_mut()[base as usize..][..size.get() as usize].fill(0);
|
||||
}
|
||||
mem.null_segment_size = 0;
|
||||
mem
|
||||
}
|
||||
|
||||
/// Sets up the null segment of the given size. There's no reason to call
|
||||
/// this outside of binary loading, and it won't be respected even if you
|
||||
/// do. The size must not have been set already, and must be page aligned.
|
||||
|
||||
@@ -128,12 +128,6 @@ mod collections {
|
||||
Some(self.remove_with_base(chunk.base).unwrap())
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn drain(self) -> impl Iterator<Item = Chunk> {
|
||||
self.chunks
|
||||
.into_iter()
|
||||
.map(|(base, size)| Chunk { base, size })
|
||||
}
|
||||
#[inline(always)]
|
||||
pub fn get_size_with_base(&self, base: VAddr) -> Option<NonZeroU32> {
|
||||
self.chunks.get(&base).copied()
|
||||
}
|
||||
@@ -383,10 +377,4 @@ impl Allocator {
|
||||
|
||||
freed.size.get()
|
||||
}
|
||||
|
||||
pub(super) fn reset_and_drain_used_chunks(&mut self) -> impl Iterator<Item = Chunk> {
|
||||
let chunks = std::mem::take(&mut self.used_chunks);
|
||||
*self = Allocator::new();
|
||||
chunks.drain()
|
||||
}
|
||||
}
|
||||
|
||||
100
src/mem/host.rs
Normal file
100
src/mem/host.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
//! Cross-platform memory management wrappers using the host's system calls.
|
||||
|
||||
/// Cross-platform memory allocation using the host's system calls.
|
||||
/// Returns an address aligned to the guest's 4KB page boundaries.
|
||||
///
|
||||
/// - The function returns a raw pointer to allocated memory.
|
||||
/// The caller is responsible for managing that memory.
|
||||
/// - The returned pointer must be freed using the corresponding
|
||||
/// [`free_memory`] call.
|
||||
#[cfg(windows)]
|
||||
pub(super) unsafe fn allocate_memory(size: usize) -> std::io::Result<*mut core::ffi::c_void> {
|
||||
use windows_sys::Win32::System::Memory::{
|
||||
VirtualAlloc, MEM_COMMIT, MEM_RESERVE, PAGE_READWRITE,
|
||||
};
|
||||
|
||||
let ptr = unsafe {
|
||||
VirtualAlloc(
|
||||
std::ptr::null(),
|
||||
size,
|
||||
MEM_RESERVE | MEM_COMMIT,
|
||||
PAGE_READWRITE,
|
||||
)
|
||||
};
|
||||
|
||||
if ptr.is_null() {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(super) unsafe fn allocate_memory(size: usize) -> std::io::Result<*mut core::ffi::c_void> {
|
||||
use libc::{mmap, sysconf, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE, _SC_PAGESIZE};
|
||||
|
||||
const PAGE_SIZE: usize = crate::mem::PAGE_SIZE as usize;
|
||||
let host_page_size = unsafe { sysconf(_SC_PAGESIZE) as usize };
|
||||
|
||||
assert!(
|
||||
host_page_size >= PAGE_SIZE,
|
||||
"Hosts with smaller than 4KiB pages are not supported."
|
||||
);
|
||||
|
||||
let ptr = unsafe {
|
||||
mmap(
|
||||
std::ptr::null_mut(),
|
||||
size,
|
||||
PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS,
|
||||
-1,
|
||||
0,
|
||||
)
|
||||
};
|
||||
|
||||
if ptr == libc::MAP_FAILED {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
Ok(ptr)
|
||||
}
|
||||
|
||||
/// Cross-platform memory free using the host's system calls.
|
||||
///
|
||||
/// # Safety
|
||||
/// - The address and size should match parameters and result of the
|
||||
/// [`allocate_memory`] call.
|
||||
#[cfg(windows)]
|
||||
pub(super) unsafe fn free_memory(
|
||||
address: *mut core::ffi::c_void,
|
||||
_size: usize,
|
||||
) -> std::io::Result<()> {
|
||||
use windows_sys::Win32::System::Memory::{VirtualFree, MEM_RELEASE};
|
||||
|
||||
let res = unsafe { VirtualFree(address, 0, MEM_RELEASE) };
|
||||
|
||||
if res == 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(super) unsafe fn free_memory(
|
||||
address: *mut core::ffi::c_void,
|
||||
size: usize,
|
||||
) -> std::io::Result<()> {
|
||||
use libc::munmap;
|
||||
|
||||
let res = unsafe { munmap(address, size) };
|
||||
|
||||
if res == -1 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user