Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369// 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;}
/* * Detect a reference to anonymous constant pool data which the compiler places * in .rodata.cst<num> and which either has an .LC<num> symbol associated with * it or (with Clang) no symbol at all. These are typically initializers for * local function stack data, so they're considered part of the function rather * than data per se. */static bool is_anonymous_const_data(struct symbol *sym){ return strstarts(sym->sec->name, ".rodata.cst") && (is_sec_sym(sym) || strstarts(sym->name, ".LC"));}
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_anonymous_const_data(sym)) { void *cst;
cst = sym->sec->data->d_buf + sym->offset + offset; __checksum_update_insn(func, insn, cst, sym->sec->sh.sh_entsize); 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);}