xref: /llvm-project/llvm/lib/TextAPI/BinaryReader/DylibReader.cpp (revision e3627e2690aba0128c502b340b9a392cfae13651)
1 //===- DylibReader.cpp -------------- TAPI MachO Dylib Reader --*- C++ -*-===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 ///
9 /// Implements the TAPI Reader for Mach-O dynamic libraries.
10 ///
11 //===----------------------------------------------------------------------===//
12 
13 #include "llvm/TextAPI/DylibReader.h"
14 #include "llvm/ADT/StringMap.h"
15 #include "llvm/Object/Binary.h"
16 #include "llvm/Object/MachOUniversal.h"
17 #include "llvm/Support/Endian.h"
18 #include "llvm/TargetParser/Triple.h"
19 #include "llvm/TextAPI/RecordsSlice.h"
20 #include "llvm/TextAPI/TextAPIError.h"
21 #include <iomanip>
22 #include <set>
23 #include <sstream>
24 #include <string>
25 
26 using namespace llvm;
27 using namespace llvm::object;
28 using namespace llvm::MachO;
29 using namespace llvm::MachO::DylibReader;
30 
31 auto TripleCmp = [](const Triple &LHS, const Triple &RHS) {
32   return LHS.getTriple() < RHS.getTriple();
33 };
34 using TripleSet = std::set<Triple, decltype(TripleCmp)>;
35 
36 static TripleSet constructTriples(MachOObjectFile *Obj,
37                                   const Architecture ArchT) {
38   auto getOSVersionStr = [](uint32_t V) {
39     PackedVersion OSVersion(V);
40     std::string Vers;
41     raw_string_ostream VStream(Vers);
42     VStream << OSVersion;
43     return VStream.str();
44   };
45   auto getOSVersion = [&](const MachOObjectFile::LoadCommandInfo &cmd) {
46     auto Vers = Obj->getVersionMinLoadCommand(cmd);
47     return getOSVersionStr(Vers.version);
48   };
49 
50   // FIXME: Can remove TripleCmp arg when building in c++20.
51   TripleSet Triples(TripleCmp);
52   bool IsIntel = ArchitectureSet(ArchT).hasX86();
53   auto Arch = getArchitectureName(ArchT);
54 
55   for (const auto &cmd : Obj->load_commands()) {
56     std::string OSVersion;
57     switch (cmd.C.cmd) {
58     case MachO::LC_VERSION_MIN_MACOSX:
59       OSVersion = getOSVersion(cmd);
60       Triples.emplace(Arch, "apple", "macos" + OSVersion);
61       break;
62     case MachO::LC_VERSION_MIN_IPHONEOS:
63       OSVersion = getOSVersion(cmd);
64       if (IsIntel)
65         Triples.emplace(Arch, "apple", "ios" + OSVersion, "simulator");
66       else
67         Triples.emplace(Arch, "apple", "ios" + OSVersion);
68       break;
69     case MachO::LC_VERSION_MIN_TVOS:
70       OSVersion = getOSVersion(cmd);
71       if (IsIntel)
72         Triples.emplace(Arch, "apple", "tvos" + OSVersion, "simulator");
73       else
74         Triples.emplace(Arch, "apple", "tvos" + OSVersion);
75       break;
76     case MachO::LC_VERSION_MIN_WATCHOS:
77       OSVersion = getOSVersion(cmd);
78       if (IsIntel)
79         Triples.emplace(Arch, "apple", "watchos" + OSVersion, "simulator");
80       else
81         Triples.emplace(Arch, "apple", "watchos" + OSVersion);
82       break;
83     case MachO::LC_BUILD_VERSION: {
84       OSVersion = getOSVersionStr(Obj->getBuildVersionLoadCommand(cmd).minos);
85       switch (Obj->getBuildVersionLoadCommand(cmd).platform) {
86       case MachO::PLATFORM_MACOS:
87         Triples.emplace(Arch, "apple", "macos" + OSVersion);
88         break;
89       case MachO::PLATFORM_IOS:
90         Triples.emplace(Arch, "apple", "ios" + OSVersion);
91         break;
92       case MachO::PLATFORM_TVOS:
93         Triples.emplace(Arch, "apple", "tvos" + OSVersion);
94         break;
95       case MachO::PLATFORM_WATCHOS:
96         Triples.emplace(Arch, "apple", "watchos" + OSVersion);
97         break;
98       case MachO::PLATFORM_BRIDGEOS:
99         Triples.emplace(Arch, "apple", "bridgeos" + OSVersion);
100         break;
101       case MachO::PLATFORM_MACCATALYST:
102         Triples.emplace(Arch, "apple", "ios" + OSVersion, "macabi");
103         break;
104       case MachO::PLATFORM_IOSSIMULATOR:
105         Triples.emplace(Arch, "apple", "ios" + OSVersion, "simulator");
106         break;
107       case MachO::PLATFORM_TVOSSIMULATOR:
108         Triples.emplace(Arch, "apple", "tvos" + OSVersion, "simulator");
109         break;
110       case MachO::PLATFORM_WATCHOSSIMULATOR:
111         Triples.emplace(Arch, "apple", "watchos" + OSVersion, "simulator");
112         break;
113       case MachO::PLATFORM_DRIVERKIT:
114         Triples.emplace(Arch, "apple", "driverkit" + OSVersion);
115         break;
116       default:
117         break; // Skip any others.
118       }
119       break;
120     }
121     default:
122       break;
123     }
124   }
125 
126   // Record unknown platform for older binaries that don't enforce platform
127   // load commands.
128   if (Triples.empty())
129     Triples.emplace(Arch, "apple", "unknown");
130 
131   return Triples;
132 }
133 
134 static Error readMachOHeader(MachOObjectFile *Obj, RecordsSlice &Slice) {
135   auto H = Obj->getHeader();
136   auto &BA = Slice.getBinaryAttrs();
137 
138   switch (H.filetype) {
139   default:
140     llvm_unreachable("unsupported binary type");
141   case MachO::MH_DYLIB:
142     BA.File = FileType::MachO_DynamicLibrary;
143     break;
144   case MachO::MH_DYLIB_STUB:
145     BA.File = FileType::MachO_DynamicLibrary_Stub;
146     break;
147   case MachO::MH_BUNDLE:
148     BA.File = FileType::MachO_Bundle;
149     break;
150   }
151 
152   if (H.flags & MachO::MH_TWOLEVEL)
153     BA.TwoLevelNamespace = true;
154   if (H.flags & MachO::MH_APP_EXTENSION_SAFE)
155     BA.AppExtensionSafe = true;
156 
157   for (const auto &LCI : Obj->load_commands()) {
158     switch (LCI.C.cmd) {
159     case MachO::LC_ID_DYLIB: {
160       auto DLLC = Obj->getDylibIDLoadCommand(LCI);
161       BA.InstallName = Slice.copyString(LCI.Ptr + DLLC.dylib.name);
162       BA.CurrentVersion = DLLC.dylib.current_version;
163       BA.CompatVersion = DLLC.dylib.compatibility_version;
164       break;
165     }
166     case MachO::LC_REEXPORT_DYLIB: {
167       auto DLLC = Obj->getDylibIDLoadCommand(LCI);
168       BA.RexportedLibraries.emplace_back(
169           Slice.copyString(LCI.Ptr + DLLC.dylib.name));
170       break;
171     }
172     case MachO::LC_SUB_FRAMEWORK: {
173       auto SFC = Obj->getSubFrameworkCommand(LCI);
174       BA.ParentUmbrella = Slice.copyString(LCI.Ptr + SFC.umbrella);
175       break;
176     }
177     case MachO::LC_SUB_CLIENT: {
178       auto SCLC = Obj->getSubClientCommand(LCI);
179       BA.AllowableClients.emplace_back(Slice.copyString(LCI.Ptr + SCLC.client));
180       break;
181     }
182     case MachO::LC_UUID: {
183       auto UUIDLC = Obj->getUuidCommand(LCI);
184       std::stringstream Stream;
185       for (unsigned I = 0; I < 16; ++I) {
186         if (I == 4 || I == 6 || I == 8 || I == 10)
187           Stream << '-';
188         Stream << std::setfill('0') << std::setw(2) << std::uppercase
189                << std::hex << static_cast<int>(UUIDLC.uuid[I]);
190       }
191       BA.UUID = Slice.copyString(Stream.str());
192       break;
193     }
194     case MachO::LC_RPATH: {
195       auto RPLC = Obj->getRpathCommand(LCI);
196       BA.RPaths.emplace_back(Slice.copyString(LCI.Ptr + RPLC.path));
197       break;
198     }
199     case MachO::LC_SEGMENT_SPLIT_INFO: {
200       auto SSILC = Obj->getLinkeditDataLoadCommand(LCI);
201       if (SSILC.datasize == 0)
202         BA.OSLibNotForSharedCache = true;
203       break;
204     }
205     default:
206       break;
207     }
208   }
209 
210   for (auto &Sect : Obj->sections()) {
211     auto SectName = Sect.getName();
212     if (!SectName)
213       return SectName.takeError();
214     if (*SectName != "__objc_imageinfo" && *SectName != "__image_info")
215       continue;
216 
217     auto Content = Sect.getContents();
218     if (!Content)
219       return Content.takeError();
220 
221     if ((Content->size() >= 8) && (Content->front() == 0)) {
222       uint32_t Flags;
223       if (Obj->isLittleEndian()) {
224         auto *p =
225             reinterpret_cast<const support::ulittle32_t *>(Content->data() + 4);
226         Flags = *p;
227       } else {
228         auto *p =
229             reinterpret_cast<const support::ubig32_t *>(Content->data() + 4);
230         Flags = *p;
231       }
232       BA.SwiftABI = (Flags >> 8) & 0xFF;
233     }
234   }
235   return Error::success();
236 }
237 
238 static Error readSymbols(MachOObjectFile *Obj, RecordsSlice &Slice,
239                          const ParseOption &Opt) {
240 
241   auto parseExport = [](const auto ExportFlags,
242                         auto Addr) -> std::tuple<SymbolFlags, RecordLinkage> {
243     SymbolFlags Flags = SymbolFlags::None;
244     switch (ExportFlags & MachO::EXPORT_SYMBOL_FLAGS_KIND_MASK) {
245     case MachO::EXPORT_SYMBOL_FLAGS_KIND_REGULAR:
246       if (ExportFlags & MachO::EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION)
247         Flags |= SymbolFlags::WeakDefined;
248       break;
249     case MachO::EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL:
250       Flags |= SymbolFlags::ThreadLocalValue;
251       break;
252     }
253 
254     RecordLinkage Linkage = (ExportFlags & MachO::EXPORT_SYMBOL_FLAGS_REEXPORT)
255                                 ? RecordLinkage::Rexported
256                                 : RecordLinkage::Exported;
257     return {Flags, Linkage};
258   };
259 
260   Error Err = Error::success();
261 
262   StringMap<std::pair<SymbolFlags, RecordLinkage>> Exports;
263   // Collect symbols from export trie first. Sometimes, there are more exports
264   // in the trie than in n-list due to stripping. This is common for swift
265   // mangled symbols.
266   for (auto &Sym : Obj->exports(Err)) {
267     auto [Flags, Linkage] = parseExport(Sym.flags(), Sym.address());
268     Slice.addRecord(Sym.name(), Flags, GlobalRecord::Kind::Unknown, Linkage);
269     Exports[Sym.name()] = {Flags, Linkage};
270   }
271 
272   for (const auto &Sym : Obj->symbols()) {
273     auto FlagsOrErr = Sym.getFlags();
274     if (!FlagsOrErr)
275       return FlagsOrErr.takeError();
276     auto Flags = *FlagsOrErr;
277 
278     auto NameOrErr = Sym.getName();
279     if (!NameOrErr)
280       return NameOrErr.takeError();
281     auto Name = *NameOrErr;
282 
283     RecordLinkage Linkage = RecordLinkage::Unknown;
284     SymbolFlags RecordFlags = SymbolFlags::None;
285 
286     if (Opt.Undefineds && (Flags & SymbolRef::SF_Undefined)) {
287       Linkage = RecordLinkage::Undefined;
288       if (Flags & SymbolRef::SF_Weak)
289         RecordFlags |= SymbolFlags::WeakReferenced;
290     } else if (Flags & SymbolRef::SF_Exported) {
291       auto Exp = Exports.find(Name);
292       // This should never be possible when binaries are produced with Apple
293       // linkers. However it is possible to craft dylibs where the export trie
294       // is either malformed or has conflicting symbols compared to n_list.
295       if (Exp != Exports.end())
296         std::tie(RecordFlags, Linkage) = Exp->second;
297       else
298         Linkage = RecordLinkage::Exported;
299     } else if (Flags & SymbolRef::SF_Hidden) {
300       Linkage = RecordLinkage::Internal;
301     } else
302       continue;
303 
304     auto TypeOrErr = Sym.getType();
305     if (!TypeOrErr)
306       return TypeOrErr.takeError();
307     auto Type = *TypeOrErr;
308 
309     GlobalRecord::Kind GV = (Type & SymbolRef::ST_Function)
310                                 ? GlobalRecord::Kind::Function
311                                 : GlobalRecord::Kind::Variable;
312 
313     if (GV == GlobalRecord::Kind::Function)
314       RecordFlags |= SymbolFlags::Text;
315     else
316       RecordFlags |= SymbolFlags::Data;
317 
318     Slice.addRecord(Name, RecordFlags, GV, Linkage);
319   }
320   return Err;
321 }
322 
323 static Error load(MachOObjectFile *Obj, RecordsSlice &Slice,
324                   const ParseOption &Opt, const Architecture Arch) {
325   if (Arch == AK_unknown)
326     return make_error<TextAPIError>(TextAPIErrorCode::UnsupportedTarget);
327 
328   if (Opt.MachOHeader)
329     if (auto Err = readMachOHeader(Obj, Slice))
330       return Err;
331 
332   if (Opt.SymbolTable)
333     if (auto Err = readSymbols(Obj, Slice, Opt))
334       return Err;
335 
336   return Error::success();
337 }
338 
339 Expected<Records> DylibReader::readFile(MemoryBufferRef Buffer,
340                                         const ParseOption &Opt) {
341   Records Results;
342 
343   auto BinOrErr = createBinary(Buffer);
344   if (!BinOrErr)
345     return BinOrErr.takeError();
346 
347   Binary &Bin = *BinOrErr.get();
348   if (auto *Obj = dyn_cast<MachOObjectFile>(&Bin)) {
349     const auto Arch = getArchitectureFromCpuType(Obj->getHeader().cputype,
350                                                  Obj->getHeader().cpusubtype);
351     if (!Opt.Archs.has(Arch))
352       return make_error<TextAPIError>(TextAPIErrorCode::NoSuchArchitecture);
353 
354     auto Triples = constructTriples(Obj, Arch);
355     for (const auto &T : Triples) {
356       if (mapToPlatformType(T) == PLATFORM_UNKNOWN)
357         return make_error<TextAPIError>(TextAPIErrorCode::UnsupportedTarget);
358       Results.emplace_back(std::make_shared<RecordsSlice>(RecordsSlice({T})));
359       if (auto Err = load(Obj, *Results.back(), Opt, Arch))
360         return std::move(Err);
361       Results.back()->getBinaryAttrs().Path = Buffer.getBufferIdentifier();
362     }
363     return Results;
364   }
365 
366   // Only expect MachO universal binaries at this point.
367   assert(isa<MachOUniversalBinary>(&Bin) &&
368          "Expected a MachO universal binary.");
369   auto *UB = cast<MachOUniversalBinary>(&Bin);
370 
371   for (auto OI = UB->begin_objects(), OE = UB->end_objects(); OI != OE; ++OI) {
372     // Skip architecture if not requested.
373     auto Arch =
374         getArchitectureFromCpuType(OI->getCPUType(), OI->getCPUSubType());
375     if (!Opt.Archs.has(Arch))
376       continue;
377 
378     // Skip unknown architectures.
379     if (Arch == AK_unknown)
380       continue;
381 
382     // This can fail if the object is an archive.
383     auto ObjOrErr = OI->getAsObjectFile();
384 
385     // Skip the archive and consume the error.
386     if (!ObjOrErr) {
387       consumeError(ObjOrErr.takeError());
388       continue;
389     }
390 
391     auto &Obj = *ObjOrErr.get();
392     switch (Obj.getHeader().filetype) {
393     default:
394       break;
395     case MachO::MH_BUNDLE:
396     case MachO::MH_DYLIB:
397     case MachO::MH_DYLIB_STUB:
398       for (const auto &T : constructTriples(&Obj, Arch)) {
399         Results.emplace_back(std::make_shared<RecordsSlice>(RecordsSlice({T})));
400         if (auto Err = load(&Obj, *Results.back(), Opt, Arch))
401           return std::move(Err);
402       }
403       break;
404     }
405   }
406 
407   if (Results.empty())
408     return make_error<TextAPIError>(TextAPIErrorCode::EmptyResults);
409   return Results;
410 }
411