diff --git a/include/linuxdeploy/core/elf_file.h b/include/linuxdeploy/core/elf_file.h index a3a52c3..65dffd2 100644 --- a/include/linuxdeploy/core/elf_file.h +++ b/include/linuxdeploy/core/elf_file.h @@ -64,6 +64,12 @@ namespace linuxdeploy { // return OS ABI uint8_t getElfABI(); + + // check if this file is a debug symbols file + bool isDebugSymbolsFile(); + + // check whether the file contains a dynsym section + bool isDynamicallyLinked(); }; } } diff --git a/src/core/elf_file.cpp b/src/core/elf_file.cpp index a13f35f..3a32121 100644 --- a/src/core/elf_file.cpp +++ b/src/core/elf_file.cpp @@ -26,6 +26,8 @@ namespace linuxdeploy { const bf::path path; uint8_t elfClass = ELFCLASSNONE; uint8_t elfABI = 0; + bool isDebugSymbolsFile = false; + bool isDynamicallyLinked = false; public: explicit PrivateData(bf::path path) : path(std::move(path)) {} @@ -72,6 +74,56 @@ namespace linuxdeploy { return patchelfPath; } + private: + template + void parseElfHeader(std::shared_ptr data) { + // TODO: the following code will _only_ work if the native byte order equals the program's + // this should not be a big problem as we don't offer ARM builds yet, and require the user to + // use a matching binary for the target binaries + + auto* ehdr = reinterpret_cast(data.get()); + + elfABI = ehdr->e_ident[EI_OSABI]; + + std::vector sections; + + // parse section header table + // first, we collect all entries in a vector so we can conveniently iterate over it + for (uint64_t i = 0; i < ehdr->e_shnum; ++i) { + auto* nextShdr = reinterpret_cast(data.get() + ehdr->e_shoff + i * sizeof(Shdr_T)); + sections.emplace_back(*nextShdr); + } + + auto getString = [data, §ions, ehdr](uint64_t offset) { + assert(ehdr->e_shstrndx != SHN_UNDEF); + const auto& stringTableSection = sections[ehdr->e_shstrndx]; + return std::string{reinterpret_cast(data.get() + stringTableSection.sh_offset + offset)}; + }; + + // now that we can look up texts, we can create a map to easily access the sections by name + std::unordered_map sectionsMap; + std::for_each(sections.begin(), sections.end(), [§ionsMap, &getString](const Shdr_T& shdr) { + const auto headerName = getString(shdr.sh_name); + sectionsMap.insert(std::make_pair(headerName, shdr)); + }); + + // this function is based on observations of the behavior of: + // - strip --only-keep-debug + // - objcopy --only-keep-debug + isDebugSymbolsFile = (sectionsMap[".text"].sh_type == SHT_NOBITS); + + // https://stackoverflow.com/a/7298931 + for (uint64_t i = 0; i < ehdr->e_phnum && !isDynamicallyLinked; ++i) { + auto* nextPhdr = reinterpret_cast(data.get() + ehdr->e_phoff + i * sizeof(Phdr_T)); + switch (nextPhdr->p_type) { + case PT_DYNAMIC: + case PT_INTERP: + isDynamicallyLinked = true; + break; + } + } + } + public: void readDataUsingElfAPI() { int fd = open(path.c_str(), O_RDONLY); @@ -96,19 +148,11 @@ namespace linuxdeploy { elfClass = data.get()[EI_CLASS]; switch (elfClass) { - case ELFCLASS32: { - auto* ehdr = reinterpret_cast(data.get()); - auto* shdr = reinterpret_cast(data.get() + ehdr->e_shoff); - - elfABI = ehdr->e_ident[EI_OSABI]; - } + case ELFCLASS32: + parseElfHeader(data); break; - case ELFCLASS64: { - auto* ehdr = reinterpret_cast(data.get()); - auto* shdr = reinterpret_cast(data.get() + ehdr->e_shoff); - - elfABI = ehdr->e_ident[EI_OSABI]; - } + case ELFCLASS64: + parseElfHeader(data); break; default: throw ElfFileParseError("Unknown ELF class: " + std::to_string(elfClass)); @@ -296,6 +340,14 @@ namespace linuxdeploy { uint8_t ElfFile::getElfABI() { return d->elfABI; } + + bool ElfFile::isDebugSymbolsFile() { + return d->isDebugSymbolsFile; + } + + bool ElfFile::isDynamicallyLinked() { + return d->isDynamicallyLinked; + } } } } diff --git a/src/subprocess/CMakeLists.txt b/src/subprocess/CMakeLists.txt index 5bf74ab..a497948 100644 --- a/src/subprocess/CMakeLists.txt +++ b/src/subprocess/CMakeLists.txt @@ -14,3 +14,6 @@ target_include_directories(linuxdeploy_subprocess PUBLIC ${PROJECT_SOURCE_DIR}/i add_executable(subprocess_demo subprocess_demo.cpp) target_link_libraries(subprocess_demo PUBLIC linuxdeploy_subprocess) + +add_executable(test_limit test_limit.cpp) +target_link_libraries(test_limit PUBLIC linuxdeploy_subprocess) diff --git a/tests/core/CMakeLists.txt b/tests/core/CMakeLists.txt index 67c2c1d..878103a 100644 --- a/tests/core/CMakeLists.txt +++ b/tests/core/CMakeLists.txt @@ -18,7 +18,6 @@ ld_add_test(test_appdir) add_dependencies(test_appdir simple_library simple_executable) - add_executable(test_linuxdeploy test_linuxdeploy.cpp ../../src/core.cpp) target_link_libraries(test_linuxdeploy PRIVATE linuxdeploy_core args gtest gtest_main) target_include_directories(test_linuxdeploy PRIVATE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src) @@ -34,6 +33,28 @@ target_compile_definitions(test_linuxdeploy PRIVATE # register in CTest ld_add_test(test_linuxdeploy) +add_executable(test_elf_file test_elf_file.cpp ../../src/core.cpp) +target_link_libraries(test_elf_file PRIVATE linuxdeploy_core args gtest gtest_main) +target_include_directories(test_elf_file PRIVATE ${PROJECT_SOURCE_DIR}/include ${PROJECT_SOURCE_DIR}/src) + +# calculate paths to resources using CMake and hardcode them in the test binary +target_compile_definitions(test_elf_file PRIVATE + -DSIMPLE_LIBRARY_PATH="$" + -DSIMPLE_LIBRARY_DEBUG_PATH="$.debug" + -DSIMPLE_LIBRARY_STRIPPED_PATH="$.stripped" + -DSIMPLE_EXECUTABLE_PATH="$" + -DSIMPLE_EXECUTABLE_STATIC_PATH="$" +) # make sure library and executable are built before test_appdir -add_dependencies(test_linuxdeploy simple_library simple_executable) +add_dependencies(test_elf_file + simple_executable + simple_executable_static + simple_library + simple_library_stripped + simple_library_debug +) + +# register in CTest +ld_add_test(test_elf_file) + diff --git a/tests/core/test_elf_file.cpp b/tests/core/test_elf_file.cpp new file mode 100644 index 0000000..cad6c21 --- /dev/null +++ b/tests/core/test_elf_file.cpp @@ -0,0 +1,30 @@ +#include "gtest/gtest.h" + +#include "linuxdeploy/core/elf_file.h" + +using namespace std; +using namespace linuxdeploy::core; +namespace bf = boost::filesystem; + +using namespace std; +using namespace linuxdeploy::core::elf_file; + +namespace LinuxDeployTest { + class ElfFileTest : public ::testing::Test {}; + + TEST_F(ElfFileTest, checkIsDebugSymbolsFile) { + ElfFile debugSymbolsFile(SIMPLE_LIBRARY_DEBUG_PATH); + EXPECT_TRUE(debugSymbolsFile.isDebugSymbolsFile()); + + ElfFile regularLibraryFile(SIMPLE_LIBRARY_PATH); + EXPECT_FALSE(regularLibraryFile.isDebugSymbolsFile()); + } + + TEST_F(ElfFileTest, checkIsDynamicallyLinked) { + ElfFile regularLibraryFile(SIMPLE_EXECUTABLE_PATH); + EXPECT_TRUE(regularLibraryFile.isDynamicallyLinked()); + + ElfFile staticLibraryFile(SIMPLE_EXECUTABLE_STATIC_PATH); + EXPECT_FALSE(staticLibraryFile.isDynamicallyLinked()); + } +} diff --git a/tests/simple_executable/CMakeLists.txt b/tests/simple_executable/CMakeLists.txt index a552550..c75d27e 100644 --- a/tests/simple_executable/CMakeLists.txt +++ b/tests/simple_executable/CMakeLists.txt @@ -1,2 +1,6 @@ add_executable(simple_executable simple_executable.cpp) -target_link_libraries(simple_executable simple_library pthread) \ No newline at end of file +target_link_libraries(simple_executable simple_library pthread) + +add_executable(simple_executable_static simple_executable.cpp) +target_link_libraries(simple_executable_static simple_library_static pthread) +target_link_options(simple_executable_static PUBLIC -static -static-libgcc -static-libstdc++) diff --git a/tests/simple_library/CMakeLists.txt b/tests/simple_library/CMakeLists.txt index 41fd25d..7088b3a 100644 --- a/tests/simple_library/CMakeLists.txt +++ b/tests/simple_library/CMakeLists.txt @@ -1,3 +1,27 @@ add_library(simple_library SHARED simple_library.cpp simple_library.h) target_link_libraries(simple_library PUBLIC CImg) target_include_directories(simple_library PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +# generate a debug symbols file +add_custom_command( + OUTPUT libsimple_library.so.debug + COMMAND objcopy --only-keep-debug libsimple_library.so libsimple_library.so.debug + DEPENDS simple_library + VERBATIM +) +add_custom_target(simple_library_debug ALL DEPENDS libsimple_library.so.debug) + +# generate a library whose debug symbols have been stripped, but which contains a GNU debug link to the symbols file +add_custom_command( + OUTPUT libsimple_library.so.stripped + COMMAND objcopy --strip-debug libsimple_library.so libsimple_library.so.stripped + COMMAND objcopy --add-gnu-debuglink=libsimple_library.so.debug libsimple_library.so.stripped + DEPENDS simple_library_debug + VERBATIM +) +add_custom_target(simple_library_stripped ALL DEPENDS libsimple_library.so.stripped) + +# add a static version, too +add_library(simple_library_static STATIC simple_library.cpp simple_library.h) +target_link_libraries(simple_library_static PUBLIC CImg) +target_include_directories(simple_library_static PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})