1 /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
3 * Copyright (c) 2015 Apple Inc. All rights reserved.
5 * @APPLE_LICENSE_HEADER_START@
7 * This file contains Original Code and/or Modifications of Original Code
8 * as defined in and that are subject to the Apple Public Source License
9 * Version 2.0 (the 'License'). You may not use this file except in
10 * compliance with the License. Please obtain a copy of the License at
11 * http://www.opensource.apple.com/apsl/ and read it before using this
14 * The Original Code and all software distributed under the License are
15 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
19 * Please see the License for the specific language governing rights and
20 * limitations under the License.
22 * @APPLE_LICENSE_HEADER_END@
26 #include <sys/param.h>
31 #include "Architectures.hpp"
32 #include "bitcode.hpp"
33 #include "MachOFileAbstraction.hpp"
34 #include "MachOTrie.hpp"
35 #include "generic_dylib_file.hpp"
36 #include "textstub_dylib_file.hpp"
41 /// A token is a light-weight reference to the content of an nmap'ed file. It
42 /// doesn't own the data and it doesn't make a copy of it. The referenced data
43 /// is only valid as long as the file is mapped in.
49 int compareMemory(const char* lhs
, const char* rhs
, size_t size
) const {
52 return ::memcmp(lhs
, rhs
, size
);
56 Token() : _p(nullptr), _size(0) {}
58 Token(const char* p
) : _p(p
), _size(0) {
63 Token(const char* p
, size_t s
) : _p(p
), _size(s
) {}
65 const char* data() const { return _p
; }
67 size_t size() const { return _size
; }
69 std::string
str() const { return std::string(_p
, _size
); }
71 bool empty() const { return _size
== 0; }
73 bool operator==(Token other
) const {
74 if (_size
!= other
._size
)
76 return compareMemory(_p
, other
._p
, _size
) == 0;
79 bool operator!=(Token other
) const {
80 return !(*this == other
);
85 /// Simple text-based dynamic library file tokenizer.
93 void fetchNextToken();
94 void scanToNextToken();
95 void skip(unsigned distance
) {
97 assert(_current
<= _end
&& "Skipped past the end");
100 const char* skipLineBreak(const char* pos
) const;
101 bool isDelimiter(const char* pos
) const;
104 Tokenizer(const char* data
, uint64_t size
) : _start(data
), _current(data
), _end(data
+ size
) {}
111 Token
peek() { return _currentToken
; }
113 Token token
= peek();
119 const char* Tokenizer::skipLineBreak(const char* pos
) const
125 if ( *pos
== 0x0D ) {
127 if ( pos
+ 1 != _end
&& *(pos
+ 1) == 0x0A)
139 void Tokenizer::scanToNextToken() {
141 while ( isDelimiter(_current
) )
144 const char* i
= skipLineBreak(_current
);
153 bool Tokenizer::isDelimiter(const char* pos
) const {
156 if ( *pos
== ' ' || *pos
== '\t' || *pos
== '\r' || *pos
== '\n' || *pos
== ',' || *pos
== ':' || *pos
== '\'' || *pos
== '\"' )
161 void Tokenizer::fetchNextToken() {
164 if (_current
== _end
) {
165 _currentToken
= Token();
169 auto start
= _current
;
170 while ( !isDelimiter(_current
) ) {
174 _currentToken
= Token(start
, _current
- start
);
178 /// Representation of a parsed text-based dynamic library file.
180 struct DynamicLibrary
{
182 uint32_t _currentVersion
;
183 uint32_t _compatibilityVersion
;
184 uint8_t _swiftVersion
;
185 ld::File::ObjcConstraint _objcConstraint
;
186 Options::Platform _platform
;
187 std::vector
<Token
> _allowedClients
;
188 std::vector
<Token
> _reexportedLibraries
;
189 std::vector
<Token
> _symbols
;
190 std::vector
<Token
> _classes
;
191 std::vector
<Token
> _ivars
;
192 std::vector
<Token
> _weakDefSymbols
;
193 std::vector
<Token
> _tlvSymbols
;
195 DynamicLibrary() : _currentVersion(0x10000), _compatibilityVersion(0x10000), _swiftVersion(0),
196 _objcConstraint(ld::File::objcConstraintNone
) {}
200 /// A simple text-based dynamic library file parser.
203 Tokenizer _tokenizer
;
205 Token
peek() { return _tokenizer
.peek(); }
206 Token
next() { return _tokenizer
.next(); }
208 void expectToken(Token str
) {
209 Token token
= next();
211 throwf("unexpected token: %s", token
.str().c_str());
214 bool hasOptionalToken(Token str
) {
216 if ( token
== str
) {
224 void parseFlowSequence(std::function
<void (Token
)> func
) {
239 void parseAllowedClients(DynamicLibrary
& lib
) {
240 if ( !hasOptionalToken("allowed-clients") )
242 parseFlowSequence([&](Token name
) {
243 lib
._allowedClients
.emplace_back(name
);
247 void parseReexportedDylibs(DynamicLibrary
& lib
) {
248 if ( !hasOptionalToken("re-exports") )
250 parseFlowSequence([&](Token name
) {
251 lib
._reexportedLibraries
.emplace_back(name
);
255 void parseSymbols(DynamicLibrary
& lib
) {
256 if ( hasOptionalToken("symbols") ) {
257 parseFlowSequence([&](Token name
) {
258 lib
._symbols
.emplace_back(name
);
262 if ( hasOptionalToken("objc-classes") ) {
263 parseFlowSequence([&](Token name
) {
264 lib
._classes
.emplace_back(name
);
268 if ( hasOptionalToken("objc-ivars") ) {
269 parseFlowSequence([&](Token name
) {
270 lib
._ivars
.emplace_back(name
);
274 if ( hasOptionalToken("weak-def-symbols") ) {
275 parseFlowSequence([&](Token name
) {
276 lib
._weakDefSymbols
.emplace_back(name
);
280 if ( hasOptionalToken("thread-local-symbols") ) {
281 parseFlowSequence([&](Token name
) {
282 lib
._tlvSymbols
.emplace_back(name
);
287 std::vector
<std::string
> parseArchFlowSequence() {
288 std::vector
<std::string
> availabledArchitectures
;
289 expectToken("archs");
290 parseFlowSequence([&](Token name
) {
291 availabledArchitectures
.emplace_back(name
.str());
293 return availabledArchitectures
;
296 bool parseArchFlowSequence(std::string
&selectedArchName
) {
297 auto availabledArchitectures
= parseArchFlowSequence();
299 for (const auto &archName
: availabledArchitectures
) {
300 if (archName
== selectedArchName
)
307 void parsePlatform(DynamicLibrary
& lib
) {
308 expectToken("platform");
311 if (token
== "macosx")
312 lib
._platform
= Options::kPlatformOSX
;
313 else if (token
== "ios")
314 lib
._platform
= Options::kPlatformiOS
;
315 else if (token
== "watchos")
316 lib
._platform
= Options::kPlatformWatchOS
;
318 else if (token
== "tvos")
319 lib
._platform
= Options::kPlatform_tvOS
;
322 lib
._platform
= Options::kPlatformUnknown
;
325 void parseInstallName(DynamicLibrary
& lib
) {
326 expectToken("install-name");
328 lib
._installName
= next();
329 if ( lib
._installName
.empty() )
330 throwf("no install name specified");
333 uint32_t parseVersionNumber32(Token token
) {
334 if ( token
.size() >= 128 )
335 throwf("malformed version number");
337 // Make a null-terminated string.
339 ::memcpy(buffer
, token
.data(), token
.size());
340 buffer
[token
.size()] = '\0';
342 return Options::parseVersionNumber32(buffer
);
345 void parseCurrentVersion(DynamicLibrary
& lib
) {
346 if ( !hasOptionalToken("current-version") )
348 lib
._currentVersion
= parseVersionNumber32(next());
351 void parseCompatibilityVersion(DynamicLibrary
& lib
) {
352 if ( !hasOptionalToken("compatibility-version") )
354 lib
._compatibilityVersion
= parseVersionNumber32(next());
357 void parseSwiftVersion(DynamicLibrary
& lib
) {
358 if ( !hasOptionalToken("swift-version") )
361 if ( token
== "1.0" )
362 lib
._swiftVersion
= 1;
363 else if ( token
== "1.1" )
364 lib
._swiftVersion
= 2;
365 else if ( token
== "2.0" )
366 lib
._swiftVersion
= 3;
368 throwf("unsupported Swift ABI version: %s", token
.str().c_str());
371 void parseObjCConstraint(DynamicLibrary
& lib
) {
372 if ( !hasOptionalToken("objc-constraint") )
375 if ( token
== "none" )
376 lib
._objcConstraint
= ld::File::objcConstraintNone
;
377 else if ( token
== "retain_release" )
378 lib
._objcConstraint
= ld::File::objcConstraintRetainRelease
;
379 else if ( token
== "retain_release_for_simulator" )
380 lib
._objcConstraint
= ld::File::objcConstraintRetainReleaseForSimulator
;
381 else if ( token
== "retain_release_or_gc" )
382 lib
._objcConstraint
= ld::File::objcConstraintRetainReleaseOrGC
;
383 else if ( token
== "gc" )
384 lib
._objcConstraint
= ld::File::objcConstraintGC
;
386 throwf("unexpected token: %s", token
.str().c_str());
388 void parseExportsBlock(DynamicLibrary
& lib
, std::string
&selectedArchName
) {
389 if ( !hasOptionalToken("exports") )
392 if ( !hasOptionalToken("-") )
396 if ( !parseArchFlowSequence(selectedArchName
) ) {
400 if ( token
== "archs" || token
== "..." || token
.empty() )
404 if (token
== "..." || token
.empty() )
410 parseAllowedClients(lib
);
411 parseReexportedDylibs(lib
);
413 if ( !hasOptionalToken("-") )
418 std::vector
<std::string
> getCompatibleArchList(std::string
&requestedArchName
) {
419 if (requestedArchName
== "i386")
421 else if (requestedArchName
== "x86_64" || requestedArchName
== "x86_64h")
422 return {"x86_64", "x86_64h"};
423 else if (requestedArchName
== "armv7" || requestedArchName
== "armv7s")
424 return {"armv7", "armv7s"};
425 else if (requestedArchName
== "armv7k")
427 else if (requestedArchName
== "arm64")
433 std::string
parseAndSelectArchitecture(std::string
&requestedArchName
) {
434 auto availabledArchitectures
= parseArchFlowSequence();
436 // First try to find an exact match (cpu type and sub-cpu type).
437 if (std::find(availabledArchitectures
.begin(), availabledArchitectures
.end(), requestedArchName
)
438 != availabledArchitectures
.end())
439 return requestedArchName
;
441 // If there is no exact match, then try to find an ABI compatible slice.
442 auto compatibleArchitectures
= getCompatibleArchList(requestedArchName
);
443 std::vector
<std::string
> result
;
444 std::sort(availabledArchitectures
.begin(), availabledArchitectures
.end());
445 std::sort(compatibleArchitectures
.begin(), compatibleArchitectures
.end());
446 std::set_intersection(availabledArchitectures
.begin(), availabledArchitectures
.end(),
447 compatibleArchitectures
.begin(), compatibleArchitectures
.end(),
448 std::back_inserter(result
));
451 return std::string();
453 return result
.front();
456 void parseDocument(DynamicLibrary
& lib
, std::string
&requestedArchName
) {
457 auto selectedArchName
= parseAndSelectArchitecture(requestedArchName
);
458 if (selectedArchName
.empty())
459 throwf("invalid arch");
462 parseInstallName(lib
);
463 parseCurrentVersion(lib
);
464 parseCompatibilityVersion(lib
);
465 parseSwiftVersion(lib
);
466 parseObjCConstraint(lib
);
467 parseExportsBlock(lib
, selectedArchName
);
471 TBDFile(const char* data
, uint64_t size
) : _tokenizer(data
, size
) {}
473 DynamicLibrary
parseFileForArch(std::string requestedArchName
) {
477 parseDocument(lib
, requestedArchName
);
482 bool validForArch(std::string requestedArchName
) {
485 if ( token
!= "---" )
487 return !parseAndSelectArchitecture(requestedArchName
).empty();
495 printf("token: %s\n", token
.str().c_str());
496 } while ( !token
.empty() );
500 } // end anonymous namespace
506 // The reader for a dylib extracts all exported symbols names from the memory-mapped
507 // dylib, builds a hash table, then unmaps the file. This is an important memory
508 // savings for large dylibs.
510 template <typename A
>
511 class File final
: public generic::dylib::File
<A
>
513 using Base
= generic::dylib::File
<A
>;
516 static bool validFile(const uint8_t* fileContent
, bool executableOrDylib
);
517 File(const uint8_t* fileContent
, uint64_t fileLength
, const char* path
,
518 time_t mTime
, ld::File::Ordinal ordinal
, bool linkingFlatNamespace
,
519 bool hoistImplicitPublicDylibs
, Options::Platform platform
,
520 cpu_type_t cpuType
, const char* archName
, uint32_t linkMinOSVersion
,
521 bool allowSimToMacOSX
, bool addVers
, bool buildingForSimulator
,
522 bool logAllFiles
, const char* installPath
, bool indirectDylib
);
523 virtual ~File() noexcept {}
526 void buildExportHashTable(const DynamicLibrary
&lib
);
531 template <typename A
>
532 File
<A
>::File(const uint8_t* fileContent
, uint64_t fileLength
, const char* path
, time_t mTime
,
533 ld::File::Ordinal ord
, bool linkingFlatNamespace
, bool hoistImplicitPublicDylibs
,
534 Options::Platform platform
, cpu_type_t cpuType
, const char* archName
,
535 uint32_t linkMinOSVersion
, bool allowSimToMacOSX
, bool addVers
,
536 bool buildingForSimulator
, bool logAllFiles
, const char* targetInstallPath
,
538 : Base(strdup(path
), mTime
, ord
, platform
, linkMinOSVersion
, linkingFlatNamespace
,
539 hoistImplicitPublicDylibs
, allowSimToMacOSX
, addVers
),
542 this->_bitcode
= std::unique_ptr
<ld::Bitcode
>(new ld::Bitcode(nullptr, 0));
543 // Text stubs are implicit app extension safe.
544 this->_appExtensionSafe
= true;
546 // write out path for -t option
548 printf("%s\n", path
);
550 TBDFile
stub((const char*)fileContent
, fileLength
);
551 auto lib
= stub
.parseFileForArch(archName
);
553 this->_noRexports
= lib
._reexportedLibraries
.empty();
554 this->_hasWeakExports
= !lib
._weakDefSymbols
.empty();
555 this->_dylibInstallPath
= strdup(lib
._installName
.str().c_str());
556 this->_dylibCurrentVersion
= lib
._currentVersion
;
557 this->_dylibCompatibilityVersion
= lib
._compatibilityVersion
;
558 this->_swiftVersion
= lib
._swiftVersion
;
559 this->_objcConstraint
= lib
._objcConstraint
;
560 this->_hasPublicInstallName
= this->isPublicLocation(this->_dylibInstallPath
);
562 // if framework, capture framework name
563 const char* lastSlash
= strrchr(this->_dylibInstallPath
, '/');
564 if ( lastSlash
!= NULL
) {
565 const char* leafName
= lastSlash
+1;
566 char frname
[strlen(leafName
)+32];
567 strcpy(frname
, leafName
);
568 strcat(frname
, ".framework/");
570 if ( strstr(this->_dylibInstallPath
, frname
) != NULL
)
571 this->_frameworkName
= leafName
;
574 // TEMPORARY HACK BEGIN: Support ancient re-export command LC_SUB_FRAMEWORK.
575 // <rdar://problem/23614899> [TAPI] Support LC_SUB_FRAMEWORK as re-export indicator.
576 auto installName
= std::string(this->_dylibInstallPath
);
578 // All sub-frameworks of ApplicationServices use LC_SUB_FRAMEWORK.
579 if (installName
.find("/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/") == 0 &&
580 installName
.find(".dylib") == std::string::npos
) {
581 this->_parentUmbrella
= "ApplicationServices";
582 } else if (installName
.find("/System/Library/Frameworks/Carbon.framework/Versions/A/Frameworks/") == 0) {
583 this->_parentUmbrella
= "Carbon";
584 } else if (installName
.find("/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/") == 0 &&
585 installName
.find(".dylib") == std::string::npos
) {
586 this->_parentUmbrella
= "CoreServices";
587 } else if (installName
.find("/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib") == 0 ||
588 installName
.find("/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib") == 0 ||
589 installName
.find("System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib") == 0) {
590 this->_parentUmbrella
= "vecLib";
591 } else if (installName
.find("/System/Library/Frameworks/WebKit.framework/Versions/A/Frameworks/WebCore.framework/Versions/A/WebCore") == 0) {
592 this->_parentUmbrella
= "WebKit";
593 } else if (installName
.find("/usr/lib/system/") == 0 &&
594 installName
!= "/usr/lib/system/libkxld.dylib") {
595 this->_parentUmbrella
= "System";
597 // TEMPORARY HACK END
599 for (auto &client
: lib
._allowedClients
) {
600 if ((this->_parentUmbrella
!= nullptr) && (client
.str() != this->_parentUmbrella
))
601 this->_allowableClients
.push_back(strdup(client
.str().c_str()));
604 // <rdar://problem/20659505> [TAPI] Don't hoist "public" (in /usr/lib/) dylibs that should not be directly linked
605 if ( !this->_allowableClients
.empty() )
606 this->_hasPublicInstallName
= false;
608 if ( (lib
._platform
!= platform
) && (platform
!= Options::kPlatformUnknown
) ) {
609 this->_wrongOS
= true;
610 if ( this->_addVersionLoadCommand
&& !indirectDylib
) {
611 if ( buildingForSimulator
) {
612 if ( !this->_allowSimToMacOSXLinking
)
613 throwf("building for %s simulator, but linking against dylib built for %s (%s).",
614 Options::platformName(platform
), Options::platformName(lib
._platform
), path
);
616 throwf("building for %s, but linking against dylib built for %s (%s).",
617 Options::platformName(platform
), Options::platformName(lib
._platform
), path
);
622 this->_dependentDylibs
.reserve(lib
._reexportedLibraries
.size());
623 for ( const auto& reexport
: lib
._reexportedLibraries
) {
624 const char *path
= strdup(reexport
.str().c_str());
625 if ( (targetInstallPath
== nullptr) || (strcmp(targetInstallPath
, path
) != 0) )
626 this->_dependentDylibs
.emplace_back(path
, true);
630 buildExportHashTable(lib
);
632 munmap((caddr_t
)fileContent
, fileLength
);
635 template <typename A
>
636 void File
<A
>::buildExportHashTable(const DynamicLibrary
& lib
) {
637 if (this->_s_logHashtable
)
638 fprintf(stderr
, "ld: building hashtable from text-stub info in %s\n", this->path());
640 for (auto &sym
: lib
._symbols
)
641 this->addSymbol(sym
.str().c_str());
643 #if SUPPORT_ARCH_i386
644 if (this->_platform
== Options::kPlatformOSX
&& _cpuType
== CPU_TYPE_I386
) {
645 for (auto &sym
: lib
._classes
)
646 this->addSymbol((".objc_class_name" + sym
.str()).c_str());
648 for (auto &sym
: lib
._classes
) {
649 this->addSymbol(("_OBJC_CLASS_$" + sym
.str()).c_str());
650 this->addSymbol(("_OBJC_METACLASS_$" + sym
.str()).c_str());
654 for (auto &sym
: lib
._classes
) {
655 this->addSymbol(("_OBJC_CLASS_$" + sym
.str()).c_str());
656 this->addSymbol(("_OBJC_METACLASS_$" + sym
.str()).c_str());
660 for (auto &sym
: lib
._ivars
)
661 this->addSymbol(("_OBJC_IVAR_$" + sym
.str()).c_str());
663 for (auto &sym
: lib
._weakDefSymbols
)
664 this->addSymbol(sym
.str().c_str(), /*weak=*/true);
666 for (auto &sym
: lib
._tlvSymbols
)
667 this->addSymbol(sym
.str().c_str(), /*weak=*/false, /*tlv=*/true);
670 template <typename A
>
674 using P
= typename
A::P
;
676 static bool validFile(const uint8_t* fileContent
, uint64_t fileLength
,
677 const std::string
&path
, const char* archName
);
678 static ld::dylib::File
* parse(const uint8_t* fileContent
, uint64_t fileLength
, const char* path
,
679 time_t mTime
, ld::File::Ordinal ordinal
, const Options
& opts
,
682 return new File
<A
>(fileContent
, fileLength
, path
, mTime
, ordinal
,
683 opts
.flatNamespace(),
684 opts
.implicitlyLinkIndirectPublicDylibs(),
687 opts
.architectureName(),
689 opts
.allowSimulatorToLinkWithMacOSX(),
690 opts
.addVersionLoadCommand(),
691 opts
.targetIOSSimulator(),
698 template <typename A
>
699 bool Parser
<A
>::validFile(const uint8_t* fileContent
, uint64_t fileLength
, const std::string
&path
,
700 const char* archName
)
702 if ( path
.find(".tbd", path
.size()-4) == std::string::npos
)
705 TBDFile
stub((const char*)fileContent
, fileLength
);
706 if ( !stub
.validForArch(archName
) )
707 throwf("missing required architecture %s in file %s", archName
, path
.c_str());
713 // main function used by linker to instantiate ld::Files
715 ld::dylib::File
* parse(const uint8_t* fileContent
, uint64_t fileLength
, const char* path
,
716 time_t modTime
, const Options
& opts
, ld::File::Ordinal ordinal
,
717 bool bundleLoader
, bool indirectDylib
)
719 switch ( opts
.architecture() ) {
720 #if SUPPORT_ARCH_x86_64
721 case CPU_TYPE_X86_64
:
722 if ( Parser
<x86_64
>::validFile(fileContent
, fileLength
, path
, opts
.architectureName()) )
723 return Parser
<x86_64
>::parse(fileContent
, fileLength
, path
, modTime
, ordinal
, opts
, indirectDylib
);
726 #if SUPPORT_ARCH_i386
728 if ( Parser
<x86
>::validFile(fileContent
, fileLength
, path
, opts
.architectureName()) )
729 return Parser
<x86
>::parse(fileContent
, fileLength
, path
, modTime
, ordinal
, opts
, indirectDylib
);
732 #if SUPPORT_ARCH_arm_any
734 if ( Parser
<arm
>::validFile(fileContent
, fileLength
, path
, opts
.architectureName()) )
735 return Parser
<arm
>::parse(fileContent
, fileLength
, path
, modTime
, ordinal
, opts
, indirectDylib
);
738 #if SUPPORT_ARCH_arm64
740 if ( Parser
<arm64
>::validFile(fileContent
, fileLength
, path
, opts
.architectureName()) )
741 return Parser
<arm64
>::parse(fileContent
, fileLength
, path
, modTime
, ordinal
, opts
, indirectDylib
);
750 } // namespace textstub