From 922206c2771e30ccd2d56b1bcd05baeecc44b84b Mon Sep 17 00:00:00 2001 From: Fabian Giesen Date: Fri, 4 Oct 2024 12:45:43 -0700 Subject: [PATCH 1/3] Don't assume pragma directives are a single word pragma->tail is described as "anything after the operation", but existing parsing passed just the first whitespace-delimited word. Change the parsing to just strip leading and trailing white space off the rest of the line, but keep interior spaces if there are any. This is preparation for a build_version pragma for Mach-O matching the llvm-as .build_version syntax. Signed-off-by: Fabian Giesen --- asm/pragma.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/asm/pragma.c b/asm/pragma.c index 1b5066e9..17f9bb9c 100644 --- a/asm/pragma.c +++ b/asm/pragma.c @@ -272,7 +272,16 @@ void process_pragma(char *str) else pragma.opcode = directive_find(pragma.opname); - pragma.tail = nasm_trim_spaces(p); + /* + * This used to use nasm_trim_spaces, but that assumes a single word. + * Instead, strip spaces at either end of the directive, but allow + * interior spaces. + */ + p = nasm_zap_spaces_fwd(p); + pragma.tail = p; + p += strlen(p); + while (p > pragma.tail && nasm_isspace(p[-1])) + *--p = 0; /* * Search the global pragma namespaces. This is done From c003420b9159c63576f99c4791501a8228812f79 Mon Sep 17 00:00:00 2001 From: Fabian Giesen Date: Fri, 4 Oct 2024 12:49:17 -0700 Subject: [PATCH 2/3] build_version pragma + macro for Mach-O Matches the llvm-as .build_version syntax. Newer MacOS linker complains when object files don't contain a LC_BUILD_VERSION. Signed-off-by: Fabian Giesen --- output/macho.h | 35 ++++++++++ output/outmacho.c | 151 ++++++++++++++++++++++++++++++++++++++++++++ output/outmacho.mac | 6 ++ 3 files changed, 192 insertions(+) diff --git a/output/macho.h b/output/macho.h index 538c531e..70ff6c62 100644 --- a/output/macho.h +++ b/output/macho.h @@ -60,6 +60,7 @@ #define LC_SEGMENT 0x1 #define LC_SEGMENT_64 0x19 #define LC_SYMTAB 0x2 +#define LC_BUILD_VERSION 0x32 /* Symbol type bits */ #define N_STAB 0xe0 @@ -150,6 +151,24 @@ #define VM_PROT_WRITE 0x02 #define VM_PROT_EXECUTE 0x04 +/* Platforms */ +/* X-macro X(_platform, _id, _name) */ +#define MACHO_ALL_PLATFORMS \ + X(PLATFORM_UNKNOWN, 0, "unknown") \ + X(PLATFORM_MACOS, 1, "macos") \ + X(PLATFORM_IOS, 2, "ios") \ + X(PLATFORM_TVOS, 3, "tvos") \ + X(PLATFORM_WATCHOS, 4, "watchos") \ + X(PLATFORM_BRIDGEOS, 5, "bridgeos") \ + X(PLATFORM_MACCATALYST, 6, "macCatalyst") \ + X(PLATFORM_IOSSIMULATOR, 7, "iossimulator") \ + X(PLATFORM_TVOSSIMULATOR, 8, "tvossimulator") \ + X(PLATFORM_WATCHOSSIMULATOR, 9, "watchossimulator") \ + X(PLATFORM_DRIVERKIT, 10, "driverkit") \ + X(PLATFORM_XROS, 11, "xros") \ + X(PLATFORM_XROS_SIMULATOR, 12, "xrsimulator") \ + /* end */ + typedef struct { uint32_t magic; uint32_t cputype; @@ -279,4 +298,20 @@ typedef struct { uint64_t n_value; } macho_nlist_64_t; +/* Adapted from LLVM include/llvm/BinaryFormat/MachO.h */ +typedef struct { + uint32_t tool; + uint32_t version; +} macho_build_tool_version_t; + +typedef struct { + uint32_t cmd; + uint32_t cmdsize; + uint32_t platform; + uint32_t minos; /* x.y.z is 0xXXXXYYZZ */ + uint32_t sdk; /* x.y.z is 0xXXXXYYZZ */ + uint32_t ntools; + /* ntools macho_build_tool_version_t follow this */ +} macho_build_version_command_t; + #endif /* OUTPUT_MACHO_H */ diff --git a/output/outmacho.c b/output/outmacho.c index 1e776f52..80cb4b41 100644 --- a/output/outmacho.c +++ b/output/outmacho.c @@ -69,12 +69,20 @@ #define MACHO_SEGCMD64_SIZE 72 #define MACHO_SECTCMD64_SIZE 80 #define MACHO_NLIST64_SIZE 16 +#define MACHO_BUILD_VERSION_SIZE 24 /* Mach-O relocations numbers */ #define VM_PROT_DEFAULT (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE) #define VM_PROT_ALL (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE) +/* Platforms enum */ +enum macho_platform { +#define X(_platform, _id, _name) _platform = _id, + MACHO_ALL_PLATFORMS +#undef X +}; + /* Our internal relocation types */ enum reltype { RL_ABS, /* Absolute relocation */ @@ -215,6 +223,10 @@ static uint64_t seg_vmsize = 0; static uint32_t seg_nsects = 0; static uint64_t rel_padcnt = 0; +static uint32_t buildver_platform = PLATFORM_UNKNOWN; +static uint32_t buildver_minos = 0; // x.y.z is 0xXXXXYYZZ +static uint32_t buildver_sdk = 0; // x.y.z is 0xXXXXYYZZ + /* * Functions for handling fixed-length zero-padded string * fields, that may or may not be null-terminated. @@ -1265,6 +1277,11 @@ static void macho_calculate_sizes (void) /* calculate size of all headers, load commands and sections to ** get a pointer to the start of all the raw data */ + if (buildver_platform != PLATFORM_UNKNOWN) { + ++head_ncmds; + head_sizeofcmds += MACHO_BUILD_VERSION_SIZE; + } + if (seg_nsects > 0) { ++head_ncmds; head_sizeofcmds += fmt.segcmd_size + seg_nsects * fmt.sectcmd_size; @@ -1647,6 +1664,16 @@ static void macho_write (void) offset = fmt.header_size + head_sizeofcmds; + /* emit the build_version command early, if desired */ + if (buildver_platform != PLATFORM_UNKNOWN) { + fwriteint32_t(LC_BUILD_VERSION, ofile); /* cmd == LC_BUILD_VERSION */ + fwriteint32_t(MACHO_BUILD_VERSION_SIZE, ofile); /* size of load command */ + fwriteint32_t(buildver_platform, ofile); /* platform */ + fwriteint32_t(buildver_minos, ofile); /* minos */ + fwriteint32_t(buildver_sdk, ofile); /* sdk */ + fwriteint32_t(0, ofile); /* ntools */ + } + /* emit the segment load command */ if (seg_nsects > 0) offset = macho_write_segment (offset); @@ -1794,6 +1821,124 @@ static enum directive_result macho_no_dead_strip(const char *labels) return rv; } +static bool macho_match_string(const char **pp, const char *target_name) +{ + const char *p = *pp; + while (*target_name) { + if (*p++ != *target_name++) + return false; + } + + /* must have exhausted the run of identifier characters */ + if (nasm_isidchar(*p)) { + return false; + } + + *pp = p; + return true; +} + +static bool macho_scan_number(const char **pp, int64_t *result) +{ + bool error = false; + const char *p = *pp; + while (nasm_isdigit(*p)) + ++p; + + if (p == *pp) { + *result = 0; + return false; + } + + *result = readnum(*pp, &error); + *pp = p; + return !error; +} + +static bool macho_scan_version(const char **pp, uint32_t *result) +{ + int64_t major = 0; + int64_t minor = 0; + int64_t trailing = 0; + + /* version: major, minor (, trailing)? */ + *result = 0; + + if (!macho_scan_number(pp, &major) || major < 0 || major > 65535) + return false; + *pp = nasm_skip_spaces(*pp); + if (**pp != ',') /* comma after major ver is required */ + return false; + *pp = nasm_skip_spaces(*pp + 1); + + if (!macho_scan_number(pp, &minor) || minor < 0 || minor > 255) + return false; + *pp = nasm_skip_spaces(*pp); + + if (**pp == ',') { + /* trailing version present */ + *pp = nasm_skip_spaces(*pp + 1); + if (!macho_scan_number(pp, &trailing) || trailing < 0 || trailing > 255) + return false; + } + + *result = (uint32_t) ((major << 16) | (minor << 8) | trailing); + return true; +} + +/* + * Specify a build version + */ +static enum directive_result macho_build_version(const char *buildversion) +{ + /* Matching .build_version directive in LLVM-MC */ + const char *p; + uint32_t platform = PLATFORM_UNKNOWN; + uint32_t minos = 0; + uint32_t sdk = 0; + + p = nasm_skip_spaces(buildversion); + +#define X(_platform,_id,_name) if (macho_match_string(&p, _name)) platform = _platform; + MACHO_ALL_PLATFORMS +#undef X + + if (platform == PLATFORM_UNKNOWN) { + nasm_nonfatal("unknown platform name"); + return DIRR_ERROR; + } + + p = nasm_skip_spaces(p); + if (*p != ',') { + nasm_nonfatal("version number required, comma expected"); + return DIRR_ERROR; + } + p = nasm_skip_spaces(p + 1); + + if (!macho_scan_version(&p, &minos)) { + nasm_nonfatal("malformed version number"); + return DIRR_ERROR; + } + + p = nasm_skip_spaces(p); + if (*p) { + if (macho_match_string(&p, "sdk_version")) { + p = nasm_skip_spaces(p); + + if (!macho_scan_version(&p, &sdk)) { + nasm_nonfatal("malformed sdk_version"); + return DIRR_ERROR; + } + } else + nasm_nonfatal("extra characters in build_version"); + } + + buildver_platform = platform; + buildver_minos = minos; + buildver_sdk = sdk; + return DIRR_OK; +} + /* * Mach-O pragmas */ @@ -1816,6 +1961,12 @@ macho_pragma(const struct pragma *pragma) case D_NO_DEAD_STRIP: return macho_no_dead_strip(pragma->tail); + case D_unknown: + if (!strcmp(pragma->opname, "build_version")) + return macho_build_version(pragma->tail); + + return DIRR_UNKNOWN; + default: return DIRR_UNKNOWN; /* Not a Mach-O directive */ } diff --git a/output/outmacho.mac b/output/outmacho.mac index 001bb3a0..33850ccb 100644 --- a/output/outmacho.mac +++ b/output/outmacho.mac @@ -47,3 +47,9 @@ OUT: macho macho32 macho64 %rotate 1 %endrep %endmacro + +; This sets LC_BUILD_VERSION in the object file, analogous to as .build_version +%imacro build_version 3+ + %pragma __?OUTPUT_FORMAT?__ %? %1,%2,%3 +%endmacro + From d060f5170492cb0bef65810cc1a20ddebd4df074 Mon Sep 17 00:00:00 2001 From: Fabian Giesen Date: Fri, 4 Oct 2024 13:10:05 -0700 Subject: [PATCH 3/3] Document Mach-O build_version directive Signed-off-by: Fabian Giesen --- doc/outfmt.src | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/doc/outfmt.src b/doc/outfmt.src index aec89150..ccbee7a8 100644 --- a/doc/outfmt.src +++ b/doc/outfmt.src @@ -1093,6 +1093,38 @@ this extension: Using with static linker will clear the private extern attribute. But linker option like \c{-keep_private_externs} can avoid it. +\S{macho-bver} \c{macho} specific directive \i\c{build_version} + +The directive \c{build_version} generates a \c{LC_BUILD_VERSION} +load command in the Mach-O header, which allows specifying a +target platform, minimum OS version and optionally SDK version. +Newer Xcode linker versions warn if this is not present in object +files. + +This directive takes the target platform name and minimum OS +version as arguments, in this form: + +\c build_version macos,10,7 + +Platform names that make sense for x86 code are \c{macos}, +\c{iossimulator}, \c{tvossimulator} and \c{watchossimulator}. + +Optionally, a trailing version number and minimum SDK version +can also be specified with this syntax: + +\c build_version macos, 10, 14, 0 sdk_version 10, 14, 0 + +This is a macro implemented as a \c{%pragma}. It can also be +specified in its \c{%pragma} form, in which case it will not +affect non-Mach-O builds of the same source code: + +\c %pragma macho build_version ... + +This latter form is also useful on the command line when using +the \c{--pragma} command-line switch: + +\c nasm -f macho64 --pragma "macho build_version macos,10,9" ... + \H{elffmt} \i\c{elf32}, \i\c{elf64}, \i\c{elfx32}: \I{ELF}\I{linux, elf}\i{Executable and Linkable Format} Object Files