如何在macosx内核中获取特定进程的虚拟地址?

时间:2018-01-11 18:45:46

标签: macos kernel virtual-memory kernel-extension

我想知道是否存在用于访问获取虚拟地址的物理地址的现有系统调用/ API? 如果没有,那么就如何实现这个工作有一些方向?

另外,如何获取MMIO的物理地址,这是不可分页的物理内存?

1 个答案:

答案 0 :(得分:2)

答案在于IOMemoryDescriptorIODMACommand个对象。

如果有问题的内存是内核分配的,则应首先通过创建IOBufferMemoryDescriptor来分配。如果这不可能,或者它是在用户空间中分配的缓冲区,则可以使用IOMemoryDescriptor::withAddressRange(address, length, options, task)或其他工厂函数之一包装相关指针。对于withAddressRange,传入的address必须是虚拟的,位于task的地址空间。

您可以通过调用IOMemoryDescriptor函数直接从getPhysicalSegment()获取实际地址范围(仅在prepare() ... complete()次调用之间有效)。但是,通常你会这样做来创建分散 - 收集列表(DMA),为此目的,Apple强烈推荐IODMACommand。您可以使用IODMACommand::withSpecification()创建这些内容。然后使用genIOVMSegments()函数生成分散 - 收集列表。

现代Mac,以及一些旧的PPC G5包含IOMMU(英特尔称之为VT-d),因此传递给PCI / Thunderbolt设备的系统内存地址实际上不是物理的,而是IO映射的。只要您使用"系统映射器" IODMACommand将为您执行此操作(默认值)并将mappingOptions设置为kMapped。如果您正在为 CPU 而非设备准备地址,则需要关闭映射 - 在kIOMemoryMapperNone选项中使用IOMemoryDescriptor。根据您尝试做的具体情况,在这种情况下您可能不需要IODMACommand

注意:集中和重用IODMACommand对象通常是明智之举,而不是释放和重新分配它们。

关于MMIO,我假设您的意思是PCI BAR和类似的 - 对于IOPCIDevice,您可以使用IOMemoryDescriptor和类似函数获取表示内存映射设备范围的getDeviceMemoryWithRegister()

示例:

如果您想要的是某些任务中给定虚拟内存范围的纯CPU空间物理地址,您可以执行以下操作(未经测试作为使用它的完整kext会相当大):

// INPUTS:
mach_vm_address_t virtual_range_start = …; // start address of virtual memory
mach_vm_size_t virtual_range_size_bytes = …; // number of bytes in range
task_t task = …; // Task object of process in which the virtual memory address is mapped
IOOptionBits direction = kIODirectionInOut; // whether the memory will be written or read, or both during the operation
IOOptionBits options =
    kIOMemoryMapperNone  // we want raw physical addresses, not IO-mapped
    | direction;

// Process for getting physical addresses:

IOMemoryDescriptor* md = IOMemoryDescriptor::withAddressRange(
    virtual_range_start, virtual_range_size_bytes, direction, task);
// TODO: check for md == nullptr

// Wire down virtual range to specific physical pages
IOReturn result = md->prepare(direction);
// TODO: do error handling

IOByteCount offset = 0;
while (offset < virtual_range_size_bytes)
{
    IOByteCount segment_len = 0;
    addr64_t phys_addr = md->getPhysicalSegment(offset, &len, kIOMemoryMapperNone);
    // TODO: do something with physical range of segment_len bytes at address phys_addr here

    offset += segment_len;
}

/* Unwire. Call this only once you're done with the physical ranges
 * as the pager can change the physical-virtual mapping outside of
 * prepare…complete blocks. */
md->complete(direction);
md->release();

如上所述,这不适合为设备I / O生成DMA分散 - 收集列表。另请注意,此代码仅对64位内核有效。如果您仍然需要支持古老的32位内核(OS X 10.7及更早版本),则需要小心,因为虚拟和物理地址仍然可以是64位(分别是64位用户进程和PAE),但并非所有内存描述符功能都已设置。有64位安全的变体可用于32位kexts。