Description
Bugzilla Link | 45268 |
Version | trunk |
OS | Linux |
Blocks | #44512 |
Reporter | LLVM Bugzilla Contributor |
CC | @zygoloid |
Extended Description
Patches (for example https://reviews.llvm.org/D76458#) introduce insertion of some instructions (for example, LFENCE, which is a SSE2 instruction) not available for all CPUs. It may cause problems:
- developers can enable these flags themselves;
- developers of binary dependencies can enable the flags themselves;
- this may be a result of developers of build tooling enabling the flags by default.
This will essentially cause all the developers to drop support of the hardware without these instructions. Not necessarily because they want, but because they have to.
For example, supporting legacy hardware may require them have separate builds for it or even building all the dependencies themselves, that may be inaceptible.
The solution of course is enabling instructions opportunistically.
- the compiler writes additional data into a special section, containing a table. A record in it has the following structure
enum class InstructionNeeded: uint64_t{
....
};
struct CPUFeature{
bool required: 1; // describes if the code by default requires the feature enabled
uint16_t id: 15; // feature ID
InstructionNeeded instruction; // instructiins needed for the feature
uint32_t pointers; // offset of a data structure describing pkaces that must be patched
};
Address collection contains a set of addresses to be patched. No patches are stored, they are computed by the patcher.
Either runtime or dynamic linker check this section, and do the necessary patches.
- A linker/runtime (let's further call it "linker") reads the CPUID and detects the features of the CPU.
- The linker reads the table and determines which patches it should apply. There are 2 kinds of patches, upgrade ones and downgrade ones.
InstructionNeeded cpuFeatures = getFromCPUID();
uint8_t featureSupported = ((cpuFeatures & feature.instructions) == feature.instructions);
enum class Action: uint8_t{
noDowngradeNeeded = 0b00,
downgrade = 0b01,
upgrade = 0b10,
noUpgradeNeeded = 0b11
}
Action actionNeeded = static_cast((featureSupported<<1) | feature.required);
switch(actionNeeded){
case Action::downgrade:
doDowngrade(feature); # replace newer instructions with older ones
break;
case Action::upgrade:
doUpgrade(feature); # replace older instructions with newer ones.
break;
}
-
When applying a patch the loader reads bytes by the offset, parses them into instructions, computes the needed replacements and writes them into the memory.
-
There can also be an API, allowing trigger applying the patches programmatically. This for example can allow applications to enable/disable protections based on values in their config files.