| /* AIX cross support for collect2. |
| Copyright (C) 2009-2022 Free Software Foundation, Inc. |
| |
| This file is part of GCC. |
| |
| GCC 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 3, or (at your option) any later |
| version. |
| |
| GCC 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 GCC; see the file COPYING3. If not see |
| <http://www.gnu.org/licenses/>. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "coretypes.h" |
| #include "tm.h" |
| #include "collect2-aix.h" |
| |
| #ifdef CROSS_AIX_SUPPORT |
| |
| /* Read SIZE bytes starting at DATA as a big-endian value. */ |
| |
| static inline bfd_vma |
| read_value (char *data, unsigned int size) |
| { |
| bfd_vma value; |
| unsigned int i; |
| |
| value = 0; |
| for (i = 0; i < size; i++) |
| { |
| value <<= 8; |
| value += (unsigned char) data[i]; |
| } |
| return value; |
| } |
| |
| /* FIELD is a char array. Read the contents as a big-endian integer. */ |
| #define READ_FIELD(FIELD) \ |
| read_value (FIELD, sizeof (FIELD)) |
| |
| /* OBJECT is a char pointer to an in-file object of type struct TYPE. |
| Return the address of field FIELD. */ |
| #define OBJECT_FIELD(OBJECT, TYPE, FIELD) \ |
| (OBJECT) + offsetof (struct TYPE, FIELD) |
| |
| /* Return the size of FIELD, which is a field of struct TYPE. */ |
| #define FIELD_SIZE(TYPE, FIELD) \ |
| sizeof (((struct TYPE *) (0))->FIELD) |
| |
| /* OBJECT is a char pointer to an in-file object of type struct TYPE. |
| Read the value of field FIELD as a big-endian integer. */ |
| #define READ_OBJECT(OBJECT, TYPE, FIELD) \ |
| read_value (OBJECT_FIELD (OBJECT, TYPE, FIELD), FIELD_SIZE (TYPE, FIELD)) |
| |
| /* Copy FIELD from an external structure of type TYPE at address FROM |
| to an internal structure pointed to by TO. */ |
| #define COPY_FIELD(TO, FROM, TYPE, FIELD) \ |
| ((TO)->FIELD = READ_OBJECT (FROM, TYPE, FIELD)) |
| |
| /* Return true if STRING is less than SIZE bytes long. EXTRA_TERMINATOR |
| is another character (besides '\0') that acts as a terminator, |
| or '\0' if none. */ |
| |
| static bool |
| string_within_bounds_p (const char *string, size_t size, char extra_terminator) |
| { |
| const char *p; |
| |
| for (p = string; p < string + size; p++) |
| if (*p == '\0' || *p == extra_terminator) |
| return true; |
| return false; |
| } |
| |
| /* STRING is a pointer to a char array. Try to read its value as an |
| ASCII-encoded integer. On success, return true and store the result |
| in TARGET. */ |
| #define PARSE_INTEGER(TARGET, STRING) \ |
| (string_within_bounds_p (&(STRING)[0], sizeof (STRING), ' ') \ |
| && ((TARGET) = strtoul (STRING, NULL, 0), true)) |
| |
| /* Check that LDFILE's current object has SIZE bytes starting at OFFSET. */ |
| |
| static inline bool |
| within_object_p (LDFILE *ldfile, size_t offset, size_t size) |
| { |
| return offset <= ldfile->object_size && offset + size <= ldfile->object_size; |
| } |
| |
| /* Try to read the file header for an XCOFF object at OFFSET bytes into |
| LDFILE. The object is expected to be OBJECT_SIZE bytes in size. |
| If the object is a member of an archive, NEXT_MEMBER is the offset |
| of the next member, otherwise it is -1. |
| |
| Return true on success, recording the object information in LDFILE. */ |
| |
| static bool |
| read_xcoff_object (LDFILE *ldfile, size_t offset, size_t object_size, |
| off_t next_member) |
| { |
| struct internal_filehdr *internal; |
| char *external; |
| void *map; |
| size_t page_size; |
| |
| /* First try to map the file into memory. */ |
| page_size = getpagesize (); |
| ldfile->page_offset = offset & (page_size - 1); |
| map = mmap (NULL, object_size + ldfile->page_offset, PROT_READ, |
| MAP_SHARED, ldfile->fd, offset - ldfile->page_offset); |
| if (map == MAP_FAILED) |
| return false; |
| |
| /* Record the success. */ |
| ldfile->object = (char *) map + ldfile->page_offset; |
| ldfile->object_size = object_size; |
| ldfile->next_member = next_member; |
| |
| /* Read the magic value to determine the type of file. */ |
| if (!within_object_p (ldfile, 0, F_MAGIC_SIZE)) |
| return false; |
| |
| internal = &ldfile->filehdr; |
| external = ldfile->object; |
| internal->f_magic = read_value (external, F_MAGIC_SIZE); |
| if (internal->f_magic == U802TOCMAGIC) |
| { |
| if (!within_object_p (ldfile, 0, sizeof (struct external_filehdr_32))) |
| return false; |
| |
| COPY_FIELD (internal, external, external_filehdr_32, f_nscns); |
| COPY_FIELD (internal, external, external_filehdr_32, f_timdat); |
| COPY_FIELD (internal, external, external_filehdr_32, f_symptr); |
| COPY_FIELD (internal, external, external_filehdr_32, f_nsyms); |
| COPY_FIELD (internal, external, external_filehdr_32, f_opthdr); |
| COPY_FIELD (internal, external, external_filehdr_32, f_flags); |
| return true; |
| } |
| else if (internal->f_magic == U803XTOCMAGIC |
| || internal->f_magic == U64_TOCMAGIC) |
| { |
| if (!within_object_p (ldfile, 0, sizeof (struct external_filehdr_64))) |
| return false; |
| |
| COPY_FIELD (internal, external, external_filehdr_64, f_nscns); |
| COPY_FIELD (internal, external, external_filehdr_64, f_timdat); |
| COPY_FIELD (internal, external, external_filehdr_64, f_symptr); |
| COPY_FIELD (internal, external, external_filehdr_64, f_nsyms); |
| COPY_FIELD (internal, external, external_filehdr_64, f_opthdr); |
| COPY_FIELD (internal, external, external_filehdr_64, f_flags); |
| return true; |
| } |
| return false; |
| } |
| |
| /* Try to read an archive member at OFFSET bytes into LDFILE. |
| Return true on success, recording the member and object |
| information in LDFILE. */ |
| |
| static bool |
| read_archive_member (LDFILE *ldfile, size_t offset) |
| { |
| struct external_big_ar_member member; |
| size_t namlen; |
| size_t size; |
| off_t next_member; |
| |
| if (lseek (ldfile->fd, offset, SEEK_SET) >= 0 |
| && read (ldfile->fd, &member, sizeof (member)) == sizeof (member) |
| && PARSE_INTEGER (namlen, member.ar_namlen) |
| /* Stop once we reach the member table entry, which has a name |
| of length 0. */ |
| && namlen > 0 |
| && PARSE_INTEGER (size, member.ar_size) |
| && PARSE_INTEGER (next_member, member.ar_nextoff)) |
| { |
| /* The archive is followed by an even-padded name, then by |
| a magic string of length SXCOFFARFMAG. The object itself |
| starts after that. */ |
| offset += sizeof (member) + namlen + SXCOFFARFMAG; |
| offset += offset & 1; |
| return read_xcoff_object (ldfile, offset, size, next_member); |
| } |
| return false; |
| } |
| |
| /* Try to treat LDFILE as a non-empty big archive. Return true |
| on success, storing the member and object information for |
| the first member in LDFILE. */ |
| |
| static bool |
| read_big_archive (LDFILE *ldfile) |
| { |
| struct external_big_ar_filehdr filehdr; |
| size_t offset; |
| |
| return (lseek (ldfile->fd, 0L, SEEK_SET) == 0 |
| && read (ldfile->fd, &filehdr, sizeof (filehdr)) == sizeof (filehdr) |
| && memcmp (filehdr.fl_magic, FL_MAGIC_BIG_AR, FL_MAGIC_SIZE) == 0 |
| && PARSE_INTEGER (offset, filehdr.fl_firstmemoff) |
| && read_archive_member (ldfile, offset)); |
| } |
| |
| /* LDFILE is a zero-initialized structure. Try to open FILENAME, |
| returning true on success. */ |
| |
| static bool |
| open_file (LDFILE *ldfile, const char *filename) |
| { |
| struct stat st; |
| |
| ldfile->fd = open (filename, O_RDONLY); |
| if (ldfile->fd < 0) |
| return false; |
| |
| if (read_big_archive (ldfile)) |
| return true; |
| |
| if (fstat (ldfile->fd, &st) < 0) |
| return false; |
| |
| return read_xcoff_object (ldfile, 0, st.st_size, -1); |
| } |
| |
| /* Release the memory associated with the current object, if one has |
| been mapped. */ |
| |
| static void |
| free_object (LDFILE *ldfile) |
| { |
| if (ldfile->object) |
| munmap (ldfile->object - ldfile->page_offset, |
| ldfile->object_size + ldfile->page_offset); |
| } |
| |
| /* Free LDFILE and all resources associated with it. */ |
| |
| static void |
| free_ldfile (LDFILE *ldfile) |
| { |
| if (ldfile->fd >= 0) |
| close (ldfile->fd); |
| XDELETE (ldfile); |
| } |
| |
| /* Implement the API-defined ldopen function. */ |
| |
| LDFILE * |
| ldopen (char *filename, LDFILE *ldfile) |
| { |
| if (ldfile == NULL) |
| { |
| ldfile = XCNEW (LDFILE); |
| if (!open_file (ldfile, filename)) |
| { |
| free_object (ldfile); |
| free_ldfile (ldfile); |
| return NULL; |
| } |
| } |
| return ldfile; |
| } |
| |
| /* Implement the API-defined ldtbread function. */ |
| |
| int |
| ldtbread (LDFILE *ldfile, long index, SYMENT *internal) |
| { |
| size_t offset, name_length; |
| char *external; |
| |
| /* Make sure that the symbol index is valid. */ |
| if (index < 0 || index >= HEADER (ldfile).f_nsyms) |
| return FAILURE; |
| |
| /* Work out the offset of the symbol table entry. */ |
| offset = HEADER (ldfile).f_symptr + index * sizeof (struct external_syment); |
| if (!within_object_p (ldfile, offset, sizeof (struct external_syment))) |
| return FAILURE; |
| |
| /* Read all the fields. The format differs between 32-bit and |
| 64-bit files. */ |
| external = ldfile->object + offset; |
| if (HEADER (ldfile).f_magic == U802TOCMAGIC) |
| { |
| /* Copy the n_zeroes/n_offset interpretation. */ |
| internal->n_zeroes = READ_OBJECT (external, external_syment, |
| u.xcoff32.u.u.n_zeroes); |
| internal->n_offset = READ_OBJECT (external, external_syment, |
| u.xcoff32.u.u.n_offset); |
| |
| /* Copy the n_name interpretation. The internal version has room |
| for a null terminator. */ |
| name_length = FIELD_SIZE (external_syment, u.xcoff32.u.n_name); |
| memcpy (internal->n_name, |
| external + offsetof (struct external_syment, u.xcoff32.u.n_name), |
| name_length); |
| internal->n_name[name_length] = 0; |
| |
| internal->n_value = READ_OBJECT (external, external_syment, |
| u.xcoff32.n_value); |
| } |
| else |
| { |
| internal->n_zeroes = 0; |
| internal->n_offset = READ_OBJECT (external, external_syment, |
| u.xcoff64.n_offset); |
| internal->n_value = READ_OBJECT (external, external_syment, |
| u.xcoff64.n_value); |
| } |
| COPY_FIELD (internal, external, external_syment, n_scnum); |
| COPY_FIELD (internal, external, external_syment, n_type); |
| COPY_FIELD (internal, external, external_syment, n_sclass); |
| COPY_FIELD (internal, external, external_syment, n_numaux); |
| return SUCCESS; |
| } |
| |
| /* Implement the API-defined ldgetname function. */ |
| |
| char * |
| ldgetname (LDFILE *ldfile, SYMENT *symbol) |
| { |
| char *name; |
| size_t offset; |
| |
| /* If the zeroes field is nonzero, the name is in the symbol table |
| entry itself. */ |
| if (symbol->n_zeroes != 0) |
| return symbol->n_name; |
| |
| /* Otherwise, the symbol table entry contains an offset into the |
| string table, which starts after the end of the symbol table. */ |
| offset = (HEADER (ldfile).f_symptr |
| + HEADER (ldfile).f_nsyms * sizeof (struct external_syment) |
| + symbol->n_offset); |
| if (offset >= ldfile->object_size) |
| return NULL; |
| |
| /* Make sure that the name is entirely contained within the object. */ |
| name = ldfile->object + offset; |
| if (!string_within_bounds_p (name, ldfile->object_size - offset, '\0')) |
| return NULL; |
| |
| return name; |
| } |
| |
| /* Implement the API-defined ldclose function. */ |
| |
| int |
| ldclose (LDFILE *ldfile) |
| { |
| free_object (ldfile); |
| if (ldfile->next_member >= 0 |
| && read_archive_member (ldfile, ldfile->next_member)) |
| return FAILURE; |
| |
| free_ldfile (ldfile); |
| return SUCCESS; |
| } |
| |
| #endif |