| // SPDX-License-Identifier: GPL-2.0-or-later | |
| #include <string.h> | |
| #include <subcmd/parse-options.h> | |
| #include <objtool/arch.h> | |
| #include <objtool/builtin.h> | |
| #include <objtool/check.h> | |
| #include <objtool/elf.h> | |
| #include <objtool/klp.h> | |
| #include <objtool/objtool.h> | |
| #include <objtool/warn.h> | |
| #include <objtool/checksum.h> | |
| static int checksum_debug_init(struct objtool_file *file) | |
| { | |
| char *dup, *s; | |
| if (!opts.debug_checksum) | |
| return 0; | |
| dup = strdup(opts.debug_checksum); | |
| if (!dup) { | |
| ERROR_GLIBC("strdup"); | |
| return -1; | |
| } | |
| s = dup; | |
| while (*s) { | |
| bool found = false; | |
| struct symbol *sym; | |
| char *comma; | |
| comma = strchr(s, ','); | |
| if (comma) | |
| *comma = '\0'; | |
| for_each_sym_by_name(file->elf, s, sym) { | |
| if (!is_func_sym(sym) && !is_object_sym(sym)) | |
| continue; | |
| sym->debug_checksum = 1; | |
| found = true; | |
| } | |
| if (!found) | |
| WARN("--debug-checksum: can't find '%s'", s); | |
| if (!comma) | |
| break; | |
| s = comma + 1; | |
| } | |
| free(dup); | |
| return 0; | |
| } | |
| static void checksum_update_insn(struct objtool_file *file, struct symbol *func, | |
| struct instruction *insn) | |
| { | |
| struct reloc *reloc = insn_reloc(file, insn); | |
| struct alternative *alt; | |
| unsigned long offset; | |
| struct symbol *sym; | |
| static bool in_alt; | |
| if (insn->fake) | |
| return; | |
| if (!reloc) { | |
| struct symbol *call_dest = insn_call_dest(insn); | |
| struct instruction *jump_dest = insn->jump_dest; | |
| /* | |
| * For a jump/call non-relocated dest offset embedded in the | |
| * instruction, the offset may vary due to changes in | |
| * surrounding code. Just hash the opcode and a | |
| * position-independent representation of the destination. | |
| */ | |
| if (call_dest || jump_dest) { | |
| unsigned char buf[16]; | |
| size_t len; | |
| len = arch_jump_opcode_bytes(file, insn, buf); | |
| __checksum_update_insn(func, insn, buf, len); | |
| if (call_dest) { | |
| __checksum_update_insn(func, insn, call_dest->demangled_name, | |
| strlen(call_dest->demangled_name)); | |
| } else if (jump_dest) { | |
| struct symbol *dest_sym; | |
| unsigned long offset; | |
| /* | |
| * use insn->_sym instead of insn_sym() here. | |
| * For alternative replacements, the latter | |
| * would give the function of the code being | |
| * replaced. | |
| */ | |
| dest_sym = jump_dest->_sym; | |
| if (!dest_sym) | |
| goto alts; | |
| __checksum_update_insn(func, insn, dest_sym->demangled_name, | |
| strlen(dest_sym->demangled_name)); | |
| offset = jump_dest->offset - dest_sym->offset; | |
| __checksum_update_insn(func, insn, &offset, sizeof(offset)); | |
| } | |
| goto alts; | |
| } | |
| } | |
| __checksum_update_insn(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); | |
| if (!reloc) | |
| goto alts; | |
| sym = reloc->sym; | |
| offset = arch_insn_adjusted_addend(insn, reloc); | |
| if (is_string_sec(sym->sec)) { | |
| char *str; | |
| str = sym->sec->data->d_buf + sym->offset + offset; | |
| __checksum_update_insn(func, insn, str, strlen(str)); | |
| goto alts; | |
| } | |
| if (is_sec_sym(sym)) { | |
| sym = find_symbol_containing(reloc->sym->sec, offset); | |
| if (!sym) | |
| goto alts; | |
| offset -= sym->offset; | |
| } | |
| __checksum_update_insn(func, insn, sym->demangled_name, | |
| strlen(sym->demangled_name)); | |
| __checksum_update_insn(func, insn, &offset, sizeof(offset)); | |
| alts: | |
| for (alt = insn->alts; alt; alt = alt->next) { | |
| struct alt_group *alt_group = alt->insn->alt_group; | |
| /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ | |
| if (in_alt) | |
| break; | |
| in_alt = true; | |
| __checksum_update_insn(func, insn, &alt->type, | |
| sizeof(alt->type)); | |
| if (alt_group && alt_group->orig_group) { | |
| struct instruction *alt_insn; | |
| __checksum_update_insn(func, insn, &alt_group->feature,sizeof(alt_group->feature)); | |
| for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { | |
| checksum_update_insn(file, func, alt_insn); | |
| if (!alt_group->last_insn || alt_insn == alt_group->last_insn) | |
| break; | |
| } | |
| } else { | |
| checksum_update_insn(file, func, alt->insn); | |
| } | |
| in_alt = false; | |
| } | |
| } | |
| static void checksum_update_object(struct objtool_file *file, struct symbol *sym) | |
| { | |
| struct reloc *reloc; | |
| __checksum_update_object(sym, 0, "len", &sym->len, sizeof(sym->len)); | |
| if (sym->sec->data->d_buf) | |
| __checksum_update_object(sym, 0, "data", | |
| sym->sec->data->d_buf + sym->offset, | |
| sym->len); | |
| sym_for_each_reloc(file->elf, sym, reloc) { | |
| unsigned long sym_offset = reloc_offset(reloc) - sym->offset; | |
| struct symbol *target = reloc->sym; | |
| s64 offset; | |
| offset = reloc_addend(reloc); | |
| if (is_string_sec(target->sec)) { | |
| char *str; | |
| str = target->sec->data->d_buf + target->offset + offset; | |
| __checksum_update_object(sym, sym_offset, | |
| "reloc string", str, strlen(str)); | |
| continue; | |
| } | |
| if (is_sec_sym(target)) { | |
| target = find_symbol_containing(reloc->sym->sec, offset); | |
| if (!target) | |
| continue; | |
| offset -= target->offset; | |
| } | |
| __checksum_update_object(sym, sym_offset, "reloc name", | |
| target->demangled_name, | |
| strlen(target->demangled_name)); | |
| __checksum_update_object(sym, sym_offset, "reloc addend", | |
| &offset, sizeof(offset)); | |
| } | |
| } | |
| int calculate_checksums(struct objtool_file *file) | |
| { | |
| struct instruction *insn; | |
| struct symbol *sym; | |
| if (checksum_debug_init(file)) | |
| return -1; | |
| for_each_sym(file->elf, sym) { | |
| /* | |
| * Skip cold subfunctions and aliases: they share the | |
| * parent's checksum via func_for_each_insn() which | |
| * follows func->cfunc into the cold subfunction. | |
| */ | |
| if (is_cold_func(sym) || is_alias_sym(sym) || !sym->len || | |
| !sym->sec || !sym->sec->data) | |
| continue; | |
| if (is_func_sym(sym)) { | |
| checksum_init(sym); | |
| func_for_each_insn(file, sym, insn) | |
| checksum_update_insn(file, sym, insn); | |
| checksum_finish(sym); | |
| } else if (is_object_sym(sym)) { | |
| checksum_init(sym); | |
| checksum_update_object(file, sym); | |
| checksum_finish(sym); | |
| } | |
| } | |
| return 0; | |
| } | |
| int create_sym_checksum_section(struct objtool_file *file) | |
| { | |
| struct section *sec; | |
| struct symbol *sym; | |
| unsigned int idx = 0; | |
| struct sym_checksum *checksum; | |
| size_t entsize = sizeof(struct sym_checksum); | |
| sec = find_section_by_name(file->elf, ".discard.sym_checksum"); | |
| if (sec) { | |
| if (!opts.dryrun) | |
| WARN("file already has .discard.sym_checksum section, skipping"); | |
| return 0; | |
| } | |
| for_each_sym(file->elf, sym) | |
| if (sym->csum.checksum) | |
| idx++; | |
| sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, | |
| idx, idx); | |
| if (!sec) | |
| return -1; | |
| idx = 0; | |
| for_each_sym(file->elf, sym) { | |
| if (!sym->csum.checksum) | |
| continue; | |
| if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize, | |
| sym, 0, R_TEXT64)) | |
| return -1; | |
| checksum = (struct sym_checksum *)sec->data->d_buf + idx; | |
| checksum->addr = 0; /* reloc */ | |
| checksum->checksum = sym->csum.checksum; | |
| mark_sec_changed(file->elf, sec, true); | |
| idx++; | |
| } | |
| return 0; | |
| } | |
| static const char * const klp_checksum_usage[] = { | |
| "objtool klp checksum [<options>] file.o", | |
| NULL, | |
| }; | |
| int cmd_klp_checksum(int argc, const char **argv) | |
| { | |
| struct objtool_file *file; | |
| int ret; | |
| const struct option options[] = { | |
| OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "syms", "enable checksum debug output"), | |
| OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), | |
| OPT_END(), | |
| }; | |
| argc = parse_options(argc, argv, options, klp_checksum_usage, 0); | |
| if (argc != 1) | |
| usage_with_options(klp_checksum_usage, options); | |
| opts.checksum = true; | |
| objname = argv[0]; | |
| file = objtool_open_read(objname); | |
| if (!file) | |
| return 1; | |
| ret = decode_file(file); | |
| if (ret) | |
| goto out; | |
| ret = calculate_checksums(file); | |
| if (ret) | |
| goto out; | |
| ret = create_sym_checksum_section(file); | |
| out: | |
| free_insns(file); | |
| if (ret) | |
| return ret; | |
| if (!opts.dryrun && file->elf->changed && elf_write(file->elf)) | |
| return 1; | |
| return elf_close(file->elf); | |
| } |