PROOF OF CONCEPT: Dynamic Functions (jump functions)
Subsystems (6)
Steven Rostedt
maintainercover 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
Steven Rostedt
maintainerRFC 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
@@ -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
@@ -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
Steven Rostedt
maintainerRe: [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]>
Signed-off-by 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.cinclude/asm-generic/vmlinux.lds.h +0
-0
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
Steven Rostedt
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Steven Rostedtinclude/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
Steven Rostedt
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Steven Rostedtinclude/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
Steven Rostedt
maintainerRe: [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
Peter Zijlstra
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Steven Rostedtinclude/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:
Steven Rostedt
maintainerRe: [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:
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
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:
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
Steven Rostedt
maintainerRe: [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
Peter Zijlstra
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Steven RostedtQuoted 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?
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?
Peter Zijlstra
maintainerRe: [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?
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.
Steven Rostedt
maintainerRe: [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
Peter Zijlstra
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Andy LutomirskiQuoted 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.
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?
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 onfoo()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?
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
Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Ard BiesheuvelQuoted 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
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 :-)
Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Jiri KosinaQuoted 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.
Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Andy LutomirskiQuoted 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
Steven Rostedt
maintainerRe: [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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -62,6 +62,7 @@ struct symbol {unsigned long offset;unsigned int len;struct symbol *pfunc, *cfunc; +bool static_call_tramp; };struct rela {
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_coresintext_poke_bp()don't always seem to be working as expected. Running this patch on my VM, the test code incmdline_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@@ -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@@ -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_HUGEPAGEarch/x86/include/asm/static_call.h new file +6
-0@@ -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 :)
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_branchblows 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
0xe9or 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
0xebdue to the assembler messing you up (see above), but this seems a bit too permissive, since a0xebwithout 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
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
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 :)
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -62,6 +62,7 @@ struct symbol {unsigned long offset;unsigned int len;struct symbol *pfunc, *cfunc; +bool static_call_tramp; };struct rela {
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
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_CALLdoesn'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.
Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Josh PoimboeufQuoted 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_branchblows 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
Steven Rostedt
maintainerRe: [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
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
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.
Steven Rostedt
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Peter ZijlstraQuoted 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
Re: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Steven RostedtQuoted 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:
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)
Masami Hiramatsu
maintainerRe: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Steven Rostedtinclude/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]>
Steven Rostedt
maintainerRe: [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 (orretpoline_reg)
----to
----
calldynfunc_A
dynfunc_A:
jmpfunc_Aoraltered_func_A
----If so, why don't we put the jmp on default
func_Adirectly?
----
callfunc_A
Quoted 8 lines
func_A:
"jmpaltered_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 (likefile_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
Masami Hiramatsu
maintainerRe: [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 (orretpoline_reg)
----to
----
calldynfunc_A
dynfunc_A:
jmpfunc_Aoraltered_func_A
----If so, why don't we put the jmp on default
func_Adirectly?
----
callfunc_A
func_A:
"jmpaltered_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 (likefile_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]>
RE: [POC][RFC][PATCH 1/2] jump_function: Addition of new feature "jump_function"
In reply to Masami HiramatsuFrom: 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)
Steven Rostedt
maintainerRFC PATCH 2/2 tracepoints: Implement it with dynamic functions
In reply to Steven RostedtFrom: "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
@@ -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
@@ -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
@@ -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