|  | /* | 
|  | * Dwarf call frame unwinding. | 
|  | * | 
|  | * The call frame unwinding values are encoded using a state machine | 
|  | * like the pc<->line mapping, but it's a different machine. | 
|  | * The expressions to generate the old values are similar in function to the | 
|  | * ``dwarf expressions'' used for locations in the code, but of course not | 
|  | * the same encoding. | 
|  | */ | 
|  | #include <u.h> | 
|  | #include <libc.h> | 
|  | #include <bio.h> | 
|  | #include "elf.h" | 
|  | #include "dwarf.h" | 
|  |  | 
|  | #define trace 0 | 
|  |  | 
|  | typedef struct State State; | 
|  | struct State | 
|  | { | 
|  | ulong loc; | 
|  | ulong endloc; | 
|  | ulong iquantum; | 
|  | ulong dquantum; | 
|  | char *augmentation; | 
|  | int version; | 
|  | ulong rareg; | 
|  | DwarfBuf init; | 
|  | DwarfExpr *cfa; | 
|  | DwarfExpr *ra; | 
|  | DwarfExpr *r; | 
|  | DwarfExpr *initr; | 
|  | int nr; | 
|  | DwarfExpr **stack; | 
|  | int nstack; | 
|  | }; | 
|  |  | 
|  | static int findfde(Dwarf*, ulong, State*, DwarfBuf*); | 
|  | static int dexec(DwarfBuf*, State*, int); | 
|  |  | 
|  | int | 
|  | dwarfunwind(Dwarf *d, ulong pc, DwarfExpr *cfa, DwarfExpr *ra, DwarfExpr *r, int nr) | 
|  | { | 
|  | int i, ret; | 
|  | DwarfBuf fde, b; | 
|  | DwarfExpr *initr; | 
|  | State s; | 
|  |  | 
|  | initr = mallocz(nr*sizeof(initr[0]), 1); | 
|  | if(initr == 0) | 
|  | return -1; | 
|  |  | 
|  | memset(&s, 0, sizeof s); | 
|  | s.loc = 0; | 
|  | s.cfa = cfa; | 
|  | s.ra = ra; | 
|  | s.r = r; | 
|  | s.nr = nr; | 
|  |  | 
|  | if(findfde(d, pc, &s, &fde) < 0){ | 
|  | free(initr); | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | memset(r, 0, nr*sizeof(r[0])); | 
|  | for(i=0; i<nr; i++) | 
|  | r[i].type = RuleSame; | 
|  | if(trace) fprint(2, "s.init %p-%p, fde %p-%p\n", s.init.p, s.init.ep, fde.p, fde.ep); | 
|  | b = s.init; | 
|  | if(dexec(&b, &s, 0) < 0) | 
|  | goto err; | 
|  |  | 
|  | s.initr = initr; | 
|  | memmove(initr, r, nr*sizeof(initr[0])); | 
|  |  | 
|  | if(trace) fprint(2, "s.loc 0x%lux pc 0x%lux\n", s.loc, pc); | 
|  | while(s.loc < pc){ | 
|  | if(trace) fprint(2, "s.loc 0x%lux pc 0x%lux\n", s.loc, pc); | 
|  | if(dexec(&fde, &s, 1) < 0) | 
|  | goto err; | 
|  | } | 
|  | *ra = s.r[s.rareg]; | 
|  |  | 
|  | ret = 0; | 
|  | goto out; | 
|  |  | 
|  | err: | 
|  | ret = -1; | 
|  | out: | 
|  | free(initr); | 
|  | for(i=0; i<s.nstack; i++) | 
|  | free(s.stack[i]); | 
|  | free(s.stack); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * XXX This turns out to be much more expensive than the actual | 
|  | * running of the machine in dexec.  It probably makes sense to | 
|  | * cache the last 10 or so fde's we've found, since stack traces | 
|  | * will keep asking for the same info over and over. | 
|  | */ | 
|  | static int | 
|  | findfde(Dwarf *d, ulong pc, State *s, DwarfBuf *fde) | 
|  | { | 
|  | static int nbad; | 
|  | char *aug; | 
|  | uchar *next; | 
|  | int i, vers; | 
|  | ulong len, id, base, size; | 
|  | DwarfBuf b; | 
|  |  | 
|  | if(d->frame.data == nil){ | 
|  | werrstr("no frame debugging information"); | 
|  | return -1; | 
|  | } | 
|  | b.d = d; | 
|  | b.p = d->frame.data; | 
|  | b.ep = b.p + d->frame.len; | 
|  | b.addrsize = d->addrsize; | 
|  | if(b.addrsize == 0) | 
|  | b.addrsize = 4;	/* where should i find this? */ | 
|  |  | 
|  | for(; b.p < b.ep; b.p = next){ | 
|  | if((i = (b.p - d->frame.data) % b.addrsize)) | 
|  | b.p += b.addrsize - i; | 
|  | len = dwarfget4(&b); | 
|  | if(len > b.ep-b.p){ | 
|  | werrstr("bad length in cie/fde header"); | 
|  | return -1; | 
|  | } | 
|  | next = b.p+len; | 
|  | id = dwarfget4(&b); | 
|  | if(id == 0xFFFFFFFF){	/* CIE */ | 
|  | vers = dwarfget1(&b); | 
|  | if(vers != 1 && vers != 2 && vers != 3){ | 
|  | if(++nbad == 1) | 
|  | fprint(2, "unknown cie version %d (wanted 1-3)\n", vers); | 
|  | continue; | 
|  | } | 
|  | aug = dwarfgetstring(&b); | 
|  | if(aug && *aug){ | 
|  | if(++nbad == 1) | 
|  | fprint(2, "unknown augmentation: %s\n", aug); | 
|  | continue; | 
|  | } | 
|  | s->iquantum = dwarfget128(&b); | 
|  | s->dquantum = dwarfget128s(&b); | 
|  | s->rareg = dwarfget128(&b); | 
|  | if(s->rareg > s->nr){ | 
|  | werrstr("return address is register %d but only have %d registers", | 
|  | s->rareg, s->nr); | 
|  | return -1; | 
|  | } | 
|  | s->init.p = b.p; | 
|  | s->init.ep = next; | 
|  | }else{	/* FDE */ | 
|  | base = dwarfgetaddr(&b); | 
|  | size = dwarfgetaddr(&b); | 
|  | fde->p = b.p; | 
|  | fde->ep = next; | 
|  | s->loc = base; | 
|  | s->endloc = base+size; | 
|  | if(base <= pc && pc < base+size) | 
|  | return 0; | 
|  | } | 
|  | } | 
|  | werrstr("cannot find call frame information for pc 0x%lux", pc); | 
|  | return -1; | 
|  |  | 
|  | } | 
|  |  | 
|  | static int | 
|  | checkreg(State *s, long r) | 
|  | { | 
|  | if(r < 0 || r >= s->nr){ | 
|  | werrstr("bad register number 0x%lux", r); | 
|  | return -1; | 
|  | } | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | static int | 
|  | dexec(DwarfBuf *b, State *s, int locstop) | 
|  | { | 
|  | int c; | 
|  | long arg1, arg2; | 
|  | DwarfExpr *e, **p; | 
|  |  | 
|  | for(;;){ | 
|  | if(b->p == b->ep){ | 
|  | if(s->initr) | 
|  | s->loc = s->endloc; | 
|  | return 0; | 
|  | } | 
|  | c = dwarfget1(b); | 
|  | if(b->p == nil){ | 
|  | werrstr("ran out of instructions during cfa program"); | 
|  | if(trace) fprint(2, "%r\n"); | 
|  | return -1; | 
|  | } | 
|  | if(trace) fprint(2, "+ loc=0x%lux op 0x%ux ", s->loc, c); | 
|  | switch(c>>6){ | 
|  | case 1:	/* advance location */ | 
|  | arg1 = c&0x3F; | 
|  | advance: | 
|  | if(trace) fprint(2, "loc += %ld\n", arg1*s->iquantum); | 
|  | s->loc += arg1 * s->iquantum; | 
|  | if(locstop) | 
|  | return 0; | 
|  | continue; | 
|  |  | 
|  | case 2:	/* offset rule */ | 
|  | arg1 = c&0x3F; | 
|  | arg2 = dwarfget128(b); | 
|  | offset: | 
|  | if(trace) fprint(2, "r%ld += %ld\n", arg1, arg2*s->dquantum); | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->r[arg1].type = RuleCfaOffset; | 
|  | s->r[arg1].offset = arg2 * s->dquantum; | 
|  | continue; | 
|  |  | 
|  | case 3:	/* restore initial setting */ | 
|  | arg1 = c&0x3F; | 
|  | restore: | 
|  | if(trace) fprint(2, "r%ld = init\n", arg1); | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->r[arg1] = s->initr[arg1]; | 
|  | continue; | 
|  | } | 
|  |  | 
|  | switch(c){ | 
|  | case 0:	/* nop */ | 
|  | if(trace) fprint(2, "nop\n"); | 
|  | continue; | 
|  |  | 
|  | case 0x01:	/* set location */ | 
|  | s->loc = dwarfgetaddr(b); | 
|  | if(trace) fprint(2, "loc = 0x%lux\n", s->loc); | 
|  | if(locstop) | 
|  | return 0; | 
|  | continue; | 
|  |  | 
|  | case 0x02:	/* advance loc1 */ | 
|  | arg1 = dwarfget1(b); | 
|  | goto advance; | 
|  |  | 
|  | case 0x03:	/* advance loc2 */ | 
|  | arg1 = dwarfget2(b); | 
|  | goto advance; | 
|  |  | 
|  | case 0x04:	/* advance loc4 */ | 
|  | arg1 = dwarfget4(b); | 
|  | goto advance; | 
|  |  | 
|  | case 0x05:	/* offset extended */ | 
|  | arg1 = dwarfget128(b); | 
|  | arg2 = dwarfget128(b); | 
|  | goto offset; | 
|  |  | 
|  | case 0x06:	/* restore extended */ | 
|  | arg1 = dwarfget128(b); | 
|  | goto restore; | 
|  |  | 
|  | case 0x07:	/* undefined */ | 
|  | arg1 = dwarfget128(b); | 
|  | if(trace) fprint(2, "r%ld = undef\n", arg1); | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->r[arg1].type = RuleUndef; | 
|  | continue; | 
|  |  | 
|  | case 0x08:	/* same value */ | 
|  | arg1 = dwarfget128(b); | 
|  | if(trace) fprint(2, "r%ld = same\n", arg1); | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->r[arg1].type = RuleSame; | 
|  | continue; | 
|  |  | 
|  | case 0x09:	/* register */ | 
|  | arg1 = dwarfget128(b); | 
|  | arg2 = dwarfget128(b); | 
|  | if(trace) fprint(2, "r%ld = r%ld\n", arg1, arg2); | 
|  | if(checkreg(s, arg1) < 0 || checkreg(s, arg2) < 0) | 
|  | return -1; | 
|  | s->r[arg1].type = RuleRegister; | 
|  | s->r[arg1].reg = arg2; | 
|  | continue; | 
|  |  | 
|  | case 0x0A:	/* remember state */ | 
|  | e = malloc(s->nr*sizeof(e[0])); | 
|  | if(trace) fprint(2, "push\n"); | 
|  | if(e == nil) | 
|  | return -1; | 
|  | p = realloc(s->stack, (s->nstack+1)*sizeof(s->stack[0])); | 
|  | if(p == nil){ | 
|  | free(e); | 
|  | return -1; | 
|  | } | 
|  | s->stack[s->nstack++] = e; | 
|  | memmove(e, s->r, s->nr*sizeof(e[0])); | 
|  | continue; | 
|  |  | 
|  | case 0x0B:	/* restore state */ | 
|  | if(trace) fprint(2, "pop\n"); | 
|  | if(s->nstack == 0){ | 
|  | werrstr("restore state underflow"); | 
|  | return -1; | 
|  | } | 
|  | e = s->stack[s->nstack-1]; | 
|  | memmove(s->r, e, s->nr*sizeof(e[0])); | 
|  | p = realloc(s->stack, (s->nstack-1)*sizeof(s->stack[0])); | 
|  | if(p == nil) | 
|  | return -1; | 
|  | free(e); | 
|  | s->nstack--; | 
|  | continue; | 
|  |  | 
|  | case 0x0C:	/* def cfa */ | 
|  | arg1 = dwarfget128(b); | 
|  | arg2 = dwarfget128(b); | 
|  | defcfa: | 
|  | if(trace) fprint(2, "cfa %ld(r%ld)\n", arg2, arg1); | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->cfa->type = RuleRegOff; | 
|  | s->cfa->reg = arg1; | 
|  | s->cfa->offset = arg2; | 
|  | continue; | 
|  |  | 
|  | case 0x0D:	/* def cfa register */ | 
|  | arg1 = dwarfget128(b); | 
|  | if(trace) fprint(2, "cfa reg r%ld\n", arg1); | 
|  | if(s->cfa->type != RuleRegOff){ | 
|  | werrstr("change CFA register but CFA not in register+offset form"); | 
|  | return -1; | 
|  | } | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->cfa->reg = arg1; | 
|  | continue; | 
|  |  | 
|  | case 0x0E:	/* def cfa offset */ | 
|  | arg1 = dwarfget128(b); | 
|  | cfaoffset: | 
|  | if(trace) fprint(2, "cfa off %ld\n", arg1); | 
|  | if(s->cfa->type != RuleRegOff){ | 
|  | werrstr("change CFA offset but CFA not in register+offset form"); | 
|  | return -1; | 
|  | } | 
|  | s->cfa->offset = arg1; | 
|  | continue; | 
|  |  | 
|  | case 0x0F:	/* def cfa expression */ | 
|  | if(trace) fprint(2, "cfa expr\n"); | 
|  | s->cfa->type = RuleLocation; | 
|  | s->cfa->loc.len = dwarfget128(b); | 
|  | s->cfa->loc.data = dwarfgetnref(b, s->cfa->loc.len); | 
|  | continue; | 
|  |  | 
|  | case 0x10:	/* def reg expression */ | 
|  | arg1 = dwarfget128(b); | 
|  | if(trace) fprint(2, "reg expr r%ld\n", arg1); | 
|  | if(checkreg(s, arg1) < 0) | 
|  | return -1; | 
|  | s->r[arg1].type = RuleLocation; | 
|  | s->r[arg1].loc.len = dwarfget128(b); | 
|  | s->r[arg1].loc.data = dwarfgetnref(b, s->r[arg1].loc.len); | 
|  | continue; | 
|  |  | 
|  | case 0x11:	/* offset extended */ | 
|  | arg1 = dwarfget128(b); | 
|  | arg2 = dwarfget128s(b); | 
|  | goto offset; | 
|  |  | 
|  | case 0x12:	/* cfa sf */ | 
|  | arg1 = dwarfget128(b); | 
|  | arg2 = dwarfget128s(b); | 
|  | goto defcfa; | 
|  |  | 
|  | case 0x13:	/* cfa offset sf */ | 
|  | arg1 = dwarfget128s(b); | 
|  | goto cfaoffset; | 
|  |  | 
|  | default:	/* unknown */ | 
|  | werrstr("unknown opcode 0x%ux in cfa program", c); | 
|  | return -1; | 
|  | } | 
|  | } | 
|  | /* not reached */ | 
|  | } | 
|  |  |