📄 unwind.c
字号:
#include "unwind_decoder.c"/* Unwind scripts. */static inline unw_hash_index_thash (unsigned long ip){# define magic 0x9e3779b97f4a7c16 /* based on (sqrt(5)/2-1)*2^64 */ return (ip >> 4)*magic >> (64 - UNW_LOG_HASH_SIZE);}static inline longcache_match (struct unw_script *script, unsigned long ip, unsigned long pr){ read_lock(&script->lock); if (ip == script->ip && ((pr ^ script->pr_val) & script->pr_mask) == 0) /* keep the read lock... */ return 1; read_unlock(&script->lock); return 0;}static inline struct unw_script *script_lookup (struct unw_frame_info *info){ struct unw_script *script = unw.cache + info->hint; unsigned short index; unsigned long ip, pr; STAT(++unw.stat.cache.lookups); ip = info->ip; pr = info->pr; if (cache_match(script, ip, pr)) { STAT(++unw.stat.cache.hinted_hits); return script; } index = unw.hash[hash(ip)]; if (index >= UNW_CACHE_SIZE) return 0; script = unw.cache + index; while (1) { if (cache_match(script, ip, pr)) { /* update hint; no locking required as single-word writes are atomic */ STAT(++unw.stat.cache.normal_hits); unw.cache[info->prev_script].hint = script - unw.cache; return script; } if (script->coll_chain >= UNW_HASH_SIZE) return 0; script = unw.cache + script->coll_chain; STAT(++unw.stat.cache.collision_chain_traversals); }}/* * On returning, a write lock for the SCRIPT is still being held. */static inline struct unw_script *script_new (unsigned long ip){ struct unw_script *script, *prev, *tmp; unw_hash_index_t index; unsigned long flags; unsigned short head; STAT(++unw.stat.script.news); /* * Can't (easily) use cmpxchg() here because of ABA problem * that is intrinsic in cmpxchg()... */ spin_lock_irqsave(&unw.lock, flags); { head = unw.lru_head; script = unw.cache + head; unw.lru_head = script->lru_chain; } spin_unlock(&unw.lock); /* * XXX We'll deadlock here if we interrupt a thread that is * holding a read lock on script->lock. A try_write_lock() * might be mighty handy here... Alternatively, we could * disable interrupts whenever we hold a read-lock, but that * seems silly. */ write_lock(&script->lock); spin_lock(&unw.lock); { /* re-insert script at the tail of the LRU chain: */ unw.cache[unw.lru_tail].lru_chain = head; unw.lru_tail = head; /* remove the old script from the hash table (if it's there): */ if (script->ip) { index = hash(script->ip); tmp = unw.cache + unw.hash[index]; prev = 0; while (1) { if (tmp == script) { if (prev) prev->coll_chain = tmp->coll_chain; else unw.hash[index] = tmp->coll_chain; break; } else prev = tmp; if (tmp->coll_chain >= UNW_CACHE_SIZE) /* old script wasn't in the hash-table */ break; tmp = unw.cache + tmp->coll_chain; } } /* enter new script in the hash table */ index = hash(ip); script->coll_chain = unw.hash[index]; unw.hash[index] = script - unw.cache; script->ip = ip; /* set new IP while we're holding the locks */ STAT(if (script->coll_chain < UNW_CACHE_SIZE) ++unw.stat.script.collisions); } spin_unlock_irqrestore(&unw.lock, flags); script->flags = 0; script->hint = 0; script->count = 0; return script;}static voidscript_finalize (struct unw_script *script, struct unw_state_record *sr){ script->pr_mask = sr->pr_mask; script->pr_val = sr->pr_val; /* * We could down-grade our write-lock on script->lock here but * the rwlock API doesn't offer atomic lock downgrading, so * we'll just keep the write-lock and release it later when * we're done using the script. */}static inline voidscript_emit (struct unw_script *script, struct unw_insn insn){ if (script->count >= UNW_MAX_SCRIPT_LEN) { dprintk("unwind: script exceeds maximum size of %u instructions!\n", UNW_MAX_SCRIPT_LEN); return; } script->insn[script->count++] = insn;}static inline voidemit_nat_info (struct unw_state_record *sr, int i, struct unw_script *script){ struct unw_reg_info *r = sr->curr.reg + i; enum unw_insn_opcode opc; struct unw_insn insn; unsigned long val = 0; switch (r->where) { case UNW_WHERE_GR: if (r->val >= 32) { /* register got spilled to a stacked register */ opc = UNW_INSN_SETNAT_TYPE; val = UNW_NAT_REGSTK; } else /* register got spilled to a scratch register */ opc = UNW_INSN_SETNAT_MEMSTK; break; case UNW_WHERE_FR: opc = UNW_INSN_SETNAT_TYPE; val = UNW_NAT_VAL; break; case UNW_WHERE_BR: opc = UNW_INSN_SETNAT_TYPE; val = UNW_NAT_NONE; break; case UNW_WHERE_PSPREL: case UNW_WHERE_SPREL: opc = UNW_INSN_SETNAT_MEMSTK; break; default: dprintk("unwind: don't know how to emit nat info for where = %u\n", r->where); return; } insn.opc = opc; insn.dst = unw.preg_index[i]; insn.val = val; script_emit(script, insn);}static voidcompile_reg (struct unw_state_record *sr, int i, struct unw_script *script){ struct unw_reg_info *r = sr->curr.reg + i; enum unw_insn_opcode opc; unsigned long val, rval; struct unw_insn insn; long need_nat_info; if (r->where == UNW_WHERE_NONE || r->when >= sr->when_target) return; opc = UNW_INSN_MOVE; val = rval = r->val; need_nat_info = (i >= UNW_REG_R4 && i <= UNW_REG_R7); switch (r->where) { case UNW_WHERE_GR: if (rval >= 32) { opc = UNW_INSN_MOVE_STACKED; val = rval - 32; } else if (rval >= 4 && rval <= 7) { if (need_nat_info) { opc = UNW_INSN_MOVE2; need_nat_info = 0; } val = unw.preg_index[UNW_REG_R4 + (rval - 4)]; } else { opc = UNW_INSN_ADD_SP; val = -sizeof(struct pt_regs) + pt_regs_off(rval); } break; case UNW_WHERE_FR: if (rval <= 5) val = unw.preg_index[UNW_REG_F2 + (rval - 1)]; else if (rval >= 16 && rval <= 31) val = unw.preg_index[UNW_REG_F16 + (rval - 16)]; else { opc = UNW_INSN_ADD_SP; val = -sizeof(struct pt_regs); if (rval <= 9) val += struct_offset(struct pt_regs, f6) + 16*(rval - 6); else dprintk("unwind: kernel may not touch f%lu\n", rval); } break; case UNW_WHERE_BR: if (rval >= 1 && rval <= 5) val = unw.preg_index[UNW_REG_B1 + (rval - 1)]; else { opc = UNW_INSN_ADD_SP; val = -sizeof(struct pt_regs); if (rval == 0) val += struct_offset(struct pt_regs, b0); else if (rval == 6) val += struct_offset(struct pt_regs, b6); else val += struct_offset(struct pt_regs, b7); } break; case UNW_WHERE_SPREL: opc = UNW_INSN_ADD_SP; break; case UNW_WHERE_PSPREL: opc = UNW_INSN_ADD_PSP; break; default: dprintk("unwind: register %u has unexpected `where' value of %u\n", i, r->where); break; } insn.opc = opc; insn.dst = unw.preg_index[i]; insn.val = val; script_emit(script, insn); if (need_nat_info) emit_nat_info(sr, i, script); if (i == UNW_REG_PSP) { /* * info->psp must contain the _value_ of the previous * sp, not it's save location. We get this by * dereferencing the value we just stored in * info->psp: */ insn.opc = UNW_INSN_LOAD; insn.dst = insn.val = unw.preg_index[UNW_REG_PSP]; script_emit(script, insn); }}static inline struct unw_table_entry *lookup (struct unw_table *table, unsigned long rel_ip){ struct unw_table_entry *e = 0; unsigned long lo, hi, mid; /* do a binary search for right entry: */ for (lo = 0, hi = table->length; lo < hi; ) { mid = (lo + hi) / 2; e = &table->array[mid]; if (rel_ip < e->start_offset) hi = mid; else if (rel_ip >= e->end_offset) lo = mid + 1; else break; } return e;}/* * Build an unwind script that unwinds from state OLD_STATE to the * entrypoint of the function that called OLD_STATE. */static inline struct unw_script *build_script (struct unw_frame_info *info){ struct unw_reg_state *rs, *next; struct unw_table_entry *e = 0; struct unw_script *script = 0; unsigned long ip = info->ip; struct unw_state_record sr; struct unw_table *table; struct unw_reg_info *r; struct unw_insn insn; u8 *dp, *desc_end; u64 hdr; int i; STAT(unsigned long start, parse_start;) STAT(++unw.stat.script.builds; start = ia64_get_itc()); /* build state record */ memset(&sr, 0, sizeof(sr)); for (r = sr.curr.reg; r < sr.curr.reg + UNW_NUM_REGS; ++r) r->when = UNW_WHEN_NEVER; sr.pr_val = info->pr; script = script_new(ip); if (!script) { dprintk("unwind: failed to create unwind script\n"); STAT(unw.stat.script.build_time += ia64_get_itc() - start); return 0; } unw.cache[info->prev_script].hint = script - unw.cache; /* search the kernels and the modules' unwind tables for IP: */ STAT(parse_start = ia64_get_itc()); for (table = unw.tables; table; table = table->next) { if (ip >= table->start && ip < table->end) { e = lookup(table, ip - table->segment_base); break; } } if (!e) { /* no info, return default unwinder (leaf proc, no mem stack, no saved regs) */ dprintk("unwind: no unwind info for ip=0x%lx (prev ip=0x%lx)\n", ip, unw.cache[info->prev_script].ip); sr.curr.reg[UNW_REG_RP].where = UNW_WHERE_BR; sr.curr.reg[UNW_REG_RP].when = -1; sr.curr.reg[UNW_REG_RP].val = 0; compile_reg(&sr, UNW_REG_RP, script); script_finalize(script, &sr); STAT(unw.stat.script.parse_time += ia64_get_itc() - parse_start); STAT(unw.stat.script.build_time += ia64_get_itc() - start); return script; } sr.when_target = (3*((ip & ~0xfUL) - (table->segment_base + e->start_offset))/16 + (ip & 0xfUL)); hdr = *(u64 *) (table->segment_base + e->info_offset); dp = (u8 *) (table->segment_base + e->info_offset + 8); desc_end = dp + 8*UNW_LENGTH(hdr); while (!sr.done && dp < desc_end) dp = unw_decode(dp, sr.in_body, &sr); if (sr.when_target > sr.epilogue_start) { /* * sp has been restored and all values on the memory stack below * psp also have been restored. */ sr.curr.reg[UNW_REG_PSP].where = UNW_WHERE_NONE; sr.curr.reg[UNW_REG_PSP].val = 0; for (r = sr.curr.reg; r < sr.curr.reg + UNW_NUM_REGS; ++r) if ((r->where == UNW_WHERE_PSPREL && r->val <= 0x10) || r->where == UNW_WHERE_SPREL) r->where = UNW_WHERE_NONE; } script->flags = sr.flags; /* * If RP did't get saved, generate entry for the return link * register. */ if (sr.curr.reg[UNW_REG_RP].when >= sr.when_target) { sr.curr.reg[UNW_REG_RP].where = UNW_WHERE_BR; sr.curr.reg[UNW_REG_RP].when = -1; sr.curr.reg[UNW_REG_RP].val = sr.return_link_reg; }#if UNW_DEBUG printk("unwind: state record for func 0x%lx, t=%u:\n", table->segment_base + e->start_offset, sr.when_target); for (r = sr.curr.reg; r < sr.curr.reg + UNW_NUM_REGS; ++r) { if (r->where != UNW_WHERE_NONE || r->when != UNW_WHEN_NEVER) { printk(" %s <- ", unw.preg_name[r - sr.curr.reg]); switch (r->where) { case UNW_WHERE_GR: printk("r%lu", r->val); break; case UNW_WHERE_FR: printk("f%lu", r->val); break; case UNW_WHERE_BR: printk("b%lu", r->val); break; case UNW_WHERE_SPREL: printk("[sp+0x%lx]", r->val); break; case UNW_WHERE_PSPREL: printk("[psp+0x%lx]", r->val); break; case UNW_WHERE_NONE: printk("%s+0x%lx", unw.preg_name[r - sr.curr.reg], r->val); break; default: printk("BADWHERE(%d)", r->where); break; } printk("\t\t%d\n", r->when); } }#endif STAT(unw.stat.script.parse_time += ia64_get_itc() - parse_start); /* translate state record into unwinder instructions: */ /* * First, set psp if we're dealing with a fixed-size frame; * subsequent instructions may depend on this value. */ if (sr.when_target > sr.curr.reg[UNW_REG_PSP].when && (sr.curr.reg[UNW_REG_PSP].where == UNW_WHERE_NONE) && sr.curr.reg[UNW_REG_PSP].val != 0) { /* new psp is sp plus frame size */ insn.opc = UNW_INSN_ADD; insn.dst = struct_offset(struct unw_frame_info, psp)/8; insn.val = sr.curr.reg[UNW_REG_PSP].val; /* frame size */ script_emit(script, insn); } /* determine where the primary UNaT is: */ if (sr.when_target < sr.curr.reg[UNW_REG_PRI_UNAT_GR].when) i = UNW_REG_PRI_UNAT_MEM; else if (sr.when_target < sr.curr.reg[UNW_REG_PRI_UNAT_MEM].when) i = UNW_REG_PRI_UNAT_GR; else if (sr.curr.reg[UNW_REG_PRI_UNAT_MEM].when > sr.curr.reg[UNW_REG_PRI_UNAT_GR].when) i = UNW_REG_PRI_UNAT_MEM; else i = UNW_REG_PRI_UNAT_GR; compile_reg(&sr, i, script); for (i = UNW_REG_BSP; i < UNW_NUM_REGS; ++i) compile_reg(&sr, i, script); /* free labelled register states & stack: */ STAT(parse_start = ia64_get_itc()); for (rs = sr.reg_state_list; rs; rs = next) { next = rs->next; free_reg_state(rs); } while (sr.stack) pop(&sr); STAT(unw.stat.script.parse_time += ia64_get_itc() - parse_start); script_finalize(script, &sr); STAT(unw.stat.script.build_time += ia64_get_itc() - start); return script;}/* * Apply the unwinding actions represented by OPS and update SR to * reflect the state that existed upon entry to the function that this * unwinder represents. */static inline voidrun_script (struct unw_script *script, struct unw_frame_info *state){ struct unw_insn *ip, *limit, next_insn; unsigned long opc, dst, val, off; unsigned long *s = (unsigned long *) state; STAT(unsigned long start;) STAT(++unw.stat.script.runs; start = ia64_get_itc()); state->flags = script->flags; ip = script->insn; limit = script->insn + script->count; next_insn = *ip; while (ip++ < limit) { opc = next_insn.opc; dst = next_insn.dst; val = next_insn.val; next_insn = *ip; redo: switch (opc) { case UNW_INSN_ADD: s[dst] += val; break; case UNW_INSN_MOVE2: if (!s[val]) goto lazy_init; s[dst+1] = s[val+1]; s[dst] = s[val]; break; case UNW_INSN_MOVE:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -