Path: blob/master/tools/perf/arch/x86/annotate/instructions.c
26292 views
// SPDX-License-Identifier: GPL-2.01/*2* x86 instruction nmemonic table to parse disasm lines for annotate.3* This table is searched twice - one for exact match and another for4* match without a size suffix (b, w, l, q) in case of AT&T syntax.5*6* So this table should not have entries with the suffix unless it's7* a complete different instruction than ones without the suffix.8*/9static struct ins x86__instructions[] = {10{ .name = "adc", .ops = &mov_ops, },11{ .name = "add", .ops = &mov_ops, },12{ .name = "addsd", .ops = &mov_ops, },13{ .name = "and", .ops = &mov_ops, },14{ .name = "andpd", .ops = &mov_ops, },15{ .name = "andps", .ops = &mov_ops, },16{ .name = "bsr", .ops = &mov_ops, },17{ .name = "bt", .ops = &mov_ops, },18{ .name = "btr", .ops = &mov_ops, },19{ .name = "bts", .ops = &mov_ops, },20{ .name = "call", .ops = &call_ops, },21{ .name = "cmovbe", .ops = &mov_ops, },22{ .name = "cmove", .ops = &mov_ops, },23{ .name = "cmovae", .ops = &mov_ops, },24{ .name = "cmp", .ops = &mov_ops, },25{ .name = "cmpxch", .ops = &mov_ops, },26{ .name = "cmpxchg", .ops = &mov_ops, },27{ .name = "cs", .ops = &mov_ops, },28{ .name = "dec", .ops = &dec_ops, },29{ .name = "divsd", .ops = &mov_ops, },30{ .name = "divss", .ops = &mov_ops, },31{ .name = "gs", .ops = &mov_ops, },32{ .name = "imul", .ops = &mov_ops, },33{ .name = "inc", .ops = &dec_ops, },34{ .name = "ja", .ops = &jump_ops, },35{ .name = "jae", .ops = &jump_ops, },36{ .name = "jb", .ops = &jump_ops, },37{ .name = "jbe", .ops = &jump_ops, },38{ .name = "jc", .ops = &jump_ops, },39{ .name = "jcxz", .ops = &jump_ops, },40{ .name = "je", .ops = &jump_ops, },41{ .name = "jecxz", .ops = &jump_ops, },42{ .name = "jg", .ops = &jump_ops, },43{ .name = "jge", .ops = &jump_ops, },44{ .name = "jl", .ops = &jump_ops, },45{ .name = "jle", .ops = &jump_ops, },46{ .name = "jmp", .ops = &jump_ops, },47{ .name = "jna", .ops = &jump_ops, },48{ .name = "jnae", .ops = &jump_ops, },49{ .name = "jnb", .ops = &jump_ops, },50{ .name = "jnbe", .ops = &jump_ops, },51{ .name = "jnc", .ops = &jump_ops, },52{ .name = "jne", .ops = &jump_ops, },53{ .name = "jng", .ops = &jump_ops, },54{ .name = "jnge", .ops = &jump_ops, },55{ .name = "jnl", .ops = &jump_ops, },56{ .name = "jnle", .ops = &jump_ops, },57{ .name = "jno", .ops = &jump_ops, },58{ .name = "jnp", .ops = &jump_ops, },59{ .name = "jns", .ops = &jump_ops, },60{ .name = "jnz", .ops = &jump_ops, },61{ .name = "jo", .ops = &jump_ops, },62{ .name = "jp", .ops = &jump_ops, },63{ .name = "jpe", .ops = &jump_ops, },64{ .name = "jpo", .ops = &jump_ops, },65{ .name = "jrcxz", .ops = &jump_ops, },66{ .name = "js", .ops = &jump_ops, },67{ .name = "jz", .ops = &jump_ops, },68{ .name = "lea", .ops = &mov_ops, },69{ .name = "lock", .ops = &lock_ops, },70{ .name = "mov", .ops = &mov_ops, },71{ .name = "movapd", .ops = &mov_ops, },72{ .name = "movaps", .ops = &mov_ops, },73{ .name = "movdqa", .ops = &mov_ops, },74{ .name = "movdqu", .ops = &mov_ops, },75{ .name = "movsd", .ops = &mov_ops, },76{ .name = "movss", .ops = &mov_ops, },77{ .name = "movsb", .ops = &mov_ops, },78{ .name = "movsw", .ops = &mov_ops, },79{ .name = "movsl", .ops = &mov_ops, },80{ .name = "movupd", .ops = &mov_ops, },81{ .name = "movups", .ops = &mov_ops, },82{ .name = "movzb", .ops = &mov_ops, },83{ .name = "movzw", .ops = &mov_ops, },84{ .name = "movzl", .ops = &mov_ops, },85{ .name = "mulsd", .ops = &mov_ops, },86{ .name = "mulss", .ops = &mov_ops, },87{ .name = "nop", .ops = &nop_ops, },88{ .name = "or", .ops = &mov_ops, },89{ .name = "orps", .ops = &mov_ops, },90{ .name = "pand", .ops = &mov_ops, },91{ .name = "paddq", .ops = &mov_ops, },92{ .name = "pcmpeqb", .ops = &mov_ops, },93{ .name = "por", .ops = &mov_ops, },94{ .name = "rcl", .ops = &mov_ops, },95{ .name = "ret", .ops = &ret_ops, },96{ .name = "sbb", .ops = &mov_ops, },97{ .name = "sete", .ops = &mov_ops, },98{ .name = "sub", .ops = &mov_ops, },99{ .name = "subsd", .ops = &mov_ops, },100{ .name = "test", .ops = &mov_ops, },101{ .name = "tzcnt", .ops = &mov_ops, },102{ .name = "ucomisd", .ops = &mov_ops, },103{ .name = "ucomiss", .ops = &mov_ops, },104{ .name = "vaddsd", .ops = &mov_ops, },105{ .name = "vandpd", .ops = &mov_ops, },106{ .name = "vmovdqa", .ops = &mov_ops, },107{ .name = "vmovq", .ops = &mov_ops, },108{ .name = "vmovsd", .ops = &mov_ops, },109{ .name = "vmulsd", .ops = &mov_ops, },110{ .name = "vorpd", .ops = &mov_ops, },111{ .name = "vsubsd", .ops = &mov_ops, },112{ .name = "vucomisd", .ops = &mov_ops, },113{ .name = "xadd", .ops = &mov_ops, },114{ .name = "xbegin", .ops = &jump_ops, },115{ .name = "xchg", .ops = &mov_ops, },116{ .name = "xor", .ops = &mov_ops, },117{ .name = "xorpd", .ops = &mov_ops, },118{ .name = "xorps", .ops = &mov_ops, },119};120121static bool amd__ins_is_fused(struct arch *arch, const char *ins1,122const char *ins2)123{124if (strstr(ins2, "jmp"))125return false;126127/* Family >= 15h supports cmp/test + branch fusion */128if (arch->family >= 0x15 && (strstarts(ins1, "test") ||129(strstarts(ins1, "cmp") && !strstr(ins1, "xchg")))) {130return true;131}132133/* Family >= 19h supports some ALU + branch fusion */134if (arch->family >= 0x19 && (strstarts(ins1, "add") ||135strstarts(ins1, "sub") || strstarts(ins1, "and") ||136strstarts(ins1, "inc") || strstarts(ins1, "dec") ||137strstarts(ins1, "or") || strstarts(ins1, "xor"))) {138return true;139}140141return false;142}143144static bool intel__ins_is_fused(struct arch *arch, const char *ins1,145const char *ins2)146{147if (arch->family != 6 || arch->model < 0x1e || strstr(ins2, "jmp"))148return false;149150if (arch->model == 0x1e) {151/* Nehalem */152if ((strstr(ins1, "cmp") && !strstr(ins1, "xchg")) ||153strstr(ins1, "test")) {154return true;155}156} else {157/* Newer platform */158if ((strstr(ins1, "cmp") && !strstr(ins1, "xchg")) ||159strstr(ins1, "test") ||160strstr(ins1, "add") ||161strstr(ins1, "sub") ||162strstr(ins1, "and") ||163strstr(ins1, "inc") ||164strstr(ins1, "dec")) {165return true;166}167}168169return false;170}171172static int x86__cpuid_parse(struct arch *arch, char *cpuid)173{174unsigned int family, model, stepping;175int ret;176177/*178* cpuid = "GenuineIntel,family,model,stepping"179*/180ret = sscanf(cpuid, "%*[^,],%u,%u,%u", &family, &model, &stepping);181if (ret == 3) {182arch->family = family;183arch->model = model;184arch->ins_is_fused = strstarts(cpuid, "AuthenticAMD") ?185amd__ins_is_fused :186intel__ins_is_fused;187return 0;188}189190return -1;191}192193static int x86__annotate_init(struct arch *arch, char *cpuid)194{195int err = 0;196197if (arch->initialized)198return 0;199200if (cpuid) {201if (x86__cpuid_parse(arch, cpuid))202err = SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_CPUID_PARSING;203}204arch->e_machine = EM_X86_64;205arch->e_flags = 0;206arch->initialized = true;207return err;208}209210#ifdef HAVE_LIBDW_SUPPORT211static void update_insn_state_x86(struct type_state *state,212struct data_loc_info *dloc, Dwarf_Die *cu_die,213struct disasm_line *dl)214{215struct annotated_insn_loc loc;216struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE];217struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];218struct type_state_reg *tsr;219Dwarf_Die type_die;220u32 insn_offset = dl->al.offset;221int fbreg = dloc->fbreg;222int fboff = 0;223224if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0)225return;226227if (ins__is_call(&dl->ins)) {228struct symbol *func = dl->ops.target.sym;229230if (func == NULL)231return;232233/* __fentry__ will preserve all registers */234if (!strcmp(func->name, "__fentry__"))235return;236237pr_debug_dtp("call [%x] %s\n", insn_offset, func->name);238239/* Otherwise invalidate caller-saved registers after call */240for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {241if (state->regs[i].caller_saved)242state->regs[i].ok = false;243}244245/* Update register with the return type (if any) */246if (die_find_func_rettype(cu_die, func->name, &type_die)) {247tsr = &state->regs[state->ret_reg];248tsr->type = type_die;249tsr->kind = TSR_KIND_TYPE;250tsr->ok = true;251252pr_debug_dtp("call [%x] return -> reg%d",253insn_offset, state->ret_reg);254pr_debug_type_name(&type_die, tsr->kind);255}256return;257}258259if (!strncmp(dl->ins.name, "add", 3)) {260u64 imm_value = -1ULL;261int offset;262const char *var_name = NULL;263struct map_symbol *ms = dloc->ms;264u64 ip = ms->sym->start + dl->al.offset;265266if (!has_reg_type(state, dst->reg1))267return;268269tsr = &state->regs[dst->reg1];270tsr->copied_from = -1;271272if (src->imm)273imm_value = src->offset;274else if (has_reg_type(state, src->reg1) &&275state->regs[src->reg1].kind == TSR_KIND_CONST)276imm_value = state->regs[src->reg1].imm_value;277else if (src->reg1 == DWARF_REG_PC) {278u64 var_addr = annotate_calc_pcrel(dloc->ms, ip,279src->offset, dl);280281if (get_global_var_info(dloc, var_addr,282&var_name, &offset) &&283!strcmp(var_name, "this_cpu_off") &&284tsr->kind == TSR_KIND_CONST) {285tsr->kind = TSR_KIND_PERCPU_BASE;286tsr->ok = true;287imm_value = tsr->imm_value;288}289}290else291return;292293if (tsr->kind != TSR_KIND_PERCPU_BASE)294return;295296if (get_global_var_type(cu_die, dloc, ip, imm_value, &offset,297&type_die) && offset == 0) {298/*299* This is not a pointer type, but it should be treated300* as a pointer.301*/302tsr->type = type_die;303tsr->kind = TSR_KIND_POINTER;304tsr->ok = true;305306pr_debug_dtp("add [%x] percpu %#"PRIx64" -> reg%d",307insn_offset, imm_value, dst->reg1);308pr_debug_type_name(&tsr->type, tsr->kind);309}310return;311}312313if (strncmp(dl->ins.name, "mov", 3))314return;315316if (dloc->fb_cfa) {317u64 ip = dloc->ms->sym->start + dl->al.offset;318u64 pc = map__rip_2objdump(dloc->ms->map, ip);319320if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0)321fbreg = -1;322}323324/* Case 1. register to register or segment:offset to register transfers */325if (!src->mem_ref && !dst->mem_ref) {326if (!has_reg_type(state, dst->reg1))327return;328329tsr = &state->regs[dst->reg1];330tsr->copied_from = -1;331332if (dso__kernel(map__dso(dloc->ms->map)) &&333src->segment == INSN_SEG_X86_GS && src->imm) {334u64 ip = dloc->ms->sym->start + dl->al.offset;335u64 var_addr;336int offset;337338/*339* In kernel, %gs points to a per-cpu region for the340* current CPU. Access with a constant offset should341* be treated as a global variable access.342*/343var_addr = src->offset;344345if (var_addr == 40) {346tsr->kind = TSR_KIND_CANARY;347tsr->ok = true;348349pr_debug_dtp("mov [%x] stack canary -> reg%d\n",350insn_offset, dst->reg1);351return;352}353354if (!get_global_var_type(cu_die, dloc, ip, var_addr,355&offset, &type_die) ||356!die_get_member_type(&type_die, offset, &type_die)) {357tsr->ok = false;358return;359}360361tsr->type = type_die;362tsr->kind = TSR_KIND_TYPE;363tsr->ok = true;364365pr_debug_dtp("mov [%x] this-cpu addr=%#"PRIx64" -> reg%d",366insn_offset, var_addr, dst->reg1);367pr_debug_type_name(&tsr->type, tsr->kind);368return;369}370371if (src->imm) {372tsr->kind = TSR_KIND_CONST;373tsr->imm_value = src->offset;374tsr->ok = true;375376pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n",377insn_offset, tsr->imm_value, dst->reg1);378return;379}380381if (!has_reg_type(state, src->reg1) ||382!state->regs[src->reg1].ok) {383tsr->ok = false;384return;385}386387tsr->type = state->regs[src->reg1].type;388tsr->kind = state->regs[src->reg1].kind;389tsr->imm_value = state->regs[src->reg1].imm_value;390tsr->ok = true;391392/* To copy back the variable type later (hopefully) */393if (tsr->kind == TSR_KIND_TYPE)394tsr->copied_from = src->reg1;395396pr_debug_dtp("mov [%x] reg%d -> reg%d",397insn_offset, src->reg1, dst->reg1);398pr_debug_type_name(&tsr->type, tsr->kind);399}400/* Case 2. memory to register transers */401if (src->mem_ref && !dst->mem_ref) {402int sreg = src->reg1;403404if (!has_reg_type(state, dst->reg1))405return;406407tsr = &state->regs[dst->reg1];408tsr->copied_from = -1;409410retry:411/* Check stack variables with offset */412if (sreg == fbreg || sreg == state->stack_reg) {413struct type_state_stack *stack;414int offset = src->offset - fboff;415416stack = find_stack_state(state, offset);417if (stack == NULL) {418tsr->ok = false;419return;420} else if (!stack->compound) {421tsr->type = stack->type;422tsr->kind = stack->kind;423tsr->ok = true;424} else if (die_get_member_type(&stack->type,425offset - stack->offset,426&type_die)) {427tsr->type = type_die;428tsr->kind = TSR_KIND_TYPE;429tsr->ok = true;430} else {431tsr->ok = false;432return;433}434435if (sreg == fbreg) {436pr_debug_dtp("mov [%x] -%#x(stack) -> reg%d",437insn_offset, -offset, dst->reg1);438} else {439pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d",440insn_offset, offset, sreg, dst->reg1);441}442pr_debug_type_name(&tsr->type, tsr->kind);443}444/* And then dereference the pointer if it has one */445else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&446state->regs[sreg].kind == TSR_KIND_TYPE &&447die_deref_ptr_type(&state->regs[sreg].type,448src->offset, &type_die)) {449tsr->type = type_die;450tsr->kind = TSR_KIND_TYPE;451tsr->ok = true;452453pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d",454insn_offset, src->offset, sreg, dst->reg1);455pr_debug_type_name(&tsr->type, tsr->kind);456}457/* Or check if it's a global variable */458else if (sreg == DWARF_REG_PC) {459struct map_symbol *ms = dloc->ms;460u64 ip = ms->sym->start + dl->al.offset;461u64 addr;462int offset;463464addr = annotate_calc_pcrel(ms, ip, src->offset, dl);465466if (!get_global_var_type(cu_die, dloc, ip, addr, &offset,467&type_die) ||468!die_get_member_type(&type_die, offset, &type_die)) {469tsr->ok = false;470return;471}472473tsr->type = type_die;474tsr->kind = TSR_KIND_TYPE;475tsr->ok = true;476477pr_debug_dtp("mov [%x] global addr=%"PRIx64" -> reg%d",478insn_offset, addr, dst->reg1);479pr_debug_type_name(&type_die, tsr->kind);480}481/* And check percpu access with base register */482else if (has_reg_type(state, sreg) &&483state->regs[sreg].kind == TSR_KIND_PERCPU_BASE) {484u64 ip = dloc->ms->sym->start + dl->al.offset;485u64 var_addr = src->offset;486int offset;487488if (src->multi_regs) {489int reg2 = (sreg == src->reg1) ? src->reg2 : src->reg1;490491if (has_reg_type(state, reg2) && state->regs[reg2].ok &&492state->regs[reg2].kind == TSR_KIND_CONST)493var_addr += state->regs[reg2].imm_value;494}495496/*497* In kernel, %gs points to a per-cpu region for the498* current CPU. Access with a constant offset should499* be treated as a global variable access.500*/501if (get_global_var_type(cu_die, dloc, ip, var_addr,502&offset, &type_die) &&503die_get_member_type(&type_die, offset, &type_die)) {504tsr->type = type_die;505tsr->kind = TSR_KIND_TYPE;506tsr->ok = true;507508if (src->multi_regs) {509pr_debug_dtp("mov [%x] percpu %#x(reg%d,reg%d) -> reg%d",510insn_offset, src->offset, src->reg1,511src->reg2, dst->reg1);512} else {513pr_debug_dtp("mov [%x] percpu %#x(reg%d) -> reg%d",514insn_offset, src->offset, sreg, dst->reg1);515}516pr_debug_type_name(&tsr->type, tsr->kind);517} else {518tsr->ok = false;519}520}521/* And then dereference the calculated pointer if it has one */522else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&523state->regs[sreg].kind == TSR_KIND_POINTER &&524die_get_member_type(&state->regs[sreg].type,525src->offset, &type_die)) {526tsr->type = type_die;527tsr->kind = TSR_KIND_TYPE;528tsr->ok = true;529530pr_debug_dtp("mov [%x] pointer %#x(reg%d) -> reg%d",531insn_offset, src->offset, sreg, dst->reg1);532pr_debug_type_name(&tsr->type, tsr->kind);533}534/* Or try another register if any */535else if (src->multi_regs && sreg == src->reg1 &&536src->reg1 != src->reg2) {537sreg = src->reg2;538goto retry;539}540else {541int offset;542const char *var_name = NULL;543544/* it might be per-cpu variable (in kernel) access */545if (src->offset < 0) {546if (get_global_var_info(dloc, (s64)src->offset,547&var_name, &offset) &&548!strcmp(var_name, "__per_cpu_offset")) {549tsr->kind = TSR_KIND_PERCPU_BASE;550tsr->ok = true;551552pr_debug_dtp("mov [%x] percpu base reg%d\n",553insn_offset, dst->reg1);554return;555}556}557558tsr->ok = false;559}560}561/* Case 3. register to memory transfers */562if (!src->mem_ref && dst->mem_ref) {563if (!has_reg_type(state, src->reg1) ||564!state->regs[src->reg1].ok)565return;566567/* Check stack variables with offset */568if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) {569struct type_state_stack *stack;570int offset = dst->offset - fboff;571572tsr = &state->regs[src->reg1];573574stack = find_stack_state(state, offset);575if (stack) {576/*577* The source register is likely to hold a type578* of member if it's a compound type. Do not579* update the stack variable type since we can580* get the member type later by using the581* die_get_member_type().582*/583if (!stack->compound)584set_stack_state(stack, offset, tsr->kind,585&tsr->type);586} else {587findnew_stack_state(state, offset, tsr->kind,588&tsr->type);589}590591if (dst->reg1 == fbreg) {592pr_debug_dtp("mov [%x] reg%d -> -%#x(stack)",593insn_offset, src->reg1, -offset);594} else {595pr_debug_dtp("mov [%x] reg%d -> %#x(reg%d)",596insn_offset, src->reg1, offset, dst->reg1);597}598pr_debug_type_name(&tsr->type, tsr->kind);599}600/*601* Ignore other transfers since it'd set a value in a struct602* and won't change the type.603*/604}605/* Case 4. memory to memory transfers (not handled for now) */606}607#endif608609610