Externalize zlib dependency

== DETAILS

- define an interface/abstract class for zlib implementation
- update the code to reference the implementation, remove zlib dependency
- use constructor injection to provide implementations to elfio
- add a test to validate the injected object gets used properly
`
This commit is contained in:
Nathan Strong 2022-10-31 00:29:46 -07:00 committed by Serge Lamikhov-Center
parent 04985836db
commit 7608269069
5 changed files with 112 additions and 95 deletions

View File

@ -63,11 +63,16 @@ class elfio
{
public:
//------------------------------------------------------------------------------
elfio() noexcept : sections( this ), segments( this )
elfio() noexcept : sections( this ), segments( this ), zlib( nullptr )
{
create( ELFCLASS32, ELFDATA2LSB );
}
elfio( wiiu_zlib_interface *zlib ) noexcept : sections( this ), segments( this ), zlib( std::shared_ptr<wiiu_zlib_interface>(zlib) )
{
elfio();
}
elfio( elfio&& other ) noexcept
: sections( this ), segments( this ),
current_file_pos( other.current_file_pos )
@ -77,10 +82,12 @@ class elfio
segments_ = std::move( other.segments_ );
convertor = std::move( other.convertor );
addr_translator = std::move( other.addr_translator );
zlib = std::move( other.zlib );
other.header = nullptr;
other.sections_.clear();
other.segments_.clear();
other.zlib = nullptr;
}
elfio& operator=( elfio&& other ) noexcept
@ -92,9 +99,11 @@ class elfio
convertor = std::move( other.convertor );
addr_translator = std::move( other.addr_translator );
current_file_pos = other.current_file_pos;
zlib = std::move( other.zlib );
other.current_file_pos = 0;
other.header = nullptr;
other.zlib = nullptr;
other.sections_.clear();
other.segments_.clear();
}
@ -405,11 +414,11 @@ class elfio
if ( file_class == ELFCLASS64 ) {
sections_.emplace_back(
new section_impl<Elf64_Shdr>( &convertor, &addr_translator ) );
new section_impl<Elf64_Shdr>( &convertor, &addr_translator, zlib ) );
}
else if ( file_class == ELFCLASS32 ) {
sections_.emplace_back(
new section_impl<Elf32_Shdr>( &convertor, &addr_translator ) );
new section_impl<Elf32_Shdr>( &convertor, &addr_translator, zlib ) );
}
else {
sections_.pop_back();
@ -1054,6 +1063,7 @@ class elfio
std::vector<std::unique_ptr<segment>> segments_;
endianess_convertor convertor;
address_translator addr_translator;
std::shared_ptr<wiiu_zlib_interface> zlib = nullptr;
Elf_Xword current_file_pos = 0;
};

View File

@ -28,8 +28,6 @@ THE SOFTWARE.
#include <new>
#include <limits>
#include <zlib.h>
namespace ELFIO {
class section
@ -76,8 +74,9 @@ template <class T> class section_impl : public section
public:
//------------------------------------------------------------------------------
section_impl( const endianess_convertor* convertor,
const address_translator* translator )
: convertor( convertor ), translator( translator )
const address_translator* translator,
const std::shared_ptr<wiiu_zlib_interface> &zlib )
: convertor( convertor ), translator( translator ), zlib(zlib)
{
}
@ -216,55 +215,30 @@ template <class T> class section_impl : public section
if ( ( 0 != size ) && ( nullptr != data ) ) {
stream.seekg(
( *translator )[( *convertor )( header.sh_offset )] );
if(get_flags() & SHF_RPX_DEFLATE) {
Elf_Xword uncompressed_size = 0;
fixup_size(stream, size, uncompressed_size);
stream.read( data.get(), size );
if ( static_cast<Elf_Xword>( stream.gcount() ) != size ) {
data = nullptr;
return false;
}
// reallocate data to be the correct size
data.reset( new (std::nothrow) char[size_t(uncompressed_size) + 1]);
// create a buffer to hold the compressed bits
auto compressed_data = std::unique_ptr<char[]>(new char[size_t(size)]);
if( data == nullptr || compressed_data == nullptr) {
std::cerr << "failed to allocate memory buffers for decompression" << std::endl;
if(get_flags() & SHF_RPX_DEFLATE) {
if(zlib == nullptr) {
std::cerr << "WARN: compressed section found but no zlib implementation provided. Skipping." << std::endl;
data = nullptr;
return false;
}
// at this point, data holds the whole compressed stream
Elf_Xword uncompressed_size = 0;
auto decompressed_data = zlib->inflate(data.get(), convertor, size, uncompressed_size);
if(decompressed_data == nullptr) {
std::cerr << "Failed to decompress section data." << std::endl;
data = nullptr;
return false;
}
set_size(uncompressed_size);
// read rest of data into data buffer
stream.read( compressed_data.get(), size);
z_stream s = { 0 };
int z_result = 0;
s.zalloc = Z_NULL;
s.zfree = Z_NULL;
s.opaque = Z_NULL;
if(Z_OK != (z_result = inflateInit_(&s, ZLIB_VERSION, sizeof(s)))) {
std::cerr << "error initializing zlib: " << z_result << std::endl;
data = nullptr;
return false;
}
s.avail_in = size;
s.next_in = (Bytef *)compressed_data.get();
s.avail_out = uncompressed_size;
s.next_out = (Bytef *)data.get();
z_result = inflate(&s, Z_FINISH);
inflateEnd(&s);
if (z_result != Z_OK && z_result != Z_STREAM_END) {
std::cerr << "error decompressing section: " << z_result << std::endl;
data = nullptr;
return false;
}
} else {
stream.read( data.get(), size );
if ( static_cast<Elf_Xword>( stream.gcount() ) != size ) {
data = nullptr;
return false;
}
data = std::move(decompressed_data);
}
// refresh size because it may have changed if we had to decompress data
size = get_size();
@ -298,14 +272,6 @@ template <class T> class section_impl : public section
//------------------------------------------------------------------------------
private:
void fixup_size(std::istream& stream, Elf_Xword& compressed_size, Elf_Xword& uncompressed_size) {
uint32_t tmp = 0;
stream.read((char *)&tmp, 4);
tmp = (*convertor)(tmp);
uncompressed_size = static_cast<Elf_Xword>(tmp);
compressed_size = get_size() - 4;
}
//------------------------------------------------------------------------------
void save_header( std::ostream& stream, std::streampos header_offset ) const
{
@ -319,9 +285,10 @@ template <class T> class section_impl : public section
{
adjust_stream_size( stream, data_offset );
if(get_flags() & SHF_RPX_DEFLATE) {
Elf_Xword compressed_size = get_size();
auto compressed_ptr = compress_data(compressed_size);
if( (get_flags() & SHF_RPX_DEFLATE) && zlib != nullptr) {
Elf_Xword decompressed_size = get_size();
Elf_Xword compressed_size = 0;
auto compressed_ptr = zlib->deflate(data.get(), convertor, decompressed_size, compressed_size);
stream.write( compressed_ptr.get(), compressed_size);
} else {
stream.write( get_data(), get_size() );
@ -330,44 +297,16 @@ template <class T> class section_impl : public section
//------------------------------------------------------------------------------
private:
std::unique_ptr<char[]> compress_data(Elf_Xword &size) const {
auto compressed = std::unique_ptr<char[]>(new char[size]);
int z_result = 0;
z_stream s = { 0 };
s.zalloc = Z_NULL;
s.zfree = Z_NULL;
s.opaque = Z_NULL;
if(Z_OK != (z_result = deflateInit(&s, Z_DEFAULT_COMPRESSION))) {
std::cerr << "failed to init zlib for compression: " << z_result << std::endl;
return nullptr;
}
s.avail_in = size;
s.next_in = (Bytef *)data.get();
s.avail_out = size;
s.next_out = (Bytef *)compressed.get();
z_result = deflate(&s, Z_FINISH);
if(z_result != Z_OK && z_result != Z_STREAM_END) {
std::cerr << "deflate failed: " << z_result << std::endl;
deflateEnd(&s);
return nullptr;
}
size = size - s.avail_out;
deflateEnd(&s);
return compressed;
}
T header = { 0 };
Elf_Half index = 0;
std::string name;
std::unique_ptr<char[]> data;
Elf_Word data_size = 0;
const endianess_convertor* convertor = nullptr;
const address_translator* translator = nullptr;
bool is_address_set = false;
size_t stream_size = 0;
Elf_Word data_size = 0;
const endianess_convertor* convertor = nullptr;
const address_translator* translator = nullptr;
const std::shared_ptr<wiiu_zlib_interface> zlib = nullptr;
bool is_address_set = false;
size_t stream_size = 0;
};
} // namespace ELFIO

View File

@ -260,6 +260,37 @@ inline void adjust_stream_size( std::ostream& stream, std::streamsize offset )
stream.seekp( offset );
}
/**
* Consumers should write an implementation of this class and pass an instance of it to the ELFIO::elfio constructor.
*/
class wiiu_zlib_interface
{
public:
virtual ~wiiu_zlib_interface() = default;
/**
* decompresses a RPX/RPL zlib-compressed section.
*
* @param data the buffer of compressed data
* @param endianness_convertor pointer to an endianness_convertor instance, used to convert numbers to/from the target endianness.
* @param compressed_size the size of the data buffer, in bytes
* @param decompressed_size a reference to a variable where the decompressed buffer size will be stored.
* @returns a smart pointer to the decompressed data.
*/
virtual std::unique_ptr<char[]> inflate(const char *data, const endianess_convertor *convertor, Elf_Xword compressed_size, Elf_Xword &uncompressed_size) const = 0;
/**
* compresses a RPX/RPL zlib-compressed section.
*
* @param data the buffer of uncompressed data
* @param endianness_convertor pointer to an endianness_convertor instance, used to convert numbers to/from the target endianness.
* @param decompressed_size the size of the data buffer, in bytes
* @param compressed_size a reference to a variable where the compressed buffer size will be stored.
* @returns a smart pointer to the compressed data.
*/
virtual std::unique_ptr<char[]> deflate(const char *data, const endianess_convertor *convertor, Elf_Xword decompressed_size, Elf_Xword &compressed_size) const = 0;
};
} // namespace ELFIO
#endif // ELFIO_UTILS_HPP

View File

@ -27,6 +27,7 @@ THE SOFTWARE.
#include <gtest/gtest.h>
#include <elfio/elfio.hpp>
#include <elfio/elfio_utils.hpp>
using namespace ELFIO;
@ -970,6 +971,42 @@ TEST( ELFIOTest, test_dynamic_64_2 )
EXPECT_EQ( value, 0 );
}
class mock_wiiu_zlib : public wiiu_zlib_interface {
public:
std::unique_ptr<char[]> inflate(const char *data, const endianess_convertor *convertor, Elf_Xword compressed_size, Elf_Xword &uncompressed_size) const {
uncompressed_size = 2 * compressed_size;
return std::unique_ptr<char[]>(new char[uncompressed_size+1]);
}
std::unique_ptr<char[]> deflate(const char *data, const endianess_convertor *convertor, Elf_Xword decompressed_size, Elf_Xword &compressed_size) const {
compressed_size = decompressed_size / 2;
return std::unique_ptr<char[]>(new char[compressed_size+1]);
}
};
////////////////////////////////////////////////////////////////////////////////
// Given: a valid RPX file
// When: we load it with no zlib implementation
// Then: the size returns the raw section size (compressed size)
// When: we load it with a mock zlib implementation
// Then: the size changes to reflect the mock zlib implementation is being called
//
// This test does not do any further validation because doing so would require providing
// a real zlib implementation
TEST( ELFIOTest, test_rpx )
{
elfio reader(new mock_wiiu_zlib());
elfio reader_no_zlib;
ASSERT_EQ( reader_no_zlib.load( "elf_examples/helloworld.rpx" ), true );
section *text1 = reader_no_zlib.sections[1];
EXPECT_EQ( text1->get_size(), 36744);
ASSERT_EQ( reader.load( "elf_examples/helloworld.rpx" ), true );
section *text2 = reader.sections[1];
EXPECT_EQ(text2->get_size(), text1->get_size() * 2);
}
////////////////////////////////////////////////////////////////////////////////
TEST( ELFIOTest, test_dynamic_64_3 )
{

Binary file not shown.