Skip to content

Commit 1e7e478

Browse files
jpoimboeingomolnar
authored andcommitted
x86/static_call: Add inline static call implementation for x86-64
Add the inline static call implementation for x86-64. The generated code is identical to the out-of-line case, except we move the trampoline into it's own section. Objtool uses the trampoline naming convention to detect all the call sites. It then annotates those call sites in the .static_call_sites section. During boot (and module init), the call sites are patched to call directly into the destination function. The temporary trampoline is then no longer used. [peterz: merged trampolines, put trampoline in section] Signed-off-by: Josh Poimboeuf <jpoimboe@redhat.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Ingo Molnar <mingo@kernel.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Link: https://lore.kernel.org/r/20200818135804.864271425@infradead.org
1 parent e6d6c07 commit 1e7e478

13 files changed

Lines changed: 193 additions & 9 deletions

File tree

arch/x86/Kconfig

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ config X86
216216
select HAVE_STACKPROTECTOR if CC_HAS_SANE_STACKPROTECTOR
217217
select HAVE_STACK_VALIDATION if X86_64
218218
select HAVE_STATIC_CALL
219+
select HAVE_STATIC_CALL_INLINE if HAVE_STACK_VALIDATION
219220
select HAVE_RSEQ
220221
select HAVE_SYSCALL_TRACEPOINTS
221222
select HAVE_UNSTABLE_SCHED_CLOCK
@@ -231,6 +232,7 @@ config X86
231232
select RTC_MC146818_LIB
232233
select SPARSE_IRQ
233234
select SRCU
235+
select STACK_VALIDATION if HAVE_STACK_VALIDATION && (HAVE_STATIC_CALL_INLINE || RETPOLINE)
234236
select SYSCTL_EXCEPTION_TRACE
235237
select THREAD_INFO_IN_TASK
236238
select USER_STACKTRACE_SUPPORT
@@ -452,7 +454,6 @@ config GOLDFISH
452454
config RETPOLINE
453455
bool "Avoid speculative indirect branches in kernel"
454456
default y
455-
select STACK_VALIDATION if HAVE_STACK_VALIDATION
456457
help
457458
Compile kernel with the retpoline compiler options to guard against
458459
kernel-to-user data leaks by avoiding speculative indirect

arch/x86/include/asm/static_call.h

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,23 @@
55
#include <asm/text-patching.h>
66

77
/*
8+
* For CONFIG_HAVE_STATIC_CALL_INLINE, this is a temporary trampoline which
9+
* uses the current value of the key->func pointer to do an indirect jump to
10+
* the function. This trampoline is only used during boot, before the call
11+
* sites get patched by static_call_update(). The name of this trampoline has
12+
* a magical aspect: objtool uses it to find static call sites so it can create
13+
* the .static_call_sites section.
14+
*
815
* For CONFIG_HAVE_STATIC_CALL, this is a permanent trampoline which
916
* does a direct jump to the function. The direct jump gets patched by
1017
* static_call_update().
18+
*
19+
* Having the trampoline in a special section forces GCC to emit a JMP.d32 when
20+
* it does tail-call optimization on the call; since you cannot compute the
21+
* relative displacement across sections.
1122
*/
1223
#define ARCH_DEFINE_STATIC_CALL_TRAMP(name, func) \
13-
asm(".pushsection .text, \"ax\" \n" \
24+
asm(".pushsection .static_call.text, \"ax\" \n" \
1425
".align 4 \n" \
1526
".globl " STATIC_CALL_TRAMP_STR(name) " \n" \
1627
STATIC_CALL_TRAMP_STR(name) ": \n" \

arch/x86/kernel/static_call.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ void arch_static_call_transform(void *site, void *tramp, void *func)
2626
if (tramp)
2727
__static_call_transform(tramp, JMP32_INSN_OPCODE, func);
2828

29+
if (IS_ENABLED(CONFIG_HAVE_STATIC_CALL_INLINE) && site)
30+
__static_call_transform(site, CALL_INSN_OPCODE, func);
31+
2932
mutex_unlock(&text_mutex);
3033
}
3134
EXPORT_SYMBOL_GPL(arch_static_call_transform);

arch/x86/kernel/vmlinux.lds.S

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ SECTIONS
136136
ENTRY_TEXT
137137
ALIGN_ENTRY_TEXT_END
138138
SOFTIRQENTRY_TEXT
139+
STATIC_CALL_TEXT
139140
*(.fixup)
140141
*(.gnu.warning)
141142

include/asm-generic/vmlinux.lds.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -642,6 +642,12 @@
642642
*(.softirqentry.text) \
643643
__softirqentry_text_end = .;
644644

645+
#define STATIC_CALL_TEXT \
646+
ALIGN_FUNCTION(); \
647+
__static_call_text_start = .; \
648+
*(.static_call.text) \
649+
__static_call_text_end = .;
650+
645651
/* Section used for early init (in .S files) */
646652
#define HEAD_TEXT KEEP(*(.head.text))
647653

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
#ifndef _STATIC_CALL_TYPES_H
3+
#define _STATIC_CALL_TYPES_H
4+
5+
#include <linux/types.h>
6+
#include <linux/stringify.h>
7+
8+
#define STATIC_CALL_KEY_PREFIX __SCK__
9+
#define STATIC_CALL_KEY_PREFIX_STR __stringify(STATIC_CALL_KEY_PREFIX)
10+
#define STATIC_CALL_KEY_PREFIX_LEN (sizeof(STATIC_CALL_KEY_PREFIX_STR) - 1)
11+
#define STATIC_CALL_KEY(name) __PASTE(STATIC_CALL_KEY_PREFIX, name)
12+
13+
#define STATIC_CALL_TRAMP_PREFIX __SCT__
14+
#define STATIC_CALL_TRAMP_PREFIX_STR __stringify(STATIC_CALL_TRAMP_PREFIX)
15+
#define STATIC_CALL_TRAMP_PREFIX_LEN (sizeof(STATIC_CALL_TRAMP_PREFIX_STR) - 1)
16+
#define STATIC_CALL_TRAMP(name) __PASTE(STATIC_CALL_TRAMP_PREFIX, name)
17+
#define STATIC_CALL_TRAMP_STR(name) __stringify(STATIC_CALL_TRAMP(name))
18+
19+
/*
20+
* The static call site table needs to be created by external tooling (objtool
21+
* or a compiler plugin).
22+
*/
23+
struct static_call_site {
24+
s32 addr;
25+
s32 key;
26+
};
27+
28+
#endif /* _STATIC_CALL_TYPES_H */

tools/objtool/check.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#include <linux/hashtable.h>
1818
#include <linux/kernel.h>
19+
#include <linux/static_call_types.h>
1920

2021
#define FAKE_JUMP_OFFSET -1
2122

@@ -433,6 +434,103 @@ static int add_dead_ends(struct objtool_file *file)
433434
return 0;
434435
}
435436

437+
static int create_static_call_sections(struct objtool_file *file)
438+
{
439+
struct section *sec, *reloc_sec;
440+
struct reloc *reloc;
441+
struct static_call_site *site;
442+
struct instruction *insn;
443+
struct symbol *key_sym;
444+
char *key_name, *tmp;
445+
int idx;
446+
447+
sec = find_section_by_name(file->elf, ".static_call_sites");
448+
if (sec) {
449+
INIT_LIST_HEAD(&file->static_call_list);
450+
WARN("file already has .static_call_sites section, skipping");
451+
return 0;
452+
}
453+
454+
if (list_empty(&file->static_call_list))
455+
return 0;
456+
457+
idx = 0;
458+
list_for_each_entry(insn, &file->static_call_list, static_call_node)
459+
idx++;
460+
461+
sec = elf_create_section(file->elf, ".static_call_sites", SHF_WRITE,
462+
sizeof(struct static_call_site), idx);
463+
if (!sec)
464+
return -1;
465+
466+
reloc_sec = elf_create_reloc_section(file->elf, sec, SHT_RELA);
467+
if (!reloc_sec)
468+
return -1;
469+
470+
idx = 0;
471+
list_for_each_entry(insn, &file->static_call_list, static_call_node) {
472+
473+
site = (struct static_call_site *)sec->data->d_buf + idx;
474+
memset(site, 0, sizeof(struct static_call_site));
475+
476+
/* populate reloc for 'addr' */
477+
reloc = malloc(sizeof(*reloc));
478+
if (!reloc) {
479+
perror("malloc");
480+
return -1;
481+
}
482+
memset(reloc, 0, sizeof(*reloc));
483+
reloc->sym = insn->sec->sym;
484+
reloc->addend = insn->offset;
485+
reloc->type = R_X86_64_PC32;
486+
reloc->offset = idx * sizeof(struct static_call_site);
487+
reloc->sec = reloc_sec;
488+
elf_add_reloc(file->elf, reloc);
489+
490+
/* find key symbol */
491+
key_name = strdup(insn->call_dest->name);
492+
if (!key_name) {
493+
perror("strdup");
494+
return -1;
495+
}
496+
if (strncmp(key_name, STATIC_CALL_TRAMP_PREFIX_STR,
497+
STATIC_CALL_TRAMP_PREFIX_LEN)) {
498+
WARN("static_call: trampoline name malformed: %s", key_name);
499+
return -1;
500+
}
501+
tmp = key_name + STATIC_CALL_TRAMP_PREFIX_LEN - STATIC_CALL_KEY_PREFIX_LEN;
502+
memcpy(tmp, STATIC_CALL_KEY_PREFIX_STR, STATIC_CALL_KEY_PREFIX_LEN);
503+
504+
key_sym = find_symbol_by_name(file->elf, tmp);
505+
if (!key_sym) {
506+
WARN("static_call: can't find static_call_key symbol: %s", tmp);
507+
return -1;
508+
}
509+
free(key_name);
510+
511+
/* populate reloc for 'key' */
512+
reloc = malloc(sizeof(*reloc));
513+
if (!reloc) {
514+
perror("malloc");
515+
return -1;
516+
}
517+
memset(reloc, 0, sizeof(*reloc));
518+
reloc->sym = key_sym;
519+
reloc->addend = 0;
520+
reloc->type = R_X86_64_PC32;
521+
reloc->offset = idx * sizeof(struct static_call_site) + 4;
522+
reloc->sec = reloc_sec;
523+
elf_add_reloc(file->elf, reloc);
524+
525+
idx++;
526+
}
527+
528+
if (elf_rebuild_reloc_section(file->elf, reloc_sec))
529+
return -1;
530+
531+
return 0;
532+
}
533+
436534
/*
437535
* Warnings shouldn't be reported for ignored functions.
438536
*/
@@ -1522,6 +1620,23 @@ static int read_intra_function_calls(struct objtool_file *file)
15221620
return 0;
15231621
}
15241622

1623+
static int read_static_call_tramps(struct objtool_file *file)
1624+
{
1625+
struct section *sec;
1626+
struct symbol *func;
1627+
1628+
for_each_sec(file, sec) {
1629+
list_for_each_entry(func, &sec->symbol_list, list) {
1630+
if (func->bind == STB_GLOBAL &&
1631+
!strncmp(func->name, STATIC_CALL_TRAMP_PREFIX_STR,
1632+
strlen(STATIC_CALL_TRAMP_PREFIX_STR)))
1633+
func->static_call_tramp = true;
1634+
}
1635+
}
1636+
1637+
return 0;
1638+
}
1639+
15251640
static void mark_rodata(struct objtool_file *file)
15261641
{
15271642
struct section *sec;
@@ -1601,6 +1716,10 @@ static int decode_sections(struct objtool_file *file)
16011716
if (ret)
16021717
return ret;
16031718

1719+
ret = read_static_call_tramps(file);
1720+
if (ret)
1721+
return ret;
1722+
16041723
return 0;
16051724
}
16061725

@@ -2432,6 +2551,11 @@ static int validate_branch(struct objtool_file *file, struct symbol *func,
24322551
if (dead_end_function(file, insn->call_dest))
24332552
return 0;
24342553

2554+
if (insn->type == INSN_CALL && insn->call_dest->static_call_tramp) {
2555+
list_add_tail(&insn->static_call_node,
2556+
&file->static_call_list);
2557+
}
2558+
24352559
break;
24362560

24372561
case INSN_JUMP_CONDITIONAL:
@@ -2791,6 +2915,7 @@ int check(const char *_objname, bool orc)
27912915

27922916
INIT_LIST_HEAD(&file.insn_list);
27932917
hash_init(file.insn_hash);
2918+
INIT_LIST_HEAD(&file.static_call_list);
27942919
file.c_file = !vmlinux && find_section_by_name(file.elf, ".comment");
27952920
file.ignore_unreachables = no_unreachable;
27962921
file.hints = false;
@@ -2838,6 +2963,11 @@ int check(const char *_objname, bool orc)
28382963
warnings += ret;
28392964
}
28402965

2966+
ret = create_static_call_sections(&file);
2967+
if (ret < 0)
2968+
goto out;
2969+
warnings += ret;
2970+
28412971
if (orc) {
28422972
ret = create_orc(&file);
28432973
if (ret < 0)

tools/objtool/check.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ struct insn_state {
2222
struct instruction {
2323
struct list_head list;
2424
struct hlist_node hash;
25+
struct list_head static_call_node;
2526
struct section *sec;
2627
unsigned long offset;
2728
unsigned int len;

tools/objtool/elf.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ struct elf *elf_open_read(const char *name, int flags)
652652
}
653653

654654
struct section *elf_create_section(struct elf *elf, const char *name,
655-
size_t entsize, int nr)
655+
unsigned int sh_flags, size_t entsize, int nr)
656656
{
657657
struct section *sec, *shstrtab;
658658
size_t size = entsize * nr;
@@ -712,7 +712,7 @@ struct section *elf_create_section(struct elf *elf, const char *name,
712712
sec->sh.sh_entsize = entsize;
713713
sec->sh.sh_type = SHT_PROGBITS;
714714
sec->sh.sh_addralign = 1;
715-
sec->sh.sh_flags = SHF_ALLOC;
715+
sec->sh.sh_flags = SHF_ALLOC | sh_flags;
716716

717717

718718
/* Add section name to .shstrtab (or .strtab for Clang) */
@@ -767,7 +767,7 @@ static struct section *elf_create_rel_reloc_section(struct elf *elf, struct sect
767767
strcpy(relocname, ".rel");
768768
strcat(relocname, base->name);
769769

770-
sec = elf_create_section(elf, relocname, sizeof(GElf_Rel), 0);
770+
sec = elf_create_section(elf, relocname, 0, sizeof(GElf_Rel), 0);
771771
free(relocname);
772772
if (!sec)
773773
return NULL;
@@ -797,7 +797,7 @@ static struct section *elf_create_rela_reloc_section(struct elf *elf, struct sec
797797
strcpy(relocname, ".rela");
798798
strcat(relocname, base->name);
799799

800-
sec = elf_create_section(elf, relocname, sizeof(GElf_Rela), 0);
800+
sec = elf_create_section(elf, relocname, 0, sizeof(GElf_Rela), 0);
801801
free(relocname);
802802
if (!sec)
803803
return NULL;

tools/objtool/elf.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ struct symbol {
5656
unsigned int len;
5757
struct symbol *pfunc, *cfunc, *alias;
5858
bool uaccess_safe;
59+
bool static_call_tramp;
5960
};
6061

6162
struct reloc {
@@ -120,7 +121,7 @@ static inline u32 reloc_hash(struct reloc *reloc)
120121
}
121122

122123
struct elf *elf_open_read(const char *name, int flags);
123-
struct section *elf_create_section(struct elf *elf, const char *name, size_t entsize, int nr);
124+
struct section *elf_create_section(struct elf *elf, const char *name, unsigned int sh_flags, size_t entsize, int nr);
124125
struct section *elf_create_reloc_section(struct elf *elf, struct section *base, int reltype);
125126
void elf_add_reloc(struct elf *elf, struct reloc *reloc);
126127
int elf_write_insn(struct elf *elf, struct section *sec,

0 commit comments

Comments
 (0)