Skip to content

[LLD][COFF] Add support for ARM64EC entry thunks. #88132

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

Merged
merged 1 commit into from
Jun 18, 2024
Merged
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
10 changes: 8 additions & 2 deletions lld/COFF/Chunks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ using llvm::support::ulittle32_t;

namespace lld::coff {

SectionChunk::SectionChunk(ObjFile *f, const coff_section *h)
: Chunk(SectionKind), file(f), header(h), repl(this) {
SectionChunk::SectionChunk(ObjFile *f, const coff_section *h, Kind k)
: Chunk(k), file(f), header(h), repl(this) {
// Initialize relocs.
if (file)
setRelocs(file->getCOFFObj()->getRelocations(header));
Expand Down Expand Up @@ -410,6 +410,12 @@ void SectionChunk::writeTo(uint8_t *buf) const {

applyRelocation(buf + rel.VirtualAddress, rel);
}

// Write the offset to EC entry thunk preceding section contents. The low bit
// is always set, so it's effectively an offset from the last byte of the
// offset.
if (Defined *entryThunk = getEntryThunk())
write32le(buf - sizeof(uint32_t), entryThunk->getRVA() - rva + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The +1 feels quite odd here. I don't doubt that it's right, but it feels quite surprising to see an odd number when it comes to arm64 function offsets (which all should be multiples of 4). Is there something more you can write here to explain it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's what I observed with MSVC and what Windows expects, I can only speculate about the reason. It could be just "have the low bit set", or it could be interpreted as the offset being from the last byte of the padding, not the first byte of the function. I added a comment about it.

}

void SectionChunk::applyRelocation(uint8_t *off,
Expand Down
40 changes: 35 additions & 5 deletions lld/COFF/Chunks.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,12 @@ enum : unsigned { Log2MaxSectionAlignment = 13 };
// doesn't even have actual data (if common or bss).
class Chunk {
public:
enum Kind : uint8_t { SectionKind, OtherKind, ImportThunkKind };
enum Kind : uint8_t {
SectionKind,
SectionECKind,
OtherKind,
ImportThunkKind
};
Kind kind() const { return chunkKind; }

// Returns the size of this chunk (even if this is a common or BSS.)
Expand Down Expand Up @@ -120,6 +125,10 @@ class Chunk {
llvm::Triple::ArchType getArch() const;
std::optional<chpe_range_type> getArm64ECRangeType() const;

// ARM64EC entry thunk associated with the chunk.
Defined *getEntryThunk() const;
void setEntryThunk(Defined *entryThunk);

protected:
Chunk(Kind k = OtherKind) : chunkKind(k), hasData(true), p2Align(0) {}

Expand Down Expand Up @@ -176,7 +185,7 @@ class NonSectionChunk : public Chunk {
// bytes, so this is used only for logging or debugging.
virtual StringRef getDebugName() const { return ""; }

static bool classof(const Chunk *c) { return c->kind() != SectionKind; }
static bool classof(const Chunk *c) { return c->kind() >= OtherKind; }

protected:
NonSectionChunk(Kind k = OtherKind) : Chunk(k) {}
Expand Down Expand Up @@ -210,7 +219,7 @@ class RuntimePseudoReloc {
};

// A chunk corresponding a section of an input file.
class SectionChunk final : public Chunk {
class SectionChunk : public Chunk {
// Identical COMDAT Folding feature accesses section internal data.
friend class ICF;

Expand All @@ -231,8 +240,8 @@ class SectionChunk final : public Chunk {
Symbol *operator*() const { return file->getSymbol(I->SymbolTableIndex); }
};

SectionChunk(ObjFile *file, const coff_section *header);
static bool classof(const Chunk *c) { return c->kind() == SectionKind; }
SectionChunk(ObjFile *file, const coff_section *header, Kind k = SectionKind);
static bool classof(const Chunk *c) { return c->kind() <= SectionECKind; }
size_t getSize() const { return header->SizeOfRawData; }
ArrayRef<uint8_t> getContents() const;
void writeTo(uint8_t *buf) const;
Expand Down Expand Up @@ -393,6 +402,16 @@ class SectionChunk final : public Chunk {
uint32_t sectionNameSize = 0;
};

// A section chunk corresponding a section of an EC input file.
class SectionChunkEC final : public SectionChunk {
public:
static bool classof(const Chunk *c) { return c->kind() == SectionECKind; }

SectionChunkEC(ObjFile *file, const coff_section *header)
: SectionChunk(file, header, SectionECKind) {}
Defined *entryThunk = nullptr;
};

// Inline methods to implement faux-virtual dispatch for SectionChunk.

inline size_t Chunk::getSize() const {
Expand Down Expand Up @@ -775,6 +794,17 @@ inline bool Chunk::isHotPatchable() const {
return false;
}

inline Defined *Chunk::getEntryThunk() const {
if (auto *c = dyn_cast<const SectionChunkEC>(this))
return c->entryThunk;
return nullptr;
}

inline void Chunk::setEntryThunk(Defined *entryThunk) {
if (auto c = dyn_cast<SectionChunkEC>(this))
c->entryThunk = entryThunk;
}

void applyMOV32T(uint8_t *off, uint32_t v);
void applyBranch24T(uint8_t *off, int32_t v);

Expand Down
2 changes: 2 additions & 0 deletions lld/COFF/Driver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2604,6 +2604,8 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (auto *arg = args.getLastArg(OPT_print_symbol_order))
config->printSymbolOrder = arg->getValue();

ctx.symtab.initializeEntryThunks();

// Identify unreferenced COMDAT sections.
if (config->doGC) {
if (config->mingw) {
Expand Down
15 changes: 12 additions & 3 deletions lld/COFF/ICF.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,25 @@ bool ICF::equalsConstant(const SectionChunk *a, const SectionChunk *b) {
// Compare "moving" part of two sections, namely relocation targets.
bool ICF::equalsVariable(const SectionChunk *a, const SectionChunk *b) {
// Compare relocations.
auto eq = [&](const coff_relocation &r1, const coff_relocation &r2) {
Symbol *b1 = a->file->getSymbol(r1.SymbolTableIndex);
Symbol *b2 = b->file->getSymbol(r2.SymbolTableIndex);
auto eqSym = [&](Symbol *b1, Symbol *b2) {
if (b1 == b2)
return true;
if (auto *d1 = dyn_cast<DefinedRegular>(b1))
if (auto *d2 = dyn_cast<DefinedRegular>(b2))
return d1->getChunk()->eqClass[cnt % 2] == d2->getChunk()->eqClass[cnt % 2];
return false;
};
auto eq = [&](const coff_relocation &r1, const coff_relocation &r2) {
Symbol *b1 = a->file->getSymbol(r1.SymbolTableIndex);
Symbol *b2 = b->file->getSymbol(r2.SymbolTableIndex);
return eqSym(b1, b2);
};

Symbol *e1 = a->getEntryThunk();
Symbol *e2 = b->getEntryThunk();
if ((e1 || e2) && (!e1 || !e2 || !eqSym(e1, e2)))
return false;

return std::equal(a->getRelocs().begin(), a->getRelocs().end(),
b->getRelocs().begin(), eq) &&
assocEquals(a, b);
Expand Down
41 changes: 40 additions & 1 deletion lld/COFF/InputFiles.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,38 @@ void ObjFile::parseLazy() {
}
}

struct ECMapEntry {
ulittle32_t src;
ulittle32_t dst;
ulittle32_t type;
};

void ObjFile::initializeECThunks() {
for (SectionChunk *chunk : hybmpChunks) {
if (chunk->getContents().size() % sizeof(ECMapEntry)) {
error("Invalid .hybmp chunk size " + Twine(chunk->getContents().size()));
continue;
}

const uint8_t *end =
chunk->getContents().data() + chunk->getContents().size();
for (const uint8_t *iter = chunk->getContents().data(); iter != end;
iter += sizeof(ECMapEntry)) {
auto entry = reinterpret_cast<const ECMapEntry *>(iter);
switch (entry->type) {
case Arm64ECThunkType::Entry:
ctx.symtab.addEntryThunk(getSymbol(entry->src), getSymbol(entry->dst));
break;
case Arm64ECThunkType::Exit:
case Arm64ECThunkType::GuestExit:
break;
default:
warn("Ignoring unknown EC thunk type " + Twine(entry->type));
}
}
}
}

void ObjFile::parse() {
// Parse a memory buffer as a COFF file.
std::unique_ptr<Binary> bin = CHECK(createBinary(mb), this);
Expand All @@ -168,6 +200,7 @@ void ObjFile::parse() {
initializeSymbols();
initializeFlags();
initializeDependencies();
initializeECThunks();
}

const coff_section *ObjFile::getSection(uint32_t i) {
Expand Down Expand Up @@ -242,7 +275,11 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,

if (sec->Characteristics & llvm::COFF::IMAGE_SCN_LNK_REMOVE)
return nullptr;
auto *c = make<SectionChunk>(this, sec);
SectionChunk *c;
if (isArm64EC(getMachineType()))
c = make<SectionChunkEC>(this, sec);
else
c = make<SectionChunk>(this, sec);
if (def)
c->checksum = def->CheckSum;

Expand All @@ -260,6 +297,8 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,
guardEHContChunks.push_back(c);
else if (name == ".sxdata")
sxDataChunks.push_back(c);
else if (isArm64EC(getMachineType()) && name == ".hybmp$x")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the section name always have the $x suffix, or should we have something like name.substr(0, name.find('$')) == ".hybmp"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked with MSVC, it seems to apply the special treatment only for that exact name. Things like ".hybmp", ".hybmp$xy" or ".hybmp$y" don't get that treatment.

hybmpChunks.push_back(c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, this change both has the effect that the .hybmp section chunks are getting parsed, but also means that they are skipped and not included in the output file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, there is no reason for them to be in the output file. Entry thunks are looked up from the offset in padding in runtime. Later, the linker will need exit thunk association for generating import thunks, but once the image is linked, there is no need for such map.

else if (ctx.config.tailMerge && sec->NumberOfRelocations == 0 &&
name == ".rdata" && leaderName.starts_with("??_C@"))
// COFF sections that look like string literal sections (i.e. no
Expand Down
3 changes: 3 additions & 0 deletions lld/COFF/InputFiles.h
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ class ObjFile : public InputFile {
void initializeSymbols();
void initializeFlags();
void initializeDependencies();
void initializeECThunks();

SectionChunk *
readSection(uint32_t sectionNumber,
Expand Down Expand Up @@ -292,6 +293,8 @@ class ObjFile : public InputFile {
std::vector<SectionChunk *> guardLJmpChunks;
std::vector<SectionChunk *> guardEHContChunks;

std::vector<SectionChunk *> hybmpChunks;

// This vector contains a list of all symbols defined or referenced by this
// file. They are indexed such that you can get a Symbol by symbol
// index. Nonexistent indices (which are occupied by auxiliary
Expand Down
4 changes: 4 additions & 0 deletions lld/COFF/MarkLive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ void markLive(COFFLinkerContext &ctx) {
// Mark associative sections if any.
for (SectionChunk &c : sc->children())
enqueue(&c);

// Mark EC entry thunks.
if (Defined *entryThunk = sc->getEntryThunk())
addSym(entryThunk);
}
}
}
22 changes: 22 additions & 0 deletions lld/COFF/SymbolTable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -561,6 +561,28 @@ std::pair<Symbol *, bool> SymbolTable::insert(StringRef name, InputFile *file) {
return result;
}

void SymbolTable::addEntryThunk(Symbol *from, Symbol *to) {
entryThunks.push_back({from, to});
}

void SymbolTable::initializeEntryThunks() {
for (auto it : entryThunks) {
auto *to = dyn_cast<Defined>(it.second);
if (!to)
continue;
auto *from = dyn_cast<DefinedRegular>(it.first);
// We need to be able to add padding to the function and fill it with an
// offset to its entry thunks. To ensure that padding the function is
// feasible, functions are required to be COMDAT symbols with no offset.
if (!from || !from->getChunk()->isCOMDAT() ||
cast<DefinedRegular>(from)->getValue()) {
error("non COMDAT symbol '" + from->getName() + "' in hybrid map");
continue;
}
from->getChunk()->setEntryThunk(to);
}
}

Symbol *SymbolTable::addUndefined(StringRef name, InputFile *f,
bool isWeakAlias) {
auto [s, wasInserted] = insert(name, f);
Expand Down
3 changes: 3 additions & 0 deletions lld/COFF/SymbolTable.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ class SymbolTable {
Symbol *addImportThunk(StringRef name, DefinedImportData *s,
uint16_t machine);
void addLibcall(StringRef name);
void addEntryThunk(Symbol *from, Symbol *to);
void initializeEntryThunks();

void reportDuplicate(Symbol *existing, InputFile *newFile,
SectionChunk *newSc = nullptr,
Expand Down Expand Up @@ -134,6 +136,7 @@ class SymbolTable {
llvm::DenseMap<llvm::CachedHashStringRef, Symbol *> symMap;
std::unique_ptr<BitcodeCompiler> lto;
bool ltoCompilationDone = false;
std::vector<std::pair<Symbol *, Symbol *>> entryThunks;

COFFLinkerContext &ctx;
};
Expand Down
4 changes: 4 additions & 0 deletions lld/COFF/Writer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,10 @@ void Writer::assignAddresses() {
}
if (padding && c->isHotPatchable())
virtualSize += padding;
// If chunk has EC entry thunk, reserve a space for an offset to the
// thunk.
if (c->getEntryThunk())
virtualSize += sizeof(uint32_t);
virtualSize = alignTo(virtualSize, c->getAlignment());
c->setRVA(rva + virtualSize);
virtualSize += c->getSize();
Expand Down
Loading
Loading