You've already forked macports-base
mirror of
https://github.com/macports/macports-base.git
synced 2026-07-12 18:18:43 -07:00
Parse ar files for Mach-O code
Signed-off-by: Paul Guyot <pguyot@kallisys.net>
This commit is contained in:
@@ -50,7 +50,8 @@ clean::
|
||||
rm -f ${SWIG_OBJS} ${PKG_INDEX}
|
||||
rm -f ${TESTS} \
|
||||
tests/libmachista-test-dependency${SHLIB_SUFFIX} \
|
||||
tests/libmachista-test-lib${SHLIB_SUFFIX}
|
||||
tests/libmachista-test-lib${SHLIB_SUFFIX} \
|
||||
tests/libmachista-test-archive.a
|
||||
rm -rf ${TESTS:%=%.dSYM} \
|
||||
tests/libmachista-test-dependency${SHLIB_SUFFIX}.dSYM \
|
||||
tests/libmachista-test-lib${SHLIB_SUFFIX}.dSYM
|
||||
@@ -61,9 +62,22 @@ distclean::
|
||||
test:: ${TESTS}
|
||||
${TESTS}
|
||||
|
||||
tests/libmachista-test: tests/libmachista-test.c libmachista.h libmachista.o hashmap.o tests/libmachista-test-lib${SHLIB_SUFFIX}
|
||||
tests/libmachista-test: tests/libmachista-test.c libmachista.h libmachista.o hashmap.o tests/libmachista-test-lib${SHLIB_SUFFIX} tests/libmachista-test-archive.a
|
||||
$(CC) $(CFLAGS) -D_POSIX_SOURCE -o $@ -I. $< libmachista.o hashmap.o
|
||||
|
||||
# A static archive (e.g. a *.a library) of more than one host-arch object, used
|
||||
# to check that libmachista parses archives and reports each arch only once.
|
||||
tests/libmachista-test-archive.a: tests/empty.c
|
||||
ifeq (darwin,@OS_PLATFORM@)
|
||||
$(CC) $(CFLAGS) -c $< -o tests/libmachista-test-archive-1.o
|
||||
$(CC) $(CFLAGS) -c $< -o tests/libmachista-test-archive-2.o
|
||||
rm -f $@
|
||||
ar rcs $@ tests/libmachista-test-archive-1.o tests/libmachista-test-archive-2.o
|
||||
rm -f tests/libmachista-test-archive-1.o tests/libmachista-test-archive-2.o
|
||||
else
|
||||
touch $@
|
||||
endif
|
||||
|
||||
# The tests for this library need a universal lib; we used to just use
|
||||
# /usr/lib/libSystem.B.dylib, but Apple has removed that with macOS 11, so now
|
||||
# we need a different alternative.
|
||||
|
||||
@@ -52,6 +52,8 @@
|
||||
#include <strings.h>
|
||||
|
||||
#ifdef __MACH__
|
||||
#include <ar.h>
|
||||
|
||||
#include <mach-o/dyld.h>
|
||||
#include <mach-o/fat.h>
|
||||
#include <mach-o/loader.h>
|
||||
@@ -230,12 +232,19 @@ static macho_loadcmd_t *macho_loadcmdlist_append (macho_arch_t *mat) {
|
||||
|
||||
/* Parse a Mach-O header */
|
||||
#ifdef __MACH__
|
||||
static int parse_ar (macho_t *mt, macho_input_t *input);
|
||||
|
||||
static int parse_macho (macho_t *mt, macho_input_t *input) {
|
||||
/* Read the file type. */
|
||||
const uint32_t *magic = macho_read(input, input->data, sizeof(uint32_t));
|
||||
if (magic == NULL)
|
||||
return MACHO_ERANGE;
|
||||
|
||||
/* A static archive (e.g. *.a) is a container of Mach-O objects */
|
||||
if (input->length >= SARMAG && memcmp(input->data, ARMAG, SARMAG) == 0) {
|
||||
return parse_ar(mt, input);
|
||||
}
|
||||
|
||||
/* Parse the Mach-O header */
|
||||
bool universal = false;
|
||||
uint32_t (*swap32)(uint32_t) = macho_nswap32;
|
||||
@@ -430,6 +439,79 @@ static int parse_macho (macho_t *mt, macho_input_t *input) {
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Parse a static archive, collecting the architectures of its Mach-O members */
|
||||
#ifdef __MACH__
|
||||
static int parse_ar (macho_t *mt, macho_input_t *input) {
|
||||
/* Skip over the "!<arch>\n" magic */
|
||||
size_t offset = SARMAG;
|
||||
|
||||
while (offset + sizeof(struct ar_hdr) <= input->length) {
|
||||
const struct ar_hdr *hdr = macho_offset(input, input->data, offset, sizeof(struct ar_hdr));
|
||||
if (hdr == NULL)
|
||||
return MACHO_ERANGE;
|
||||
|
||||
if (memcmp(hdr->ar_fmag, ARFMAG, sizeof(hdr->ar_fmag)) != 0)
|
||||
return MACHO_EMAGIC;
|
||||
|
||||
/* ar_size counts the extended name (if any) plus the member data */
|
||||
char sizebuf[sizeof(hdr->ar_size) + 1];
|
||||
memcpy(sizebuf, hdr->ar_size, sizeof(hdr->ar_size));
|
||||
sizebuf[sizeof(hdr->ar_size)] = '\0';
|
||||
long member_size = strtol(sizebuf, NULL, 10);
|
||||
if (member_size <= 0)
|
||||
return MACHO_ERANGE;
|
||||
|
||||
size_t data_offset = offset + sizeof(struct ar_hdr);
|
||||
size_t data_size = (size_t) member_size;
|
||||
|
||||
/* BSD ar stores a long name as "#1/<length>" followed by the name */
|
||||
size_t name_len = 0;
|
||||
if (strncmp(hdr->ar_name, AR_EFMT1, sizeof(AR_EFMT1) - 1) == 0) {
|
||||
name_len = (size_t) strtol(hdr->ar_name + sizeof(AR_EFMT1) - 1, NULL, 10);
|
||||
if (name_len > data_size)
|
||||
return MACHO_ERANGE;
|
||||
}
|
||||
|
||||
macho_input_t member;
|
||||
member.length = data_size - name_len;
|
||||
member.data = macho_offset(input, input->data, data_offset + name_len, member.length);
|
||||
if (member.data == NULL)
|
||||
return MACHO_ERANGE;
|
||||
|
||||
/* non-Mach-O members (e.g. the symbol table) report EMAGIC; ignore them */
|
||||
int res = parse_macho(mt, &member);
|
||||
if (res != MACHO_SUCCESS && res != MACHO_EMAGIC)
|
||||
return res;
|
||||
|
||||
/* members are padded to a two-byte boundary */
|
||||
offset = data_offset + data_size;
|
||||
if ((offset & 1) != 0)
|
||||
offset++;
|
||||
}
|
||||
|
||||
/* collapse the duplicate archs of the many same-arch objects in an archive */
|
||||
for (macho_arch_t *a = mt->mt_archs; a != NULL; a = a->next) {
|
||||
macho_arch_t *prev = a;
|
||||
macho_arch_t *cur = a->next;
|
||||
while (cur != NULL) {
|
||||
if (cur->mat_arch == a->mat_arch) {
|
||||
prev->next = cur->next;
|
||||
free_macho_arch_t(cur);
|
||||
cur = prev->next;
|
||||
} else {
|
||||
prev = cur;
|
||||
cur = cur->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mt->mt_archs == NULL)
|
||||
return MACHO_EMAGIC;
|
||||
|
||||
return MACHO_SUCCESS;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Parse a (possible Mach-O) file. For a more detailed description, see the header */
|
||||
#ifdef __MACH__
|
||||
int macho_parse_file(macho_handle_t *handle, const char *filepath, const macho_t **res) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#define TEST_LIB_PATH "tests/libmachista-test-lib.dylib"
|
||||
#define TEST_ARCHIVE_PATH "tests/libmachista-test-archive.a"
|
||||
#define OTOOL_PATH "/usr/bin/otool"
|
||||
|
||||
// check helper
|
||||
@@ -305,6 +306,47 @@ static bool test_libsystem(void) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test reading a static archive (e.g. a *.a library). The fixture holds two
|
||||
* objects of the same arch, so parsing must succeed and report exactly one
|
||||
* (deduplicated), valid architecture.
|
||||
*/
|
||||
static void forked_test_archive(void) {
|
||||
macho_handle_t *handle = macho_create_handle();
|
||||
const macho_t *result;
|
||||
int ret;
|
||||
bool ok = true;
|
||||
|
||||
if ((ret = macho_parse_file(handle, TEST_ARCHIVE_PATH, &result)) != MACHO_SUCCESS) {
|
||||
printf("\tError parsing `%s': %s\n", TEST_ARCHIVE_PATH, macho_strerror(ret));
|
||||
macho_destroy_handle(handle);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (result->mt_archs == NULL) {
|
||||
printf("\tNo architecture reported for `%s'\n", TEST_ARCHIVE_PATH);
|
||||
ok = false;
|
||||
} else if (result->mt_archs->next != NULL) {
|
||||
printf("\tExpected a single (deduplicated) architecture for `%s'\n", TEST_ARCHIVE_PATH);
|
||||
ok = false;
|
||||
} else if (macho_get_arch_name(result->mt_archs->mat_arch) == NULL) {
|
||||
printf("\tUnknown architecture reported for `%s'\n", TEST_ARCHIVE_PATH);
|
||||
ok = false;
|
||||
}
|
||||
|
||||
macho_destroy_handle(handle);
|
||||
exit(!ok);
|
||||
}
|
||||
static bool test_archive(void) {
|
||||
puts("Testing parsing " TEST_ARCHIVE_PATH);
|
||||
if (fork_test(forked_test_archive, "Error parsing " TEST_ARCHIVE_PATH)) {
|
||||
puts("\tOK");
|
||||
return true;
|
||||
}
|
||||
puts("\tError");
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test macho_format_dylib_version
|
||||
*/
|
||||
@@ -351,6 +393,7 @@ int main(void) {
|
||||
result &= test_handle();
|
||||
result &= test_format_dylib_version();
|
||||
result &= test_libsystem();
|
||||
result &= test_archive();
|
||||
return !result;
|
||||
#else
|
||||
return 0;
|
||||
|
||||
@@ -80,6 +80,7 @@
|
||||
#endif
|
||||
|
||||
#ifdef __MACH__
|
||||
#include <ar.h>
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/fat.h>
|
||||
#endif
|
||||
@@ -559,6 +560,16 @@ static int fileIsBinaryCmd(ClientData clientData UNUSED, Tcl_Interp *interp, int
|
||||
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(true));
|
||||
return TCL_OK;
|
||||
}
|
||||
if (memcmp(&magic, ARMAG, sizeof(magic)) == 0) {
|
||||
/* static archive ("!<arch>\n", e.g. *.a); holds mach-o objects */
|
||||
char rest[SARMAG - sizeof(magic)];
|
||||
if (sizeof(rest) == fread(rest, 1, sizeof(rest), file)
|
||||
&& memcmp(rest, &ARMAG[sizeof(magic)], sizeof(rest)) == 0) {
|
||||
fclose(file);
|
||||
Tcl_SetObjResult(interp, Tcl_NewBooleanObj(true));
|
||||
return TCL_OK;
|
||||
}
|
||||
}
|
||||
if (magic == htonl(FAT_MAGIC)) {
|
||||
uint32_t archcount;
|
||||
/* either universal binary or java class (FAT_MAGIC == 0xcafebabe)
|
||||
|
||||
@@ -33,7 +33,7 @@ proc porttest::get_file_archs {handle fpath} {
|
||||
set returncode [lindex $resultlist 0]
|
||||
set result [lindex $resultlist 1]
|
||||
if {$returncode != $::machista::SUCCESS} {
|
||||
# fails on static libs, ignore
|
||||
# EMAGIC means not Mach-O (nor an archive of Mach-O objects); ignore
|
||||
if {$returncode != $::machista::EMAGIC} {
|
||||
ui_warn "Error parsing file ${fpath}: [machista::strerror $returncode]"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user