2 * Copyright (c) 2007-2009 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
33 #include <sys/errno.h>
34 #include <mach-o/fat.h>
35 #include <mach-o/arch.h>
36 #include <mach-o/loader.h>
38 // from "objc-private.h"
39 // masks for objc_image_info.flags
40 #define OBJC_IMAGE_SUPPORTS_GC (1<<1)
42 // Some OS X SDKs don't define these.
44 #define CPU_TYPE_ARM ((cpu_type_t) 12)
46 #ifndef CPU_ARCH_ABI64
47 #define CPU_ARCH_ABI64 0x01000000 /* 64 bit ABI */
49 #ifndef CPU_TYPE_ARM64
50 #define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
53 // File abstraction taken from ld64/FileAbstraction.hpp
54 // and ld64/MachOFileAbstraction.hpp.
57 #define INLINE __attribute__((always_inline))
63 // This abstraction layer is for use with file formats that have 64-bit/32-bit and Big-Endian/Little-Endian variants
65 // For example: to make a utility that handles 32-bit little enidan files use: Pointer32<LittleEndian>
68 // get16() read a 16-bit number from an E endian struct
69 // set16() write a 16-bit number to an E endian struct
70 // get32() read a 32-bit number from an E endian struct
71 // set32() write a 32-bit number to an E endian struct
72 // get64() read a 64-bit number from an E endian struct
73 // set64() write a 64-bit number to an E endian struct
75 // getBits() read a bit field from an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
76 // setBits() write a bit field to an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
78 // getBitsRaw() read a bit field from a struct with native endianness
79 // setBitsRaw() write a bit field from a struct with native endianness
85 static uint16_t get16(const uint16_t& from
) INLINE
{ return OSReadBigInt16(&from
, 0); }
86 static void set16(uint16_t& into
, uint16_t value
) INLINE
{ OSWriteBigInt16(&into
, 0, value
); }
88 static uint32_t get32(const uint32_t& from
) INLINE
{ return OSReadBigInt32(&from
, 0); }
89 static void set32(uint32_t& into
, uint32_t value
) INLINE
{ OSWriteBigInt32(&into
, 0, value
); }
91 static uint64_t get64(const uint64_t& from
) INLINE
{ return OSReadBigInt64(&from
, 0); }
92 static void set64(uint64_t& into
, uint64_t value
) INLINE
{ OSWriteBigInt64(&into
, 0, value
); }
94 static uint32_t getBits(const uint32_t& from
,
95 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ return getBitsRaw(get32(from
), firstBit
, bitCount
); }
96 static void setBits(uint32_t& into
, uint32_t value
,
97 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ uint32_t temp
= get32(into
); setBitsRaw(temp
, value
, firstBit
, bitCount
); set32(into
, temp
); }
99 static uint32_t getBitsRaw(const uint32_t& from
,
100 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ return ((from
>> (32-firstBit
-bitCount
)) & ((1<<bitCount
)-1)); }
101 static void setBitsRaw(uint32_t& into
, uint32_t value
,
102 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ uint32_t temp
= into
;
103 const uint32_t mask
= ((1<<bitCount
)-1);
104 temp
&= ~(mask
<< (32-firstBit
-bitCount
));
105 temp
|= ((value
& mask
) << (32-firstBit
-bitCount
));
107 enum { little_endian
= 0 };
114 static uint16_t get16(const uint16_t& from
) INLINE
{ return OSReadLittleInt16(&from
, 0); }
115 static void set16(uint16_t& into
, uint16_t value
) INLINE
{ OSWriteLittleInt16(&into
, 0, value
); }
117 static uint32_t get32(const uint32_t& from
) INLINE
{ return OSReadLittleInt32(&from
, 0); }
118 static void set32(uint32_t& into
, uint32_t value
) INLINE
{ OSWriteLittleInt32(&into
, 0, value
); }
120 static uint64_t get64(const uint64_t& from
) INLINE
{ return OSReadLittleInt64(&from
, 0); }
121 static void set64(uint64_t& into
, uint64_t value
) INLINE
{ OSWriteLittleInt64(&into
, 0, value
); }
123 static uint32_t getBits(const uint32_t& from
,
124 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ return getBitsRaw(get32(from
), firstBit
, bitCount
); }
125 static void setBits(uint32_t& into
, uint32_t value
,
126 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ uint32_t temp
= get32(into
); setBitsRaw(temp
, value
, firstBit
, bitCount
); set32(into
, temp
); }
128 static uint32_t getBitsRaw(const uint32_t& from
,
129 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ return ((from
>> firstBit
) & ((1<<bitCount
)-1)); }
130 static void setBitsRaw(uint32_t& into
, uint32_t value
,
131 uint8_t firstBit
, uint8_t bitCount
) INLINE
{ uint32_t temp
= into
;
132 const uint32_t mask
= ((1<<bitCount
)-1);
133 temp
&= ~(mask
<< firstBit
);
134 temp
|= ((value
& mask
) << firstBit
);
136 enum { little_endian
= 1 };
140 typedef BigEndian CurrentEndian
;
141 typedef LittleEndian OtherEndian
;
142 #elif __LITTLE_ENDIAN__
143 typedef LittleEndian CurrentEndian
;
144 typedef BigEndian OtherEndian
;
146 #error unknown endianness
150 template <typename _E
>
154 typedef uint32_t uint_t
;
155 typedef int32_t sint_t
;
158 static uint64_t getP(const uint_t
& from
) INLINE
{ return _E::get32(from
); }
159 static void setP(uint_t
& into
, uint64_t value
) INLINE
{ _E::set32(into
, value
); }
163 template <typename _E
>
167 typedef uint64_t uint_t
;
168 typedef int64_t sint_t
;
171 static uint64_t getP(const uint_t
& from
) INLINE
{ return _E::get64(from
); }
172 static void setP(uint_t
& into
, uint64_t value
) INLINE
{ _E::set64(into
, value
); }
177 // mach-o file header
179 template <typename P
> struct macho_header_content
{};
180 template <> struct macho_header_content
<Pointer32
<BigEndian
> > { mach_header fields
; };
181 template <> struct macho_header_content
<Pointer64
<BigEndian
> > { mach_header_64 fields
; };
182 template <> struct macho_header_content
<Pointer32
<LittleEndian
> > { mach_header fields
; };
183 template <> struct macho_header_content
<Pointer64
<LittleEndian
> > { mach_header_64 fields
; };
185 template <typename P
>
188 uint32_t magic() const INLINE
{ return E::get32(header
.fields
.magic
); }
189 void set_magic(uint32_t value
) INLINE
{ E::set32(header
.fields
.magic
, value
); }
191 uint32_t cputype() const INLINE
{ return E::get32(header
.fields
.cputype
); }
192 void set_cputype(uint32_t value
) INLINE
{ E::set32((uint32_t&)header
.fields
.cputype
, value
); }
194 uint32_t cpusubtype() const INLINE
{ return E::get32(header
.fields
.cpusubtype
); }
195 void set_cpusubtype(uint32_t value
) INLINE
{ E::set32((uint32_t&)header
.fields
.cpusubtype
, value
); }
197 uint32_t filetype() const INLINE
{ return E::get32(header
.fields
.filetype
); }
198 void set_filetype(uint32_t value
) INLINE
{ E::set32(header
.fields
.filetype
, value
); }
200 uint32_t ncmds() const INLINE
{ return E::get32(header
.fields
.ncmds
); }
201 void set_ncmds(uint32_t value
) INLINE
{ E::set32(header
.fields
.ncmds
, value
); }
203 uint32_t sizeofcmds() const INLINE
{ return E::get32(header
.fields
.sizeofcmds
); }
204 void set_sizeofcmds(uint32_t value
) INLINE
{ E::set32(header
.fields
.sizeofcmds
, value
); }
206 uint32_t flags() const INLINE
{ return E::get32(header
.fields
.flags
); }
207 void set_flags(uint32_t value
) INLINE
{ E::set32(header
.fields
.flags
, value
); }
209 uint32_t reserved() const INLINE
{ return E::get32(header
.fields
.reserved
); }
210 void set_reserved(uint32_t value
) INLINE
{ E::set32(header
.fields
.reserved
, value
); }
212 typedef typename
P::E E
;
214 macho_header_content
<P
> header
;
219 // mach-o load command
221 template <typename P
>
222 class macho_load_command
{
224 uint32_t cmd() const INLINE
{ return E::get32(command
.cmd
); }
225 void set_cmd(uint32_t value
) INLINE
{ E::set32(command
.cmd
, value
); }
227 uint32_t cmdsize() const INLINE
{ return E::get32(command
.cmdsize
); }
228 void set_cmdsize(uint32_t value
) INLINE
{ E::set32(command
.cmdsize
, value
); }
230 typedef typename
P::E E
;
232 load_command command
;
239 // mach-o segment load command
241 template <typename P
> struct macho_segment_content
{};
242 template <> struct macho_segment_content
<Pointer32
<BigEndian
> > { segment_command fields
; enum { CMD
= LC_SEGMENT
}; };
243 template <> struct macho_segment_content
<Pointer64
<BigEndian
> > { segment_command_64 fields
; enum { CMD
= LC_SEGMENT_64
}; };
244 template <> struct macho_segment_content
<Pointer32
<LittleEndian
> > { segment_command fields
; enum { CMD
= LC_SEGMENT
}; };
245 template <> struct macho_segment_content
<Pointer64
<LittleEndian
> > { segment_command_64 fields
; enum { CMD
= LC_SEGMENT_64
}; };
247 template <typename P
>
248 class macho_segment_command
{
250 uint32_t cmd() const INLINE
{ return E::get32(segment
.fields
.cmd
); }
251 void set_cmd(uint32_t value
) INLINE
{ E::set32(segment
.fields
.cmd
, value
); }
253 uint32_t cmdsize() const INLINE
{ return E::get32(segment
.fields
.cmdsize
); }
254 void set_cmdsize(uint32_t value
) INLINE
{ E::set32(segment
.fields
.cmdsize
, value
); }
256 const char* segname() const INLINE
{ return segment
.fields
.segname
; }
257 void set_segname(const char* value
) INLINE
{ strncpy(segment
.fields
.segname
, value
, 16); }
259 uint64_t vmaddr() const INLINE
{ return P::getP(segment
.fields
.vmaddr
); }
260 void set_vmaddr(uint64_t value
) INLINE
{ P::setP(segment
.fields
.vmaddr
, value
); }
262 uint64_t vmsize() const INLINE
{ return P::getP(segment
.fields
.vmsize
); }
263 void set_vmsize(uint64_t value
) INLINE
{ P::setP(segment
.fields
.vmsize
, value
); }
265 uint64_t fileoff() const INLINE
{ return P::getP(segment
.fields
.fileoff
); }
266 void set_fileoff(uint64_t value
) INLINE
{ P::setP(segment
.fields
.fileoff
, value
); }
268 uint64_t filesize() const INLINE
{ return P::getP(segment
.fields
.filesize
); }
269 void set_filesize(uint64_t value
) INLINE
{ P::setP(segment
.fields
.filesize
, value
); }
271 uint32_t maxprot() const INLINE
{ return E::get32(segment
.fields
.maxprot
); }
272 void set_maxprot(uint32_t value
) INLINE
{ E::set32((uint32_t&)segment
.fields
.maxprot
, value
); }
274 uint32_t initprot() const INLINE
{ return E::get32(segment
.fields
.initprot
); }
275 void set_initprot(uint32_t value
) INLINE
{ E::set32((uint32_t&)segment
.fields
.initprot
, value
); }
277 uint32_t nsects() const INLINE
{ return E::get32(segment
.fields
.nsects
); }
278 void set_nsects(uint32_t value
) INLINE
{ E::set32(segment
.fields
.nsects
, value
); }
280 uint32_t flags() const INLINE
{ return E::get32(segment
.fields
.flags
); }
281 void set_flags(uint32_t value
) INLINE
{ E::set32(segment
.fields
.flags
, value
); }
284 CMD
= macho_segment_content
<P
>::CMD
287 typedef typename
P::E E
;
289 macho_segment_content
<P
> segment
;
296 template <typename P
> struct macho_section_content
{};
297 template <> struct macho_section_content
<Pointer32
<BigEndian
> > { section fields
; };
298 template <> struct macho_section_content
<Pointer64
<BigEndian
> > { section_64 fields
; };
299 template <> struct macho_section_content
<Pointer32
<LittleEndian
> > { section fields
; };
300 template <> struct macho_section_content
<Pointer64
<LittleEndian
> > { section_64 fields
; };
302 template <typename P
>
303 class macho_section
{
305 const char* sectname() const INLINE
{ return section
.fields
.sectname
; }
306 void set_sectname(const char* value
) INLINE
{ strncpy(section
.fields
.sectname
, value
, 16); }
308 const char* segname() const INLINE
{ return section
.fields
.segname
; }
309 void set_segname(const char* value
) INLINE
{ strncpy(section
.fields
.segname
, value
, 16); }
311 uint64_t addr() const INLINE
{ return P::getP(section
.fields
.addr
); }
312 void set_addr(uint64_t value
) INLINE
{ P::setP(section
.fields
.addr
, value
); }
314 uint64_t size() const INLINE
{ return P::getP(section
.fields
.size
); }
315 void set_size(uint64_t value
) INLINE
{ P::setP(section
.fields
.size
, value
); }
317 uint32_t offset() const INLINE
{ return E::get32(section
.fields
.offset
); }
318 void set_offset(uint32_t value
) INLINE
{ E::set32(section
.fields
.offset
, value
); }
320 uint32_t align() const INLINE
{ return E::get32(section
.fields
.align
); }
321 void set_align(uint32_t value
) INLINE
{ E::set32(section
.fields
.align
, value
); }
323 uint32_t reloff() const INLINE
{ return E::get32(section
.fields
.reloff
); }
324 void set_reloff(uint32_t value
) INLINE
{ E::set32(section
.fields
.reloff
, value
); }
326 uint32_t nreloc() const INLINE
{ return E::get32(section
.fields
.nreloc
); }
327 void set_nreloc(uint32_t value
) INLINE
{ E::set32(section
.fields
.nreloc
, value
); }
329 uint32_t flags() const INLINE
{ return E::get32(section
.fields
.flags
); }
330 void set_flags(uint32_t value
) INLINE
{ E::set32(section
.fields
.flags
, value
); }
332 uint32_t reserved1() const INLINE
{ return E::get32(section
.fields
.reserved1
); }
333 void set_reserved1(uint32_t value
) INLINE
{ E::set32(section
.fields
.reserved1
, value
); }
335 uint32_t reserved2() const INLINE
{ return E::get32(section
.fields
.reserved2
); }
336 void set_reserved2(uint32_t value
) INLINE
{ E::set32(section
.fields
.reserved2
, value
); }
338 typedef typename
P::E E
;
340 macho_section_content
<P
> section
;
346 static bool debug
= true;
348 bool processFile(const char *filename
);
350 int main(int argc
, const char *argv
[]) {
351 for (int i
= 1; i
< argc
; ++i
) {
352 if (!processFile(argv
[i
])) return 1;
363 // Segment and section names are 16 bytes and may be un-terminated.
364 bool segnameEquals(const char *lhs
, const char *rhs
)
366 return 0 == strncmp(lhs
, rhs
, 16);
369 bool segnameStartsWith(const char *segname
, const char *prefix
)
371 return 0 == strncmp(segname
, prefix
, strlen(prefix
));
374 bool sectnameEquals(const char *lhs
, const char *rhs
)
376 return segnameEquals(lhs
, rhs
);
380 template <typename P
>
381 void dosect(uint8_t *start
, macho_section
<P
> *sect
, bool isOldABI
, bool isOSX
)
383 if (debug
) printf("section %.16s from segment %.16s\n",
384 sect
->sectname(), sect
->segname());
387 // Add "supports GC" flag to objc image info
388 if ((segnameStartsWith(sect
->segname(), "__DATA") &&
389 sectnameEquals(sect
->sectname(), "__objc_imageinfo")) ||
390 (segnameEquals(sect
->segname(), "__OBJC") &&
391 sectnameEquals(sect
->sectname(), "__image_info")))
393 imageinfo
*ii
= (imageinfo
*)(start
+ sect
->offset());
394 P::E::set32(ii
->flags
, P::E::get32(ii
->flags
) | OBJC_IMAGE_SUPPORTS_GC
);
395 if (debug
) printf("added GC support flag\n");
400 // Keep init funcs because libSystem doesn't call _objc_init().
402 // Strip S_MOD_INIT/TERM_FUNC_POINTERS. We don't want dyld to call
403 // our init funcs because it is too late, and we don't want anyone to
404 // call our term funcs ever.
405 if (segnameStartsWith(sect
->segname(), "__DATA") &&
406 sectnameEquals(sect
->sectname(), "__mod_init_func"))
408 // section type 0 is S_REGULAR
409 sect
->set_flags(sect
->flags() & ~SECTION_TYPE
);
410 sect
->set_sectname("__objc_init_func");
411 if (debug
) printf("disabled __mod_init_func section\n");
413 if (segnameStartsWith(sect
->segname(), "__DATA") &&
414 sectnameEquals(sect
->sectname(), "__mod_term_func"))
416 // section type 0 is S_REGULAR
417 sect
->set_flags(sect
->flags() & ~SECTION_TYPE
);
418 sect
->set_sectname("__objc_term_func");
419 if (debug
) printf("disabled __mod_term_func section\n");
424 template <typename P
>
425 void doseg(uint8_t *start
, macho_segment_command
<P
> *seg
,
426 bool isOldABI
, bool isOSX
)
428 if (debug
) printf("segment name: %.16s, nsects %u\n",
429 seg
->segname(), seg
->nsects());
430 macho_section
<P
> *sect
= (macho_section
<P
> *)(seg
+ 1);
431 for (uint32_t i
= 0; i
< seg
->nsects(); ++i
) {
432 dosect(start
, §
[i
], isOldABI
, isOSX
);
438 bool parse_macho(uint8_t *buffer
)
440 macho_header
<P
>* mh
= (macho_header
<P
>*)buffer
;
443 bool isOldABI
= false;
445 cmds
= (uint8_t *)(mh
+ 1);
446 for (uint32_t c
= 0; c
< mh
->ncmds(); c
++) {
447 macho_load_command
<P
>* cmd
= (macho_load_command
<P
>*)cmds
;
448 cmds
+= cmd
->cmdsize();
449 if (cmd
->cmd() == LC_SEGMENT
|| cmd
->cmd() == LC_SEGMENT_64
) {
450 macho_segment_command
<P
>* seg
= (macho_segment_command
<P
>*)cmd
;
451 if (segnameEquals(seg
->segname(), "__OBJC")) isOldABI
= true;
453 else if (cmd
->cmd() == LC_VERSION_MIN_MACOSX
) {
458 if (debug
) printf("ABI=%s, OS=%s\n",
459 isOldABI
? "old" : "new", isOSX
? "osx" : "ios");
461 cmds
= (uint8_t *)(mh
+ 1);
462 for (uint32_t c
= 0; c
< mh
->ncmds(); c
++) {
463 macho_load_command
<P
>* cmd
= (macho_load_command
<P
>*)cmds
;
464 cmds
+= cmd
->cmdsize();
465 if (cmd
->cmd() == LC_SEGMENT
|| cmd
->cmd() == LC_SEGMENT_64
) {
466 doseg(buffer
, (macho_segment_command
<P
>*)cmd
, isOldABI
, isOSX
);
474 bool parse_macho(uint8_t *buffer
)
476 uint32_t magic
= *(uint32_t *)buffer
;
480 return parse_macho
<Pointer64
<CurrentEndian
>>(buffer
);
482 return parse_macho
<Pointer32
<CurrentEndian
>>(buffer
);
484 return parse_macho
<Pointer64
<OtherEndian
>>(buffer
);
486 return parse_macho
<Pointer32
<OtherEndian
>>(buffer
);
488 printf("file is not mach-o (magic %x)\n", magic
);
494 bool parse_fat(uint8_t *buffer
, size_t size
)
498 if (size
< sizeof(magic
)) {
499 printf("file is too small\n");
503 magic
= *(uint32_t *)buffer
;
504 if (magic
!= FAT_MAGIC
&& magic
!= FAT_CIGAM
) {
506 return parse_macho(buffer
);
508 struct fat_header
*fh
;
509 uint32_t fat_magic
, fat_nfat_arch
;
510 struct fat_arch
*archs
;
512 if (size
< sizeof(struct fat_header
)) {
513 printf("file is too small\n");
517 fh
= (struct fat_header
*)buffer
;
518 fat_magic
= OSSwapBigToHostInt32(fh
->magic
);
519 fat_nfat_arch
= OSSwapBigToHostInt32(fh
->nfat_arch
);
521 if (size
< (sizeof(struct fat_header
) + fat_nfat_arch
* sizeof(struct fat_arch
))) {
522 printf("file is too small\n");
526 archs
= (struct fat_arch
*)(buffer
+ sizeof(struct fat_header
));
528 /* Special case hidden CPU_TYPE_ARM64 */
529 if (size
>= (sizeof(struct fat_header
) + (fat_nfat_arch
+ 1) * sizeof(struct fat_arch
))) {
530 if (fat_nfat_arch
> 0
531 && OSSwapBigToHostInt32(archs
[fat_nfat_arch
].cputype
) == CPU_TYPE_ARM64
) {
535 /* End special case hidden CPU_TYPE_ARM64 */
537 if (debug
) printf("%d fat architectures\n",
540 for (uint32_t i
= 0; i
< fat_nfat_arch
; i
++) {
541 uint32_t arch_cputype
= OSSwapBigToHostInt32(archs
[i
].cputype
);
542 uint32_t arch_cpusubtype
= OSSwapBigToHostInt32(archs
[i
].cpusubtype
);
543 uint32_t arch_offset
= OSSwapBigToHostInt32(archs
[i
].offset
);
544 uint32_t arch_size
= OSSwapBigToHostInt32(archs
[i
].size
);
546 if (debug
) printf("cputype %d cpusubtype %d\n",
547 arch_cputype
, arch_cpusubtype
);
549 /* Check that slice data is after all fat headers and archs */
550 if (arch_offset
< (sizeof(struct fat_header
) + fat_nfat_arch
* sizeof(struct fat_arch
))) {
551 printf("file is badly formed\n");
555 /* Check that the slice ends before the file does */
556 if (arch_offset
> size
) {
557 printf("file is badly formed\n");
561 if (arch_size
> size
) {
562 printf("file is badly formed\n");
566 if (arch_offset
> (size
- arch_size
)) {
567 printf("file is badly formed\n");
571 bool ok
= parse_macho(buffer
+ arch_offset
);
572 if (!ok
) return false;
578 bool processFile(const char *filename
)
580 if (debug
) printf("file %s\n", filename
);
581 int fd
= open(filename
, O_RDWR
);
583 printf("open %s: %s\n", filename
, strerror(errno
));
588 if (fstat(fd
, &st
) < 0) {
589 printf("fstat %s: %s\n", filename
, strerror(errno
));
593 void *buffer
= mmap(NULL
, (size_t)st
.st_size
, PROT_READ
|PROT_WRITE
,
594 MAP_FILE
|MAP_SHARED
, fd
, 0);
595 if (buffer
== MAP_FAILED
) {
596 printf("mmap %s: %s\n", filename
, strerror(errno
));
600 bool result
= parse_fat((uint8_t *)buffer
, (size_t)st
.st_size
);
601 munmap(buffer
, (size_t)st
.st_size
);