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
kernel os linux
Something went wrong. Try again.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249// SPDX-License-Identifier: GPL-2.0-or-later/* * Copyright (C) 2015-2017 Josh Poimboeuf <jpoimboe@redhat.com> */
#define _GNU_SOURCE#include <fnmatch.h>
#include <objtool/arch.h>#include <objtool/check.h>#include <objtool/disas.h>#include <objtool/special.h>#include <objtool/warn.h>
#include <bfd.h>#include <linux/string.h>#include <tools/dis-asm-compat.h>
/* * Size of the buffer for storing the result of disassembling * a single instruction. */#define DISAS_RESULT_SIZE 1024
struct disas_context { struct objtool_file *file; struct instruction *insn; bool alt_applied; char result[DISAS_RESULT_SIZE]; disassembler_ftype disassembler; struct disassemble_info info;};
/* * Maximum number of alternatives */#define DISAS_ALT_MAX 5
/* * Maximum number of instructions per alternative */#define DISAS_ALT_INSN_MAX 50
/* * Information to disassemble an alternative */struct disas_alt { struct instruction *orig_insn; /* original instruction */ struct alternative *alt; /* alternative or NULL if default code */ char *name; /* name for this alternative */ int width; /* formatting width */ struct { char *str; /* instruction string */ int offset; /* instruction offset */ int nops; /* number of nops */ } insn[DISAS_ALT_INSN_MAX]; /* alternative instructions */ int insn_idx; /* index of the next instruction to print */};
#define DALT_DEFAULT(dalt) (!(dalt)->alt)#define DALT_INSN(dalt) (DALT_DEFAULT(dalt) ? (dalt)->orig_insn : (dalt)->alt->insn)#define DALT_GROUP(dalt) (DALT_INSN(dalt)->alt_group)#define DALT_ALTID(dalt) ((dalt)->orig_insn->offset)
#define ALT_FLAGS_SHIFT 16#define ALT_FLAG_NOT (1 << 0)#define ALT_FLAG_DIRECT_CALL (1 << 1)#define ALT_FEATURE_MASK ((1 << ALT_FLAGS_SHIFT) - 1)
static int alt_feature(unsigned int ft_flags){ return (ft_flags & ALT_FEATURE_MASK);}
static int alt_flags(unsigned int ft_flags){ return (ft_flags >> ALT_FLAGS_SHIFT);}
/* * Wrapper around asprintf() to allocate and format a string. * Return the allocated string or NULL on error. */static char *strfmt(const char *fmt, ...){ va_list ap; char *str; int rv;
va_start(ap, fmt); rv = vasprintf(&str, fmt, ap); va_end(ap);
return rv == -1 ? NULL : str;}
static int sprint_name(char *str, const char *name, unsigned long offset){ int len;
if (offset) len = sprintf(str, "%s+0x%lx", name, offset); else len = sprintf(str, "%s", name);
return len;}
#define DINFO_FPRINTF(dinfo, ...) \ ((*(dinfo)->fprintf_func)((dinfo)->stream, __VA_ARGS__))#define bfd_vma_fmt \ __builtin_choose_expr(sizeof(bfd_vma) == sizeof(unsigned long), "%#lx <%s>", "%#llx <%s>")
static int disas_result_fprintf(struct disas_context *dctx, const char *fmt, va_list ap){ char *buf = dctx->result; int avail, len;
len = strlen(buf); if (len >= DISAS_RESULT_SIZE - 1) { WARN_FUNC(dctx->insn->sec, dctx->insn->offset, "disassembly buffer is full"); return -1; } avail = DISAS_RESULT_SIZE - len;
len = vsnprintf(buf + len, avail, fmt, ap); if (len < 0 || len >= avail) { WARN_FUNC(dctx->insn->sec, dctx->insn->offset, "disassembly buffer is truncated"); return -1; }
return 0;}
static int disas_fprintf(void *stream, const char *fmt, ...){ va_list arg; int rv;
va_start(arg, fmt); rv = disas_result_fprintf(stream, fmt, arg); va_end(arg);
return rv;}
/* * For init_disassemble_info_compat(). */static int disas_fprintf_styled(void *stream, enum disassembler_style style, const char *fmt, ...){ va_list arg; int rv;
va_start(arg, fmt); rv = disas_result_fprintf(stream, fmt, arg); va_end(arg);
return rv;}
static void disas_print_addr_sym(struct section *sec, struct symbol *sym, bfd_vma addr, struct disassemble_info *dinfo){ char symstr[1024]; char *str;
if (sym) { sprint_name(symstr, sym->name, addr - sym->offset); DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, symstr); } else { str = offstr(sec, addr); DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, str); free(str); }}
static bool disas_print_addr_alt(bfd_vma addr, struct disassemble_info *dinfo){ struct disas_context *dctx = dinfo->application_data; struct instruction *orig_first_insn; struct alt_group *alt_group; unsigned long offset; struct symbol *sym;
/* * Check if we are processing an alternative at the original * instruction address (i.e. if alt_applied is true) and if * we are referencing an address inside the alternative. * * For example, this happens if there is a branch inside an * alternative. In that case, the address should be updated * to a reference inside the original instruction flow. */ if (!dctx->alt_applied) return false;
alt_group = dctx->insn->alt_group; if (!alt_group || !alt_group->orig_group || addr < alt_group->first_insn->offset || addr > alt_group->last_insn->offset) return false;
orig_first_insn = alt_group->orig_group->first_insn; offset = addr - alt_group->first_insn->offset;
addr = orig_first_insn->offset + offset; sym = insn_sym(orig_first_insn);
disas_print_addr_sym(orig_first_insn->sec, sym, addr, dinfo);
return true;}
static void disas_print_addr_noreloc(bfd_vma addr, struct disassemble_info *dinfo){ struct disas_context *dctx = dinfo->application_data; struct instruction *insn = dctx->insn; struct symbol *sym = insn_sym(insn);
if (disas_print_addr_alt(addr, dinfo)) return;
if (sym && (addr < sym->offset || addr >= sym->offset + sym->len)) sym = NULL;
disas_print_addr_sym(insn->sec, sym, addr, dinfo);}
static void disas_print_addr_reloc(bfd_vma addr, struct disassemble_info *dinfo){ struct disas_context *dctx = dinfo->application_data; struct instruction *insn = dctx->insn; unsigned long offset; struct reloc *reloc; char symstr[1024]; char *str;
reloc = find_reloc_by_dest_range(dctx->file->elf, insn->sec, insn->offset, insn->len); if (!reloc) { /* * There is no relocation for this instruction although * the address to resolve points to the next instruction. * So this is an effective reference to the next IP, for * example: "lea 0x0(%rip),%rdi". The kernel can reference * the next IP with _THIS_IP_ macro. */ DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, "_THIS_IP_"); return; }
offset = arch_insn_adjusted_addend(insn, reloc);
/* * If the relocation symbol is a section name (for example ".bss") * then we try to further resolve the name. */ if (is_sec_sym(reloc->sym)) { str = offstr(reloc->sym->sec, reloc->sym->offset + offset); DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, str); free(str); } else { sprint_name(symstr, reloc->sym->name, offset); DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, symstr); }}
/* * Resolve an address into a "<symbol>+<offset>" string. */static void disas_print_address(bfd_vma addr, struct disassemble_info *dinfo){ struct disas_context *dctx = dinfo->application_data; struct instruction *insn = dctx->insn; struct instruction *jump_dest; struct symbol *sym; bool is_reloc;
/* * If the instruction is a call/jump and it references a * destination then this is likely the address we are looking * up. So check it first. */ jump_dest = insn->jump_dest; if (jump_dest && insn_sym(jump_dest) && jump_dest->offset == addr) { if (!disas_print_addr_alt(addr, dinfo)) disas_print_addr_sym(jump_dest->sec, insn_sym(jump_dest), addr, dinfo); return; }
/* * If the address points to the next instruction then there is * probably a relocation. It can be a false positive when the * current instruction is referencing the address of the next * instruction. This particular case will be handled in * disas_print_addr_reloc(). */ is_reloc = (addr == insn->offset + insn->len);
/* * The call destination offset can be the address we are looking * up, or 0 if there is a relocation. */ sym = insn_call_dest(insn); if (sym && (sym->offset == addr || (sym->offset == 0 && is_reloc))) { DINFO_FPRINTF(dinfo, bfd_vma_fmt, addr, sym->name); return; }
if (!is_reloc) disas_print_addr_noreloc(addr, dinfo); else disas_print_addr_reloc(addr, dinfo);}
/* * Initialize disassemble info arch, mach (32 or 64-bit) and options. */int disas_info_init(struct disassemble_info *dinfo, int arch, int mach32, int mach64, const char *options){ struct disas_context *dctx = dinfo->application_data; struct objtool_file *file = dctx->file;
dinfo->arch = arch;
switch (file->elf->ehdr.e_ident[EI_CLASS]) { case ELFCLASS32: dinfo->mach = mach32; break; case ELFCLASS64: dinfo->mach = mach64; break; default: return -1; }
dinfo->disassembler_options = options;
return 0;}
struct disas_context *disas_context_create(struct objtool_file *file){ struct disas_context *dctx; struct disassemble_info *dinfo; int err;
dctx = malloc(sizeof(*dctx)); if (!dctx) { WARN("failed to allocate disassembly context"); return NULL; }
dctx->file = file; dinfo = &dctx->info;
init_disassemble_info_compat(dinfo, dctx, disas_fprintf, disas_fprintf_styled);
dinfo->read_memory_func = buffer_read_memory; dinfo->print_address_func = disas_print_address; dinfo->application_data = dctx;
/* * bfd_openr() is not used to avoid doing ELF data processing * and caching that has already being done. Here, we just need * to identify the target file so we call an arch specific * function to fill some disassemble info (arch, mach). */
dinfo->arch = bfd_arch_unknown; dinfo->mach = 0;
err = arch_disas_info_init(dinfo); if (err || dinfo->arch == bfd_arch_unknown || dinfo->mach == 0) { WARN("failed to init disassembly arch"); goto error; }
dinfo->endian = (file->elf->ehdr.e_ident[EI_DATA] == ELFDATA2MSB) ? BFD_ENDIAN_BIG : BFD_ENDIAN_LITTLE;
disassemble_init_for_target(dinfo);
dctx->disassembler = disassembler(dinfo->arch, dinfo->endian == BFD_ENDIAN_BIG, dinfo->mach, NULL); if (!dctx->disassembler) { WARN("failed to create disassembler function"); goto error; }
return dctx;
error: free(dctx); return NULL;}
void disas_context_destroy(struct disas_context *dctx){ free(dctx);}
char *disas_result(struct disas_context *dctx){ return dctx->result;}
#define DISAS_INSN_OFFSET_SPACE 10#define DISAS_INSN_SPACE 60
#define DISAS_PRINSN(dctx, insn, depth) \ disas_print_insn(stdout, dctx, insn, depth, "\n")
/* * Print a message in the instruction flow. If sec is not NULL then the * address at the section offset is printed in addition of the message, * otherwise only the message is printed. */static int disas_vprint(FILE *stream, struct section *sec, unsigned long offset, int depth, const char *format, va_list ap){ const char *addr_str; int i, n; int len;
len = sym_name_max_len + DISAS_INSN_OFFSET_SPACE; if (depth < 0) { len += depth; depth = 0; }
n = 0;
if (sec) { addr_str = offstr(sec, offset); n += fprintf(stream, "%6lx: %-*s ", offset, len, addr_str); free((char *)addr_str); } else { len += DISAS_INSN_OFFSET_SPACE + 1; n += fprintf(stream, "%-*s", len, ""); }
/* print vertical bars to show the code flow */ for (i = 0; i < depth; i++) n += fprintf(stream, "| ");
if (format) n += vfprintf(stream, format, ap);
return n;}
static int disas_print(FILE *stream, struct section *sec, unsigned long offset, int depth, const char *format, ...){ va_list args; int len;
va_start(args, format); len = disas_vprint(stream, sec, offset, depth, format, args); va_end(args);
return len;}
/* * Print a message in the instruction flow. If insn is not NULL then * the instruction address is printed in addition of the message, * otherwise only the message is printed. In all cases, the instruction * itself is not printed. */void disas_print_info(FILE *stream, struct instruction *insn, int depth, const char *format, ...){ struct section *sec; unsigned long off; va_list args;
if (insn) { sec = insn->sec; off = insn->offset; } else { sec = NULL; off = 0; }
va_start(args, format); disas_vprint(stream, sec, off, depth, format, args); va_end(args);}
/* * Print an instruction address (offset and function), the instruction itself * and an optional message. */void disas_print_insn(FILE *stream, struct disas_context *dctx, struct instruction *insn, int depth, const char *format, ...){ char fake_nop_insn[32]; const char *insn_str; bool fake_nop; va_list args; int len;
/* * Alternative can insert a fake nop, sometimes with no * associated section so nothing to disassemble. */ fake_nop = (!insn->sec && insn->type == INSN_NOP); if (fake_nop) { snprintf(fake_nop_insn, 32, "<fake nop> (%d bytes)", insn->len); insn_str = fake_nop_insn; } else { disas_insn(dctx, insn); insn_str = disas_result(dctx); }
/* print the instruction */ len = (depth + 1) * 2 < DISAS_INSN_SPACE ? DISAS_INSN_SPACE - (depth+1) * 2 : 1; disas_print_info(stream, insn, depth, "%-*s", len, insn_str);
/* print message if any */ if (!format) return;
if (strcmp(format, "\n") == 0) { fprintf(stream, "\n"); return; }
fprintf(stream, " - "); va_start(args, format); vfprintf(stream, format, args); va_end(args);}
/* * Disassemble a single instruction. Return the size of the instruction. * * If alt_applied is true then insn should be an instruction from of an * alternative (i.e. insn->alt_group != NULL), and it is disassembled * at the location of the original code it is replacing. When the * instruction references any address inside the alternative then * these references will be re-adjusted to replace the original code. */static size_t disas_insn_common(struct disas_context *dctx, struct instruction *insn, bool alt_applied){ disassembler_ftype disasm = dctx->disassembler; struct disassemble_info *dinfo = &dctx->info;
dctx->insn = insn; dctx->alt_applied = alt_applied; dctx->result[0] = '\0';
if (insn->type == INSN_NOP) { DINFO_FPRINTF(dinfo, "nop%d", insn->len); return insn->len; }
/* * Set the disassembler buffer to read data from the section * containing the instruction to disassemble. */ dinfo->buffer = insn->sec->data->d_buf; dinfo->buffer_vma = 0; dinfo->buffer_length = sec_size(insn->sec);
return disasm(insn->offset, &dctx->info);}
size_t disas_insn(struct disas_context *dctx, struct instruction *insn){ return disas_insn_common(dctx, insn, false);}
static size_t disas_insn_alt(struct disas_context *dctx, struct instruction *insn){ return disas_insn_common(dctx, insn, true);}
static struct instruction *next_insn_same_alt(struct objtool_file *file, struct alt_group *alt_grp, struct instruction *insn){ if (alt_grp->last_insn == insn || alt_grp->nop == insn) return NULL;
return next_insn_same_sec(file, insn);}
#define alt_for_each_insn(file, alt_grp, insn) \ for (insn = alt_grp->first_insn; \ insn; \ insn = next_insn_same_alt(file, alt_grp, insn))
/* * Provide a name for the type of alternatives present at the * specified instruction. * * An instruction can have alternatives with different types, for * example alternative instructions and an exception table. In that * case the name for the alternative instructions type is used. * * Return NULL if the instruction as no alternative. */const char *disas_alt_type_name(struct instruction *insn){ struct alternative *alt; const char *name;
name = NULL; for (alt = insn->alts; alt; alt = alt->next) { if (alt->type == ALT_TYPE_INSTRUCTIONS) { name = "alternative"; break; }
switch (alt->type) { case ALT_TYPE_EX_TABLE: name = "ex_table"; break; case ALT_TYPE_JUMP_TABLE: name = "jump_table"; break; default: name = "unknown"; break; } }
return name;}
/* * Provide a name for an alternative. */char *disas_alt_name(struct alternative *alt){ char pfx[4] = { 0 }; char *str = NULL; const char *name; int feature; int flags; int num;
switch (alt->type) {
case ALT_TYPE_EX_TABLE: str = strdup("EXCEPTION"); break;
case ALT_TYPE_JUMP_TABLE: str = strdup("JUMP"); break;
case ALT_TYPE_INSTRUCTIONS: /* * This is a non-default group alternative. Create a name * based on the feature and flags associated with this * alternative. Use either the feature name (it is available) * or the feature number. And add a prefix to show the flags * used. * * Prefix flags characters: * * '!' alternative used when feature not enabled * '+' direct call alternative * '?' unknown flag */
if (!alt->insn->alt_group) return NULL;
feature = alt->insn->alt_group->feature; num = alt_feature(feature); flags = alt_flags(feature); str = pfx;
if (flags & ~(ALT_FLAG_NOT | ALT_FLAG_DIRECT_CALL)) *str++ = '?'; if (flags & ALT_FLAG_DIRECT_CALL) *str++ = '+'; if (flags & ALT_FLAG_NOT) *str++ = '!';
name = arch_cpu_feature_name(num); if (!name) str = strfmt("%sFEATURE 0x%X", pfx, num); else str = strfmt("%s%s", pfx, name);
break; }
return str;}
/* * Initialize an alternative. The default alternative should be initialized * with alt=NULL. */static int disas_alt_init(struct disas_alt *dalt, struct instruction *orig_insn, struct alternative *alt){ dalt->orig_insn = orig_insn; dalt->alt = alt; dalt->insn_idx = 0; dalt->name = alt ? disas_alt_name(alt) : strdup("DEFAULT"); if (!dalt->name) return -1; dalt->width = strlen(dalt->name);
return 0;}
static int disas_alt_add_insn(struct disas_alt *dalt, int index, char *insn_str, int offset, int nops){ int len;
if (index >= DISAS_ALT_INSN_MAX) { WARN("Alternative %lx.%s has more instructions than supported", DALT_ALTID(dalt), dalt->name); return -1; }
len = strlen(insn_str); dalt->insn[index].str = insn_str; dalt->insn[index].offset = offset; dalt->insn[index].nops = nops; if (len > dalt->width) dalt->width = len;
return 0;}
static int disas_alt_jump(struct disas_alt *dalt){ struct instruction *orig_insn; struct instruction *dest_insn; char suffix[2] = { 0 }; char *str; int nops;
orig_insn = dalt->orig_insn; dest_insn = dalt->alt->insn;
if (orig_insn->type == INSN_NOP) { if (orig_insn->len == 5) suffix[0] = 'q'; str = strfmt("jmp%-3s %lx <%s+0x%lx>", suffix, dest_insn->offset, insn_sym(dest_insn)->name, dest_insn->offset - insn_sym(dest_insn)->offset); nops = 0; } else { str = strfmt("nop%d", orig_insn->len); nops = orig_insn->len; }
if (!str) return -1;
disas_alt_add_insn(dalt, 0, str, 0, nops);
return 1;}
/* * Disassemble an exception table alternative. */static int disas_alt_extable(struct disas_alt *dalt){ struct instruction *alt_insn; char *str;
alt_insn = dalt->alt->insn; str = strfmt("resume at 0x%lx <%s+0x%lx>", alt_insn->offset, insn_sym(alt_insn)->name, alt_insn->offset - insn_sym(alt_insn)->offset); if (!str) return -1;
disas_alt_add_insn(dalt, 0, str, 0, 0);
return 1;}
/* * Disassemble an alternative and store instructions in the disas_alt * structure. Return the number of instructions in the alternative. */static int disas_alt_group(struct disas_context *dctx, struct disas_alt *dalt){ struct objtool_file *file; struct instruction *insn; int offset; char *str; int count; int nops; int err;
file = dctx->file; count = 0; offset = 0; nops = 0;
alt_for_each_insn(file, DALT_GROUP(dalt), insn) {
disas_insn_alt(dctx, insn); str = strdup(disas_result(dctx)); if (!str) return -1;
nops = insn->type == INSN_NOP ? insn->len : 0; err = disas_alt_add_insn(dalt, count, str, offset, nops); if (err) break; offset += insn->len; count++; }
return count;}
/* * Disassemble the default alternative. */static int disas_alt_default(struct disas_context *dctx, struct disas_alt *dalt){ char *str; int nops; int err;
if (DALT_GROUP(dalt)) return disas_alt_group(dctx, dalt);
/* * Default alternative with no alt_group: this is the default * code associated with either a jump table or an exception * table and no other instruction alternatives. In that case * the default alternative is made of a single instruction. */ disas_insn(dctx, dalt->orig_insn); str = strdup(disas_result(dctx)); if (!str) return -1; nops = dalt->orig_insn->type == INSN_NOP ? dalt->orig_insn->len : 0; err = disas_alt_add_insn(dalt, 0, str, 0, nops); if (err) return -1;
return 1;}
/* * For each alternative, if there is an instruction at the specified * offset then print this instruction, otherwise print a blank entry. * The offset is an offset from the start of the alternative. * * Return the offset for the next instructions to print, or -1 if all * instructions have been printed. */static int disas_alt_print_insn(struct disas_alt *dalts, int alt_count, int insn_count, int offset){ struct disas_alt *dalt; int offset_next; char *str; int i, j;
offset_next = -1;
for (i = 0; i < alt_count; i++) { dalt = &dalts[i]; j = dalt->insn_idx; if (j == -1) { printf("| %-*s ", dalt->width, ""); continue; }
if (dalt->insn[j].offset == offset) { str = dalt->insn[j].str; printf("| %-*s ", dalt->width, str ?: ""); if (++j < insn_count) { dalt->insn_idx = j; } else { dalt->insn_idx = -1; continue; } } else { printf("| %-*s ", dalt->width, ""); }
if (dalt->insn[j].offset > 0 && (offset_next == -1 || (dalt->insn[j].offset < offset_next))) offset_next = dalt->insn[j].offset; } printf("\n");
return offset_next;}
/* * Print all alternatives side-by-side. */static void disas_alt_print_wide(char *alt_name, struct disas_alt *dalts, int alt_count, int insn_count){ struct instruction *orig_insn; int offset_next; int offset; int i;
orig_insn = dalts[0].orig_insn;
/* * Print an header with the name of each alternative. */ disas_print_info(stdout, orig_insn, -2, NULL);
if (strlen(alt_name) > dalts[0].width) dalts[0].width = strlen(alt_name); printf("| %-*s ", dalts[0].width, alt_name);
for (i = 1; i < alt_count; i++) printf("| %-*s ", dalts[i].width, dalts[i].name);
printf("\n");
/* * Print instructions for each alternative. */ offset_next = 0; do { offset = offset_next; disas_print(stdout, orig_insn->sec, orig_insn->offset + offset, -2, NULL); offset_next = disas_alt_print_insn(dalts, alt_count, insn_count, offset); } while (offset_next > offset);}
/* * Print all alternatives one above the other. */static void disas_alt_print_compact(char *alt_name, struct disas_alt *dalts, int alt_count, int insn_count){ struct instruction *orig_insn; int width; int i, j; int len;
orig_insn = dalts[0].orig_insn;
len = disas_print(stdout, orig_insn->sec, orig_insn->offset, 0, NULL); printf("%s\n", alt_name);
/* * If all alternatives have a single instruction then print each * alternative on a single line. Otherwise, print alternatives * one above the other with a clear separation. */
if (insn_count == 1) { width = 0; for (i = 0; i < alt_count; i++) { if (dalts[i].width > width) width = dalts[i].width; }
for (i = 0; i < alt_count; i++) { printf("%*s= %-*s (if %s)\n", len, "", width, dalts[i].insn[0].str, dalts[i].name); }
return; }
for (i = 0; i < alt_count; i++) { printf("%*s= %s\n", len, "", dalts[i].name); for (j = 0; j < insn_count; j++) { if (!dalts[i].insn[j].str) break; disas_print(stdout, orig_insn->sec, orig_insn->offset + dalts[i].insn[j].offset, 0, "| %s\n", dalts[i].insn[j].str); } printf("%*s|\n", len, ""); }}
/* * Trim NOPs in alternatives. This replaces trailing NOPs in alternatives * with a single indication of the number of bytes covered with NOPs. * * Return the maximum numbers of instructions in all alternatives after * trailing NOPs have been trimmed. */static int disas_alt_trim_nops(struct disas_alt *dalts, int alt_count, int insn_count){ struct disas_alt *dalt; int nops_count; const char *s; int offset; int count; int nops; int i, j;
count = 0; for (i = 0; i < alt_count; i++) { offset = 0; nops = 0; nops_count = 0; dalt = &dalts[i]; for (j = insn_count - 1; j >= 0; j--) { if (!dalt->insn[j].str || !dalt->insn[j].nops) break; offset = dalt->insn[j].offset; free(dalt->insn[j].str); dalt->insn[j].offset = 0; dalt->insn[j].str = NULL; nops += dalt->insn[j].nops; nops_count++; }
/* * All trailing NOPs have been removed. If there was a single * NOP instruction then re-add it. If there was a block of * NOPs then indicate the number of bytes than the block * covers (nop*<number-of-bytes>). */ if (nops_count) { s = nops_count == 1 ? "" : "*"; dalt->insn[j + 1].str = strfmt("nop%s%d", s, nops); dalt->insn[j + 1].offset = offset; dalt->insn[j + 1].nops = nops; j++; }
if (j > count) count = j; }
return count + 1;}
/* * Disassemble an alternative. * * Return the last instruction in the default alternative so that * disassembly can continue with the next instruction. Return NULL * on error. */static void *disas_alt(struct disas_context *dctx, struct instruction *orig_insn){ struct disas_alt dalts[DISAS_ALT_MAX] = { 0 }; struct instruction *last_insn = NULL; struct alternative *alt; struct disas_alt *dalt; int insn_count = 0; int alt_count = 0; char *alt_name; int count; int i, j; int err;
alt_name = strfmt("<%s.%lx>", disas_alt_type_name(orig_insn), orig_insn->offset); if (!alt_name) { WARN("Failed to define name for alternative at instruction 0x%lx", orig_insn->offset); goto done; }
/* * Initialize and disassemble the default alternative. */ err = disas_alt_init(&dalts[0], orig_insn, NULL); if (err) { WARN("%s: failed to initialize default alternative", alt_name); goto done; }
insn_count = disas_alt_default(dctx, &dalts[0]); if (insn_count < 0) { WARN("%s: failed to disassemble default alternative", alt_name); goto done; }
/* * Initialize and disassemble all other alternatives. */ i = 1; for (alt = orig_insn->alts; alt; alt = alt->next) { if (i >= DISAS_ALT_MAX) { WARN("%s has more alternatives than supported", alt_name); break; }
dalt = &dalts[i]; err = disas_alt_init(dalt, orig_insn, alt); if (err) { WARN("%s: failed to disassemble alternative", alt_name); goto done; }
count = -1; switch (dalt->alt->type) { case ALT_TYPE_INSTRUCTIONS: count = disas_alt_group(dctx, dalt); break; case ALT_TYPE_EX_TABLE: count = disas_alt_extable(dalt); break; case ALT_TYPE_JUMP_TABLE: count = disas_alt_jump(dalt); break; } if (count < 0) { WARN("%s: failed to disassemble alternative %s", alt_name, dalt->name); goto done; }
insn_count = count > insn_count ? count : insn_count; i++; } alt_count = i;
/* * Print default and non-default alternatives. */
insn_count = disas_alt_trim_nops(dalts, alt_count, insn_count);
if (opts.wide) disas_alt_print_wide(alt_name, dalts, alt_count, insn_count); else disas_alt_print_compact(alt_name, dalts, alt_count, insn_count);
last_insn = orig_insn->alt_group ? orig_insn->alt_group->last_insn : orig_insn;
done: for (i = 0; i < alt_count; i++) { free(dalts[i].name); for (j = 0; j < insn_count; j++) free(dalts[i].insn[j].str); }
free(alt_name);
return last_insn;}
/* * Disassemble a function. */static void disas_func(struct disas_context *dctx, struct symbol *func){ struct instruction *insn_start; struct instruction *insn;
printf("%s:\n", func->name); sym_for_each_insn(dctx->file, func, insn) { if (insn->alts) { insn_start = insn; insn = disas_alt(dctx, insn); if (insn) continue; /* * There was an error with disassembling * the alternative. Resume disassembling * at the current instruction, this will * disassemble the default alternative * only and continue with the code after * the alternative. */ insn = insn_start; }
DISAS_PRINSN(dctx, insn, 0); } printf("\n");}
/* * Disassemble all warned functions. */void disas_warned_funcs(struct disas_context *dctx){ struct symbol *sym;
if (!dctx) return;
for_each_sym(dctx->file->elf, sym) { if (sym->warned) disas_func(dctx, sym); }}
void disas_funcs(struct disas_context *dctx){ bool disas_all = !strcmp(opts.disas, "*"); struct section *sec; struct symbol *sym;
for_each_sec(dctx->file->elf, sec) {
if (!is_text_sec(sec)) continue;
sec_for_each_sym(sec, sym) { /* * If the function had a warning and the verbose * option is used then the function was already * disassemble. */ if (opts.verbose && sym->warned) continue;
if (disas_all || fnmatch(opts.disas, sym->name, 0) == 0) disas_func(dctx, sym); } }}