/*------------------------------------------------------------------------------------------*\ * * Copyright (C) 2017 AVM GmbH * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA \*------------------------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include extern void show_registers(const struct pt_regs *regs); #if defined(CONFIG_AVM_WP) #define SET_WATCHPOINT_FAIL_WRONG_TYPE -1 #define SET_WATCHPOINT_FAIL_NO_REG -2 #define SET_WATCHPOINT_SUCCESS 0 #define NR_WATCHPOINTS 4 /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ struct _avm_wp { void *ref; void *expr; spinlock_t wp_lock; unsigned int real_exception_count; unsigned int exception_count; unsigned int addr; int mask; int hit; unsigned long ra; unsigned long epc; cpumask_t cpu_mask; int watch_nr; #define wp_activ 0x1 #define wp_inactiv 0X2 atomic_t active; enum _wp_type type; int (*handler)(void *ref, enum _wp_type status, int deferred, struct pt_regs *); }; struct _avm_wp watchpoints[NR_CPUS][NR_WATCHPOINTS]; static int default_wp_handler(void *ref, enum _wp_type status, int deferred, struct pt_regs *regs); static void __del_watchpoint(int watch_nr, const cpumask_t *cpu_mask); static int get_expression_value(void *ref, enum _cond_type type, unsigned int *value); static int get_expression_symbol(void *ref, enum _cond_type type, char *symbol, unsigned int *value); /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static const char *name_wp_type(char *txt, unsigned txtlen, enum _wp_type type) { int len = snprintf(txt, txtlen, "%s%s%s%s", type == 0 ? "no support " : "", type & wp_type_write ? "write+" : "", type & wp_type_read ? "read " : "", type & wp_type_instruction ? "instruction " : ""); if(len > 0) { txt[len-1] = 0 ; } return txt; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void write_c0_watchloX(int wd_nr, unsigned int value) { switch(wd_nr) { case 0: write_c0_watchlo0(value); break; case 1: write_c0_watchlo1(value); break; case 2: write_c0_watchlo2(value); break; case 3: write_c0_watchlo3(value); break; case 4: write_c0_watchlo4(value); break; case 5: write_c0_watchlo5(value); break; case 6: write_c0_watchlo6(value); break; case 7: write_c0_watchlo7(value); break; } } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static unsigned int read_c0_watchloX(int wd_nr) { switch(wd_nr) { case 0: return read_c0_watchlo0(); case 1: return read_c0_watchlo1(); case 2: return read_c0_watchlo2(); case 3: return read_c0_watchlo3(); case 4: return read_c0_watchlo4(); case 5: return read_c0_watchlo5(); case 6: return read_c0_watchlo6(); case 7: return read_c0_watchlo7(); } return 0; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void write_c0_watchhiX(int wd_nr, unsigned int value) { switch(wd_nr) { case 0: write_c0_watchhi0(value); break; case 1: write_c0_watchhi1(value); break; case 2: write_c0_watchhi2(value); break; case 3: write_c0_watchhi3(value); break; case 4: write_c0_watchhi4(value); break; case 5: write_c0_watchhi5(value); break; case 6: write_c0_watchhi6(value); break; case 7: write_c0_watchhi7(value); break; } } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static unsigned int read_c0_watchhiX(int wd_nr) { switch(wd_nr) { case 0: return read_c0_watchhi0(); case 1: return read_c0_watchhi1(); case 2: return read_c0_watchhi2(); case 3: return read_c0_watchhi3(); case 4: return read_c0_watchhi4(); case 5: return read_c0_watchhi5(); case 6: return read_c0_watchhi6(); case 7: return read_c0_watchhi7(); } return 0; } #if 0 /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void clear_watchpoint_per_cpu(void *info) { struct _avm_wp *pwp = (struct _avm_wp *)info; unsigned long flags; spin_lock_irqsave(&pwp->wp_lock, flags); atomic_set(&pwp->active, wp_inactiv); write_c0_watchhiX(pwp->watch_nr, read_c0_watchhiX(pwp->watch_nr) & 0x7); write_c0_watchloX(pwp->watch_nr, 0); printk(KERN_ERR"[%x]%s: %u: loX:%x hiX:%x\n", raw_smp_processor_id(), __func__, pwp->watch_nr, read_c0_watchloX(pwp->watch_nr), read_c0_watchhiX(pwp->watch_nr)); spin_unlock_irqrestore(&pwp->wp_lock, flags); } #endif /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void set_watchpoint_per_cpu(void *info) { struct _avm_wp *pwp = (struct _avm_wp *)info; unsigned long flags; spin_lock_irqsave(&pwp->wp_lock, flags); atomic_set(&pwp->active, wp_activ); write_c0_watchloX(pwp->watch_nr, (pwp->addr & 0xFFFFFFF8) | (pwp->type & 0x7) ); write_c0_watchhiX(pwp->watch_nr, (1 << 30) /*--- global ---*/ | (pwp->mask & (0x1FF << 3) ) | (read_c0_watchhiX(pwp->watch_nr) & 0x7)); #if 0 { char txt[32]; printk(KERN_ERR"[%x]%s: %u: %pS %s loX:%x hiX:%x\n", raw_smp_processor_id(), __func__, pwp->watch_nr, (void *)pwp->addr, name_wp_type(txt, sizeof(txt), pwp->type), read_c0_watchloX(pwp->watch_nr), read_c0_watchhiX(pwp->watch_nr)); } #endif spin_unlock_irqrestore(&pwp->wp_lock, flags); } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int check_type(int wd_nr, enum _wp_type type) { unsigned int lo_reg; write_c0_watchloX(wd_nr, 7); mb(); lo_reg = read_c0_watchloX(wd_nr); if((lo_reg & type) == 0){ char txt[2][32]; printk(KERN_ERR"%s: wrong type %x: (%s) for watch_nr=%u (%x %s)\n", __func__, type, name_wp_type(txt[0], sizeof(txt[0]), type), wd_nr, lo_reg, name_wp_type(txt[1], sizeof(txt[1]), lo_reg)); return -1; } return 0; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ int set_watchpoint_expression(void *expression, int watch_nr, const cpumask_t *cpu_mask){ int cpu; /*--- vorhergehende austragen ---*/ if((watch_nr >= current_cpu_data.watch_reg_count ) || ( watch_nr >= NR_WATCHPOINTS)){ printk(KERN_ERR"%s: unsupported watchpoint-number %u\n", __func__, watch_nr); return -1; } __del_watchpoint(watch_nr, cpu_mask); for_each_online_cpu(cpu) { if(cpumask_test_cpu(cpu, cpu_mask)) { struct _avm_wp *pwp = &watchpoints[cpu][watch_nr]; condition_put(pwp->expr); /*--- alten freigeben ---*/ condition_get(expression); pwp->expr = expression; /*--- printk("%s: %p:%p\n", __func__, pwp,pwp->expr); ---*/ pwp->exception_count = 0; pwp->real_exception_count = 0; pwp->ra = 0; pwp->epc = 0; } } return 0; } /**--------------------------------------------------------------------------------**\ * watch_nr: 0, 1 only instruction * 2, 3 only data * mask: coressponding adressbits ignored (bit 11-3) * type: bit0: write, bit1: read, bit2:instruction * cpu_mask: 0: all cpu's (instead choosed cpus's) * ref: ref == NULL -> wp-intance for handler \**--------------------------------------------------------------------------------**/ int set_watchpoint_handler(void *ref, int watch_nr, unsigned long addr, unsigned long mask, enum _wp_type type, int (*wp_handler)(void *ref, enum _wp_type status , int deferred, struct pt_regs *), const cpumask_t *cpu_mask){ char txt[32]; int cpu; if((watch_nr >= current_cpu_data.watch_reg_count ) || ( watch_nr >= NR_WATCHPOINTS)){ printk(KERN_ERR"%s: unsupported watchpoint-number %u\n", __func__, watch_nr); return SET_WATCHPOINT_FAIL_NO_REG; } /**--------------------------------------------------------------------------------**\ * wir gehen mal davon aus, dass alle Cores identisch sind: nur aktuelle CPU testen \**--------------------------------------------------------------------------------**/ if(check_type(watch_nr, type)) { return SET_WATCHPOINT_FAIL_WRONG_TYPE; } mb(); /*--- vorhergehende austragen ---*/ __del_watchpoint(watch_nr, cpu_mask); printk(KERN_ERR"%s: set watchpoint %u on addr %pS (mask=%lx) type=%x(%s) cpu_mask=%lx\n", __func__, watch_nr, (void *)addr, mask, type, name_wp_type(txt, sizeof(txt), type), cpumask_bits(cpu_mask)[0]); for_each_online_cpu(cpu) { if(cpumask_test_cpu(cpu, cpu_mask)) { struct _avm_wp *pwp = &watchpoints[cpu][watch_nr]; pwp->ref = ref ? ref : pwp; pwp->addr = addr; pwp->mask = mask; pwp->type = type; pwp->handler = wp_handler; pwp->watch_nr = watch_nr; pwp->exception_count = 0; pwp->hit = 0; cpumask_copy(&pwp->cpu_mask, cpu_mask); smp_call_function_single(cpu, (smp_call_func_t)set_watchpoint_per_cpu, pwp, true); } } return SET_WATCHPOINT_SUCCESS; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static cpumask_t *cpu_id_to_cpumask(cpumask_t *cpu_mask, int cpu_id) { if((cpu_id >= NR_CPUS) || (cpu_id < 0)) { cpumask_setall(cpu_mask); } else { cpumask_clear(cpu_mask); cpumask_set_cpu(cpu_id, cpu_mask); } return cpu_mask; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ int set_watchpoint(int watch_nr, unsigned long addr, int mask, enum _wp_type type, int cpu_id){ cpumask_t cpu_mask; return set_watchpoint_handler(NULL, watch_nr, addr, mask, type, default_wp_handler, cpu_id_to_cpumask(&cpu_mask, cpu_id)); } /**--------------------------------------------------------------------------------**\ * delete all watch_nr corresponding with cpu_mask and also cpu_mask from wp-instance \**--------------------------------------------------------------------------------**/ static void __del_watchpoint(int watch_nr, const cpumask_t *pcpu_mask){ cpumask_t cpu_mask; struct _avm_wp *pwp; int cpu; /*--- printk(KERN_ERR"%s: %u cpu_mask=%lx\n", __func__, watch_nr, cpumask_bits(cpu_mask)[0]); ---*/ if((watch_nr >= current_cpu_data.watch_reg_count ) || ( watch_nr >= NR_WATCHPOINTS)){ printk(KERN_ERR"%s: unsupported watchpoint-number %u\n", __func__, watch_nr); return; } cpumask_copy(&cpu_mask, pcpu_mask); /*--- markiere alle per mask gebundelten zum austragen ---*/ for_each_online_cpu(cpu) { pwp = &watchpoints[cpu][watch_nr]; if(cpumask_test_cpu(cpu, &cpu_mask)) { cpumask_clear(&pwp->cpu_mask); atomic_set(&pwp->active, wp_inactiv); } else { /*--- auch wenn nicht gebundelt: austragen ---*/ cpumask_andnot(&pwp->cpu_mask, &pwp->cpu_mask, &cpu_mask); } } cpu = get_cpu(); /*--- im aktuellen CPU-Kontext austragen: ---*/ pwp = &watchpoints[cpu][watch_nr]; if(atomic_read(&pwp->active) == wp_inactiv) { unsigned long flags; spin_lock_irqsave(&pwp->wp_lock, flags); /*--- falls aktuelle CPU real clearen ---*/ write_c0_watchhiX(watch_nr, read_c0_watchhiX(watch_nr) & 0x7); /*--- Bit0-2: W1C ---*/ write_c0_watchloX(watch_nr, 0); spin_unlock_irqrestore(&pwp->wp_lock, flags); /*--- printk("[%x]%s: cleared: hiX: %x lox%x\n", raw_smp_processor_id(), __func__, read_c0_watchhiX(watch_nr), read_c0_watchloX(watch_nr)); ---*/ } put_cpu(); } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void reset_watchpoints_per_cpu(void *info) { unsigned int watch_nr; for(watch_nr = 0; watch_nr <= min((int)current_cpu_data.watch_reg_count, NR_WATCHPOINTS); watch_nr++){ write_c0_watchloX(watch_nr, 0x0); write_c0_watchhiX(watch_nr, 0x7); } } /**--------------------------------------------------------------------------------**\ * watchpoint-unit fuer Userland (ptrace) wieder nutzbar machen \**--------------------------------------------------------------------------------**/ static void reset_all_watchpoints(void){ unsigned int cpu, watch_nr; for_each_online_cpu(cpu) { smp_call_function_single(cpu, (smp_call_func_t)reset_watchpoints_per_cpu, NULL, true); for(watch_nr = 0; watch_nr <= min((int)current_cpu_data.watch_reg_count, NR_WATCHPOINTS); watch_nr++){ struct _avm_wp *pwp = &watchpoints[cpu][watch_nr]; atomic_set(&pwp->active, 0); } } } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ void del_watchpoint(int watch_nr, int cpu_id){ cpumask_t cpu_mask; __del_watchpoint(watch_nr, cpu_id_to_cpumask(&cpu_mask, cpu_id)); } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ int watchpoint_busy( int watch_nr ){ return read_c0_watchloX(watch_nr); } /**--------------------------------------------------------------------------------**\ * ret: 0 watchpoint loeschen * ref muss auf _avm_wp zeigen \**--------------------------------------------------------------------------------**/ static int default_wp_handler(void *ref, enum _wp_type status, int deferred, struct pt_regs *regs) { struct _avm_wp *pwp = (struct _avm_wp *)ref; char txt[32], oldlog_level; char condtxt[512]; unsigned int value = 0; condtxt[0] = 0; pwp->exception_count++; condition_get(pwp->expr); if(pwp->expr) { if(condition_calculate(pwp->expr, regs, &value) || (value == 0)) { condition_put(pwp->expr); return 1; } condition_print(pwp->expr, condtxt, sizeof(condtxt)); } condition_put(pwp->expr); pwp->hit = 1; pwp->ra = regs->regs[31]; pwp->epc = regs->cp0_epc; oldlog_level = console_loglevel; console_verbose(); printk(KERN_ERR"--------------------------------------------------------------------------------\n" "WATCHPOINT%u occured (%s), deferred=%d epc=%pS %s%s%s\n", pwp->watch_nr, name_wp_type(txt, sizeof(txt), status), deferred, (void *)regs->cp0_epc, condtxt[0] ? "(" : "", condtxt, condtxt[0] ? " is true)" :""); show_registers(regs); #if 0 if(status & wp_type_write) { unsigned long start_addr, end_addr; unsigned long addr = read_c0_watchloX(pwp->watch_nr) & 0xFFFFFFF8; unsigned long mask = read_c0_watchhiX(pwp->watch_nr) & (0x1FF << 3); start_addr = addr & ~(mask | 0x7); end_addr = start_addr | (mask | 0x7); addr = start_addr; printk(KERN_ERR"Memorydump:"); while(addr <= end_addr) { printk(KERN_ERR"%08lx: %*ph\n", addr, (int)min(32UL, end_addr - addr + 4), (unsigned char *)addr); addr += 32; } } #endif console_loglevel = oldlog_level; return 0; } #define GENERAL_OPCODE(a) (((a) >> 26) & 0x3F) #define SPECIAL_OPCODE(a) (((a) >> 0) & 0x3F) #define REGIMM_OPCODE(a) (((a) >> 16) & 0x1F) /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int is_branch_or_jump(unsigned int instruction) { unsigned int s_op, g_op = GENERAL_OPCODE(instruction); switch(g_op) { case 0: s_op = SPECIAL_OPCODE(instruction); if((s_op >= 8) && (s_op <= 9)) { /*--- JR, JALR ---*/ return 1; } break; case 1: s_op = REGIMM_OPCODE(instruction); if((s_op >= 0) && (s_op <= 4)) { /*--- BLTZ, BGEZ, BLTZL, BGEZL ---*/ return 1; } if((s_op >= 16) && (s_op <= 20)) { /*--- BLTZAL, BGEZAL, BLTZ, BGEZLAL ---*/ return 1; } if((s_op >= 28) && (s_op <= 29)) { /*--- BPOSGE32, BPOSGE64 ---*/ return 1; } break; case 2 ... 7: /*--- J, JAL, BEQ, BNE, BLEZ, BGTZ ---*/ return 1; case 20 ... 27: /*--- BEQL, BNEL, BLEZL, BGTZL ---*/ return 1; } return 0; } #define DBG_WP_DISPATCHER(args...) /*--- #define DBG_WP_DISPATCHER(args...) printk(KERN_INFO args) ---*/ /**--------------------------------------------------------------------------------**\ * da die Watchunit vor Ausfuehrung zuschlaegt, wird eine Endlosloop getriggert, wenn * man gleich neu aufsetzt * Loesung: Watchpoint um 8 Byte weitersetzen und dann wieder zurueck * Probleme: (1) ein branch oder jump fuehrt zu keinem Neuaufsetzen * -> schon abfangen beim Aufsetzen - is_branch_or_jump() * (2) die zu überwachende Adresse muss Doubleword-aligned sein * (3) nicht Anwenden falls Adress-Range überwacht wird \**--------------------------------------------------------------------------------**/ static int workarround_for_instruction(struct _avm_wp *pwp, unsigned int loX) { if((pwp->type & wp_type_instruction) && (pwp->mask == 0)) { if((loX & 0xFFFFFFF8) == ((pwp->addr) & 0xFFFFFFF8)) { write_c0_watchloX(pwp->watch_nr, ((pwp->addr + 8) & 0xFFFFFFF8) | (pwp->type & 0x7)); DBG_WP_DISPATCHER("%s: loX=%x hiX=%x", __func__, read_c0_watchloX(pwp->watch_nr), read_c0_watchhiX(pwp->watch_nr)); return 0; } else { DBG_WP_DISPATCHER("%s: wk hit loX=%x hiX=%x %pS", __func__, read_c0_watchloX(pwp->watch_nr), read_c0_watchhiX(pwp->watch_nr), (void *)read_c0_epc()); write_c0_watchhiX(pwp->watch_nr, read_c0_watchhiX(pwp->watch_nr)); write_c0_watchloX(pwp->watch_nr, (pwp->addr & 0xFFFFFFF8) | (pwp->type & 0x7) ); return 1; } } return 0; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int check_addr_range(unsigned long addr1, unsigned long addr2, int mask) { mask |= 0x7; return (addr1 & ~mask) == (addr2 & ~mask); } /**--------------------------------------------------------------------------------**\ * wie kann man die Exceptions unterscheiden, wenn sie zeitgleich auftreten ? \**--------------------------------------------------------------------------------**/ int avm_wp_dispatcher( struct pt_regs *regs){ int act_cpu; struct _avm_wp *pwp = NULL; unsigned int hi_reg, lo_reg, cause; int handled = 0; int watch_nr; int status; int deferred = 0; act_cpu = raw_smp_processor_id(); /*--- Clear WP (bit 22) bit of cause register so we don't loop forever. ---*/ cause = read_c0_cause(); deferred = (cause & ( 1 < 22 )) ; cause &= ~(1 << 22); write_c0_cause( cause); DBG_WP_DISPATCHER("[%x]%s cause=%x %pS %pS a0=%lx\n", act_cpu, __func__, cause, (void *)read_c0_epc(), (void *)regs->regs[31], regs->regs[4]); for (watch_nr = 0; watch_nr < NR_WATCHPOINTS; watch_nr++){ int workarround = 0; unsigned long flags; pwp = &watchpoints[act_cpu][watch_nr]; spin_lock_irqsave(&pwp->wp_lock, flags); hi_reg = read_c0_watchhiX(watch_nr); lo_reg = read_c0_watchloX(watch_nr); status = hi_reg & 7; if(status) { if(atomic_read(&pwp->active) == 0) { spin_unlock_irqrestore(&pwp->wp_lock, flags); continue; } /*--- nur kernel-watchdog: ---*/ pwp->real_exception_count++; write_c0_watchhiX(watch_nr, hi_reg); workarround = workarround_for_instruction(pwp, lo_reg); spin_unlock_irqrestore(&pwp->wp_lock, flags); handled = 1; DBG_WP_DISPATCHER("[%x]%s watch_nr=%u hi_reg=%x(%x) lo_reg=%x active=%u\n", act_cpu, __func__, watch_nr, hi_reg, read_c0_watchhiX(watch_nr), read_c0_watchloX(watch_nr), atomic_read(&pwp->active)); if(atomic_read(&pwp->active) == wp_inactiv) { /*--- exception aufgetreten aber schon als inactive markiert ---*/ __del_watchpoint(watch_nr, &pwp->cpu_mask); } else { if(workarround) { continue; } if((pwp->type & wp_type_instruction) && !check_addr_range(pwp->addr, regs->cp0_epc, pwp->mask)) { DBG_WP_DISPATCHER("[%x]%s watch_nr=%u not the watching addr=%x epc=%lx\n", act_cpu, __func__, watch_nr, pwp->addr, regs->cp0_epc); continue; } if(cpumask_test_cpu(act_cpu, &pwp->cpu_mask) && pwp->handler) { if(pwp->handler(pwp->ref, status, deferred, regs) == 0) { __del_watchpoint(watch_nr, &pwp->cpu_mask); } } } } else { spin_unlock_irqrestore(&pwp->wp_lock, flags); } } return handled; } /**--------------------------------------------------------------------------------**\ * complete: 1 \**--------------------------------------------------------------------------------**/ static char *find_word(char *string, char *match, int complete) { int val; char *p; if((p = strstr(string, match))) { if(p != string) { val = *(p-1); if((val != ' ') && (val != '\t') && (val != '\r') && (val != '\n')) { return NULL; } } if(complete) { int val = *(p + strlen(match)); if((val != 0) &&(val != ' ') && (val != '\t' && (val != '\r') && (val != '\n') )) { return NULL; } } } return p; } #define SKIP_UNTIL_SEP_OR_SIGN(txt, sign) while(*txt && ((*txt != '\r') && (*txt != '\n') && (*txt != ' ') && (*txt != '\t') && (*txt != sign))) txt++ #define SKIP_UNTIL_SPACES(txt) while(*txt && ((*txt != ' ') && (*txt != '\t'))) txt++ #define SKIP_SPACES(txt) while(*txt && (((*txt == ' ') || (*txt == '\t')))) txt++ /*--------------------------------------------------------------------------------*\ * ret: negval -> no match \*--------------------------------------------------------------------------------*/ static int generic_param_parse(char *string, char *match, char **pos) { char *p = string; int ret = -1; if(pos) *pos = ""; if((p = find_word(string, match, 0))){ p += strlen(match); if(*p == '0' && *(p+1) == 'x') { sscanf(p, "0x%x", &ret); } else if(isdigit(*p)) { sscanf(p, "%d", &ret); } else { ret = 0; } SKIP_UNTIL_SPACES(p); SKIP_SPACES(p); if(pos) *pos = p; } return ret; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static unsigned long get_addr(char *p, char *endmarker) { char condtxt[512]; void *expression_handle; unsigned int value; unsigned long addr = 0; unsigned int len = 0; if(endmarker) { len = endmarker - p; } expression_handle = condition_alloc(get_expression_value, get_expression_symbol, 50); if(condition_parse(expression_handle, NULL, p, 0)) { printk(KERN_ERR"invalid expression to get watchaddr: '%s'\n", p); goto fini; } if(condition_calculate(expression_handle, NULL, &value) || (value == 0)) { printk(KERN_ERR"can't resolve expression '%s' to get watchaddr\n", condition_print(expression_handle, condtxt, sizeof(condtxt))); goto fini; } addr = value; fini: condition_free(expression_handle); return addr; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int lproc_wp_set(char *string, void *priv __attribute__((unused))) { char txtbuf[256]; cpumask_t cpu_mask; unsigned long addr; unsigned int mask; char *expr, *endmarker = NULL; void *expression_handle = NULL; char *pos = NULL; int i, iwatch_nr, watch_nr, del_watch_nr, cpu, reset; if(find_word(string, "help", 1)){ printk(KERN_ERR"del[] [cpu=] : 0-1 instruction 2-3 data\n"); printk(KERN_ERR"reset\n"); printk(KERN_ERR"iwatch[] [mask=0x0-0x1ff] [cpu=] [if ]\n"); printk(KERN_ERR"watch[] [mask=0x0-0x1ff] [cpu=] (read+write-watch)\n"); printk(KERN_ERR"rwatch[] [mask=0x0-0x1ff] [cpu=] (read-watch)\n"); printk(KERN_ERR"wwatch[] [mask=0x0-0x1ff] [cpu=] (write-watch)\n"); printk(KERN_ERR", , if : optional\n"); printk(KERN_ERR"/ as expression include register $a0, symbol-name, memory-access (*addr)\n"); printk(KERN_ERR": start_addr = addr & ~(mask<<3)| 0x7); end_addr = (start_addr | (mask<<3)| 0x7)\n"); printk(KERN_ERR"example: echo 'iwatch1 __kmalloc + 0x4 cpu=0 if $a0 < 10' > /proc/avm/wp\n"); return 0; } reset = generic_param_parse(string, "reset", &pos); if(reset != -1) { /*--- fuer ptrace wieder nutzbar machen ---*/ reset_all_watchpoints(); return 0; } cpu = generic_param_parse(string, "cpu=", &pos); cpu_id_to_cpumask(&cpu_mask, cpu); if(pos) { endmarker = pos; } mask = generic_param_parse(string, "mask=", &pos); if(endmarker && (pos < endmarker)) { endmarker = pos; } if(mask == -1) { mask = 0; } del_watch_nr = generic_param_parse(string, "del", &pos); if(del_watch_nr != -1) { printk(KERN_ERR"delete watch%u on cpumask=%lx\n", del_watch_nr, cpumask_bits(&cpu_mask)[0]); __del_watchpoint(del_watch_nr, &cpu_mask); return 0; } if((expr = find_word(string, "if", 1))) { /*--- int len = strlen(expr) - 1; ---*/ /*--- if(expr[len] != '\n') len = 0; ---*/ endmarker = expr; expr += sizeof("if") - 1; /*--- len -= sizeof("if") - 1; ---*/ expression_handle = condition_alloc(get_expression_value, get_expression_symbol, 50); if(condition_parse(expression_handle, NULL, expr, 0)) { printk(KERN_ERR "invalid expr '%s'\n", expr); goto fini; } printk(KERN_ERR"expression: %s\n", condition_print(expression_handle, txtbuf, sizeof(txtbuf))); } if(mask && expression_handle) { printk(KERN_ERR"no support for mask in combination with condition\n"); goto fini; } iwatch_nr = generic_param_parse(string, "iwatch", &pos); if(iwatch_nr != -1) { addr = get_addr(pos, endmarker) & ~0x3; if(mask) { unsigned int start_addr, end_addr; start_addr = addr & ~((mask << 3) | 0x7); end_addr = start_addr | ((mask << 3) | 0x7); printk(KERN_ERR"mask=0x%x<<3: extended addr-range for iwatch %08x - %8x\n", mask, start_addr, end_addr); addr = start_addr; } else if(addr & 0x7) { addr = (addr + 4) & ~0x7; printk(KERN_ERR"addr not aligned on doubleword - forward-correction: addr=%08lx", addr); } if(!virt_addr_valid(addr) && !is_vmalloc_or_module_addr((void *)addr)) { printk(KERN_ERR"invalid addr/symbol"); } else if((mask == 0) && (is_branch_or_jump(*((unsigned int *)(addr & ~0x7))) || is_branch_or_jump(*((unsigned int *)((addr & ~0x7) + 4)))) ) { printk(KERN_ERR"can't install watchpoint on jump-instruction\n"); } else { set_watchpoint_expression(expression_handle, iwatch_nr, &cpu_mask); set_watchpoint_handler(NULL, iwatch_nr, addr, mask << 3, wp_type_instruction, default_wp_handler, &cpu_mask); } goto fini; } for(i = 0; i < 3; i++) { watch_nr = generic_param_parse(string, i == 0 ? "watch" : i == 1 ? "rwatch" : "wwatch", &pos); if(watch_nr != -1) { if(watch_nr < 2) watch_nr += 2; addr = get_addr(pos, endmarker) & ~0x3; if(!virt_addr_valid(addr) && !is_vmalloc_or_module_addr((void *)addr)) { printk(KERN_ERR"invalid addr/symbol"); } else if(expression_handle){ printk(KERN_ERR"no support for expressions on data-watchpoint\n"); } else { if(mask) { unsigned int start_addr, end_addr; start_addr = addr & ~((mask << 3)| 0x7); end_addr = start_addr | ((mask << 3) | 0x7); printk(KERN_ERR"mask=0x%x<<3: extended addr-range: %08x - %8x\n", mask,start_addr, end_addr); addr = start_addr; } else if(addr & 0x7) { addr &= ~0x7; printk(KERN_ERR"addr not aligned on doubleword - backward-correction: addr=%08lx", addr); } set_watchpoint_expression(expression_handle, watch_nr, &cpu_mask); set_watchpoint_handler(NULL, watch_nr, addr, mask << 3, i == 0 ? wp_type_read | wp_type_write : i == 1 ? wp_type_read : wp_type_write, default_wp_handler, &cpu_mask); } goto fini; } } fini: if(expression_handle) condition_free(expression_handle); return 0; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int get_expression_value(void *ref, enum _cond_type type, unsigned int *value){ unsigned int *addr = (unsigned int *)*value; struct pt_regs *pptregs = (struct pt_regs *)ref; /*--- printk(KERN_ERR"%s: type=%u value=%x\n", __func__, type, *value); ---*/ switch(type){ case cond_type_register: if(pptregs) { *value = pptregs->regs[*value]; break; } return -1; case cond_type_addr: if(virt_addr_valid(addr) || is_vmalloc_or_module_addr(addr)) { *value = *addr; break; } /*--- kein break ---*/ default: printk(KERN_ERR"%s: type=%u addr=%p failed\n", __func__, type, addr); return -1; } return 0; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static const char *map_register[] = { "zero", "at", "v0", "v1", "a0", "a1", "a2", "a3", "t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "s0", "s1", "s2", "s3", "s4", "s5", "s6", "s7", "t8", "t9", "k0", "k1", "gp", "sp", "s8", "ra", NULL }; /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static int map_regname_to_idx(const char *regname) { unsigned int idx = 0; while(map_register[idx] && strcmp(regname, map_register[idx])) idx++; return map_register[idx] ? idx : -1; } /**--------------------------------------------------------------------------------**\ * symbol == NULL: (nur bei cond_type_register) checke register-idx \**--------------------------------------------------------------------------------**/ static int get_expression_symbol(void *ref, enum _cond_type type, char *symbol, unsigned int *value) { int ret = -1; /*--- printk(KERN_ERR"%s: %s(%d) '%s' value=%d\n", __func__, type == cond_type_register ? "register" : "addr", type, symbol, *value); ---*/ switch(type){ case cond_type_register: if(symbol == NULL) { if(*value < ARRAY_SIZE(map_register) -1) { ret = 0; } break; } ret = map_regname_to_idx(symbol); *value = ret; break; default: *value = (unsigned long)kallsyms_lookup_name(symbol); if(*value) { /*--- printk(KERN_ERR"%s: symbol '%s'to value=%x\n", __func__, symbol, *value); ---*/ ret = 0; } } if(ret < 0) printk(KERN_ERR"%s: type=%d '%s' value=%d failed\n", __func__, type, symbol, *value); return ret < 0 ? ret : 0; } #define snprintf_add(ptxt, txtlen, args...) { int local_add_len;\ if((local_add_len = snprintf(ptxt, txtlen, args)) > 0) { \ int tail = min((int)txtlen, local_add_len); \ (ptxt) += tail, (txtlen) -= tail; \ }} /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static char *print_exception_count(int watch_nr, char *txt, unsigned int txt_len) { char *start_txt = txt; int cpu; for_each_online_cpu(cpu) { struct _avm_wp *pwp = &watchpoints[cpu][watch_nr]; /*--- if(cpumask_test_cpu(cpu, &pwp->cpu_mask)) { ---*/ snprintf_add(txt, txt_len, "%u/", pwp->exception_count); /*--- } ---*/ } if(txt > start_txt) { *(txt-1) = 0; } return start_txt; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static char *print_activ_state(struct _avm_wp *pwp, int act_cpu, char *txt, unsigned int txt_len) { if(pwp->hit) { snprintf(txt, txt_len, "hit on cpu%u", act_cpu); } else if(atomic_read(&pwp->active) == wp_activ){ snprintf(txt, txt_len, "activ on cpumask=0x%lx", cpumask_bits(&pwp->cpu_mask)[0]); } else { snprintf(txt, txt_len, "off"); } return txt; } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ static void lproc_wp_get(struct seq_file *seq, void *priv){ struct _avm_wp *pwp; char txt[32]; char condtxt[512]; char counttxt[128]; char statetxt[32]; char ratxt[KSYM_NAME_LEN+32]; int cpu, watch_nr; for(watch_nr = 0; watch_nr < NR_WATCHPOINTS; watch_nr++) { cpumask_t cpu_print_mask; cpumask_clear(&cpu_print_mask); for_each_online_cpu(cpu) { pwp = &watchpoints[cpu][watch_nr]; if((cpumask_test_cpu(cpu, &pwp->cpu_mask) || atomic_read(&pwp->active)) && !cpumask_test_cpu(cpu, &cpu_print_mask)) { condtxt[0] = 0; counttxt[0] = 0; condition_get(pwp->expr); if(pwp->expr) { condition_print(pwp->expr, condtxt, sizeof(condtxt)); } condition_put(pwp->expr); if(pwp->hit) { snprintf(ratxt, sizeof(ratxt), " from [<%8lx>] (%pS)", pwp->ra, (void *)pwp->ra); } else { ratxt[0] = 0; } if((atomic_read(&pwp->active) == wp_activ) || ((atomic_read(&pwp->active) == wp_inactiv) && pwp->hit)) { seq_printf(seq, "%u: %s %s [<%08lx>] (%pS)%s mask=%x '%s' %s exceptions(%s)\n", watch_nr, print_activ_state(pwp, cpu, statetxt, sizeof(statetxt)), name_wp_type(txt, sizeof(txt), pwp->type), pwp->hit ? pwp->epc : pwp->addr, pwp->hit ? (void *)pwp->epc : (void *)pwp->addr, ratxt, pwp->mask, condtxt, condtxt[0] && pwp->hit ? " == true" : " ", print_exception_count(watch_nr, counttxt, sizeof(counttxt)) ); cpumask_or(&cpu_print_mask, &cpu_print_mask, &pwp->cpu_mask); } } } } } /**--------------------------------------------------------------------------------**\ \**--------------------------------------------------------------------------------**/ int __init avm_wp_init(void) { int cpu, watch_nr; add_simple_proc_file( "avm/wp", lproc_wp_set, lproc_wp_get, NULL); for(cpu = 0 ; cpu < NR_CPUS; cpu++) { for(watch_nr = 0; watch_nr < NR_WATCHPOINTS; watch_nr++) { struct _avm_wp *pwp = &watchpoints[cpu][watch_nr]; spin_lock_init(&pwp->wp_lock); } } return 0; } late_initcall(avm_wp_init); #if defined(CONFIG_DUMP_CP0) #error bla #define NR_CP0_REGS 80 int cp0_dump_array[NR_CP0_REGS]; int read_cp0_array(int i) { if (i >= NR_CP0_REGS) return -1; else return cp0_dump_array[i]; } void dump_cp0(void){ cp0_dump_array[0] = __read_32bit_c0_register($0, 0); cp0_dump_array[1] = __read_32bit_c0_register($0, 1); cp0_dump_array[2] = __read_32bit_c0_register($0, 2); cp0_dump_array[3] = __read_32bit_c0_register($0, 3); cp0_dump_array[4] = __read_32bit_c0_register($1, 0); cp0_dump_array[5] = __read_32bit_c0_register($1, 1); cp0_dump_array[6] = __read_32bit_c0_register($1, 2); cp0_dump_array[7] = __read_32bit_c0_register($1, 3); cp0_dump_array[8] = __read_32bit_c0_register($1, 4); cp0_dump_array[9] = __read_32bit_c0_register($1, 5); cp0_dump_array[10] = __read_32bit_c0_register($1, 6); cp0_dump_array[11] = __read_32bit_c0_register($1, 7); cp0_dump_array[12] = __read_32bit_c0_register($2, 0); cp0_dump_array[13] = __read_32bit_c0_register($2, 1); cp0_dump_array[14] = __read_32bit_c0_register($2, 2); cp0_dump_array[15] = __read_32bit_c0_register($2, 3); cp0_dump_array[16] = __read_32bit_c0_register($2, 4); cp0_dump_array[17] = __read_32bit_c0_register($2, 5); cp0_dump_array[18] = __read_32bit_c0_register($2, 6); cp0_dump_array[19] = __read_32bit_c0_register($2, 7); cp0_dump_array[20] = __read_32bit_c0_register($4, 0); cp0_dump_array[21] = __read_32bit_c0_register($4, 2); cp0_dump_array[22] = __read_32bit_c0_register($5, 0); cp0_dump_array[23] = __read_32bit_c0_register($6, 0); cp0_dump_array[24] = __read_32bit_c0_register($6, 1); cp0_dump_array[25] = __read_32bit_c0_register($6, 2); cp0_dump_array[26] = __read_32bit_c0_register($6, 3); cp0_dump_array[27] = __read_32bit_c0_register($6, 4); cp0_dump_array[28] = __read_32bit_c0_register($6, 5); cp0_dump_array[29] = __read_32bit_c0_register($7, 0); cp0_dump_array[30] = __read_32bit_c0_register($8, 0); cp0_dump_array[31] = __read_32bit_c0_register($9, 0); cp0_dump_array[32] = __read_32bit_c0_register($10, 0); cp0_dump_array[33] = __read_32bit_c0_register($11, 0); cp0_dump_array[34] = __read_32bit_c0_register($12, 0); cp0_dump_array[35] = __read_32bit_c0_register($12, 1); cp0_dump_array[36] = __read_32bit_c0_register($12, 2); cp0_dump_array[37] = __read_32bit_c0_register($12, 3); cp0_dump_array[38] = __read_32bit_c0_register($13, 0); cp0_dump_array[39] = __read_32bit_c0_register($14, 0); cp0_dump_array[40] = __read_32bit_c0_register($15, 0); cp0_dump_array[41] = __read_32bit_c0_register($15, 1); cp0_dump_array[42] = __read_32bit_c0_register($16, 0); cp0_dump_array[43] = __read_32bit_c0_register($16, 1); cp0_dump_array[44] = __read_32bit_c0_register($16, 2); cp0_dump_array[45] = __read_32bit_c0_register($16, 3); cp0_dump_array[46] = __read_32bit_c0_register($16, 7); cp0_dump_array[47] = __read_32bit_c0_register($17, 0); cp0_dump_array[48] = __read_32bit_c0_register($18, 0); cp0_dump_array[49] = __read_32bit_c0_register($18, 1); cp0_dump_array[50] = __read_32bit_c0_register($18, 2); cp0_dump_array[51] = __read_32bit_c0_register($18, 3); cp0_dump_array[52] = __read_32bit_c0_register($19, 0); cp0_dump_array[53] = __read_32bit_c0_register($19, 1); cp0_dump_array[54] = __read_32bit_c0_register($19, 2); cp0_dump_array[55] = __read_32bit_c0_register($19, 3); cp0_dump_array[56] = __read_32bit_c0_register($23, 0); cp0_dump_array[57] = __read_32bit_c0_register($23, 1); cp0_dump_array[58] = __read_32bit_c0_register($23, 2); cp0_dump_array[59] = __read_32bit_c0_register($23, 3); cp0_dump_array[60] = __read_32bit_c0_register($23, 4); cp0_dump_array[61] = __read_32bit_c0_register($23, 5); cp0_dump_array[62] = __read_32bit_c0_register($24, 0); cp0_dump_array[63] = __read_32bit_c0_register($25, 0); cp0_dump_array[64] = __read_32bit_c0_register($25, 1); cp0_dump_array[65] = __read_32bit_c0_register($25, 2); cp0_dump_array[66] = __read_32bit_c0_register($25, 3); cp0_dump_array[67] = __read_32bit_c0_register($26, 0); cp0_dump_array[68] = __read_32bit_c0_register($27, 0); cp0_dump_array[69] = __read_32bit_c0_register($28, 0); cp0_dump_array[70] = __read_32bit_c0_register($28, 1); cp0_dump_array[71] = __read_32bit_c0_register($28, 2); cp0_dump_array[72] = __read_32bit_c0_register($28, 3); cp0_dump_array[73] = __read_32bit_c0_register($28, 4); cp0_dump_array[74] = __read_32bit_c0_register($28, 5); cp0_dump_array[75] = __read_32bit_c0_register($29, 1); cp0_dump_array[76] = __read_32bit_c0_register($29, 5); cp0_dump_array[77] = __read_32bit_c0_register($30, 0); cp0_dump_array[78] = __read_32bit_c0_register($31, 0); } static int proc_read_cp0(char *page, char **start __attribute__ ((unused)), off_t off __attribute__((unused)), int count __attribute__ ((unused)), int *eof, void *data __attribute__ ((unused))) { int len = 0; int i; dump_cp0(); for ( i = 0; i < 78; i++) len += sprintf(page + len, "cp0_dump_array[%d] = %#x\n", i, read_cp0_array(i)); *eof = 1; return len; } static int __init setup_proc_wp(void){ struct proc_dir_entry *res = NULL; res = create_proc_entry("dump_cp0", 0, NULL); if (res) { res->read_proc = proc_read_cp0; res->write_proc = NULL; } return 0; } late_initcall(setup_proc_wp); #endif EXPORT_SYMBOL(set_watchpoint); EXPORT_SYMBOL(del_watchpoint); EXPORT_SYMBOL(watchpoint_busy); #endif