5 // Created by Louis Gerbarg on 1/27/16.
9 #include <mach-o/loader.h>
10 #include <mach-o/fat.h>
12 #include "mega-dylib-utils.h"
15 #include "MachOProxy.h"
18 std::map
<std::string
, MachOProxy
*> mapMachOFile( const std::string
& path
) {
19 std::map
<std::string
, MachOProxy
*> retval
;
20 const uint8_t* p
= (uint8_t*)( -1 );
24 std::tie( p
, stat_buf
, rootless
) = fileCache
.cacheLoad( path
);
26 if ( p
== (uint8_t*)( -1 ) ) {
30 // if fat file, process each architecture
31 const fat_header
* fh
= (fat_header
*)p
;
32 const mach_header
* mh
= (mach_header
*)p
;
33 if ( OSSwapBigToHostInt32( fh
->magic
) == FAT_MAGIC
) {
34 // Fat header is always big-endian
35 const fat_arch
* slices
= (const fat_arch
*)( (char*)fh
+ sizeof( fat_header
) );
36 const uint32_t sliceCount
= OSSwapBigToHostInt32( fh
->nfat_arch
);
37 for ( uint32_t i
= 0; i
< sliceCount
; ++i
) {
38 // FIXME Should we validate the fat header matches the slices?
39 ArchPair
arch( OSSwapBigToHostInt32( slices
[i
].cputype
), OSSwapBigToHostInt32( slices
[i
].cpusubtype
) );
40 uint32_t fileOffset
= OSSwapBigToHostInt32( slices
[i
].offset
);
41 const mach_header
* th
= (mach_header
*)(p
+fileOffset
);
42 if ( ( OSSwapLittleToHostInt32( th
->magic
) == MH_MAGIC
) || ( OSSwapLittleToHostInt32( th
->magic
) == MH_MAGIC_64
) ) {
43 uint32_t fileSize
= static_cast<uint32_t>( stat_buf
.st_size
);
44 retval
[stringForArch( arch
)] = new MachOProxy( path
, stat_buf
.st_ino
, stat_buf
.st_mtime
, fileOffset
, fileSize
, rootless
);
47 } else if ( ( OSSwapLittleToHostInt32( mh
->magic
) == MH_MAGIC
) || ( OSSwapLittleToHostInt32( mh
->magic
) == MH_MAGIC_64
) ) {
48 ArchPair
arch( OSSwapLittleToHostInt32( mh
->cputype
), OSSwapLittleToHostInt32( mh
->cpusubtype
) );
49 uint32_t fileOffset
= OSSwapBigToHostInt32( 0 );
50 uint32_t fileSize
= static_cast<uint32_t>( stat_buf
.st_size
);
51 retval
[stringForArch( arch
)] = new MachOProxy( path
, stat_buf
.st_ino
, stat_buf
.st_mtime
, fileOffset
, fileSize
, rootless
);
53 // warning( "file '%s' is not contain requested a MachO", path.c_str() );
60 std::string
MachOProxy::machoParser(bool ignoreUncacheableDylibsInExecutables
)
62 const uint8_t* buffer
= getBuffer();
63 bool hasSplitSegInfo
= false;
64 bool hasDylidInfo
= false;
65 const macho_header
<P
>* mh
= (const macho_header
<P
>*)buffer
;
66 const macho_symtab_command
<P
>* symTab
= nullptr;
67 const macho_dysymtab_command
<P
>* dynSymTab
= nullptr;
68 const macho_load_command
<P
>* const cmds
= (macho_load_command
<P
>*)((uint8_t*)mh
+ sizeof(macho_header
<P
>));
69 const uint32_t cmd_count
= mh
->ncmds();
70 const macho_load_command
<P
>* cmd
= cmds
;
71 _dylib
= (mh
->filetype() == MH_DYLIB
);
72 _executable
= (mh
->filetype() == MH_EXECUTE
);
73 if (mh
->filetype() == MH_DYLIB_STUB
) {
76 for (uint32_t i
= 0; i
< cmd_count
; ++i
) {
79 macho_dylib_command
<P
>* dylib
= (macho_dylib_command
<P
>*)cmd
;
80 if (dylib
->name()[0] != '/') {
81 if (strncmp(dylib
->name(), "@rpath", 6) == 0)
82 return "@rpath cannot be used in -install_name for OS dylibs";
84 return "-install_name is not an absolute path";
86 installName
= dylib
->name();
87 installNameOffsetInTEXT
= (uint32_t)((uint8_t*)cmd
- buffer
) + dylib
->name_offset();
90 const macho_uuid_command
<P
>* uuidCmd
= (macho_uuid_command
<P
>*)cmd
;
91 ::memcpy(uuid
, uuidCmd
->uuid(), sizeof(uuid_t
));
94 case LC_LOAD_WEAK_DYLIB
:
95 case LC_REEXPORT_DYLIB
:
96 case LC_LOAD_UPWARD_DYLIB
: {
97 macho_dylib_command
<P
>* dylib
= (macho_dylib_command
<P
>*)cmd
;
98 std::string depName
= dylib
->name();
99 if ( _executable
&& ignoreUncacheableDylibsInExecutables
&& !has_prefix(depName
, "/usr/lib/") && !has_prefix(depName
, "/System/Library/") ) {
100 // <rdar://problem/25918268> in update_dyld_shared_cache don't warn if root executable links with something not eligible for shared cache
103 else if ( depName
[0] != '/' ) {
104 return "linked against a dylib whose -install_name was non-absolute (e.g. @rpath)";
106 dependencies
.insert(depName
);
108 case macho_segment_command
<P
>::CMD
: {
109 const macho_segment_command
<P
>* segCmd
= (macho_segment_command
<P
>*)cmd
;
110 MachOProxy::Segment seg
;
111 seg
.name
= segCmd
->segname();
112 seg
.size
= align(segCmd
->vmsize(), 12);
113 seg
.diskSize
= (uint32_t)segCmd
->filesize();
114 seg
.fileOffset
= (uint32_t)segCmd
->fileoff();
115 seg
.protection
= segCmd
->initprot();
116 if (segCmd
->nsects() > 0) {
118 const macho_section
<P
>* const sectionsStart
= (macho_section
<P
>*)((uint8_t*)segCmd
+ sizeof(macho_segment_command
<P
>));
119 const macho_section
<P
>* const sectionsLast
= §ionsStart
[segCmd
->nsects() - 1];
120 const macho_section
<P
>* const sectionsEnd
= §ionsStart
[segCmd
->nsects()];
121 for (const macho_section
<P
>* sect
= sectionsStart
; sect
< sectionsEnd
; ++sect
) {
122 if (sect
->align() > seg
.p2align
)
123 seg
.p2align
= sect
->align();
125 seg
.sizeOfSections
= sectionsLast
->addr() + sectionsLast
->size() - segCmd
->vmaddr();
129 segments
.push_back(seg
);
131 case LC_SEGMENT_SPLIT_INFO
:
132 hasSplitSegInfo
= true;
135 symTab
= (macho_symtab_command
<P
>*)cmd
;
138 dynSymTab
= (macho_dysymtab_command
<P
>*)cmd
;
141 case LC_DYLD_INFO_ONLY
:
145 cmd
= (const macho_load_command
<P
>*)(((uint8_t*)cmd
) + cmd
->cmdsize());
153 return "built for old OS";
156 if ((mh
->flags() & MH_TWOLEVEL
) == 0) {
157 return "built with -flat_namespace";
160 if (!hasSplitSegInfo
) {
161 bool inUsrLib
= (installName
.size() > 9) && (installName
.substr(0, 9) == "/usr/lib/");
162 bool inSystemLibrary
= (installName
.size() > 16) && (installName
.substr(0, 16) == "/System/Library/");
163 if (!inUsrLib
&& !inSystemLibrary
) {
164 return "-install_name not /usr/lib/* or /System/Library/*";
166 return "no shared region info";
169 if ((symTab
== nullptr) && (dynSymTab
== nullptr)) {
170 return "no symbol table";
173 if (installName
.empty()) {
174 return "dylib missing install name";
177 // scan undefines looking for invalid ordinals
178 const macho_nlist
<P
>* symbolTable
= (macho_nlist
<P
>*)((uint8_t*)mh
+ symTab
->symoff());
179 const uint32_t startUndefs
= dynSymTab
->iundefsym();
180 const uint32_t endUndefs
= startUndefs
+ dynSymTab
->nundefsym();
181 for (uint32_t i
= startUndefs
; i
< endUndefs
; ++i
) {
182 uint8_t ordinal
= GET_LIBRARY_ORDINAL(symbolTable
[i
].n_desc());
183 if (ordinal
== DYNAMIC_LOOKUP_ORDINAL
) {
184 return "built with '-undefined dynamic_lookup'";
185 } else if (ordinal
== EXECUTABLE_ORDINAL
) {
186 return "built with -bundle_loader";
193 const bool MachOProxy::isDylib()
198 const bool MachOProxy::isExecutable()
203 std::map
<std::string
, MachOProxy
*> MachOProxy::findDylibInfo(const std::string
& path
, bool warnOnProblems
, bool ignoreUncacheableDylibsInExecutables
) {
204 std::map
<std::string
, MachOProxy
*> slices
= mapMachOFile( path
);
205 std::vector
<std::string
> errorSlices
;
207 for ( auto& slice
: slices
) {
208 std::string errorMessage
;
209 verboseLog( "analyzing file '%s'", path
.c_str() );
210 switch ( archForString( slice
.first
).arch
) {
213 errorMessage
= slice
.second
->machoParser
<Pointer32
<LittleEndian
>>(ignoreUncacheableDylibsInExecutables
);
215 case CPU_TYPE_X86_64
:
217 errorMessage
= slice
.second
->machoParser
<Pointer64
<LittleEndian
>>(ignoreUncacheableDylibsInExecutables
);
220 errorMessage
= "unsupported arch '" + slice
.first
+ "'";
224 if ( !errorMessage
.empty() ) {
225 if ( warnOnProblems
)
226 warning( "%s (%s)", errorMessage
.c_str(), path
.c_str() );
227 errorSlices
.push_back( slice
.first
);
231 for ( const auto& slice
: errorSlices
) {
232 slices
.erase( slice
);
238 const uint8_t* MachOProxy::getBuffer() {
239 const uint8_t* p
= (uint8_t*)( -1 );
240 struct stat stat_buf
;
242 std::tie(p
, stat_buf
,rootless
) = fileCache
.cacheLoad(path
);
243 return p
+ fatFileOffset
;
246 bool MachOProxy::addAlias( const std::string
& alias
) {
247 if (!has_prefix(alias
, "/usr/lib/") && !has_prefix(alias
, "/System/Library/"))
249 if ( alias
!= installName
&& installNameAliases
.count( alias
) == 0 ) {
250 installNameAliases
.insert( alias
);