summaryrefslogtreecommitdiffstats
path: root/libelf/elf_compress.c
diff options
context:
space:
mode:
authorMark Wielaard <[email protected]>2015-12-19 01:29:54 +0100
committerMark Wielaard <[email protected]>2016-01-06 14:27:10 +0100
commit272018bba1f253bae00b5ba280ad0e0f18c04006 (patch)
treef0b20b43f9caf6d193ae9c05f5583699f34bc59d /libelf/elf_compress.c
parentb7105b40ccd73a8e6b7fce6c11d2088eb1298b33 (diff)
libelf: Add elf_compress and elf_compress_gnu.
Signed-off-by: Mark Wielaard <[email protected]>
Diffstat (limited to 'libelf/elf_compress.c')
-rw-r--r--libelf/elf_compress.c490
1 files changed, 490 insertions, 0 deletions
diff --git a/libelf/elf_compress.c b/libelf/elf_compress.c
new file mode 100644
index 00000000..17b80f2d
--- /dev/null
+++ b/libelf/elf_compress.c
@@ -0,0 +1,490 @@
+/* Compress or decompress a section.
+ Copyright (C) 2015 Red Hat, Inc.
+ This file is part of elfutils.
+
+ This file is free software; you can redistribute it and/or modify
+ it under the terms of either
+
+ * the GNU Lesser General Public License as published by the Free
+ Software Foundation; either version 3 of the License, or (at
+ your option) any later version
+
+ or
+
+ * the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at
+ your option) any later version
+
+ or both in parallel, as here.
+
+ 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 copies of the GNU General Public License and
+ the GNU Lesser General Public License along with this program. If
+ not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <libelf.h>
+#include "libelfP.h"
+#include "common.h"
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <zlib.h>
+
+#ifndef MAX
+# define MAX(a, b) ((a) > (b) ? (a) : (b))
+#endif
+
+/* Given a section, uses the (in-memory) Elf_Data to extract the
+ original data size (including the given header size) and data
+ alignment. Returns a buffer that has at least hsize bytes (for the
+ caller to fill in with a header) plus zlib compressed date. Also
+ returns the new buffer size in new_size (hsize + compressed data
+ size). Returns (void *) -1 when FORCE is false and the compressed
+ data would be bigger than the original data. */
+void *
+internal_function
+__libelf_compress (Elf_Scn *scn, size_t hsize, int ei_data,
+ size_t *orig_size, size_t *orig_addralign,
+ size_t *new_size, bool force)
+{
+ /* The compressed data is the on-disk data. We simplify the
+ implementation a bit by asking for the (converted) in-memory
+ data (which might be all there is if the user created it with
+ elf_newdata) and then convert back to raw if needed before
+ compressing. Should be made a bit more clever to directly
+ use raw if that is directly available. */
+ Elf_Data *data = elf_getdata (scn, NULL);
+ if (data == NULL)
+ return NULL;
+
+ /* When not forced and we immediately know we would use more data by
+ compressing, because of the header plus zlib overhead (five bytes
+ per 16 KB block, plus a one-time overhead of six bytes for the
+ entire stream), don't do anything. */
+ Elf_Data *next_data = elf_getdata (scn, data);
+ if (next_data == NULL && !force
+ && data->d_size <= hsize + 5 + 6)
+ return (void *) -1;
+
+ *orig_addralign = data->d_align;
+ *orig_size = data->d_size;
+
+ /* Guess an output block size. 1/8th of the original Elf_Data plus
+ hsize. Make the first chunk twice that size (25%), then increase
+ by a block (12.5%) when necessary. */
+ size_t block = (data->d_size / 8) + hsize;
+ size_t out_size = 2 * block;
+ void *out_buf = malloc (out_size);
+ if (out_buf == NULL)
+ {
+ __libelf_seterrno (ELF_E_NOMEM);
+ return NULL;
+ }
+
+ /* Caller gets to fill in the header at the start. Just skip it here. */
+ size_t used = hsize;
+
+ z_stream z;
+ z.zalloc = Z_NULL;
+ z.zfree = Z_NULL;
+ z.opaque = Z_NULL;
+ int zrc = deflateInit (&z, Z_BEST_COMPRESSION);
+ if (zrc != Z_OK)
+ {
+ __libelf_seterrno (ELF_E_COMPRESS_ERROR);
+ return NULL;
+ }
+
+ Elf_Data cdata;
+ cdata.d_buf = NULL;
+
+ /* Cleanup and return result. Don't leak memory. */
+ void *deflate_cleanup (void *result)
+ {
+ deflateEnd (&z);
+ free (out_buf);
+ if (ei_data != MY_ELFDATA)
+ free (cdata.d_buf);
+ return result;
+ }
+
+ /* Loop over data buffers. */
+ int flush = Z_NO_FLUSH;
+ do
+ {
+ /* Convert to raw if different endianess. */
+ cdata = *data;
+ if (ei_data != MY_ELFDATA)
+ {
+ /* Don't do this conversion in place, we might want to keep
+ the original data around, caller decides. */
+ cdata.d_buf = malloc (data->d_size);
+ if (cdata.d_buf == NULL)
+ {
+ __libelf_seterrno (ELF_E_NOMEM);
+ return deflate_cleanup (NULL);
+ }
+ if (gelf_xlatetof (scn->elf, &cdata, data, ei_data) == NULL)
+ return deflate_cleanup (NULL);
+ }
+
+ z.avail_in = cdata.d_size;
+ z.next_in = cdata.d_buf;
+
+ /* Get next buffer to see if this is the last one. */
+ data = next_data;
+ if (data != NULL)
+ {
+ *orig_addralign = MAX (*orig_addralign, data->d_align);
+ *orig_size += data->d_size;
+ next_data = elf_getdata (scn, data);
+ }
+ else
+ flush = Z_FINISH;
+
+ /* Flush one data buffer. */
+ do
+ {
+ z.avail_out = out_size - used;
+ z.next_out = out_buf + used;
+ zrc = deflate (&z, flush);
+ if (zrc == Z_STREAM_ERROR)
+ {
+ __libelf_seterrno (ELF_E_COMPRESS_ERROR);
+ return deflate_cleanup (NULL);
+ }
+ used += (out_size - used) - z.avail_out;
+
+ /* Bail out if we are sure the user doesn't want the
+ compression forced and we are using more compressed data
+ than original data. */
+ if (!force && flush == Z_FINISH && used >= *orig_size)
+ return deflate_cleanup ((void *) -1);
+
+ if (z.avail_out == 0)
+ {
+ void *bigger = realloc (out_buf, out_size + block);
+ if (bigger == NULL)
+ {
+ __libelf_seterrno (ELF_E_NOMEM);
+ return deflate_cleanup (NULL);
+ }
+ out_buf = bigger;
+ out_size += block;
+ }
+ }
+ while (z.avail_out == 0); /* Need more output buffer. */
+
+ if (ei_data != MY_ELFDATA)
+ {
+ free (cdata.d_buf);
+ cdata.d_buf = NULL;
+ }
+ }
+ while (flush != Z_FINISH); /* More data blocks. */
+
+ zrc = deflateEnd (&z);
+ if (zrc != Z_OK)
+ {
+ __libelf_seterrno (ELF_E_COMPRESS_ERROR);
+ return deflate_cleanup (NULL);
+ }
+
+ *new_size = used;
+ return out_buf;
+}
+
+void *
+internal_function
+__libelf_decompress (void *buf_in, size_t size_in, size_t size_out)
+{
+ void *buf_out = malloc (size_out);
+ if (unlikely (buf_out == NULL))
+ {
+ __libelf_seterrno (ELF_E_NOMEM);
+ return NULL;
+ }
+
+ z_stream z =
+ {
+ .next_in = buf_in,
+ .avail_in = size_in,
+ .next_out = buf_out,
+ .avail_out = size_out
+ };
+ int zrc = inflateInit (&z);
+ while (z.avail_in > 0 && likely (zrc == Z_OK))
+ {
+ z.next_out = buf_out + (size_out - z.avail_out);
+ zrc = inflate (&z, Z_FINISH);
+ if (unlikely (zrc != Z_STREAM_END))
+ {
+ zrc = Z_DATA_ERROR;
+ break;
+ }
+ zrc = inflateReset (&z);
+ }
+ if (likely (zrc == Z_OK))
+ zrc = inflateEnd (&z);
+
+ if (unlikely (zrc != Z_OK) || unlikely (z.avail_out != 0))
+ {
+ free (buf_out);
+ __libelf_seterrno (ELF_E_DECOMPRESS_ERROR);
+ return NULL;
+ }
+
+ return buf_out;
+}
+
+void
+internal_function
+__libelf_reset_rawdata (Elf_Scn *scn, void *buf, size_t size, size_t align,
+ Elf_Type type)
+{
+ /* This is the new raw data, replace and possibly free old data. */
+ scn->rawdata.d.d_off = 0;
+ scn->rawdata.d.d_version = __libelf_version;
+ scn->rawdata.d.d_buf = buf;
+ scn->rawdata.d.d_size = size;
+ scn->rawdata.d.d_align = align;
+ scn->rawdata.d.d_type = type;
+
+ /* Existing existing data is no longer valid. */
+ scn->data_list_rear = NULL;
+ if (scn->data_base != scn->rawdata_base)
+ free (scn->data_base);
+ scn->data_base = NULL;
+ if (scn->elf->map_address == NULL
+ || scn->rawdata_base == scn->zdata_base)
+ free (scn->rawdata_base);
+
+ scn->rawdata_base = buf;
+}
+
+int
+elf_compress (Elf_Scn *scn, int type, unsigned int flags)
+{
+ if (scn == NULL)
+ return -1;
+
+ if ((flags & ~ELF_CHF_FORCE) != 0)
+ {
+ __libelf_seterrno (ELF_E_INVALID_OPERAND);
+ return -1;
+ }
+
+ bool force = (flags & ELF_CHF_FORCE) != 0;
+
+ Elf *elf = scn->elf;
+ GElf_Ehdr ehdr;
+ if (gelf_getehdr (elf, &ehdr) == NULL)
+ return -1;
+
+ int elfclass = elf->class;
+ int elfdata = ehdr.e_ident[EI_DATA];
+
+ Elf64_Xword sh_flags;
+ Elf64_Word sh_type;
+ Elf64_Xword sh_addralign;
+ if (elfclass == ELFCLASS32)
+ {
+ Elf32_Shdr *shdr = elf32_getshdr (scn);
+ if (shdr == NULL)
+ return -1;
+
+ sh_flags = shdr->sh_flags;
+ sh_type = shdr->sh_type;
+ sh_addralign = shdr->sh_addralign;
+ }
+ else
+ {
+ Elf64_Shdr *shdr = elf64_getshdr (scn);
+ if (shdr == NULL)
+ return -1;
+
+ sh_flags = shdr->sh_flags;
+ sh_type = shdr->sh_type;
+ sh_addralign = shdr->sh_addralign;
+ }
+
+ if ((sh_flags & SHF_ALLOC) != 0)
+ {
+ __libelf_seterrno (ELF_E_INVALID_SECTION_FLAGS);
+ return -1;
+ }
+
+ if (sh_type == SHT_NULL || sh_type == SHT_NOBITS)
+ {
+ __libelf_seterrno (ELF_E_INVALID_SECTION_TYPE);
+ return -1;
+ }
+
+ int compressed = (sh_flags & SHF_COMPRESSED);
+ if (type == ELFCOMPRESS_ZLIB)
+ {
+ /* Compress/Deflate. */
+ if (compressed == 1)
+ {
+ __libelf_seterrno (ELF_E_ALREADY_COMPRESSED);
+ return -1;
+ }
+
+ size_t hsize = (elfclass == ELFCLASS32
+ ? sizeof (Elf32_Chdr) : sizeof (Elf64_Chdr));
+ size_t orig_size, orig_addralign, new_size;
+ void *out_buf = __libelf_compress (scn, hsize, elfdata,
+ &orig_size, &orig_addralign,
+ &new_size, force);
+
+ /* Compression would make section larger, don't change anything. */
+ if (out_buf == (void *) -1)
+ return 0;
+
+ /* Compression failed, return error. */
+ if (out_buf == NULL)
+ return -1;
+
+ /* Put the header in front of the data. */
+ if (elfclass == ELFCLASS32)
+ {
+ Elf32_Chdr chdr;
+ chdr.ch_type = ELFCOMPRESS_ZLIB;
+ chdr.ch_size = orig_size;
+ chdr.ch_addralign = orig_addralign;
+ if (elfdata != MY_ELFDATA)
+ {
+ CONVERT (chdr.ch_type);
+ CONVERT (chdr.ch_size);
+ CONVERT (chdr.ch_addralign);
+ }
+ memcpy (out_buf, &chdr, sizeof (Elf32_Chdr));
+ }
+ else
+ {
+ Elf64_Chdr chdr;
+ chdr.ch_type = ELFCOMPRESS_ZLIB;
+ chdr.ch_reserved = 0;
+ chdr.ch_size = orig_size;
+ chdr.ch_addralign = sh_addralign;
+ if (elfdata != MY_ELFDATA)
+ {
+ CONVERT (chdr.ch_type);
+ CONVERT (chdr.ch_reserved);
+ CONVERT (chdr.ch_size);
+ CONVERT (chdr.ch_addralign);
+ }
+ memcpy (out_buf, &chdr, sizeof (Elf64_Chdr));
+ }
+
+ /* Note we keep the sh_entsize as is, we assume it is setup
+ correctly and ignored when SHF_COMPRESSED is set. */
+ if (elfclass == ELFCLASS32)
+ {
+ Elf32_Shdr *shdr = elf32_getshdr (scn);
+ shdr->sh_size = new_size;
+ shdr->sh_addralign = 1;
+ shdr->sh_flags |= SHF_COMPRESSED;
+ }
+ else
+ {
+ Elf64_Shdr *shdr = elf64_getshdr (scn);
+ shdr->sh_size = new_size;
+ shdr->sh_addralign = 1;
+ shdr->sh_flags |= SHF_COMPRESSED;
+ }
+
+ __libelf_reset_rawdata (scn, out_buf, new_size, 1, ELF_T_CHDR);
+
+ /* The section is now compressed, we could keep the uncompressed
+ data around, but since that might have been multiple Elf_Data
+ buffers let the user uncompress it explicitly again if they
+ want it to simplify bookkeeping. */
+ scn->zdata_base = NULL;
+
+ return 1;
+ }
+ else if (type == 0)
+ {
+ /* Decompress/Inflate. */
+ if (compressed == 0)
+ {
+ __libelf_seterrno (ELF_E_NOT_COMPRESSED);
+ return -1;
+ }
+
+ GElf_Chdr chdr;
+ if (gelf_getchdr (scn, &chdr) == NULL)
+ return -1;
+
+ if (chdr.ch_type != ELFCOMPRESS_ZLIB)
+ {
+ __libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
+ return -1;
+ }
+
+ if (! powerof2 (chdr.ch_addralign))
+ {
+ __libelf_seterrno (ELF_E_INVALID_ALIGN);
+ return -1;
+ }
+
+ /* Take the in-memory representation, so we can even handle a
+ section that has just been constructed (maybe it was copied
+ over from some other ELF file first with elf_newdata). This
+ is slightly inefficient when the raw data needs to be
+ converted since then we'll be converting the whole buffer and
+ not just Chdr. */
+ Elf_Data *data = elf_getdata (scn, NULL);
+ if (data == NULL)
+ return -1;
+
+ size_t hsize = (elfclass == ELFCLASS32
+ ? sizeof (Elf32_Chdr) : sizeof (Elf64_Chdr));
+ size_t size_in = data->d_size - hsize;
+ void *buf_in = data->d_buf + hsize;
+ void *buf_out = __libelf_decompress (buf_in, size_in, chdr.ch_size);
+ if (buf_out == NULL)
+ return -1;
+
+ /* Note we keep the sh_entsize as is, we assume it is setup
+ correctly and ignored when SHF_COMPRESSED is set. */
+ if (elfclass == ELFCLASS32)
+ {
+ Elf32_Shdr *shdr = elf32_getshdr (scn);
+ shdr->sh_size = chdr.ch_size;
+ shdr->sh_addralign = chdr.ch_addralign;
+ shdr->sh_flags &= ~SHF_COMPRESSED;
+ }
+ else
+ {
+ Elf64_Shdr *shdr = elf64_getshdr (scn);
+ shdr->sh_size = chdr.ch_size;
+ shdr->sh_addralign = chdr.ch_addralign;
+ shdr->sh_flags &= ~SHF_COMPRESSED;
+ }
+
+ __libelf_reset_rawdata (scn, buf_out, chdr.ch_size, chdr.ch_addralign,
+ __libelf_data_type (elf, sh_type));
+
+ scn->zdata_base = buf_out;
+
+ return 1;
+ }
+ else
+ {
+ __libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE);
+ return -1;
+ }
+}