diff options
| -rw-r--r-- | arch/x86/include/asm/cpufeature.h | 15 | ||||
| -rw-r--r-- | arch/x86/include/asm/insn.h | 18 | ||||
| -rw-r--r-- | arch/x86/kernel/kprobes/core.c | 4 | ||||
| -rw-r--r-- | arch/x86/kernel/machine_kexec_32.c | 6 | ||||
| -rw-r--r-- | arch/x86/kernel/machine_kexec_64.c | 5 | ||||
| -rw-r--r-- | arch/x86/kernel/uprobes.c | 4 | ||||
| -rw-r--r-- | samples/bpf/Makefile | 2 | ||||
| -rw-r--r-- | tools/objtool/arch/x86/include/asm/insn.h | 18 | ||||
| -rw-r--r-- | tools/objtool/check.c | 167 | ||||
| -rw-r--r-- | tools/objtool/elf.c | 42 | ||||
| -rw-r--r-- | tools/objtool/elf.h | 2 |
11 files changed, 206 insertions, 77 deletions
diff --git a/arch/x86/include/asm/cpufeature.h b/arch/x86/include/asm/cpufeature.h index b27da9602a6d..aced6c9290d6 100644 --- a/arch/x86/include/asm/cpufeature.h +++ b/arch/x86/include/asm/cpufeature.h | |||
| @@ -140,6 +140,20 @@ extern void clear_cpu_cap(struct cpuinfo_x86 *c, unsigned int bit); | |||
| 140 | 140 | ||
| 141 | #define setup_force_cpu_bug(bit) setup_force_cpu_cap(bit) | 141 | #define setup_force_cpu_bug(bit) setup_force_cpu_cap(bit) |
| 142 | 142 | ||
| 143 | #if defined(__clang__) && !defined(CC_HAVE_ASM_GOTO) | ||
| 144 | |||
| 145 | /* | ||
| 146 | * Workaround for the sake of BPF compilation which utilizes kernel | ||
| 147 | * headers, but clang does not support ASM GOTO and fails the build. | ||
| 148 | */ | ||
| 149 | #ifndef __BPF_TRACING__ | ||
| 150 | #warning "Compiler lacks ASM_GOTO support. Add -D __BPF_TRACING__ to your compiler arguments" | ||
| 151 | #endif | ||
| 152 | |||
| 153 | #define static_cpu_has(bit) boot_cpu_has(bit) | ||
| 154 | |||
| 155 | #else | ||
| 156 | |||
| 143 | /* | 157 | /* |
| 144 | * Static testing of CPU features. Used the same as boot_cpu_has(). | 158 | * Static testing of CPU features. Used the same as boot_cpu_has(). |
| 145 | * These will statically patch the target code for additional | 159 | * These will statically patch the target code for additional |
| @@ -195,6 +209,7 @@ t_no: | |||
| 195 | boot_cpu_has(bit) : \ | 209 | boot_cpu_has(bit) : \ |
| 196 | _static_cpu_has(bit) \ | 210 | _static_cpu_has(bit) \ |
| 197 | ) | 211 | ) |
| 212 | #endif | ||
| 198 | 213 | ||
| 199 | #define cpu_has_bug(c, bit) cpu_has(c, (bit)) | 214 | #define cpu_has_bug(c, bit) cpu_has(c, (bit)) |
| 200 | #define set_cpu_bug(c, bit) set_cpu_cap(c, (bit)) | 215 | #define set_cpu_bug(c, bit) set_cpu_cap(c, (bit)) |
diff --git a/arch/x86/include/asm/insn.h b/arch/x86/include/asm/insn.h index b3e32b010ab1..c2c01f84df75 100644 --- a/arch/x86/include/asm/insn.h +++ b/arch/x86/include/asm/insn.h | |||
| @@ -208,4 +208,22 @@ static inline int insn_offset_immediate(struct insn *insn) | |||
| 208 | return insn_offset_displacement(insn) + insn->displacement.nbytes; | 208 | return insn_offset_displacement(insn) + insn->displacement.nbytes; |
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | #define POP_SS_OPCODE 0x1f | ||
| 212 | #define MOV_SREG_OPCODE 0x8e | ||
| 213 | |||
| 214 | /* | ||
| 215 | * Intel SDM Vol.3A 6.8.3 states; | ||
| 216 | * "Any single-step trap that would be delivered following the MOV to SS | ||
| 217 | * instruction or POP to SS instruction (because EFLAGS.TF is 1) is | ||
| 218 | * suppressed." | ||
| 219 | * This function returns true if @insn is MOV SS or POP SS. On these | ||
| 220 | * instructions, single stepping is suppressed. | ||
| 221 | */ | ||
| 222 | static inline int insn_masking_exception(struct insn *insn) | ||
| 223 | { | ||
| 224 | return insn->opcode.bytes[0] == POP_SS_OPCODE || | ||
| 225 | (insn->opcode.bytes[0] == MOV_SREG_OPCODE && | ||
| 226 | X86_MODRM_REG(insn->modrm.bytes[0]) == 2); | ||
| 227 | } | ||
| 228 | |||
| 211 | #endif /* _ASM_X86_INSN_H */ | 229 | #endif /* _ASM_X86_INSN_H */ |
diff --git a/arch/x86/kernel/kprobes/core.c b/arch/x86/kernel/kprobes/core.c index 0715f827607c..6f4d42377fe5 100644 --- a/arch/x86/kernel/kprobes/core.c +++ b/arch/x86/kernel/kprobes/core.c | |||
| @@ -370,6 +370,10 @@ int __copy_instruction(u8 *dest, u8 *src, u8 *real, struct insn *insn) | |||
| 370 | if (insn->opcode.bytes[0] == BREAKPOINT_INSTRUCTION) | 370 | if (insn->opcode.bytes[0] == BREAKPOINT_INSTRUCTION) |
| 371 | return 0; | 371 | return 0; |
| 372 | 372 | ||
| 373 | /* We should not singlestep on the exception masking instructions */ | ||
| 374 | if (insn_masking_exception(insn)) | ||
| 375 | return 0; | ||
| 376 | |||
| 373 | #ifdef CONFIG_X86_64 | 377 | #ifdef CONFIG_X86_64 |
| 374 | /* Only x86_64 has RIP relative instructions */ | 378 | /* Only x86_64 has RIP relative instructions */ |
| 375 | if (insn_rip_relative(insn)) { | 379 | if (insn_rip_relative(insn)) { |
diff --git a/arch/x86/kernel/machine_kexec_32.c b/arch/x86/kernel/machine_kexec_32.c index 60cdec6628b0..d1ab07ec8c9a 100644 --- a/arch/x86/kernel/machine_kexec_32.c +++ b/arch/x86/kernel/machine_kexec_32.c | |||
| @@ -57,12 +57,17 @@ static void load_segments(void) | |||
| 57 | static void machine_kexec_free_page_tables(struct kimage *image) | 57 | static void machine_kexec_free_page_tables(struct kimage *image) |
| 58 | { | 58 | { |
| 59 | free_page((unsigned long)image->arch.pgd); | 59 | free_page((unsigned long)image->arch.pgd); |
| 60 | image->arch.pgd = NULL; | ||
| 60 | #ifdef CONFIG_X86_PAE | 61 | #ifdef CONFIG_X86_PAE |
| 61 | free_page((unsigned long)image->arch.pmd0); | 62 | free_page((unsigned long)image->arch.pmd0); |
| 63 | image->arch.pmd0 = NULL; | ||
| 62 | free_page((unsigned long)image->arch.pmd1); | 64 | free_page((unsigned long)image->arch.pmd1); |
| 65 | image->arch.pmd1 = NULL; | ||
| 63 | #endif | 66 | #endif |
| 64 | free_page((unsigned long)image->arch.pte0); | 67 | free_page((unsigned long)image->arch.pte0); |
| 68 | image->arch.pte0 = NULL; | ||
| 65 | free_page((unsigned long)image->arch.pte1); | 69 | free_page((unsigned long)image->arch.pte1); |
| 70 | image->arch.pte1 = NULL; | ||
| 66 | } | 71 | } |
| 67 | 72 | ||
| 68 | static int machine_kexec_alloc_page_tables(struct kimage *image) | 73 | static int machine_kexec_alloc_page_tables(struct kimage *image) |
| @@ -79,7 +84,6 @@ static int machine_kexec_alloc_page_tables(struct kimage *image) | |||
| 79 | !image->arch.pmd0 || !image->arch.pmd1 || | 84 | !image->arch.pmd0 || !image->arch.pmd1 || |
| 80 | #endif | 85 | #endif |
| 81 | !image->arch.pte0 || !image->arch.pte1) { | 86 | !image->arch.pte0 || !image->arch.pte1) { |
| 82 | machine_kexec_free_page_tables(image); | ||
| 83 | return -ENOMEM; | 87 | return -ENOMEM; |
| 84 | } | 88 | } |
| 85 | return 0; | 89 | return 0; |
diff --git a/arch/x86/kernel/machine_kexec_64.c b/arch/x86/kernel/machine_kexec_64.c index a5e55d832d0a..6010449ca6d2 100644 --- a/arch/x86/kernel/machine_kexec_64.c +++ b/arch/x86/kernel/machine_kexec_64.c | |||
| @@ -39,9 +39,13 @@ const struct kexec_file_ops * const kexec_file_loaders[] = { | |||
| 39 | static void free_transition_pgtable(struct kimage *image) | 39 | static void free_transition_pgtable(struct kimage *image) |
| 40 | { | 40 | { |
| 41 | free_page((unsigned long)image->arch.p4d); | 41 | free_page((unsigned long)image->arch.p4d); |
| 42 | image->arch.p4d = NULL; | ||
| 42 | free_page((unsigned long)image->arch.pud); | 43 | free_page((unsigned long)image->arch.pud); |
| 44 | image->arch.pud = NULL; | ||
| 43 | free_page((unsigned long)image->arch.pmd); | 45 | free_page((unsigned long)image->arch.pmd); |
| 46 | image->arch.pmd = NULL; | ||
| 44 | free_page((unsigned long)image->arch.pte); | 47 | free_page((unsigned long)image->arch.pte); |
| 48 | image->arch.pte = NULL; | ||
| 45 | } | 49 | } |
| 46 | 50 | ||
| 47 | static int init_transition_pgtable(struct kimage *image, pgd_t *pgd) | 51 | static int init_transition_pgtable(struct kimage *image, pgd_t *pgd) |
| @@ -91,7 +95,6 @@ static int init_transition_pgtable(struct kimage *image, pgd_t *pgd) | |||
| 91 | set_pte(pte, pfn_pte(paddr >> PAGE_SHIFT, PAGE_KERNEL_EXEC_NOENC)); | 95 | set_pte(pte, pfn_pte(paddr >> PAGE_SHIFT, PAGE_KERNEL_EXEC_NOENC)); |
| 92 | return 0; | 96 | return 0; |
| 93 | err: | 97 | err: |
| 94 | free_transition_pgtable(image); | ||
| 95 | return result; | 98 | return result; |
| 96 | } | 99 | } |
| 97 | 100 | ||
diff --git a/arch/x86/kernel/uprobes.c b/arch/x86/kernel/uprobes.c index 85c7ef23d99f..c84bb5396958 100644 --- a/arch/x86/kernel/uprobes.c +++ b/arch/x86/kernel/uprobes.c | |||
| @@ -299,6 +299,10 @@ static int uprobe_init_insn(struct arch_uprobe *auprobe, struct insn *insn, bool | |||
| 299 | if (is_prefix_bad(insn)) | 299 | if (is_prefix_bad(insn)) |
| 300 | return -ENOTSUPP; | 300 | return -ENOTSUPP; |
| 301 | 301 | ||
| 302 | /* We should not singlestep on the exception masking instructions */ | ||
| 303 | if (insn_masking_exception(insn)) | ||
| 304 | return -ENOTSUPP; | ||
| 305 | |||
| 302 | if (x86_64) | 306 | if (x86_64) |
| 303 | good_insns = good_insns_64; | 307 | good_insns = good_insns_64; |
| 304 | else | 308 | else |
diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile index 4d6a6edd4bf6..092947676143 100644 --- a/samples/bpf/Makefile +++ b/samples/bpf/Makefile | |||
| @@ -255,7 +255,7 @@ $(obj)/tracex5_kern.o: $(obj)/syscall_nrs.h | |||
| 255 | $(obj)/%.o: $(src)/%.c | 255 | $(obj)/%.o: $(src)/%.c |
| 256 | $(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) -I$(obj) \ | 256 | $(CLANG) $(NOSTDINC_FLAGS) $(LINUXINCLUDE) $(EXTRA_CFLAGS) -I$(obj) \ |
| 257 | -I$(srctree)/tools/testing/selftests/bpf/ \ | 257 | -I$(srctree)/tools/testing/selftests/bpf/ \ |
| 258 | -D__KERNEL__ -Wno-unused-value -Wno-pointer-sign \ | 258 | -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ |
| 259 | -D__TARGET_ARCH_$(ARCH) -Wno-compare-distinct-pointer-types \ | 259 | -D__TARGET_ARCH_$(ARCH) -Wno-compare-distinct-pointer-types \ |
| 260 | -Wno-gnu-variable-sized-type-not-at-end \ | 260 | -Wno-gnu-variable-sized-type-not-at-end \ |
| 261 | -Wno-address-of-packed-member -Wno-tautological-compare \ | 261 | -Wno-address-of-packed-member -Wno-tautological-compare \ |
diff --git a/tools/objtool/arch/x86/include/asm/insn.h b/tools/objtool/arch/x86/include/asm/insn.h index b3e32b010ab1..c2c01f84df75 100644 --- a/tools/objtool/arch/x86/include/asm/insn.h +++ b/tools/objtool/arch/x86/include/asm/insn.h | |||
| @@ -208,4 +208,22 @@ static inline int insn_offset_immediate(struct insn *insn) | |||
| 208 | return insn_offset_displacement(insn) + insn->displacement.nbytes; | 208 | return insn_offset_displacement(insn) + insn->displacement.nbytes; |
| 209 | } | 209 | } |
| 210 | 210 | ||
| 211 | #define POP_SS_OPCODE 0x1f | ||
| 212 | #define MOV_SREG_OPCODE 0x8e | ||
| 213 | |||
| 214 | /* | ||
| 215 | * Intel SDM Vol.3A 6.8.3 states; | ||
| 216 | * "Any single-step trap that would be delivered following the MOV to SS | ||
| 217 | * instruction or POP to SS instruction (because EFLAGS.TF is 1) is | ||
| 218 | * suppressed." | ||
| 219 | * This function returns true if @insn is MOV SS or POP SS. On these | ||
| 220 | * instructions, single stepping is suppressed. | ||
| 221 | */ | ||
| 222 | static inline int insn_masking_exception(struct insn *insn) | ||
| 223 | { | ||
| 224 | return insn->opcode.bytes[0] == POP_SS_OPCODE || | ||
| 225 | (insn->opcode.bytes[0] == MOV_SREG_OPCODE && | ||
| 226 | X86_MODRM_REG(insn->modrm.bytes[0]) == 2); | ||
| 227 | } | ||
| 228 | |||
| 211 | #endif /* _ASM_X86_INSN_H */ | 229 | #endif /* _ASM_X86_INSN_H */ |
diff --git a/tools/objtool/check.c b/tools/objtool/check.c index 5409f6f6c48d..3a31b238f885 100644 --- a/tools/objtool/check.c +++ b/tools/objtool/check.c | |||
| @@ -59,6 +59,31 @@ static struct instruction *next_insn_same_sec(struct objtool_file *file, | |||
| 59 | return next; | 59 | return next; |
| 60 | } | 60 | } |
| 61 | 61 | ||
| 62 | static struct instruction *next_insn_same_func(struct objtool_file *file, | ||
| 63 | struct instruction *insn) | ||
| 64 | { | ||
| 65 | struct instruction *next = list_next_entry(insn, list); | ||
| 66 | struct symbol *func = insn->func; | ||
| 67 | |||
| 68 | if (!func) | ||
| 69 | return NULL; | ||
| 70 | |||
| 71 | if (&next->list != &file->insn_list && next->func == func) | ||
| 72 | return next; | ||
| 73 | |||
| 74 | /* Check if we're already in the subfunction: */ | ||
| 75 | if (func == func->cfunc) | ||
| 76 | return NULL; | ||
| 77 | |||
| 78 | /* Move to the subfunction: */ | ||
| 79 | return find_insn(file, func->cfunc->sec, func->cfunc->offset); | ||
| 80 | } | ||
| 81 | |||
| 82 | #define func_for_each_insn_all(file, func, insn) \ | ||
| 83 | for (insn = find_insn(file, func->sec, func->offset); \ | ||
| 84 | insn; \ | ||
| 85 | insn = next_insn_same_func(file, insn)) | ||
| 86 | |||
| 62 | #define func_for_each_insn(file, func, insn) \ | 87 | #define func_for_each_insn(file, func, insn) \ |
| 63 | for (insn = find_insn(file, func->sec, func->offset); \ | 88 | for (insn = find_insn(file, func->sec, func->offset); \ |
| 64 | insn && &insn->list != &file->insn_list && \ | 89 | insn && &insn->list != &file->insn_list && \ |
| @@ -149,10 +174,14 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func, | |||
| 149 | if (!strcmp(func->name, global_noreturns[i])) | 174 | if (!strcmp(func->name, global_noreturns[i])) |
| 150 | return 1; | 175 | return 1; |
| 151 | 176 | ||
| 152 | if (!func->sec) | 177 | if (!func->len) |
| 153 | return 0; | 178 | return 0; |
| 154 | 179 | ||
| 155 | func_for_each_insn(file, func, insn) { | 180 | insn = find_insn(file, func->sec, func->offset); |
| 181 | if (!insn->func) | ||
| 182 | return 0; | ||
| 183 | |||
| 184 | func_for_each_insn_all(file, func, insn) { | ||
| 156 | empty = false; | 185 | empty = false; |
| 157 | 186 | ||
| 158 | if (insn->type == INSN_RETURN) | 187 | if (insn->type == INSN_RETURN) |
| @@ -167,35 +196,28 @@ static int __dead_end_function(struct objtool_file *file, struct symbol *func, | |||
| 167 | * case, the function's dead-end status depends on whether the target | 196 | * case, the function's dead-end status depends on whether the target |
| 168 | * of the sibling call returns. | 197 | * of the sibling call returns. |
| 169 | */ | 198 | */ |
| 170 | func_for_each_insn(file, func, insn) { | 199 | func_for_each_insn_all(file, func, insn) { |
| 171 | if (insn->sec != func->sec || | ||
| 172 | insn->offset >= func->offset + func->len) | ||
| 173 | break; | ||
| 174 | |||
| 175 | if (insn->type == INSN_JUMP_UNCONDITIONAL) { | 200 | if (insn->type == INSN_JUMP_UNCONDITIONAL) { |
| 176 | struct instruction *dest = insn->jump_dest; | 201 | struct instruction *dest = insn->jump_dest; |
| 177 | struct symbol *dest_func; | ||
| 178 | 202 | ||
| 179 | if (!dest) | 203 | if (!dest) |
| 180 | /* sibling call to another file */ | 204 | /* sibling call to another file */ |
| 181 | return 0; | 205 | return 0; |
| 182 | 206 | ||
| 183 | if (dest->sec != func->sec || | 207 | if (dest->func && dest->func->pfunc != insn->func->pfunc) { |
| 184 | dest->offset < func->offset || | ||
| 185 | dest->offset >= func->offset + func->len) { | ||
| 186 | /* local sibling call */ | ||
| 187 | dest_func = find_symbol_by_offset(dest->sec, | ||
| 188 | dest->offset); | ||
| 189 | if (!dest_func) | ||
| 190 | continue; | ||
| 191 | 208 | ||
| 209 | /* local sibling call */ | ||
| 192 | if (recursion == 5) { | 210 | if (recursion == 5) { |
| 193 | WARN_FUNC("infinite recursion (objtool bug!)", | 211 | /* |
| 194 | dest->sec, dest->offset); | 212 | * Infinite recursion: two functions |
| 195 | return -1; | 213 | * have sibling calls to each other. |
| 214 | * This is a very rare case. It means | ||
| 215 | * they aren't dead ends. | ||
| 216 | */ | ||
| 217 | return 0; | ||
| 196 | } | 218 | } |
| 197 | 219 | ||
| 198 | return __dead_end_function(file, dest_func, | 220 | return __dead_end_function(file, dest->func, |
| 199 | recursion + 1); | 221 | recursion + 1); |
| 200 | } | 222 | } |
| 201 | } | 223 | } |
| @@ -422,7 +444,7 @@ static void add_ignores(struct objtool_file *file) | |||
| 422 | if (!ignore_func(file, func)) | 444 | if (!ignore_func(file, func)) |
| 423 | continue; | 445 | continue; |
| 424 | 446 | ||
| 425 | func_for_each_insn(file, func, insn) | 447 | func_for_each_insn_all(file, func, insn) |
| 426 | insn->ignore = true; | 448 | insn->ignore = true; |
| 427 | } | 449 | } |
| 428 | } | 450 | } |
| @@ -782,30 +804,35 @@ out: | |||
| 782 | return ret; | 804 | return ret; |
| 783 | } | 805 | } |
| 784 | 806 | ||
| 785 | static int add_switch_table(struct objtool_file *file, struct symbol *func, | 807 | static int add_switch_table(struct objtool_file *file, struct instruction *insn, |
| 786 | struct instruction *insn, struct rela *table, | 808 | struct rela *table, struct rela *next_table) |
| 787 | struct rela *next_table) | ||
| 788 | { | 809 | { |
| 789 | struct rela *rela = table; | 810 | struct rela *rela = table; |
| 790 | struct instruction *alt_insn; | 811 | struct instruction *alt_insn; |
| 791 | struct alternative *alt; | 812 | struct alternative *alt; |
| 813 | struct symbol *pfunc = insn->func->pfunc; | ||
| 814 | unsigned int prev_offset = 0; | ||
| 792 | 815 | ||
| 793 | list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) { | 816 | list_for_each_entry_from(rela, &file->rodata->rela->rela_list, list) { |
| 794 | if (rela == next_table) | 817 | if (rela == next_table) |
| 795 | break; | 818 | break; |
| 796 | 819 | ||
| 797 | if (rela->sym->sec != insn->sec || | 820 | /* Make sure the switch table entries are consecutive: */ |
| 798 | rela->addend <= func->offset || | 821 | if (prev_offset && rela->offset != prev_offset + 8) |
| 799 | rela->addend >= func->offset + func->len) | ||
| 800 | break; | 822 | break; |
| 801 | 823 | ||
| 802 | alt_insn = find_insn(file, insn->sec, rela->addend); | 824 | /* Detect function pointers from contiguous objects: */ |
| 803 | if (!alt_insn) { | 825 | if (rela->sym->sec == pfunc->sec && |
| 804 | WARN("%s: can't find instruction at %s+0x%x", | 826 | rela->addend == pfunc->offset) |
| 805 | file->rodata->rela->name, insn->sec->name, | 827 | break; |
| 806 | rela->addend); | 828 | |
| 807 | return -1; | 829 | alt_insn = find_insn(file, rela->sym->sec, rela->addend); |
| 808 | } | 830 | if (!alt_insn) |
| 831 | break; | ||
| 832 | |||
| 833 | /* Make sure the jmp dest is in the function or subfunction: */ | ||
| 834 | if (alt_insn->func->pfunc != pfunc) | ||
| 835 | break; | ||
| 809 | 836 | ||
| 810 | alt = malloc(sizeof(*alt)); | 837 | alt = malloc(sizeof(*alt)); |
| 811 | if (!alt) { | 838 | if (!alt) { |
| @@ -815,6 +842,13 @@ static int add_switch_table(struct objtool_file *file, struct symbol *func, | |||
| 815 | 842 | ||
| 816 | alt->insn = alt_insn; | 843 | alt->insn = alt_insn; |
| 817 | list_add_tail(&alt->list, &insn->alts); | 844 | list_add_tail(&alt->list, &insn->alts); |
| 845 | prev_offset = rela->offset; | ||
| 846 | } | ||
| 847 | |||
| 848 | if (!prev_offset) { | ||
| 849 | WARN_FUNC("can't find switch jump table", | ||
| 850 | insn->sec, insn->offset); | ||
| 851 | return -1; | ||
| 818 | } | 852 | } |
| 819 | 853 | ||
| 820 | return 0; | 854 | return 0; |
| @@ -869,40 +903,21 @@ static struct rela *find_switch_table(struct objtool_file *file, | |||
| 869 | { | 903 | { |
| 870 | struct rela *text_rela, *rodata_rela; | 904 | struct rela *text_rela, *rodata_rela; |
| 871 | struct instruction *orig_insn = insn; | 905 | struct instruction *orig_insn = insn; |
| 906 | unsigned long table_offset; | ||
| 872 | 907 | ||
| 873 | text_rela = find_rela_by_dest_range(insn->sec, insn->offset, insn->len); | ||
| 874 | if (text_rela && text_rela->sym == file->rodata->sym) { | ||
| 875 | /* case 1 */ | ||
| 876 | rodata_rela = find_rela_by_dest(file->rodata, | ||
| 877 | text_rela->addend); | ||
| 878 | if (rodata_rela) | ||
| 879 | return rodata_rela; | ||
| 880 | |||
| 881 | /* case 2 */ | ||
| 882 | rodata_rela = find_rela_by_dest(file->rodata, | ||
| 883 | text_rela->addend + 4); | ||
| 884 | if (!rodata_rela) | ||
| 885 | return NULL; | ||
| 886 | |||
| 887 | file->ignore_unreachables = true; | ||
| 888 | return rodata_rela; | ||
| 889 | } | ||
| 890 | |||
| 891 | /* case 3 */ | ||
| 892 | /* | 908 | /* |
| 893 | * Backward search using the @first_jump_src links, these help avoid | 909 | * Backward search using the @first_jump_src links, these help avoid |
| 894 | * much of the 'in between' code. Which avoids us getting confused by | 910 | * much of the 'in between' code. Which avoids us getting confused by |
| 895 | * it. | 911 | * it. |
| 896 | */ | 912 | */ |
| 897 | for (insn = list_prev_entry(insn, list); | 913 | for (; |
| 898 | |||
| 899 | &insn->list != &file->insn_list && | 914 | &insn->list != &file->insn_list && |
| 900 | insn->sec == func->sec && | 915 | insn->sec == func->sec && |
| 901 | insn->offset >= func->offset; | 916 | insn->offset >= func->offset; |
| 902 | 917 | ||
| 903 | insn = insn->first_jump_src ?: list_prev_entry(insn, list)) { | 918 | insn = insn->first_jump_src ?: list_prev_entry(insn, list)) { |
| 904 | 919 | ||
| 905 | if (insn->type == INSN_JUMP_DYNAMIC) | 920 | if (insn != orig_insn && insn->type == INSN_JUMP_DYNAMIC) |
| 906 | break; | 921 | break; |
| 907 | 922 | ||
| 908 | /* allow small jumps within the range */ | 923 | /* allow small jumps within the range */ |
| @@ -918,18 +933,29 @@ static struct rela *find_switch_table(struct objtool_file *file, | |||
| 918 | if (!text_rela || text_rela->sym != file->rodata->sym) | 933 | if (!text_rela || text_rela->sym != file->rodata->sym) |
| 919 | continue; | 934 | continue; |
| 920 | 935 | ||
| 936 | table_offset = text_rela->addend; | ||
| 937 | if (text_rela->type == R_X86_64_PC32) | ||
| 938 | table_offset += 4; | ||
| 939 | |||
| 921 | /* | 940 | /* |
| 922 | * Make sure the .rodata address isn't associated with a | 941 | * Make sure the .rodata address isn't associated with a |
| 923 | * symbol. gcc jump tables are anonymous data. | 942 | * symbol. gcc jump tables are anonymous data. |
| 924 | */ | 943 | */ |
| 925 | if (find_symbol_containing(file->rodata, text_rela->addend)) | 944 | if (find_symbol_containing(file->rodata, table_offset)) |
| 926 | continue; | 945 | continue; |
| 927 | 946 | ||
| 928 | rodata_rela = find_rela_by_dest(file->rodata, text_rela->addend); | 947 | rodata_rela = find_rela_by_dest(file->rodata, table_offset); |
| 929 | if (!rodata_rela) | 948 | if (rodata_rela) { |
| 930 | continue; | 949 | /* |
| 950 | * Use of RIP-relative switch jumps is quite rare, and | ||
| 951 | * indicates a rare GCC quirk/bug which can leave dead | ||
| 952 | * code behind. | ||
| 953 | */ | ||
| 954 | if (text_rela->type == R_X86_64_PC32) | ||
| 955 | file->ignore_unreachables = true; | ||
| 931 | 956 | ||
| 932 | return rodata_rela; | 957 | return rodata_rela; |
| 958 | } | ||
| 933 | } | 959 | } |
| 934 | 960 | ||
| 935 | return NULL; | 961 | return NULL; |
| @@ -943,7 +969,7 @@ static int add_func_switch_tables(struct objtool_file *file, | |||
| 943 | struct rela *rela, *prev_rela = NULL; | 969 | struct rela *rela, *prev_rela = NULL; |
| 944 | int ret; | 970 | int ret; |
| 945 | 971 | ||
| 946 | func_for_each_insn(file, func, insn) { | 972 | func_for_each_insn_all(file, func, insn) { |
| 947 | if (!last) | 973 | if (!last) |
| 948 | last = insn; | 974 | last = insn; |
| 949 | 975 | ||
| @@ -974,8 +1000,7 @@ static int add_func_switch_tables(struct objtool_file *file, | |||
| 974 | * the beginning of another switch table in the same function. | 1000 | * the beginning of another switch table in the same function. |
| 975 | */ | 1001 | */ |
| 976 | if (prev_jump) { | 1002 | if (prev_jump) { |
| 977 | ret = add_switch_table(file, func, prev_jump, prev_rela, | 1003 | ret = add_switch_table(file, prev_jump, prev_rela, rela); |
| 978 | rela); | ||
| 979 | if (ret) | 1004 | if (ret) |
| 980 | return ret; | 1005 | return ret; |
| 981 | } | 1006 | } |
| @@ -985,7 +1010,7 @@ static int add_func_switch_tables(struct objtool_file *file, | |||
| 985 | } | 1010 | } |
| 986 | 1011 | ||
| 987 | if (prev_jump) { | 1012 | if (prev_jump) { |
| 988 | ret = add_switch_table(file, func, prev_jump, prev_rela, NULL); | 1013 | ret = add_switch_table(file, prev_jump, prev_rela, NULL); |
| 989 | if (ret) | 1014 | if (ret) |
| 990 | return ret; | 1015 | return ret; |
| 991 | } | 1016 | } |
| @@ -1749,15 +1774,13 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, | |||
| 1749 | while (1) { | 1774 | while (1) { |
| 1750 | next_insn = next_insn_same_sec(file, insn); | 1775 | next_insn = next_insn_same_sec(file, insn); |
| 1751 | 1776 | ||
| 1752 | 1777 | if (file->c_file && func && insn->func && func != insn->func->pfunc) { | |
| 1753 | if (file->c_file && func && insn->func && func != insn->func) { | ||
| 1754 | WARN("%s() falls through to next function %s()", | 1778 | WARN("%s() falls through to next function %s()", |
| 1755 | func->name, insn->func->name); | 1779 | func->name, insn->func->name); |
| 1756 | return 1; | 1780 | return 1; |
| 1757 | } | 1781 | } |
| 1758 | 1782 | ||
| 1759 | if (insn->func) | 1783 | func = insn->func ? insn->func->pfunc : NULL; |
| 1760 | func = insn->func; | ||
| 1761 | 1784 | ||
| 1762 | if (func && insn->ignore) { | 1785 | if (func && insn->ignore) { |
| 1763 | WARN_FUNC("BUG: why am I validating an ignored function?", | 1786 | WARN_FUNC("BUG: why am I validating an ignored function?", |
| @@ -1778,7 +1801,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, | |||
| 1778 | 1801 | ||
| 1779 | i = insn; | 1802 | i = insn; |
| 1780 | save_insn = NULL; | 1803 | save_insn = NULL; |
| 1781 | func_for_each_insn_continue_reverse(file, func, i) { | 1804 | func_for_each_insn_continue_reverse(file, insn->func, i) { |
| 1782 | if (i->save) { | 1805 | if (i->save) { |
| 1783 | save_insn = i; | 1806 | save_insn = i; |
| 1784 | break; | 1807 | break; |
| @@ -1865,7 +1888,7 @@ static int validate_branch(struct objtool_file *file, struct instruction *first, | |||
| 1865 | case INSN_JUMP_UNCONDITIONAL: | 1888 | case INSN_JUMP_UNCONDITIONAL: |
| 1866 | if (insn->jump_dest && | 1889 | if (insn->jump_dest && |
| 1867 | (!func || !insn->jump_dest->func || | 1890 | (!func || !insn->jump_dest->func || |
| 1868 | func == insn->jump_dest->func)) { | 1891 | insn->jump_dest->func->pfunc == func)) { |
| 1869 | ret = validate_branch(file, insn->jump_dest, | 1892 | ret = validate_branch(file, insn->jump_dest, |
| 1870 | state); | 1893 | state); |
| 1871 | if (ret) | 1894 | if (ret) |
| @@ -2060,7 +2083,7 @@ static int validate_functions(struct objtool_file *file) | |||
| 2060 | 2083 | ||
| 2061 | for_each_sec(file, sec) { | 2084 | for_each_sec(file, sec) { |
| 2062 | list_for_each_entry(func, &sec->symbol_list, list) { | 2085 | list_for_each_entry(func, &sec->symbol_list, list) { |
| 2063 | if (func->type != STT_FUNC) | 2086 | if (func->type != STT_FUNC || func->pfunc != func) |
| 2064 | continue; | 2087 | continue; |
| 2065 | 2088 | ||
| 2066 | insn = find_insn(file, sec, func->offset); | 2089 | insn = find_insn(file, sec, func->offset); |
diff --git a/tools/objtool/elf.c b/tools/objtool/elf.c index c1c338661699..4e60e105583e 100644 --- a/tools/objtool/elf.c +++ b/tools/objtool/elf.c | |||
| @@ -79,6 +79,19 @@ struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset) | |||
| 79 | return NULL; | 79 | return NULL; |
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | struct symbol *find_symbol_by_name(struct elf *elf, const char *name) | ||
| 83 | { | ||
| 84 | struct section *sec; | ||
| 85 | struct symbol *sym; | ||
| 86 | |||
| 87 | list_for_each_entry(sec, &elf->sections, list) | ||
| 88 | list_for_each_entry(sym, &sec->symbol_list, list) | ||
| 89 | if (!strcmp(sym->name, name)) | ||
| 90 | return sym; | ||
| 91 | |||
| 92 | return NULL; | ||
| 93 | } | ||
| 94 | |||
| 82 | struct symbol *find_symbol_containing(struct section *sec, unsigned long offset) | 95 | struct symbol *find_symbol_containing(struct section *sec, unsigned long offset) |
| 83 | { | 96 | { |
| 84 | struct symbol *sym; | 97 | struct symbol *sym; |
| @@ -203,10 +216,11 @@ static int read_sections(struct elf *elf) | |||
| 203 | 216 | ||
| 204 | static int read_symbols(struct elf *elf) | 217 | static int read_symbols(struct elf *elf) |
| 205 | { | 218 | { |
| 206 | struct section *symtab; | 219 | struct section *symtab, *sec; |
| 207 | struct symbol *sym; | 220 | struct symbol *sym, *pfunc; |
| 208 | struct list_head *entry, *tmp; | 221 | struct list_head *entry, *tmp; |
| 209 | int symbols_nr, i; | 222 | int symbols_nr, i; |
| 223 | char *coldstr; | ||
| 210 | 224 | ||
| 211 | symtab = find_section_by_name(elf, ".symtab"); | 225 | symtab = find_section_by_name(elf, ".symtab"); |
| 212 | if (!symtab) { | 226 | if (!symtab) { |
| @@ -281,6 +295,30 @@ static int read_symbols(struct elf *elf) | |||
| 281 | hash_add(sym->sec->symbol_hash, &sym->hash, sym->idx); | 295 | hash_add(sym->sec->symbol_hash, &sym->hash, sym->idx); |
| 282 | } | 296 | } |
| 283 | 297 | ||
| 298 | /* Create parent/child links for any cold subfunctions */ | ||
| 299 | list_for_each_entry(sec, &elf->sections, list) { | ||
| 300 | list_for_each_entry(sym, &sec->symbol_list, list) { | ||
| 301 | if (sym->type != STT_FUNC) | ||
| 302 | continue; | ||
| 303 | sym->pfunc = sym->cfunc = sym; | ||
| 304 | coldstr = strstr(sym->name, ".cold."); | ||
| 305 | if (coldstr) { | ||
| 306 | coldstr[0] = '\0'; | ||
| 307 | pfunc = find_symbol_by_name(elf, sym->name); | ||
| 308 | coldstr[0] = '.'; | ||
| 309 | |||
| 310 | if (!pfunc) { | ||
| 311 | WARN("%s(): can't find parent function", | ||
| 312 | sym->name); | ||
| 313 | goto err; | ||
| 314 | } | ||
| 315 | |||
| 316 | sym->pfunc = pfunc; | ||
| 317 | pfunc->cfunc = sym; | ||
| 318 | } | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 284 | return 0; | 322 | return 0; |
| 285 | 323 | ||
| 286 | err: | 324 | err: |
diff --git a/tools/objtool/elf.h b/tools/objtool/elf.h index d86e2ff14466..de5cd2ddded9 100644 --- a/tools/objtool/elf.h +++ b/tools/objtool/elf.h | |||
| @@ -61,6 +61,7 @@ struct symbol { | |||
| 61 | unsigned char bind, type; | 61 | unsigned char bind, type; |
| 62 | unsigned long offset; | 62 | unsigned long offset; |
| 63 | unsigned int len; | 63 | unsigned int len; |
| 64 | struct symbol *pfunc, *cfunc; | ||
| 64 | }; | 65 | }; |
| 65 | 66 | ||
| 66 | struct rela { | 67 | struct rela { |
| @@ -86,6 +87,7 @@ struct elf { | |||
| 86 | struct elf *elf_open(const char *name, int flags); | 87 | struct elf *elf_open(const char *name, int flags); |
| 87 | struct section *find_section_by_name(struct elf *elf, const char *name); | 88 | struct section *find_section_by_name(struct elf *elf, const char *name); |
| 88 | struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); | 89 | struct symbol *find_symbol_by_offset(struct section *sec, unsigned long offset); |
| 90 | struct symbol *find_symbol_by_name(struct elf *elf, const char *name); | ||
| 89 | struct symbol *find_symbol_containing(struct section *sec, unsigned long offset); | 91 | struct symbol *find_symbol_containing(struct section *sec, unsigned long offset); |
| 90 | struct rela *find_rela_by_dest(struct section *sec, unsigned long offset); | 92 | struct rela *find_rela_by_dest(struct section *sec, unsigned long offset); |
| 91 | struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset, | 93 | struct rela *find_rela_by_dest_range(struct section *sec, unsigned long offset, |
