]>
Commit | Line | Data |
---|---|---|
d696c285 A |
1 | /* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*- |
2 | * | |
2f2f92e4 | 3 | * Copyright (c) 2005-2008 Apple Inc. All rights reserved. |
d696c285 A |
4 | * |
5 | * @APPLE_LICENSE_HEADER_START@ | |
6 | * | |
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 | |
12 | * file. | |
13 | * | |
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. | |
21 | * | |
22 | * @APPLE_LICENSE_HEADER_END@ | |
23 | */ | |
24 | ||
2f2f92e4 A |
25 | #ifndef __OBJECT_FILE_ARCHIVE__ |
26 | #define __OBJECT_FILE_ARCHIVE__ | |
d696c285 A |
27 | |
28 | #include <stdint.h> | |
29 | #include <math.h> | |
30 | #include <unistd.h> | |
31 | #include <sys/param.h> | |
32 | #include <mach-o/ranlib.h> | |
33 | #include <ar.h> | |
34 | ||
35 | #include <vector> | |
36 | #include <set> | |
37 | #include <algorithm> | |
38 | #include <ext/hash_map> | |
39 | ||
40 | #include "MachOFileAbstraction.hpp" | |
41 | #include "ObjectFile.h" | |
42 | #include "MachOReaderRelocatable.hpp" | |
2f2f92e4 A |
43 | #if LTO_SUPPORT |
44 | #include "LTOReader.hpp" | |
45 | #endif | |
46 | ||
d696c285 A |
47 | namespace archive { |
48 | ||
49 | typedef const struct ranlib* ConstRanLibPtr; | |
50 | ||
51 | template <typename A> | |
52 | class Reader : public ObjectFile::Reader | |
53 | { | |
54 | public: | |
55 | static bool validFile(const uint8_t* fileContent, uint64_t fileLength); | |
a61fdf0a A |
56 | Reader(const uint8_t fileContent[], uint64_t fileLength, |
57 | const char* path, time_t modTime, | |
55e3d2f6 | 58 | const LibraryOptions& archiveOptions, |
a61fdf0a | 59 | const ObjectFile::ReaderOptions& options, uint32_t ordinalBase); |
d696c285 A |
60 | virtual ~Reader() {} |
61 | ||
62 | virtual const char* getPath() { return fPath; } | |
a61fdf0a | 63 | virtual time_t getModificationTime(){ return fModTime; } |
d696c285 A |
64 | virtual DebugInfoKind getDebugInfoKind() { return ObjectFile::Reader::kDebugInfoNone; } |
65 | virtual std::vector<class ObjectFile::Atom*>& getAtoms(); | |
66 | virtual std::vector<class ObjectFile::Atom*>* getJustInTimeAtomsFor(const char* name); | |
67 | virtual std::vector<Stab>* getStabs() { return NULL; } | |
55e3d2f6 A |
68 | virtual bool optimize(const std::vector<ObjectFile::Atom*>&, std::vector<ObjectFile::Atom*>&, |
69 | std::vector<const char*>&, const std::set<ObjectFile::Atom*>&, | |
70 | std::vector<ObjectFile::Atom*>& newDeadAtoms, | |
71 | uint32_t, ObjectFile::Reader* writer, | |
72 | ObjectFile::Atom* entryPointAtom, | |
73 | const std::vector<const char*>& llvmOptions, | |
2f2f92e4 A |
74 | bool allGlobalsAReDeadStripRoots, int okind, |
75 | bool verbose, bool saveTemps, const char* outputFilePath, | |
76 | bool pie, bool allowTextRelocs); | |
d696c285 A |
77 | |
78 | private: | |
2f2f92e4 A |
79 | static bool validMachOFile(const uint8_t* fileContent, uint64_t fileLength); |
80 | static bool validLTOFile(const uint8_t* fileContent, uint64_t fileLength); | |
81 | static cpu_type_t architecture(); | |
82 | ||
83 | ||
d696c285 A |
84 | class Entry : ar_hdr |
85 | { | |
86 | public: | |
87 | const char* getName() const; | |
88 | time_t getModTime() const; | |
89 | const uint8_t* getContent() const; | |
90 | uint32_t getContentSize() const; | |
91 | const Entry* getNext() const; | |
92 | private: | |
93 | bool hasLongName() const; | |
94 | unsigned int getLongNameSpace() const; | |
95 | ||
96 | }; | |
97 | ||
98 | class CStringEquals | |
99 | { | |
100 | public: | |
101 | bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } | |
102 | }; | |
103 | typedef __gnu_cxx::hash_map<const char*, const struct ranlib*, __gnu_cxx::hash<const char*>, CStringEquals> NameToEntryMap; | |
104 | ||
105 | typedef typename A::P P; | |
106 | typedef typename A::P::E E; | |
107 | ||
d696c285 A |
108 | const struct ranlib* ranlibHashSearch(const char* name); |
109 | ObjectFile::Reader* makeObjectReaderForMember(const Entry* member); | |
110 | void dumpTableOfContents(); | |
111 | void buildHashTable(); | |
112 | ||
113 | const char* fPath; | |
114 | time_t fModTime; | |
115 | const ObjectFile::ReaderOptions& fOptions; | |
a61fdf0a | 116 | uint32_t fOrdinalBase; |
d696c285 A |
117 | const uint8_t* fFileContent; |
118 | uint64_t fFileLength; | |
119 | const struct ranlib* fTableOfContents; | |
120 | uint32_t fTableOfContentCount; | |
121 | const char* fStringPool; | |
122 | std::vector<class ObjectFile::Atom*> fAllAtoms; | |
2f2f92e4 | 123 | std::vector<class ObjectFile::Reader*> fInstantiatedReaders; |
d696c285 A |
124 | std::set<const class Entry*> fInstantiatedEntries; |
125 | std::set<const class Entry*> fPossibleEntries; | |
126 | NameToEntryMap fHashTable; | |
55e3d2f6 | 127 | bool fForceLoad; |
d696c285 A |
128 | |
129 | static std::vector<class ObjectFile::Atom*> fgEmptyList; | |
130 | }; | |
131 | ||
132 | template <typename A> | |
133 | std::vector<class ObjectFile::Atom*> Reader<A>::fgEmptyList; | |
134 | ||
135 | ||
136 | template <typename A> | |
137 | bool Reader<A>::Entry::hasLongName() const | |
138 | { | |
139 | return ( strncmp(this->ar_name, AR_EFMT1, strlen(AR_EFMT1)) == 0 ); | |
140 | } | |
141 | ||
142 | template <typename A> | |
143 | unsigned int Reader<A>::Entry::getLongNameSpace() const | |
144 | { | |
145 | char* endptr; | |
146 | long result = strtol(&this->ar_name[strlen(AR_EFMT1)], &endptr, 10); | |
147 | return result; | |
148 | } | |
149 | ||
150 | template <typename A> | |
151 | const char* Reader<A>::Entry::getName() const | |
152 | { | |
153 | if ( this->hasLongName() ) { | |
154 | int len = this->getLongNameSpace(); | |
155 | static char longName[256]; | |
156 | strncpy(longName, ((char*)this)+sizeof(ar_hdr), len); | |
157 | longName[len] = '\0'; | |
158 | return longName; | |
159 | } | |
160 | else { | |
161 | static char shortName[20]; | |
162 | strncpy(shortName, this->ar_name, 16); | |
163 | shortName[16] = '\0'; | |
164 | char* space = strchr(shortName, ' '); | |
165 | if ( space != NULL ) | |
166 | *space = '\0'; | |
167 | return shortName; | |
168 | } | |
169 | } | |
170 | ||
171 | template <typename A> | |
172 | time_t Reader<A>::Entry::getModTime() const | |
173 | { | |
174 | char temp[14]; | |
a61fdf0a | 175 | strncpy(temp, this->ar_date, 12); |
d696c285 A |
176 | temp[12] = '\0'; |
177 | char* endptr; | |
a61fdf0a | 178 | return (time_t)strtol(temp, &endptr, 10); |
d696c285 A |
179 | } |
180 | ||
181 | ||
182 | template <typename A> | |
183 | const uint8_t* Reader<A>::Entry::getContent() const | |
184 | { | |
185 | if ( this->hasLongName() ) | |
186 | return ((uint8_t*)this) + sizeof(ar_hdr) + this->getLongNameSpace(); | |
187 | else | |
188 | return ((uint8_t*)this) + sizeof(ar_hdr); | |
189 | } | |
190 | ||
191 | ||
192 | template <typename A> | |
193 | uint32_t Reader<A>::Entry::getContentSize() const | |
194 | { | |
195 | char temp[12]; | |
196 | strncpy(temp, this->ar_size, 10); | |
197 | temp[10] = '\0'; | |
198 | char* endptr; | |
199 | long size = strtol(temp, &endptr, 10); | |
200 | // long name is included in ar_size | |
201 | if ( this->hasLongName() ) | |
202 | size -= this->getLongNameSpace(); | |
203 | return size; | |
204 | } | |
205 | ||
206 | ||
207 | template <typename A> | |
208 | const class Reader<A>::Entry* Reader<A>::Entry::getNext() const | |
209 | { | |
210 | const uint8_t* p = this->getContent() + getContentSize(); | |
211 | p = (const uint8_t*)(((uintptr_t)p+3) & (-4)); // 4-byte align | |
212 | return (class Reader<A>::Entry*)p; | |
213 | } | |
214 | ||
2f2f92e4 A |
215 | |
216 | template <> cpu_type_t Reader<ppc>::architecture() { return CPU_TYPE_POWERPC; } | |
217 | template <> cpu_type_t Reader<ppc64>::architecture() { return CPU_TYPE_POWERPC64; } | |
218 | template <> cpu_type_t Reader<x86>::architecture() { return CPU_TYPE_I386; } | |
219 | template <> cpu_type_t Reader<x86_64>::architecture() { return CPU_TYPE_X86_64; } | |
220 | template <> cpu_type_t Reader<arm>::architecture() { return CPU_TYPE_ARM; } | |
221 | ||
222 | ||
223 | template <typename A> | |
224 | bool Reader<A>::validMachOFile(const uint8_t* fileContent, uint64_t fileLength) | |
225 | { | |
226 | return mach_o::relocatable::Reader<A>::validFile(fileContent); | |
227 | } | |
228 | ||
229 | template <typename A> | |
230 | bool Reader<A>::validLTOFile(const uint8_t* fileContent, uint64_t fileLength) | |
231 | { | |
232 | #if LTO_SUPPORT | |
233 | return lto::Reader::validFile(fileContent, fileLength, architecture()); | |
234 | #else | |
235 | return false; | |
236 | #endif | |
237 | } | |
238 | ||
239 | ||
240 | ||
d696c285 A |
241 | template <typename A> |
242 | bool Reader<A>::validFile(const uint8_t* fileContent, uint64_t fileLength) | |
243 | { | |
244 | // must have valid archive header | |
245 | if ( strncmp((const char*)fileContent, "!<arch>\n", 8) != 0 ) | |
246 | return false; | |
247 | ||
248 | // peak at first .o file and verify it is correct architecture | |
249 | const Entry* const start = (Entry*)&fileContent[8]; | |
250 | const Entry* const end = (Entry*)&fileContent[fileLength]; | |
251 | for (const Entry* p=start; p < end; p = p->getNext()) { | |
252 | const char* memberName = p->getName(); | |
253 | // skip option table-of-content member | |
254 | if ( (p==start) && ((strcmp(memberName, SYMDEF_SORTED) == 0) || (strcmp(memberName, SYMDEF) == 0)) ) | |
255 | continue; | |
256 | // archive is valid if first .o file is valid | |
2f2f92e4 | 257 | return (validMachOFile(p->getContent(), p->getContentSize()) || validLTOFile(p->getContent(), p->getContentSize())); |
d696c285 A |
258 | } |
259 | // empty archive | |
260 | return true; | |
261 | } | |
262 | ||
263 | template <typename A> | |
a61fdf0a | 264 | Reader<A>::Reader(const uint8_t fileContent[], uint64_t fileLength, const char* path, time_t modTime, |
55e3d2f6 | 265 | const LibraryOptions& archiveOptions, |
a61fdf0a A |
266 | const ObjectFile::ReaderOptions& options, uint32_t ordinalBase) |
267 | : fPath(NULL), fModTime(modTime), fOptions(options), fOrdinalBase(ordinalBase), fFileContent(NULL), | |
55e3d2f6 | 268 | fTableOfContents(NULL), fTableOfContentCount(0), fStringPool(NULL), fForceLoad(archiveOptions.fForceLoad) |
d696c285 A |
269 | { |
270 | fPath = strdup(path); | |
271 | fFileContent = fileContent; | |
272 | fFileLength = fileLength; | |
273 | ||
274 | if ( strncmp((const char*)fileContent, "!<arch>\n", 8) != 0 ) | |
275 | throw "not an archive"; | |
276 | ||
a61fdf0a A |
277 | // write out path for -whatsloaded option |
278 | if ( options.fLogAllFiles ) | |
279 | printf("%s\n", path); | |
280 | ||
55e3d2f6 | 281 | if ( !options.fFullyLoadArchives && !fForceLoad ) { |
d696c285 A |
282 | const Entry* const firstMember = (Entry*)&fFileContent[8]; |
283 | if ( (strcmp(firstMember->getName(), SYMDEF_SORTED) == 0) || (strcmp(firstMember->getName(), SYMDEF) == 0) ) { | |
284 | const uint8_t* contents = firstMember->getContent(); | |
285 | uint32_t ranlibArrayLen = E::get32(*((uint32_t*)contents)); | |
286 | fTableOfContents = (const struct ranlib*)&contents[4]; | |
287 | fTableOfContentCount = ranlibArrayLen / sizeof(struct ranlib); | |
288 | fStringPool = (const char*)&contents[ranlibArrayLen+8]; | |
289 | if ( ((uint8_t*)(&fTableOfContents[fTableOfContentCount]) > &fileContent[fileLength]) | |
290 | || ((uint8_t*)fStringPool > &fileContent[fileLength]) ) | |
291 | throw "malformed archive, perhaps wrong architecture"; | |
292 | this->buildHashTable(); | |
293 | } | |
294 | else | |
295 | throw "archive has no table of contents"; | |
296 | } | |
297 | } | |
298 | ||
299 | ||
300 | template <typename A> | |
301 | ObjectFile::Reader* Reader<A>::makeObjectReaderForMember(const Entry* member) | |
302 | { | |
303 | const char* memberName = member->getName(); | |
304 | char memberPath[strlen(fPath) + strlen(memberName)+4]; | |
305 | strcpy(memberPath, fPath); | |
306 | strcat(memberPath, "("); | |
307 | strcat(memberPath, memberName); | |
308 | strcat(memberPath, ")"); | |
309 | //fprintf(stderr, "using %s from %s\n", memberName, fPath); | |
310 | try { | |
a61fdf0a A |
311 | // offset the ordinals in this mach-o .o file, so that atoms layout in same order as in archive |
312 | uint32_t ordinalBase = fOrdinalBase + (uint8_t*)member - fFileContent; | |
2f2f92e4 A |
313 | if ( validMachOFile(member->getContent(), member->getContentSize()) ) { |
314 | return new typename mach_o::relocatable::Reader<A>::Reader(member->getContent(), memberPath, member->getModTime(), fOptions, ordinalBase); | |
315 | } | |
316 | #if LTO_SUPPORT | |
317 | else if ( validLTOFile(member->getContent(), member->getContentSize()) ) { | |
318 | return new typename lto::Reader(member->getContent(), member->getContentSize(), memberPath, member->getModTime(), fOptions, architecture()); | |
319 | } | |
320 | #endif | |
55e3d2f6 | 321 | throwf("archive member '%s' with length %d is not mach-o or bitcode", memberName, member->getContentSize()); |
d696c285 A |
322 | } |
323 | catch (const char* msg) { | |
324 | throwf("in %s, %s", memberPath, msg); | |
325 | } | |
326 | } | |
327 | ||
a61fdf0a | 328 | |
d696c285 A |
329 | template <typename A> |
330 | std::vector<class ObjectFile::Atom*>& Reader<A>::getAtoms() | |
331 | { | |
55e3d2f6 | 332 | if ( fOptions.fFullyLoadArchives || fForceLoad ) { |
d696c285 A |
333 | // build vector of all atoms from all .o files in this archive |
334 | const Entry* const start = (Entry*)&fFileContent[8]; | |
335 | const Entry* const end = (Entry*)&fFileContent[fFileLength]; | |
d696c285 A |
336 | for (const Entry* p=start; p < end; p = p->getNext()) { |
337 | const char* memberName = p->getName(); | |
338 | if ( (p==start) && ((strcmp(memberName, SYMDEF_SORTED) == 0) || (strcmp(memberName, SYMDEF) == 0)) ) | |
339 | continue; | |
55e3d2f6 A |
340 | if ( fOptions.fWhyLoad ) { |
341 | if ( fForceLoad ) | |
342 | printf("-force_load forced load of %s(%s)\n", this->getPath(), memberName); | |
343 | else | |
344 | printf("-all_load forced load of %s(%s)\n", this->getPath(), memberName); | |
345 | } | |
d696c285 | 346 | ObjectFile::Reader* r = this->makeObjectReaderForMember(p); |
d696c285 A |
347 | std::vector<class ObjectFile::Atom*>& atoms = r->getAtoms(); |
348 | fAllAtoms.insert(fAllAtoms.end(), atoms.begin(), atoms.end()); | |
2f2f92e4 | 349 | fInstantiatedReaders.push_back(r); |
d696c285 A |
350 | } |
351 | return fAllAtoms; | |
352 | } | |
a61fdf0a A |
353 | else if ( fOptions.fLoadAllObjcObjectsFromArchives ) { |
354 | // build vector of all atoms from all .o files containing objc classes in this archive | |
355 | for(class NameToEntryMap::iterator it = fHashTable.begin(); it != fHashTable.end(); ++it) { | |
356 | if ( (strncmp(it->first, ".objc_c", 7) == 0) || (strncmp(it->first, "_OBJC_CLASS_$_", 14) == 0) ) { | |
357 | const Entry* member = (Entry*)&fFileContent[E::get32(it->second->ran_off)]; | |
358 | if ( fInstantiatedEntries.count(member) == 0 ) { | |
359 | if ( fOptions.fWhyLoad ) | |
360 | printf("-ObjC forced load of %s(%s)\n", this->getPath(), member->getName()); | |
361 | // only return these atoms once | |
362 | fInstantiatedEntries.insert(member); | |
363 | ObjectFile::Reader* r = makeObjectReaderForMember(member); | |
364 | std::vector<class ObjectFile::Atom*>& atoms = r->getAtoms(); | |
365 | fAllAtoms.insert(fAllAtoms.end(), atoms.begin(), atoms.end()); | |
2f2f92e4 | 366 | fInstantiatedReaders.push_back(r); |
a61fdf0a A |
367 | } |
368 | } | |
369 | } | |
370 | return fAllAtoms; | |
371 | } | |
d696c285 A |
372 | else { |
373 | // return nonthing for now, getJustInTimeAtomsFor() will return atoms as needed | |
374 | return fgEmptyList; | |
375 | } | |
376 | } | |
377 | ||
2f2f92e4 | 378 | template <typename A> |
55e3d2f6 A |
379 | bool Reader<A>::optimize(const std::vector<ObjectFile::Atom*>& allAtoms, std::vector<ObjectFile::Atom*>& newAtoms, |
380 | std::vector<const char*>& additionalUndefines, const std::set<ObjectFile::Atom*>& deadAtoms, | |
381 | std::vector<ObjectFile::Atom*>& newDeadAtoms, | |
382 | uint32_t nextOrdinal, ObjectFile::Reader* writer, ObjectFile::Atom* entryPointAtom, | |
383 | const std::vector<const char*>& llvmOptions, | |
2f2f92e4 A |
384 | bool allGlobalsAReDeadStripRoots, int okind, |
385 | bool verbose, bool saveTemps, const char* outputFilePath, | |
386 | bool pie, bool allowTextRelocs) | |
387 | { | |
55e3d2f6 | 388 | bool result = false; |
2f2f92e4 | 389 | for(std::vector<ObjectFile::Reader*>::iterator it=fInstantiatedReaders.begin(); it != fInstantiatedReaders.end(); ++it) { |
55e3d2f6 A |
390 | result |= (*it)->optimize(allAtoms, newAtoms, additionalUndefines, deadAtoms, newDeadAtoms, nextOrdinal, |
391 | writer, entryPointAtom, llvmOptions, allGlobalsAReDeadStripRoots, okind, | |
392 | verbose, saveTemps, outputFilePath, pie, allowTextRelocs); | |
2f2f92e4 | 393 | } |
55e3d2f6 | 394 | return result; |
2f2f92e4 A |
395 | } |
396 | ||
397 | ||
d696c285 | 398 | |
d696c285 A |
399 | template <typename A> |
400 | ConstRanLibPtr Reader<A>::ranlibHashSearch(const char* name) | |
401 | { | |
402 | class NameToEntryMap::iterator pos = fHashTable.find(name); | |
403 | if ( pos != fHashTable.end() ) | |
404 | return pos->second; | |
405 | else | |
406 | return NULL; | |
407 | } | |
408 | ||
409 | template <typename A> | |
410 | void Reader<A>::buildHashTable() | |
411 | { | |
412 | // walk through list backwards, adding/overwriting entries | |
413 | // this assures that with duplicates those earliest in the list will be found | |
414 | for (int i = fTableOfContentCount-1; i >= 0; --i) { | |
415 | const struct ranlib* entry = &fTableOfContents[i]; | |
416 | const char* entryName = &fStringPool[E::get32(entry->ran_un.ran_strx)]; | |
417 | const Entry* member = (Entry*)&fFileContent[E::get32(entry->ran_off)]; | |
418 | //fprintf(stderr, "adding hash %d, %s -> %p\n", i, entryName, entry); | |
419 | fHashTable[entryName] = entry; | |
420 | fPossibleEntries.insert(member); | |
421 | } | |
422 | } | |
423 | ||
424 | template <typename A> | |
425 | void Reader<A>::dumpTableOfContents() | |
426 | { | |
427 | for (unsigned int i=0; i < fTableOfContentCount; ++i) { | |
428 | const struct ranlib* e = &fTableOfContents[i]; | |
429 | printf("%s in %s\n", &fStringPool[E::get32(e->ran_un.ran_strx)], ((Entry*)&fFileContent[E::get32(e->ran_off)])->getName()); | |
430 | } | |
431 | } | |
432 | ||
433 | template <typename A> | |
434 | std::vector<class ObjectFile::Atom*>* Reader<A>::getJustInTimeAtomsFor(const char* name) | |
435 | { | |
55e3d2f6 | 436 | if ( fOptions.fFullyLoadArchives || fForceLoad ) { |
d696c285 A |
437 | return NULL; |
438 | } | |
439 | else { | |
440 | const struct ranlib* result = NULL; | |
441 | // do a hash search of table of contents looking for requested symbol | |
442 | result = ranlibHashSearch(name); | |
443 | if ( result != NULL ) { | |
444 | const Entry* member = (Entry*)&fFileContent[E::get32(result->ran_off)]; | |
445 | if ( fInstantiatedEntries.count(member) == 0 ) { | |
55e3d2f6 | 446 | if ( fOptions.fWhyLoad ) |
69a49097 | 447 | printf("%s forced load of %s(%s)\n", name, this->getPath(), member->getName()); |
d696c285 A |
448 | // only return these atoms once |
449 | fInstantiatedEntries.insert(member); | |
450 | ObjectFile::Reader* r = makeObjectReaderForMember(member); | |
2f2f92e4 | 451 | fInstantiatedReaders.push_back(r); |
d696c285 A |
452 | return new std::vector<class ObjectFile::Atom*>(r->getAtoms()); |
453 | } | |
454 | } | |
455 | //fprintf(stderr, "%s NOT found in archive %s\n", name, fPath); | |
456 | return NULL; | |
457 | } | |
458 | } | |
459 | ||
460 | ||
461 | ||
462 | ||
463 | ||
464 | }; // namespace archive | |
d696c285 A |
465 | |
466 | ||
2f2f92e4 | 467 | #endif // __OBJECT_FILE_ARCHIVE__ |