diff options
Diffstat (limited to 'src/tools/windeployqt/elfreader.cpp')
-rw-r--r-- | src/tools/windeployqt/elfreader.cpp | 440 |
1 files changed, 440 insertions, 0 deletions
diff --git a/src/tools/windeployqt/elfreader.cpp b/src/tools/windeployqt/elfreader.cpp new file mode 100644 index 0000000000..f375f5841d --- /dev/null +++ b/src/tools/windeployqt/elfreader.cpp @@ -0,0 +1,440 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "elfreader.h" + +#include <QDir> + +QT_BEGIN_NAMESPACE + +/* This is a copy of the ELF reader contained in Qt Creator (src/libs/utils), + * extended by the dependencies() function to read out the dependencies of a dynamic executable. */ + +quint16 getHalfWord(const unsigned char *&s, const ElfData &context) +{ + quint16 res; + if (context.endian == Elf_ELFDATA2MSB) + res = qFromBigEndian<quint16>(s); + else + res = qFromLittleEndian<quint16>(s); + s += 2; + return res; +} + +quint32 getWord(const unsigned char *&s, const ElfData &context) +{ + quint32 res; + if (context.endian == Elf_ELFDATA2MSB) + res = qFromBigEndian<quint32>(s); + else + res = qFromLittleEndian<quint32>(s); + s += 4; + return res; +} + +quint64 getAddress(const unsigned char *&s, const ElfData &context) +{ + quint64 res; + if (context.elfclass == Elf_ELFCLASS32) { + if (context.endian == Elf_ELFDATA2MSB) + res = qFromBigEndian<quint32>(s); + else + res = qFromLittleEndian<quint32>(s); + s += 4; + } else { + if (context.endian == Elf_ELFDATA2MSB) + res = qFromBigEndian<quint64>(s); + else + res = qFromLittleEndian<quint64>(s); + s += 8; + } + return res; +} + +quint64 getOffset(const unsigned char *&s, const ElfData &context) +{ + return getAddress(s, context); +} + +static void parseSectionHeader(const uchar *s, ElfSectionHeader *sh, const ElfData &context) +{ + sh->index = getWord(s, context); + sh->type = getWord(s, context); + sh->flags = quint32(getOffset(s, context)); + sh->addr = getAddress(s, context); + sh->offset = getOffset(s, context); + sh->size = getOffset(s, context); +} + +static void parseProgramHeader(const uchar *s, ElfProgramHeader *sh, const ElfData &context) +{ + sh->type = getWord(s, context); + sh->offset = getOffset(s, context); + /* p_vaddr = */ getAddress(s, context); + /* p_paddr = */ getAddress(s, context); + sh->filesz = getWord(s, context); + sh->memsz = getWord(s, context); +} + +class ElfMapper +{ +public: + ElfMapper(const ElfReader *reader) : file(reader->m_binary) {} + + bool map() + { + if (!file.open(QIODevice::ReadOnly)) + return false; + + fdlen = quint64(file.size()); + ustart = file.map(0, qint64(fdlen)); + if (ustart == 0) { + // Try reading the data into memory instead. + raw = file.readAll(); + start = raw.constData(); + fdlen = quint64(raw.size()); + } + return true; + } + +public: + QFile file; + QByteArray raw; + union { const char *start; const uchar *ustart; }; + quint64 fdlen; +}; + +ElfReader::ElfReader(const QString &binary) + : m_binary(binary) +{ +} + +ElfData ElfReader::readHeaders() +{ + readIt(); + return m_elfData; +} + +static inline QString msgInvalidElfObject(const QString &binary, const QString &why) +{ + return QStringLiteral("'%1' is an invalid ELF object (%2)") + .arg(QDir::toNativeSeparators(binary), why); +} + +ElfReader::Result ElfReader::readIt() +{ + if (!m_elfData.sectionHeaders.isEmpty()) + return Ok; + if (!m_elfData.programHeaders.isEmpty()) + return Ok; + + ElfMapper mapper(this); + if (!mapper.map()) + return Corrupt; + + const quint64 fdlen = mapper.fdlen; + + if (fdlen < 64) { + m_errorString = QStringLiteral("'%1' is not an ELF object (file too small)").arg(QDir::toNativeSeparators(m_binary)); + return NotElf; + } + + if (strncmp(mapper.start, "\177ELF", 4) != 0) { + m_errorString = QStringLiteral("'%1' is not an ELF object").arg(QDir::toNativeSeparators(m_binary)); + return NotElf; + } + + // 32 or 64 bit + m_elfData.elfclass = ElfClass(mapper.start[4]); + const bool is64Bit = m_elfData.elfclass == Elf_ELFCLASS64; + if (m_elfData.elfclass != Elf_ELFCLASS32 && m_elfData.elfclass != Elf_ELFCLASS64) { + m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd cpu architecture")); + return Corrupt; + } + + // int bits = (data[4] << 5); + // If you remove this check to read ELF objects of a different arch, + // please make sure you modify the typedefs + // to match the _plugin_ architecture. + // if ((sizeof(void*) == 4 && bits != 32) + // || (sizeof(void*) == 8 && bits != 64)) { + // if (errorString) + // *errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)") + // .arg(m_binary).arg(QLatin1String("wrong cpu architecture")); + // return Corrupt; + // } + + // Read Endianhness. + m_elfData.endian = ElfEndian(mapper.ustart[5]); + if (m_elfData.endian != Elf_ELFDATA2LSB && m_elfData.endian != Elf_ELFDATA2MSB) { + m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("odd endianness")); + return Corrupt; + } + + const uchar *data = mapper.ustart + 16; // e_ident + m_elfData.elftype = ElfType(getHalfWord(data, m_elfData)); + m_elfData.elfmachine = ElfMachine(getHalfWord(data, m_elfData)); + /* e_version = */ getWord(data, m_elfData); + m_elfData.entryPoint = getAddress(data, m_elfData); + + quint64 e_phoff = getOffset(data, m_elfData); + quint64 e_shoff = getOffset(data, m_elfData); + /* e_flags = */ getWord(data, m_elfData); + + quint32 e_shsize = getHalfWord(data, m_elfData); + + if (e_shsize > fdlen) { + m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shsize")); + return Corrupt; + } + + quint32 e_phentsize = getHalfWord(data, m_elfData); + if (e_phentsize != (is64Bit ? 56 : 32)) { + m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("invalid structure")); + return ElfReader::Corrupt; + } + quint32 e_phnum = getHalfWord(data, m_elfData); + + quint32 e_shentsize = getHalfWord(data, m_elfData); + + if (e_shentsize % 4) { + m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_shentsize")); + return Corrupt; + } + + quint32 e_shnum = getHalfWord(data, m_elfData); + quint32 e_shtrndx = getHalfWord(data, m_elfData); + if (data != mapper.ustart + (is64Bit ? 64 : 52)) { + m_errorString = msgInvalidElfObject(m_binary, QStringLiteral("unexpected e_phentsize")); + return ElfReader::Corrupt; + } + + if (quint64(e_shnum) * e_shentsize > fdlen) { + const QString reason = QStringLiteral("announced %1 sections, each %2 bytes, exceed file size").arg(e_shnum).arg(e_shentsize); + m_errorString = msgInvalidElfObject(m_binary, reason); + return Corrupt; + } + + quint64 soff = e_shoff + e_shentsize * e_shtrndx; + +// if ((soff + e_shentsize) > fdlen || soff % 4 || soff == 0) { +// m_errorString = QLibrary::QStringLiteral("'%1' is an invalid ELF object (%2)") +// .arg(m_binary) +// .arg(QLatin1String("shstrtab section header seems to be at %1")) +// .arg(QString::number(soff, 16)); +// return Corrupt; +// } + + if (e_shoff) { + ElfSectionHeader strtab; + parseSectionHeader(mapper.ustart + soff, &strtab, m_elfData); + const quint64 stringTableFileOffset = strtab.offset; + if (quint32(stringTableFileOffset + e_shentsize) >= fdlen + || stringTableFileOffset == 0) { + const QString reason = QStringLiteral("string table seems to be at 0x%1").arg(soff, 0, 16); + m_errorString = msgInvalidElfObject(m_binary, reason); + return Corrupt; + } + + for (quint32 i = 0; i < e_shnum; ++i) { + const uchar *s = mapper.ustart + e_shoff + i * e_shentsize; + ElfSectionHeader sh; + parseSectionHeader(s, &sh, m_elfData); + + if (stringTableFileOffset + sh.index > fdlen) { + const QString reason = QStringLiteral("section name %1 of %2 behind end of file") + .arg(i).arg(e_shnum); + m_errorString = msgInvalidElfObject(m_binary, reason); + return Corrupt; + } + + sh.name = mapper.start + stringTableFileOffset + sh.index; + if (sh.name == ".gdb_index") { + m_elfData.symbolsType = FastSymbols; + } else if (sh.name == ".debug_info") { + m_elfData.symbolsType = PlainSymbols; + } else if (sh.name == ".gnu_debuglink") { + m_elfData.debugLink = QByteArray(mapper.start + sh.offset); + m_elfData.symbolsType = LinkedSymbols; + } else if (sh.name == ".note.gnu.build-id") { + m_elfData.symbolsType = BuildIdSymbols; + if (sh.size > 16) + m_elfData.buildId = QByteArray(mapper.start + sh.offset + 16, + int(sh.size) - 16).toHex(); + } + m_elfData.sectionHeaders.append(sh); + } + } + + if (e_phoff) { + for (quint32 i = 0; i < e_phnum; ++i) { + const uchar *s = mapper.ustart + e_phoff + i * e_phentsize; + ElfProgramHeader ph; + parseProgramHeader(s, &ph, m_elfData); + m_elfData.programHeaders.append(ph); + } + } + return Ok; +} + +QByteArray ElfReader::readSection(const QByteArray &name) +{ + readIt(); + int i = m_elfData.indexOf(name); + if (i == -1) + return QByteArray(); + + ElfMapper mapper(this); + if (!mapper.map()) + return QByteArray(); + + const ElfSectionHeader §ion = m_elfData.sectionHeaders.at(i); + return QByteArray(mapper.start + section.offset, int(section.size)); +} + +static QByteArray cutout(const char *s) +{ + QByteArray res(s, 80); + const int pos = res.indexOf('\0'); + if (pos != -1) + res.resize(pos - 1); + return res; +} + +QByteArray ElfReader::readCoreName(bool *isCore) +{ + *isCore = false; + + readIt(); + + ElfMapper mapper(this); + if (!mapper.map()) + return QByteArray(); + + if (m_elfData.elftype != Elf_ET_CORE) + return QByteArray(); + + *isCore = true; + + for (int i = 0, n = m_elfData.sectionHeaders.size(); i != n; ++i) + if (m_elfData.sectionHeaders.at(i).type == Elf_SHT_NOTE) { + const ElfSectionHeader &header = m_elfData.sectionHeaders.at(i); + return cutout(mapper.start + header.offset + 0x40); + } + + for (int i = 0, n = m_elfData.programHeaders.size(); i != n; ++i) + if (m_elfData.programHeaders.at(i).type == Elf_PT_NOTE) { + const ElfProgramHeader &header = m_elfData.programHeaders.at(i); + return cutout(mapper.start + header.offset + 0xec); + } + + return QByteArray(); +} + +int ElfData::indexOf(const QByteArray &name) const +{ + for (int i = 0, n = sectionHeaders.size(); i != n; ++i) + if (sectionHeaders.at(i).name == name) + return i; + return -1; +} + +/* Helpers for reading out the .dynamic section containing the dependencies. + * The ".dynamic" section is an array of + * typedef struct { + * Elf32_Sword d_tag; + * union { + * Elf32_Word d_val; + * dElf32_Addr d_ptr; + * } d_un; + * } Elf32_Dyn + * with entries where a tag DT_NEEDED indicates that m_val is an offset into + * the string table ".dynstr". The documentation states that entries with the + * tag DT_STRTAB contain an offset for the string table to be used, but that + * has been found not to contain valid entries. */ + +enum DynamicSectionTags { + DT_NULL = 0, + DT_NEEDED = 1, + DT_STRTAB = 5, + DT_SONAME = 14, + DT_RPATH = 15 +}; + +QList<QByteArray> ElfReader::dependencies() +{ + QList<QByteArray> result; + + ElfMapper mapper(this); + if (!mapper.map()) { + m_errorString = QStringLiteral("Mapper failure"); + return result; + } + quint64 dynStrOffset = 0; + quint64 dynamicOffset = 0; + quint64 dynamicSize = 0; + + const QList<ElfSectionHeader> &headers = readHeaders().sectionHeaders; + for (const ElfSectionHeader &eh : headers) { + if (eh.name == QByteArrayLiteral(".dynstr")) { + dynStrOffset = eh.offset; + } else if (eh.name == QByteArrayLiteral(".dynamic")) { + dynamicOffset = eh.offset; + dynamicSize = eh.size; + } + if (dynStrOffset && dynamicOffset) + break; + } + + if (!dynStrOffset || !dynamicOffset) { + m_errorString = QStringLiteral("Not a dynamically linked executable."); + return result; + } + + const unsigned char *dynamicData = mapper.ustart + dynamicOffset; + const unsigned char *dynamicDataEnd = dynamicData + dynamicSize; + while (dynamicData < dynamicDataEnd) { + const quint32 tag = getWord(dynamicData, m_elfData); + if (tag == DT_NULL) + break; + if (m_elfData.elfclass == Elf_ELFCLASS64) + dynamicData += sizeof(quint32); // padding to d_val/d_ptr. + if (tag == DT_NEEDED) { + const quint32 offset = getWord(dynamicData, m_elfData); + if (m_elfData.elfclass == Elf_ELFCLASS64) + dynamicData += sizeof(quint32); // past d_ptr. + const char *name = mapper.start + dynStrOffset + offset; + result.push_back(name); + } else { + dynamicData += m_elfData.elfclass == Elf_ELFCLASS64 ? 8 : 4; + } + } + return result; +} + +QT_END_NAMESPACE |