Skip to contentSkip to conversation

jump-label: allow early jump_label_enable()

RFC PATCH Superseded Jeremy Fitzhardinge 8 participants 53 messages Oct 1, 2011 – Oct 10, 2011

Subsystems (4)

v2 of 4 Superseded Latest is v4 →

lore
Share & tools
lore
#0 lore raw

v2 cover jump-label: allow early jump_label_enable()

From: Jeremy Fitzhardinge <[email protected]>

[ This is a new version of this series, which drops the need for any
special _early() function in favour of making sure stop_machine() can operate correctly and efficiently in a pre-SMP envronment. ]

While trying to use the jump-label stuff for my PV ticketlock changes, I had some problems using jump labels early in the kernel's lifetime (pre-SMP).

The basic problem is that even if I enable a jump_label_key, the jump_label_init() initializer ends up nopping out all the code sites.

This series enables early use of jump labels by making jump_label_init() respect already-enabled keys.

To do this, I've dropped arch_jump_label_poke_text_early() and replaced it with arch_jump_label_transform(), allowing it to either insert an optimal nop, or leave the jump in place.

Part of this series makes sure that stop_machine() is safe to call in an early pre-SMP environment, by making it just call the function with interrupts disabled.

git://github.com/jsgf/linux-xen upstream/jump-label-noearly

Jeremy Fitzhardinge (5):
jump_label: use proper atomic_t initializer
stop_machine: make stop_machine safe and efficient to call early
jump_label: if a key has already been initialized, don't nop it out x86/jump_label: drop arch_jump_label_text_poke_early() sparc/jump_label: drop arch_jump_label_text_poke_early()

 arch/sparc/kernel/jump_label.c |    8 --------
 arch/x86/kernel/jump_label.c   |    6 ------
 include/linux/jump_label.h     |    7 ++++---
 kernel/jump_label.c            |   20 ++++++++------------
 kernel/stop_machine.c          |   21 +++++++++++++++++++++
 5 files changed, 33 insertions(+), 29 deletions(-)
Signature · 1 line
1.7.6.2
#1 lore raw

RFC PATCH v2 1/5 jump_label: use proper atomic_t initializer

From: Jeremy Fitzhardinge <[email protected]>

ATOMIC_INIT() is the proper thing to use.

---
 include/linux/jump_label.h |    4 ++--
 1 files changed, 2 insertions(+), 2 deletions(-)
include/linux/jump_label.h +2 -2
index 66f23dc..1213e9d 100644
@@ -28,9 +28,9 @@ struct module;
 #ifdef HAVE_JUMP_LABEL
 
 #ifdef CONFIG_MODULES
-#define JUMP_LABEL_INIT {{ 0 }, NULL, NULL}
+#define JUMP_LABEL_INIT {ATOMIC_INIT(0), NULL, NULL}
 #else
-#define JUMP_LABEL_INIT {{ 0 }, NULL}
+#define JUMP_LABEL_INIT {ATOMIC_INIT(0), NULL}
 #endif
 
 static __always_inline bool static_branch(struct jump_label_key *key)
Signature · 1 line
1.7.6.2
#2 lore raw

RFC PATCH v2 2/5 stop_machine: make stop_machine safe and efficient to call early

In reply to Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

Make stop_machine() safe to call early in boot, before SMP has been set up, by simply calling the callback function directly if there's only one CPU online.

[ Fixes from AKPM:
- add comment
- local_irq_flags, not save_flags
- also call hard_irq_disable() for systems which need it

Tejun suggested using an explicit flag rather than just looking at the online cpu count. ]

Cc Tejun Heo <[email protected]>
Cc Rusty Russell <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Andrew Morton <[email protected]>
Cc H. Peter Anvin <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Steven Rostedt <[email protected]>
---
 kernel/stop_machine.c |   21 +++++++++++++++++++++
 1 files changed, 21 insertions(+), 0 deletions(-)
kernel/stop_machine.c +21 -0
index ba5070c..9c59d9e 100644
@@ -41,6 +41,7 @@ struct cpu_stopper {
 };
 
 static DEFINE_PER_CPU(struct cpu_stopper, cpu_stopper);
+static bool stop_machine_initialized = false;
 
 static void cpu_stop_init_done(struct cpu_stop_done *done, unsigned int nr_todo)
 {
@@ -386,6 +387,8 @@ static int __init cpu_stop_init(void)
 	cpu_stop_cpu_callback(&cpu_stop_cpu_notifier, CPU_ONLINE, bcpu);
 	register_cpu_notifier(&cpu_stop_cpu_notifier);
 
+	stop_machine_initialized = true;
+
 	return 0;
 }
 early_initcall(cpu_stop_init);
@@ -485,6 +488,24 @@ int __stop_machine(int (*fn)(void *), void *data, const struct cpumask *cpus)
 					    .num_threads = num_online_cpus(),
 					    .active_cpus = cpus };
 
+	if (!stop_machine_initialized) {
+		/*
+		 * Handle the case where stop_machine() is called early in boot
+		 * before SMP startup.
+		 */
+ 		unsigned long flags;
+		int ret;
+
+		WARN_ON_ONCE(smdata.num_threads != 1);
+
+		local_irq_save(flags);
+		hard_irq_disable();
+		ret = (*fn)(data);
+		local_irq_restore(flags);
+
+		return ret;
+	}
+
 	/* Set the initial state and stop all online cpus. */
 	set_state(&smdata, STOPMACHINE_PREPARE);
 	return stop_cpus(cpu_online_mask, stop_machine_cpu_stop, &smdata);
Signature · 1 line
1.7.6.2
Acked-by on v2 2/5
#3 lore raw

Re: [PATCH RFC V2 2/5] stop_machine: make stop_machine safe and efficient to call early

Hello,

Quoted 5 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

Make stop_machine() safe to call early in boot, before SMP has been set up, by simply calling the callback function directly if there's only one CPU online.

Maybe "before stop_machine is initialized" is better wording now both here and in the comment?

Quoted 8 lines

[ Fixes from AKPM:
- add comment
- local_irq_flags, not save_flags
- also call hard_irq_disable() for systems which need it

Tejun suggested using an explicit flag rather than just looking at the online cpu count. ]

Quoted 8 lines
Cc Tejun Heo <[email protected]>
Cc Rusty Russell <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Andrew Morton <[email protected]>
Cc H. Peter Anvin <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Steven Rostedt <[email protected]>

Other than that,

Thanks.

Signature · 1 line
tejun
#4 lore raw

Re: [Xen-devel] [PATCH RFC V2 2/5] stop_machine: make stop_machine safe and efficient to call early

In reply to Jeremy Fitzhardinge
Quoted 31 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

Make stop_machine() safe to call early in boot, before SMP has been set up, by simply calling the callback function directly if there's only one CPU online.

[ Fixes from AKPM:
- add comment
- local_irq_flags, not save_flags
- also call hard_irq_disable() for systems which need it

Tejun suggested using an explicit flag rather than just looking at the online cpu count. ]

Cc Tejun Heo <[email protected]>
Cc Rusty Russell <[email protected]>
Cc Peter Zijlstra <[email protected]>
Cc Andrew Morton <[email protected]>
Cc H. Peter Anvin <[email protected]>
Cc Ingo Molnar <[email protected]>
Cc Steven Rostedt <[email protected]>
---
 kernel/stop_machine.c |   21 +++++++++++++++++++++
 1 files changed, 21 insertions(+), 0 deletions(-)
kernel/stop_machine.c +0 -0
index ba5070c..9c59d9e 100644

kernel/stop_machine.c struct cpu_stopper { · line 44

Quoted 4 lines
kernel/stop_machine.c +1 -0
@@ -41,6 +41,7 @@ struct cpu_stopper {
 };
 
 static DEFINE_PER_CPU(struct cpu_stopper, cpu_stopper);
+static bool stop_machine_initialized = false;

__read_mostly?

Thought it probably does not really matter that much in what section it is put in.

#5 lore raw

RFC PATCH v2 3/5 jump_label: if a key has already been initialized, don't nop it out

In reply to Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +8 -12
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2
#6 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Hi,

(Sorry for the late reply - I was away for a few days).

The early enable is really nice - it means there are not restrictions on when jump_label_inc()/dec() can be called which is nice.

comments below.

Quoted 47 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +0 -7
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;

kernel/jump_label.c static __init int jump_label_init(void) · line 139

Quoted 10 lines
kernel/jump_label.c +5 -2
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);

The only reason I called this at boot-time was that the 'ideal' x86 no-op isn't known until boot time. Thus, in the enabled case we could skip the the arch_jump_label_transform() call. ie:

if (!enabled)

	arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
Quoted 21 lines
kernel/jump_label.c +3 -3
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

hmmm...this is used on module load in smp - so this would introduce a number of calls to stop_machine() where we didn't have them before. Yes, module load is a very slow path to begin with, but I think its at least worth pointing out...

thanks,

-Jason

#7 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 21 lines · Jason Baron

if (!enabled)

	arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
Quoted 17 lines
kernel/jump_label.c +3 -3
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
Quoted 9 lines
Quoted 4 lines
kernel/jump_label.c +0 -0
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

hmmm...this is used on module load in smp - so this would introduce a number of calls to stop_machine() where we didn't have them before. Yes, module load is a very slow path to begin with, but I think its at least worth pointing out...

And it is a good point to point out. As stop_machine becomes noticeable by users on large scale CPU boxes. Ideally, we want to avoid stopmachine when we do not need it.

-- Steve

#8 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to Jason Baron
Quoted 72 lines · Jason Baron

Hi,

(Sorry for the late reply - I was away for a few days).

The early enable is really nice - it means there are not restrictions on when jump_label_inc()/dec() can be called which is nice.

comments below.

Quoted 47 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +0 -7
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;

kernel/jump_label.c static __init int jump_label_init(void) · line 139

Quoted 10 lines
kernel/jump_label.c +5 -2
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);

The only reason I called this at boot-time was that the 'ideal' x86 no-op isn't known until boot time. Thus, in the enabled case we could skip the the arch_jump_label_transform() call. ie:

Quoted 2 lines

if (!enabled)

	arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);

Yep, fair enough.

Quoted 19 lines
Quoted 19 lines
kernel/jump_label.c +3 -3
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
Quoted 8 lines
Quoted 4 lines
kernel/jump_label.c +0 -0
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

hmmm...this is used on module load in smp - so this would introduce a number of calls to stop_machine() where we didn't have them before. Yes, module load is a very slow path to begin with, but I think its at least worth pointing out...

Ah, that explains it - the module stuff certainly isn't "early" except - I guess - in the module's lifetime.

Well, I suppose I could introduce either second variant of the function, or add a "live" flag (ie, may be updating code that a processor is executing), which requires a stop_machine, or direct update if it doesn't.

But is there any reason why we couldn't just generate a reasonably efficient 5-byte atomic nop in the first place, and get rid of all that fooling around? It looks like x86 is the only arch where it makes any difference at all, and how much difference does it really make? Or is there no one 5-byte atomic nop that works on all x86 variants, aside from jmp +0?

J

#9 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 114 lines · Jeremy Fitzhardinge
Quoted 72 lines · Jason Baron

Hi,

(Sorry for the late reply - I was away for a few days).

The early enable is really nice - it means there are not restrictions on when jump_label_inc()/dec() can be called which is nice.

comments below.

Quoted 47 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +0 -7
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;

kernel/jump_label.c static __init int jump_label_init(void) · line 139

Quoted 10 lines
kernel/jump_label.c +5 -2
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);

The only reason I called this at boot-time was that the 'ideal' x86 no-op isn't known until boot time. Thus, in the enabled case we could skip the the arch_jump_label_transform() call. ie:

Quoted 2 lines

if (!enabled)

	arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);

Yep, fair enough.

Quoted 19 lines
Quoted 19 lines
kernel/jump_label.c +3 -3
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
Quoted 8 lines
Quoted 4 lines
kernel/jump_label.c +0 -0
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

hmmm...this is used on module load in smp - so this would introduce a number of calls to stop_machine() where we didn't have them before. Yes, module load is a very slow path to begin with, but I think its at least worth pointing out...

Ah, that explains it - the module stuff certainly isn't "early" except - I guess - in the module's lifetime.

Well, I suppose I could introduce either second variant of the function, or add a "live" flag (ie, may be updating code that a processor is executing), which requires a stop_machine, or direct update if it doesn't.

Quoted 8 lines

But is there any reason why we couldn't just generate a reasonably efficient 5-byte atomic nop in the first place, and get rid of all that fooling around? It looks like x86 is the only arch where it makes any difference at all, and how much difference does it really make? Or is there no one 5-byte atomic nop that works on all x86 variants, aside from jmp +0?

J

Yes, there are really two reasons for the initial no-op patching pass:

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

2) This optimization is about as close to a 0 cost off case as possible. I know there have been various no-op benchmarks posted on lkml in the past, so the choice of no-op does seem to make a difference. see:
http://lkml.indiana.edu/hypermail/linux/kernel/0808.1/2416.html, for example. So at least to me, if we are not using the lowest cost no-op, we are at least in-part defeating the point of this optimization.

I like the "live" flag suggestion mentioned above. Less functions is better, and non-x86 arches can simply ignore the flag.

Thanks,

-Jason

#10 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 134 lines · Jason Baron
Quoted 111 lines · Jeremy Fitzhardinge
Quoted 72 lines · Jason Baron

Hi,

(Sorry for the late reply - I was away for a few days).

The early enable is really nice - it means there are not restrictions on when jump_label_inc()/dec() can be called which is nice.

comments below.

Quoted 47 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +0 -7
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;

kernel/jump_label.c static __init int jump_label_init(void) · line 139

Quoted 10 lines
kernel/jump_label.c +5 -2
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);

The only reason I called this at boot-time was that the 'ideal' x86 no-op isn't known until boot time. Thus, in the enabled case we could skip the the arch_jump_label_transform() call. ie:

Quoted 2 lines

if (!enabled)

	arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);

Yep, fair enough.

Quoted 18 lines
Quoted 18 lines
kernel/jump_label.c +3 -3
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
Quoted 8 lines
Quoted 4 lines
kernel/jump_label.c +0 -0
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

hmmm...this is used on module load in smp - so this would introduce a number of calls to stop_machine() where we didn't have them before. Yes, module load is a very slow path to begin with, but I think its at least worth pointing out...

Ah, that explains it - the module stuff certainly isn't "early" except - I guess - in the module's lifetime.

Well, I suppose I could introduce either second variant of the function, or add a "live" flag (ie, may be updating code that a processor is executing), which requires a stop_machine, or direct update if it doesn't.

Quoted 8 lines

But is there any reason why we couldn't just generate a reasonably efficient 5-byte atomic nop in the first place, and get rid of all that fooling around? It looks like x86 is the only arch where it makes any difference at all, and how much difference does it really make? Or is there no one 5-byte atomic nop that works on all x86 variants, aside from jmp +0?

J

Yes, there are really two reasons for the initial no-op patching pass:

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

2) This optimization is about as close to a 0 cost off case as possible. I know there have been various no-op benchmarks posted on lkml in the past, so the choice of no-op does seem to make a difference. see:
http://lkml.indiana.edu/hypermail/linux/kernel/0808.1/2416.html, for example. So at least to me, if we are not using the lowest cost no-op, we are at least in-part defeating the point of this optimization.

Quoted 2 lines

I like the "live" flag suggestion mentioned above. Less functions is better, and non-x86 arches can simply ignore the flag.

I went the other way and added a second function, arch_jump_label_transform_static(), which has a weak default implementation which calls arch_jump_label_transform(). That way only the architectures which really care about it need implement a second variant. I did x86 and s390 by adapting the patches I had from the other series; it didn't look like mips/sparc/power were very heavyweight at all.

J

#11 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to Jason Baron
Quoted 6 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

There are *plenty*. jmp+0 is about as pessimal as you can get.

The current recommendation when you don't know the CPU you're running at is:

	3E 8D 74 26 00	(GENERIC_NOP5_ATOMIC)

... on 32 bits and ...

	0F 1F 44 00 00	(P6_NOP5_ATOMIC)

... on 64 bits.

-hpa

Signature · 2 lines
H. Peter Anvin, Intel Open Source Technology Center
I work for Intel.  I don't speak on their behalf.
#12 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 14 lines · H. Peter Anvin
Quoted 6 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

There are *plenty*. jmp+0 is about as pessimal as you can get.

The current recommendation when you don't know the CPU you're running at is:

	3E 8D 74 26 00	(GENERIC_NOP5_ATOMIC)
Quoted 8 lines

... on 32 bits and ...

	0F 1F 44 00 00	(P6_NOP5_ATOMIC)

... on 64 bits.

-hpa

We're currently patching the code at run-time (boot and module load time), with the 'ideal' no-op anyway, so the initial no-op doesn't really matter much (other than to save patching if the initial and ideal match).

-Jason

#13 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 21 lines · Jason Baron
Quoted 20 lines · H. Peter Anvin
Quoted 6 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

There are *plenty*. jmp+0 is about as pessimal as you can get.

The current recommendation when you don't know the CPU you're running at is:

	3E 8D 74 26 00	(GENERIC_NOP5_ATOMIC)

... on 32 bits and ...

	0F 1F 44 00 00	(P6_NOP5_ATOMIC)

... on 64 bits.

Quoted 7 lines
Quoted 2 lines

-hpa

We're currently patching the code at run-time (boot and module load time), with the 'ideal' no-op anyway, so the initial no-op doesn't really matter much (other than to save patching if the initial and ideal match).

Out of correctness, we should still update this to use the proper "default" nops, as mcount already does.

-- Steve

#14 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to H. Peter Anvin
Quoted 7 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

J

#15 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 7 lines · Jeremy Fitzhardinge
Quoted 6 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

Quoted 6 lines
Quoted 1 line

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

I don't know for sure, no. I probably depends on the CPU.

-hpa

Signature · 2 lines
H. Peter Anvin, Intel Open Source Technology Center
I work for Intel.  I don't speak on their behalf.
#16 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 8 lines · H. Peter Anvin
Quoted 7 lines · Jeremy Fitzhardinge
Quoted 6 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

Quoted 6 lines
Quoted 5 lines
Quoted 1 line

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

I don't know for sure, no. I probably depends on the CPU.

I was thinking about making the jump-label stuff generate a small jmp if the offset is small (specifically "jmp; nop3", or perhaps "jmp; ud2a; nop" to make absolutely sure there's no speculation beyond the jmp), on the grounds that, while it probably doesn't matter for any modern Intel/AMD processor, it may help for others. But I couldn't find any concrete evidence to support it, and there's already enough questions about doing "live" code updates without adding more instruction patterns.

J

#17 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to H. Peter Anvin
Quoted 8 lines · H. Peter Anvin
Quoted 7 lines · Jeremy Fitzhardinge
Quoted 6 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

Quoted 6 lines
Quoted 5 lines
Quoted 1 line

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

I don't know for sure, no. I probably depends on the CPU.

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

J

#include <stdio.h>

struct {
	unsigned char flag;
	unsigned char val;
} l;

#define JMP2 asm volatile ("jmp 1f; .byte 0x0f,0x1f,0x00; 1: ");
#define JMPJMP2 JMP2 JMP2
#define JMPJMPJMP2 JMPJMP2 JMPJMP2
#define JMPJMPJMP2 JMPJMP2 JMPJMP2
#define JMPJMPJMPJMP2 JMPJMPJMP2 JMPJMPJMP2
#define JMPJMPJMPJMPJMP2 JMPJMPJMPJMP2 JMPJMPJMPJMP2
#define JMPJMPJMPJMPJMPJMP2 JMPJMPJMPJMPJMP2 JMPJMPJMPJMPJMP2

int main(int argc, char **argv)

{
	int i;

	for (i = 0; i < 100000000; i++) {
		JMPJMPJMPJMPJMPJMP2;
		asm volatile("" : : : "memory");
	}

	return 0;
}

#include <stdio.h>

struct {
	unsigned char flag;
	unsigned char val;
} l;

#define JMP5 asm volatile (".byte 0xe9; .long 0");
#define JMPJMP5 JMP5 JMP5
#define JMPJMPJMP5 JMPJMP5 JMPJMP5
#define JMPJMPJMP5 JMPJMP5 JMPJMP5
#define JMPJMPJMPJMP5 JMPJMPJMP5 JMPJMPJMP5
#define JMPJMPJMPJMPJMP5 JMPJMPJMPJMP5 JMPJMPJMPJMP5
#define JMPJMPJMPJMPJMPJMP5 JMPJMPJMPJMPJMP5 JMPJMPJMPJMPJMP5

int main(int argc, char **argv)

{
	int i;

	for (i = 0; i < 100000000; i++) {
		JMPJMPJMPJMPJMPJMP5;
		asm volatile("" : : : "memory");
	}

	return 0;
}
#18 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 14 lines · Jeremy Fitzhardinge
Quoted 13 lines · H. Peter Anvin
Quoted 12 lines · Jeremy Fitzhardinge
Quoted 7 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

Quoted 8 lines
Quoted 1 line

I don't know for sure, no. I probably depends on the CPU.

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

J

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

thanks,

-Jason

Quoted 53 lines

#include <stdio.h>

struct {
	unsigned char flag;
	unsigned char val;
} l;

#define JMP2 asm volatile ("jmp 1f; .byte 0x0f,0x1f,0x00; 1: ");
#define JMPJMP2 JMP2 JMP2
#define JMPJMPJMP2 JMPJMP2 JMPJMP2
#define JMPJMPJMP2 JMPJMP2 JMPJMP2
#define JMPJMPJMPJMP2 JMPJMPJMP2 JMPJMPJMP2
#define JMPJMPJMPJMPJMP2 JMPJMPJMPJMP2 JMPJMPJMPJMP2
#define JMPJMPJMPJMPJMPJMP2 JMPJMPJMPJMPJMP2 JMPJMPJMPJMPJMP2

int main(int argc, char **argv)

{
	int i;

	for (i = 0; i < 100000000; i++) {
		JMPJMPJMPJMPJMPJMP2;
		asm volatile("" : : : "memory");
	}

	return 0;
}

#include <stdio.h>

struct {
	unsigned char flag;
	unsigned char val;
} l;

#define JMP5 asm volatile (".byte 0xe9; .long 0");
#define JMPJMP5 JMP5 JMP5
#define JMPJMPJMP5 JMPJMP5 JMPJMP5
#define JMPJMPJMP5 JMPJMP5 JMPJMP5
#define JMPJMPJMPJMP5 JMPJMPJMP5 JMPJMPJMP5
#define JMPJMPJMPJMPJMP5 JMPJMPJMPJMP5 JMPJMPJMPJMP5
#define JMPJMPJMPJMPJMPJMP5 JMPJMPJMPJMPJMP5 JMPJMPJMPJMPJMP5

int main(int argc, char **argv)

{
	int i;

	for (i = 0; i < 100000000; i++) {
		JMPJMPJMPJMPJMPJMP5;
		asm volatile("" : : : "memory");
	}

	return 0;
}
#19 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 7 lines · Jason Baron

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

Yes, that would be a win just based on icache footprint alone.

-hpa

Signature · 2 lines
H. Peter Anvin, Intel Open Source Technology Center
I work for Intel.  I don't speak on their behalf.
#20 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 8 lines · H. Peter Anvin
Quoted 6 lines · Jason Baron

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

Yes, that would be a win just based on icache footprint alone.

I'm not sure it would be a win, necessarily. My test with back-to-back jmp2 was definitely slower than with the nop padding it out to 5 bytes; I suspect that's a result of having too many jmps within one cacheline. Of course, there's no reason why the CPU would optimise for jumps to jumps, so perhaps its just hitting a "stupid programmer" path.

J

#21 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 5 lines · Jeremy Fitzhardinge

I'm not sure it would be a win, necessarily. My test with back-to-back jmp2 was definitely slower than with the nop padding it out to 5 bytes; I suspect that's a result of having too many jmps within one cacheline. Of course, there's no reason why the CPU would optimise for jumps to jumps, so perhaps its just hitting a "stupid programmer" path.

Just a note. Micro-benchmarks are known to be as good as political polls and statistics. They show a much different view of the world than what is really there.

-- Steve

#22 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to Jason Baron
Quoted 19 lines · Jason Baron
Quoted 18 lines · Jeremy Fitzhardinge
Quoted 8 lines · H. Peter Anvin
Quoted 7 lines · Jeremy Fitzhardinge
Quoted 6 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

Quoted 6 lines
Quoted 5 lines
Quoted 1 line

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

I don't know for sure, no. I probably depends on the CPU.

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

Quoted 8 lines
Quoted 3 lines

But insignificant difference on Nehalem.

J

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

I'll have a look at it later today if I get a moment. Should be fairly straightforward.

What about the rest of the series. Do you think it looks cooked enough for next mergewindow?

J

#23 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 23 lines · Jeremy Fitzhardinge
Quoted 22 lines · Jason Baron
Quoted 21 lines · Jeremy Fitzhardinge
Quoted 8 lines · H. Peter Anvin
Quoted 7 lines · Jeremy Fitzhardinge
Quoted 6 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

1) The jmp +0, is a 'safe' no-op that I know is going to initially boot for all x86. I'm not sure if there is a 5-byte nop that works on all x86 variants - but by using jmp +0, we make it much easier to debug cases where we may be using broken no-ops.

Quoted 6 lines
Quoted 5 lines
Quoted 1 line

There are *plenty*. jmp+0 is about as pessimal as you can get.

As an aside, do you know if a 2-byte unconditional jmp is any more efficient than 5-byte, aside from just being a smaller instruction and taking less icache?

I don't know for sure, no. I probably depends on the CPU.

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

J

Quoted 9 lines
Quoted 5 lines

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

I'll have a look at it later today if I get a moment. Should be fairly straightforward.

cool. It does add some complication, I think...detecting the 2-byte vs. 5-byte, and if done at boot time, possibly taking the undesired branch...

Quoted 4 lines

What about the rest of the series. Do you think it looks cooked enough for next mergewindow?

J

Yes, it looks good to me thanks! Feel free to add my ack to the series.

thanks,

-Jason

#24 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 5 lines · Jason Baron

cool. It does add some complication, I think...detecting the 2-byte vs. 5-byte, and if done at boot time, possibly taking the undesired branch...

It has to be done at build time to be sane, IMO.

-hpa

Signature · 2 lines
H. Peter Anvin, Intel Open Source Technology Center
I work for Intel.  I don't speak on their behalf.
#25 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 11 lines · H. Peter Anvin
Quoted 5 lines · Jason Baron

cool. It does add some complication, I think...detecting the 2-byte vs. 5-byte, and if done at boot time, possibly taking the undesired branch...

It has to be done at build time to be sane, IMO.

-hpa

agreed.

-Jason

#26 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to Jason Baron
Quoted 5 lines · Jason Baron
Quoted 5 lines

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

Quoted 7 lines
Quoted 1 line

J

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

Why not just do it?

jump_label is encapsulated in arch_static_branch() which on x86 looks like:

static __always_inline bool arch_static_branch(struct jump_label_key *key)

{
	asm goto("1:"
		JUMP_LABEL_INITIAL_NOP
		".pushsection __jump_table,  \"aw\" \n\t"
		_ASM_ALIGN "\n\t"
		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
		".popsection \n\t"
		: :  "i" (key) : : l_yes);
	return false;
l_yes:
	return true;
}

That jmp to l_yes should easily be a two byte jump.

If not I'm sure it would be easy to catch it before modifying the code. And then complain real loudly about it.

-- Steve

#27 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 35 lines · Steven Rostedt
Quoted 5 lines · Jason Baron
Quoted 5 lines

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

Quoted 7 lines
Quoted 1 line

J

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

Why not just do it?

jump_label is encapsulated in arch_static_branch() which on x86 looks like:

static __always_inline bool arch_static_branch(struct jump_label_key *key)

{
	asm goto("1:"
		JUMP_LABEL_INITIAL_NOP
		".pushsection __jump_table,  \"aw\" \n\t"
		_ASM_ALIGN "\n\t"
		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
		".popsection \n\t"
		: :  "i" (key) : : l_yes);
	return false;
l_yes:
	return true;
}
Quoted 5 lines

That jmp to l_yes should easily be a two byte jump.

If not I'm sure it would be easy to catch it before modifying the code. And then complain real loudly about it.

The important thing is that it requires the build-time elimination of jumps. It's just work.

-hpa

Signature · 2 lines
H. Peter Anvin, Intel Open Source Technology Center
I work for Intel.  I don't speak on their behalf.
#28 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 36 lines · H. Peter Anvin
Quoted 35 lines · Steven Rostedt
Quoted 5 lines · Jason Baron
Quoted 5 lines

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

Quoted 7 lines
Quoted 1 line

J

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

Why not just do it?

jump_label is encapsulated in arch_static_branch() which on x86 looks like:

static __always_inline bool arch_static_branch(struct jump_label_key *key)

{
	asm goto("1:"
		JUMP_LABEL_INITIAL_NOP
		".pushsection __jump_table,  \"aw\" \n\t"
		_ASM_ALIGN "\n\t"
		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
		".popsection \n\t"
		: :  "i" (key) : : l_yes);
	return false;
l_yes:
	return true;
}
Quoted 1 line
Quoted 1 line

That jmp to l_yes should easily be a two byte jump.

remember the compiler is moving the l_yes out of line, so its not necessarily always a two byte jump. Also, I plan to look at a possible 'cold' label for the 'l_yes' branch, so that it can moved to a separate 'cold' section, but we might only want that for some cases...

Quoted 10 lines
Quoted 4 lines

If not I'm sure it would be easy to catch it before modifying the code. And then complain real loudly about it.

The important thing is that it requires the build-time elimination of jumps. It's just work.

-hpa

Right, its certainly doable, but I'm not sure its so simple, since we'll need a pass to eliminate the jumps - which can be keyed off the '__jump_table' section.

thanks,

-Jason

#29 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 3 lines · Jason Baron

Right, its certainly doable, but I'm not sure its so simple, since we'll need a pass to eliminate the jumps - which can be keyed off the '__jump_table' section.

Look at the code of scripts/recordmcount.c and friends.

It does two things.

1) find all the callers of mcount and make a section for it.

2) For those callers of mcount that is in sections that are not whitelisted, and therefor will not be patched, to replace the call to mcount with a nop.

We can use this code, or a copy of it, to do the same with jump_label. Have the x86 jump_label be:

static __always_inline bool arch_static_branch(struct jump_label_key *key)

{
	asm goto("1:"
		"jmp l_yes\n"
		".pushsection __jump_table,  \"aw\" \n\t"
		_ASM_ALIGN "\n\t"
		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
		".popsection \n\t"
		: :  "i" (key) : : l_yes);
	return false;
l_yes:
	return true;
}

Then have the record_jumplabel.c (or whatever it's called) find all the jmps at run time, and convert them into the appropriate nop.

Then at runtime patching, the jumplabel code could figure out what size jump it needs to replace it.

-- Steve

#30 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 36 lines · Steven Rostedt
Quoted 3 lines · Jason Baron

Right, its certainly doable, but I'm not sure its so simple, since we'll need a pass to eliminate the jumps - which can be keyed off the '__jump_table' section.

Look at the code of scripts/recordmcount.c and friends.

It does two things.

1) find all the callers of mcount and make a section for it.

2) For those callers of mcount that is in sections that are not whitelisted, and therefor will not be patched, to replace the call to mcount with a nop.

We can use this code, or a copy of it, to do the same with jump_label. Have the x86 jump_label be:

static __always_inline bool arch_static_branch(struct jump_label_key *key)

{
	asm goto("1:"
		"jmp l_yes\n"
		".pushsection __jump_table,  \"aw\" \n\t"
		_ASM_ALIGN "\n\t"
		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
		".popsection \n\t"
		: :  "i" (key) : : l_yes);
	return false;
l_yes:
	return true;
}
Quoted 3 lines

Then have the record_jumplabel.c (or whatever it's called) find all the jmps at run time, and convert them into the appropriate nop.

I'd prefer to do this at build-time as hpa said. We don't want there to be some race b/w patching in the no-ops and taking an incorrect branch.

Quoted 6 lines

Then at runtime patching, the jumplabel code could figure out what size jump it needs to replace it.

-- Steve

sounds like a good plan. thanks for the pointers!

-Jason

#31 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 6 lines · Jason Baron
Quoted 3 lines

Then have the record_jumplabel.c (or whatever it's called) find all the jmps at run time, and convert them into the appropriate nop.

I'd prefer to do this at build-time as hpa said. We don't want there to be some race b/w patching in the no-ops and taking an incorrect branch.

Yep, this record_jumplabel.c would execute at build time. Just like the record_mcount.c does.

Quoted 3 lines
Quoted 3 lines

Then at runtime patching, the jumplabel code could figure out what size jump it needs to replace it.

The runtime patching is when we can figure out what size the compiler used. That wont change. It should be pretty trivial to do. I can't see any races here.

-- Steve

#32 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to Steven Rostedt
Quoted 35 lines · Steven Rostedt
Quoted 5 lines · Jason Baron
Quoted 5 lines

Looks like jmp2 is about 5% faster than jmp5 on Sandybridge with this benchmark.

But insignificant difference on Nehalem.

Quoted 7 lines
Quoted 1 line

J

It would be cool if we could make the total width 2-bytes, when possible. It might be possible by making the initial 'JUMP_LABEL_INITIAL_NOP' as a 'jmp' to the 'l_yes' label. And then patching that with a no-op at boot time or link time - letting the compiler pick the width. In that way we could get the optimal width...

Why not just do it?

jump_label is encapsulated in arch_static_branch() which on x86 looks like:

static __always_inline bool arch_static_branch(struct jump_label_key *key)

{
	asm goto("1:"
		JUMP_LABEL_INITIAL_NOP
		".pushsection __jump_table,  \"aw\" \n\t"
		_ASM_ALIGN "\n\t"
		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
		".popsection \n\t"
		: :  "i" (key) : : l_yes);
	return false;
l_yes:
	return true;
}
Quoted 1 line

That jmp to l_yes should easily be a two byte jump.

Until the compiler decides to re-order the code. That's the problem -- in the general case you do not know how far away the destination is really going to be.

There are a couple of possibilities for improvement:

(1) Do as Jason suggests above and let the assembler figure out the size of the branch that is needed. Without adding more data to __jump_table, you'll want to be extremely careful about checking the two pointers to see what size branch has been installed.

(2) Always reserve 5 bytes of space, but if the distance is small enough patch in a 2-byte jump. That doesn't help with the icache footprint.

(3) There is no 3. I was going to say something clever about gas .ifne conditionals, but a quick test revealed they don't work for forward declarations.

r~

#33 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 8 lines · Richard Henderson
Quoted 2 lines · Steven Rostedt

That jmp to l_yes should easily be a two byte jump.

Until the compiler decides to re-order the code. That's the problem -- in the general case you do not know how far away the destination is really going to be.

Yeah, I was discussing this with Peter Zijlstra on IRC.

Quoted 7 lines

There are a couple of possibilities for improvement:

(1) Do as Jason suggests above and let the assembler figure out the size of the branch that is needed. Without adding more data to __jump_table, you'll want to be extremely careful about checking the two pointers to see what size branch has been installed.

Yeah, that could be done at patch time.

Quoted 3 lines

(2) Always reserve 5 bytes of space, but if the distance is small enough patch in a 2-byte jump. That doesn't help with the icache footprint.

I don't think this one is worth it.

-- Steve

#34 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 3 lines · Steven Rostedt
Quoted 2 lines

(2) Always reserve 5 bytes of space, but if the distance is small enough patch in a 2-byte jump. That doesn't help with the icache footprint.

I don't think this one is worth it.

I disagree. This is what I benchmarked as having a 5% improvement. If squashing out the padding helps, then that's a separate optimisation.

J

#35 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 7 lines · Jeremy Fitzhardinge
Quoted 3 lines · Steven Rostedt
Quoted 2 lines

(2) Always reserve 5 bytes of space, but if the distance is small enough patch in a 2-byte jump. That doesn't help with the icache footprint.

I don't think this one is worth it.

I disagree. This is what I benchmarked as having a 5% improvement. If squashing out the padding helps, then that's a separate optimisation.

But it only speeds up the tracing case. The non-tracing case is a nop and 5bytes is 5bytes regardless.

Did you see a 5% speed up while tracing was happening? How did you do your test. I find a 5 byte compared to a 2 byte jump being negligible with the rest of the overhead of tracing, but I could be wrong.

-- Steve

#36 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 6 lines · Steven Rostedt

But it only speeds up the tracing case. The non-tracing case is a nop and 5bytes is 5bytes regardless.

Did you see a 5% speed up while tracing was happening? How did you do your test. I find a 5 byte compared to a 2 byte jump being negligible with the rest of the overhead of tracing, but I could be wrong.

You're right, this was a completely artificial microbenchmark. In practice the improvement would be a much smaller effect.

But bear in mind, I'm not using jump-label for tracing. While its important for the "disabled" state to be quick, performance of the "enabled" state is also important.

Thanks,
J

#37 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 5 lines · Jeremy Fitzhardinge

On 10/06/2011 03:06 PM, Steven Rostedt wrote:

But bear in mind, I'm not using jump-label for tracing. While its important for the "disabled" state to be quick, performance of the "enabled" state is also important.

Sorry, I'm still thinking jump-label for tracing over.

But that said, having the nop match is the best of both worlds.

I think having a update_jumplabel.c that is just like the record_mcount.c which modifies the code right after it was compiled is the best thing to do. That is, have the assembler determine what size jumps to use and update them to nops right in the object file before linking. This should be rather trivial to do.

-- Steve

#38 lore raw

RFC PATCH 1/1 jump_labels/x86: Use either 5 byte or 2 byte jumps

In reply to Jeremy Fitzhardinge

Note, this is just hacked together and needs to be cleaned up. Please do not comment on formatting or other sloppiness of this patch. I know it's sloppy and I left debug statements in. I want the comments to be on the idea of the patch.

I created a new file called scripts/update_jump_label.[ch] based on some of the work of recordmcount.c. This is executed at build time on all object files just like recordmcount is. But it does not add any new sections, it just modifies the code at build time to convert all jump labels into nops.

The idea is in arch/x86/include/asm/jump_label.h to not place a nop, but instead to insert a jmp to the label. Depending on how gcc optimizes the code, the jmp will be either end up being a 2 byte or 5 byte jump.

After an object is compiled, update_jump_label is executed on this file and it reads the ELF relocation table to find the jump label locations and examines what jump was used. It then converts the jump into either a 2 byte or 5 byte nop that is appropriate.

At boot time, the jump labels no longer need to be converted (although we may do so in the future to use better nops depending on the machine that is used). When jump labels are enabled, the code is examined to see if a two byte or 5 byte version was used, and the appropriate update is made.

I just booted this patch and it worked. I was able to enable and disable trace points using jump labels. Benchmarks are welcomed :)

Comments and thoughts?

-- Steve

Makefile +8 -1
index 31f967c..8368f42 100644
@@ -245,7 +245,7 @@ CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
 
 HOSTCC       = gcc
 HOSTCXX      = g++
-HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer
+HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -g -fomit-frame-pointer
 HOSTCXXFLAGS = -O2
 
 # Decide whether to build built-in, modular, or both.
@@ -611,6 +611,13 @@ ifdef CONFIG_DYNAMIC_FTRACE
 endif
 endif
 
+ifdef CONFIG_JUMP_LABEL
+	ifdef CONFIG_HAVE_BUILD_TIME_JUMP_LABEL
+		BUILD_UPDATE_JUMP_LABEL := y
+		export BUILD_UPDATE_JUMP_LABEL
+	endif
+endif
+
 # We trigger additional mismatches with less inlining
 ifdef CONFIG_DEBUG_SECTION_MISMATCH
 KBUILD_CFLAGS += $(call cc-option, -fno-inline-functions-called-once)
arch/Kconfig +6 -0
index 4b0669c..8fa6934 100644
@@ -169,6 +169,12 @@ config HAVE_PERF_EVENTS_NMI
 	  subsystem.  Also has support for calculating CPU cycle events
 	  to determine how many clock cycles in a given period.
 
+config HAVE_BUILD_TIME_JUMP_LABEL
+       bool
+       help
+	If an arch uses scripts/update_jump_label to patch in jump nops
+	at build time, then it must enable this option.
+
 config HAVE_ARCH_JUMP_LABEL
 	bool
 
arch/x86/Kconfig +1 -0
index 6a47bb2..6de726a 100644
@@ -61,6 +61,7 @@ config X86
 	select HAVE_ARCH_KMEMCHECK
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_ARCH_JUMP_LABEL
+	select HAVE_BUILD_TIME_JUMP_LABEL
 	select HAVE_TEXT_POKE_SMP
 	select HAVE_GENERIC_HARDIRQS
 	select HAVE_SPARSE_IRQ
arch/x86/include/asm/jump_label.h +1 -1
index a32b18c..872b3e1 100644
@@ -14,7 +14,7 @@
 static __always_inline bool arch_static_branch(struct jump_label_key *key)
 {
 	asm goto("1:"
-		JUMP_LABEL_INITIAL_NOP
+		"jmp %l[l_yes]\n"
 		".pushsection __jump_table,  \"aw\" \n\t"
 		_ASM_ALIGN "\n\t"
 		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
arch/x86/kernel/jump_label.c +47 -6

81 lines not on this page · raw message

scripts/Makefile +1 -0
index df7678f..738b65c 100644
@@ -13,6 +13,7 @@ hostprogs-$(CONFIG_LOGO)         += pnmtologo
 hostprogs-$(CONFIG_VT)           += conmakehash
 hostprogs-$(CONFIG_IKCONFIG)     += bin2c
 hostprogs-$(BUILD_C_RECORDMCOUNT) += recordmcount
+hostprogs-$(BUILD_UPDATE_JUMP_LABEL) += update_jump_label
 
 always		:= $(hostprogs-y) $(hostprogs-m)
 
scripts/Makefile.build +13 -2
index a0fd502..bc0d89b 100644
@@ -258,6 +258,15 @@ cmd_modversions =								\
 	fi;
 endif
 
+ifdef BUILD_UPDATE_JUMP_LABEL
+update_jump_label_source := $(srctree)/scripts/update_jump_label.c \
+			$(srctree)/scripts/update_jump_label.h
+cmd_update_jump_label =						\
+	if [ $(@) != "scripts/mod/empty.o" ]; then		\
+		$(objtree)/scripts/update_jump_label "$(@)";	\
+	fi;
+endif
+
 ifdef CONFIG_FTRACE_MCOUNT_RECORD
 ifdef BUILD_C_RECORDMCOUNT
 ifeq ("$(origin RECORDMCOUNT_WARN)", "command line")
@@ -294,6 +303,7 @@ define rule_cc_o_c
 	$(cmd_modversions)						  \
 	$(call echo-cmd,record_mcount)					  \
 	$(cmd_record_mcount)						  \
+	$(cmd_update_jump_label)					  \
 	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
 	                                              $(dot-target).tmp;  \
 	rm -f $(depfile);						  \
@@ -301,13 +311,14 @@ define rule_cc_o_c
 endef
 
 # Built-in and composite module parts
-$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 
 # Single-part modules are special since we need to mark them in $(MODVERDIR)
 
-$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) \
+		  $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 	@{ echo $(@:.o=.ko); echo $@; } > $(MODVERDIR)/$(@F:.o=.mod)
scripts/update_jump_label.c new file +349 -0

349 lines not on this page · raw message

scripts/update_jump_label.h new file +322 -0

322 lines not on this page · raw message

#39 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

Quoted 26 lines · Steven Rostedt

Note, this is just hacked together and needs to be cleaned up. Please do not comment on formatting or other sloppiness of this patch. I know it's sloppy and I left debug statements in. I want the comments to be on the idea of the patch.

I created a new file called scripts/update_jump_label.[ch] based on some of the work of recordmcount.c. This is executed at build time on all object files just like recordmcount is. But it does not add any new sections, it just modifies the code at build time to convert all jump labels into nops.

The idea is in arch/x86/include/asm/jump_label.h to not place a nop, but instead to insert a jmp to the label. Depending on how gcc optimizes the code, the jmp will be either end up being a 2 byte or 5 byte jump.

After an object is compiled, update_jump_label is executed on this file and it reads the ELF relocation table to find the jump label locations and examines what jump was used. It then converts the jump into either a 2 byte or 5 byte nop that is appropriate.

At boot time, the jump labels no longer need to be converted (although we may do so in the future to use better nops depending on the machine that is used). When jump labels are enabled, the code is examined to see if a two byte or 5 byte version was used, and the appropriate update is made.

Quoted 5 lines

I just booted this patch and it worked. I was able to enable and disable trace points using jump labels. Benchmarks are welcomed :)

Comments and thoughts?

Generally, I really like it, I guess b/c I suggested it :) I'll try and run some workloads on it - A real simple one, I used recently was putting a single jump label in 'getppid()' and then calling it in a loop - I wonder if the short nop vs long nop would show up there, as a baseline test. (fwiw, the jump label vs. no jump label for this test was anywhere b/w 1-5% improvement).

Anyways, some comments below.

Quoted 107 lines

-- Steve

Makefile +8 -1
index 31f967c..8368f42 100644
@@ -245,7 +245,7 @@ CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
 
 HOSTCC       = gcc
 HOSTCXX      = g++
-HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer
+HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -g -fomit-frame-pointer
 HOSTCXXFLAGS = -O2
 
 # Decide whether to build built-in, modular, or both.
@@ -611,6 +611,13 @@ ifdef CONFIG_DYNAMIC_FTRACE
 endif
 endif
 
+ifdef CONFIG_JUMP_LABEL
+	ifdef CONFIG_HAVE_BUILD_TIME_JUMP_LABEL
+		BUILD_UPDATE_JUMP_LABEL := y
+		export BUILD_UPDATE_JUMP_LABEL
+	endif
+endif
+
 # We trigger additional mismatches with less inlining
 ifdef CONFIG_DEBUG_SECTION_MISMATCH
 KBUILD_CFLAGS += $(call cc-option, -fno-inline-functions-called-once)
arch/Kconfig +6 -0
index 4b0669c..8fa6934 100644
@@ -169,6 +169,12 @@ config HAVE_PERF_EVENTS_NMI
 	  subsystem.  Also has support for calculating CPU cycle events
 	  to determine how many clock cycles in a given period.
 
+config HAVE_BUILD_TIME_JUMP_LABEL
+       bool
+       help
+	If an arch uses scripts/update_jump_label to patch in jump nops
+	at build time, then it must enable this option.
+
 config HAVE_ARCH_JUMP_LABEL
 	bool
 
arch/x86/Kconfig +1 -0
index 6a47bb2..6de726a 100644
@@ -61,6 +61,7 @@ config X86
 	select HAVE_ARCH_KMEMCHECK
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_ARCH_JUMP_LABEL
+	select HAVE_BUILD_TIME_JUMP_LABEL
 	select HAVE_TEXT_POKE_SMP
 	select HAVE_GENERIC_HARDIRQS
 	select HAVE_SPARSE_IRQ
arch/x86/include/asm/jump_label.h +1 -1
index a32b18c..872b3e1 100644
@@ -14,7 +14,7 @@
 static __always_inline bool arch_static_branch(struct jump_label_key *key)
 {
 	asm goto("1:"
-		JUMP_LABEL_INITIAL_NOP
+		"jmp %l[l_yes]\n"
 		".pushsection __jump_table,  \"aw\" \n\t"
 		_ASM_ALIGN "\n\t"
 		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
arch/x86/kernel/jump_label.c +13 -0
index 3fee346..1f7f88f 100644
@@ -16,34 +16,75 @@
 
 #ifdef HAVE_JUMP_LABEL
 
+static unsigned char nop_short[] = { P6_NOP2 };
+
 union jump_code_union {
 	char code[JUMP_LABEL_NOP_SIZE];
 	struct {
 		char jump;
 		int offset;
 	} __attribute__((packed));
+	struct {
+		char jump_short;
+		char offset_short;
+	} __attribute__((packed));
 };
 
 void arch_jump_label_transform(struct jump_entry *entry,
 			       enum jump_label_type type)
 {
 	union jump_code_union code;
+	unsigned char op;
+	unsigned size;
+	unsigned char nop;
+
+	/* Use probe_kernel_read()? */
+	op = *(unsigned char *)entry->code;
+	nop = ideal_nops[NOP_ATOMIC5][0];
 

arch/x86/kernel/jump_label.c line 49

Quoted 10 lines
arch/x86/kernel/jump_label.c +4 -5
 	if (type == JUMP_LABEL_ENABLE) {
-		code.jump = 0xe9;
-		code.offset = entry->target -
-				(entry->code + JUMP_LABEL_NOP_SIZE);
-	} else
-		memcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE);
+		if (op == 0xe9 || op == 0xeb)
+			/* Already enabled. Warn? */
+			return;
+

Using the jump_label_inc/dec interface this shouldn't happen, I would have it be BUG

arch/x86/kernel/jump_label.c line 62

Quoted 13 lines
arch/x86/kernel/jump_label.c +13 -0
+		/* FIXME for all archs */
+		if (op == nop_short[0]) {
+			size = 2;
+			code.jump_short = 0xeb;
+			code.offset = entry->target -
+				(entry->code + 2);
+			/* Check for overflow ? */
+		} else if (op == nop) {
+			size = JUMP_LABEL_NOP_SIZE;
+			code.jump = 0xe9;
+			code.offset = entry->target - (entry->code + size);
+		} else
+			return; /* WARN ? */

same here, at least WARN, more likely BUG()

arch/x86/kernel/jump_label.c line 68

Quoted 6 lines
arch/x86/kernel/jump_label.c +6 -0
+
+	} else {
+		if (op == nop_short[0] || nop)
+			/* Already disabled, warn? */
+			return;
+

same here.

arch/x86/kernel/jump_label.c line 76

Quoted 8 lines
arch/x86/kernel/jump_label.c +8 -0
+		if (op == 0xe9) {
+			size = JUMP_LABEL_NOP_SIZE;
+			memcpy(&code, ideal_nops[NOP_ATOMIC5], size);
+		} else if (op == 0xeb) {
+			size = 2;
+			memcpy(&code, nop_short, size);
+		} else
+			return; /* WARN ? */

same here

Quoted 5 lines
arch/x86/kernel/jump_label.c +2 -1
+	}
 	get_online_cpus();
 	mutex_lock(&text_mutex);
-	text_poke_smp((void *)entry->code, &code, JUMP_LABEL_NOP_SIZE);
+	text_poke_smp((void *)entry->code, &code, size);

arch/x86/kernel/jump_label.c line 90

Quoted 10 lines
arch/x86/kernel/jump_label.c +1 -0
 	mutex_unlock(&text_mutex);
 	put_online_cpus();
 }
 
 void arch_jump_label_text_poke_early(jump_label_t addr)
 {
+	return;
 	text_poke_early((void *)addr, ideal_nops[NOP_ATOMIC5],
 			JUMP_LABEL_NOP_SIZE);
 }

hmmm...we spent a bunch of time selecting the 'ideal' run-time noops I wouldn't want to drop that work.

Quoted 728 lines
scripts/Makefile +1 -0
index df7678f..738b65c 100644
@@ -13,6 +13,7 @@ hostprogs-$(CONFIG_LOGO)         += pnmtologo
 hostprogs-$(CONFIG_VT)           += conmakehash
 hostprogs-$(CONFIG_IKCONFIG)     += bin2c
 hostprogs-$(BUILD_C_RECORDMCOUNT) += recordmcount
+hostprogs-$(BUILD_UPDATE_JUMP_LABEL) += update_jump_label
 
 always		:= $(hostprogs-y) $(hostprogs-m)
 
scripts/Makefile.build +13 -2
index a0fd502..bc0d89b 100644
@@ -258,6 +258,15 @@ cmd_modversions =								\
 	fi;
 endif
 
+ifdef BUILD_UPDATE_JUMP_LABEL
+update_jump_label_source := $(srctree)/scripts/update_jump_label.c \
+			$(srctree)/scripts/update_jump_label.h
+cmd_update_jump_label =						\
+	if [ $(@) != "scripts/mod/empty.o" ]; then		\
+		$(objtree)/scripts/update_jump_label "$(@)";	\
+	fi;
+endif
+
 ifdef CONFIG_FTRACE_MCOUNT_RECORD
 ifdef BUILD_C_RECORDMCOUNT
 ifeq ("$(origin RECORDMCOUNT_WARN)", "command line")
@@ -294,6 +303,7 @@ define rule_cc_o_c
 	$(cmd_modversions)						  \
 	$(call echo-cmd,record_mcount)					  \
 	$(cmd_record_mcount)						  \
+	$(cmd_update_jump_label)					  \
 	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
 	                                              $(dot-target).tmp;  \
 	rm -f $(depfile);						  \
@@ -301,13 +311,14 @@ define rule_cc_o_c
 endef
 
 # Built-in and composite module parts
-$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 
 # Single-part modules are special since we need to mark them in $(MODVERDIR)
 
-$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) \
+		  $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 	@{ echo $(@:.o=.ko); echo $@; } > $(MODVERDIR)/$(@F:.o=.mod)
scripts/update_jump_label.c new file +349 -0
new file mode 100644
index 0000000..86e17bc
@@ -0,0 +1,349 @@
+/*
+ * update_jump_label.c: replace jmps with nops at compile time.
+ * Copyright 2010 Steven Rostedt <[email protected]>, Red Hat Inc.
+ *  Parsing of the elf file was influenced by recordmcount.c
+ *  originally written by and copyright to John F. Reiser <[email protected]>.
+ */
+
+/*
+ * Note, this code is originally designed for x86, but may be used by
+ * other archs to do the nop updates at compile time instead of at boot time.
+ * X86 uses this as an optimization, as jmps can be either 2 bytes or 5 bytes.
+ * Inserting a 2 byte where possible helps with both CPU performance and
+ * icache strain.
+ */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+static int fd_map;	/* File descriptor for file being modified. */
+static struct stat sb;	/* Remember .st_size, etc. */
+static int mmap_failed; /* Boolean flag. */
+
+static void die(const char *err, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (err)
+		perror(err);
+
+	if (fmt) {
+		va_start(ap, fmt);
+		fprintf(stderr, "Fatal error:  ");
+		vfprintf(stderr, fmt, ap);
+		fprintf(stderr, "\n");
+		va_end(ap);
+	}
+
+	exit(1);
+}
+
+static void usage(char **argv)
+{
+	char *arg = argv[0];
+	char *p = arg+strlen(arg);
+
+	while (p >= arg && *p != '/')
+		p--;
+	p++;
+
+	printf("usage: %s file\n"
+	       "\n",p);
+	exit(-1);
+}
+
+/* w8rev, w8nat, ...: Handle endianness. */
+
+static uint64_t w8rev(uint64_t const x)
+{
+	return   ((0xff & (x >> (0 * 8))) << (7 * 8))
+	       | ((0xff & (x >> (1 * 8))) << (6 * 8))
+	       | ((0xff & (x >> (2 * 8))) << (5 * 8))
+	       | ((0xff & (x >> (3 * 8))) << (4 * 8))
+	       | ((0xff & (x >> (4 * 8))) << (3 * 8))
+	       | ((0xff & (x >> (5 * 8))) << (2 * 8))
+	       | ((0xff & (x >> (6 * 8))) << (1 * 8))
+	       | ((0xff & (x >> (7 * 8))) << (0 * 8));
+}
+
+static uint32_t w4rev(uint32_t const x)
+{
+	return   ((0xff & (x >> (0 * 8))) << (3 * 8))
+	       | ((0xff & (x >> (1 * 8))) << (2 * 8))
+	       | ((0xff & (x >> (2 * 8))) << (1 * 8))
+	       | ((0xff & (x >> (3 * 8))) << (0 * 8));
+}
+
+static uint32_t w2rev(uint16_t const x)
+{
+	return   ((0xff & (x >> (0 * 8))) << (1 * 8))
+	       | ((0xff & (x >> (1 * 8))) << (0 * 8));
+}
+
+static uint64_t w8nat(uint64_t const x)
+{
+	return x;
+}
+
+static uint32_t w4nat(uint32_t const x)
+{
+	return x;
+}
+
+static uint32_t w2nat(uint16_t const x)
+{
+	return x;
+}
+
+static uint64_t (*w8)(uint64_t);
+static uint32_t (*w)(uint32_t);
+static uint32_t (*w2)(uint16_t);
+
+/* ulseek, uread, ...:  Check return value for errors. */
+
+static off_t
+ulseek(int const fd, off_t const offset, int const whence)
+{
+	off_t const w = lseek(fd, offset, whence);
+	if (w == (off_t)-1)
+		die("lseek", NULL);
+
+	return w;
+}
+
+static size_t
+uread(int const fd, void *const buf, size_t const count)
+{
+	size_t const n = read(fd, buf, count);
+	if (n != count)
+		die("read", NULL);
+
+	return n;
+}
+
+static size_t
+uwrite(int const fd, void const *const buf, size_t const count)
+{
+	size_t const n = write(fd, buf, count);
+	if (n != count)
+		die("write", NULL);
+
+	return n;
+}
+
+static void *
+umalloc(size_t size)
+{
+	void *const addr = malloc(size);
+	if (addr == 0)
+		die("malloc", "malloc failed: %zu bytes\n", size);
+
+	return addr;
+}
+
+/*
+ * Get the whole file as a programming convenience in order to avoid
+ * malloc+lseek+read+free of many pieces.  If successful, then mmap
+ * avoids copying unused pieces; else just read the whole file.
+ * Open for both read and write; new info will be appended to the file.
+ * Use MAP_PRIVATE so that a few changes to the in-memory ElfXX_Ehdr
+ * do not propagate to the file until an explicit overwrite at the last.
+ * This preserves most aspects of consistency (all except .st_size)
+ * for simultaneous readers of the file while we are appending to it.
+ * However, multiple writers still are bad.  We choose not to use
+ * locking because it is expensive and the use case of kernel build
+ * makes multiple writers unlikely.
+ */
+static void *mmap_file(char const *fname)
+{
+	void *addr;
+
+	fd_map = open(fname, O_RDWR);
+	if (fd_map < 0 || fstat(fd_map, &sb) < 0)
+		die(fname, "failed to open file");
+
+	if (!S_ISREG(sb.st_mode))
+		die(NULL, "not a regular file: %s\n", fname);
+
+	addr = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE,
+		    fd_map, 0);
+
+	mmap_failed = 0;
+	if (addr == MAP_FAILED) {
+		mmap_failed = 1;
+		addr = umalloc(sb.st_size);
+		uread(fd_map, addr, sb.st_size);
+	}
+	return addr;
+}
+
+static void munmap_file(void *addr)
+{
+	if (!mmap_failed)
+		munmap(addr, sb.st_size);
+	else
+		free(addr);
+	close(fd_map);
+}
+
+static unsigned char ideal_nop5_x86_64[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 };
+static unsigned char ideal_nop5_x86_32[5] = { 0x3e, 0x8d, 0x74, 0x26, 0x00 };
+static unsigned char ideal_nop2_x86[2] = { 0x66, 0x99 };
+static unsigned char *ideal_nop;
+
+static int (*make_nop)(void *map, size_t const offset);
+
+static int make_nop_x86(void *map, size_t const offset)
+{
+	unsigned char *op;
+	unsigned char *nop;
+	int size;
+
+	/* Determine which type of jmp this is 2 byte or 5. */
+	op = map + offset;
+	switch (*op) {
+	case 0xeb: /* 2 byte */
+		size = 2;
+		nop = ideal_nop2_x86;
+		break;
+	case 0xe9: /* 5 byte */
+		size = 5;
+		nop = ideal_nop;
+		break;
+	default:
+		die(NULL, "Bad jump label section\n");
+	}
+
+	/* convert to nop */
+	ulseek(fd_map, offset, SEEK_SET);
+	uwrite(fd_map, nop, size);
+	return 0;
+}
+
+/* 32 bit and 64 bit are very similar */
+#include "update_jump_label.h"
+#define UPDATE_JUMP_LABEL_64
+#include "update_jump_label.h"
+
+static int do_file(const char *fname)
+{
+	Elf32_Ehdr *const ehdr = mmap_file(fname);
+	unsigned int reltype = 0;
+
+	w = w4nat;
+	w2 = w2nat;
+	w8 = w8nat;
+	switch (ehdr->e_ident[EI_DATA]) {
+		static unsigned int const endian = 1;
+	default:
+		die(NULL, "unrecognized ELF data encoding %d: %s\n",
+			ehdr->e_ident[EI_DATA], fname);
+		break;
+	case ELFDATA2LSB:
+		if (*(unsigned char const *)&endian != 1) {
+			/* main() is big endian, file.o is little endian. */
+			w = w4rev;
+			w2 = w2rev;
+			w8 = w8rev;
+		}
+		break;
+	case ELFDATA2MSB:
+		if (*(unsigned char const *)&endian != 0) {
+			/* main() is little endian, file.o is big endian. */
+			w = w4rev;
+			w2 = w2rev;
+			w8 = w8rev;
+		}
+		break;
+	}  /* end switch */
+
+	if (memcmp(ELFMAG, ehdr->e_ident, SELFMAG) != 0 ||
+	    w2(ehdr->e_type) != ET_REL ||
+	    ehdr->e_ident[EI_VERSION] != EV_CURRENT)
+		die(NULL, "unrecognized ET_REL file %s\n", fname);
+
+	switch (w2(ehdr->e_machine)) {
+	default:
+		die(NULL, "unrecognized e_machine %d %s\n",
+		    w2(ehdr->e_machine), fname);
+		break;
+	case EM_386:
+		reltype = R_386_32;
+		make_nop = make_nop_x86;
+		ideal_nop = ideal_nop5_x86_32;
+		break;
+	case EM_ARM:	 reltype = R_ARM_ABS32;
+			 break;
+	case EM_IA_64:	 reltype = R_IA64_IMM64; break;
+	case EM_MIPS:	 /* reltype: e_class    */ break;
+	case EM_PPC:	 reltype = R_PPC_ADDR32;   break;
+	case EM_PPC64:	 reltype = R_PPC64_ADDR64; break;
+	case EM_S390:    /* reltype: e_class    */ break;
+	case EM_SH:	 reltype = R_SH_DIR32;                 break;
+	case EM_SPARCV9: reltype = R_SPARC_64;     break;
+	case EM_X86_64:
+		make_nop = make_nop_x86;
+		ideal_nop = ideal_nop5_x86_64;
+		reltype = R_X86_64_64;
+		break;
+	}  /* end switch */
+
+	switch (ehdr->e_ident[EI_CLASS]) {
+	default:
+		die(NULL, "unrecognized ELF class %d %s\n",
+		    ehdr->e_ident[EI_CLASS], fname);
+		break;
+	case ELFCLASS32:
+		if (w2(ehdr->e_ehsize) != sizeof(Elf32_Ehdr)
+		||  w2(ehdr->e_shentsize) != sizeof(Elf32_Shdr))
+			die(NULL, "unrecognized ET_REL file: %s\n", fname);
+
+		if (w2(ehdr->e_machine) == EM_S390) {
+			reltype = R_390_32;
+		}
+		if (w2(ehdr->e_machine) == EM_MIPS) {
+			reltype = R_MIPS_32;
+		}
+		do_func32(ehdr, fname, reltype);
+		break;
+	case ELFCLASS64: {
+		Elf64_Ehdr *const ghdr = (Elf64_Ehdr *)ehdr;
+		if (w2(ghdr->e_ehsize) != sizeof(Elf64_Ehdr)
+		||  w2(ghdr->e_shentsize) != sizeof(Elf64_Shdr))
+			die(NULL, "unrecognized ET_REL file: %s\n", fname);
+
+		if (w2(ghdr->e_machine) == EM_S390)
+			reltype = R_390_64;
+
+#if 0
+		if (w2(ghdr->e_machine) == EM_MIPS) {
+			reltype = R_MIPS_64;
+			Elf64_r_sym = MIPS64_r_sym;
+		}
+#endif
+		do_func64(ghdr, fname, reltype);
+		break;
+	}
+	}  /* end switch */
+
+	munmap_file(ehdr);
+	return 0;
+}
+
+int main (int argc, char **argv)
+{
+	if (argc != 2)
+		usage(argv);
+	
+	return do_file(argv[1]);
+}
+
scripts/update_jump_label.h new file +310 -0
new file mode 100644
index 0000000..6ff9846
@@ -0,0 +1,322 @@
+/*
+ * recordmcount.h
+ *
+ * This code was taken out of recordmcount.c written by
+ * Copyright 2009 John F. Reiser <[email protected]>.  All rights reserved.
+ *
+ * The original code had the same algorithms for both 32bit
+ * and 64bit ELF files, but the code was duplicated to support
+ * the difference in structures that were used. This
+ * file creates a macro of everything that is different between
+ * the 64 and 32 bit code, such that by including this header
+ * twice we can create both sets of functions by including this
+ * header once with RECORD_MCOUNT_64 undefined, and again with
+ * it defined.
+ *
+ * This conversion to macros was done by:
+ * Copyright 2010 Steven Rostedt <[email protected]>, Red Hat Inc.
+ *
+ * Licensed under the GNU General Public License, version 2 (GPLv2).
+ */
+
+#undef EBITS
+#undef _w
+#undef _align
+#undef _size
+
+#ifdef UPDATE_JUMP_LABEL_64
+# define EBITS			64
+# define _w			w8
+# define _align			7u
+# define _size			8
+#else
+# define EBITS			32
+# define _w			w
+# define _align			3u
+# define _size			4
+#endif
+
+#define _FBITS(x, e)	x##e
+#define FBITS(x, e)	_FBITS(x,e)
+#define FUNC(x)		FBITS(x,EBITS)
+
+#undef Elf_Addr
+#undef Elf_Ehdr
+#undef Elf_Shdr
+#undef Elf_Rel
+#undef Elf_Rela
+#undef Elf_Sym
+#undef ELF_R_SYM
+#undef ELF_R_TYPE
+
+#define __ATTACH(x,y,z)	x##y##z
+#define ATTACH(x,y,z)	__ATTACH(x,y,z)
+
+#define Elf_Addr	ATTACH(Elf,EBITS,_Addr)
+#define Elf_Ehdr	ATTACH(Elf,EBITS,_Ehdr)
+#define Elf_Shdr	ATTACH(Elf,EBITS,_Shdr)
+#define Elf_Rel		ATTACH(Elf,EBITS,_Rel)
+#define Elf_Rela	ATTACH(Elf,EBITS,_Rela)
+#define Elf_Sym		ATTACH(Elf,EBITS,_Sym)
+#define uint_t		ATTACH(uint,EBITS,_t)
+#define ELF_R_SYM	ATTACH(ELF,EBITS,_R_SYM)
+#define ELF_R_TYPE	ATTACH(ELF,EBITS,_R_TYPE)
+
+#undef get_shdr
+#define get_shdr(ehdr) ((Elf_Shdr *)(_w((ehdr)->e_shoff) + (void *)(ehdr)))
+
+#undef get_section_loc
+#define get_section_loc(ehdr, shdr)(_w((shdr)->sh_offset) + (void *)(ehdr))
+
+/* Functions and pointers that do_file() may override for specific e_machine. */
+
+#if 0
+static uint_t FUNC(fn_ELF_R_SYM)(Elf_Rel const *rp)
+{
+	return ELF_R_SYM(_w(rp->r_info));
+}
+static uint_t (*FUNC(Elf_r_sym))(Elf_Rel const *rp) = FUNC(fn_ELF_R_SYM);
+#endif
+
+static void FUNC(get_sym_str_and_relp)(Elf_Shdr const *const relhdr,
+				 Elf_Ehdr const *const ehdr,
+				 Elf_Sym const **sym0,
+				 char const **str0,
+				 Elf_Rel const **relp)
+{
+	Elf_Shdr *const shdr0 = get_shdr(ehdr);
+	unsigned const symsec_sh_link = w(relhdr->sh_link);
+	Elf_Shdr const *const symsec = &shdr0[symsec_sh_link];
+	Elf_Shdr const *const strsec = &shdr0[w(symsec->sh_link)];
+	Elf_Rel const *const rel0 =
+		(Elf_Rel const *)get_section_loc(ehdr, relhdr);
+
+	*sym0 = (Elf_Sym const *)get_section_loc(ehdr, symsec);
+
+	*str0 = (char const *)get_section_loc(ehdr, strsec);
+
+	*relp = rel0;
+}
+
+/*
+ * Read the relocation table again, but this time its called on sections
+ * that are not going to be traced. The mcount calls here will be converted
+ * into nops.
+ */
+static void FUNC(nop_jump_label)(Elf_Shdr const *const relhdr,
+		       Elf_Ehdr const *const ehdr,
+		       const char *const txtname)
+{
+	Elf_Shdr *const shdr0 = get_shdr(ehdr);
+	Elf_Sym const *sym0;
+	char const *str0;
+	Elf_Rel const *relp;
+	Elf_Rela const *relap;
+	Elf_Shdr const *const shdr = &shdr0[w(relhdr->sh_info)];
+	unsigned rel_entsize = w(relhdr->sh_entsize);
+	unsigned const nrel = _w(relhdr->sh_size) / rel_entsize;
+	int t;
+
+	FUNC(get_sym_str_and_relp)(relhdr, ehdr, &sym0, &str0, &relp);
+
+	for (t = nrel; t > 0; t -= 3) {
+		int ret = -1;
+
+		relap = (Elf_Rela const *)relp;
+		printf("rel offset=%lx info=%lx sym=%lx type=%lx addend=%lx\n",
+		       (long)relap->r_offset, (long)relap->r_info,
+		       (long)ELF_R_SYM(relap->r_info),
+		       (long)ELF_R_TYPE(relap->r_info),
+		       (long)relap->r_addend);
+
+		if (0 && make_nop)
+			ret = make_nop((void *)ehdr, shdr->sh_offset + relp->r_offset);
+
+		/* jump label sections are paired in threes */
+		relp = (Elf_Rel const *)(rel_entsize * 3 + (void *)relp);
+	}
+}
+
+/* Evade ISO C restriction: no declaration after statement in has_rel_mcount. */
+static char const *
+FUNC(__has_rel_jump_table)(Elf_Shdr const *const relhdr,  /* is SHT_REL or SHT_RELA */
+		 Elf_Shdr const *const shdr0,
+		 char const *const shstrtab,
+		 char const *const fname)
+{
+	/* .sh_info depends on .sh_type == SHT_REL[,A] */
+	Elf_Shdr const *const txthdr = &shdr0[w(relhdr->sh_info)];
+	char const *const txtname = &shstrtab[w(txthdr->sh_name)];
+
+	if (strcmp("__jump_table", txtname) == 0) {
+		fprintf(stderr, "warning: __mcount_loc already exists: %s\n",
+			fname);
+//		succeed_file();
+	}
+	if (w(txthdr->sh_type) != SHT_PROGBITS ||
+	    !(w(txthdr->sh_flags) & SHF_EXECINSTR))
+		return NULL;
+	return txtname;
+}
+
+static char const *FUNC(has_rel_jump_table)(Elf_Shdr const *const relhdr,
+				      Elf_Shdr const *const shdr0,
+				      char const *const shstrtab,
+				      char const *const fname)
+{
+	if (w(relhdr->sh_type) != SHT_REL && w(relhdr->sh_type) != SHT_RELA)
+		return NULL;
+	return FUNC(__has_rel_jump_table)(relhdr, shdr0, shstrtab, fname);
+}
+
+/* Find relocation section hdr for a given section */
+static const Elf_Shdr *
+FUNC(find_relhdr)(const Elf_Ehdr *ehdr, const Elf_Shdr *shdr)
+{
+	const Elf_Shdr *shdr0 = get_shdr(ehdr);
+	int nhdr = w2(ehdr->e_shnum);
+	const Elf_Shdr *hdr;
+	int i;
+
+	for (hdr = shdr0, i = 0; i < nhdr; hdr = &shdr0[++i]) {
+		if (w(hdr->sh_type) != SHT_REL &&
+		    w(hdr->sh_type) != SHT_RELA)
+			continue;
+
+		/*
+		 * The relocation section's info field holds
+		 * the section index that it represents.
+		 */
+		if (shdr == &shdr0[w(hdr->sh_info)])
+			return hdr;
+	}
+	return NULL;
+}
+
+/* Find a section headr based on name and type */
+static const Elf_Shdr *
+FUNC(find_shdr)(const Elf_Ehdr *ehdr, const char *name, uint_t type)
+{
+	const Elf_Shdr *shdr0 = get_shdr(ehdr);
+	const Elf_Shdr *shstr = &shdr0[w2(ehdr->e_shstrndx)];
+	const char *shstrtab = (char *)get_section_loc(ehdr, shstr);
+	int nhdr = w2(ehdr->e_shnum);
+	const Elf_Shdr *hdr;
+	const char *hdrname;
+	int i;
+
+	for (hdr = shdr0, i = 0; i < nhdr; hdr = &shdr0[++i]) {
+		if (w(hdr->sh_type) != type)
+			continue;
+
+		/* If we are just looking for a section by type (ie. SYMTAB) */
+		if (!name)
+			return hdr;
+
+		hdrname = &shstrtab[w(hdr->sh_name)];
+		if (strcmp(hdrname, name) == 0)
+			return hdr;
+	}
+	return NULL;
+}
+
+static void
+FUNC(section_update)(const Elf_Ehdr *ehdr, const Elf_Shdr *symhdr,
+		     unsigned shtype, const Elf_Rel *rel, void *data)
+{
+	const Elf_Shdr *shdr0 = get_shdr(ehdr);
+	const Elf_Shdr *targethdr;
+	const Elf_Rela *rela;
+	const Elf_Sym *syment;
+	uint_t offset = _w(rel->r_offset);
+	uint_t info = _w(rel->r_info);
+	uint_t sym = ELF_R_SYM(info);
+	uint_t type = ELF_R_TYPE(info);
+	uint_t addend;
+	uint_t targetloc;
+
+	if (shtype == SHT_RELA) {
+		rela = (const Elf_Rela *)rel;
+		addend = _w(rela->r_addend);
+	} else
+		addend = _w(*(unsigned short *)(data + offset));
+
+	syment = (const Elf_Sym *)get_section_loc(ehdr, symhdr);
+	targethdr = &shdr0[w2(syment[sym].st_shndx)];
+	targetloc = _w(targethdr->sh_offset);
+
+	/* TODO, need a separate function for all archs */
+	if (type != R_386_32)
+		die(NULL, "Arch relocation type %d not supported", type);
+
+	targetloc += addend;
+
+#if 1
+	printf("offset=%x target=%x shoffset=%x add=%x\n",
+	       offset, targetloc, _w(targethdr->sh_offset), addend);
+#endif
+	*(uint_t *)(data + offset) = targetloc;
+}
+
+/* Overall supervision for Elf32 ET_REL file. */
+static void
+FUNC(do_func)(Elf_Ehdr *ehdr, char const *const fname, unsigned const reltype)
+{
+	const Elf_Shdr *jlshdr;
+	const Elf_Shdr *jlrhdr;
+	const Elf_Shdr *symhdr;
+	const Elf_Rel *rel;
+	unsigned size;
+	unsigned cnt;
+	unsigned i;
+	uint_t type;
+	void *jdata;
+	void *data;
+
+	jlshdr = FUNC(find_shdr)(ehdr, "__jump_table", SHT_PROGBITS);
+	if (!jlshdr)
+		return;
+
+	jlrhdr = FUNC(find_relhdr)(ehdr, jlshdr);
+	if (!jlrhdr)
+		return;
+
+	/*
+	 * Create and fill in the __jump_table section and use it to
+	 * find the offsets into the text that we want to update.
+	 * We create it so that we do not depend on the order of the
+	 * relocations, and use the table directly, as it is broken
+	 * up into sections.
+	 */
+	size = _w(jlshdr->sh_size);
+	data = umalloc(size);
+
+	jdata = (void *)get_section_loc(ehdr, jlshdr);
+	memcpy(data, jdata, size);
+
+	cnt = _w(jlrhdr->sh_size) / w(jlrhdr->sh_entsize);
+
+	rel = (const Elf_Rel *)get_section_loc(ehdr, jlrhdr);
+
+	/* Is this as Rel or Rela? */
+	type = w(jlrhdr->sh_type);
+
+	symhdr = FUNC(find_shdr)(ehdr, NULL, SHT_SYMTAB);
+
+	for (i = 0; i < cnt; i++) {
+		FUNC(section_update)(ehdr, symhdr, type, rel, data);
+		rel = (void *)rel + w(jlrhdr->sh_entsize);
+	}
+

scripts/update_jump_label.h line 320

Quoted 10 lines
scripts/update_jump_label.h new file +10 -0
+	/*
+	 * This is specific to x86. The jump_table is stored in three
+	 * long words. The first is the location of the jmp target we
+	 * must update.
+	 */
+	cnt = size / sizeof(uint_t);
+
+	for (i = 0; i < cnt; i += 3)
+		if (0)make_nop((void *)ehdr, *(uint_t *)(data + i * sizeof(uint_t)));
+

hmmmm, isn't this the line that actually writes in the no-ops? why isn't it disabled?

scripts/update_jump_label.h line 322

Quoted 4 lines
scripts/update_jump_label.h new file +2 -0
+	free(data);
+}

Thanks again for doing this...I was still understanding recordmcount.c ;)

-Jason

#40 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

Quoted 27 lines · Jason Baron
Quoted 26 lines · Steven Rostedt

Note, this is just hacked together and needs to be cleaned up. Please do not comment on formatting or other sloppiness of this patch. I know it's sloppy and I left debug statements in. I want the comments to be on the idea of the patch.

I created a new file called scripts/update_jump_label.[ch] based on some of the work of recordmcount.c. This is executed at build time on all object files just like recordmcount is. But it does not add any new sections, it just modifies the code at build time to convert all jump labels into nops.

The idea is in arch/x86/include/asm/jump_label.h to not place a nop, but instead to insert a jmp to the label. Depending on how gcc optimizes the code, the jmp will be either end up being a 2 byte or 5 byte jump.

After an object is compiled, update_jump_label is executed on this file and it reads the ELF relocation table to find the jump label locations and examines what jump was used. It then converts the jump into either a 2 byte or 5 byte nop that is appropriate.

At boot time, the jump labels no longer need to be converted (although we may do so in the future to use better nops depending on the machine that is used). When jump labels are enabled, the code is examined to see if a two byte or 5 byte version was used, and the appropriate update is made.

Quoted 7 lines
Quoted 5 lines

I just booted this patch and it worked. I was able to enable and disable trace points using jump labels. Benchmarks are welcomed :)

Comments and thoughts?

Generally, I really like it, I guess b/c I suggested it :)

Yeah, I saw your suggestion about using jump for the asm. I didn't read carefully enough if you suggested the build time changes or not. I thought about recordmcount at that moment, and decided to try it ;)

Quoted 131 lines

I'll try and
run some workloads on it - A real simple one, I used recently was putting a single jump label in 'getppid()' and then calling it in a loop - I wonder if the short nop vs long nop would show up there, as a baseline test. (fwiw, the jump label vs. no jump label for this test was anywhere b/w 1-5% improvement).

Anyways, some comments below.

Quoted 107 lines

-- Steve

Makefile +8 -1
index 31f967c..8368f42 100644
@@ -245,7 +245,7 @@ CONFIG_SHELL := $(shell if [ -x "$$BASH" ]; then echo $$BASH; \
 
 HOSTCC       = gcc
 HOSTCXX      = g++
-HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -O2 -fomit-frame-pointer
+HOSTCFLAGS   = -Wall -Wmissing-prototypes -Wstrict-prototypes -g -fomit-frame-pointer
 HOSTCXXFLAGS = -O2
 
 # Decide whether to build built-in, modular, or both.
@@ -611,6 +611,13 @@ ifdef CONFIG_DYNAMIC_FTRACE
 endif
 endif
 
+ifdef CONFIG_JUMP_LABEL
+	ifdef CONFIG_HAVE_BUILD_TIME_JUMP_LABEL
+		BUILD_UPDATE_JUMP_LABEL := y
+		export BUILD_UPDATE_JUMP_LABEL
+	endif
+endif
+
 # We trigger additional mismatches with less inlining
 ifdef CONFIG_DEBUG_SECTION_MISMATCH
 KBUILD_CFLAGS += $(call cc-option, -fno-inline-functions-called-once)
arch/Kconfig +6 -0
index 4b0669c..8fa6934 100644
@@ -169,6 +169,12 @@ config HAVE_PERF_EVENTS_NMI
 	  subsystem.  Also has support for calculating CPU cycle events
 	  to determine how many clock cycles in a given period.
 
+config HAVE_BUILD_TIME_JUMP_LABEL
+       bool
+       help
+	If an arch uses scripts/update_jump_label to patch in jump nops
+	at build time, then it must enable this option.
+
 config HAVE_ARCH_JUMP_LABEL
 	bool
 
arch/x86/Kconfig +1 -0
index 6a47bb2..6de726a 100644
@@ -61,6 +61,7 @@ config X86
 	select HAVE_ARCH_KMEMCHECK
 	select HAVE_USER_RETURN_NOTIFIER
 	select HAVE_ARCH_JUMP_LABEL
+	select HAVE_BUILD_TIME_JUMP_LABEL
 	select HAVE_TEXT_POKE_SMP
 	select HAVE_GENERIC_HARDIRQS
 	select HAVE_SPARSE_IRQ
arch/x86/include/asm/jump_label.h +1 -1
index a32b18c..872b3e1 100644
@@ -14,7 +14,7 @@
 static __always_inline bool arch_static_branch(struct jump_label_key *key)
 {
 	asm goto("1:"
-		JUMP_LABEL_INITIAL_NOP
+		"jmp %l[l_yes]\n"
 		".pushsection __jump_table,  \"aw\" \n\t"
 		_ASM_ALIGN "\n\t"
 		_ASM_PTR "1b, %l[l_yes], %c0 \n\t"
arch/x86/kernel/jump_label.c +13 -0
index 3fee346..1f7f88f 100644
@@ -16,34 +16,75 @@
 
 #ifdef HAVE_JUMP_LABEL
 
+static unsigned char nop_short[] = { P6_NOP2 };
+
 union jump_code_union {
 	char code[JUMP_LABEL_NOP_SIZE];
 	struct {
 		char jump;
 		int offset;
 	} __attribute__((packed));
+	struct {
+		char jump_short;
+		char offset_short;
+	} __attribute__((packed));
 };
 
 void arch_jump_label_transform(struct jump_entry *entry,
 			       enum jump_label_type type)
 {
 	union jump_code_union code;
+	unsigned char op;
+	unsigned size;
+	unsigned char nop;
+
+	/* Use probe_kernel_read()? */
+	op = *(unsigned char *)entry->code;
+	nop = ideal_nops[NOP_ATOMIC5][0];
 

arch/x86/kernel/jump_label.c line 49

Quoted 10 lines
arch/x86/kernel/jump_label.c +4 -5
 	if (type == JUMP_LABEL_ENABLE) {
-		code.jump = 0xe9;
-		code.offset = entry->target -
-				(entry->code + JUMP_LABEL_NOP_SIZE);
-	} else
-		memcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE);
+		if (op == 0xe9 || op == 0xeb)
+			/* Already enabled. Warn? */
+			return;
+

Using the jump_label_inc/dec interface this shouldn't happen, I would have it be BUG

Quoted 15 lines

arch/x86/kernel/jump_label.c line 62

Quoted 13 lines
arch/x86/kernel/jump_label.c +13 -0
+		/* FIXME for all archs */
+		if (op == nop_short[0]) {
+			size = 2;
+			code.jump_short = 0xeb;
+			code.offset = entry->target -
+				(entry->code + 2);
+			/* Check for overflow ? */
+		} else if (op == nop) {
+			size = JUMP_LABEL_NOP_SIZE;
+			code.jump = 0xe9;
+			code.offset = entry->target - (entry->code + size);
+		} else
+			return; /* WARN ? */

same here, at least WARN, more likely BUG()

I just don't like using BUG(). BUG() means that if we continue we will corrupt the filesystem or make you go blind. WARN and returning here should not cause any harm and will even let those with X terminals see oops in /var/log/messages.

Quoted 10 lines

arch/x86/kernel/jump_label.c line 68

Quoted 7 lines
arch/x86/kernel/jump_label.c +6 -0
+
+	} else {
+		if (op == nop_short[0] || nop)
+			/* Already disabled, warn? */
+			return;
+

same here.

Quoted 10 lines

arch/x86/kernel/jump_label.c line 76

Quoted 8 lines
arch/x86/kernel/jump_label.c +8 -0
+		if (op == 0xe9) {
+			size = JUMP_LABEL_NOP_SIZE;
+			memcpy(&code, ideal_nops[NOP_ATOMIC5], size);
+		} else if (op == 0xeb) {
+			size = 2;
+			memcpy(&code, nop_short, size);
+		} else
+			return; /* WARN ? */

same here

WARN is better.

Quoted 10 lines
Quoted 10 lines
arch/x86/kernel/jump_label.c +2 -1
+	}
 	get_online_cpus();
 	mutex_lock(&text_mutex);
-	text_poke_smp((void *)entry->code, &code, JUMP_LABEL_NOP_SIZE);
+	text_poke_smp((void *)entry->code, &code, size);
 	mutex_unlock(&text_mutex);
 	put_online_cpus();
 }
 
Quoted 9 lines

arch/x86/kernel/jump_label.c line 90

Quoted 6 lines
arch/x86/kernel/jump_label.c +1 -0
 void arch_jump_label_text_poke_early(jump_label_t addr)
 {
+	return;
 	text_poke_early((void *)addr, ideal_nops[NOP_ATOMIC5],
 			JUMP_LABEL_NOP_SIZE);
 }

hmmm...we spent a bunch of time selecting the 'ideal' run-time noops I wouldn't want to drop that work.

Read my change log again :)

Quoted 62 lines
Quoted 61 lines
scripts/Makefile +1 -0
index df7678f..738b65c 100644
@@ -13,6 +13,7 @@ hostprogs-$(CONFIG_LOGO)         += pnmtologo
 hostprogs-$(CONFIG_VT)           += conmakehash
 hostprogs-$(CONFIG_IKCONFIG)     += bin2c
 hostprogs-$(BUILD_C_RECORDMCOUNT) += recordmcount
+hostprogs-$(BUILD_UPDATE_JUMP_LABEL) += update_jump_label
 
 always		:= $(hostprogs-y) $(hostprogs-m)
 
scripts/Makefile.build +13 -2
index a0fd502..bc0d89b 100644
@@ -258,6 +258,15 @@ cmd_modversions =								\
 	fi;
 endif
 
+ifdef BUILD_UPDATE_JUMP_LABEL
+update_jump_label_source := $(srctree)/scripts/update_jump_label.c \
+			$(srctree)/scripts/update_jump_label.h
+cmd_update_jump_label =						\
+	if [ $(@) != "scripts/mod/empty.o" ]; then		\
+		$(objtree)/scripts/update_jump_label "$(@)";	\
+	fi;
+endif
+
 ifdef CONFIG_FTRACE_MCOUNT_RECORD
 ifdef BUILD_C_RECORDMCOUNT
 ifeq ("$(origin RECORDMCOUNT_WARN)", "command line")
@@ -294,6 +303,7 @@ define rule_cc_o_c
 	$(cmd_modversions)						  \
 	$(call echo-cmd,record_mcount)					  \
 	$(cmd_record_mcount)						  \
+	$(cmd_update_jump_label)					  \
 	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
 	                                              $(dot-target).tmp;  \
 	rm -f $(depfile);						  \
@@ -301,13 +311,14 @@ define rule_cc_o_c
 endef
 
 # Built-in and composite module parts
-$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 
 # Single-part modules are special since we need to mark them in $(MODVERDIR)
 
-$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) \
+		  $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 	@{ echo $(@:.o=.ko); echo $@; } > $(MODVERDIR)/$(@F:.o=.mod)
scripts/update_jump_label.c new file +0 -0
new file mode 100644
index 0000000..86e17bc
Quoted 13 lines

scripts/update_jump_label.c

Quoted 10 lines
scripts/update_jump_label.c new file +10 -0
+	/*
+	 * This is specific to x86. The jump_table is stored in three
+	 * long words. The first is the location of the jmp target we
+	 * must update.
+	 */
+	cnt = size / sizeof(uint_t);
+
+	for (i = 0; i < cnt; i += 3)
+		if (0)make_nop((void *)ehdr, *(uint_t *)(data + i * sizeof(uint_t)));
+

hmmmm, isn't this the line that actually writes in the no-ops? why isn't it disabled?

*DOH!*

Oh crap! I added that to dump more debug info. Let me make sure it actually works ;)

Quoted 7 lines

scripts/update_jump_label.c

Quoted 5 lines
scripts/update_jump_label.c new file +2 -0
+	free(data);
+}

Thanks again for doing this...I was still understanding recordmcount.c ;)

And after this, I plan on giving recordmcount.c an overhaul so it is easier to read. Right now it is written with lots of micro-optimizations, with the sacrifice to simplicity. Modifying this code is nasty, and it needs to be read by mere mortals.

I'll go and test with actual modifications and see if it still works.

-- Steve

#41 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

Quoted 8 lines · Steven Rostedt
Quoted 2 lines

same here, at least WARN, more likely BUG()

I just don't like using BUG(). BUG() means that if we continue we will corrupt the filesystem or make you go blind. WARN and returning here should not cause any harm and will even let those with X terminals see oops in /var/log/messages.

Uh, NO.

If this is wrong something in the kernel code stream is corrupted (heck, you might just have caught a rootkit!)

Die. NOW.

-hpa

#42 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

Quoted 10 lines · H. Peter Anvin
Quoted 8 lines · Steven Rostedt
Quoted 2 lines

same here, at least WARN, more likely BUG()

I just don't like using BUG(). BUG() means that if we continue we will corrupt the filesystem or make you go blind. WARN and returning here should not cause any harm and will even let those with X terminals see oops in /var/log/messages.

Quoted 6 lines

Uh, NO.

If this is wrong something in the kernel code stream is corrupted (heck, you might just have caught a rootkit!)

Die. NOW.

Ouch, quite shaken by k.org? I guess I should have substituted go blind with being hacked.

The thing is, it may be as simple as an out of tree module screwing up the jump table. Or worse, gcc not doing things that we did not expect. If this is the case, jump labels can be disabled from modifying code.

But if we just want to do the BUG() case, this will be a big hammer to the code and we just prevent any further progress until the issue is addressed. Which may be tell people to disable jump labels in their code, or use a different compiler.

Currently ftrace takes the approach to WARN() and disable itself when it finds an anomaly from what it expects to modify. The times this has triggered has been either a problem with writing to the code, due to securities preventing code modification, or the scan of the relocation tables mistook a data point as code. The later I could foresee happening with jump labels.

-- Steve

#43 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

Quoted 4 lines · Steven Rostedt

Ouch, quite shaken by k.org? I guess I should have substituted go blind with being hacked.

Well, yes, but I would have said exactly the same thing before.

Quoted 8 lines

The thing is, it may be as simple as an out of tree module screwing up the jump table. Or worse, gcc not doing things that we did not expect. If this is the case, jump labels can be disabled from modifying code.

But if we just want to do the BUG() case, this will be a big hammer to the code and we just prevent any further progress until the issue is addressed. Which may be tell people to disable jump labels in their code, or use a different compiler.

That is EXACTLY what should happen. Something is wrong to the point of the kernel is *known* to be executing the wrong code. That is an extremely serious condition and should be treated as such.

If you want, you could have a debug option to demote this to WARN, but I really don't want to see it by default.

-hpa

#44 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

In reply to Jason Baron
Quoted 9 lines · Steven Rostedt
Quoted 9 lines

scripts/update_jump_label.h line 319

Quoted 9 lines
scripts/update_jump_label.h new file +9 -0
+	/*
+	 * This is specific to x86. The jump_table is stored in three
+	 * long words. The first is the location of the jmp target we
+	 * must update.
+	 */
+	cnt = size / sizeof(uint_t);
+
+	for (i = 0; i < cnt; i += 3)
+		if (0)make_nop((void *)ehdr, *(uint_t *)(data + i * sizeof(uint_t)));

I just compiled and booted the

- if (0)make_nop((void *)ehdr, *(uint_t *)(data + i * sizeof(uint_t)));
+ make_nop((void *)ehdr, *(uint_t *)(data + i * sizeof(uint_t)));

version, and it still works.

Phew!

-- Steve

#45 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

In reply to Steven Rostedt
Quoted 35 lines · Steven Rostedt
arch/x86/kernel/jump_label.c +13 -0
index 3fee346..1f7f88f 100644
@@ -16,34 +16,75 @@
 
 #ifdef HAVE_JUMP_LABEL
 
+static unsigned char nop_short[] = { P6_NOP2 };
+
 union jump_code_union {
 	char code[JUMP_LABEL_NOP_SIZE];
 	struct {
 		char jump;
 		int offset;
 	} __attribute__((packed));
+	struct {
+		char jump_short;
+		char offset_short;
+	} __attribute__((packed));
 };
 
 void arch_jump_label_transform(struct jump_entry *entry,
 			       enum jump_label_type type)
 {
 	union jump_code_union code;
+	unsigned char op;
+	unsigned size;
+	unsigned char nop;
+
+	/* Use probe_kernel_read()? */
+	op = *(unsigned char *)entry->code;
+	nop = ideal_nops[NOP_ATOMIC5][0];
 
 	if (type == JUMP_LABEL_ENABLE) {

arch/x86/kernel/jump_label.c line 50

Quoted 10 lines
arch/x86/kernel/jump_label.c +5 -5
-		code.jump = 0xe9;
-		code.offset = entry->target -
-				(entry->code + JUMP_LABEL_NOP_SIZE);
-	} else
-		memcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE);
+		if (op == 0xe9 || op == 0xeb)
+			/* Already enabled. Warn? */
+			return;
+
+		/* FIXME for all archs */

By "archs", do you mean different x86 variants?

arch/x86/kernel/jump_label.c line 51

Quoted 1 line
arch/x86/kernel/jump_label.c +1 -0
+		if (op == nop_short[0]) {

My gut feeling is that all this "trying to determine the jump size by sniffing the instruction" stuff seems pretty fragile. Couldn't you store the jump size in the jump_label structure (even as a bit hidden away somewhere)?

J

Quoted 782 lines
arch/x86/kernel/jump_label.c +28 -1
+			size = 2;
+			code.jump_short = 0xeb;
+			code.offset = entry->target -
+				(entry->code + 2);
+			/* Check for overflow ? */
+		} else if (op == nop) {
+			size = JUMP_LABEL_NOP_SIZE;
+			code.jump = 0xe9;
+			code.offset = entry->target - (entry->code + size);
+		} else
+			return; /* WARN ? */
+
+	} else {
+		if (op == nop_short[0] || nop)
+			/* Already disabled, warn? */
+			return;
+
+		if (op == 0xe9) {
+			size = JUMP_LABEL_NOP_SIZE;
+			memcpy(&code, ideal_nops[NOP_ATOMIC5], size);
+		} else if (op == 0xeb) {
+			size = 2;
+			memcpy(&code, nop_short, size);
+		} else
+			return; /* WARN ? */
+	}
 	get_online_cpus();
 	mutex_lock(&text_mutex);
-	text_poke_smp((void *)entry->code, &code, JUMP_LABEL_NOP_SIZE);
+	text_poke_smp((void *)entry->code, &code, size);
 	mutex_unlock(&text_mutex);
 	put_online_cpus();
 }
 
 void arch_jump_label_text_poke_early(jump_label_t addr)
 {
+	return;
 	text_poke_early((void *)addr, ideal_nops[NOP_ATOMIC5],
 			JUMP_LABEL_NOP_SIZE);
 }
scripts/Makefile +1 -0
index df7678f..738b65c 100644
@@ -13,6 +13,7 @@ hostprogs-$(CONFIG_LOGO)         += pnmtologo
 hostprogs-$(CONFIG_VT)           += conmakehash
 hostprogs-$(CONFIG_IKCONFIG)     += bin2c
 hostprogs-$(BUILD_C_RECORDMCOUNT) += recordmcount
+hostprogs-$(BUILD_UPDATE_JUMP_LABEL) += update_jump_label
 
 always		:= $(hostprogs-y) $(hostprogs-m)
 
scripts/Makefile.build +13 -2
index a0fd502..bc0d89b 100644
@@ -258,6 +258,15 @@ cmd_modversions =								\
 	fi;
 endif
 
+ifdef BUILD_UPDATE_JUMP_LABEL
+update_jump_label_source := $(srctree)/scripts/update_jump_label.c \
+			$(srctree)/scripts/update_jump_label.h
+cmd_update_jump_label =						\
+	if [ $(@) != "scripts/mod/empty.o" ]; then		\
+		$(objtree)/scripts/update_jump_label "$(@)";	\
+	fi;
+endif
+
 ifdef CONFIG_FTRACE_MCOUNT_RECORD
 ifdef BUILD_C_RECORDMCOUNT
 ifeq ("$(origin RECORDMCOUNT_WARN)", "command line")
@@ -294,6 +303,7 @@ define rule_cc_o_c
 	$(cmd_modversions)						  \
 	$(call echo-cmd,record_mcount)					  \
 	$(cmd_record_mcount)						  \
+	$(cmd_update_jump_label)					  \
 	scripts/basic/fixdep $(depfile) $@ '$(call make-cmd,cc_o_c)' >    \
 	                                              $(dot-target).tmp;  \
 	rm -f $(depfile);						  \
@@ -301,13 +311,14 @@ define rule_cc_o_c
 endef
 
 # Built-in and composite module parts
-$(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(obj)/%.o: $(src)/%.c $(recordmcount_source) $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 
 # Single-part modules are special since we need to mark them in $(MODVERDIR)
 
-$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) FORCE
+$(single-used-m): $(obj)/%.o: $(src)/%.c $(recordmcount_source) \
+		  $(update_jump_label_source) FORCE
 	$(call cmd,force_checksrc)
 	$(call if_changed_rule,cc_o_c)
 	@{ echo $(@:.o=.ko); echo $@; } > $(MODVERDIR)/$(@F:.o=.mod)
scripts/update_jump_label.c new file +349 -0
new file mode 100644
index 0000000..86e17bc
@@ -0,0 +1,349 @@
+/*
+ * update_jump_label.c: replace jmps with nops at compile time.
+ * Copyright 2010 Steven Rostedt <[email protected]>, Red Hat Inc.
+ *  Parsing of the elf file was influenced by recordmcount.c
+ *  originally written by and copyright to John F. Reiser <[email protected]>.
+ */
+
+/*
+ * Note, this code is originally designed for x86, but may be used by
+ * other archs to do the nop updates at compile time instead of at boot time.
+ * X86 uses this as an optimization, as jmps can be either 2 bytes or 5 bytes.
+ * Inserting a 2 byte where possible helps with both CPU performance and
+ * icache strain.
+ */
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <getopt.h>
+#include <elf.h>
+#include <fcntl.h>
+#include <setjmp.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+static int fd_map;	/* File descriptor for file being modified. */
+static struct stat sb;	/* Remember .st_size, etc. */
+static int mmap_failed; /* Boolean flag. */
+
+static void die(const char *err, const char *fmt, ...)
+{
+	va_list ap;
+
+	if (err)
+		perror(err);
+
+	if (fmt) {
+		va_start(ap, fmt);
+		fprintf(stderr, "Fatal error:  ");
+		vfprintf(stderr, fmt, ap);
+		fprintf(stderr, "\n");
+		va_end(ap);
+	}
+
+	exit(1);
+}
+
+static void usage(char **argv)
+{
+	char *arg = argv[0];
+	char *p = arg+strlen(arg);
+
+	while (p >= arg && *p != '/')
+		p--;
+	p++;
+
+	printf("usage: %s file\n"
+	       "\n",p);
+	exit(-1);
+}
+
+/* w8rev, w8nat, ...: Handle endianness. */
+
+static uint64_t w8rev(uint64_t const x)
+{
+	return   ((0xff & (x >> (0 * 8))) << (7 * 8))
+	       | ((0xff & (x >> (1 * 8))) << (6 * 8))
+	       | ((0xff & (x >> (2 * 8))) << (5 * 8))
+	       | ((0xff & (x >> (3 * 8))) << (4 * 8))
+	       | ((0xff & (x >> (4 * 8))) << (3 * 8))
+	       | ((0xff & (x >> (5 * 8))) << (2 * 8))
+	       | ((0xff & (x >> (6 * 8))) << (1 * 8))
+	       | ((0xff & (x >> (7 * 8))) << (0 * 8));
+}
+
+static uint32_t w4rev(uint32_t const x)
+{
+	return   ((0xff & (x >> (0 * 8))) << (3 * 8))
+	       | ((0xff & (x >> (1 * 8))) << (2 * 8))
+	       | ((0xff & (x >> (2 * 8))) << (1 * 8))
+	       | ((0xff & (x >> (3 * 8))) << (0 * 8));
+}
+
+static uint32_t w2rev(uint16_t const x)
+{
+	return   ((0xff & (x >> (0 * 8))) << (1 * 8))
+	       | ((0xff & (x >> (1 * 8))) << (0 * 8));
+}
+
+static uint64_t w8nat(uint64_t const x)
+{
+	return x;
+}
+
+static uint32_t w4nat(uint32_t const x)
+{
+	return x;
+}
+
+static uint32_t w2nat(uint16_t const x)
+{
+	return x;
+}
+
+static uint64_t (*w8)(uint64_t);
+static uint32_t (*w)(uint32_t);
+static uint32_t (*w2)(uint16_t);
+
+/* ulseek, uread, ...:  Check return value for errors. */
+
+static off_t
+ulseek(int const fd, off_t const offset, int const whence)
+{
+	off_t const w = lseek(fd, offset, whence);
+	if (w == (off_t)-1)
+		die("lseek", NULL);
+
+	return w;
+}
+
+static size_t
+uread(int const fd, void *const buf, size_t const count)
+{
+	size_t const n = read(fd, buf, count);
+	if (n != count)
+		die("read", NULL);
+
+	return n;
+}
+
+static size_t
+uwrite(int const fd, void const *const buf, size_t const count)
+{
+	size_t const n = write(fd, buf, count);
+	if (n != count)
+		die("write", NULL);
+
+	return n;
+}
+
+static void *
+umalloc(size_t size)
+{
+	void *const addr = malloc(size);
+	if (addr == 0)
+		die("malloc", "malloc failed: %zu bytes\n", size);
+
+	return addr;
+}
+
+/*
+ * Get the whole file as a programming convenience in order to avoid
+ * malloc+lseek+read+free of many pieces.  If successful, then mmap
+ * avoids copying unused pieces; else just read the whole file.
+ * Open for both read and write; new info will be appended to the file.
+ * Use MAP_PRIVATE so that a few changes to the in-memory ElfXX_Ehdr
+ * do not propagate to the file until an explicit overwrite at the last.
+ * This preserves most aspects of consistency (all except .st_size)
+ * for simultaneous readers of the file while we are appending to it.
+ * However, multiple writers still are bad.  We choose not to use
+ * locking because it is expensive and the use case of kernel build
+ * makes multiple writers unlikely.
+ */
+static void *mmap_file(char const *fname)
+{
+	void *addr;
+
+	fd_map = open(fname, O_RDWR);
+	if (fd_map < 0 || fstat(fd_map, &sb) < 0)
+		die(fname, "failed to open file");
+
+	if (!S_ISREG(sb.st_mode))
+		die(NULL, "not a regular file: %s\n", fname);
+
+	addr = mmap(0, sb.st_size, PROT_READ|PROT_WRITE, MAP_PRIVATE,
+		    fd_map, 0);
+
+	mmap_failed = 0;
+	if (addr == MAP_FAILED) {
+		mmap_failed = 1;
+		addr = umalloc(sb.st_size);
+		uread(fd_map, addr, sb.st_size);
+	}
+	return addr;
+}
+
+static void munmap_file(void *addr)
+{
+	if (!mmap_failed)
+		munmap(addr, sb.st_size);
+	else
+		free(addr);
+	close(fd_map);
+}
+
+static unsigned char ideal_nop5_x86_64[5] = { 0x0f, 0x1f, 0x44, 0x00, 0x00 };
+static unsigned char ideal_nop5_x86_32[5] = { 0x3e, 0x8d, 0x74, 0x26, 0x00 };
+static unsigned char ideal_nop2_x86[2] = { 0x66, 0x99 };
+static unsigned char *ideal_nop;
+
+static int (*make_nop)(void *map, size_t const offset);
+
+static int make_nop_x86(void *map, size_t const offset)
+{
+	unsigned char *op;
+	unsigned char *nop;
+	int size;
+
+	/* Determine which type of jmp this is 2 byte or 5. */
+	op = map + offset;
+	switch (*op) {
+	case 0xeb: /* 2 byte */
+		size = 2;
+		nop = ideal_nop2_x86;
+		break;
+	case 0xe9: /* 5 byte */
+		size = 5;
+		nop = ideal_nop;
+		break;
+	default:
+		die(NULL, "Bad jump label section\n");
+	}
+
+	/* convert to nop */
+	ulseek(fd_map, offset, SEEK_SET);
+	uwrite(fd_map, nop, size);
+	return 0;
+}
+
+/* 32 bit and 64 bit are very similar */
+#include "update_jump_label.h"
+#define UPDATE_JUMP_LABEL_64
+#include "update_jump_label.h"
+
+static int do_file(const char *fname)
+{
+	Elf32_Ehdr *const ehdr = mmap_file(fname);
+	unsigned int reltype = 0;
+
+	w = w4nat;
+	w2 = w2nat;
+	w8 = w8nat;
+	switch (ehdr->e_ident[EI_DATA]) {
+		static unsigned int const endian = 1;
+	default:
+		die(NULL, "unrecognized ELF data encoding %d: %s\n",
+			ehdr->e_ident[EI_DATA], fname);
+		break;
+	case ELFDATA2LSB:
+		if (*(unsigned char const *)&endian != 1) {
+			/* main() is big endian, file.o is little endian. */
+			w = w4rev;
+			w2 = w2rev;
+			w8 = w8rev;
+		}
+		break;
+	case ELFDATA2MSB:
+		if (*(unsigned char const *)&endian != 0) {
+			/* main() is little endian, file.o is big endian. */
+			w = w4rev;
+			w2 = w2rev;
+			w8 = w8rev;
+		}
+		break;
+	}  /* end switch */
+
+	if (memcmp(ELFMAG, ehdr->e_ident, SELFMAG) != 0 ||
+	    w2(ehdr->e_type) != ET_REL ||
+	    ehdr->e_ident[EI_VERSION] != EV_CURRENT)
+		die(NULL, "unrecognized ET_REL file %s\n", fname);
+
+	switch (w2(ehdr->e_machine)) {
+	default:
+		die(NULL, "unrecognized e_machine %d %s\n",
+		    w2(ehdr->e_machine), fname);
+		break;
+	case EM_386:
+		reltype = R_386_32;
+		make_nop = make_nop_x86;
+		ideal_nop = ideal_nop5_x86_32;
+		break;
+	case EM_ARM:	 reltype = R_ARM_ABS32;
+			 break;
+	case EM_IA_64:	 reltype = R_IA64_IMM64; break;
+	case EM_MIPS:	 /* reltype: e_class    */ break;
+	case EM_PPC:	 reltype = R_PPC_ADDR32;   break;
+	case EM_PPC64:	 reltype = R_PPC64_ADDR64; break;
+	case EM_S390:    /* reltype: e_class    */ break;
+	case EM_SH:	 reltype = R_SH_DIR32;                 break;
+	case EM_SPARCV9: reltype = R_SPARC_64;     break;
+	case EM_X86_64:
+		make_nop = make_nop_x86;
+		ideal_nop = ideal_nop5_x86_64;
+		reltype = R_X86_64_64;
+		break;
+	}  /* end switch */
+
+	switch (ehdr->e_ident[EI_CLASS]) {
+	default:
+		die(NULL, "unrecognized ELF class %d %s\n",
+		    ehdr->e_ident[EI_CLASS], fname);
+		break;
+	case ELFCLASS32:
+		if (w2(ehdr->e_ehsize) != sizeof(Elf32_Ehdr)
+		||  w2(ehdr->e_shentsize) != sizeof(Elf32_Shdr))
+			die(NULL, "unrecognized ET_REL file: %s\n", fname);
+
+		if (w2(ehdr->e_machine) == EM_S390) {
+			reltype = R_390_32;
+		}
+		if (w2(ehdr->e_machine) == EM_MIPS) {
+			reltype = R_MIPS_32;
+		}
+		do_func32(ehdr, fname, reltype);
+		break;
+	case ELFCLASS64: {
+		Elf64_Ehdr *const ghdr = (Elf64_Ehdr *)ehdr;
+		if (w2(ghdr->e_ehsize) != sizeof(Elf64_Ehdr)
+		||  w2(ghdr->e_shentsize) != sizeof(Elf64_Shdr))
+			die(NULL, "unrecognized ET_REL file: %s\n", fname);
+
+		if (w2(ghdr->e_machine) == EM_S390)
+			reltype = R_390_64;
+
+#if 0
+		if (w2(ghdr->e_machine) == EM_MIPS) {
+			reltype = R_MIPS_64;
+			Elf64_r_sym = MIPS64_r_sym;
+		}
+#endif
+		do_func64(ghdr, fname, reltype);
+		break;
+	}
+	}  /* end switch */
+
+	munmap_file(ehdr);
+	return 0;
+}
+
+int main (int argc, char **argv)
+{
+	if (argc != 2)
+		usage(argv);
+	
+	return do_file(argv[1]);
+}
+
scripts/update_jump_label.h new file +322 -0
new file mode 100644
index 0000000..6ff9846
@@ -0,0 +1,322 @@
+/*
+ * recordmcount.h
+ *
+ * This code was taken out of recordmcount.c written by
+ * Copyright 2009 John F. Reiser <[email protected]>.  All rights reserved.
+ *
+ * The original code had the same algorithms for both 32bit
+ * and 64bit ELF files, but the code was duplicated to support
+ * the difference in structures that were used. This
+ * file creates a macro of everything that is different between
+ * the 64 and 32 bit code, such that by including this header
+ * twice we can create both sets of functions by including this
+ * header once with RECORD_MCOUNT_64 undefined, and again with
+ * it defined.
+ *
+ * This conversion to macros was done by:
+ * Copyright 2010 Steven Rostedt <[email protected]>, Red Hat Inc.
+ *
+ * Licensed under the GNU General Public License, version 2 (GPLv2).
+ */
+
+#undef EBITS
+#undef _w
+#undef _align
+#undef _size
+
+#ifdef UPDATE_JUMP_LABEL_64
+# define EBITS			64
+# define _w			w8
+# define _align			7u
+# define _size			8
+#else
+# define EBITS			32
+# define _w			w
+# define _align			3u
+# define _size			4
+#endif
+
+#define _FBITS(x, e)	x##e
+#define FBITS(x, e)	_FBITS(x,e)
+#define FUNC(x)		FBITS(x,EBITS)
+
+#undef Elf_Addr
+#undef Elf_Ehdr
+#undef Elf_Shdr
+#undef Elf_Rel
+#undef Elf_Rela
+#undef Elf_Sym
+#undef ELF_R_SYM
+#undef ELF_R_TYPE
+
+#define __ATTACH(x,y,z)	x##y##z
+#define ATTACH(x,y,z)	__ATTACH(x,y,z)
+
+#define Elf_Addr	ATTACH(Elf,EBITS,_Addr)
+#define Elf_Ehdr	ATTACH(Elf,EBITS,_Ehdr)
+#define Elf_Shdr	ATTACH(Elf,EBITS,_Shdr)
+#define Elf_Rel		ATTACH(Elf,EBITS,_Rel)
+#define Elf_Rela	ATTACH(Elf,EBITS,_Rela)
+#define Elf_Sym		ATTACH(Elf,EBITS,_Sym)
+#define uint_t		ATTACH(uint,EBITS,_t)
+#define ELF_R_SYM	ATTACH(ELF,EBITS,_R_SYM)
+#define ELF_R_TYPE	ATTACH(ELF,EBITS,_R_TYPE)
+
+#undef get_shdr
+#define get_shdr(ehdr) ((Elf_Shdr *)(_w((ehdr)->e_shoff) + (void *)(ehdr)))
+
+#undef get_section_loc
+#define get_section_loc(ehdr, shdr)(_w((shdr)->sh_offset) + (void *)(ehdr))
+
+/* Functions and pointers that do_file() may override for specific e_machine. */
+
+#if 0
+static uint_t FUNC(fn_ELF_R_SYM)(Elf_Rel const *rp)
+{
+	return ELF_R_SYM(_w(rp->r_info));
+}
+static uint_t (*FUNC(Elf_r_sym))(Elf_Rel const *rp) = FUNC(fn_ELF_R_SYM);
+#endif
+
+static void FUNC(get_sym_str_and_relp)(Elf_Shdr const *const relhdr,
+				 Elf_Ehdr const *const ehdr,
+				 Elf_Sym const **sym0,
+				 char const **str0,
+				 Elf_Rel const **relp)
+{
+	Elf_Shdr *const shdr0 = get_shdr(ehdr);
+	unsigned const symsec_sh_link = w(relhdr->sh_link);
+	Elf_Shdr const *const symsec = &shdr0[symsec_sh_link];
+	Elf_Shdr const *const strsec = &shdr0[w(symsec->sh_link)];
+	Elf_Rel const *const rel0 =
+		(Elf_Rel const *)get_section_loc(ehdr, relhdr);
+
+	*sym0 = (Elf_Sym const *)get_section_loc(ehdr, symsec);
+
+	*str0 = (char const *)get_section_loc(ehdr, strsec);
+
+	*relp = rel0;
+}
+
+/*
+ * Read the relocation table again, but this time its called on sections
+ * that are not going to be traced. The mcount calls here will be converted
+ * into nops.
+ */
+static void FUNC(nop_jump_label)(Elf_Shdr const *const relhdr,
+		       Elf_Ehdr const *const ehdr,
+		       const char *const txtname)
+{
+	Elf_Shdr *const shdr0 = get_shdr(ehdr);
+	Elf_Sym const *sym0;
+	char const *str0;
+	Elf_Rel const *relp;
+	Elf_Rela const *relap;
+	Elf_Shdr const *const shdr = &shdr0[w(relhdr->sh_info)];
+	unsigned rel_entsize = w(relhdr->sh_entsize);
+	unsigned const nrel = _w(relhdr->sh_size) / rel_entsize;
+	int t;
+
+	FUNC(get_sym_str_and_relp)(relhdr, ehdr, &sym0, &str0, &relp);
+
+	for (t = nrel; t > 0; t -= 3) {
+		int ret = -1;
+
+		relap = (Elf_Rela const *)relp;
+		printf("rel offset=%lx info=%lx sym=%lx type=%lx addend=%lx\n",
+		       (long)relap->r_offset, (long)relap->r_info,
+		       (long)ELF_R_SYM(relap->r_info),
+		       (long)ELF_R_TYPE(relap->r_info),
+		       (long)relap->r_addend);
+
+		if (0 && make_nop)
+			ret = make_nop((void *)ehdr, shdr->sh_offset + relp->r_offset);
+
+		/* jump label sections are paired in threes */
+		relp = (Elf_Rel const *)(rel_entsize * 3 + (void *)relp);
+	}
+}
+
+/* Evade ISO C restriction: no declaration after statement in has_rel_mcount. */
+static char const *
+FUNC(__has_rel_jump_table)(Elf_Shdr const *const relhdr,  /* is SHT_REL or SHT_RELA */
+		 Elf_Shdr const *const shdr0,
+		 char const *const shstrtab,
+		 char const *const fname)
+{
+	/* .sh_info depends on .sh_type == SHT_REL[,A] */
+	Elf_Shdr const *const txthdr = &shdr0[w(relhdr->sh_info)];
+	char const *const txtname = &shstrtab[w(txthdr->sh_name)];
+
+	if (strcmp("__jump_table", txtname) == 0) {
+		fprintf(stderr, "warning: __mcount_loc already exists: %s\n",
+			fname);
+//		succeed_file();
+	}
+	if (w(txthdr->sh_type) != SHT_PROGBITS ||
+	    !(w(txthdr->sh_flags) & SHF_EXECINSTR))
+		return NULL;
+	return txtname;
+}
+
+static char const *FUNC(has_rel_jump_table)(Elf_Shdr const *const relhdr,
+				      Elf_Shdr const *const shdr0,
+				      char const *const shstrtab,
+				      char const *const fname)
+{
+	if (w(relhdr->sh_type) != SHT_REL && w(relhdr->sh_type) != SHT_RELA)
+		return NULL;
+	return FUNC(__has_rel_jump_table)(relhdr, shdr0, shstrtab, fname);
+}
+
+/* Find relocation section hdr for a given section */
+static const Elf_Shdr *
+FUNC(find_relhdr)(const Elf_Ehdr *ehdr, const Elf_Shdr *shdr)
+{
+	const Elf_Shdr *shdr0 = get_shdr(ehdr);
+	int nhdr = w2(ehdr->e_shnum);
+	const Elf_Shdr *hdr;
+	int i;
+
+	for (hdr = shdr0, i = 0; i < nhdr; hdr = &shdr0[++i]) {
+		if (w(hdr->sh_type) != SHT_REL &&
+		    w(hdr->sh_type) != SHT_RELA)
+			continue;
+
+		/*
+		 * The relocation section's info field holds
+		 * the section index that it represents.
+		 */
+		if (shdr == &shdr0[w(hdr->sh_info)])
+			return hdr;
+	}
+	return NULL;
+}
+
+/* Find a section headr based on name and type */
+static const Elf_Shdr *
+FUNC(find_shdr)(const Elf_Ehdr *ehdr, const char *name, uint_t type)
+{
+	const Elf_Shdr *shdr0 = get_shdr(ehdr);
+	const Elf_Shdr *shstr = &shdr0[w2(ehdr->e_shstrndx)];
+	const char *shstrtab = (char *)get_section_loc(ehdr, shstr);
+	int nhdr = w2(ehdr->e_shnum);
+	const Elf_Shdr *hdr;
+	const char *hdrname;
+	int i;
+
+	for (hdr = shdr0, i = 0; i < nhdr; hdr = &shdr0[++i]) {
+		if (w(hdr->sh_type) != type)
+			continue;
+
+		/* If we are just looking for a section by type (ie. SYMTAB) */
+		if (!name)
+			return hdr;
+
+		hdrname = &shstrtab[w(hdr->sh_name)];
+		if (strcmp(hdrname, name) == 0)
+			return hdr;
+	}
+	return NULL;
+}
+
+static void
+FUNC(section_update)(const Elf_Ehdr *ehdr, const Elf_Shdr *symhdr,
+		     unsigned shtype, const Elf_Rel *rel, void *data)
+{
+	const Elf_Shdr *shdr0 = get_shdr(ehdr);
+	const Elf_Shdr *targethdr;
+	const Elf_Rela *rela;
+	const Elf_Sym *syment;
+	uint_t offset = _w(rel->r_offset);
+	uint_t info = _w(rel->r_info);
+	uint_t sym = ELF_R_SYM(info);
+	uint_t type = ELF_R_TYPE(info);
+	uint_t addend;
+	uint_t targetloc;
+
+	if (shtype == SHT_RELA) {
+		rela = (const Elf_Rela *)rel;
+		addend = _w(rela->r_addend);
+	} else
+		addend = _w(*(unsigned short *)(data + offset));
+
+	syment = (const Elf_Sym *)get_section_loc(ehdr, symhdr);
+	targethdr = &shdr0[w2(syment[sym].st_shndx)];
+	targetloc = _w(targethdr->sh_offset);
+
+	/* TODO, need a separate function for all archs */
+	if (type != R_386_32)
+		die(NULL, "Arch relocation type %d not supported", type);
+
+	targetloc += addend;
+
+#if 1
+	printf("offset=%x target=%x shoffset=%x add=%x\n",
+	       offset, targetloc, _w(targethdr->sh_offset), addend);
+#endif
+	*(uint_t *)(data + offset) = targetloc;
+}
+
+/* Overall supervision for Elf32 ET_REL file. */
+static void
+FUNC(do_func)(Elf_Ehdr *ehdr, char const *const fname, unsigned const reltype)
+{
+	const Elf_Shdr *jlshdr;
+	const Elf_Shdr *jlrhdr;
+	const Elf_Shdr *symhdr;
+	const Elf_Rel *rel;
+	unsigned size;
+	unsigned cnt;
+	unsigned i;
+	uint_t type;
+	void *jdata;
+	void *data;
+
+	jlshdr = FUNC(find_shdr)(ehdr, "__jump_table", SHT_PROGBITS);
+	if (!jlshdr)
+		return;
+
+	jlrhdr = FUNC(find_relhdr)(ehdr, jlshdr);
+	if (!jlrhdr)
+		return;
+
+	/*
+	 * Create and fill in the __jump_table section and use it to
+	 * find the offsets into the text that we want to update.
+	 * We create it so that we do not depend on the order of the
+	 * relocations, and use the table directly, as it is broken
+	 * up into sections.
+	 */
+	size = _w(jlshdr->sh_size);
+	data = umalloc(size);
+
+	jdata = (void *)get_section_loc(ehdr, jlshdr);
+	memcpy(data, jdata, size);
+
+	cnt = _w(jlrhdr->sh_size) / w(jlrhdr->sh_entsize);
+
+	rel = (const Elf_Rel *)get_section_loc(ehdr, jlrhdr);
+
+	/* Is this as Rel or Rela? */
+	type = w(jlrhdr->sh_type);
+
+	symhdr = FUNC(find_shdr)(ehdr, NULL, SHT_SYMTAB);
+
+	for (i = 0; i < cnt; i++) {
+		FUNC(section_update)(ehdr, symhdr, type, rel, data);
+		rel = (void *)rel + w(jlrhdr->sh_entsize);
+	}
+
+	/*
+	 * This is specific to x86. The jump_table is stored in three
+	 * long words. The first is the location of the jmp target we
+	 * must update.
+	 */
+	cnt = size / sizeof(uint_t);
+
+	for (i = 0; i < cnt; i += 3)
+		if (0)make_nop((void *)ehdr, *(uint_t *)(data + i * sizeof(uint_t)));
+
+	free(data);
+}
#46 lore raw

Re: [PATCH][RFC] jump_labels/x86: Use either 5 byte or 2 byte jumps

Quoted 35 lines · Jeremy Fitzhardinge
Quoted 34 lines · Steven Rostedt
arch/x86/kernel/jump_label.c +13 -0
index 3fee346..1f7f88f 100644
@@ -16,34 +16,75 @@
 
 #ifdef HAVE_JUMP_LABEL
 
+static unsigned char nop_short[] = { P6_NOP2 };
+
 union jump_code_union {
 	char code[JUMP_LABEL_NOP_SIZE];
 	struct {
 		char jump;
 		int offset;
 	} __attribute__((packed));
+	struct {
+		char jump_short;
+		char offset_short;
+	} __attribute__((packed));
 };
 
 void arch_jump_label_transform(struct jump_entry *entry,
 			       enum jump_label_type type)
 {
 	union jump_code_union code;
+	unsigned char op;
+	unsigned size;
+	unsigned char nop;
+
+	/* Use probe_kernel_read()? */
+	op = *(unsigned char *)entry->code;
+	nop = ideal_nops[NOP_ATOMIC5][0];
 
Quoted 13 lines

arch/x86/kernel/jump_label.c line 50

Quoted 11 lines
arch/x86/kernel/jump_label.c +5 -5
 	if (type == JUMP_LABEL_ENABLE) {
-		code.jump = 0xe9;
-		code.offset = entry->target -
-				(entry->code + JUMP_LABEL_NOP_SIZE);
-	} else
-		memcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE);
+		if (op == 0xe9 || op == 0xeb)
+			/* Already enabled. Warn? */
+			return;
+
+		/* FIXME for all archs */

By "archs", do you mean different x86 variants?

Yeah, that was a confusing use of archs. This was to make sure it works for all nops for different variants of x86.

Quoted 7 lines

arch/x86/kernel/jump_label.c line 51

Quoted 2 lines
arch/x86/kernel/jump_label.c +1 -0
+		if (op == nop_short[0]) {

My gut feeling is that all this "trying to determine the jump size by sniffing the instruction" stuff seems pretty fragile. Couldn't you store the jump size in the jump_label structure (even as a bit hidden away somewhere)?

We could but it's not as fragile as you think. This is machine code, and it should be a jump or not. I could add more checks, that is, to look at the full nop to make sure it is truly a nop. But for the jump side, a byte instruction that starts with e9 is definitely a jump.

I could harden this more like what we do with mcount updates in the function tracer. I actually calculate what I expect to be there before looking at what is there. The entire instruction is checked. If it does not match, then we fail and give big warnings about it.

Other than that, it should be quite solid. If we don't get a match, we should warn and disable jump labels.

No BUG()!

-- Steve

#48 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

In reply to Jeremy Fitzhardinge
Quoted 78 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +8 -12
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

Hi,

I just realized that the early call to jump_label_inc(), isn't being honored with this patch until later when we invoke jump_label_init(). That strikes me as being inconsistent. When jump_label_inc() returns we should expect the branch to be updated.

Thus, I think what probably want is to add a new 'int jump_label_init' flag. If its not set we can call 'jump_label_init()' from jump_label_inc()/dec(). And jump_label_init() can avoid initialization if its already set.

Thanks,

-Jason

#49 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 81 lines · Jason Baron
Quoted 78 lines · Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

If a key has been enabled before jump_label_init() is called, don't nop it out.

This removes arch_jump_label_text_poke_early() (which can only nop out a site) and uses arch_jump_label_transform() instead.

---
 include/linux/jump_label.h |    3 ++-
 kernel/jump_label.c        |   20 ++++++++------------
 2 files changed, 10 insertions(+), 13 deletions(-)
include/linux/jump_label.h +2 -1
index 1213e9d..c8fb1b3 100644
@@ -45,7 +45,8 @@ extern void jump_label_lock(void);
 extern void jump_label_unlock(void);
 extern void arch_jump_label_transform(struct jump_entry *entry,
 				 enum jump_label_type type);
-extern void arch_jump_label_text_poke_early(jump_label_t addr);
+extern void arch_jump_label_transform_early(struct jump_entry *entry,
+				 enum jump_label_type type);
 extern int jump_label_text_reserved(void *start, void *end);
 extern void jump_label_inc(struct jump_label_key *key);
 extern void jump_label_dec(struct jump_label_key *key);
kernel/jump_label.c +8 -12
index a8ce450..059202d5 100644
@@ -121,13 +121,6 @@ static void __jump_label_update(struct jump_label_key *key,
 	}
 }
 
-/*
- * Not all archs need this.
- */
-void __weak arch_jump_label_text_poke_early(jump_label_t addr)
-{
-}
-
 static __init int jump_label_init(void)
 {
 	struct jump_entry *iter_start = __start___jump_table;
@@ -139,12 +132,15 @@ static __init int jump_label_init(void)
 	jump_label_sort_entries(iter_start, iter_stop);
 
 	for (iter = iter_start; iter < iter_stop; iter++) {
-		arch_jump_label_text_poke_early(iter->code);
-		if (iter->key == (jump_label_t)(unsigned long)key)
+		struct jump_label_key *iterk;
+
+		iterk = (struct jump_label_key *)(unsigned long)iter->key;
+		arch_jump_label_transform(iter, jump_label_enabled(iterk) ?
+					  JUMP_LABEL_ENABLE : JUMP_LABEL_DISABLE);
+		if (iterk == key)
 			continue;
 
-		key = (struct jump_label_key *)(unsigned long)iter->key;
-		atomic_set(&key->enabled, 0);
+		key = iterk;
 		key->entries = iter;
 #ifdef CONFIG_MODULES
 		key->next = NULL;
@@ -212,7 +208,7 @@ void jump_label_apply_nops(struct module *mod)
 		return;
 
 	for (iter = iter_start; iter < iter_stop; iter++)
-		arch_jump_label_text_poke_early(iter->code);
+		arch_jump_label_transform(iter, JUMP_LABEL_DISABLE);
 }
 
 static int jump_label_add_module(struct module *mod)
Signature · 1 line
1.7.6.2

Hi,

Quoted 4 lines

I just realized that the early call to jump_label_inc(), isn't being honored with this patch until later when we invoke jump_label_init(). That strikes me as being inconsistent. When jump_label_inc() returns we should expect the branch to be updated.

Why is that? It looks to me like it will unconditionally update the instruction, irrespective of whether _init() has been called?

Quoted 3 lines

Thus, I think what probably want is to add a new 'int jump_label_init' flag. If its not set we can call 'jump_label_init()' from jump_label_inc()/dec().

Hm. I worry that it may end up calling jump_label_init() in an unexpected context, especially since it may well be config-dependent, or adding a jump_label_inc() later on starts mysteriously failing.

Quoted 2 lines

And jump_label_init() can avoid initialization
if its already set.

That doesn't seem worthwhile in itself. I suspect the number of "early" jump_label_incs will be very small (or we should look at doing the init earlier).

J

#50 lore raw

Re: [PATCH RFC V2 3/5] jump_label: if a key has already been initialized, don't nop it out

Quoted 10 lines · Jeremy Fitzhardinge
Quoted 6 lines

Hi,

I just realized that the early call to jump_label_inc(), isn't being honored with this patch until later when we invoke jump_label_init(). That strikes me as being inconsistent. When jump_label_inc() returns we should expect the branch to be updated.

Why is that? It looks to me like it will unconditionally update the instruction, irrespective of whether _init() has been called?

No. jump_label_init(), sets up key->entries, to point into the jump table...before that jump_label_update(), doesn't know where the table is located, and will just return, without doing the update.

Quoted 7 lines
Quoted 3 lines

Thus, I think what probably want is to add a new 'int jump_label_init' flag. If its not set we can call 'jump_label_init()' from jump_label_inc()/dec().

Hm. I worry that it may end up calling jump_label_init() in an unexpected context, especially since it may well be config-dependent, or adding a jump_label_inc() later on starts mysteriously failing.

good point.

Quoted 9 lines
Quoted 3 lines

And jump_label_init() can avoid initialization
if its already set.

That doesn't seem worthwhile in itself. I suspect the number of "early" jump_label_incs will be very small (or we should look at doing the init earlier).

J

I have it as 'early_initcall()', but perhaps it should be moved into init/main.c. I don't think there's any reason it can't be done super early. So I think this might be the best answer. It will also simplify your series.

Thanks,

-Jason

#51 lore raw

RFC PATCH v2 4/5 x86/jump_label: drop arch_jump_label_text_poke_early()

In reply to Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

It is no longer used.

---
 arch/x86/kernel/jump_label.c |    6 ------
 1 files changed, 0 insertions(+), 6 deletions(-)
arch/x86/kernel/jump_label.c +0 -6
index 3fee346..2ad0298 100644
@@ -42,10 +42,4 @@ void arch_jump_label_transform(struct jump_entry *entry,
 	put_online_cpus();
 }
 
-void arch_jump_label_text_poke_early(jump_label_t addr)
-{
-	text_poke_early((void *)addr, ideal_nops[NOP_ATOMIC5],
-			JUMP_LABEL_NOP_SIZE);
-}
-
 #endif
Signature · 1 line
1.7.6.2
#52 lore raw

RFC PATCH v2 5/5 sparc/jump_label: drop arch_jump_label_text_poke_early()

In reply to Jeremy Fitzhardinge

From: Jeremy Fitzhardinge <[email protected]>

It is no longer used.

---
 arch/sparc/kernel/jump_label.c |    8 --------
 1 files changed, 0 insertions(+), 8 deletions(-)
arch/sparc/kernel/jump_label.c +0 -8
index ea2dafc..971fd43 100644
@@ -36,12 +36,4 @@ void arch_jump_label_transform(struct jump_entry *entry,
 	put_online_cpus();
 }
 
-void arch_jump_label_text_poke_early(jump_label_t addr)
-{
-	u32 *insn_p = (u32 *) (unsigned long) addr;
-
-	*insn_p = 0x01000000;
-	flushi(insn_p);
-}
-
 #endif
Signature · 1 line
1.7.6.2