1 // SPDX-License-Identifier: GPL-2.0-or-later 2 #include <string.h> 3 #include <subcmd/parse-options.h> 4 5 #include <objtool/arch.h> 6 #include <objtool/builtin.h> 7 #include <objtool/check.h> 8 #include <objtool/elf.h> 9 #include <objtool/klp.h> 10 #include <objtool/objtool.h> 11 #include <objtool/warn.h> 12 #include <objtool/checksum.h> 13 14 static int checksum_debug_init(struct objtool_file *file) 15 { 16 char *dup, *s; 17 18 if (!opts.debug_checksum) 19 return 0; 20 21 dup = strdup(opts.debug_checksum); 22 if (!dup) { 23 ERROR_GLIBC("strdup"); 24 return -1; 25 } 26 27 s = dup; 28 while (*s) { 29 bool found = false; 30 struct symbol *sym; 31 char *comma; 32 33 comma = strchr(s, ','); 34 if (comma) 35 *comma = '\0'; 36 37 for_each_sym_by_name(file->elf, s, sym) { 38 if (!is_func_sym(sym)) 39 continue; 40 sym->debug_checksum = 1; 41 found = true; 42 } 43 44 if (!found) 45 WARN("--debug-checksum: can't find '%s'", s); 46 47 if (!comma) 48 break; 49 50 s = comma + 1; 51 } 52 53 free(dup); 54 return 0; 55 } 56 57 static void checksum_update_insn(struct objtool_file *file, struct symbol *func, 58 struct instruction *insn) 59 { 60 struct reloc *reloc = insn_reloc(file, insn); 61 struct alternative *alt; 62 unsigned long offset; 63 struct symbol *sym; 64 static bool in_alt; 65 66 if (insn->fake) 67 return; 68 69 checksum_update(func, insn, insn->sec->data->d_buf + insn->offset, insn->len); 70 71 if (!reloc) { 72 struct symbol *call_dest = insn_call_dest(insn); 73 74 if (call_dest) 75 checksum_update(func, insn, call_dest->demangled_name, 76 strlen(call_dest->demangled_name)); 77 goto alts; 78 } 79 80 sym = reloc->sym; 81 offset = arch_insn_adjusted_addend(insn, reloc); 82 83 if (is_string_sec(sym->sec)) { 84 char *str; 85 86 str = sym->sec->data->d_buf + sym->offset + offset; 87 checksum_update(func, insn, str, strlen(str)); 88 goto alts; 89 } 90 91 if (is_sec_sym(sym)) { 92 sym = find_symbol_containing(reloc->sym->sec, offset); 93 if (!sym) 94 goto alts; 95 96 offset -= sym->offset; 97 } 98 99 checksum_update(func, insn, sym->demangled_name, strlen(sym->demangled_name)); 100 checksum_update(func, insn, &offset, sizeof(offset)); 101 102 alts: 103 for (alt = insn->alts; alt; alt = alt->next) { 104 struct alt_group *alt_group = alt->insn->alt_group; 105 106 /* Prevent __ex_table recursion, e.g. LOAD_SEGMENT() */ 107 if (in_alt) 108 break; 109 in_alt = true; 110 111 checksum_update(func, insn, &alt->type, sizeof(alt->type)); 112 113 if (alt_group && alt_group->orig_group) { 114 struct instruction *alt_insn; 115 116 checksum_update(func, insn, &alt_group->feature, sizeof(alt_group->feature)); 117 118 for (alt_insn = alt->insn; alt_insn; alt_insn = next_insn_same_sec(file, alt_insn)) { 119 checksum_update_insn(file, func, alt_insn); 120 if (!alt_group->last_insn || alt_insn == alt_group->last_insn) 121 break; 122 } 123 } else { 124 checksum_update_insn(file, func, alt->insn); 125 } 126 127 in_alt = false; 128 } 129 } 130 131 int calculate_checksums(struct objtool_file *file) 132 { 133 struct instruction *insn; 134 struct symbol *func; 135 136 if (checksum_debug_init(file)) 137 return -1; 138 139 for_each_sym(file->elf, func) { 140 /* 141 * Skip cold subfunctions and aliases: they share the 142 * parent's checksum via func_for_each_insn() which 143 * follows func->cfunc into the cold subfunction. 144 */ 145 if (!is_func_sym(func) || is_cold_func(func) || 146 is_alias_sym(func) || !func->len) 147 continue; 148 149 checksum_init(func); 150 151 func_for_each_insn(file, func, insn) 152 checksum_update_insn(file, func, insn); 153 154 checksum_finish(func); 155 } 156 return 0; 157 } 158 159 int create_sym_checksum_section(struct objtool_file *file) 160 { 161 struct section *sec; 162 struct symbol *sym; 163 unsigned int idx = 0; 164 struct sym_checksum *checksum; 165 size_t entsize = sizeof(struct sym_checksum); 166 167 sec = find_section_by_name(file->elf, ".discard.sym_checksum"); 168 if (sec) { 169 if (!opts.dryrun) 170 WARN("file already has .discard.sym_checksum section, skipping"); 171 172 return 0; 173 } 174 175 for_each_sym(file->elf, sym) 176 if (sym->csum.checksum) 177 idx++; 178 179 sec = elf_create_section_pair(file->elf, ".discard.sym_checksum", entsize, 180 idx, idx); 181 if (!sec) 182 return -1; 183 184 idx = 0; 185 for_each_sym(file->elf, sym) { 186 if (!sym->csum.checksum) 187 continue; 188 189 if (!elf_init_reloc(file->elf, sec->rsec, idx, idx * entsize, 190 sym, 0, R_TEXT64)) 191 return -1; 192 193 checksum = (struct sym_checksum *)sec->data->d_buf + idx; 194 checksum->addr = 0; /* reloc */ 195 checksum->checksum = sym->csum.checksum; 196 197 mark_sec_changed(file->elf, sec, true); 198 199 idx++; 200 } 201 202 return 0; 203 } 204 205 static const char * const klp_checksum_usage[] = { 206 "objtool klp checksum [<options>] file.o", 207 NULL, 208 }; 209 210 int cmd_klp_checksum(int argc, const char **argv) 211 { 212 struct objtool_file *file; 213 int ret; 214 215 const struct option options[] = { 216 OPT_STRING(0, "debug-checksum", &opts.debug_checksum, "funcs", "enable checksum debug output"), 217 OPT_BOOLEAN(0, "dry-run", &opts.dryrun, "don't write modifications"), 218 OPT_END(), 219 }; 220 221 argc = parse_options(argc, argv, options, klp_checksum_usage, 0); 222 if (argc != 1) 223 usage_with_options(klp_checksum_usage, options); 224 225 opts.checksum = true; 226 227 objname = argv[0]; 228 229 file = objtool_open_read(objname); 230 if (!file) 231 return 1; 232 233 ret = decode_file(file); 234 if (ret) 235 goto out; 236 237 ret = calculate_checksums(file); 238 if (ret) 239 goto out; 240 241 ret = create_sym_checksum_section(file); 242 243 out: 244 free_insns(file); 245 246 if (ret) 247 return ret; 248 249 if (!opts.dryrun && file->elf->changed && elf_write(file->elf)) 250 return 1; 251 252 return elf_close(file->elf); 253 } 254