From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from smtp-out2.suse.de (smtp-out2.suse.de [195.135.220.29]) by sourceware.org (Postfix) with ESMTPS id A41E53858407 for ; Tue, 29 Nov 2022 12:05:46 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org A41E53858407 Authentication-Results: sourceware.org; dmarc=none (p=none dis=none) header.from=suse.cz Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=suse.cz Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by smtp-out2.suse.de (Postfix) with ESMTPS id D76FC1F38D; Tue, 29 Nov 2022 12:05:45 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_rsa; t=1669723545; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=haR/d4wxwyVwVIYXFaAISNr/97cImktuAJzos1QXUf4=; b=qlKsKei8f7eH6IXeFJEBK7ibeWwLnQeNqi14olHrPIW81MeLHaR9BRjUuD5XG1Iu8KROFM anVx+awogaGwTjolTAKgiYeBewnrC1sqMGW2fbKNq6++qWRVaWdalSyRFucZfSnbBLAiZf 8mIO+tuG+VYAVrZnmhd2kSzEUegHBhI= DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=suse.cz; s=susede2_ed25519; t=1669723545; h=from:from:reply-to:date:date:message-id:message-id:to:to:cc:cc: mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=haR/d4wxwyVwVIYXFaAISNr/97cImktuAJzos1QXUf4=; b=uoVuvCSixfGswx9lRoajpVwMxEtnDnoUHGWANJ2AfMnk79uMQJoZQpNP+86l7QkURXF41d 3pOtj6lh5Yf8kDDg== Received: from imap2.suse-dmz.suse.de (imap2.suse-dmz.suse.de [192.168.254.74]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-521) server-digest SHA512) (No client certificate requested) by imap2.suse-dmz.suse.de (Postfix) with ESMTPS id C3EB813428; Tue, 29 Nov 2022 12:05:45 +0000 (UTC) Received: from dovecot-director2.suse.de ([192.168.254.65]) by imap2.suse-dmz.suse.de with ESMTPSA id Ptr1Lpn1hWPLfgAAMHmgww (envelope-from ); Tue, 29 Nov 2022 12:05:45 +0000 Message-ID: Date: Tue, 29 Nov 2022 13:05:45 +0100 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.5.0 From: =?UTF-8?Q?Martin_Li=c5=a1ka?= Subject: [PATCHv2] support ZSTD compression algorithm To: elfutils-devel@sourceware.org References: <24d7165b-b8ac-ea5d-a046-aec2203696a9@suse.cz> Content-Language: en-US Cc: Mark Wielaard In-Reply-To: <24d7165b-b8ac-ea5d-a046-aec2203696a9@suse.cz> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Spam-Status: No, score=-11.9 required=5.0 tests=BAYES_00,DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,GIT_PATCH_0,SPF_HELO_NONE,SPF_PASS,TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org List-Id: Hi. There's second version of the patch that fully support both compression and decompression. Changes from the v1: - compression support added - zstd detection is fixed - new tests are added - builds fine w/ and w/o the ZSTD library What's currently missing and where I need a help: 1) When I build ./configure --without-zstd, I don't have a reasonable error message (something similar to binutils's readelf: readelf: Warning: section '.debug_str' has unsupported compress type: 2) even though, __libelf_decompress returns NULL and __libelf_seterrno). One can see a garbage in the console. How to handle that properly? 2) How should I run my newly added tests conditionally if zstd configuration support is enabled? Cheers, Martin --- configure.ac | 8 +- libelf/Makefile.am | 2 +- libelf/elf_compress.c | 294 ++++++++++++++++++++++++++++++------- libelf/elf_compress_gnu.c | 5 +- libelf/libelfP.h | 4 +- src/elfcompress.c | 144 ++++++++++-------- src/readelf.c | 18 ++- tests/run-compress-test.sh | 24 +++ 8 files changed, 373 insertions(+), 126 deletions(-) diff --git a/configure.ac b/configure.ac index 59be27ac..07cfa54b 100644 --- a/configure.ac +++ b/configure.ac @@ -410,6 +410,11 @@ dnl Test for bzlib and xz/lzma/zstd, gives BZLIB/LZMALIB/ZSTD .am dnl conditional and config.h USE_BZLIB/USE_LZMALIB/USE_ZSTD #define. save_LIBS="$LIBS" LIBS= +eu_ZIPLIB(zstd,ZSTD,zstd,ZSTD_decompress,[ZSTD (zst)]) +AS_IF([test "x$with_zstd" = xyes], [LIBZSTD="libzstd"], [LIBLZSTD=""]) +AC_SUBST([LIBZSTD]) +zstd_LIBS="$LIBS" +AC_SUBST([zstd_LIBS]) eu_ZIPLIB(bzlib,BZLIB,bz2,BZ2_bzdopen,bzip2) # We need this since bzip2 doesn't have a pkgconfig file. BZ2_LIB="$LIBS" @@ -417,9 +422,6 @@ AC_SUBST([BZ2_LIB]) eu_ZIPLIB(lzma,LZMA,lzma,lzma_auto_decoder,[LZMA (xz)]) AS_IF([test "x$with_lzma" = xyes], [LIBLZMA="liblzma"], [LIBLZMA=""]) AC_SUBST([LIBLZMA]) -eu_ZIPLIB(zstd,ZSTD,zstd,ZSTD_decompress,[ZSTD (zst)]) -AS_IF([test "x$with_zstd" = xyes], [LIBZSTD="libzstd"], [LIBLZSTD=""]) -AC_SUBST([LIBZSTD]) zip_LIBS="$LIBS" LIBS="$save_LIBS" AC_SUBST([zip_LIBS]) diff --git a/libelf/Makefile.am b/libelf/Makefile.am index 560ed45f..24c25cf8 100644 --- a/libelf/Makefile.am +++ b/libelf/Makefile.am @@ -106,7 +106,7 @@ libelf_pic_a_SOURCES = am_libelf_pic_a_OBJECTS = $(libelf_a_SOURCES:.c=.os) libelf_so_DEPS = ../lib/libeu.a -libelf_so_LDLIBS = $(libelf_so_DEPS) -lz +libelf_so_LDLIBS = $(libelf_so_DEPS) -lz $(zstd_LIBS) if USE_LOCKS libelf_so_LDLIBS += -lpthread endif diff --git a/libelf/elf_compress.c b/libelf/elf_compress.c index d7f53af2..7a6e37a4 100644 --- a/libelf/elf_compress.c +++ b/libelf/elf_compress.c @@ -39,6 +39,10 @@ #include #include +#ifdef USE_ZSTD +#include +#endif + /* Cleanup and return result. Don't leak memory. */ static void * do_deflate_cleanup (void *result, z_stream *z, void *out_buf, @@ -54,53 +58,14 @@ do_deflate_cleanup (void *result, z_stream *z, void *out_buf, #define deflate_cleanup(result, cdata) \ do_deflate_cleanup(result, &z, out_buf, cdata) -/* 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) +__libelf_compress_zlib (Elf_Scn *scn, size_t hsize, int ei_data, + size_t *orig_size, size_t *orig_addralign, + size_t *new_size, bool force, + Elf_Data *data, Elf_Data *next_data, + void *out_buf, size_t out_size, size_t block) { - /* 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; @@ -205,9 +170,186 @@ __libelf_compress (Elf_Scn *scn, size_t hsize, int ei_data, return out_buf; } +#ifdef USE_ZSTD +/* Cleanup and return result. Don't leak memory. */ +static void * +do_zstd_cleanup (void *result, ZSTD_CCtx * const cctx, void *out_buf, + Elf_Data *cdatap) +{ + ZSTD_freeCCtx (cctx); + free (out_buf); + if (cdatap != NULL) + free (cdatap->d_buf); + return result; +} + +#define zstd_cleanup(result, cdata) \ + do_zstd_cleanup(result, cctx, out_buf, cdata) + void * internal_function -__libelf_decompress (void *buf_in, size_t size_in, size_t size_out) +__libelf_compress_zstd (Elf_Scn *scn, size_t hsize, int ei_data, + size_t *orig_size, size_t *orig_addralign, + size_t *new_size, bool force, + Elf_Data *data, Elf_Data *next_data, + void *out_buf, size_t out_size, size_t block) +{ + /* Caller gets to fill in the header at the start. Just skip it here. */ + size_t used = hsize; + + ZSTD_CCtx* const cctx = ZSTD_createCCtx(); + Elf_Data cdata; + cdata.d_buf = NULL; + + /* Loop over data buffers. */ + ZSTD_EndDirective mode = ZSTD_e_continue; + + do + { + /* Convert to raw if different endianness. */ + cdata = *data; + bool convert = ei_data != MY_ELFDATA && data->d_size > 0; + if (convert) + { + /* 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 zstd_cleanup (NULL, NULL); + } + if (gelf_xlatetof (scn->elf, &cdata, data, ei_data) == NULL) + return zstd_cleanup (NULL, &cdata); + } + + ZSTD_inBuffer ib = { cdata.d_buf, cdata.d_size, 0 }; + + /* 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 + mode = ZSTD_e_end; + + /* Flush one data buffer. */ + for (;;) + { + ZSTD_outBuffer ob = { out_buf + used, out_size - used, 0 }; + size_t ret = ZSTD_compressStream2 (cctx, &ob, &ib, mode); + if (ZSTD_isError (ret)) + { + __libelf_seterrno (ELF_E_COMPRESS_ERROR); + return zstd_cleanup (NULL, convert ? &cdata : NULL); + } + used += ob.pos; + + /* 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 && mode == ZSTD_e_end && used >= *orig_size) + return zstd_cleanup ((void *) -1, convert ? &cdata : NULL); + + if (ret > 0) + { + void *bigger = realloc (out_buf, out_size + block); + if (bigger == NULL) + { + __libelf_seterrno (ELF_E_NOMEM); + return zstd_cleanup (NULL, convert ? &cdata : NULL); + } + out_buf = bigger; + out_size += block; + } + else + break; + } + + if (convert) + { + free (cdata.d_buf); + cdata.d_buf = NULL; + } + } + while (mode != ZSTD_e_end); /* More data blocks. */ + + ZSTD_freeCCtx (cctx); + *new_size = used; + return out_buf; +} +#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, bool use_zstd) +{ + /* 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. + Size estimation for ZSTD compression would be similar. */ + 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; + } + + if (use_zstd) +#ifdef USE_ZSTD + return __libelf_compress_zstd (scn, hsize, ei_data, orig_size, + orig_addralign, new_size, force, + data, next_data, out_buf, out_size, + block); +#else + return NULL; +#endif + else + return __libelf_compress_zlib (scn, hsize, ei_data, orig_size, + orig_addralign, new_size, force, + data, next_data, out_buf, out_size, + block); +} + +void * +internal_function +__libelf_decompress_zlib (void *buf_in, size_t size_in, size_t size_out) { /* Catch highly unlikely compression ratios so we don't allocate some giant amount of memory for nothing. The max compression @@ -218,7 +360,7 @@ __libelf_decompress (void *buf_in, size_t size_in, size_t size_out) return NULL; } - /* Malloc might return NULL when requestion zero size. This is highly + /* Malloc might return NULL when requesting zero size. This is highly unlikely, it would only happen when the compression was forced. But we do need a non-NULL buffer to return and set as result. Just make sure to always allocate at least 1 byte. */ @@ -260,6 +402,51 @@ __libelf_decompress (void *buf_in, size_t size_in, size_t size_out) return buf_out; } +#ifdef USE_ZSTD +void * +internal_function +__libelf_decompress_zstd (void *buf_in, size_t size_in, size_t size_out) +{ + /* Malloc might return NULL when requesting zero size. This is highly + unlikely, it would only happen when the compression was forced. + But we do need a non-NULL buffer to return and set as result. + Just make sure to always allocate at least 1 byte. */ + void *buf_out = malloc (size_out ?: 1); + if (unlikely (buf_out == NULL)) + { + __libelf_seterrno (ELF_E_NOMEM); + return NULL; + } + + size_t ret = ZSTD_decompress (buf_out, size_out, buf_in, size_in); + if (ZSTD_isError (ret)) + { + free (buf_out); + __libelf_seterrno (ELF_E_DECOMPRESS_ERROR); + return NULL; + } + else + return buf_out; +} +#endif + +void * +internal_function +__libelf_decompress (int chtype, void *buf_in, size_t size_in, size_t size_out) +{ + if (chtype == ELFCOMPRESS_ZLIB) + return __libelf_decompress_zlib (buf_in, size_in, size_out); + else + { +#ifdef USE_ZSTD + return __libelf_decompress_zstd (buf_in, size_in, size_out); +#else + __libelf_seterrno (ELF_E_DECOMPRESS_ERROR); + return NULL; +#endif + } +} + void * internal_function __libelf_decompress_elf (Elf_Scn *scn, size_t *size_out, size_t *addralign) @@ -268,7 +455,7 @@ __libelf_decompress_elf (Elf_Scn *scn, size_t *size_out, size_t *addralign) if (gelf_getchdr (scn, &chdr) == NULL) return NULL; - if (chdr.ch_type != ELFCOMPRESS_ZLIB) + if (chdr.ch_type != ELFCOMPRESS_ZLIB && chdr.ch_type != ELFCOMPRESS_ZSTD) { __libelf_seterrno (ELF_E_UNKNOWN_COMPRESSION_TYPE); return NULL; @@ -295,7 +482,9 @@ __libelf_decompress_elf (Elf_Scn *scn, size_t *size_out, size_t *addralign) ? 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); + void *buf_out + = __libelf_decompress (chdr.ch_type, buf_in, size_in, chdr.ch_size); + *size_out = chdr.ch_size; *addralign = chdr.ch_addralign; return buf_out; @@ -394,7 +583,7 @@ elf_compress (Elf_Scn *scn, int type, unsigned int flags) } int compressed = (sh_flags & SHF_COMPRESSED); - if (type == ELFCOMPRESS_ZLIB) + if (type == ELFCOMPRESS_ZLIB || type == ELFCOMPRESS_ZSTD) { /* Compress/Deflate. */ if (compressed == 1) @@ -408,7 +597,8 @@ elf_compress (Elf_Scn *scn, int type, unsigned int flags) size_t orig_size, orig_addralign, new_size; void *out_buf = __libelf_compress (scn, hsize, elfdata, &orig_size, &orig_addralign, - &new_size, force); + &new_size, force, + type == ELFCOMPRESS_ZSTD); /* Compression would make section larger, don't change anything. */ if (out_buf == (void *) -1) @@ -422,7 +612,7 @@ elf_compress (Elf_Scn *scn, int type, unsigned int flags) if (elfclass == ELFCLASS32) { Elf32_Chdr chdr; - chdr.ch_type = ELFCOMPRESS_ZLIB; + chdr.ch_type = type; chdr.ch_size = orig_size; chdr.ch_addralign = orig_addralign; if (elfdata != MY_ELFDATA) @@ -436,7 +626,7 @@ elf_compress (Elf_Scn *scn, int type, unsigned int flags) else { Elf64_Chdr chdr; - chdr.ch_type = ELFCOMPRESS_ZLIB; + chdr.ch_type = type; chdr.ch_reserved = 0; chdr.ch_size = orig_size; chdr.ch_addralign = sh_addralign; diff --git a/libelf/elf_compress_gnu.c b/libelf/elf_compress_gnu.c index 3d2977e7..8e20b30e 100644 --- a/libelf/elf_compress_gnu.c +++ b/libelf/elf_compress_gnu.c @@ -103,7 +103,8 @@ elf_compress_gnu (Elf_Scn *scn, int inflate, unsigned int flags) size_t orig_size, new_size, orig_addralign; void *out_buf = __libelf_compress (scn, hsize, elfdata, &orig_size, &orig_addralign, - &new_size, force); + &new_size, force, + /* use_zstd */ false); /* Compression would make section larger, don't change anything. */ if (out_buf == (void *) -1) @@ -178,7 +179,7 @@ elf_compress_gnu (Elf_Scn *scn, int inflate, unsigned int flags) size_t size = gsize; size_t size_in = data->d_size - hsize; void *buf_in = data->d_buf + hsize; - void *buf_out = __libelf_decompress (buf_in, size_in, size); + void *buf_out = __libelf_decompress (ELFCOMPRESS_ZLIB, buf_in, size_in, size); if (buf_out == NULL) return -1; diff --git a/libelf/libelfP.h b/libelf/libelfP.h index d88a613c..6624f38a 100644 --- a/libelf/libelfP.h +++ b/libelf/libelfP.h @@ -574,10 +574,10 @@ extern uint32_t __libelf_crc32 (uint32_t crc, unsigned char *buf, size_t len) extern void * __libelf_compress (Elf_Scn *scn, size_t hsize, int ei_data, size_t *orig_size, size_t *orig_addralign, - size_t *size, bool force) + size_t *size, bool force, bool use_zstd) internal_function; -extern void * __libelf_decompress (void *buf_in, size_t size_in, +extern void * __libelf_decompress (int chtype, void *buf_in, size_t size_in, size_t size_out) internal_function; extern void * __libelf_decompress_elf (Elf_Scn *scn, size_t *size_out, size_t *addralign) diff --git a/src/elfcompress.c b/src/elfcompress.c index eff765e8..b0f1677c 100644 --- a/src/elfcompress.c +++ b/src/elfcompress.c @@ -55,9 +55,10 @@ enum ch_type UNSET = -1, NONE, ZLIB, + ZSTD, /* Maximal supported ch_type. */ - MAXIMAL_CH_TYPE = ZLIB, + MAXIMAL_CH_TYPE = ZSTD, ZLIB_GNU = 1 << 16 }; @@ -139,6 +140,12 @@ parse_opt (int key, char *arg __attribute__ ((unused)), type = ZLIB; else if (strcmp ("zlib-gnu", arg) == 0 || strcmp ("gnu", arg) == 0) type = ZLIB_GNU; + else if (strcmp ("zstd", arg) == 0) +#ifdef USE_ZSTD + type = ZSTD; +#else + argp_error (state, N_("ZSTD support is not enabled")); +#endif else argp_error (state, N_("unknown compression type '%s'"), arg); break; @@ -281,6 +288,44 @@ get_sections (unsigned int *sections, size_t shnum) return s; } +/* Return compression type of a given section SHDR. */ + +static enum ch_type +get_section_chtype (Elf_Scn *scn, GElf_Shdr *shdr, const char *sname, + size_t ndx) +{ + enum ch_type chtype = UNSET; + if ((shdr->sh_flags & SHF_COMPRESSED) != 0) + { + GElf_Chdr chdr; + if (gelf_getchdr (scn, &chdr) != NULL) + { + chtype = (enum ch_type)chdr.ch_type; + if (chtype == NONE) + { + error (0, 0, "Compression type for section %zd" + " can't be zero ", ndx); + chtype = UNSET; + } + else if (chtype > MAXIMAL_CH_TYPE) + { + error (0, 0, "Compression type (%d) for section %zd" + " is unsupported ", chtype, ndx); + chtype = UNSET; + } + } + else + error (0, 0, "Couldn't get chdr for section %zd", ndx); + } + /* Set ZLIB_GNU compression manually for .zdebug* sections. */ + else if (startswith (sname, ".zdebug")) + chtype = ZLIB_GNU; + else + chtype = NONE; + + return chtype; +} + static int process_file (const char *fname) { @@ -461,26 +506,29 @@ process_file (const char *fname) if (section_name_matches (sname)) { - if (!force && type == NONE - && (shdr->sh_flags & SHF_COMPRESSED) == 0 - && !startswith (sname, ".zdebug")) - { - if (verbose > 0) - printf ("[%zd] %s already decompressed\n", ndx, sname); - } - else if (!force && type == ZLIB - && (shdr->sh_flags & SHF_COMPRESSED) != 0) - { - if (verbose > 0) - printf ("[%zd] %s already compressed\n", ndx, sname); - } - else if (!force && type == ZLIB_GNU - && startswith (sname, ".zdebug")) + enum ch_type schtype = get_section_chtype (scn, shdr, sname, ndx); + if (!force && verbose > 0) { - if (verbose > 0) - printf ("[%zd] %s already GNU compressed\n", ndx, sname); + /* The current compression matches the final one. */ + if (type == schtype) + switch (type) + { + case NONE: + printf ("[%zd] %s already decompressed\n", ndx, sname); + break; + case ZLIB: + case ZSTD: + printf ("[%zd] %s already compressed\n", ndx, sname); + break; + case ZLIB_GNU: + printf ("[%zd] %s already GNU compressed\n", ndx, sname); + break; + default: + abort (); + } } - else if (shdr->sh_type != SHT_NOBITS + + if (shdr->sh_type != SHT_NOBITS && (shdr->sh_flags & SHF_ALLOC) == 0) { set_section (sections, ndx); @@ -692,37 +740,12 @@ process_file (const char *fname) (de)compressed, invalidating the string pointers. */ sname = xstrdup (sname); + /* Detect source compression that is how is the section compressed now. */ - GElf_Chdr chdr; - enum ch_type schtype = NONE; - if ((shdr->sh_flags & SHF_COMPRESSED) != 0) - { - if (gelf_getchdr (scn, &chdr) != NULL) - { - schtype = (enum ch_type)chdr.ch_type; - if (schtype == NONE) - { - error (0, 0, "Compression type for section %zd" - " can't be zero ", ndx); - goto cleanup; - } - else if (schtype > MAXIMAL_CH_TYPE) - { - error (0, 0, "Compression type (%d) for section %zd" - " is unsupported ", schtype, ndx); - goto cleanup; - } - } - else - { - error (0, 0, "Couldn't get chdr for section %zd", ndx); - goto cleanup; - } - } - /* Set ZLIB compression manually for .zdebug* sections. */ - else if (startswith (sname, ".zdebug")) - schtype = ZLIB_GNU; + enum ch_type schtype = get_section_chtype (scn, shdr, sname, ndx); + if (schtype == UNSET) + goto cleanup; /* We might want to decompress (and rename), but not compress during this pass since we might need the section @@ -754,7 +777,7 @@ process_file (const char *fname) case ZLIB_GNU: if (startswith (sname, ".debug")) { - if (schtype == ZLIB) + if (schtype == ZLIB || schtype == ZSTD) { /* First decompress to recompress GNU style. Don't report even when verbose. */ @@ -818,19 +841,22 @@ process_file (const char *fname) break; case ZLIB: - if ((shdr->sh_flags & SHF_COMPRESSED) == 0) + case ZSTD: + if (schtype != type) { - if (schtype == ZLIB_GNU) + if (schtype != NONE) { - /* First decompress to recompress zlib style. - Don't report even when verbose. */ + /* Decompress first. */ if (compress_section (scn, size, sname, NULL, ndx, schtype, NONE, false) < 0) goto cleanup; - snamebuf[0] = '.'; - strcpy (&snamebuf[1], &sname[2]); - newname = snamebuf; + if (schtype == ZLIB_GNU) + { + snamebuf[0] = '.'; + strcpy (&snamebuf[1], &sname[2]); + newname = snamebuf; + } } if (skip_compress_section) @@ -838,7 +864,7 @@ process_file (const char *fname) if (ndx == shdrstrndx) { shstrtab_size = size; - shstrtab_compressed = ZLIB; + shstrtab_compressed = type; if (shstrtab_name != NULL || shstrtab_newname != NULL) { @@ -855,7 +881,7 @@ process_file (const char *fname) else { symtab_size = size; - symtab_compressed = ZLIB; + symtab_compressed = type; symtab_name = xstrdup (sname); symtab_newname = (newname == NULL ? NULL : xstrdup (newname)); @@ -1378,7 +1404,7 @@ main (int argc, char **argv) N_("Place (de)compressed output into FILE"), 0 }, { "type", 't', "TYPE", 0, - N_("What type of compression to apply. TYPE can be 'none' (decompress), 'zlib' (ELF ZLIB compression, the default, 'zlib-gabi' is an alias) or 'zlib-gnu' (.zdebug GNU style compression, 'gnu' is an alias)"), + N_("What type of compression to apply. TYPE can be 'none' (decompress), 'zlib' (ELF ZLIB compression, the default, 'zlib-gabi' is an alias), 'zlib-gnu' (.zdebug GNU style compression, 'gnu' is an alias) or 'zstd'"), 0 }, { "name", 'n', "SECTION", 0, N_("SECTION name to (de)compress, SECTION is an extended wildcard pattern (defaults to '.?(z)debug*')"), diff --git a/src/readelf.c b/src/readelf.c index cc3e0229..451f8400 100644 --- a/src/readelf.c +++ b/src/readelf.c @@ -1238,13 +1238,17 @@ get_visibility_type (int value) static const char * elf_ch_type_name (unsigned int code) { - if (code == 0) - return "NONE"; - - if (code == ELFCOMPRESS_ZLIB) - return "ZLIB"; - - return "UNKNOWN"; + switch (code) + { + case 0: + return "NONE"; + case ELFCOMPRESS_ZLIB: + return "ZLIB"; + case ELFCOMPRESS_ZSTD: + return "ZSTD"; + default: + return "UNKNOWN"; + } } /* Print the section headers. */ diff --git a/tests/run-compress-test.sh b/tests/run-compress-test.sh index a6a298f5..3f9c990e 100755 --- a/tests/run-compress-test.sh +++ b/tests/run-compress-test.sh @@ -61,6 +61,30 @@ testrun_elfcompress_file() echo "uncompress $elfcompressedfile -> $elfuncompressedfile" testrun ${abs_top_builddir}/src/elfcompress -v -t none -o ${elfuncompressedfile} ${elfcompressedfile} testrun ${abs_top_builddir}/src/elfcmp ${uncompressedfile} ${elfuncompressedfile} + + outputfile="${infile}.gabi.zstd" + tempfiles "$outputfile" + echo "zstd compress $elfcompressedfile -> $outputfile" + testrun ${abs_top_builddir}/src/elfcompress -v -t zstd -o ${outputfile} ${elfcompressedfile} + testrun ${abs_top_builddir}/src/elfcmp ${uncompressedfile} ${outputfile} + echo "checking compressed section header" $outputfile + testrun ${abs_top_builddir}/src/readelf -Sz ${outputfile} | grep "ELF ZSTD" >/dev/null + + zstdfile="${infile}.zstd" + tempfiles "$zstdfile" + echo "zstd compress $uncompressedfile -> $zstdfile" + testrun ${abs_top_builddir}/src/elfcompress -v -t zstd -o ${zstdfile} ${elfuncompressedfile} + testrun ${abs_top_builddir}/src/elfcmp ${uncompressedfile} ${zstdfile} + echo "checking compressed section header" $zstdfile + testrun ${abs_top_builddir}/src/readelf -Sz ${zstdfile} | grep "ELF ZSTD" >/dev/null + + zstdgnufile="${infile}.zstd.gnu" + tempfiles "$zstdgnufile" + echo "zstd re-compress to GNU ZLIB $zstdfile -> $zstdgnufile" + testrun ${abs_top_builddir}/src/elfcompress -v -t zlib-gnu -o ${zstdgnufile} ${zstdfile} + testrun ${abs_top_builddir}/src/elfcmp ${uncompressedfile} ${zstdgnufile} + echo "checking .zdebug section name" $zstdgnufile + testrun ${abs_top_builddir}/src/readelf -S ${zstdgnufile} | grep ".zdebug" >/dev/null } testrun_elfcompress() -- 2.38.1