Detect dynamically linked and debug symbols only ELF files

TODO: extract ELF stuff into new small C++ wrapper library that can be used in various places (e.g., AppImageLauncher, the AppImage runtime, ...)
This commit is contained in:
TheAssassin 2021-05-29 01:19:42 +02:00
parent 86c99ccfb4
commit 3c6096433d
7 changed files with 155 additions and 15 deletions

View File

@ -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();
};
}
}

View File

@ -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<typename Ehdr_T, typename Shdr_T, typename Phdr_T>
void parseElfHeader(std::shared_ptr<uint8_t> 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<Ehdr_T*>(data.get());
elfABI = ehdr->e_ident[EI_OSABI];
std::vector<Shdr_T> 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<Shdr_T*>(data.get() + ehdr->e_shoff + i * sizeof(Shdr_T));
sections.emplace_back(*nextShdr);
}
auto getString = [data, &sections, ehdr](uint64_t offset) {
assert(ehdr->e_shstrndx != SHN_UNDEF);
const auto& stringTableSection = sections[ehdr->e_shstrndx];
return std::string{reinterpret_cast<char*>(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<std::string, Shdr_T> sectionsMap;
std::for_each(sections.begin(), sections.end(), [&sectionsMap, &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<Phdr_T*>(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<Elf32_Ehdr*>(data.get());
auto* shdr = reinterpret_cast<Elf32_Shdr*>(data.get() + ehdr->e_shoff);
elfABI = ehdr->e_ident[EI_OSABI];
}
case ELFCLASS32:
parseElfHeader<Elf32_Ehdr, Elf32_Shdr, Elf32_Phdr>(data);
break;
case ELFCLASS64: {
auto* ehdr = reinterpret_cast<Elf32_Ehdr*>(data.get());
auto* shdr = reinterpret_cast<Elf32_Shdr*>(data.get() + ehdr->e_shoff);
elfABI = ehdr->e_ident[EI_OSABI];
}
case ELFCLASS64:
parseElfHeader<Elf64_Ehdr, Elf64_Shdr, Elf64_Phdr>(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;
}
}
}
}

View File

@ -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)

View File

@ -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="$<TARGET_FILE:simple_library>"
-DSIMPLE_LIBRARY_DEBUG_PATH="$<TARGET_FILE:simple_library>.debug"
-DSIMPLE_LIBRARY_STRIPPED_PATH="$<TARGET_FILE:simple_library>.stripped"
-DSIMPLE_EXECUTABLE_PATH="$<TARGET_FILE:simple_executable>"
-DSIMPLE_EXECUTABLE_STATIC_PATH="$<TARGET_FILE:simple_executable_static>"
)
# 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)

View File

@ -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());
}
}

View File

@ -1,2 +1,6 @@
add_executable(simple_executable simple_executable.cpp)
target_link_libraries(simple_executable simple_library pthread)
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++)

View File

@ -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})