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