diff options
Diffstat (limited to 'tools/perf/arch/x86')
| -rw-r--r-- | tools/perf/arch/x86/Makefile | 13 | ||||
| -rw-r--r-- | tools/perf/arch/x86/include/perf_regs.h | 86 | ||||
| -rw-r--r-- | tools/perf/arch/x86/tests/dwarf-unwind.c | 60 | ||||
| -rw-r--r-- | tools/perf/arch/x86/tests/regs_load.S | 98 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/dwarf-regs.c | 2 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/header.c | 59 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/tsc.c | 59 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/tsc.h | 20 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/unwind-libdw.c | 51 | ||||
| -rw-r--r-- | tools/perf/arch/x86/util/unwind-libunwind.c | 111 | 
10 files changed, 558 insertions, 1 deletions
diff --git a/tools/perf/arch/x86/Makefile b/tools/perf/arch/x86/Makefile index 15130b50dfe..1641542e363 100644 --- a/tools/perf/arch/x86/Makefile +++ b/tools/perf/arch/x86/Makefile @@ -2,3 +2,16 @@ ifndef NO_DWARF  PERF_HAVE_DWARF_REGS := 1  LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o  endif +ifndef NO_LIBUNWIND +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libunwind.o +endif +ifndef NO_LIBDW_DWARF_UNWIND +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libdw.o +endif +ifndef NO_DWARF_UNWIND +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/regs_load.o +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/dwarf-unwind.o +endif +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/tsc.o +LIB_H += arch/$(ARCH)/util/tsc.h diff --git a/tools/perf/arch/x86/include/perf_regs.h b/tools/perf/arch/x86/include/perf_regs.h new file mode 100644 index 00000000000..7df517acfef --- /dev/null +++ b/tools/perf/arch/x86/include/perf_regs.h @@ -0,0 +1,86 @@ +#ifndef ARCH_PERF_REGS_H +#define ARCH_PERF_REGS_H + +#include <stdlib.h> +#include <linux/types.h> +#include <asm/perf_regs.h> + +void perf_regs_load(u64 *regs); + +#ifndef HAVE_ARCH_X86_64_SUPPORT +#define PERF_REGS_MASK ((1ULL << PERF_REG_X86_32_MAX) - 1) +#define PERF_REGS_MAX PERF_REG_X86_32_MAX +#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_32 +#else +#define REG_NOSUPPORT ((1ULL << PERF_REG_X86_DS) | \ +		       (1ULL << PERF_REG_X86_ES) | \ +		       (1ULL << PERF_REG_X86_FS) | \ +		       (1ULL << PERF_REG_X86_GS)) +#define PERF_REGS_MASK (((1ULL << PERF_REG_X86_64_MAX) - 1) & ~REG_NOSUPPORT) +#define PERF_REGS_MAX PERF_REG_X86_64_MAX +#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_64 +#endif +#define PERF_REG_IP PERF_REG_X86_IP +#define PERF_REG_SP PERF_REG_X86_SP + +static inline const char *perf_reg_name(int id) +{ +	switch (id) { +	case PERF_REG_X86_AX: +		return "AX"; +	case PERF_REG_X86_BX: +		return "BX"; +	case PERF_REG_X86_CX: +		return "CX"; +	case PERF_REG_X86_DX: +		return "DX"; +	case PERF_REG_X86_SI: +		return "SI"; +	case PERF_REG_X86_DI: +		return "DI"; +	case PERF_REG_X86_BP: +		return "BP"; +	case PERF_REG_X86_SP: +		return "SP"; +	case PERF_REG_X86_IP: +		return "IP"; +	case PERF_REG_X86_FLAGS: +		return "FLAGS"; +	case PERF_REG_X86_CS: +		return "CS"; +	case PERF_REG_X86_SS: +		return "SS"; +	case PERF_REG_X86_DS: +		return "DS"; +	case PERF_REG_X86_ES: +		return "ES"; +	case PERF_REG_X86_FS: +		return "FS"; +	case PERF_REG_X86_GS: +		return "GS"; +#ifdef HAVE_ARCH_X86_64_SUPPORT +	case PERF_REG_X86_R8: +		return "R8"; +	case PERF_REG_X86_R9: +		return "R9"; +	case PERF_REG_X86_R10: +		return "R10"; +	case PERF_REG_X86_R11: +		return "R11"; +	case PERF_REG_X86_R12: +		return "R12"; +	case PERF_REG_X86_R13: +		return "R13"; +	case PERF_REG_X86_R14: +		return "R14"; +	case PERF_REG_X86_R15: +		return "R15"; +#endif /* HAVE_ARCH_X86_64_SUPPORT */ +	default: +		return NULL; +	} + +	return NULL; +} + +#endif /* ARCH_PERF_REGS_H */ diff --git a/tools/perf/arch/x86/tests/dwarf-unwind.c b/tools/perf/arch/x86/tests/dwarf-unwind.c new file mode 100644 index 00000000000..9f89f899ccc --- /dev/null +++ b/tools/perf/arch/x86/tests/dwarf-unwind.c @@ -0,0 +1,60 @@ +#include <string.h> +#include "perf_regs.h" +#include "thread.h" +#include "map.h" +#include "event.h" +#include "tests/tests.h" + +#define STACK_SIZE 8192 + +static int sample_ustack(struct perf_sample *sample, +			 struct thread *thread, u64 *regs) +{ +	struct stack_dump *stack = &sample->user_stack; +	struct map *map; +	unsigned long sp; +	u64 stack_size, *buf; + +	buf = malloc(STACK_SIZE); +	if (!buf) { +		pr_debug("failed to allocate sample uregs data\n"); +		return -1; +	} + +	sp = (unsigned long) regs[PERF_REG_X86_SP]; + +	map = map_groups__find(thread->mg, MAP__VARIABLE, (u64) sp); +	if (!map) { +		pr_debug("failed to get stack map\n"); +		free(buf); +		return -1; +	} + +	stack_size = map->end - sp; +	stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size; + +	memcpy(buf, (void *) sp, stack_size); +	stack->data = (char *) buf; +	stack->size = stack_size; +	return 0; +} + +int test__arch_unwind_sample(struct perf_sample *sample, +			     struct thread *thread) +{ +	struct regs_dump *regs = &sample->user_regs; +	u64 *buf; + +	buf = malloc(sizeof(u64) * PERF_REGS_MAX); +	if (!buf) { +		pr_debug("failed to allocate sample uregs data\n"); +		return -1; +	} + +	perf_regs_load(buf); +	regs->abi  = PERF_SAMPLE_REGS_ABI; +	regs->regs = buf; +	regs->mask = PERF_REGS_MASK; + +	return sample_ustack(sample, thread, buf); +} diff --git a/tools/perf/arch/x86/tests/regs_load.S b/tools/perf/arch/x86/tests/regs_load.S new file mode 100644 index 00000000000..60875d5c556 --- /dev/null +++ b/tools/perf/arch/x86/tests/regs_load.S @@ -0,0 +1,98 @@ +#include <linux/linkage.h> + +#define AX	 0 +#define BX	 1 * 8 +#define CX	 2 * 8 +#define DX	 3 * 8 +#define SI	 4 * 8 +#define DI	 5 * 8 +#define BP	 6 * 8 +#define SP	 7 * 8 +#define IP	 8 * 8 +#define FLAGS	 9 * 8 +#define CS	10 * 8 +#define SS	11 * 8 +#define DS	12 * 8 +#define ES	13 * 8 +#define FS	14 * 8 +#define GS	15 * 8 +#define R8	16 * 8 +#define R9	17 * 8 +#define R10	18 * 8 +#define R11	19 * 8 +#define R12	20 * 8 +#define R13	21 * 8 +#define R14	22 * 8 +#define R15	23 * 8 + +.text +#ifdef HAVE_ARCH_X86_64_SUPPORT +ENTRY(perf_regs_load) +	movq %rax, AX(%rdi) +	movq %rbx, BX(%rdi) +	movq %rcx, CX(%rdi) +	movq %rdx, DX(%rdi) +	movq %rsi, SI(%rdi) +	movq %rdi, DI(%rdi) +	movq %rbp, BP(%rdi) + +	leaq 8(%rsp), %rax /* exclude this call.  */ +	movq %rax, SP(%rdi) + +	movq 0(%rsp), %rax +	movq %rax, IP(%rdi) + +	movq $0, FLAGS(%rdi) +	movq $0, CS(%rdi) +	movq $0, SS(%rdi) +	movq $0, DS(%rdi) +	movq $0, ES(%rdi) +	movq $0, FS(%rdi) +	movq $0, GS(%rdi) + +	movq %r8,  R8(%rdi) +	movq %r9,  R9(%rdi) +	movq %r10, R10(%rdi) +	movq %r11, R11(%rdi) +	movq %r12, R12(%rdi) +	movq %r13, R13(%rdi) +	movq %r14, R14(%rdi) +	movq %r15, R15(%rdi) +	ret +ENDPROC(perf_regs_load) +#else +ENTRY(perf_regs_load) +	push %edi +	movl 8(%esp), %edi +	movl %eax, AX(%edi) +	movl %ebx, BX(%edi) +	movl %ecx, CX(%edi) +	movl %edx, DX(%edi) +	movl %esi, SI(%edi) +	pop %eax +	movl %eax, DI(%edi) +	movl %ebp, BP(%edi) + +	leal 4(%esp), %eax /* exclude this call.  */ +	movl %eax, SP(%edi) + +	movl 0(%esp), %eax +	movl %eax, IP(%edi) + +	movl $0, FLAGS(%edi) +	movl $0, CS(%edi) +	movl $0, SS(%edi) +	movl $0, DS(%edi) +	movl $0, ES(%edi) +	movl $0, FS(%edi) +	movl $0, GS(%edi) +	ret +ENDPROC(perf_regs_load) +#endif + +/* + * We need to provide note.GNU-stack section, saying that we want + * NOT executable stack. Otherwise the final linking will assume that + * the ELF stack should not be restricted at all and set it RWX. + */ +.section .note.GNU-stack,"",@progbits diff --git a/tools/perf/arch/x86/util/dwarf-regs.c b/tools/perf/arch/x86/util/dwarf-regs.c index a794d308192..be22dd46323 100644 --- a/tools/perf/arch/x86/util/dwarf-regs.c +++ b/tools/perf/arch/x86/util/dwarf-regs.c @@ -20,7 +20,7 @@   *   */ -#include <libio.h> +#include <stddef.h>  #include <dwarf-regs.h>  /* diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c new file mode 100644 index 00000000000..146d12a1cec --- /dev/null +++ b/tools/perf/arch/x86/util/header.c @@ -0,0 +1,59 @@ +#include <sys/types.h> +#include <unistd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "../../util/header.h" + +static inline void +cpuid(unsigned int op, unsigned int *a, unsigned int *b, unsigned int *c, +      unsigned int *d) +{ +	__asm__ __volatile__ (".byte 0x53\n\tcpuid\n\t" +			      "movl %%ebx, %%esi\n\t.byte 0x5b" +			: "=a" (*a), +			"=S" (*b), +			"=c" (*c), +			"=d" (*d) +			: "a" (op)); +} + +int +get_cpuid(char *buffer, size_t sz) +{ +	unsigned int a, b, c, d, lvl; +	int family = -1, model = -1, step = -1; +	int nb; +	char vendor[16]; + +	cpuid(0, &lvl, &b, &c, &d); +	strncpy(&vendor[0], (char *)(&b), 4); +	strncpy(&vendor[4], (char *)(&d), 4); +	strncpy(&vendor[8], (char *)(&c), 4); +	vendor[12] = '\0'; + +	if (lvl >= 1) { +		cpuid(1, &a, &b, &c, &d); + +		family = (a >> 8) & 0xf;  /* bits 11 - 8 */ +		model  = (a >> 4) & 0xf;  /* Bits  7 - 4 */ +		step   = a & 0xf; + +		/* extended family */ +		if (family == 0xf) +			family += (a >> 20) & 0xff; + +		/* extended model */ +		if (family >= 0x6) +			model += ((a >> 16) & 0xf) << 4; +	} +	nb = scnprintf(buffer, sz, "%s,%u,%u,%u$", vendor, family, model, step); + +	/* look for end marker to ensure the entire data fit */ +	if (strchr(buffer, '$')) { +		buffer[nb-1] = '\0'; +		return 0; +	} +	return -1; +} diff --git a/tools/perf/arch/x86/util/tsc.c b/tools/perf/arch/x86/util/tsc.c new file mode 100644 index 00000000000..40021fa3129 --- /dev/null +++ b/tools/perf/arch/x86/util/tsc.c @@ -0,0 +1,59 @@ +#include <stdbool.h> +#include <errno.h> + +#include <linux/perf_event.h> + +#include "../../perf.h" +#include <linux/types.h> +#include "../../util/debug.h" +#include "tsc.h" + +u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc) +{ +	u64 t, quot, rem; + +	t = ns - tc->time_zero; +	quot = t / tc->time_mult; +	rem  = t % tc->time_mult; +	return (quot << tc->time_shift) + +	       (rem << tc->time_shift) / tc->time_mult; +} + +u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc) +{ +	u64 quot, rem; + +	quot = cyc >> tc->time_shift; +	rem  = cyc & ((1 << tc->time_shift) - 1); +	return tc->time_zero + quot * tc->time_mult + +	       ((rem * tc->time_mult) >> tc->time_shift); +} + +int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, +			     struct perf_tsc_conversion *tc) +{ +	bool cap_user_time_zero; +	u32 seq; +	int i = 0; + +	while (1) { +		seq = pc->lock; +		rmb(); +		tc->time_mult = pc->time_mult; +		tc->time_shift = pc->time_shift; +		tc->time_zero = pc->time_zero; +		cap_user_time_zero = pc->cap_user_time_zero; +		rmb(); +		if (pc->lock == seq && !(seq & 1)) +			break; +		if (++i > 10000) { +			pr_debug("failed to get perf_event_mmap_page lock\n"); +			return -EINVAL; +		} +	} + +	if (!cap_user_time_zero) +		return -EOPNOTSUPP; + +	return 0; +} diff --git a/tools/perf/arch/x86/util/tsc.h b/tools/perf/arch/x86/util/tsc.h new file mode 100644 index 00000000000..2affe0366b5 --- /dev/null +++ b/tools/perf/arch/x86/util/tsc.h @@ -0,0 +1,20 @@ +#ifndef TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ +#define TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ + +#include <linux/types.h> + +struct perf_tsc_conversion { +	u16 time_shift; +	u32 time_mult; +	u64 time_zero; +}; + +struct perf_event_mmap_page; + +int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, +			     struct perf_tsc_conversion *tc); + +u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc); +u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc); + +#endif /* TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ */ diff --git a/tools/perf/arch/x86/util/unwind-libdw.c b/tools/perf/arch/x86/util/unwind-libdw.c new file mode 100644 index 00000000000..c4b72176ca8 --- /dev/null +++ b/tools/perf/arch/x86/util/unwind-libdw.c @@ -0,0 +1,51 @@ +#include <elfutils/libdwfl.h> +#include "../../util/unwind-libdw.h" +#include "../../util/perf_regs.h" + +bool libdw__arch_set_initial_registers(Dwfl_Thread *thread, void *arg) +{ +	struct unwind_info *ui = arg; +	struct regs_dump *user_regs = &ui->sample->user_regs; +	Dwarf_Word dwarf_regs[17]; +	unsigned nregs; + +#define REG(r) ({						\ +	Dwarf_Word val = 0;					\ +	perf_reg_value(&val, user_regs, PERF_REG_X86_##r);	\ +	val;							\ +}) + +	if (user_regs->abi == PERF_SAMPLE_REGS_ABI_32) { +		dwarf_regs[0] = REG(AX); +		dwarf_regs[1] = REG(CX); +		dwarf_regs[2] = REG(DX); +		dwarf_regs[3] = REG(BX); +		dwarf_regs[4] = REG(SP); +		dwarf_regs[5] = REG(BP); +		dwarf_regs[6] = REG(SI); +		dwarf_regs[7] = REG(DI); +		dwarf_regs[8] = REG(IP); +		nregs = 9; +	} else { +		dwarf_regs[0]  = REG(AX); +		dwarf_regs[1]  = REG(DX); +		dwarf_regs[2]  = REG(CX); +		dwarf_regs[3]  = REG(BX); +		dwarf_regs[4]  = REG(SI); +		dwarf_regs[5]  = REG(DI); +		dwarf_regs[6]  = REG(BP); +		dwarf_regs[7]  = REG(SP); +		dwarf_regs[8]  = REG(R8); +		dwarf_regs[9]  = REG(R9); +		dwarf_regs[10] = REG(R10); +		dwarf_regs[11] = REG(R11); +		dwarf_regs[12] = REG(R12); +		dwarf_regs[13] = REG(R13); +		dwarf_regs[14] = REG(R14); +		dwarf_regs[15] = REG(R15); +		dwarf_regs[16] = REG(IP); +		nregs = 17; +	} + +	return dwfl_thread_state_registers(thread, 0, nregs, dwarf_regs); +} diff --git a/tools/perf/arch/x86/util/unwind-libunwind.c b/tools/perf/arch/x86/util/unwind-libunwind.c new file mode 100644 index 00000000000..3261f68c6a7 --- /dev/null +++ b/tools/perf/arch/x86/util/unwind-libunwind.c @@ -0,0 +1,111 @@ + +#include <errno.h> +#include <libunwind.h> +#include "perf_regs.h" +#include "../../util/unwind.h" + +#ifdef HAVE_ARCH_X86_64_SUPPORT +int libunwind__arch_reg_id(int regnum) +{ +	int id; + +	switch (regnum) { +	case UNW_X86_64_RAX: +		id = PERF_REG_X86_AX; +		break; +	case UNW_X86_64_RDX: +		id = PERF_REG_X86_DX; +		break; +	case UNW_X86_64_RCX: +		id = PERF_REG_X86_CX; +		break; +	case UNW_X86_64_RBX: +		id = PERF_REG_X86_BX; +		break; +	case UNW_X86_64_RSI: +		id = PERF_REG_X86_SI; +		break; +	case UNW_X86_64_RDI: +		id = PERF_REG_X86_DI; +		break; +	case UNW_X86_64_RBP: +		id = PERF_REG_X86_BP; +		break; +	case UNW_X86_64_RSP: +		id = PERF_REG_X86_SP; +		break; +	case UNW_X86_64_R8: +		id = PERF_REG_X86_R8; +		break; +	case UNW_X86_64_R9: +		id = PERF_REG_X86_R9; +		break; +	case UNW_X86_64_R10: +		id = PERF_REG_X86_R10; +		break; +	case UNW_X86_64_R11: +		id = PERF_REG_X86_R11; +		break; +	case UNW_X86_64_R12: +		id = PERF_REG_X86_R12; +		break; +	case UNW_X86_64_R13: +		id = PERF_REG_X86_R13; +		break; +	case UNW_X86_64_R14: +		id = PERF_REG_X86_R14; +		break; +	case UNW_X86_64_R15: +		id = PERF_REG_X86_R15; +		break; +	case UNW_X86_64_RIP: +		id = PERF_REG_X86_IP; +		break; +	default: +		pr_err("unwind: invalid reg id %d\n", regnum); +		return -EINVAL; +	} + +	return id; +} +#else +int libunwind__arch_reg_id(int regnum) +{ +	int id; + +	switch (regnum) { +	case UNW_X86_EAX: +		id = PERF_REG_X86_AX; +		break; +	case UNW_X86_EDX: +		id = PERF_REG_X86_DX; +		break; +	case UNW_X86_ECX: +		id = PERF_REG_X86_CX; +		break; +	case UNW_X86_EBX: +		id = PERF_REG_X86_BX; +		break; +	case UNW_X86_ESI: +		id = PERF_REG_X86_SI; +		break; +	case UNW_X86_EDI: +		id = PERF_REG_X86_DI; +		break; +	case UNW_X86_EBP: +		id = PERF_REG_X86_BP; +		break; +	case UNW_X86_ESP: +		id = PERF_REG_X86_SP; +		break; +	case UNW_X86_EIP: +		id = PERF_REG_X86_IP; +		break; +	default: +		pr_err("unwind: invalid reg id %d\n", regnum); +		return -EINVAL; +	} + +	return id; +} +#endif /* HAVE_ARCH_X86_64_SUPPORT */  | 
