Skip to content

Commit 83655f0

Browse files
Fabian GiesenH. Peter Anvin
authored andcommitted
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 <fabian.giesen@epicgames.com>
1 parent 0ee113c commit 83655f0

File tree

3 files changed

+191
-0
lines changed

3 files changed

+191
-0
lines changed

output/macho.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#define LC_SEGMENT 0x1
3131
#define LC_SEGMENT_64 0x19
3232
#define LC_SYMTAB 0x2
33+
#define LC_BUILD_VERSION 0x32
3334

3435
/* Symbol type bits */
3536
#define N_STAB 0xe0
@@ -120,6 +121,24 @@
120121
#define VM_PROT_WRITE 0x02
121122
#define VM_PROT_EXECUTE 0x04
122123

124+
/* Platforms */
125+
/* X-macro X(_platform, _id, _name) */
126+
#define MACHO_ALL_PLATFORMS \
127+
X(PLATFORM_UNKNOWN, 0, "unknown") \
128+
X(PLATFORM_MACOS, 1, "macos") \
129+
X(PLATFORM_IOS, 2, "ios") \
130+
X(PLATFORM_TVOS, 3, "tvos") \
131+
X(PLATFORM_WATCHOS, 4, "watchos") \
132+
X(PLATFORM_BRIDGEOS, 5, "bridgeos") \
133+
X(PLATFORM_MACCATALYST, 6, "macCatalyst") \
134+
X(PLATFORM_IOSSIMULATOR, 7, "iossimulator") \
135+
X(PLATFORM_TVOSSIMULATOR, 8, "tvossimulator") \
136+
X(PLATFORM_WATCHOSSIMULATOR, 9, "watchossimulator") \
137+
X(PLATFORM_DRIVERKIT, 10, "driverkit") \
138+
X(PLATFORM_XROS, 11, "xros") \
139+
X(PLATFORM_XROS_SIMULATOR, 12, "xrsimulator") \
140+
/* end */
141+
123142
typedef struct {
124143
uint32_t magic;
125144
uint32_t cputype;
@@ -249,4 +268,20 @@ typedef struct {
249268
uint64_t n_value;
250269
} macho_nlist_64_t;
251270

271+
/* Adapted from LLVM include/llvm/BinaryFormat/MachO.h */
272+
typedef struct {
273+
uint32_t tool;
274+
uint32_t version;
275+
} macho_build_tool_version_t;
276+
277+
typedef struct {
278+
uint32_t cmd;
279+
uint32_t cmdsize;
280+
uint32_t platform;
281+
uint32_t minos; /* x.y.z is 0xXXXXYYZZ */
282+
uint32_t sdk; /* x.y.z is 0xXXXXYYZZ */
283+
uint32_t ntools;
284+
/* ntools macho_build_tool_version_t follow this */
285+
} macho_build_version_command_t;
286+
252287
#endif /* OUTPUT_MACHO_H */

output/outmacho.c

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,20 @@
3939
#define MACHO_SEGCMD64_SIZE 72
4040
#define MACHO_SECTCMD64_SIZE 80
4141
#define MACHO_NLIST64_SIZE 16
42+
#define MACHO_BUILD_VERSION_SIZE 24
4243

4344
/* Mach-O relocations numbers */
4445

4546
#define VM_PROT_DEFAULT (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)
4647
#define VM_PROT_ALL (VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE)
4748

49+
/* Platforms enum */
50+
enum macho_platform {
51+
#define X(_platform, _id, _name) _platform = _id,
52+
MACHO_ALL_PLATFORMS
53+
#undef X
54+
};
55+
4856
/* Our internal relocation types */
4957
enum reltype {
5058
RL_ABS, /* Absolute relocation */
@@ -185,6 +193,10 @@ static uint64_t seg_vmsize = 0;
185193
static uint32_t seg_nsects = 0;
186194
static uint64_t rel_padcnt = 0;
187195

196+
static uint32_t buildver_platform = PLATFORM_UNKNOWN;
197+
static uint32_t buildver_minos = 0; // x.y.z is 0xXXXXYYZZ
198+
static uint32_t buildver_sdk = 0; // x.y.z is 0xXXXXYYZZ
199+
188200
/*
189201
* Functions for handling fixed-length zero-padded string
190202
* fields, that may or may not be null-terminated.
@@ -1235,6 +1247,11 @@ static void macho_calculate_sizes (void)
12351247

12361248
/* calculate size of all headers, load commands and sections to
12371249
** get a pointer to the start of all the raw data */
1250+
if (buildver_platform != PLATFORM_UNKNOWN) {
1251+
++head_ncmds;
1252+
head_sizeofcmds += MACHO_BUILD_VERSION_SIZE;
1253+
}
1254+
12381255
if (seg_nsects > 0) {
12391256
++head_ncmds;
12401257
head_sizeofcmds += fmt.segcmd_size + seg_nsects * fmt.sectcmd_size;
@@ -1617,6 +1634,16 @@ static void macho_write (void)
16171634

16181635
offset = fmt.header_size + head_sizeofcmds;
16191636

1637+
/* emit the build_version command early, if desired */
1638+
if (buildver_platform != PLATFORM_UNKNOWN) {
1639+
fwriteint32_t(LC_BUILD_VERSION, ofile); /* cmd == LC_BUILD_VERSION */
1640+
fwriteint32_t(MACHO_BUILD_VERSION_SIZE, ofile); /* size of load command */
1641+
fwriteint32_t(buildver_platform, ofile); /* platform */
1642+
fwriteint32_t(buildver_minos, ofile); /* minos */
1643+
fwriteint32_t(buildver_sdk, ofile); /* sdk */
1644+
fwriteint32_t(0, ofile); /* ntools */
1645+
}
1646+
16201647
/* emit the segment load command */
16211648
if (seg_nsects > 0)
16221649
offset = macho_write_segment (offset);
@@ -1764,6 +1791,124 @@ static enum directive_result macho_no_dead_strip(const char *labels)
17641791
return rv;
17651792
}
17661793

1794+
static bool macho_match_string(const char **pp, const char *target_name)
1795+
{
1796+
const char *p = *pp;
1797+
while (*target_name) {
1798+
if (*p++ != *target_name++)
1799+
return false;
1800+
}
1801+
1802+
/* must have exhausted the run of identifier characters */
1803+
if (nasm_isidchar(*p)) {
1804+
return false;
1805+
}
1806+
1807+
*pp = p;
1808+
return true;
1809+
}
1810+
1811+
static bool macho_scan_number(const char **pp, int64_t *result)
1812+
{
1813+
bool error = false;
1814+
const char *p = *pp;
1815+
while (nasm_isdigit(*p))
1816+
++p;
1817+
1818+
if (p == *pp) {
1819+
*result = 0;
1820+
return false;
1821+
}
1822+
1823+
*result = readnum(*pp, &error);
1824+
*pp = p;
1825+
return !error;
1826+
}
1827+
1828+
static bool macho_scan_version(const char **pp, uint32_t *result)
1829+
{
1830+
int64_t major = 0;
1831+
int64_t minor = 0;
1832+
int64_t trailing = 0;
1833+
1834+
/* version: major, minor (, trailing)? */
1835+
*result = 0;
1836+
1837+
if (!macho_scan_number(pp, &major) || major < 0 || major > 65535)
1838+
return false;
1839+
*pp = nasm_skip_spaces(*pp);
1840+
if (**pp != ',') /* comma after major ver is required */
1841+
return false;
1842+
*pp = nasm_skip_spaces(*pp + 1);
1843+
1844+
if (!macho_scan_number(pp, &minor) || minor < 0 || minor > 255)
1845+
return false;
1846+
*pp = nasm_skip_spaces(*pp);
1847+
1848+
if (**pp == ',') {
1849+
/* trailing version present */
1850+
*pp = nasm_skip_spaces(*pp + 1);
1851+
if (!macho_scan_number(pp, &trailing) || trailing < 0 || trailing > 255)
1852+
return false;
1853+
}
1854+
1855+
*result = (uint32_t) ((major << 16) | (minor << 8) | trailing);
1856+
return true;
1857+
}
1858+
1859+
/*
1860+
* Specify a build version
1861+
*/
1862+
static enum directive_result macho_build_version(const char *buildversion)
1863+
{
1864+
/* Matching .build_version directive in LLVM-MC */
1865+
const char *p;
1866+
uint32_t platform = PLATFORM_UNKNOWN;
1867+
uint32_t minos = 0;
1868+
uint32_t sdk = 0;
1869+
1870+
p = nasm_skip_spaces(buildversion);
1871+
1872+
#define X(_platform,_id,_name) if (macho_match_string(&p, _name)) platform = _platform;
1873+
MACHO_ALL_PLATFORMS
1874+
#undef X
1875+
1876+
if (platform == PLATFORM_UNKNOWN) {
1877+
nasm_nonfatal("unknown platform name");
1878+
return DIRR_ERROR;
1879+
}
1880+
1881+
p = nasm_skip_spaces(p);
1882+
if (*p != ',') {
1883+
nasm_nonfatal("version number required, comma expected");
1884+
return DIRR_ERROR;
1885+
}
1886+
p = nasm_skip_spaces(p + 1);
1887+
1888+
if (!macho_scan_version(&p, &minos)) {
1889+
nasm_nonfatal("malformed version number");
1890+
return DIRR_ERROR;
1891+
}
1892+
1893+
p = nasm_skip_spaces(p);
1894+
if (*p) {
1895+
if (macho_match_string(&p, "sdk_version")) {
1896+
p = nasm_skip_spaces(p);
1897+
1898+
if (!macho_scan_version(&p, &sdk)) {
1899+
nasm_nonfatal("malformed sdk_version");
1900+
return DIRR_ERROR;
1901+
}
1902+
} else
1903+
nasm_nonfatal("extra characters in build_version");
1904+
}
1905+
1906+
buildver_platform = platform;
1907+
buildver_minos = minos;
1908+
buildver_sdk = sdk;
1909+
return DIRR_OK;
1910+
}
1911+
17671912
/*
17681913
* Mach-O pragmas
17691914
*/
@@ -1786,6 +1931,12 @@ macho_pragma(const struct pragma *pragma)
17861931
case D_NO_DEAD_STRIP:
17871932
return macho_no_dead_strip(pragma->tail);
17881933

1934+
case D_unknown:
1935+
if (!strcmp(pragma->opname, "build_version"))
1936+
return macho_build_version(pragma->tail);
1937+
1938+
return DIRR_UNKNOWN;
1939+
17891940
default:
17901941
return DIRR_UNKNOWN; /* Not a Mach-O directive */
17911942
}

output/outmacho.mac

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,8 @@ OUT: macho macho32 macho64
1717
%rotate 1
1818
%endrep
1919
%endmacro
20+
21+
; This sets LC_BUILD_VERSION in the object file, analogous to as .build_version
22+
%imacro build_version 3+
23+
%pragma __?OUTPUT_FORMAT?__ %? %1,%2,%3
24+
%endmacro

0 commit comments

Comments
 (0)