diff options
Diffstat (limited to 'arch/mips/kernel/process.c')
-rw-r--r-- | arch/mips/kernel/process.c | 257 |
1 files changed, 146 insertions, 111 deletions
diff --git a/arch/mips/kernel/process.c b/arch/mips/kernel/process.c index 7ab67f786bfe..2613a0dd4b82 100644 --- a/arch/mips/kernel/process.c +++ b/arch/mips/kernel/process.c | |||
@@ -273,104 +273,107 @@ long kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) | |||
273 | return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); | 273 | return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, ®s, 0, NULL, NULL); |
274 | } | 274 | } |
275 | 275 | ||
276 | static struct mips_frame_info { | 276 | /* |
277 | void *func; | 277 | * |
278 | unsigned long func_size; | 278 | */ |
279 | int frame_size; | 279 | struct mips_frame_info { |
280 | int pc_offset; | 280 | void *func; |
281 | } *schedule_frame, mfinfo[64]; | 281 | unsigned long func_size; |
282 | static int mfinfo_num; | 282 | int frame_size; |
283 | 283 | int pc_offset; | |
284 | static int __init get_frame_info(struct mips_frame_info *info) | 284 | }; |
285 | |||
286 | static inline int is_ra_save_ins(union mips_instruction *ip) | ||
285 | { | 287 | { |
286 | int i; | 288 | /* sw / sd $ra, offset($sp) */ |
287 | void *func = info->func; | 289 | return (ip->i_format.opcode == sw_op || ip->i_format.opcode == sd_op) && |
288 | union mips_instruction *ip = (union mips_instruction *)func; | 290 | ip->i_format.rs == 29 && |
291 | ip->i_format.rt == 31; | ||
292 | } | ||
293 | |||
294 | static inline int is_jal_jalr_jr_ins(union mips_instruction *ip) | ||
295 | { | ||
296 | if (ip->j_format.opcode == jal_op) | ||
297 | return 1; | ||
298 | if (ip->r_format.opcode != spec_op) | ||
299 | return 0; | ||
300 | return ip->r_format.func == jalr_op || ip->r_format.func == jr_op; | ||
301 | } | ||
302 | |||
303 | static inline int is_sp_move_ins(union mips_instruction *ip) | ||
304 | { | ||
305 | /* addiu/daddiu sp,sp,-imm */ | ||
306 | if (ip->i_format.rs != 29 || ip->i_format.rt != 29) | ||
307 | return 0; | ||
308 | if (ip->i_format.opcode == addiu_op || ip->i_format.opcode == daddiu_op) | ||
309 | return 1; | ||
310 | return 0; | ||
311 | } | ||
312 | |||
313 | static int get_frame_info(struct mips_frame_info *info) | ||
314 | { | ||
315 | union mips_instruction *ip = info->func; | ||
316 | unsigned max_insns = info->func_size / sizeof(union mips_instruction); | ||
317 | unsigned i; | ||
318 | |||
289 | info->pc_offset = -1; | 319 | info->pc_offset = -1; |
290 | info->frame_size = 0; | 320 | info->frame_size = 0; |
291 | for (i = 0; i < 128; i++, ip++) { | ||
292 | /* if jal, jalr, jr, stop. */ | ||
293 | if (ip->j_format.opcode == jal_op || | ||
294 | (ip->r_format.opcode == spec_op && | ||
295 | (ip->r_format.func == jalr_op || | ||
296 | ip->r_format.func == jr_op))) | ||
297 | break; | ||
298 | 321 | ||
299 | if (info->func_size && i >= info->func_size / 4) | 322 | if (!ip) |
323 | goto err; | ||
324 | |||
325 | if (max_insns == 0) | ||
326 | max_insns = 128U; /* unknown function size */ | ||
327 | max_insns = min(128U, max_insns); | ||
328 | |||
329 | for (i = 0; i < max_insns; i++, ip++) { | ||
330 | |||
331 | if (is_jal_jalr_jr_ins(ip)) | ||
300 | break; | 332 | break; |
301 | if ( | 333 | if (!info->frame_size) { |
302 | #ifdef CONFIG_32BIT | 334 | if (is_sp_move_ins(ip)) |
303 | ip->i_format.opcode == addiu_op && | 335 | info->frame_size = - ip->i_format.simmediate; |
304 | #endif | 336 | continue; |
305 | #ifdef CONFIG_64BIT | ||
306 | ip->i_format.opcode == daddiu_op && | ||
307 | #endif | ||
308 | ip->i_format.rs == 29 && | ||
309 | ip->i_format.rt == 29) { | ||
310 | /* addiu/daddiu sp,sp,-imm */ | ||
311 | if (info->frame_size) | ||
312 | continue; | ||
313 | info->frame_size = - ip->i_format.simmediate; | ||
314 | } | 337 | } |
315 | 338 | if (info->pc_offset == -1 && is_ra_save_ins(ip)) { | |
316 | if ( | ||
317 | #ifdef CONFIG_32BIT | ||
318 | ip->i_format.opcode == sw_op && | ||
319 | #endif | ||
320 | #ifdef CONFIG_64BIT | ||
321 | ip->i_format.opcode == sd_op && | ||
322 | #endif | ||
323 | ip->i_format.rs == 29 && | ||
324 | ip->i_format.rt == 31) { | ||
325 | /* sw / sd $ra, offset($sp) */ | ||
326 | if (info->pc_offset != -1) | ||
327 | continue; | ||
328 | info->pc_offset = | 339 | info->pc_offset = |
329 | ip->i_format.simmediate / sizeof(long); | 340 | ip->i_format.simmediate / sizeof(long); |
341 | break; | ||
330 | } | 342 | } |
331 | } | 343 | } |
332 | if (info->pc_offset == -1 || info->frame_size == 0) { | 344 | if (info->frame_size && info->pc_offset >= 0) /* nested */ |
333 | if (func == schedule) | 345 | return 0; |
334 | printk("Can't analyze prologue code at %p\n", func); | 346 | if (info->pc_offset < 0) /* leaf */ |
335 | info->pc_offset = -1; | 347 | return 1; |
336 | info->frame_size = 0; | 348 | /* prologue seems boggus... */ |
337 | } | 349 | err: |
338 | 350 | return -1; | |
339 | return 0; | ||
340 | } | 351 | } |
341 | 352 | ||
353 | static struct mips_frame_info schedule_mfi __read_mostly; | ||
354 | |||
342 | static int __init frame_info_init(void) | 355 | static int __init frame_info_init(void) |
343 | { | 356 | { |
344 | int i; | 357 | unsigned long size = 0; |
345 | #ifdef CONFIG_KALLSYMS | 358 | #ifdef CONFIG_KALLSYMS |
359 | unsigned long ofs; | ||
346 | char *modname; | 360 | char *modname; |
347 | char namebuf[KSYM_NAME_LEN + 1]; | 361 | char namebuf[KSYM_NAME_LEN + 1]; |
348 | unsigned long start, size, ofs; | 362 | |
349 | extern char __sched_text_start[], __sched_text_end[]; | 363 | kallsyms_lookup((unsigned long)schedule, &size, &ofs, &modname, namebuf); |
350 | extern char __lock_text_start[], __lock_text_end[]; | ||
351 | |||
352 | start = (unsigned long)__sched_text_start; | ||
353 | for (i = 0; i < ARRAY_SIZE(mfinfo); i++) { | ||
354 | if (start == (unsigned long)schedule) | ||
355 | schedule_frame = &mfinfo[i]; | ||
356 | if (!kallsyms_lookup(start, &size, &ofs, &modname, namebuf)) | ||
357 | break; | ||
358 | mfinfo[i].func = (void *)(start + ofs); | ||
359 | mfinfo[i].func_size = size; | ||
360 | start += size - ofs; | ||
361 | if (start >= (unsigned long)__lock_text_end) | ||
362 | break; | ||
363 | if (start == (unsigned long)__sched_text_end) | ||
364 | start = (unsigned long)__lock_text_start; | ||
365 | } | ||
366 | #else | ||
367 | mfinfo[0].func = schedule; | ||
368 | schedule_frame = &mfinfo[0]; | ||
369 | #endif | 364 | #endif |
370 | for (i = 0; i < ARRAY_SIZE(mfinfo) && mfinfo[i].func; i++) | 365 | schedule_mfi.func = schedule; |
371 | get_frame_info(&mfinfo[i]); | 366 | schedule_mfi.func_size = size; |
367 | |||
368 | get_frame_info(&schedule_mfi); | ||
369 | |||
370 | /* | ||
371 | * Without schedule() frame info, result given by | ||
372 | * thread_saved_pc() and get_wchan() are not reliable. | ||
373 | */ | ||
374 | if (schedule_mfi.pc_offset < 0) | ||
375 | printk("Can't analyze schedule() prologue at %p\n", schedule); | ||
372 | 376 | ||
373 | mfinfo_num = i; | ||
374 | return 0; | 377 | return 0; |
375 | } | 378 | } |
376 | 379 | ||
@@ -386,54 +389,86 @@ unsigned long thread_saved_pc(struct task_struct *tsk) | |||
386 | /* New born processes are a special case */ | 389 | /* New born processes are a special case */ |
387 | if (t->reg31 == (unsigned long) ret_from_fork) | 390 | if (t->reg31 == (unsigned long) ret_from_fork) |
388 | return t->reg31; | 391 | return t->reg31; |
389 | 392 | if (schedule_mfi.pc_offset < 0) | |
390 | if (!schedule_frame || schedule_frame->pc_offset < 0) | ||
391 | return 0; | 393 | return 0; |
392 | return ((unsigned long *)t->reg29)[schedule_frame->pc_offset]; | 394 | return ((unsigned long *)t->reg29)[schedule_mfi.pc_offset]; |
393 | } | 395 | } |
394 | 396 | ||
395 | /* get_wchan - a maintenance nightmare^W^Wpain in the ass ... */ | 397 | |
396 | unsigned long get_wchan(struct task_struct *p) | 398 | #ifdef CONFIG_KALLSYMS |
399 | /* used by show_backtrace() */ | ||
400 | unsigned long unwind_stack(struct task_struct *task, unsigned long *sp, | ||
401 | unsigned long pc, unsigned long ra) | ||
397 | { | 402 | { |
398 | unsigned long stack_page; | 403 | unsigned long stack_page; |
399 | unsigned long pc; | 404 | struct mips_frame_info info; |
400 | #ifdef CONFIG_KALLSYMS | 405 | char *modname; |
401 | unsigned long frame; | 406 | char namebuf[KSYM_NAME_LEN + 1]; |
402 | #endif | 407 | unsigned long size, ofs; |
408 | int leaf; | ||
403 | 409 | ||
404 | if (!p || p == current || p->state == TASK_RUNNING) | 410 | stack_page = (unsigned long)task_stack_page(task); |
411 | if (!stack_page) | ||
405 | return 0; | 412 | return 0; |
406 | 413 | ||
407 | stack_page = (unsigned long)task_stack_page(p); | 414 | if (!kallsyms_lookup(pc, &size, &ofs, &modname, namebuf)) |
408 | if (!stack_page || !mfinfo_num) | 415 | return 0; |
416 | /* | ||
417 | * Return ra if an exception occured at the first instruction | ||
418 | */ | ||
419 | if (unlikely(ofs == 0)) | ||
420 | return ra; | ||
421 | |||
422 | info.func = (void *)(pc - ofs); | ||
423 | info.func_size = ofs; /* analyze from start to ofs */ | ||
424 | leaf = get_frame_info(&info); | ||
425 | if (leaf < 0) | ||
426 | return 0; | ||
427 | |||
428 | if (*sp < stack_page || | ||
429 | *sp + info.frame_size > stack_page + THREAD_SIZE - 32) | ||
409 | return 0; | 430 | return 0; |
410 | 431 | ||
411 | pc = thread_saved_pc(p); | 432 | if (leaf) |
433 | /* | ||
434 | * For some extreme cases, get_frame_info() can | ||
435 | * consider wrongly a nested function as a leaf | ||
436 | * one. In that cases avoid to return always the | ||
437 | * same value. | ||
438 | */ | ||
439 | pc = pc != ra ? ra : 0; | ||
440 | else | ||
441 | pc = ((unsigned long *)(*sp))[info.pc_offset]; | ||
442 | |||
443 | *sp += info.frame_size; | ||
444 | return __kernel_text_address(pc) ? pc : 0; | ||
445 | } | ||
446 | #endif | ||
447 | |||
448 | /* | ||
449 | * get_wchan - a maintenance nightmare^W^Wpain in the ass ... | ||
450 | */ | ||
451 | unsigned long get_wchan(struct task_struct *task) | ||
452 | { | ||
453 | unsigned long pc = 0; | ||
412 | #ifdef CONFIG_KALLSYMS | 454 | #ifdef CONFIG_KALLSYMS |
413 | if (!in_sched_functions(pc)) | 455 | unsigned long sp; |
414 | return pc; | 456 | #endif |
415 | 457 | ||
416 | frame = p->thread.reg29 + schedule_frame->frame_size; | 458 | if (!task || task == current || task->state == TASK_RUNNING) |
417 | do { | 459 | goto out; |
418 | int i; | 460 | if (!task_stack_page(task)) |
461 | goto out; | ||
419 | 462 | ||
420 | if (frame < stack_page || frame > stack_page + THREAD_SIZE - 32) | 463 | pc = thread_saved_pc(task); |
421 | return 0; | ||
422 | 464 | ||
423 | for (i = mfinfo_num - 1; i >= 0; i--) { | 465 | #ifdef CONFIG_KALLSYMS |
424 | if (pc >= (unsigned long) mfinfo[i].func) | 466 | sp = task->thread.reg29 + schedule_mfi.frame_size; |
425 | break; | ||
426 | } | ||
427 | if (i < 0) | ||
428 | break; | ||
429 | 467 | ||
430 | pc = ((unsigned long *)frame)[mfinfo[i].pc_offset]; | 468 | while (in_sched_functions(pc)) |
431 | if (!mfinfo[i].frame_size) | 469 | pc = unwind_stack(task, &sp, pc, 0); |
432 | break; | ||
433 | frame += mfinfo[i].frame_size; | ||
434 | } while (in_sched_functions(pc)); | ||
435 | #endif | 470 | #endif |
436 | 471 | ||
472 | out: | ||
437 | return pc; | 473 | return pc; |
438 | } | 474 | } |
439 | |||