Convert PLT table and call site to execute only on AMD64. (#33120)
authormonojenkins <jo.shields+jenkins@xamarin.com>
Tue, 10 Mar 2020 11:20:11 +0000 (07:20 -0400)
committerGitHub <noreply@github.com>
Tue, 10 Mar 2020 11:20:11 +0000 (12:20 +0100)
commit170c05ea7adb773138c36120b903422bafaa0fb7
treef6d68adc7b24c7dd1f761bd56c767fbacfcde559
parent86bef04610f1b7ac410746ce8c741150834a6ac2
Convert PLT table and call site to execute only on AMD64. (#33120)

Current implementation embedded critical runtime information directly into PLT slot. It also depends on finding call site in generic trampoline reading call target from instruction stream in order to locate PLT slot in use and then read GOT offset as well as PLT info offset from PLT slot instruction stream.

This is problematic on platforms where code is execute only. Fix changes how the metadata needed in order to correctly patch PLT is discovered. Instead of depending on reading instruction stream, it is loaded into RAX (free to be used when calling through PLT on AMD64) in PLT slot before jmp takes place that moves control over to generic trampoline.

The PLT slot is the only place where we have access to both GOT index (used in jmp), PLT info offset (currently embedded after jmp instruction) and emitted PLT slot index. Since PLT slot index can be used to recover GOT offset used by PLT slot as well as PLT info offset at runtime, PLT slot index will be emitted into instruction stream and loaded into RAX (prepared to be configurable to other reg if needed) before doing the jump over to generic trampoline. Size of emitted imm constant is optimized based on number of total PLT slots used in image, meaning that 1, 2, or 4 bytes could be used to store PLT slot index as an imm constant in instruction stream. The additional jmp should have minimal to no performance overhead since it should complete within 1 cycle, reading imm constant from instruction stream that shouldn’t incur additional cache misses and sine there is no data dependency between mov and jmp, there should be options to pipeline both instructions.  Since mov has smaller latency than indirect jmp, mov should be complete once control gets into the generic tramp (if pipelined).

In order to resolve plt info offset at runtime, needed information is now emitted as part of got_info_offsets, increase table with 4 bytes/plt slot, same size currently emitted into the PLT slot instruction stream.

Code size of PLT slot is currently 10 bytes (6 bytes jmp and 4 byte PLT info offset). Since PLT info offset has been moved into got_info_offset table, size of PLT slot will be 2, 4, 5 byte mov (depending on needed imm size) and 6 bytes jmp instruction. As an example, mscorlib can emit all its PLT slots using 2 byte imm constant, meaning that the PLT slot will still be 10 bytes, but since PLT info offset of 4 bytes is moved into got_info_offset, total image increase will be 4 bytes/PLT slot. This is however still cheaper than alternatives that would burn 1 trampoline/PLT slot (at least 10 additional bytes/slot) or setup lookup tables in image or calculate more info at runtime, all-consuming more memory in total.

Note, using this approach is optional and runtime needs to be built using MONO_ARCH_CODE_EXEC_ONLY in order to enable it since it’s only an opt in features on platforms that can't read from instructions stream.

Current implementation is AMD64 only, but same pattern could be applied to other architectures when needed.

Co-authored-by: lateralusX <lateralusX@users.noreply.github.com>
src/mono/mono/mini/aot-compiler.c
src/mono/mono/mini/aot-runtime.c
src/mono/mono/mini/aot-runtime.h
src/mono/mono/mini/mini-llvm.c
src/mono/mono/mini/mini-trampolines.c
src/mono/mono/mini/tramp-amd64.c