是否有一种方法(操作系统调用等)在运行时让 macOS 应用程序检查指针是否有效(用于读取和/或写入),而不会在指针无效时崩溃或导致信号? (指向进程地址空间之外、NULL+1 等)
无论是在 C ((char *)someLongInt) 中,还是在 Swift 中(对于不安全的原始绑定)。
是的,这可以使用
mach_vm_region
调用来实现。请参阅下面的示例代码:
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <stdio.h>
#include <stdbool.h>
bool ptr_is_valid(void *ptr, vm_prot_t needs_access) {
vm_map_t task = mach_task_self();
mach_vm_address_t address = (mach_vm_address_t)ptr;
mach_vm_size_t size = 0;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name;
kern_return_t ret = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &count, &object_name);
if (ret != KERN_SUCCESS) return false;
return ((mach_vm_address_t)ptr) >= address && ((info.protection & needs_access) == needs_access);
}
#define TEST(ptr,acc) printf("ptr_is_valid(%p,access=%d)=%d\n", (void*)(ptr), (acc), ptr_is_valid((void*)(ptr),(acc)))
int main(int argc, char**argv) {
TEST(0,0);
TEST(0,VM_PROT_READ);
TEST(123456789,VM_PROT_READ);
TEST(main,0);
TEST(main,VM_PROT_READ);
TEST(main,VM_PROT_READ|VM_PROT_EXECUTE);
TEST(main,VM_PROT_EXECUTE);
TEST(main,VM_PROT_WRITE);
TEST((void*)(-1),0);
return 0;
}
对于内存地址,如果地址有效,则
mach_vm_region
返回包含内存区域的起始位置、大小和属性;如果地址无效,则返回下一个最高有效地址的信息,否则,如果没有更高的有效地址,则调用失败。因此,通过检查调用是否成功,以及您传递的内存地址是否大于或等于返回的地址,您可以判断该地址是否有效。
此外,人们可能想要读/写/执行该地址,因此检查包含内存区域的内存保护是否允许您这样做也很重要。
bool VmIsBadPtr(void* ptr)
{
vm_map_t task = mach_task_self();
mach_vm_address_t address = (mach_vm_address_t)ptr;
mach_vm_size_t size = 0;
vm_region_basic_info_data_64_t info;
mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
mach_port_t object_name;
kern_return_t ret = mach_vm_region(task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_t)&info, &count, &object_name);
// Check if the mach_vm_region call was successful and the pointer is within a valid memory region.
if (ret != KERN_SUCCESS || (mach_vm_address_t)ptr < address || (mach_vm_address_t)ptr >= address + size)
return true; // Pointer is invalid
// Check if the protection bits indicate no permissions (not readable, writable, or executable).
if ((info.protection & (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)) == 0)
return true; // Pointer is invalid
// Pointer is considered valid
return false;
}
我采用了 Simon 发布的原始代码,并对其进行了一些改进。希望对其他人有用。