Rewrite kernel image support: use calculation instead of brute force, support uncompressed payloads.
diff --git a/libdwfl/ChangeLog b/libdwfl/ChangeLog
index 9d2bf8c..39ae70d 100644
--- a/libdwfl/ChangeLog
+++ b/libdwfl/ChangeLog
@@ -1,3 +1,17 @@
+2009-08-27 Roland McGrath <roland@redhat.com>
+
+ * image-header.c: New file.
+ * Makefile.am (libdwfl_a_SOURCES): Add it.
+ * libdwflP.h: Declare __libdw_image_header.
+ * open.c (decompress): Don't consume ELF on failure.
+ (what_kind): New function, broken out of ...
+ (__libdw_open_file): ... here. Call it.
+ If it fails, try __libdw_image_header and then try what_kind again.
+
+ * gzip.c (unzip): Reuse *WHOLE as first INPUT_BUFFER,
+ leave it behind for next decompressor.
+ * open.c (decompress): Free BUFFER on failure.
+
2009-08-26 Roland McGrath <roland@redhat.com>
* gzip.c (find_zImage_payload): New function, broken out of ...
diff --git a/libdwfl/Makefile.am b/libdwfl/Makefile.am
index af83a96..69bef7d 100644
--- a/libdwfl/Makefile.am
+++ b/libdwfl/Makefile.am
@@ -74,7 +74,7 @@
dwfl_module_return_value_location.c \
dwfl_module_register_names.c \
dwfl_segment_report_module.c \
- link_map.c core-file.c open.c
+ link_map.c core-file.c open.c image-header.c
if ZLIB
libdwfl_a_SOURCES += gzip.c
diff --git a/libdwfl/gzip.c b/libdwfl/gzip.c
index daf250b..5604d49 100644
--- a/libdwfl/gzip.c
+++ b/libdwfl/gzip.c
@@ -57,7 +57,8 @@
# include <lzma.h>
# define unzip __libdw_unlzma
# define DWFL_E_ZLIB DWFL_E_LZMA
-# define MAGIC "\xFD" "7zXZ\0"
+# define MAGIC "\xFD" "7zXZ\0" /* XZ file format. */
+# define MAGIC2 "\x5d\0" /* Raw LZMA format. */
# define Z(what) LZMA_##what
# define LZMA_ERRNO LZMA_PROG_ERROR
# define z_stream lzma_stream
@@ -85,70 +86,14 @@
# define Z(what) Z_##what
#endif
-/* We can also handle Linux kernel zImage format in a very hackish way.
- If it looks like one, we actually just scan the image for the right
- magic bytes to figure out where the compressed image starts. */
-
-#define LINUX_MAGIC_OFFSET 514
-#define LINUX_MAGIC "HdrS"
-#define LINUX_MAX_SCAN 32768
-
-static void *
-find_zImage_payload (void *buffer, size_t size)
-{
- void *p = memmem (buffer, size, MAGIC, sizeof MAGIC - 1);
-#ifdef LZMA
- /* The raw LZMA format doesn't have any helpful header magic bytes to
- match. So instead we just consider any byte that could possibly be
- the start of an LZMA header, and try feeding the input to the decoder
- to see if it likes the data. */
- if (p == NULL)
- for (; size > 0; ++buffer, --size)
- if (*(uint8_t *) buffer < (9 * 5 * 5))
- {
- uint8_t dummy[512];
- lzma_stream z = { .next_in = buffer, .avail_in = size,
- .next_out = dummy, .avail_out = sizeof dummy };
- int result = lzma_alone_decoder (&z, 1 << 30);
- if (result != LZMA_OK)
- break;
- result = lzma_code (&z, LZMA_RUN);
- lzma_end (&z);
- if (result == LZMA_OK)
- return buffer;
- }
-#endif
- return p;
-}
-
-static bool
-mapped_zImage (off64_t *start_offset, void **mapped, size_t *mapped_size)
-{
- const size_t pos = LINUX_MAGIC_OFFSET + sizeof LINUX_MAGIC;
- if (*mapped_size > pos
- && !memcmp (*mapped + LINUX_MAGIC_OFFSET,
- LINUX_MAGIC, sizeof LINUX_MAGIC - 1))
- {
- size_t scan = *mapped_size - pos;
- if (scan > LINUX_MAX_SCAN)
- scan = LINUX_MAX_SCAN;
- void *p = find_zImage_payload (*mapped + pos, scan);
- if (p != NULL)
- {
- *start_offset += p - *mapped;
- *mapped_size = *mapped + *mapped_size - p,
- *mapped = p;
- return true;
- }
- }
- return false;
-}
-
#define READ_SIZE (1 << 20)
/* If this is not a compressed image, return DWFL_E_BADELF.
If we uncompressed it into *WHOLE, *WHOLE_SIZE, return DWFL_E_NOERROR.
- Otherwise return an error for bad compressed data or I/O failure. */
+ Otherwise return an error for bad compressed data or I/O failure.
+ If we return an error after reading the first part of the file,
+ leave that portion malloc'd in *WHOLE, *WHOLE_SIZE. If *WHOLE
+ is not null on entry, we'll use it in lieu of repeating a read. */
Dwfl_Error internal_function
unzip (int fd, off64_t start_offset,
@@ -176,45 +121,66 @@
}
void *input_buffer = NULL;
- off_t input_pos;
+ off_t input_pos = 0;
+
+ inline Dwfl_Error fail (Dwfl_Error failure)
+ {
+ if (input_pos == (off_t) mapped_size)
+ *whole = input_buffer;
+ else
+ {
+ free (input_buffer);
+ *whole = NULL;
+ }
+ free (buffer);
+ return failure;
+ }
inline Dwfl_Error zlib_fail (int result)
{
- Dwfl_Error failure = DWFL_E_ZLIB;
switch (result)
{
case Z (MEM_ERROR):
- failure = DWFL_E_NOMEM;
- break;
+ return fail (DWFL_E_NOMEM);
case Z (ERRNO):
- failure = DWFL_E_ERRNO;
- break;
+ return fail (DWFL_E_ERRNO);
+ default:
+ return fail (DWFL_E_ZLIB);
}
- free (buffer);
- free (input_buffer);
- *whole = NULL;
- return failure;
}
if (mapped == NULL)
{
- input_buffer = malloc (READ_SIZE);
- if (unlikely (input_buffer == NULL))
- return DWFL_E_NOMEM;
+ if (*whole == NULL)
+ {
+ input_buffer = malloc (READ_SIZE);
+ if (unlikely (input_buffer == NULL))
+ return DWFL_E_NOMEM;
- ssize_t n = pread_retry (fd, input_buffer, READ_SIZE, 0);
- if (unlikely (n < 0))
- return zlib_fail (Z (ERRNO));
+ ssize_t n = pread_retry (fd, input_buffer, READ_SIZE, start_offset);
+ if (unlikely (n < 0))
+ return zlib_fail (Z (ERRNO));
- input_pos = n;
- mapped = input_buffer;
- mapped_size = n;
+ input_pos = n;
+ mapped = input_buffer;
+ mapped_size = n;
+ }
+ else
+ {
+ input_buffer = *whole;
+ input_pos = mapped_size = *whole_size;
+ }
}
+#define NOMAGIC(magic) \
+ (mapped_size <= sizeof magic || memcmp (mapped, magic, sizeof magic - 1))
+
/* First, look at the header. */
- if ((mapped_size <= sizeof MAGIC
- || memcmp (mapped, MAGIC, sizeof MAGIC - 1))
- && !mapped_zImage (&start_offset, &mapped, &mapped_size))
+ if (NOMAGIC (MAGIC)
+#ifdef MAGIC2
+ && NOMAGIC (MAGIC2)
+#endif
+ )
/* Not a compressed file. */
return DWFL_E_BADELF;
@@ -236,7 +202,8 @@
{
if (z.avail_in == 0 && input_buffer != NULL)
{
- ssize_t n = pread_retry (fd, input_buffer, READ_SIZE, input_pos);
+ ssize_t n = pread_retry (fd, input_buffer, READ_SIZE,
+ start_offset + input_pos);
if (unlikely (n < 0))
{
inflateEnd (&z);
@@ -308,37 +275,12 @@
if (result == DWFL_E_NOERROR && gzdirect (zf))
{
- bool found = false;
- char buf[sizeof LINUX_MAGIC - 1];
- gzseek (zf, start_offset + LINUX_MAGIC_OFFSET, SEEK_SET);
- int n = gzread (zf, buf, sizeof buf);
- if (n == sizeof buf
- && !memcmp (buf, LINUX_MAGIC, sizeof LINUX_MAGIC - 1))
- while (gzread (zf, buf, sizeof MAGIC - 1) == sizeof MAGIC - 1)
- if (!memcmp (buf, MAGIC, sizeof MAGIC - 1))
- {
- start_offset = gztell (zf) - (sizeof MAGIC - 1);
- found = true;
- break;
- }
- else if (gztell (zf) > LINUX_MAX_SCAN)
- break;
gzclose (zf);
- if (found)
- {
- result = open_stream ();
- if (result == DWFL_E_NOERROR && unlikely (gzdirect (zf)))
- {
- gzclose (zf);
- result = DWFL_E_BADELF;
- }
- }
- else
- result = DWFL_E_BADELF;
+ return fail (DWFL_E_BADELF);
}
if (result != DWFL_E_NOERROR)
- return result;
+ return fail (result);
ptrdiff_t pos = 0;
while (1)
@@ -365,6 +307,8 @@
smaller_buffer (pos);
#endif
+ free (input_buffer);
+
*whole = buffer;
*whole_size = size;
diff --git a/libdwfl/image-header.c b/libdwfl/image-header.c
new file mode 100644
index 0000000..054fba7
--- /dev/null
+++ b/libdwfl/image-header.c
@@ -0,0 +1,124 @@
+/* Linux kernel image support for libdwfl.
+ Copyright (C) 2009 Red Hat, Inc.
+ This file is part of Red Hat elfutils.
+
+ Red Hat elfutils is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by the
+ Free Software Foundation; version 2 of the License.
+
+ Red Hat elfutils 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
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with Red Hat elfutils; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA.
+
+ In addition, as a special exception, Red Hat, Inc. gives You the
+ additional right to link the code of Red Hat elfutils with code licensed
+ under any Open Source Initiative certified open source license
+ (http://www.opensource.org/licenses/index.php) which requires the
+ distribution of source code with any binary distribution and to
+ distribute linked combinations of the two. Non-GPL Code permitted under
+ this exception must only link to the code of Red Hat elfutils through
+ those well defined interfaces identified in the file named EXCEPTION
+ found in the source code files (the "Approved Interfaces"). The files
+ of Non-GPL Code may instantiate templates or use macros or inline
+ functions from the Approved Interfaces without causing the resulting
+ work to be covered by the GNU General Public License. Only Red Hat,
+ Inc. may make changes or additions to the list of Approved Interfaces.
+ Red Hat's grant of this exception is conditioned upon your not adding
+ any new exceptions. If you wish to add a new Approved Interface or
+ exception, please contact Red Hat. You must obey the GNU General Public
+ License in all respects for all of the Red Hat elfutils code and other
+ code used in conjunction with Red Hat elfutils except the Non-GPL Code
+ covered by this exception. If you modify this file, you may extend this
+ exception to your version of the file, but you are not obligated to do
+ so. If you do not wish to provide this exception without modification,
+ you must delete this exception statement from your version and license
+ this file solely under the GPL without exception.
+
+ Red Hat elfutils is an included package of the Open Invention Network.
+ An included package of the Open Invention Network is a package for which
+ Open Invention Network licensees cross-license their patents. No patent
+ license is granted, either expressly or impliedly, by designation as an
+ included package. Should you wish to participate in the Open Invention
+ Network licensing program, please visit www.openinventionnetwork.com
+ <http://www.openinventionnetwork.com>. */
+
+#include "libdwflP.h"
+#include "system.h"
+
+#include <unistd.h>
+#include <endian.h>
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+# define LE16(x) (x)
+# define LE32(x) (x)
+#else
+# define LE16(x) bswap_16 (x)
+# define LE32(x) bswap_32 (x)
+#endif
+
+/* See Documentation/x86/boot.txt in Linux kernel sources
+ for an explanation of these format details. */
+
+#define MAGIC1 0xaa55
+#define MAGIC2 0x53726448 /* "HdrS" little-endian */
+#define MIN_VERSION 0x0208
+
+#define H_START (H_SETUP_SECTS & -4)
+#define H_SETUP_SECTS 0x1f1
+#define H_MAGIC1 0x1fe
+#define H_MAGIC2 0x202
+#define H_VERSION 0x206
+#define H_PAYLOAD_OFFSET 0x248
+#define H_PAYLOAD_LENGTH 0x24c
+#define H_END 0x250
+#define H_READ_SIZE (H_END - H_START)
+
+Dwfl_Error
+internal_function
+__libdw_image_header (int fd, off64_t *start_offset,
+ void *mapped, size_t mapped_size)
+{
+ if (likely (mapped_size > H_END))
+ {
+ const void *header = mapped;
+ char header_buffer[H_READ_SIZE];
+ if (header == NULL)
+ {
+ ssize_t n = pread_retry (fd, header_buffer, H_READ_SIZE,
+ *start_offset + H_START);
+ if (n < 0)
+ return DWFL_E_ERRNO;
+ if (n < H_READ_SIZE)
+ return DWFL_E_BADELF;
+
+ header = header_buffer - H_START;
+ }
+
+ if (*(uint16_t *) (header + H_MAGIC1) == LE16 (MAGIC1)
+ && *(uint32_t *) (header + H_MAGIC2) == LE32 (MAGIC2)
+ && LE16 (*(uint16_t *) (header + H_VERSION)) >= MIN_VERSION)
+ {
+ /* The magic numbers match and the version field is sufficient.
+ Extract the payload bounds. */
+
+ uint32_t offset = LE32 (*(uint32_t *) (header + H_PAYLOAD_OFFSET));
+ uint32_t length = LE32 (*(uint32_t *) (header + H_PAYLOAD_LENGTH));
+
+ offset += ((*(uint8_t *) (header + H_SETUP_SECTS) ?: 4) + 1) * 512;
+
+ if (offset > H_END && offset < mapped_size
+ && mapped_size - length >= offset)
+ {
+ /* It looks kosher. Use it! */
+ *start_offset += offset;
+ return DWFL_E_NOERROR;
+ }
+ }
+ }
+ return DWFL_E_BADELF;
+}
diff --git a/libdwfl/libdwflP.h b/libdwfl/libdwflP.h
index 0c52d25..d08d8a7 100644
--- a/libdwfl/libdwflP.h
+++ b/libdwfl/libdwflP.h
@@ -344,6 +344,11 @@
void **whole, size_t *whole_size)
internal_function;
+/* Skip the image header before a file image: updates *START_OFFSET. */
+extern Dwfl_Error __libdw_image_header (int fd, off64_t *start_offset,
+ void *mapped, size_t mapped_size)
+ internal_function;
+
/* Open Elf handle on *FDP. This handles decompression and checks
elf_kind. Succeed only for ELF_K_ELF, or also ELF_K_AR if ARCHIVE_OK.
Returns DWFL_E_NOERROR and sets *ELFP on success, resets *FDP to -1 if
diff --git a/libdwfl/open.c b/libdwfl/open.c
index e78eb21..397af35 100644
--- a/libdwfl/open.c
+++ b/libdwfl/open.c
@@ -65,8 +65,7 @@
# define __libdw_unlzma(...) false
#endif
-/* Always consumes *ELF, never consumes FD.
- Replaces *ELF on success. */
+/* Consumes and replaces *ELF only on success. */
static Dwfl_Error
decompress (int fd __attribute__ ((unused)), Elf **elf)
{
@@ -77,7 +76,7 @@
#if USE_ZLIB || USE_BZLIB || USE_LZMA
const off64_t offset = (*elf)->start_offset;
void *const mapped = ((*elf)->map_address == NULL ? NULL
- : (*elf)->map_address + (*elf)->start_offset);
+ : (*elf)->map_address + offset);
const size_t mapped_size = (*elf)->maximum_size;
if (mapped_size == 0)
return error;
@@ -89,9 +88,6 @@
error = __libdw_unlzma (fd, offset, mapped, mapped_size, &buffer, &size);
#endif
- elf_end (*elf);
- *elf = NULL;
-
if (error == DWFL_E_NOERROR)
{
if (unlikely (size == 0))
@@ -101,39 +97,86 @@
}
else
{
- *elf = elf_memory (buffer, size);
- if (*elf == NULL)
+ Elf *memelf = elf_memory (buffer, size);
+ if (memelf == NULL)
{
error = DWFL_E_LIBELF;
free (buffer);
}
else
- (*elf)->flags |= ELF_F_MALLOCED;
+ {
+ memelf->flags |= ELF_F_MALLOCED;
+ elf_end (*elf);
+ *elf = memelf;
+ }
}
}
+ else
+ free (buffer);
return error;
}
+static Dwfl_Error
+what_kind (int fd, Elf **elfp, Elf_Kind *kind, bool *close_fd)
+{
+ Dwfl_Error error = DWFL_E_NOERROR;
+ *kind = elf_kind (*elfp);
+ if (unlikely (*kind == ELF_K_NONE))
+ {
+ if (unlikely (*elfp == NULL))
+ error = DWFL_E_LIBELF;
+ else
+ {
+ error = decompress (fd, elfp);
+ if (error == DWFL_E_NOERROR)
+ {
+ *close_fd = true;
+ *kind = elf_kind (*elfp);
+ }
+ }
+ }
+ return error;
+}
+
Dwfl_Error internal_function
__libdw_open_file (int *fdp, Elf **elfp, bool close_on_fail, bool archive_ok)
{
bool close_fd = false;
- Dwfl_Error error = DWFL_E_NOERROR;
Elf *elf = elf_begin (*fdp, ELF_C_READ_MMAP_PRIVATE, NULL);
- Elf_Kind kind = elf_kind (elf);
- if (unlikely (kind == ELF_K_NONE))
+
+ Elf_Kind kind;
+ Dwfl_Error error = what_kind (*fdp, &elf, &kind, &close_fd);
+ if (error == DWFL_E_BADELF)
{
- if (unlikely (elf == NULL))
- error = DWFL_E_LIBELF;
- else
+ /* It's not an ELF file or a compressed file.
+ See if it's an image with a header preceding the real file. */
+
+ off64_t offset = elf->start_offset;
+ error = __libdw_image_header (*fdp, &offset,
+ (elf->map_address == NULL ? NULL
+ : elf->map_address + offset),
+ elf->maximum_size);
+ if (error == DWFL_E_NOERROR)
{
- error = decompress (*fdp, &elf);
- if (error == DWFL_E_NOERROR)
+ /* Pure evil. libelf needs some better interfaces. */
+ elf->kind = ELF_K_AR;
+ elf->state.ar.elf_ar_hdr.ar_name = "libdwfl is faking you out";
+ elf->state.ar.elf_ar_hdr.ar_size = elf->maximum_size - offset;
+ elf->state.ar.offset = offset - sizeof (struct ar_hdr);
+ Elf *subelf = elf_begin (-1, ELF_C_READ_MMAP_PRIVATE, elf);
+ elf->kind = ELF_K_NONE;
+ if (unlikely (subelf == NULL))
+ error = DWFL_E_LIBELF;
+ else
{
- close_fd = true;
- kind = elf_kind (elf);
+ subelf->parent = NULL;
+ subelf->flags |= elf->flags & (ELF_F_MMAPPED | ELF_F_MALLOCED);
+ elf->flags &= ~(ELF_F_MMAPPED | ELF_F_MALLOCED);
+ elf_end (elf);
+ elf = subelf;
+ error = what_kind (*fdp, &elf, &kind, &close_fd);
}
}
}
@@ -141,10 +184,12 @@
if (error == DWFL_E_NOERROR
&& kind != ELF_K_ELF
&& !(archive_ok && kind == ELF_K_AR))
+ error = DWFL_E_BADELF;
+
+ if (error != DWFL_E_NOERROR)
{
elf_end (elf);
elf = NULL;
- error = DWFL_E_BADELF;
}
if (error == DWFL_E_NOERROR ? close_fd : close_on_fail)