/* * Ueber echo "(arg0, .... arg4) [cpu] [simulate]" >/proc/avm/call * eine beliebige Kernelfunktion ausfuehren - nur Entwicklerversion */ #include #include #include #include #include #include #include #include #include #include #include #include #if defined(CONFIG_MIPS) && LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) #include #endif/*--- #if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) && defined(CONFIG_MIPS) ---*/ #include struct _call_list { unsigned int args[5]; unsigned int argcount; unsigned int argstring_mask; unsigned int simulate; unsigned int asmlink; unsigned long func; char *tmpbuf; }; static char *print_callback_string(char *txt, unsigned int txt_len, struct _call_list *call) { char *p = txt; unsigned int idx = 0, len; len = snprintf(p, txt_len, "%pS(", (void *)call->func); if (txt_len >= len) { txt_len -= len; p += len; } for (idx = 0; idx < call->argcount; idx++) { if (call->argstring_mask & (1 << idx)) { len = snprintf(p, txt_len, "%s\"%s\"", idx ? ", " : "", (char *)call->args[idx]); } else { len = snprintf(p, txt_len, "%s0x%x", idx ? ", " : "", call->args[idx]); } if (txt_len >= len) { txt_len -= len; p += len; } } snprintf(p, txt_len, ")"); return txt; } static void call_function(struct _call_list *call) { unsigned int ret = 0; char txt[256]; if (call->simulate) { pr_err("Call%s:%s\n", call->asmlink ? "(asmlinkage)" : "", print_callback_string(txt, sizeof(txt), call)); kfree(call); return; } if (call->asmlink) { switch (call->argcount) { case 0: ret = ((asmlinkage unsigned int (*)(void))call->func)(); break; case 1: ret = ((asmlinkage unsigned int (*)(unsigned int))call->func)(call->args[0]); break; case 2: ret = ((asmlinkage unsigned int (*)(unsigned int, unsigned int))call->func)(call->args[0], call->args[1]); break; case 3: ret = ((asmlinkage unsigned int (*)(unsigned int, unsigned int, unsigned int))call->func)(call->args[0], call->args[1], call->args[2]); break; case 4: ret = ((asmlinkage unsigned int (*)(unsigned int, unsigned int, unsigned int, unsigned int))call->func)(call->args[0], call->args[1], call->args[2], call->args[3]); break; case 5: ret = ((asmlinkage unsigned int (*)(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int))call->func)(call->args[0], call->args[1], call->args[2], call->args[3], call->args[4]); break; } } else { switch (call->argcount) { case 0: ret = ((unsigned int (*)(void))call->func)(); break; case 1: ret = ((unsigned int (*)(unsigned int))call->func)(call->args[0]); break; case 2: ret = ((unsigned int (*)(unsigned int, unsigned int))call->func)(call->args[0], call->args[1]); break; case 3: ret = ((unsigned int (*)(unsigned int, unsigned int, unsigned int))call->func)(call->args[0], call->args[1], call->args[2]); break; case 4: ret = ((unsigned int (*)(unsigned int, unsigned int, unsigned int, unsigned int))call->func)(call->args[0], call->args[1], call->args[2], call->args[3]); break; case 5: ret = ((unsigned int (*)(unsigned int, unsigned int, unsigned int, unsigned int, unsigned int))call->func)(call->args[0], call->args[1], call->args[2], call->args[3], call->args[4]); break; } } pr_err("\n----> Called%s:%s ret=0x%08x\n", call->asmlink ? "(asmlinkage)" : "", print_callback_string(txt, sizeof(txt), call), ret); kfree(call); } #if defined(CONFIG_SMP) static void start_func_on_cpu(void *param) { call_function((struct _call_list *)param); } #endif/*--- #if defined(CONFIG_SMP) ---*/ #define SKIP_UNTIL_SPACES_OR_SIGN(txt, sign) do { while (*txt && ((*txt != ' ') && (*txt != '\t') && (*txt != sign))) txt++; } while (0) #define SKIP_UNTIL_SEPERATOR(txt) do { while (*txt && ((*txt != ' ') && (*txt != ',') && (*txt != '\t') && (*txt != ')'))) txt++; } while (0) #define SKIP_UNTIL(txt, sign) do { while (*txt && (*txt != sign)) txt++; } while (0) /* * format: (arg1, arg2, arg3 ...) * arg 0x -> hex * val -> dezimal * 'string' -> string * "string" -> string * * ret: NULL Fehler, sonst pointer hinter ')' * Beispiel: * echo 'strstr("test", "s" ) ' >/proc/avm/call */ static char *scan_arguments(char *txt, struct _call_list *call) { unsigned int idx = 0; txt = skip_spaces(txt); SKIP_UNTIL(txt, '('); if (*txt == 0) { pr_err("invalid arguments - missing '('\n"); return NULL; } txt++; for (;;) { txt = skip_spaces(txt); if (txt[0] == 0) { pr_err("%s:missing ')'\n", __func__); return NULL; } if (txt[0] == ')') { txt++; break; } if (idx >= ARRAY_SIZE(call->args)) { pr_err("%s:too much arguments\n", __func__); return NULL; } if (txt[0] == '0' && txt[1] == 'x') { sscanf(txt, "0x%x", &call->args[idx]); } else if (txt[0] == '\'' || txt[0] == '"') { unsigned int len; unsigned char endsign = txt[0]; char *end; txt++; end = txt; SKIP_UNTIL(end, endsign); if (*end != endsign) { pr_err("%s:invalid arguments - missing %c\n", __func__, endsign); return NULL; } len = end - txt; memcpy(call->tmpbuf, txt, len); call->args[idx] = (unsigned int)call->tmpbuf; call->argstring_mask |= 1 << idx; call->tmpbuf += len + 1; txt = end + 1; } else { sscanf(txt, "%d", &call->args[idx]); } idx++; SKIP_UNTIL_SEPERATOR(txt); if (*txt == ',') txt++; } call->argcount = idx; return txt; } /* * fuehrt eine Funktion aus: */ static int lproc_call(char *buffer, void *priv __attribute__((unused))) { struct _call_list *pcall; char namebuf[KSYM_NAME_LEN]; char *p1, *p = buffer; unsigned int cpu, this_cpu; pcall = kzalloc(sizeof(struct _call_list) + strlen(buffer) + 1 + ARRAY_SIZE(pcall->args), GFP_KERNEL); if (pcall == NULL) { return 0; } pcall->tmpbuf = (char *)(pcall + 1); p = skip_spaces(p); /*--- extrahiere Funktionsname/Funktionspointer: ---*/ p1 = p; SKIP_UNTIL_SPACES_OR_SIGN(p1, '('); if (*p1) { size_t len = min((size_t)(p1 - p), sizeof(namebuf) - 1); memcpy(namebuf, p, len); namebuf[len] = 0; pcall->func = (unsigned long)kallsyms_lookup_name(namebuf); } if (pcall->func == 0) { sscanf(p, "%lx", &pcall->func); } if (!func_ptr_is_kernel_text((void *)pcall->func)) { pr_err("invalid func-addr use: (arg0, .. arg4) [cpu] [simulate] [asmlinkage] ('arg' for string)\n"); kfree(pcall); return 0; } /*--- extrahiere Argumente: ---*/ if ((p = scan_arguments(p, pcall)) == NULL) { kfree(pcall); return 0; } /*--- nur simulieren (geparste Argumente anzeigen) ? ---*/ if (strstr(p, "sim")) { pcall->simulate = 1; } if (strstr(p, "asm")) { pcall->asmlink = 1; } /*---ausfuehren auf CPU ? ---*/ this_cpu = get_cpu(); if ((p = strstr(p, "cpu"))) { p += sizeof("cpu") - 1; cpu = p[0] - '0'; } else { cpu = this_cpu; } #if defined(CONFIG_SMP) if (cpu_online(cpu) && (cpu != this_cpu)) { smp_call_function_single(cpu, start_func_on_cpu, (void *)pcall, 0); } else #endif /*--- #if defined(CONFIG_SMP) ---*/ call_function(pcall); put_cpu(); return 0; } static void simulate_kernel_crash(void) { int *inval_pointer = NULL; *inval_pointer = 0x43524153; } /** */ static void event_dummy(void *context __attribute__((unused)), enum _avm_event_id id __attribute__((unused))) { } /** */ static void event_received(void *context __attribute__((unused)), unsigned char *buf __attribute__((unused)), unsigned int len __attribute__((unused))) { msleep(10); /*--- Abarbeitung kuenstlich verzoegern ---*/ /*--- pr_err("%s: %u:\n", __func__, __LINE__); ---*/ }; static struct task_struct *event_kthread; static unsigned int event_trigger_msec, event_trigger_count; /** * use remote_watchdog-event (only used on puma6) to simulate event-overun or stress a little bit */ static int simulate_event_ovr_thread(void *data __maybe_unused) { void *sink_handle, *source_handle; struct _avm_event_id_mask id_mask; const struct _avm_event_remotewatchdog event = { .event_header = { .id = avm_event_id_remotewatchdog }, .cmd = wdt_trigger, .name = "DUMMY", .param = 120 + WDT_DEFAULT_TIME * 4, }; source_handle = avm_event_source_register("avm_event_remotewatchdog", avm_event_build_id_mask(&id_mask, 1, avm_event_id_remotewatchdog), event_dummy, NULL); sink_handle = avm_event_sink_register("avm_event_remotewatchdog_sink", avm_event_build_id_mask(&id_mask, 1, avm_event_id_remotewatchdog), event_received, NULL); if ((source_handle == NULL) || (sink_handle == NULL)) { goto exit_thread; } while (!kthread_should_stop() && event_trigger_count) { struct _avm_event_remotewatchdog *pevent = kmalloc(sizeof(struct _avm_event_remotewatchdog), GFP_KERNEL); event_trigger_count--; if (pevent == NULL) { pr_warn("%s: can't alloc event\n", __func__); break; } memcpy(pevent, &event, sizeof(struct _avm_event_remotewatchdog)); avm_event_source_trigger(source_handle, avm_event_id_remotewatchdog, sizeof(struct _avm_event_remotewatchdog), pevent); msleep(event_trigger_msec); } exit_thread: if (source_handle) avm_event_source_release(source_handle); if (sink_handle) avm_event_sink_release(sink_handle); event_kthread = NULL; return 0; } static void **memtable; static unsigned int free_type; static void simulate_oom(unsigned int slab, unsigned int order, unsigned int limit) { unsigned int size = 1U << order; unsigned int i, ii; if (memtable) { i = 0; while (memtable[i]) { if (free_type) { kfree(memtable[i]); } else { /*--- pr_info("[%u]vfree(%p)\n", i, memtable[i]); ---*/ vfree(memtable[i]); } i++; } kfree(memtable); memtable = NULL; } if (limit) { memtable = kzalloc((limit + 1) * sizeof(void *), GFP_KERNEL); free_type = slab; } else { limit = (unsigned int)-1; } for (i = 0; i < limit; i++) { unsigned int *p; if (slab) { p = kmalloc(size, GFP_KERNEL); if (memtable) { memtable[i] = p; } } else { p = vmalloc(size); if (memtable) { memtable[i] = p; /*--- pr_info("[%u]vmalloc(%u) = %p\n", i, size, memtable[i]); ---*/ } } if (p == NULL) { break; } for (ii = 0; ii < size / sizeof(unsigned int); ii++) { p[ii] = ii ^ (unsigned int)p; } } } #define SIMULATE_WD "simulate_wdr" static void simulate_wdr(void) { #if defined(CONFIG_AVM_WATCHDOG) AVM_WATCHDOG_register(0, SIMULATE_WD, sizeof(SIMULATE_WD)); #endif/*--- #if defined(CONFIG_AVM_WATCHDOG) ---*/ } #if defined(CONFIG_SMP) static void knockout_cpu(void *dummy __attribute__((unused))) { local_irq_disable(); for (;;) ; } #endif/*--- #if defined(CONFIG_SMP) ---*/ #define SKIP_UNTIL_SPACES(txt) do { while (*txt && ((*txt != ' ') && (*txt != '\t'))) txt++; } while (0) static unsigned long scan_addr_and_param(char *txt, unsigned int *param) { size_t len; char namebuf[KSYM_NAME_LEN]; unsigned long addr; char *p; txt = skip_spaces(txt); p = txt; SKIP_UNTIL_SPACES(p); len = min((size_t)(p - txt), sizeof(namebuf) - 1); memcpy(namebuf, txt, len); namebuf[len] = 0; addr = (unsigned long)kallsyms_lookup_name(namebuf); if (addr == 0) { sscanf(txt, "%lx %x", &addr, param); } else { if (*p) p++; txt = skip_spaces(txt); sscanf(p, "%x", param); } return addr; } struct cmd { const char *name; const char *help; int (*func)(char *params); }; #if defined(print_memory_classifier) static int class_cmd(char *p) { unsigned long addr = 0; unsigned int i, val = 0; char sym[KSYM_SYMBOL_LEN]; addr = scan_addr_and_param(p, &val); if (val == 0) val = 1; for (i = 0; i < val; i++) { print_memory_classifier(sym, sizeof(sym), addr, 0); if (sym[0] || (i == 0) || (i == (val - 1))) { pr_err("%08lx --> %s\n", addr, sym); } addr += PAGE_SIZE; } return 0; } #endif static int wdr_cmd(char *p __maybe_unused) { pr_err("\nSimulate Watchdog-Reboot with '%s'\n", SIMULATE_WD); simulate_wdr(); return 0; } static int waddr_cmd(char *p) { unsigned long addr; unsigned int val = 0; addr = scan_addr_and_param(p, &val); if (addr < PAGE_SIZE) { return 0; } pr_err("waddr: *(%lx) = %x\n", addr, val); *((unsigned int *)addr) = val; wmb(); return 0; } static int raddr_cmd(char *p) { unsigned long addr; unsigned int i, s = 3, count = 0, val; addr = scan_addr_and_param(p, &count); if (addr == 0) { return 0; } if (count == 0) count = 1; pr_err("raddr: addr=%lx count=%x\n", addr, count); for (i = 0; i < count; i++) { val = *((unsigned int *)addr); mb(); if (++s == 4) { s = 0; pr_cont("\n%08lx: %08x", addr, val); } else { pr_cont(" %08x", val); } addr += 4; } pr_cont("\n"); return 0; } static int oomslab_cmd(char *p) { unsigned int order = 0, limit = 0; sscanf(p, "%u %u", &order, &limit); if (order == 0) { order = 14; } pr_err("\nSimulate OOM per kmalloc order=%u limit=%u\n", order, limit); simulate_oom(1, order, limit); return 0; } static int oom_cmd(char *p) { unsigned int order = 0, limit = 0; sscanf(p, "%u %u", &order, &limit); if (order == 0) { order = 14; } pr_err("\nSimulate OOM per vmalloc order=%u limit=%u\n", order, limit); simulate_oom(0, order, limit); return 0; } static int bug_on_cmd(char *p __maybe_unused) { int is_true = false; BUG_ON(is_true == false); return 0; } static int event_ovr_cmd(char *p) { sscanf(p, "%u %u", &event_trigger_msec, &event_trigger_count); if (event_trigger_count == 0) { event_trigger_count = 100; } if (event_trigger_msec > 1000) { event_trigger_msec = 1000; } if (event_kthread) { kthread_stop(event_kthread); } event_kthread = kthread_run(simulate_event_ovr_thread, NULL, "event_ovr"); return 0; } static int kcrash_cmd(char *p __maybe_unused) { pr_err("\nSimulate Kernel-Crash\n"); simulate_kernel_crash(); return 0; } static int hw_wdog_cmd(char *p __maybe_unused) { #if defined(CONFIG_SMP) on_each_cpu(knockout_cpu, NULL, 0); #endif/*--- #if defined(CONFIG_SMP) ---*/ local_irq_disable(); for (;;) ; return 0; } static DEFINE_PER_CPU(struct timer_list, trigger_timer); static void waste_timer_func(unsigned long percent) { int cpu; unsigned long ta, te, tdiff; struct timer_list *pt; ta = avm_get_cycles(); tdiff = ((avm_get_cyclefreq() / 10 / 100) * percent); cpu = get_cpu(); pt = &per_cpu(trigger_timer, cpu); mod_timer(pt, jiffies + msecs_to_jiffies(100)); put_cpu(); for (;;) { te = avm_get_cycles(); if ((te - ta) > tdiff) { break; } udelay(10); } { static int count[4]; if(count[cpu]++ > 50) { pr_err("%s: %u: percent=%lu te=%lu ta=%lu tdiff=%lu\n", __func__, __LINE__, percent, te, ta, tdiff); count[cpu] = 0; } } } static int timer_cmd(char *p) { unsigned int cpu; cpumask_t cpu_mask; unsigned int percent; percent = 50; cpu = num_possible_cpus(); sscanf(p, "%u %u", &percent, &cpu); if (cpu >= num_possible_cpus()) { cpumask_setall(&cpu_mask); } else { cpumask_clear(&cpu_mask); cpumask_set_cpu(cpu, &cpu_mask); } if (percent > 100) { percent = 100; } pr_err("\nSimulate waste %u %% on timer on %scpu%c\n", percent, cpu >= num_possible_cpus() ? "all " : "", cpu >= num_possible_cpus() ? 's': cpu + '0'); for_each_online_cpu(cpu) { struct timer_list *pt = &per_cpu(trigger_timer, cpu); if (!cpumask_test_cpu(cpu, &cpu_mask)) { continue; } if (pt->function) { del_timer_sync(pt); pt->function = NULL; } if (percent == 0) { continue; } setup_timer(pt, waste_timer_func, percent); pt->expires = jiffies + msecs_to_jiffies(100); add_timer_on(pt, cpu); } return 0; } /* * NOTICE: currently command names are only matched as prefix, therefore * longer more specific commands must be listed first. */ static struct cmd cmds[] = { #if defined(print_memory_classifier) {.name = "class", .func = class_cmd, .help = " - Classify pages"}, #endif {.name = "wdr", .func = wdr_cmd}, {.name = "waddr", .func = waddr_cmd, .help = " - Write to address"}, {.name = "raddr", .func = raddr_cmd, .help = " - Read from address"}, {.name = "oomslab", .func = oomslab_cmd, .help = "- OOM via slab allocation"}, {.name = "oom", .func = oom_cmd, .help = "- OOM via vmalloc"}, {.name = "bug_on", .func = bug_on_cmd}, {.name = "event_ovr", .func = event_ovr_cmd, .help = " "}, {.name = "kcrash", .func = kcrash_cmd}, {.name = "hw_wdog", .func = hw_wdog_cmd, .help = "- Lock up all cpus"}, {.name = "timer", .func = timer_cmd, .help = "- waste time in sw-irq on "}, {} }; static int lproc_simulate_write(char *buffer, void *priv __attribute__((unused))) { int i; char parsbuf[256], *p; strncpy(parsbuf, buffer, sizeof(parsbuf) - 1); parsbuf[sizeof(parsbuf) - 1] = '\0'; p = parsbuf; p = skip_spaces(p); for (i = 0; cmds[i].name != NULL; i++) { size_t len = strlen(cmds[i].name); if (strncmp(p, cmds[i].name, len) == 0) { p += len; p = skip_spaces(p); return cmds[i].func(p); } } pr_err("Unkown simulate command %s\n", p); return 0; } static void lproc_simulate_read(struct seq_file *f, void *ctx __maybe_unused) { int i; for (i = 0; cmds[i].name != NULL; i++) { seq_printf(f, "%s %s\n", cmds[i].name, cmds[i].help ?: ""); } } static int __init avm_proc_call_init(void) { add_simple_proc_file("avm/call", lproc_call, NULL, NULL); add_simple_proc_file("avm/simulate", lproc_simulate_write, lproc_simulate_read, NULL); return 0; } module_init(avm_proc_call_init); MODULE_LICENSE("GPL");