【问题标题】:snprintf in signal handler creates segmentation fault if started with valgrind如果使用 valgrind 启动,信号处理程序中的 snprintf 会产生分段错误
【发布时间】:2011-10-09 03:35:40
【问题描述】:

这个非常简单的 C 程序在使用 valgrind 运行时给我一个分段错误。 正常启动时运行良好。 当您向进程发送 USR1 信号时,它会崩溃。

问题似乎是snprintf 处理浮点值格式的方式,因为如果您使用字符串 (%s) 或 int (%d) 格式参数,它可以正常工作。

附:我知道你不应该在信号处理程序中调用任何 printf 系列函数,但为什么它只会在 valgrind 时崩溃。

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

    void sig_usr1(int sig) {
            char buf[128];
            snprintf(buf, sizeof(buf), "%f", 1.0);
    }

    int main(int argc, char **argv) {
            (void) signal(SIGUSR1, sig_usr1);
            while(1);
    }

【问题讨论】:

  • snprintf 在信号处理程序中使用是否安全?
  • @mu 这是一个带有信号安全 posix 函数的列表:securecoding.cert.org/confluence/display/seccode/…
  • @RedX:谢谢:“......在 OpenBSD 中是异步安全的,但“可能不在其他系统上”,包括 snprintf(),......”。这个list 也没有提到snprintf
  • 您有程序的 SEGV(生成vcore 文件)或 valgrind 本身(生成core 文件)吗?请使用 gdb 从 vcore 文件中获取回溯:gdb yourprogram vcore.XXXXX;然后bt.

标签: c segmentation-fault printf valgrind


【解决方案1】:

正如cnicutar 所指出的,valgrind 可能对任何与时序相关的事物产生影响,并且信号处理程序肯定符合条件。

我不认为snprintf 在信号处理程序中使用是安全的,因此它可能只是偶然地在非 valgrind 情况下工作,然后 valgrind 进来,改变了时间,你会得到火焰般的死亡没有valigrind,你是在冒险。

我在这里找到了信号处理程序中安全的函数列表(根据 POSIX.1-2003):

http://linux.die.net/man/2/signal

是的,linux.die.net 手册页有点过时,但这里的列表(感谢 RedX 找到这个):

https://www.securecoding.cert.org/confluence/display/seccode/SIG30-C.+Call+only+asynchronous-safe+functions+within+signal+handlers

也没有提到snprintf,除非在 OpenBSD 的上下文中它说:

...在 OpenBSD 中是异步安全的,但“可能不在其他系统上”,包括 snprintf(),...

因此暗示snprintf 通常在信号处理程序中并不安全。

而且,感谢Nemo,我们有一个权威的函数列表,可以在信号处理程序中安全使用:

http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03

从该链接开始并向下搜索_Exit,您将看到列表;那么你会看到snprintf不在列表中。

另外,我记得在信号处理程序中使用了write(),因为fprintf 对于信号处理程序不安全,但那是很久以前的事了。

我没有相关标准的副本,所以我无法用任何真正权威的东西来支持它,但我想我还是会提到它。

【讨论】:

  • 规范列表位于section 2.4.3 of the POSIX spec。 (向下滚动一点。)
  • @Nemo:谢谢,我很难找到权威参考。我知道snprintf 不在名单上,但“知道”和“证明”是完全不同的。
【解决方案2】:

来自手册:http://www.network-theory.co.uk/docs/valgrind/valgrind_27.htmlhttp://www.network-theory.co.uk/docs/valgrind/valgrind_24.html

Valgrind 的信号模拟不如预期的那么强大。提供了基本的 POSIX 兼容的 sigaction 和 sigprocmask 功能,但可以想象,如果你对信号做了奇怪的事情,事情可能会出现严重错误。解决方法:不要。执行非 POSIX 信号技巧的程序在任何情况下本质上都是不可移植的,因此应尽可能避免使用。

因此,信号处理程序中的 snprintf 不是 POSIX 允许的信号技巧,而 valgrind 有权对您的程序进行砖块处理。

为什么 snprintf 不是信号安全的?

glibc 手册说:http://www.gnu.org/software/hello/manual/libc/Nonreentrancy.html

如果一个函数使用并修改了一个你提供的对象,那么它可能是不可重入的;如果两个调用使用相同的对象,它们可能会相互干扰。

当您使用流进行 I/O 时会出现这种情况。假设信号处理程序使用 fprintf 打印一条消息。假设在传递信号时程序正在使用相同的流进行 fprintf 调用。信号处理程序的消息和程序的数据都可能被破坏,因为这两个调用都在同一个数据结构上操作——流本身。

但是,如果您知道处理程序使用的流不可能在信号可以到达的时候被程序使用,那么您是安全的。如果程序使用其他流也没问题。

您可以说 s*printf* 不在流上,而是在字符串上。但在内部,glibc 的 snprintf 确实适用于特殊流:

ftp://sources.redhat.com/pub/glibc/snapshots/glibc-latest.tar.bz2/glibc-20090518/libio/vsnprintf.c

int
_IO_vsnprintf (string, maxlen, format, args)
{
  _IO_strnfile sf; // <<-- FILE*-like descriptor

glibc 中的%f 输出代码内部也有一个 malloc 调用:

ftp://sources.redhat.com/pub/glibc/snapshots/glibc-latest.tar.bz2/glibc-20090518/stdio-common/printf_fp.c

/* Allocate buffer for output.  We need two more because while rounding
   it is possible that we need two more characters in front of all the
   other output.  If the amount of memory we have to allocate is too
   large use `malloc' instead of `alloca'.  */
size_t wbuffer_to_alloc = (2 + (size_t) chars_needed) * sizeof (wchar_t);
buffer_malloced = ! __libc_use_alloca (chars_needed * 2 * sizeof (wchar_t));
if (__builtin_expect (buffer_malloced, 0))
  {
    wbuffer = (wchar_t *) malloc (wbuffer_to_alloc);
    if (wbuffer == NULL)
      /* Signal an error to the caller.  */
      return -1;
  }
else
  wbuffer = (wchar_t *) alloca (wbuffer_to_alloc);

【讨论】:

    【解决方案3】:

    Valgrind 会稍微改变程序中的时间。

    FAQ获得战利品。

    我的程序正常崩溃,但不在 Valgrind 下,或者反之 反之。发生了什么事?

    程序在 Valgrind 下运行时,其环境略 与本机运行时不同。 大多数时候这没有任何区别,但它可以, 特别是如果您的程序有问题。

    【讨论】:

    • 但是为什么改变时间会导致 segv?
    【解决方案4】:

    这是一个 valgrind 错误。它使用不是 ABI 要求的 16 字节对齐的堆栈调用您的信号处理程序。在 x86_64 上,浮点参数在 XMM 寄存器中传递,这些寄存器只能存储在 16 字节对齐的地址中。您可以通过编译为 32 位 (gcc -m32) 来解决此问题。

    【讨论】:

    • 我知道这是旧的——但是你知道是否有错误报告的链接吗?
    • @villapx:这已在 2012 年修复。对于 Linux,它是:bugs.kde.org/show_bug.cgi?id=280114 对于 OS X,它是作为一部分修复的:bugs.kde.org/show_bug.cgi?id=254646
    • 有趣——对于我来说,OP 的确切代码仍然失败,因为 Valgrind 3.8.1(Linux 2.6.32、x86_64、GCC 4.4.7)上的段错误
    • @villapx:第一个带有 Linux 修复(错误 280114)的版本是 3.9.0。 OS X 修复(错误 254646)在 3.8.0 中。当前版本是 3.11.0。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多