如何在macOS下使用C代码从运行中的PCSX2进程读取导出符号EEmem
如何在macOS下使用C代码从运行中的PCSX2进程读取导出符号EEmem
我来帮你搞定这个问题!你已经成功通过task_for_pid()获取到了PCSX2进程的任务端口,接下来只需要完成符号地址查找和内存读取这两个核心步骤就行。下面是完整的实现方案,包括代码补充和关键逻辑说明:
前提说明
首先要确认你已经满足两个必要条件:
- 关闭了macOS的SIP(系统完整性保护),因为
task_for_pid()需要更高的权限 - 以
sudo身份运行你的C程序,否则会权限不足
完整实现代码
我已经在你提供的基础代码上补充了ReadSymbol函数以及相关的辅助逻辑,用来定位并读取EEmem的值:
#include <sys/sysctl.h> #include <sys/proc.h> #include <mach/mach.h> #include <mach/vm_map.h> #include <mach/task.h> #include <mach/mach_vm.h> #include <dlfcn.h> #include <mach-o/dyld.h> #include <mach-o/loader.h> #include <mach-o/nlist.h> #include <string.h> #include <stdlib.h> #include <stdio.h> int GetProcessID(char* processname) { int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; size_t size; // 获取进程列表所需内存大小 if (sysctl(mib, 4, NULL, &size, NULL, 0) < 0) { return 0; } // 分配内存存储进程列表 struct kinfo_proc *procs = malloc(size); if (!procs) { return 0; } // 获取实际进程列表 if (sysctl(mib, 4, procs, &size, NULL, 0) < 0) { free(procs); return 0; } int num_procs = size / sizeof(struct kinfo_proc); int result = 0; // 根据进程名查找PID for (int i = 0; i < num_procs; i++) { if (strcmp(processname, procs[i].kp_proc.p_comm) == 0) { result = procs[i].kp_proc.p_pid; break; } } free(procs); return result; } // 辅助函数:读取目标进程的内存数据 static kern_return_t read_process_memory(task_t task, mach_vm_address_t addr, void *buf, mach_vm_size_t size) { mach_vm_size_t out_size; return mach_vm_read_overwrite(task, addr, size, (mach_vm_address_t)buf, &out_size); } // 查找目标进程中主可执行文件的基地址和Mach-O头信息 static kern_return_t get_main_executable_info(task_t task, mach_vm_address_t *base_addr, struct mach_header_64 *mh) { vm_address_t addr = 0; vm_size_t size = 0; kern_return_t kr; // 获取目标进程的dyld信息,找到主可执行文件的加载地址 kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&addr, &size); if (kr != KERN_SUCCESS) { return kr; } struct task_dyld_info dyld_info; kr = read_process_memory(task, (mach_vm_address_t)addr, &dyld_info, sizeof(dyld_info)); if (kr != KERN_SUCCESS) { return kr; } *base_addr = dyld_info.all_image_info_addr; struct dyld_all_image_infos all_image_info; kr = read_process_memory(task, *base_addr, &all_image_info, sizeof(all_image_info)); if (kr != KERN_SUCCESS) { return kr; } // 获取第一个镜像(主可执行文件)的信息 struct dyld_image_info image_info; kr = read_process_memory(task, (mach_vm_address_t)all_image_info.infoArray, &image_info, sizeof(image_info)); if (kr != KERN_SUCCESS) { return kr; } *base_addr = (mach_vm_address_t)image_info.imageLoadAddress; // 读取Mach-O头 return read_process_memory(task, *base_addr, mh, sizeof(struct mach_header_64)); } // 核心函数:查找目标进程中的符号地址并读取其值 mach_vm_address_t ReadSymbol(task_t task, const char *symbol_name) { struct mach_header_64 mh; mach_vm_address_t base_addr = 0; kern_return_t kr; // 获取主可执行文件的基地址和Mach-O头 kr = get_main_executable_info(task, &base_addr, &mh); if (kr != KERN_SUCCESS) { printf("Failed to get main executable info: %s\n", mach_error_string(kr)); return 0; } // 遍历Mach-O的加载命令,找到符号表和字符串表 mach_vm_address_t cmd_addr = base_addr + sizeof(struct mach_header_64); struct load_command cmd; struct symtab_command sym_cmd; mach_vm_address_t symtab_addr = 0; mach_vm_address_t strtab_addr = 0; uint32_t nsyms = 0; for (uint32_t i = 0; i < mh.ncmds; i++) { kr = read_process_memory(task, cmd_addr, &cmd, sizeof(cmd)); if (kr != KERN_SUCCESS) { printf("Failed to read load command: %s\n", mach_error_string(kr)); return 0; } if (cmd.cmd == LC_SYMTAB) { kr = read_process_memory(task, cmd_addr, &sym_cmd, sizeof(sym_cmd)); if (kr != KERN_SUCCESS) { printf("Failed to read symtab command: %s\n", mach_error_string(kr)); return 0; } symtab_addr = base_addr + sym_cmd.symoff; strtab_addr = base_addr + sym_cmd.stroff; nsyms = sym_cmd.nsyms; break; } cmd_addr += cmd.cmdsize; } if (symtab_addr == 0) { printf("Could not find symtab in target process\n"); return 0; } // 遍历符号表,查找目标符号 struct nlist_64 sym; for (uint32_t i = 0; i < nsyms; i++) { mach_vm_address_t sym_entry_addr = symtab_addr + (i * sizeof(struct nlist_64)); kr = read_process_memory(task, sym_entry_addr, &sym, sizeof(sym)); if (kr != KERN_SUCCESS) { printf("Failed to read symbol entry: %s\n", mach_error_string(kr)); return 0; } // 只查找全局符号(N_EXT)且名称匹配的符号 if ((sym.n_type & N_EXT) && sym.n_un.n_strx != 0) { char sym_name[256]; kr = read_process_memory(task, strtab_addr + sym.n_un.n_strx, sym_name, sizeof(sym_name)); if (kr != KERN_SUCCESS) { continue; } if (strcmp(sym_name, symbol_name) == 0) { // 符号的实际地址是基地址加上偏移 mach_vm_address_t symbol_addr = base_addr + sym.n_value; // 读取符号的值(EEmem是指针,这里读取它指向的内存地址) mach_vm_address_t eemem_value; kr = read_process_memory(task, symbol_addr, &eemem_value, sizeof(eemem_value)); if (kr != KERN_SUCCESS) { printf("Failed to read EEmem value: %s\n", mach_error_string(kr)); return 0; } return eemem_value; } } } printf("Could not find symbol %s\n", symbol_name); return 0; } int main() { pid_t ProcessID = 0; task_t ProcessHandle = 0; // macOS task port ProcessID = GetProcessID("pcsx2-qt"); if (ProcessID == 0){ ProcessID = GetProcessID("PCSX2"); } if (ProcessID == 0){ ProcessID = GetProcessID("pcsx2"); } if (ProcessID == 0){ printf("cannot find PCSX2 process\n"); return 0; } // 获取进程的task端口 // 需要关闭SIP并以sudo运行 kern_return_t kr = task_for_pid(mach_task_self(), ProcessID, &ProcessHandle); if (kr != KERN_SUCCESS) { printf("Failed to get task_for_pid %d: %s\n", ProcessID, mach_error_string(kr)); return 0; } printf("Success get task_for_pid %d\n", ProcessID); // 读取EEmem的值 mach_vm_address_t EEmemValue = ReadSymbol(ProcessHandle, "EEmem"); if (EEmemValue != 0) { printf("EEmem value: 0x%llx\n", EEmemValue); } return 0; }
关键逻辑说明
- 获取主可执行文件信息:通过
task_dyld_info获取目标进程中主可执行文件的加载基地址,这是计算符号实际地址的基础。 - 解析Mach-O符号表:遍历Mach-O的加载命令找到符号表(
LC_SYMTAB),然后逐个检查符号表中的条目,匹配EEmem的名称。 - 读取符号值:找到符号的偏移后,加上基地址得到实际内存地址,再通过
mach_vm_read_overwrite读取该地址存储的值(因为EEmem是一个指针,所以这里读取的是它指向的内存地址,和你用lldb打印的结果一致)。
测试验证
编译运行这个程序(记得用sudo),如果一切正常,你会得到和lldb中一样的输出:
Success get task_for_pid 68807 EEmem value: 0x0000000140000000
内容来源于stack exchange
相关产品推荐
相关产品推荐

