/* * addr2line support. Conceptually, we fork addr2line to look up info * about addresses, for stack traces. (The main program uses this in * conjunction with elf_stab; see its description in user-*.c.) * * In order to handle multiple processes wanting to potentially talk to * addr2line, we do a dance similar to the one used for tracing. * There is an AF_LOCAL SOCK_DGRAM socket, shared by all processes, * set up early in startup, to a helper process. This is a2lh. It is * created in a2l_startup and never closed. a2l is a per-process * AF_LOCAL SOCK_STREAM connection to the helper process. If a2l is * nonnegative, a2pid is the process ID it goes with; if not, a2pid is * meaningless. The helper process deals with forking addr2line * instances as necessary. * * If a2l is -1, here just hasn't yet been any connection set up and we * need to set it up. If it's less than -1, though, either we've * tried to set it up and failed or there was some error which caused * us to abort the link to the lookup process. * * Because we run in a chroot, and addr2line needs to be a * host-architecture executable, the Makefile arranges for * a2l_host_bin[] to be the relevant addr2line binary. * * For a new client: * * - Client generates an AF_LOCAL SCOCK_STREAM socketpair. * - Client sends one half of the socketpair, attached with * SCM_RIGHTS to one octet of data, which must be 0x00, on the * shared SOCK_DGRAM connection. * - Client generates a set-path message on the stream socket. * * Over the SOCK_STREAM connection, the protocol looks like: * * To change its path, the client sends the path as * "p%d.%s",strlen(path),path. The helper does not respond. * * To request an address lookup, the client sends the address as * "l%x.",addr. The helper looks up function name, filename, and * line number; it returns them as * "r%d.%s%d.%s%d.",strlen(fn),fn,strlen(file),file,lno. If * addr2line fails, its error string (two question marks) is * mapped to a zero-length string for reply purposes. If * addr2line doesn't run at all, or if the client hasn't declared * a path, the function and file name strings are zero-length and * the line number is zero. */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern const char *__progname; #include "tracemgr.h" #include "scm-rights.h" #include "a2l.h" typedef struct client CLIENT; typedef struct worker WORKER; typedef struct exe EXE; typedef struct rqip RQIP; struct rqip { RQIP *link; uint32_t addr; CLIENT *c; } ; struct exe { int plen; char *path; WORKER *worker; int nclients; } ; struct worker { EXE *exe; pid_t kid; AIO_OQ oq; int tpipe; int fpipe; RQIP *rqq; RQIP **rqqt; ES obuf; int ll[2]; int nlines; int tpid; int fpid; } ; struct client { CLIENT *flink; CLIENT *blink; int fd; EXE *exe; AIO_OQ oq; int iphase; int len; uint32_t addr; ES ib; int pid; int bid; RQIP *rq; } ; static const char a2l_host_bin[] = "/z-a2l"; static int a2lh; static int hid; static int a2l; static pid_t a2pid; static CLIENT *clients; static AVL *exes_path; static int rtest_client(void *cv) { return(!((CLIENT *)cv)->rq); } static int wtest_client(void *cv) { return(aio_oq_nonempty(&((CLIENT *)cv)->oq)); } static unsigned int digitval(unsigned char ch) { switch (ch) { case '0': return(0); break; case '1': return(1); break; case '2': return(2); break; case '3': return(3); break; case '4': return(4); break; case '5': return(5); break; case '6': return(6); break; case '7': return(7); break; case '8': return(8); break; case '9': return(9); break; case 'a': case 'A': return(10); break; case 'b': case 'B': return(11); break; case 'c': case 'C': return(12); break; case 'd': case 'D': return(13); break; case 'e': case 'E': return(14); break; case 'f': case 'F': return(15); break; } return(16); } static int std_wr(AIO_OQ *oq, int fd) { int nw; nw = aio_oq_writev(oq,fd,-1); if (nw < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return(0); break; } return(1); } aio_oq_dropdata(oq,nw); return(0); } static void set_exe(CLIENT *c, EXE *e) { if (c->exe) c->exe->nclients --; c->exe = e; if (e) e->nclients ++; } static void client_set_exe(CLIENT *c, int len, char *path) { EXE ec; EXE *e; char *npath; struct stat stb; set_exe(c,0); if (len < 1) return; ec.plen = len; ec.path = path; e = avl_find(exes_path,&ec); if (! e) { npath = malloc(len+1); bcopy(path,npath,len); npath[len] = '\0'; if (stat(npath,&stb) < 0) return; e = malloc(sizeof(EXE)); e->plen = len; e->path = npath; e->worker = 0; e->nclients = 0; if (avl_insert(exes_path,e)) abort(); } set_exe(c,e); } static void lookup_result(CLIENT *c, int fl, const char *fn, int pl, const char *path, int lno) { aio_oq_queue_printf(&c->oq,"r%d.%.*s%d.%.*s%d.",fl,fl,fn?fn:"",pl,pl,path?path:"",lno); } static void lookup_failed(CLIENT *c) { lookup_result(c,0,0,0,0,0); } static void rq_done(RQIP *rq) { WORKER *w; if (rq->c->rq != rq) abort(); w = rq->c->exe->worker; if (rq != w->rqq) abort(); w->rqq = rq->link; if (! w->rqq) w->rqqt = &w->rqq; rq->c->rq = 0; free(rq); } static void worker_done(WORKER *w) { if (w->rqq) abort(); if (w->exe->worker != w) abort(); w->exe->worker = 0; aio_oq_flush(&w->oq); close(w->tpipe); close(w->fpipe); es_done(&w->obuf); aio_remove_poll(w->tpid); aio_remove_poll(w->fpid); free(w); } static void worker_failed(WORKER *w) { RQIP *rq; while ((rq = w->rqq)) { lookup_failed(rq->c); rq_done(rq); } worker_done(w); } static int wtest_w(void *wv) { return(aio_oq_nonempty(&((WORKER *)wv)->oq)); } static void wr_w(void *wv) { WORKER *w; w = wv; if (std_wr(&w->oq,w->tpipe)) worker_failed(w); } static void reset_obuf(WORKER *w) { es_clear(&w->obuf); w->nlines = 0; w->ll[0] = 0; w->ll[1] = 0; } static int worker_result(WORKER *w) { const unsigned char *bp0; const unsigned char *bp; int fl; const unsigned char *fxn; int pl; const unsigned char *path; int lno; int i; int j; int dv; const unsigned char *colon; bp0 = es_buf(&w->obuf); bp = bp0; fl = w->ll[0]; fxn = bp; if ((fl == 2) && (fxn[0] == '?') && (fxn[1] == '?')) { fl = 0; fxn = 0; } bp += w->ll[0] + 1; colon = memchr(bp,':',w->ll[1]); if (! colon) { fprintf(stderr,"%s: no colon in second line of addr2line output:\n\t%.*s\n\t%.*s\n",__progname,w->ll[0],bp0,w->ll[1],bp); worker_failed(w); return(1); } pl = colon - bp; path = bp; if ((pl == 2) && (path[0] == '?') && (path[1] == '?')) { pl = 0; path = 0; } i = (colon + 1) - bp; bp += i; j = w->ll[1] - i; lno = 0; for (i=0;i 10) { fprintf(stderr,"%s: bad digit in line number of addr2line output:\n\t%.*s\n\t%.*s\n",__progname,w->ll[0],bp0,w->ll[1],bp); worker_failed(w); return(1); } lno = (lno * 10) + dv; } lookup_result(w->rqq->c,fl,fxn,pl,path,lno); rq_done(w->rqq); return(0); } static int worker_in(WORKER *w, unsigned char c) { if (! w->rqq) { fprintf(stderr,"%s: worker generated unexpected output\n",__progname); worker_failed(w); return(1); } es_append_1(&w->obuf,c); if (c == '\n') w->nlines ++; else w->ll[w->nlines] ++; if (w->nlines > 1) { if (worker_result(w)) return(1); reset_obuf(w); } return(0); } static void rd_w(void *wv) { WORKER *w; unsigned char rbuf[64]; int nr; int i; w = wv; nr = read(w->fpipe,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; } fprintf(stderr,"%s: read from worker: %s\n",__progname,strerror(errno)); worker_failed(w); return; } if (nr == 0) { fprintf(stderr,"%s: read from worker got EOF\n",__progname); worker_failed(w); return; } for (i=0;i { if (pipe(&ip[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); break <"fail">; } do <"fail"> { if (pipe(&op[0]) < 0) { fprintf(stderr,"%s: pipe: %s\n",__progname,strerror(errno)); break <"fail">; } do <"fail"> { fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s: can't fork worker: %s\n",__progname,strerror(errno)); break <"fail">; } if (kid == 0) { readlink("a2l worker",0,0); // for syscall traces close(xp[0]); fcntl(xp[1],F_SETFD,1); close(ip[1]); dup2(ip[0],0); close(ip[0]); close(op[0]); dup2(op[1],1); close(op[1]); execl(&a2l_host_bin[0],&a2l_host_bin[0],"-f","-C","-e",exe->path,(char *)0); e = errno; write(xp[1],&e,sizeof(int)); _exit(1); } close(xp[1]); xp[1] = -1; close(ip[0]); ip[0] = -1; close(op[1]); op[1] = -1; n = recv(xp[0],&e,sizeof(int),MSG_WAITALL); if (n < 0) { fprintf(stderr,"%s: exec recv error: %s\n",__progname,strerror(errno)); break <"fail">; } if (n == sizeof(int)) { fprintf(stderr,"%s: exec %s: %s\n",__progname,&a2l_host_bin[0],strerror(e)); break <"fail">; } if (n != 0) { fprintf(stderr,"%s: exec protocol error: got %d, expected %d or 0\n",__progname,n,(int)sizeof(int)); break <"fail">; } close(xp[0]); w = malloc(sizeof(WORKER)); w->exe = exe; w->kid = kid; aio_oq_init(&w->oq); w->tpipe = ip[1]; w->fpipe = op[0]; w->rqq = 0; w->rqqt = &w->rqq; es_init(&w->obuf); reset_obuf(w); w->tpid = aio_add_poll(w->tpipe,&aio_rwtest_never,&wtest_w,0,&wr_w,w); w->fpid = aio_add_poll(w->fpipe,&aio_rwtest_always,&aio_rwtest_never,&rd_w,0,w); exe->worker = w; return(0); } while (0); if (op[0] >= 0) close(op[0]); if (op[1] >= 0) close(op[1]); } while (0); if (ip[0] >= 0) close(ip[0]); if (ip[1] >= 0) close(ip[1]); } while (0); if (xp[0] >= 0) close(xp[0]); if (xp[1] >= 0) close(xp[1]); return(1); } static void start_lookup(CLIENT *c) { RQIP *rq; if (! c->exe) { lookup_failed(c); return; } if (! c->exe->worker) { if (start_worker(c->exe)) { lookup_failed(c); return; } } rq = malloc(sizeof(RQIP)); rq->addr = c->addr; rq->c = c; rq->link = 0; *c->exe->worker->rqqt = rq; c->exe->worker->rqqt = &rq->link; aio_oq_queue_printf(&c->exe->worker->oq,"%llx\n",(unsigned long long int)c->addr); if (c->rq) abort(); c->rq = rq; } static void client_in(CLIENT *cl, unsigned char ch) { int dv; switch (cl->iphase) { case 0: switch (ch) { case 'p': cl->iphase = 1; cl->len = 0; break; case 'l': cl->iphase = 11; cl->addr = 0; break; default: fprintf(stderr,"%s: %s: bad first client octet %02x\n",__progname,__func__,ch); exit(1); break; } break; case 1: dv = digitval(ch); if (dv < 10) { cl->len = (cl->len * 10) + dv; return; } if (ch != '.') { fprintf(stderr,"%s: %s: bad client digit %02x\n",__progname,__func__,ch); exit(1); } cl->iphase = 2; es_clear(&cl->ib); if (cl->len == 0) { client_set_exe(cl,0,0); cl->iphase = 0; } break; case 2: es_append_1(&cl->ib,ch); cl->len --; if (cl->len < 1) { client_set_exe(cl,es_len(&cl->ib),es_buf(&cl->ib)); cl->iphase = 0; } break; case 11: dv = digitval(ch); if (dv < 16) { cl->addr = (cl->addr * 16) + dv; return; } if (ch != '.') { fprintf(stderr,"%s: %s: bad client digit %02x\n",__progname,__func__,ch); exit(1); } start_lookup(cl); cl->iphase = 0; break; } } static void client_free(void *cv) { free(cv); } static void client_close(CLIENT *c) { if (c->pid == AIO_NOID) return; if (c->flink) c->flink->blink = c->blink; if (c->blink) c->blink->flink = c->flink; else clients = c->flink; close(c->fd); c->fd = -1; set_exe(c,0); aio_oq_flush(&c->oq); es_done(&c->ib); aio_remove_poll(c->pid); c->pid = AIO_NOID; if (c->bid != AIO_NOID) abort(); aio_call_once(&client_free,c); } static void rd_client(void *cv) { CLIENT *c; unsigned char rbuf[512]; int nr; int i; c = cv; nr = read(c->fd,&rbuf[0],sizeof(rbuf)); if (nr < 0) { switch (errno) { case EINTR: case EWOULDBLOCK: return; break; } fprintf(stderr,"%s: %s: client read: %s\n",__progname,__func__,strerror(errno)); exit(1); } if (nr == 0) { client_close(c); return; } for (i=0;ioq,c->fd)) client_close(c); } static void rd_h(void *arg __attribute__((__unused__))) { struct msghdr mh; struct cmsghdr cmh; struct iovec iov; unsigned char ctlbuf[CMSPACE(sizeof(int))]; unsigned char body; int r; int fd; CLIENT *c; iov.iov_base = &body; iov.iov_len = 1; mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = &ctlbuf[0]; mh.msg_controllen = CMSPACE(sizeof(int)); mh.msg_flags = 0; r = recvmsg(a2lh,&mh,0); if (r < 0) { if (errno == ECONNRESET) { // This seems to be what we get once all clients are gone // EOF would make more sense to me exit(0); } else { fprintf(stderr,"%s: %s: shared a2l recvmsg: %s\n",__progname,__func__,strerror(errno)); exit(1); } } if (r != 1) { fprintf(stderr,"%s: %s: data length %d is unexpected\n",__progname,__func__,r); return; } if (body != 0) { fprintf(stderr,"%s: %s: wrong body octet 0x%02x\n",__progname,__func__,body); return; } if (mh.msg_controllen < sizeof(struct cmsghdr)) { fprintf(stderr,"%s: %s: control length %d too small\n",__progname,__func__,(int)mh.msg_controllen); return; } bcopy(&ctlbuf[0],&cmh,sizeof(struct cmsghdr)); if (cmh.cmsg_level != SOL_SOCKET) { fprintf(stderr,"%s: %s: control level (%d) isn't SOL_SOCKET (%d)\n",__progname,__func__,(int)cmh.cmsg_level,(int)SOL_SOCKET); return; } if (cmh.cmsg_type != SCM_RIGHTS) { fprintf(stderr,"%s: %s: control type (%d) isn't SCM_RIGHTS (%d)\n",__progname,__func__,(int)cmh.cmsg_type,(int)SCM_RIGHTS); return; } if (cmh.cmsg_len < CMLEN(sizeof(int))) { fprintf(stderr,"%s: %s: cmsg length (%d) too small\n",__progname,__func__,(int)cmh.cmsg_len); return; } bcopy(&ctlbuf[CMSKIP(&cmh)],&fd,sizeof(int)); fcntl(fd,F_SETFL,fcntl(fd,F_GETFL,0)|O_NONBLOCK); fcntl(fd,F_SETFD,1); c = malloc(sizeof(CLIENT)); c->fd = fd; c->exe = 0; aio_oq_init(&c->oq); c->iphase = 0; es_init(&c->ib); c->flink = clients; c->blink = 0; if (clients) clients->blink = c; clients = c; c->pid = aio_add_poll(fd,&rtest_client,&wtest_client,&rd_client,&wr_client,c); c->bid = AIO_NOID; } static int cmp_exe_path(void *av, void *bv) { EXE *a; EXE *b; a = av; b = bv; if (a->plen != b->plen) return(a->plen-b->plen); return(strncmp(a->path,b->path,a->plen)); } static const AVLOPS ao_exe_path = { .compare = &cmp_exe_path, .flags = AVL_F_NODUPS }; static void helper_main(void) { aio_poll_init(); clients = 0; exes_path = avl_new(&ao_exe_path); hid = aio_add_poll(a2lh,&aio_rwtest_always,&aio_rwtest_never,&rd_h,0,0); aio_event_loop(); } static int nbwrite(int fd, const void *data, int len) { const unsigned char *dp; int nw; struct pollfd pfd; dp = data; while (len > 0) { nw = write(fd,dp,len); if (nw < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: pfd.fd = fd; pfd.events = POLLOUT | POLLWRNORM; poll(&pfd,1,INFTIM); continue; break; default: return(1); break; } } dp += nw; len -= nw; } return(0); } void a2l_startup(void) { int p[2]; pid_t kid; if (socketpair(AF_LOCAL,SOCK_DGRAM,0,&p[0]) < 0) { fprintf(stderr,"%s; %s: socketpair: %s\n",__progname,__func__,strerror(errno)); exit(1); } fflush(0); fflush(0); kid = fork(); if (kid < 0) { fprintf(stderr,"%s; %s: fork: %s\n",__progname,__func__,strerror(errno)); exit(1); } if (kid == 0) { readlink("a2l_startup intermediate",0,0); // for syscall traces kid = fork(); if (kid) exit(0); readlink("a2l_startup real",0,0); // for syscall traces close(p[0]); a2lh = p[1]; fcntl(a2lh,F_SETFD,1); trcmgr_drop(); helper_main(); _exit(1); } close(p[1]); a2lh = p[0]; a2l = -1; } void a2l_this_proc(void) { pid_t ourpid; int p[2]; struct cmsghdr cmh; struct msghdr mh; struct iovec iov; unsigned char payload; unsigned char cbuf[CMSPACE(sizeof(int))]; ourpid = getpid(); if ((a2l >= 0) && (ourpid != a2pid)) { close(a2l); a2l = -1; } if (a2l < 0) { if (a2l < -1) return; a2pid = ourpid; if (socketpair(AF_LOCAL,SOCK_STREAM,0,&p[0]) < 0) { fprintf(stderr,"%s: socketpair: %s\n",__progname,strerror(errno)); exit(1); } payload = 0; iov.iov_base = &payload; iov.iov_len = 1; cmh.cmsg_len = CMLEN(sizeof(int)); cmh.cmsg_level = SOL_SOCKET; cmh.cmsg_type = SCM_RIGHTS; bcopy(&cmh,&cbuf[0],sizeof(struct cmsghdr)); bcopy(&p[1],&cbuf[CMSKIP(&cmh)],sizeof(int)); mh.msg_name = 0; mh.msg_namelen = 0; mh.msg_iov = &iov; mh.msg_iovlen = 1; mh.msg_control = (void *)&cbuf[0]; mh.msg_controllen = CMLEN(sizeof(int)); mh.msg_flags = 0; if (sendmsg(a2lh,&mh,0) < 0) { fprintf(stderr,"%s: a2l setup sendmsg: %s\n",__progname,strerror(errno)); close(p[0]); close(p[1]); a2l = -2; return; } close(p[1]); a2l = p[0]; fcntl(a2l,F_SETFL,fcntl(a2l,F_GETFL,0)|O_NONBLOCK); } } void a2l_set_path(const char *path) { char *h; int hl; int pl; if (a2l < 0) return; pl = path ? strlen(path) : 0; hl = asprintf(&h,"p%d.",pl); if (nbwrite(a2l,h,hl) || nbwrite(a2l,path,pl)) { close(a2l); a2l = -3; } free(h); } A2L_LNO a2l_lookup(uint32_t pc) { char *m; int ml; unsigned char rbuf[8]; int rfill; int rptr; int num; static ES fxn = ES_STATIC_INIT; static ES path = ES_STATIC_INIT; int stage; struct pollfd pfd; unsigned char c; int dv; if (a2l < 0) return((A2L_LNO){.good=0}); ml = asprintf(&m,"l%llx.",(unsigned long long int)pc); if (nbwrite(a2l,m,ml)) { free(m); close(a2l); a2l = -4; return((A2L_LNO){.good=0}); } rfill = 0; rptr = 0; es_clear(&fxn); es_clear(&path); do <"fail"> { stage = 0; while (1) { if (rptr >= rfill) { rfill = read(a2l,&rbuf[0],sizeof(rbuf)); if (rfill < 0) { switch (errno) { case EINTR: continue; break; case EWOULDBLOCK: pfd.fd = a2l; pfd.events = POLLIN | POLLRDNORM; poll(&pfd,1,INFTIM); continue; break; default: fprintf(stderr,"%s: read error from a2l: %s\n",__progname,strerror(errno)); break <"fail">; } } if (rfill == 0) { fprintf(stderr,"%s: read EOF from a2l\n",__progname); break <"fail">; } rptr = 0; } c = rbuf[rptr++]; switch (stage) { case 0: if (c != 'r') { fprintf(stderr,"%s: response key char is %02x, not r\n",__progname,c); break <"fail">; } stage = 1; num = 0; break; case 1: case 3: case 5: if (c == '.') { stage ++; switch (stage) { case 2: case 4: if (num < 1) stage ++; break; case 6: return((A2L_LNO){ .good=1, .flen=es_len(&fxn), .fxn=es_buf(&fxn), .plen=es_len(&path), .path=es_buf(&path), .line=num }); break; default: abort(); break; } break; } dv = digitval(c); if (dv < 10) { num = (num * 10) + (c - '0'); break; } fprintf(stderr,"%s: bad digit %02x (state %d) from a2l\n",__progname,c,stage); break <"fail">; case 2: es_append_1(&fxn,c); num --; if (num < 1) stage ++; break; case 4: es_append_1(&path,c); num --; if (num < 1) stage ++; break; default: abort(); break; } } } while (0); close(a2l); a2l = -5; return((A2L_LNO){.good=0}); }