Coverage Report

Created: 2026-03-12 06:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/CMake/Source/cmBinUtilsLinuxELFLinker.cxx
Line
Count
Source
1
/* Distributed under the OSI-approved BSD 3-Clause License.  See accompanying
2
   file LICENSE.rst or https://cmake.org/licensing for details.  */
3
4
#include "cmBinUtilsLinuxELFLinker.h"
5
6
#include <queue>
7
#include <sstream>
8
#include <unordered_set>
9
#include <utility>
10
11
#include <cm/memory>
12
#include <cm/string_view>
13
14
#include <cmsys/RegularExpression.hxx>
15
16
#include "cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool.h"
17
#include "cmELF.h"
18
#include "cmLDConfigLDConfigTool.h"
19
#include "cmMakefile.h"
20
#include "cmMessageType.h"
21
#include "cmRuntimeDependencyArchive.h"
22
#include "cmStringAlgorithms.h"
23
#include "cmSystemTools.h"
24
25
static std::string ReplaceOrigin(std::string const& rpath,
26
                                 std::string const& origin)
27
0
{
28
0
  static cmsys::RegularExpression const originRegex(
29
0
    "(\\$ORIGIN)([^a-zA-Z0-9_]|$)");
30
0
  static cmsys::RegularExpression const originCurlyRegex("\\${ORIGIN}");
31
32
0
  cmsys::RegularExpressionMatch match;
33
0
  if (originRegex.find(rpath.c_str(), match)) {
34
0
    cm::string_view pathv(rpath);
35
0
    auto begin = pathv.substr(0, match.start(1));
36
0
    auto end = pathv.substr(match.end(1));
37
0
    return cmStrCat(begin, origin, end);
38
0
  }
39
0
  if (originCurlyRegex.find(rpath.c_str(), match)) {
40
0
    cm::string_view pathv(rpath);
41
0
    auto begin = pathv.substr(0, match.start());
42
0
    auto end = pathv.substr(match.end());
43
0
    return cmStrCat(begin, origin, end);
44
0
  }
45
0
  return rpath;
46
0
}
47
48
cmBinUtilsLinuxELFLinker::cmBinUtilsLinuxELFLinker(
49
  cmRuntimeDependencyArchive* archive)
50
0
  : cmBinUtilsLinker(archive)
51
0
{
52
0
}
53
54
bool cmBinUtilsLinuxELFLinker::Prepare()
55
0
{
56
0
  std::string tool = this->Archive->GetGetRuntimeDependenciesTool();
57
0
  if (tool.empty()) {
58
0
    tool = "objdump";
59
0
  }
60
0
  if (tool == "objdump") {
61
0
    this->Tool =
62
0
      cm::make_unique<cmBinUtilsLinuxELFObjdumpGetRuntimeDependenciesTool>(
63
0
        this->Archive);
64
0
  } else {
65
0
    std::ostringstream e;
66
0
    e << "Invalid value for CMAKE_GET_RUNTIME_DEPENDENCIES_TOOL: " << tool;
67
0
    this->SetError(e.str());
68
0
    return false;
69
0
  }
70
71
0
  std::string ldConfigTool =
72
0
    this->Archive->GetMakefile()->GetSafeDefinition("CMAKE_LDCONFIG_TOOL");
73
0
  if (ldConfigTool.empty()) {
74
0
    ldConfigTool = "ldconfig";
75
0
  }
76
0
  if (ldConfigTool == "ldconfig") {
77
0
    this->LDConfigTool =
78
0
      cm::make_unique<cmLDConfigLDConfigTool>(this->Archive);
79
0
    if (!this->LDConfigTool->GetLDConfigPaths(this->LDConfigPaths)) {
80
0
      return false;
81
0
    }
82
0
  } else {
83
0
    std::ostringstream e;
84
0
    e << "Invalid value for CMAKE_LDCONFIG_TOOL: " << ldConfigTool;
85
0
    this->SetError(e.str());
86
0
    return false;
87
0
  }
88
89
0
  return true;
90
0
}
91
92
bool cmBinUtilsLinuxELFLinker::ScanDependencies(
93
  std::string const& file, cmStateEnums::TargetType /* unused */)
94
0
{
95
0
  cmELF elf(file.c_str());
96
0
  if (!elf) {
97
0
    return false;
98
0
  }
99
0
  if (elf.GetMachine() != 0) {
100
0
    if (this->Machine != 0) {
101
0
      if (elf.GetMachine() != this->Machine) {
102
0
        this->SetError("All files must have the same architecture.");
103
0
        return false;
104
0
      }
105
0
    } else {
106
0
      this->Machine = elf.GetMachine();
107
0
    }
108
0
  }
109
110
0
  return this->ScanDependencies(file);
111
0
}
112
113
bool cmBinUtilsLinuxELFLinker::ScanDependencies(std::string const& mainFile)
114
0
{
115
0
  std::unordered_set<std::string> resolvedDependencies;
116
0
  std::queue<std::pair<std::string, std::vector<std::string>>> queueToResolve;
117
0
  queueToResolve.push(std::make_pair(mainFile, std::vector<std::string>{}));
118
119
0
  while (!queueToResolve.empty()) {
120
0
    std::string file = std::move(queueToResolve.front().first);
121
0
    std::vector<std::string> parentRpaths =
122
0
      std::move(queueToResolve.front().second);
123
0
    queueToResolve.pop();
124
125
0
    std::string origin = cmSystemTools::GetFilenamePath(file);
126
0
    std::vector<std::string> needed;
127
0
    std::vector<std::string> rpaths;
128
0
    std::vector<std::string> runpaths;
129
0
    if (!this->Tool->GetFileInfo(file, needed, rpaths, runpaths)) {
130
0
      return false;
131
0
    }
132
0
    for (auto& runpath : runpaths) {
133
0
      runpath = ReplaceOrigin(runpath, origin);
134
0
    }
135
0
    for (auto& rpath : rpaths) {
136
0
      rpath = ReplaceOrigin(rpath, origin);
137
0
    }
138
139
0
    std::vector<std::string> searchPaths;
140
0
    if (!runpaths.empty()) {
141
0
      searchPaths = runpaths;
142
0
    } else {
143
0
      searchPaths = rpaths;
144
0
      searchPaths.insert(searchPaths.end(), parentRpaths.begin(),
145
0
                         parentRpaths.end());
146
0
    }
147
148
0
    searchPaths.insert(searchPaths.end(), this->LDConfigPaths.begin(),
149
0
                       this->LDConfigPaths.end());
150
151
0
    for (auto const& dep : needed) {
152
0
      if (resolvedDependencies.count(dep) != 0 ||
153
0
          this->Archive->IsPreExcluded(dep)) {
154
0
        continue;
155
0
      }
156
157
0
      std::string path;
158
0
      bool resolved = false;
159
0
      if (dep.find('/') != std::string::npos) {
160
0
        this->SetError("Paths to dependencies are not supported");
161
0
        return false;
162
0
      }
163
0
      if (!this->ResolveDependency(dep, searchPaths, path, resolved)) {
164
0
        return false;
165
0
      }
166
0
      if (resolved) {
167
0
        resolvedDependencies.emplace(dep);
168
0
        if (!this->Archive->IsPostExcluded(path)) {
169
0
          bool unique;
170
0
          this->Archive->AddResolvedPath(dep, path, unique);
171
0
          if (unique) {
172
0
            std::vector<std::string> combinedParentRpaths = parentRpaths;
173
0
            combinedParentRpaths.insert(combinedParentRpaths.end(),
174
0
                                        rpaths.begin(), rpaths.end());
175
176
0
            queueToResolve.push(std::make_pair(path, combinedParentRpaths));
177
0
          }
178
0
        }
179
0
      } else {
180
0
        this->Archive->AddUnresolvedPath(dep);
181
0
      }
182
0
    }
183
0
  }
184
185
0
  return true;
186
0
}
187
188
namespace {
189
bool FileHasArchitecture(char const* filename, std::uint16_t machine)
190
0
{
191
0
  cmELF elf(filename);
192
0
  if (!elf) {
193
0
    return false;
194
0
  }
195
0
  return machine == 0 || machine == elf.GetMachine();
196
0
}
197
}
198
199
bool cmBinUtilsLinuxELFLinker::ResolveDependency(
200
  std::string const& name, std::vector<std::string> const& searchPaths,
201
  std::string& path, bool& resolved)
202
0
{
203
0
  for (auto const& searchPath : searchPaths) {
204
0
    path = cmStrCat(searchPath, '/', name);
205
0
    if (cmSystemTools::PathExists(path) &&
206
0
        FileHasArchitecture(path.c_str(), this->Machine)) {
207
0
      this->NormalizePath(path);
208
0
      resolved = true;
209
0
      return true;
210
0
    }
211
0
  }
212
213
0
  for (auto const& searchPath : this->Archive->GetSearchDirectories()) {
214
0
    path = cmStrCat(searchPath, '/', name);
215
0
    if (cmSystemTools::PathExists(path) &&
216
0
        FileHasArchitecture(path.c_str(), this->Machine)) {
217
0
      std::ostringstream warning;
218
0
      warning << "Dependency " << name << " found in search directory:\n  "
219
0
              << searchPath
220
0
              << "\nSee file(GET_RUNTIME_DEPENDENCIES) documentation for "
221
0
              << "more information.";
222
0
      this->Archive->GetMakefile()->IssueMessage(MessageType::WARNING,
223
0
                                                 warning.str());
224
0
      resolved = true;
225
0
      return true;
226
0
    }
227
0
  }
228
229
0
  resolved = false;
230
0
  return true;
231
0
}