| /* simple-object-mach-o.c -- routines to manipulate Mach-O object files. |
| Copyright (C) 2010-2023 Free Software Foundation, Inc. |
| Written by Ian Lance Taylor, Google. |
| |
| This program is free software; you can redistribute it and/or modify it |
| under the terms of the GNU General Public License as published by the |
| Free Software Foundation; either version 2, or (at your option) any |
| later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program; if not, write to the Free Software |
| Foundation, 51 Franklin Street - Fifth Floor, |
| Boston, MA 02110-1301, USA. */ |
| |
| #include "config.h" |
| #include "libiberty.h" |
| #include "simple-object.h" |
| |
| #include <stddef.h> |
| |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| |
| #ifdef HAVE_STDINT_H |
| #include <stdint.h> |
| #endif |
| |
| #ifdef HAVE_STRING_H |
| #include <string.h> |
| #endif |
| |
| #ifdef HAVE_INTTYPES_H |
| #include <inttypes.h> |
| #endif |
| |
| #include "simple-object-common.h" |
| |
| /* Mach-O structures and constants. */ |
| |
| /* Mach-O header (32-bit version). */ |
| |
| struct mach_o_header_32 |
| { |
| unsigned char magic[4]; /* Magic number. */ |
| unsigned char cputype[4]; /* CPU that this object is for. */ |
| unsigned char cpusubtype[4]; /* CPU subtype. */ |
| unsigned char filetype[4]; /* Type of file. */ |
| unsigned char ncmds[4]; /* Number of load commands. */ |
| unsigned char sizeofcmds[4]; /* Total size of load commands. */ |
| unsigned char flags[4]; /* Flags for special featues. */ |
| }; |
| |
| /* Mach-O header (64-bit version). */ |
| |
| struct mach_o_header_64 |
| { |
| unsigned char magic[4]; /* Magic number. */ |
| unsigned char cputype[4]; /* CPU that this object is for. */ |
| unsigned char cpusubtype[4]; /* CPU subtype. */ |
| unsigned char filetype[4]; /* Type of file. */ |
| unsigned char ncmds[4]; /* Number of load commands. */ |
| unsigned char sizeofcmds[4]; /* Total size of load commands. */ |
| unsigned char flags[4]; /* Flags for special featues. */ |
| unsigned char reserved[4]; /* Reserved. Duh. */ |
| }; |
| |
| /* For magic field in header. */ |
| |
| #define MACH_O_MH_MAGIC 0xfeedface |
| #define MACH_O_MH_MAGIC_64 0xfeedfacf |
| |
| /* For filetype field in header. */ |
| |
| #define MACH_O_MH_OBJECT 0x01 |
| |
| /* A Mach-O file is a list of load commands. This is the header of a |
| load command. */ |
| |
| struct mach_o_load_command |
| { |
| unsigned char cmd[4]; /* The type of load command. */ |
| unsigned char cmdsize[4]; /* Size in bytes of entire command. */ |
| }; |
| |
| /* For cmd field in load command. */ |
| |
| #define MACH_O_LC_SEGMENT 0x01 |
| #define MACH_O_LC_SEGMENT_64 0x19 |
| |
| /* LC_SEGMENT load command. */ |
| |
| struct mach_o_segment_command_32 |
| { |
| unsigned char cmd[4]; /* The type of load command (LC_SEGMENT). */ |
| unsigned char cmdsize[4]; /* Size in bytes of entire command. */ |
| unsigned char segname[16]; /* Name of this segment. */ |
| unsigned char vmaddr[4]; /* Virtual memory address of this segment. */ |
| unsigned char vmsize[4]; /* Size there, in bytes. */ |
| unsigned char fileoff[4]; /* Offset in bytes of the data to be mapped. */ |
| unsigned char filesize[4]; /* Size in bytes on disk. */ |
| unsigned char maxprot[4]; /* Maximum permitted vmem protection. */ |
| unsigned char initprot[4]; /* Initial vmem protection. */ |
| unsigned char nsects[4]; /* Number of sections in this segment. */ |
| unsigned char flags[4]; /* Flags that affect the loading. */ |
| }; |
| |
| /* LC_SEGMENT_64 load command. */ |
| |
| struct mach_o_segment_command_64 |
| { |
| unsigned char cmd[4]; /* The type of load command (LC_SEGMENT_64). */ |
| unsigned char cmdsize[4]; /* Size in bytes of entire command. */ |
| unsigned char segname[16]; /* Name of this segment. */ |
| unsigned char vmaddr[8]; /* Virtual memory address of this segment. */ |
| unsigned char vmsize[8]; /* Size there, in bytes. */ |
| unsigned char fileoff[8]; /* Offset in bytes of the data to be mapped. */ |
| unsigned char filesize[8]; /* Size in bytes on disk. */ |
| unsigned char maxprot[4]; /* Maximum permitted vmem protection. */ |
| unsigned char initprot[4]; /* Initial vmem protection. */ |
| unsigned char nsects[4]; /* Number of sections in this segment. */ |
| unsigned char flags[4]; /* Flags that affect the loading. */ |
| }; |
| |
| /* 32-bit section header. */ |
| |
| struct mach_o_section_32 |
| { |
| unsigned char sectname[16]; /* Section name. */ |
| unsigned char segname[16]; /* Segment that the section belongs to. */ |
| unsigned char addr[4]; /* Address of this section in memory. */ |
| unsigned char size[4]; /* Size in bytes of this section. */ |
| unsigned char offset[4]; /* File offset of this section. */ |
| unsigned char align[4]; /* log2 of this section's alignment. */ |
| unsigned char reloff[4]; /* File offset of this section's relocs. */ |
| unsigned char nreloc[4]; /* Number of relocs for this section. */ |
| unsigned char flags[4]; /* Section flags/attributes. */ |
| unsigned char reserved1[4]; |
| unsigned char reserved2[4]; |
| }; |
| |
| /* 64-bit section header. */ |
| |
| struct mach_o_section_64 |
| { |
| unsigned char sectname[16]; /* Section name. */ |
| unsigned char segname[16]; /* Segment that the section belongs to. */ |
| unsigned char addr[8]; /* Address of this section in memory. */ |
| unsigned char size[8]; /* Size in bytes of this section. */ |
| unsigned char offset[4]; /* File offset of this section. */ |
| unsigned char align[4]; /* log2 of this section's alignment. */ |
| unsigned char reloff[4]; /* File offset of this section's relocs. */ |
| unsigned char nreloc[4]; /* Number of relocs for this section. */ |
| unsigned char flags[4]; /* Section flags/attributes. */ |
| unsigned char reserved1[4]; |
| unsigned char reserved2[4]; |
| unsigned char reserved3[4]; |
| }; |
| |
| /* Flags for Mach-O sections. */ |
| |
| #define MACH_O_S_ATTR_DEBUG 0x02000000 |
| |
| /* The length of a segment or section name. */ |
| |
| #define MACH_O_NAME_LEN (16) |
| |
| /* A GNU specific extension for long section names. */ |
| |
| #define GNU_SECTION_NAMES "__section_names" |
| |
| /* A GNU-specific extension to wrap multiple sections using three |
| mach-o sections within a given segment. The section '__wrapper_sects' |
| is subdivided according to the index '__wrapper_index' and each sub |
| sect is named according to the names supplied in '__wrapper_names'. */ |
| |
| #define GNU_WRAPPER_SECTS "__wrapper_sects" |
| #define GNU_WRAPPER_INDEX "__wrapper_index" |
| #define GNU_WRAPPER_NAMES "__wrapper_names" |
| |
| /* Private data for an simple_object_read. */ |
| |
| struct simple_object_mach_o_read |
| { |
| /* User specified segment name. */ |
| char *segment_name; |
| /* Magic number. */ |
| unsigned int magic; |
| /* Whether this file is big-endian. */ |
| int is_big_endian; |
| /* CPU type from header. */ |
| unsigned int cputype; |
| /* CPU subtype from header. */ |
| unsigned int cpusubtype; |
| /* Number of commands, from header. */ |
| unsigned int ncmds; |
| /* Flags from header. */ |
| unsigned int flags; |
| /* Reserved field from header, only used on 64-bit. */ |
| unsigned int reserved; |
| }; |
| |
| /* Private data for an simple_object_attributes. */ |
| |
| struct simple_object_mach_o_attributes |
| { |
| /* Magic number. */ |
| unsigned int magic; |
| /* Whether this file is big-endian. */ |
| int is_big_endian; |
| /* CPU type from header. */ |
| unsigned int cputype; |
| /* CPU subtype from header. */ |
| unsigned int cpusubtype; |
| /* Flags from header. */ |
| unsigned int flags; |
| /* Reserved field from header, only used on 64-bit. */ |
| unsigned int reserved; |
| }; |
| |
| /* See if we have a Mach-O MH_OBJECT file: |
| |
| A standard MH_OBJECT (from as) will have three load commands: |
| 0 - LC_SEGMENT/LC_SEGMENT64 |
| 1 - LC_SYMTAB |
| 2 - LC_DYSYMTAB |
| |
| The LC_SEGMENT/LC_SEGMENT64 will introduce a single anonymous segment |
| containing all the sections. |
| |
| Files written by simple-object will have only the segment command |
| (no symbol tables). */ |
| |
| static void * |
| simple_object_mach_o_match ( |
| unsigned char header[SIMPLE_OBJECT_MATCH_HEADER_LEN], |
| int descriptor, |
| off_t offset, |
| const char *segment_name, |
| const char **errmsg, |
| int *err) |
| { |
| unsigned int magic; |
| int is_big_endian; |
| unsigned int (*fetch_32) (const unsigned char *); |
| unsigned int filetype; |
| struct simple_object_mach_o_read *omr; |
| unsigned char buf[sizeof (struct mach_o_header_64)]; |
| unsigned char *b; |
| |
| magic = simple_object_fetch_big_32 (header); |
| if (magic == MACH_O_MH_MAGIC || magic == MACH_O_MH_MAGIC_64) |
| is_big_endian = 1; |
| else |
| { |
| magic = simple_object_fetch_little_32 (header); |
| if (magic == MACH_O_MH_MAGIC || magic == MACH_O_MH_MAGIC_64) |
| is_big_endian = 0; |
| else |
| { |
| *errmsg = NULL; |
| *err = 0; |
| return NULL; |
| } |
| } |
| |
| #ifndef UNSIGNED_64BIT_TYPE |
| if (magic == MACH_O_MH_MAGIC_64) |
| { |
| *errmsg = "64-bit Mach-O objects not supported"; |
| *err = 0; |
| return NULL; |
| } |
| #endif |
| |
| /* We require the user to provide a segment name. This is |
| unfortunate but I don't see any good choices here. */ |
| |
| if (segment_name == NULL) |
| { |
| *errmsg = "Mach-O file found but no segment name specified"; |
| *err = 0; |
| return NULL; |
| } |
| |
| if (strlen (segment_name) > MACH_O_NAME_LEN) |
| { |
| *errmsg = "Mach-O segment name too long"; |
| *err = 0; |
| return NULL; |
| } |
| |
| /* The 32-bit and 64-bit headers are similar enough that we can use |
| the same code. */ |
| |
| fetch_32 = (is_big_endian |
| ? simple_object_fetch_big_32 |
| : simple_object_fetch_little_32); |
| |
| if (!simple_object_internal_read (descriptor, offset, buf, |
| (magic == MACH_O_MH_MAGIC |
| ? sizeof (struct mach_o_header_32) |
| : sizeof (struct mach_o_header_64)), |
| errmsg, err)) |
| return NULL; |
| |
| b = &buf[0]; |
| |
| filetype = (*fetch_32) (b + offsetof (struct mach_o_header_32, filetype)); |
| if (filetype != MACH_O_MH_OBJECT) |
| { |
| *errmsg = "Mach-O file is not object file"; |
| *err = 0; |
| return NULL; |
| } |
| |
| omr = XNEW (struct simple_object_mach_o_read); |
| omr->segment_name = xstrdup (segment_name); |
| omr->magic = magic; |
| omr->is_big_endian = is_big_endian; |
| omr->cputype = (*fetch_32) (b + offsetof (struct mach_o_header_32, cputype)); |
| omr->cpusubtype = (*fetch_32) (b |
| + offsetof (struct mach_o_header_32, |
| cpusubtype)); |
| omr->ncmds = (*fetch_32) (b + offsetof (struct mach_o_header_32, ncmds)); |
| omr->flags = (*fetch_32) (b + offsetof (struct mach_o_header_32, flags)); |
| if (magic == MACH_O_MH_MAGIC) |
| omr->reserved = 0; |
| else |
| omr->reserved = (*fetch_32) (b |
| + offsetof (struct mach_o_header_64, |
| reserved)); |
| |
| return (void *) omr; |
| } |
| |
| /* Get the file offset and size from a section header. */ |
| |
| static void |
| simple_object_mach_o_section_info (int is_big_endian, int is_32, |
| const unsigned char *sechdr, off_t *offset, |
| size_t *size) |
| { |
| unsigned int (*fetch_32) (const unsigned char *); |
| ulong_type (*fetch_64) (const unsigned char *); |
| |
| fetch_32 = (is_big_endian |
| ? simple_object_fetch_big_32 |
| : simple_object_fetch_little_32); |
| |
| fetch_64 = NULL; |
| #ifdef UNSIGNED_64BIT_TYPE |
| fetch_64 = (is_big_endian |
| ? simple_object_fetch_big_64 |
| : simple_object_fetch_little_64); |
| #endif |
| |
| if (is_32) |
| { |
| *offset = fetch_32 (sechdr |
| + offsetof (struct mach_o_section_32, offset)); |
| *size = fetch_32 (sechdr |
| + offsetof (struct mach_o_section_32, size)); |
| } |
| else |
| { |
| *offset = fetch_32 (sechdr |
| + offsetof (struct mach_o_section_64, offset)); |
| *size = fetch_64 (sechdr |
| + offsetof (struct mach_o_section_64, size)); |
| } |
| } |
| |
| /* Handle a segment in a Mach-O Object file. |
| |
| This will callback to the function pfn for each "section found" the meaning |
| of which depends on gnu extensions to mach-o: |
| |
| If we find mach-o sections (with the segment name as specified) which also |
| contain: a 'sects' wrapper, an index, and a name table, we expand this into |
| as many sections as are specified in the index. In this case, there will |
| be a callback for each of these. |
| |
| We will also allow an extension that permits long names (more than 16 |
| characters) to be used with mach-o. In this case, the section name has |
| a specific format embedding an index into a name table, and the file must |
| contain such name table. |
| |
| Return 1 if we should continue, 0 if the caller should return. */ |
| |
| #define SOMO_SECTS_PRESENT 0x01 |
| #define SOMO_INDEX_PRESENT 0x02 |
| #define SOMO_NAMES_PRESENT 0x04 |
| #define SOMO_LONGN_PRESENT 0x08 |
| #define SOMO_WRAPPING (SOMO_SECTS_PRESENT | SOMO_INDEX_PRESENT \ |
| | SOMO_NAMES_PRESENT) |
| |
| static int |
| simple_object_mach_o_segment (simple_object_read *sobj, off_t offset, |
| const unsigned char *segbuf, |
| int (*pfn) (void *, const char *, off_t offset, |
| off_t length), |
| void *data, |
| const char **errmsg, int *err) |
| { |
| struct simple_object_mach_o_read *omr = |
| (struct simple_object_mach_o_read *) sobj->data; |
| unsigned int (*fetch_32) (const unsigned char *); |
| int is_32; |
| size_t seghdrsize; |
| size_t sechdrsize; |
| size_t segname_offset; |
| size_t sectname_offset; |
| unsigned int nsects; |
| unsigned char *secdata; |
| unsigned int i; |
| unsigned int gnu_sections_found; |
| unsigned int strtab_index; |
| unsigned int index_index; |
| unsigned int nametab_index; |
| unsigned int sections_index; |
| char *strtab; |
| char *nametab; |
| unsigned char *index; |
| size_t strtab_size; |
| size_t nametab_size; |
| size_t index_size; |
| unsigned int n_wrapped_sects; |
| size_t wrapper_sect_size; |
| off_t wrapper_sect_offset = 0; |
| |
| fetch_32 = (omr->is_big_endian |
| ? simple_object_fetch_big_32 |
| : simple_object_fetch_little_32); |
| |
| is_32 = omr->magic == MACH_O_MH_MAGIC; |
| |
| if (is_32) |
| { |
| seghdrsize = sizeof (struct mach_o_segment_command_32); |
| sechdrsize = sizeof (struct mach_o_section_32); |
| segname_offset = offsetof (struct mach_o_section_32, segname); |
| sectname_offset = offsetof (struct mach_o_section_32, sectname); |
| nsects = (*fetch_32) (segbuf |
| + offsetof (struct mach_o_segment_command_32, |
| nsects)); |
| } |
| else |
| { |
| seghdrsize = sizeof (struct mach_o_segment_command_64); |
| sechdrsize = sizeof (struct mach_o_section_64); |
| segname_offset = offsetof (struct mach_o_section_64, segname); |
| sectname_offset = offsetof (struct mach_o_section_64, sectname); |
| nsects = (*fetch_32) (segbuf |
| + offsetof (struct mach_o_segment_command_64, |
| nsects)); |
| } |
| |
| /* Fetch the section headers from the segment command. */ |
| |
| secdata = XNEWVEC (unsigned char, nsects * sechdrsize); |
| if (!simple_object_internal_read (sobj->descriptor, offset + seghdrsize, |
| secdata, nsects * sechdrsize, errmsg, err)) |
| { |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| |
| /* Scan for special sections that signal GNU extensions to the format. */ |
| |
| gnu_sections_found = 0; |
| index_index = nsects; |
| sections_index = nsects; |
| strtab_index = nsects; |
| nametab_index = nsects; |
| for (i = 0; i < nsects; ++i) |
| { |
| size_t nameoff; |
| |
| nameoff = i * sechdrsize + segname_offset; |
| if (strcmp ((char *) secdata + nameoff, omr->segment_name) != 0) |
| continue; |
| |
| nameoff = i * sechdrsize + sectname_offset; |
| if (strcmp ((char *) secdata + nameoff, GNU_WRAPPER_NAMES) == 0) |
| { |
| nametab_index = i; |
| gnu_sections_found |= SOMO_NAMES_PRESENT; |
| } |
| else if (strcmp ((char *) secdata + nameoff, GNU_WRAPPER_INDEX) == 0) |
| { |
| index_index = i; |
| gnu_sections_found |= SOMO_INDEX_PRESENT; |
| } |
| else if (strcmp ((char *) secdata + nameoff, GNU_WRAPPER_SECTS) == 0) |
| { |
| sections_index = i; |
| gnu_sections_found |= SOMO_SECTS_PRESENT; |
| } |
| else if (strcmp ((char *) secdata + nameoff, GNU_SECTION_NAMES) == 0) |
| { |
| strtab_index = i; |
| gnu_sections_found |= SOMO_LONGN_PRESENT; |
| } |
| } |
| |
| /* If any of the special wrapper section components is present, then |
| they all should be. */ |
| |
| if ((gnu_sections_found & SOMO_WRAPPING) != 0) |
| { |
| off_t nametab_offset; |
| off_t index_offset; |
| |
| if ((gnu_sections_found & SOMO_WRAPPING) != SOMO_WRAPPING) |
| { |
| *errmsg = "GNU Mach-o section wrapper: required section missing"; |
| *err = 0; /* No useful errno. */ |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| |
| /* Fetch the name table. */ |
| |
| simple_object_mach_o_section_info (omr->is_big_endian, is_32, |
| secdata + nametab_index * sechdrsize, |
| &nametab_offset, &nametab_size); |
| nametab = XNEWVEC (char, nametab_size); |
| if (!simple_object_internal_read (sobj->descriptor, |
| sobj->offset + nametab_offset, |
| (unsigned char *) nametab, nametab_size, |
| errmsg, err)) |
| { |
| XDELETEVEC (nametab); |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| |
| /* Fetch the index. */ |
| |
| simple_object_mach_o_section_info (omr->is_big_endian, is_32, |
| secdata + index_index * sechdrsize, |
| &index_offset, &index_size); |
| index = XNEWVEC (unsigned char, index_size); |
| if (!simple_object_internal_read (sobj->descriptor, |
| sobj->offset + index_offset, |
| index, index_size, |
| errmsg, err)) |
| { |
| XDELETEVEC (index); |
| XDELETEVEC (nametab); |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| |
| /* The index contains 4 unsigned ints per sub-section: |
| sub-section offset/length, sub-section name/length. |
| We fix this for both 32 and 64 bit mach-o for now, since |
| other fields limit the maximum size of an object to 4G. */ |
| n_wrapped_sects = index_size / 16; |
| |
| /* Get the parameters for the wrapper too. */ |
| simple_object_mach_o_section_info (omr->is_big_endian, is_32, |
| secdata + sections_index * sechdrsize, |
| &wrapper_sect_offset, |
| &wrapper_sect_size); |
| } |
| else |
| { |
| index = NULL; |
| index_size = 0; |
| nametab = NULL; |
| nametab_size = 0; |
| n_wrapped_sects = 0; |
| } |
| |
| /* If we have a long names section, fetch it. */ |
| |
| if ((gnu_sections_found & SOMO_LONGN_PRESENT) != 0) |
| { |
| off_t strtab_offset; |
| |
| simple_object_mach_o_section_info (omr->is_big_endian, is_32, |
| secdata + strtab_index * sechdrsize, |
| &strtab_offset, &strtab_size); |
| strtab = XNEWVEC (char, strtab_size); |
| if (!simple_object_internal_read (sobj->descriptor, |
| sobj->offset + strtab_offset, |
| (unsigned char *) strtab, strtab_size, |
| errmsg, err)) |
| { |
| XDELETEVEC (strtab); |
| XDELETEVEC (index); |
| XDELETEVEC (nametab); |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| } |
| else |
| { |
| strtab = NULL; |
| strtab_size = 0; |
| strtab_index = nsects; |
| } |
| |
| /* Process the sections. */ |
| |
| for (i = 0; i < nsects; ++i) |
| { |
| const unsigned char *sechdr; |
| char namebuf[MACH_O_NAME_LEN * 2 + 2]; |
| char *name; |
| off_t secoffset; |
| size_t secsize; |
| int l; |
| |
| sechdr = secdata + i * sechdrsize; |
| |
| /* We've already processed the long section names. */ |
| |
| if ((gnu_sections_found & SOMO_LONGN_PRESENT) != 0 |
| && i == strtab_index) |
| continue; |
| |
| /* We only act on the segment named. */ |
| |
| if (strcmp ((char *) sechdr + segname_offset, omr->segment_name) != 0) |
| continue; |
| |
| /* Process sections associated with the wrapper. */ |
| |
| if ((gnu_sections_found & SOMO_WRAPPING) != 0) |
| { |
| if (i == nametab_index || i == index_index) |
| continue; |
| |
| if (i == sections_index) |
| { |
| unsigned int j; |
| for (j = 0; j < n_wrapped_sects; ++j) |
| { |
| unsigned int subsect_offset, subsect_length, name_offset; |
| subsect_offset = (*fetch_32) (index + 16 * j); |
| subsect_length = (*fetch_32) (index + 16 * j + 4); |
| name_offset = (*fetch_32) (index + 16 * j + 8); |
| /* We don't need the name_length yet. */ |
| |
| secoffset = wrapper_sect_offset + subsect_offset; |
| secsize = subsect_length; |
| name = nametab + name_offset; |
| |
| if (!(*pfn) (data, name, secoffset, secsize)) |
| { |
| *errmsg = NULL; |
| *err = 0; |
| XDELETEVEC (index); |
| XDELETEVEC (nametab); |
| XDELETEVEC (strtab); |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| } |
| continue; |
| } |
| } |
| |
| if ((gnu_sections_found & SOMO_LONGN_PRESENT) != 0) |
| { |
| memcpy (namebuf, sechdr + sectname_offset, MACH_O_NAME_LEN); |
| namebuf[MACH_O_NAME_LEN] = '\0'; |
| |
| name = &namebuf[0]; |
| if (strtab != NULL && name[0] == '_' && name[1] == '_') |
| { |
| unsigned long stringoffset; |
| |
| if (sscanf (name + 2, "%08lX", &stringoffset) == 1) |
| { |
| if (stringoffset >= strtab_size) |
| { |
| *errmsg = "section name offset out of range"; |
| *err = 0; |
| XDELETEVEC (index); |
| XDELETEVEC (nametab); |
| XDELETEVEC (strtab); |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| |
| name = strtab + stringoffset; |
| } |
| } |
| } |
| else |
| { |
| /* Otherwise, make a name like __segment,__section as per the |
| convention in mach-o asm. */ |
| name = &namebuf[0]; |
| memcpy (namebuf, (char *) sechdr + segname_offset, MACH_O_NAME_LEN); |
| namebuf[MACH_O_NAME_LEN] = '\0'; |
| l = strlen (namebuf); |
| namebuf[l] = ','; |
| memcpy (namebuf + l + 1, (char *) sechdr + sectname_offset, |
| MACH_O_NAME_LEN); |
| namebuf[l + 1 + MACH_O_NAME_LEN] = '\0'; |
| } |
| |
| simple_object_mach_o_section_info (omr->is_big_endian, is_32, sechdr, |
| &secoffset, &secsize); |
| |
| if (!(*pfn) (data, name, secoffset, secsize)) |
| { |
| *errmsg = NULL; |
| *err = 0; |
| XDELETEVEC (index); |
| XDELETEVEC (nametab); |
| XDELETEVEC (strtab); |
| XDELETEVEC (secdata); |
| return 0; |
| } |
| } |
| |
| XDELETEVEC (index); |
| XDELETEVEC (nametab); |
| XDELETEVEC (strtab); |
| XDELETEVEC (secdata); |
| |
| return 1; |
| } |
| |
| /* Find all sections in a Mach-O file. */ |
| |
| static const char * |
| simple_object_mach_o_find_sections (simple_object_read *sobj, |
| int (*pfn) (void *, const char *, |
| off_t offset, off_t length), |
| void *data, |
| int *err) |
| { |
| struct simple_object_mach_o_read *omr = |
| (struct simple_object_mach_o_read *) sobj->data; |
| off_t offset; |
| size_t seghdrsize; |
| unsigned int (*fetch_32) (const unsigned char *); |
| const char *errmsg; |
| unsigned int i; |
| |
| if (omr->magic == MACH_O_MH_MAGIC) |
| { |
| offset = sizeof (struct mach_o_header_32); |
| seghdrsize = sizeof (struct mach_o_segment_command_32); |
| } |
| else |
| { |
| offset = sizeof (struct mach_o_header_64); |
| seghdrsize = sizeof (struct mach_o_segment_command_64); |
| } |
| |
| fetch_32 = (omr->is_big_endian |
| ? simple_object_fetch_big_32 |
| : simple_object_fetch_little_32); |
| |
| for (i = 0; i < omr->ncmds; ++i) |
| { |
| unsigned char loadbuf[sizeof (struct mach_o_load_command)]; |
| unsigned int cmd; |
| unsigned int cmdsize; |
| |
| if (!simple_object_internal_read (sobj->descriptor, |
| sobj->offset + offset, |
| loadbuf, |
| sizeof (struct mach_o_load_command), |
| &errmsg, err)) |
| return errmsg; |
| |
| cmd = (*fetch_32) (loadbuf + offsetof (struct mach_o_load_command, cmd)); |
| cmdsize = (*fetch_32) (loadbuf |
| + offsetof (struct mach_o_load_command, cmdsize)); |
| |
| if (cmd == MACH_O_LC_SEGMENT || cmd == MACH_O_LC_SEGMENT_64) |
| { |
| unsigned char segbuf[sizeof (struct mach_o_segment_command_64)]; |
| int r; |
| |
| if (!simple_object_internal_read (sobj->descriptor, |
| sobj->offset + offset, |
| segbuf, seghdrsize, &errmsg, err)) |
| return errmsg; |
| |
| r = simple_object_mach_o_segment (sobj, offset, segbuf, pfn, |
| data, &errmsg, err); |
| if (!r) |
| return errmsg; |
| } |
| |
| offset += cmdsize; |
| } |
| |
| return NULL; |
| } |
| |
| /* Fetch the attributes for an simple_object_read. */ |
| |
| static void * |
| simple_object_mach_o_fetch_attributes (simple_object_read *sobj, |
| const char **errmsg ATTRIBUTE_UNUSED, |
| int *err ATTRIBUTE_UNUSED) |
| { |
| struct simple_object_mach_o_read *omr = |
| (struct simple_object_mach_o_read *) sobj->data; |
| struct simple_object_mach_o_attributes *ret; |
| |
| ret = XNEW (struct simple_object_mach_o_attributes); |
| ret->magic = omr->magic; |
| ret->is_big_endian = omr->is_big_endian; |
| ret->cputype = omr->cputype; |
| ret->cpusubtype = omr->cpusubtype; |
| ret->flags = omr->flags; |
| ret->reserved = omr->reserved; |
| return ret; |
| } |
| |
| /* Release the private data for an simple_object_read. */ |
| |
| static void |
| simple_object_mach_o_release_read (void *data) |
| { |
| struct simple_object_mach_o_read *omr = |
| (struct simple_object_mach_o_read *) data; |
| |
| free (omr->segment_name); |
| XDELETE (omr); |
| } |
| |
| /* Compare two attributes structures. */ |
| |
| static const char * |
| simple_object_mach_o_attributes_merge (void *todata, void *fromdata, int *err) |
| { |
| struct simple_object_mach_o_attributes *to = |
| (struct simple_object_mach_o_attributes *) todata; |
| struct simple_object_mach_o_attributes *from = |
| (struct simple_object_mach_o_attributes *) fromdata; |
| |
| if (to->magic != from->magic |
| || to->is_big_endian != from->is_big_endian |
| || to->cputype != from->cputype) |
| { |
| *err = 0; |
| return "Mach-O object format mismatch"; |
| } |
| return NULL; |
| } |
| |
| /* Release the private data for an attributes structure. */ |
| |
| static void |
| simple_object_mach_o_release_attributes (void *data) |
| { |
| XDELETE (data); |
| } |
| |
| /* Prepare to write out a file. */ |
| |
| static void * |
| simple_object_mach_o_start_write (void *attributes_data, |
| const char **errmsg ATTRIBUTE_UNUSED, |
| int *err ATTRIBUTE_UNUSED) |
| { |
| struct simple_object_mach_o_attributes *attrs = |
| (struct simple_object_mach_o_attributes *) attributes_data; |
| struct simple_object_mach_o_attributes *ret; |
| |
| /* We're just going to record the attributes, but we need to make a |
| copy because the user may delete them. */ |
| ret = XNEW (struct simple_object_mach_o_attributes); |
| *ret = *attrs; |
| return ret; |
| } |
| |
| /* Write out the header of a Mach-O file. */ |
| |
| static int |
| simple_object_mach_o_write_header (simple_object_write *sobj, int descriptor, |
| size_t nsects, const char **errmsg, |
| int *err) |
| { |
| struct simple_object_mach_o_attributes *attrs = |
| (struct simple_object_mach_o_attributes *) sobj->data; |
| void (*set_32) (unsigned char *, unsigned int); |
| unsigned char hdrbuf[sizeof (struct mach_o_header_64)]; |
| unsigned char *hdr; |
| size_t wrsize; |
| |
| set_32 = (attrs->is_big_endian |
| ? simple_object_set_big_32 |
| : simple_object_set_little_32); |
| |
| memset (hdrbuf, 0, sizeof hdrbuf); |
| |
| /* The 32-bit and 64-bit headers start out the same. */ |
| |
| hdr = &hdrbuf[0]; |
| set_32 (hdr + offsetof (struct mach_o_header_32, magic), attrs->magic); |
| set_32 (hdr + offsetof (struct mach_o_header_32, cputype), attrs->cputype); |
| set_32 (hdr + offsetof (struct mach_o_header_32, cpusubtype), |
| attrs->cpusubtype); |
| set_32 (hdr + offsetof (struct mach_o_header_32, filetype), MACH_O_MH_OBJECT); |
| set_32 (hdr + offsetof (struct mach_o_header_32, ncmds), 1); |
| set_32 (hdr + offsetof (struct mach_o_header_32, flags), attrs->flags); |
| if (attrs->magic == MACH_O_MH_MAGIC) |
| { |
| wrsize = sizeof (struct mach_o_header_32); |
| set_32 (hdr + offsetof (struct mach_o_header_32, sizeofcmds), |
| (sizeof (struct mach_o_segment_command_32) |
| + nsects * sizeof (struct mach_o_section_32))); |
| } |
| else |
| { |
| set_32 (hdr + offsetof (struct mach_o_header_64, sizeofcmds), |
| (sizeof (struct mach_o_segment_command_64) |
| + nsects * sizeof (struct mach_o_section_64))); |
| set_32 (hdr + offsetof (struct mach_o_header_64, reserved), |
| attrs->reserved); |
| wrsize = sizeof (struct mach_o_header_64); |
| } |
| |
| return simple_object_internal_write (descriptor, 0, hdrbuf, wrsize, |
| errmsg, err); |
| } |
| |
| /* Write a Mach-O section header. */ |
| |
| static int |
| simple_object_mach_o_write_section_header (simple_object_write *sobj, |
| int descriptor, |
| size_t sechdr_offset, |
| const char *name, const char *segn, |
| size_t secaddr, size_t secsize, |
| size_t offset, unsigned int align, |
| const char **errmsg, int *err) |
| { |
| struct simple_object_mach_o_attributes *attrs = |
| (struct simple_object_mach_o_attributes *) sobj->data; |
| void (*set_32) (unsigned char *, unsigned int); |
| unsigned char hdrbuf[sizeof (struct mach_o_section_64)]; |
| unsigned char *hdr; |
| size_t sechdrsize; |
| |
| set_32 = (attrs->is_big_endian |
| ? simple_object_set_big_32 |
| : simple_object_set_little_32); |
| |
| memset (hdrbuf, 0, sizeof hdrbuf); |
| |
| hdr = &hdrbuf[0]; |
| if (attrs->magic == MACH_O_MH_MAGIC) |
| { |
| strncpy ((char *) hdr + offsetof (struct mach_o_section_32, sectname), |
| name, MACH_O_NAME_LEN); |
| strncpy ((char *) hdr + offsetof (struct mach_o_section_32, segname), |
| segn, MACH_O_NAME_LEN); |
| set_32 (hdr + offsetof (struct mach_o_section_32, addr), secaddr); |
| set_32 (hdr + offsetof (struct mach_o_section_32, size), secsize); |
| set_32 (hdr + offsetof (struct mach_o_section_32, offset), offset); |
| set_32 (hdr + offsetof (struct mach_o_section_32, align), align); |
| /* reloff left as zero. */ |
| /* nreloc left as zero. */ |
| set_32 (hdr + offsetof (struct mach_o_section_32, flags), |
| MACH_O_S_ATTR_DEBUG); |
| /* reserved1 left as zero. */ |
| /* reserved2 left as zero. */ |
| sechdrsize = sizeof (struct mach_o_section_32); |
| } |
| else |
| { |
| #ifdef UNSIGNED_64BIT_TYPE |
| void (*set_64) (unsigned char *, ulong_type); |
| |
| set_64 = (attrs->is_big_endian |
| ? simple_object_set_big_64 |
| : simple_object_set_little_64); |
| |
| strncpy ((char *) hdr + offsetof (struct mach_o_section_64, sectname), |
| name, MACH_O_NAME_LEN); |
| strncpy ((char *) hdr + offsetof (struct mach_o_section_64, segname), |
| segn, MACH_O_NAME_LEN); |
| set_64 (hdr + offsetof (struct mach_o_section_64, addr), secaddr); |
| set_64 (hdr + offsetof (struct mach_o_section_64, size), secsize); |
| set_32 (hdr + offsetof (struct mach_o_section_64, offset), offset); |
| set_32 (hdr + offsetof (struct mach_o_section_64, align), align); |
| /* reloff left as zero. */ |
| /* nreloc left as zero. */ |
| set_32 (hdr + offsetof (struct mach_o_section_64, flags), |
| MACH_O_S_ATTR_DEBUG); |
| /* reserved1 left as zero. */ |
| /* reserved2 left as zero. */ |
| /* reserved3 left as zero. */ |
| #endif |
| sechdrsize = sizeof (struct mach_o_section_64); |
| } |
| |
| return simple_object_internal_write (descriptor, sechdr_offset, hdr, |
| sechdrsize, errmsg, err); |
| } |
| |
| /* Write out the single (anonymous) segment containing the sections of a Mach-O |
| Object file. |
| |
| As a GNU extension to mach-o, when the caller specifies a segment name in |
| sobj->segment_name, all the sections passed will be output under a single |
| mach-o section header. The caller's sections are indexed within this |
| 'wrapper' section by a table stored in a second mach-o section. Finally, |
| arbitrary length section names are permitted by the extension and these are |
| stored in a table in a third mach-o section. |
| |
| Note that this is only likely to make any sense for the __GNU_LTO segment |
| at present. |
| |
| If the wrapper extension is not in force, we assume that the section name |
| is in the form __SEGMENT_NAME,__section_name as per Mach-O asm. */ |
| |
| static int |
| simple_object_mach_o_write_segment (simple_object_write *sobj, int descriptor, |
| size_t *nsects, const char **errmsg, |
| int *err) |
| { |
| struct simple_object_mach_o_attributes *attrs = |
| (struct simple_object_mach_o_attributes *) sobj->data; |
| void (*set_32) (unsigned char *, unsigned int); |
| size_t hdrsize; |
| size_t seghdrsize; |
| size_t sechdrsize; |
| size_t cmdsize; |
| size_t offset; |
| size_t sechdr_offset; |
| size_t secaddr; |
| unsigned int name_offset; |
| simple_object_write_section *section; |
| unsigned char hdrbuf[sizeof (struct mach_o_segment_command_64)]; |
| unsigned char *hdr; |
| size_t nsects_in; |
| unsigned int *index; |
| char *snames; |
| unsigned int sect; |
| |
| set_32 = (attrs->is_big_endian |
| ? simple_object_set_big_32 |
| : simple_object_set_little_32); |
| |
| /* Write out the sections first. */ |
| |
| if (attrs->magic == MACH_O_MH_MAGIC) |
| { |
| hdrsize = sizeof (struct mach_o_header_32); |
| seghdrsize = sizeof (struct mach_o_segment_command_32); |
| sechdrsize = sizeof (struct mach_o_section_32); |
| } |
| else |
| { |
| hdrsize = sizeof (struct mach_o_header_64); |
| seghdrsize = sizeof (struct mach_o_segment_command_64); |
| sechdrsize = sizeof (struct mach_o_section_64); |
| } |
| |
| name_offset = 0; |
| *nsects = nsects_in = 0; |
| |
| /* Count the number of sections we start with. */ |
| |
| for (section = sobj->sections; section != NULL; section = section->next) |
| nsects_in++; |
| |
| if (sobj->segment_name != NULL) |
| { |
| /* We will only write 3 sections: wrapped data, index and names. */ |
| |
| *nsects = 3; |
| |
| /* The index has four entries per wrapped section: |
| Section Offset, length, Name offset, length. |
| Where the offsets are based at the start of the wrapper and name |
| sections respectively. |
| The values are stored as 32 bit int for both 32 and 64 bit mach-o |
| since the size of a mach-o MH_OBJECT cannot exceed 4G owing to |
| other constraints. */ |
| |
| index = XNEWVEC (unsigned int, nsects_in * 4); |
| |
| /* We now need to figure out the size of the names section. This just |
| stores the names as null-terminated c strings, packed without any |
| alignment padding. */ |
| |
| for (section = sobj->sections, sect = 0; section != NULL; |
| section = section->next, sect++) |
| { |
| index[sect*4+2] = name_offset; |
| index[sect*4+3] = strlen (section->name) + 1; |
| name_offset += strlen (section->name) + 1; |
| } |
| snames = XNEWVEC (char, name_offset); |
| } |
| else |
| { |
| *nsects = nsects_in; |
| index = NULL; |
| snames = NULL; |
| } |
| |
| sechdr_offset = hdrsize + seghdrsize; |
| cmdsize = seghdrsize + *nsects * sechdrsize; |
| offset = hdrsize + cmdsize; |
| secaddr = 0; |
| |
| for (section = sobj->sections, sect = 0; |
| section != NULL; section = section->next, sect++) |
| { |
| size_t mask; |
| size_t new_offset; |
| size_t secsize; |
| struct simple_object_write_section_buffer *buffer; |
| |
| mask = (1U << section->align) - 1; |
| new_offset = offset + mask; |
| new_offset &= ~ mask; |
| while (new_offset > offset) |
| { |
| unsigned char zeroes[16]; |
| size_t write; |
| |
| memset (zeroes, 0, sizeof zeroes); |
| write = new_offset - offset; |
| if (write > sizeof zeroes) |
| write = sizeof zeroes; |
| if (!simple_object_internal_write (descriptor, offset, zeroes, write, |
| errmsg, err)) |
| return 0; |
| offset += write; |
| } |
| |
| secsize = 0; |
| for (buffer = section->buffers; buffer != NULL; buffer = buffer->next) |
| { |
| if (!simple_object_internal_write (descriptor, offset + secsize, |
| ((const unsigned char *) |
| buffer->buffer), |
| buffer->size, errmsg, err)) |
| return 0; |
| secsize += buffer->size; |
| } |
| |
| if (sobj->segment_name != NULL) |
| { |
| index[sect*4+0] = (unsigned int) offset; |
| index[sect*4+1] = secsize; |
| /* Stash the section name in our table. */ |
| memcpy (snames + index[sect * 4 + 2], section->name, |
| index[sect * 4 + 3]); |
| } |
| else |
| { |
| char namebuf[MACH_O_NAME_LEN + 1]; |
| char segnbuf[MACH_O_NAME_LEN + 1]; |
| char *comma; |
| |
| /* Try to extract segment,section from the input name. */ |
| |
| memset (namebuf, 0, sizeof namebuf); |
| memset (segnbuf, 0, sizeof segnbuf); |
| comma = strchr (section->name, ','); |
| if (comma != NULL) |
| { |
| int len = comma - section->name; |
| len = len > MACH_O_NAME_LEN ? MACH_O_NAME_LEN : len; |
| strncpy (namebuf, section->name, len); |
| strncpy (segnbuf, comma + 1, MACH_O_NAME_LEN); |
| } |
| else /* just try to copy the name, leave segment blank. */ |
| strncpy (namebuf, section->name, MACH_O_NAME_LEN); |
| |
| if (!simple_object_mach_o_write_section_header (sobj, descriptor, |
| sechdr_offset, |
| namebuf, segnbuf, |
| secaddr, secsize, |
| offset, |
| section->align, |
| errmsg, err)) |
| return 0; |
| sechdr_offset += sechdrsize; |
| } |
| |
| offset += secsize; |
| secaddr += secsize; |
| } |
| |
| if (sobj->segment_name != NULL) |
| { |
| size_t secsize; |
| unsigned int i; |
| |
| /* Write the section header for the wrapper. */ |
| /* Account for any initial aligment - which becomes the alignment for this |
| created section. */ |
| |
| secsize = (offset - index[0]); |
| if (!simple_object_mach_o_write_section_header (sobj, descriptor, |
| sechdr_offset, |
| GNU_WRAPPER_SECTS, |
| sobj->segment_name, |
| 0 /*secaddr*/, |
| secsize, index[0], |
| sobj->sections->align, |
| errmsg, err)) |
| return 0; |
| |
| /* Subtract the wrapper section start from the begining of each sub |
| section. */ |
| |
| for (i = 1; i < nsects_in; ++i) |
| index[4 * i] -= index[0]; |
| index[0] = 0; |
| |
| /* Swap the indices, if required. */ |
| |
| for (i = 0; i < (nsects_in * 4); ++i) |
| set_32 ((unsigned char *) &index[i], index[i]); |
| |
| sechdr_offset += sechdrsize; |
| |
| /* Write out the section names. |
| ... the header ... |
| name_offset contains the length of the section. It is not aligned. */ |
| |
| if (!simple_object_mach_o_write_section_header (sobj, descriptor, |
| sechdr_offset, |
| GNU_WRAPPER_NAMES, |
| sobj->segment_name, |
| 0 /*secaddr*/, |
| name_offset, |
| offset, |
| 0, errmsg, err)) |
| return 0; |
| |
| /* ... and the content.. */ |
| if (!simple_object_internal_write (descriptor, offset, |
| (const unsigned char *) snames, |
| name_offset, errmsg, err)) |
| return 0; |
| |
| sechdr_offset += sechdrsize; |
| secaddr += name_offset; |
| offset += name_offset; |
| |
| /* Now do the index, we'll align this to 4 bytes although the read code |
| will handle unaligned. */ |
| |
| offset += 3; |
| offset &= ~0x03; |
| if (!simple_object_mach_o_write_section_header (sobj, descriptor, |
| sechdr_offset, |
| GNU_WRAPPER_INDEX, |
| sobj->segment_name, |
| 0 /*secaddr*/, |
| nsects_in * 16, |
| offset, |
| 2, errmsg, err)) |
| return 0; |
| |
| /* ... and the content.. */ |
| if (!simple_object_internal_write (descriptor, offset, |
| (const unsigned char *) index, |
| nsects_in*16, errmsg, err)) |
| return 0; |
| |
| XDELETEVEC (index); |
| XDELETEVEC (snames); |
| } |
| |
| /* Write out the segment header. */ |
| |
| memset (hdrbuf, 0, sizeof hdrbuf); |
| |
| hdr = &hdrbuf[0]; |
| if (attrs->magic == MACH_O_MH_MAGIC) |
| { |
| set_32 (hdr + offsetof (struct mach_o_segment_command_32, cmd), |
| MACH_O_LC_SEGMENT); |
| set_32 (hdr + offsetof (struct mach_o_segment_command_32, cmdsize), |
| cmdsize); |
| /* MH_OBJECTS have a single, anonymous, segment - so the segment name |
| is left empty. */ |
| /* vmaddr left as zero. */ |
| /* vmsize left as zero. */ |
| set_32 (hdr + offsetof (struct mach_o_segment_command_32, fileoff), |
| hdrsize + cmdsize); |
| set_32 (hdr + offsetof (struct mach_o_segment_command_32, filesize), |
| offset - (hdrsize + cmdsize)); |
| /* maxprot left as zero. */ |
| /* initprot left as zero. */ |
| set_32 (hdr + offsetof (struct mach_o_segment_command_32, nsects), |
| *nsects); |
| /* flags left as zero. */ |
| } |
| else |
| { |
| #ifdef UNSIGNED_64BIT_TYPE |
| void (*set_64) (unsigned char *, ulong_type); |
| |
| set_64 = (attrs->is_big_endian |
| ? simple_object_set_big_64 |
| : simple_object_set_little_64); |
| |
| set_32 (hdr + offsetof (struct mach_o_segment_command_64, cmd), |
| MACH_O_LC_SEGMENT); |
| set_32 (hdr + offsetof (struct mach_o_segment_command_64, cmdsize), |
| cmdsize); |
| /* MH_OBJECTS have a single, anonymous, segment - so the segment name |
| is left empty. */ |
| /* vmaddr left as zero. */ |
| /* vmsize left as zero. */ |
| set_64 (hdr + offsetof (struct mach_o_segment_command_64, fileoff), |
| hdrsize + cmdsize); |
| set_64 (hdr + offsetof (struct mach_o_segment_command_64, filesize), |
| offset - (hdrsize + cmdsize)); |
| /* maxprot left as zero. */ |
| /* initprot left as zero. */ |
| set_32 (hdr + offsetof (struct mach_o_segment_command_64, nsects), |
| *nsects); |
| /* flags left as zero. */ |
| #endif |
| } |
| |
| return simple_object_internal_write (descriptor, hdrsize, hdr, seghdrsize, |
| errmsg, err); |
| } |
| |
| /* Write out a complete Mach-O file. */ |
| |
| static const char * |
| simple_object_mach_o_write_to_file (simple_object_write *sobj, int descriptor, |
| int *err) |
| { |
| size_t nsects = 0; |
| const char *errmsg; |
| |
| if (!simple_object_mach_o_write_segment (sobj, descriptor, &nsects, |
| &errmsg, err)) |
| return errmsg; |
| |
| if (!simple_object_mach_o_write_header (sobj, descriptor, nsects, |
| &errmsg, err)) |
| return errmsg; |
| |
| return NULL; |
| } |
| |
| /* Release the private data for an simple_object_write structure. */ |
| |
| static void |
| simple_object_mach_o_release_write (void *data) |
| { |
| XDELETE (data); |
| } |
| |
| /* The Mach-O functions. */ |
| |
| const struct simple_object_functions simple_object_mach_o_functions = |
| { |
| simple_object_mach_o_match, |
| simple_object_mach_o_find_sections, |
| simple_object_mach_o_fetch_attributes, |
| simple_object_mach_o_release_read, |
| simple_object_mach_o_attributes_merge, |
| simple_object_mach_o_release_attributes, |
| simple_object_mach_o_start_write, |
| simple_object_mach_o_write_to_file, |
| simple_object_mach_o_release_write, |
| NULL |
| }; |