[PATCH] Support gzip compressed exec and core files in gdb

Michael Eager
2015-03-10T23:01:47Z

[-- Attachment #1: Type: text/plain, Size: 1279 bytes --]

Add support to automatically unzip compressed executable and core files.
Files will be uncompressed into temporary directory (/tmp or $TMPDIR)
and are deleted when GDB exits. This should be transparent to users,
except for disk space requirements. The name of the uncompressed file is
mentioned, but all references to the file in GDB messages is to the file
which the user specified.

This operation cannot be done completely by BFD because BFD allows an opened
file to be passed to it for processing. GDB uses this functionality.

BFD:
* bfd-in2.h: Regenerate.
* bfd.c (struct bfd): Add uncompressed_filename.
* bfdio.c (bfd_get_mtime): Set bfd->mtime_set to true.
* cache.c (bfd_open): Open previously created uncompressed file.

GDB:
* common/filestuff.c (struct compressed_file_cache_search, eq_compressed_file,
is_gzip, decompress_gzip, do_compressed_cleanup, gdb_uncompress): New.
* common/filestuff.h (gdb_uncompress): Declare.
* corelow.c (core_open): Uncompress core file.
* exec.c (exec_file_attach): Uncompress exe file.
* symfile.c (symfile_bfd_open): Uncompress sym (exe) file.

GDB/DOC:
* gdb.texinfo: Mention gzipped exec and core files.

--
Michael Eager eager@eagercon.com
1960 Park Blvd., Palo Alto, CA 94306 650-325-8077

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-GDB-support-compressed-exec-and-core-files.patch --]
[-- Type: text/x-patch; name="0001-GDB-support-compressed-exec-and-core-files.patch", Size: 13816 bytes --]

From b675cf554012ad1067e5bb1693fe695844cc09d6 Mon Sep 17 00:00:00 2001
From: Michael Eager <meager@cisco.com>
Date: Tue, 10 Mar 2015 15:31:11 -0700
Subject: [PATCH] GDB support compressed exec and core files.

Add support to automatically unzip compressed executable and core files.
Files will be uncompressed into temporary directory (/tmp or $TMPDIR)
and are deleted when GDB exits. This should be transparent to users,
except for disk space requirements. The name of the uncompressed file is
mentioned, but all references to the file in GDB messages is to the file
which the user specified.

This operation cannot be done completely by BFD because BFD allows an opened
file to be passed to it for processing. GDB uses this functionality.

BFD:
* bfd-in2.h: Regenerate.
* bfd.c (struct bfd): Add uncompressed_filename.
* bfdio.c (bfd_get_mtime): Set bfd->mtime_set to true.
* cache.c (bfd_open): Open previously created uncompressed file.

GDB:
* common/filestuff.c (struct compressed_file_cache_search, eq_compressed_file,
is_gzip, decompress_gzip, do_compressed_cleanup, gdb_uncompress): New.
* common/filestuff.h (gdb_uncompress): Declare.
* corelow.c (core_open): Uncompress core file.
* exec.c (exec_file_attach): Uncompress exe file.
* symfile.c (symfile_bfd_open): Uncompress sym (exe) file.

GDB/DOC:
* gdb.texinfo: Mention gzipped exec and core files.
---
bfd/bfd-in2.h | 3 +
bfd/bfd.c | 3 +
bfd/bfdio.c | 1 +
bfd/cache.c | 6 +-
gdb/common/filestuff.c | 204 +++++++++++++++++++++++++++++++++++++++++++++++++
gdb/common/filestuff.h | 6 ++
gdb/corelow.c | 10 +++
gdb/doc/gdb.texinfo | 5 ++
gdb/exec.c | 15 +++-
gdb/symfile.c | 13 +++-
10 files changed, 263 insertions(+), 3 deletions(-)

diff --git a/bfd/bfd-in2.h b/bfd/bfd-in2.h
index 2d2e7ac..dd5d6ea 100644
--- a/bfd/bfd-in2.h
+++ b/bfd/bfd-in2.h
@@ -6294,6 +6294,9 @@ struct bfd
/* The filename the application opened the BFD with. */
const char *filename;

+ /* The file name after being uncompressed. */
+ const char *uncompressed_filename;
+
/* A pointer to the target jump table. */
const struct bfd_target *xvec;

diff --git a/bfd/bfd.c b/bfd/bfd.c
index 5ae5eca..4c78e7b 100644
--- a/bfd/bfd.c
+++ b/bfd/bfd.c
@@ -56,6 +56,9 @@ CODE_FRAGMENT
. {* The filename the application opened the BFD with. *}
. const char *filename;
.
+. {* The file name after being uncompressed. *}
+. const char *uncompressed_filename;
+.
. {* A pointer to the target jump table. *}
. const struct bfd_target *xvec;
.
diff --git a/bfd/bfdio.c b/bfd/bfdio.c
index 89406e3..e82c888 100644
--- a/bfd/bfdio.c
+++ b/bfd/bfdio.c
@@ -382,6 +382,7 @@ bfd_get_mtime (bfd *abfd)
return 0;

abfd->mtime = buf.st_mtime; /* Save value in case anyone wants it */
+ abfd->mtime_set = TRUE; /* Say that it is valid. */
return buf.st_mtime;
}

diff --git a/bfd/cache.c b/bfd/cache.c
index 94a82da..f46e9dd 100644
--- a/bfd/cache.c
+++ b/bfd/cache.c
@@ -588,7 +588,11 @@ bfd_open_file (bfd *abfd)
{
case read_direction:
case no_direction:
- abfd->iostream = real_fopen (abfd->filename, FOPEN_RB);
+ if (abfd->uncompressed_filename)
+ /* BFD was previously uncompressed. Reopen file. */
+ abfd->iostream = real_fopen (abfd->uncompressed_filename, FOPEN_RB);
+ else
+ abfd->iostream = real_fopen (abfd->filename, FOPEN_RB);
break;
case both_direction:
case write_direction:
diff --git a/gdb/common/filestuff.c b/gdb/common/filestuff.c
index 14d6324..b2c31fd 100644
--- a/gdb/common/filestuff.c
+++ b/gdb/common/filestuff.c
@@ -19,10 +19,16 @@
#include "common-defs.h"
#include "filestuff.h"
#include "gdb_vecs.h"
+#include "hashtab.h"
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <stdio.h>
+
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif

#ifdef USE_WIN32API
#include <winsock2.h>
@@ -47,6 +53,27 @@
#define SOCK_CLOEXEC 0
#endif

+#ifdef HAVE_ZLIB_H
+/* Hash table of compressed files. */
+
+static htab_t compressed_file_cache;
+
+struct compressed_file_cache_search
+{
+ const char *filename;
+ char *uncompressed_filename;
+ time_t mtime;
+};
+
+static int
+eq_compressed_file (const void *a, const void *b)
+{
+ const struct compressed_file_cache_search *entry = a;
+ const struct compressed_file_cache_search *search = b;
+
+ return (strcmp (entry->filename, search->filename) == 0);
+}
+#endif
\f

#ifndef HAVE_FDWALK
@@ -404,3 +431,180 @@ gdb_pipe_cloexec (int filedes[2])

return result;
}
+\f
+#ifndef GDBSERVER
+/* Test if file is compressed with gzip. */
+
+static inline int
+is_gzip (unsigned char *buf)
+{
+ return (buf[0] == 037 && buf[1] == 0213); /* From /usr/share/magic. */
+}
+
+#define COMPRESS_BUF_SIZE (1024*1024)
+static int
+decompress_gzip (const char *filename, FILE *tmp)
+{
+#ifdef HAVE_ZLIB_H
+ char *buf = malloc (COMPRESS_BUF_SIZE);
+ gzFile compressed = gzopen (filename, "r");
+ int count, res;
+
+ if (buf == NULL || compressed == NULL)
+ {
+ printf (_("error copying gzip file\n"));
+ free (buf);
+ return 0;
+ }
+
+ while ((count = gzread (compressed, buf, COMPRESS_BUF_SIZE)))
+ {
+ res = fwrite (buf, 1, count, tmp);
+ if (res != count)
+ {
+ printf (_("error decompressing gzip file\n"));
+ free (buf);
+ return 0;
+ }
+ }
+
+ fflush (tmp);
+ gzclose (compressed);
+ free (buf);
+ return 1;
+#else
+ return 0;
+#endif
+}
+
+/* Delete uncompressed temp file when terminating. */
+static void
+do_compressed_cleanup (void *filename)
+{
+ unlink (filename);
+ xfree (filename);
+}
+
+/* If file is compressed, uncompress it into a temporary. */
+
+int
+gdb_uncompress (const char *filename, char **uncompressed_filename)
+{
+ FILE *handle;
+ struct compressed_file_cache_search search, *found;
+ struct stat st;
+ hashval_t hash;
+ void **slot;
+ static unsigned char buffer[1024];
+ size_t count;
+ enum {NONE, GZIP, BZIP2} file_compression = NONE;
+ int decomp_fd;
+ FILE *decomp_file;
+ int ret = 0;
+ char *tmpdir, *p;
+ char *template = xmalloc(128 + 12 + 7 + 1);
+ if (compressed_file_cache == NULL)
+ compressed_file_cache = htab_create_alloc (1, htab_hash_string,
+ eq_compressed_file,
+ NULL, xcalloc, xfree);
+
+ if ((stat (filename, &st) < 0))
+ return 0;
+
+ search.filename = filename;
+ search.uncompressed_filename = NULL;
+
+ hash = htab_hash_string (filename);
+ found = htab_find_with_hash (compressed_file_cache, &search, hash);
+
+ if (found)
+ {
+ /* We previously uncompressed the file. */
+ if (found->mtime == st.st_mtime)
+ {
+ /* Return file if compressed file not changed. */
+ *uncompressed_filename = found->uncompressed_filename;
+ return 1;
+ }
+ else
+ {
+ /* Delete old uncompressed file. */
+ unlink (found->uncompressed_filename);
+ xfree ((void *)found->filename);
+ xfree (found->uncompressed_filename);
+ }
+ }
+
+ if ((handle = fopen (filename, "rb")) == NULL)
+ return 0;
+
+ if ((count = fread (buffer, 1, sizeof buffer, handle)) > 0)
+ {
+ if (is_gzip (buffer))
+ file_compression = GZIP;
+ }
+
+ fclose (handle);
+
+ if (file_compression == NONE)
+ return 0;
+
+ /* Create temporary file name for uncompressed file. */
+ if (!(tmpdir = getenv ("TMPDIR")))
+ tmpdir = "/tmp";
+ strncpy (template, tmpdir, 128);
+ strcat (template, "/");
+ for (p = (char *)filename + strlen (filename) - 1;
+ p >= filename && *p != '/'; p--) /* find final slash. */ ;
+ strncat (template, ++p, 128);
+ p = template + strlen (template);
+ if (strcmp (p - 3, ".gz") == 0)
+ *(p - 3) = '\0';
+ strcat (template, "-XXXXXX");
+
+ if ((decomp_fd = mkstemp (template)) != -1)
+ {
+ decomp_file = fdopen (decomp_fd, "w+b");
+
+ if (file_compression == GZIP)
+ {
+ printf (_("Decompressing %s to %s\n"), filename, template);
+ ret = decompress_gzip (filename, decomp_file);
+ }
+ else
+ {
+ xfree (template);
+ return 0;
+ }
+ fclose (decomp_file);
+
+ if (ret)
+ {
+ if (!found)
+ {
+ slot = htab_find_slot_with_hash (compressed_file_cache,
+ &search, hash, INSERT);
+ gdb_assert (slot && !*slot);
+ found = xmalloc (sizeof (struct compressed_file_cache_search));
+ *slot = found;
+ }
+ found->filename = strdup (filename);
+ found->mtime = st.st_mtime;
+ found->uncompressed_filename = template;
+ }
+ }
+ else
+ {
+ warning (_("Decompression failed\n"));
+ xfree (template);
+ return 0;
+ }
+
+ *uncompressed_filename = template;
+
+ /* Schedule delete of temp file when gdb ends. */
+ make_final_cleanup (do_compressed_cleanup, xstrdup (template));
+
+ return 1;
+}
+#endif
diff --git a/gdb/common/filestuff.h b/gdb/common/filestuff.h
index 98522a6..7a1d12d 100644
--- a/gdb/common/filestuff.h
+++ b/gdb/common/filestuff.h
@@ -67,4 +67,10 @@ extern int gdb_socket_cloexec (int domain, int style, int protocol);

extern int gdb_pipe_cloexec (int filedes[2]);

+/* If 'filename' is compressed, uncompress it and return name of
+ uncompressed file. */
+
+extern int gdb_uncompress (const char *filename,
+ char **uncompressed_filename);
+
#endif /* FILESTUFF_H */
diff --git a/gdb/corelow.c b/gdb/corelow.c
index 9218003..13662a7 100644
--- a/gdb/corelow.c
+++ b/gdb/corelow.c
@@ -279,6 +279,7 @@ core_open (const char *arg, int from_tty)
int scratch_chan;
int flags;
char *filename;
+ char *uncompressed_filename;

target_preopen (from_tty);
if (!arg)
@@ -316,6 +317,15 @@ core_open (const char *arg, int from_tty)
if (temp_bfd == NULL)
perror_with_name (filename);

+ if (!write_files && gdb_uncompress (filename, &uncompressed_filename))
+ {
+ close (scratch_chan);
+ scratch_chan = gdb_open_cloexec (uncompressed_filename, flags, 0);
+ temp_bfd = gdb_bfd_fopen (uncompressed_filename, gnutarget,
+ FOPEN_RB, scratch_chan);
+ temp_bfd->uncompressed_filename = uncompressed_filename;
+ }
+
if (!bfd_check_format (temp_bfd, bfd_core)
&& !gdb_check_format (temp_bfd))
{
diff --git a/gdb/doc/gdb.texinfo b/gdb/doc/gdb.texinfo
index 35dbe86..3b4d389 100644
--- a/gdb/doc/gdb.texinfo
+++ b/gdb/doc/gdb.texinfo
@@ -17391,6 +17391,11 @@ via @code{gdbserver} (@pxref{Server, file, Using the @code{gdbserver}
Program}). In these situations the @value{GDBN} commands to specify
new files are useful.

+Executable and core files may be compressed using @code{gzip}. These
+files will be uncompressed into temporary files either in /tmp or in
+@code{$TMPDIR} if this is set in the environment. The files will have
+a unique name and will be deleted when @value{GDBN} terminates.
+
@table @code
@cindex executable file
@kindex file
diff --git a/gdb/exec.c b/gdb/exec.c
index 696458d..6d3c1fc 100644
--- a/gdb/exec.c
+++ b/gdb/exec.c
@@ -35,6 +35,7 @@
#include "progspace.h"
#include "gdb_bfd.h"
#include "gcore.h"
+#include "filestuff.h"

#include <fcntl.h>
#include "readline/readline.h"
@@ -155,6 +156,7 @@ void
exec_file_attach (const char *filename, int from_tty)
{
struct cleanup *cleanups;
+ char *uncompressed_filename = NULL;

/* First, acquire a reference to the current exec_bfd. We release
this at the end of the function; but acquiring it now lets the
@@ -209,7 +211,18 @@ exec_file_attach (const char *filename, int from_tty)
exec_bfd = gdb_bfd_fopen (canonical_pathname, gnutarget,
FOPEN_RUB, scratch_chan);
else
- exec_bfd = gdb_bfd_open (canonical_pathname, gnutarget, scratch_chan);
+ {
+ if (!gdb_uncompress (canonical_pathname, &uncompressed_filename))
+ exec_bfd = gdb_bfd_open (canonical_pathname, gnutarget, scratch_chan);
+ else
+ {
+ close (scratch_chan);
+ scratch_chan = openp ("", 0, uncompressed_filename,
+ O_RDONLY | O_BINARY, &scratch_pathname);
+
+ exec_bfd = gdb_bfd_open (uncompressed_filename, gnutarget, scratch_chan);
+ }
+ }

if (!exec_bfd)
{
diff --git a/gdb/symfile.c b/gdb/symfile.c
index c2a71ec..0d173f1 100644
--- a/gdb/symfile.c
+++ b/gdb/symfile.c
@@ -56,6 +56,7 @@
#include "stack.h"
#include "gdb_bfd.h"
#include "cli/cli-utils.h"
+#include "filestuff.h"

#include <sys/types.h>
#include <fcntl.h>
@@ -1739,6 +1740,7 @@ symfile_bfd_open (const char *cname)
bfd *sym_bfd;
int desc;
char *name, *absolute_name;
+ char *uncompressed_filename;
struct cleanup *back_to;

if (remote_filename_p (cname))
@@ -1783,7 +1785,16 @@ symfile_bfd_open (const char *cname)
name = absolute_name;
back_to = make_cleanup (xfree, name);

- sym_bfd = gdb_bfd_open (name, gnutarget, desc);
+ if (!gdb_uncompress (name, &uncompressed_filename))
+ sym_bfd = gdb_bfd_open (name, gnutarget, desc);
+ else
+ {
+ close (desc);
+ desc = openp ("", 0, uncompressed_filename, O_RDONLY | O_BINARY, &absolute_name);
+
+ sym_bfd = gdb_bfd_open (uncompressed_filename, gnutarget, desc);
+ }
+
if (!sym_bfd)
error (_("`%s': can't open to read symbols: %s."), name,
bfd_errmsg (bfd_get_error ()));
--
2.2.1