news 2026/7/24 1:12:23

【Rust中级教程】1.8. 内存 Pt.6:通过操作系统扫描地址空间

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【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来调用诸如GetCurrentProcessK32GetProcessMemoryInfoVirtualQueryEx等 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-sys0.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);
  1. let mut mem_counters: PROCESS_MEMORY_COUNTERS = mem::zeroed();
    - 使用mem::zeroed()函数创建并初始化一个PROCESS_MEMORY_COUNTERS结构体,使其所有字段值都为零。
    -PROCESS_MEMORY_COUNTERS是一个 Windows 系统预定义的结构体,用于存储进程的内存统计信息。

  2. let mem_counters_size = mem::size_of::<PROCESS_MEMORY_COUNTERS>() as u32;
    - 通过mem::size_of函数计算PROCESS_MEMORY_COUNTERS结构体的大小(单位为字节)。
    - 该大小会被转换为u32类型,用以作为后续 API 函数调用的参数。

  3. K32GetProcessMemoryInfo(this_proc, &mut mem_counters, mem_counters_size);
    -参数解释:

    • this_proc:当前进程的句柄,表示你需要查询哪个进程的内存数据。
    • &mut mem_countersPROCESS_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();
  1. let MEMINFO_SIZE = mem::size_of::<MEMORY_BASIC_INFORMATION>();
    -作用:计算MEMORY_BASIC_INFORMATION结构体的大小(单位为字节)并存储在MEMINFO_SIZE变量中。
    -原因VirtualQueryEx函数要求提供这个结构体缓冲区的大小,以便正确写入查询结果。

  2. let mut base_addr: PVOID = min_addr;
    -作用:初始化虚拟内存扫描的起始地址,将第一个扫描地址设置为min_addr
    -base_addr:表示当前查询的内存起始地址,会在后续循环中逐步增加,遍历整个虚拟地址空间。

  3. 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;:获取当前进程的id
  • OpenProcess(pid);:打开这个进程

Linux提供了简单的API:process_vm_readv()process-vm_writev(),对应Windows中的ReadProcessMemory()WriteProcessMemory()

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/24 1:12:13

C# WinForm飞机大战游戏开发:从零实现GDI+图形绘制与游戏循环

1. 项目概述与核心思路最近在整理硬盘&#xff0c;翻出来一个十多年前刚学C#时做的飞机大战游戏项目。当时WinForm还是桌面开发的主流&#xff0c;用GDI在窗体上“画”出一个能打飞机的游戏&#xff0c;对初学者来说成就感爆棚。今天重新审视这个项目&#xff0c;发现它麻雀虽小…

作者头像 李华
网站建设 2026/7/24 1:08:55

AI Agent开发指南:从架构设计到实战应用

1. 初识AI Agent&#xff1a;从概念到实践AI Agent&#xff08;人工智能代理&#xff09;正在掀起一场智能革命。作为一名长期关注AI技术发展的从业者&#xff0c;我见证了从简单的聊天机器人到如今能够自主执行复杂任务的智能代理的演进过程。AI Agent与传统AI系统的本质区别在…

作者头像 李华
网站建设 2026/7/24 1:07:20

Java DDD(领域驱动设计)

一句话核心&#xff1a; DDD&#xff08;领域驱动设计&#xff09;不是一套框架&#xff0c;而是一种代码组织哲学。它要求你用代码直接翻译业务语言&#xff0c;而不是把业务逻辑散落在增删改查&#xff08;CRUD&#xff09;里。 对于Java开发者&#xff0c;最经典的DDD范式就…

作者头像 李华
网站建设 2026/7/24 0:18:02

本地大模型部署全攻略:从Ollama到企业级容器化

1. 本地大模型部署方式全景解析在AI技术快速发展的当下&#xff0c;本地部署大模型已成为企业数字化转型和个人开发者探索AI能力的重要选择。与云端API调用相比&#xff0c;本地部署能够提供更高的数据隐私性、更低的长期使用成本以及更灵活的定制空间。目前主流的本地部署方式…

作者头像 李华