You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

关键逻辑说明

  1. 获取主可执行文件信息:通过task_dyld_info获取目标进程中主可执行文件的加载基地址,这是计算符号实际地址的基础。
  2. 解析Mach-O符号表:遍历Mach-O的加载命令找到符号表(LC_SYMTAB),然后逐个检查符号表中的条目,匹配EEmem的名称。
  3. 读取符号值:找到符号的偏移后,加上基地址得到实际内存地址,再通过mach_vm_read_overwrite读取该地址存储的值(因为EEmem是一个指针,所以这里读取的是它指向的内存地址,和你用lldb打印的结果一致)。

测试验证

编译运行这个程序(记得用sudo),如果一切正常,你会得到和lldb中一样的输出:

Success get task_for_pid 68807
EEmem value: 0x0000000140000000

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 13:18:07