From 22ff13436381c175a3d91bcf3e88ab6b067eb93c Mon Sep 17 00:00:00 2001 From: Serge Lamikhov-Center Date: Tue, 22 Dec 2020 14:10:16 +0200 Subject: [PATCH] Address array section accessor added The accessor is useful for manipulation of such sections as .ctors, .dtors, .init_array and .fini_array --- Makefile.am | 3 +- Makefile.in | 3 +- elfio/elfio.hpp | 1 + elfio/elfio_array.hpp | 111 +++++++++++++++++++++++++ elfio/elfio_dynamic.hpp | 6 +- examples/c_wrapper/c_example.c | 15 ++++ examples/c_wrapper/elfio_c_wrapper.cpp | 29 +++++++ examples/c_wrapper/elfio_c_wrapper.h | 14 ++++ tests/ELFIOTest2.cpp | 90 ++++++++++++++++++++ tests/elf_examples/ctors | Bin 0 -> 22232 bytes 10 files changed, 267 insertions(+), 5 deletions(-) create mode 100644 elfio/elfio_array.hpp create mode 100755 tests/elf_examples/ctors diff --git a/Makefile.am b/Makefile.am index 4abbae2..a674be1 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,7 +7,8 @@ nobase_include_HEADERS = elfio/elf_types.hpp elfio/elfio_dynamic.hpp \ elfio/elfio_section.hpp elfio/elfio_segment.hpp \ elfio/elfio_strings.hpp elfio/elfio_symbols.hpp \ elfio/elfio_utils.hpp elfio/elfio_dump.hpp \ - elfio/elfio_version.hpp elfio/elfio_modinfo.hpp + elfio/elfio_version.hpp elfio/elfio_modinfo.hpp \ + elfio/elfio_array.hpp EXTRA_DIST = doc/elfio.pdf CMakeLists.txt examples/CMakeLists.txt cmake/elfioConfig.cmake.in diff --git a/Makefile.in b/Makefile.in index 7d629df..2689a89 100644 --- a/Makefile.in +++ b/Makefile.in @@ -505,7 +505,8 @@ nobase_include_HEADERS = elfio/elf_types.hpp elfio/elfio_dynamic.hpp \ elfio/elfio_section.hpp elfio/elfio_segment.hpp \ elfio/elfio_strings.hpp elfio/elfio_symbols.hpp \ elfio/elfio_utils.hpp elfio/elfio_dump.hpp \ - elfio/elfio_version.hpp elfio/elfio_modinfo.hpp + elfio/elfio_version.hpp elfio/elfio_modinfo.hpp \ + elfio/elfio_array.hpp EXTRA_DIST = doc/elfio.pdf CMakeLists.txt examples/CMakeLists.txt cmake/elfioConfig.cmake.in TESTS = examples/elfdump/elfdump diff --git a/elfio/elfio.hpp b/elfio/elfio.hpp index cf93267..f9474d9 100644 --- a/elfio/elfio.hpp +++ b/elfio/elfio.hpp @@ -947,6 +947,7 @@ class elfio #include #include #include +#include #include #ifdef _MSC_VER diff --git a/elfio/elfio_array.hpp b/elfio/elfio_array.hpp new file mode 100644 index 0000000..6e6edcb --- /dev/null +++ b/elfio/elfio_array.hpp @@ -0,0 +1,111 @@ +/* +Copyright (C) 2001-2020 by Serge Lamikhov-Center + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#ifndef ELFIO_ARRAY_HPP +#define ELFIO_ARRAY_HPP + +#include + +namespace ELFIO { + +//------------------------------------------------------------------------------ +template class array_section_accessor_template +{ + public: + //------------------------------------------------------------------------------ + array_section_accessor_template( const elfio& elf_file_, S* section_ ) + : elf_file( elf_file_ ), array_section( section_ ) + { + } + + //------------------------------------------------------------------------------ + Elf_Xword get_entries_num() const + { + Elf_Xword entry_size = elf_file.get_class() == ELFCLASS32 + ? sizeof( Elf32_Addr ) + : sizeof( Elf64_Addr ); + return array_section->get_size() / entry_size; + } + + //------------------------------------------------------------------------------ + bool get_entry( Elf_Xword index, Elf64_Addr& address ) const + { + if ( index >= get_entries_num() ) { // Is index valid + return false; + } + + if ( elf_file.get_class() == ELFCLASS32 ) { + generic_get_entry_arr( index, address ); + } + else { + generic_get_entry_arr( index, address ); + } + + return true; + } + + //------------------------------------------------------------------------------ + void add_entry( Elf64_Addr address ) + { + if ( elf_file.get_class() == ELFCLASS32 ) { + generic_add_entry_arr( address ); + } + else { + generic_add_entry_arr( address ); + } + } + + private: + //------------------------------------------------------------------------------ + template + void generic_get_entry_arr( Elf_Xword index, Elf64_Addr& address ) const + { + const endianess_convertor& convertor = elf_file.get_convertor(); + + const T temp = *reinterpret_cast( array_section->get_data() + + index * sizeof( T ) ); + address = convertor( temp ); + } + + //------------------------------------------------------------------------------ + template void generic_add_entry_arr( Elf64_Addr address ) + { + const endianess_convertor& convertor = elf_file.get_convertor(); + + T temp = (T)address; + array_section->append_data( reinterpret_cast( &temp ), + sizeof( temp ) ); + } + + //------------------------------------------------------------------------------ + private: + const elfio& elf_file; + S* array_section; +}; + +using array_section_accessor = array_section_accessor_template
; +using const_array_section_accessor = + array_section_accessor_template; + +} // namespace ELFIO + +#endif // ELFIO_ARRAY_HPP diff --git a/elfio/elfio_dynamic.hpp b/elfio/elfio_dynamic.hpp index 2783cef..7d7224c 100644 --- a/elfio/elfio_dynamic.hpp +++ b/elfio/elfio_dynamic.hpp @@ -99,10 +99,10 @@ template class dynamic_section_accessor_template void add_entry( Elf_Xword tag, Elf_Xword value ) { if ( elf_file.get_class() == ELFCLASS32 ) { - generic_add_entry( tag, value ); + generic_add_entry_dyn( tag, value ); } else { - generic_add_entry( tag, value ); + generic_add_entry_dyn( tag, value ); } } @@ -189,7 +189,7 @@ template class dynamic_section_accessor_template } //------------------------------------------------------------------------------ - template void generic_add_entry( Elf_Xword tag, Elf_Xword value ) + template void generic_add_entry_dyn( Elf_Xword tag, Elf_Xword value ) { const endianess_convertor& convertor = elf_file.get_convertor(); diff --git a/examples/c_wrapper/c_example.c b/examples/c_wrapper/c_example.c index cd0c8da..4c94729 100644 --- a/examples/c_wrapper/c_example.c +++ b/examples/c_wrapper/c_example.c @@ -161,6 +161,21 @@ int main( int argc, char* argv[] ) } elfio_dynamic_section_accessor_delete( pdynamic ); + //----------------------------------------------------------------------------- + // array + //----------------------------------------------------------------------------- + psection = elfio_get_section_by_name( pelfio, ".init_array" ); + if ( psection != 0 ) { + parray_t parray = elfio_array_section_accessor_new( pelfio, psection ); + Elf_Xword arrno = elfio_array_get_entries_num( parray ); + for ( int i = 0; i < arrno; i++ ) { + Elf64_Addr addr; + elfio_array_get_entry( parray, i, &addr ); + // printf( "[%4d] %16lx\n", i, addr ); + } + elfio_array_section_accessor_delete( parray ); + } + elfio_delete( pelfio ); return 0; diff --git a/examples/c_wrapper/elfio_c_wrapper.cpp b/examples/c_wrapper/elfio_c_wrapper.cpp index 8961ca2..bf27e1e 100644 --- a/examples/c_wrapper/elfio_c_wrapper.cpp +++ b/examples/c_wrapper/elfio_c_wrapper.cpp @@ -455,3 +455,32 @@ void elfio_dynamic_add_entry( pdynamic_t pdynamic, { pdynamic->add_entry( tag, value ); } + +//----------------------------------------------------------------------------- +// array +//----------------------------------------------------------------------------- +parray_t elfio_array_section_accessor_new( pelfio_t pelfio, + psection_t psection ) +{ + return new array_section_accessor( *pelfio, psection ); +} + +void elfio_array_section_accessor_delete( parray_t parray ) { delete parray; } + +Elf_Xword elfio_array_get_entries_num( parray_t parray ) +{ + return parray->get_entries_num(); +} + +bool elfio_array_get_entry( parray_t parray, + Elf_Xword index, + Elf64_Addr* paddress ) +{ + bool ret = parray->get_entry( index, *paddress); + + return ret; +} + +void elfio_array_add_entry( parray_t parray, Elf64_Addr address ) { + parray->add_entry( address ); +} diff --git a/examples/c_wrapper/elfio_c_wrapper.h b/examples/c_wrapper/elfio_c_wrapper.h index 8bb57b1..65a96d6 100644 --- a/examples/c_wrapper/elfio_c_wrapper.h +++ b/examples/c_wrapper/elfio_c_wrapper.h @@ -85,6 +85,7 @@ typedef ELFIO::string_section_accessor* pstring_t; typedef ELFIO::note_section_accessor* pnote_t; typedef ELFIO::modinfo_section_accessor* pmodinfo_t; typedef ELFIO::dynamic_section_accessor* pdynamic_t; +typedef ELFIO::array_section_accessor* parray_t; extern "C" { @@ -98,6 +99,7 @@ typedef void* pstring_t; typedef void* pnote_t; typedef void* pmodinfo_t; typedef void* pdynamic_t; +typedef void* parray_t; typedef int bool; #endif @@ -301,6 +303,18 @@ typedef int bool; Elf_Xword tag, Elf_Xword value ); + //----------------------------------------------------------------------------- + // array + //----------------------------------------------------------------------------- + parray_t elfio_array_section_accessor_new( pelfio_t pelfio, + psection_t psection ); + void elfio_array_section_accessor_delete( parray_t parray ); + Elf_Xword elfio_array_get_entries_num( parray_t parray ); + bool elfio_array_get_entry( parray_t parray, + Elf_Xword index, + Elf64_Addr* paddress ); + void elfio_array_add_entry( parray_t parray, Elf64_Addr address ); + #ifdef __cplusplus } #endif diff --git a/tests/ELFIOTest2.cpp b/tests/ELFIOTest2.cpp index b8e8697..79702b6 100644 --- a/tests/ELFIOTest2.cpp +++ b/tests/ELFIOTest2.cpp @@ -142,3 +142,93 @@ BOOST_AUTO_TEST_CASE( modinfo_write ) BOOST_CHECK_EQUAL( value, attributes[i].value ); } } + +//////////////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE( array_read_32 ) +{ + elfio reader; + BOOST_REQUIRE_EQUAL( reader.load( "elf_examples/hello_32" ), true ); + + section* array_sec = reader.sections[".ctors"]; + BOOST_REQUIRE_NE( array_sec, nullptr ); + + const_array_section_accessor array( reader, array_sec ); + BOOST_REQUIRE_EQUAL( array.get_entries_num(), (Elf_Xword)2 ); + Elf64_Addr addr; + BOOST_CHECK_EQUAL( array.get_entry( 0, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0xFFFFFFFF ); + BOOST_CHECK_EQUAL( array.get_entry( 1, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x00000000 ); +} + +//////////////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE( array_read_64 ) +{ + elfio reader; + BOOST_REQUIRE_EQUAL( reader.load( "elf_examples/hello_64" ), true ); + + section* array_sec = reader.sections[".ctors"]; + BOOST_REQUIRE_NE( array_sec, nullptr ); + + const_array_section_accessor array( reader, array_sec ); + BOOST_REQUIRE_EQUAL( array.get_entries_num(), (Elf_Xword)2 ); + Elf64_Addr addr; + BOOST_CHECK_EQUAL( array.get_entry( 0, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0xFFFFFFFFFFFFFFFF ); + BOOST_CHECK_EQUAL( array.get_entry( 1, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x0000000000000000 ); +} + +//////////////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE( init_array_read_64 ) +{ + elfio reader; + Elf64_Addr addr; + BOOST_REQUIRE_EQUAL( reader.load( "elf_examples/ctors" ), true ); + + section* array_sec = reader.sections[".init_array"]; + BOOST_REQUIRE_NE( array_sec, nullptr ); + + const_array_section_accessor array( reader, array_sec ); + BOOST_REQUIRE_EQUAL( array.get_entries_num(), (Elf_Xword)2 ); + BOOST_CHECK_EQUAL( array.get_entry( 0, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x12C0 ); + BOOST_CHECK_EQUAL( array.get_entry( 1, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x149F ); + + array_sec = reader.sections[".fini_array"]; + BOOST_REQUIRE_NE( array_sec, nullptr ); + + array_section_accessor arrayf( reader, array_sec ); + BOOST_REQUIRE_EQUAL( arrayf.get_entries_num(), (Elf_Xword)1 ); + BOOST_CHECK_EQUAL( arrayf.get_entry( 0, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x1280 ); +} + +//////////////////////////////////////////////////////////////////////////////// +BOOST_AUTO_TEST_CASE( init_array_write_64 ) +{ + elfio reader; + Elf64_Addr addr; + BOOST_REQUIRE_EQUAL( reader.load( "elf_examples/ctors" ), true ); + + section* array_sec = reader.sections[".init_array"]; + BOOST_REQUIRE_NE( array_sec, nullptr ); + + array_section_accessor array( reader, array_sec ); + BOOST_REQUIRE_EQUAL( array.get_entries_num(), (Elf_Xword)2 ); + BOOST_CHECK_EQUAL( array.get_entry( 0, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x12C0 ); + BOOST_CHECK_EQUAL( array.get_entry( 1, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x149F ); + + array.add_entry( 0x12345678 ); + + BOOST_REQUIRE_EQUAL( array.get_entries_num(), (Elf_Xword)3 ); + BOOST_CHECK_EQUAL( array.get_entry( 0, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x12C0 ); + BOOST_CHECK_EQUAL( array.get_entry( 1, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x149F ); + BOOST_CHECK_EQUAL( array.get_entry( 2, addr ), true ); + BOOST_CHECK_EQUAL( addr, 0x12345678 ); +} diff --git a/tests/elf_examples/ctors b/tests/elf_examples/ctors new file mode 100755 index 0000000000000000000000000000000000000000..c5433230e7d78af221539ed0bedbc11d727cdfd5 GIT binary patch literal 22232 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 P{6kHW<0^-MqpJN67jtGD literal 0 HcmV?d00001