[PATCH 3/7] ppc64: detect zImage files and load the uncompressed vmlinux

Cédric Le Goater clg at fr.ibm.com
Fri Apr 18 07:08:36 PDT 2014


The zImage* executable files on ppc64 use a special section called
".kernel:vmlinux.strip" which contains a compressed vmlinux executable
file.

This patch adds a service to detect and unzip such a section in a
malloc'ed buffer. The buffer is then used in elf_ppc64_load() to
load the new vmlinux.

Signed-off-by: Cédric Le Goater <clg at fr.ibm.com>
---
 kexec/arch/ppc64/kexec-elf-ppc64.c    |   15 ++++
 kexec/arch/ppc64/kexec-ppc64.h        |    2 +
 kexec/arch/ppc64/kexec-zImage-ppc64.c |  123 +++++++++++++++++++++++++++++++++
 3 files changed, 140 insertions(+)

diff --git a/kexec/arch/ppc64/kexec-elf-ppc64.c b/kexec/arch/ppc64/kexec-elf-ppc64.c
index ce1036762582..069d8ba6e690 100644
--- a/kexec/arch/ppc64/kexec-elf-ppc64.c
+++ b/kexec/arch/ppc64/kexec-elf-ppc64.c
@@ -116,6 +116,8 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len,
 	uint64_t toc_addr;
 	uint32_t my_run_at_load;
 	unsigned int slave_code[256/sizeof (unsigned int)], master_entry;
+	void *vmlinux_addr;
+	int vmlinux_size;
 
 	/* See options.h -- add any more there, too. */
 	static const struct option options[] = {
@@ -184,6 +186,7 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len,
 		modified_cmdline_len = strlen(modified_cmdline);
 	}
 
+retry:
 	/* Parse the Elf file */
 	result = build_elf_exec_info(buf, len, &ehdr, 0);
 	if (result < 0) {
@@ -191,6 +194,18 @@ int elf_ppc64_load(int argc, char **argv, const char *buf, off_t len,
 		return result;
 	}
 
+	/* If this is a zimage boot wrapper, rebuild the elf info on
+	 * the new vmlinux which has been unzipped and go on with the
+	 * kernel load.
+	 */
+	if (!zImage_ppc64_unzip(&ehdr, &vmlinux_addr, &vmlinux_size)) {
+		free_elf_info(&ehdr);
+		free((void *) buf);
+		buf = vmlinux_addr;
+		len = vmlinux_size;
+		goto retry;
+	}
+
 	/* Load the Elf data. Physical load addresses in elf64 header do not
 	 * show up correctly. Use user supplied address for now to patch the
 	 * elf header
diff --git a/kexec/arch/ppc64/kexec-ppc64.h b/kexec/arch/ppc64/kexec-ppc64.h
index 9a0aecff7b33..e546d4737bfa 100644
--- a/kexec/arch/ppc64/kexec-ppc64.h
+++ b/kexec/arch/ppc64/kexec-ppc64.h
@@ -37,4 +37,6 @@ typedef struct mem_rgns {
 
 extern mem_rgns_t usablemem_rgns;
 
+int zImage_ppc64_unzip(struct mem_ehdr *ehdr, void **buf, int *len);
+
 #endif /* KEXEC_PPC64_H */
diff --git a/kexec/arch/ppc64/kexec-zImage-ppc64.c b/kexec/arch/ppc64/kexec-zImage-ppc64.c
index d084ee587be5..67d751f19d7d 100644
--- a/kexec/arch/ppc64/kexec-zImage-ppc64.c
+++ b/kexec/arch/ppc64/kexec-zImage-ppc64.c
@@ -30,6 +30,9 @@
 #include <getopt.h>
 #include <linux/elf.h>
 #include "../../kexec.h"
+#include "kexec-ppc64.h"
+
+#include <zlib.h>
 
 #define MAX_HEADERS 32
 
@@ -175,3 +178,123 @@ void zImage_ppc64_usage(void)
 {
 	fprintf(stderr, "zImage support is still broken\n");
 }
+
+#define HEAD_CRC	2
+#define EXTRA_FIELD	4
+#define ORIG_NAME	8
+#define COMMENT		0x10
+#define RESERVED	0xe0
+
+static int get_header_len(const char *header)
+{
+	int len = 10;
+	int flags = header[3];
+
+	/* check for gzip header */
+	if ((header[0] != 0x1f) || (header[1] != 0x8b) ||
+	    (header[2] != Z_DEFLATED) || (flags & RESERVED) != 0) {
+		fprintf(stderr, "bad gzip header\n");
+		return -1;
+	}
+
+	if ((flags & EXTRA_FIELD) != 0)
+		len = 12 + header[10] + (header[11] << 8);
+
+	if ((flags & ORIG_NAME) != 0)
+		while (header[len++] != 0)
+				;
+	if ((flags & COMMENT) != 0)
+		while (header[len++] != 0)
+			;
+	if ((flags & HEAD_CRC) != 0)
+		len += 2;
+
+	return len;
+}
+
+static int gunzip(void *src, int srclen, void *dst, int dstlen)
+{
+	z_stream strm;
+	int hdrlen;
+	int len;
+	int ret;
+
+	strm.zalloc = Z_NULL;
+	strm.zfree = Z_NULL;
+	strm.opaque = Z_NULL;
+	strm.avail_in = 0;
+	strm.next_in = Z_NULL;
+
+	hdrlen = get_header_len(src);
+	if (hdrlen == -1)
+		return -1;
+
+	if (hdrlen >= srclen) {
+		fprintf(stderr, "gzip header too large : %d\n", hdrlen);
+		return -1;
+	}
+
+	ret = inflateInit2(&strm, -MAX_WBITS);
+	if (ret != Z_OK) {
+		fprintf(stderr, "inflateInit2 failed : %d\n", ret);
+		return -1;
+	}
+
+	/* skip gzip header */
+	strm.total_in = hdrlen;
+	strm.next_in = src + hdrlen;
+	strm.avail_in = srclen - hdrlen;
+
+	strm.next_out = dst;
+	strm.avail_out = dstlen;
+
+	ret = inflate(&strm, Z_FULL_FLUSH);
+	if (ret != Z_OK && ret != Z_STREAM_END) {
+		fprintf(stderr, "inflate failed: %d %s\n", ret, strm.msg);
+		return -1;
+	}
+
+	len = strm.next_out - (unsigned char *) dst;
+
+	inflateEnd(&strm);
+
+	return len;
+}
+
+int zImage_ppc64_unzip(struct mem_ehdr *ehdr, void **buf, int *len)
+{
+	struct mem_shdr *shdr;
+	void *vmlinuz_addr;
+	unsigned long vmlinuz_size;
+	unsigned int *vmlinux_sizep;
+
+	void *vmlinux_addr;
+	int vmlinux_size;
+
+	shdr = elf_rel_find_section(ehdr, ".kernel:vmlinux.strip");
+	if (!shdr)
+		return -1;
+
+	vmlinuz_addr = (void *) shdr->sh_data;
+	vmlinuz_size = shdr->sh_size;
+
+	 /* The size of the uncompressed file is stored in the last 4
+	  * bytes. The vmlinux size should be less than 4G ... */
+	vmlinux_sizep = (vmlinuz_addr + vmlinuz_size) - 4;
+
+	fprintf(stderr, "Found vmlinuz at %p, unzipping %d bytes\n",
+		vmlinuz_addr, *vmlinux_sizep);
+	vmlinux_addr = xmalloc(*vmlinux_sizep);
+
+	vmlinux_size = gunzip(vmlinuz_addr, vmlinuz_size,
+			      vmlinux_addr, *vmlinux_sizep);
+	if (vmlinux_size != *vmlinux_sizep) {
+		fprintf(stderr, "gunzip failed : only got %d of %d bytes.\n",
+				vmlinux_size, *vmlinux_sizep);
+		return -1;
+	}
+
+	*buf = vmlinux_addr;
+	*len = vmlinux_size;
+	return 0;
+}
-- 
1.7.10.4




More information about the kexec mailing list