Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mach-O build version #95

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion asm/pragma.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 32 additions & 0 deletions doc/outfmt.src
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
35 changes: 35 additions & 0 deletions output/macho.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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 */
151 changes: 151 additions & 0 deletions output/outmacho.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
*/
Expand All @@ -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 */
}
Expand Down
6 changes: 6 additions & 0 deletions output/outmacho.mac
Original file line number Diff line number Diff line change
Expand Up @@ -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