4

Raw symbol names in inline assembly

 7 months ago
source link: https://maskray.me/blog/2024-01-30-raw-symbol-names-in-inline-assembly
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Raw symbol names in inline assembly

GCC's aarch64 port, from the beginning (2012), supports the constraint "S" to reference a symbol or label. This is nice, as you can create an artificial reference for linker garbage collection, defining sections to hold symbol addresses, or even enabling more creative applications.

namespace ns { extern int a[4]; }
void fun();
void foo() {
label:
asm(".pushsection .xxx,\"aw\"; .dc.a %0; .popsection" :: "S"(&ns::a[3]));
asm(".reloc ., BFD_RELOC_NONE, %0" :: "S"(fun));
asm("// %0" :: "S"(&&label));
}
// Materialize the symbol address manually
asm("adrp %0, %1\nadd %0, %0, :lo12:%1" : "=r"(ret) : "S"(&var));

Clang 7 also implemented "S".

Using the generic r or m constraint in such cases would instruct GCC to generate instructions to compute the address, which can be wasteful if the materialized address isn't actually needed.

// aarch64
asm("// %0" :: "r"(fun));
// adrp x0, _GLOBAL_OFFSET_TABLE_
// ldr x0, [x0, #:gotpage_lo15:_Z3funv]

C++ templates can make this easier to use.

template <class T, T &x>
void ref() { asm (".reloc ., BFD_RELOC_NONE, %0" :: "S"(x)); }
void use() { ref<decltype(ns::a), ns::a>(); }

RISC-V

GCC's RISC-V port, from the beginning (2017), supports the constraint "S" for a similar purpose as well. However, the operand cannot be a preemptible symbol (non-local symbol in GCC's term). I have just filed [PATCH] RISC-V: Allow constraint "S" even if the symbol does not bind locally to relax the condition.

I implemented the constraint "S" in Clang 14.

Unfortunately, there had been no suitable constraint for x86 for a long time. We can use the constraint "i" with the modifier "p" to print raw symbol name without syntax-specific prefixes, but it does not work when:

  • -mcmodel=large is used
  • or the symbol is preemptible (similar to RISC-V's "S" problem)
void foo() {
label:
asm("// %p0" :: "i"(foo)); // Does not work if foo is preemptible
asm("// %p0" :: "i"(&&label));
}

I filed the feature request for a new constraint in May 2022 and eventually went ahead and implement it by myself. The patch landed today, catching up the GCC 14 release. I have also implemented "Ws" for Clang 18.1.

BTW, you can also the modifier "c".

Require a constant operand and print the constant expression with no punctuation.

I think having such a long list of modifiers is unfortunate.

Applications

Linux kernel's config JUMP_LABEL showcases the practical benefits of accessing raw symbol names in inline assembly. Actually, this feature likely drew me down this rabbit hole about raw symbol names a few years ago.

Many ports use the constraint "i", which is more or less a hack.

// gcc/common.md
(define_constraint "i"
"Matches a general integer constant."
(and (match_test "CONSTANT_P (op)")
(match_test "!flag_pic || LEGITIMATE_PIC_OPERAND_P (op)")))

In the non-PIC mode, "i" does works with a constant, symbol reference, or label reference. However, in the PIC mode, "i" on a symbol reference is rejected by certain GCC ports (e.g. aarch64). I went ahead and sent an arm64 patch:)

Share Comments


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK