diff options
Diffstat (limited to 'tools')
110 files changed, 2863 insertions, 1739 deletions
diff --git a/tools/Makefile b/tools/Makefile index c8a90d01dd8e..221e1ce78b06 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -19,6 +19,7 @@ help: @echo ' kvm_stat - top-like utility for displaying kvm statistics' @echo ' leds - LEDs tools' @echo ' lguest - a minimal 32-bit x86 hypervisor' + @echo ' liblockdep - user-space wrapper for kernel locking-validator' @echo ' net - misc networking tools' @echo ' perf - Linux performance measurement and analysis tool' @echo ' selftests - various kernel selftests' @@ -89,7 +90,7 @@ freefall: FORCE kvm_stat: FORCE $(call descend,kvm/$@) -all: acpi cgroup cpupower gpio hv firewire lguest \ +all: acpi cgroup cpupower gpio hv firewire lguest liblockdep \ perf selftests turbostat usb \ virtio vm net x86_energy_perf_policy \ tmon freefall objtool kvm_stat @@ -103,6 +104,9 @@ cpupower_install: cgroup_install firewire_install gpio_install hv_install lguest_install perf_install usb_install virtio_install vm_install net_install objtool_install: $(call descend,$(@:_install=),install) +liblockdep_install: + $(call descend,lib/lockdep,install) + selftests_install: $(call descend,testing/$(@:_install=),install) @@ -119,7 +123,7 @@ kvm_stat_install: $(call descend,kvm/$(@:_install=),install) install: acpi_install cgroup_install cpupower_install gpio_install \ - hv_install firewire_install lguest_install \ + hv_install firewire_install lguest_install liblockdep_install \ perf_install selftests_install turbostat_install usb_install \ virtio_install vm_install net_install x86_energy_perf_policy_install \ tmon_install freefall_install objtool_install kvm_stat_install diff --git a/tools/include/asm/sections.h b/tools/include/asm/sections.h new file mode 100644 index 000000000000..a80643d7a7f1 --- /dev/null +++ b/tools/include/asm/sections.h @@ -0,0 +1,4 @@ +#ifndef __TOOLS_INCLUDE_LINUX_ASM_SECTIONS_H +#define __TOOLS_INCLUDE_LINUX_ASM_SECTIONS_H + +#endif /* __TOOLS_INCLUDE_LINUX_ASM_SECTIONS_H */ diff --git a/tools/include/linux/bitops.h b/tools/include/linux/bitops.h index 1aecad369af5..969db1981868 100644 --- a/tools/include/linux/bitops.h +++ b/tools/include/linux/bitops.h @@ -61,4 +61,14 @@ static inline unsigned fls_long(unsigned long l) return fls64(l); } +/** + * rol32 - rotate a 32-bit value left + * @word: value to rotate + * @shift: bits to roll + */ +static inline __u32 rol32(__u32 word, unsigned int shift) +{ + return (word << shift) | (word >> ((-shift) & 31)); +} + #endif diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h index 8b129e314c7e..d7a5604c38d7 100644 --- a/tools/include/linux/compiler.h +++ b/tools/include/linux/compiler.h @@ -49,6 +49,10 @@ # define __maybe_unused __attribute__((unused)) #endif +#ifndef __used +# define __used __attribute__((__unused__)) +#endif + #ifndef __packed # define __packed __attribute__((__packed__)) #endif @@ -69,6 +73,14 @@ # define unlikely(x) __builtin_expect(!!(x), 0) #endif +#ifndef __init +# define __init +#endif + +#ifndef noinline +# define noinline +#endif + #define uninitialized_var(x) x = *(&(x)) #define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x)) diff --git a/tools/lib/lockdep/uinclude/linux/debug_locks.h b/tools/include/linux/debug_locks.h index f38eb64df794..61cc7f501168 100644 --- a/tools/lib/lockdep/uinclude/linux/debug_locks.h +++ b/tools/include/linux/debug_locks.h @@ -3,8 +3,9 @@ #include <stddef.h> #include <linux/compiler.h> +#include <asm/bug.h> -#define DEBUG_LOCKS_WARN_ON(x) (x) +#define DEBUG_LOCKS_WARN_ON(x) WARN_ON(x) extern bool debug_locks; extern bool debug_locks_silent; diff --git a/tools/include/linux/delay.h b/tools/include/linux/delay.h new file mode 100644 index 000000000000..55aa4173af1f --- /dev/null +++ b/tools/include/linux/delay.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_DELAY_H +#define _TOOLS_INCLUDE_LINUX_DELAY_H + +#endif /* _TOOLS_INCLUDE_LINUX_DELAY_H */ diff --git a/tools/include/linux/err.h b/tools/include/linux/err.h index bdc3dd8131d4..abf0478a8fb2 100644 --- a/tools/include/linux/err.h +++ b/tools/include/linux/err.h @@ -46,4 +46,9 @@ static inline bool __must_check IS_ERR(__force const void *ptr) return IS_ERR_VALUE((unsigned long)ptr); } +static inline bool __must_check IS_ERR_OR_NULL(__force const void *ptr) +{ + return unlikely(!ptr) || IS_ERR_VALUE((unsigned long)ptr); +} + #endif /* _LINUX_ERR_H */ diff --git a/tools/include/linux/ftrace.h b/tools/include/linux/ftrace.h new file mode 100644 index 000000000000..949f541ce11e --- /dev/null +++ b/tools/include/linux/ftrace.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_FTRACE_H +#define _TOOLS_INCLUDE_LINUX_FTRACE_H + +#endif /* _TOOLS_INCLUDE_LINUX_FTRACE_H */ diff --git a/tools/include/linux/gfp.h b/tools/include/linux/gfp.h new file mode 100644 index 000000000000..22030756fbc0 --- /dev/null +++ b/tools/include/linux/gfp.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_GFP_H +#define _TOOLS_INCLUDE_LINUX_GFP_H + +#endif /* _TOOLS_INCLUDE_LINUX_GFP_H */ diff --git a/tools/lib/lockdep/uinclude/linux/hardirq.h b/tools/include/linux/hardirq.h index c8f3f8f58729..c8f3f8f58729 100644 --- a/tools/lib/lockdep/uinclude/linux/hardirq.h +++ b/tools/include/linux/hardirq.h diff --git a/tools/include/linux/interrupt.h b/tools/include/linux/interrupt.h new file mode 100644 index 000000000000..6be25bbdca9e --- /dev/null +++ b/tools/include/linux/interrupt.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_INTERRUPT_H +#define _TOOLS_INCLUDE_LINUX_INTERRUPT_H + +#endif /* _TOOLS_INCLUDE_LINUX_INTERRUPT_H */ diff --git a/tools/lib/lockdep/uinclude/linux/irqflags.h b/tools/include/linux/irqflags.h index 6cc296f0fad0..df77669cfe1c 100644 --- a/tools/lib/lockdep/uinclude/linux/irqflags.h +++ b/tools/include/linux/irqflags.h @@ -17,19 +17,19 @@ #define raw_local_irq_disable() do { } while (0) #define raw_local_irq_enable() do { } while (0) #define raw_local_irq_save(flags) ((flags) = 0) -#define raw_local_irq_restore(flags) do { } while (0) +#define raw_local_irq_restore(flags) ((void)(flags)) #define raw_local_save_flags(flags) ((flags) = 0) -#define raw_irqs_disabled_flags(flags) do { } while (0) +#define raw_irqs_disabled_flags(flags) ((void)(flags)) #define raw_irqs_disabled() 0 #define raw_safe_halt() #define local_irq_enable() do { } while (0) #define local_irq_disable() do { } while (0) #define local_irq_save(flags) ((flags) = 0) -#define local_irq_restore(flags) do { } while (0) +#define local_irq_restore(flags) ((void)(flags)) #define local_save_flags(flags) ((flags) = 0) #define irqs_disabled() (1) -#define irqs_disabled_flags(flags) (0) +#define irqs_disabled_flags(flags) ((void)(flags), 0) #define safe_halt() do { } while (0) #define trace_lock_release(x, y) diff --git a/tools/include/linux/jhash.h b/tools/include/linux/jhash.h new file mode 100644 index 000000000000..348c6f47e4cc --- /dev/null +++ b/tools/include/linux/jhash.h @@ -0,0 +1,175 @@ +#ifndef _LINUX_JHASH_H +#define _LINUX_JHASH_H + +/* jhash.h: Jenkins hash support. + * + * Copyright (C) 2006. Bob Jenkins (bob_jenkins@burtleburtle.net) + * + * http://burtleburtle.net/bob/hash/ + * + * These are the credits from Bob's sources: + * + * lookup3.c, by Bob Jenkins, May 2006, Public Domain. + * + * These are functions for producing 32-bit hashes for hash table lookup. + * hashword(), hashlittle(), hashlittle2(), hashbig(), mix(), and final() + * are externally useful functions. Routines to test the hash are included + * if SELF_TEST is defined. You can use this free for any purpose. It's in + * the public domain. It has no warranty. + * + * Copyright (C) 2009-2010 Jozsef Kadlecsik (kadlec@blackhole.kfki.hu) + * + * I've modified Bob's hash to be useful in the Linux kernel, and + * any bugs present are my fault. + * Jozsef + */ +#include <linux/bitops.h> +#include <linux/unaligned/packed_struct.h> + +/* Best hash sizes are of power of two */ +#define jhash_size(n) ((u32)1<<(n)) +/* Mask the hash value, i.e (value & jhash_mask(n)) instead of (value % n) */ +#define jhash_mask(n) (jhash_size(n)-1) + +/* __jhash_mix -- mix 3 32-bit values reversibly. */ +#define __jhash_mix(a, b, c) \ +{ \ + a -= c; a ^= rol32(c, 4); c += b; \ + b -= a; b ^= rol32(a, 6); a += c; \ + c -= b; c ^= rol32(b, 8); b += a; \ + a -= c; a ^= rol32(c, 16); c += b; \ + b -= a; b ^= rol32(a, 19); a += c; \ + c -= b; c ^= rol32(b, 4); b += a; \ +} + +/* __jhash_final - final mixing of 3 32-bit values (a,b,c) into c */ +#define __jhash_final(a, b, c) \ +{ \ + c ^= b; c -= rol32(b, 14); \ + a ^= c; a -= rol32(c, 11); \ + b ^= a; b -= rol32(a, 25); \ + c ^= b; c -= rol32(b, 16); \ + a ^= c; a -= rol32(c, 4); \ + b ^= a; b -= rol32(a, 14); \ + c ^= b; c -= rol32(b, 24); \ +} + +/* An arbitrary initial parameter */ +#define JHASH_INITVAL 0xdeadbeef + +/* jhash - hash an arbitrary key + * @k: sequence of bytes as key + * @length: the length of the key + * @initval: the previous hash, or an arbitray value + * + * The generic version, hashes an arbitrary sequence of bytes. + * No alignment or length assumptions are made about the input key. + * + * Returns the hash value of the key. The result depends on endianness. + */ +static inline u32 jhash(const void *key, u32 length, u32 initval) +{ + u32 a, b, c; + const u8 *k = key; + + /* Set up the internal state */ + a = b = c = JHASH_INITVAL + length + initval; + + /* All but the last block: affect some 32 bits of (a,b,c) */ + while (length > 12) { + a += __get_unaligned_cpu32(k); + b += __get_unaligned_cpu32(k + 4); + c += __get_unaligned_cpu32(k + 8); + __jhash_mix(a, b, c); + length -= 12; + k += 12; + } + /* Last block: affect all 32 bits of (c) */ + /* All the case statements fall through */ + switch (length) { + case 12: c += (u32)k[11]<<24; + case 11: c += (u32)k[10]<<16; + case 10: c += (u32)k[9]<<8; + case 9: c += k[8]; + case 8: b += (u32)k[7]<<24; + case 7: b += (u32)k[6]<<16; + case 6: b += (u32)k[5]<<8; + case 5: b += k[4]; + case 4: a += (u32)k[3]<<24; + case 3: a += (u32)k[2]<<16; + case 2: a += (u32)k[1]<<8; + case 1: a += k[0]; + __jhash_final(a, b, c); + case 0: /* Nothing left to add */ + break; + } + + return c; +} + +/* jhash2 - hash an array of u32's + * @k: the key which must be an array of u32's + * @length: the number of u32's in the key + * @initval: the previous hash, or an arbitray value + * + * Returns the hash value of the key. + */ +static inline u32 jhash2(const u32 *k, u32 length, u32 initval) +{ + u32 a, b, c; + + /* Set up the internal state */ + a = b = c = JHASH_INITVAL + (length<<2) + initval; + + /* Handle most of the key */ + while (length > 3) { + a += k[0]; + b += k[1]; + c += k[2]; + __jhash_mix(a, b, c); + length -= 3; + k += 3; + } + + /* Handle the last 3 u32's: all the case statements fall through */ + switch (length) { + case 3: c += k[2]; + case 2: b += k[1]; + case 1: a += k[0]; + __jhash_final(a, b, c); + case 0: /* Nothing left to add */ + break; + } + + return c; +} + + +/* __jhash_nwords - hash exactly 3, 2 or 1 word(s) */ +static inline u32 __jhash_nwords(u32 a, u32 b, u32 c, u32 initval) +{ + a += initval; + b += initval; + c += initval; + + __jhash_final(a, b, c); + + return c; +} + +static inline u32 jhash_3words(u32 a, u32 b, u32 c, u32 initval) +{ + return __jhash_nwords(a, b, c, initval + JHASH_INITVAL + (3 << 2)); +} + +static inline u32 jhash_2words(u32 a, u32 b, u32 initval) +{ + return __jhash_nwords(a, b, 0, initval + JHASH_INITVAL + (2 << 2)); +} + +static inline u32 jhash_1word(u32 a, u32 initval) +{ + return __jhash_nwords(a, 0, 0, initval + JHASH_INITVAL + (1 << 2)); +} + +#endif /* _LINUX_JHASH_H */ diff --git a/tools/lib/lockdep/uinclude/linux/kallsyms.h b/tools/include/linux/kallsyms.h index b0f2dbdf1a15..582cc1e5f3a4 100644 --- a/tools/lib/lockdep/uinclude/linux/kallsyms.h +++ b/tools/include/linux/kallsyms.h @@ -3,6 +3,7 @@ #include <linux/kernel.h> #include <stdio.h> +#include <unistd.h> #define KSYM_NAME_LEN 128 @@ -24,7 +25,7 @@ static inline void print_ip_sym(unsigned long ip) name = backtrace_symbols((void **)&ip, 1); - printf("%s\n", *name); + dprintf(STDOUT_FILENO, "%s\n", *name); free(name); } diff --git a/tools/lib/lockdep/uinclude/linux/kern_levels.h b/tools/include/linux/kern_levels.h index 3b9bade28698..3b9bade28698 100644 --- a/tools/lib/lockdep/uinclude/linux/kern_levels.h +++ b/tools/include/linux/kern_levels.h diff --git a/tools/include/linux/kernel.h b/tools/include/linux/kernel.h index 039bb85e4171..77d2e94ca5df 100644 --- a/tools/include/linux/kernel.h +++ b/tools/include/linux/kernel.h @@ -34,6 +34,7 @@ (type *)((char *)__mptr - offsetof(type, member)); }) #endif +#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) #define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); })) #ifndef max @@ -112,4 +113,7 @@ int scnprintf(char * buf, size_t size, const char * fmt, ...); #define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1) #define round_down(x, y) ((x) & ~__round_mask(x, y)) +#define current_gfp_context(k) 0 +#define synchronize_sched() + #endif diff --git a/tools/lib/lockdep/uinclude/linux/kmemcheck.h b/tools/include/linux/kmemcheck.h index 94d598bc6abe..94d598bc6abe 100644 --- a/tools/lib/lockdep/uinclude/linux/kmemcheck.h +++ b/tools/include/linux/kmemcheck.h diff --git a/tools/include/linux/linkage.h b/tools/include/linux/linkage.h new file mode 100644 index 000000000000..bc763d500262 --- /dev/null +++ b/tools/include/linux/linkage.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_LINKAGE_H +#define _TOOLS_INCLUDE_LINUX_LINKAGE_H + +#endif /* _TOOLS_INCLUDE_LINUX_LINKAGE_H */ diff --git a/tools/lib/lockdep/uinclude/linux/lockdep.h b/tools/include/linux/lockdep.h index c808c7d02d21..8da3e8effafa 100644 --- a/tools/lib/lockdep/uinclude/linux/lockdep.h +++ b/tools/include/linux/lockdep.h @@ -7,8 +7,15 @@ #include <limits.h> #include <linux/utsname.h> #include <linux/compiler.h> +#include <linux/export.h> +#include <linux/kern_levels.h> +#include <linux/err.h> +#include <linux/rcu.h> +#include <linux/list.h> +#include <linux/hardirq.h> +#include <unistd.h> -#define MAX_LOCK_DEPTH 2000UL +#define MAX_LOCK_DEPTH 63UL #define asmlinkage #define __visible @@ -29,31 +36,32 @@ extern struct task_struct *__curr(void); #define current (__curr()) -#define debug_locks_off() 1 +static inline int debug_locks_off(void) +{ + return 1; +} + #define task_pid_nr(tsk) ((tsk)->pid) #define KSYM_NAME_LEN 128 -#define printk printf +#define printk(...) dprintf(STDOUT_FILENO, __VA_ARGS__) +#define pr_err(format, ...) fprintf (stderr, format, ## __VA_ARGS__) +#define pr_warn pr_err #define list_del_rcu list_del #define atomic_t unsigned long #define atomic_inc(x) ((*(x))++) -static struct new_utsname *init_utsname(void) -{ - static struct new_utsname n = (struct new_utsname) { - .release = "liblockdep", - .version = LIBLOCKDEP_VERSION, - }; - - return &n; -} - #define print_tainted() "" #define static_obj(x) 1 #define debug_show_all_locks() extern void debug_check_no_locks_held(void); +static __used bool __is_kernel_percpu_address(unsigned long addr, void *can_addr) +{ + return false; +} + #endif diff --git a/tools/lib/lockdep/uinclude/linux/module.h b/tools/include/linux/module.h index 09c7a7be8ccc..07055db296f3 100644 --- a/tools/lib/lockdep/uinclude/linux/module.h +++ b/tools/include/linux/module.h @@ -3,4 +3,9 @@ #define module_param(name, type, perm) +static inline bool __is_module_percpu_address(unsigned long addr, unsigned long *can_addr) +{ + return false; +} + #endif diff --git a/tools/include/linux/mutex.h b/tools/include/linux/mutex.h new file mode 100644 index 000000000000..a8180d25f2fc --- /dev/null +++ b/tools/include/linux/mutex.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_MUTEX_H +#define _TOOLS_INCLUDE_LINUX_MUTEX_H + +#endif /* _TOOLS_INCLUDE_LINUX_MUTEX_H */ diff --git a/tools/include/linux/proc_fs.h b/tools/include/linux/proc_fs.h new file mode 100644 index 000000000000..8b3b03b64fda --- /dev/null +++ b/tools/include/linux/proc_fs.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_PROC_FS_H +#define _TOOLS_INCLUDE_LINUX_PROC_FS_H + +#endif /* _TOOLS_INCLUDE_LINUX_PROC_FS_H */ diff --git a/tools/lib/lockdep/uinclude/linux/rcu.h b/tools/include/linux/rcu.h index 042ee8e463c9..5080649dad04 100644 --- a/tools/lib/lockdep/uinclude/linux/rcu.h +++ b/tools/include/linux/rcu.h @@ -18,4 +18,7 @@ static inline bool rcu_is_watching(void) return false; } +#define rcu_assign_pointer(p, v) ((p) = (v)) +#define RCU_INIT_POINTER(p, v) p=(v) + #endif diff --git a/tools/include/linux/sched/clock.h b/tools/include/linux/sched/clock.h new file mode 100644 index 000000000000..5837d17c4182 --- /dev/null +++ b/tools/include/linux/sched/clock.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_PERF_LINUX_SCHED_CLOCK_H +#define _TOOLS_PERF_LINUX_SCHED_CLOCK_H + +#endif /* _TOOLS_PERF_LINUX_SCHED_CLOCK_H */ diff --git a/tools/include/linux/sched/mm.h b/tools/include/linux/sched/mm.h new file mode 100644 index 000000000000..c8d9f19c1f35 --- /dev/null +++ b/tools/include/linux/sched/mm.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_PERF_LINUX_SCHED_MM_H +#define _TOOLS_PERF_LINUX_SCHED_MM_H + +#endif /* _TOOLS_PERF_LINUX_SCHED_MM_H */ diff --git a/tools/include/linux/sched/task.h b/tools/include/linux/sched/task.h new file mode 100644 index 000000000000..a97890eca110 --- /dev/null +++ b/tools/include/linux/sched/task.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_PERF_LINUX_SCHED_TASK_H +#define _TOOLS_PERF_LINUX_SCHED_TASK_H + +#endif /* _TOOLS_PERF_LINUX_SCHED_TASK_H */ diff --git a/tools/include/linux/seq_file.h b/tools/include/linux/seq_file.h new file mode 100644 index 000000000000..102fd9217f1f --- /dev/null +++ b/tools/include/linux/seq_file.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_LINUX_SEQ_FILE_H +#define _TOOLS_INCLUDE_LINUX_SEQ_FILE_H + +#endif /* _TOOLS_INCLUDE_LINUX_SEQ_FILE_H */ diff --git a/tools/include/linux/spinlock.h b/tools/include/linux/spinlock.h index 58397dcb19d6..417cda4f793f 100644 --- a/tools/include/linux/spinlock.h +++ b/tools/include/linux/spinlock.h @@ -1,5 +1,31 @@ +#ifndef __LINUX_SPINLOCK_H_ +#define __LINUX_SPINLOCK_H_ + +#include <pthread.h> +#include <stdbool.h> + #define spinlock_t pthread_mutex_t #define DEFINE_SPINLOCK(x) pthread_mutex_t x = PTHREAD_MUTEX_INITIALIZER; #define spin_lock_irqsave(x, f) (void)f, pthread_mutex_lock(x) #define spin_unlock_irqrestore(x, f) (void)f, pthread_mutex_unlock(x) + +#define arch_spinlock_t pthread_mutex_t +#define __ARCH_SPIN_LOCK_UNLOCKED PTHREAD_MUTEX_INITIALIZER + +static inline void arch_spin_lock(arch_spinlock_t *mutex) +{ + pthread_mutex_lock(mutex); +} + +static inline void arch_spin_unlock(arch_spinlock_t *mutex) +{ + pthread_mutex_unlock(mutex); +} + +static inline bool arch_spin_is_locked(arch_spinlock_t *mutex) +{ + return true; +} + +#endif diff --git a/tools/lib/lockdep/uinclude/linux/stacktrace.h b/tools/include/linux/stacktrace.h index 39aecc6b19d1..39aecc6b19d1 100644 --- a/tools/lib/lockdep/uinclude/linux/stacktrace.h +++ b/tools/include/linux/stacktrace.h diff --git a/tools/include/linux/unaligned/packed_struct.h b/tools/include/linux/unaligned/packed_struct.h new file mode 100644 index 000000000000..c0d817de4df2 --- /dev/null +++ b/tools/include/linux/unaligned/packed_struct.h @@ -0,0 +1,46 @@ +#ifndef _LINUX_UNALIGNED_PACKED_STRUCT_H +#define _LINUX_UNALIGNED_PACKED_STRUCT_H + +#include <linux/kernel.h> + +struct __una_u16 { u16 x; } __packed; +struct __una_u32 { u32 x; } __packed; +struct __una_u64 { u64 x; } __packed; + +static inline u16 __get_unaligned_cpu16(const void *p) +{ + const struct __una_u16 *ptr = (const struct __una_u16 *)p; + return ptr->x; +} + +static inline u32 __get_unaligned_cpu32(const void *p) +{ + const struct __una_u32 *ptr = (const struct __una_u32 *)p; + return ptr->x; +} + +static inline u64 __get_unaligned_cpu64(const void *p) +{ + const struct __una_u64 *ptr = (const struct __una_u64 *)p; + return ptr->x; +} + +static inline void __put_unaligned_cpu16(u16 val, void *p) +{ + struct __una_u16 *ptr = (struct __una_u16 *)p; + ptr->x = val; +} + +static inline void __put_unaligned_cpu32(u32 val, void *p) +{ + struct __una_u32 *ptr = (struct __una_u32 *)p; + ptr->x = val; +} + +static inline void __put_unaligned_cpu64(u64 val, void *p) +{ + struct __una_u64 *ptr = (struct __una_u64 *)p; + ptr->x = val; +} + +#endif /* _LINUX_UNALIGNED_PACKED_STRUCT_H */ diff --git a/tools/include/trace/events/lock.h b/tools/include/trace/events/lock.h new file mode 100644 index 000000000000..5b15fd5ee1af --- /dev/null +++ b/tools/include/trace/events/lock.h @@ -0,0 +1,4 @@ +#ifndef _TOOLS_INCLUDE_TRACE_EVENTS_LOCK_H +#define _TOOLS_INCLUDE_TRACE_EVENTS_LOCK_H + +#endif /* _TOOLS_INCLUDE_TRACE_EVENTS_LOCK_H */ diff --git a/tools/lib/lockdep/Makefile b/tools/lib/lockdep/Makefile index 3bc0ef9f8923..ed9ace59d112 100644 --- a/tools/lib/lockdep/Makefile +++ b/tools/lib/lockdep/Makefile @@ -79,6 +79,7 @@ INCLUDES = -I. -I./uinclude -I./include -I../../include $(CONFIG_INCLUDES) # Set compile option CFLAGS if not set elsewhere CFLAGS ?= -g -DCONFIG_LOCKDEP -DCONFIG_STACKTRACE -DCONFIG_PROVE_LOCKING -DBITS_PER_LONG=__WORDSIZE -DLIBLOCKDEP_VERSION='"$(LIBLOCKDEP_VERSION)"' -rdynamic -O0 -g CFLAGS += -fPIC +CFLAGS += -Wall override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ) @@ -100,7 +101,7 @@ include $(srctree)/tools/build/Makefile.include do_compile_shared_library = \ ($(print_shared_lib_compile) \ - $(CC) --shared $^ -o $@ -lpthread -ldl -Wl,-soname='"$@"';$(shell ln -sf $@ liblockdep.so)) + $(CC) $(LDFLAGS) --shared $^ -o $@ -lpthread -ldl -Wl,-soname='$(@F)';$(shell ln -sf $(@F) $(@D)/liblockdep.so)) do_build_static_lib = \ ($(print_static_lib_build) \ @@ -118,10 +119,10 @@ all_cmd: $(CMD_TARGETS) $(LIB_IN): force $(Q)$(MAKE) $(build)=liblockdep -liblockdep.so.$(LIBLOCKDEP_VERSION): $(LIB_IN) +$(OUTPUT)liblockdep.so.$(LIBLOCKDEP_VERSION): $(LIB_IN) $(Q)$(do_compile_shared_library) -liblockdep.a: $(LIB_IN) +$(OUTPUT)liblockdep.a: $(LIB_IN) $(Q)$(do_build_static_lib) tags: force @@ -149,7 +150,7 @@ install_lib: all_cmd install: install_lib clean: - $(RM) *.o *~ $(TARGETS) *.a *liblockdep*.so* $(VERSION_FILES) .*.d .*.cmd + $(RM) $(OUTPUT)*.o *~ $(TARGETS) $(OUTPUT)*.a $(OUTPUT)*liblockdep*.so* $(VERSION_FILES) $(OUTPUT).*.d $(OUTPUT).*.cmd $(RM) tags TAGS PHONY += force diff --git a/tools/lib/lockdep/lockdep.c b/tools/lib/lockdep/lockdep.c index a0a2e3a266af..ced6d7443cea 100644 --- a/tools/lib/lockdep/lockdep.c +++ b/tools/lib/lockdep/lockdep.c @@ -1,8 +1,27 @@ #include <linux/lockdep.h> +#include <stdlib.h> /* Trivial API wrappers, we don't (yet) have RCU in user-space: */ #define hlist_for_each_entry_rcu hlist_for_each_entry #define hlist_add_head_rcu hlist_add_head #define hlist_del_rcu hlist_del +#define list_for_each_entry_rcu list_for_each_entry +#define list_add_tail_rcu list_add_tail + +u32 prandom_u32(void) +{ + /* Used only by lock_pin_lock() which is dead code */ + abort(); +} + +static struct new_utsname *init_utsname(void) +{ + static struct new_utsname n = (struct new_utsname) { + .release = "liblockdep", + .version = LIBLOCKDEP_VERSION, + }; + + return &n; +} #include "../../../kernel/locking/lockdep.c" diff --git a/tools/lib/lockdep/preload.c b/tools/lib/lockdep/preload.c index 52844847569c..6a2d3c5d4e92 100644 --- a/tools/lib/lockdep/preload.c +++ b/tools/lib/lockdep/preload.c @@ -4,6 +4,7 @@ #include <dlfcn.h> #include <stdlib.h> #include <sysexits.h> +#include <unistd.h> #include "include/liblockdep/mutex.h" #include "../../include/linux/rbtree.h" @@ -122,8 +123,6 @@ static struct rb_node **__get_lock_node(void *lock, struct rb_node **parent) #define LIBLOCKDEP_STATIC_ENTRIES 1024 #endif -#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) - static struct lock_lookup __locks[LIBLOCKDEP_STATIC_ENTRIES]; static int __locks_nr; @@ -149,7 +148,7 @@ static struct lock_lookup *alloc_lock(void) int idx = __locks_nr++; if (idx >= ARRAY_SIZE(__locks)) { - fprintf(stderr, + dprintf(STDERR_FILENO, "LOCKDEP error: insufficient LIBLOCKDEP_STATIC_ENTRIES\n"); exit(EX_UNAVAILABLE); } diff --git a/tools/lib/lockdep/rbtree.c b/tools/lib/lockdep/rbtree.c index f7f43033c8b7..297c304571f8 100644 --- a/tools/lib/lockdep/rbtree.c +++ b/tools/lib/lockdep/rbtree.c @@ -1 +1 @@ -#include "../../../lib/rbtree.c" +#include "../../lib/rbtree.c" diff --git a/tools/lib/lockdep/run_tests.sh b/tools/lib/lockdep/run_tests.sh index 1069d96248c1..f9b94098fc98 100755 --- a/tools/lib/lockdep/run_tests.sh +++ b/tools/lib/lockdep/run_tests.sh @@ -4,9 +4,9 @@ make &> /dev/null for i in `ls tests/*.c`; do testname=$(basename "$i" .c) - gcc -o tests/$testname -pthread -lpthread $i liblockdep.a -Iinclude -D__USE_LIBLOCKDEP &> /dev/null + gcc -o tests/$testname -pthread $i liblockdep.a -Iinclude -D__USE_LIBLOCKDEP &> /dev/null echo -ne "$testname... " - if [ $(timeout 1 ./tests/$testname | wc -l) -gt 0 ]; then + if [ $(timeout 1 ./tests/$testname 2>&1 | wc -l) -gt 0 ]; then echo "PASSED!" else echo "FAILED!" @@ -18,9 +18,9 @@ done for i in `ls tests/*.c`; do testname=$(basename "$i" .c) - gcc -o tests/$testname -pthread -lpthread -Iinclude $i &> /dev/null + gcc -o tests/$testname -pthread -Iinclude $i &> /dev/null echo -ne "(PRELOAD) $testname... " - if [ $(timeout 1 ./lockdep ./tests/$testname | wc -l) -gt 0 ]; then + if [ $(timeout 1 ./lockdep ./tests/$testname 2>&1 | wc -l) -gt 0 ]; then echo "PASSED!" else echo "FAILED!" diff --git a/tools/lib/lockdep/uinclude/asm/hash.h b/tools/lib/lockdep/uinclude/asm/hash.h deleted file mode 100644 index d82b170bb216..000000000000 --- a/tools/lib/lockdep/uinclude/asm/hash.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef __ASM_GENERIC_HASH_H -#define __ASM_GENERIC_HASH_H - -/* Stub */ - -#endif /* __ASM_GENERIC_HASH_H */ diff --git a/tools/lib/lockdep/uinclude/asm/hweight.h b/tools/lib/lockdep/uinclude/asm/hweight.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/asm/hweight.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/asm/sections.h b/tools/lib/lockdep/uinclude/asm/sections.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/asm/sections.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/bitops.h b/tools/lib/lockdep/uinclude/linux/bitops.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/bitops.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/compiler.h b/tools/lib/lockdep/uinclude/linux/compiler.h deleted file mode 100644 index fd3e56a83fc2..000000000000 --- a/tools/lib/lockdep/uinclude/linux/compiler.h +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef _LIBLOCKDEP_LINUX_COMPILER_H_ -#define _LIBLOCKDEP_LINUX_COMPILER_H_ - -#define __used __attribute__((__unused__)) -#define unlikely -#define READ_ONCE(x) (x) -#define WRITE_ONCE(x, val) x=(val) -#define RCU_INIT_POINTER(p, v) p=(v) - -#endif diff --git a/tools/lib/lockdep/uinclude/linux/delay.h b/tools/lib/lockdep/uinclude/linux/delay.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/delay.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/ftrace.h b/tools/lib/lockdep/uinclude/linux/ftrace.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/ftrace.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/gfp.h b/tools/lib/lockdep/uinclude/linux/gfp.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/gfp.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/hash.h b/tools/lib/lockdep/uinclude/linux/hash.h deleted file mode 100644 index 0f8479858dc0..000000000000 --- a/tools/lib/lockdep/uinclude/linux/hash.h +++ /dev/null @@ -1 +0,0 @@ -#include "../../../include/linux/hash.h" diff --git a/tools/lib/lockdep/uinclude/linux/interrupt.h b/tools/lib/lockdep/uinclude/linux/interrupt.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/interrupt.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/kernel.h b/tools/lib/lockdep/uinclude/linux/kernel.h deleted file mode 100644 index 276c7a8b2ed1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/kernel.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef _LIBLOCKDEP_LINUX_KERNEL_H_ -#define _LIBLOCKDEP_LINUX_KERNEL_H_ - -#include <linux/export.h> -#include <linux/types.h> -#include <linux/rcu.h> -#include <linux/hardirq.h> -#include <linux/kern_levels.h> - -#ifndef container_of -#define container_of(ptr, type, member) ({ \ - const typeof(((type *)0)->member) * __mptr = (ptr); \ - (type *)((char *)__mptr - offsetof(type, member)); }) -#endif - -#define max(x, y) ({ \ - typeof(x) _max1 = (x); \ - typeof(y) _max2 = (y); \ - (void) (&_max1 == &_max2); \ - _max1 > _max2 ? _max1 : _max2; }) - -#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) -#define WARN_ON(x) (x) -#define WARN_ON_ONCE(x) (x) -#define likely(x) (x) -#define WARN(x, y...) (x) -#define uninitialized_var(x) x -#define __init -#define noinline -#define list_add_tail_rcu list_add_tail -#define list_for_each_entry_rcu list_for_each_entry -#define barrier() -#define synchronize_sched() - -#ifndef CALLER_ADDR0 -#define CALLER_ADDR0 ((unsigned long)__builtin_return_address(0)) -#endif - -#ifndef _RET_IP_ -#define _RET_IP_ CALLER_ADDR0 -#endif - -#ifndef _THIS_IP_ -#define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; }) -#endif - -#endif diff --git a/tools/lib/lockdep/uinclude/linux/linkage.h b/tools/lib/lockdep/uinclude/linux/linkage.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/linkage.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/list.h b/tools/lib/lockdep/uinclude/linux/list.h deleted file mode 100644 index 6e9ef31ed82e..000000000000 --- a/tools/lib/lockdep/uinclude/linux/list.h +++ /dev/null @@ -1 +0,0 @@ -#include "../../../include/linux/list.h" diff --git a/tools/lib/lockdep/uinclude/linux/mutex.h b/tools/lib/lockdep/uinclude/linux/mutex.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/mutex.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/poison.h b/tools/lib/lockdep/uinclude/linux/poison.h deleted file mode 100644 index 0c27bdf14233..000000000000 --- a/tools/lib/lockdep/uinclude/linux/poison.h +++ /dev/null @@ -1 +0,0 @@ -#include "../../../include/linux/poison.h" diff --git a/tools/lib/lockdep/uinclude/linux/prefetch.h b/tools/lib/lockdep/uinclude/linux/prefetch.h deleted file mode 100644 index d73fe6f850ac..000000000000 --- a/tools/lib/lockdep/uinclude/linux/prefetch.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef _LIBLOCKDEP_LINUX_PREFETCH_H_ -#define _LIBLOCKDEP_LINUX_PREFETCH_H - -static inline void prefetch(void *a __attribute__((unused))) { } - -#endif diff --git a/tools/lib/lockdep/uinclude/linux/proc_fs.h b/tools/lib/lockdep/uinclude/linux/proc_fs.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/proc_fs.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h b/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h deleted file mode 100644 index c3759477379c..000000000000 --- a/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h +++ /dev/null @@ -1,2 +0,0 @@ -#define __always_inline -#include "../../../include/linux/rbtree_augmented.h" diff --git a/tools/lib/lockdep/uinclude/linux/seq_file.h b/tools/lib/lockdep/uinclude/linux/seq_file.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/linux/seq_file.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/lib/lockdep/uinclude/linux/spinlock.h b/tools/lib/lockdep/uinclude/linux/spinlock.h deleted file mode 100644 index 68c1aa2bcba5..000000000000 --- a/tools/lib/lockdep/uinclude/linux/spinlock.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef _LIBLOCKDEP_SPINLOCK_H_ -#define _LIBLOCKDEP_SPINLOCK_H_ - -#include <pthread.h> -#include <stdbool.h> - -#define arch_spinlock_t pthread_mutex_t -#define __ARCH_SPIN_LOCK_UNLOCKED PTHREAD_MUTEX_INITIALIZER - -static inline void arch_spin_lock(arch_spinlock_t *mutex) -{ - pthread_mutex_lock(mutex); -} - -static inline void arch_spin_unlock(arch_spinlock_t *mutex) -{ - pthread_mutex_unlock(mutex); -} - -static inline bool arch_spin_is_locked(arch_spinlock_t *mutex) -{ - return true; -} - -#endif diff --git a/tools/lib/lockdep/uinclude/linux/stringify.h b/tools/lib/lockdep/uinclude/linux/stringify.h deleted file mode 100644 index 05dfcd1ac118..000000000000 --- a/tools/lib/lockdep/uinclude/linux/stringify.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef _LIBLOCKDEP_LINUX_STRINGIFY_H_ -#define _LIBLOCKDEP_LINUX_STRINGIFY_H_ - -#define __stringify_1(x...) #x -#define __stringify(x...) __stringify_1(x) - -#endif diff --git a/tools/lib/lockdep/uinclude/trace/events/lock.h b/tools/lib/lockdep/uinclude/trace/events/lock.h deleted file mode 100644 index fab00ff936d1..000000000000 --- a/tools/lib/lockdep/uinclude/trace/events/lock.h +++ /dev/null @@ -1,3 +0,0 @@ - -/* empty file */ - diff --git a/tools/objtool/Build b/tools/objtool/Build index d6cdece5e58b..6f2e1987c4d9 100644 --- a/tools/objtool/Build +++ b/tools/objtool/Build @@ -1,5 +1,6 @@ objtool-y += arch/$(SRCARCH)/ objtool-y += builtin-check.o +objtool-y += check.o objtool-y += elf.o objtool-y += special.o objtool-y += objtool.o diff --git a/tools/objtool/Documentation/stack-validation.txt b/tools/objtool/Documentation/stack-validation.txt index 55a60d331f47..17c1195f11f4 100644 --- a/tools/objtool/Documentation/stack-validation.txt +++ b/tools/objtool/Documentation/stack-validation.txt @@ -127,28 +127,13 @@ b) 100% reliable stack traces for DWARF enabled kernels c) Higher live patching compatibility rate - (NOTE: This is not yet implemented) - - Currently with CONFIG_LIVEPATCH there's a basic live patching - framework which is safe for roughly 85-90% of "security" fixes. But - patches can't have complex features like function dependency or - prototype changes, or data structure changes. - - There's a strong need to support patches which have the more complex - features so that the patch compatibility rate for security fixes can - eventually approach something resembling 100%. To achieve that, a - "consistency model" is needed, which allows tasks to be safely - transitioned from an unpatched state to a patched state. - - One of the key requirements of the currently proposed livepatch - consistency model [*] is that it needs to walk the stack of each - sleeping task to determine if it can be transitioned to the patched - state. If objtool can ensure that stack traces are reliable, this - consistency model can be used and the live patching compatibility - rate can be improved significantly. - - [*] https://lkml.kernel.org/r/cover.1423499826.git.jpoimboe@redhat.com + Livepatch has an optional "consistency model", which is needed for + more complex patches. In order for the consistency model to work, + stack traces need to be reliable (or an unreliable condition needs to + be detectable). Objtool makes that possible. + For more details, see the livepatch documentation in the Linux kernel + source tree at Documentation/livepatch/livepatch.txt. Rules ----- @@ -201,80 +186,84 @@ To achieve the validation, objtool enforces the following rules: return normally. -Errors in .S files ------------------- +Objtool warnings +---------------- -If you're getting an error in a compiled .S file which you don't -understand, first make sure that the affected code follows the above -rules. +For asm files, if you're getting an error which doesn't make sense, +first make sure that the affected code follows the above rules. + +For C files, the common culprits are inline asm statements and calls to +"noreturn" functions. See below for more details. + +Another possible cause for errors in C code is if the Makefile removes +-fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options. Here are some examples of common warnings reported by objtool, what they mean, and suggestions for how to fix them. -1. asm_file.o: warning: objtool: func()+0x128: call without frame pointer save/setup +1. file.o: warning: objtool: func()+0x128: call without frame pointer save/setup The func() function made a function call without first saving and/or - updating the frame pointer. - - If func() is indeed a callable function, add proper frame pointer - logic using the FRAME_BEGIN and FRAME_END macros. Otherwise, remove - its ELF function annotation by changing ENDPROC to END. + updating the frame pointer, and CONFIG_FRAME_POINTER is enabled. - If you're getting this error in a .c file, see the "Errors in .c - files" section. + If the error is for an asm file, and func() is indeed a callable + function, add proper frame pointer logic using the FRAME_BEGIN and + FRAME_END macros. Otherwise, if it's not a callable function, remove + its ELF function annotation by changing ENDPROC to END, and instead + use the manual CFI hint macros in asm/undwarf.h. + If it's a GCC-compiled .c file, the error may be because the function + uses an inline asm() statement which has a "call" instruction. An + asm() statement with a call instruction must declare the use of the + stack pointer in its output operand. For example, on x86_64: -2. asm_file.o: warning: objtool: .text+0x53: return instruction outside of a callable function - - A return instruction was detected, but objtool couldn't find a way - for a callable function to reach the instruction. + register void *__sp asm("rsp"); + asm volatile("call func" : "+r" (__sp)); - If the return instruction is inside (or reachable from) a callable - function, the function needs to be annotated with the ENTRY/ENDPROC - macros. + Otherwise the stack frame may not get created before the call. - If you _really_ need a return instruction outside of a function, and - are 100% sure that it won't affect stack traces, you can tell - objtool to ignore it. See the "Adding exceptions" section below. +2. file.o: warning: objtool: .text+0x53: unreachable instruction -3. asm_file.o: warning: objtool: func()+0x9: function has unreachable instruction + Objtool couldn't find a code path to reach the instruction. - The instruction lives inside of a callable function, but there's no - possible control flow path from the beginning of the function to the - instruction. + If the error is for an asm file, and the instruction is inside (or + reachable from) a callable function, the function should be annotated + with the ENTRY/ENDPROC macros (ENDPROC is the important one). + Otherwise, the code should probably be annotated with the CFI hint + macros in asm/undwarf.h so objtool and the unwinder can know the + stack state associated with the code. - If the instruction is actually needed, and it's actually in a - callable function, ensure that its function is properly annotated - with ENTRY/ENDPROC. + If you're 100% sure the code won't affect stack traces, or if you're + a just a bad person, you can tell objtool to ignore it. See the + "Adding exceptions" section below. If it's not actually in a callable function (e.g. kernel entry code), change ENDPROC to END. -4. asm_file.o: warning: objtool: func(): can't find starting instruction +4. file.o: warning: objtool: func(): can't find starting instruction or - asm_file.o: warning: objtool: func()+0x11dd: can't decode instruction + file.o: warning: objtool: func()+0x11dd: can't decode instruction - Did you put data in a text section? If so, that can confuse + Does the file have data in a text section? If so, that can confuse objtool's instruction decoder. Move the data to a more appropriate section like .data or .rodata. -5. asm_file.o: warning: objtool: func()+0x6: kernel entry/exit from callable instruction - - This is a kernel entry/exit instruction like sysenter or sysret. - Such instructions aren't allowed in a callable function, and are most - likely part of the kernel entry code. +5. file.o: warning: objtool: func()+0x6: unsupported instruction in callable function - If the instruction isn't actually in a callable function, change - ENDPROC to END. + This is a kernel entry/exit instruction like sysenter or iret. Such + instructions aren't allowed in a callable function, and are most + likely part of the kernel entry code. They should usually not have + the callable function annotation (ENDPROC) and should always be + annotated with the CFI hint macros in asm/undwarf.h. -6. asm_file.o: warning: objtool: func()+0x26: sibling call from callable instruction with changed frame pointer +6. file.o: warning: objtool: func()+0x26: sibling call from callable instruction with modified stack frame - This is a dynamic jump or a jump to an undefined symbol. Stacktool + This is a dynamic jump or a jump to an undefined symbol. Objtool assumed it's a sibling call and detected that the frame pointer wasn't first restored to its original state. @@ -282,24 +271,28 @@ they mean, and suggestions for how to fix them. destination code to the local file. If the instruction is not actually in a callable function (e.g. - kernel entry code), change ENDPROC to END. + kernel entry code), change ENDPROC to END and annotate manually with + the CFI hint macros in asm/undwarf.h. -7. asm_file: warning: objtool: func()+0x5c: frame pointer state mismatch +7. file: warning: objtool: func()+0x5c: stack state mismatch The instruction's frame pointer state is inconsistent, depending on which execution path was taken to reach the instruction. - Make sure the function pushes and sets up the frame pointer (for - x86_64, this means rbp) at the beginning of the function and pops it - at the end of the function. Also make sure that no other code in the - function touches the frame pointer. + Make sure that, when CONFIG_FRAME_POINTER is enabled, the function + pushes and sets up the frame pointer (for x86_64, this means rbp) at + the beginning of the function and pops it at the end of the function. + Also make sure that no other code in the function touches the frame + pointer. + Another possibility is that the code has some asm or inline asm which + does some unusual things to the stack or the frame pointer. In such + cases it's probably appropriate to use the CFI hint macros in + asm/undwarf.h. -Errors in .c files ------------------- -1. c_file.o: warning: objtool: funcA() falls through to next function funcB() +8. file.o: warning: objtool: funcA() falls through to next function funcB() This means that funcA() doesn't end with a return instruction or an unconditional jump, and that objtool has determined that the function @@ -318,22 +311,6 @@ Errors in .c files might be corrupt due to a gcc bug. For more details, see: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70646 -2. If you're getting any other objtool error in a compiled .c file, it - may be because the file uses an asm() statement which has a "call" - instruction. An asm() statement with a call instruction must declare - the use of the stack pointer in its output operand. For example, on - x86_64: - - register void *__sp asm("rsp"); - asm volatile("call func" : "+r" (__sp)); - - Otherwise the stack frame may not get created before the call. - -3. Another possible cause for errors in C code is if the Makefile removes - -fno-omit-frame-pointer or adds -fomit-frame-pointer to the gcc options. - -Also see the above section for .S file errors for more information what -the individual error messages mean. If the error doesn't seem to make sense, it could be a bug in objtool. Feel free to ask the objtool maintainer for help. diff --git a/tools/objtool/Makefile b/tools/objtool/Makefile index 27e019c09bd2..0e2765e243c0 100644 --- a/tools/objtool/Makefile +++ b/tools/objtool/Makefile @@ -25,7 +25,7 @@ OBJTOOL_IN := $(OBJTOOL)-in.o all: $(OBJTOOL) INCLUDES := -I$(srctree)/tools/include -I$(srctree)/tools/arch/$(HOSTARCH)/include/uapi -CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -fomit-frame-pointer -O2 -g $(INCLUDES) +CFLAGS += -Wall -Werror $(EXTRA_WARNINGS) -Wno-switch-default -Wno-switch-enum -fomit-frame-pointer -O2 -g $(INCLUDES) LDFLAGS += -lelf $(LIBSUBCMD) # Allow old libelf to be used: diff --git a/tools/objtool/arch.h b/tools/objtool/arch.h index a59e061c0b4a..21aeca874edb 100644 --- a/tools/objtool/arch.h +++ b/tools/objtool/arch.h @@ -19,25 +19,63 @@ #define _ARCH_H #include <stdbool.h> +#include <linux/list.h> #include "elf.h" +#include "cfi.h" -#define INSN_FP_SAVE 1 -#define INSN_FP_SETUP 2 -#define INSN_FP_RESTORE 3 -#define INSN_JUMP_CONDITIONAL 4 -#define INSN_JUMP_UNCONDITIONAL 5 -#define INSN_JUMP_DYNAMIC 6 -#define INSN_CALL 7 -#define INSN_CALL_DYNAMIC 8 -#define INSN_RETURN 9 -#define INSN_CONTEXT_SWITCH 10 -#define INSN_NOP 11 -#define INSN_OTHER 12 +#define INSN_JUMP_CONDITIONAL 1 +#define INSN_JUMP_UNCONDITIONAL 2 +#define INSN_JUMP_DYNAMIC 3 +#define INSN_CALL 4 +#define INSN_CALL_DYNAMIC 5 +#define INSN_RETURN 6 +#define INSN_CONTEXT_SWITCH 7 +#define INSN_STACK 8 +#define INSN_NOP 9 +#define INSN_OTHER 10 #define INSN_LAST INSN_OTHER +enum op_dest_type { + OP_DEST_REG, + OP_DEST_REG_INDIRECT, + OP_DEST_MEM, + OP_DEST_PUSH, + OP_DEST_LEAVE, +}; + +struct op_dest { + enum op_dest_type type; + unsigned char reg; + int offset; +}; + +enum op_src_type { + OP_SRC_REG, + OP_SRC_REG_INDIRECT, + OP_SRC_CONST, + OP_SRC_POP, + OP_SRC_ADD, + OP_SRC_AND, +}; + +struct op_src { + enum op_src_type type; + unsigned char reg; + int offset; +}; + +struct stack_op { + struct op_dest dest; + struct op_src src; +}; + +void arch_initial_func_cfi_state(struct cfi_state *state); + int arch_decode_instruction(struct elf *elf, struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, unsigned char *type, - unsigned long *displacement); + unsigned long *immediate, struct stack_op *op); + +bool arch_callee_saved_reg(unsigned char reg); #endif /* _ARCH_H */ diff --git a/tools/objtool/arch/x86/decode.c b/tools/objtool/arch/x86/decode.c index 6ac99e3266eb..a36c2eba64e7 100644 --- a/tools/objtool/arch/x86/decode.c +++ b/tools/objtool/arch/x86/decode.c @@ -27,6 +27,17 @@ #include "../../arch.h" #include "../../warn.h" +static unsigned char op_to_cfi_reg[][2] = { + {CFI_AX, CFI_R8}, + {CFI_CX, CFI_R9}, + {CFI_DX, CFI_R10}, + {CFI_BX, CFI_R11}, + {CFI_SP, CFI_R12}, + {CFI_BP, CFI_R13}, + {CFI_SI, CFI_R14}, + {CFI_DI, CFI_R15}, +}; + static int is_x86_64(struct elf *elf) { switch (elf->ehdr.e_machine) { @@ -40,24 +51,50 @@ static int is_x86_64(struct elf *elf) } } +bool arch_callee_saved_reg(unsigned char reg) +{ + switch (reg) { + case CFI_BP: + case CFI_BX: + case CFI_R12: + case CFI_R13: + case CFI_R14: + case CFI_R15: + return true; + + case CFI_AX: + case CFI_CX: + case CFI_DX: + case CFI_SI: + case CFI_DI: + case CFI_SP: + case CFI_R8: + case CFI_R9: + case CFI_R10: + case CFI_R11: + case CFI_RA: + default: + return false; + } +} + int arch_decode_instruction(struct elf *elf, struct section *sec, unsigned long offset, unsigned int maxlen, unsigned int *len, unsigned char *type, - unsigned long *immediate) + unsigned long *immediate, struct stack_op *op) { struct insn insn; - int x86_64; - unsigned char op1, op2, ext; + int x86_64, sign; + unsigned char op1, op2, rex = 0, rex_b = 0, rex_r = 0, rex_w = 0, + modrm = 0, modrm_mod = 0, modrm_rm = 0, modrm_reg = 0, + sib = 0; x86_64 = is_x86_64(elf); if (x86_64 == -1) return -1; - insn_init(&insn, (void *)(sec->data + offset), maxlen, x86_64); + insn_init(&insn, sec->data->d_buf + offset, maxlen, x86_64); insn_get_length(&insn); - insn_get_opcode(&insn); - insn_get_modrm(&insn); - insn_get_immediate(&insn); if (!insn_complete(&insn)) { WARN_FUNC("can't decode instruction", sec, offset); @@ -73,67 +110,323 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, op1 = insn.opcode.bytes[0]; op2 = insn.opcode.bytes[1]; + if (insn.rex_prefix.nbytes) { + rex = insn.rex_prefix.bytes[0]; + rex_w = X86_REX_W(rex) >> 3; + rex_r = X86_REX_R(rex) >> 2; + rex_b = X86_REX_B(rex); + } + + if (insn.modrm.nbytes) { + modrm = insn.modrm.bytes[0]; + modrm_mod = X86_MODRM_MOD(modrm); + modrm_reg = X86_MODRM_REG(modrm); + modrm_rm = X86_MODRM_RM(modrm); + } + + if (insn.sib.nbytes) + sib = insn.sib.bytes[0]; + switch (op1) { - case 0x55: - if (!insn.rex_prefix.nbytes) - /* push rbp */ - *type = INSN_FP_SAVE; + + case 0x1: + case 0x29: + if (rex_w && !rex_b && modrm_mod == 3 && modrm_rm == 4) { + + /* add/sub reg, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_SRC_REG; + op->dest.reg = CFI_SP; + } + break; + + case 0x50 ... 0x57: + + /* push reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[op1 & 0x7][rex_b]; + op->dest.type = OP_DEST_PUSH; + break; - case 0x5d: - if (!insn.rex_prefix.nbytes) - /* pop rbp */ - *type = INSN_FP_RESTORE; + case 0x58 ... 0x5f: + + /* pop reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_REG; + op->dest.reg = op_to_cfi_reg[op1 & 0x7][rex_b]; + + break; + + case 0x68: + case 0x6a: + /* push immediate */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; break; case 0x70 ... 0x7f: *type = INSN_JUMP_CONDITIONAL; break; + case 0x81: + case 0x83: + if (rex != 0x48) + break; + + if (modrm == 0xe4) { + /* and imm, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_AND; + op->src.reg = CFI_SP; + op->src.offset = insn.immediate.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (modrm == 0xc4) + sign = 1; + else if (modrm == 0xec) + sign = -1; + else + break; + + /* add/sub imm, %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = insn.immediate.value * sign; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + case 0x89: - if (insn.rex_prefix.nbytes == 1 && - insn.rex_prefix.bytes[0] == 0x48 && - insn.modrm.nbytes && insn.modrm.bytes[0] == 0xe5) - /* mov rsp, rbp */ - *type = INSN_FP_SETUP; + if (rex == 0x48 && modrm == 0xe5) { + + /* mov %rsp, %rbp */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = CFI_SP; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_BP; + break; + } + /* fallthrough */ + case 0x88: + if (!rex_b && + (modrm_mod == 1 || modrm_mod == 2) && modrm_rm == 5) { + + /* mov reg, disp(%rbp) */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_DEST_REG_INDIRECT; + op->dest.reg = CFI_BP; + op->dest.offset = insn.displacement.value; + + } else if (rex_w && !rex_b && modrm_rm == 4 && sib == 0x24) { + + /* mov reg, disp(%rsp) */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG; + op->src.reg = op_to_cfi_reg[modrm_reg][rex_r]; + op->dest.type = OP_DEST_REG_INDIRECT; + op->dest.reg = CFI_SP; + op->dest.offset = insn.displacement.value; + } + + break; + + case 0x8b: + if (rex_w && !rex_b && modrm_mod == 1 && modrm_rm == 5) { + + /* mov disp(%rbp), reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG_INDIRECT; + op->src.reg = CFI_BP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; + + } else if (rex_w && !rex_b && sib == 0x24 && + modrm_mod != 3 && modrm_rm == 4) { + + /* mov disp(%rsp), reg */ + *type = INSN_STACK; + op->src.type = OP_SRC_REG_INDIRECT; + op->src.reg = CFI_SP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = op_to_cfi_reg[modrm_reg][rex_r]; + } + break; case 0x8d: - if (insn.rex_prefix.nbytes && - insn.rex_prefix.bytes[0] == 0x48 && - insn.modrm.nbytes && insn.modrm.bytes[0] == 0x2c && - insn.sib.nbytes && insn.sib.bytes[0] == 0x24) - /* lea %(rsp), %rbp */ - *type = INSN_FP_SETUP; + if (rex == 0x48 && modrm == 0x65) { + + /* lea -disp(%rbp), %rsp */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_BP; + op->src.offset = insn.displacement.value; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (rex == 0x4c && modrm == 0x54 && sib == 0x24 && + insn.displacement.value == 8) { + + /* + * lea 0x8(%rsp), %r10 + * + * Here r10 is the "drap" pointer, used as a stack + * pointer helper when the stack gets realigned. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = 8; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_R10; + break; + } + + if (rex == 0x4c && modrm == 0x6c && sib == 0x24 && + insn.displacement.value == 16) { + + /* + * lea 0x10(%rsp), %r13 + * + * Here r13 is the "drap" pointer, used as a stack + * pointer helper when the stack gets realigned. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_SP; + op->src.offset = 16; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_R13; + break; + } + + if (rex == 0x49 && modrm == 0x62 && + insn.displacement.value == -8) { + + /* + * lea -0x8(%r10), %rsp + * + * Restoring rsp back to its original value after a + * stack realignment. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_R10; + op->src.offset = -8; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + if (rex == 0x49 && modrm == 0x65 && + insn.displacement.value == -16) { + + /* + * lea -0x10(%r13), %rsp + * + * Restoring rsp back to its original value after a + * stack realignment. + */ + *type = INSN_STACK; + op->src.type = OP_SRC_ADD; + op->src.reg = CFI_R13; + op->src.offset = -16; + op->dest.type = OP_DEST_REG; + op->dest.reg = CFI_SP; + break; + } + + break; + + case 0x8f: + /* pop to mem */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_MEM; break; case 0x90: *type = INSN_NOP; break; + case 0x9c: + /* pushf */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; + break; + + case 0x9d: + /* popf */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_MEM; + break; + case 0x0f: + if (op2 >= 0x80 && op2 <= 0x8f) *type = INSN_JUMP_CONDITIONAL; else if (op2 == 0x05 || op2 == 0x07 || op2 == 0x34 || op2 == 0x35) + /* sysenter, sysret */ *type = INSN_CONTEXT_SWITCH; + else if (op2 == 0x0d || op2 == 0x1f) + /* nopl/nopw */ *type = INSN_NOP; - else if (op2 == 0x01 && insn.modrm.nbytes && - (insn.modrm.bytes[0] == 0xc2 || - insn.modrm.bytes[0] == 0xd8)) - /* vmlaunch, vmrun */ - *type = INSN_CONTEXT_SWITCH; + + else if (op2 == 0xa0 || op2 == 0xa8) { + + /* push fs/gs */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; + + } else if (op2 == 0xa1 || op2 == 0xa9) { + + /* pop fs/gs */ + *type = INSN_STACK; + op->src.type = OP_SRC_POP; + op->dest.type = OP_DEST_MEM; + } break; - case 0xc9: /* leave */ - *type = INSN_FP_RESTORE; + case 0xc9: + /* + * leave + * + * equivalent to: + * mov bp, sp + * pop bp + */ + *type = INSN_STACK; + op->dest.type = OP_DEST_LEAVE; + break; - case 0xe3: /* jecxz/jrcxz */ + case 0xe3: + /* jecxz/jrcxz */ *type = INSN_JUMP_CONDITIONAL; break; @@ -158,14 +451,27 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, break; case 0xff: - ext = X86_MODRM_REG(insn.modrm.bytes[0]); - if (ext == 2 || ext == 3) + if (modrm_reg == 2 || modrm_reg == 3) + *type = INSN_CALL_DYNAMIC; - else if (ext == 4) + + else if (modrm_reg == 4) + *type = INSN_JUMP_DYNAMIC; - else if (ext == 5) /*jmpf */ + + else if (modrm_reg == 5) + + /* jmpf */ *type = INSN_CONTEXT_SWITCH; + else if (modrm_reg == 6) { + + /* push from mem */ + *type = INSN_STACK; + op->src.type = OP_SRC_CONST; + op->dest.type = OP_DEST_PUSH; + } + break; default: @@ -176,3 +482,21 @@ int arch_decode_instruction(struct elf *elf, struct section *sec, return 0; } + +void arch_initial_func_cfi_state(struct cfi_state *state) +{ + int i; + + for (i = 0; i < CFI_NUM_REGS; i++) { + state->regs[i].base = CFI_UNDEFINED; + state->regs[i].offset = 0; + } + + /* initial CFA (call frame address) */ + state->cfa.base = CFI_SP; + state->cfa.offset = 8; + + /* initial RA (return address) */ + state->regs[16].base = CFI_CFA; + state->regs[16].offset = -8; +} diff --git a/tools/objtool/builtin-check.c b/tools/objtool/builtin-check.c index 5f66697fe1e0..365c34ecab26 100644 --- a/tools/objtool/builtin-check.c +++ b/tools/objtool/builtin-check.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2015 Josh Poimboeuf <jpoimboe@redhat.com> + * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -25,1287 +25,32 @@ * For more information, see tools/objtool/Documentation/stack-validation.txt. */ -#include <string.h> -#include <stdlib.h> #include <subcmd/parse-options.h> - #include "builtin.h" -#include "elf.h" -#include "special.h" -#include "arch.h" -#include "warn.h" - -#include <linux/hashtable.h> -#include <linux/kernel.h> - -#define STATE_FP_SAVED 0x1 -#define STATE_FP_SETUP 0x2 -#define STATE_FENTRY 0x4 - -struct instruction { - struct list_head list; - struct hlist_node hash; - struct section *sec; - unsigned long offset; - unsigned int len, state; - unsigned char type; - unsigned long immediate; - bool alt_group, visited, dead_end; - struct symbol *call_dest; - struct instruction *jump_dest; - struct list_head alts; - struct symbol *func; -}; - -struct alternative { - struct list_head list; - struct instruction *insn; -}; - -struct objtool_file { - struct elf *elf; - struct list_head insn_list; - DECLARE_HASHTABLE(insn_hash, 16); - struct section *rodata, *whitelist; - bool ignore_unreachables, c_file; -}; - -const char *objname; -static bool nofp; - -static struct instruction *find_insn(struct objtool_file *file, - struct section *sec, unsigned long offset) -{ - struct instruction *insn; - - hash_for_each_possible(file->insn_hash, insn, hash, offset) - if (insn->sec == sec && insn->offset == offset) - return insn; - - return NULL; -} - -static struct instruction *next_insn_same_sec(struct objtool_file *file, - struct instruction *insn) -{ - struct instruction *next = list_next_entry(insn, list); - - if (&next->list == &file->insn_list || next->sec != insn->sec) - return NULL; - - return next; -} - -static bool gcov_enabled(struct objtool_file *file) -{ - struct section *sec; - struct symbol *sym; - - list_for_each_entry(sec, &file->elf->sections, list) - list_for_each_entry(sym, &sec->symbol_list, list) - if (!strncmp(sym->name, "__gcov_.", 8)) - return true; - - return false; -} - -#define for_each_insn(file, insn) \ - list_for_each_entry(insn, &file->insn_list, list) - -#define func_for_each_insn(file, func, insn) \ - for (insn = find_insn(file, func->sec, func->offset); \ - insn && &insn->list != &file->insn_list && \ - insn->sec == func->sec && \ - insn->offset < func->offset + func->len; \ - insn = list_next_entry(insn, list)) - -#define func_for_each_insn_continue_reverse(file, func, insn) \ - for (insn = list_prev_entry(insn, list); \ - &insn->list != &file->insn_list && \ - insn->sec == func->sec && insn->offset >= func->offset; \ - insn = list_prev_entry(insn, list)) - -#define sec_for_each_insn_from(file, insn) \ - for (; insn; insn = next_insn_same_sec(file, insn)) - - -/* - * Check if the function has been manually whitelisted with the - * STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted - * due to its use of a context switching instruction. - */ -static bool ignore_func(struct objtool_file *file, struct symbol *func) -{ - struct rela *rela; - struct instruction *insn; - - /* check for STACK_FRAME_NON_STANDARD */ - if (file->whitelist && file->whitelist->rela) - list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) { - if (rela->sym->type == STT_SECTION && - rela->sym->sec == func->sec && - rela->addend == func->offset) - return true; - if (rela->sym->type == STT_FUNC && rela->sym == func) - return true; - } - - /* check if it has a context switching instruction */ - func_for_each_insn(file, func, insn) - if (insn->type == INSN_CONTEXT_SWITCH) - return true; - - return false; -} - -/* - * This checks to see if the given function is a "noreturn" function. - * - * For global functions which are outside the scope of this object file, we - * have to keep a manual list of them. - * - * For local functions, we have to detect them manually by simply looking for - * the lack of a return instruction. - * - * Returns: - * -1: error - * 0: no dead end - * 1: dead end - */ -static int __dead_end_function(struct objtool_file *file, struct symbol *func, - int recursion) -{ - int i; - struct instruction *insn; - bool empty = true; - - /* - * Unfortunately these have to be hard coded because the noreturn - * attribute isn't provided in ELF data. - */ - static const char * const global_noreturns[] = { - "__stack_chk_fail", - "panic", - "do_exit", - "do_task_dead", - "__module_put_and_exit", - "complete_and_exit", - "kvm_spurious_fault", - "__reiserfs_panic", - "lbug_with_loc", - "fortify_panic", - }; - - if (func->bind == STB_WEAK) - return 0; - - if (func->bind == STB_GLOBAL) - for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) - if (!strcmp(func->name, global_noreturns[i])) - return 1; - - if (!func->sec) - return 0; - - func_for_each_insn(file, func, insn) { - empty = false; - - if (insn->type == INSN_RETURN) - return 0; - } - - if (empty) - return 0; - - /* - * A function can have a sibling call instead of a return. In that - * case, the function's dead-end status depends on whether the target - * of the sibling call returns. - */ - func_for_each_insn(file, func, insn) { - if (insn->sec != func->sec || - insn->offset >= func->offset + func->len) - break; - - if (insn->type == INSN_JUMP_UNCONDITIONAL) { - struct instruction *dest = insn->jump_dest; - struct symbol *dest_func; - - if (!dest) - /* sibling call to another file */ - return 0; - - if (dest->sec != func->sec || - dest->offset < func->offset || - dest->offset >= func->offset + func->len) { - /* local sibling call */ - dest_func = find_symbol_by_offset(dest->sec, - dest->offset); - if (!dest_func) - continue; - - if (recursion == 5) { - WARN_FUNC("infinite recursion (objtool bug!)", - dest->sec, dest->offset); - return -1; - } - - return __dead_end_function(file, dest_func, - recursion + 1); - } - } - - if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts)) - /* sibling call */ - return 0; - } - - return 1; -} - -static int dead_end_function(struct objtool_file *file, struct symbol *func) -{ - return __dead_end_function(file, func, 0); -} - -/* - * Call the arch-specific instruction decoder for all the instructions and add - * them to the global instruction list. - */ -static int decode_instructions(struct objtool_file *file) -{ - struct section *sec; - struct symbol *func; - unsigned long offset; - struct instruction *insn; - int ret; - - list_for_each_entry(sec, &file->elf->sections, list) { - - if (!(sec->sh.sh_flags & SHF_EXECINSTR)) - continue; - - for (offset = 0; offset < sec->len; offset += insn->len) { - insn = malloc(sizeof(*insn)); - memset(insn, 0, sizeof(*insn)); - - INIT_LIST_HEAD(&insn->alts); - insn->sec = sec; - insn->offset = offset; - - ret = arch_decode_instruction(file->elf, sec, offset, - sec->len - offset, - &insn->len, &insn->type, - &insn->immediate); - if (ret) - return ret; - - if (!insn->type || insn->type > INSN_LAST) { - WARN_FUNC("invalid instruction type %d", - insn->sec, insn->offset, insn->type); - return -1; - } - - hash_add(file->insn_hash, &insn->hash, insn->offset); - list_add_tail(&insn->list, &file->insn_list); - } - - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; - - if (!find_insn(file, sec, func->offset)) { - WARN("%s(): can't find starting instruction", - func->name); - return -1; - } - - func_for_each_insn(file, func, insn) - if (!insn->func) - insn->func = func; - } - } - - return 0; -} - -/* - * Find all uses of the unreachable() macro, which are code path dead ends. - */ -static int add_dead_ends(struct objtool_file *file) -{ - struct section *sec; - struct rela *rela; - struct instruction *insn; - bool found; - - sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); - if (!sec) - return 0; - - list_for_each_entry(rela, &sec->rela_list, list) { - if (rela->sym->type != STT_SECTION) { - WARN("unexpected relocation symbol type in %s", sec->name); - return -1; - } - insn = find_insn(file, rela->sym->sec, rela->addend); - if (insn) - insn = list_prev_entry(insn, list); - else if (rela->addend == rela->sym->sec->len) { - found = false; - list_for_each_entry_reverse(insn, &file->insn_list, list) { - if (insn->sec == rela->sym->sec) { - found = true; - break; - } - } - - if (!found) { - WARN("can't find unreachable insn at %s+0x%x", - rela->sym->sec->name, rela->addend); - return -1; - } - } else { - WARN("can't find unreachable insn at %s+0x%x", - rela->sym->sec->name, rela->addend); - return -1; - } - - insn->dead_end = true; - } - - return 0; -} - -/* - * Warnings shouldn't be reported for ignored functions. - */ -static void add_ignores(struct objtool_file *file) -{ - struct instruction *insn; - struct section *sec; - struct symbol *func; - - list_for_each_entry(sec, &file->elf->sections, list) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; - - if (!ignore_func(file, func)) - continue; - - func_for_each_insn(file, func, insn) - insn->visited = true; - } - } -} - -/* - * Find the destination instructions for all jumps. - */ -static int add_jump_destinations(struct objtool_file *file) -{ - struct instruction *insn; - struct rela *rela; - struct section *dest_sec; - unsigned long dest_off; - - for_each_insn(file, insn) { - if (insn->type != INSN_JUMP_CONDITIONAL && - insn->type != INSN_JUMP_UNCONDITIONAL) - continue; - - /* skip ignores */ - if (insn->visited) - continue; - - rela = find_rela_by_dest_range(insn->sec, insn->offset, - insn->len); - if (!rela) { - dest_sec = insn->sec; - dest_off = insn->offset + insn->len + insn->immediate; - } else if (rela->sym->type == STT_SECTION) { - dest_sec = rela->sym->sec; - dest_off = rela->addend + 4; - } else if (rela->sym->sec->idx) { - dest_sec = rela->sym->sec; - dest_off = rela->sym->sym.st_value + rela->addend + 4; - } else { - /* sibling call */ - insn->jump_dest = 0; - continue; - } - - insn->jump_dest = find_insn(file, dest_sec, dest_off); - if (!insn->jump_dest) { - - /* - * This is a special case where an alt instruction - * jumps past the end of the section. These are - * handled later in handle_group_alt(). - */ - if (!strcmp(insn->sec->name, ".altinstr_replacement")) - continue; - - WARN_FUNC("can't find jump dest instruction at %s+0x%lx", - insn->sec, insn->offset, dest_sec->name, - dest_off); - return -1; - } - } - - return 0; -} - -/* - * Find the destination instructions for all calls. - */ -static int add_call_destinations(struct objtool_file *file) -{ - struct instruction *insn; - unsigned long dest_off; - struct rela *rela; - - for_each_insn(file, insn) { - if (insn->type != INSN_CALL) - continue; - - rela = find_rela_by_dest_range(insn->sec, insn->offset, - insn->len); - if (!rela) { - dest_off = insn->offset + insn->len + insn->immediate; - insn->call_dest = find_symbol_by_offset(insn->sec, - dest_off); - if (!insn->call_dest) { - WARN_FUNC("can't find call dest symbol at offset 0x%lx", - insn->sec, insn->offset, dest_off); - return -1; - } - } else if (rela->sym->type == STT_SECTION) { - insn->call_dest = find_symbol_by_offset(rela->sym->sec, - rela->addend+4); - if (!insn->call_dest || - insn->call_dest->type != STT_FUNC) { - WARN_FUNC("can't find call dest symbol at %s+0x%x", - insn->sec, insn->offset, - rela->sym->sec->name, - rela->addend + 4); - return -1; - } - } else - insn->call_dest = rela->sym; - } - - return 0; -} - -/* - * The .alternatives section requires some extra special care, over and above - * what other special sections require: - * - * 1. Because alternatives are patched in-place, we need to insert a fake jump - * instruction at the end so that validate_branch() skips all the original - * replaced instructions when validating the new instruction path. - * - * 2. An added wrinkle is that the new instruction length might be zero. In - * that case the old instructions are replaced with noops. We simulate that - * by creating a fake jump as the only new instruction. - * - * 3. In some cases, the alternative section includes an instruction which - * conditionally jumps to the _end_ of the entry. We have to modify these - * jumps' destinations to point back to .text rather than the end of the - * entry in .altinstr_replacement. - * - * 4. It has been requested that we don't validate the !POPCNT feature path - * which is a "very very small percentage of machines". - */ -static int handle_group_alt(struct objtool_file *file, - struct special_alt *special_alt, - struct instruction *orig_insn, - struct instruction **new_insn) -{ - struct instruction *last_orig_insn, *last_new_insn, *insn, *fake_jump; - unsigned long dest_off; - - last_orig_insn = NULL; - insn = orig_insn; - sec_for_each_insn_from(file, insn) { - if (insn->offset >= special_alt->orig_off + special_alt->orig_len) - break; - - if (special_alt->skip_orig) - insn->type = INSN_NOP; - - insn->alt_group = true; - last_orig_insn = insn; - } - - if (!next_insn_same_sec(file, last_orig_insn)) { - WARN("%s: don't know how to handle alternatives at end of section", - special_alt->orig_sec->name); - return -1; - } - - fake_jump = malloc(sizeof(*fake_jump)); - if (!fake_jump) { - WARN("malloc failed"); - return -1; - } - memset(fake_jump, 0, sizeof(*fake_jump)); - INIT_LIST_HEAD(&fake_jump->alts); - fake_jump->sec = special_alt->new_sec; - fake_jump->offset = -1; - fake_jump->type = INSN_JUMP_UNCONDITIONAL; - fake_jump->jump_dest = list_next_entry(last_orig_insn, list); - - if (!special_alt->new_len) { - *new_insn = fake_jump; - return 0; - } - - last_new_insn = NULL; - insn = *new_insn; - sec_for_each_insn_from(file, insn) { - if (insn->offset >= special_alt->new_off + special_alt->new_len) - break; - - last_new_insn = insn; - - if (insn->type != INSN_JUMP_CONDITIONAL && - insn->type != INSN_JUMP_UNCONDITIONAL) - continue; - - if (!insn->immediate) - continue; - - dest_off = insn->offset + insn->len + insn->immediate; - if (dest_off == special_alt->new_off + special_alt->new_len) - insn->jump_dest = fake_jump; - - if (!insn->jump_dest) { - WARN_FUNC("can't find alternative jump destination", - insn->sec, insn->offset); - return -1; - } - } - - if (!last_new_insn) { - WARN_FUNC("can't find last new alternative instruction", - special_alt->new_sec, special_alt->new_off); - return -1; - } - - list_add(&fake_jump->list, &last_new_insn->list); - - return 0; -} - -/* - * A jump table entry can either convert a nop to a jump or a jump to a nop. - * If the original instruction is a jump, make the alt entry an effective nop - * by just skipping the original instruction. - */ -static int handle_jump_alt(struct objtool_file *file, - struct special_alt *special_alt, - struct instruction *orig_insn, - struct instruction **new_insn) -{ - if (orig_insn->type == INSN_NOP) - return 0; - - if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) { - WARN_FUNC("unsupported instruction at jump label", - orig_insn->sec, orig_insn->offset); - return -1; - } - - *new_insn = list_next_entry(orig_insn, list); - return 0; -} - -/* - * Read all the special sections which have alternate instructions which can be - * patched in or redirected to at runtime. Each instruction having alternate - * instruction(s) has them added to its insn->alts list, which will be - * traversed in validate_branch(). - */ -static int add_special_section_alts(struct objtool_file *file) -{ - struct list_head special_alts; - struct instruction *orig_insn, *new_insn; - struct special_alt *special_alt, *tmp; - struct alternative *alt; - int ret; - - ret = special_get_alts(file->elf, &special_alts); - if (ret) - return ret; - - list_for_each_entry_safe(special_alt, tmp, &special_alts, list) { - alt = malloc(sizeof(*alt)); - if (!alt) { - WARN("malloc failed"); - ret = -1; - goto out; - } - - orig_insn = find_insn(file, special_alt->orig_sec, - special_alt->orig_off); - if (!orig_insn) { - WARN_FUNC("special: can't find orig instruction", - special_alt->orig_sec, special_alt->orig_off); - ret = -1; - goto out; - } +#include "check.h" - new_insn = NULL; - if (!special_alt->group || special_alt->new_len) { - new_insn = find_insn(file, special_alt->new_sec, - special_alt->new_off); - if (!new_insn) { - WARN_FUNC("special: can't find new instruction", - special_alt->new_sec, - special_alt->new_off); - ret = -1; - goto out; - } - } +bool nofp; - if (special_alt->group) { - ret = handle_group_alt(file, special_alt, orig_insn, - &new_insn); - if (ret) - goto out; - } else if (special_alt->jump_or_nop) { - ret = handle_jump_alt(file, special_alt, orig_insn, - &new_insn); - if (ret) - goto out; - } - - alt->insn = new_insn; - list_add_tail(&alt->list, &orig_insn->alts); - - list_del(&special_alt->list); - free(special_alt); - } - -out: - return ret; -} - -static int add_switch_table(struct objtool_file *file, struct symbol *func, - struct instruction *insn, struct rela *table, - struct rela *next_table) -{ - struct rela *rela = table; - struct instruction *alt_insn; - struct alternative *alt; - - list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) { - if (rela == next_table) - break; - - if (rela->sym->sec != insn->sec || - rela->addend <= func->offset || - rela->addend >= func->offset + func->len) - break; - - alt_insn = find_insn(file, insn->sec, rela->addend); - if (!alt_insn) { - WARN("%s: can't find instruction at %s+0x%x", - file->rodata->rela->name, insn->sec->name, - rela->addend); - return -1; - } - - alt = malloc(sizeof(*alt)); - if (!alt) { - WARN("malloc failed"); - return -1; - } - - alt->insn = alt_insn; - list_add_tail(&alt->list, &insn->alts); - } - - return 0; -} - -/* - * find_switch_table() - Given a dynamic jump, find the switch jump table in - * .rodata associated with it. - * - * There are 3 basic patterns: - * - * 1. jmpq *[rodata addr](,%reg,8) - * - * This is the most common case by far. It jumps to an address in a simple - * jump table which is stored in .rodata. - * - * 2. jmpq *[rodata addr](%rip) - * - * This is caused by a rare GCC quirk, currently only seen in three driver - * functions in the kernel, only with certain obscure non-distro configs. - * - * As part of an optimization, GCC makes a copy of an existing switch jump - * table, modifies it, and then hard-codes the jump (albeit with an indirect - * jump) to use a single entry in the table. The rest of the jump table and - * some of its jump targets remain as dead code. - * - * In such a case we can just crudely ignore all unreachable instruction - * warnings for the entire object file. Ideally we would just ignore them - * for the function, but that would require redesigning the code quite a - * bit. And honestly that's just not worth doing: unreachable instruction - * warnings are of questionable value anyway, and this is such a rare issue. - * - * 3. mov [rodata addr],%reg1 - * ... some instructions ... - * jmpq *(%reg1,%reg2,8) - * - * This is a fairly uncommon pattern which is new for GCC 6. As of this - * writing, there are 11 occurrences of it in the allmodconfig kernel. - * - * TODO: Once we have DWARF CFI and smarter instruction decoding logic, - * ensure the same register is used in the mov and jump instructions. - */ -static struct rela *find_switch_table(struct objtool_file *file, - struct symbol *func, - struct instruction *insn) -{ - struct rela *text_rela, *rodata_rela; - struct instruction *orig_insn = insn; - - text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len); - if (text_rela && text_rela->sym == file->rodata->sym) { - /* case 1 */ - rodata_rela = find_rela_by_dest(file->rodata, - text_rela->addend); - if (rodata_rela) - return rodata_rela; - - /* case 2 */ - rodata_rela = find_rela_by_dest(file->rodata, - text_rela->addend + 4); - if (!rodata_rela) - return NULL; - file->ignore_unreachables = true; - return rodata_rela; - } - - /* case 3 */ - func_for_each_insn_continue_reverse(file, func, insn) { - if (insn->type == INSN_JUMP_DYNAMIC) - break; - - /* allow small jumps within the range */ - if (insn->type == INSN_JUMP_UNCONDITIONAL && - insn->jump_dest && - (insn->jump_dest->offset <= insn->offset || - insn->jump_dest->offset > orig_insn->offset)) - break; - - /* look for a relocation which references .rodata */ - text_rela = find_rela_by_dest_range(insn->sec, insn->offset, - insn->len); - if (!text_rela || text_rela->sym != file->rodata->sym) - continue; - - /* - * Make sure the .rodata address isn't associated with a - * symbol. gcc jump tables are anonymous data. - */ - if (find_symbol_containing(file->rodata, text_rela->addend)) - continue; - - return find_rela_by_dest(file->rodata, text_rela->addend); - } - - return NULL; -} - -static int add_func_switch_tables(struct objtool_file *file, - struct symbol *func) -{ - struct instruction *insn, *prev_jump = NULL; - struct rela *rela, *prev_rela = NULL; - int ret; - - func_for_each_insn(file, func, insn) { - if (insn->type != INSN_JUMP_DYNAMIC) - continue; - - rela = find_switch_table(file, func, insn); - if (!rela) - continue; - - /* - * We found a switch table, but we don't know yet how big it - * is. Don't add it until we reach the end of the function or - * the beginning of another switch table in the same function. - */ - if (prev_jump) { - ret = add_switch_table(file, func, prev_jump, prev_rela, - rela); - if (ret) - return ret; - } - - prev_jump = insn; - prev_rela = rela; - } - - if (prev_jump) { - ret = add_switch_table(file, func, prev_jump, prev_rela, NULL); - if (ret) - return ret; - } - - return 0; -} - -/* - * For some switch statements, gcc generates a jump table in the .rodata - * section which contains a list of addresses within the function to jump to. - * This finds these jump tables and adds them to the insn->alts lists. - */ -static int add_switch_table_alts(struct objtool_file *file) -{ - struct section *sec; - struct symbol *func; - int ret; - - if (!file->rodata || !file->rodata->rela) - return 0; - - list_for_each_entry(sec, &file->elf->sections, list) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; - - ret = add_func_switch_tables(file, func); - if (ret) - return ret; - } - } - - return 0; -} - -static int decode_sections(struct objtool_file *file) -{ - int ret; - - ret = decode_instructions(file); - if (ret) - return ret; - - ret = add_dead_ends(file); - if (ret) - return ret; - - add_ignores(file); - - ret = add_jump_destinations(file); - if (ret) - return ret; - - ret = add_call_destinations(file); - if (ret) - return ret; - - ret = add_special_section_alts(file); - if (ret) - return ret; - - ret = add_switch_table_alts(file); - if (ret) - return ret; - - return 0; -} - -static bool is_fentry_call(struct instruction *insn) -{ - if (insn->type == INSN_CALL && - insn->call_dest->type == STT_NOTYPE && - !strcmp(insn->call_dest->name, "__fentry__")) - return true; - - return false; -} - -static bool has_modified_stack_frame(struct instruction *insn) -{ - return (insn->state & STATE_FP_SAVED) || - (insn->state & STATE_FP_SETUP); -} - -static bool has_valid_stack_frame(struct instruction *insn) -{ - return (insn->state & STATE_FP_SAVED) && - (insn->state & STATE_FP_SETUP); -} - -static unsigned int frame_state(unsigned long state) -{ - return (state & (STATE_FP_SAVED | STATE_FP_SETUP)); -} - -/* - * Follow the branch starting at the given instruction, and recursively follow - * any other branches (jumps). Meanwhile, track the frame pointer state at - * each instruction and validate all the rules described in - * tools/objtool/Documentation/stack-validation.txt. - */ -static int validate_branch(struct objtool_file *file, - struct instruction *first, unsigned char first_state) -{ - struct alternative *alt; - struct instruction *insn; - struct section *sec; - struct symbol *func = NULL; - unsigned char state; - int ret; - - insn = first; - sec = insn->sec; - state = first_state; - - if (insn->alt_group && list_empty(&insn->alts)) { - WARN_FUNC("don't know how to handle branch to middle of alternative instruction group", - sec, insn->offset); - return 1; - } - - while (1) { - if (file->c_file && insn->func) { - if (func && func != insn->func) { - WARN("%s() falls through to next function %s()", - func->name, insn->func->name); - return 1; - } - - func = insn->func; - } - - if (insn->visited) { - if (frame_state(insn->state) != frame_state(state)) { - WARN_FUNC("frame pointer state mismatch", - sec, insn->offset); - return 1; - } - - return 0; - } - - insn->visited = true; - insn->state = state; - - list_for_each_entry(alt, &insn->alts, list) { - ret = validate_branch(file, alt->insn, state); - if (ret) - return 1; - } - - switch (insn->type) { - - case INSN_FP_SAVE: - if (!nofp) { - if (state & STATE_FP_SAVED) { - WARN_FUNC("duplicate frame pointer save", - sec, insn->offset); - return 1; - } - state |= STATE_FP_SAVED; - } - break; - - case INSN_FP_SETUP: - if (!nofp) { - if (state & STATE_FP_SETUP) { - WARN_FUNC("duplicate frame pointer setup", - sec, insn->offset); - return 1; - } - state |= STATE_FP_SETUP; - } - break; - - case INSN_FP_RESTORE: - if (!nofp) { - if (has_valid_stack_frame(insn)) - state &= ~STATE_FP_SETUP; - - state &= ~STATE_FP_SAVED; - } - break; - - case INSN_RETURN: - if (!nofp && has_modified_stack_frame(insn)) { - WARN_FUNC("return without frame pointer restore", - sec, insn->offset); - return 1; - } - return 0; - - case INSN_CALL: - if (is_fentry_call(insn)) { - state |= STATE_FENTRY; - break; - } - - ret = dead_end_function(file, insn->call_dest); - if (ret == 1) - return 0; - if (ret == -1) - return 1; - - /* fallthrough */ - case INSN_CALL_DYNAMIC: - if (!nofp && !has_valid_stack_frame(insn)) { - WARN_FUNC("call without frame pointer save/setup", - sec, insn->offset); - return 1; - } - break; - - case INSN_JUMP_CONDITIONAL: - case INSN_JUMP_UNCONDITIONAL: - if (insn->jump_dest) { - ret = validate_branch(file, insn->jump_dest, - state); - if (ret) - return 1; - } else if (has_modified_stack_frame(insn)) { - WARN_FUNC("sibling call from callable instruction with changed frame pointer", - sec, insn->offset); - return 1; - } /* else it's a sibling call */ - - if (insn->type == INSN_JUMP_UNCONDITIONAL) - return 0; - - break; - - case INSN_JUMP_DYNAMIC: - if (list_empty(&insn->alts) && - has_modified_stack_frame(insn)) { - WARN_FUNC("sibling call from callable instruction with changed frame pointer", - sec, insn->offset); - return 1; - } - - return 0; - - default: - break; - } - - if (insn->dead_end) - return 0; - - insn = next_insn_same_sec(file, insn); - if (!insn) { - WARN("%s: unexpected end of section", sec->name); - return 1; - } - } - - return 0; -} - -static bool is_kasan_insn(struct instruction *insn) -{ - return (insn->type == INSN_CALL && - !strcmp(insn->call_dest->name, "__asan_handle_no_return")); -} - -static bool is_ubsan_insn(struct instruction *insn) -{ - return (insn->type == INSN_CALL && - !strcmp(insn->call_dest->name, - "__ubsan_handle_builtin_unreachable")); -} - -static bool ignore_unreachable_insn(struct symbol *func, - struct instruction *insn) -{ - int i; - - if (insn->type == INSN_NOP) - return true; - - /* - * Check if this (or a subsequent) instruction is related to - * CONFIG_UBSAN or CONFIG_KASAN. - * - * End the search at 5 instructions to avoid going into the weeds. - */ - for (i = 0; i < 5; i++) { - - if (is_kasan_insn(insn) || is_ubsan_insn(insn)) - return true; - - if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest) { - insn = insn->jump_dest; - continue; - } - - if (insn->offset + insn->len >= func->offset + func->len) - break; - insn = list_next_entry(insn, list); - } - - return false; -} - -static int validate_functions(struct objtool_file *file) -{ - struct section *sec; - struct symbol *func; - struct instruction *insn; - int ret, warnings = 0; - - list_for_each_entry(sec, &file->elf->sections, list) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; - - insn = find_insn(file, sec, func->offset); - if (!insn) - continue; - - ret = validate_branch(file, insn, 0); - warnings += ret; - } - } - - list_for_each_entry(sec, &file->elf->sections, list) { - list_for_each_entry(func, &sec->symbol_list, list) { - if (func->type != STT_FUNC) - continue; - - func_for_each_insn(file, func, insn) { - if (insn->visited) - continue; - - insn->visited = true; - - if (file->ignore_unreachables || warnings || - ignore_unreachable_insn(func, insn)) - continue; - - /* - * gcov produces a lot of unreachable - * instructions. If we get an unreachable - * warning and the file has gcov enabled, just - * ignore it, and all other such warnings for - * the file. - */ - if (!file->ignore_unreachables && - gcov_enabled(file)) { - file->ignore_unreachables = true; - continue; - } - - WARN_FUNC("function has unreachable instruction", insn->sec, insn->offset); - warnings++; - } - } - } - - return warnings; -} - -static int validate_uncallable_instructions(struct objtool_file *file) -{ - struct instruction *insn; - int warnings = 0; - - for_each_insn(file, insn) { - if (!insn->visited && insn->type == INSN_RETURN) { - WARN_FUNC("return instruction outside of a callable function", - insn->sec, insn->offset); - warnings++; - } - } - - return warnings; -} - -static void cleanup(struct objtool_file *file) -{ - struct instruction *insn, *tmpinsn; - struct alternative *alt, *tmpalt; - - list_for_each_entry_safe(insn, tmpinsn, &file->insn_list, list) { - list_for_each_entry_safe(alt, tmpalt, &insn->alts, list) { - list_del(&alt->list); - free(alt); - } - list_del(&insn->list); - hash_del(&insn->hash); - free(insn); - } - elf_close(file->elf); -} - -const char * const check_usage[] = { +static const char * const check_usage[] = { "objtool check [<options>] file.o", NULL, }; +const struct option check_options[] = { + OPT_BOOLEAN('f', "no-fp", &nofp, "Skip frame pointer validation"), + OPT_END(), +}; + int cmd_check(int argc, const char **argv) { - struct objtool_file file; - int ret, warnings = 0; - - const struct option options[] = { - OPT_BOOLEAN('f', "no-fp", &nofp, "Skip frame pointer validation"), - OPT_END(), - }; + const char *objname; - argc = parse_options(argc, argv, options, check_usage, 0); + argc = parse_options(argc, argv, check_options, check_usage, 0); if (argc != 1) - usage_with_options(check_usage, options); + usage_with_options(check_usage, check_options); objname = argv[0]; - file.elf = elf_open(objname); - if (!file.elf) { - fprintf(stderr, "error reading elf file %s\n", objname); - return 1; - } - - INIT_LIST_HEAD(&file.insn_list); - hash_init(file.insn_hash); - file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard"); - file.rodata = find_section_by_name(file.elf, ".rodata"); - file.ignore_unreachables = false; - file.c_file = find_section_by_name(file.elf, ".comment"); - - ret = decode_sections(&file); - if (ret < 0) - goto out; - warnings += ret; - - ret = validate_functions(&file); - if (ret < 0) - goto out; - warnings += ret; - - ret = validate_uncallable_instructions(&file); - if (ret < 0) - goto out; - warnings += ret; - -out: - cleanup(&file); - - /* ignore warnings for now until we get all the code cleaned up */ - if (ret || warnings) - return 0; - return 0; + return check(objname, nofp); } diff --git a/tools/objtool/cfi.h b/tools/objtool/cfi.h new file mode 100644 index 000000000000..443ab2c69992 --- /dev/null +++ b/tools/objtool/cfi.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _OBJTOOL_CFI_H +#define _OBJTOOL_CFI_H + +#define CFI_UNDEFINED -1 +#define CFI_CFA -2 +#define CFI_SP_INDIRECT -3 +#define CFI_BP_INDIRECT -4 + +#define CFI_AX 0 +#define CFI_DX 1 +#define CFI_CX 2 +#define CFI_BX 3 +#define CFI_SI 4 +#define CFI_DI 5 +#define CFI_BP 6 +#define CFI_SP 7 +#define CFI_R8 8 +#define CFI_R9 9 +#define CFI_R10 10 +#define CFI_R11 11 +#define CFI_R12 12 +#define CFI_R13 13 +#define CFI_R14 14 +#define CFI_R15 15 +#define CFI_RA 16 +#define CFI_NUM_REGS 17 + +struct cfi_reg { + int base; + int offset; +}; + +struct cfi_state { + struct cfi_reg cfa; + struct cfi_reg regs[CFI_NUM_REGS]; +}; + +#endif /* _OBJTOOL_CFI_H */ diff --git a/tools/objtool/check.c b/tools/objtool/check.c new file mode 100644 index 000000000000..fea222192c57 --- /dev/null +++ b/tools/objtool/check.c @@ -0,0 +1,1655 @@ +/* + * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <string.h> +#include <stdlib.h> + +#include "check.h" +#include "elf.h" +#include "special.h" +#include "arch.h" +#include "warn.h" + +#include <linux/hashtable.h> +#include <linux/kernel.h> + +struct alternative { + struct list_head list; + struct instruction *insn; +}; + +const char *objname; +static bool nofp; +struct cfi_state initial_func_cfi; + +static struct instruction *find_insn(struct objtool_file *file, + struct section *sec, unsigned long offset) +{ + struct instruction *insn; + + hash_for_each_possible(file->insn_hash, insn, hash, offset) + if (insn->sec == sec && insn->offset == offset) + return insn; + + return NULL; +} + +static struct instruction *next_insn_same_sec(struct objtool_file *file, + struct instruction *insn) +{ + struct instruction *next = list_next_entry(insn, list); + + if (!next || &next->list == &file->insn_list || next->sec != insn->sec) + return NULL; + + return next; +} + +static bool gcov_enabled(struct objtool_file *file) +{ + struct section *sec; + struct symbol *sym; + + for_each_sec(file, sec) + list_for_each_entry(sym, &sec->symbol_list, list) + if (!strncmp(sym->name, "__gcov_.", 8)) + return true; + + return false; +} + +#define func_for_each_insn(file, func, insn) \ + for (insn = find_insn(file, func->sec, func->offset); \ + insn && &insn->list != &file->insn_list && \ + insn->sec == func->sec && \ + insn->offset < func->offset + func->len; \ + insn = list_next_entry(insn, list)) + +#define func_for_each_insn_continue_reverse(file, func, insn) \ + for (insn = list_prev_entry(insn, list); \ + &insn->list != &file->insn_list && \ + insn->sec == func->sec && insn->offset >= func->offset; \ + insn = list_prev_entry(insn, list)) + +#define sec_for_each_insn_from(file, insn) \ + for (; insn; insn = next_insn_same_sec(file, insn)) + +#define sec_for_each_insn_continue(file, insn) \ + for (insn = next_insn_same_sec(file, insn); insn; \ + insn = next_insn_same_sec(file, insn)) + +/* + * Check if the function has been manually whitelisted with the + * STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted + * due to its use of a context switching instruction. + */ +static bool ignore_func(struct objtool_file *file, struct symbol *func) +{ + struct rela *rela; + struct instruction *insn; + + /* check for STACK_FRAME_NON_STANDARD */ + if (file->whitelist && file->whitelist->rela) + list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) { + if (rela->sym->type == STT_SECTION && + rela->sym->sec == func->sec && + rela->addend == func->offset) + return true; + if (rela->sym->type == STT_FUNC && rela->sym == func) + return true; + } + + /* check if it has a context switching instruction */ + func_for_each_insn(file, func, insn) + if (insn->type == INSN_CONTEXT_SWITCH) + return true; + + return false; +} + +/* + * This checks to see if the given function is a "noreturn" function. + * + * For global functions which are outside the scope of this object file, we + * have to keep a manual list of them. + * + * For local functions, we have to detect them manually by simply looking for + * the lack of a return instruction. + * + * Returns: + * -1: error + * 0: no dead end + * 1: dead end + */ +static int __dead_end_function(struct objtool_file *file, struct symbol *func, + int recursion) +{ + int i; + struct instruction *insn; + bool empty = true; + + /* + * Unfortunately these have to be hard coded because the noreturn + * attribute isn't provided in ELF data. + */ + static const char * const global_noreturns[] = { + "__stack_chk_fail", + "panic", + "do_exit", + "do_task_dead", + "__module_put_and_exit", + "complete_and_exit", + "kvm_spurious_fault", + "__reiserfs_panic", + "lbug_with_loc", + "fortify_panic", + }; + + if (func->bind == STB_WEAK) + return 0; + + if (func->bind == STB_GLOBAL) + for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) + if (!strcmp(func->name, global_noreturns[i])) + return 1; + + if (!func->sec) + return 0; + + func_for_each_insn(file, func, insn) { + empty = false; + + if (insn->type == INSN_RETURN) + return 0; + } + + if (empty) + return 0; + + /* + * A function can have a sibling call instead of a return. In that + * case, the function's dead-end status depends on whether the target + * of the sibling call returns. + */ + func_for_each_insn(file, func, insn) { + if (insn->sec != func->sec || + insn->offset >= func->offset + func->len) + break; + + if (insn->type == INSN_JUMP_UNCONDITIONAL) { + struct instruction *dest = insn->jump_dest; + struct symbol *dest_func; + + if (!dest) + /* sibling call to another file */ + return 0; + + if (dest->sec != func->sec || + dest->offset < func->offset || + dest->offset >= func->offset + func->len) { + /* local sibling call */ + dest_func = find_symbol_by_offset(dest->sec, + dest->offset); + if (!dest_func) + continue; + + if (recursion == 5) { + WARN_FUNC("infinite recursion (objtool bug!)", + dest->sec, dest->offset); + return -1; + } + + return __dead_end_function(file, dest_func, + recursion + 1); + } + } + + if (insn->type == INSN_JUMP_DYNAMIC && list_empty(&insn->alts)) + /* sibling call */ + return 0; + } + + return 1; +} + +static int dead_end_function(struct objtool_file *file, struct symbol *func) +{ + return __dead_end_function(file, func, 0); +} + +static void clear_insn_state(struct insn_state *state) +{ + int i; + + memset(state, 0, sizeof(*state)); + state->cfa.base = CFI_UNDEFINED; + for (i = 0; i < CFI_NUM_REGS; i++) + state->regs[i].base = CFI_UNDEFINED; + state->drap_reg = CFI_UNDEFINED; +} + +/* + * Call the arch-specific instruction decoder for all the instructions and add + * them to the global instruction list. + */ +static int decode_instructions(struct objtool_file *file) +{ + struct section *sec; + struct symbol *func; + unsigned long offset; + struct instruction *insn; + int ret; + + for_each_sec(file, sec) { + + if (!(sec->sh.sh_flags & SHF_EXECINSTR)) + continue; + + for (offset = 0; offset < sec->len; offset += insn->len) { + insn = malloc(sizeof(*insn)); + if (!insn) { + WARN("malloc failed"); + return -1; + } + memset(insn, 0, sizeof(*insn)); + INIT_LIST_HEAD(&insn->alts); + clear_insn_state(&insn->state); + + insn->sec = sec; + insn->offset = offset; + + ret = arch_decode_instruction(file->elf, sec, offset, + sec->len - offset, + &insn->len, &insn->type, + &insn->immediate, + &insn->stack_op); + if (ret) + return ret; + + if (!insn->type || insn->type > INSN_LAST) { + WARN_FUNC("invalid instruction type %d", + insn->sec, insn->offset, insn->type); + return -1; + } + + hash_add(file->insn_hash, &insn->hash, insn->offset); + list_add_tail(&insn->list, &file->insn_list); + } + + list_for_each_entry(func, &sec->symbol_list, list) { + if (func->type != STT_FUNC) + continue; + + if (!find_insn(file, sec, func->offset)) { + WARN("%s(): can't find starting instruction", + func->name); + return -1; + } + + func_for_each_insn(file, func, insn) + if (!insn->func) + insn->func = func; + } + } + + return 0; +} + +/* + * Find all uses of the unreachable() macro, which are code path dead ends. + */ +static int add_dead_ends(struct objtool_file *file) +{ + struct section *sec; + struct rela *rela; + struct instruction *insn; + bool found; + + sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); + if (!sec) + return 0; + + list_for_each_entry(rela, &sec->rela_list, list) { + if (rela->sym->type != STT_SECTION) { + WARN("unexpected relocation symbol type in %s", sec->name); + return -1; + } + insn = find_insn(file, rela->sym->sec, rela->addend); + if (insn) + insn = list_prev_entry(insn, list); + else if (rela->addend == rela->sym->sec->len) { + found = false; + list_for_each_entry_reverse(insn, &file->insn_list, list) { + if (insn->sec == rela->sym->sec) { + found = true; + break; + } + } + + if (!found) { + WARN("can't find unreachable insn at %s+0x%x", + rela->sym->sec->name, rela->addend); + return -1; + } + } else { + WARN("can't find unreachable insn at %s+0x%x", + rela->sym->sec->name, rela->addend); + return -1; + } + + insn->dead_end = true; + } + + return 0; +} + +/* + * Warnings shouldn't be reported for ignored functions. + */ +static void add_ignores(struct objtool_file *file) +{ + struct instruction *insn; + struct section *sec; + struct symbol *func; + + for_each_sec(file, sec) { + list_for_each_entry(func, &sec->symbol_list, list) { + if (func->type != STT_FUNC) + continue; + + if (!ignore_func(file, func)) + continue; + + func_for_each_insn(file, func, insn) + insn->ignore = true; + } + } +} + +/* + * Find the destination instructions for all jumps. + */ +static int add_jump_destinations(struct objtool_file *file) +{ + struct instruction *insn; + struct rela *rela; + struct section *dest_sec; + unsigned long dest_off; + + for_each_insn(file, insn) { + if (insn->type != INSN_JUMP_CONDITIONAL && + insn->type != INSN_JUMP_UNCONDITIONAL) + continue; + + if (insn->ignore) + continue; + + rela = find_rela_by_dest_range(insn->sec, insn->offset, + insn->len); + if (!rela) { + dest_sec = insn->sec; + dest_off = insn->offset + insn->len + insn->immediate; + } else if (rela->sym->type == STT_SECTION) { + dest_sec = rela->sym->sec; + dest_off = rela->addend + 4; + } else if (rela->sym->sec->idx) { + dest_sec = rela->sym->sec; + dest_off = rela->sym->sym.st_value + rela->addend + 4; + } else { + /* sibling call */ + insn->jump_dest = 0; + continue; + } + + insn->jump_dest = find_insn(file, dest_sec, dest_off); + if (!insn->jump_dest) { + + /* + * This is a special case where an alt instruction + * jumps past the end of the section. These are + * handled later in handle_group_alt(). + */ + if (!strcmp(insn->sec->name, ".altinstr_replacement")) + continue; + + WARN_FUNC("can't find jump dest instruction at %s+0x%lx", + insn->sec, insn->offset, dest_sec->name, + dest_off); + return -1; + } + } + + return 0; +} + +/* + * Find the destination instructions for all calls. + */ +static int add_call_destinations(struct objtool_file *file) +{ + struct instruction *insn; + unsigned long dest_off; + struct rela *rela; + + for_each_insn(file, insn) { + if (insn->type != INSN_CALL) + continue; + + rela = find_rela_by_dest_range(insn->sec, insn->offset, + insn->len); + if (!rela) { + dest_off = insn->offset + insn->len + insn->immediate; + insn->call_dest = find_symbol_by_offset(insn->sec, + dest_off); + if (!insn->call_dest) { + WARN_FUNC("can't find call dest symbol at offset 0x%lx", + insn->sec, insn->offset, dest_off); + return -1; + } + } else if (rela->sym->type == STT_SECTION) { + insn->call_dest = find_symbol_by_offset(rela->sym->sec, + rela->addend+4); + if (!insn->call_dest || + insn->call_dest->type != STT_FUNC) { + WARN_FUNC("can't find call dest symbol at %s+0x%x", + insn->sec, insn->offset, + rela->sym->sec->name, + rela->addend + 4); + return -1; + } + } else + insn->call_dest = rela->sym; + } + + return 0; +} + +/* + * The .alternatives section requires some extra special care, over and above + * what other special sections require: + * + * 1. Because alternatives are patched in-place, we need to insert a fake jump + * instruction at the end so that validate_branch() skips all the original + * replaced instructions when validating the new instruction path. + * + * 2. An added wrinkle is that the new instruction length might be zero. In + * that case the old instructions are replaced with noops. We simulate that + * by creating a fake jump as the only new instruction. + * + * 3. In some cases, the alternative section includes an instruction which + * conditionally jumps to the _end_ of the entry. We have to modify these + * jumps' destinations to point back to .text rather than the end of the + * entry in .altinstr_replacement. + * + * 4. It has been requested that we don't validate the !POPCNT feature path + * which is a "very very small percentage of machines". + */ +static int handle_group_alt(struct objtool_file *file, + struct special_alt *special_alt, + struct instruction *orig_insn, + struct instruction **new_insn) +{ + struct instruction *last_orig_insn, *last_new_insn, *insn, *fake_jump; + unsigned long dest_off; + + last_orig_insn = NULL; + insn = orig_insn; + sec_for_each_insn_from(file, insn) { + if (insn->offset >= special_alt->orig_off + special_alt->orig_len) + break; + + if (special_alt->skip_orig) + insn->type = INSN_NOP; + + insn->alt_group = true; + last_orig_insn = insn; + } + + if (!next_insn_same_sec(file, last_orig_insn)) { + WARN("%s: don't know how to handle alternatives at end of section", + special_alt->orig_sec->name); + return -1; + } + + fake_jump = malloc(sizeof(*fake_jump)); + if (!fake_jump) { + WARN("malloc failed"); + return -1; + } + memset(fake_jump, 0, sizeof(*fake_jump)); + INIT_LIST_HEAD(&fake_jump->alts); + clear_insn_state(&fake_jump->state); + + fake_jump->sec = special_alt->new_sec; + fake_jump->offset = -1; + fake_jump->type = INSN_JUMP_UNCONDITIONAL; + fake_jump->jump_dest = list_next_entry(last_orig_insn, list); + fake_jump->ignore = true; + + if (!special_alt->new_len) { + *new_insn = fake_jump; + return 0; + } + + last_new_insn = NULL; + insn = *new_insn; + sec_for_each_insn_from(file, insn) { + if (insn->offset >= special_alt->new_off + special_alt->new_len) + break; + + last_new_insn = insn; + + if (insn->type != INSN_JUMP_CONDITIONAL && + insn->type != INSN_JUMP_UNCONDITIONAL) + continue; + + if (!insn->immediate) + continue; + + dest_off = insn->offset + insn->len + insn->immediate; + if (dest_off == special_alt->new_off + special_alt->new_len) + insn->jump_dest = fake_jump; + + if (!insn->jump_dest) { + WARN_FUNC("can't find alternative jump destination", + insn->sec, insn->offset); + return -1; + } + } + + if (!last_new_insn) { + WARN_FUNC("can't find last new alternative instruction", + special_alt->new_sec, special_alt->new_off); + return -1; + } + + list_add(&fake_jump->list, &last_new_insn->list); + + return 0; +} + +/* + * A jump table entry can either convert a nop to a jump or a jump to a nop. + * If the original instruction is a jump, make the alt entry an effective nop + * by just skipping the original instruction. + */ +static int handle_jump_alt(struct objtool_file *file, + struct special_alt *special_alt, + struct instruction *orig_insn, + struct instruction **new_insn) +{ + if (orig_insn->type == INSN_NOP) + return 0; + + if (orig_insn->type != INSN_JUMP_UNCONDITIONAL) { + WARN_FUNC("unsupported instruction at jump label", + orig_insn->sec, orig_insn->offset); + return -1; + } + + *new_insn = list_next_entry(orig_insn, list); + return 0; +} + +/* + * Read all the special sections which have alternate instructions which can be + * patched in or redirected to at runtime. Each instruction having alternate + * instruction(s) has them added to its insn->alts list, which will be + * traversed in validate_branch(). + */ +static int add_special_section_alts(struct objtool_file *file) +{ + struct list_head special_alts; + struct instruction *orig_insn, *new_insn; + struct special_alt *special_alt, *tmp; + struct alternative *alt; + int ret; + + ret = special_get_alts(file->elf, &special_alts); + if (ret) + return ret; + + list_for_each_entry_safe(special_alt, tmp, &special_alts, list) { + alt = malloc(sizeof(*alt)); + if (!alt) { + WARN("malloc failed"); + ret = -1; + goto out; + } + + orig_insn = find_insn(file, special_alt->orig_sec, + special_alt->orig_off); + if (!orig_insn) { + WARN_FUNC("special: can't find orig instruction", + special_alt->orig_sec, special_alt->orig_off); + ret = -1; + goto out; + } + + new_insn = NULL; + if (!special_alt->group || special_alt->new_len) { + new_insn = find_insn(file, special_alt->new_sec, + special_alt->new_off); + if (!new_insn) { + WARN_FUNC("special: can't find new instruction", + special_alt->new_sec, + special_alt->new_off); + ret = -1; + goto out; + } + } + + if (special_alt->group) { + ret = handle_group_alt(file, special_alt, orig_insn, + &new_insn); + if (ret) + goto out; + } else if (special_alt->jump_or_nop) { + ret = handle_jump_alt(file, special_alt, orig_insn, + &new_insn); + if (ret) + goto out; + } + + alt->insn = new_insn; + list_add_tail(&alt->list, &orig_insn->alts); + + list_del(&special_alt->list); + free(special_alt); + } + +out: + return ret; +} + +static int add_switch_table(struct objtool_file *file, struct symbol *func, + struct instruction *insn, struct rela *table, + struct rela *next_table) +{ + struct rela *rela = table; + struct instruction *alt_insn; + struct alternative *alt; + + list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) { + if (rela == next_table) + break; + + if (rela->sym->sec != insn->sec || + rela->addend <= func->offset || + rela->addend >= func->offset + func->len) + break; + + alt_insn = find_insn(file, insn->sec, rela->addend); + if (!alt_insn) { + WARN("%s: can't find instruction at %s+0x%x", + file->rodata->rela->name, insn->sec->name, + rela->addend); + return -1; + } + + alt = malloc(sizeof(*alt)); + if (!alt) { + WARN("malloc failed"); + return -1; + } + + alt->insn = alt_insn; + list_add_tail(&alt->list, &insn->alts); + } + + return 0; +} + +/* + * find_switch_table() - Given a dynamic jump, find the switch jump table in + * .rodata associated with it. + * + * There are 3 basic patterns: + * + * 1. jmpq *[rodata addr](,%reg,8) + * + * This is the most common case by far. It jumps to an address in a simple + * jump table which is stored in .rodata. + * + * 2. jmpq *[rodata addr](%rip) + * + * This is caused by a rare GCC quirk, currently only seen in three driver + * functions in the kernel, only with certain obscure non-distro configs. + * + * As part of an optimization, GCC makes a copy of an existing switch jump + * table, modifies it, and then hard-codes the jump (albeit with an indirect + * jump) to use a single entry in the table. The rest of the jump table and + * some of its jump targets remain as dead code. + * + * In such a case we can just crudely ignore all unreachable instruction + * warnings for the entire object file. Ideally we would just ignore them + * for the function, but that would require redesigning the code quite a + * bit. And honestly that's just not worth doing: unreachable instruction + * warnings are of questionable value anyway, and this is such a rare issue. + * + * 3. mov [rodata addr],%reg1 + * ... some instructions ... + * jmpq *(%reg1,%reg2,8) + * + * This is a fairly uncommon pattern which is new for GCC 6. As of this + * writing, there are 11 occurrences of it in the allmodconfig kernel. + * + * TODO: Once we have DWARF CFI and smarter instruction decoding logic, + * ensure the same register is used in the mov and jump instructions. + */ +static struct rela *find_switch_table(struct objtool_file *file, + struct symbol *func, + struct instruction *insn) +{ + struct rela *text_rela, *rodata_rela; + struct instruction *orig_insn = insn; + + text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len); + if (text_rela && text_rela->sym == file->rodata->sym) { + /* case 1 */ + rodata_rela = find_rela_by_dest(file->rodata, + text_rela->addend); + if (rodata_rela) + return rodata_rela; + + /* case 2 */ + rodata_rela = find_rela_by_dest(file->rodata, + text_rela->addend + 4); + if (!rodata_rela) + return NULL; + file->ignore_unreachables = true; + return rodata_rela; + } + + /* case 3 */ + func_for_each_insn_continue_reverse(file, func, insn) { + if (insn->type == INSN_JUMP_DYNAMIC) + break; + + /* allow small jumps within the range */ + if (insn->type == INSN_JUMP_UNCONDITIONAL && + insn->jump_dest && + (insn->jump_dest->offset <= insn->offset || + insn->jump_dest->offset > orig_insn->offset)) + break; + + /* look for a relocation which references .rodata */ + text_rela = find_rela_by_dest_range(insn->sec, insn->offset, + insn->len); + if (!text_rela || text_rela->sym != file->rodata->sym) + continue; + + /* + * Make sure the .rodata address isn't associated with a + * symbol. gcc jump tables are anonymous data. + */ + if (find_symbol_containing(file->rodata, text_rela->addend)) + continue; + + return find_rela_by_dest(file->rodata, text_rela->addend); + } + + return NULL; +} + +static int add_func_switch_tables(struct objtool_file *file, + struct symbol *func) +{ + struct instruction *insn, *prev_jump = NULL; + struct rela *rela, *prev_rela = NULL; + int ret; + + func_for_each_insn(file, func, insn) { + if (insn->type != INSN_JUMP_DYNAMIC) + continue; + + rela = find_switch_table(file, func, insn); + if (!rela) + continue; + + /* + * We found a switch table, but we don't know yet how big it + * is. Don't add it until we reach the end of the function or + * the beginning of another switch table in the same function. + */ + if (prev_jump) { + ret = add_switch_table(file, func, prev_jump, prev_rela, + rela); + if (ret) + return ret; + } + + prev_jump = insn; + prev_rela = rela; + } + + if (prev_jump) { + ret = add_switch_table(file, func, prev_jump, prev_rela, NULL); + if (ret) + return ret; + } + + return 0; +} + +/* + * For some switch statements, gcc generates a jump table in the .rodata + * section which contains a list of addresses within the function to jump to. + * This finds these jump tables and adds them to the insn->alts lists. + */ +static int add_switch_table_alts(struct objtool_file *file) +{ + struct section *sec; + struct symbol *func; + int ret; + + if (!file->rodata || !file->rodata->rela) + return 0; + + for_each_sec(file, sec) { + list_for_each_entry(func, &sec->symbol_list, list) { + if (func->type != STT_FUNC) + continue; + + ret = add_func_switch_tables(file, func); + if (ret) + return ret; + } + } + + return 0; +} + +static int decode_sections(struct objtool_file *file) +{ + int ret; + + ret = decode_instructions(file); + if (ret) + return ret; + + ret = add_dead_ends(file); + if (ret) + return ret; + + add_ignores(file); + + ret = add_jump_destinations(file); + if (ret) + return ret; + + ret = add_call_destinations(file); + if (ret) + return ret; + + ret = add_special_section_alts(file); + if (ret) + return ret; + + ret = add_switch_table_alts(file); + if (ret) + return ret; + + return 0; +} + +static bool is_fentry_call(struct instruction *insn) +{ + if (insn->type == INSN_CALL && + insn->call_dest->type == STT_NOTYPE && + !strcmp(insn->call_dest->name, "__fentry__")) + return true; + + return false; +} + +static bool has_modified_stack_frame(struct insn_state *state) +{ + int i; + + if (state->cfa.base != initial_func_cfi.cfa.base || + state->cfa.offset != initial_func_cfi.cfa.offset || + state->stack_size != initial_func_cfi.cfa.offset || + state->drap) + return true; + + for (i = 0; i < CFI_NUM_REGS; i++) + if (state->regs[i].base != initial_func_cfi.regs[i].base || + state->regs[i].offset != initial_func_cfi.regs[i].offset) + return true; + + return false; +} + +static bool has_valid_stack_frame(struct insn_state *state) +{ + if (state->cfa.base == CFI_BP && state->regs[CFI_BP].base == CFI_CFA && + state->regs[CFI_BP].offset == -16) + return true; + + if (state->drap && state->regs[CFI_BP].base == CFI_BP) + return true; + + return false; +} + +static void save_reg(struct insn_state *state, unsigned char reg, int base, + int offset) +{ + if ((arch_callee_saved_reg(reg) || + (state->drap && reg == state->drap_reg)) && + state->regs[reg].base == CFI_UNDEFINED) { + state->regs[reg].base = base; + state->regs[reg].offset = offset; + } +} + +static void restore_reg(struct insn_state *state, unsigned char reg) +{ + state->regs[reg].base = CFI_UNDEFINED; + state->regs[reg].offset = 0; +} + +/* + * A note about DRAP stack alignment: + * + * GCC has the concept of a DRAP register, which is used to help keep track of + * the stack pointer when aligning the stack. r10 or r13 is used as the DRAP + * register. The typical DRAP pattern is: + * + * 4c 8d 54 24 08 lea 0x8(%rsp),%r10 + * 48 83 e4 c0 and $0xffffffffffffffc0,%rsp + * 41 ff 72 f8 pushq -0x8(%r10) + * 55 push %rbp + * 48 89 e5 mov %rsp,%rbp + * (more pushes) + * 41 52 push %r10 + * ... + * 41 5a pop %r10 + * (more pops) + * 5d pop %rbp + * 49 8d 62 f8 lea -0x8(%r10),%rsp + * c3 retq + * + * There are some variations in the epilogues, like: + * + * 5b pop %rbx + * 41 5a pop %r10 + * 41 5c pop %r12 + * 41 5d pop %r13 + * 41 5e pop %r14 + * c9 leaveq + * 49 8d 62 f8 lea -0x8(%r10),%rsp + * c3 retq + * + * and: + * + * 4c 8b 55 e8 mov -0x18(%rbp),%r10 + * 48 8b 5d e0 mov -0x20(%rbp),%rbx + * 4c 8b 65 f0 mov -0x10(%rbp),%r12 + * 4c 8b 6d f8 mov -0x8(%rbp),%r13 + * c9 leaveq + * 49 8d 62 f8 lea -0x8(%r10),%rsp + * c3 retq + * + * Sometimes r13 is used as the DRAP register, in which case it's saved and + * restored beforehand: + * + * 41 55 push %r13 + * 4c 8d 6c 24 10 lea 0x10(%rsp),%r13 + * 48 83 e4 f0 and $0xfffffffffffffff0,%rsp + * ... + * 49 8d 65 f0 lea -0x10(%r13),%rsp + * 41 5d pop %r13 + * c3 retq + */ +static int update_insn_state(struct instruction *insn, struct insn_state *state) +{ + struct stack_op *op = &insn->stack_op; + struct cfi_reg *cfa = &state->cfa; + struct cfi_reg *regs = state->regs; + + /* stack operations don't make sense with an undefined CFA */ + if (cfa->base == CFI_UNDEFINED) { + if (insn->func) { + WARN_FUNC("undefined stack state", insn->sec, insn->offset); + return -1; + } + return 0; + } + + switch (op->dest.type) { + + case OP_DEST_REG: + switch (op->src.type) { + + case OP_SRC_REG: + if (cfa->base == op->src.reg && cfa->base == CFI_SP && + op->dest.reg == CFI_BP && regs[CFI_BP].base == CFI_CFA && + regs[CFI_BP].offset == -cfa->offset) { + + /* mov %rsp, %rbp */ + cfa->base = op->dest.reg; + state->bp_scratch = false; + } else if (state->drap) { + + /* drap: mov %rsp, %rbp */ + regs[CFI_BP].base = CFI_BP; + regs[CFI_BP].offset = -state->stack_size; + state->bp_scratch = false; + } else if (!nofp) { + + WARN_FUNC("unknown stack-related register move", + insn->sec, insn->offset); + return -1; + } + + break; + + case OP_SRC_ADD: + if (op->dest.reg == CFI_SP && op->src.reg == CFI_SP) { + + /* add imm, %rsp */ + state->stack_size -= op->src.offset; + if (cfa->base == CFI_SP) + cfa->offset -= op->src.offset; + break; + } + + if (op->dest.reg == CFI_SP && op->src.reg == CFI_BP) { + + /* lea disp(%rbp), %rsp */ + state->stack_size = -(op->src.offset + regs[CFI_BP].offset); + break; + } + + if (op->dest.reg != CFI_BP && op->src.reg == CFI_SP && + cfa->base == CFI_SP) { + + /* drap: lea disp(%rsp), %drap */ + state->drap_reg = op->dest.reg; + break; + } + + if (state->drap && op->dest.reg == CFI_SP && + op->src.reg == state->drap_reg) { + + /* drap: lea disp(%drap), %rsp */ + cfa->base = CFI_SP; + cfa->offset = state->stack_size = -op->src.offset; + state->drap_reg = CFI_UNDEFINED; + state->drap = false; + break; + } + + if (op->dest.reg == state->cfa.base) { + WARN_FUNC("unsupported stack register modification", + insn->sec, insn->offset); + return -1; + } + + break; + + case OP_SRC_AND: + if (op->dest.reg != CFI_SP || + (state->drap_reg != CFI_UNDEFINED && cfa->base != CFI_SP) || + (state->drap_reg == CFI_UNDEFINED && cfa->base != CFI_BP)) { + WARN_FUNC("unsupported stack pointer realignment", + insn->sec, insn->offset); + return -1; + } + + if (state->drap_reg != CFI_UNDEFINED) { + /* drap: and imm, %rsp */ + cfa->base = state->drap_reg; + cfa->offset = state->stack_size = 0; + state->drap = true; + + } + + /* + * Older versions of GCC (4.8ish) realign the stack + * without DRAP, with a frame pointer. + */ + + break; + + case OP_SRC_POP: + if (!state->drap && op->dest.type == OP_DEST_REG && + op->dest.reg == cfa->base) { + + /* pop %rbp */ + cfa->base = CFI_SP; + } + + if (regs[op->dest.reg].offset == -state->stack_size) { + + if (state->drap && cfa->base == CFI_BP_INDIRECT && + op->dest.type == OP_DEST_REG && + op->dest.reg == state->drap_reg) { + + /* drap: pop %drap */ + cfa->base = state->drap_reg; + cfa->offset = 0; + } + + restore_reg(state, op->dest.reg); + } + + state->stack_size -= 8; + if (cfa->base == CFI_SP) + cfa->offset -= 8; + + break; + + case OP_SRC_REG_INDIRECT: + if (state->drap && op->src.reg == CFI_BP && + op->src.offset == regs[op->dest.reg].offset) { + + /* drap: mov disp(%rbp), %reg */ + if (op->dest.reg == state->drap_reg) { + cfa->base = state->drap_reg; + cfa->offset = 0; + } + + restore_reg(state, op->dest.reg); + + } else if (op->src.reg == cfa->base && + op->src.offset == regs[op->dest.reg].offset + cfa->offset) { + + /* mov disp(%rbp), %reg */ + /* mov disp(%rsp), %reg */ + restore_reg(state, op->dest.reg); + } + + break; + + default: + WARN_FUNC("unknown stack-related instruction", + insn->sec, insn->offset); + return -1; + } + + break; + + case OP_DEST_PUSH: + state->stack_size += 8; + if (cfa->base == CFI_SP) + cfa->offset += 8; + + if (op->src.type != OP_SRC_REG) + break; + + if (state->drap) { + if (op->src.reg == cfa->base && op->src.reg == state->drap_reg) { + + /* drap: push %drap */ + cfa->base = CFI_BP_INDIRECT; + cfa->offset = -state->stack_size; + + /* save drap so we know when to undefine it */ + save_reg(state, op->src.reg, CFI_CFA, -state->stack_size); + + } else if (op->src.reg == CFI_BP && cfa->base == state->drap_reg) { + + /* drap: push %rbp */ + state->stack_size = 0; + + } else if (regs[op->src.reg].base == CFI_UNDEFINED) { + + /* drap: push %reg */ + save_reg(state, op->src.reg, CFI_BP, -state->stack_size); + } + + } else { + + /* push %reg */ + save_reg(state, op->src.reg, CFI_CFA, -state->stack_size); + } + + /* detect when asm code uses rbp as a scratch register */ + if (!nofp && insn->func && op->src.reg == CFI_BP && + cfa->base != CFI_BP) + state->bp_scratch = true; + break; + + case OP_DEST_REG_INDIRECT: + + if (state->drap) { + if (op->src.reg == cfa->base && op->src.reg == state->drap_reg) { + + /* drap: mov %drap, disp(%rbp) */ + cfa->base = CFI_BP_INDIRECT; + cfa->offset = op->dest.offset; + + /* save drap so we know when to undefine it */ + save_reg(state, op->src.reg, CFI_CFA, op->dest.offset); + } + + else if (regs[op->src.reg].base == CFI_UNDEFINED) { + + /* drap: mov reg, disp(%rbp) */ + save_reg(state, op->src.reg, CFI_BP, op->dest.offset); + } + + } else if (op->dest.reg == cfa->base) { + + /* mov reg, disp(%rbp) */ + /* mov reg, disp(%rsp) */ + save_reg(state, op->src.reg, CFI_CFA, + op->dest.offset - state->cfa.offset); + } + + break; + + case OP_DEST_LEAVE: + if ((!state->drap && cfa->base != CFI_BP) || + (state->drap && cfa->base != state->drap_reg)) { + WARN_FUNC("leave instruction with modified stack frame", + insn->sec, insn->offset); + return -1; + } + + /* leave (mov %rbp, %rsp; pop %rbp) */ + + state->stack_size = -state->regs[CFI_BP].offset - 8; + restore_reg(state, CFI_BP); + + if (!state->drap) { + cfa->base = CFI_SP; + cfa->offset -= 8; + } + + break; + + case OP_DEST_MEM: + if (op->src.type != OP_SRC_POP) { + WARN_FUNC("unknown stack-related memory operation", + insn->sec, insn->offset); + return -1; + } + + /* pop mem */ + state->stack_size -= 8; + if (cfa->base == CFI_SP) + cfa->offset -= 8; + + break; + + default: + WARN_FUNC("unknown stack-related instruction", + insn->sec, insn->offset); + return -1; + } + + return 0; +} + +static bool insn_state_match(struct instruction *insn, struct insn_state *state) +{ + struct insn_state *state1 = &insn->state, *state2 = state; + int i; + + if (memcmp(&state1->cfa, &state2->cfa, sizeof(state1->cfa))) { + WARN_FUNC("stack state mismatch: cfa1=%d%+d cfa2=%d%+d", + insn->sec, insn->offset, + state1->cfa.base, state1->cfa.offset, + state2->cfa.base, state2->cfa.offset); + + } else if (memcmp(&state1->regs, &state2->regs, sizeof(state1->regs))) { + for (i = 0; i < CFI_NUM_REGS; i++) { + if (!memcmp(&state1->regs[i], &state2->regs[i], + sizeof(struct cfi_reg))) + continue; + + WARN_FUNC("stack state mismatch: reg1[%d]=%d%+d reg2[%d]=%d%+d", + insn->sec, insn->offset, + i, state1->regs[i].base, state1->regs[i].offset, + i, state2->regs[i].base, state2->regs[i].offset); + break; + } + + } else if (state1->drap != state2->drap || + (state1->drap && state1->drap_reg != state2->drap_reg)) { + WARN_FUNC("stack state mismatch: drap1=%d(%d) drap2=%d(%d)", + insn->sec, insn->offset, + state1->drap, state1->drap_reg, + state2->drap, state2->drap_reg); + + } else + return true; + + return false; +} + +/* + * Follow the branch starting at the given instruction, and recursively follow + * any other branches (jumps). Meanwhile, track the frame pointer state at + * each instruction and validate all the rules described in + * tools/objtool/Documentation/stack-validation.txt. + */ +static int validate_branch(struct objtool_file *file, struct instruction *first, + struct insn_state state) +{ + struct alternative *alt; + struct instruction *insn; + struct section *sec; + struct symbol *func = NULL; + int ret; + + insn = first; + sec = insn->sec; + + if (insn->alt_group && list_empty(&insn->alts)) { + WARN_FUNC("don't know how to handle branch to middle of alternative instruction group", + sec, insn->offset); + return -1; + } + + while (1) { + if (file->c_file && insn->func) { + if (func && func != insn->func) { + WARN("%s() falls through to next function %s()", + func->name, insn->func->name); + return 1; + } + } + + func = insn->func; + + if (insn->visited) { + if (!!insn_state_match(insn, &state)) + return 1; + + return 0; + } + + insn->state = state; + + insn->visited = true; + + list_for_each_entry(alt, &insn->alts, list) { + ret = validate_branch(file, alt->insn, state); + if (ret) + return 1; + } + + switch (insn->type) { + + case INSN_RETURN: + if (func && has_modified_stack_frame(&state)) { + WARN_FUNC("return with modified stack frame", + sec, insn->offset); + return 1; + } + + if (state.bp_scratch) { + WARN("%s uses BP as a scratch register", + insn->func->name); + return 1; + } + + return 0; + + case INSN_CALL: + if (is_fentry_call(insn)) + break; + + ret = dead_end_function(file, insn->call_dest); + if (ret == 1) + return 0; + if (ret == -1) + return 1; + + /* fallthrough */ + case INSN_CALL_DYNAMIC: + if (!nofp && func && !has_valid_stack_frame(&state)) { + WARN_FUNC("call without frame pointer save/setup", + sec, insn->offset); + return 1; + } + break; + + case INSN_JUMP_CONDITIONAL: + case INSN_JUMP_UNCONDITIONAL: + if (insn->jump_dest) { + ret = validate_branch(file, insn->jump_dest, + state); + if (ret) + return 1; + } else if (func && has_modified_stack_frame(&state)) { + WARN_FUNC("sibling call from callable instruction with modified stack frame", + sec, insn->offset); + return 1; + } /* else it's a sibling call */ + + if (insn->type == INSN_JUMP_UNCONDITIONAL) + return 0; + + break; + + case INSN_JUMP_DYNAMIC: + if (func && list_empty(&insn->alts) && + has_modified_stack_frame(&state)) { + WARN_FUNC("sibling call from callable instruction with modified stack frame", + sec, insn->offset); + return 1; + } + + return 0; + + case INSN_STACK: + if (update_insn_state(insn, &state)) + return -1; + + break; + + default: + break; + } + + if (insn->dead_end) + return 0; + + insn = next_insn_same_sec(file, insn); + if (!insn) { + WARN("%s: unexpected end of section", sec->name); + return 1; + } + } + + return 0; +} + +static bool is_kasan_insn(struct instruction *insn) +{ + return (insn->type == INSN_CALL && + !strcmp(insn->call_dest->name, "__asan_handle_no_return")); +} + +static bool is_ubsan_insn(struct instruction *insn) +{ + return (insn->type == INSN_CALL && + !strcmp(insn->call_dest->name, + "__ubsan_handle_builtin_unreachable")); +} + +static bool ignore_unreachable_insn(struct instruction *insn) +{ + int i; + + if (insn->ignore || insn->type == INSN_NOP) + return true; + + /* + * Ignore any unused exceptions. This can happen when a whitelisted + * function has an exception table entry. + */ + if (!strcmp(insn->sec->name, ".fixup")) + return true; + + /* + * Check if this (or a subsequent) instruction is related to + * CONFIG_UBSAN or CONFIG_KASAN. + * + * End the search at 5 instructions to avoid going into the weeds. + */ + if (!insn->func) + return false; + for (i = 0; i < 5; i++) { + + if (is_kasan_insn(insn) || is_ubsan_insn(insn)) + return true; + + if (insn->type == INSN_JUMP_UNCONDITIONAL && insn->jump_dest) { + insn = insn->jump_dest; + continue; + } + + if (insn->offset + insn->len >= insn->func->offset + insn->func->len) + break; + insn = list_next_entry(insn, list); + } + + return false; +} + +static int validate_functions(struct objtool_file *file) +{ + struct section *sec; + struct symbol *func; + struct instruction *insn; + struct insn_state state; + int ret, warnings = 0; + + clear_insn_state(&state); + + state.cfa = initial_func_cfi.cfa; + memcpy(&state.regs, &initial_func_cfi.regs, + CFI_NUM_REGS * sizeof(struct cfi_reg)); + state.stack_size = initial_func_cfi.cfa.offset; + + for_each_sec(file, sec) { + list_for_each_entry(func, &sec->symbol_list, list) { + if (func->type != STT_FUNC) + continue; + + insn = find_insn(file, sec, func->offset); + if (!insn || insn->ignore) + continue; + + ret = validate_branch(file, insn, state); + warnings += ret; + } + } + + return warnings; +} + +static int validate_reachable_instructions(struct objtool_file *file) +{ + struct instruction *insn; + + if (file->ignore_unreachables) + return 0; + + for_each_insn(file, insn) { + if (insn->visited || ignore_unreachable_insn(insn)) + continue; + + /* + * gcov produces a lot of unreachable instructions. If we get + * an unreachable warning and the file has gcov enabled, just + * ignore it, and all other such warnings for the file. Do + * this here because this is an expensive function. + */ + if (gcov_enabled(file)) + return 0; + + WARN_FUNC("unreachable instruction", insn->sec, insn->offset); + return 1; + } + + return 0; +} + +static void cleanup(struct objtool_file *file) +{ + struct instruction *insn, *tmpinsn; + struct alternative *alt, *tmpalt; + + list_for_each_entry_safe(insn, tmpinsn, &file->insn_list, list) { + list_for_each_entry_safe(alt, tmpalt, &insn->alts, list) { + list_del(&alt->list); + free(alt); + } + list_del(&insn->list); + hash_del(&insn->hash); + free(insn); + } + elf_close(file->elf); +} + +int check(const char *_objname, bool _nofp) +{ + struct objtool_file file; + int ret, warnings = 0; + + objname = _objname; + nofp = _nofp; + + file.elf = elf_open(objname); + if (!file.elf) + return 1; + + INIT_LIST_HEAD(&file.insn_list); + hash_init(file.insn_hash); + file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard"); + file.rodata = find_section_by_name(file.elf, ".rodata"); + file.ignore_unreachables = false; + file.c_file = find_section_by_name(file.elf, ".comment"); + + arch_initial_func_cfi_state(&initial_func_cfi); + + ret = decode_sections(&file); + if (ret < 0) + goto out; + warnings += ret; + + if (list_empty(&file.insn_list)) + goto out; + + ret = validate_functions(&file); + if (ret < 0) + goto out; + warnings += ret; + + if (!warnings) { + ret = validate_reachable_instructions(&file); + if (ret < 0) + goto out; + warnings += ret; + } + +out: + cleanup(&file); + + /* ignore warnings for now until we get all the code cleaned up */ + if (ret || warnings) + return 0; + return 0; +} diff --git a/tools/objtool/check.h b/tools/objtool/check.h new file mode 100644 index 000000000000..da85f5b00ec6 --- /dev/null +++ b/tools/objtool/check.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2017 Josh Poimboeuf <jpoimboe@redhat.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef _CHECK_H +#define _CHECK_H + +#include <stdbool.h> +#include "elf.h" +#include "cfi.h" +#include "arch.h" +#include <linux/hashtable.h> + +struct insn_state { + struct cfi_reg cfa; + struct cfi_reg regs[CFI_NUM_REGS]; + int stack_size; + bool bp_scratch; + bool drap; + int drap_reg; +}; + +struct instruction { + struct list_head list; + struct hlist_node hash; + struct section *sec; + unsigned long offset; + unsigned int len; + unsigned char type; + unsigned long immediate; + bool alt_group, visited, dead_end, ignore; + struct symbol *call_dest; + struct instruction *jump_dest; + struct list_head alts; + struct symbol *func; + struct stack_op stack_op; + struct insn_state state; +}; + +struct objtool_file { + struct elf *elf; + struct list_head insn_list; + DECLARE_HASHTABLE(insn_hash, 16); + struct section *rodata, *whitelist; + bool ignore_unreachables, c_file; +}; + +int check(const char *objname, bool nofp); + +#define for_each_insn(file, insn) \ + list_for_each_entry(insn, &file->insn_list, list) + +#endif /* _CHECK_H */ diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index d897702ce742..1a7e8aa2af58 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c @@ -37,6 +37,9 @@ #define ELF_C_READ_MMAP ELF_C_READ #endif +#define WARN_ELF(format, ...) \ + WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1)) + struct section *find_section_by_name(struct elf *elf, const char *name) { struct section *sec; @@ -139,12 +142,12 @@ static int read_sections(struct elf *elf) int i; if (elf_getshdrnum(elf->elf, §ions_nr)) { - perror("elf_getshdrnum"); + WARN_ELF("elf_getshdrnum"); return -1; } if (elf_getshdrstrndx(elf->elf, &shstrndx)) { - perror("elf_getshdrstrndx"); + WARN_ELF("elf_getshdrstrndx"); return -1; } @@ -165,37 +168,36 @@ static int read_sections(struct elf *elf) s = elf_getscn(elf->elf, i); if (!s) { - perror("elf_getscn"); + WARN_ELF("elf_getscn"); return -1; } sec->idx = elf_ndxscn(s); if (!gelf_getshdr(s, &sec->sh)) { - perror("gelf_getshdr"); + WARN_ELF("gelf_getshdr"); return -1; } sec->name = elf_strptr(elf->elf, shstrndx, sec->sh.sh_name); if (!sec->name) { - perror("elf_strptr"); + WARN_ELF("elf_strptr"); return -1; } - sec->elf_data = elf_getdata(s, NULL); - if (!sec->elf_data) { - perror("elf_getdata"); + sec->data = elf_getdata(s, NULL); + if (!sec->data) { + WARN_ELF("elf_getdata"); return -1; } - if (sec->elf_data->d_off != 0 || - sec->elf_data->d_size != sec->sh.sh_size) { + if (sec->data->d_off != 0 || + sec->data->d_size != sec->sh.sh_size) { WARN("unexpected data attributes for %s", sec->name); return -1; } - sec->data = (unsigned long)sec->elf_data->d_buf; - sec->len = sec->elf_data->d_size; + sec->len = sec->data->d_size; } /* sanity check, one more call to elf_nextscn() should return NULL */ @@ -232,15 +234,15 @@ static int read_symbols(struct elf *elf) sym->idx = i; - if (!gelf_getsym(symtab->elf_data, i, &sym->sym)) { - perror("gelf_getsym"); + if (!gelf_getsym(symtab->data, i, &sym->sym)) { + WARN_ELF("gelf_getsym"); goto err; } sym->name = elf_strptr(elf->elf, symtab->sh.sh_link, sym->sym.st_name); if (!sym->name) { - perror("elf_strptr"); + WARN_ELF("elf_strptr"); goto err; } @@ -322,8 +324,8 @@ static int read_relas(struct elf *elf) } memset(rela, 0, sizeof(*rela)); - if (!gelf_getrela(sec->elf_data, i, &rela->rela)) { - perror("gelf_getrela"); + if (!gelf_getrela(sec->data, i, &rela->rela)) { + WARN_ELF("gelf_getrela"); return -1; } @@ -362,12 +364,6 @@ struct elf *elf_open(const char *name) INIT_LIST_HEAD(&elf->sections); - elf->name = strdup(name); - if (!elf->name) { - perror("strdup"); - goto err; - } - elf->fd = open(name, O_RDONLY); if (elf->fd == -1) { perror("open"); @@ -376,12 +372,12 @@ struct elf *elf_open(const char *name) elf->elf = elf_begin(elf->fd, ELF_C_READ_MMAP, NULL); if (!elf->elf) { - perror("elf_begin"); + WARN_ELF("elf_begin"); goto err; } if (!gelf_getehdr(elf->elf, &elf->ehdr)) { - perror("gelf_getehdr"); + WARN_ELF("gelf_getehdr"); goto err; } @@ -407,6 +403,12 @@ void elf_close(struct elf *elf) struct symbol *sym, *tmpsym; struct rela *rela, *tmprela; + if (elf->elf) + elf_end(elf->elf); + + if (elf->fd > 0) + close(elf->fd); + list_for_each_entry_safe(sec, tmpsec, &elf->sections, list) { list_for_each_entry_safe(sym, tmpsym, &sec->symbol_list, list) { list_del(&sym->list); @@ -421,11 +423,6 @@ void elf_close(struct elf *elf) list_del(&sec->list); free(sec); } - if (elf->name) - free(elf->name); - if (elf->fd > 0) - close(elf->fd); - if (elf->elf) - elf_end(elf->elf); + free(elf); } diff --git a/tools/objtool/elf.h b/tools/objtool/elf.h index 731973e1a3f5..343968b778cb 100644 --- a/tools/objtool/elf.h +++ b/tools/objtool/elf.h @@ -37,10 +37,9 @@ struct section { DECLARE_HASHTABLE(rela_hash, 16); struct section *base, *rela; struct symbol *sym; - Elf_Data *elf_data; + Elf_Data *data; char *name; int idx; - unsigned long data; unsigned int len; }; @@ -86,6 +85,7 @@ struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset, struct symbol *find_containing_func(struct section *sec, unsigned long offset); void elf_close(struct elf *elf); - +#define for_each_sec(file, sec) \ + list_for_each_entry(sec, &file->elf->sections, list) #endif /* _OBJTOOL_ELF_H */ diff --git a/tools/objtool/special.c b/tools/objtool/special.c index bff8abb3a4aa..84f001d52322 100644 --- a/tools/objtool/special.c +++ b/tools/objtool/special.c @@ -91,16 +91,16 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry, alt->jump_or_nop = entry->jump_or_nop; if (alt->group) { - alt->orig_len = *(unsigned char *)(sec->data + offset + + alt->orig_len = *(unsigned char *)(sec->data->d_buf + offset + entry->orig_len); - alt->new_len = *(unsigned char *)(sec->data + offset + + alt->new_len = *(unsigned char *)(sec->data->d_buf + offset + entry->new_len); } if (entry->feature) { unsigned short feature; - feature = *(unsigned short *)(sec->data + offset + + feature = *(unsigned short *)(sec->data->d_buf + offset + entry->feature); /* diff --git a/tools/objtool/warn.h b/tools/objtool/warn.h index ac7e07523e84..afd9f7a05f6d 100644 --- a/tools/objtool/warn.h +++ b/tools/objtool/warn.h @@ -18,6 +18,13 @@ #ifndef _WARN_H #define _WARN_H +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "elf.h" + extern const char *objname; static inline char *offstr(struct section *sec, unsigned long offset) @@ -57,4 +64,7 @@ static inline char *offstr(struct section *sec, unsigned long offset) free(_str); \ }) +#define WARN_ELF(format, ...) \ + WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1)) + #endif /* _WARN_H */ diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index d7f31cb0a4cb..5de2b86b9880 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -1209,10 +1209,12 @@ int machine__create_kernel_maps(struct machine *machine) */ map_groups__fixup_end(&machine->kmaps); - if (machine__get_running_kernel_start(machine, &name, &addr)) { - } else if (maps__set_kallsyms_ref_reloc_sym(machine->vmlinux_maps, name, addr)) { - machine__destroy_kernel_maps(machine); - return -1; + if (!machine__get_running_kernel_start(machine, &name, &addr)) { + if (name && + maps__set_kallsyms_ref_reloc_sym(machine->vmlinux_maps, name, addr)) { + machine__destroy_kernel_maps(machine); + return -1; + } } return 0; diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 84e7e698411e..a2670e9d652d 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -619,7 +619,7 @@ static int post_process_probe_trace_point(struct probe_trace_point *tp, struct map *map, unsigned long offs) { struct symbol *sym; - u64 addr = tp->address + tp->offset - offs; + u64 addr = tp->address - offs; sym = map__find_symbol(map, addr); if (!sym) diff --git a/tools/testing/nvdimm/test/iomap.c b/tools/testing/nvdimm/test/iomap.c index 64cae1a5deff..e1f75a1914a1 100644 --- a/tools/testing/nvdimm/test/iomap.c +++ b/tools/testing/nvdimm/test/iomap.c @@ -370,7 +370,7 @@ acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path, } EXPORT_SYMBOL(__wrap_acpi_evaluate_object); -union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid, +union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid, u64 rev, u64 func, union acpi_object *argv4) { union acpi_object *obj = ERR_PTR(-ENXIO); @@ -379,11 +379,11 @@ union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const u8 *uuid, rcu_read_lock(); ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list); if (ops) - obj = ops->evaluate_dsm(handle, uuid, rev, func, argv4); + obj = ops->evaluate_dsm(handle, guid, rev, func, argv4); rcu_read_unlock(); if (IS_ERR(obj)) - return acpi_evaluate_dsm(handle, uuid, rev, func, argv4); + return acpi_evaluate_dsm(handle, guid, rev, func, argv4); return obj; } EXPORT_SYMBOL(__wrap_acpi_evaluate_dsm); diff --git a/tools/testing/nvdimm/test/nfit.c b/tools/testing/nvdimm/test/nfit.c index c2187178fb13..28859da78edf 100644 --- a/tools/testing/nvdimm/test/nfit.c +++ b/tools/testing/nvdimm/test/nfit.c @@ -1559,7 +1559,7 @@ static unsigned long nfit_ctl_handle; union acpi_object *result; static union acpi_object *nfit_test_evaluate_dsm(acpi_handle handle, - const u8 *uuid, u64 rev, u64 func, union acpi_object *argv4) + const guid_t *guid, u64 rev, u64 func, union acpi_object *argv4) { if (handle != &nfit_ctl_handle) return ERR_PTR(-ENXIO); diff --git a/tools/testing/nvdimm/test/nfit_test.h b/tools/testing/nvdimm/test/nfit_test.h index f54c0032c6ff..d3d63dd5ed38 100644 --- a/tools/testing/nvdimm/test/nfit_test.h +++ b/tools/testing/nvdimm/test/nfit_test.h @@ -13,6 +13,7 @@ #ifndef __NFIT_TEST_H__ #define __NFIT_TEST_H__ #include <linux/list.h> +#include <linux/uuid.h> #include <linux/ioport.h> #include <linux/spinlock_types.h> @@ -36,7 +37,8 @@ typedef void *acpi_handle; typedef struct nfit_test_resource *(*nfit_test_lookup_fn)(resource_size_t); typedef union acpi_object *(*nfit_test_evaluate_dsm_fn)(acpi_handle handle, - const u8 *uuid, u64 rev, u64 func, union acpi_object *argv4); + const guid_t *guid, u64 rev, u64 func, + union acpi_object *argv4); void __iomem *__wrap_ioremap_nocache(resource_size_t offset, unsigned long size); void __wrap_iounmap(volatile void __iomem *addr); diff --git a/tools/testing/selftests/bpf/test_verifier.c b/tools/testing/selftests/bpf/test_verifier.c index cabb19b1e371..0ff8c55c0464 100644 --- a/tools/testing/selftests/bpf/test_verifier.c +++ b/tools/testing/selftests/bpf/test_verifier.c @@ -3749,6 +3749,72 @@ static struct bpf_test tests[] = { .errstr = "invalid bpf_context access", }, { + "leak pointer into ctx 1", + .insns = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0, + offsetof(struct __sk_buff, cb[0])), + BPF_LD_MAP_FD(BPF_REG_2, 0), + BPF_STX_XADD(BPF_DW, BPF_REG_1, BPF_REG_2, + offsetof(struct __sk_buff, cb[0])), + BPF_EXIT_INSN(), + }, + .fixup_map1 = { 2 }, + .errstr_unpriv = "R2 leaks addr into mem", + .result_unpriv = REJECT, + .result = ACCEPT, + }, + { + "leak pointer into ctx 2", + .insns = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_0, + offsetof(struct __sk_buff, cb[0])), + BPF_STX_XADD(BPF_DW, BPF_REG_1, BPF_REG_10, + offsetof(struct __sk_buff, cb[0])), + BPF_EXIT_INSN(), + }, + .errstr_unpriv = "R10 leaks addr into mem", + .result_unpriv = REJECT, + .result = ACCEPT, + }, + { + "leak pointer into ctx 3", + .insns = { + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_LD_MAP_FD(BPF_REG_2, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_1, BPF_REG_2, + offsetof(struct __sk_buff, cb[0])), + BPF_EXIT_INSN(), + }, + .fixup_map1 = { 1 }, + .errstr_unpriv = "R2 leaks addr into ctx", + .result_unpriv = REJECT, + .result = ACCEPT, + }, + { + "leak pointer into map val", + .insns = { + BPF_MOV64_REG(BPF_REG_6, BPF_REG_1), + BPF_ST_MEM(BPF_DW, BPF_REG_10, -8, 0), + BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), + BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -8), + BPF_LD_MAP_FD(BPF_REG_1, 0), + BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, + BPF_FUNC_map_lookup_elem), + BPF_JMP_IMM(BPF_JEQ, BPF_REG_0, 0, 3), + BPF_MOV64_IMM(BPF_REG_3, 0), + BPF_STX_MEM(BPF_DW, BPF_REG_0, BPF_REG_3, 0), + BPF_STX_XADD(BPF_DW, BPF_REG_0, BPF_REG_6, 0), + BPF_MOV64_IMM(BPF_REG_0, 0), + BPF_EXIT_INSN(), + }, + .fixup_map1 = { 4 }, + .errstr_unpriv = "R6 leaks addr into mem", + .result_unpriv = REJECT, + .result = ACCEPT, + }, + { "helper access to map: full range", .insns = { BPF_MOV64_REG(BPF_REG_2, BPF_REG_10), diff --git a/tools/testing/selftests/ntb/ntb_test.sh b/tools/testing/selftests/ntb/ntb_test.sh index a676d3eefefb..13f5198ba0ee 100755 --- a/tools/testing/selftests/ntb/ntb_test.sh +++ b/tools/testing/selftests/ntb/ntb_test.sh @@ -305,7 +305,7 @@ function perf_test() echo "Running remote perf test $WITH DMA" write_file "" $REMOTE_PERF/run echo -n " " - read_file $LOCAL_PERF/run + read_file $REMOTE_PERF/run echo " Passed" _modprobe -r ntb_perf diff --git a/tools/testing/selftests/rcutorture/bin/configcheck.sh b/tools/testing/selftests/rcutorture/bin/configcheck.sh index eee31e261bf7..70fca318a82b 100755 --- a/tools/testing/selftests/rcutorture/bin/configcheck.sh +++ b/tools/testing/selftests/rcutorture/bin/configcheck.sh @@ -27,7 +27,7 @@ cat $1 > $T/.config cat $2 | sed -e 's/\(.*\)=n/# \1 is not set/' -e 's/^#CHECK#//' | awk ' -BEGIN { +{ print "if grep -q \"" $0 "\" < '"$T/.config"'"; print "then"; print "\t:"; diff --git a/tools/testing/selftests/rcutorture/bin/kvm-build.sh b/tools/testing/selftests/rcutorture/bin/kvm-build.sh index 00cb0db2643d..c29f2ec0bf9f 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm-build.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm-build.sh @@ -45,7 +45,7 @@ T=/tmp/test-linux.sh.$$ trap 'rm -rf $T' 0 mkdir $T -grep -v 'CONFIG_[A-Z]*_TORTURE_TEST' < ${config_template} > $T/config +grep -v 'CONFIG_[A-Z]*_TORTURE_TEST=' < ${config_template} > $T/config cat << ___EOF___ >> $T/config CONFIG_INITRAMFS_SOURCE="$TORTURE_INITRD" CONFIG_VIRTIO_PCI=y diff --git a/tools/testing/selftests/rcutorture/bin/kvm.sh b/tools/testing/selftests/rcutorture/bin/kvm.sh index 3b3c1b693ee1..50091de3a911 100755 --- a/tools/testing/selftests/rcutorture/bin/kvm.sh +++ b/tools/testing/selftests/rcutorture/bin/kvm.sh @@ -296,10 +296,7 @@ if test -d .git then git status >> $resdir/$ds/testid.txt git rev-parse HEAD >> $resdir/$ds/testid.txt - if ! git diff HEAD > $T/git-diff 2>&1 - then - cp $T/git-diff $resdir/$ds - fi + git diff HEAD >> $resdir/$ds/testid.txt fi ___EOF___ awk < $T/cfgcpu.pack \ diff --git a/tools/testing/selftests/rcutorture/configs/rcu/CFLIST b/tools/testing/selftests/rcutorture/configs/rcu/CFLIST index a3a1a05a2b5c..6a0b9f69faad 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/CFLIST +++ b/tools/testing/selftests/rcutorture/configs/rcu/CFLIST @@ -9,6 +9,8 @@ TREE08 TREE09 SRCU-N SRCU-P +SRCU-t +SRCU-u TINY01 TINY02 TASKS01 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-C.boot b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-C.boot new file mode 100644 index 000000000000..84a7d51b7481 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-C.boot @@ -0,0 +1 @@ +rcutorture.torture_type=srcud diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N index 1a087c3c8bb8..2da8b49589a0 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N @@ -5,4 +5,4 @@ CONFIG_HOTPLUG_CPU=y CONFIG_PREEMPT_NONE=y CONFIG_PREEMPT_VOLUNTARY=n CONFIG_PREEMPT=n -CONFIG_RCU_EXPERT=y +#CHECK#CONFIG_RCU_EXPERT=n diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P index 4837430a71c0..ab7ccd38232b 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P @@ -2,7 +2,11 @@ CONFIG_RCU_TRACE=n CONFIG_SMP=y CONFIG_NR_CPUS=8 CONFIG_HOTPLUG_CPU=y +CONFIG_RCU_EXPERT=y +CONFIG_RCU_FANOUT=2 +CONFIG_RCU_FANOUT_LEAF=2 CONFIG_PREEMPT_NONE=n CONFIG_PREEMPT_VOLUNTARY=n CONFIG_PREEMPT=y -#CHECK#CONFIG_RCU_EXPERT=n +CONFIG_DEBUG_LOCK_ALLOC=y +CONFIG_PROVE_LOCKING=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-t b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-t new file mode 100644 index 000000000000..6c78022c8cd8 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-t @@ -0,0 +1,10 @@ +CONFIG_SMP=n +CONFIG_PREEMPT_NONE=y +CONFIG_PREEMPT_VOLUNTARY=n +CONFIG_PREEMPT=n +#CHECK#CONFIG_TINY_SRCU=y +CONFIG_RCU_TRACE=n +CONFIG_DEBUG_LOCK_ALLOC=n +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n +CONFIG_DEBUG_ATOMIC_SLEEP=y +#CHECK#CONFIG_PREEMPT_COUNT=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-t.boot b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-t.boot new file mode 100644 index 000000000000..238bfe3bd0cc --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-t.boot @@ -0,0 +1 @@ +rcutorture.torture_type=srcu diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-u b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-u new file mode 100644 index 000000000000..6bc24e99862f --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-u @@ -0,0 +1,9 @@ +CONFIG_SMP=n +CONFIG_PREEMPT_NONE=y +CONFIG_PREEMPT_VOLUNTARY=n +CONFIG_PREEMPT=n +#CHECK#CONFIG_TINY_SRCU=y +CONFIG_RCU_TRACE=n +CONFIG_DEBUG_LOCK_ALLOC=n +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n +CONFIG_PREEMPT_COUNT=n diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-u.boot b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-u.boot new file mode 100644 index 000000000000..84a7d51b7481 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-u.boot @@ -0,0 +1 @@ +rcutorture.torture_type=srcud diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TINY02 b/tools/testing/selftests/rcutorture/configs/rcu/TINY02 index a59f7686e219..d8674264318d 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TINY02 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TINY02 @@ -6,10 +6,9 @@ CONFIG_PREEMPT=n CONFIG_HZ_PERIODIC=y CONFIG_NO_HZ_IDLE=n CONFIG_NO_HZ_FULL=n -CONFIG_RCU_TRACE=y CONFIG_PROVE_LOCKING=y -CONFIG_PROVE_RCU_REPEATEDLY=y #CHECK#CONFIG_PROVE_RCU=y CONFIG_DEBUG_LOCK_ALLOC=y +CONFIG_DEBUG_OBJECTS=y CONFIG_DEBUG_OBJECTS_RCU_HEAD=y -CONFIG_PREEMPT_COUNT=y +CONFIG_DEBUG_ATOMIC_SLEEP=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE01 b/tools/testing/selftests/rcutorture/configs/rcu/TREE01 index 359cb258f639..b5b53973c01e 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE01 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE01 @@ -10,12 +10,9 @@ CONFIG_RCU_FAST_NO_HZ=y CONFIG_RCU_TRACE=y CONFIG_HOTPLUG_CPU=y CONFIG_MAXSMP=y +CONFIG_CPUMASK_OFFSTACK=y CONFIG_RCU_NOCB_CPU=y -CONFIG_RCU_NOCB_CPU_ZERO=y CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_RCU_BOOST=n CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot index adc3abc82fb8..1d14e1383016 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot @@ -1 +1,5 @@ rcutorture.torture_type=rcu_bh maxcpus=8 +rcutree.gp_preinit_delay=3 +rcutree.gp_init_delay=3 +rcutree.gp_cleanup_delay=3 +rcu_nocbs=0 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE02 b/tools/testing/selftests/rcutorture/configs/rcu/TREE02 index c1ab5926568b..35e639e39366 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE02 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE02 @@ -18,9 +18,6 @@ CONFIG_RCU_NOCB_CPU=n CONFIG_DEBUG_LOCK_ALLOC=y CONFIG_PROVE_LOCKING=n CONFIG_RCU_BOOST=n -CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y +CONFIG_DEBUG_OBJECTS=y CONFIG_DEBUG_OBJECTS_RCU_HEAD=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE03 b/tools/testing/selftests/rcutorture/configs/rcu/TREE03 index 3b93ee544e70..2dc31b16e506 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE03 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE03 @@ -14,9 +14,5 @@ CONFIG_RCU_FANOUT_LEAF=2 CONFIG_RCU_NOCB_CPU=n CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_RCU_BOOST=y -CONFIG_RCU_KTHREAD_PRIO=2 CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot index 120c0c88d100..5d2cc0bd50a0 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE03.boot @@ -1 +1,5 @@ rcutorture.onoff_interval=1 rcutorture.onoff_holdoff=30 +rcutree.gp_preinit_delay=3 +rcutree.gp_init_delay=3 +rcutree.gp_cleanup_delay=3 +rcutree.kthread_prio=2 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 b/tools/testing/selftests/rcutorture/configs/rcu/TREE04 index 5af758e783c7..27d22695d64c 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE04 @@ -15,11 +15,7 @@ CONFIG_SUSPEND=n CONFIG_HIBERNATION=n CONFIG_RCU_FANOUT=4 CONFIG_RCU_FANOUT_LEAF=3 -CONFIG_RCU_NOCB_CPU=n CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y CONFIG_RCU_EQS_DEBUG=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE05 b/tools/testing/selftests/rcutorture/configs/rcu/TREE05 index d4cdc0d74e16..2dde0d9964e3 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE05 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE05 @@ -13,12 +13,8 @@ CONFIG_HOTPLUG_CPU=y CONFIG_RCU_FANOUT=6 CONFIG_RCU_FANOUT_LEAF=6 CONFIG_RCU_NOCB_CPU=y -CONFIG_RCU_NOCB_CPU_NONE=y CONFIG_DEBUG_LOCK_ALLOC=y CONFIG_PROVE_LOCKING=y #CHECK#CONFIG_PROVE_RCU=y CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot index 15b3e1a86f74..c7fd050dfcd9 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot @@ -1,2 +1,5 @@ rcutorture.torture_type=sched rcupdate.rcu_self_test_sched=1 +rcutree.gp_preinit_delay=3 +rcutree.gp_init_delay=3 +rcutree.gp_cleanup_delay=3 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE06 b/tools/testing/selftests/rcutorture/configs/rcu/TREE06 index 4cb02bd28f08..05a4eec3f27b 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE06 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE06 @@ -18,8 +18,6 @@ CONFIG_RCU_NOCB_CPU=n CONFIG_DEBUG_LOCK_ALLOC=y CONFIG_PROVE_LOCKING=y #CHECK#CONFIG_PROVE_RCU=y +CONFIG_DEBUG_OBJECTS=y CONFIG_DEBUG_OBJECTS_RCU_HEAD=y CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE06.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE06.boot index dd90f28ed700..ad18b52a2cad 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE06.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE06.boot @@ -2,3 +2,6 @@ rcupdate.rcu_self_test=1 rcupdate.rcu_self_test_bh=1 rcupdate.rcu_self_test_sched=1 rcutree.rcu_fanout_exact=1 +rcutree.gp_preinit_delay=3 +rcutree.gp_init_delay=3 +rcutree.gp_cleanup_delay=3 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE07 b/tools/testing/selftests/rcutorture/configs/rcu/TREE07 index b12a3ea1867e..0f4759f4232e 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE07 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE07 @@ -1,6 +1,5 @@ CONFIG_SMP=y CONFIG_NR_CPUS=16 -CONFIG_CPUMASK_OFFSTACK=y CONFIG_PREEMPT_NONE=y CONFIG_PREEMPT_VOLUNTARY=n CONFIG_PREEMPT=n @@ -9,16 +8,11 @@ CONFIG_HZ_PERIODIC=n CONFIG_NO_HZ_IDLE=n CONFIG_NO_HZ_FULL=y CONFIG_NO_HZ_FULL_ALL=n -CONFIG_NO_HZ_FULL_SYSIDLE=y CONFIG_RCU_FAST_NO_HZ=n CONFIG_RCU_TRACE=y CONFIG_HOTPLUG_CPU=y CONFIG_RCU_FANOUT=2 CONFIG_RCU_FANOUT_LEAF=2 -CONFIG_RCU_NOCB_CPU=n CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_DEBUG_OBJECTS_RCU_HEAD=n CONFIG_RCU_EXPERT=y -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP=y -CONFIG_RCU_TORTURE_TEST_SLOW_INIT=y -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE08 b/tools/testing/selftests/rcutorture/configs/rcu/TREE08 index 099cc63c6a3b..fb1c763c10c5 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE08 +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE08 @@ -15,7 +15,6 @@ CONFIG_HIBERNATION=n CONFIG_RCU_FANOUT=3 CONFIG_RCU_FANOUT_LEAF=2 CONFIG_RCU_NOCB_CPU=y -CONFIG_RCU_NOCB_CPU_ALL=y CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_PROVE_LOCKING=n CONFIG_RCU_BOOST=n diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE08-T b/tools/testing/selftests/rcutorture/configs/rcu/TREE08-T deleted file mode 100644 index 2ad13f0d29cc..000000000000 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE08-T +++ /dev/null @@ -1,21 +0,0 @@ -CONFIG_SMP=y -CONFIG_NR_CPUS=16 -CONFIG_PREEMPT_NONE=n -CONFIG_PREEMPT_VOLUNTARY=n -CONFIG_PREEMPT=y -#CHECK#CONFIG_PREEMPT_RCU=y -CONFIG_HZ_PERIODIC=n -CONFIG_NO_HZ_IDLE=y -CONFIG_NO_HZ_FULL=n -CONFIG_RCU_FAST_NO_HZ=n -CONFIG_RCU_TRACE=y -CONFIG_HOTPLUG_CPU=n -CONFIG_SUSPEND=n -CONFIG_HIBERNATION=n -CONFIG_RCU_FANOUT=3 -CONFIG_RCU_FANOUT_LEAF=2 -CONFIG_RCU_NOCB_CPU=y -CONFIG_RCU_NOCB_CPU_ALL=y -CONFIG_DEBUG_LOCK_ALLOC=n -CONFIG_RCU_BOOST=n -CONFIG_DEBUG_OBJECTS_RCU_HEAD=n diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot index fb066dc82769..1bd8efc4141e 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot @@ -2,3 +2,4 @@ rcutorture.torture_type=sched rcupdate.rcu_self_test=1 rcupdate.rcu_self_test_sched=1 rcutree.rcu_fanout_exact=1 +rcu_nocbs=0-7 diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE02-T b/tools/testing/selftests/rcutorture/configs/rcuperf/TINY index 917d2517b5b5..fb05ef5279b4 100644 --- a/tools/testing/selftests/rcutorture/configs/rcu/TREE02-T +++ b/tools/testing/selftests/rcutorture/configs/rcuperf/TINY @@ -1,21 +1,16 @@ -CONFIG_SMP=y -CONFIG_NR_CPUS=8 -CONFIG_PREEMPT_NONE=n +CONFIG_SMP=n +CONFIG_PREEMPT_NONE=y CONFIG_PREEMPT_VOLUNTARY=n -CONFIG_PREEMPT=y -#CHECK#CONFIG_PREEMPT_RCU=y +CONFIG_PREEMPT=n +#CHECK#CONFIG_TINY_RCU=y CONFIG_HZ_PERIODIC=n CONFIG_NO_HZ_IDLE=y CONFIG_NO_HZ_FULL=n CONFIG_RCU_FAST_NO_HZ=n -CONFIG_RCU_TRACE=y -CONFIG_HOTPLUG_CPU=n -CONFIG_SUSPEND=n -CONFIG_HIBERNATION=n -CONFIG_RCU_FANOUT=3 -CONFIG_RCU_FANOUT_LEAF=3 CONFIG_RCU_NOCB_CPU=n -CONFIG_DEBUG_LOCK_ALLOC=y +CONFIG_DEBUG_LOCK_ALLOC=n CONFIG_PROVE_LOCKING=n CONFIG_RCU_BOOST=n CONFIG_DEBUG_OBJECTS_RCU_HEAD=n +CONFIG_RCU_EXPERT=y +CONFIG_RCU_TRACE=y diff --git a/tools/testing/selftests/rcutorture/configs/rcuperf/TREE b/tools/testing/selftests/rcutorture/configs/rcuperf/TREE index a312f671a29a..721cfda76ab2 100644 --- a/tools/testing/selftests/rcutorture/configs/rcuperf/TREE +++ b/tools/testing/selftests/rcutorture/configs/rcuperf/TREE @@ -7,7 +7,6 @@ CONFIG_HZ_PERIODIC=n CONFIG_NO_HZ_IDLE=y CONFIG_NO_HZ_FULL=n CONFIG_RCU_FAST_NO_HZ=n -CONFIG_RCU_TRACE=n CONFIG_HOTPLUG_CPU=n CONFIG_SUSPEND=n CONFIG_HIBERNATION=n diff --git a/tools/testing/selftests/rcutorture/configs/rcuperf/TREE54 b/tools/testing/selftests/rcutorture/configs/rcuperf/TREE54 index 985fb170d13c..7629f5dd73b2 100644 --- a/tools/testing/selftests/rcutorture/configs/rcuperf/TREE54 +++ b/tools/testing/selftests/rcutorture/configs/rcuperf/TREE54 @@ -8,7 +8,6 @@ CONFIG_HZ_PERIODIC=n CONFIG_NO_HZ_IDLE=y CONFIG_NO_HZ_FULL=n CONFIG_RCU_FAST_NO_HZ=n -CONFIG_RCU_TRACE=n CONFIG_HOTPLUG_CPU=n CONFIG_SUSPEND=n CONFIG_HIBERNATION=n diff --git a/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt b/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt index 24396ae8355b..a75b16991a92 100644 --- a/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt +++ b/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt @@ -18,7 +18,6 @@ CONFIG_PROVE_RCU In common code tested by TREE_RCU test cases. -CONFIG_NO_HZ_FULL_SYSIDLE CONFIG_RCU_NOCB_CPU Meaningless for TINY_RCU. diff --git a/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt b/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt index 364801b1a230..9ad3f89c8dc7 100644 --- a/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt +++ b/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt @@ -9,28 +9,20 @@ CONFIG_DEBUG_OBJECTS_RCU_HEAD -- Do one. CONFIG_HOTPLUG_CPU -- Do half. (Every second.) CONFIG_HZ_PERIODIC -- Do one. CONFIG_NO_HZ_IDLE -- Do those not otherwise specified. (Groups of two.) -CONFIG_NO_HZ_FULL -- Do two, one with CONFIG_NO_HZ_FULL_SYSIDLE. -CONFIG_NO_HZ_FULL_SYSIDLE -- Do one. +CONFIG_NO_HZ_FULL -- Do two, one with partial CPU enablement. CONFIG_PREEMPT -- Do half. (First three and #8.) CONFIG_PROVE_LOCKING -- Do several, covering CONFIG_DEBUG_LOCK_ALLOC=y and not. CONFIG_PROVE_RCU -- Hardwired to CONFIG_PROVE_LOCKING. -CONFIG_PROVE_RCU_REPEATEDLY -- Do one. CONFIG_RCU_BOOST -- one of PREEMPT_RCU. -CONFIG_RCU_KTHREAD_PRIO -- set to 2 for _BOOST testing. CONFIG_RCU_FANOUT -- Cover hierarchy, but overlap with others. CONFIG_RCU_FANOUT_LEAF -- Do one non-default. -CONFIG_RCU_FAST_NO_HZ -- Do one, but not with CONFIG_RCU_NOCB_CPU_ALL. -CONFIG_RCU_NOCB_CPU -- Do three, see below. -CONFIG_RCU_NOCB_CPU_ALL -- Do one. -CONFIG_RCU_NOCB_CPU_NONE -- Do one. -CONFIG_RCU_NOCB_CPU_ZERO -- Do one. +CONFIG_RCU_FAST_NO_HZ -- Do one, but not with all nohz_full CPUs. +CONFIG_RCU_NOCB_CPU -- Do three, one with no rcu_nocbs CPUs, one with + rcu_nocbs=0, and one with all rcu_nocbs CPUs. CONFIG_RCU_TRACE -- Do half. CONFIG_SMP -- Need one !SMP for PREEMPT_RCU. CONFIG_RCU_EXPERT=n -- Do a few, but these have to be vanilla configurations. CONFIG_RCU_EQS_DEBUG -- Do at least one for CONFIG_NO_HZ_FULL and not. -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP -- Do for all but a couple TREE scenarios. -CONFIG_RCU_TORTURE_TEST_SLOW_INIT -- Do for all but a couple TREE scenarios. -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT -- Do for all but a couple TREE scenarios. RCU-bh: Do one with PREEMPT and one with !PREEMPT. RCU-sched: Do one with PREEMPT but not BOOST. @@ -52,10 +44,6 @@ CONFIG_64BIT Used only to check CONFIG_RCU_FANOUT value, inspection suffices. -CONFIG_NO_HZ_FULL_SYSIDLE_SMALL - - Defer until Frederic uses this. - CONFIG_PREEMPT_COUNT CONFIG_PREEMPT_RCU @@ -78,30 +66,16 @@ CONFIG_RCU_TORTURE_TEST_RUNNABLE Always used in KVM testing. -CONFIG_RCU_TORTURE_TEST_SLOW_PREINIT_DELAY -CONFIG_RCU_TORTURE_TEST_SLOW_INIT_DELAY -CONFIG_RCU_TORTURE_TEST_SLOW_CLEANUP_DELAY - - Inspection suffices, ignore. - CONFIG_PREEMPT_RCU CONFIG_TREE_RCU CONFIG_TINY_RCU These are controlled by CONFIG_PREEMPT and/or CONFIG_SMP. -CONFIG_SPARSE_RCU_POINTER - - Makes sense only for sparse runs, not for kernel builds. - CONFIG_SRCU CONFIG_TASKS_RCU Selected by CONFIG_RCU_TORTURE_TEST, so cannot disable. -CONFIG_RCU_TRACE - - Implied by CONFIG_RCU_TRACE for Tree RCU. - boot parameters ignored: TBD diff --git a/tools/testing/selftests/rcutorture/formal/srcu-cbmc/modify_srcu.awk b/tools/testing/selftests/rcutorture/formal/srcu-cbmc/modify_srcu.awk index 8ff89043d0a9..c9e8bc5082a7 100755 --- a/tools/testing/selftests/rcutorture/formal/srcu-cbmc/modify_srcu.awk +++ b/tools/testing/selftests/rcutorture/formal/srcu-cbmc/modify_srcu.awk @@ -1,4 +1,4 @@ -#!/bin/awk -f +#!/usr/bin/awk -f # Modify SRCU for formal verification. The first argument should be srcu.h and # the second should be srcu.c. Outputs modified srcu.h and srcu.c into the |