如何轻松诊断由于访问未映射的mmap区域而引起的问题?

问题描述 投票:1回答:2

我最近发现了一个段错误,Valgrind或Address Sanitizer都无法提供任何有用的信息。发生这种情况是因为有错误的程序munmap解析了文件,然后尝试访问以前的mmap ped区域。

下面的示例演示了问题:

#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>

int main()
{
    const int fd=open("/tmp/test.txt", O_RDWR);
    if(fd<0) abort();
    const char buf[]="Hello";
    if(write(fd, buf, sizeof buf) != sizeof buf) abort();

    char*const volatile ptr=mmap(NULL,sizeof buf,PROT_READ,MAP_SHARED,fd,0);
    if(!ptr) abort();
    printf("1%c\n", ptr[0]);

    if(close(fd)<0) abort();
    printf("2%c\n", ptr[0]);

    if(munmap(ptr, sizeof buf)<0) abort();
    printf("3%c\n", ptr[0]); // Cause a segfault
}

使用Address Sanitizer,我得到以下输出:

1H
2H
AddressSanitizer:DEADLYSIGNAL
=================================================================
==8503==ERROR: AddressSanitizer: SEGV on unknown address 0x7fe7d0836000 (pc 0x55bda425c055 bp 0x7ffda5887210 sp 0x7ffda5887140 T0)
==8503==The signal is caused by a READ memory access.
    #0 0x55bda425c054 in main /tmp/test/test1.c:22
    #1 0x7fe7cf64fb96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #2 0x55bda425bcd9 in _start (/tmp/test/test1+0xcd9)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV /tmp/test/test1.c:22 in main

这是Valgrind的输出的相关部分:

1H
2H
==8863== Invalid read of size 1
==8863==    at 0x108940: main (test1.c:22)
==8863==  Address 0x4029000 is not stack'd, malloc'd or (recently) free'd
==8863== 
==8863== 
==8863== Process terminating with default action of signal 11 (SIGSEGV)
==8863==  Access not within mapped region at address 0x4029000
==8863==    at 0x108940: main (test1.c:22)

与在malloc之后访问free的区域的情况进行比较。测试程序:

#include <stdio.h>
#include <string.h>
#include <malloc.h>

int main()
{
    const char buf[]="Hello";
    char*const volatile ptr=malloc(sizeof buf);
    if(!ptr)
    {
        fprintf(stderr, "malloc failed");
        return 1;
    }
    memcpy(ptr,buf,sizeof buf);
    printf("1%c\n", ptr[0]);
    free(ptr);
    printf("2%c\n", ptr[0]); // Cause a segfault
}

带有地址清理器的输出:

1H
=================================================================
==7057==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000010 at pc 0x55b8f96b5003 bp 0x7ffff5179b70 sp 0x7ffff5179b60
READ of size 1 at 0x602000000010 thread T0
    #0 0x55b8f96b5002 in main /tmp/test/test1.c:17
    #1 0x7f4298fd8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #2 0x55b8f96b4c49 in _start (/tmp/test/test1+0xc49)

0x602000000010 is located 0 bytes inside of 6-byte region [0x602000000010,0x602000000016)
freed by thread T0 here:
    #0 0x7f42994b3b4f in free (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10bb4f)
    #1 0x55b8f96b4fca in main /tmp/test/test1.c:16
    #2 0x7f4298fd8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

previously allocated by thread T0 here:
    #0 0x7f42994b3f48 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.5+0x10bf48)
    #1 0x55b8f96b4e25 in main /tmp/test/test1.c:8
    #2 0x7f4298fd8b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)

使用Valgrind输出:

1H
==6888== Invalid read of size 1
==6888==    at 0x108845: main (test1.c:17)
==6888==  Address 0x522d040 is 0 bytes inside a block of size 6 free'd
==6888==    at 0x4C30D3B: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6888==    by 0x108840: main (test1.c:16)
==6888==  Block was alloc'd at
==6888==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==6888==    by 0x1087D2: main (test1.c:8)

我的问题:有什么方法可以使Valgrind或Sanitizer或其他与Linux兼容的工具输出对访问munmap ped区域的上下文(例如,曾经是mmap的区域)的诊断有用。 ped和munmap ped),类似于上面访问后free的给定输出?

linux debugging valgrind mmap address-sanitizer
2个回答
0
投票

至少在Asan的情况下,您很不走运-mmap区域不会被跟踪(因为大多数用户不需要此区域。


0
投票

valgrind(我猜想asan也是一样)可以输出“释放后使用”错误因为它维护着“最近释放”的块的列表。此类块在逻辑上被释放,但不会(直接)返回给用于进一步的malloc调用的可用内存。相反,它们被标记为不可寻址。可以使用

调整此“最近释放”的阻止列表的大小
--freelist-vol=<number>          volume of freed blocks queue     [20000000]
--freelist-big-blocks=<number>   releases first blocks with size>= [1000000]

可以对munmap的内存使用类似的技术:而不是对其进行物理映射,可以将其保存在最近的列表中未映射的块,在逻辑上未映射,但标记为不可寻址。

请注意,您可以通过在函数中模拟一个函数my_unmap并不会真正执行取消映射,而是使用客户端请求valgrind将其标记为不可寻址。

© www.soinside.com 2019 - 2024. All rights reserved.