Skip to content

Commit

Permalink
Fix macOS ld: multiple errors: symbol count from symbol table and dyn…
Browse files Browse the repository at this point in the history
…amic symbol table differ

Encode macosx_version_min or build_version into the object file,
originally authored by @jacob-carlborg in #10476.

This has been simplified to omit parsing the SDK version. Based on what
I see GCC is doing (`-platform_version macos $version_min 0.0`), this
information is not required in order for things to work.

Co-authored-by: Jacob Carlborg <doob@me.com>
  • Loading branch information
ibuclaw and jacob-carlborg committed Feb 11, 2024
1 parent 11240a9 commit 7e6cdb2
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 2 deletions.
3 changes: 3 additions & 0 deletions compiler/src/build.d
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,9 @@ alias toolchainInfo = makeRule!((builder, rule) => builder

app.put("==== Toolchain Information ====\n");

version (OSX)
show("OS", ["sw_vers"]);

version (Windows)
show("SYSTEM", ["systeminfo"]);
else
Expand Down
65 changes: 65 additions & 0 deletions compiler/src/dmd/backend/mach.d
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ enum
LC_SYMTAB = 2,
LC_DYSYMTAB = 11,
LC_SEGMENT_64 = 0x19,

/// Build for MacOSX min OS version.
LC_VERSION_MIN_MACOSX = 0x24,

/// Build for platform min OS version.
LC_BUILD_VERSION = 0x32,
}

struct load_command
Expand Down Expand Up @@ -407,3 +413,62 @@ struct scattered_relocation_info

int r_value;
}

/**
* The version_min_command contains the min OS version on which this binary was
* built to run.
*/
struct version_min_command
{
///
uint cmd = LC_VERSION_MIN_MACOSX;

///
uint cmdsize = typeof(this).sizeof;

/// X.Y.Z is encoded in nibbles xxxx.yy.zz
uint version_;

/// X.Y.Z is encoded in nibbles xxxx.yy.zz
uint sdk = 0;
}

/**
* The `build_version_command` contains the min OS version on which this binary
* was built to run for its platform.
*/
struct build_version_command
{
///
uint cmd = LC_BUILD_VERSION;

///
uint cmdsize = typeof(this).sizeof;

/// Platform
uint platform = PLATFORM_MACOS;

/// X.Y.Z is encoded in nibbles xxxx.yy.zz
uint minos;

/// X.Y.Z is encoded in nibbles xxxx.yy.zz
uint sdk;

/// Number of tool entries following this
uint ntools = 0;
}

/// Known values for the platform field in `build_version_command`
enum
{
PLATFORM_MACOS = 1,
PLATFORM_IOS = 2,
PLATFORM_TVOS = 3,
PLATFORM_WATCHOS = 4,
PLATFORM_BRIDGEOS = 5,
PLATFORM_MACCATALYST = 6,
PLATFORM_IOSSIMULATOR = 7,
PLATFORM_TVOSSIMULATOR = 8,
PLATFORM_WATCHOSSIMULATOR = 9,
PLATFORM_DRIVERKIT = 10
}
175 changes: 173 additions & 2 deletions compiler/src/dmd/backend/machobj.d
Original file line number Diff line number Diff line change
Expand Up @@ -699,13 +699,15 @@ void MachObj_term(const(char)* objfilename)
* { sections }
* symtab_command
* dysymtab_command
* build_version_command/version_min_command
* { segment contents }
* { relocations }
* symbol table
* string table
* indirect symbol table
*/

auto versionCommand = VersionCommand(operatingSystemVersion);
uint foffset;
uint headersize;
uint sizeofcmds;
Expand All @@ -719,7 +721,7 @@ void MachObj_term(const(char)* objfilename)
header.cputype = CPU_TYPE_X86_64;
header.cpusubtype = CPU_SUBTYPE_I386_ALL;
header.filetype = MH_OBJECT;
header.ncmds = 3;
header.ncmds = 4;
header.sizeofcmds = cast(uint)(segment_command_64.sizeof +
(section_cnt - 1) * section_64.sizeof +
symtab_command.sizeof +
Expand Down Expand Up @@ -747,7 +749,8 @@ void MachObj_term(const(char)* objfilename)
header.sizeofcmds = cast(uint)(segment_command.sizeof +
(section_cnt - 1) * section.sizeof +
symtab_command.sizeof +
dysymtab_command.sizeof);
dysymtab_command.sizeof +
versionCommand.size);
header.flags = MH_SUBSECTIONS_VIA_SYMBOLS;
fobjbuf.write(&header, header.sizeof);
foffset = header.sizeof; // start after header
Expand Down Expand Up @@ -1475,6 +1478,7 @@ void MachObj_term(const(char)* objfilename)
}
fobjbuf.write(&symtab_cmd, symtab_cmd.sizeof);
fobjbuf.write(&dysymtab_cmd, dysymtab_cmd.sizeof);
fobjbuf.write(versionCommand.data, versionCommand.size);
fobjbuf.position(foffset, 0);
}

Expand Down Expand Up @@ -2956,3 +2960,170 @@ int dwarf_eh_frame_fixup(int dfseg, targ_size_t offset, Symbol *s, targ_size_t v

return I64 ? 8 : 4;
}


private:

/**
* Encapsulates the build_version_command/version_min_command load commands.
*
* For the 10.14 and later SDK, the `build_version_command` load command is used.
* For earlier versions, the `version_min_command` load command is used.
*/
const struct VersionCommand
{
pure:
nothrow:
@nogc:
@safe:

private
{
/**
* This is the absolute minimum supported version of macOS (64 bit) for DMD.
*
* Earlier versions did not support thread local storage.
*/
enum fallbackOSVersion = Version(10, 7).encode;

/// The first minor version that uses the `build_version_command`.
enum firstMinorUsingBuildVersionCommand = 14;

/// `true` if the `build_version_command` load command should be used.
bool useBuild;

/// The `build_version_command` load command.
build_version_command buildVersionCommand;

/// The `version_min_command` load command.
version_min_command versionMinCommand;
}

/**
* Initializes the VersionCommand.
*
* Params:
* os = the version of the operating system
*/
this(Version os)
{
useBuild = os.minor >= firstMinorUsingBuildVersionCommand;

const encodedOs = os.isValid ? os.encode : fallbackOSVersion;

const build_version_command buildVersionCommand = { minos: encodedOs };
const version_min_command versionMinCommand = { version_: encodedOs };

this.buildVersionCommand = buildVersionCommand;
this.versionMinCommand = versionMinCommand;
}

/// Returns: the size of the load command.
size_t size()
{
return useBuild ? build_version_command.sizeof : version_min_command.sizeof;
}

/// Returns: the data for the load command.
const(void)* data() return
{
return useBuild ? cast(const(void)*) &buildVersionCommand : cast(const(void)*) &versionMinCommand;
}
}

/// Holds an operating system version or a SDK version.
immutable struct Version
{
///
int major;

///
int minor;

///
int build;

/// Returns: `true` if the version is valid
bool isValid() pure nothrow @nogc @safe
{
return major >= 10 && major < 100 &&
minor >= 0 && minor < 100 &&
build >= 0 && build < 100;
}
}

/**
* Returns the given version encoded as a single integer.
*
* Params:
* version_ = the version to encode. Needs to be a valid version
* (`version_.isValid`)
*
* Returns: the encoded version
*/
int encode(Version version_) pure @nogc @safe
in
{
assert(version_.isValid);
}
body
{
with (version_)
return major * 2^^16 + minor * 2^^8 + build * 2^^0;
}

unittest
{
assert(Version(10, 14, 0).encode == 658944);
assert(Version(10, 14, 1).encode == 658945);
assert(Version(10, 14, 6).encode == 658950);
assert(Version(10, 14, 99).encode == 659043);

assert(Version(10, 15, 6).encode == 659206);

assert(Version(10, 16, 0).encode == 659456);
assert(Version(10, 16, 6).encode == 659462);

assert(Version(10, 17, 0).encode == 659712);
}

/// Returns: the version of the currently running operating system.
@trusted
Version operatingSystemVersion()
{
if (const deploymentTarget = getenv("MACOSX_DEPLOYMENT_TARGET"))
{
const version_ = toVersion(deploymentTarget);

if (version_.isValid)
return version_;

error(null, 0, 0, "invalid version number in 'MACOSX_DEPLOYMENT_TARGET=%s'", deploymentTarget);
}
return Version();
}

/**
* Converts the given string to a `Version`.
*
* Params:
* str = the string to convert. Should have the format `XX.YY(.ZZ)`. Needs to
* be `\0` terminated.
*
* Returns: the converted `Version`.
*/
@trusted
Version toVersion(const char* str) @nogc
{
import core.stdc.stdio : sscanf;

if (!str)
return Version();

Version version_;

with (version_)
str.sscanf("%d.%d.%d", &major, &minor, &build);

return version_;
}

0 comments on commit 7e6cdb2

Please sign in to comment.