Xcode的诊断配置

Xcode的scheme配置中,有个Diagnostics栏目,其中有很多内存相关的配置,用来帮助开发者定位内存问题。常用的功能有这些。
Address Sanitizer
Guard Malloc
Zombie Objects
Malloc Scribble
Maloc Guard Edges
Malloc Stack Logging

mac OS和iOS系统分配的内存是16字节对齐的,即使申请了1个字节,也会分配16个字节的内存空间(malloc_size函数)。所以即使我们访问内存地址越界了,大多数情况下都不会出现问题,但这存在隐藏的风险。

Address Sanitizer

地址消毒剂,做法是将分配的内存空间周围标记为中毒状态,当访问内存周围的地址时,Xcode就提示报错。当勾选后,分配的内存空间就是实际申请的空间。
具体原理就是通过用一个字节代表8个字节的中毒情况,
0表示都没有中毒;k表示前k个没有中毒,后8-k为中毒;负数表示全部中毒。这一个字节的数据存储在特定的内存区域,该区域和分配的内存通过一定的规则进行映射。
举个例子,当访问一处内存地址时,会先计算该内存地址对于分配的内存空间的偏移,然后通过映射关系,获取该地址在特定内存区域的地址,获取里面的值,如果偏移>=地址里面的值,即表明访问了中毒的区域,就提示报错。

  char *ptr = (char*)malloc(12);//分配了12字节的内存空间
  ptr[12] = 0;//访问第13个字节的地址,越界访问

报错信息如下

==34112==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000051c at pc 0x000100003f2a bp 0x7ffeefbff3a0 sp 0x7ffeefbff398
WRITE of size 1 at 0x60200000051c thread T0
    #0 0x100003f29 in main main.m:18
    #1 0x7fff2063cf5c in start+0x0 (libdyld.dylib:x86_64+0x15f5c)

0x60200000051c is located 0 bytes to the right of 12-byte region [0x602000000510,0x60200000051c)
allocated by thread T0 here:
    #0 0x1001703a0 in wrap_malloc+0xa0 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x443a0)
    #1 0x100003ed0 in main main.m:17
    #2 0x7fff2063cf5c in start+0x0 (libdyld.dylib:x86_64+0x15f5c)

SUMMARY: AddressSanitizer: heap-buffer-overflow main.m:18 in main
Shadow bytes around the buggy address:
  0x1c0400000050: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 00 00
  0x1c0400000060: fa fa 00 00 fa fa 00 fa fa fa 00 00 fa fa 00 00
  0x1c0400000070: fa fa 00 00 fa fa 07 fa fa fa 00 07 fa fa 00 fa
  0x1c0400000080: fa fa 00 fa fa fa fd fa fa fa fd fd fa fa 00 00
  0x1c0400000090: fa fa 00 00 fa fa 00 00 fa fa 00 00 fa fa 00 00
=>0x1c04000000a0: fa fa 00[04]fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000000b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000000c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000000d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000000e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c04000000f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

注意箭头所指的的0x1c04000000a0,第四个字节即0x1c04000000a43处的值是04,即前4个没有中毒,后4个是中毒的。而04前面的00表示该处对应的8个字节都是没有中毒的。4+8合起来就是我们所申请的12个字节的内存空间。

Guard Malloc

前面提到,实际分配的内存大小是16字节对齐的,这可能导致实际分配的要比申请的大。
在这种情况下,就能访问到超过申请大小的内存地址,甚至能访问超过分配大小的内存地址,严格来说访问超过申请大小的内存地址即存在着风险。Guard Malloc就是为了解决这种问题的,但即使开启了Guard Malloc,分配的内存大小还是16字节对齐的,依旧可能导致实际分配的要比申请的大,但访问超过分配大小的内存地址时就会error。提示越界访问,其原理就是将分配内存区域的周边内存地址属性置为不可读不可写。当访问时就会产生权限错误

    char *a = malloc(sizeof(char)*10);// 申请10个字节,但因为16字节对齐,实际分配了16个字节
    mach_port_t task = mach_task_self();
    vm_size_t size = 0;
    int *p = a + 16;// 获取越界的地址
    vm_address_t address = (vm_address_t)p;
    memory_object_name_t object;
    
    mach_msg_type_number_t count = VM_REGION_BASIC_INFO_COUNT_64;
    vm_region_basic_info_data_64_t info;
    kern_return_t info_ret = vm_region_64(task, &address, &size, VM_REGION_BASIC_INFO_64, (vm_region_info_64_t)&info, &count, &object);
	//    info.protection为0,即不可读,不可写
Malloc Scribble

将分配的内存填充0xAA(通过malloc分配的,calloc不会填充),释放后的填充0x55,以便及时发现内存使用问题。其实现就是替换了malloc和free函数。做了memset处理。
可以使用malloc_zone_from_ptr函数获取分配的内存的malloc、calloc、free函数。
在正常情况下,calloc函数是libsystem_malloc.dylib库的default_zone_calloc,malloc是libsystem_malloc.dylib库的default_zone_malloc,free函数是
libsystem_malloc.dylib库的default_zone_free。
而勾选Malloc Scribble后,

Malloc Stack Logging

malloc_history 16214(pid) 0x6000009743b0(address)
察看该地址的分配过程

其他文档
https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm
https://www.mikeash.com/pyblog/friday-qa-2015-07-03-address-sanitizer.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值