Here's the call tree for a "hello world" program using glibc 2.9 on i386, statically linked:
$ ./itrace ./hellow-static | python treeify.py ./hellow-static _start ??:0 8048130 __libc_start_main glibc/csu/libc-start.c:93 8048220 _dl_aux_init glibc/elf/dl-support.c:165 80518e0 _dl_discover_osversion glibc/elf/../sysdeps/unix/sysv/linux/dl-sysdep.c:67 80521e0 __uname ??:0 806e160 __pthread_initialize_minimal glibc/csu/libc-tls.c:247 8048750 __libc_setup_tls glibc/csu/libc-tls.c:111 8048550 __sbrk glibc/misc/sbrk.c:34 80505d0 __brk glibc/misc/../sysdeps/unix/sysv/linux/i386/brk.c:36 806f9c0 __brk glibc/misc/../sysdeps/unix/sysv/linux/i386/brk.c:36 806f9c0 memcpy glibc/string/memcpy.c:33 804fc10 init_slotinfo glibc/csu/libc-tls.c:83 80486cb init_static_tls glibc/csu/libc-tls.c:100 8048708 _dl_setup_stack_chk_guard glibc/csu/../sysdeps/unix/sysv/linux/dl-osinfo.h:76 8048283 __libc_init_first glibc/csu/../sysdeps/unix/sysv/linux/init-first.c:44 80522f0 __setfpucw glibc/math/../sysdeps/i386/setfpucw.c:29 8056270 __libc_init_secure glibc/elf/enbl-secure.c:34 8052190 _dl_non_dynamic_init glibc/elf/dl-support.c:235 8051b50 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 _dl_init_paths glibc/elf/dl-load.c:630 8072ae0 _dl_important_hwcaps glibc/elf/dl-support.c:306 8051b20 __libc_malloc glibc/malloc/malloc.c:3540 804eb60 malloc_hook_ini glibc/malloc/hooks.c:35 804f360 ptmalloc_init glibc/malloc/arena.c:426 804b370 ptmalloc_init_minimal glibc/malloc/arena.c:374 804b39f __getpagesize glibc/misc/../sysdeps/unix/sysv/linux/getpagesize.c:29 8050650 __linkin_atfork glibc/nptl/../nptl/sysdeps/unix/sysv/linux/register-atfork.c:115 80513a0 next_env_entry glibc/malloc/arena.c:344 804b45d __libc_malloc glibc/malloc/malloc.c:3540 804eb60 _int_malloc glibc/malloc/malloc.c:4130 804cae0 malloc_consolidate glibc/malloc/malloc.c:4835 804afd0 malloc_init_state glibc/malloc/malloc.c:2412 804a8e0 sYSMALLOc glibc/malloc/malloc.c:2929 804d200 __default_morecore glibc/malloc/morecore.c:48 804f9c0 __sbrk glibc/misc/sbrk.c:34 80505d0 __brk glibc/misc/../sysdeps/unix/sysv/linux/i386/brk.c:36 806f9c0 __default_morecore glibc/malloc/morecore.c:48 804f9c0 __sbrk glibc/misc/sbrk.c:34 80505d0 __brk glibc/misc/../sysdeps/unix/sysv/linux/i386/brk.c:36 806f9c0 __libc_malloc glibc/malloc/malloc.c:3540 804eb60 _int_malloc glibc/malloc/malloc.c:4130 804cae0 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 dl_platform_init glibc/elf/../sysdeps/i386/dl-machine.h:273 8051c38 getenv glibc/stdlib/getenv.c:36 8056ea0 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 __init_misc glibc/misc/init-misc.c:31 8051120 rindex glibc/string/../sysdeps/i386/strrchr.S:37 8067a30 __cxa_atexit glibc/stdlib/cxa_atexit.c:34 8048ab0 __new_exitfn glibc/stdlib/cxa_atexit.c:63 8048960 __libc_csu_init glibc/csu/elf-init.c:65 80487b0 _init /build/buildd/glibc-2.7/build-tree/amd64-i386/csu/crti.S:15 80480f4 _init ??:0 8048116 frame_dummy crtstuff.c:0 80481b0 __register_frame_info ??:0 80a2b50 __register_frame_info_bases ??:0 80a2ab0 __i686.get_pc_thunk.bx ??:0 80a29f9 __do_global_ctors_aux crtstuff.c:0 80a4b10 _init /build/buildd/glibc-2.7/build-tree/amd64-i386/csu/crtn.S:10 8048120 _setjmp glibc/setjmp/../sysdeps/i386/bsd-_setjmp.S:36 8048830 main ??:0 80481f0 _IO_puts glibc/libio/ioputs.c:34 8048b10 strlen glibc/string/../sysdeps/i386/i486/strlen.S:34 804f9f0 _IO_new_file_xsputn glibc/libio/fileops.c:1288 8065870 _IO_new_file_overflow glibc/libio/fileops.c:829 80661a0 _IO_doallocbuf glibc/libio/genops.c:419 8049780 _IO_file_doallocate glibc/libio/filedoalloc.c:88 808bd50 _IO_file_stat glibc/libio/fileops.c:1225 8065b90 ___fxstat64 glibc/io/../sysdeps/unix/sysv/linux/fxstat64.c:46 804fe20 mmap glibc/misc/../sysdeps/unix/sysv/linux/i386/mmap.S:81 8051080 _IO_setb glibc/libio/genops.c:404 8049670 _IO_new_do_write glibc/libio/fileops.c:493 8065a50 _IO_default_xsputn glibc/libio/genops.c:452 8049850 _IO_acquire_lock_fct glibc/libio/libioP.h:968 8048b8d exit glibc/stdlib/exit.c:34 8048870 __libc_csu_fini glibc/csu/elf-init.c:91 8048770 _fini /build/buildd/glibc-2.7/build-tree/amd64-i386/csu/crti.S:41 80a5574 _fini ??:0 80a5587 __do_global_dtors_aux crtstuff.c:0 8048160 fini glibc/dlfcn/dlerror.c:207 80975e0 check_free glibc/dlfcn/dlerror.c:188 8097560 __deregister_frame_info ??:0 80a3c30 __deregister_frame_info_bases ??:0 80a3b20 __i686.get_pc_thunk.bx ??:0 80a29f9 _fini /build/buildd/glibc-2.7/build-tree/amd64-i386/csu/crtn.S:21 80a558c _IO_cleanup glibc/libio/genops.c:1007 8049fd0 _IO_flush_all_lockp glibc/libio/genops.c:823 8049db0 _IO_new_file_overflow glibc/libio/fileops.c:829 80661a0 _IO_new_do_write glibc/libio/fileops.c:493 8065a50 new_do_write glibc/libio/fileops.c:505 8065760 _IO_new_file_write glibc/libio/fileops.c:1261 8065a80 ?? ??:0 8 __libc_write ??:0 804ffa0 __write_nocancel ??:0 804ffaa _IO_unbuffer_write glibc/libio/genops.c:951 8049fe8 _IO_new_file_setbuf glibc/libio/fileops.c:445 8066380 _IO_default_setbuf glibc/libio/genops.c:562 80496d0 _IO_new_file_sync glibc/libio/fileops.c:891 80660d0 _IO_setb glibc/libio/genops.c:404 8049670 _exit glibc/posix/../sysdeps/unix/sysv/linux/i386/_exit.S:25 804fdc0Believe it or not, this trace is quite illuminating. Working out that these calls will happen by reading the glibc source could take a long time!
The Python script below (treeify.py) does two things:
- Firstly, it filters the trace to include only function entry points. We can just about infer function entry points from addr2line's output. We assume that when a function name (plus filename and line number) appears for the first time in the trace, the current address is the function's entry point. This doesn't work fully when inline functions are instantiated multiple times though. We could use nm to find symbol addresses, but addr2line gives us source filenames.
- Secondly, it works out the nesting of the call tree by looking at the stack pointer.
import subprocess import sys def read(): for line in sys.stdin: try: regs = [int(x, 16) for x in line.split(" ")] yield {"eip": regs[0], "esp": regs[1]} # Ignore lines interspersed with other output! except (ValueError, IndexError): pass def addr2line(iterable): proc = subprocess.Popen(["addr2line", "-e", sys.argv[1], "-f"], stdin=subprocess.PIPE, stdout=subprocess.PIPE) for regs in iterable: proc.stdin.write("%x\n" % regs["eip"]) a = proc.stdout.readline().rstrip("\n") b = proc.stdout.readline().rstrip("\n") regs["func"] = "%s %s" % (a, b) yield regs def entry_points(iterable): funcs = {} # We treat the first address we see for the function as its entry # point, and only report those entries from this point on. for regs in iterable: func = regs["func"].split(":")[0] if funcs.setdefault(func, regs["eip"]) == regs["eip"]: yield regs def add_nesting(iterable): stack = [2 ** 64] for regs in iterable: stack_pos = regs["esp"] if stack_pos < stack[-1]: stack.append(stack_pos) while stack_pos > stack[-1]: stack.pop() regs["indent"] = " " * len(stack) yield regs for x in add_nesting(entry_points(addr2line(read()))): print x["indent"], x["func"], "%x" % x["eip"]