Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,11 @@
perf-util-y += annotate-arc.o
perf-util-y += annotate-arm.o
perf-util-y += annotate-arm64.o
perf-util-y += annotate-csky.o
perf-util-y += annotate-loongarch.o
perf-util-y += annotate-mips.o
perf-util-y += annotate-x86.o
perf-util-y += annotate-powerpc.o
perf-util-y += annotate-riscv64.o
perf-util-y += annotate-s390.o
perf-util-y += annotate-sparc.o
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include "../disasm.h"
const struct arch *arch__new_arc(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "arc";
arch->id = *id;
arch->objdump.comment_char = ';';
return arch;
}
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include <errno.h>
#include <regex.h>
#include "../annotate.h"
#include "../disasm.h"
struct arch_arm {
struct arch arch;
regex_t call_insn;
regex_t jump_insn;
};
static const struct ins_ops *arm__associate_instruction_ops(struct arch *arch, const char *name)
{
struct arch_arm *arm = container_of(arch, struct arch_arm, arch);
const struct ins_ops *ops;
regmatch_t match[2];
if (!regexec(&arm->call_insn, name, 2, match, 0))
ops = &call_ops;
else if (!regexec(&arm->jump_insn, name, 2, match, 0))
ops = &jump_ops;
else
return NULL;
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_arm(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
int err;
struct arch_arm *arm = zalloc(sizeof(*arm));
struct arch *arch;
if (!arm)
return NULL;
arch = &arm->arch;
arch->name = "arm";
arch->id = *id;
arch->objdump.comment_char = ';';
arch->objdump.skip_functions_char = '+';
arch->associate_instruction_ops = arm__associate_instruction_ops;
#define ARM_CONDS "(cc|cs|eq|ge|gt|hi|le|ls|lt|mi|ne|pl|vc|vs)"
err = regcomp(&arm->call_insn, "^blx?" ARM_CONDS "?$", REG_EXTENDED);
if (err)
goto out_free_arm;
err = regcomp(&arm->jump_insn, "^bx?" ARM_CONDS "?$", REG_EXTENDED);
if (err)
goto out_free_call;
#undef ARM_CONDS
return arch;
out_free_call:
regfree(&arm->call_insn);
out_free_arm:
free(arm);
errno = SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_REGEXP;
return NULL;
}
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/compiler.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <linux/zalloc.h>
#include <regex.h>
#include "../annotate.h"
#include "../disasm.h"
struct arch_arm64 {
struct arch arch;
regex_t call_insn;
regex_t jump_insn;
};
static int arm64_mov__parse(const struct arch *arch __maybe_unused,
struct ins_operands *ops,
struct map_symbol *ms __maybe_unused,
struct disasm_line *dl __maybe_unused)
{
char *s = strchr(ops->raw, ','), *target, *endptr;
if (s == NULL)
return -1;
*s = '\0';
ops->source.raw = strdup(ops->raw);
*s = ',';
if (ops->source.raw == NULL)
return -1;
target = ++s;
ops->target.raw = strdup(target);
if (ops->target.raw == NULL)
goto out_free_source;
ops->target.addr = strtoull(target, &endptr, 16);
if (endptr == target)
goto out_free_target;
s = strchr(endptr, '<');
if (s == NULL)
goto out_free_target;
endptr = strchr(s + 1, '>');
if (endptr == NULL)
goto out_free_target;
*endptr = '\0';
*s = ' ';
ops->target.name = strdup(s);
*s = '<';
*endptr = '>';
if (ops->target.name == NULL)
goto out_free_target;
return 0;
out_free_target:
zfree(&ops->target.raw);
out_free_source:
zfree(&ops->source.raw);
return -1;
}
static const struct ins_ops arm64_mov_ops = {
.parse = arm64_mov__parse,
.scnprintf = mov__scnprintf,
};
static const struct ins_ops *arm64__associate_instruction_ops(struct arch *arch, const char *name)
{
struct arch_arm64 *arm = container_of(arch, struct arch_arm64, arch);
const struct ins_ops *ops;
regmatch_t match[2];
if (!regexec(&arm->jump_insn, name, 2, match, 0))
ops = &jump_ops;
else if (!regexec(&arm->call_insn, name, 2, match, 0))
ops = &call_ops;
else if (!strcmp(name, "ret"))
ops = &ret_ops;
else
ops = &arm64_mov_ops;
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_arm64(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
int err;
struct arch_arm64 *arm = zalloc(sizeof(*arm));
struct arch *arch;
if (!arm)
return NULL;
arch = &arm->arch;
arch->name = "arm64";
arch->id = *id;
arch->objdump.comment_char = '/';
arch->objdump.skip_functions_char = '+';
arch->associate_instruction_ops = arm64__associate_instruction_ops;
/* bl, blr */
err = regcomp(&arm->call_insn, "^blr?$", REG_EXTENDED);
if (err)
goto out_free_arm;
/* b, b.cond, br, cbz/cbnz, tbz/tbnz */
err = regcomp(&arm->jump_insn, "^[ct]?br?\\.?(cc|cs|eq|ge|gt|hi|hs|le|lo|ls|lt|mi|ne|pl|vc|vs)?n?z?$",
REG_EXTENDED);
if (err)
goto out_free_call;
return arch;
out_free_call:
regfree(&arm->call_insn);
out_free_arm:
free(arm);
errno = SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_REGEXP;
return NULL;
}
@@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2019 Hangzhou C-SKY Microsystems co.,ltd.
#include <string.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include "../disasm.h"
static const struct ins_ops *csky__associate_ins_ops(struct arch *arch,
const char *name)
{
const struct ins_ops *ops = NULL;
/* catch all kind of jumps */
if (!strcmp(name, "bt") ||
!strcmp(name, "bf") ||
!strcmp(name, "bez") ||
!strcmp(name, "bnez") ||
!strcmp(name, "bnezad") ||
!strcmp(name, "bhsz") ||
!strcmp(name, "bhz") ||
!strcmp(name, "blsz") ||
!strcmp(name, "blz") ||
!strcmp(name, "br") ||
!strcmp(name, "jmpi") ||
!strcmp(name, "jmp"))
ops = &jump_ops;
/* catch function call */
if (!strcmp(name, "bsr") ||
!strcmp(name, "jsri") ||
!strcmp(name, "jsr"))
ops = &call_ops;
/* catch function return */
if (!strcmp(name, "rts"))
ops = &ret_ops;
if (ops)
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_csky(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "csky";
arch->id = *id;
arch->objdump.comment_char = '/';
arch->associate_instruction_ops = csky__associate_ins_ops;
return arch;
}
@@ -0,0 +1,157 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Perf annotate functions.
*
* Copyright (C) 2020-2023 Loongson Technology Corporation Limited
*/
#include <stdlib.h>
#include <string.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include "../disasm.h"
#include "../map.h"
#include "../maps.h"
#include "../symbol.h"
#include "../thread.h"
static int loongarch_call__parse(const struct arch *arch, struct ins_operands *ops,
struct map_symbol *ms,
struct disasm_line *dl __maybe_unused)
{
char *c, *endptr, *tok, *name;
struct map *map = ms->map;
struct addr_map_symbol target;
c = strchr(ops->raw, '#');
if (c++ == NULL)
return -1;
ops->target.addr = strtoull(c, &endptr, 16);
name = strchr(endptr, '<');
name++;
if (arch->objdump.skip_functions_char &&
strchr(name, arch->objdump.skip_functions_char))
return -1;
tok = strchr(name, '>');
if (tok == NULL)
return -1;
*tok = '\0';
ops->target.name = strdup(name);
*tok = '>';
if (ops->target.name == NULL)
return -1;
target = (struct addr_map_symbol) {
.ms = { .map = map__get(map), },
.addr = map__objdump_2mem(map, ops->target.addr),
};
if (maps__find_ams(thread__maps(ms->thread), &target) == 0 &&
map__rip_2objdump(target.ms.map, map__map_ip(target.ms.map, target.addr)) == ops->target.addr)
ops->target.sym = target.ms.sym;
addr_map_symbol__exit(&target);
return 0;
}
static const struct ins_ops loongarch_call_ops = {
.parse = loongarch_call__parse,
.scnprintf = call__scnprintf,
.is_call = true,
};
static int loongarch_jump__parse(const struct arch *arch, struct ins_operands *ops,
struct map_symbol *ms,
struct disasm_line *dl __maybe_unused)
{
struct map *map = ms->map;
struct symbol *sym = ms->sym;
struct addr_map_symbol target = {
.ms = { .map = map__get(map), },
};
const char *c = strchr(ops->raw, '#');
u64 start, end;
ops->jump.raw_comment = strchr(ops->raw, arch->objdump.comment_char);
ops->jump.raw_func_start = strchr(ops->raw, '<');
if (ops->jump.raw_func_start && c > ops->jump.raw_func_start)
c = NULL;
if (c++ != NULL)
ops->target.addr = strtoull(c, NULL, 16);
else
ops->target.addr = strtoull(ops->raw, NULL, 16);
target.addr = map__objdump_2mem(map, ops->target.addr);
start = map__unmap_ip(map, sym->start);
end = map__unmap_ip(map, sym->end);
ops->target.outside = target.addr < start || target.addr >= end;
if (maps__find_ams(thread__maps(ms->thread), &target) == 0 &&
map__rip_2objdump(target.ms.map, map__map_ip(target.ms.map, target.addr)) == ops->target.addr)
ops->target.sym = target.ms.sym;
if (!ops->target.outside) {
ops->target.offset = target.addr - start;
ops->target.offset_avail = true;
} else {
ops->target.offset_avail = false;
}
addr_map_symbol__exit(&target);
return 0;
}
static const struct ins_ops loongarch_jump_ops = {
.free = jump__delete,
.parse = loongarch_jump__parse,
.scnprintf = jump__scnprintf,
.is_jump = true,
};
static
const struct ins_ops *loongarch__associate_ins_ops(struct arch *arch, const char *name)
{
const struct ins_ops *ops = NULL;
if (!strcmp(name, "bl"))
ops = &loongarch_call_ops;
else if (!strcmp(name, "jirl"))
ops = &ret_ops;
else if (!strcmp(name, "b") ||
!strncmp(name, "beq", 3) ||
!strncmp(name, "bne", 3) ||
!strncmp(name, "blt", 3) ||
!strncmp(name, "bge", 3) ||
!strncmp(name, "bltu", 4) ||
!strncmp(name, "bgeu", 4))
ops = &loongarch_jump_ops;
else
return NULL;
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_loongarch(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "loongarch";
arch->id = *id;
arch->associate_instruction_ops = loongarch__associate_ins_ops;
arch->objdump.comment_char = '#';
return arch;
}
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include "../disasm.h"
static
const struct ins_ops *mips__associate_ins_ops(struct arch *arch, const char *name)
{
const struct ins_ops *ops = NULL;
if (!strncmp(name, "bal", 3) ||
!strncmp(name, "bgezal", 6) ||
!strncmp(name, "bltzal", 6) ||
!strncmp(name, "bgtzal", 6) ||
!strncmp(name, "blezal", 6) ||
!strncmp(name, "beqzal", 6) ||
!strncmp(name, "bnezal", 6) ||
!strncmp(name, "bgtzl", 5) ||
!strncmp(name, "bltzl", 5) ||
!strncmp(name, "bgezl", 5) ||
!strncmp(name, "blezl", 5) ||
!strncmp(name, "jialc", 5) ||
!strncmp(name, "beql", 4) ||
!strncmp(name, "bnel", 4) ||
!strncmp(name, "jal", 3))
ops = &call_ops;
else if (!strncmp(name, "jr", 2))
ops = &ret_ops;
else if (name[0] == 'j' || name[0] == 'b')
ops = &jump_ops;
else
return NULL;
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_mips(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "mips";
arch->id = *id;
arch->objdump.comment_char = '#';
arch->associate_instruction_ops = mips__associate_ins_ops;
return arch;
}
@@ -0,0 +1,410 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <linux/compiler.h>
#include <linux/kernel.h>
#include "../annotate-data.h"
#include "../debug.h"
#include "../disasm.h"
#define PPC_OP(op) (((op) >> 26) & 0x3F)
#define PPC_21_30(R) (((R) >> 1) & 0x3ff)
#define PPC_22_30(R) (((R) >> 1) & 0x1ff)
#define MINUS_EXT_XO_FORM 234
#define SUB_EXT_XO_FORM 232
#define ADD_ZERO_EXT_XO_FORM 202
#define SUB_ZERO_EXT_XO_FORM 200
static int arithmetic__scnprintf(const struct ins *ins, char *bf, size_t size,
struct ins_operands *ops, int max_ins_name)
{
return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name,
ops->raw);
}
/*
* Sets the fields: multi_regs and "mem_ref".
* "mem_ref" is set for ops->source which is later used to
* fill the objdump->memory_ref-char field. This ops is currently
* used by powerpc and since binary instruction code is used to
* extract opcode, regs and offset, no other parsing is needed here.
*
* Dont set multi regs for 4 cases since it has only one operand
* for source:
* - Add to Minus One Extended XO-form ( Ex: addme, addmeo )
* - Subtract From Minus One Extended XO-form ( Ex: subfme )
* - Add to Zero Extended XO-form ( Ex: addze, addzeo )
* - Subtract From Zero Extended XO-form ( Ex: subfze )
*/
static int arithmetic__parse(const struct arch *arch __maybe_unused, struct ins_operands *ops,
struct map_symbol *ms __maybe_unused, struct disasm_line *dl)
{
int opcode = PPC_OP(dl->raw.raw_insn);
ops->source.mem_ref = false;
if (opcode == 31) {
if ((opcode != MINUS_EXT_XO_FORM) && (opcode != SUB_EXT_XO_FORM) &&
(opcode != ADD_ZERO_EXT_XO_FORM) && (opcode != SUB_ZERO_EXT_XO_FORM))
ops->source.multi_regs = true;
}
ops->target.mem_ref = false;
ops->target.multi_regs = false;
return 0;
}
static const struct ins_ops arithmetic_ops = {
.parse = arithmetic__parse,
.scnprintf = arithmetic__scnprintf,
};
static int load_store__scnprintf(const struct ins *ins, char *bf, size_t size,
struct ins_operands *ops, int max_ins_name)
{
return scnprintf(bf, size, "%-*s %s", max_ins_name, ins->name,
ops->raw);
}
/*
* Sets the fields: multi_regs and "mem_ref".
* "mem_ref" is set for ops->source which is later used to
* fill the objdump->memory_ref-char field. This ops is currently
* used by powerpc and since binary instruction code is used to
* extract opcode, regs and offset, no other parsing is needed here
*/
static int load_store__parse(const struct arch *arch __maybe_unused, struct ins_operands *ops,
struct map_symbol *ms __maybe_unused, struct disasm_line *dl __maybe_unused)
{
ops->source.mem_ref = true;
ops->source.multi_regs = false;
/* opcode 31 is of X form */
if (PPC_OP(dl->raw.raw_insn) == 31)
ops->source.multi_regs = true;
ops->target.mem_ref = false;
ops->target.multi_regs = false;
return 0;
}
static const struct ins_ops load_store_ops = {
.parse = load_store__parse,
.scnprintf = load_store__scnprintf,
};
static const struct ins_ops *powerpc__associate_instruction_ops(struct arch *arch, const char *name)
{
int i;
const struct ins_ops *ops;
/*
* - Interested only if instruction starts with 'b'.
* - Few start with 'b', but aren't branch instructions.
*/
if (name[0] != 'b' ||
!strncmp(name, "bcd", 3) ||
!strncmp(name, "brinc", 5) ||
!strncmp(name, "bper", 4))
return NULL;
ops = &jump_ops;
i = strlen(name) - 1;
if (i < 0)
return NULL;
/* ignore optional hints at the end of the instructions */
if (name[i] == '+' || name[i] == '-')
i--;
if (name[i] == 'l' || (name[i] == 'a' && name[i-1] == 'l')) {
/*
* if the instruction ends up with 'l' or 'la', then
* those are considered 'calls' since they update LR.
* ... except for 'bnl' which is branch if not less than
* and the absolute form of the same.
*/
if (strcmp(name, "bnl") && strcmp(name, "bnl+") &&
strcmp(name, "bnl-") && strcmp(name, "bnla") &&
strcmp(name, "bnla+") && strcmp(name, "bnla-"))
ops = &call_ops;
}
if (name[i] == 'r' && name[i-1] == 'l')
/*
* instructions ending with 'lr' are considered to be
* return instructions
*/
ops = &ret_ops;
arch__associate_ins_ops(arch, name, ops);
return ops;
}
struct insn_offset {
const char *name;
int value;
};
/*
* There are memory instructions with opcode 31 which are
* of X Form, Example:
* ldx RT,RA,RB
* ______________________________________
* | 31 | RT | RA | RB | 21 |/|
* --------------------------------------
* 0 6 11 16 21 30 31
*
* But all instructions with opcode 31 are not memory.
* Example: add RT,RA,RB
*
* Use bits 21 to 30 to check memory insns with 31 as opcode.
* In ins_array below, for ldx instruction:
* name => OP_31_XOP_LDX
* value => 21
*/
static struct insn_offset ins_array[] = {
{ .name = "OP_31_XOP_LXSIWZX", .value = 12, },
{ .name = "OP_31_XOP_LWARX", .value = 20, },
{ .name = "OP_31_XOP_LDX", .value = 21, },
{ .name = "OP_31_XOP_LWZX", .value = 23, },
{ .name = "OP_31_XOP_LDUX", .value = 53, },
{ .name = "OP_31_XOP_LWZUX", .value = 55, },
{ .name = "OP_31_XOP_LXSIWAX", .value = 76, },
{ .name = "OP_31_XOP_LDARX", .value = 84, },
{ .name = "OP_31_XOP_LBZX", .value = 87, },
{ .name = "OP_31_XOP_LVX", .value = 103, },
{ .name = "OP_31_XOP_LBZUX", .value = 119, },
{ .name = "OP_31_XOP_STXSIWX", .value = 140, },
{ .name = "OP_31_XOP_STDX", .value = 149, },
{ .name = "OP_31_XOP_STWX", .value = 151, },
{ .name = "OP_31_XOP_STDUX", .value = 181, },
{ .name = "OP_31_XOP_STWUX", .value = 183, },
{ .name = "OP_31_XOP_STBX", .value = 215, },
{ .name = "OP_31_XOP_STVX", .value = 231, },
{ .name = "OP_31_XOP_STBUX", .value = 247, },
{ .name = "OP_31_XOP_LHZX", .value = 279, },
{ .name = "OP_31_XOP_LHZUX", .value = 311, },
{ .name = "OP_31_XOP_LXVDSX", .value = 332, },
{ .name = "OP_31_XOP_LWAX", .value = 341, },
{ .name = "OP_31_XOP_LHAX", .value = 343, },
{ .name = "OP_31_XOP_LWAUX", .value = 373, },
{ .name = "OP_31_XOP_LHAUX", .value = 375, },
{ .name = "OP_31_XOP_STHX", .value = 407, },
{ .name = "OP_31_XOP_STHUX", .value = 439, },
{ .name = "OP_31_XOP_LXSSPX", .value = 524, },
{ .name = "OP_31_XOP_LDBRX", .value = 532, },
{ .name = "OP_31_XOP_LSWX", .value = 533, },
{ .name = "OP_31_XOP_LWBRX", .value = 534, },
{ .name = "OP_31_XOP_LFSUX", .value = 567, },
{ .name = "OP_31_XOP_LXSDX", .value = 588, },
{ .name = "OP_31_XOP_LSWI", .value = 597, },
{ .name = "OP_31_XOP_LFDX", .value = 599, },
{ .name = "OP_31_XOP_LFDUX", .value = 631, },
{ .name = "OP_31_XOP_STXSSPX", .value = 652, },
{ .name = "OP_31_XOP_STDBRX", .value = 660, },
{ .name = "OP_31_XOP_STXWX", .value = 661, },
{ .name = "OP_31_XOP_STWBRX", .value = 662, },
{ .name = "OP_31_XOP_STFSX", .value = 663, },
{ .name = "OP_31_XOP_STFSUX", .value = 695, },
{ .name = "OP_31_XOP_STXSDX", .value = 716, },
{ .name = "OP_31_XOP_STSWI", .value = 725, },
{ .name = "OP_31_XOP_STFDX", .value = 727, },
{ .name = "OP_31_XOP_STFDUX", .value = 759, },
{ .name = "OP_31_XOP_LXVW4X", .value = 780, },
{ .name = "OP_31_XOP_LHBRX", .value = 790, },
{ .name = "OP_31_XOP_LXVD2X", .value = 844, },
{ .name = "OP_31_XOP_LFIWAX", .value = 855, },
{ .name = "OP_31_XOP_LFIWZX", .value = 887, },
{ .name = "OP_31_XOP_STXVW4X", .value = 908, },
{ .name = "OP_31_XOP_STHBRX", .value = 918, },
{ .name = "OP_31_XOP_STXVD2X", .value = 972, },
{ .name = "OP_31_XOP_STFIWX", .value = 983, },
};
/*
* Arithmetic instructions which are having opcode as 31.
* These instructions are tracked to save the register state
* changes. Example:
*
* lwz r10,264(r3)
* add r31, r3, r3
* lwz r9, 0(r31)
*
* Here instruction tracking needs to identify the "add"
* instruction and save data type of r3 to r31. If a sample
* is hit at next "lwz r9, 0(r31)", by this instruction tracking,
* data type of r31 can be resolved.
*/
static struct insn_offset arithmetic_ins_op_31[] = {
{ .name = "SUB_CARRY_XO_FORM", .value = 8, },
{ .name = "MUL_HDW_XO_FORM1", .value = 9, },
{ .name = "ADD_CARRY_XO_FORM", .value = 10, },
{ .name = "MUL_HW_XO_FORM1", .value = 11, },
{ .name = "SUB_XO_FORM", .value = 40, },
{ .name = "MUL_HDW_XO_FORM", .value = 73, },
{ .name = "MUL_HW_XO_FORM", .value = 75, },
{ .name = "SUB_EXT_XO_FORM", .value = 136, },
{ .name = "ADD_EXT_XO_FORM", .value = 138, },
{ .name = "SUB_ZERO_EXT_XO_FORM", .value = 200, },
{ .name = "ADD_ZERO_EXT_XO_FORM", .value = 202, },
{ .name = "SUB_EXT_XO_FORM2", .value = 232, },
{ .name = "MUL_DW_XO_FORM", .value = 233, },
{ .name = "ADD_EXT_XO_FORM2", .value = 234, },
{ .name = "MUL_W_XO_FORM", .value = 235, },
{ .name = "ADD_XO_FORM", .value = 266, },
{ .name = "DIV_DW_XO_FORM1", .value = 457, },
{ .name = "DIV_W_XO_FORM1", .value = 459, },
{ .name = "DIV_DW_XO_FORM", .value = 489, },
{ .name = "DIV_W_XO_FORM", .value = 491, },
};
static struct insn_offset arithmetic_two_ops[] = {
{ .name = "mulli", .value = 7, },
{ .name = "subfic", .value = 8, },
{ .name = "addic", .value = 12, },
{ .name = "addic.", .value = 13, },
{ .name = "addi", .value = 14, },
{ .name = "addis", .value = 15, },
};
static int cmp_offset(const void *a, const void *b)
{
const struct insn_offset *val1 = a;
const struct insn_offset *val2 = b;
return (val1->value - val2->value);
}
const struct ins_ops *check_ppc_insn(struct disasm_line *dl)
{
int raw_insn = dl->raw.raw_insn;
int opcode = PPC_OP(raw_insn);
int mem_insn_31 = PPC_21_30(raw_insn);
struct insn_offset *ret;
struct insn_offset mem_insns_31_opcode = {
"OP_31_INSN",
mem_insn_31
};
char name_insn[32];
/*
* Instructions with opcode 32 to 63 are memory
* instructions in powerpc
*/
if ((opcode & 0x20)) {
/*
* Set name in case of raw instruction to
* opcode to be used in insn-stat
*/
if (!strlen(dl->ins.name)) {
sprintf(name_insn, "%d", opcode);
dl->ins.name = strdup(name_insn);
}
return &load_store_ops;
} else if (opcode == 31) {
/* Check for memory instructions with opcode 31 */
ret = bsearch(&mem_insns_31_opcode, ins_array, ARRAY_SIZE(ins_array), sizeof(ins_array[0]), cmp_offset);
if (ret) {
if (!strlen(dl->ins.name))
dl->ins.name = strdup(ret->name);
return &load_store_ops;
} else {
mem_insns_31_opcode.value = PPC_22_30(raw_insn);
ret = bsearch(&mem_insns_31_opcode, arithmetic_ins_op_31, ARRAY_SIZE(arithmetic_ins_op_31),
sizeof(arithmetic_ins_op_31[0]), cmp_offset);
if (ret != NULL)
return &arithmetic_ops;
/* Bits 21 to 30 has value 444 for "mr" insn ie, OR X form */
if (PPC_21_30(raw_insn) == 444)
return &arithmetic_ops;
}
} else {
mem_insns_31_opcode.value = opcode;
ret = bsearch(&mem_insns_31_opcode, arithmetic_two_ops, ARRAY_SIZE(arithmetic_two_ops),
sizeof(arithmetic_two_ops[0]), cmp_offset);
if (ret != NULL)
return &arithmetic_ops;
}
return NULL;
}
/*
* Instruction tracking function to track register state moves.
* Example sequence:
* ld r10,264(r3)
* mr r31,r3
* <<after some sequence>
* ld r9,312(r31)
*
* Previous instruction sequence shows that register state of r3
* is moved to r31. update_insn_state_powerpc tracks these state
* changes
*/
#ifdef HAVE_LIBDW_SUPPORT
static void update_insn_state_powerpc(struct type_state *state,
struct data_loc_info *dloc, Dwarf_Die * cu_die __maybe_unused,
struct disasm_line *dl)
{
struct annotated_insn_loc loc;
struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE];
struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];
struct type_state_reg *tsr;
u32 insn_offset = dl->al.offset;
if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0)
return;
/*
* Value 444 for bits 21:30 is for "mr"
* instruction. "mr" is extended OR. So set the
* source and destination reg correctly
*/
if (PPC_21_30(dl->raw.raw_insn) == 444) {
int src_reg = src->reg1;
src->reg1 = dst->reg1;
dst->reg1 = src_reg;
}
if (!has_reg_type(state, dst->reg1))
return;
tsr = &state->regs[dst->reg1];
if (!has_reg_type(state, src->reg1) ||
!state->regs[src->reg1].ok) {
tsr->ok = false;
return;
}
tsr->type = state->regs[src->reg1].type;
tsr->kind = state->regs[src->reg1].kind;
tsr->ok = true;
pr_debug_dtp("mov [%x] reg%d -> reg%d",
insn_offset, src->reg1, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
#endif /* HAVE_LIBDW_SUPPORT */
const struct arch *arch__new_powerpc(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "powerpc";
arch->id = *id;
arch->objdump.comment_char = '#';
annotate_opts.show_asm_raw = true;
arch->associate_instruction_ops = powerpc__associate_instruction_ops;
#ifdef HAVE_LIBDW_SUPPORT
arch->update_insn_state = update_insn_state_powerpc;
#endif
return arch;
}
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include "../disasm.h"
static
const struct ins_ops *riscv64__associate_ins_ops(struct arch *arch, const char *name)
{
const struct ins_ops *ops = NULL;
if (!strncmp(name, "jal", 3) ||
!strncmp(name, "jr", 2) ||
!strncmp(name, "call", 4))
ops = &call_ops;
else if (!strncmp(name, "ret", 3))
ops = &ret_ops;
else if (name[0] == 'j' || name[0] == 'b')
ops = &jump_ops;
else
return NULL;
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_riscv64(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "riscv";
arch->id = *id;
arch->objdump.comment_char = '#';
arch->associate_instruction_ops = riscv64__associate_ins_ops;
return arch;
}
@@ -0,0 +1,190 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <linux/compiler.h>
#include "../debug.h"
#include "../disasm.h"
#include "../map.h"
#include "../maps.h"
#include "../symbol.h"
#include "../thread.h"
#include "../annotate.h"
#include "../annotate-data.h"
static int s390_call__parse(const struct arch *arch, struct ins_operands *ops,
struct map_symbol *ms,
struct disasm_line *dl __maybe_unused)
{
char *endptr, *tok, *name;
struct map *map = ms->map;
struct addr_map_symbol target;
tok = strchr(ops->raw, ',');
if (!tok)
return -1;
ops->target.addr = strtoull(tok + 1, &endptr, 16);
name = strchr(endptr, '<');
if (name == NULL)
return -1;
name++;
if (arch->objdump.skip_functions_char &&
strchr(name, arch->objdump.skip_functions_char))
return -1;
tok = strchr(name, '>');
if (tok == NULL)
return -1;
*tok = '\0';
ops->target.name = strdup(name);
*tok = '>';
if (ops->target.name == NULL)
return -1;
target = (struct addr_map_symbol) {
.ms = { .map = map__get(map), },
.addr = map__objdump_2mem(map, ops->target.addr),
};
if (maps__find_ams(thread__maps(ms->thread), &target) == 0 &&
map__rip_2objdump(target.ms.map, map__map_ip(target.ms.map, target.addr)) == ops->target.addr)
ops->target.sym = target.ms.sym;
addr_map_symbol__exit(&target);
return 0;
}
static const struct ins_ops s390_call_ops = {
.parse = s390_call__parse,
.scnprintf = call__scnprintf,
.is_call = true,
};
static int s390_mov__parse(const struct arch *arch __maybe_unused,
struct ins_operands *ops,
struct map_symbol *ms __maybe_unused,
struct disasm_line *dl __maybe_unused)
{
char *s = strchr(ops->raw, ','), *target, *endptr;
if (s == NULL)
return -1;
*s = '\0';
ops->source.raw = strdup(ops->raw);
*s = ',';
if (ops->source.raw == NULL)
return -1;
target = ++s;
ops->target.raw = strdup(target);
if (ops->target.raw == NULL)
goto out_free_source;
ops->target.addr = strtoull(target, &endptr, 16);
if (endptr == target)
goto out_free_target;
s = strchr(endptr, '<');
if (s == NULL)
goto out_free_target;
endptr = strchr(s + 1, '>');
if (endptr == NULL)
goto out_free_target;
*endptr = '\0';
ops->target.name = strdup(s + 1);
*endptr = '>';
if (ops->target.name == NULL)
goto out_free_target;
return 0;
out_free_target:
zfree(&ops->target.raw);
out_free_source:
zfree(&ops->source.raw);
return -1;
}
static const struct ins_ops s390_mov_ops = {
.parse = s390_mov__parse,
.scnprintf = mov__scnprintf,
};
static const struct ins_ops *s390__associate_ins_ops(struct arch *arch, const char *name)
{
const struct ins_ops *ops = NULL;
/* catch all kind of jumps */
if (strchr(name, 'j') ||
!strncmp(name, "bct", 3) ||
!strncmp(name, "br", 2))
ops = &jump_ops;
/* override call/returns */
if (!strcmp(name, "bras") ||
!strcmp(name, "brasl") ||
!strcmp(name, "basr"))
ops = &s390_call_ops;
if (!strcmp(name, "br"))
ops = &ret_ops;
/* override load/store relative to PC */
if (!strcmp(name, "lrl") ||
!strcmp(name, "lgrl") ||
!strcmp(name, "lgfrl") ||
!strcmp(name, "llgfrl") ||
!strcmp(name, "strl") ||
!strcmp(name, "stgrl"))
ops = &s390_mov_ops;
if (ops)
arch__associate_ins_ops(arch, name, ops);
return ops;
}
static int s390__cpuid_parse(struct arch *arch, const char *cpuid)
{
unsigned int family;
char model[16], model_c[16], cpumf_v[16], cpumf_a[16];
int ret;
/*
* cpuid string format:
* "IBM,family,model-capacity,model[,cpum_cf-version,cpum_cf-authorization]"
*/
ret = sscanf(cpuid, "%*[^,],%u,%[^,],%[^,],%[^,],%s", &family, model_c,
model, cpumf_v, cpumf_a);
if (ret >= 2) {
arch->family = family;
arch->model = 0;
return 0;
}
return -1;
}
const struct arch *arch__new_s390(const struct e_machine_and_e_flags *id, const char *cpuid)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "s390";
arch->id = *id;
arch->associate_instruction_ops = s390__associate_ins_ops;
if (cpuid) {
if (s390__cpuid_parse(arch, cpuid)) {
errno = SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_CPUID_PARSING;
return NULL;
}
}
arch->objdump.comment_char = '#';
return arch;
}
@@ -0,0 +1,177 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include "../../util/disasm.h"
static int is_branch_cond(const char *cond)
{
if (cond[0] == '\0')
return 1;
if (cond[0] == 'a' && cond[1] == '\0')
return 1;
if (cond[0] == 'c' &&
(cond[1] == 'c' || cond[1] == 's') &&
cond[2] == '\0')
return 1;
if (cond[0] == 'e' &&
(cond[1] == '\0' ||
(cond[1] == 'q' && cond[2] == '\0')))
return 1;
if (cond[0] == 'g' &&
(cond[1] == '\0' ||
(cond[1] == 't' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == 'u' && cond[3] == '\0')))
return 1;
if (cond[0] == 'l' &&
(cond[1] == '\0' ||
(cond[1] == 't' && cond[2] == '\0') ||
(cond[1] == 'u' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == 'u' && cond[3] == '\0')))
return 1;
if (cond[0] == 'n' &&
(cond[1] == '\0' ||
(cond[1] == 'e' && cond[2] == '\0') ||
(cond[1] == 'z' && cond[2] == '\0') ||
(cond[1] == 'e' && cond[2] == 'g' && cond[3] == '\0')))
return 1;
if (cond[0] == 'b' &&
cond[1] == 'p' &&
cond[2] == 'o' &&
cond[3] == 's' &&
cond[4] == '\0')
return 1;
if (cond[0] == 'v' &&
(cond[1] == 'c' || cond[1] == 's') &&
cond[2] == '\0')
return 1;
if (cond[0] == 'b' &&
cond[1] == 'z' &&
cond[2] == '\0')
return 1;
return 0;
}
static int is_branch_reg_cond(const char *cond)
{
if ((cond[0] == 'n' || cond[0] == 'l') &&
cond[1] == 'z' &&
cond[2] == '\0')
return 1;
if (cond[0] == 'z' &&
cond[1] == '\0')
return 1;
if ((cond[0] == 'g' || cond[0] == 'l') &&
cond[1] == 'e' &&
cond[2] == 'z' &&
cond[3] == '\0')
return 1;
if (cond[0] == 'g' &&
cond[1] == 'z' &&
cond[2] == '\0')
return 1;
return 0;
}
static int is_branch_float_cond(const char *cond)
{
if (cond[0] == '\0')
return 1;
if ((cond[0] == 'a' || cond[0] == 'e' ||
cond[0] == 'z' || cond[0] == 'g' ||
cond[0] == 'l' || cond[0] == 'n' ||
cond[0] == 'o' || cond[0] == 'u') &&
cond[1] == '\0')
return 1;
if (((cond[0] == 'g' && cond[1] == 'e') ||
(cond[0] == 'l' && (cond[1] == 'e' ||
cond[1] == 'g')) ||
(cond[0] == 'n' && (cond[1] == 'e' ||
cond[1] == 'z')) ||
(cond[0] == 'u' && (cond[1] == 'e' ||
cond[1] == 'g' ||
cond[1] == 'l'))) &&
cond[2] == '\0')
return 1;
if (cond[0] == 'u' &&
(cond[1] == 'g' || cond[1] == 'l') &&
cond[2] == 'e' &&
cond[3] == '\0')
return 1;
return 0;
}
static const struct ins_ops *sparc__associate_instruction_ops(struct arch *arch, const char *name)
{
const struct ins_ops *ops = NULL;
if (!strcmp(name, "call") ||
!strcmp(name, "jmp") ||
!strcmp(name, "jmpl")) {
ops = &call_ops;
} else if (!strcmp(name, "ret") ||
!strcmp(name, "retl") ||
!strcmp(name, "return")) {
ops = &ret_ops;
} else if (!strcmp(name, "mov")) {
ops = &mov_ops;
} else {
if (name[0] == 'c' &&
(name[1] == 'w' || name[1] == 'x'))
name += 2;
if (name[0] == 'b') {
const char *cond = name + 1;
if (cond[0] == 'r') {
if (is_branch_reg_cond(cond + 1))
ops = &jump_ops;
} else if (is_branch_cond(cond)) {
ops = &jump_ops;
}
} else if (name[0] == 'f' && name[1] == 'b') {
if (is_branch_float_cond(name + 2))
ops = &jump_ops;
}
}
if (ops)
arch__associate_ins_ops(arch, name, ops);
return ops;
}
const struct arch *arch__new_sparc(const struct e_machine_and_e_flags *id,
const char *cpuid __maybe_unused)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "sparc";
arch->id = *id;
arch->associate_instruction_ops = sparc__associate_instruction_ops;
arch->objdump.comment_char = '#';
return arch;
}
@@ -0,0 +1,852 @@
// SPDX-License-Identifier: GPL-2.0
#include <string.h>
#include <linux/compiler.h>
#include <linux/zalloc.h>
#include <assert.h>
#include <inttypes.h>
#include "../annotate-data.h"
#include "../debug.h"
#include "../disasm.h"
#include "../dso.h"
#include "../map.h"
#include "../string2.h" // strstarts
#include "../symbol.h"
/*
* x86 instruction nmemonic table to parse disasm lines for annotate.
* This table is searched twice - one for exact match and another for
* match without a size suffix (b, w, l, q) in case of AT&T syntax.
*
* So this table should not have entries with the suffix unless it's
* a complete different instruction than ones without the suffix.
*/
static const struct ins x86__instructions[] = {
{ .name = "adc", .ops = &mov_ops, },
{ .name = "add", .ops = &mov_ops, },
{ .name = "addsd", .ops = &mov_ops, },
{ .name = "and", .ops = &mov_ops, },
{ .name = "andpd", .ops = &mov_ops, },
{ .name = "andps", .ops = &mov_ops, },
{ .name = "bsr", .ops = &mov_ops, },
{ .name = "bt", .ops = &mov_ops, },
{ .name = "btr", .ops = &mov_ops, },
{ .name = "bts", .ops = &mov_ops, },
{ .name = "call", .ops = &call_ops, },
{ .name = "cmovae", .ops = &mov_ops, },
{ .name = "cmovbe", .ops = &mov_ops, },
{ .name = "cmove", .ops = &mov_ops, },
{ .name = "cmp", .ops = &mov_ops, },
{ .name = "cmpxch", .ops = &mov_ops, },
{ .name = "cmpxchg", .ops = &mov_ops, },
{ .name = "cs", .ops = &mov_ops, },
{ .name = "dec", .ops = &dec_ops, },
{ .name = "divsd", .ops = &mov_ops, },
{ .name = "divss", .ops = &mov_ops, },
{ .name = "gs", .ops = &mov_ops, },
{ .name = "imul", .ops = &mov_ops, },
{ .name = "inc", .ops = &dec_ops, },
{ .name = "ja", .ops = &jump_ops, },
{ .name = "jae", .ops = &jump_ops, },
{ .name = "jb", .ops = &jump_ops, },
{ .name = "jbe", .ops = &jump_ops, },
{ .name = "jc", .ops = &jump_ops, },
{ .name = "jcxz", .ops = &jump_ops, },
{ .name = "je", .ops = &jump_ops, },
{ .name = "jecxz", .ops = &jump_ops, },
{ .name = "jg", .ops = &jump_ops, },
{ .name = "jge", .ops = &jump_ops, },
{ .name = "jl", .ops = &jump_ops, },
{ .name = "jle", .ops = &jump_ops, },
{ .name = "jmp", .ops = &jump_ops, },
{ .name = "jna", .ops = &jump_ops, },
{ .name = "jnae", .ops = &jump_ops, },
{ .name = "jnb", .ops = &jump_ops, },
{ .name = "jnbe", .ops = &jump_ops, },
{ .name = "jnc", .ops = &jump_ops, },
{ .name = "jne", .ops = &jump_ops, },
{ .name = "jng", .ops = &jump_ops, },
{ .name = "jnge", .ops = &jump_ops, },
{ .name = "jnl", .ops = &jump_ops, },
{ .name = "jnle", .ops = &jump_ops, },
{ .name = "jno", .ops = &jump_ops, },
{ .name = "jnp", .ops = &jump_ops, },
{ .name = "jns", .ops = &jump_ops, },
{ .name = "jnz", .ops = &jump_ops, },
{ .name = "jo", .ops = &jump_ops, },
{ .name = "jp", .ops = &jump_ops, },
{ .name = "jpe", .ops = &jump_ops, },
{ .name = "jpo", .ops = &jump_ops, },
{ .name = "jrcxz", .ops = &jump_ops, },
{ .name = "js", .ops = &jump_ops, },
{ .name = "jz", .ops = &jump_ops, },
{ .name = "lea", .ops = &mov_ops, },
{ .name = "lock", .ops = &lock_ops, },
{ .name = "mov", .ops = &mov_ops, },
{ .name = "movapd", .ops = &mov_ops, },
{ .name = "movaps", .ops = &mov_ops, },
{ .name = "movdqa", .ops = &mov_ops, },
{ .name = "movdqu", .ops = &mov_ops, },
{ .name = "movsb", .ops = &mov_ops, },
{ .name = "movsd", .ops = &mov_ops, },
{ .name = "movsl", .ops = &mov_ops, },
{ .name = "movss", .ops = &mov_ops, },
{ .name = "movsw", .ops = &mov_ops, },
{ .name = "movupd", .ops = &mov_ops, },
{ .name = "movups", .ops = &mov_ops, },
{ .name = "movzb", .ops = &mov_ops, },
{ .name = "movzl", .ops = &mov_ops, },
{ .name = "movzw", .ops = &mov_ops, },
{ .name = "mulsd", .ops = &mov_ops, },
{ .name = "mulss", .ops = &mov_ops, },
{ .name = "nop", .ops = &nop_ops, },
{ .name = "or", .ops = &mov_ops, },
{ .name = "orps", .ops = &mov_ops, },
{ .name = "paddq", .ops = &mov_ops, },
{ .name = "pand", .ops = &mov_ops, },
{ .name = "pcmpeqb", .ops = &mov_ops, },
{ .name = "por", .ops = &mov_ops, },
{ .name = "rcl", .ops = &mov_ops, },
{ .name = "ret", .ops = &ret_ops, },
{ .name = "sbb", .ops = &mov_ops, },
{ .name = "sete", .ops = &mov_ops, },
{ .name = "sub", .ops = &mov_ops, },
{ .name = "subsd", .ops = &mov_ops, },
{ .name = "test", .ops = &mov_ops, },
{ .name = "tzcnt", .ops = &mov_ops, },
{ .name = "ucomisd", .ops = &mov_ops, },
{ .name = "ucomiss", .ops = &mov_ops, },
{ .name = "vaddsd", .ops = &mov_ops, },
{ .name = "vandpd", .ops = &mov_ops, },
{ .name = "vmovdqa", .ops = &mov_ops, },
{ .name = "vmovq", .ops = &mov_ops, },
{ .name = "vmovsd", .ops = &mov_ops, },
{ .name = "vmulsd", .ops = &mov_ops, },
{ .name = "vorpd", .ops = &mov_ops, },
{ .name = "vsubsd", .ops = &mov_ops, },
{ .name = "vucomisd", .ops = &mov_ops, },
{ .name = "xadd", .ops = &mov_ops, },
{ .name = "xbegin", .ops = &jump_ops, },
{ .name = "xchg", .ops = &mov_ops, },
{ .name = "xor", .ops = &mov_ops, },
{ .name = "xorpd", .ops = &mov_ops, },
{ .name = "xorps", .ops = &mov_ops, },
};
static bool amd__ins_is_fused(const struct arch *arch, const char *ins1,
const char *ins2)
{
if (strstr(ins2, "jmp"))
return false;
/* Family >= 15h supports cmp/test + branch fusion */
if (arch->family >= 0x15 && (strstarts(ins1, "test") ||
(strstarts(ins1, "cmp") && !strstr(ins1, "xchg")))) {
return true;
}
/* Family >= 19h supports some ALU + branch fusion */
if (arch->family >= 0x19 && (strstarts(ins1, "add") ||
strstarts(ins1, "sub") || strstarts(ins1, "and") ||
strstarts(ins1, "inc") || strstarts(ins1, "dec") ||
strstarts(ins1, "or") || strstarts(ins1, "xor"))) {
return true;
}
return false;
}
static bool intel__ins_is_fused(const struct arch *arch, const char *ins1,
const char *ins2)
{
if (arch->family != 6 || arch->model < 0x1e || strstr(ins2, "jmp"))
return false;
if (arch->model == 0x1e) {
/* Nehalem */
if ((strstr(ins1, "cmp") && !strstr(ins1, "xchg")) ||
strstr(ins1, "test")) {
return true;
}
} else {
/* Newer platform */
if ((strstr(ins1, "cmp") && !strstr(ins1, "xchg")) ||
strstr(ins1, "test") ||
strstr(ins1, "add") ||
strstr(ins1, "sub") ||
strstr(ins1, "and") ||
strstr(ins1, "inc") ||
strstr(ins1, "dec")) {
return true;
}
}
return false;
}
static int x86__cpuid_parse(struct arch *arch, const char *cpuid)
{
unsigned int family, model, stepping;
int ret;
/*
* cpuid = "GenuineIntel,family,model,stepping"
*/
ret = sscanf(cpuid, "%*[^,],%u,%u,%u", &family, &model, &stepping);
if (ret == 3) {
arch->family = family;
arch->model = model;
arch->ins_is_fused = strstarts(cpuid, "AuthenticAMD") ?
amd__ins_is_fused :
intel__ins_is_fused;
return 0;
}
return -1;
}
#ifdef HAVE_LIBDW_SUPPORT
static void invalidate_reg_state(struct type_state_reg *reg)
{
reg->kind = TSR_KIND_INVALID;
reg->ok = false;
reg->lifetime_active = false;
reg->lifetime_end = 0;
reg->copied_from = -1;
}
static void update_insn_state_x86(struct type_state *state,
struct data_loc_info *dloc, Dwarf_Die *cu_die,
struct disasm_line *dl)
{
struct annotated_insn_loc loc;
struct annotated_op_loc *src = &loc.ops[INSN_OP_SOURCE];
struct annotated_op_loc *dst = &loc.ops[INSN_OP_TARGET];
struct type_state_reg *tsr;
Dwarf_Die type_die;
u32 insn_offset = dl->al.offset;
int fbreg = dloc->fbreg;
int fboff = 0;
if (annotate_get_insn_location(dloc->arch, dl, &loc) < 0)
return;
if (ins__is_call(&dl->ins)) {
struct symbol *func = dl->ops.target.sym;
const char *call_name;
u64 call_addr;
/* Try to resolve the call target name */
if (func)
call_name = func->name;
else
call_name = dl->ops.target.name;
/* __fentry__ will preserve all registers */
if (call_name && !strcmp(call_name, "__fentry__"))
return;
if (call_name)
pr_debug_dtp("call [%x] %s\n", insn_offset, call_name);
else
pr_debug_dtp("call [%x] <unknown>\n", insn_offset);
/* Invalidate caller-saved registers after call */
call_addr = map__rip_2objdump(dloc->ms->map,
dloc->ms->sym->start + dl->al.offset);
for (unsigned i = 0; i < ARRAY_SIZE(state->regs); i++) {
struct type_state_reg *reg = &state->regs[i];
if (!reg->caller_saved)
continue;
/* Keep register valid within DWARF location lifetime */
if (reg->lifetime_active && call_addr < reg->lifetime_end)
continue;
invalidate_reg_state(reg);
}
/* Update register with the return type (if any) */
if (call_name && die_find_func_rettype(cu_die, call_name, &type_die)) {
tsr = &state->regs[state->ret_reg];
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("call [%x] return -> reg%d",
insn_offset, state->ret_reg);
pr_debug_type_name(&type_die, tsr->kind);
}
return;
}
if (!strncmp(dl->ins.name, "add", 3)) {
u64 imm_value = -1ULL;
int offset;
const char *var_name = NULL;
struct map_symbol *ms = dloc->ms;
u64 ip = ms->sym->start + dl->al.offset;
if (!has_reg_type(state, dst->reg1))
return;
tsr = &state->regs[dst->reg1];
tsr->copied_from = -1;
tsr->lifetime_active = false;
tsr->lifetime_end = 0;
if (src->imm)
imm_value = src->offset;
else if (has_reg_type(state, src->reg1) &&
state->regs[src->reg1].kind == TSR_KIND_CONST)
imm_value = state->regs[src->reg1].imm_value;
else if (src->reg1 == DWARF_REG_PC) {
u64 var_addr = annotate_calc_pcrel(dloc->ms, ip,
src->offset, dl);
if (get_global_var_info(dloc, var_addr,
&var_name, &offset) &&
!strcmp(var_name, "this_cpu_off") &&
tsr->kind == TSR_KIND_CONST) {
tsr->kind = TSR_KIND_PERCPU_BASE;
tsr->offset = 0;
tsr->ok = true;
imm_value = tsr->imm_value;
}
}
else
return;
/* Ignore add to non-pointer or non-const types */
if (tsr->kind == TSR_KIND_POINTER ||
(dwarf_tag(&tsr->type) == DW_TAG_pointer_type &&
src->reg1 != DWARF_REG_PC && tsr->kind == TSR_KIND_TYPE && !dst->mem_ref)) {
tsr->offset += imm_value;
pr_debug_dtp("add [%x] offset %#"PRIx64" to reg%d",
insn_offset, imm_value, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
if (tsr->kind == TSR_KIND_CONST)
tsr->imm_value += imm_value;
if (tsr->kind != TSR_KIND_PERCPU_BASE)
return;
if (get_global_var_type(cu_die, dloc, ip, imm_value, &offset,
&type_die) && offset == 0) {
/*
* This is not a pointer type, but it should be treated
* as a pointer.
*/
tsr->type = type_die;
tsr->kind = TSR_KIND_PERCPU_POINTER;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("add [%x] percpu %#"PRIx64" -> reg%d",
insn_offset, imm_value, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
return;
}
if (!strncmp(dl->ins.name, "sub", 3)) {
u64 imm_value = -1ULL;
if (!has_reg_type(state, dst->reg1))
return;
tsr = &state->regs[dst->reg1];
tsr->copied_from = -1;
tsr->lifetime_active = false;
tsr->lifetime_end = 0;
if (src->imm)
imm_value = src->offset;
else if (has_reg_type(state, src->reg1) &&
state->regs[src->reg1].kind == TSR_KIND_CONST)
imm_value = state->regs[src->reg1].imm_value;
if (tsr->kind == TSR_KIND_POINTER ||
(dwarf_tag(&tsr->type) == DW_TAG_pointer_type &&
src->reg1 != DWARF_REG_PC && tsr->kind == TSR_KIND_TYPE && !dst->mem_ref)) {
tsr->offset -= imm_value;
pr_debug_dtp("sub [%x] offset %#"PRIx64" to reg%d",
insn_offset, imm_value, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
if (tsr->kind == TSR_KIND_CONST)
tsr->imm_value -= imm_value;
return;
}
if (!strncmp(dl->ins.name, "lea", 3)) {
int sreg = src->reg1;
struct type_state_reg src_tsr;
if (!has_reg_type(state, sreg) ||
!has_reg_type(state, dst->reg1) ||
!src->mem_ref)
return;
src_tsr = state->regs[sreg];
tsr = &state->regs[dst->reg1];
invalidate_reg_state(tsr);
/* Case 1: Based on stack pointer or frame pointer */
if (sreg == fbreg || sreg == state->stack_reg) {
struct type_state_stack *stack;
int offset = src->offset - fboff;
stack = find_stack_state(state, offset);
if (!stack)
return;
tsr->type = stack->type;
tsr->kind = TSR_KIND_POINTER;
tsr->offset = offset - stack->offset;
tsr->ok = true;
if (sreg == fbreg) {
pr_debug_dtp("lea [%x] address of -%#x(stack) -> reg%d",
insn_offset, -src->offset, dst->reg1);
} else {
pr_debug_dtp("lea [%x] address of %#x(reg%d) -> reg%d",
insn_offset, src->offset, sreg, dst->reg1);
}
pr_debug_type_name(&tsr->type, tsr->kind);
}
/* Case 2: Based on a register holding a typed pointer */
else if (src_tsr.ok && (src_tsr.kind == TSR_KIND_POINTER ||
(dwarf_tag(&src_tsr.type) == DW_TAG_pointer_type &&
src_tsr.kind == TSR_KIND_TYPE))) {
if (src_tsr.kind == TSR_KIND_TYPE &&
__die_get_real_type(&state->regs[sreg].type, &type_die) == NULL)
return;
if (src_tsr.kind == TSR_KIND_POINTER)
type_die = state->regs[sreg].type;
/* Check if the target type has a member at the new offset */
if (die_get_member_type(&type_die,
src->offset + src_tsr.offset, &type_die) == NULL)
return;
tsr->type = src_tsr.type;
tsr->kind = src_tsr.kind;
tsr->offset = src->offset + src_tsr.offset;
tsr->ok = true;
pr_debug_dtp("lea [%x] address of %s%#x(reg%d) -> reg%d",
insn_offset, src->offset < 0 ? "-" : "",
abs(src->offset), sreg, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
return;
}
/* Invalidate register states for other ops which may change pointers */
if (has_reg_type(state, dst->reg1) && !dst->mem_ref &&
dwarf_tag(&state->regs[dst->reg1].type) == DW_TAG_pointer_type) {
if (!strncmp(dl->ins.name, "imul", 4) || !strncmp(dl->ins.name, "mul", 3) ||
!strncmp(dl->ins.name, "idiv", 4) || !strncmp(dl->ins.name, "div", 3) ||
!strncmp(dl->ins.name, "shl", 3) || !strncmp(dl->ins.name, "shr", 3) ||
!strncmp(dl->ins.name, "sar", 3) || !strncmp(dl->ins.name, "and", 3) ||
!strncmp(dl->ins.name, "or", 2) || !strncmp(dl->ins.name, "neg", 3) ||
!strncmp(dl->ins.name, "inc", 3) || !strncmp(dl->ins.name, "dec", 3)) {
pr_debug_dtp("%s [%x] invalidate reg%d\n",
dl->ins.name, insn_offset, dst->reg1);
invalidate_reg_state(&state->regs[dst->reg1]);
return;
}
if (!strncmp(dl->ins.name, "xor", 3) && dst->reg1 == src->reg1) {
/* xor reg, reg clears the register */
pr_debug_dtp("xor [%x] clear reg%d\n",
insn_offset, dst->reg1);
state->regs[dst->reg1].kind = TSR_KIND_CONST;
state->regs[dst->reg1].imm_value = 0;
state->regs[dst->reg1].ok = true;
state->regs[dst->reg1].lifetime_active = false;
state->regs[dst->reg1].lifetime_end = 0;
state->regs[dst->reg1].copied_from = -1;
return;
}
}
if (strncmp(dl->ins.name, "mov", 3))
return;
if (dloc->fb_cfa) {
u64 ip = dloc->ms->sym->start + dl->al.offset;
u64 pc = map__rip_2objdump(dloc->ms->map, ip);
if (die_get_cfa(dloc->di->dbg, pc, &fbreg, &fboff) < 0)
fbreg = -1;
}
/* Case 1. register to register or segment:offset to register transfers */
if (!src->mem_ref && !dst->mem_ref) {
if (!has_reg_type(state, dst->reg1))
return;
tsr = &state->regs[dst->reg1];
tsr->copied_from = -1;
if (dso__kernel(map__dso(dloc->ms->map)) &&
src->segment == INSN_SEG_X86_GS && src->imm) {
u64 ip = dloc->ms->sym->start + dl->al.offset;
u64 var_addr;
int offset;
/*
* In kernel, %gs points to a per-cpu region for the
* current CPU. Access with a constant offset should
* be treated as a global variable access.
*/
var_addr = src->offset;
if (var_addr == 40) {
tsr->kind = TSR_KIND_CANARY;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] stack canary -> reg%d\n",
insn_offset, dst->reg1);
return;
}
if (!get_global_var_type(cu_die, dloc, ip, var_addr,
&offset, &type_die) ||
!die_get_member_type(&type_die, offset, &type_die)) {
invalidate_reg_state(tsr);
return;
}
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] this-cpu addr=%#"PRIx64" -> reg%d",
insn_offset, var_addr, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
return;
}
if (src->imm) {
tsr->kind = TSR_KIND_CONST;
tsr->imm_value = src->offset;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] imm=%#x -> reg%d\n",
insn_offset, tsr->imm_value, dst->reg1);
return;
}
if (!has_reg_type(state, src->reg1) ||
!state->regs[src->reg1].ok) {
invalidate_reg_state(tsr);
return;
}
tsr->type = state->regs[src->reg1].type;
tsr->kind = state->regs[src->reg1].kind;
tsr->imm_value = state->regs[src->reg1].imm_value;
tsr->offset = state->regs[src->reg1].offset;
tsr->lifetime_active = state->regs[src->reg1].lifetime_active;
tsr->lifetime_end = state->regs[src->reg1].lifetime_end;
tsr->ok = true;
/* To copy back the variable type later (hopefully) */
if (tsr->kind == TSR_KIND_TYPE || tsr->kind == TSR_KIND_POINTER)
tsr->copied_from = src->reg1;
pr_debug_dtp("mov [%x] reg%d -> reg%d",
insn_offset, src->reg1, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
/* Case 2. memory to register transers */
if (src->mem_ref && !dst->mem_ref) {
int sreg = src->reg1;
if (!has_reg_type(state, dst->reg1))
return;
tsr = &state->regs[dst->reg1];
tsr->copied_from = -1;
retry:
/* Check stack variables with offset */
if (sreg == fbreg || sreg == state->stack_reg) {
struct type_state_stack *stack;
int offset = src->offset - fboff;
stack = find_stack_state(state, offset);
if (stack == NULL) {
invalidate_reg_state(tsr);
return;
} else if (!stack->compound) {
tsr->type = stack->type;
tsr->kind = stack->kind;
tsr->offset = stack->ptr_offset;
tsr->ok = true;
} else if (die_get_member_type(&stack->type,
offset - stack->offset,
&type_die)) {
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
} else {
invalidate_reg_state(tsr);
return;
}
if (sreg == fbreg) {
pr_debug_dtp("mov [%x] -%#x(stack) -> reg%d",
insn_offset, -offset, dst->reg1);
} else {
pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d",
insn_offset, offset, sreg, dst->reg1);
}
pr_debug_type_name(&tsr->type, tsr->kind);
}
/* And then dereference the pointer if it has one */
else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&
state->regs[sreg].kind == TSR_KIND_TYPE &&
die_deref_ptr_type(&state->regs[sreg].type,
src->offset + state->regs[sreg].offset, &type_die)) {
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] %#x(reg%d) -> reg%d",
insn_offset, src->offset, sreg, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
/* Handle dereference of TSR_KIND_POINTER registers */
else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&
state->regs[sreg].kind == TSR_KIND_POINTER &&
die_get_member_type(&state->regs[sreg].type,
src->offset + state->regs[sreg].offset, &type_die)) {
tsr->type = state->regs[sreg].type;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = src->offset + state->regs[sreg].offset;
tsr->ok = true;
pr_debug_dtp("mov [%x] addr %#x(reg%d) -> reg%d",
insn_offset, src->offset, sreg, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
/* Or check if it's a global variable */
else if (sreg == DWARF_REG_PC) {
struct map_symbol *ms = dloc->ms;
u64 ip = ms->sym->start + dl->al.offset;
u64 addr;
int offset;
addr = annotate_calc_pcrel(ms, ip, src->offset, dl);
if (!get_global_var_type(cu_die, dloc, ip, addr, &offset,
&type_die) ||
!die_get_member_type(&type_die, offset, &type_die)) {
invalidate_reg_state(tsr);
return;
}
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] global addr=%"PRIx64" -> reg%d",
insn_offset, addr, dst->reg1);
pr_debug_type_name(&type_die, tsr->kind);
}
/* And check percpu access with base register */
else if (has_reg_type(state, sreg) &&
state->regs[sreg].kind == TSR_KIND_PERCPU_BASE) {
u64 ip = dloc->ms->sym->start + dl->al.offset;
u64 var_addr = src->offset;
int offset;
if (src->multi_regs) {
int reg2 = (sreg == src->reg1) ? src->reg2 : src->reg1;
if (has_reg_type(state, reg2) && state->regs[reg2].ok &&
state->regs[reg2].kind == TSR_KIND_CONST)
var_addr += state->regs[reg2].imm_value;
}
/*
* In kernel, %gs points to a per-cpu region for the
* current CPU. Access with a constant offset should
* be treated as a global variable access.
*/
if (get_global_var_type(cu_die, dloc, ip, var_addr,
&offset, &type_die) &&
die_get_member_type(&type_die, offset, &type_die)) {
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
if (src->multi_regs) {
pr_debug_dtp("mov [%x] percpu %#x(reg%d,reg%d) -> reg%d",
insn_offset, src->offset, src->reg1,
src->reg2, dst->reg1);
} else {
pr_debug_dtp("mov [%x] percpu %#x(reg%d) -> reg%d",
insn_offset, src->offset, sreg, dst->reg1);
}
pr_debug_type_name(&tsr->type, tsr->kind);
} else {
invalidate_reg_state(tsr);
}
}
/* And then dereference the calculated pointer if it has one */
else if (has_reg_type(state, sreg) && state->regs[sreg].ok &&
state->regs[sreg].kind == TSR_KIND_PERCPU_POINTER &&
die_get_member_type(&state->regs[sreg].type,
src->offset, &type_die)) {
tsr->type = type_die;
tsr->kind = TSR_KIND_TYPE;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] pointer %#x(reg%d) -> reg%d",
insn_offset, src->offset, sreg, dst->reg1);
pr_debug_type_name(&tsr->type, tsr->kind);
}
/* Or try another register if any */
else if (src->multi_regs && sreg == src->reg1 &&
src->reg1 != src->reg2) {
sreg = src->reg2;
goto retry;
}
else {
int offset;
const char *var_name = NULL;
/* it might be per-cpu variable (in kernel) access */
if (src->offset < 0) {
if (get_global_var_info(dloc, (s64)src->offset,
&var_name, &offset) &&
!strcmp(var_name, "__per_cpu_offset")) {
tsr->kind = TSR_KIND_PERCPU_BASE;
tsr->offset = 0;
tsr->ok = true;
pr_debug_dtp("mov [%x] percpu base reg%d\n",
insn_offset, dst->reg1);
return;
}
}
invalidate_reg_state(tsr);
}
}
/* Case 3. register to memory transfers */
if (!src->mem_ref && dst->mem_ref) {
if (!has_reg_type(state, src->reg1) ||
!state->regs[src->reg1].ok)
return;
/* Check stack variables with offset */
if (dst->reg1 == fbreg || dst->reg1 == state->stack_reg) {
struct type_state_stack *stack;
int offset = dst->offset - fboff;
tsr = &state->regs[src->reg1];
stack = find_stack_state(state, offset);
if (stack) {
/*
* The source register is likely to hold a type
* of member if it's a compound type. Do not
* update the stack variable type since we can
* get the member type later by using the
* die_get_member_type().
*/
if (!stack->compound)
set_stack_state(stack, offset, tsr->kind,
&tsr->type, tsr->offset);
} else {
findnew_stack_state(state, offset, tsr->kind,
&tsr->type, tsr->offset);
}
if (dst->reg1 == fbreg) {
pr_debug_dtp("mov [%x] reg%d -> -%#x(stack)",
insn_offset, src->reg1, -offset);
} else {
pr_debug_dtp("mov [%x] reg%d -> %#x(reg%d)",
insn_offset, src->reg1, offset, dst->reg1);
}
if (tsr->offset != 0) {
pr_debug_dtp(" reg%d offset %#x ->",
src->reg1, tsr->offset);
}
pr_debug_type_name(&tsr->type, tsr->kind);
}
/*
* Ignore other transfers since it'd set a value in a struct
* and won't change the type.
*/
}
/* Case 4. memory to memory transfers (not handled for now) */
}
#endif
const struct arch *arch__new_x86(const struct e_machine_and_e_flags *id, const char *cpuid)
{
struct arch *arch = zalloc(sizeof(*arch));
if (!arch)
return NULL;
arch->name = "x86";
arch->id = *id;
if (cpuid) {
if (x86__cpuid_parse(arch, cpuid)) {
errno = SYMBOL_ANNOTATE_ERRNO__ARCH_INIT_CPUID_PARSING;
return NULL;
}
}
arch->instructions = x86__instructions;
arch->nr_instructions = ARRAY_SIZE(x86__instructions);
#ifndef NDEBUG
{
static bool sorted_check;
if (!sorted_check) {
for (size_t i = 0; i < arch->nr_instructions - 1; i++) {
assert(strcmp(arch->instructions[i].name,
arch->instructions[i + 1].name) <= 0);
}
sorted_check = true;
}
}
#endif
arch->sorted_instructions = true;
arch->objdump.comment_char = '#';
arch->objdump.register_char = '%';
arch->objdump.memory_ref_char = '(';
arch->objdump.imm_char = '$';
arch->insn_suffix = "bwlq";
#ifdef HAVE_LIBDW_SUPPORT
arch->update_insn_state = update_insn_state_x86;
#endif
return arch;
}