[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