1 /* prof.c - initialize sampling profiler
2 load user executable in ptrace mode
3 at exec catchpoint, set breakpoint at main
4 add LD_PRELOAD = libprofile
5 run ld-linux to map libraries, stop at main
6 add ptr to main as argument, start profileStart
18 #include <sys/ptrace.h>
20 #include <sys/types.h>
24 #define unlikely(x) __builtin_expect(!!(x), 0)
25 #define dprintf(s...) do { if( unlikely(debug!=0) ) fprintf(s); } while(0)
46 #define SYM2MAX 1024*256
51 #define PROF_OUTPUT "--"
52 #define PROF_TMR_TYPE 0
53 #define PROF_TMR_USEC 10000
55 static int debug = DEBUG;
56 static int prof_debug = PROF_DEBUG;
57 static int prof_type = PROF_TMR_TYPE;
58 static int prof_tmrval = PROF_TMR_USEC;
59 static long prof_stkptr = 0;
60 static char *prof_output = NULL;
61 static const int max_signals = sizeof(signal_name)/sizeof(signal_name[0]);
62 static const int max_sysreqs = sizeof(sysreq_name)/sizeof(sysreq_name[0]);
64 #define LIBPROFILE_NAME "libprofile.so"
65 #define LIBPROFILE_PATH LIB LIBPROFILE_NAME
68 #define LD_LINUX_SO LIB "ld-linux.so.2"
71 #define LD_LINUX_SO LIB "ld-linux-x86-64.so.2"
74 char *libprofile_path = NULL;
97 if( (tp=malloc(sizeof(*tp))) != NULL ) {
99 tp->m = sizeof(tp->sym)/sizeof(tp->sym[0]);
105 symCmpr(const void *a, const void *b)
107 const char *anm = ((tSym const*)a)->nm;
108 const char *bnm = ((tSym const*)b)->nm;
109 return strcmp(anm,bnm);
113 newSym(tSymTbl **ptp, const char *nm, unsigned long adr)
121 m = m < SYM2MAX ? m*2 : m+SYMINCR;
122 l = sizeof(*tp)-sizeof(tp->sym)+m*sizeof(tp->sym[0]);
123 rp = (tSymTbl*) realloc(tp,l);
124 if( rp == NULL ) return -1;
136 delSymTbl(tSymTbl **ptp)
140 tSym *sp = &tp->sym[0];
141 for( i=tp->n; --i>=0; ++sp ) {
142 if( (nm=sp->nm) != NULL )
150 isSymNm(char *cp, char *bp)
152 if( !*cp || *cp=='\n' ) return 0;
154 while( (ch=*cp++) != 0 ) {
155 if( ch == '\n' ) break;
157 if( ch == '#' || ch == '$' || ch == '.' ) return 0;
164 mapObj(char *fn,tSymTbl **ptp,const char *typs)
166 char nmLine[512], nmCmd[512], *lp, *rp;
169 if( access(fn,R_OK) < 0 ) return -1;
170 strcpy(&nmCmd[0],"smap 2> /dev/null < ");
171 strcat(&nmCmd[0],fn);
172 if( (fp=popen(&nmCmd[0],"r")) == NULL ) return -1;
173 while((lp=fgets(&nmLine[0],sizeof nmLine,fp)) != NULL ) {
174 int t = nmLine[nmType];
175 if( t != 'T' && t != 't' && (!typs || !strchr(typs,t)) ) continue;
176 if( isSymNm(&nmLine[nmName],&nmCmd[0]) == 0 ) continue;
177 adr = strtoul(&nmLine[nmAddr],&rp,16);
178 if( (rp-&nmLine[nmAddr]) != nmType-1 ) continue;
179 if( newSym(ptp,&nmCmd[0],adr) != 0 ) break;
182 return lp != NULL ? -1 : 0;
187 mapFile(char *fn,const char *typs)
189 tSymTbl *tp = newSymTbl();
190 if( tp && mapObj(fn,&tp,typs) != 0 )
193 qsort(&tp->sym,tp->n,sizeof(tp->sym[0]),symCmpr);
198 symLkup(tSymTbl *tp,const char *nm)
202 tSym *sp = &tp->sym[0];
203 l = n = -1; r = tp->n;
207 n = strcmp(mp->nm,nm);
212 return n == 0 ? mp : NULL;
216 findSym(tSymTbl *tp,char *nm,tSym **psp)
218 tSym *sp = symLkup(tp,nm);
220 fprintf(stderr,"cant find symbol '%s'\n",nm);
227 execute_program(char *fn,char **argv)
236 if (ptrace(PTRACE_TRACEME, 0, 1, 0)<0) {
237 perror("PTRACE_TRACEME");
240 setenv("LD_PRELOAD",libprofile_path,1);
241 //setenv("LD_DEBUG","files",1);
243 fprintf(stderr, "Can't execute `%s': %s\n", fn, strerror(errno));
249 peek_text(pid_t pid,long addr)
251 return ptrace(PTRACE_PEEKTEXT,pid,addr,0);
255 poke_text(pid_t pid,long addr,long value)
257 ptrace(PTRACE_POKETEXT,pid,addr,value);
263 struct user_regs_struct regs;
264 ptrace(PTRACE_GETREGS,pid,0,®s);
269 set_eip(pid_t pid,long addr)
271 struct user_regs_struct regs;
272 ptrace(PTRACE_GETREGS,pid,0,®s);
274 ptrace(PTRACE_SETREGS,pid,0,®s);
278 get_sysreq(pid_t pid)
280 struct user_regs_struct regs;
281 ptrace(PTRACE_GETREGS,pid,0,®s);
282 return regs.orig_eax;
286 get_sysret(pid_t pid)
288 struct user_regs_struct regs;
289 ptrace(PTRACE_GETREGS,pid,0,®s);
296 struct user_regs_struct regs;
297 ptrace(PTRACE_GETREGS,pid,0,®s);
302 peek_stk(pid_t pid,long addr)
304 return ptrace(PTRACE_PEEKDATA,pid,addr,0);
308 poke_stk(pid_t pid,long addr,long value)
310 ptrace(PTRACE_POKEDATA,pid,addr,value);
317 peek_text(pid_t pid,long addr)
319 long ret = ptrace(PTRACE_PEEKTEXT,pid,addr,0);
324 poke_text(pid_t pid,long addr,long value)
326 ptrace(PTRACE_POKETEXT,pid,addr,value);
332 struct user_regs_struct regs;
333 ptrace(PTRACE_GETREGS,pid,0,®s);
338 set_eip(pid_t pid,long addr)
340 struct user_regs_struct regs;
341 ptrace(PTRACE_GETREGS,pid,0,®s);
343 ptrace(PTRACE_SETREGS,pid,0,®s);
347 get_sysreq(pid_t pid)
349 struct user_regs_struct regs;
350 ptrace(PTRACE_GETREGS,pid,0,®s);
351 return regs.orig_rax;
355 get_sysret(pid_t pid)
357 struct user_regs_struct regs;
358 ptrace(PTRACE_GETREGS,pid,0,®s);
365 struct user_regs_struct regs;
366 ptrace(PTRACE_GETREGS,pid,0,®s);
371 peek_stk(pid_t pid,long addr)
374 long ret = ptrace(PTRACE_PEEKDATA,pid,addr,0);
375 if( ret == -1 && errno )
376 dprintf(stderr,"peek_stk failed @ "_LX" = %d:%s",
377 addr, errno, strerror(errno));
382 poke_stk(pid_t pid,long addr,long value)
384 ptrace(PTRACE_POKEDATA,pid,addr,value);
389 pstrcmp(pid_t pid,long addr,char *cp)
391 int n, v; long w, adr;
392 adr = addr & ~(sizeof(w)-1);
393 n = addr & (sizeof(w)-1);
394 w = peek_stk(pid,adr);
399 if( (v=(w&0xff)-*cp) != 0 ) break;
400 if( *cp == 0 ) break;
402 w = peek_stk(pid,adr);
414 pstrcpy(pid_t pid,long addr,char *cp)
417 unsigned char *bp = (unsigned char *)cp;
418 long adr = addr & ~(sizeof(w)-1);
419 int n = 8*(addr & (sizeof(w)-1));
420 w = peek_stk(pid,adr);
425 if( (n+=8) >= sizeof(w)*8 ) {
428 w = peek_stk(pid,adr);
437 /* instruction = int 3 */
438 #define BREAKPOINT_VALUE 0xcc
439 #define BREAKPOINT_LENGTH 1
440 #define DECR_PC_AFTER_BREAK 1
443 enable_breakpoint(breakpoint *bp)
445 if( bp->enabled == 0 ) {
446 int shft; long word, mask;
448 unsigned long addr = bp->addr & ~(sizeof(long)-1);
449 word = peek_text(pid,addr);
450 bp->orig_value = word;
451 shft = 8 * (bp->addr&(sizeof(long)-1));
452 mask = 0xffUL << shft;
453 word = (word & ~mask) | ((unsigned long)BREAKPOINT_VALUE << shft);
454 poke_text(pid,addr,word);
456 dprintf(stderr,"set bp "_LX"\n",bp->addr);
461 disable_breakpoint(breakpoint *bp)
463 if( bp->enabled != 0 ) {
464 int shft; long word, mask;
466 unsigned long addr = bp->addr & ~(sizeof(long)-1);
467 word = peek_text(pid,addr);
468 shft = 8 * (bp->addr&(sizeof(long)-1));
469 mask = 0xffUL << shft;
470 word = (word & ~mask) | (bp->orig_value & mask);
471 poke_text(pid,addr,word);
476 breakpoint *newBrkpt(pid_t pid,unsigned long addr)
479 bp = (breakpoint *)malloc(sizeof(*bp));
480 memset(bp,0,sizeof(*bp));
487 get_map_fwa(pid_t pid,char *pfx1, char *pfx2)
489 FILE *fp; int n, len1, len2;
490 long fwa, lwa1, pgoff;
493 char f1, f2, f3, f4, path[512], bfr[512];
494 sprintf(&bfr[0],"/proc/%d/maps",(int)pid);
495 if( (fp=fopen(&bfr[0],"r")) == NULL ) {
496 perror(&bfr[0]); exit(1);
498 len1 = pfx1 ? strlen(pfx1) : 0;
499 len2 = pfx2 ? strlen(pfx2) : 0;
501 if( fgets(&bfr[0],sizeof(bfr),fp) == NULL ) {
502 fprintf(stderr,"cant find process %d '%s' maps\n",(int)pid,pfx1);
505 n = sscanf(&bfr[0],"%lx-%lx %c%c%c%c %lx %x:%x %lu %s",
506 &fwa,&lwa1,&f1,&f2,&f3,&f4,&pgoff,&major,&minor,&inode,&path[0]);
507 if( n != 11 ) continue;
508 // if( f3 != 'x' ) continue;
509 if( !pfx1 && !pfx2 ) break;
510 if( pfx1 && strncmp(pfx1,&path[0],len1) == 0 ) break;
511 if( pfx2 && strncmp(pfx2,&path[0],len2) == 0 ) break;
517 #define CHILD(v) (peek_stk(pid,(long)(v)))
518 #define ACHILD(v) CHILD(v)
521 #define CHILD(v) (peek_stk(pid,(long)(v)&0x7fffffffffffL))
522 #define ACHILD(v) (CHILD(v)&0x7fffffffffffL)
524 #define VCHILD(v) ((void*)CHILD(v))
527 set_parm(pid_t pid,tSymTbl *tprof,long ofs,char *nm,long val)
530 dprintf(stderr,"set %s = %#lx\n",nm,val);
531 findSym(tprof,nm,&var);
533 poke_stk(pid,var->adr,val);
539 fprintf(stderr,"usage: %s [-o path] [-d] [-e] [-p libpath] [-012] [-u #] cmd args...\n",av0);
540 fprintf(stderr," -o profile output pathname, -=stdout, --=stderr\n");
541 fprintf(stderr," -d debug output enabled\n");
542 fprintf(stderr," -e child debug output enabled\n");
543 fprintf(stderr," -p specify path for libprofile.so\n");
544 fprintf(stderr," -0 usr+sys cpu timer intervals (sigprof)\n");
545 fprintf(stderr," -1 usr only cpu timer intervals (sigvtalrm)\n");
546 fprintf(stderr," -2 real time timer intervals (sigalrm)\n");
547 fprintf(stderr," -u profile timer interval in usecs\n");
550 long run_to_breakpoint(pid_t ppid, int init_syscall)
552 int in_syscall = init_syscall;
554 ptrace(PTRACE_SYSCALL,ppid,0,0);
557 pid_t pid = wait(&status); /* wait for ptrace status */
558 if( ppid != pid ) continue;
559 if( WIFSTOPPED(status) ) {
560 int stopsig = WSTOPSIG(status);
561 if( stopsig == SIGTRAP ) {
562 long eip = get_eip(pid);
563 int sysnum = get_sysreq(pid);
564 if( in_syscall < 0 ) { /* starting syscall */
565 if( sysnum < 0 ) { /* hit breakpoint */
566 dprintf(stderr,"hit bp at 0x"_LX"\n",eip);
569 dprintf(stderr,"syscall %3d "_LX" - %s",sysnum,eip,
570 sysnum >= max_sysreqs ? "unknown" : sysreq_name[sysnum]);
573 else { /* finishing syscall */
574 long ret = get_sysret(pid);
575 if( in_syscall != sysnum )
576 dprintf(stderr,"(%d!)",sysnum);
577 dprintf(stderr," = %#lx\n",ret);
578 if( init_syscall >= 0 )
583 /* resume execution */
584 ptrace(PTRACE_SYSCALL,pid,0,0);
586 else { /* unexepected status returned */
587 if( WIFEXITED(status) ) {
588 int exit_code = WEXITSTATUS(status);
589 fprintf(stderr,"exit %d\n",exit_code);
591 else if( WIFSIGNALED(status) ) {
592 int signum = WTERMSIG(status);
593 fprintf(stderr,"killed %d - %s\n", signum,
594 signum < 0 || signum >= max_signals ?
595 "unknown" : signal_name[signum]);
598 fprintf(stderr,"unknown status %d\n",status);
606 setup_profile(pid_t pid,long r_debug,long bp_main)
611 long absOffset = get_map_fwa(pid, LIB "ld-", "/usr" LIB "ld-");
612 struct r_debug *rdebug = (struct r_debug *)(r_debug+absOffset);
613 /* look through maps - find libprofile image, save absOffset */
614 dprintf(stderr,"rdebug "_LX"\n",(long)rdebug);
615 for( so=VCHILD(&rdebug->r_map); so!=NULL; so=VCHILD(&so->l_next) ) {
616 absOffset = CHILD(&so->l_addr);
617 if( absOffset == 0 ) continue;
618 if( pstrcmp(pid,ACHILD(&so->l_name),libprofile_path) == 0 ) break;
621 fprintf(stderr,"%s did not preload\n",libprofile_path);
624 dprintf(stderr,"libprofile "_LX"\n",absOffset);
625 /* map libprofile with nm */
626 tprof = mapFile(libprofile_path,"bd");
627 prof_stkptr = get_esp(pid);
628 dprintf(stderr,"orig esp "_LX"\n",prof_stkptr);
629 set_parm(pid,tprof,absOffset,"prof_stkptr",prof_stkptr);
630 set_parm(pid,tprof,absOffset,"prof_debug",prof_debug);
631 set_parm(pid,tprof,absOffset,"prof_type",prof_type);
632 set_parm(pid,tprof,absOffset,"prof_tmrval",prof_tmrval);
633 findSym(tprof,"prof_output",&sym);
634 sym->adr += absOffset;
635 pstrcpy(pid,sym->adr,&prof_output[0]);
636 findSym(tprof,"profileMain",&sym);
637 sym->adr += absOffset;
638 dprintf(stderr,"set eip = %#lx\n",sym->adr);
639 /* resume execution in profileStart */
640 set_eip(pid,sym->adr);
643 int main(int ac, char **av)
646 tSymTbl *tpath, *tld_linux;
647 tSym *sym_main, *sym__r_debug;
648 int pid, status, execve;
649 breakpoint *main_bp = NULL;
650 char *lib_path = NULL, *lib_argv = NULL;
653 /* decode cmdline params */
654 while( ac > 1 && *(cp=av[1]) == '-' ) {
656 case 'd': /* enable debug output */
659 case 'e': /* enable child debug output */
662 case '0': /* use sigprof */
665 case '1': /* use sigvtalrm */
668 case '2': /* use sigalrm */
671 case 'u': /* use timer interval usec */
672 prof_tmrval = strtoul(av[2],NULL,0);
675 case 'o': /* specify output path */
679 case 'p': /* specify libprofile */
684 fprintf(stderr,"unknown parameter - '%s'\n",cp);
697 if( lib_argv != NULL ) {
698 if( access(&lib_argv[0],R_OK) == 0 )
699 lib_path = strdup(lib_argv);
701 if( lib_path == NULL ) {
702 cp = "./" LIBPROFILE_NAME;
703 if( access(cp,R_OK) == 0 )
704 lib_path = strdup(cp);
706 if( lib_path == NULL && (cp=getenv("HOME")) != NULL ) {
707 char libpath[PATH_MAX];
708 snprintf(&libpath[0],sizeof(libpath),"%s/bin/%s",
710 if( access(&libpath[0],R_OK) == 0 )
711 lib_path = strdup(&libpath[0]);
713 if( lib_path == NULL ) {
714 if( access(cp=LIBPROFILE_PATH,R_OK) == 0 )
715 lib_path = strdup(cp);
717 if( lib_path == NULL ) {
718 fprintf(stderr,"cant find %s\n",LIBPROFILE_NAME);
721 libprofile_path = lib_path;
723 if( prof_output == NULL )
724 prof_output = PROF_OUTPUT;
726 /* read nm output for executable */
727 tpath = mapFile(av[1],0);
728 if( tpath == NULL ) {
729 fprintf(stderr,"cant map %s\n",av[1]);
732 /* read nm output for ld-linux */
733 tld_linux = mapFile(LD_LINUX_SO,"BD");
734 if( tld_linux == NULL ) {
735 fprintf(stderr,"cant map %s\n",LD_LINUX_SO);
739 /* lookup main, _r_debug */
740 findSym(tpath,"main",&sym_main);
741 findSym(tld_linux,"_r_debug",&sym__r_debug);
742 /* fork child with executable */
743 pid = execute_program(av[1],&av[1]);
749 execve = sizeof(sysreq_name)/sizeof(sysreq_name[0]);
750 while( --execve >= 0 && strcmp("execve",sysreq_name[execve]) );
752 fprintf(stderr,"cant find execve sysreq\n");
755 main_bp = newBrkpt(pid,sym_main->adr);
756 run_to_breakpoint(pid, execve);
757 /* add offset of main module */
758 main_bp->addr += get_map_fwa(pid,0,0);
759 enable_breakpoint(main_bp);
760 run_to_breakpoint(pid, -1);
761 /* hit breakpoint at 'main', setup profiler */
762 setup_profile(pid,sym__r_debug->adr,main_bp->addr);
763 disable_breakpoint(main_bp);
764 ptrace(PTRACE_DETACH,pid,0,0); /* turn off ptrace */
765 pid = wait(&status); /* wait for child status */
766 if( WIFEXITED(status) ) {
767 status = WEXITSTATUS(status);
768 dprintf(stderr,"exit %d\n",status);
770 else if( WIFSIGNALED(status) ) {
771 int signum = WTERMSIG(status);
772 fprintf(stderr,"killed %d - %s\n", signum,
773 signum < 0 || signum >= max_signals ?
774 "unknown" : signal_name[signum]);
777 fprintf(stderr,"unknown status %d\n",status);