Merge branch 'perf-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip

* 'perf-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/linux-2.6-tip:
  perf, hw_breakpoint, kgdb: Do not take mutex for kernel debugger
  x86, hw_breakpoints, kgdb: Fix kgdb to use hw_breakpoint API
  hw_breakpoints: Release the bp slot if arch_validate_hwbkpt_settings() fails.
  perf: Ignore perf.data.old
  perf report: Fix segmentation fault when running with '-g none'
This commit is contained in:
Linus Torvalds 2010-02-01 10:45:00 -08:00
commit 834db333ed
6 changed files with 218 additions and 68 deletions

View File

@ -42,6 +42,7 @@
#include <linux/init.h>
#include <linux/smp.h>
#include <linux/nmi.h>
#include <linux/hw_breakpoint.h>
#include <asm/debugreg.h>
#include <asm/apicdef.h>
@ -204,40 +205,81 @@ void gdb_regs_to_pt_regs(unsigned long *gdb_regs, struct pt_regs *regs)
static struct hw_breakpoint {
unsigned enabled;
unsigned type;
unsigned len;
unsigned long addr;
int len;
int type;
struct perf_event **pev;
} breakinfo[4];
static void kgdb_correct_hw_break(void)
{
unsigned long dr7;
int correctit = 0;
int breakbit;
int breakno;
get_debugreg(dr7, 7);
for (breakno = 0; breakno < 4; breakno++) {
breakbit = 2 << (breakno << 1);
if (!(dr7 & breakbit) && breakinfo[breakno].enabled) {
correctit = 1;
dr7 |= breakbit;
dr7 &= ~(0xf0000 << (breakno << 2));
dr7 |= ((breakinfo[breakno].len << 2) |
breakinfo[breakno].type) <<
((breakno << 2) + 16);
set_debugreg(breakinfo[breakno].addr, breakno);
} else {
if ((dr7 & breakbit) && !breakinfo[breakno].enabled) {
correctit = 1;
dr7 &= ~breakbit;
dr7 &= ~(0xf0000 << (breakno << 2));
}
}
struct perf_event *bp;
struct arch_hw_breakpoint *info;
int val;
int cpu = raw_smp_processor_id();
if (!breakinfo[breakno].enabled)
continue;
bp = *per_cpu_ptr(breakinfo[breakno].pev, cpu);
info = counter_arch_bp(bp);
if (bp->attr.disabled != 1)
continue;
bp->attr.bp_addr = breakinfo[breakno].addr;
bp->attr.bp_len = breakinfo[breakno].len;
bp->attr.bp_type = breakinfo[breakno].type;
info->address = breakinfo[breakno].addr;
info->len = breakinfo[breakno].len;
info->type = breakinfo[breakno].type;
val = arch_install_hw_breakpoint(bp);
if (!val)
bp->attr.disabled = 0;
}
if (correctit)
set_debugreg(dr7, 7);
hw_breakpoint_restore();
}
static int hw_break_reserve_slot(int breakno)
{
int cpu;
int cnt = 0;
struct perf_event **pevent;
for_each_online_cpu(cpu) {
cnt++;
pevent = per_cpu_ptr(breakinfo[breakno].pev, cpu);
if (dbg_reserve_bp_slot(*pevent))
goto fail;
}
return 0;
fail:
for_each_online_cpu(cpu) {
cnt--;
if (!cnt)
break;
pevent = per_cpu_ptr(breakinfo[breakno].pev, cpu);
dbg_release_bp_slot(*pevent);
}
return -1;
}
static int hw_break_release_slot(int breakno)
{
struct perf_event **pevent;
int cpu;
for_each_online_cpu(cpu) {
pevent = per_cpu_ptr(breakinfo[breakno].pev, cpu);
if (dbg_release_bp_slot(*pevent))
/*
* The debugger is responisble for handing the retry on
* remove failure.
*/
return -1;
}
return 0;
}
static int
@ -251,6 +293,10 @@ kgdb_remove_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
if (i == 4)
return -1;
if (hw_break_release_slot(i)) {
printk(KERN_ERR "Cannot remove hw breakpoint at %lx\n", addr);
return -1;
}
breakinfo[i].enabled = 0;
return 0;
@ -259,15 +305,23 @@ kgdb_remove_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
static void kgdb_remove_all_hw_break(void)
{
int i;
int cpu = raw_smp_processor_id();
struct perf_event *bp;
for (i = 0; i < 4; i++)
memset(&breakinfo[i], 0, sizeof(struct hw_breakpoint));
for (i = 0; i < 4; i++) {
if (!breakinfo[i].enabled)
continue;
bp = *per_cpu_ptr(breakinfo[i].pev, cpu);
if (bp->attr.disabled == 1)
continue;
arch_uninstall_hw_breakpoint(bp);
bp->attr.disabled = 1;
}
}
static int
kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
{
unsigned type;
int i;
for (i = 0; i < 4; i++)
@ -278,27 +332,42 @@ kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
switch (bptype) {
case BP_HARDWARE_BREAKPOINT:
type = 0;
len = 1;
len = 1;
breakinfo[i].type = X86_BREAKPOINT_EXECUTE;
break;
case BP_WRITE_WATCHPOINT:
type = 1;
breakinfo[i].type = X86_BREAKPOINT_WRITE;
break;
case BP_ACCESS_WATCHPOINT:
type = 3;
breakinfo[i].type = X86_BREAKPOINT_RW;
break;
default:
return -1;
}
if (len == 1 || len == 2 || len == 4)
breakinfo[i].len = len - 1;
else
switch (len) {
case 1:
breakinfo[i].len = X86_BREAKPOINT_LEN_1;
break;
case 2:
breakinfo[i].len = X86_BREAKPOINT_LEN_2;
break;
case 4:
breakinfo[i].len = X86_BREAKPOINT_LEN_4;
break;
#ifdef CONFIG_X86_64
case 8:
breakinfo[i].len = X86_BREAKPOINT_LEN_8;
break;
#endif
default:
return -1;
breakinfo[i].enabled = 1;
}
breakinfo[i].addr = addr;
breakinfo[i].type = type;
if (hw_break_reserve_slot(i)) {
breakinfo[i].addr = 0;
return -1;
}
breakinfo[i].enabled = 1;
return 0;
}
@ -313,8 +382,21 @@ kgdb_set_hw_break(unsigned long addr, int len, enum kgdb_bptype bptype)
*/
void kgdb_disable_hw_debug(struct pt_regs *regs)
{
int i;
int cpu = raw_smp_processor_id();
struct perf_event *bp;
/* Disable hardware debugging while we are in kgdb: */
set_debugreg(0UL, 7);
for (i = 0; i < 4; i++) {
if (!breakinfo[i].enabled)
continue;
bp = *per_cpu_ptr(breakinfo[i].pev, cpu);
if (bp->attr.disabled == 1)
continue;
arch_uninstall_hw_breakpoint(bp);
bp->attr.disabled = 1;
}
}
/**
@ -378,7 +460,6 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
struct pt_regs *linux_regs)
{
unsigned long addr;
unsigned long dr6;
char *ptr;
int newPC;
@ -404,20 +485,6 @@ int kgdb_arch_handle_exception(int e_vector, int signo, int err_code,
raw_smp_processor_id());
}
get_debugreg(dr6, 6);
if (!(dr6 & 0x4000)) {
int breakno;
for (breakno = 0; breakno < 4; breakno++) {
if (dr6 & (1 << breakno) &&
breakinfo[breakno].type == 0) {
/* Set restore flag: */
linux_regs->flags |= X86_EFLAGS_RF;
break;
}
}
}
set_debugreg(0UL, 6);
kgdb_correct_hw_break();
return 0;
@ -485,8 +552,7 @@ static int __kgdb_notify(struct die_args *args, unsigned long cmd)
break;
case DIE_DEBUG:
if (atomic_read(&kgdb_cpu_doing_single_step) ==
raw_smp_processor_id()) {
if (atomic_read(&kgdb_cpu_doing_single_step) != -1) {
if (user_mode(regs))
return single_step_cont(regs, args);
break;
@ -539,7 +605,42 @@ static struct notifier_block kgdb_notifier = {
*/
int kgdb_arch_init(void)
{
return register_die_notifier(&kgdb_notifier);
int i, cpu;
int ret;
struct perf_event_attr attr;
struct perf_event **pevent;
ret = register_die_notifier(&kgdb_notifier);
if (ret != 0)
return ret;
/*
* Pre-allocate the hw breakpoint structions in the non-atomic
* portion of kgdb because this operation requires mutexs to
* complete.
*/
attr.bp_addr = (unsigned long)kgdb_arch_init;
attr.type = PERF_TYPE_BREAKPOINT;
attr.bp_len = HW_BREAKPOINT_LEN_1;
attr.bp_type = HW_BREAKPOINT_W;
attr.disabled = 1;
for (i = 0; i < 4; i++) {
breakinfo[i].pev = register_wide_hw_breakpoint(&attr, NULL);
if (IS_ERR(breakinfo[i].pev)) {
printk(KERN_ERR "kgdb: Could not allocate hw breakpoints\n");
breakinfo[i].pev = NULL;
kgdb_arch_exit();
return -1;
}
for_each_online_cpu(cpu) {
pevent = per_cpu_ptr(breakinfo[i].pev, cpu);
pevent[0]->hw.sample_period = 1;
if (pevent[0]->destroy != NULL) {
pevent[0]->destroy = NULL;
release_bp_slot(*pevent);
}
}
}
return ret;
}
/**
@ -550,6 +651,13 @@ int kgdb_arch_init(void)
*/
void kgdb_arch_exit(void)
{
int i;
for (i = 0; i < 4; i++) {
if (breakinfo[i].pev) {
unregister_wide_hw_breakpoint(breakinfo[i].pev);
breakinfo[i].pev = NULL;
}
}
unregister_die_notifier(&kgdb_notifier);
}

View File

@ -75,6 +75,8 @@ extern int __register_perf_hw_breakpoint(struct perf_event *bp);
extern void unregister_hw_breakpoint(struct perf_event *bp);
extern void unregister_wide_hw_breakpoint(struct perf_event **cpu_events);
extern int dbg_reserve_bp_slot(struct perf_event *bp);
extern int dbg_release_bp_slot(struct perf_event *bp);
extern int reserve_bp_slot(struct perf_event *bp);
extern void release_bp_slot(struct perf_event *bp);

View File

@ -243,38 +243,70 @@ static void toggle_bp_slot(struct perf_event *bp, bool enable)
* ((per_cpu(nr_bp_flexible, *) > 1) + max(per_cpu(nr_cpu_bp_pinned, *))
* + max(per_cpu(nr_task_bp_pinned, *))) < HBP_NUM
*/
int reserve_bp_slot(struct perf_event *bp)
static int __reserve_bp_slot(struct perf_event *bp)
{
struct bp_busy_slots slots = {0};
int ret = 0;
mutex_lock(&nr_bp_mutex);
fetch_bp_busy_slots(&slots, bp);
/* Flexible counters need to keep at least one slot */
if (slots.pinned + (!!slots.flexible) == HBP_NUM) {
ret = -ENOSPC;
goto end;
}
if (slots.pinned + (!!slots.flexible) == HBP_NUM)
return -ENOSPC;
toggle_bp_slot(bp, true);
end:
return 0;
}
int reserve_bp_slot(struct perf_event *bp)
{
int ret;
mutex_lock(&nr_bp_mutex);
ret = __reserve_bp_slot(bp);
mutex_unlock(&nr_bp_mutex);
return ret;
}
static void __release_bp_slot(struct perf_event *bp)
{
toggle_bp_slot(bp, false);
}
void release_bp_slot(struct perf_event *bp)
{
mutex_lock(&nr_bp_mutex);
toggle_bp_slot(bp, false);
__release_bp_slot(bp);
mutex_unlock(&nr_bp_mutex);
}
/*
* Allow the kernel debugger to reserve breakpoint slots without
* taking a lock using the dbg_* variant of for the reserve and
* release breakpoint slots.
*/
int dbg_reserve_bp_slot(struct perf_event *bp)
{
if (mutex_is_locked(&nr_bp_mutex))
return -1;
return __reserve_bp_slot(bp);
}
int dbg_release_bp_slot(struct perf_event *bp)
{
if (mutex_is_locked(&nr_bp_mutex))
return -1;
__release_bp_slot(bp);
return 0;
}
int register_perf_hw_breakpoint(struct perf_event *bp)
{
@ -296,6 +328,10 @@ int register_perf_hw_breakpoint(struct perf_event *bp)
if (!bp->attr.disabled || !bp->overflow_handler)
ret = arch_validate_hwbkpt_settings(bp, bp->ctx->task);
/* if arch_validate_hwbkpt_settings() fails then release bp slot */
if (ret)
release_bp_slot(bp);
return ret;
}

View File

@ -583,6 +583,9 @@ static void kgdb_wait(struct pt_regs *regs)
smp_wmb();
atomic_set(&cpu_in_kgdb[cpu], 1);
/* Disable any cpu specific hw breakpoints */
kgdb_disable_hw_debug(regs);
/* Wait till primary CPU is done with debugging */
while (atomic_read(&passive_cpu_wait[cpu]))
cpu_relax();

View File

@ -13,6 +13,7 @@ perf*.xml
perf*.html
common-cmds.h
perf.data
perf.data.old
tags
TAGS
cscope*

View File

@ -269,7 +269,7 @@ parse_callchain_opt(const struct option *opt __used, const char *arg,
else if (!strncmp(tok, "none", strlen(arg))) {
callchain_param.mode = CHAIN_NONE;
symbol_conf.use_callchain = true;
symbol_conf.use_callchain = false;
return 0;
}