// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> */ #include <string.h>#include <stdlib.h> #include <arch/elf.h>#include <objtool/builtin.h>#include <objtool/cfi.h>#include <objtool/arch.h>#include <objtool/check.h>#include <objtool/special.h>#include <objtool/warn.h>#include <objtool/endianness.h> #include <linux/objtool.h>#include <linux/hashtable.h>#include <linux/kernel.h>#include <linux/static_call_types.h> struct alternative { struct list_head list; struct instruction *insn; bool skip_orig;}; struct cfi_init_state initial_func_cfi; 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, sec_offset_hash(sec, 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 struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn){ struct instruction *next = list_next_entry(insn, list); struct symbol *func = insn->func; if (!func) return NULL; if (&next->list != &file->insn_list && next->func == func) return next; /* Check if we're already in the subfunction: */ if (func == func->cfunc) return NULL; /* Move to the subfunction: */ return find_insn(file, func->cfunc->sec, func->cfunc->offset);} static struct instruction *prev_insn_same_sym(struct objtool_file *file, struct instruction *insn){ struct instruction *prev = list_prev_entry(insn, list); if (&prev->list != &file->insn_list && prev->func == insn->func) return prev; return NULL;} #define func_for_each_insn(file, func, insn) \ for (insn = find_insn(file, func->sec, func->offset); \ insn; \ insn = next_insn_same_func(file, insn)) #define sym_for_each_insn(file, sym, insn) \ for (insn = find_insn(file, sym->sec, sym->offset); \ insn && &insn->list != &file->insn_list && \ insn->sec == sym->sec && \ insn->offset < sym->offset + sym->len; \ insn = list_next_entry(insn, list)) #define sym_for_each_insn_continue_reverse(file, sym, insn) \ for (insn = list_prev_entry(insn, list); \ &insn->list != &file->insn_list && \ insn->sec == sym->sec && insn->offset >= sym->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)) static bool is_jump_table_jump(struct instruction *insn){ struct alt_group *alt_group = insn->alt_group; if (insn->jump_table) return true; /* Retpoline alternative for a jump table? */ return alt_group && alt_group->orig_group && alt_group->orig_group->first_insn->jump_table;} static bool is_sibling_call(struct instruction *insn){ /* * Assume only ELF functions can make sibling calls. This ensures * sibling call detection consistency between vmlinux.o and individual * objects. */ if (!insn->func) return false; /* An indirect jump is either a sibling call or a jump to a table. */ if (insn->type == INSN_JUMP_DYNAMIC) return !is_jump_table_jump(insn); /* add_jump_destinations() sets insn->call_dest for sibling calls. */ return (is_static_jump(insn) && insn->call_dest);} /* * 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. */static bool __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", "__reiserfs_panic", "lbug_with_loc", "fortify_panic", "usercopy_abort", "machine_real_restart", "rewind_stack_do_exit", "kunit_try_catch_throw", "xen_start_kernel", }; if (!func) return false; if (func->bind == STB_WEAK) return false; if (func->bind == STB_GLOBAL) for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) if (!strcmp(func->name, global_noreturns[i])) return true; if (!func->len) return false; insn = find_insn(file, func->sec, func->offset); if (!insn->func) return false; func_for_each_insn(file, func, insn) { empty = false; if (insn->type == INSN_RETURN) return false; } if (empty) return false; /* * 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 (is_sibling_call(insn)) { struct instruction *dest = insn->jump_dest; if (!dest) /* sibling call to another file */ return false; /* local sibling call */ if (recursion == 5) { /* * Infinite recursion: two functions have * sibling calls to each other. This is a very * rare case. It means they aren't dead ends. */ return false; } return __dead_end_function(file, dest->func, recursion+1); } } return true;} static bool dead_end_function(struct objtool_file *file, struct symbol *func){ return __dead_end_function(file, func, 0);} static void init_cfi_state(struct cfi_state *cfi){ int i; for (i = 0; i < CFI_NUM_REGS; i++) { cfi->regs[i].base = CFI_UNDEFINED; cfi->vals[i].base = CFI_UNDEFINED; } cfi->cfa.base = CFI_UNDEFINED; cfi->drap_reg = CFI_UNDEFINED; cfi->drap_offset = -1;} static void init_insn_state(struct insn_state *state, struct section *sec){ memset(state, 0, sizeof(*state)); init_cfi_state(&state->cfi); /* * We need the full vmlinux for noinstr validation, otherwise we can * not correctly determine insn->call_dest->sec (external symbols do * not have a section). */ if (vmlinux && noinstr && sec) state->noinstr = sec->noinstr;} /* * 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; unsigned long nr_insns = 0; int ret; for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; if (strcmp(sec->name, ".altinstr_replacement") && strcmp(sec->name, ".altinstr_aux") && strncmp(sec->name, ".discard.", 9)) sec->text = true; if (!strcmp(sec->name, ".noinstr.text") || !strcmp(sec->name, ".entry.text")) sec->noinstr = true; 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); INIT_LIST_HEAD(&insn->stack_ops); init_cfi_state(&insn->cfi); 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_ops); if (ret) goto err; hash_add(file->insn_hash, &insn->hash, sec_offset_hash(sec, insn->offset)); list_add_tail(&insn->list, &file->insn_list); nr_insns++; } list_for_each_entry(func, &sec->symbol_list, list) { if (func->type != STT_FUNC || func->alias != func) continue; if (!find_insn(file, sec, func->offset)) { WARN("%s(): can't find starting instruction", func->name); return -1; } sym_for_each_insn(file, func, insn) insn->func = func; } } if (stats) printf("nr_insns: %lu\n", nr_insns); return 0; err: free(insn); return ret;} static struct instruction *find_last_insn(struct objtool_file *file, struct section *sec){ struct instruction *insn = NULL; unsigned int offset; unsigned int end = (sec->len > 10) ? sec->len - 10 : 0; for (offset = sec->len - 1; offset >= end && !insn; offset--) insn = find_insn(file, sec, offset); return insn;} /* * Mark "ud2" instructions and manually annotated dead ends. */static int add_dead_ends(struct objtool_file *file){ struct section *sec; struct reloc *reloc; struct instruction *insn; /* * By default, "ud2" is a dead end unless otherwise annotated, because * GCC 7 inserts it for certain divide-by-zero cases. */ for_each_insn(file, insn) if (insn->type == INSN_BUG) insn->dead_end = true; /* * Check for manually annotated dead ends. */ sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); if (!sec) goto reachable; list_for_each_entry(reloc, &sec->reloc_list, list) { if (reloc->sym->type != STT_SECTION) { WARN("unexpected relocation symbol type in %s", sec->name); return -1; } insn = find_insn(file, reloc->sym->sec, reloc->addend); if (insn) insn = list_prev_entry(insn, list); else if (reloc->addend == reloc->sym->sec->len) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { WARN("can't find unreachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } } else { WARN("can't find unreachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } insn->dead_end = true; } reachable: /* * These manually annotated reachable checks are needed for GCC 4.4, * where the Linux unreachable() macro isn't supported. In that case * GCC doesn't know the "ud2" is fatal, so it generates code as if it's * not a dead end. */ sec = find_section_by_name(file->elf, ".rela.discard.reachable"); if (!sec) return 0; list_for_each_entry(reloc, &sec->reloc_list, list) { if (reloc->sym->type != STT_SECTION) { WARN("unexpected relocation symbol type in %s", sec->name); return -1; } insn = find_insn(file, reloc->sym->sec, reloc->addend); if (insn) insn = list_prev_entry(insn, list); else if (reloc->addend == reloc->sym->sec->len) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { WARN("can't find reachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } } else { WARN("can't find reachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } insn->dead_end = false; } return 0;} static int create_static_call_sections(struct objtool_file *file){ struct section *sec; struct static_call_site *site; struct instruction *insn; struct symbol *key_sym; char *key_name, *tmp; int idx; sec = find_section_by_name(file->elf, ".static_call_sites"); if (sec) { INIT_LIST_HEAD(&file->static_call_list); WARN("file already has .static_call_sites section, skipping"); return 0; } if (list_empty(&file->static_call_list)) return 0; idx = 0; list_for_each_entry(insn, &file->static_call_list, call_node) idx++; sec = elf_create_section(file->elf, ".static_call_sites", SHF_WRITE, sizeof(struct static_call_site), idx); if (!sec) return -1; idx = 0; list_for_each_entry(insn, &file->static_call_list, call_node) { site = (struct static_call_site *)sec->data->d_buf + idx; memset(site, 0, sizeof(struct static_call_site)); /* populate reloc for 'addr' */ if (elf_add_reloc_to_insn(file->elf, sec, idx * sizeof(struct static_call_site), R_X86_64_PC32, insn->sec, insn->offset)) return -1; /* find key symbol */ key_name = strdup(insn->call_dest->name); if (!key_name) { perror("strdup"); return -1; } if (strncmp(key_name, STATIC_CALL_TRAMP_PREFIX_STR, STATIC_CALL_TRAMP_PREFIX_LEN)) { WARN("static_call: trampoline name malformed: %s", key_name); return -1; } tmp = key_name + STATIC_CALL_TRAMP_PREFIX_LEN - STATIC_CALL_KEY_PREFIX_LEN; memcpy(tmp, STATIC_CALL_KEY_PREFIX_STR, STATIC_CALL_KEY_PREFIX_LEN); Lines 1–500 of 3170. Load more
// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> */ #include <string.h> #include <stdlib.h> #include <arch/elf.h> #include <objtool/builtin.h> #include <objtool/cfi.h> #include <objtool/arch.h> #include <objtool/check.h> #include <objtool/special.h> #include <objtool/warn.h> #include <objtool/endianness.h> #include <linux/objtool.h> #include <linux/hashtable.h> #include <linux/kernel.h> #include <linux/static_call_types.h> struct alternative { struct list_head list; struct instruction *insn; bool skip_orig; }; struct cfi_init_state initial_func_cfi; 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, sec_offset_hash(sec, 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 struct instruction *next_insn_same_func(struct objtool_file *file, struct instruction *insn) { struct instruction *next = list_next_entry(insn, list); struct symbol *func = insn->func; if (!func) return NULL; if (&next->list != &file->insn_list && next->func == func) return next; /* Check if we're already in the subfunction: */ if (func == func->cfunc) return NULL; /* Move to the subfunction: */ return find_insn(file, func->cfunc->sec, func->cfunc->offset); } static struct instruction *prev_insn_same_sym(struct objtool_file *file, struct instruction *insn) { struct instruction *prev = list_prev_entry(insn, list); if (&prev->list != &file->insn_list && prev->func == insn->func) return prev; return NULL; } #define func_for_each_insn(file, func, insn) \ for (insn = find_insn(file, func->sec, func->offset); \ insn; \ insn = next_insn_same_func(file, insn)) #define sym_for_each_insn(file, sym, insn) \ for (insn = find_insn(file, sym->sec, sym->offset); \ insn && &insn->list != &file->insn_list && \ insn->sec == sym->sec && \ insn->offset < sym->offset + sym->len; \ insn = list_next_entry(insn, list)) #define sym_for_each_insn_continue_reverse(file, sym, insn) \ for (insn = list_prev_entry(insn, list); \ &insn->list != &file->insn_list && \ insn->sec == sym->sec && insn->offset >= sym->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)) static bool is_jump_table_jump(struct instruction *insn) { struct alt_group *alt_group = insn->alt_group; if (insn->jump_table) return true; /* Retpoline alternative for a jump table? */ return alt_group && alt_group->orig_group && alt_group->orig_group->first_insn->jump_table; } static bool is_sibling_call(struct instruction *insn) { /* * Assume only ELF functions can make sibling calls. This ensures * sibling call detection consistency between vmlinux.o and individual * objects. */ if (!insn->func) return false; /* An indirect jump is either a sibling call or a jump to a table. */ if (insn->type == INSN_JUMP_DYNAMIC) return !is_jump_table_jump(insn); /* add_jump_destinations() sets insn->call_dest for sibling calls. */ return (is_static_jump(insn) && insn->call_dest); } /* * 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. */ static bool __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", "__reiserfs_panic", "lbug_with_loc", "fortify_panic", "usercopy_abort", "machine_real_restart", "rewind_stack_do_exit", "kunit_try_catch_throw", "xen_start_kernel", }; if (!func) return false; if (func->bind == STB_WEAK) return false; if (func->bind == STB_GLOBAL) for (i = 0; i < ARRAY_SIZE(global_noreturns); i++) if (!strcmp(func->name, global_noreturns[i])) return true; if (!func->len) return false; insn = find_insn(file, func->sec, func->offset); if (!insn->func) return false; func_for_each_insn(file, func, insn) { empty = false; if (insn->type == INSN_RETURN) return false; } if (empty) return false; /* * 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 (is_sibling_call(insn)) { struct instruction *dest = insn->jump_dest; if (!dest) /* sibling call to another file */ return false; /* local sibling call */ if (recursion == 5) { /* * Infinite recursion: two functions have * sibling calls to each other. This is a very * rare case. It means they aren't dead ends. */ return false; } return __dead_end_function(file, dest->func, recursion+1); } } return true; } static bool dead_end_function(struct objtool_file *file, struct symbol *func) { return __dead_end_function(file, func, 0); } static void init_cfi_state(struct cfi_state *cfi) { int i; for (i = 0; i < CFI_NUM_REGS; i++) { cfi->regs[i].base = CFI_UNDEFINED; cfi->vals[i].base = CFI_UNDEFINED; } cfi->cfa.base = CFI_UNDEFINED; cfi->drap_reg = CFI_UNDEFINED; cfi->drap_offset = -1; } static void init_insn_state(struct insn_state *state, struct section *sec) { memset(state, 0, sizeof(*state)); init_cfi_state(&state->cfi); /* * We need the full vmlinux for noinstr validation, otherwise we can * not correctly determine insn->call_dest->sec (external symbols do * not have a section). */ if (vmlinux && noinstr && sec) state->noinstr = sec->noinstr; } /* * 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; unsigned long nr_insns = 0; int ret; for_each_sec(file, sec) { if (!(sec->sh.sh_flags & SHF_EXECINSTR)) continue; if (strcmp(sec->name, ".altinstr_replacement") && strcmp(sec->name, ".altinstr_aux") && strncmp(sec->name, ".discard.", 9)) sec->text = true; if (!strcmp(sec->name, ".noinstr.text") || !strcmp(sec->name, ".entry.text")) sec->noinstr = true; 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); INIT_LIST_HEAD(&insn->stack_ops); init_cfi_state(&insn->cfi); 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_ops); if (ret) goto err; hash_add(file->insn_hash, &insn->hash, sec_offset_hash(sec, insn->offset)); list_add_tail(&insn->list, &file->insn_list); nr_insns++; } list_for_each_entry(func, &sec->symbol_list, list) { if (func->type != STT_FUNC || func->alias != func) continue; if (!find_insn(file, sec, func->offset)) { WARN("%s(): can't find starting instruction", func->name); return -1; } sym_for_each_insn(file, func, insn) insn->func = func; } } if (stats) printf("nr_insns: %lu\n", nr_insns); return 0; err: free(insn); return ret; } static struct instruction *find_last_insn(struct objtool_file *file, struct section *sec) { struct instruction *insn = NULL; unsigned int offset; unsigned int end = (sec->len > 10) ? sec->len - 10 : 0; for (offset = sec->len - 1; offset >= end && !insn; offset--) insn = find_insn(file, sec, offset); return insn; } /* * Mark "ud2" instructions and manually annotated dead ends. */ static int add_dead_ends(struct objtool_file *file) { struct section *sec; struct reloc *reloc; struct instruction *insn; /* * By default, "ud2" is a dead end unless otherwise annotated, because * GCC 7 inserts it for certain divide-by-zero cases. */ for_each_insn(file, insn) if (insn->type == INSN_BUG) insn->dead_end = true; /* * Check for manually annotated dead ends. */ sec = find_section_by_name(file->elf, ".rela.discard.unreachable"); if (!sec) goto reachable; list_for_each_entry(reloc, &sec->reloc_list, list) { if (reloc->sym->type != STT_SECTION) { WARN("unexpected relocation symbol type in %s", sec->name); return -1; } insn = find_insn(file, reloc->sym->sec, reloc->addend); if (insn) insn = list_prev_entry(insn, list); else if (reloc->addend == reloc->sym->sec->len) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { WARN("can't find unreachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } } else { WARN("can't find unreachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } insn->dead_end = true; } reachable: /* * These manually annotated reachable checks are needed for GCC 4.4, * where the Linux unreachable() macro isn't supported. In that case * GCC doesn't know the "ud2" is fatal, so it generates code as if it's * not a dead end. */ sec = find_section_by_name(file->elf, ".rela.discard.reachable"); if (!sec) return 0; list_for_each_entry(reloc, &sec->reloc_list, list) { if (reloc->sym->type != STT_SECTION) { WARN("unexpected relocation symbol type in %s", sec->name); return -1; } insn = find_insn(file, reloc->sym->sec, reloc->addend); if (insn) insn = list_prev_entry(insn, list); else if (reloc->addend == reloc->sym->sec->len) { insn = find_last_insn(file, reloc->sym->sec); if (!insn) { WARN("can't find reachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } } else { WARN("can't find reachable insn at %s+0x%x", reloc->sym->sec->name, reloc->addend); return -1; } insn->dead_end = false; } return 0; } static int create_static_call_sections(struct objtool_file *file) { struct section *sec; struct static_call_site *site; struct instruction *insn; struct symbol *key_sym; char *key_name, *tmp; int idx; sec = find_section_by_name(file->elf, ".static_call_sites"); if (sec) { INIT_LIST_HEAD(&file->static_call_list); WARN("file already has .static_call_sites section, skipping"); return 0; } if (list_empty(&file->static_call_list)) return 0; idx = 0; list_for_each_entry(insn, &file->static_call_list, call_node) idx++; sec = elf_create_section(file->elf, ".static_call_sites", SHF_WRITE, sizeof(struct static_call_site), idx); if (!sec) return -1; idx = 0; list_for_each_entry(insn, &file->static_call_list, call_node) { site = (struct static_call_site *)sec->data->d_buf + idx; memset(site, 0, sizeof(struct static_call_site)); /* populate reloc for 'addr' */ if (elf_add_reloc_to_insn(file->elf, sec, idx * sizeof(struct static_call_site), R_X86_64_PC32, insn->sec, insn->offset)) return -1; /* find key symbol */ key_name = strdup(insn->call_dest->name); if (!key_name) { perror("strdup"); return -1; } if (strncmp(key_name, STATIC_CALL_TRAMP_PREFIX_STR, STATIC_CALL_TRAMP_PREFIX_LEN)) { WARN("static_call: trampoline name malformed: %s", key_name); return -1; } tmp = key_name + STATIC_CALL_TRAMP_PREFIX_LEN - STATIC_CALL_KEY_PREFIX_LEN; memcpy(tmp, STATIC_CALL_KEY_PREFIX_STR, STATIC_CALL_KEY_PREFIX_LEN);