File indexing completed on 2024-05-19 05:44:09

0001 /*
0002     Copyright (C) 2013-2014 Volker Krause <vkrause@kde.org>
0003 
0004     This program is free software; you can redistribute it and/or modify it
0005     under the terms of the GNU Library General Public License as published by
0006     the Free Software Foundation; either version 2 of the License, or (at your
0007     option) any later version.
0008 
0009     This program is distributed in the hope that it will be useful, but WITHOUT
0010     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
0011     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Library General Public
0012     License for more details.
0013 
0014     You should have received a copy of the GNU General Public License
0015     along with this program.  If not, see <https://www.gnu.org/licenses/>.
0016 */
0017 
0018 #include "elffileset.h"
0019 #include "elfheader.h"
0020 #include "elfgnudebuglinksection.h"
0021 
0022 #include <QDebug>
0023 #include <QDir>
0024 #include <QFileInfo>
0025 
0026 #include <cassert>
0027 
0028 ElfFileSet::ElfFileSet(QObject* parent) : QObject(parent)
0029 {
0030     parseLdConf();
0031     foreach (const auto &path, qgetenv("LD_LIBRARY_PATH").split(':'))
0032         m_baseSearchPaths.push_back(path);
0033 
0034     m_globalDebugSearchPath.push_back(QStringLiteral("/usr/lib/debug")); // seems hardcoded?
0035 }
0036 
0037 ElfFileSet::~ElfFileSet()
0038 {
0039     qDeleteAll(m_files);
0040 }
0041 
0042 void ElfFileSet::addFile(const QString& fileName)
0043 {
0044     ElfFile* f = new ElfFile(fileName);
0045     if (!f->open(QIODevice::ReadOnly) || !f->isValid()) {
0046         delete f;
0047         return;
0048     }
0049     addFile(f);
0050 }
0051 
0052 static void resolvePlaceholder(QVector<QByteArray> &paths, const QByteArray &originPath)
0053 {
0054     for (auto it = paths.begin(); it != paths.end(); ++it)
0055         (*it).replace("$ORIGIN", originPath);
0056 }
0057 
0058 void ElfFileSet::addFile(ElfFile* file)
0059 {
0060     assert(file);
0061     assert(file->isValid());
0062 
0063     findSeparateDebugFile(file);
0064     m_files.push_back(file);
0065 
0066     if (!file->dynamicSection())
0067         return;
0068 
0069     auto rpaths = file->dynamicSection()->rpaths();
0070     auto runpaths = file->dynamicSection()->runpaths();
0071     auto originPath = QFileInfo(file->fileName()).absolutePath().toUtf8();
0072     resolvePlaceholder(rpaths, originPath);
0073     resolvePlaceholder(runpaths, originPath);
0074 
0075     QVector<QByteArray> searchPaths;
0076     searchPaths.reserve(rpaths.size() + m_ldLibraryPaths.size() + runpaths.size() + m_baseSearchPaths.size());
0077     if (runpaths.isEmpty()) // DT_RPATH is supposed to be ignored if DT_RUNPATH is present
0078         searchPaths += rpaths;
0079     searchPaths += m_ldLibraryPaths;
0080     searchPaths += runpaths;
0081     searchPaths += m_baseSearchPaths;
0082 
0083     foreach (const auto &lib, file->dynamicSection()->neededLibraries()) {
0084         if (std::find_if(m_files.cbegin(), m_files.cend(), [lib](ElfFile *file){ return file->dynamicSection()->soName() == lib; }) != m_files.cend())
0085             continue;
0086         bool dependencyFound = false;
0087         foreach (const auto &dir, searchPaths) {
0088             const auto fullPath = dir + '/' + lib;
0089             if (!QFile::exists(fullPath))
0090                 continue;
0091             ElfFile *dep = new ElfFile(fullPath);
0092             ElfFile *firstFile = m_files.at(0);
0093             if (dep->open(QIODevice::ReadOnly) && dep->isValid() && dep->type() == firstFile->type() && dep->header()->machine() == firstFile->header()->machine()) {
0094                 dependencyFound = true;
0095                 addFile(dep);
0096                 break;
0097             }
0098             delete dep;
0099         }
0100 
0101         // deal with NEEDED entries containing absolute paths
0102         if (!dependencyFound && lib.startsWith('/')) {
0103             if (std::find_if(m_files.cbegin(), m_files.cend(), [lib](ElfFile *file){ return file->fileName() == lib; }) != m_files.cend())
0104                 continue;
0105             if (QFile::exists(lib)) {
0106                 ElfFile *dep = new ElfFile(lib);
0107                 ElfFile *firstFile = m_files.at(0);
0108                 if (dep->open(QIODevice::ReadOnly) && dep->isValid() && dep->type() == firstFile->type() && dep->header()->machine() == firstFile->header()->machine()) {
0109                     dependencyFound = true;
0110                     addFile(dep);
0111                 } else {
0112                     delete dep;
0113                 }
0114             }
0115         }
0116 
0117         if (!dependencyFound)
0118             qWarning() << "Unable to locate dependency" << lib;
0119     }
0120 }
0121 
0122 int ElfFileSet::size() const
0123 {
0124     return m_files.size();
0125 }
0126 
0127 ElfFile* ElfFileSet::file(int index) const
0128 {
0129     return m_files.at(index);
0130 }
0131 
0132 static bool hasUnresolvedDependencies(ElfFile *file, const QVector<ElfFile*> &resolved, int startIndex)
0133 {
0134     if (!file->dynamicSection())
0135         return false;
0136 
0137     foreach (const auto &lib, file->dynamicSection()->neededLibraries()) {
0138         const auto it = std::find_if(resolved.constBegin() + startIndex, resolved.constEnd(), [lib](ElfFile *file){ return file->dynamicSection()->soName() == lib; });
0139         if (it == resolved.constEnd()) {
0140             return true;
0141         }
0142     }
0143     return false;
0144 }
0145 
0146 void ElfFileSet::topologicalSort()
0147 {
0148     QVector<ElfFile*> sorted;
0149     sorted.resize(m_files.size());
0150 
0151     QVector<ElfFile*> remaining = m_files;
0152 
0153     for (int i = sorted.size() - 1; i >= 0; --i) {
0154         for (auto it = std::begin(remaining); it != std::end(remaining); ++it) {
0155             if (!hasUnresolvedDependencies(*it, sorted, i + 1)) {
0156                 sorted[i] = *it;
0157                 remaining.erase(it);
0158                 break;
0159             }
0160         }
0161 
0162         // we did not find one with unresolved dependencies, shouldn't happen, unless there's a cycle
0163         // so just take one and see how far we get
0164         if (sorted.at(i) == nullptr)
0165             sorted[i] = remaining.takeFirst();
0166     }
0167 
0168 #if 0
0169     qDebug() << "input";
0170     foreach(const auto f, m_files)
0171         qDebug() << f->displayName() << f->fileName();
0172     qDebug() << "sorted";
0173     foreach(const auto f, sorted)
0174         qDebug() << f->displayName();
0175     qDebug() << "remaining";
0176     foreach(const auto f, remaining)
0177         qDebug() << f->displayName();
0178 #endif
0179 
0180     Q_ASSERT(remaining.isEmpty());
0181     if (sorted.first() != m_files.first()) {
0182         qWarning() << "FILE SET ORDER IS MESSED UP\nThis mostly happens due to missing dependencies. Let's try to ignore it for now...";
0183     }
0184 
0185     m_files = sorted;
0186 }
0187 
0188 void ElfFileSet::parseLdConf()
0189 {
0190     parseLdConf(QStringLiteral("/etc/ld.so.conf"));
0191 
0192     // built-in defaults
0193     m_baseSearchPaths.push_back("/lib64");
0194     m_baseSearchPaths.push_back("/lib");
0195     m_baseSearchPaths.push_back("/usr/lib64");
0196     m_baseSearchPaths.push_back("/usr/lib");
0197 }
0198 
0199 void ElfFileSet::parseLdConf(const QString& fileName)
0200 {
0201     QFile file(fileName);
0202     if (!file.open(QFile::ReadOnly)) {
0203         qWarning() << file.errorString();
0204         return;
0205     }
0206 
0207     while (!file.atEnd()) {
0208         const auto line = file.readLine().trimmed();
0209         if (line.isEmpty())
0210             continue;
0211         if (line.startsWith('#'))
0212             continue;
0213         if (line.startsWith("include")) {
0214             const auto fileGlob = line.mid(8);
0215             if (QFileInfo::exists(fileGlob)) {
0216                 parseLdConf(fileGlob);
0217             } else {
0218                 const auto idx = fileGlob.lastIndexOf('/');
0219                 assert(idx >= 0);
0220                 QDir dir(fileGlob.left(idx));
0221                 foreach (const auto &file, dir.entryList(QStringList() << fileGlob.mid(idx + 1)))
0222                     parseLdConf(dir.absolutePath() + '/' + file);
0223             }
0224             continue;
0225         }
0226         if (line.startsWith('/')) {
0227             if (QFileInfo::exists(line))
0228                 m_baseSearchPaths.push_back(line);
0229             continue;
0230         }
0231         qWarning() << "unable to handle ld.so.conf line:" << line;
0232     }
0233 }
0234 
0235 void ElfFileSet::findSeparateDebugFile(ElfFile* file) const
0236 {
0237     // (1) via build id
0238     const auto buildId = file->buildId().toHex();
0239     foreach (const auto &debugDir, m_globalDebugSearchPath) {
0240         auto debugFile = debugDir + "/.build-id/" + buildId.left(2) + "/" + buildId.mid(2) + ".debug";
0241         if (QFile::exists(debugFile)) {
0242             file->setSeparateDebugFile(debugFile);
0243             return;
0244         }
0245     }
0246 
0247     // (2) via debug link
0248     const auto debugLinkIndex = file->indexOfSection(".gnu_debuglink");
0249     if (debugLinkIndex < 0)
0250         return;
0251     const auto debugLinkSection = file->section<ElfGnuDebugLinkSection>(debugLinkIndex);
0252     assert(debugLinkSection);
0253     if (debugLinkSection->fileName().isEmpty())
0254         return;
0255     const auto dir = QFileInfo(file->fileName()).absolutePath();
0256 
0257     // (2a) next to file
0258     auto debugFile = dir + "/" + debugLinkSection->fileName();
0259     if (isValidDebugLinkFile(debugFile, debugLinkSection->crc())) {
0260         file->setSeparateDebugFile(debugFile);
0261         return;
0262     }
0263 
0264     // (2b) in .debug sub-folder next to file
0265     debugFile = dir + "/.debug/" + debugLinkSection->fileName();
0266     if (isValidDebugLinkFile(debugFile, debugLinkSection->crc())) {
0267         file->setSeparateDebugFile(debugFile);
0268         return;
0269     }
0270 
0271     // (2c) in global debug directories
0272     foreach (const auto &debugDir, m_globalDebugSearchPath) {
0273         debugFile = debugDir + dir + "/" + debugLinkSection->fileName();
0274         if (isValidDebugLinkFile(debugFile, debugLinkSection->crc())) {
0275             file->setSeparateDebugFile(debugFile);
0276             return;
0277         }
0278     }
0279 }
0280 
0281 // from GDB manual
0282 static uint32_t gnu_debuglink_crc32 (uint32_t crc, unsigned char *buf, size_t len)
0283 {
0284     static const unsigned long crc32_table[256] =
0285     {
0286         0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
0287         0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
0288         0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
0289         0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
0290         0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
0291         0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
0292         0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
0293         0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0294         0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
0295         0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
0296         0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
0297         0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
0298         0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
0299         0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
0300         0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
0301         0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0302         0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
0303         0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
0304         0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
0305         0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
0306         0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
0307         0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
0308         0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
0309         0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0310         0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
0311         0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
0312         0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
0313         0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
0314         0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
0315         0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
0316         0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
0317         0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0318         0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
0319         0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
0320         0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
0321         0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
0322         0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
0323         0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
0324         0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
0325         0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0326         0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
0327         0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
0328         0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
0329         0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
0330         0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
0331         0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
0332         0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
0333         0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0334         0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
0335         0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
0336         0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
0337         0x2d02ef8d
0338     };
0339     unsigned char *end;
0340 
0341     crc = ~crc & 0xffffffff;
0342     for (end = buf + len; buf < end; ++buf)
0343         crc = crc32_table[(crc ^ *buf) & 0xff] ^ (crc >> 8);
0344     return ~crc & 0xffffffff;
0345 }
0346 
0347 bool ElfFileSet::isValidDebugLinkFile(const QString& fileName, uint32_t expectedCrc)
0348 {
0349     if (!QFile::exists(fileName))
0350         return false;
0351 
0352     QFile f(fileName);
0353     if (!f.open(QFile::ReadOnly))
0354         return false;
0355 
0356     auto data = f.map(0, f.size());
0357     if (!data)
0358         return false;
0359 
0360     auto actualCrc = gnu_debuglink_crc32(0, data, f.size());
0361     qDebug() << fileName << expectedCrc << actualCrc;
0362     return actualCrc == expectedCrc;
0363 }