[lld][MachO] Add support for LC_VERSION_MIN_* load commands

This diff adds initial support for the legacy LC_VERSION_MIN_* load commands.

Test plan: make check-lld-macho

Differential revision: https://reviews.llvm.org/D100523
This commit is contained in:
Alexander Shaposhnikov 2021-04-21 05:41:14 -07:00
parent 8c16c8b7ef
commit 5c835e1ae5
13 changed files with 237 additions and 90 deletions

View File

@ -33,6 +33,7 @@ using SectionRenameMap = llvm::DenseMap<NamePair, NamePair>;
using SegmentRenameMap = llvm::DenseMap<llvm::StringRef, llvm::StringRef>;
struct PlatformInfo {
llvm::MachO::Target target;
llvm::VersionTuple minimum;
llvm::VersionTuple sdk;
};
@ -101,7 +102,6 @@ struct Configuration {
llvm::StringRef ltoObjPath;
llvm::StringRef thinLTOJobs;
bool demangle = false;
llvm::MachO::Target target;
PlatformInfo platformInfo;
NamespaceKind namespaceKind = NamespaceKind::twolevel;
UndefinedSymbolTreatment undefinedSymbolTreatment =

View File

@ -616,9 +616,10 @@ static TargetInfo *createTargetInfo(InputArgList &args) {
fatal("must specify -arch");
PlatformKind platform = parsePlatformVersion(args);
config->target = MachO::Target(getArchitectureFromName(archName), platform);
config->platformInfo.target =
MachO::Target(getArchitectureFromName(archName), platform);
switch (getCPUTypeFromArchitecture(config->target.Arch).first) {
switch (getCPUTypeFromArchitecture(config->platformInfo.target.Arch).first) {
case CPU_TYPE_X86_64:
return createX86_64TargetInfo();
case CPU_TYPE_ARM64:
@ -699,17 +700,18 @@ static const char *getReproduceOption(InputArgList &args) {
static bool isPie(InputArgList &args) {
if (config->outputType != MH_EXECUTE || args.hasArg(OPT_no_pie))
return false;
if (config->target.Arch == AK_arm64 || config->target.Arch == AK_arm64e ||
config->target.Arch == AK_arm64_32)
if (config->platformInfo.target.Arch == AK_arm64 ||
config->platformInfo.target.Arch == AK_arm64e ||
config->platformInfo.target.Arch == AK_arm64_32)
return true;
// TODO: add logic here as we support more archs. E.g. i386 should default
// to PIE from 10.7
assert(config->target.Arch == AK_x86_64 ||
config->target.Arch == AK_x86_64h ||
config->target.Arch == AK_arm64_32);
assert(config->platformInfo.target.Arch == AK_x86_64 ||
config->platformInfo.target.Arch == AK_x86_64h ||
config->platformInfo.target.Arch == AK_arm64_32);
PlatformKind kind = config->target.Platform;
PlatformKind kind = config->platformInfo.target.Platform;
if (kind == PlatformKind::macOS &&
config->platformInfo.minimum >= VersionTuple(10, 6))
return true;
@ -1029,7 +1031,7 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
StringRef segName = arg->getValue(0);
uint32_t maxProt = parseProtection(arg->getValue(1));
uint32_t initProt = parseProtection(arg->getValue(2));
if (maxProt != initProt && config->target.Arch != AK_i386)
if (maxProt != initProt && config->platformInfo.target.Arch != AK_i386)
error("invalid argument '" + arg->getAsString(args) +
"': max and init must be the same for non-i386 archs");
if (segName == segment_names::linkEdit)
@ -1051,8 +1053,9 @@ bool macho::link(ArrayRef<const char *> argsArr, bool canExitEarly,
config->adhocCodesign = args.hasFlag(
OPT_adhoc_codesign, OPT_no_adhoc_codesign,
(config->target.Arch == AK_arm64 || config->target.Arch == AK_arm64e) &&
config->target.Platform == PlatformKind::macOS);
(config->platformInfo.target.Arch == AK_arm64 ||
config->platformInfo.target.Arch == AK_arm64e) &&
config->platformInfo.target.Platform == PlatformKind::macOS);
if (args.hasArg(OPT_v)) {
message(getLLDVersion());

View File

@ -95,6 +95,71 @@ SetVector<InputFile *> macho::inputFiles;
std::unique_ptr<TarWriter> macho::tar;
int InputFile::idCount = 0;
static VersionTuple decodeVersion(uint32_t version) {
unsigned major = version >> 16;
unsigned minor = (version >> 8) & 0xffu;
unsigned subMinor = version & 0xffu;
return VersionTuple(major, minor, subMinor);
}
template <class LP>
static Optional<PlatformInfo> getPlatformInfo(const InputFile *input) {
if (!isa<ObjFile>(input) && !isa<DylibFile>(input))
return None;
using Header = typename LP::mach_header;
auto *hdr = reinterpret_cast<const Header *>(input->mb.getBufferStart());
PlatformInfo platformInfo;
if (const auto *cmd =
findCommand<build_version_command>(hdr, LC_BUILD_VERSION)) {
platformInfo.target.Platform = static_cast<PlatformKind>(cmd->platform);
platformInfo.minimum = decodeVersion(cmd->minos);
return platformInfo;
} else if (const auto *cmd =
findCommand<version_min_command>(hdr, LC_VERSION_MIN_MACOSX)) {
platformInfo.target.Platform = PlatformKind::macOS;
platformInfo.minimum = decodeVersion(cmd->version);
return platformInfo;
} else if (const auto *cmd = findCommand<version_min_command>(
hdr, LC_VERSION_MIN_IPHONEOS)) {
platformInfo.target.Platform = PlatformKind::iOS;
platformInfo.minimum = decodeVersion(cmd->version);
return platformInfo;
} else if (const auto *cmd =
findCommand<version_min_command>(hdr, LC_VERSION_MIN_TVOS)) {
platformInfo.target.Platform = PlatformKind::tvOS;
platformInfo.minimum = decodeVersion(cmd->version);
} else if (const auto *cmd = findCommand<version_min_command>(
hdr, LC_VERSION_MIN_WATCHOS)) {
platformInfo.target.Platform = PlatformKind::watchOS;
platformInfo.minimum = decodeVersion(cmd->version);
return platformInfo;
}
return None;
}
template <class LP> static bool checkCompatibility(const InputFile *input) {
Optional<PlatformInfo> platformInfo = getPlatformInfo<LP>(input);
if (!platformInfo)
return true;
// TODO: Correctly detect simulator platforms or relax this check.
if (config->platformInfo.target.Platform != platformInfo->target.Platform) {
error(toString(input) + " has platform " +
getPlatformName(platformInfo->target.Platform) +
Twine(", which is different from target platform ") +
getPlatformName(config->platformInfo.target.Platform));
return false;
}
if (platformInfo->minimum >= config->platformInfo.minimum)
return true;
error(toString(input) + " has version " +
platformInfo->minimum.getAsString() +
", which is incompatible with target version of " +
config->platformInfo.minimum.getAsString());
return false;
}
// Open a given file path and return it as a memory-mapped file.
Optional<MemoryBufferRef> macho::readFile(StringRef path) {
// Open a file.
@ -365,32 +430,6 @@ static macho::Symbol *createDefined(const NList &sym, StringRef name,
/*isExternal=*/false, /*isPrivateExtern=*/false);
}
// Checks if the version specified in `cmd` is compatible with target
// version. IOW, check if cmd's version >= config's version.
static bool hasCompatVersion(const InputFile *input,
const build_version_command *cmd) {
if (config->target.Platform != static_cast<PlatformKind>(cmd->platform)) {
error(toString(input) + " has platform " +
getPlatformName(static_cast<PlatformKind>(cmd->platform)) +
Twine(", which is different from target platform ") +
getPlatformName(config->target.Platform));
return false;
}
unsigned major = cmd->minos >> 16;
unsigned minor = (cmd->minos >> 8) & 0xffu;
unsigned subMinor = cmd->minos & 0xffu;
VersionTuple version(major, minor, subMinor);
if (version >= config->platformInfo.minimum)
return true;
error(toString(input) + " has version " + version.getAsString() +
", which is incompatible with target version of " +
config->platformInfo.minimum.getAsString());
return false;
}
// Absolute symbols are defined symbols that do not have an associated
// InputSection. They cannot be weak.
template <class NList>
@ -542,18 +581,15 @@ template <class LP> void ObjFile::parse() {
auto *hdr = reinterpret_cast<const Header *>(mb.getBufferStart());
Architecture arch = getArchitectureFromCpuType(hdr->cputype, hdr->cpusubtype);
if (arch != config->target.Arch) {
if (arch != config->platformInfo.target.Arch) {
error(toString(this) + " has architecture " + getArchitectureName(arch) +
" which is incompatible with target architecture " +
getArchitectureName(config->target.Arch));
getArchitectureName(config->platformInfo.target.Arch));
return;
}
if (const auto *cmd =
findCommand<build_version_command>(hdr, LC_BUILD_VERSION)) {
if (!hasCompatVersion(this, cmd))
return;
}
if (!checkCompatibility<LP>(this))
return;
if (const load_command *cmd = findCommand(hdr, LC_LINKER_OPTION)) {
auto *c = reinterpret_cast<const linker_option_command *>(cmd);
@ -719,11 +755,8 @@ template <class LP> void DylibFile::parse(DylibFile *umbrella) {
return;
}
if (const build_version_command *cmd =
findCommand<build_version_command>(hdr, LC_BUILD_VERSION)) {
if (!hasCompatVersion(this, cmd))
return;
}
if (!checkCompatibility<LP>(this))
return;
// Initialize symbols.
DylibFile *exportingFile = isImplicitlyLinked(dylibName) ? this : umbrella;
@ -791,9 +824,9 @@ DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
"/usr/lib/system/libsystem_pthread.dylib"};
if (!is_contained(skipPlatformChecks, dylibName) &&
!is_contained(interface.targets(), config->target)) {
!is_contained(interface.targets(), config->platformInfo.target)) {
error(toString(this) + " is incompatible with " +
std::string(config->target));
std::string(config->platformInfo.target));
return;
}
@ -806,7 +839,7 @@ DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
// TODO(compnerd) filter out symbols based on the target platform
// TODO: handle weak defs, thread locals
for (const auto *symbol : interface.symbols()) {
if (!symbol->getArchitectures().has(config->target.Arch))
if (!symbol->getArchitectures().has(config->platformInfo.target.Arch))
continue;
switch (symbol->getKind()) {
@ -834,7 +867,7 @@ DylibFile::DylibFile(const InterfaceFile &interface, DylibFile *umbrella,
for (InterfaceFileRef intfRef : interface.reexportedLibraries()) {
InterfaceFile::const_target_range targets = intfRef.targets();
if (is_contained(skipPlatformChecks, intfRef.getInstallName()) ||
is_contained(targets, config->target))
is_contained(targets, config->platformInfo.target))
loadReexport(intfRef.getInstallName(), exportingFile, topLevel);
}
}

View File

@ -37,6 +37,7 @@ class TarWriter;
namespace lld {
namespace macho {
struct PlatformInfo;
class InputSection;
class Symbol;
struct Reloc;

View File

@ -117,9 +117,10 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
uint32_t modTime = 0;
if (!config->ltoObjPath.empty()) {
filePath = config->ltoObjPath;
path::append(filePath, Twine(i) + "." +
getArchitectureName(config->target.Arch) +
".lto.o");
path::append(filePath,
Twine(i) + "." +
getArchitectureName(config->platformInfo.target.Arch) +
".lto.o");
saveBuffer(buf[i], filePath);
modTime = getModTime(filePath);
}

View File

@ -108,8 +108,9 @@ void macho::writeMapFile() {
os << format("# Path: %s\n", config->outputFile.str().c_str());
// Dump output architecture.
os << format("# Arch: %s\n",
getArchitectureName(config->target.Arch).str().c_str());
os << format(
"# Arch: %s\n",
getArchitectureName(config->platformInfo.target.Arch).str().c_str());
// Dump table of object files.
os << "# Object files:\n";

View File

@ -37,7 +37,7 @@ static uint32_t initProt(StringRef name) {
}
static uint32_t maxProt(StringRef name) {
assert(config->target.Arch != AK_i386 &&
assert(config->platformInfo.target.Arch != AK_i386 &&
"TODO: i386 has different maxProt requirements");
return initProt(name);
}

View File

@ -93,7 +93,7 @@ static uint32_t cpuSubtype() {
if (config->outputType == MH_EXECUTE && !config->staticLink &&
target->cpuSubtype == CPU_SUBTYPE_X86_64_ALL &&
config->target.Platform == PlatformKind::macOS &&
config->platformInfo.target.Platform == PlatformKind::macOS &&
config->platformInfo.minimum >= VersionTuple(10, 5))
subtype |= CPU_SUBTYPE_LIB64;

View File

@ -359,10 +359,54 @@ private:
StringRef path;
};
static uint32_t encodeVersion(const VersionTuple &version) {
return ((version.getMajor() << 020) |
(version.getMinor().getValueOr(0) << 010) |
version.getSubminor().getValueOr(0));
}
class LCMinVersion : public LoadCommand {
public:
explicit LCMinVersion(const PlatformInfo &platformInfo)
: platformInfo(platformInfo) {}
uint32_t getSize() const override { return sizeof(version_min_command); }
void writeTo(uint8_t *buf) const override {
auto *c = reinterpret_cast<version_min_command *>(buf);
switch (platformInfo.target.Platform) {
case PlatformKind::macOS:
c->cmd = LC_VERSION_MIN_MACOSX;
break;
case PlatformKind::iOS:
case PlatformKind::iOSSimulator:
c->cmd = LC_VERSION_MIN_IPHONEOS;
break;
case PlatformKind::tvOS:
case PlatformKind::tvOSSimulator:
c->cmd = LC_VERSION_MIN_TVOS;
break;
case PlatformKind::watchOS:
case PlatformKind::watchOSSimulator:
c->cmd = LC_VERSION_MIN_WATCHOS;
break;
default:
llvm_unreachable("invalid platform");
break;
}
c->cmdsize = getSize();
c->version = encodeVersion(platformInfo.minimum);
c->sdk = encodeVersion(platformInfo.sdk);
}
private:
const PlatformInfo &platformInfo;
};
class LCBuildVersion : public LoadCommand {
public:
LCBuildVersion(PlatformKind platform, const PlatformInfo &platformInfo)
: platform(platform), platformInfo(platformInfo) {}
explicit LCBuildVersion(const PlatformInfo &platformInfo)
: platformInfo(platformInfo) {}
const int ntools = 1;
@ -374,21 +418,17 @@ public:
auto *c = reinterpret_cast<build_version_command *>(buf);
c->cmd = LC_BUILD_VERSION;
c->cmdsize = getSize();
c->platform = static_cast<uint32_t>(platform);
c->minos = ((platformInfo.minimum.getMajor() << 020) |
(platformInfo.minimum.getMinor().getValueOr(0) << 010) |
platformInfo.minimum.getSubminor().getValueOr(0));
c->sdk = ((platformInfo.sdk.getMajor() << 020) |
(platformInfo.sdk.getMinor().getValueOr(0) << 010) |
platformInfo.sdk.getSubminor().getValueOr(0));
c->platform = static_cast<uint32_t>(platformInfo.target.Platform);
c->minos = encodeVersion(platformInfo.minimum);
c->sdk = encodeVersion(platformInfo.sdk);
c->ntools = ntools;
auto *t = reinterpret_cast<build_tool_version *>(&c[1]);
t->tool = TOOL_LD;
t->version = (LLVM_VERSION_MAJOR << 020) | (LLVM_VERSION_MINOR << 010) |
LLVM_VERSION_PATCH;
t->version = encodeVersion(llvm::VersionTuple(
LLVM_VERSION_MAJOR, LLVM_VERSION_MINOR, LLVM_VERSION_PATCH));
}
PlatformKind platform;
private:
const PlatformInfo &platformInfo;
};
@ -553,6 +593,20 @@ void Writer::scanSymbols() {
}
}
// TODO: ld64 enforces the old load commands in a few other cases.
static bool useLCBuildVersion(const PlatformInfo &platformInfo) {
static const std::map<PlatformKind, llvm::VersionTuple> minVersion = {
{PlatformKind::macOS, llvm::VersionTuple(10, 14)},
{PlatformKind::iOS, llvm::VersionTuple(12, 0)},
{PlatformKind::iOSSimulator, llvm::VersionTuple(13, 0)},
{PlatformKind::tvOS, llvm::VersionTuple(12, 0)},
{PlatformKind::tvOSSimulator, llvm::VersionTuple(13, 0)},
{PlatformKind::watchOS, llvm::VersionTuple(5, 0)},
{PlatformKind::watchOSSimulator, llvm::VersionTuple(6, 0)}};
auto it = minVersion.find(platformInfo.target.Platform);
return it == minVersion.end() ? true : platformInfo.minimum >= it->second;
}
template <class LP> void Writer::createLoadCommands() {
uint8_t segIndex = 0;
for (OutputSegment *seg : outputSegments) {
@ -589,8 +643,10 @@ template <class LP> void Writer::createLoadCommands() {
uuidCommand = make<LCUuid>();
in.header->addLoadCommand(uuidCommand);
in.header->addLoadCommand(
make<LCBuildVersion>(config->target.Platform, config->platformInfo));
if (useLCBuildVersion(config->platformInfo))
in.header->addLoadCommand(make<LCBuildVersion>(config->platformInfo));
else
in.header->addLoadCommand(make<LCMinVersion>(config->platformInfo));
int64_t dylibOrdinal = 1;
for (InputFile *file : inputFiles) {

View File

@ -1,16 +1,68 @@
# REQUIRES: x86
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
# RUN: %lld -platform_version macos 10.14.1 10.15 -o %t %t.o
# RUN: llvm-objdump --macho --all-headers %t | FileCheck %s
# CHECK: cmd LC_BUILD_VERSION
# CHECK-NEXT: cmdsize 32
# CHECK-NEXT: platform macos
# CHECK-NEXT: sdk 10.15
# CHECK-NEXT: minos 10.14.1
# CHECK-NEXT: ntools 1
# CHECK-NEXT: tool ld
# CHECK-NEXT: version {{[0-9\.]+}}
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
# RUN: %lld -platform_version macos 10.14 10.15 -o %t.macos_10_14 %t.o
# RUN: llvm-objdump --macho --all-headers %t.macos_10_14 | FileCheck %s --check-prefix=MACOS_10_14
# MACOS_10_14: cmd LC_BUILD_VERSION
# MACOS_10_14-NEXT: cmdsize 32
# MACOS_10_14-NEXT: platform macos
# MACOS_10_14-NEXT: sdk 10.15
# MACOS_10_14-NEXT: minos 10.14
# MACOS_10_14-NEXT: ntools 1
# MACOS_10_14-NEXT: tool ld
# MACOS_10_14-NEXT: version {{[0-9\.]+}}
# RUN: %lld -platform_version macos 10.13 10.15 -o %t.macos_10_13 %t.o
# RUN: llvm-objdump --macho --all-headers %t.macos_10_13 | FileCheck %s --check-prefix=MACOS_10_13
# MACOS_10_13: cmd LC_VERSION_MIN_MACOSX
# MACOS_10_13-NEXT: cmdsize 16
# MACOS_10_13-NEXT: version 10.13
# MACOS_10_13-NEXT: sdk 10.15
# RUN: %lld -platform_version ios 12.0 10.15 -o %t.ios_12_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.ios_12_0 | FileCheck %s --check-prefix=IOS_12_0
# RUN: %lld -platform_version ios-simulator 13.0 10.15 -o %t.ios_sim_13_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.ios_sim_13_0 | FileCheck %s --check-prefix=IOS_12_0
# IOS_12_0: cmd LC_BUILD_VERSION
# RUN: %lld -platform_version ios 11.0 10.15 -o %t.ios_11_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.ios_11_0 | FileCheck %s --check-prefix=IOS_11_0
# RUN: %lld -platform_version ios-simulator 12.0 10.15 -o %t.ios_sim_12_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.ios_sim_12_0 | FileCheck %s --check-prefix=IOS_11_0
# IOS_11_0: cmd LC_VERSION_MIN_IPHONEOS
# RUN: %lld -platform_version tvos 12.0 10.15 -o %t.tvos_12_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.tvos_12_0 | FileCheck %s --check-prefix=TVOS_12_0
# RUN: %lld -platform_version tvos-simulator 13.0 10.15 -o %t.tvos_sim_13_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.tvos_sim_13_0 | FileCheck %s --check-prefix=TVOS_12_0
# TVOS_12_0: cmd LC_BUILD_VERSION
# RUN: %lld -platform_version tvos 11.0 10.15 -o %t.tvos_11_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.tvos_11_0 | FileCheck %s --check-prefix=TVOS_11_0
# RUN: %lld -platform_version tvos-simulator 12.0 10.15 -o %t.tvos_sim_12_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.tvos_sim_12_0 | FileCheck %s --check-prefix=TVOS_11_0
# TVOS_11_0: cmd LC_VERSION_MIN_TVOS
# RUN: %lld -platform_version watchos 5.0 10.15 -o %t.watchos_5_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.watchos_5_0 | FileCheck %s --check-prefix=WATCHOS_5_0
# RUN: %lld -platform_version watchos-simulator 6.0 10.15 -o %t.watchos_sim_6_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.watchos_sim_6_0 | FileCheck %s --check-prefix=WATCHOS_5_0
# WATCHOS_5_0: cmd LC_BUILD_VERSION
# RUN: %lld -platform_version watchos 4.0 10.15 -o %t.watchos_4_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.watchos_4_0 | FileCheck %s --check-prefix=WATCHOS_4_0
# RUN: %lld -platform_version watchos-simulator 5.0 10.15 -o %t.watchos_sim_5_0 %t.o
# RUN: llvm-objdump --macho --all-headers %t.watchos_sim_5_0 | FileCheck %s --check-prefix=WATCHOS_4_0
# WATCHOS_4_0: cmd LC_VERSION_MIN_WATCHOS
.text
.global _main

View File

@ -47,7 +47,7 @@
# DYLIB: cmd LC_ID_DYLIB
# COMMON: cmd LC_UUID
# COMMON: cmd LC_BUILD_VERSION
# COMMON: cmd LC_VERSION_MIN_MACOSX
# COMMON: cmd LC_LOAD_DYLIB
.section __TEXT,__cstring

View File

@ -12,12 +12,12 @@
## address offset and the contents at that address very similarly, so am using
## --match-full-lines to make sure we match on the right thing.
# CHECK: Contents of section __TEXT,__cstring:
# CHECK-NEXT: 100000434 {{.*}}
# CHECK-NEXT: 100000424 {{.*}}
## 1st 8 bytes refer to the start of __cstring + 0xe, 2nd 8 bytes refer to the
## start of __cstring
# CHECK: Contents of section __DATA_CONST,__got:
# CHECK-NEXT: [[#%X,ADDR:]] 42040000 01000000 34040000 01000000 {{.*}}
# CHECK-NEXT: [[#%X,ADDR:]] 32040000 01000000 24040000 01000000 {{.*}}
# CHECK-NEXT: [[#ADDR + 16]] 00000000 00000000 {{.*}}
## Check that the rebase table is empty.

View File

@ -21,7 +21,7 @@
# RUN: llvm-objdump --section=__const --full-contents %t | FileCheck %s --check-prefix=NONPCREL
# NONPCREL: Contents of section __DATA,__const:
# NONPCREL-NEXT: 100001000 18040000 01000000 18040000 01000000
# NONPCREL-NEXT: 100001000 08040000 01000000 08040000 01000000
.section __TEXT,__text
.globl _main, _f