【问题标题】:How get output of address sanitizer when emiting SIGINT to halt a loop发出 SIGINT 以停止循环时如何获取地址清理程序的输出
【发布时间】:2018-08-22 09:19:27
【问题描述】:

当我编译这个简单的测试程序时,我从地址清理程序中得到了明显的泄漏报告,但是当我编译相同的程序但有一个无限循环并打破它发出 SIGINT 时,我没有得到任何输出。

检查 asm 输出,malloc 没有被优化掉(如果可能的话)

这是地址清理程序的预期行为吗?我在其他开发中没有遇到这个问题。

工作示例:

#include <stdlib.h>
int main(void)
{
    char *a = malloc(1024);
    return 1;
}

不工作(用 SIGINT 杀死):

#include <stdlib.h>
int main(void)
{
    char *a = malloc(1024);
    for(;;);
    return 1;
}

编译:gcc test.c -o test -fsanitize=address

我在一个完整的程序中遇到了这个问题,但我把它简化为这个最小的例子。

【问题讨论】:

  • 实际上,这是预期的行为。原因是,否则你肯定会得到一堆误报:它们越多,你的应用程序就越大。一个例子为什么:假设在你的代码中,在循环之后你有一个free(a)。所以从技术上讲,你的代码没有泄漏。但是,在对应用程序进行 SIGINT 时,您会收到无效的泄漏报告,因为在您终止应用程序时内存尚未释放。基本上,SIGINT/SIGTERM 意味着,除了您明确绑定到信号的那些之外,不会运行任何清理/析构函数。

标签: c address-sanitizer


【解决方案1】:

我尝试了很多方法,通过exit()abort() 调用,这很有效:

#include <stdlib.h>
#include <signal.h>
#include <stdio.h>
#include <setjmp.h>

jmp_buf jmpbuf;
void handler (int signum) {    
        printf("handler %d \n", signum);
        // we jump from here to main()
        // and then call return
        longjmp(jmpbuf, 1);
}

int main(int argc, char *argv[])
{
    if (setjmp(jmpbuf)) { 
        // we are in signal context here
        return 2;
    }
    signal(SIGINT, handler);
    signal(SIGTERM, handler);

    char *a = malloc(1024);
    while (argc - 1);
    return 1;
}

结果:

> gcc file.c -fsanitize=address && timeout 1 ./a.out arg
handler 15 

=================================================================
==12970==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1024 byte(s) in 1 object(s) allocated from:
    #0 0x7f4798c9bd99 in __interceptor_malloc /build/gcc/src/gcc/libsanitizer/asan/asan_malloc_linux.cc:86
    #1 0x5569e64e0acd in main (/tmp/a.out+0xacd)
    #2 0x7f479881206a in __libc_start_main (/usr/lib/libc.so.6+0x2306a)

SUMMARY: AddressSanitizer: 1024 byte(s) leaked in 1 allocation(s).

我猜地址清理函数是在 main 返回之后执行的。

【讨论】:

  • 我猜你是对的,但我很惊讶在调用 exit() 时泄漏消毒剂没有运行;我接受您的回答,因为它提供了一种使用 setjmp 强制输出的方法。
  • @Bungow 很可能它不起作用,因为调用退出不会触发堆栈展开
【解决方案2】:

负责打印该错误输出的代码称为析构函数 (fini) 过程。由于您的程序在没有调用任何进程析构函数的情况下终止(由于 SIGINT),因此您不会收到任何错误打印输出。

【讨论】:

  • 你能告诉我一些相关的信息吗?为什么如果我在我的代码中安装一个 SIGINT 处理程序,只调用exit(),则不会调用“析构函数”?抱歉,我在这里有点迷路,因为使用其他库,比如 SDL,我可以用 SIGINT 杀死他的主循环,然后我得到一份报告。谢谢。
  • 我不确定。 ASAN 代码清楚地显示它使用 atexit 运行,但我无法弄清楚注册何时发生。也许 Kamil Cuk 是对的,它在 main 完成后注册处理程序......这似乎很奇怪
  • @Neowizard 注册发生在__lsan_init 函数中(参见 [lsan.cpp](github.com/llvm-mirror/compiler-rt/blob/master/lib/lsan/…)),该函数在 preinit 阶段(即在所有 C/C++ 构造函数之前)注册。
【解决方案3】:

__lsan_do_leak_check() 可以帮助您避免在@KamilCuk 的回答中使用 longjmp:

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sanitizer/lsan_interface.h>

void handler (int signum) {
   __lsan_do_leak_check();
}

int main(int argc, char *argv[])
{
   signal(SIGINT, handler);
   char *a = malloc(1024);
   a=0;
   printf("lost pointer\n");
   for(;;);
   return 1;
}

演示:

clang test.c -fsanitize=address -fno-omit-frame-pointer -g -O0 -o test && ./test
lost pointer
  C-c C-c
=================================================================
==29365==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1024 byte(s) in 1 object(s) allocated from:
    #0 0x4c9ca3 in malloc (/home/bjacob/test+0x4c9ca3)
    #1 0x4f9187 in main /home/bjacob/test.c:13:14
    #2 0x7fbc9898409a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2409a)

SUMMARY: AddressSanitizer: 1024 byte(s) leaked in 1 allocation(s).

请注意,我添加了 a=0 以创建泄漏。

我还添加了一个 printf。如果没有该 printf,则不会打印泄漏错误。尽管我使用了 -O0 编译器选项,但我怀疑编译器优化了变量“a”的使用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-08-24
    • 2020-01-14
    • 1970-01-01
    • 2012-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多