Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

objtool: Fix address references in alternatives

When using the --disas option, alternatives are disassembled but
address references in non-default alternatives can be incorrect.

The problem is that alternatives are shown as if they were replacing the
original code of the alternative. So if an alternative is referencing
an address inside the alternative then the reference has to be
adjusted to the location of the original code.

Signed-off-by: Alexandre Chartre <alexandre.chartre@oracle.com>
Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org>
Acked-by: Josh Poimboeuf <jpoimboe@kernel.org>
Link: https://patch.msgid.link/20251121095340.464045-25-alexandre.chartre@oracle.com

authored by

Alexandre Chartre and committed by
Peter Zijlstra
4aae0d3f 7e017720

+66 -4
+66 -4
tools/objtool/disas.c
··· 24 24 struct disas_context { 25 25 struct objtool_file *file; 26 26 struct instruction *insn; 27 + bool alt_applied; 27 28 char result[DISAS_RESULT_SIZE]; 28 29 disassembler_ftype disassembler; 29 30 struct disassemble_info info; ··· 161 160 } 162 161 } 163 162 163 + static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo) 164 + { 165 + struct disas_context *dctx = dinfo->application_data; 166 + struct instruction *orig_first_insn; 167 + struct alt_group *alt_group; 168 + unsigned long offset; 169 + struct symbol *sym; 170 + 171 + /* 172 + * Check if we are processing an alternative at the original 173 + * instruction address (i.e. if alt_applied is true) and if 174 + * we are referencing an address inside the alternative. 175 + * 176 + * For example, this happens if there is a branch inside an 177 + * alternative. In that case, the address should be updated 178 + * to a reference inside the original instruction flow. 179 + */ 180 + if (!dctx->alt_applied) 181 + return false; 182 + 183 + alt_group = dctx->insn->alt_group; 184 + if (!alt_group || !alt_group->orig_group || 185 + addr < alt_group->first_insn->offset || 186 + addr > alt_group->last_insn->offset) 187 + return false; 188 + 189 + orig_first_insn = alt_group->orig_group->first_insn; 190 + offset = addr - alt_group->first_insn->offset; 191 + 192 + addr = orig_first_insn->offset + offset; 193 + sym = orig_first_insn->sym; 194 + 195 + disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo); 196 + 197 + return true; 198 + } 199 + 164 200 static void disas_print_addr_noreloc(bfd_vma addr, 165 201 struct disassemble_info *dinfo) 166 202 { 167 203 struct disas_context *dctx = dinfo->application_data; 168 204 struct instruction *insn = dctx->insn; 169 205 struct symbol *sym = NULL; 206 + 207 + if (disas_print_addr_alt(addr, dinfo)) 208 + return; 170 209 171 210 if (insn->sym && addr >= insn->sym->offset && 172 211 addr < insn->sym->offset + insn->sym->len) { ··· 273 232 */ 274 233 jump_dest = insn->jump_dest; 275 234 if (jump_dest && jump_dest->sym && jump_dest->offset == addr) { 276 - disas_print_addr_sym(jump_dest->sec, jump_dest->sym, 277 - addr, dinfo); 235 + if (!disas_print_addr_alt(addr, dinfo)) 236 + disas_print_addr_sym(jump_dest->sec, jump_dest->sym, 237 + addr, dinfo); 278 238 return; 279 239 } 280 240 ··· 532 490 533 491 /* 534 492 * Disassemble a single instruction. Return the size of the instruction. 493 + * 494 + * If alt_applied is true then insn should be an instruction from of an 495 + * alternative (i.e. insn->alt_group != NULL), and it is disassembled 496 + * at the location of the original code it is replacing. When the 497 + * instruction references any address inside the alternative then 498 + * these references will be re-adjusted to replace the original code. 535 499 */ 536 - size_t disas_insn(struct disas_context *dctx, struct instruction *insn) 500 + static size_t disas_insn_common(struct disas_context *dctx, 501 + struct instruction *insn, 502 + bool alt_applied) 537 503 { 538 504 disassembler_ftype disasm = dctx->disassembler; 539 505 struct disassemble_info *dinfo = &dctx->info; 540 506 541 507 dctx->insn = insn; 508 + dctx->alt_applied = alt_applied; 542 509 dctx->result[0] = '\0'; 543 510 544 511 if (insn->type == INSN_NOP) { ··· 564 513 dinfo->buffer_length = insn->sec->sh.sh_size; 565 514 566 515 return disasm(insn->offset, &dctx->info); 516 + } 517 + 518 + size_t disas_insn(struct disas_context *dctx, struct instruction *insn) 519 + { 520 + return disas_insn_common(dctx, insn, false); 521 + } 522 + 523 + static size_t disas_insn_alt(struct disas_context *dctx, 524 + struct instruction *insn) 525 + { 526 + return disas_insn_common(dctx, insn, true); 567 527 } 568 528 569 529 static struct instruction *next_insn_same_alt(struct objtool_file *file, ··· 768 706 769 707 alt_for_each_insn(file, DALT_GROUP(dalt), insn) { 770 708 771 - disas_insn(dctx, insn); 709 + disas_insn_alt(dctx, insn); 772 710 str = strdup(disas_result(dctx)); 773 711 if (!str) 774 712 return -1;