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 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 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 +