【Rust中级教程】1.8. 内存 Pt.6:通过操作系统扫描地址空间
1.8 内存 Pt.6:通过操作系统扫描地址空间
1.8.1. 通过操作系统扫描地址空间(例子)
操作系统提供了接口可让程序发出请求——系统调用(system call)。在Windows里,KERNEL.DLL提供了用于检查和操纵运行进程内存 的功能。
这个例子我们在Windows上进行。为什么以Windows为例呢?
- 函数命名易于理解
- 无需POSIX API的知识
1.8.2. 依赖项
本例使用了 windows-sys 这个 crate,它提供了对 Windows API 的低级绑定。在Cargo.toml中添加如下依赖:
[dependencies]
windows-sys = { version = "0.59.0", features = [
"Win32_Foundation",
"Win32_System_Memory",
"Win32_System_ProcessStatus",
"Win32_System_Threading",
] }
- 这里我们使用
windows-sys来调用诸如GetCurrentProcess、K32GetProcessMemoryInfo和VirtualQueryEx等 Windows API。这些模块受 feature 控制,因此必须启用上面的 features。
1.8.3. 主程序
然后在main.rs把需要的东西引入作用域:
use std::ffi::c_void;
use std::mem;
use windows_sys::Win32::System::Memory::{VirtualQueryEx, MEMORY_BASIC_INFORMATION};
use windows_sys::Win32::System::ProcessStatus::{PROCESS_MEMORY_COUNTERS, K32GetProcessMemoryInfo};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, GetCurrentProcessId};
/// Windows API 中的 `PVOID` / `SIZE_T`。
/// 在 `windows-sys` 0.59+ 中,它们不再作为 `Win32::Foundation` 下的具名别名导出,
/// 而是直接用原始 Rust 类型表示。
type PVOID = *mut c_void;
type SIZE_T = usize;
• PVOID:代表 void* 指针,用于表示不透明内存地址。这里是本地别名,对应 *mut c_void。
• SIZE_T:对应无符号整型,用来表示内存区域大小。这里是本地别名,对应 usize(与 windows-sys 0.59+ 的函数签名一致)。
• MEMORY_BASIC_INFORMATION:系统内定义的结构,用于描述一段内存区域的基本信息。
• PROCESS_MEMORY_COUNTERS:用于记录进程内存使用情况的结构体。
• K32GetProcessMemoryInfo:通过该函数获取当前进程的内存信息。
• GetCurrentProcess与GetCurrentProcessId:分别获取当前进程的句柄与进程 ID。
为了方便Debug格式的输出,我们将Windows API返回的PROCESS_MEMORY_COUNTERS包装在一个自定义的ProcessInfo结构体中:
#[derive(Debug)]
struct ProcessInfo {
cb: u32,
page_fault_count: u32,
peak_working_set_size: usize,
working_set_size: usize,
quota_peak_paged_pool_usage: usize,
quota_paged_pool_usage: usize,
quota_peak_non_paged_pool_usage: usize,
quota_non_paged_pool_usage: usize,
pagefile_usage: usize,
peak_pagefile_usage: usize,
}
cb(u32):- 描述:结构体的大小(以字节为单位)。
-
用途:标识该结构体的大小,用于兼容性。
-
page_fault_count(u32): - 描述:进程自启动以来的页面错误总数。
-
用途:页面错误代表内存访问未命中物理内存而触发的处理过程。它包括软故障(从文件缓存中获取数据)和硬故障(需要从磁盘加载)。
-
peak_working_set_size(usize): - 描述:进程使用的工作集(实际驻留在物理内存中的内存)的峰值大小。
-
用途:用于监控进程的内存使用高峰。
-
working_set_size(usize): - 描述:进程当前使用的工作集大小。
-
用途:展示进程当前占用的物理内存量。
-
quota_peak_paged_pool_usage(usize): - 描述:进程使用的分页池(Paged Pool)配额的峰值大小。
-
用途:分页池是指可以分页到磁盘的内核模式内存。
-
quota_paged_pool_usage(usize) - 描述:进程当前使用的分页池配额大小。
-
用途:用于监控当前使用的可分页内核内存量。
-
quota_peak_non_paged_pool_usage(usize): - 描述:进程使用的非分页池(Non-paged Pool)配额的峰值大小。
-
用途:非分页池是指永久驻留在物理内存中的内核模式内存。
-
quota_non_paged_pool_usage(usize): - 描述:进程当前使用的非分页池配额大小。
-
用途:用于监控当前使用的不可分页内核内存量。
-
pagefile_usage(usize): - 描述:进程当前在页面文件中使用的空间大小。
-
用途:表示进程将数据分页到磁盘上的数量。
-
peak_pagefile_usage(usize) - 描述:进程使用的页面文件空间的峰值大小。
- 用途:用于监控进程的页面文件使用高水位标记。
windows-sys 中的 MEMORY_BASIC_INFORMATION 没有实现 Debug,因此我们也把它的字段包装起来以便打印:
#[derive(Debug)]
struct MemoryBasicInfo {
base_address: *mut c_void,
allocation_base: *mut c_void,
allocation_protect: u32,
region_size: usize,
state: u32,
protect: u32,
type_: u32,
}
获取当前进程句柄与进程ID(要放在unsafe块中):
let this_proc = GetCurrentProcess();
let this_pid = GetCurrentProcessId();
获取进程内存信息:
let mut mem_counters: PROCESS_MEMORY_COUNTERS = mem::zeroed();
let mem_counters_size = mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
K32GetProcessMemoryInfo(this_proc, &mut mem_counters, mem_counters_size);
-
let mut mem_counters: PROCESS_MEMORY_COUNTERS = mem::zeroed();
- 使用mem::zeroed()函数创建并初始化一个PROCESS_MEMORY_COUNTERS结构体,使其所有字段值都为零。
-PROCESS_MEMORY_COUNTERS是一个 Windows 系统预定义的结构体,用于存储进程的内存统计信息。 -
let mem_counters_size = mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
- 通过mem::size_of函数计算PROCESS_MEMORY_COUNTERS结构体的大小(单位为字节)。
- 该大小会被转换为u32类型,用以作为后续 API 函数调用的参数。 -
K32GetProcessMemoryInfo(this_proc, &mut mem_counters, mem_counters_size);
- 参数解释:this_proc:当前进程的句柄,表示你需要查询哪个进程的内存数据。&mut mem_counters:PROCESS_MEMORY_COUNTERS结构体的可变引用,用于接收从 API 返回的内存统计信息。mem_counters_size:传递结构体的大小,以确保 API 函数能够正确读取并填充结构体数据。- 作用:
调用K32GetProcessMemoryInfo函数,将当前进程的内存状态(如页面故障数量、工作集大小、页面文件使用量等信息)填充到mem_counters结构体中。
将内存信息包装成我们自定义的ProcessInfo:
let proc_info = ProcessInfo {
cb: mem_counters.cb,
page_fault_count: mem_counters.PageFaultCount,
peak_working_set_size: mem_counters.PeakWorkingSetSize,
working_set_size: mem_counters.WorkingSetSize,
quota_peak_paged_pool_usage: mem_counters.QuotaPeakPagedPoolUsage,
quota_paged_pool_usage: mem_counters.QuotaPagedPoolUsage,
quota_peak_non_paged_pool_usage: mem_counters.QuotaPeakNonPagedPoolUsage,
quota_non_paged_pool_usage: mem_counters.QuotaNonPagedPoolUsage,
pagefile_usage: mem_counters.PagefileUsage,
peak_pagefile_usage: mem_counters.PeakPagefileUsage,
};
定义扫描地址的起始与结束地址:
let min_addr: PVOID = 0 as PVOID;
设定一个典型的 64 位用户模式地址空间上限:
let max_addr: PVOID = 0x00007FFF_FFFF_FFFF as PVOID;
输出进程信息和地址区间:
println!("{:p} @ {:p}", this_pid as *const (), this_proc as *const ());
println!("{:?}", proc_info);
println!("min: {:p}, max: {:p}", min_addr, max_addr);
初始化VirtualQueryEx所需参数:
let MEMINFO_SIZE = mem::size_of::<MEMORY_BASIC_INFORMATION>();
let mut base_addr: PVOID = min_addr;
let mut mem_info: MEMORY_BASIC_INFORMATION = mem::zeroed();
-
let MEMINFO_SIZE = mem::size_of::<MEMORY_BASIC_INFORMATION>();
- 作用:计算MEMORY_BASIC_INFORMATION结构体的大小(单位为字节)并存储在MEMINFO_SIZE变量中。
- 原因:VirtualQueryEx函数要求提供这个结构体缓冲区的大小,以便正确写入查询结果。 -
let mut base_addr: PVOID = min_addr;
- 作用:初始化虚拟内存扫描的起始地址,将第一个扫描地址设置为min_addr。
-base_addr:表示当前查询的内存起始地址,会在后续循环中逐步增加,遍历整个虚拟地址空间。 -
let mut mem_info: MEMORY_BASIC_INFORMATION = mem::zeroed();
- 作用:使用mem::zeroed()函数创建并初始化一个MEMORY_BASIC_INFORMATION结构体,并将所有字段值置为零。
- 原因:该结构体将用于存储VirtualQueryEx的结果,即当前查询地址对应内存区域的详细信息。
循环调用VirtualQueryEx扫描整个地址空间:
loop {
let rc: SIZE_T = VirtualQueryEx(this_proc, base_addr, &mut mem_info, MEMINFO_SIZE as SIZE_T);
if rc == 0 {
break;
}
// `windows-sys` 中的 `MEMORY_BASIC_INFORMATION` 没有实现 `Debug`,
// 因此包装我们关心的字段再打印。
let printable = MemoryBasicInfo {
base_address: mem_info.BaseAddress,
allocation_base: mem_info.AllocationBase,
allocation_protect: mem_info.AllocationProtect,
region_size: mem_info.RegionSize,
state: mem_info.State,
protect: mem_info.Protect,
type_: mem_info.Type,
};
println!("{:#?}", printable);
// 累加当前区域的大小,得到下一个查询地址
base_addr = ((base_addr as usize) + mem_info.RegionSize) as PVOID;
if (base_addr as usize) >= (max_addr as usize) {
break;
}
}
1.8.4. 完整代码
main.rs:
use std::ffi::c_void;
use std::mem;
use windows_sys::Win32::System::Memory::{VirtualQueryEx, MEMORY_BASIC_INFORMATION};
use windows_sys::Win32::System::ProcessStatus::{PROCESS_MEMORY_COUNTERS, K32GetProcessMemoryInfo};
use windows_sys::Win32::System::Threading::{GetCurrentProcess, GetCurrentProcessId};
/// Windows API 中的 `PVOID` / `SIZE_T`。
/// 在 `windows-sys` 0.59+ 中,它们不再作为 `Win32::Foundation` 下的具名别名导出,
/// 而是直接用原始 Rust 类型表示。
type PVOID = *mut c_void;
type SIZE_T = usize;
/// 为了能够用 Debug 格式输出,我们自己包装了 PROCESS_MEMORY_COUNTERS。
#[derive(Debug)]
struct ProcessInfo {
cb: u32,
page_fault_count: u32,
peak_working_set_size: usize,
working_set_size: usize,
quota_peak_paged_pool_usage: usize,
quota_paged_pool_usage: usize,
quota_peak_non_paged_pool_usage: usize,
quota_non_paged_pool_usage: usize,
pagefile_usage: usize,
peak_pagefile_usage: usize,
}
/// `windows-sys` 中的 `MEMORY_BASIC_INFORMATION` 没有实现 `Debug`。
#[derive(Debug)]
struct MemoryBasicInfo {
base_address: *mut c_void,
allocation_base: *mut c_void,
allocation_protect: u32,
region_size: usize,
state: u32,
protect: u32,
type_: u32,
}
fn main() {
unsafe {
// 获取当前进程句柄与进程ID
let this_proc = GetCurrentProcess();
let this_pid = GetCurrentProcessId();
// 获取进程内存信息
let mut mem_counters: PROCESS_MEMORY_COUNTERS = mem::zeroed();
let mem_counters_size = mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
K32GetProcessMemoryInfo(this_proc, &mut mem_counters, mem_counters_size);
// 将内存信息包装成我们自定义的 ProcessInfo
let proc_info = ProcessInfo {
cb: mem_counters.cb,
page_fault_count: mem_counters.PageFaultCount,
peak_working_set_size: mem_counters.PeakWorkingSetSize,
working_set_size: mem_counters.WorkingSetSize,
quota_peak_paged_pool_usage: mem_counters.QuotaPeakPagedPoolUsage,
quota_paged_pool_usage: mem_counters.QuotaPagedPoolUsage,
quota_peak_non_paged_pool_usage: mem_counters.QuotaPeakNonPagedPoolUsage,
quota_non_paged_pool_usage: mem_counters.QuotaNonPagedPoolUsage,
pagefile_usage: mem_counters.PagefileUsage,
peak_pagefile_usage: mem_counters.PeakPagefileUsage,
};
// 定义扫描地址的起始与结束地址
let min_addr: PVOID = 0 as PVOID;
// 这里设定一个典型的 64 位用户模式地址空间上限
let max_addr: PVOID = 0x00007FFF_FFFF_FFFF as PVOID;
// 输出
println!("{:p} @ {:p}", this_pid as *const (), this_proc as *const ());
println!("{:?}", proc_info);
println!("min: {:p}, max: {:p}", min_addr, max_addr);
// 初始化 VirtualQueryEx 所需参数
let MEMINFO_SIZE = mem::size_of::<MEMORY_BASIC_INFORMATION>();
let mut base_addr: PVOID = min_addr;
let mut mem_info: MEMORY_BASIC_INFORMATION = mem::zeroed();
// 循环调用 VirtualQueryEx 扫描整个地址空间
loop {
let rc: SIZE_T =
VirtualQueryEx(this_proc, base_addr, &mut mem_info, MEMINFO_SIZE as SIZE_T);
if rc == 0 {
break;
}
let printable = MemoryBasicInfo {
base_address: mem_info.BaseAddress,
allocation_base: mem_info.AllocationBase,
allocation_protect: mem_info.AllocationProtect,
region_size: mem_info.RegionSize,
state: mem_info.State,
protect: mem_info.Protect,
type_: mem_info.Type,
};
println!("{:#?}", printable);
// 累加当前区域的大小,得到下一个查询地址
base_addr = ((base_addr as usize) + mem_info.RegionSize) as PVOID;
if (base_addr as usize) >= (max_addr as usize) {
break;
}
}
}
}
Cargo.toml:
[package]
name = "RustStudy"
version = "0.1.0"
edition = "2021"
[dependencies]
windows-sys = { version = "0.59.0", features = [
"Win32_Foundation",
"Win32_System_Memory",
"Win32_System_ProcessStatus",
"Win32_System_Threading",
] }
1.8.5. 读取和写入进程内存的步骤
读取和写入进程内存的逻辑还算简单,伪代码如下:
let pid = some_process_id;
OpenProcess(pid);
loop 地址空间 {
调用VirtualQueryEx()来访问下个内存块
通过调用ReadProcessMemory()来访问内存块
寻找某种特定的模式
使用所需的值调用WriteProcessMemory
}
let pid = some_process_id;:获取当前进程的idOpenProcess(pid);:打开这个进程
Linux提供了简单的API:process_vm_readv()、process-vm_writev(),对应Windows中的ReadProcessMemory()、WriteProcessMemory()
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐



所有评论(0)