This is an archive of the discontinued LLVM Phabricator instance.

[LLD][ELF] Calculate sizes of SHF_ALLOC Synthetic Sections early
ClosedPublic

Authored by peter.smith on Feb 15 2017, 3:33 AM.

Details

Summary

This change splits out the finalizing of SyntheticSections size and the overall finalizing. This permits us to move createThunks() to the point where the addresses and sizes will be stable. It involves:

  • Separating out the addition of GlobalSymbols to the dynamic symbol table and the static symbol table. The existing symbol table is built to assume that all local symbols are added before globals.
  • Added a new member function finalizeAllocSize() to set the SyntheticSections size.
  • Call the finalizeAllocSize() prior to createThunks(), which has been moved as late as it can.

I chose the name finalizeAllocSize to emphasize that only SHF_ALLOC sizes need to be finalized. We only need to worry about SyntheticSections that can disrupt thunk creation.

There is an argument that the DynamicSection and GOT sections don't need their size finalized early as they are in RW and well behaved applications will have all their code in RO, and all RO comes before RW. However since it wasn't much more effort to do so I thought it better to be consistent and to remove the possibility of a linker script breaking the assumption that they don't matter.

I initially tried to just move some of the existing finalize() calls before createThunks() but ended up in a somewhat tangled web of ordering dependencies. I think it would be less fragile to explicitly separate out the size finalization.

In the patch I've left the size calculations in the finalize() as asserts to check that createThunks() does not affect the size calculation.

The motivation is to allow addresses to be calculated prior to createThunks so that the information can be used in determining whether calls are within range. By finalizing the size of the SyntheticSections that have the SHF_ALLOC we remove a potential source of errors where range calculations made in createThunks are broken by changes in the sizes of SyntheticSections.

My next step is to allow address assignment prior to, and immediately after createThunks. At present the linker script address assignment is designed to only be run once so we can't just use the existing Script<ELFT>::X->assignAddresses(Phdrs). Any suggestions of how to approach that task would be most welcome as I don't have a lot of experience with that code.

Diff Detail

Event Timeline

peter.smith created this revision.Feb 15 2017, 3:33 AM
ruiu added inline comments.Feb 15 2017, 1:53 PM
ELF/SyntheticSections.cpp
858–872

Is this computing the same value just for the last assertion? If so it seems a bit too much.

1085

I think I prefer a more functional style than this. You could change this function to return std::vector<Entry> and call that twice, once in finalizeAllocSize and the other in finalize.

1086

nit: For short lambdas like this probably just [=] is cognitively lightweight than explicit variable lists.

1324–1326

The reason why you need this is because if it is a dynamic symbol table, finalizing it may change (or fix) the size of a .gnu.hash section, right? Please add a comment.

ELF/Writer.cpp
1202

What is this?

Thank you both for the comments. I'm thinking that it may be better to rework this a little bit and resubmit as the MipsGot::PageIndex calculation breaks my assertion that adding range extension thunks can't affect the size of the SyntheticSections. The size of the MipsGot is proportional to the size of the OutputSections size (modulo 64k), while the amount of Thunks added is likely to be smaller than 64k adding a Thunk could potentially extend over a page boundary.

Instead of adding a finalizeAllocSize() I'm thinking about the following pseudo-code:

// if need thunks ...
bool AddressesChanged = true;
do {
  if (AddressesChanged)
    updateSyntheticSectionAllocSizes();
  AssignAddresses();
  AddressesChanged = createThunks();
} while (AddressesChanged)

I'll have a go at implementing using this model to see if it comes out a bit cleaner. Let me know if this sounds like a bad idea?

Some specific follow-up:

  • The OS->size += 8 after the ARMExidxSentinelSection is best removed. The OS->Size of the .ARM.exidx Output Section isn't updated until assignOffsets() is called. This caused a problem when I experimentally assigned addresses beforehand, correcting the size fixed the problem with the early address assignment but we don't need it right now.
  • I think it would be possible to change the SymbolTableSection to accept the addition of locals and globals in any order, I'm happy to have a go at that but it will be worth submitting as a separate patch. Would you prefer that to be done before doing this work or after?

As an aside I've experimented a bit with LinkerScript address assignment and it is possible to re-run the address assignment if all the state in the class members such as LMAOffset, AlreadyOutputOS is reset to the initial value.

ruiu edited edge metadata.Feb 16 2017, 11:55 AM

Thank you both for the comments. I'm thinking that it may be better to rework this a little bit and resubmit as the MipsGot::PageIndex calculation breaks my assertion that adding range extension thunks can't affect the size of the SyntheticSections. The size of the MipsGot is proportional to the size of the OutputSections size (modulo 64k), while the amount of Thunks added is likely to be smaller than 64k adding a Thunk could potentially extend over a page boundary.

Instead of adding a finalizeAllocSize() I'm thinking about the following pseudo-code:

// if need thunks ...
bool AddressesChanged = true;
do {
  if (AddressesChanged)
    updateSyntheticSectionAllocSizes();
  AssignAddresses();
  AddressesChanged = createThunks();
} while (AddressesChanged)

I'll have a go at implementing using this model to see if it comes out a bit cleaner. Let me know if this sounds like a bad idea?

This is what I expected. We need a for-loop somewhere which adds thunks until a convergence is obtained (all addresses are fixed), so that seems overall fine.

Some specific follow-up:

  • The OS->size += 8 after the ARMExidxSentinelSection is best removed. The OS->Size of the .ARM.exidx Output Section isn't updated until assignOffsets() is called. This caused a problem when I experimentally assigned addresses beforehand, correcting the size fixed the problem with the early address assignment but we don't need it right now.
  • I think it would be possible to change the SymbolTableSection to accept the addition of locals and globals in any order, I'm happy to have a go at that but it will be worth submitting as a separate patch. Would you prefer that to be done before doing this work or after?

Maybe before, if that makes it easier for you to make other changes?

As an aside I've experimented a bit with LinkerScript address assignment and it is possible to re-run the address assignment if all the state in the class members such as LMAOffset, AlreadyOutputOS is reset to the initial value.

Updated diff to:

  • Rebased on top of D30085 (not reviewed yet) which allows us to add local symbols after globals
  • Moved the addition of symbols to the GNU Hash Table to a separate member function that can be called immediately after the dynamic symbols have been added.
  • Change finalizeAllocSize() to updateAllocSize(). The intention is that this will be called each time thunks are added, and immediately prior to thunks being added, to prevent the MipsGot missing an additional page.
  • Hopefully addressed any review comments that still apply

I've not put a loop in for createThunks() yet as they need some work to make sure that they don't create Thunks to Thunks and loop into infinity. This would be best addressed in a later patch.

Thank you very much for the comments, I've updated the diff to incorporate the suggestions.

I will be on vacation with limited computer access till the 1st March, I'll pick this back up when I return.

I like the suggestion of splitting up the finalize into 3 separate parts. I've had a go at implementing that in this diff. This has a nice side-effect that the dynamic section finalize only has to run once as the dynamic symbol table early finalize means we don't need to update the entries. I think that this could be hidden inside a single finalize member but I think we might have to track the state to make sure finalize can be called more than once.

In the general case we will need to assign addresses multiple times as it is possible to write linker scripts where adding thunks causes address expressions in the script to change in ways that breaks the address calculations.

I've copied Rafael's comments into Phabricator for reference:

+ if (In<ELFT>::GnuHashTab && In<ELFT>::DynSymTab)
+ In<ELFT>::DynSymTab->addSymbolsToGnuHashTab();

Having the gnu hash implies that we also have a dynamic symbol table,
no?

// Dynamic section must be the last one in this list and dynamic
// symbol table section (DynSymTab) must be the first one.

This comment should stay with the definition of the Synthetics array.

+ Add remaining entries to complete .dynamic contents.
+template <class ELFT> void DynamicSection<ELFT>::updateAllocSize() {
+ std::vector<Entry> LateEntries = addLateEntries();
+
+1 for DT_NULL
+ this->Size = (Entries.size() + LateEntries.size() + 1) * this->Entsize;
+}
+
+// Add remaining entries to complete .dynamic contents.
+template <class ELFT> void DynamicSection<ELFT>::finalize() {
+ this->Link = In<ELFT>::DynStrTab->OutSec->SectionIndex;
+ std::vector<Entry> LateEntries = addLateEntries();
+ Entries.insert(Entries.end(), LateEntries.begin(), LateEntries.end());

this->OutSec->Entsize = this->Entsize;
this->OutSec->Link = this->Link;
  • // +1 for DT_NULL this->Size = (Entries.size() + 1) * this->Entsize;

Can you call updateAllocSize instead? Why do you need to split
addLateEntries btw? The actual number of entries doesn't change when
adding thunks, right? I looks like it should be sufficient to just
populate Entries earlier.

-template <class ELFT> void GnuHashTableSection<ELFT>::finalize() {
+template <class ELFT> void GnuHashTableSection<ELFT>::updateAllocSize() {

unsigned NumHashed = Symbols.size();
NBuckets = calcNBuckets(NumHashed);
MaskWords = calcMaskWords(NumHashed);

Do you need to do this in every updateAllocSize?

If I understand it correctly, this patch is in an odd middle ground. I
think I would prefer to either call the full finalize multiple times if
thunks are added or split finalize is 3 methods:

  • One that is called early and only once. It would do most of the work that finalize currently does.
  • updateAllocSize, which is empty for most sections and just updates whatever can change the size in response to other sections changing size.
  • A real finalize method, that is called only once and can set values that depend on the final address of the sections.

Cheers,
Rafael

ruiu added inline comments.Feb 21 2017, 2:31 PM
ELF/SyntheticSections.h
47

We are probably using the term finalize too much in the linker. It used to be that one class, OutputSection, has a finalizer function that fix the output section layout. Now many classes have finalizers functions and they do different things at different stages. That is hard to read. I'd like to remove all uses of finalize from the linker.

Thanks for the comments, I'll pick this back up when I get back to the office next week. It is helpful to know what the intentions are with respect to naming.

Rebased and updated the patch.

I had a go at Rafael's suggestion of renaming the split of finalizeContent into startLayout, updateAllocSize and endLayout. After moving as much of I could into startLayout we aren't left with much in updateAllocSize or endLayout. With this in mind I've tried a different idea that keeps finalizeContent and instead adds updateAllocSize and postThunkContent.

In Writer.cpp I've called updateAllocSize and postThunkContent only on the sections that need changing.

Rafael's email comments for the benefit of Phabriacator:

+ applySynthetic<ELFT>(Synthetics,
+ [](SyntheticSection<ELFT> *SS) { SS->finalize(); });

For the names, how about:

  • startLayout
  • updateAllocSize
  • endLayout

A more precise set of names would be atLayoutStart, updateAllocSize and
atLayoutEnd, but not sure if it is actually better.

}

template <class ELFT> void Writer<ELFT>::addPredefinedSections() {

Index: ELF/SyntheticSections.h

  • ELF/SyntheticSections.h

+++ ELF/SyntheticSections.h
@@ -47,6 +47,12 @@

virtual ~SyntheticSection() = default;
virtual void writeTo(uint8_t *Buf) = 0;
virtual size_t getSize() const = 0;

+ Finalize is split up into 3 parts to accommodate Thunks, early finalize
+
should do as much as possible including finalizing the section size

"finalizing" is a bit strong since we have updateAllocSize. It should
compute as good a lower bound as possible.

+ // If the size calculation needs to be updated during Thunk creation

incomplete sentence.

+ virtual void updateAllocSize() {}
+ // Runs after all content has been added, must not alter section
size

and all addresses are know?

+template <class ELFT> void DynamicSection<ELFT>::earlyFinalize() {

if (In<ELFT>::RelaDyn->OutSec->Size > 0) {
  bool IsRela = Config->Rela;
  add({IsRela ? DT_RELA : DT_REL, In<ELFT>::RelaDyn});

@@ -941,13 +941,17 @@

    add({DT_MIPS_RLD_MAP, In<ELFT>::MipsRldMap});
}
  • this->OutSec->Entsize = this->Entsize;
  • this->OutSec->Link = this->Link;

Why does setting the link has to be delayed too?

+template <class ELFT> void SymbolTableSection<ELFT>::finalize() {
+ this->OutSec->Link = this->Link = StrTabSec.OutSec->SectionIndex;
+ this->OutSec->Entsize = this->Entsize;
+
+ if (StrTabSec.isDynamic()) {
+ this->OutSec->Info = this->Info = 1;

This can be set early, no?

Cheers,
Rafael

rui314 accepted this revision.Mar 6 2017, 10:48 AM
rui314 added a subscriber: rui314.

This is looking good. LGTM.

This revision is now accepted and ready to land.Mar 6 2017, 10:48 AM
ruiu removed a reviewer: rui314.Mar 6 2017, 10:48 AM
This revision now requires review to proceed.Mar 6 2017, 10:48 AM

Apologies, I'm a bit confused about the status of this patch after the last comment/status change, I'll assume this is still waiting for further comments for now.

grimar edited edge metadata.Mar 7 2017, 2:05 AM

Apologies, I'm a bit confused about the status of this patch after the last comment/status change, I'll assume this is still waiting for further comments for now.

@rui314 and @ruiu are the same person. You still have LGTM from Rui I believe.

ruiu accepted this revision.Mar 7 2017, 9:45 AM

Yes, both are my accounts. I'm using ruiu as the main account though. Sorry for the confusion. It's still LGTM.

This revision is now accepted and ready to land.Mar 7 2017, 9:45 AM
This revision was automatically updated to reflect the committed changes.