[FFmpeg-devel] [PATCH] lavu: add a feature to test memory allocation failure.

Nicolas George george at nsup.org
Sun Dec 29 19:11:05 CET 2013


Currently only works on GNU due to the use of glibc-specific
backtrace utilities.

Signed-off-by: Nicolas George <george at nsup.org>
---
 configure               |   7 +++
 libavutil/Makefile      |   1 +
 libavutil/malloc_fail.c | 122 ++++++++++++++++++++++++++++++++++++++++++++++++
 libavutil/malloc_fail.h |  61 ++++++++++++++++++++++++
 libavutil/mem.c         |  11 +++++
 tools/mallocfail        | 114 ++++++++++++++++++++++++++++++++++++++++++++
 6 files changed, 316 insertions(+)
 create mode 100644 libavutil/malloc_fail.c
 create mode 100644 libavutil/malloc_fail.h
 create mode 100755 tools/mallocfail

diff --git a/configure b/configure
index 1fec30f..fca52b5 100755
--- a/configure
+++ b/configure
@@ -344,6 +344,7 @@ Developer options (useful when working on FFmpeg itself):
   --assert-level=level     0(default), 1 or 2, amount of assertion testing,
                            2 causes a slowdown at runtime.
   --enable-memory-poisoning fill heap uninitialized allocated space with arbitrary data
+  --enable-malloc-fail     allow to cause memory allocation failures
   --valgrind=VALGRIND      run "make fate" tests through valgrind to detect memory
                            leaks and errors, using the specified valgrind binary.
                            Cannot be combined with --target-exec
@@ -1362,6 +1363,7 @@ CONFIG_LIST="
     lsp
     lzo
     mdct
+    malloc_fail
     memalign_hack
     memory_poisoning
     network
@@ -4672,6 +4674,11 @@ check_optflags $optflags
 check_optflags -fno-math-errno
 check_optflags -fno-signed-zeros
 
+if enabled malloc_fail; then
+  check_func backtrace_symbols_fd ||
+  die "malloc-fail requires backtrace functions (GNU)"
+fi
+
 enabled ftrapv && check_cflags -ftrapv
 
 check_cc -mno-red-zone <<EOF && noredzone_flags="-mno-red-zone"
diff --git a/libavutil/Makefile b/libavutil/Makefile
index 89708fc..55b2de9 100644
--- a/libavutil/Makefile
+++ b/libavutil/Makefile
@@ -120,6 +120,7 @@ OBJS = adler32.o                                                        \
        xtea.o                                                           \
 
 OBJS-$(CONFIG_LZO)                      += lzo.o
+OBJS-$(CONFIG_MALLOC_FAIL)              += malloc_fail.o
 OBJS-$(CONFIG_OPENCL)                   += opencl.o opencl_internal.o
 
 OBJS += $(COMPAT_OBJS:%=../compat/%)
diff --git a/libavutil/malloc_fail.c b/libavutil/malloc_fail.c
new file mode 100644
index 0000000..0ebbfc1
--- /dev/null
+++ b/libavutil/malloc_fail.c
@@ -0,0 +1,122 @@
+/*
+ * Copyright (c) 2013 Nicolas George
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <execinfo.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include "avstring.h"
+#include "malloc_fail.h"
+
+static volatile int ctl_fd = -1;
+
+static int get_ctl_fd(void)
+{
+    unsigned i;
+    static const char sign[] = "!!!!! mallocfail INIT\n";
+    char buf[sizeof(sign) - 1];
+    const char *fd_text;
+
+    if (ctl_fd != -1)
+        return ctl_fd >= 0;
+    for (i = 0; environ[i]; i++) {
+        if (av_strstart(environ[i], "MALLOCFAIL_CTL_FD=", &fd_text)) {
+            ctl_fd = strtol(fd_text, NULL, 0);
+            break;
+        }
+    }
+    if (ctl_fd < 0) {
+        ctl_fd = -2;
+        return 0;
+    }
+    if (read(ctl_fd, buf, sizeof(buf)) != sizeof(buf) ||
+        memcmp(buf, sign, sizeof(buf)))
+        abort();
+    return 1;
+}
+
+static void ctl_lock(void)
+{
+    struct flock lock = { .l_type = F_WRLCK, .l_whence = SEEK_SET };
+    if (fcntl(ctl_fd, F_SETLK, &lock) < 0)
+        abort();
+}
+
+static void ctl_unlock(void)
+{
+    struct flock lock = { .l_type = F_UNLCK, .l_whence = SEEK_SET };
+    if (fcntl(ctl_fd, F_SETLK, &lock) < 0)
+        abort();
+}
+
+static void ctl_write(const void *buf)
+{
+    size_t size = strlen(buf);
+    ssize_t r;
+
+    while (size > 0) {
+        if ((r = write(ctl_fd, buf, size)) < 0)
+            abort();
+        size -= r;
+        buf = (char *)buf + r;
+    }
+}
+
+int ff_malloc_fail_query(const char *function, size_t size)
+{
+    void *bt[64];
+    int bt_size;
+    char buf[128];
+    const size_t ans_size = 3;
+
+    if (!get_ctl_fd())
+        return 1;
+    ctl_lock();
+
+    snprintf(buf, sizeof(buf), "function %s %zd\n", function, size);
+    ctl_write(buf);
+    bt_size = backtrace(bt, sizeof(bt) / sizeof(*bt));
+    backtrace_symbols_fd(bt, bt_size, ctl_fd);
+
+    ctl_write("!!!!! mallocfail ASK\n");
+    if (read(ctl_fd, buf, ans_size) != ans_size)
+        abort();
+
+    ctl_unlock();
+    if (!memcmp(buf, "OK\n", ans_size))
+        return 1;
+    if (!memcmp(buf, "KO\n", ans_size))
+        return 0;
+    abort();
+}
+
+void ff_malloc_fail_tag(const char *tag)
+{
+    char buf[128];
+
+    if (!get_ctl_fd())
+        return;
+    ctl_lock();
+    snprintf(buf, sizeof(buf), "tag %s\n", tag);
+    ctl_write(buf);
+    ctl_unlock();
+}
diff --git a/libavutil/malloc_fail.h b/libavutil/malloc_fail.h
new file mode 100644
index 0000000..dcd090d
--- /dev/null
+++ b/libavutil/malloc_fail.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2013 Nicolas George
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#ifndef AVUTIL_MALLOC_FAIL_H
+#define AVUTIL_MALLOC_FAIL_H
+
+/**
+ * Query the malloc-fail debug system.
+ *
+ * Send a message on the malloc-fail socket with the function name,
+ * allocation size and current backtrace, and wait for an answer.
+ *
+ * The intended use of this function is this:
+ *
+ * If libavutil was built with --enable-malloc-fail and the
+ * MALLOCFAIL_CTL_FD environment variable is set, it is supposed to hold the
+ * number of a file descriptor connected to a control server. All
+ * av_malloc() and av_realloc() calls will cause a message to be sent on the
+ * socket. If the answer to the message is "OK", then  the function call
+ * will continue normally; if it is "KO", the function will return as if the
+ * memory allocation did fail.
+ *
+ * If MALLOCFAIL_CTL_FD is not set, the function calls continue normally
+ * with a tiny overhead. If malloc-fail was not enabled at build time,
+ * nothing happens.
+ *
+ * The tools/mallocfail script can be used to establish the socket and
+ * interact with the malloc-fail mechanism.
+ *
+ * @return  1 for OK, 0 for KO
+ */
+int ff_malloc_fail_query(const char *function, size_t size);
+
+/**
+ * Send a tag on the malloc-fail socket.
+ *
+ * This function is only meant to be used during debugging.
+ *
+ * Adding a tag can simplify the task of the control server, because stack
+ * backtraces are not easy to read.
+ */
+void ff_malloc_fail_tag(const char *tag);
+
+#endif /* AVUTIL_MALLOC_FAIL_H */
diff --git a/libavutil/mem.c b/libavutil/mem.c
index 10b0137..82e9616 100644
--- a/libavutil/mem.c
+++ b/libavutil/mem.c
@@ -41,6 +41,7 @@
 #include "common.h"
 #include "intreadwrite.h"
 #include "mem.h"
+#include "malloc_fail.h"
 
 #ifdef MALLOC_PREFIX
 
@@ -82,6 +83,11 @@ void *av_malloc(size_t size)
     if (size > (max_alloc_size - 32))
         return NULL;
 
+#if CONFIG_MALLOC_FAIL
+    if (!ff_malloc_fail_query("av_malloc", size))
+        return NULL;
+#endif
+
 #if CONFIG_MEMALIGN_HACK
     ptr = malloc(size + ALIGN);
     if (!ptr)
@@ -149,6 +155,11 @@ void *av_realloc(void *ptr, size_t size)
     if (size > (max_alloc_size - 32))
         return NULL;
 
+#if CONFIG_MALLOC_FAIL
+    if (!ff_malloc_fail_query("av_realloc", size))
+        return NULL;
+#endif
+
 #if CONFIG_MEMALIGN_HACK
     //FIXME this isn't aligned correctly, though it probably isn't needed
     if (!ptr)
diff --git a/tools/mallocfail b/tools/mallocfail
new file mode 100755
index 0000000..513224e
--- /dev/null
+++ b/tools/mallocfail
@@ -0,0 +1,114 @@
+#!/usr/bin/perl
+
+# Copyright (c) 2013 Nicolas George
+#
+# This file is part of FFmpeg.
+#
+# FFmpeg is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public License
+# as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# FFmpeg is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+use strict;
+use warnings;
+use Config;
+use Socket;
+use Fcntl;
+
+=pod
+
+=head1 NAME
+
+mallocfail - server for the malloc-fail feature
+
+=head1 SYNOPSIS
+
+tools/make_chlayout_test I<script> I<command> I<options>
+
+=head1 DESCRIPTION
+
+This script acts as a server for B<libavutil> when compiled with
+C<--enable-malloc-fail>. It will run the provided command and react to all
+memory allocation functions.
+
+I<script> is a snippet of Perl code that must return a CODE reference. The
+reference will be called at each memory allocation function, with C<$_>
+containing the stack backtrace and other message from B<libavutil>. If it
+returns B<false>, it will cause the memory allocation to fail.
+
+As a special case, if I<script> starts with a C<E<lt>>, the rest is
+interpreted the name of a file to source. In other workds, C<E<lt>fail.plx>
+is equivalent to C<do "fail.plx"> with additional error reporting.
+
+=head1 EXAMPLES
+
+Cause frame allocation to fail inside B<libavfilter> and check if the
+cleanup does not cause memory leaks:
+
+  tools/make_chlayout_test 'sub{ !/av_frame_alloc.*avfilter/s }' \
+    valgrind --leak-check=full ./ffmpeg_g -i ...
+
+=cut
+
+my ($script, @cmd) = @ARGV;
+die "Usage: $0 script command [args ...]\n" unless @cmd;
+my $cmd = $cmd[0];
+
+my $callback = do {;
+  no strict;
+  $script =~ s/^<// ? do $script : eval $script;
+};
+die $@ if $@;
+die "script must return a sub ref\n" unless ref($callback) eq "CODE";
+
+socketpair my $fd_master, my $fd_slave, AF_UNIX, SOCK_STREAM, 0
+  or die "socketpair: $!\n";
+
+my $pid = fork;
+die "fork: $!\n" unless defined $pid;
+if (!$pid) {
+  close $fd_master;
+  fcntl $fd_slave, F_SETFD, fcntl($fd_slave, F_GETFD, 0) & ~FD_CLOEXEC;
+  $ENV{MALLOCFAIL_CTL_FD} = fileno($fd_slave);
+  exec @cmd;
+}
+
+close $fd_slave;
+select $fd_master;
+$| = 1;
+select STDOUT;
+print $fd_master "!!!!! mallocfail INIT\n";
+
+my $message = "";
+while (<$fd_master>) {
+  if (/^!!!!! mallocfail ASK\n/) {
+    local $_ = $message;
+    $message = "";
+    my $ok = $callback->($message);
+    print $fd_master $ok ? "OK\n" : "KO\n";
+  } else {
+    $message .= $_;
+  }
+}
+
+wait;
+my $status = $?;
+if ($status & 255) {
+  my $core_dump = $status & 128 ? " (core dumped)" : "";
+  $status &= 127;
+  my $signame = (split " ", $Config{sig_name})[$status] // "signal $status";
+  warn "$cmd: killed by $signame$core_dump\n";
+} else {
+  $status >>= 8;
+  warn "$cmd: exit $status\n" if $status;
+  exit $status;
+}
-- 
1.8.5.2



More information about the ffmpeg-devel mailing list