File crash-compressed-kernel of Package crash

# HG changeset patch
# User Bernhard Walle <bwalle@suse.de>
# Date 1232040417 -3600
# Node ID c06f161cde02b5df2782d91b641165b9f373ca11
# Parent  73c6c45110e8495a000a6f31aa3f99258c7cd538
[PATCH] Implement support for compressed kernel images

This patch implements support for compressed kernel images. The patch uses zlib
(which is already required, so no additional dependency is added). It
uncompresses the kernel to a temporary file (in $TMPDIR or /tmp if $TMPDIR
is unset) and operates on that file. That was necessary because the kernel file
name is used in various places and it would be too heavy to modify BFD calls
to operate on data instead on operating directly on the file.

The patch matches sure that the debuginfo file is still found (because the
original location differs from the tempfile location). It is therefore
necessary to keep the original file name in pc->orig_namelist (new member).

TODO:
 - Find all places where pc->namelist is printed to the user and change this to
   pc->orig_namelist because the temporary file may confuse the user.


Signed-off-by: Bernhard Walle <bwalle@suse.de>

---
 defs.h    |    3
 filesys.c |    1
 defs.h    |    3 
 filesys.c |    1 
 kernel.c  |    4 -
 main.c    |   24 ++++---
 symbols.c |  212 ++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 5 files changed, 209 insertions(+), 35 deletions(-)
* * *
Fix segmentation fault

* Executing crash without any parameter results in a segmentation fault.
* Add a NULL check for pc->orig_namelist to avoid the segmentation fault.


Signed-off-by: Sachin Sant <sachinp@in.ibm.com>
Acked-by: Bernhard Walle <bwalle@suse.de>

--- a/defs.h
+++ b/defs.h
@@ -320,6 +313,8 @@ struct program_context {
 	char *prompt;                   /* this program's prompt */
 	unsigned long long flags;       /* flags from above */
 	char *namelist;         	/* linux namelist */
+	char *orig_namelist;         	/* original namelist when namelist is
+					   uncompressed at runtime */
 	char *dumpfile;         	/* dumpfile or /dev/kmem */ 
 	char *live_memsrc;              /* live memory driver */
 	char *system_map;               /* get symbol values from System.map */
@@ -3632,6 +3410,7 @@ void dump_struct_table(ulong);
 void dump_offset_table(char *, ulong);
 int is_elf_file(char *);
 int is_kernel(char *);
+int is_kernel_uncompress(char *file, char *new_filename);
 int is_shared_object(char *);
 int file_elf_version(char *);
 int is_system_map(char *);
--- a/filesys.c
+++ b/filesys.c
@@ -601,6 +590,7 @@ find_booted_kernel(void)
 			if (CRASHDEBUG(1))
 				fprintf(fp, "find_booted_kernel: found: %s\n", 
 					pc->namelist);
+			pc->orig_namelist = pc->namelist;
                         return TRUE;
                 }
 	}
--- a/kernel.c
+++ b/kernel.c
@@ -3884,10 +3837,10 @@ display_sys_stats(void)
         	if (pc->system_map) {
                 	fprintf(fp, "  SYSTEM MAP: %s\n", pc->system_map);
 			fprintf(fp, "DEBUG KERNEL: %s %s\n", 
-					pc->namelist,
+					pc->orig_namelist,
 					debug_kernel_version(pc->namelist));
 		} else
-			fprintf(fp, "      KERNEL: %s\n", pc->namelist);
+			fprintf(fp, "      KERNEL: %s\n", pc->orig_namelist);
 	}
 
 	if (pc->debuginfo_file)
--- a/main.c
+++ b/main.c
@@ -343,6 +339,7 @@ main(int argc, char **argv)
 	 *  Take the kernel and dumpfile arguments in either order.
 	 */
 	while (argv[optind]) {
+		char uncompressed_file[PATH_MAX] = "";
 
 		if (is_remote_daemon(argv[optind])) {
                 	if (pc->flags & DUMPFILE_TYPES) {
@@ -361,21 +352,28 @@ main(int argc, char **argv)
 			continue;
 		}
 
-       		if (!file_exists(argv[optind], NULL)) {
-                	error(INFO, "%s: %s\n", argv[optind], strerror(ENOENT));
-                	program_usage(SHORT_FORM);
-        	} else if (!is_readable(argv[optind])) 
+		if (!file_exists(argv[optind], NULL)) {
+			error(INFO, "%s: %s\n", argv[optind], strerror(ENOENT));
 			program_usage(SHORT_FORM);
+		} else if (!is_readable(argv[optind]))
+			program_usage(SHORT_FORM);
+
+		if (is_kernel_uncompress(argv[optind], uncompressed_file)) {
+			char *file = strlen(uncompressed_file)
+				? strdup(uncompressed_file)
+				: argv[optind];
 
-		if (is_kernel(argv[optind])) {
 			if (pc->namelist || pc->server_namelist) {
-				if (!select_namelist(argv[optind])) {
+				if (!select_namelist(file)) {
                                		error(INFO, 
 					    "too many namelist arguments\n");
                                		program_usage(SHORT_FORM);
 				}
-			} else
-				pc->namelist = argv[optind];
+			} else {
+				pc->namelist = file;
+				/* always set this, also if it's the same */
+				pc->orig_namelist = argv[optind];
+			}
 
 		} else if (!(pc->flags & KERNEL_DEBUG_QUERY)) {
 
--- a/symbols.c
+++ b/symbols.c
@@ -60,7 +61,7 @@ static void symname_hash_init(void);
 static void symname_hash_install(struct syment *);
 static struct syment *symname_hash_search(char *);
 static void gnu_qsort(bfd *, void *, long, unsigned int, asymbol *, asymbol *);
-static int check_gnu_debuglink(bfd *);
+static int check_gnu_debuglink(bfd *, int no_bfd_reset);
 static int separate_debug_file_exists(const char *, unsigned long, int *);
 static int store_module_kallsyms_v1(struct load_module *, int, int, char *);
 static int store_module_kallsyms_v2(struct load_module *, int, int, char *);
@@ -162,6 +161,14 @@ symtab_init(void)
 	if (file_elf_version(pc->namelist) == EV_DWARFEXTRACT)
 		pc->flags |= KERNTYPES;
 
+	/*
+	 * on gzip compressed kernel images, we need to set
+	 * namelist_debug so that GDB is able to find debuginfo
+	 */
+	if (!(LKCD_KERNTYPES()) && !STRNEQ(pc->namelist, pc->orig_namelist))
+		if (!check_gnu_debuglink(st->bfd, TRUE))
+			no_debugging_data(FATAL);
+
 	if (pc->flags & SYSMAP) {
 		bfd_map_over_sections(st->bfd, section_header_info, 
 			VERIFY_SECTIONS);
@@ -185,7 +192,7 @@ symtab_init(void)
 	 */
 	if (!(LKCD_KERNTYPES()) &&
 	    !(bfd_get_file_flags(st->bfd) & HAS_SYMS)) {
-		if (!check_gnu_debuglink(st->bfd))
+		if (!check_gnu_debuglink(st->bfd, FALSE))
 			no_debugging_data(FATAL);
 	}
 	
@@ -236,7 +243,7 @@ symtab_init(void)
  *           /usr/lib/debug/boot (since we know it's a Red Hat kernel)
  */
 static int
-check_gnu_debuglink(bfd *bfd)
+check_gnu_debuglink(bfd *bfd, int no_bfd_reset)
 {
 	int i, exists, found;
 	asection *sect;
@@ -271,16 +278,19 @@ check_gnu_debuglink(bfd *bfd)
 		error(NOTE, "gnu_debuglink file: %s\ncrc32: %lx\n",
 			contents, crc32);
 
+	if (pc->orig_namelist == NULL)
+		return FALSE;
+
   	if ((pc->debuginfo_file = (char *)
-	    malloc(((strlen(pc->namelist) + strlen("/.debug/") +
+	    malloc(((strlen(pc->orig_namelist) + strlen("/.debug/") +
 	    + strlen(".debug") + strlen(" /usr/lib/debug/boot/ "))*10)
 	    + strlen(pc->namelist_debug ? pc->namelist_debug : " "))) == NULL)
 		error(FATAL, "debuginfo file name malloc: %s\n", 
 			strerror(errno));
 
-	filename = basename(pc->namelist);
-	dirname = GETBUF(strlen(pc->namelist)+1);
-	strcpy(dirname, pc->namelist);
+	filename = basename(pc->orig_namelist);
+	dirname = GETBUF(strlen(pc->orig_namelist)+1);
+	strcpy(dirname, pc->orig_namelist);
 
   	for (i = strlen(dirname)-1; i >= 0; i--)
     	{
@@ -385,13 +395,15 @@ check_gnu_debuglink(bfd *bfd)
 
 reset_bfd:
 
-        if ((st->bfd = bfd_openr(pc->debuginfo_file, NULL)) == NULL)
-                error(FATAL, "cannot open object file: %s\n", 
-			pc->debuginfo_file);
-
-        if (!bfd_check_format_matches(st->bfd, bfd_object, &matching))
-                error(FATAL, "cannot determine object file format: %s\n",
-                        pc->debuginfo_file);
+	if (!no_bfd_reset) {
+		if ((st->bfd = bfd_openr(pc->debuginfo_file, NULL)) == NULL)
+			error(FATAL, "cannot open object file: %s\n",
+				pc->debuginfo_file);
+
+		if (!bfd_check_format_matches(st->bfd, bfd_object, &matching))
+			error(FATAL, "cannot determine object file format: %s\n",
+				pc->debuginfo_file);
+	}
 
 	FREEBUF(contents);
 	FREEBUF(dirname);
@@ -460,17 +472,17 @@ no_debugging_data(int error_type)
 	switch (error_type)
 	{
 	case INFO:
-		error(INFO, "%s: no debugging data available\n", pc->namelist);
+		error(INFO, "%s: no debugging data available\n", pc->orig_namelist);
 		break;
 
 	case FATAL:
         	error(FATAL, "%s%s: no debugging data available\n",
-			pc->flags & RUNTIME ? "" : "\n", pc->namelist);
+			pc->flags & RUNTIME ? "" : "\n", pc->orig_namelist);
 		clean_exit(1);
 
 	case WARNING:
                 error(FATAL, "\n%s: no debugging data available\n",
-                        pc->namelist);
+                        pc->orig_namelist);
 		clean_exit(1);
 	}
 }
@@ -2795,27 +2606,148 @@ is_elf_file(char *s)
 }
 
 /*
+ * Checks if the file is gzip-compressed. Just uses the magic checksum for
+ * this, doesn't check the contents.
+ */
+int is_gzip_file(const char *file)
+{
+	int fd = -1;
+	unsigned char buffer[2];
+
+	if ((fd = open(file, O_RDONLY)) < 0) {
+		error(INFO, "%s: %s\n", file, strerror(errno));
+		return FALSE;
+	}
+
+	/* read the first 2 bytes */
+	if (read(fd, buffer, 2) != 2) {
+		error(INFO, "%s: %s\n", file, strerror(errno));
+		close(fd);
+		return FALSE;
+	}
+
+	close(fd);
+
+	return buffer[0] == 0x1f && buffer[1] == 0x8b;
+}
+
+/*
+ * Uncompresses the given file. Saves the name of the new file in new_file.
+ * Size of new_file must be large enough for PATH_MAX characters.
+ *
+ * Returns TRUE on success and FALSE on error.
+ */
+int uncompress_file(const char *file, char *new_file)
+{
+	gzFile source = NULL;
+	int target;
+	char buffer[BUFSIZE];
+	int bytes_read;
+	int retval = FALSE;
+	const char *tmpfile_suffix = "/vmlinux-crash-XXXXXX";
+
+	if (!new_file) {
+		error(WARNING, "uncompress_file: new_file must not be NULL.");
+		goto out;
+	}
+
+	source = gzopen(file, "r");
+	if (source == Z_NULL) {
+		error(WARNING, "Cannot open %s: %s\n", file, strerror(errno));
+		goto out;
+	}
+
+	/*
+	 * construct the path where the temporary file resides. Follow the procedure
+	 * which the mktemp shell utility uses:
+	 *   1. If the user's TMPDIR environment variable is set,  the  directory
+	 *      contained therein is used.
+         *   2. If none of the above apply, /tmp is used.
+	 */
+	if (getenv("TMPDIR")) {
+		size_t maxlen = PATH_MAX - strlen(tmpfile_suffix);
+		if (strlen(getenv("TMPDIR")) > maxlen) {
+			error(WARNING, "TMPDIR is too long.");
+			goto out;
+		}
+		strcpy(new_file, getenv("TMPDIR"));
+	} else
+		strcpy(new_file, "/tmp");
+
+	/* add the file name */
+	strcat(new_file, tmpfile_suffix);
+
+	target = mkstemp(new_file);
+	if (target < 0) {
+		error(WARNING, "Error in creating target file: %s\n", target);
+		goto out;
+	}
+
+	do {
+		bytes_read = gzread(source, buffer, BUFSIZE);
+		if (write(target, buffer, bytes_read) != bytes_read) {
+			error(WARNING, "Error in creating target file: %s\n", target);
+			goto out;
+		}
+	} while (bytes_read > 0);
+
+	retval = TRUE;
+
+out:
+	if (source)
+		gzclose(source);
+	if (target > 0)
+		close(target);
+
+	return retval;
+}
+
+/*
+ * Wrapper to delete a file in an on_exit() function.
+ * The file gets freed in this function, so you usually register
+ * that function with 'on_exit(remove_on_exit, strdup(filename))'
+ */
+static void remove_on_exit(int arg1, void *arg2)
+{
+	const char *file = (const char *)arg2;
+
+	remove(file);
+	free(arg2);
+}
+
+/*
  *  Verify a vmlinux file, issuing a warning for processor and endianness
  *  mismatches.
  */
 int
-is_kernel(char *file)
+is_kernel_uncompress(char *file, char *new_file)
 {
-	int fd, swap;
+	gzFile fp;
+	int swap, err;
 	char eheader[BUFSIZE];
 	Elf32_Ehdr *elf32;
 	Elf64_Ehdr *elf64;
+	int is_gzipped;
 
-	if ((fd = open(file, O_RDONLY)) < 0) {
+	/*
+	 * if the file is gzipped and we cannot extract it, then
+	 * we must say this is no kernel even if it would be one after
+	 * unextracting ... (compatibility to is_kernel())
+	 */
+	is_gzipped = is_gzip_file(file);
+	if (!new_file && is_gzipped)
+		return FALSE;
+
+	if ((fp = gzopen(file, "r")) == Z_NULL) {
 		error(INFO, "%s: %s\n", file, strerror(errno));
 		return FALSE;
 	}
-	if (read(fd, eheader, BUFSIZE) != BUFSIZE) {
-                /* error(INFO, "%s: %s\n", file, strerror(errno)); */
-		close(fd);
+	if ((err = gzread(fp, eheader, BUFSIZE)) != BUFSIZE) {
+                error(INFO, "%s: %s\n", file, zError(err));
+		gzclose(fp);
 		return FALSE;
 	}  
-	close(fd);
+	gzclose(fp);
 
 	if (!STRNEQ(eheader, ELFMAG) || eheader[EI_VERSION] != EV_CURRENT)
 		return FALSE;
@@ -2905,7 +2831,35 @@ is_kernel(char *file)
 	}
 
 bailout:
-	return(is_bfd_format(file));
+
+	/* we must unextract it before presenting BFD */
+	if (is_gzipped) {
+		if (!uncompress_file(file, new_file)) {
+			error(INFO, "Uncompression failed for %s\n", file);
+			return FALSE;
+		}
+
+		/* if it's BFD, then make sure that it is deleted afterwards */
+		if (is_bfd_format(new_file)) {
+			on_exit(remove_on_exit, strdup(new_file));
+			return TRUE;
+		/* if not, delete it immediately */
+		} else {
+			remove(new_file);
+			return FALSE;
+		}
+	} else
+		return(is_bfd_format(file));
+ }
+
+/*
+ *  Verify a vmlinux file, issuing a warning for processor and endianness
+ *  mismatches.
+ */
+int
+is_kernel(char *file)
+{
+	return is_kernel_uncompress(file, NULL);
 }
 
 int
openSUSE Build Service is sponsored by