file.mx raw
1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
4
5 /*
6 Package macho implements access to Mach-O object files.
7
8 # Security
9
10 This package is not designed to be hardened against adversarial inputs, and is
11 outside the scope of https://go.dev/security/policy. In particular, only basic
12 validation is done when parsing object files. As such, care should be taken when
13 parsing untrusted inputs, as parsing malformed files may consume significant
14 resources, or cause panics.
15 */
16 package macho
17
18 // High level access to low level data structures.
19
20 import (
21 "bytes"
22 "compress/zlib"
23 "debug/dwarf"
24 "encoding/binary"
25 "fmt"
26 "internal/saferio"
27 "io"
28 "os"
29 )
30
31 // A File represents an open Mach-O file.
32 type File struct {
33 FileHeader
34 ByteOrder binary.ByteOrder
35 Loads []Load
36 Sections []*Section
37
38 Symtab *Symtab
39 Dysymtab *Dysymtab
40
41 closer io.Closer
42 }
43
44 // A Load represents any Mach-O load command.
45 type Load interface {
46 Raw() []byte
47 }
48
49 // A LoadBytes is the uninterpreted bytes of a Mach-O load command.
50 type LoadBytes []byte
51
52 func (b LoadBytes) Raw() []byte { return b }
53
54 // A SegmentHeader is the header for a Mach-O 32-bit or 64-bit load segment command.
55 type SegmentHeader struct {
56 Cmd LoadCmd
57 Len uint32
58 Name string
59 Addr uint64
60 Memsz uint64
61 Offset uint64
62 Filesz uint64
63 Maxprot uint32
64 Prot uint32
65 Nsect uint32
66 Flag uint32
67 }
68
69 // A Segment represents a Mach-O 32-bit or 64-bit load segment command.
70 type Segment struct {
71 LoadBytes
72 SegmentHeader
73
74 // Embed ReaderAt for ReadAt method.
75 // Do not embed SectionReader directly
76 // to avoid having Read and Seek.
77 // If a client wants Read and Seek it must use
78 // Open() to avoid fighting over the seek offset
79 // with other clients.
80 io.ReaderAt
81 sr *io.SectionReader
82 }
83
84 // Data reads and returns the contents of the segment.
85 func (s *Segment) Data() ([]byte, error) {
86 return saferio.ReadDataAt(s.sr, s.Filesz, 0)
87 }
88
89 // Open returns a new ReadSeeker reading the segment.
90 func (s *Segment) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
91
92 type SectionHeader struct {
93 Name string
94 Seg string
95 Addr uint64
96 Size uint64
97 Offset uint32
98 Align uint32
99 Reloff uint32
100 Nreloc uint32
101 Flags uint32
102 }
103
104 // A Reloc represents a Mach-O relocation.
105 type Reloc struct {
106 Addr uint32
107 Value uint32
108 // when Scattered == false && Extern == true, Value is the symbol number.
109 // when Scattered == false && Extern == false, Value is the section number.
110 // when Scattered == true, Value is the value that this reloc refers to.
111 Type uint8
112 Len uint8 // 0=byte, 1=word, 2=long, 3=quad
113 Pcrel bool
114 Extern bool // valid if Scattered == false
115 Scattered bool
116 }
117
118 type Section struct {
119 SectionHeader
120 Relocs []Reloc
121
122 // Embed ReaderAt for ReadAt method.
123 // Do not embed SectionReader directly
124 // to avoid having Read and Seek.
125 // If a client wants Read and Seek it must use
126 // Open() to avoid fighting over the seek offset
127 // with other clients.
128 io.ReaderAt
129 sr *io.SectionReader
130 }
131
132 // Data reads and returns the contents of the Mach-O section.
133 func (s *Section) Data() ([]byte, error) {
134 return saferio.ReadDataAt(s.sr, s.Size, 0)
135 }
136
137 // Open returns a new ReadSeeker reading the Mach-O section.
138 func (s *Section) Open() io.ReadSeeker { return io.NewSectionReader(s.sr, 0, 1<<63-1) }
139
140 // A Dylib represents a Mach-O load dynamic library command.
141 type Dylib struct {
142 LoadBytes
143 Name string
144 Time uint32
145 CurrentVersion uint32
146 CompatVersion uint32
147 }
148
149 // A Symtab represents a Mach-O symbol table command.
150 type Symtab struct {
151 LoadBytes
152 SymtabCmd
153 Syms []Symbol
154 }
155
156 // A Dysymtab represents a Mach-O dynamic symbol table command.
157 type Dysymtab struct {
158 LoadBytes
159 DysymtabCmd
160 IndirectSyms []uint32 // indices into Symtab.Syms
161 }
162
163 // A Rpath represents a Mach-O rpath command.
164 type Rpath struct {
165 LoadBytes
166 Path string
167 }
168
169 // A Symbol is a Mach-O 32-bit or 64-bit symbol table entry.
170 type Symbol struct {
171 Name string
172 Type uint8
173 Sect uint8
174 Desc uint16
175 Value uint64
176 }
177
178 /*
179 * Mach-O reader
180 */
181
182 // FormatError is returned by some operations if the data does
183 // not have the correct format for an object file.
184 type FormatError struct {
185 off int64
186 msg string
187 val any
188 }
189
190 func (e *FormatError) Error() string {
191 msg := e.msg
192 if e.val != nil {
193 msg += fmt.Sprintf(" '%v'", e.val)
194 }
195 msg += fmt.Sprintf(" in record at byte %#x", e.off)
196 return msg
197 }
198
199 // Open opens the named file using [os.Open] and prepares it for use as a Mach-O binary.
200 func Open(name string) (*File, error) {
201 f, err := os.Open(name)
202 if err != nil {
203 return nil, err
204 }
205 ff, err := NewFile(f)
206 if err != nil {
207 f.Close()
208 return nil, err
209 }
210 ff.closer = f
211 return ff, nil
212 }
213
214 // Close closes the [File].
215 // If the [File] was created using [NewFile] directly instead of [Open],
216 // Close has no effect.
217 func (f *File) Close() error {
218 var err error
219 if f.closer != nil {
220 err = f.closer.Close()
221 f.closer = nil
222 }
223 return err
224 }
225
226 // NewFile creates a new [File] for accessing a Mach-O binary in an underlying reader.
227 // The Mach-O binary is expected to start at position 0 in the ReaderAt.
228 func NewFile(r io.ReaderAt) (*File, error) {
229 f := &File{}
230 sr := io.NewSectionReader(r, 0, 1<<63-1)
231
232 // Read and decode Mach magic to determine byte order, size.
233 // Magic32 and Magic64 differ only in the bottom bit.
234 var ident [4]byte
235 if _, err := r.ReadAt(ident[0:], 0); err != nil {
236 return nil, err
237 }
238 be := binary.BigEndian.Uint32(ident[0:])
239 le := binary.LittleEndian.Uint32(ident[0:])
240 switch Magic32 &^ 1 {
241 case be &^ 1:
242 f.ByteOrder = binary.BigEndian
243 f.Magic = be
244 case le &^ 1:
245 f.ByteOrder = binary.LittleEndian
246 f.Magic = le
247 default:
248 return nil, &FormatError{0, "invalid magic number", nil}
249 }
250
251 // Read entire file header.
252 if err := binary.Read(sr, f.ByteOrder, &f.FileHeader); err != nil {
253 return nil, err
254 }
255
256 // Then load commands.
257 offset := int64(fileHeaderSize32)
258 if f.Magic == Magic64 {
259 offset = fileHeaderSize64
260 }
261 dat, err := saferio.ReadDataAt(r, uint64(f.Cmdsz), offset)
262 if err != nil {
263 return nil, err
264 }
265 c := saferio.SliceCap[Load](uint64(f.Ncmd))
266 if c < 0 {
267 return nil, &FormatError{offset, "too many load commands", nil}
268 }
269 f.Loads = []Load{:0:c}
270 bo := f.ByteOrder
271 for i := uint32(0); i < f.Ncmd; i++ {
272 // Each load command begins with uint32 command and length.
273 if len(dat) < 8 {
274 return nil, &FormatError{offset, "command block too small", nil}
275 }
276 cmd, siz := LoadCmd(bo.Uint32(dat[0:4])), bo.Uint32(dat[4:8])
277 if siz < 8 || siz > uint32(len(dat)) {
278 return nil, &FormatError{offset, "invalid command block size", nil}
279 }
280 var cmddat []byte
281 cmddat, dat = dat[0:siz], dat[siz:]
282 offset += int64(siz)
283 var s *Segment
284 switch cmd {
285 default:
286 f.Loads = append(f.Loads, LoadBytes(cmddat))
287
288 case LoadCmdRpath:
289 var hdr RpathCmd
290 b := bytes.NewReader(cmddat)
291 if err := binary.Read(b, bo, &hdr); err != nil {
292 return nil, err
293 }
294 l := &Rpath{}
295 if hdr.Path >= uint32(len(cmddat)) {
296 return nil, &FormatError{offset, "invalid path in rpath command", hdr.Path}
297 }
298 l.Path = cstring(cmddat[hdr.Path:])
299 l.LoadBytes = LoadBytes(cmddat)
300 f.Loads = append(f.Loads, l)
301
302 case LoadCmdDylib:
303 var hdr DylibCmd
304 b := bytes.NewReader(cmddat)
305 if err := binary.Read(b, bo, &hdr); err != nil {
306 return nil, err
307 }
308 l := &Dylib{}
309 if hdr.Name >= uint32(len(cmddat)) {
310 return nil, &FormatError{offset, "invalid name in dynamic library command", hdr.Name}
311 }
312 l.Name = cstring(cmddat[hdr.Name:])
313 l.Time = hdr.Time
314 l.CurrentVersion = hdr.CurrentVersion
315 l.CompatVersion = hdr.CompatVersion
316 l.LoadBytes = LoadBytes(cmddat)
317 f.Loads = append(f.Loads, l)
318
319 case LoadCmdSymtab:
320 var hdr SymtabCmd
321 b := bytes.NewReader(cmddat)
322 if err := binary.Read(b, bo, &hdr); err != nil {
323 return nil, err
324 }
325 strtab, err := saferio.ReadDataAt(r, uint64(hdr.Strsize), int64(hdr.Stroff))
326 if err != nil {
327 return nil, err
328 }
329 var symsz int
330 if f.Magic == Magic64 {
331 symsz = 16
332 } else {
333 symsz = 12
334 }
335 symdat, err := saferio.ReadDataAt(r, uint64(hdr.Nsyms)*uint64(symsz), int64(hdr.Symoff))
336 if err != nil {
337 return nil, err
338 }
339 st, err := f.parseSymtab(symdat, strtab, cmddat, &hdr, offset)
340 if err != nil {
341 return nil, err
342 }
343 f.Loads = append(f.Loads, st)
344 f.Symtab = st
345
346 case LoadCmdDysymtab:
347 var hdr DysymtabCmd
348 b := bytes.NewReader(cmddat)
349 if err := binary.Read(b, bo, &hdr); err != nil {
350 return nil, err
351 }
352 if f.Symtab == nil {
353 return nil, &FormatError{offset, "dynamic symbol table seen before any ordinary symbol table", nil}
354 } else if hdr.Iundefsym > uint32(len(f.Symtab.Syms)) {
355 return nil, &FormatError{offset, fmt.Sprintf(
356 "undefined symbols index in dynamic symbol table command is greater than symbol table length (%d > %d)",
357 hdr.Iundefsym, len(f.Symtab.Syms)), nil}
358 } else if hdr.Iundefsym+hdr.Nundefsym > uint32(len(f.Symtab.Syms)) {
359 return nil, &FormatError{offset, fmt.Sprintf(
360 "number of undefined symbols after index in dynamic symbol table command is greater than symbol table length (%d > %d)",
361 hdr.Iundefsym+hdr.Nundefsym, len(f.Symtab.Syms)), nil}
362 }
363 dat, err := saferio.ReadDataAt(r, uint64(hdr.Nindirectsyms)*4, int64(hdr.Indirectsymoff))
364 if err != nil {
365 return nil, err
366 }
367 x := []uint32{:hdr.Nindirectsyms}
368 if err := binary.Read(bytes.NewReader(dat), bo, x); err != nil {
369 return nil, err
370 }
371 st := &Dysymtab{}
372 st.LoadBytes = LoadBytes(cmddat)
373 st.DysymtabCmd = hdr
374 st.IndirectSyms = x
375 f.Loads = append(f.Loads, st)
376 f.Dysymtab = st
377
378 case LoadCmdSegment:
379 var seg32 Segment32
380 b := bytes.NewReader(cmddat)
381 if err := binary.Read(b, bo, &seg32); err != nil {
382 return nil, err
383 }
384 s = &Segment{}
385 s.LoadBytes = cmddat
386 s.Cmd = cmd
387 s.Len = siz
388 s.Name = cstring(seg32.Name[0:])
389 s.Addr = uint64(seg32.Addr)
390 s.Memsz = uint64(seg32.Memsz)
391 s.Offset = uint64(seg32.Offset)
392 s.Filesz = uint64(seg32.Filesz)
393 s.Maxprot = seg32.Maxprot
394 s.Prot = seg32.Prot
395 s.Nsect = seg32.Nsect
396 s.Flag = seg32.Flag
397 f.Loads = append(f.Loads, s)
398 for i := 0; i < int(s.Nsect); i++ {
399 var sh32 Section32
400 if err := binary.Read(b, bo, &sh32); err != nil {
401 return nil, err
402 }
403 sh := &Section{}
404 sh.Name = cstring(sh32.Name[0:])
405 sh.Seg = cstring(sh32.Seg[0:])
406 sh.Addr = uint64(sh32.Addr)
407 sh.Size = uint64(sh32.Size)
408 sh.Offset = sh32.Offset
409 sh.Align = sh32.Align
410 sh.Reloff = sh32.Reloff
411 sh.Nreloc = sh32.Nreloc
412 sh.Flags = sh32.Flags
413 if err := f.pushSection(sh, r); err != nil {
414 return nil, err
415 }
416 }
417
418 case LoadCmdSegment64:
419 var seg64 Segment64
420 b := bytes.NewReader(cmddat)
421 if err := binary.Read(b, bo, &seg64); err != nil {
422 return nil, err
423 }
424 s = &Segment{}
425 s.LoadBytes = cmddat
426 s.Cmd = cmd
427 s.Len = siz
428 s.Name = cstring(seg64.Name[0:])
429 s.Addr = seg64.Addr
430 s.Memsz = seg64.Memsz
431 s.Offset = seg64.Offset
432 s.Filesz = seg64.Filesz
433 s.Maxprot = seg64.Maxprot
434 s.Prot = seg64.Prot
435 s.Nsect = seg64.Nsect
436 s.Flag = seg64.Flag
437 f.Loads = append(f.Loads, s)
438 for i := 0; i < int(s.Nsect); i++ {
439 var sh64 Section64
440 if err := binary.Read(b, bo, &sh64); err != nil {
441 return nil, err
442 }
443 sh := &Section{}
444 sh.Name = cstring(sh64.Name[0:])
445 sh.Seg = cstring(sh64.Seg[0:])
446 sh.Addr = sh64.Addr
447 sh.Size = sh64.Size
448 sh.Offset = sh64.Offset
449 sh.Align = sh64.Align
450 sh.Reloff = sh64.Reloff
451 sh.Nreloc = sh64.Nreloc
452 sh.Flags = sh64.Flags
453 if err := f.pushSection(sh, r); err != nil {
454 return nil, err
455 }
456 }
457 }
458 if s != nil {
459 if int64(s.Offset) < 0 {
460 return nil, &FormatError{offset, "invalid section offset", s.Offset}
461 }
462 if int64(s.Filesz) < 0 {
463 return nil, &FormatError{offset, "invalid section file size", s.Filesz}
464 }
465 s.sr = io.NewSectionReader(r, int64(s.Offset), int64(s.Filesz))
466 s.ReaderAt = s.sr
467 }
468 }
469 return f, nil
470 }
471
472 func (f *File) parseSymtab(symdat, strtab, cmddat []byte, hdr *SymtabCmd, offset int64) (*Symtab, error) {
473 bo := f.ByteOrder
474 c := saferio.SliceCap[Symbol](uint64(hdr.Nsyms))
475 if c < 0 {
476 return nil, &FormatError{offset, "too many symbols", nil}
477 }
478 symtab := []Symbol{:0:c}
479 b := bytes.NewReader(symdat)
480 for i := 0; i < int(hdr.Nsyms); i++ {
481 var n Nlist64
482 if f.Magic == Magic64 {
483 if err := binary.Read(b, bo, &n); err != nil {
484 return nil, err
485 }
486 } else {
487 var n32 Nlist32
488 if err := binary.Read(b, bo, &n32); err != nil {
489 return nil, err
490 }
491 n.Name = n32.Name
492 n.Type = n32.Type
493 n.Sect = n32.Sect
494 n.Desc = n32.Desc
495 n.Value = uint64(n32.Value)
496 }
497 if n.Name >= uint32(len(strtab)) {
498 return nil, &FormatError{offset, "invalid name in symbol table", n.Name}
499 }
500 // We add "_" to Go symbols. Strip it here. See issue 33808.
501 name := cstring(strtab[n.Name:])
502 if bytes.Contains(name, ".") && name[0] == '_' {
503 name = name[1:]
504 }
505 symtab = append(symtab, Symbol{
506 Name: name,
507 Type: n.Type,
508 Sect: n.Sect,
509 Desc: n.Desc,
510 Value: n.Value,
511 })
512 }
513 st := &Symtab{}
514 st.LoadBytes = LoadBytes(cmddat)
515 st.Syms = symtab
516 return st, nil
517 }
518
519 type relocInfo struct {
520 Addr uint32
521 Symnum uint32
522 }
523
524 func (f *File) pushSection(sh *Section, r io.ReaderAt) error {
525 f.Sections = append(f.Sections, sh)
526 sh.sr = io.NewSectionReader(r, int64(sh.Offset), int64(sh.Size))
527 sh.ReaderAt = sh.sr
528
529 if sh.Nreloc > 0 {
530 reldat, err := saferio.ReadDataAt(r, uint64(sh.Nreloc)*8, int64(sh.Reloff))
531 if err != nil {
532 return err
533 }
534 b := bytes.NewReader(reldat)
535
536 bo := f.ByteOrder
537
538 sh.Relocs = []Reloc{:sh.Nreloc}
539 for i := range sh.Relocs {
540 rel := &sh.Relocs[i]
541
542 var ri relocInfo
543 if err := binary.Read(b, bo, &ri); err != nil {
544 return err
545 }
546
547 if ri.Addr&(1<<31) != 0 { // scattered
548 rel.Addr = ri.Addr & (1<<24 - 1)
549 rel.Type = uint8((ri.Addr >> 24) & (1<<4 - 1))
550 rel.Len = uint8((ri.Addr >> 28) & (1<<2 - 1))
551 rel.Pcrel = ri.Addr&(1<<30) != 0
552 rel.Value = ri.Symnum
553 rel.Scattered = true
554 } else {
555 switch bo {
556 case binary.LittleEndian:
557 rel.Addr = ri.Addr
558 rel.Value = ri.Symnum & (1<<24 - 1)
559 rel.Pcrel = ri.Symnum&(1<<24) != 0
560 rel.Len = uint8((ri.Symnum >> 25) & (1<<2 - 1))
561 rel.Extern = ri.Symnum&(1<<27) != 0
562 rel.Type = uint8((ri.Symnum >> 28) & (1<<4 - 1))
563 case binary.BigEndian:
564 rel.Addr = ri.Addr
565 rel.Value = ri.Symnum >> 8
566 rel.Pcrel = ri.Symnum&(1<<7) != 0
567 rel.Len = uint8((ri.Symnum >> 5) & (1<<2 - 1))
568 rel.Extern = ri.Symnum&(1<<4) != 0
569 rel.Type = uint8(ri.Symnum & (1<<4 - 1))
570 default:
571 panic("unreachable")
572 }
573 }
574 }
575 }
576
577 return nil
578 }
579
580 func cstring(b []byte) string {
581 i := bytes.IndexByte(b, 0)
582 if i == -1 {
583 i = len(b)
584 }
585 return string(b[0:i])
586 }
587
588 // Segment returns the first Segment with the given name, or nil if no such segment exists.
589 func (f *File) Segment(name string) *Segment {
590 for _, l := range f.Loads {
591 if s, ok := l.(*Segment); ok && s.Name == name {
592 return s
593 }
594 }
595 return nil
596 }
597
598 // Section returns the first section with the given name, or nil if no such
599 // section exists.
600 func (f *File) Section(name string) *Section {
601 for _, s := range f.Sections {
602 if s.Name == name {
603 return s
604 }
605 }
606 return nil
607 }
608
609 // DWARF returns the DWARF debug information for the Mach-O file.
610 func (f *File) DWARF() (*dwarf.Data, error) {
611 dwarfSuffix := func(s *Section) string {
612 sectname := s.Name
613 var pfx int
614 switch {
615 case bytes.HasPrefix(sectname, "__debug_"):
616 pfx = 8
617 case bytes.HasPrefix(sectname, "__zdebug_"):
618 pfx = 9
619 default:
620 return ""
621 }
622 // Mach-O executables truncate section names to 16 characters, mangling some DWARF sections.
623 // As of DWARFv5 these are the only problematic section names (see DWARFv5 Appendix G).
624 for _, longname := range [][]byte{
625 "__debug_str_offsets",
626 "__zdebug_line_str",
627 "__zdebug_loclists",
628 "__zdebug_pubnames",
629 "__zdebug_pubtypes",
630 "__zdebug_rnglists",
631 "__zdebug_str_offsets",
632 } {
633 if sectname == longname[:16] {
634 sectname = longname
635 break
636 }
637 }
638 return sectname[pfx:]
639 }
640 sectionData := func(s *Section) ([]byte, error) {
641 b, err := s.Data()
642 if err != nil && uint64(len(b)) < s.Size {
643 return nil, err
644 }
645
646 if len(b) >= 12 && string(b[:4]) == "ZLIB" {
647 dlen := binary.BigEndian.Uint64(b[4:12])
648 dbuf := []byte{:dlen}
649 r, err := zlib.NewReader(bytes.NewBuffer(b[12:]))
650 if err != nil {
651 return nil, err
652 }
653 if _, err := io.ReadFull(r, dbuf); err != nil {
654 return nil, err
655 }
656 if err := r.Close(); err != nil {
657 return nil, err
658 }
659 b = dbuf
660 }
661 return b, nil
662 }
663
664 // There are many other DWARF sections, but these
665 // are the ones the debug/dwarf package uses.
666 // Don't bother loading others.
667 var dat = map[string][]byte{"abbrev": nil, "info": nil, "str": nil, "line": nil, "ranges": nil}
668 for _, s := range f.Sections {
669 suffix := dwarfSuffix(s)
670 if suffix == "" {
671 continue
672 }
673 if _, ok := dat[suffix]; !ok {
674 continue
675 }
676 b, err := sectionData(s)
677 if err != nil {
678 return nil, err
679 }
680 dat[suffix] = b
681 }
682
683 d, err := dwarf.New(dat["abbrev"], nil, nil, dat["info"], dat["line"], nil, dat["ranges"], dat["str"])
684 if err != nil {
685 return nil, err
686 }
687
688 // Look for DWARF4 .debug_types sections and DWARF5 sections.
689 for i, s := range f.Sections {
690 suffix := dwarfSuffix(s)
691 if suffix == "" {
692 continue
693 }
694 if _, ok := dat[suffix]; ok {
695 // Already handled.
696 continue
697 }
698
699 b, err := sectionData(s)
700 if err != nil {
701 return nil, err
702 }
703
704 if suffix == "types" {
705 err = d.AddTypes(fmt.Sprintf("types-%d", i), b)
706 } else {
707 err = d.AddSection(".debug_"+suffix, b)
708 }
709 if err != nil {
710 return nil, err
711 }
712 }
713
714 return d, nil
715 }
716
717 // ImportedSymbols returns the names of all symbols
718 // referred to by the binary f that are expected to be
719 // satisfied by other libraries at dynamic load time.
720 func (f *File) ImportedSymbols() ([][]byte, error) {
721 if f.Dysymtab == nil || f.Symtab == nil {
722 return nil, &FormatError{0, "missing symbol table", nil}
723 }
724
725 st := f.Symtab
726 dt := f.Dysymtab
727 var all [][]byte
728 for _, s := range st.Syms[dt.Iundefsym : dt.Iundefsym+dt.Nundefsym] {
729 all = append(all, s.Name)
730 }
731 return all, nil
732 }
733
734 // ImportedLibraries returns the paths of all libraries
735 // referred to by the binary f that are expected to be
736 // linked with the binary at dynamic link time.
737 func (f *File) ImportedLibraries() ([][]byte, error) {
738 var all [][]byte
739 for _, l := range f.Loads {
740 if lib, ok := l.(*Dylib); ok {
741 all = append(all, lib.Name)
742 }
743 }
744 return all, nil
745 }
746