Convert PLT table and call site to execute only on AMD64. (#19114)
commitfd61732c833e268a1eaa5a2ebf38716921ab3de1
authorJohan Lorensson <lateralusx.github@gmail.com>
Tue, 10 Mar 2020 11:19:52 +0000 (10 12:19 +0100)
committerGitHub <noreply@github.com>
Tue, 10 Mar 2020 11:19:52 +0000 (10 12:19 +0100)
tree0f88c769b0cd51a364a4cd788bbd4c5f22e95348
parent1ec2c9f7fd2d6692efcf55e915e8b2e696d5b75e
Convert PLT table and call site to execute only on AMD64. (#19114)

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.
mono/mini/aot-compiler.c
mono/mini/aot-runtime.c
mono/mini/aot-runtime.h
mono/mini/mini-llvm.c
mono/mini/mini-trampolines.c
mono/mini/tramp-amd64.c