Skip to contentSkip to conversation

PROOF OF CONCEPT: Dynamic Functions (jump functions)

RFC PATCH Superseded Steven Rostedt 9 participants 43 messages Oct 6, 2018 – Oct 11, 2018

Subsystems (6)

v1 of 2 Superseded Latest is v2 →

lore
Share & tools
lore

Steven Rostedt

author maintainer
#0 lore raw

cover PROOF OF CONCEPT: Dynamic Functions (jump functions)

This is just a Proof Of Concept (POC), as I have done some "no no"s like having x86 asm code in generic code paths, and it also needs a way of working when an arch does not support this feature. Not to mention, I didn't add proper change logs (that will come later).

Background:

During David Woodhouse's presentation on Spectre and Meltdown at Kernel
Recipes he talked about how retpolines are implemented. I haven't had time to look at the details so I haven't given it much thought. But as he demonstrated that it has a measurable overhead on indirect calls, I realized how much this can affect tracepoints. Tracepoints are implemented with indirect calls, where the code iterates over an array calling each callback that has registered with the tracepoint.

I ran a test to see how much overhead this entails.

With RETPOLINE disabled (CONFIG_RETPOLINE=n):

# trace-cmd start -e all
# perf stat -r 10 /work/c/hackbench 50
Time: 29.369
Time: 28.998
Time: 28.816
Time: 28.734
Time: 29.034
Time: 28.631
Time: 28.594
Time: 28.762
Time: 28.915
Time: 28.741

Performance counter stats for '/work/c/hackbench 50' (10 runs):

     232926.801609      task-clock (msec)         #    7.465 CPUs utilized            ( +-  0.26% )
         3,175,526      context-switches          #    0.014 M/sec                    ( +-  0.50% )
           394,920      cpu-migrations            #    0.002 M/sec                    ( +-  1.71% )
            44,273      page-faults               #    0.190 K/sec                    ( +-  1.06% )
   859,904,212,284      cycles                    #    3.692 GHz                      ( +-  0.26% )
   526,010,328,375      stalled-cycles-frontend   #   61.17% frontend cycles idle     ( +-  0.26% )
   799,414,387,443      instructions              #    0.93  insn per cycle
                                                  #    0.66  stalled cycles per insn  ( +-  0.25% )
   157,516,396,866      branches                  #  676.248 M/sec                    ( +-  0.25% )
       445,888,666      branch-misses             #    0.28% of all branches          ( +-  0.19% )

31.201263687 seconds time elapsed ( +- 0.24% )

With RETPOLINE enabled (CONFIG_RETPOLINE=y)

# trace-cmd start -e all
# perf stat -r 10 /work/c/hackbench 50
Time: 31.087
Time: 31.180
Time: 31.250
Time: 30.905
Time: 31.024
Time: 32.056
Time: 31.312
Time: 31.409
Time: 31.451
Time: 31.275

Performance counter stats for '/work/c/hackbench 50' (10 runs):

     252893.216212      task-clock (msec)         #    7.444 CPUs utilized            ( +-  0.31% )
         3,218,524      context-switches          #    0.013 M/sec                    ( +-  0.45% )
           427,129      cpu-migrations            #    0.002 M/sec                    ( +-  1.52% )
            43,666      page-faults               #    0.173 K/sec                    ( +-  0.92% )
   933,615,337,142      cycles                    #    3.692 GHz                      ( +-  0.31% )
   593,141,521,286      stalled-cycles-frontend   #   63.53% frontend cycles idle     ( +-  0.32% )
   806,848,677,318      instructions              #    0.86  insn per cycle
                                                  #    0.74  stalled cycles per insn  ( +-  0.30% )
   161,289,933,342      branches                  #  637.779 M/sec                    ( +-  0.29% )
     2,070,719,044      branch-misses             #    1.28% of all branches          ( +-  0.25% )

33.971942318 seconds time elapsed ( +- 0.28% )

What the above represents, is running "hackbench 50" with all trace events enabled, went from: 31.201263687 to: 33.971942318 to perform, which is an 8.9% increase!

So I thought about how to solve this, and came up with "jump_functions". These are similar to jump_labels, but instead of having a static branch, we would have a dynamic function. A function "dynfunc_X()" that can be assigned any other function, just as if it was a variable, and have it call the new function. Talking with other kernel developers at Kernel Recipes, I was told that this feature would be useful for other subsystems in the kernel and not just for tracing.

The first attempt created a call in inline assembly, and did macro tricks to create the parameters, but this was overly complex, especially when one of the trace events has 12 parameters!

Then I decided to simplify it to have the dynfunc_X() call a trampoline, that does a direct jump. It's similar to what a retpoline does, but a retpoline does an indirect jump. A direct jump is much more efficient.

When changing what function a dynamic function should call, text_poke_bp() is used to modify the trampoline to call the new function.

The first "no change log" patch implements the dynamic function (poorly, as its just a proof of concept), and the second "no change log" patch implements a way that tracepoints can take advantage of it.

The tracepoints creates a "default" function that does the iteration over the tracepoint array like it currently does. But if only a single callback is attached to the tracepoint (the most common case), it changes the dynamic function to call the callback directly, without any iteration over the list.

After implementing this, running the above test produced:

# trace-cmd start -e all
# perf stat -r 10 /work/c/hackbench 50
Time: 29.927
Time: 29.504
Time: 29.761
Time: 29.693
Time: 29.430
Time: 29.999
Time: 29.389
Time: 29.404
Time: 29.871
Time: 29.335

Performance counter stats for '/work/c/hackbench 50' (10 runs):

     239377.553785      task-clock (msec)         #    7.447 CPUs utilized            ( +-  0.27% )
         3,203,640      context-switches          #    0.013 M/sec                    ( +-  0.36% )
           417,511      cpu-migrations            #    0.002 M/sec                    ( +-  1.56% )
            43,462      page-faults               #    0.182 K/sec                    ( +-  0.98% )
   883,720,553,554      cycles                    #    3.692 GHz                      ( +-  0.27% )
   553,115,449,444      stalled-cycles-frontend   #   62.59% frontend cycles idle     ( +-  0.27% )
   792,603,930,472      instructions              #    0.90  insn per cycle
                                                  #    0.70  stalled cycles per insn  ( +-  0.27% )
   159,390,986,499      branches                  #  665.856 M/sec                    ( +-  0.27% )
     1,310,355,667      branch-misses             #    0.82% of all branches          ( +-  0.18% )

32.146081513 seconds time elapsed ( +- 0.25% )

We didn't get back 100% of performance. I didn't expect to, as retpolines will cause overhead in other areas than just tracing. But we went from 33.971942318 to 32.146081513. Instead of being 8.9% slower with retpoline enabled, we are now just 3% slower.

I tried this patch set without RETPOLINE and had this:

# trace-cmd start -e all
# perf stat -r 10 /work/c/hackbench 50
Time: 28.830
Time: 28.457
Time: 29.078
Time: 28.606
Time: 28.377
Time: 28.629
Time: 28.642
Time: 29.005
Time: 28.513
Time: 28.357

Performance counter stats for '/work/c/hackbench 50' (10 runs):

     231452.110483      task-clock (msec)         #    7.466 CPUs utilized            ( +-  0.28% )
         3,181,305      context-switches          #    0.014 M/sec                    ( +-  0.44% )
           393,496      cpu-migrations            #    0.002 M/sec                    ( +-  1.20% )
            43,673      page-faults               #    0.189 K/sec                    ( +-  0.61% )
   854,481,304,821      cycles                    #    3.692 GHz                      ( +-  0.28% )
   528,175,627,905      stalled-cycles-frontend   #   61.81% frontend cycles idle     ( +-  0.28% )
   787,765,717,278      instructions              #    0.92  insn per cycle
                                                  #    0.67  stalled cycles per insn  ( +-  0.28% )
   157,169,268,775      branches                  #  679.057 M/sec                    ( +-  0.27% )
       366,443,397      branch-misses             #    0.23% of all branches          ( +-  0.15% )
      31.002540109 seconds time elapsed 

Which went from 31.201263687 to 31.002540109 which is a 0.6% speed up. Not great, but not bad either.

Notice, there's also test code that creates some files in the debugfs directory. There's files called: func0, func1, func2 and func3, where each has a dynamic function associated to it with the number of parameters that is the same as the number in the name of the file. There's three functions that each of these dynamic functions can be change to, and echoing in "0", "1" or "2" will update the dynamic function. Reading from the function causes the called functions to printk() to the console to see how it worked.

Now what?

OK, for the TODO, if nobody has any issues with this, I was going to hand this off to Matt Helsley to make this into something thats actually presentable for inclusion.

1) We need to move the x86 specific code into x86 specific locations.

2) We need to have this work without doing the dynamic updates (for archs that don't have this implemented). Basically, the dynamic function is going to probably be a macro with a function pointer that does an indirect jump to the code that is assigned to the dynamic function.

3) Write up proper change logs ;-)

And I'm sure there's more to do.

Enjoy,

-- Steve

git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace.git
ftrace/jump_function

Head SHA1: 1a2e530e7534d82b95eaa9ddc5218c5652a60d49

Steven Rostedt (VMware) (2):
jump_function: Addition of new feature "jump_function"
tracepoints: Implement it with dynamic functions

----

 include/asm-generic/vmlinux.lds.h |   4 +
 include/linux/jump_function.h     |  93 ++++++++++
 include/linux/tracepoint-defs.h   |   3 +
 include/linux/tracepoint.h        |  65 ++++---
 include/trace/define_trace.h      |  14 +-
 kernel/Makefile                   |   2 +-
 kernel/jump_function.c            | 368 ++++++++++++++++++++++++++++++++++++++
 kernel/tracepoint.c               |  29 ++-
 8 files changed, 545 insertions(+), 33 deletions(-)
 create mode 100644 include/linux/jump_function.h
 create mode 100644 kernel/jump_function.c
author maintainer
#1 lore raw

RFC PATCH 1/2 jump_function: Addition of new feature "jump_function"

From: "Steven Rostedt (VMware)" <[email protected]>

---
 include/asm-generic/vmlinux.lds.h |   4 +
 include/linux/jump_function.h     |  93 ++++++++
 kernel/Makefile                   |   2 +-
 kernel/jump_function.c            | 368 ++++++++++++++++++++++++++++++
 4 files changed, 466 insertions(+), 1 deletion(-)
 create mode 100644 include/linux/jump_function.h
 create mode 100644 kernel/jump_function.c
include/asm-generic/vmlinux.lds.h +4 -0
index 7b75ff6e2fce..0e205069ff36 100644
@@ -257,6 +257,10 @@
 	__start___jump_table = .;					\
 	KEEP(*(__jump_table))                                           \
 	__stop___jump_table = .;					\
+	. = ALIGN(8);                                                   \
+	__start___dynfunc_table = .;					\
+	KEEP(*(__dynfunc_table))					\
+	__stop___dynfunc_table = .;					\
 	. = ALIGN(8);							\
 	__start___verbose = .;						\
 	KEEP(*(__verbose))                                              \
include/linux/jump_function.h new file +93 -0

93 lines not on this page · raw message

kernel/Makefile +1 -1
index 7a63d567fdb5..c647c7f15318 100644
@@ -10,7 +10,7 @@ obj-y     = fork.o exec_domain.o panic.o \
 	    extable.o params.o \
 	    kthread.o sys_ni.o nsproxy.o \
 	    notifier.o ksysfs.o cred.o reboot.o \
-	    async.o range.o smpboot.o ucount.o
+	    async.o range.o smpboot.o ucount.o jump_function.o
 
 obj-$(CONFIG_MODULES) += kmod.o
 obj-$(CONFIG_MULTIUSER) += groups.o
kernel/jump_function.c new file +368 -0

368 lines not on this page · raw message

Signature · 1 line
2.19.0
author maintainer
#2 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 18 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:

From: "Steven Rostedt (VMware)" <[email protected]>

---
 include/asm-generic/vmlinux.lds.h |   4 +
 include/linux/jump_function.h     |  93 ++++++++
 kernel/Makefile                   |   2 +-
 kernel/jump_function.c            | 368 ++++++++++++++++++++++++++++++
 4 files changed, 466 insertions(+), 1 deletion(-)
 create mode 100644 include/linux/jump_function.h
 create mode 100644 kernel/jump_function.c
include/asm-generic/vmlinux.lds.h +0 -0
index 7b75ff6e2fce..0e205069ff36 100644

include/asm-generic/vmlinux.lds.h line 266

Quoted 10 lines
include/asm-generic/vmlinux.lds.h +4 -0
@@ -257,6 +257,10 @@
 	__start___jump_table = .;					\
 	KEEP(*(__jump_table))                                           \
 	__stop___jump_table = .;					\
+	. = ALIGN(8);                                                   \
+	__start___dynfunc_table = .;					\
+	KEEP(*(__dynfunc_table))					\
+	__stop___dynfunc_table = .;					\
 	. = ALIGN(8);							\
 	__start___verbose = .;						\
 	KEEP(*(__verbose))                                              \

BAH, this is leftover from my first attempt. It's not needed and can be nuked.

-- Steve

author maintainer
#3 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Steven Rostedt

include/linux/jump_function.h line 19

Quoted 8 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:
include/linux/jump_function.h new file +8 -0
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )
+

Note, the assembler can easily put in a two byte jump here. The .balign was suppose to also have some padding (nop) incase that happens. It's fine, because we can just replace it with a 5 byte jump, as long as we have 3 bytes afterward if it is a two byte jump.

-- Steve

author maintainer
#4 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Steven Rostedt

include/linux/jump_function.h line 31

Quoted 8 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:
include/linux/jump_function.h new file +8 -0
+#ifndef PARAMS
+#define PARAMS(x...) x
+#endif
+
+#ifndef ARGS
+#define ARGS(x...) x
+#endif
+

This is also leftover from the first attempt and can be nuked.

Yeah, yeah, I should have reviewed my patches better before sending them. But I was so excited that I got it working I just wanted to share the joy!

-- Steve

author maintainer
#5 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 14 lines · On Fri, 5 Oct 2018 22:03:51 -0400 Steven Rostedt <[email protected]> wrote:

include/linux/jump_function.h line 31

Quoted 8 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:
include/linux/jump_function.h new file +8 -0
+#ifndef PARAMS
+#define PARAMS(x...) x
+#endif
+
+#ifndef ARGS
+#define ARGS(x...) x
+#endif
+  

This is also leftover from the first attempt and can be nuked.

I take this back. It is still needed.

-- Steve

maintainer
#6 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Steven Rostedt

include/linux/jump_function.h line 18

Quoted 7 lines · Steven Rostedt
include/linux/jump_function.h new file +7 -0
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )

Bah, what is it with you people and trampolines. Why can't we, just like jump_label, patch the call directly?

The whole call+jmp thing is silly, don't do that. It just wrecks I$ and is slower for no real reason afaict.

Steve, also see:

https://lkml.kernel.org/r/[email protected]

author maintainer
#7 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 12 lines · On Sat, 6 Oct 2018 14:12:11 +0200 Peter Zijlstra <[email protected]> wrote:

include/linux/jump_function.h line 18

Quoted 7 lines · Steven Rostedt
include/linux/jump_function.h new file +7 -0
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )  

Bah, what is it with you people and trampolines. Why can't we, just like jump_label, patch the call directly?

Quoted 2 lines

The whole call+jmp thing is silly, don't do that. It just wrecks I$ and is slower for no real reason afaict.

My first attempt was to do just that. But to add a label at the call site required handling all the parameters too. See my branch:
ftrace/jump_function-v1 for how ugly it got (and it didn't work).

Quoted 4 lines

Steve, also see:

https://lkml.kernel.org/r/[email protected]

Interesting. I don't have time to look at it at the moment to see what was done, but will do so in the near future.

Remember, this was a proof of concept and even with the trampolines, it showed a great level of improvement. One thought was to do a "recordmcount.c" type of action to find where the calls were and patch them directly at boot up. I tried to keep the API the same where this could actually be done as an improvement later.

Perhaps a gcc plugin might work too.

I'll have to see what Ard did to handle the function parameters.

-- Steve

#8 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 34 lines · Steven Rostedt
Quoted 9 lines · On Sat, 6 Oct 2018 14:12:11 +0200 Peter Zijlstra <[email protected]> wrote:

include/linux/jump_function.h line 18

Quoted 8 lines · Steven Rostedt
include/linux/jump_function.h new file +7 -0
+#define arch_dynfunc_trampoline(name, def)    \
+    asm volatile (                \
+    ".globl dynfunc_" #name "; \n\t"    \
+    "dynfunc_" #name ": \n\t"        \
+    "jmp " #def " \n\t"            \
+    ".balign 8 \n \t"            \
+    : : : "memory" )  
Quoted 5 lines

Bah, what is it with you people and trampolines. Why can't we, just like jump_label, patch the call directly?

The whole call+jmp thing is silly, don't do that. It just wrecks I$ and is slower for no real reason afaict.

My first attempt was to do just that. But to add a label at the call site required handling all the parameters too. See my branch:
ftrace/jump_function-v1 for how ugly it got (and it didn't work).

Quoted 4 lines

Steve, also see:

https://lkml.kernel.org/r/[email protected]

Interesting. I don't have time to look at it at the moment to see what was done, but will do so in the near future.

Quoted 8 lines

Remember, this was a proof of concept and even with the trampolines, it showed a great level of improvement. One thought was to do a "recordmcount.c" type of action to find where the calls were and patch them directly at boot up. I tried to keep the API the same where this could actually be done as an improvement later.

Perhaps a gcc plugin might work too.

My suggestion was to have objtool do the dirty work. Josh said something suspiciously like “sounds fun” on IRC :)

Quoted 4 lines

I'll have to see what Ard did to handle the function parameters.

-- Steve

author maintainer
#9 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 5 lines · On Sat, 6 Oct 2018 08:13:18 -0700 Andy Lutomirski <[email protected]> wrote:
Quoted 2 lines

Perhaps a gcc plugin might work too.

My suggestion was to have objtool do the dirty work. Josh said something suspiciously like “sounds fun” on IRC :)

objtool does basically the same thing as recordmcount does. Josh and I have both said that it's on our todo list to combine the two and make it more generic for operations like this.

Seems like now's the time to do it.

-- Steve

maintainer
#10 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Steven Rostedt
Quoted 15 lines · Steven Rostedt
Quoted 12 lines · On Sat, 6 Oct 2018 14:12:11 +0200 Peter Zijlstra <[email protected]> wrote:

include/linux/jump_function.h line 18

Quoted 7 lines · Steven Rostedt
include/linux/jump_function.h new file +7 -0
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )  

Bah, what is it with you people and trampolines. Why can't we, just like jump_label, patch the call directly?

Quoted 6 lines
Quoted 2 lines

The whole call+jmp thing is silly, don't do that. It just wrecks I$ and is slower for no real reason afaict.

My first attempt was to do just that. But to add a label at the call site required handling all the parameters too. See my branch:
ftrace/jump_function-v1 for how ugly it got (and it didn't work).

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

#11 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 23 lines · Peter Zijlstra
Quoted 20 lines · Steven Rostedt
Quoted 14 lines · On Sat, 6 Oct 2018 14:12:11 +0200 Peter Zijlstra <[email protected]> wrote:

include/linux/jump_function.h line 18

Quoted 8 lines · Steven Rostedt
include/linux/jump_function.h new file +7 -0
+#define arch_dynfunc_trampoline(name, def)    \
+    asm volatile (                \
+    ".globl dynfunc_" #name "; \n\t"    \
+    "dynfunc_" #name ": \n\t"        \
+    "jmp " #def " \n\t"            \
+    ".balign 8 \n \t"            \
+    : : : "memory" )  

Bah, what is it with you people and trampolines. Why can't we, just like jump_label, patch the call directly?

The whole call+jmp thing is silly, don't do that. It just wrecks I$ and is slower for no real reason afaict.

Quoted 6 lines
Quoted 3 lines

My first attempt was to do just that. But to add a label at the call site required handling all the parameters too. See my branch:
ftrace/jump_function-v1 for how ugly it got (and it didn't work).

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction. If the instruction crosses a cache line boundary, don’t we need to use the int3 patching trick? And that requires knowing which byte to patch with int3.

Or am I wrong and can the CPUs we care about correctly handle a locked write to part of an instruction that crosses a cache line boundary?

maintainer
#12 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

#13 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 13 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

Quoted 6 lines

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

author maintainer
#14 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 14 lines · On Mon, 8 Oct 2018 09:29:56 -0700 Andy Lutomirski <[email protected]> wrote:
Quoted 14 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

Quoted 8 lines
Quoted 3 lines

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

FYI, your email client is horrible to read from decent email clients :-p

Anyway,

I'd like to have these "dynamic functions" be "special" where they can't be added to tables or what not. If you need to add one to a table or function pointer, then you need to have a wrapper function that does the call. I think we can come up with some kind of wrapper or linker magic to enforce this too.

-- Steve

maintainer
#15 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Andy Lutomirski
Quoted 16 lines · Andy Lutomirski
Quoted 16 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

Quoted 8 lines
Quoted 3 lines

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

Oh, I had not considered tail calls..

Quoted 5 lines

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

We can have objtool verify the CALL/JMP/Jcc only condition. So if someone tries to take the address of a patchable function, it will error out.

Heck, it could initially even error out on tail calls.

#16 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 35 lines · Peter Zijlstra
Quoted 16 lines · Andy Lutomirski
Quoted 16 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

Quoted 8 lines
Quoted 3 lines

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

Oh, I had not considered tail calls..

Quoted 5 lines

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

Quoted 3 lines

We can have objtool verify the CALL/JMP/Jcc only condition. So if someone tries to take the address of a patchable function, it will error out.

I think we should just ignore the sites that take the address and maybe issue a warning. After all, GCC can create them all by itself. We'll always have a plain wrapper function, and I think we should just not patch code that takes its address. So we do, roughly:

void default_foo(void);

GLOBAL(foo)
jmp *current_foo(%rip)
ENDPROC(foo)

And code that does:

foo();

as a call, a tail call, a conditional tail call, etc, gets discovered by objtool + relocation processing or whatever and gets patched. (And foo() itself gets patched, too, as a special case. But we patch foo itself at some point during boot to turn it into a direct JMP. Doing it this way means that the whole mechanism works from very early boot.) And anything awful like:

switch(whatever) {
case 0:
foo();
};

that gets translated to a jump table and gets optimized such that it jumps straight to foo just gets left alone, since it still works. It's just a bit suboptimial. Similarly, code that does:

void (*ptr)(void);
ptr = foo;

gets a bona fide pointer to foo(), and any calls through the pointer land on foo() and jump to the current selected foo with only a single indirect branch / retpoline.

Does this seem reasonable? Is there a reason we should make it more restrictive?

#17 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 53 lines · Andy Lutomirski
Quoted 35 lines · Peter Zijlstra
Quoted 16 lines · Andy Lutomirski
Quoted 16 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

Quoted 8 lines
Quoted 3 lines

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

Oh, I had not considered tail calls..

Quoted 5 lines

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

Quoted 3 lines

We can have objtool verify the CALL/JMP/Jcc only condition. So if someone tries to take the address of a patchable function, it will error out.

I think we should just ignore the sites that take the address and maybe issue a warning. After all, GCC can create them all by itself. We'll always have a plain wrapper function, and I think we should just not patch code that takes its address. So we do, roughly:

void default_foo(void);

GLOBAL(foo)
jmp *current_foo(%rip)
ENDPROC(foo)

And code that does:

Quoted 8 lines

foo();

as a call, a tail call, a conditional tail call, etc, gets discovered by objtool + relocation processing or whatever and gets patched. (And foo() itself gets patched, too, as a special case. But we patch foo itself at some point during boot to turn it into a direct JMP. Doing it this way means that the whole mechanism works from very early boot.)

Does that mean that architectures could opt out of doing the whole objtool + relocation processing thing, and instead take the hit of going through the trampoline for all calls?

Quoted 20 lines

And anything awful like:

switch(whatever) {
case 0:
foo();
};

that gets translated to a jump table and gets optimized such that it jumps straight to foo just gets left alone, since it still works. It's just a bit suboptimial. Similarly, code that does:

void (*ptr)(void);
ptr = foo;

gets a bona fide pointer to foo(), and any calls through the pointer land on foo() and jump to the current selected foo with only a single indirect branch / retpoline.

Does this seem reasonable? Is there a reason we should make it more restrictive?

#18 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 64 lines · Ard Biesheuvel
Quoted 53 lines · Andy Lutomirski
Quoted 35 lines · Peter Zijlstra
Quoted 16 lines · Andy Lutomirski
Quoted 16 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

Quoted 8 lines
Quoted 3 lines

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

Oh, I had not considered tail calls..

Quoted 5 lines

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

Quoted 3 lines

We can have objtool verify the CALL/JMP/Jcc only condition. So if someone tries to take the address of a patchable function, it will error out.

I think we should just ignore the sites that take the address and maybe issue a warning. After all, GCC can create them all by itself. We'll always have a plain wrapper function, and I think we should just not patch code that takes its address. So we do, roughly:

void default_foo(void);

GLOBAL(foo)
jmp *current_foo(%rip)
ENDPROC(foo)

And code that does:

Quoted 8 lines

foo();

as a call, a tail call, a conditional tail call, etc, gets discovered by objtool + relocation processing or whatever and gets patched. (And foo() itself gets patched, too, as a special case. But we patch foo itself at some point during boot to turn it into a direct JMP. Doing it this way means that the whole mechanism works from very early boot.)

Quoted 4 lines

Does that mean that architectures could opt out of doing the whole objtool + relocation processing thing, and instead take the hit of going through the trampoline for all calls?

I don't see why not.

--Andy

#19 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Ard Biesheuvel
Quoted 3 lines · Ard Biesheuvel

Does that mean that architectures could opt out of doing the whole objtool + relocation processing thing, and instead take the hit of going through the trampoline for all calls?

There are architectures that aren't [currently] supported by objtool at all anyway.

Signature · 2 lines
Jiri Kosina
SUSE Labs
#20 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 9 lines · Jiri Kosina
Quoted 3 lines · Ard Biesheuvel

Does that mean that architectures could opt out of doing the whole objtool + relocation processing thing, and instead take the hit of going through the trampoline for all calls?

There are architectures that aren't [currently] supported by objtool at all anyway.

That was kind of my point :-)

#21 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Jiri Kosina
Quoted 10 lines · Jiri Kosina
Quoted 3 lines · Ard Biesheuvel

Does that mean that architectures could opt out of doing the whole objtool + relocation processing thing, and instead take the hit of going through the trampoline for all calls?

There are architectures that aren't [currently] supported by objtool at all anyway.

The the credit of most architectures, though, the only reason x86 would want to use objtool instead of digging the results directly out of the relocation data is that x86 has an overcomplicated instruction encoding and there's no fully reliable way to find the address of the instruction that contains a given relocation without fully disassembling the binary.

#22 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Andy Lutomirski
Quoted 16 lines · Andy Lutomirski
Quoted 16 lines · Peter Zijlstra
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

Quoted 8 lines
Quoted 3 lines

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

There’s also JMP and Jcc, any of which can be used for rail calls, but those are also one byte. I suppose GCC is unlikely to emit a prefixed form of any of these. So maybe we really can assume they’re all one byte.

I'm pretty sure only a basic JMP is used for tail calls.

Quoted 5 lines

But there is a nasty potential special case: anything that takes the function’s address. This includes jump tables, computed gotos, and plain old function pointers. And I suspect that any of these could have one of the rather large number of CALL/JMP/Jcc bytes before the relocation by coincidence.

But those special cases aren't in a text section, right? If we just make sure the relocations are applied to a text section, and that they're preceded by the CALL or JMP byte, wouldn't that be sufficient?

I'm not really convinced we need objtool for this, maybe I'll try whipping up a POC.

Signature · 1 line
Josh
author maintainer
#23 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 2 lines · On Mon, 8 Oct 2018 21:17:10 -0500 Josh Poimboeuf <[email protected]> wrote:

I'm not really convinced we need objtool for this, maybe I'll try whipping up a POC.

Awesome!

I wasn't thinking of actually having objtool itself perform this task, but instead breaking the internals of objtool up into more of a generic infrastructure, that recordmcount.c, objtool, and whatever this does can use.

-- Steve

#24 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 6 lines · Steven Rostedt
Quoted 2 lines · On Mon, 8 Oct 2018 21:17:10 -0500 Josh Poimboeuf <[email protected]> wrote:

I'm not really convinced we need objtool for this, maybe I'll try whipping up a POC.

Quoted 6 lines

Awesome!

I wasn't thinking of actually having objtool itself perform this task, but instead breaking the internals of objtool up into more of a generic infrastructure, that recordmcount.c, objtool, and whatever this does can use.

So I had been thinking that we could find the call sites at runtime, by looking at the relocations. But I managed to forget that vmlinux relocations are resolved during linking. So yeah, some kind of tooling magic would be needed.

I worked up a POC using objtool. It doesn't *have* to be done with objtool, but since it's already reading/writing all the ELF stuff anyway, it was pretty easy to add this on.

This patch has at least a few issues:

- No module support.

- For some reason, the sync_cores in text_poke_bp() don't always seem to be working as expected. Running this patch on my VM, the test code in cmdline_proc_show() works *most* of the time, but it occasionally branches off into the weeds. I have no idea what the problem is yet.

arch/Kconfig +3 -0
index 9d329608913e..20ff5624dad7 100644
@@ -865,6 +865,9 @@ config HAVE_ARCH_PREL32_RELOCATIONS
 	  architectures, and don't require runtime relocation on relocatable
 	  kernels.
 
+config HAVE_ARCH_STATIC_CALL
+	bool
+
 source "kernel/gcov/Kconfig"
 
 source "scripts/gcc-plugins/Kconfig"
arch/x86/Kconfig +1 -0
index 5136a1281870..1a14c8f87876 100644
@@ -128,6 +128,7 @@ config X86
 	select HAVE_ARCH_COMPAT_MMAP_BASES	if MMU && COMPAT
 	select HAVE_ARCH_PREL32_RELOCATIONS
 	select HAVE_ARCH_SECCOMP_FILTER
+	select HAVE_ARCH_STATIC_CALL		if X86_64
 	select HAVE_ARCH_THREAD_STRUCT_WHITELIST
 	select HAVE_ARCH_TRACEHOOK
 	select HAVE_ARCH_TRANSPARENT_HUGEPAGE
arch/x86/include/asm/static_call.h new file +35 -0
new file mode 100644
index 000000000000..40fec631b760
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_STATIC_CALL_H
+#define _ASM_STATIC_CALL_H
+
+#ifdef CONFIG_X86_64
+
+#include <linux/frame.h>
+
+void static_call_init(void);
+extern void __static_call_update(void *tramp, void *func);
+
+#define DECLARE_STATIC_CALL(tramp, func)				\
+	extern typeof(func) tramp;					\
+	static void __used __section(.discard.static_call_tramps)	\
+		*__static_call_tramp_##tramp = tramp
+
+#define DEFINE_STATIC_CALL(tramp, func)					\
+	DECLARE_STATIC_CALL(tramp, func);				\
+	asm(".pushsection .text, \"ax\"				\n"	\
+	    ".align 4						\n"	\
+	    ".globl " #tramp "					\n"	\
+	    ".type " #tramp ", @function			\n"	\
+	    #tramp ":						\n"	\
+	    "jmp " #func "					\n"	\
+	    ASM_NOP3 "						\n"	\
+	    ".popsection					\n")
+
+#define static_call_update(tramp, func)					\
+	__static_call_update(tramp, func)
+
+#else /* !CONFIG_X86_64 */
+static inline void static_call_init(void) {}
+#endif
+
+#endif /* _ASM_STATIC_CALL_H */
arch/x86/kernel/Makefile +1 -0
index 8824d01c0c35..e5d9f3a1e73f 100644
@@ -62,6 +62,7 @@ obj-y			+= tsc.o tsc_msr.o io_delay.o rtc.o
 obj-y			+= pci-iommu_table.o
 obj-y			+= resource.o
 obj-y			+= irqflags.o
+obj-$(CONFIG_X86_64)	+= static_call.o
 
 obj-y				+= process.o
 obj-y				+= fpu/
arch/x86/kernel/setup.c +2 -0
index b4866badb235..447401fc8d65 100644
@@ -117,6 +117,7 @@
 #include <asm/microcode.h>
 #include <asm/kaslr.h>
 #include <asm/unwind.h>
+#include <asm/static_call.h>
 
 /*
  * max_low_pfn_mapped: highest direct mapped pfn under 4GB
@@ -874,6 +875,7 @@ void __init setup_arch(char **cmdline_p)
 	early_cpu_init();
 	arch_init_ideal_nops();
 	jump_label_init();
+	static_call_init();
 	early_ioremap_init();
 
 	setup_olpc_ofw_pgd();
arch/x86/kernel/static_call.c new file +117 -0

117 lines not on this page · raw message

arch/x86/kernel/vmlinux.lds.S +3 -0
index 0d618ee634ac..cf0566f8a13c 100644
@@ -185,6 +185,9 @@ SECTIONS
 
 	BUG_TABLE
 
+	/* FIXME: move to read-only section */
+	STATIC_CALL_TABLE
+
 	ORC_UNWIND_TABLE
 
 	. = ALIGN(PAGE_SIZE);
fs/proc/cmdline.c +18 -0
index fa762c5fbcb2..c704b9e1fe5f 100644
@@ -3,9 +3,27 @@
 #include <linux/init.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
+#include <linux/static_call.h>
+
+extern int my_func_add(int arg1, int arg2);
+extern int my_func_sub(int arg1, int arg2);
+DECLARE_STATIC_CALL(my_static_call, my_func_add);
 
 static int cmdline_proc_show(struct seq_file *m, void *v)
 {
+	int ret;
+
+	ret = my_static_call(1, 2);
+	printk("static call (orig): ret=%d\n", ret);
+
+	static_call_update(my_static_call, my_func_sub);
+	ret = my_static_call(1, 2);
+	printk("static call (sub): ret=%d\n", ret);
+
+	static_call_update(my_static_call, my_func_add);
+	ret = my_static_call(1, 2);
+	printk("static call (add): ret=%d\n", ret);
+
 	seq_puts(m, saved_command_line);
 	seq_putc(m, '\n');
 	return 0;
include/asm-generic/vmlinux.lds.h +8 -0
index f09ee3c544bc..a1c7bda1b22a 100644
@@ -722,6 +722,14 @@
 #define BUG_TABLE
 #endif
 
+#define STATIC_CALL_TABLE						\
+	. = ALIGN(8);							\
+	__static_call_table : AT(ADDR(__static_call_table) - LOAD_OFFSET) { \
+		__start_static_call_table = .;			\
+		KEEP(*(__static_call_table))				\
+		__stop_static_call_table = .;				\
+	}
+
 #ifdef CONFIG_UNWINDER_ORC
 #define ORC_UNWIND_TABLE						\
 	. = ALIGN(4);							\
include/linux/static_call.h new file +20 -0
new file mode 100644
index 000000000000..729e7ee4c66b
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_STATIC_CALL_H
+#define _LINUX_STATIC_CALL_H
+
+#ifdef CONFIG_HAVE_ARCH_STATIC_CALL
+#include <asm/static_call.h>
+#else
+
+#define DECLARE_STATIC_CALL(ptr, func)					\
+	extern typeof(func) *ptr
+
+#define DEFINE_STATIC_CALL(ptr, func)					\
+	typeof(func) *ptr = func
+
+#define static_call_update(ptr, func)					\
+	WRITE_ONCE(ptr, func)
+
+#endif /* !CONFIG_HAVE_ARCH_STATIC_CALL */
+
+#endif /* _LINUX_STATIC_CALL_H */
tools/objtool/check.c +113 -0

162 lines not on this page · raw message

tools/objtool/check.h +2 -0
index e6e8a655b556..56b8b7fb1bd1 100644
@@ -39,6 +39,7 @@ struct insn_state {
 struct instruction {
 	struct list_head list;
 	struct hlist_node hash;
+	struct list_head static_call_node;
 	struct section *sec;
 	unsigned long offset;
 	unsigned int len;
@@ -60,6 +61,7 @@ struct objtool_file {
 	struct elf *elf;
 	struct list_head insn_list;
 	DECLARE_HASHTABLE(insn_hash, 16);
+	struct list_head static_call_list;
 	struct section *whitelist;
 	bool ignore_unreachables, c_file, hints, rodata;
 };
tools/objtool/elf.h +1 -0
index bc97ed86b9cd..3cf44d7cc3ac 100644
@@ -62,6 +62,7 @@ struct symbol {
 	unsigned long offset;
 	unsigned int len;
 	struct symbol *pfunc, *cfunc;
+	bool static_call_tramp;
 };
 
 struct rela {
#25 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 71 lines · Josh Poimboeuf
Quoted 6 lines · Steven Rostedt
Quoted 2 lines · On Mon, 8 Oct 2018 21:17:10 -0500 Josh Poimboeuf <[email protected]> wrote:

I'm not really convinced we need objtool for this, maybe I'll try whipping up a POC.

Quoted 6 lines

Awesome!

I wasn't thinking of actually having objtool itself perform this task, but instead breaking the internals of objtool up into more of a generic infrastructure, that recordmcount.c, objtool, and whatever this does can use.

So I had been thinking that we could find the call sites at runtime, by looking at the relocations. But I managed to forget that vmlinux relocations are resolved during linking. So yeah, some kind of tooling magic would be needed.

I worked up a POC using objtool. It doesn't *have* to be done with objtool, but since it's already reading/writing all the ELF stuff anyway, it was pretty easy to add this on.

This patch has at least a few issues:

- No module support.

- For some reason, the sync_cores in text_poke_bp() don't always seem to be working as expected. Running this patch on my VM, the test code in cmdline_proc_show() works *most* of the time, but it occasionally branches off into the weeds. I have no idea what the problem is yet.

arch/Kconfig +3 -0
index 9d329608913e..20ff5624dad7 100644
@@ -865,6 +865,9 @@ config HAVE_ARCH_PREL32_RELOCATIONS
          architectures, and don't require runtime relocation on relocatable
          kernels.
 
+config HAVE_ARCH_STATIC_CALL
+       bool
+
 source "kernel/gcov/Kconfig"
 
 source "scripts/gcc-plugins/Kconfig"
arch/x86/Kconfig +1 -0
index 5136a1281870..1a14c8f87876 100644
@@ -128,6 +128,7 @@ config X86
        select HAVE_ARCH_COMPAT_MMAP_BASES      if MMU && COMPAT
        select HAVE_ARCH_PREL32_RELOCATIONS
        select HAVE_ARCH_SECCOMP_FILTER
+       select HAVE_ARCH_STATIC_CALL            if X86_64
        select HAVE_ARCH_THREAD_STRUCT_WHITELIST
        select HAVE_ARCH_TRACEHOOK
        select HAVE_ARCH_TRANSPARENT_HUGEPAGE
arch/x86/include/asm/static_call.h new file +6 -0
new file mode 100644
index 000000000000..40fec631b760
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _ASM_STATIC_CALL_H
+#define _ASM_STATIC_CALL_H
+
+#ifdef CONFIG_X86_64
+

arch/x86/include/asm/static_call.h line 16

Quoted 10 lines
arch/x86/include/asm/static_call.h new file +10 -0
+#include <linux/frame.h>
+
+void static_call_init(void);
+extern void __static_call_update(void *tramp, void *func);
+
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

arch/x86/include/asm/static_call.h line 24

Quoted 8 lines
arch/x86/include/asm/static_call.h new file +8 -0
+#define DEFINE_STATIC_CALL(tramp, func)                                        \
+       DECLARE_STATIC_CALL(tramp, func);                               \
+       asm(".pushsection .text, \"ax\"                         \n"     \
+           ".align 4                                           \n"     \
+           ".globl " #tramp "                                  \n"     \
+           ".type " #tramp ", @function                        \n"     \
+           #tramp ":                                           \n"     \
+           "jmp " #func "                                      \n"     \

I think this would be nicer as an indirect call that gets patched to a direct call so that the update mechanism works even before it's initialized. (Currently static_branch blows up horribly if you try to update one too early, and that's rather annoying IMO.)

Also, I think you're looking for "jmp.d32", which is available in newer toolchains. For older toolchains, you could use .byte 0xe9 or you could use a different section (I think) to force a real 32-bit jump.

Quoted 19 lines
arch/x86/include/asm/static_call.h new file +19 -0
+void __init static_call_init(void)
+{
+       struct static_call_entry *entry;
+       unsigned long insn, tramp, func;
+       unsigned char insn_opcode, tramp_opcode;
+       s32 call_dest;
+
+       for (entry = __start_static_call_table;
+            entry < __stop_static_call_table; entry++) {
+
+               insn = (long)entry->insn + (unsigned long)&entry->insn;
+               tramp = (long)entry->tramp + (unsigned long)&entry->tramp;
+
+               insn_opcode = *(unsigned char *)insn;
+               if (insn_opcode != 0xe8 && insn_opcode != 0xe9) {
+                       WARN_ONCE(1, "unexpected static call insn opcode %x at %pS",
+                                 insn_opcode, (void *)insn);
+                       continue;
+               }

arch/x86/include/asm/static_call.h

Quoted 10 lines
arch/x86/include/asm/static_call.h new file +10 -0
+
+               tramp_opcode = *(unsigned char *)tramp;
+               if (tramp_opcode != 0xeb && tramp_opcode != 0xe9) {
+                       WARN_ONCE(1, "unexpected trampoline jump opcode %x at %ps",
+                                tramp_opcode, (void *)tramp);
+                       continue;
+               }
+
+               if (tramp_opcode == 0xeb)
+                       func = *(s8 *)(tramp + 1) + (tramp + 2);

I realize you expect some instances of 0xeb due to the assembler messing you up (see above), but this seems a bit too permissive, since a 0xeb without the appropriate set of NOPs is going to explode. And:

arch/x86/include/asm/static_call.h

Quoted 8 lines
arch/x86/include/asm/static_call.h new file +8 -0
+               else
+                       func = *(s32 *)(tramp + 1) + (tramp + 5);
+
+               call_dest = (long)(func) - (long)(insn + 5);
+
+               printk("static_call_init: poking %lx at %lx\n", (unsigned long)call_dest, (insn+1));
+
+               text_poke_early((void *)(insn + 1), &call_dest, 4);

If you really are going to rewrite an 8-bit jump to a 32-bit jump, I think you need to actually patch the opcode byte :)

#26 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 8 lines · Andy Lutomirski

arch/x86/include/asm/static_call.h line 16

Quoted 5 lines
arch/x86/include/asm/static_call.h new file +5 -0
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

This is the magic needed for objtool to find all the call sites.

The variable itself isn't needed, but the .discard.static_call_tramps entry is. Objtool reads that section to find out which function call sites are targeted to a static call trampoline.

Quoted 9 lines

arch/x86/include/asm/static_call.h line 24

Quoted 8 lines
arch/x86/include/asm/static_call.h new file +8 -0
+#define DEFINE_STATIC_CALL(tramp, func)                                        \
+       DECLARE_STATIC_CALL(tramp, func);                               \
+       asm(".pushsection .text, \"ax\"                         \n"     \
+           ".align 4                                           \n"     \
+           ".globl " #tramp "                                  \n"     \
+           ".type " #tramp ", @function                        \n"     \
+           #tramp ":                                           \n"     \
+           "jmp " #func "                                      \n"     \
Quoted 4 lines

I think this would be nicer as an indirect call that gets patched to a direct call so that the update mechanism works even before it's initialized. (Currently static_branch blows up horribly if you try to update one too early, and that's rather annoying IMO.)

Yeah, that would be better. It would also allow trampoline function pointers to work, which I think you mentioned elsewhere. And then I shouldn't trample this code in __static_call_update() -- that was already kind of nasty anyway.

Quoted 4 lines

Also, I think you're looking for "jmp.d32", which is available in newer toolchains. For older toolchains, you could use .byte 0xe9 or you could use a different section (I think) to force a real 32-bit jump.

Good idea.

Quoted 25 lines
Quoted 25 lines
arch/x86/kernel/static_call.c new file +25 -0
+void __init static_call_init(void)
+{
+       struct static_call_entry *entry;
+       unsigned long insn, tramp, func;
+       unsigned char insn_opcode, tramp_opcode;
+       s32 call_dest;
+
+       for (entry = __start_static_call_table;
+            entry < __stop_static_call_table; entry++) {
+
+               insn = (long)entry->insn + (unsigned long)&entry->insn;
+               tramp = (long)entry->tramp + (unsigned long)&entry->tramp;
+
+               insn_opcode = *(unsigned char *)insn;
+               if (insn_opcode != 0xe8 && insn_opcode != 0xe9) {
+                       WARN_ONCE(1, "unexpected static call insn opcode %x at %pS",
+                                 insn_opcode, (void *)insn);
+                       continue;
+               }
+
+               tramp_opcode = *(unsigned char *)tramp;
+               if (tramp_opcode != 0xeb && tramp_opcode != 0xe9) {
+                       WARN_ONCE(1, "unexpected trampoline jump opcode %x at %ps",
+                                tramp_opcode, (void *)tramp);
+                       continue;
Quoted 8 lines

arch/x86/kernel/static_call.c line 49

Quoted 4 lines
arch/x86/kernel/static_call.c new file +4 -0
+               }
+
+               if (tramp_opcode == 0xeb)
+                       func = *(s8 *)(tramp + 1) + (tramp + 2);

I realize you expect some instances of 0xeb due to the assembler messing you up (see above), but this seems a bit too permissive, since a 0xeb without the appropriate set of NOPs is going to explode. And:

Yep.

Quoted 11 lines

arch/x86/kernel/static_call.c line 57

Quoted 8 lines
arch/x86/kernel/static_call.c new file +8 -0
+               else
+                       func = *(s32 *)(tramp + 1) + (tramp + 5);
+
+               call_dest = (long)(func) - (long)(insn + 5);
+
+               printk("static_call_init: poking %lx at %lx\n", (unsigned long)call_dest, (insn+1));
+
+               text_poke_early((void *)(insn + 1), &call_dest, 4);

If you really are going to rewrite an 8-bit jump to a 32-bit jump, I think you need to actually patch the opcode byte :)

Oops :-)

Signature · 1 line
Josh
#27 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 10 lines · Josh Poimboeuf
Quoted 8 lines · Andy Lutomirski

arch/x86/include/asm/static_call.h line 16

Quoted 5 lines
arch/x86/include/asm/static_call.h new file +5 -0
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

Quoted 5 lines

This is the magic needed for objtool to find all the call sites.

The variable itself isn't needed, but the .discard.static_call_tramps entry is. Objtool reads that section to find out which function call sites are targeted to a static call trampoline.

To clarify: objtool reads that section to find out which functions are really static call trampolines. Then it annotates all the instructions which call/jmp to those trampolines. Those annotations are then read by the kernel.

Signature · 1 line
Josh
#28 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 14 lines · Josh Poimboeuf
Quoted 12 lines · Josh Poimboeuf
Quoted 8 lines · Andy Lutomirski

arch/x86/include/asm/static_call.h line 16

Quoted 5 lines
arch/x86/include/asm/static_call.h new file +5 -0
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

This is the magic needed for objtool to find all the call sites.

Quoted 9 lines
Quoted 3 lines

The variable itself isn't needed, but the .discard.static_call_tramps entry is. Objtool reads that section to find out which function call sites are targeted to a static call trampoline.

To clarify: objtool reads that section to find out which functions are really static call trampolines. Then it annotates all the instructions which call/jmp to those trampolines. Those annotations are then read by the kernel.

Ah, right, and objtool runs on a per-object basis so it has no other way to know what symbols are actually static calls.

There's another way to skin this cat, though:

extern typeof(func) __static_call_trampoline_##tramp; #define tramp __static_call_trampoline_##tramp

And objtool could recognize it by name. But, of course, you can't put a #define in a macro. But maybe there's a way to hack it up with a static inline?

Anyway, your way is probably fine with a few caveats:

- It won't really work if the call comes from a .S file.
- There should probably be a comment to help de-confuse future people
like me :)

#29 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 28 lines · Andy Lutomirski
Quoted 14 lines · Josh Poimboeuf
Quoted 12 lines · Josh Poimboeuf
Quoted 8 lines · Andy Lutomirski

Review

Quoted 5 lines
(quoted diff) +5 -0
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

This is the magic needed for objtool to find all the call sites.

Quoted 9 lines
Quoted 3 lines

The variable itself isn't needed, but the .discard.static_call_tramps entry is. Objtool reads that section to find out which function call sites are targeted to a static call trampoline.

To clarify: objtool reads that section to find out which functions are really static call trampolines. Then it annotates all the instructions which call/jmp to those trampolines. Those annotations are then read by the kernel.

Ah, right, and objtool runs on a per-object basis so it has no other way to know what symbols are actually static calls.

Quoted 8 lines

There's another way to skin this cat, though:

extern typeof(func) __static_call_trampoline_##tramp; #define tramp __static_call_trampoline_##tramp

And objtool could recognize it by name. But, of course, you can't put a #define in a macro. But maybe there's a way to hack it up with a static inline?

Not sure how to do that...

Quoted 3 lines

Anyway, your way is probably fine with a few caveats:

- It won't really work if the call comes from a .S file.

Maybe we can have similar macros for asm code.

Quoted 2 lines

- There should probably be a comment to help de-confuse future people
like me :)

Done. I also converted the trampoline to use an indirect jump. The latest code is below. I'm going off the grid this weekend but I can probably post proper patches next week.

arch/Kconfig +3 -0
index 9d329608913e..20ff5624dad7 100644
@@ -865,6 +865,9 @@ config HAVE_ARCH_PREL32_RELOCATIONS
 	  architectures, and don't require runtime relocation on relocatable
 	  kernels.
 
+config HAVE_ARCH_STATIC_CALL
+	bool
+
 source "kernel/gcov/Kconfig"
 
 source "scripts/gcc-plugins/Kconfig"
arch/x86/Kconfig +1 -0
index 5136a1281870..1a14c8f87876 100644
@@ -128,6 +128,7 @@ config X86
 	select HAVE_ARCH_COMPAT_MMAP_BASES	if MMU && COMPAT
 	select HAVE_ARCH_PREL32_RELOCATIONS
 	select HAVE_ARCH_SECCOMP_FILTER
+	select HAVE_ARCH_STATIC_CALL		if X86_64
 	select HAVE_ARCH_THREAD_STRUCT_WHITELIST
 	select HAVE_ARCH_TRACEHOOK
 	select HAVE_ARCH_TRANSPARENT_HUGEPAGE
arch/x86/include/asm/static_call.h new file +47 -0

47 lines not on this page · raw message

arch/x86/kernel/Makefile +1 -0
index 8824d01c0c35..e5d9f3a1e73f 100644
@@ -62,6 +62,7 @@ obj-y			+= tsc.o tsc_msr.o io_delay.o rtc.o
 obj-y			+= pci-iommu_table.o
 obj-y			+= resource.o
 obj-y			+= irqflags.o
+obj-$(CONFIG_X86_64)	+= static_call.o
 
 obj-y				+= process.o
 obj-y				+= fpu/
arch/x86/kernel/setup.c +2 -0
index b4866badb235..447401fc8d65 100644
@@ -117,6 +117,7 @@
 #include <asm/microcode.h>
 #include <asm/kaslr.h>
 #include <asm/unwind.h>
+#include <asm/static_call.h>
 
 /*
  * max_low_pfn_mapped: highest direct mapped pfn under 4GB
@@ -874,6 +875,7 @@ void __init setup_arch(char **cmdline_p)
 	early_cpu_init();
 	arch_init_ideal_nops();
 	jump_label_init();
+	static_call_init();
 	early_ioremap_init();
 
 	setup_olpc_ofw_pgd();
arch/x86/kernel/static_call.c new file +118 -0

118 lines not on this page · raw message

arch/x86/kernel/vmlinux.lds.S +3 -0
index 0d618ee634ac..cf0566f8a13c 100644
@@ -185,6 +185,9 @@ SECTIONS
 
 	BUG_TABLE
 
+	/* FIXME: move to read-only section */
+	STATIC_CALL_TABLE
+
 	ORC_UNWIND_TABLE
 
 	. = ALIGN(PAGE_SIZE);
fs/proc/cmdline.c +18 -0
index fa762c5fbcb2..c704b9e1fe5f 100644
@@ -3,9 +3,27 @@
 #include <linux/init.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
+#include <linux/static_call.h>
+
+extern int my_func_add(int arg1, int arg2);
+extern int my_func_sub(int arg1, int arg2);
+DECLARE_STATIC_CALL(my_static_call, my_func_add);
 
 static int cmdline_proc_show(struct seq_file *m, void *v)
 {
+	int ret;
+
+	ret = my_static_call(1, 2);
+	printk("static call (orig): ret=%d\n", ret);
+
+	static_call_update(my_static_call, my_func_sub);
+	ret = my_static_call(1, 2);
+	printk("static call (sub): ret=%d\n", ret);
+
+	static_call_update(my_static_call, my_func_add);
+	ret = my_static_call(1, 2);
+	printk("static call (add): ret=%d\n", ret);
+
 	seq_puts(m, saved_command_line);
 	seq_putc(m, '\n');
 	return 0;
include/asm-generic/vmlinux.lds.h +8 -0
index f09ee3c544bc..a1c7bda1b22a 100644
@@ -722,6 +722,14 @@
 #define BUG_TABLE
 #endif
 
+#define STATIC_CALL_TABLE						\
+	. = ALIGN(8);							\
+	__static_call_table : AT(ADDR(__static_call_table) - LOAD_OFFSET) { \
+		__start_static_call_table = .;			\
+		KEEP(*(__static_call_table))				\
+		__stop_static_call_table = .;				\
+	}
+
 #ifdef CONFIG_UNWINDER_ORC
 #define ORC_UNWIND_TABLE						\
 	. = ALIGN(4);							\
include/linux/static_call.h new file +20 -0
new file mode 100644
index 000000000000..729e7ee4c66b
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _LINUX_STATIC_CALL_H
+#define _LINUX_STATIC_CALL_H
+
+#ifdef CONFIG_HAVE_ARCH_STATIC_CALL
+#include <asm/static_call.h>
+#else
+
+#define DECLARE_STATIC_CALL(ptr, func)					\
+	extern typeof(func) *ptr
+
+#define DEFINE_STATIC_CALL(ptr, func)					\
+	typeof(func) *ptr = func
+
+#define static_call_update(ptr, func)					\
+	WRITE_ONCE(ptr, func)
+
+#endif /* !CONFIG_HAVE_ARCH_STATIC_CALL */
+
+#endif /* _LINUX_STATIC_CALL_H */
tools/objtool/check.c +129 -0

178 lines not on this page · raw message

tools/objtool/check.h +2 -0
index e6e8a655b556..56b8b7fb1bd1 100644
@@ -39,6 +39,7 @@ struct insn_state {
 struct instruction {
 	struct list_head list;
 	struct hlist_node hash;
+	struct list_head static_call_node;
 	struct section *sec;
 	unsigned long offset;
 	unsigned int len;
@@ -60,6 +61,7 @@ struct objtool_file {
 	struct elf *elf;
 	struct list_head insn_list;
 	DECLARE_HASHTABLE(insn_hash, 16);
+	struct list_head static_call_list;
 	struct section *whitelist;
 	bool ignore_unreachables, c_file, hints, rodata;
 };
tools/objtool/elf.h +1 -0
index bc97ed86b9cd..3cf44d7cc3ac 100644
@@ -62,6 +62,7 @@ struct symbol {
 	unsigned long offset;
 	unsigned int len;
 	struct symbol *pfunc, *cfunc;
+	bool static_call_tramp;
 };
 
 struct rela {
#30 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 29 lines · Josh Poimboeuf
Quoted 28 lines · Andy Lutomirski
Quoted 14 lines · Josh Poimboeuf
Quoted 12 lines · Josh Poimboeuf
Quoted 8 lines · Andy Lutomirski

arch/x86/include/asm/static_call.h line 25

Quoted 5 lines
arch/x86/include/asm/static_call.h new file +5 -0
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

This is the magic needed for objtool to find all the call sites.

Quoted 9 lines
Quoted 3 lines

The variable itself isn't needed, but the .discard.static_call_tramps entry is. Objtool reads that section to find out which function call sites are targeted to a static call trampoline.

To clarify: objtool reads that section to find out which functions are really static call trampolines. Then it annotates all the instructions which call/jmp to those trampolines. Those annotations are then read by the kernel.

Ah, right, and objtool runs on a per-object basis so it has no other way to know what symbols are actually static calls.

Quoted 8 lines
Quoted 8 lines

There's another way to skin this cat, though:

extern typeof(func) __static_call_trampoline_##tramp; #define tramp __static_call_trampoline_##tramp

And objtool could recognize it by name. But, of course, you can't put a #define in a macro. But maybe there's a way to hack it up with a static inline?

I went with something similar in the latest version. It's less surprising in a couple of ways:

- DECLARE_STATIC_CALL doesn't have the magic objtool definition.

- Call sites use the static_call() wrapper, which makes static calls clearly visible.

#define STATIC_CALL_TRAMP(key) ____static_call_tramp_##key
#define STATIC_CALL_PTR(key) ____static_call_ptr_##key

#define STATIC_CALL_TRAMP_STR(key) __stringify(STATIC_CALL_TRAMP(key)) #define STATIC_CALL_PTR_STR(key) __stringify(STATIC_CALL_PTR(key))

#define DECLARE_STATIC_CALL(key, func)					\
	extern typeof(func)  STATIC_CALL_TRAMP(key);			\
	extern typeof(func) *STATIC_CALL_PTR(key)
#define DEFINE_STATIC_CALL(key, func)					\
	typeof(func) *STATIC_CALL_PTR(key) = func;			\
	asm(".pushsection .text, \"ax\"				\n"	\
	    ".align 4						\n"	\
	    ".globl " STATIC_CALL_TRAMP_STR(key) "		\n"	\
	    ".type " STATIC_CALL_TRAMP_STR(key) ", @function	\n"	\
	    STATIC_CALL_TRAMP_STR(key) ":			\n"	\
	    "jmpq *" STATIC_CALL_PTR_STR(key) "(%rip)		\n"	\
	    ".popsection					\n")

#define static_call(key, args...) STATIC_CALL_TRAMP(key)(args)

#define static_call_update(key, func) \

({									\
	STATIC_CALL_PTR(key) = func;					\
	__static_call_update((void **)&STATIC_CALL_PTR(key));		\
})
Signature · 1 line
Josh
#31 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 43 lines · Josh Poimboeuf
Quoted 31 lines · Josh Poimboeuf
Quoted 29 lines · Andy Lutomirski
Quoted 15 lines · Josh Poimboeuf
Quoted 14 lines · Josh Poimboeuf
Quoted 8 lines · Andy Lutomirski

arch/x86/include/asm/static_call.h line 25

Quoted 5 lines
arch/x86/include/asm/static_call.h new file +5 -0
+#define DECLARE_STATIC_CALL(tramp, func)                               \
+       extern typeof(func) tramp;                                      \
+       static void __used __section(.discard.static_call_tramps)       \
+               *__static_call_tramp_##tramp = tramp
+

Confused. What's the __static_call_tramp_##tramp variable for? And why is a DECLARE_ macro defining a variable?

This is the magic needed for objtool to find all the call sites.

Quoted 9 lines
Quoted 3 lines

The variable itself isn't needed, but the .discard.static_call_tramps entry is. Objtool reads that section to find out which function call sites are targeted to a static call trampoline.

To clarify: objtool reads that section to find out which functions are really static call trampolines. Then it annotates all the instructions which call/jmp to those trampolines. Those annotations are then read by the kernel.

Ah, right, and objtool runs on a per-object basis so it has no other way to know what symbols are actually static calls.

Quoted 8 lines
Quoted 8 lines

There's another way to skin this cat, though:

extern typeof(func) __static_call_trampoline_##tramp; #define tramp __static_call_trampoline_##tramp

And objtool could recognize it by name. But, of course, you can't put a #define in a macro. But maybe there's a way to hack it up with a static inline?

Quoted 7 lines

I went with something similar in the latest version. It's less surprising in a couple of ways:

- DECLARE_STATIC_CALL doesn't have the magic objtool definition.

- Call sites use the static_call() wrapper, which makes static calls clearly visible.

Seems reasonable. Also, for a real patch, it should be straightforward to have a fallback implementation in include/linux/static_call.h that just dereferences the pointer.

#32 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Josh Poimboeuf
Quoted 14 lines · Josh Poimboeuf
Quoted 9 lines

arch/x86/include/asm/static_call.h line 24

Quoted 8 lines
arch/x86/include/asm/static_call.h new file +8 -0
+#define DEFINE_STATIC_CALL(tramp, func)                                        \
+       DECLARE_STATIC_CALL(tramp, func);                               \
+       asm(".pushsection .text, \"ax\"                         \n"     \
+           ".align 4                                           \n"     \
+           ".globl " #tramp "                                  \n"     \
+           ".type " #tramp ", @function                        \n"     \
+           #tramp ":                                           \n"     \
+           "jmp " #func "                                      \n"     \
Quoted 4 lines

I think this would be nicer as an indirect call that gets patched to a direct call so that the update mechanism works even before it's initialized. (Currently static_branch blows up horribly if you try to update one too early, and that's rather annoying IMO.)

Quoted 4 lines

Yeah, that would be better. It would also allow trampoline function pointers to work, which I think you mentioned elsewhere. And then I shouldn't trample this code in __static_call_update() -- that was already kind of nasty anyway.

Re-reading your suggestion, I may have misunderstood what you're suggesting here, but I'm thinking about doing something like what you proposed earlier:

GLOBAL(tramp)
jmp *current_func(%rip)
ENDPROC(tramp)

That is, doing an indirect jump instead of the above direct jump, so that any previous references to the trampoline would still work (and it would also work during early boot).

Though it should probably be a retpoline instead of an indirect jump.

Signature · 1 line
Josh
author maintainer
#33 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 8 lines · On Wed, 10 Oct 2018 13:33:30 -0500 Josh Poimboeuf <[email protected]> wrote:

Re-reading your suggestion, I may have misunderstood what you're suggesting here, but I'm thinking about doing something like what you proposed earlier:

GLOBAL(tramp)
jmp *current_func(%rip)
ENDPROC(tramp)

Quoted 5 lines

That is, doing an indirect jump instead of the above direct jump, so that any previous references to the trampoline would still work (and it would also work during early boot).

Though it should probably be a retpoline instead of an indirect jump.

But do we care, as it only takes place during text_poke_bp() right?

I don't think we need to worry about training trampoline branch prediction that can only be hit when something enables the jump.

-- Steve

#34 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 15 lines · Steven Rostedt
Quoted 12 lines · On Wed, 10 Oct 2018 13:33:30 -0500 Josh Poimboeuf <[email protected]> wrote:

Re-reading your suggestion, I may have misunderstood what you're suggesting here, but I'm thinking about doing something like what you proposed earlier:

GLOBAL(tramp)
jmp *current_func(%rip)
ENDPROC(tramp)

That is, doing an indirect jump instead of the above direct jump, so that any previous references to the trampoline would still work (and it would also work during early boot).

Quoted 6 lines
Quoted 1 line

Though it should probably be a retpoline instead of an indirect jump.

But do we care, as it only takes place during text_poke_bp() right?

I don't think we need to worry about training trampoline branch prediction that can only be hit when something enables the jump.

Yeah, I guess it depends on if we'd expect anybody (or gcc) to get a function pointer to the trampoline itself. I can just create a warning for that in objtool.

Signature · 1 line
Josh
#35 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 19 lines · Josh Poimboeuf
Quoted 17 lines · Steven Rostedt
Quoted 13 lines · On Wed, 10 Oct 2018 13:33:30 -0500 Josh Poimboeuf <[email protected]> wrote:

Re-reading your suggestion, I may have misunderstood what you're suggesting here, but I'm thinking about doing something like what you proposed earlier:

GLOBAL(tramp)
jmp *current_func(%rip)
ENDPROC(tramp)

That is, doing an indirect jump instead of the above direct jump, so that any previous references to the trampoline would still work (and it would also work during early boot).

Though it should probably be a retpoline instead of an indirect jump.

Quoted 9 lines
Quoted 4 lines

But do we care, as it only takes place during text_poke_bp() right?

I don't think we need to worry about training trampoline branch prediction that can only be hit when something enables the jump.

Yeah, I guess it depends on if we'd expect anybody (or gcc) to get a function pointer to the trampoline itself. I can just create a warning for that in objtool.

The jmp * in the trampoline itself is harmless even with Spectre because it won't ever execute -- static_call_init() should just patch it out even if the actual call target is never updated. And gcc has no business generating any unprotected indirect branches to it from anywhere else, since, as far as gcc is concerned, they're just like indirect branches to any other function.

author maintainer
#36 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Peter Zijlstra
Quoted 9 lines · On Mon, 8 Oct 2018 17:57:57 +0200 Peter Zijlstra <[email protected]> wrote:
Quoted 7 lines · Andy Lutomirski
Quoted 2 lines

Can't we hijack the relocation records for these functions before they get thrown out in the (final) link pass or something?

I could be talking out my arse here, but I thought we could do this, too, then changed my mind. The relocation records give us the location of the call or jump operand, but they don’t give the address of the beginning of the instruction.

Quoted 6 lines

But that's like 1 byte before the operand, right? We could even double check this by reading back that byte and ensuring it is in fact 0xE8 (CALL).

AFAICT there is only the _1_ CALL encoding, and that is the 5 byte: E8 <PLT32>, so if we have the PLT32 location, we also have the instruction location. Or am I missing something?

Yes, this is exactly what I was thinking of doing. All we need to do is have objtool (or a modification of whatever we come up with), to find the call sites of a specific function (we can have a table to look up for), that creates a section listing all these call sites, and on boot up, we can confirm that they are indeed calls (e8 operations).

-- Steve

#37 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Steven Rostedt
Quoted 36 lines · Steven Rostedt
Quoted 9 lines · On Sat, 6 Oct 2018 14:12:11 +0200 Peter Zijlstra <[email protected]> wrote:

include/linux/jump_function.h line 18

Quoted 7 lines · Steven Rostedt
include/linux/jump_function.h new file +7 -0
+#define arch_dynfunc_trampoline(name, def) \
+   asm volatile (                          \
+   ".globl dynfunc_" #name "; \n\t"        \
+   "dynfunc_" #name ": \n\t"               \
+   "jmp " #def " \n\t"                     \
+   ".balign 8 \n \t"                       \
+   : : : "memory" )
Quoted 5 lines

Bah, what is it with you people and trampolines. Why can't we, just like jump_label, patch the call directly?

The whole call+jmp thing is silly, don't do that. It just wrecks I$ and is slower for no real reason afaict.

My first attempt was to do just that. But to add a label at the call site required handling all the parameters too. See my branch:
ftrace/jump_function-v1 for how ugly it got (and it didn't work).

Quoted 4 lines

Steve, also see:

https://lkml.kernel.org/r/[email protected]

Interesting. I don't have time to look at it at the moment to see what was done, but will do so in the near future.

Remember, this was a proof of concept and even with the trampolines, it showed a great level of improvement. One thought was to do a "recordmcount.c" type of action to find where the calls were and patch them directly at boot up. I tried to keep the API the same where this could actually be done as an improvement later.

Quoted 4 lines

Perhaps a gcc plugin might work too.

I'll have to see what Ard did to handle the function parameters.

I didn't. My approach is just a generic function pointer which can be overridden by the arch to be emitted as a trampoline instead.

Patching the call directly simply isn't feasible without compiler support like we have with asm goto for jump_labels.

The use case I am proposing is providing different implementations of crypto routines or CRC computation etc without having to rely on function pointers, but still keep them as loadable modules. These routines are a) heavy weight or we wouldn't bother providing alternatives in the first place, and b) likely to have a considerable I$ footprint already (and crypto libraries that have encrypt/decrypt/setkey or init/update/final entry points will end up with multiple trampolines in a single I-cache line). Also, the actual patching only occurs on module load/unload.

I have no idea whether this reasoning applies to Steven's use case as well, though, I haven't looked at his patches (I wasn't on cc)

maintainer
#38 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Steven Rostedt

include/linux/jump_function.h line 19

Quoted 12 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:
include/linux/jump_function.h new file +12 -0
+typedef long dynfunc_t;
+
+struct dynfunc_struct;
+
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )
+

I have just a question, what is this different from livepatch? :)

I think we can replace the first 5 bytes of the default function to jmp instruction (to alternative function) instead of making this trampoline.

IOW, as far as I can see, this is changing

----
call %reg (or retpoline_reg)
----

to

----
call dynfunc_A

dynfunc_A:
jmp func_A or altered_func_A
----

If so, why don't we put the jmp on default func_A directly?
----
call func_A

func_A:
"jmp altered_func" or "original sequence"
----
(this is idealy same as jprobes did)

Of course we have to arbitrate it with ftrace (fentry) but it may not so hard (simplest way is just adding "notrace" on the default function)

BTW, I think "dynamic_function" may not correct name, it may be "alternative_function" or something like that, because this function must be replaced system-wide and this means we can not use this for generic function pointer usage which depends on thread context (like file_operations). But good for something pluggable code (LSM?).

Thank you,

Signature · 1 line
Masami Hiramatsu <[email protected]>
author maintainer
#39 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 17 lines · On Tue, 9 Oct 2018 12:44:01 +0900 Masami Hiramatsu <[email protected]> wrote:

include/linux/jump_function.h line 19

Quoted 12 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:
include/linux/jump_function.h new file +12 -0
+typedef long dynfunc_t;
+
+struct dynfunc_struct;
+
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )
+  

I have just a question, what is this different from livepatch? :)

I actually thought about this a bit, but decided against it.

I didn't want to hook another infrastructure into the fentry nop. It's already complex enough with kprobes, live patching and ftrace.

The ideal solution is what Peter suggested, and that's to patch the call sites, and I think that is attainable with objtool modifications.

Quoted 24 lines

I think we can replace the first 5 bytes of the default function to jmp instruction (to alternative function) instead of making this trampoline.

IOW, as far as I can see, this is changing

----
call %reg (or retpoline_reg)
----

to

----
call dynfunc_A

dynfunc_A:
jmp func_A or altered_func_A
----

If so, why don't we put the jmp on default func_A directly?
----
call func_A

Quoted 8 lines

func_A:
"jmp altered_func" or "original sequence"
----
(this is idealy same as jprobes did)

Of course we have to arbitrate it with ftrace (fentry) but it may not so hard (simplest way is just adding "notrace" on the default function)

Then we lose the 5 byte nop.

Quoted 7 lines

BTW, I think "dynamic_function" may not correct name, it may be "alternative_function" or something like that, because this function must be replaced system-wide and this means we can not use this for generic function pointer usage which depends on thread context (like file_operations). But good for something pluggable code (LSM?).

I don't like the name alternative, as that's usually a one shot deal (SMP vs UP).

It is dynamic, as it's a function that changes dynamically. Yes its global, but that's not mutually exclusive to dynamic.

The use case I want this for is for tracing. But it can be useful for KVM and power management governors. Basically anything that has a global function pointer (hmm, even the idle call can use this).

-- Steve

maintainer
#40 lore raw

Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

Quoted 21 lines · On Mon, 8 Oct 2018 23:55:34 -0400 Steven Rostedt <[email protected]> wrote:
Quoted 17 lines · On Tue, 9 Oct 2018 12:44:01 +0900 Masami Hiramatsu <[email protected]> wrote:

include/linux/jump_function.h line 19

Quoted 12 lines · On Fri, 05 Oct 2018 21:51:11 -0400 Steven Rostedt <[email protected]> wrote:
include/linux/jump_function.h new file +12 -0
+typedef long dynfunc_t;
+
+struct dynfunc_struct;
+
+#define arch_dynfunc_trampoline(name, def)	\
+	asm volatile (				\
+	".globl dynfunc_" #name "; \n\t"	\
+	"dynfunc_" #name ": \n\t"		\
+	"jmp " #def " \n\t"			\
+	".balign 8 \n \t"			\
+	: : : "memory" )
+  

I have just a question, what is this different from livepatch? :)

Quoted 7 lines

I actually thought about this a bit, but decided against it.

I didn't want to hook another infrastructure into the fentry nop. It's already complex enough with kprobes, live patching and ftrace.

The ideal solution is what Peter suggested, and that's to patch the call sites, and I think that is attainable with objtool modifications.

OK, the ideal solution sounds good to me.

Quoted 30 lines
Quoted 30 lines

I think we can replace the first 5 bytes of the default function to jmp instruction (to alternative function) instead of making this trampoline.

IOW, as far as I can see, this is changing

----
call %reg (or retpoline_reg)
----

to

----
call dynfunc_A

dynfunc_A:
jmp func_A or altered_func_A
----

If so, why don't we put the jmp on default func_A directly?
----
call func_A

func_A:
"jmp altered_func" or "original sequence"
----
(this is idealy same as jprobes did)

Quoted 5 lines
Quoted 3 lines

Of course we have to arbitrate it with ftrace (fentry) but it may not so hard (simplest way is just adding "notrace" on the default function)

Then we lose the 5 byte nop.

Yeah, but we can remove the trampoline code.

Quoted 7 lines
Quoted 6 lines

BTW, I think "dynamic_function" may not correct name, it may be "alternative_function" or something like that, because this function must be replaced system-wide and this means we can not use this for generic function pointer usage which depends on thread context (like file_operations). But good for something pluggable code (LSM?).

Quoted 5 lines

I don't like the name alternative, as that's usually a one shot deal (SMP vs UP).

It is dynamic, as it's a function that changes dynamically. Yes its global, but that's not mutually exclusive to dynamic.

OK, so we may add a note that this is "global" patching :)

Quoted 3 lines

The use case I want this for is for tracing. But it can be useful for KVM and power management governors. Basically anything that has a global function pointer (hmm, even the idle call can use this).

Indeed.

Thanks,

Quoted 2 lines

-- Steve

Signature · 1 line
Masami Hiramatsu <[email protected]>
#41 lore raw

RE: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"

In reply to Masami Hiramatsu

From: Masami Hiramatsu

Quoted 1 line

Sent: 09 October 2018 04:44

...

Quoted 3 lines

I think we can replace the first 5 bytes of the default function to jmp instruction (to alternative function) instead of making this trampoline.

Or have a trampoline that is just a jump instruction and overwrite the target address at run time to select the non-default code. With care the target address can be aligned so that the write is atomic and can be done while other cpu might be calling the function.

This will be lower impact that a 'jump indirect' - especially since the latter would have to be implemented using a 'retpoline'.

It would also make it possible to re-instate the default function. (By saving its address after the jump.)

David

-
Registered Address Lakeside, Bramley Road, Mount Farm, Milton Keynes, MK1 1PT, UK Registration No: 1397386 (Wales)

author maintainer
#42 lore raw

RFC PATCH 2/2 tracepoints: Implement it with dynamic functions

In reply to Steven Rostedt

From: "Steven Rostedt (VMware)" <[email protected]>

---
 include/linux/tracepoint-defs.h |  3 ++
 include/linux/tracepoint.h      | 65 ++++++++++++++++++++++-----------
 include/trace/define_trace.h    | 14 +++----
 kernel/tracepoint.c             | 29 +++++++++++++--
 4 files changed, 79 insertions(+), 32 deletions(-)
include/linux/tracepoint-defs.h +3 -0
index 22c5a46e9693..a9d267be98de 100644
@@ -11,6 +11,8 @@
 #include <linux/atomic.h>
 #include <linux/static_key.h>
 
+struct dynfunc_struct;
+
 struct trace_print_flags {
 	unsigned long		mask;
 	const char		*name;
@@ -30,6 +32,7 @@ struct tracepoint_func {
 struct tracepoint {
 	const char *name;		/* Tracepoint name */
 	struct static_key key;
+	struct dynfunc_struct *dynfunc;
 	int (*regfunc)(void);
 	void (*unregfunc)(void);
 	struct tracepoint_func __rcu *funcs;
include/linux/tracepoint.h +44 -21

130 lines not on this page · raw message

include/trace/define_trace.h +7 -7
index cb30c5532144..c19aea44efb2 100644
@@ -25,7 +25,7 @@
 
 #undef TRACE_EVENT
 #define TRACE_EVENT(name, proto, args, tstruct, assign, print)	\
-	DEFINE_TRACE(name)
+	DEFINE_TRACE(name, PARAMS(proto), PARAMS(args))
 
 #undef TRACE_EVENT_CONDITION
 #define TRACE_EVENT_CONDITION(name, proto, args, cond, tstruct, assign, print) \
@@ -39,24 +39,24 @@
 #undef TRACE_EVENT_FN
 #define TRACE_EVENT_FN(name, proto, args, tstruct,		\
 		assign, print, reg, unreg)			\
-	DEFINE_TRACE_FN(name, reg, unreg)
+	DEFINE_TRACE_FN(name, reg, unreg, PARAMS(proto), PARAMS(args))
 
 #undef TRACE_EVENT_FN_COND
 #define TRACE_EVENT_FN_COND(name, proto, args, cond, tstruct,		\
 		assign, print, reg, unreg)			\
-	DEFINE_TRACE_FN(name, reg, unreg)
+	DEFINE_TRACE_FN(name, reg, unreg, PARAMS(proto), PARAMS(args))
 
 #undef DEFINE_EVENT
 #define DEFINE_EVENT(template, name, proto, args) \
-	DEFINE_TRACE(name)
+	DEFINE_TRACE(name, PARAMS(proto), PARAMS(args))
 
 #undef DEFINE_EVENT_FN
 #define DEFINE_EVENT_FN(template, name, proto, args, reg, unreg) \
-	DEFINE_TRACE_FN(name, reg, unreg)
+	DEFINE_TRACE_FN(name, reg, unreg, PARAMS(proto), PARAMS(args))
 
 #undef DEFINE_EVENT_PRINT
 #define DEFINE_EVENT_PRINT(template, name, proto, args, print)	\
-	DEFINE_TRACE(name)
+	DEFINE_TRACE(name, PARAMS(proto), PARAMS(args))
 
 #undef DEFINE_EVENT_CONDITION
 #define DEFINE_EVENT_CONDITION(template, name, proto, args, cond) \
@@ -64,7 +64,7 @@
 
 #undef DECLARE_TRACE
 #define DECLARE_TRACE(name, proto, args)	\
-	DEFINE_TRACE(name)
+	DEFINE_TRACE(name, PARAMS(proto), PARAMS(args))
 
 #undef TRACE_INCLUDE
 #undef __TRACE_INCLUDE
kernel/tracepoint.c +25 -4
index bf2c06ef9afc..b141f25d4b3a 100644
@@ -140,7 +140,7 @@ static void debug_print_probes(struct tracepoint_func *funcs)
 
 static struct tracepoint_func *
 func_add(struct tracepoint_func **funcs, struct tracepoint_func *tp_func,
-	 int prio)
+	 int prio, int *tot_probes)
 {
 	struct tracepoint_func *old, *new;
 	int nr_probes = 0;
@@ -183,11 +183,12 @@ func_add(struct tracepoint_func **funcs, struct tracepoint_func *tp_func,
 	new[nr_probes + 1].func = NULL;
 	*funcs = new;
 	debug_print_probes(*funcs);
+	*tot_probes = nr_probes + 1;
 	return old;
 }
 
 static void *func_remove(struct tracepoint_func **funcs,
-		struct tracepoint_func *tp_func)
+		struct tracepoint_func *tp_func, int *left)
 {
 	int nr_probes = 0, nr_del = 0, i;
 	struct tracepoint_func *old, *new;
@@ -241,6 +242,7 @@ static int tracepoint_add_func(struct tracepoint *tp,
 			       struct tracepoint_func *func, int prio)
 {
 	struct tracepoint_func *old, *tp_funcs;
+	int probes = 0;
 	int ret;
 
 	if (tp->regfunc && !static_key_enabled(&tp->key)) {
@@ -251,7 +253,7 @@ static int tracepoint_add_func(struct tracepoint *tp,
 
 	tp_funcs = rcu_dereference_protected(tp->funcs,
 			lockdep_is_held(&tracepoints_mutex));
-	old = func_add(&tp_funcs, func, prio);
+	old = func_add(&tp_funcs, func, prio, &probes);
 	if (IS_ERR(old)) {
 		WARN_ON_ONCE(PTR_ERR(old) != -ENOMEM);
 		return PTR_ERR(old);
@@ -266,6 +268,15 @@ static int tracepoint_add_func(struct tracepoint *tp,
 	rcu_assign_pointer(tp->funcs, tp_funcs);
 	if (!static_key_enabled(&tp->key))
 		static_key_slow_inc(&tp->key);
+
+	if (probes == 1) {
+//		printk("make direct call to %pS\n", tp_funcs->func);
+		assign_dynamic_function(tp->dynfunc, tp_funcs->func);
+	} else {
+//		printk("[%d] make call to iterator %pS\n", probes, tp->dynfunc->func);
+		assign_dynamic_function(tp->dynfunc, tp->dynfunc->func);
+	}
+
 	release_probes(old);
 	return 0;
 }
@@ -280,10 +291,11 @@ static int tracepoint_remove_func(struct tracepoint *tp,
 		struct tracepoint_func *func)
 {
 	struct tracepoint_func *old, *tp_funcs;
+	int probes_left = 0;
 
 	tp_funcs = rcu_dereference_protected(tp->funcs,
 			lockdep_is_held(&tracepoints_mutex));
-	old = func_remove(&tp_funcs, func);
+	old = func_remove(&tp_funcs, func, &probes_left);
 	if (IS_ERR(old)) {
 		WARN_ON_ONCE(PTR_ERR(old) != -ENOMEM);
 		return PTR_ERR(old);
@@ -297,6 +309,15 @@ static int tracepoint_remove_func(struct tracepoint *tp,
 		if (static_key_enabled(&tp->key))
 			static_key_slow_dec(&tp->key);
 	}
+
+	if (probes_left == 1) {
+//		printk("make direct call to %pS\n", tp_funcs->func);
+		assign_dynamic_function(tp->dynfunc, tp_funcs->func);
+	} else {
+//		printk("[%d] make call to iterator %pS\n", probes_left, tp->dynfunc->func);
+		assign_dynamic_function(tp->dynfunc, tp->dynfunc->func);
+	}
+
 	rcu_assign_pointer(tp->funcs, tp_funcs);
 	release_probes(old);
 	return 0;
Signature · 1 line
2.19.0