diff options
181 files changed, 18267 insertions, 3987 deletions
@@ -31,6 +31,10 @@ W: http://cespedes.org/ E: cespedes@debian.org D: All development up to 0.5.3 +N: Luca Clementi +E: luca.clementi@gmail.com +D: stack trace improvement + N: Joe Damato E: ice799@gmail.com D: libdl support @@ -49,6 +53,10 @@ N: Morten Eriksen E: mortene@sim.no D: Fixes +N: Thierry Fauck +E: thierry@linux.vnet.ibm.com +D: ppc64el port + N: Timothy Fesig E: slate@us.ibm.com D: s390 port @@ -73,6 +81,10 @@ D: x86-64 fixes D: libelf support D: Fixes +N: Dima Kogan +E: dima@secretsauce.net +D: Reading prototypes from DWARF information + N: Michal Ludvig E: mludvig@suse.cz D: x86-64 fixes @@ -106,6 +118,11 @@ N: Ian Wienand E: ianw@gelato.unsw.edu.au D: ia64 port +N: Peter Wu +E: lekensteyn@gmail.com +W: https://lekensteyn.nl/ +D: Fixes + N: Andrey Zonov E: zont@FreeBSD.org D: Portability fixes diff --git a/Makefile.am b/Makefile.am index 6e46949..6ab11f5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,5 +1,5 @@ # This file is part of ltrace. -# Copyright (C) 2012 Petr Machata, Red Hat Inc. +# Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. # Copyright (C) 2010 Marc Kleine-Budde, Pengutronix # Copyright (C) 2010 Zachary T Welch, CodeSourcery # @@ -25,52 +25,28 @@ SUBDIRS = \ . \ testsuite -AM_CPPFLAGS += \ - $(libelf_CFLAGS) \ - -DSYSCONFDIR=\"$(sysconfdir)\" - noinst_LTLIBRARIES = \ libltrace.la -libltrace_la_SOURCES = \ - breakpoints.c \ - debug.c \ - demangle.c \ - dict.c \ - ltrace-elf.c \ - execute_program.c \ - handle_event.c \ - libltrace.c \ - options.c \ - output.c \ - proc.c \ - read_config_file.c \ - summary.c \ - library.c \ - filter.c \ - glob.c \ - type.c \ - value.c \ - value_dict.c \ - expr.c \ - fetch.c \ - vect.c \ - param.c \ - printf.c \ - zero.c \ - lens.c \ - lens_default.c \ - lens_enum.c +libltrace_la_SOURCES = bits.c breakpoints.c debug.c demangle.c dict.c \ + ltrace-elf.c execute_program.c handle_event.c libltrace.c \ + options.c output.c proc.c read_config_file.c summary.c \ + library.c filter.c glob.c type.c value.c value_dict.c expr.c \ + fetch.c vect.c param.c printf.c zero.c lens.c lens_default.c \ + lens_enum.c memstream.c prototype.c +if HAVE_LIBDW +libltrace_la_SOURCES += dwarf_prototypes.c +endif libltrace_la_LIBADD = \ $(libelf_LIBS) \ $(liberty_LIBS) \ $(libsupcxx_LIBS) \ $(libstdcxx_LIBS) \ + $(libdw_LIBS) \ $(libunwind_LIBS) \ sysdeps/libos.la - bin_PROGRAMS = \ ltrace @@ -80,50 +56,25 @@ ltrace_SOURCES = \ ltrace_LDADD = \ libltrace.la - -noinst_HEADERS = \ - backend.h \ - breakpoint.h \ - common.h \ - debug.h \ - defs.h \ - demangle.h \ - dict.h \ - forward.h \ - ltrace-elf.h \ - ltrace.h \ - options.h \ - output.h \ - proc.h \ - read_config_file.h \ - library.h \ - filter.h \ - glob.h \ - vect.h \ - type.h \ - value.h \ - value_dict.h \ - callback.h \ - expr.h \ - fetch.h \ - vect.h \ - param.h \ - printf.h \ - zero.h \ - lens.h \ - lens_default.h \ - lens_enum.h +noinst_HEADERS = bits.h backend.h breakpoint.h common.h debug.h \ + defs.h demangle.h dict.h forward.h ltrace-elf.h ltrace.h \ + options.h output.h proc.h read_config_file.h summary.h \ + library.h filter.h glob.h vect.h type.h value.h value_dict.h \ + callback.h expr.h fetch.h vect.h param.h printf.h zero.h \ + lens.h lens_default.h lens_enum.h memstream.h prototype.h +if HAVE_LIBDW +noinst_HEADERS += dwarf_prototypes.h +endif dist_man1_MANS = ltrace.1 dist_man5_MANS = ltrace.conf.5 dist_doc_DATA = COPYING CREDITS INSTALL README TODO -dist_sysconf_DATA = \ - etc/ltrace.conf +dist_pkgdata_DATA = etc/syscalls.conf etc/libc.so.conf \ + etc/libm.so.conf etc/libacl.so.conf EXTRA_DIST = \ - ltrace.spec \ debian/changelog \ debian/compat \ debian/control \ @@ -1,4 +1,68 @@ -*-org-*- +* Version 0.8.0 +** Prototype libraries + - Each DSO can now ship an ltrace config file (called prototype + library) that ltrace will open when that DSO is loaded to process + image. See ltrace(1) for details. + + - ltrace.conf is no longer part of installation tarball. Instead, + we now ship libc.so.conf, libm.so.conf, libacl.so.conf, and + syscalls.conf. Those are now istalled to /usr/share/ltrace by + default. /etc/ltrace.conf and $HOME/.ltrace.conf are still + loaded if present, and can contain arbitrary user configuration. + + - The option -F was retrofitted to be a colon-separated list of + prototype libraries, and directories to look for prototype + libraries in. On Linux, ltrace looks into XDG_CONFIG_HOME, + XDG_CONFIG_DIRS, and /usr/share/ltrace as well. + + - Wide character strings are supported in prototypes. Use "string" + lens as usual, but use array of integers as underlying type. + libc.so.conf now contains prototypes of wide character functions. + + - Sole void function parameter such as in the following example, is + now considered obsolete: + + | int fork(void); | + + This use is still accepted, taken to mean "hide(int)", but + produces a warning, and will be removed in future. + + - Prototypes are now read from DWARF debug info, if available. This + complements the data available in config files + +** Architectural support + - MIPS and MIPSel are now handled by the same backend. + - ARMv6, ARMv7 and ARMv8 (AArch64) are supported, including full + fetch backend. ARMv8 backend doesn't support tracing of 32-bit + binaries, as currently there's no 32-bit userspace available for + ARM64 processors. + - Imagination Technologies Meta is now supported. + - PowerPC64 ELFv2 little-endian ABI is now supported including full + fetch backend. + + - On Linux, tracing of IFUNC symbols is supported. On i386, + x86_64, ppc32 with secure PLT and ppc64, IRELATIVE PLT slots are + traced as well. + +** -w output now shows full library path + The output format is similar to glibc's backtrace_symbols, e.g.: + > /bin/ls(_init+0x19be) [0x40398e] + > /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed) [0x7f50cbc3676d] + > /bin/ls(_init+0x25fd) [0x4045cd] + +* Version 0.7.3 [2013-09-15 Sun] +** Bugfixes +*** [MIPS] Fix build on mips big endian + This bug caused messages like: + | Making all in mips + | /bin/sh: line 17: cd: mips: No such file or directory +*** [MIPS] Fix SIGSEGV on mips big endian + This bug caused runtime messages like: + | [0x4000000] --- SIGSEGV (Segmentation fault) --- + | [0xffffffff] +++ killed by SIGSEGV +++ +*** Fix build with CLANG on FREEBSD + * Version 0.7.2 [2012-12-07 Fri] ** Bugfixes *** (Again) detect VDSO entry in r_debug linkmap with non-empty name @@ -286,7 +350,7 @@ * License ------------------------------------------------------------------------------- -Copyright (C) 2012 Petr Machata <pmachata@redhat.com> +Copyright (C) 2012-2014 Petr Machata <pmachata@redhat.com> This file is part of ltrace. ltrace is free software; you can redistribute it and/or modify it @@ -24,12 +24,17 @@ The following targets are currently (at least somewhat) supported. Some of them may be more or less broken in reality, it is not feasible to test each release comprehensively on each target. + aarch64-*-linux-gnu + armv6l-*-linux-gnueabi + armv7l-*-linux-gnueabihf i[4567]86-*-linux-gnu ia64-*-linux-gnu m68k-*-linux-gnu + metag-*-linux-uclibc mips-*-linux-gnu powerpc-*-linux-gnu powerpc64-*-linux-gnu + powerpc64le-*-linux-gnu s390-*-linux-gnu s390x-*-linux-gnu x86_64-*-linux-gnu @@ -41,11 +46,6 @@ current status is unknown: sparc64*-*-linux-gnu alpha*-*-linux-gnu -Support of the following systems is known to be broken and requires -fixing: - - arm-*-linux-gnueabi - Bug Reports ----------- @@ -83,7 +83,7 @@ quick one-liner), it is advisable to send an e-mail beforehand. ------------------------------------------------------------------------------- -Copyright (C) 2012 Petr Machata <pmachata@redhat.com> +Copyright (C) 2012-2014 Petr Machata <pmachata@redhat.com> Copyright (C) 1997-2009 Juan Cespedes <cespedes@debian.org> This file is part of ltrace. @@ -1,15 +1,17 @@ -*-org-*- * TODO +** Keep exit code of traced process + See https://bugzilla.redhat.com/show_bug.cgi?id=105371 for details. + ** Automatic prototype discovery: *** Use debuginfo if available Alternatively, use debuginfo to generate configure file. -*** Demangled identifiers contain partial prototypes themselves +*** Mangled identifiers contain partial prototypes themselves + They don't contain return type info, which can change the + parameter passing convention. We could use it and hope for the + best. Also they don't include the potentially present hidden this + pointer. ** Automatically update list of syscalls? -** Update /etc/ltrace.conf - In particular, we could use a config directory, where packages - would install their ltrace config scripts. The config file could - be named after SONAME, and automatically read when corresponding - library is mapped. ** More operating systems (solaris?) ** Get rid of EVENT_ARCH_SYSCALL and EVENT_ARCH_SYSRET ** Implement displaced tracing @@ -21,6 +23,10 @@ reenablement. ** Create different ltrace processes to trace different children ** Config file syntax +*** mark some symbols as exported + For PLT hits, only exported prototypes would be considered. For + symtab entry point hits, all would be. + *** named arguments This would be useful for replacing the arg1, emt2 etc. @@ -57,9 +63,29 @@ Perhaps we should hook to something after all. +*** system call error returns + + This is closely related to above. Take the following syscall + prototype: + + | long read(int,+string0,ulong); + + string0 means the same as string(array(char, zero(retval))*). But + if read returns a negative value, that signifies errno. But zero + takes this at face value and is suspicious: + + | read@SYS(3 <no return ...> + | error: maximum array length seems negative + | , "\n\003\224\003\n", 4096) = -11 + + Ideally we would do what strace does, e.g.: + + | read@SYS(3, 0x12345678, 4096) = -EAGAIN + *** errno tracking Some calls result in setting errno. Somehow mark those, and on - failure, show errno. + failure, show errno. System calls return errno as a negative + value (see the previous point). *** second conversions? This definitely calls for some general scripting. The goal is to @@ -101,6 +127,14 @@ | void func(int*, int*, +long*, long*); | | void func(in int*, in int*, out long*, out long*); | + This is useful in particular for: + + | ulong mbsrtowcs(+wstring3_t, string*, ulong, addr); | + | ulong wcsrtombs(+string3, wstring_t*, ulong, addr); | + + Where we would like to render arg2 on the way in, and arg1 on the + way out. + But sometimes we may want to see a different type on the way in and on the way out. E.g. in asprintf, what's interesting on the way in is the address, but on the way out we want to see buffer contents. @@ -129,10 +163,63 @@ according to architecture rules. Maybe this could be achieved by a per-arch config file with typedefs such as: - | typedef ulong = uint8_t | + | typedef ulong = uint8_t; | + +** Support for ARM/AARCH64 types + - ARM and AARCH64 both support half-precision floating point + - there are two different half-precision formats, IEEE 754-2008 + and "alternative". Both have 10 bits of mantissa and 5 bits of + exponent, and differ only in how exponent==0x1F is handled. In + IEEE format, we get NaN's and infinities; in alternative + format, this encodes normalized value -1S × 2¹⁶ × (1.mant) + - The Floating-Point Control Register, FPCR, controls: — The + half-precision format where applicable, FPCR.AHP bit. + - AARCH64 supports fixed-point interpretation of {,double}words + - e.g. fixed(int, X) (int interpreted as a decimal number with X + binary digits of fraction). + - AARCH64 supports 128-bit quad words in SIMD ** Some more functions in vect might be made to take const* Or even marked __attribute__((pure)). +** pretty printer support + GDB supports python pretty printers. We migh want to hook this in + and use it to format certain types. + +** support new Linux kernel features + - PTRACE_SIEZE + - /proc/PID/map_files/* (but only root seems to be able to read + this as of now) + * BUGS ** After a clone(), syscalls may be seen as sysrets in s390 (see trace.c:syscall_p()) +** leak in regex matching + >> I looked into this. Ltrace is definitely leaking it. The regex is + >> released when filter_destroy() calls filter_rule_destroy(), but those + >> are not called by anything. + > + >Ah, there we go. I just saw that we call regfree, but didn't check + >whether we actually call those. Will you roll this into your change + >set, or should I look into it? + + I'd rather you looked at it, if you don't mind. + +** unconditional follow of pthread_create + + Basically we'd like to follow pthread_create always, and fork only if -f + is given. ltrace now follows nothing, unless -f is given, and then it + follows everything. (Really it follows everything alway and detaches + from newly-created children unless -f is given.) + + The thing is, in Linux, we can choose to follow only {v,}forks by + setting PTRACE_O_TRACE{V,}FORK. We can also choose to follow all clones + by setting PTRACE_O_TRACECLONE, but that captures pthread_create as well + as fork (as all these are built on top of the underlying clone system + call), as well as direct clone calls. + + So what would make sense would be to tweak the current logic to only + detach if what happened was an actual fork, which we can tell from the + parameters of the system call. That might provide a more useful user + experience. Tracing only a single thread is problematic anyway, because + _all_ the threads will hit the breakpoints that ltrace sets anyway, so + pre-emptively tracing all threads is what you generally need. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..14bfd75 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,42 @@ +#!/bin/sh +# This file is part of ltrace. +# Copyright (C) 2010 Marc Kleine-Budde, Pengutronix +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +set -e + +# +# usage: +# +# banner <target name> +# +banner() { + echo + TG=`echo $1 | sed -e "s,/.*/,,g"` + LINE=`echo $TG |sed -e "s/./-/g"` + echo $LINE + echo $TG + echo $LINE + echo +} + +banner "autoreconf" + +mkdir -p config/autoconf config/m4 +autoreconf --force --install --verbose -Wall || exit $? + +banner "Finished" @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -27,12 +27,12 @@ #include <gelf.h> enum process_status { - ps_invalid, /* Failure. */ - ps_stop, /* Job-control stop. */ - ps_tracing_stop, - ps_sleeping, - ps_zombie, - ps_other, /* Necessary other states can be added as needed. */ + PS_INVALID, /* Failure. */ + PS_STOP, /* Job-control stop. */ + PS_TRACING_STOP, + PS_SLEEPING, + PS_ZOMBIE, + PS_OTHER, /* Necessary other states can be added as needed. */ }; /* @@ -70,7 +70,7 @@ int wait_for_proc(pid_t pid); int task_kill(pid_t pid, int sig); /* Called after PID is attached, but before it is continued. */ -void trace_set_options(struct Process *proc); +void trace_set_options(struct process *proc); /* Called after ltrace forks. Should attach the newly created child, * in whose context this function is called. */ @@ -86,7 +86,7 @@ void untrace_pid(pid_t pid); /* The back end may need to store arbitrary data to a process. This * is a place where it can initialize PROC->arch_dep. XXX this should * be dropped in favor of arhc_process_init on pmachata/libs. */ -void get_arch_dep(struct Process *proc); +void get_arch_dep(struct process *proc); /* Return current instruction pointer of PROC. * @@ -95,34 +95,30 @@ void get_arch_dep(struct Process *proc); * that would otherwise support this. Above we have a definition of * arch_addr_t. This should be converted to an integral type and * used for target addresses throughout. */ -void *get_instruction_pointer(struct Process *proc); +void *get_instruction_pointer(struct process *proc); /* Set instruction pointer of PROC to ADDR. XXX see above. */ -void set_instruction_pointer(struct Process *proc, void *addr); +void set_instruction_pointer(struct process *proc, void *addr); /* Return current stack pointer of PROC. XXX see above. */ -void *get_stack_pointer(struct Process *proc); +void *get_stack_pointer(struct process *proc); /* Find and return caller address, i.e. the address where the current * function returns. */ -void *get_return_addr(struct Process *proc, void *stack_pointer); - -/* Adjust PROC so that when the current function returns, it returns - * to ADDR. */ -void set_return_addr(struct Process *proc, void *addr); +void *get_return_addr(struct process *proc, void *stack_pointer); /* Enable breakpoint SBP in process PROC. */ -void enable_breakpoint(struct Process *proc, struct breakpoint *sbp); +void enable_breakpoint(struct process *proc, struct breakpoint *sbp); /* Disable breakpoint SBP in process PROC. */ -void disable_breakpoint(struct Process *proc, struct breakpoint *sbp); +void disable_breakpoint(struct process *proc, struct breakpoint *sbp); /* Determine whether the event that we have just seen (and that is * recorded in STATUS) was a syscall. If it was, return 1. If it was * a return from syscall, return 2. In both cases, set *SYSNUM to the * number of said syscall. If it wasn't a syscall, return 0. If * there was an error, return -1. */ -int syscall_p(struct Process *proc, int status, int *sysnum); +int syscall_p(struct process *proc, int status, int *sysnum); /* Continue execution of the process with given PID. */ void continue_process(pid_t pid); @@ -136,17 +132,21 @@ void continue_after_signal(pid_t pid, int signum); * is system call, otherwise it's return from a system call. The * callback should do whatever book-keeping is necessary and continue * the process if necessary. */ -void continue_after_syscall(struct Process *proc, int sysnum, int ret_p); +void continue_after_syscall(struct process *proc, int sysnum, int ret_p); /* Called after we hit a breakpoint SBP. Should do whatever * book-keeping is necessary and then continue the process. */ -void continue_after_breakpoint(struct Process *proc, struct breakpoint *sbp); +void continue_after_breakpoint(struct process *proc, struct breakpoint *sbp); /* Called after we received a vfork. Should do whatever book-keeping * is necessary and continue the process if necessary. N.B. right * now, with Linux/GNU the only back end, this is not necessary. I * imagine other systems may be different. */ -void continue_after_vfork(struct Process *proc); +void continue_after_vfork(struct process *proc); + +/* Called after the process exec's. Should do whatever book-keeping + * is necessary and then continue the process. */ +void continue_after_exec(struct process *proc); /* Called when trace_me or primary trace_pid fail. This may plug in * any platform-specific knowledge of why it could be so. */ @@ -171,14 +171,15 @@ void os_ltrace_exiting(void); /* Should copy COUNT bytes from address ADDR of process PROC to local * buffer BUF. */ -size_t umovebytes (struct Process *proc, void *addr, void *buf, size_t count); +size_t umovebytes(struct process *proc, arch_addr_t addr, + void *buf, size_t count); /* Find out an address of symbol SYM in process PROC, and return. * Returning NULL delays breakpoint insertion and enables heaps of * arch-specific black magic that we should clean up some day. * * XXX the same points as for get_instruction_pointer apply. */ -void *sym2addr(struct Process *proc, struct library_symbol *sym); +void *sym2addr(struct process *proc, struct library_symbol *sym); /* Obtain address of PLT entry corresponding to relocation RELA in * file LTE. This is NDX-th PLT entry in the file. @@ -189,7 +190,7 @@ GElf_Addr arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela); /* Called at some point after we have attached to PROC. This callback * should insert an introspection breakpoint for handling dynamic * linker library loads. */ -int linkmap_init(struct Process *proc, arch_addr_t dyn_addr); +int linkmap_init(struct process *proc, arch_addr_t dyn_addr); /* This should produce and return the next event of one of the traced * processes. The returned pointer will not be freed by the core and @@ -198,14 +199,14 @@ int linkmap_init(struct Process *proc, arch_addr_t dyn_addr); struct Event *next_event(void); /* Called when process PROC was removed. */ -void process_removed(struct Process *proc); +void process_removed(struct process *proc); /* This should extract entry point address and interpreter (dynamic * linker) bias if possible. Returns 0 if there were no errors, -1 * otherwise. Sets *ENTRYP and *INTERP_BIASP to non-zero values if * the corresponding value is known, or zero otherwise; this is not * done for pointers that are NULL. */ -int process_get_entry(struct Process *proc, +int process_get_entry(struct process *proc, arch_addr_t *entryp, arch_addr_t *interp_biasp); @@ -227,21 +228,49 @@ int process_get_entry(struct Process *proc, int arch_elf_init(struct ltelf *lte, struct library *lib); void arch_elf_destroy(struct ltelf *lte); +/* The following callbacks have to be implemented in OS backend if + * os.h defines OS_HAVE_BREAKPOINT_DATA. Those are used to init, + * destroy, and clone SBP->os. os_breakpoint_init and + * os_breakpoint_clone return 0 on success or a negative value on + * failure. */ +int os_breakpoint_init(struct process *proc, struct breakpoint *sbp); +void os_breakpoint_destroy(struct breakpoint *sbp); +int os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp); + /* The following callbacks have to be implemented in backend if arch.h * defines ARCH_HAVE_BREAKPOINT_DATA. Those are used to init, * destroy, and clone SBP->arch. arch_breakpoint_init and * arch_breakpoint_clone return 0 on success or a negative value on * failure. */ -int arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp); +int arch_breakpoint_init(struct process *proc, struct breakpoint *sbp); void arch_breakpoint_destroy(struct breakpoint *sbp); int arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp); +/* The following callbacks have to be implemented in OS backend if + * os.h defines OS_HAVE_LIBRARY_DATA. Those are used to init, destroy + * and clone LIB->os. os_library_init and os_library_clone return 0 + * on success or a negative value on failure. */ +int os_library_init(struct library *lib); +void os_library_destroy(struct library *lib); +int os_library_clone(struct library *retp, struct library *lib); + /* The following callbacks have to be implemented in backend if arch.h * defines ARCH_HAVE_LIBRARY_DATA. Those are used to init, destroy - * and clone LIB->arch. */ -void arch_library_init(struct library *lib); + * and clone LIB->arch. arch_library_init and arch_library_clone + * return 0 on success or a negative value on failure. */ +int arch_library_init(struct library *lib); void arch_library_destroy(struct library *lib); -void arch_library_clone(struct library *retp, struct library *lib); +int arch_library_clone(struct library *retp, struct library *lib); + +/* The following callbacks have to be implemented in OS backend if + * os.h defines OS_HAVE_LIBRARY_SYMBOL_DATA. Those are used to init, + * destroy and clone LIBSYM->os. os_library_symbol_init and + * os_library_symbol_clone return 0 on success or a negative value on + * failure. */ +int os_library_symbol_init(struct library_symbol *libsym); +void os_library_symbol_destroy(struct library_symbol *libsym); +int os_library_symbol_clone(struct library_symbol *retp, + struct library_symbol *libsym); /* The following callbacks have to be implemented in backend if arch.h * defines ARCH_HAVE_LIBRARY_SYMBOL_DATA. Those are used to init, @@ -253,71 +282,100 @@ void arch_library_symbol_destroy(struct library_symbol *libsym); int arch_library_symbol_clone(struct library_symbol *retp, struct library_symbol *libsym); +/* The following callbacks have to be implemented in OS backend if + * os.h defines OS_HAVE_PROCESS_DATA. The protocol is same as for, + * respectively, arch_process_init, arch_process_destroy, + * arch_process_clone and arch_process_exec. */ +int os_process_init(struct process *proc); +void os_process_destroy(struct process *proc); +int os_process_clone(struct process *retp, struct process *proc); +int os_process_exec(struct process *proc); + /* The following callbacks have to be implemented in backend if arch.h * defines ARCH_HAVE_PROCESS_DATA. Those are used to init, destroy * and clone PROC->arch. arch_process_exec is called to update * PROC->arch in case that PROC underwent an exec. See notes at * process_init, process_destroy, process_clone and process_exec in * proc.h. */ -int arch_process_init(struct Process *proc); -void arch_process_destroy(struct Process *proc); -int arch_process_clone(struct Process *retp, struct Process *proc); -int arch_process_exec(struct Process *proc); - -/* The following callbacks have to be implemented in OS backend if - * os.h defines OS_HAVE_PROCESS_DATA. The protocol is same as for, - * respectively, arch_process_init, arch_process_destroy, - * arch_process_clone and arch_process_exec. */ -int os_process_init(struct Process *proc); -void os_process_destroy(struct Process *proc); -int os_process_clone(struct Process *retp, struct Process *proc); -int os_process_exec(struct Process *proc); +int arch_process_init(struct process *proc); +void arch_process_destroy(struct process *proc); +int arch_process_clone(struct process *retp, struct process *proc); +int arch_process_exec(struct process *proc); /* The following callback has to be implemented in backend if arch.h * defines ARCH_HAVE_GET_SYM_INFO. * - * This is called for every PLT relocation R in ELF file LTE, that - * ltrace is about to add to it's internal representation of the - * program under trace. - * The corresponding PLT entry is for SYM_INDEX-th relocation in the file. - * - * The callback is responsible for initializing RELA and SYM. - * - * Return 0 if OK. - * Return a negative value if this symbol (SYM_INDEX) should be ignored. */ -int arch_get_sym_info(struct ltelf *lte, const char *filename, - size_t sym_index, GElf_Rela *rela, GElf_Sym *sym); + * This is called for every PLT relocation RELA in ELF file LTE (which + * is named FILENAME), that ltrace is about to add. The corresponding + * PLT entry is for SYM_INDEX-th relocation in the file. This call is + * supposed to initialize SYM and RELA. It returns 0 if there were no + * errors and given symbol should be used, 1 if the symbol should not + * be used, or a negative value if there were errors. */ +int arch_get_sym_info(struct ltelf *lte, const char *filename, size_t sym_index, + GElf_Rela *rela, GElf_Sym *sym); enum plt_status { - plt_fail, - plt_ok, - plt_default, + PLT_FAIL, + PLT_OK, + PLT_DEFAULT, }; -/* The following callback has to be implemented in backend if arch.h - * defines ARCH_HAVE_ADD_PLT_ENTRY. +/* The following callback has to be implemented in OS backend if os.h + * defines OS_HAVE_ADD_PLT_ENTRY. * * This is called for every PLT relocation R in ELF file LTE, that * ltrace is about to add to a library constructed in process PROC. * The corresponding PLT entry is for symbol called NAME, and it's * I-th relocation in the file. * - * If this function returns plt_default, PLT address is obtained by - * calling arch_plt_sym_val, and symbol is allocated. If plt_ok or - * plt_default are returned, the chain of symbols passed back in RET + * If this function returns PLT_DEFAULT, PLT address is obtained by + * calling arch_plt_sym_val, and symbol is allocated. If PLT_OK or + * PLT_DEFAULT are returned, the chain of symbols passed back in RET * is added to library under construction. */ -enum plt_status arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, +enum plt_status os_elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *name, GElf_Rela *rela, + size_t i, struct library_symbol **ret); + +/* Like os_elf_add_plt_entry, but tied to ARCH_HAVE_ADD_PLT_ENTRY in + * arch.h. The arch callback is called first. If it returns + * PLT_DEFAULT, the os callback is called next. */ +enum plt_status arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, const char *name, GElf_Rela *rela, size_t i, struct library_symbol **ret); +/* The following callback has to be implemented in OS backend if os.h + * defines OS_HAVE_ADD_FUNC_ENTRY. + * + * This is called for every symbol in ltrace is about to add to the + * library constructed for LTE in process PROC. + * + * If this function returns PLT_DEFAULT, then if there is a + * pre-existing symbol, its name may be updated if the newly-found + * name is shorter. Otherwise a new symbol is created. + * + * If PLT_OK or PLT_DEFAULT are returned, the chain of symbols passed + * back in RET is added to library under construction. */ +enum plt_status os_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret); + +/* Like os_elf_add_func_entry, but tied to ARCH_HAVE_ADD_FUNC_ENTRY in + * arch.h. The arch callback is called first. If it returns + * PLT_DEFAULT, the os callback is called next. */ +enum plt_status arch_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret); + /* This callback needs to be implemented if arch.h defines * ARCH_HAVE_DYNLINK_DONE. It is called after the dynamic linker is - * done with the process startup. */ -void arch_dynlink_done(struct Process *proc); + * done with the process start-up. */ +void arch_dynlink_done(struct process *proc); /* This callback needs to be implemented if arch.h defines * ARCH_HAVE_SYMBOL_RET. It is called after a traced call returns. */ -void arch_symbol_ret(struct Process *proc, struct library_symbol *libsym); +void arch_symbol_ret(struct process *proc, struct library_symbol *libsym); /* This callback needs to be implemented if arch.h defines @@ -327,9 +385,36 @@ void arch_symbol_ret(struct Process *proc, struct library_symbol *libsym); * DYN_ADDR holds the address of the dynamic section. * If the debug area is found, return 0 and fill in the address in *RET. * If the debug area is not found, return a negative value. */ -int arch_find_dl_debug(struct Process *proc, arch_addr_t dyn_addr, +int arch_find_dl_debug(struct process *proc, arch_addr_t dyn_addr, arch_addr_t *ret); +/* This is called to obtain a list of directories to search when + * loading config files. The callback sets *RETP to a pointer to the + * first element of a NULL-terminated array of directory names. It's + * legitimate to set *RETP to NULL to indicate there are no + * directories. The function returns 0 on success or a negative value + * on a failure. + * + * If PRIVATE is set, the list in *RETP should contain only user's own + * directories (presumably under HOME if there's any such thing on the + * given OS). Otherwise only system directories should be reported. + * + * The directories don't have to exist. Directories passed in -F are + * handled separately by the caller and this callback shouldn't + * concern itself with it. */ +int os_get_config_dirs(int private, const char ***retp); + +/* This is called to obtain list of legacy config files to import, if + * any. A reference to initialized vector of char* is passed in. + * + * This returns 0 on success, in which case strings from *RETP (if + * any) are interpreted as files names. These files belong to the + * caller and will eventually be freed. + * + * Returns a negative value for failure, in which case *RETP contents + * are not consulted in any way. */ +int os_get_ltrace_conf_filenames(struct vect *retp); + /* If arch.h defines ARCH_HAVE_FETCH_ARG, the following callbacks have * to be implemented: arch_fetch_arg_init, arch_fetch_arg_clone, * arch_fetch_arg_done, arch_fetch_arg_next and arch_fetch_retval. @@ -340,4 +425,34 @@ int arch_find_dl_debug(struct Process *proc, arch_addr_t dyn_addr, * implemented: arch_fetch_param_pack_start, * arch_fetch_param_pack_end. See fetch.h for details. */ +enum sw_singlestep_status { + SWS_FAIL, + SWS_OK, + SWS_HW, +}; +struct sw_singlestep_data; + +/* The following callback has to be implemented in backend if arch.h + * defines ARCH_HAVE_SW_SINGLESTEP. + * + * This is called before the OS backend requests hardware singlestep. + * arch_sw_singlestep should consider whether a singlestep needs to be + * done in software. If not, it returns SWS_HW. Otherwise it needs + * to add one or several breakpoints by calling ADD_CB. When it is + * done, it continues the process as appropriate, and answers either + * SWS_OK, or SWS_FAIL, depending on how it went. + * + * PROC is the process that should perform the singlestep, BP the + * breakpoint that we are singlestepping over. ADD_CB is a callback + * to request adding breakpoints that should trap the process after + * it's continued. The arguments to ADD_CB are the address where the + * breakpoint should be added, and DATA. ADD_CB returns 0 on success + * or a negative value on failure. It is expected that + * arch_sw_singlestep returns SWS_FAIL if ADD_CB returns error. */ +enum sw_singlestep_status arch_sw_singlestep(struct process *proc, + struct breakpoint *bp, + int (*add_cb)(arch_addr_t addr, + struct sw_singlestep_data *), + struct sw_singlestep_data *data); + #endif /* BACKEND_H */ @@ -0,0 +1,34 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "bits.h" + +/* This is called rarely, and any overhead will be lost in ptrace + * noise, so the algorithm doesn't need to be terribly clever. For + * the same reason we don't bother defining the corresponding _32 + * variant. */ +unsigned +bitcount(uint64_t u) +{ + int c = 0; + for (; u > 0; u &= u - 1) + c++; + return c; +} @@ -0,0 +1,29 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef BITS_H +#define BITS_H + +#include <stdint.h> + +/* Count bits in U that are 1. */ +unsigned bitcount(uint64_t u); + +#endif /* BITS_H */ diff --git a/breakpoint.h b/breakpoint.h index 7cd914e..c36f673 100644 --- a/breakpoint.h +++ b/breakpoint.h @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -41,14 +41,22 @@ #include "sysdep.h" #include "library.h" - -struct Process; -struct breakpoint; +#include "forward.h" struct bp_callbacks { - void (*on_hit)(struct breakpoint *bp, struct Process *proc); - void (*on_continue)(struct breakpoint *bp, struct Process *proc); - void (*on_retract)(struct breakpoint *bp, struct Process *proc); + void (*on_hit)(struct breakpoint *bp, struct process *proc); + void (*on_continue)(struct breakpoint *bp, struct process *proc); + void (*on_install)(struct breakpoint *bp, struct process *proc); + void (*on_retract)(struct breakpoint *bp, struct process *proc); + + /* Create a new breakpoint that should handle return from the + * function. BP is the breakpoint that was just hit and for + * which we wish to find the corresponding return breakpoint. + * This returns 0 on success (in which case *RET will have + * been initialized to desired breakpoint object, or NULL if + * none is necessary) or a negative value on failure. */ + int (*get_return_bp)(struct breakpoint **ret, + struct breakpoint *bp, struct process *proc); }; struct breakpoint { @@ -58,37 +66,48 @@ struct breakpoint { unsigned char orig_value[BREAKPOINT_LENGTH]; int enabled; struct arch_breakpoint_data arch; + struct os_breakpoint_data os; }; -/* Call on-hit handler of BP, if any is set. */ -void breakpoint_on_hit(struct breakpoint *bp, struct Process *proc); +/* Call ON_HIT handler of BP, if any is set. */ +void breakpoint_on_hit(struct breakpoint *bp, struct process *proc); -/* Call on-continue handler of BP. If none is set, call +/* Call ON_CONTINUE handler of BP. If none is set, call * continue_after_breakpoint. */ -void breakpoint_on_continue(struct breakpoint *bp, struct Process *proc); +void breakpoint_on_continue(struct breakpoint *bp, struct process *proc); -/* Call on-retract handler of BP, if any is set. This should be +/* Call ON_RETRACT handler of BP, if any is set. This should be * called before the breakpoints are destroyed. The reason for a * separate interface is that breakpoint_destroy has to be callable * without PROC. ON_DISABLE might be useful as well, but that would * be called every time we disable the breakpoint, which is too often * (a breakpoint has to be disabled every time that we need to execute * the instruction underneath it). */ -void breakpoint_on_retract(struct breakpoint *bp, struct Process *proc); +void breakpoint_on_retract(struct breakpoint *bp, struct process *proc); + +/* Call ON_INSTALL handler of BP, if any is set. This should be + * called after the breakpoint is enabled for the first time, not + * every time it's enabled (such as after stepping over a site of a + * temporarily disabled breakpoint). */ +void breakpoint_on_install(struct breakpoint *bp, struct process *proc); + +/* Call GET_RETURN_BP handler of BP, if any is set. If none is set, + * call CREATE_DEFAULT_RETURN_BP to obtain one. */ +int breakpoint_get_return_bp(struct breakpoint **ret, + struct breakpoint *bp, struct process *proc); /* Initialize a breakpoint structure. That doesn't actually realize * the breakpoint. The breakpoint is initially assumed to be * disabled. orig_value has to be set separately. CBS may be * NULL. */ -int breakpoint_init(struct breakpoint *bp, struct Process *proc, +int breakpoint_init(struct breakpoint *bp, struct process *proc, arch_addr_t addr, struct library_symbol *libsym); /* Make a clone of breakpoint BP into the area of memory pointed to by - * RETP. The original breakpoint was assigned to process OLD_PROC, - * the cloned breakpoint will be attached to process NEW_PROC. + * RETP. Symbols of cloned breakpoint are looked up in NEW_PROC. * Returns 0 on success or a negative value on failure. */ -int breakpoint_clone(struct breakpoint *retp, struct Process *new_proc, - struct breakpoint *bp, struct Process *old_proc); +int breakpoint_clone(struct breakpoint *retp, struct process *new_proc, + struct breakpoint *bp); /* Set callbacks. If CBS is non-NULL, then BP->cbs shall be NULL. */ void breakpoint_set_callbacks(struct breakpoint *bp, struct bp_callbacks *cbs); @@ -98,23 +117,29 @@ void breakpoint_destroy(struct breakpoint *bp); /* Call enable_breakpoint the first time it's called. Returns 0 on * success and a negative value on failure. */ -int breakpoint_turn_on(struct breakpoint *bp, struct Process *proc); +int breakpoint_turn_on(struct breakpoint *bp, struct process *proc); /* Call disable_breakpoint when turned off the same number of times * that it was turned on. Returns 0 on success and a negative value * on failure. */ -int breakpoint_turn_off(struct breakpoint *bp, struct Process *proc); - -/* Utility function that does what typically needs to be done when a - * breakpoint is to be inserted. It checks whether there is another - * breakpoint in PROC->LEADER for given ADDR. If not, it allocates - * memory for a new breakpoint on the heap, initializes it, and calls - * PROC_ADD_BREAKPOINT to add the newly-created breakpoint. For newly - * added as well as preexisting breakpoints, it then calls - * BREAKPOINT_TURN_ON. If anything fails, it cleans up and returns - * NULL. Otherwise it returns the breakpoint for ADDR. */ -struct breakpoint *insert_breakpoint(struct Process *proc, void *addr, - struct library_symbol *libsym); +int breakpoint_turn_off(struct breakpoint *bp, struct process *proc); + +/* Allocate and initialize a default return breakpoint. Returns NULL + * on failure. */ +struct breakpoint *create_default_return_bp(struct process *proc); + +/* This allocates and initializes new breakpoint at ADDR, then calls + * INSERT_BREAKPOINT. Returns the new breakpoint or NULL if there are + * errors. */ +struct breakpoint *insert_breakpoint_at(struct process *proc, arch_addr_t addr, + struct library_symbol *libsym); + +/* Check if there is a breakpoint on this address already. If yes, + * return that breakpoint instead (BP was not added). If no, try to + * PROC_ADD_BREAKPOINT and BREAKPOINT_TURN_ON. If it all works, + * return BP. Otherwise return NULL. */ +struct breakpoint *insert_breakpoint(struct process *proc, + struct breakpoint *bp); /* Name of a symbol associated with BP. May be NULL. */ const char *breakpoint_name(const struct breakpoint *bp); @@ -127,12 +152,12 @@ struct library *breakpoint_library(const struct breakpoint *bp); * - proc_remove_breakpoint * - breakpoint_destroy * XXX */ -void delete_breakpoint(struct Process *proc, void *addr); +void delete_breakpoint_at(struct process *proc, void *addr); +int delete_breakpoint(struct process *proc, struct breakpoint *bp); /* XXX some of the following belongs to proc.h/proc.c. */ -struct breakpoint *address2bpstruct(struct Process *proc, void *addr); -void enable_all_breakpoints(struct Process *proc); -void disable_all_breakpoints(struct Process *proc); -int breakpoints_init(struct Process *proc); +struct breakpoint *address2bpstruct(struct process *proc, void *addr); +void disable_all_breakpoints(struct process *proc); +int breakpoints_init(struct process *proc); #endif /* BREAKPOINT_H */ diff --git a/breakpoints.c b/breakpoints.c index 258be93..c3fa275 100644 --- a/breakpoints.c +++ b/breakpoints.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2006,2007,2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2006,2007,2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2009 Juan Cespedes * Copyright (C) 1998,2001,2002,2003,2007,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand @@ -42,7 +42,7 @@ #ifndef ARCH_HAVE_TRANSLATE_ADDRESS int -arch_translate_address_dyn(struct Process *proc, +arch_translate_address_dyn(struct process *proc, arch_addr_t addr, arch_addr_t *ret) { *ret = addr; @@ -60,7 +60,7 @@ arch_translate_address(struct ltelf *lte, #endif void -breakpoint_on_hit(struct breakpoint *bp, struct Process *proc) +breakpoint_on_hit(struct breakpoint *bp, struct process *proc) { assert(bp != NULL); if (bp->cbs != NULL && bp->cbs->on_hit != NULL) @@ -68,7 +68,7 @@ breakpoint_on_hit(struct breakpoint *bp, struct Process *proc) } void -breakpoint_on_continue(struct breakpoint *bp, struct Process *proc) +breakpoint_on_continue(struct breakpoint *bp, struct process *proc) { assert(bp != NULL); if (bp->cbs != NULL && bp->cbs->on_continue != NULL) @@ -78,28 +78,73 @@ breakpoint_on_continue(struct breakpoint *bp, struct Process *proc) } void -breakpoint_on_retract(struct breakpoint *bp, struct Process *proc) +breakpoint_on_retract(struct breakpoint *bp, struct process *proc) { assert(bp != NULL); if (bp->cbs != NULL && bp->cbs->on_retract != NULL) (bp->cbs->on_retract)(bp, proc); } +void +breakpoint_on_install(struct breakpoint *bp, struct process *proc) +{ + assert(bp != NULL); + if (bp->cbs != NULL && bp->cbs->on_install != NULL) + (bp->cbs->on_install)(bp, proc); +} + +int +breakpoint_get_return_bp(struct breakpoint **ret, + struct breakpoint *bp, struct process *proc) +{ + assert(bp != NULL); + if (bp->cbs != NULL && bp->cbs->get_return_bp != NULL) + return (bp->cbs->get_return_bp)(ret, bp, proc); + + if ((*ret = create_default_return_bp(proc)) == NULL) + return -1; + + return 0; +} + /*****************************************************************************/ struct breakpoint * -address2bpstruct(Process *proc, void *addr) +address2bpstruct(struct process *proc, arch_addr_t addr) { assert(proc != NULL); assert(proc->breakpoints != NULL); assert(proc->leader == proc); debug(DEBUG_FUNCTION, "address2bpstruct(pid=%d, addr=%p)", proc->pid, addr); - return dict_find_entry(proc->breakpoints, addr); + + struct breakpoint *found; + if (DICT_FIND_VAL(proc->breakpoints, &addr, &found) < 0) + return NULL; + return found; +} + +#ifndef OS_HAVE_BREAKPOINT_DATA +int +os_breakpoint_init(struct process *proc, struct breakpoint *sbp) +{ + return 0; } +void +os_breakpoint_destroy(struct breakpoint *sbp) +{ +} + +int +os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp) +{ + return 0; +} +#endif + #ifndef ARCH_HAVE_BREAKPOINT_DATA int -arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp) +arch_breakpoint_init(struct process *proc, struct breakpoint *sbp) { return 0; } @@ -117,7 +162,7 @@ arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp) #endif static void -breakpoint_init_base(struct breakpoint *bp, struct Process *proc, +breakpoint_init_base(struct breakpoint *bp, arch_addr_t addr, struct library_symbol *libsym) { bp->cbs = NULL; @@ -132,11 +177,17 @@ breakpoint_init_base(struct breakpoint *bp, struct Process *proc, * static lookups of various sections in the ELF file. We shouldn't * need process for anything. */ int -breakpoint_init(struct breakpoint *bp, struct Process *proc, +breakpoint_init(struct breakpoint *bp, struct process *proc, arch_addr_t addr, struct library_symbol *libsym) { - breakpoint_init_base(bp, proc, addr, libsym); - return arch_breakpoint_init(proc, bp); + breakpoint_init_base(bp, addr, libsym); + if (os_breakpoint_init(proc, bp) < 0) + return -1; + if (arch_breakpoint_init(proc, bp) < 0) { + os_breakpoint_destroy(bp); + return -1; + } + return 0; } void @@ -153,11 +204,12 @@ breakpoint_destroy(struct breakpoint *bp) if (bp == NULL) return; arch_breakpoint_destroy(bp); + os_breakpoint_destroy(bp); } int -breakpoint_clone(struct breakpoint *retp, struct Process *new_proc, - struct breakpoint *bp, struct Process *old_proc) +breakpoint_clone(struct breakpoint *retp, struct process *new_proc, + struct breakpoint *bp) { struct library_symbol *libsym = NULL; if (bp->libsym != NULL) { @@ -165,28 +217,33 @@ breakpoint_clone(struct breakpoint *retp, struct Process *new_proc, assert(rc == 0); } - breakpoint_init_base(retp, new_proc, bp->addr, libsym); + breakpoint_init_base(retp, bp->addr, libsym); memcpy(retp->orig_value, bp->orig_value, sizeof(bp->orig_value)); retp->enabled = bp->enabled; - if (arch_breakpoint_clone(retp, bp) < 0) + if (os_breakpoint_clone(retp, bp) < 0) return -1; + if (arch_breakpoint_clone(retp, bp) < 0) { + os_breakpoint_destroy(retp); + return -1; + } breakpoint_set_callbacks(retp, bp->cbs); return 0; } int -breakpoint_turn_on(struct breakpoint *bp, struct Process *proc) +breakpoint_turn_on(struct breakpoint *bp, struct process *proc) { bp->enabled++; if (bp->enabled == 1) { assert(proc->pid != 0); enable_breakpoint(proc, bp); + breakpoint_on_install(bp, proc); } return 0; } int -breakpoint_turn_off(struct breakpoint *bp, struct Process *proc) +breakpoint_turn_off(struct breakpoint *bp, struct process *proc) { bp->enabled--; if (bp->enabled == 0) @@ -196,21 +253,59 @@ breakpoint_turn_off(struct breakpoint *bp, struct Process *proc) } struct breakpoint * -insert_breakpoint(struct Process *proc, void *addr, - struct library_symbol *libsym) +create_default_return_bp(struct process *proc) +{ + struct breakpoint *bp = malloc(sizeof *bp); + arch_addr_t return_addr = get_return_addr(proc, proc->stack_pointer); + if (return_addr == 0 || bp == NULL + || breakpoint_init(bp, proc, return_addr, NULL) < 0) { + free(bp); + return NULL; + } + return bp; +} + +struct breakpoint * +insert_breakpoint_at(struct process *proc, arch_addr_t addr, + struct library_symbol *libsym) { - Process *leader = proc->leader; + debug(DEBUG_FUNCTION, + "insert_breakpoint_at(pid=%d, addr=%p, symbol=%s)", + proc->pid, addr, libsym ? libsym->name : "NULL"); + + assert(addr != 0); + struct breakpoint *bp = malloc(sizeof *bp); + if (bp == NULL || breakpoint_init(bp, proc, addr, libsym) < 0) { + free(bp); + return NULL; + } + + /* N.B. (and XXX): BP->addr might differ from ADDR. On ARM + * this is a real possibility. The problem here is that to + * create a return breakpoint ltrace calls get_return_addr and + * then insert_breakpoint_at. So get_return_addr needs to + * encode all the information necessary for breakpoint_init + * into the address itself, so ADDR is potentially + * mangled. */ + + struct breakpoint *tmp = insert_breakpoint(proc, bp); + if (tmp != bp) { + breakpoint_destroy(bp); + free(bp); + } + return tmp; +} + +struct breakpoint * +insert_breakpoint(struct process *proc, struct breakpoint *bp) +{ /* Only the group leader should be getting the breakpoints and * thus have ->breakpoint initialized. */ + struct process *leader = proc->leader; assert(leader != NULL); assert(leader->breakpoints != NULL); - debug(DEBUG_FUNCTION, "insert_breakpoint(pid=%d, addr=%p, symbol=%s)", - proc->pid, addr, libsym ? libsym->name : "NULL"); - - assert(addr != 0); - /* XXX what we need to do instead is have a list of * breakpoints that are enabled at this address. The * following works if every breakpoint is the same and there's @@ -218,54 +313,57 @@ insert_breakpoint(struct Process *proc, void *addr, * will suffice, about the only realistic case where we need * to have more than one breakpoint per address is return from * a recursive library call. */ - struct breakpoint *sbp = dict_find_entry(leader->breakpoints, addr); - if (sbp == NULL) { - sbp = malloc(sizeof(*sbp)); - if (sbp == NULL - || breakpoint_init(sbp, proc, addr, libsym) < 0) { - free(sbp); - return NULL; - } - if (proc_add_breakpoint(leader, sbp) < 0) { - fail: - breakpoint_destroy(sbp); - free(sbp); + struct breakpoint *ext_bp = bp; + if (DICT_FIND_VAL(leader->breakpoints, &bp->addr, &ext_bp) != 0) { + if (proc_add_breakpoint(leader, bp) < 0) return NULL; - } + ext_bp = bp; } - if (breakpoint_turn_on(sbp, proc) < 0) { - proc_remove_breakpoint(leader, sbp); - goto fail; + if (breakpoint_turn_on(ext_bp, proc) < 0) { + if (ext_bp != bp) + proc_remove_breakpoint(leader, bp); + return NULL; } - return sbp; + return ext_bp; } void -delete_breakpoint(Process *proc, void *addr) +delete_breakpoint_at(struct process *proc, arch_addr_t addr) { - debug(DEBUG_FUNCTION, "delete_breakpoint(pid=%d, addr=%p)", proc->pid, addr); + debug(DEBUG_FUNCTION, "delete_breakpoint_at(pid=%d, addr=%p)", + proc->pid, addr); - Process * leader = proc->leader; + struct process *leader = proc->leader; assert(leader != NULL); - struct breakpoint *sbp = dict_find_entry(leader->breakpoints, addr); - assert(sbp != NULL); - /* This should only happen on out-of-memory conditions. */ - if (sbp == NULL) - return; + struct breakpoint *bp = NULL; + DICT_FIND_VAL(leader->breakpoints, &addr, &bp); + assert(bp != NULL); - if (breakpoint_turn_off(sbp, proc) < 0) { + if (delete_breakpoint(proc, bp) < 0) { fprintf(stderr, "Couldn't turn off the breakpoint %s@%p\n", - breakpoint_name(sbp), sbp->addr); - return; + breakpoint_name(bp), bp->addr); } - if (sbp->enabled == 0) { - proc_remove_breakpoint(leader, sbp); - breakpoint_destroy(sbp); - free(sbp); +} + +int +delete_breakpoint(struct process *proc, struct breakpoint *bp) +{ + struct process *leader = proc->leader; + assert(leader != NULL); + + if (breakpoint_turn_off(bp, proc) < 0) + return -1; + + if (bp->enabled == 0) { + proc_remove_breakpoint(leader, bp); + breakpoint_destroy(bp); + free(bp); } + + return 0; } const char * @@ -282,85 +380,53 @@ breakpoint_library(const struct breakpoint *bp) return bp->libsym != NULL ? bp->libsym->lib : NULL; } -static void -enable_bp_cb(void *addr, void *sbp, void *proc) +static enum callback_status +disable_bp_cb(arch_addr_t *addr, struct breakpoint **bpp, void *data) { - debug(DEBUG_FUNCTION, "enable_bp_cb(pid=%d)", ((Process *)proc)->pid); - if (((struct breakpoint *)sbp)->enabled) - enable_breakpoint(proc, sbp); + struct process *proc = data; + debug(DEBUG_FUNCTION, "disable_bp_cb(pid=%d)", proc->pid); + if ((*bpp)->enabled) + disable_breakpoint(proc, *bpp); + return CBS_CONT; } void -enable_all_breakpoints(Process *proc) +disable_all_breakpoints(struct process *proc) { - debug(DEBUG_FUNCTION, "enable_all_breakpoints(pid=%d)", proc->pid); - - debug(1, "Enabling breakpoints for pid %u...", proc->pid); - if (proc->breakpoints) { - dict_apply_to_all(proc->breakpoints, enable_bp_cb, - proc); - } -} - -static void -disable_bp_cb(void *addr, void *sbp, void *proc) -{ - debug(DEBUG_FUNCTION, "disable_bp_cb(pid=%d)", ((Process *)proc)->pid); - if (((struct breakpoint *)sbp)->enabled) - disable_breakpoint(proc, sbp); -} - -void -disable_all_breakpoints(Process *proc) { debug(DEBUG_FUNCTION, "disable_all_breakpoints(pid=%d)", proc->pid); assert(proc->leader == proc); - dict_apply_to_all(proc->breakpoints, disable_bp_cb, proc); + DICT_EACH(proc->breakpoints, arch_addr_t, struct breakpoint *, + NULL, disable_bp_cb, proc); } -/* XXX This is not currently properly supported. On clone, this is - * just sliced. Hopefully at the point that clone is done, this - * breakpoint is not necessary anymore. If this use case ends up - * being important, we need to add a clone and destroy callbacks to - * breakpoints, and we should also probably drop arch_breakpoint_data - * so that we don't end up with two different customization mechanisms - * for one structure. */ -struct entry_breakpoint { - struct breakpoint super; - arch_addr_t dyn_addr; -}; - static void -entry_breakpoint_on_hit(struct breakpoint *a, struct Process *proc) +entry_breakpoint_on_hit(struct breakpoint *bp, struct process *proc) { - struct entry_breakpoint *bp = (void *)a; if (proc == NULL || proc->leader == NULL) return; - arch_addr_t dyn_addr = bp->dyn_addr; - delete_breakpoint(proc, bp->super.addr); - linkmap_init(proc, dyn_addr); - arch_dynlink_done(proc); + delete_breakpoint_at(proc, bp->addr); + process_hit_start(proc); } int -entry_breakpoint_init(struct Process *proc, - struct entry_breakpoint *bp, arch_addr_t addr, +entry_breakpoint_init(struct process *proc, + struct breakpoint *bp, arch_addr_t addr, struct library *lib) { assert(addr != 0); - int err = breakpoint_init(&bp->super, proc, addr, NULL); + int err = breakpoint_init(bp, proc, addr, NULL); if (err < 0) return err; static struct bp_callbacks entry_callbacks = { .on_hit = entry_breakpoint_on_hit, }; - bp->super.cbs = &entry_callbacks; - bp->dyn_addr = lib->dyn_addr; + bp->cbs = &entry_callbacks; return 0; } int -breakpoints_init(Process *proc) +breakpoints_init(struct process *proc) { debug(DEBUG_FUNCTION, "breakpoints_init(pid=%d)", proc->pid); @@ -376,17 +442,17 @@ breakpoints_init(Process *proc) assert(proc->filename != NULL); struct library *lib = ltelf_read_main_binary(proc, proc->filename); - struct entry_breakpoint *entry_bp = NULL; + struct breakpoint *entry_bp = NULL; int bp_state = 0; int result = -1; - switch (lib != NULL) { + switch ((int)(lib != NULL)) { fail: switch (bp_state) { case 2: proc_remove_library(proc, lib); - proc_remove_breakpoint(proc, &entry_bp->super); + proc_remove_breakpoint(proc, entry_bp); case 1: - breakpoint_destroy(&entry_bp->super); + breakpoint_destroy(entry_bp); } library_destroy(lib); free(entry_bp); @@ -406,11 +472,11 @@ breakpoints_init(Process *proc) } else { ++bp_state; - if ((result = proc_add_breakpoint(proc, &entry_bp->super)) < 0) + if ((result = proc_add_breakpoint(proc, entry_bp)) < 0) goto fail; ++bp_state; - if ((result = breakpoint_turn_on(&entry_bp->super, proc)) < 0) + if ((result = breakpoint_turn_on(entry_bp, proc)) < 0) goto fail; } proc_add_library(proc, lib); @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,8 +18,8 @@ * 02110-1301 USA */ -#ifndef _CALLBACK_H_ -#define _CALLBACK_H_ +#ifndef CALLBACK_H +#define CALLBACK_H /* Notes on the iteration interface used across ltrace. Typically the * iteration function looks something like this: @@ -39,6 +39,21 @@ * thing as CBS_STOP. There's no provision for returning error * states. Errors need to be signaled to the caller via DATA, * together with any other data that the callback needs. + * + * A richer iteration interface looks like this: + * + * struct each_foo_t { + * foo *restart; + * int status; + * } each_foo(foo *start_after, + * enum callback_status (*cb)(foo *f, void *data), + * void *data); + * + * These provide error handling. The two-part structure encodes both + * the restart cookie and status. Status of 0 means success, negative + * values signify failures. Status of -1 is dedicated to failures in + * user callback (such that the callback returns CBS_FAIL). Other + * negative values signal failures in the iteration mechanism itself. */ enum callback_status { CBS_STOP, /* The iteration should stop. */ @@ -47,4 +62,7 @@ enum callback_status { * and return error. */ }; -#endif /* _CALLBACK_H_ */ +#define CBS_STOP_IF(X) ((X) ? CBS_STOP : CBS_CONT) +#define CBS_CONT_IF(X) ((X) ? CBS_CONT : CBS_STOP) + +#endif /* CALLBACK_H */ @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Joe Damato * Copyright (C) 2009 Juan Cespedes * @@ -39,42 +39,14 @@ #include "proc.h" #include "forward.h" -#if defined HAVE_LIBSUPC__ || defined HAVE_LIBSTDC__ -# define USE_CXA_DEMANGLE -#endif -#if defined HAVE_LIBIBERTY || defined USE_CXA_DEMANGLE -# define USE_DEMANGLE -#endif - extern char * command; extern int exiting; /* =1 if we have to exit ASAP */ -typedef struct Function Function; -struct Function { - const char * name; - struct param *params; - struct arg_type_info *return_info; - int own_return_info; - size_t num_params; - Function * next; -}; - -extern Function * list_of_functions; extern char *PLTs_initialized_by_here; -struct opt_c_struct { - int count; - struct timeval tv; -}; - #include "options.h" #include "output.h" -#ifdef USE_DEMANGLE -#include "demangle.h" -#endif - -extern Dict * dict_opt_c; /* Events */ extern Event * next_event(void); @@ -82,8 +54,6 @@ extern void handle_event(Event * event); extern pid_t execute_program(const char * command, char ** argv); -extern void show_summary(void); - struct breakpoint; struct library_symbol; @@ -94,4 +64,9 @@ struct library_symbol; int format_argument(FILE *stream, struct value *value, struct value_dict *arguments); +/* Set *RET to either a duplicate of STR (if WHETHER), or STR + * (otherwise). Return 0 on success or a negative value on failure. + * The duplication is not done if STR is NULL. */ +int strdup_if(const char **ret, const char *str, int whether); + #endif diff --git a/configure.ac b/configure.ac index 20c84f4..6fe5e3b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,6 @@ # -*- Autoconf -*- # This file is part of ltrace. -# Copyright (C) 2010,2012 Petr Machata, Red Hat Inc. +# Copyright (C) 2010,2012,2013,2014 Petr Machata, Red Hat Inc. # Copyright (C) 2010,2011 Joe Damato # Copyright (C) 2010 Marc Kleine-Budde # Copyright (C) 2010 Zachary T Welch @@ -21,9 +21,10 @@ # 02110-1301 USA # Process this file with autoconf to produce a configure script. -AC_PREREQ(2.65) +AC_PREREQ([2.65]) -AC_INIT([ltrace],[0.7.2],[ltrace-devel@lists.alioth.debian.org]) +AC_INIT([ltrace],[0.7.91],[ltrace-devel@lists.alioth.debian.org], + [ltrace],[http://ltrace.alioth.debian.org/]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_SRCDIR(libltrace.c) AC_CONFIG_MACRO_DIR([config/m4]) @@ -32,7 +33,8 @@ AC_CANONICAL_BUILD AC_CANONICAL_HOST case "${host_os}" in - linux-gnu*) HOST_OS="linux-gnu" ;; + linux-gnu*) HOST_OS="linux-gnu" ;; + linux-uclibc*) HOST_OS="linux-gnu" ;; *) AC_MSG_ERROR([unkown host-os ${host_os}]) ;; esac AC_SUBST(HOST_OS) @@ -40,9 +42,8 @@ AC_SUBST(HOST_OS) case "${host_cpu}" in arm*|sa110) HOST_CPU="arm" ;; cris*) HOST_CPU="cris" ;; - mips*el) HOST_CPU="mipsel" ;; mips*) HOST_CPU="mips" ;; - powerpc|powerpc64) HOST_CPU="ppc" ;; + powerpc|powerpc64|powerpc64le) HOST_CPU="ppc" ;; sun4u|sparc64) HOST_CPU="sparc" ;; s390x) HOST_CPU="s390" ;; i?86|x86_64) HOST_CPU="x86" ;; @@ -57,6 +58,15 @@ LT_INIT AM_INIT_AUTOMAKE([foreign no-exeext dist-bzip2]) AM_MAINTAINER_MODE +# +# We use stat(2). Even though we don't care about the file size or +# inode number, stat will fail with EOVERFLOW if either of these +# exceeds 32 bits. We therefore ask for stat64 if available. Do this +# test as soon as possible, as large file support may influence +# whether other headers are available. +# +AC_SYS_LARGEFILE + AC_ARG_WITH([libelf], AS_HELP_STRING([--with-libelf], [Prefix of libelf headers/library]), [case "${withval}" in @@ -118,6 +128,51 @@ dnl Check security_get_boolean_active availability. AC_CHECK_HEADERS(selinux/selinux.h) AC_CHECK_LIB(selinux, security_get_boolean_active) +dnl Whether (and which) elfutils libdw.so to use for unwinding. +AC_ARG_WITH(elfutils, + AS_HELP_STRING([--with-elfutils], [Use elfutils libdwfl unwinding support]), + [case "${withval}" in + (yes|no) enable_elfutils=$withval;; + (*) enable_elfutils=yes + AM_CPPFLAGS="${AM_CPPFLAGS} -I${withval}/include" + AM_LDFLAGS="${AM_LDFLAGS} -L${withval}/lib" + elfutils_LD_LIBRARY_PATH="${withval}/lib:${withval}/lib/elfutils" + ;; +esac],[enable_elfutils=maybe]) + +dnl Check whether we have the elfutils libdwfl.h header installed. +saved_CPPFLAGS="${CPPFLAGS}" +CPPFLAGS="${CPPFLAGS} ${AM_CPPFLAGS}" +AC_CHECK_HEADERS([elfutils/libdwfl.h],[have_libdwfl_h=yes]) +CPPFLAGS="${saved_CPPFLAGS}" + +dnl And whether libdw.so provides the unwinding functions. +saved_LDFLAGS="${LDFLAGS}" +LDFLAGS="${LDFLAGS} ${AM_LDFLAGS}" +AC_CHECK_LIB([dw], [dwfl_getthread_frames], [have_libdw_dwfl_frames=yes]) +LDFLAGS="${saved_LDFLAGS}" + +AC_MSG_CHECKING([whether to use elfutils libdwfl unwinding support]) +case "${enable_elfutils}" in +(yes|maybe) + if test x$have_libdwfl_h = xyes -a x$have_libdw_dwfl_frames = xyes; then + enable_elfutils=yes + elif test $enable_elfutils = maybe; then + enable_elfutils=no + else + AC_MSG_RESULT([$enable_elfutils]) + AC_MSG_ERROR([Missing elfutils/libdwfl.h or dwfl_getthread_frames not in libdw.so]) + fi + ;; +(*) ;; +esac +AC_MSG_RESULT([$enable_elfutils]) + +if test x"$enable_elfutils" = xyes; then + libdw_LIBS=-ldw + AC_SUBST(libdw_LIBS) + AC_DEFINE([HAVE_LIBDW], [1], [we have elfutils libdw]) +fi # HAVE_LIBUNWIND AC_ARG_WITH(libunwind, @@ -155,12 +210,12 @@ AC_MSG_RESULT([$enable_libunwind]) if test x"$enable_libunwind" = xyes; then case "${host_cpu}" in - arm*|sa110) UNWIND_ARCH="arm" ;; - i?86) UNWIND_ARCH="x86" ;; - powerpc) UNWIND_ARCH="ppc32" ;; - powerpc64) UNWIND_ARCH="ppc64" ;; - mips*) UNWIND_ARCH="mips" ;; - *) UNWIND_ARCH="${host_cpu}" ;; + arm*|sa110) UNWIND_ARCH="arm" ;; + i?86) UNWIND_ARCH="x86" ;; + powerpc) UNWIND_ARCH="ppc32" ;; + powerpc64|powerpc64le) UNWIND_ARCH="ppc64" ;; + mips*) UNWIND_ARCH="mips" ;; + *) UNWIND_ARCH="${host_cpu}" ;; esac saved_LDFLAGS="${LDFLAGS}" @@ -183,6 +238,13 @@ if test x"$enable_libunwind" = xyes; then LDFLAGS="${saved_LDFLAGS}" fi +if test x"$enable_elfutils" = xyes -a x"$enable_libunwind" = xyes; then + AC_MSG_ERROR([Cannot enable both --with-libunwind and --with-elfutils]) +fi + +if test x"$enable_elfutils" = xyes -o x"$enable_libunwind" = xyes; then + AC_DEFINE([HAVE_UNWINDER], [1], [we have an unwinder available]) +fi saved_CPPFLAGS="${CPPFLAGS}" saved_LDFLAGS="${LDFLAGS}" @@ -215,6 +277,8 @@ CPPFLAGS="${saved_CPPFLAGS}" LDFLAGS="${saved_LDFLAGS}" AM_CPPFLAGS=" \ + -DSYSCONFDIR="'\"$(sysconfdir)\"'" \ + -DPKGDATADIR="'\"$(pkgdatadir)\"'" \ ${AM_CPPFLAGS} \ -I\$(top_srcdir)/sysdeps/${HOST_OS}/${HOST_CPU} \ -I\$(top_srcdir)/sysdeps/${HOST_OS} \ @@ -245,23 +309,48 @@ AC_CHECK_SIZEOF([long]) # Checks for library functions. -AC_FUNC_ERROR_AT_LINE AC_FUNC_FORK AC_CHECK_FUNCS([ \ alarm \ atexit \ - getcwd \ gettimeofday \ memset \ - mkdir \ - rmdir \ strchr \ strdup \ strerror \ + strsignal \ strtol \ strtoul \ ]) +# +# Define HAVE_OPEN_MEMSTREAM if open_memstream is available. glibc +# before 2.10, eglibc and uClibc all need _GNU_SOURCE defined for +# open_memstream to become visible, so check for that as well. If +# unavailable, require that tmpfile be present. There's no +# HAVE_TMPFILE, as we plain require that to be present as a fallback. +# +AC_CHECK_FUNCS([open_memstream], [], + [AC_MSG_CHECKING([for open_memstream with _GNU_SOURCE]) + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[#define _GNU_SOURCE 1 + #include <stdio.h>]], + [[char *buf; size_t sz; + return open_memstream(&buf, &sz) != 0;]])], + + [AC_MSG_RESULT([yes]) + AC_DEFINE([HAVE_OPEN_MEMSTREAM], [1], + [Define if open_memstream exists.])], + + [AC_MSG_RESULT([no]) + AC_CHECK_FUNC([tmpfile], [], + [AC_MSG_ERROR( + [Either open_memstream or tmpfile required.])])])]) + +# +# Define HAVE_GETOPT_LONG if that is available. +# +AC_CHECK_HEADER([getopt.h], [AC_CHECK_FUNCS([getopt_long])]) # # Debugging @@ -298,23 +387,27 @@ if test x$use_valgrind = xyes; then fi fi AM_CONDITIONAL(USE_VALGRIND, test "$use_valgrind" = yes) +AM_CONDITIONAL(HAVE_LIBDW, test x"$enable_elfutils" = xyes) AC_SUBST(AM_CPPFLAGS) AC_SUBST(AM_CFLAGS) AC_SUBST(AM_LDFLAGS) AC_SUBST(libelf_LD_LIBRARY_PATH) +AC_SUBST(elfutils_LD_LIBRARY_PATH) AC_SUBST(libunwind_LD_LIBRARY_PATH) AC_CONFIG_FILES([ Makefile sysdeps/Makefile sysdeps/linux-gnu/Makefile + sysdeps/linux-gnu/aarch64/Makefile sysdeps/linux-gnu/alpha/Makefile sysdeps/linux-gnu/arm/Makefile sysdeps/linux-gnu/cris/Makefile sysdeps/linux-gnu/ia64/Makefile sysdeps/linux-gnu/m68k/Makefile - sysdeps/linux-gnu/mipsel/Makefile + sysdeps/linux-gnu/metag/Makefile + sysdeps/linux-gnu/mips/Makefile sysdeps/linux-gnu/ppc/Makefile sysdeps/linux-gnu/s390/Makefile sysdeps/linux-gnu/sparc/Makefile @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. * Copyright (C) 2003,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -23,6 +24,7 @@ #include <stdarg.h> #include "common.h" +#include "backend.h" void debug_(int level, const char *file, int line, const char *fmt, ...) { @@ -40,95 +42,26 @@ debug_(int level, const char *file, int line, const char *fmt, ...) { fflush(options.output); } -/* - * The following section provides a way to print things, like hex dumps, - * with out using buffered output. This was written by Steve Munroe of IBM. - */ - -#include <stdio.h> -#include <errno.h> -#include <unistd.h> -#include <stdlib.h> -#include <sys/ptrace.h> - -static int -xwritehexl(long i) { - int rc = 0; - char text[17]; - int j; - unsigned long temp = (unsigned long)i; - - for (j = 15; j >= 0; j--) { - char c; - c = (char)((temp & 0x0f) + '0'); - if (c > '9') { - c = (char)(c + ('a' - '9' - 1)); - } - text[j] = c; - temp = temp >> 4; - } - - rc = write(1, text, 16); - return rc; -} - -static int -xwritec(char c) { - char temp = c; - char *text = &temp; - int rc = 0; - rc = write(1, text, 1); - return rc; -} - -static int -xwritecr(void) { - return xwritec('\n'); -} - static int -xwritedump(void *ptr, long addr, int len) { - int rc = 0; - long *tprt = (long *)ptr; - int i; - - for (i = 0; i < len; i += 8) { - xwritehexl(addr); - xwritec('-'); - xwritec('>'); - xwritehexl(*tprt++); - xwritecr(); +xwritedump(long *ptr, arch_addr_t addr, size_t count) +{ + size_t i; + for (i = 0; i < count; ++i) { + if (fprintf(stderr, "%p->%0*lx\n", + addr, 2 * (int)sizeof(long), ptr[i]) < 0) + return -1; addr += sizeof(long); } - return rc; + return 0; } int -xinfdump(long pid, void *ptr, int len) { - int rc; - int i; - long wrdcnt; - long *infwords; - long addr; - - wrdcnt = len / sizeof(long) + 1; - infwords = malloc(wrdcnt * sizeof(long)); - if (!infwords) { - perror("ltrace: malloc"); - exit(1); - } - addr = (long)ptr; - - addr = ((addr + sizeof(long) - 1) / sizeof(long)) * sizeof(long); - - for (i = 0; i < wrdcnt; ++i) { - infwords[i] = ptrace(PTRACE_PEEKTEXT, pid, (void *)addr, NULL); - addr += sizeof(long); - } - - rc = xwritedump(infwords, (long)ptr, len); - - free(infwords); - return rc; +xinfdump(struct process *proc, arch_addr_t addr, size_t length) +{ + unsigned char buf[length]; + size_t got = umovebytes(proc, addr, buf, length); + if (got == (size_t)-1) + return -1; + return xwritedump((long *)buf, addr, got / sizeof(long)); } @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. * Copyright (C) 2003,2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -18,8 +19,11 @@ * 02110-1301 USA */ -#ifndef _DEBUG_H -#define _DEBUG_H +#ifndef DEBUG_H +#define DEBUG_H + +#include "backend.h" +#include "forward.h" /* debug levels: */ @@ -32,8 +36,10 @@ enum { void debug_(int level, const char *file, int line, const char *fmt, ...) __attribute__((format(printf,4,5))); -int xinfdump(long, void *, int); +/* Dump LENGTH bytes of memory starting on address ADDR of inferior + * PID. */ +int xinfdump(struct process *proc, arch_addr_t addr, size_t length); -# define debug(level, expr...) debug_(level, __FILE__, __LINE__, expr) +#define debug(level, expr...) debug_(level, __FILE__, __LINE__, expr) -#endif +#endif /* DEBUG_H */ @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,1999,2003,2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -25,40 +26,59 @@ #include <stdlib.h> #include <stdio.h> -#include "common.h" +#include "demangle.h" +#include "dict.h" +#include "debug.h" #ifdef USE_DEMANGLE /*****************************************************************************/ -static Dict *d = NULL; +static struct dict *name_cache = NULL; const char * my_demangle(const char *function_name) { - const char *tmp, *fn_copy; #ifdef USE_CXA_DEMANGLE extern char *__cxa_demangle(const char *, char *, size_t *, int *); #endif debug(DEBUG_FUNCTION, "my_demangle(name=%s)", function_name); - if (!d) - d = dict_init(dict_key2hash_string, dict_key_cmp_string); + if (name_cache == NULL) { + name_cache = malloc(sizeof(*name_cache)); + if (name_cache != NULL) + DICT_INIT(name_cache, char *, const char *, + dict_hash_string, dict_eq_string, NULL); + } + + const char *tmp = NULL; + if (name_cache != NULL + && DICT_FIND_VAL(name_cache, &function_name, &tmp) == 0) + return tmp; - tmp = dict_find_entry(d, (void *)function_name); - if (!tmp) { - fn_copy = strdup(function_name); #ifdef HAVE_LIBIBERTY - tmp = cplus_demangle(function_name, DMGL_ANSI | DMGL_PARAMS); + tmp = cplus_demangle(function_name, + DMGL_ANSI | DMGL_PARAMS); #elif defined USE_CXA_DEMANGLE - int status = 0; - tmp = __cxa_demangle(function_name, NULL, NULL, &status); + int status = 0; + tmp = __cxa_demangle(function_name, NULL, NULL, &status); #endif - if (!tmp) - tmp = fn_copy; - if (tmp) - dict_enter(d, (void *)fn_copy, (void *)tmp); + if (name_cache == NULL || tmp == NULL) { + fail: + if (tmp == NULL) + return function_name; + return tmp; + } + + const char *fn_copy = strdup(function_name); + if (fn_copy == NULL) + goto fail; + + if (DICT_INSERT(name_cache, &fn_copy, &tmp) < 0) { + free((char *)fn_copy); + goto fail; } + return tmp; } @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. * Copyright (C) 2006 Ian Wienand * * This program is free software; you can redistribute it and/or @@ -20,6 +21,13 @@ #include "config.h" +#if defined HAVE_LIBSUPC__ || defined HAVE_LIBSTDC__ +# define USE_CXA_DEMANGLE +#endif +#if defined HAVE_LIBIBERTY || defined USE_CXA_DEMANGLE +# define USE_DEMANGLE +#endif + extern char *cplus_demangle(const char *mangled, int options); const char *my_demangle(const char *function_name); @@ -1,8 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata - * Copyright (C) 2003,2004,2008,2009 Juan Cespedes - * Copyright (C) 2006 Ian Wienand + * Copyright (C) 2012, 2013, 2014 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -20,298 +18,628 @@ * 02110-1301 USA */ -#include <stdio.h> -#include <stdlib.h> #include <string.h> -#include <assert.h> +#include <stdlib.h> +#include <stdio.h> +#include "dict.h" -#include "common.h" +struct status_bits { + unsigned char taken : 1; + unsigned char erased : 1; +}; -/* - * Dictionary based on code by Morten Eriksen <mortene@sim.no>. - */ +static struct status_bits * +bitp(struct dict *dict, size_t n) +{ + return VECT_ELEMENT(&dict->status, struct status_bits, n); +} -struct dict_entry { - unsigned int hash; - void *key; - void *value; - struct dict_entry *next; -}; +void +dict_init(struct dict *dict, + size_t key_size, size_t value_size, + size_t (*hash1)(const void *), + int (*eq)(const void *, const void *), + size_t (*hash2)(size_t)) +{ + assert(hash1 != NULL); + assert(eq != NULL); -/* #define DICTTABLESIZE 97 */ -#define DICTTABLESIZE 997 /* Semi-randomly selected prime number. */ -/* #define DICTTABLESIZE 9973 */ -/* #define DICTTABLESIZE 99991 */ -/* #define DICTTABLESIZE 999983 */ + vect_init(&dict->keys, key_size); + vect_init(&dict->values, value_size); + VECT_INIT(&dict->status, struct status_bits); + dict->size = 0; -struct dict { - struct dict_entry *buckets[DICTTABLESIZE]; - unsigned int (*key2hash) (const void *); - int (*key_cmp) (const void *, const void *); + dict->hash1 = hash1; + dict->hash2 = hash2; + dict->eq = eq; +} + +struct clone_data { + struct dict *target; + int (*clone_key)(void *tgt, const void *src, void *data); + int (*clone_value)(void *tgt, const void *src, void *data); + void (*dtor_key)(void *tgt, void *data); + void (*dtor_value)(void *tgt, void *data); + void *data; }; -Dict * -dict_init(unsigned int (*key2hash) (const void *), - int (*key_cmp) (const void *, const void *)) +static enum callback_status +clone_cb(void *key, void *value, void *data) { - Dict *d; - int i; - - debug(DEBUG_FUNCTION, "dict_init()"); - - d = malloc(sizeof(Dict)); - if (!d) { - perror("malloc()"); - exit(1); + struct clone_data *clone_data = data; + + char nkey[clone_data->target->keys.elt_size]; + if (clone_data->clone_key == NULL) + memmove(nkey, key, sizeof(nkey)); + else if (clone_data->clone_key(&nkey, key, clone_data->data) < 0) + return CBS_STOP; + + char nvalue[clone_data->target->values.elt_size]; + if (clone_data->clone_value == NULL) { + memmove(nvalue, value, sizeof(nvalue)); + } else if (clone_data->clone_value(&nvalue, value, + clone_data->data) < 0) { + fail: + if (clone_data->clone_key != NULL) + clone_data->dtor_key(&nkey, clone_data->data); + return CBS_STOP; } - for (i = 0; i < DICTTABLESIZE; i++) { /* better use memset()? */ - d->buckets[i] = NULL; + + if (dict_insert(clone_data->target, nkey, nvalue) < 0) { + if (clone_data->clone_value != NULL) + clone_data->dtor_value(&nvalue, clone_data->data); + goto fail; } - d->key2hash = key2hash; - d->key_cmp = key_cmp; - return d; + + return CBS_CONT; } -void -dict_clear(Dict *d) { - int i; - struct dict_entry *entry, *nextentry; - - debug(DEBUG_FUNCTION, "dict_clear()"); - assert(d); - for (i = 0; i < DICTTABLESIZE; i++) { - for (entry = d->buckets[i]; entry != NULL; entry = nextentry) { - nextentry = entry->next; - free(entry); - } - d->buckets[i] = NULL; +int +dict_clone(struct dict *target, const struct dict *source, + int (*clone_key)(void *tgt, const void *src, void *data), + void (*dtor_key)(void *tgt, void *data), + int (*clone_value)(void *tgt, const void *src, void *data), + void (*dtor_value)(void *tgt, void *data), + void *data) +{ + assert((clone_key != NULL) == (dtor_key != NULL)); + assert((clone_value != NULL) == (dtor_value != NULL)); + + dict_init(target, source->keys.elt_size, source->values.elt_size, + source->hash1, source->eq, source->hash2); + struct clone_data clone_data = { + target, clone_key, clone_value, dtor_key, dtor_value, data + }; + if (dict_each((struct dict *)source, NULL, + clone_cb, &clone_data) != NULL) { + dict_destroy(target, dtor_key, dtor_value, data); + return -1; } - free(d); + return 0; +} + +size_t +dict_size(const struct dict *dict) +{ + return dict->size; } int -dict_enter(Dict *d, void *key, void *value) { - struct dict_entry *entry, *newentry; - unsigned int hash; - unsigned int bucketpos; +dict_empty(const struct dict *dict) +{ + return dict->size == 0; +} - debug(DEBUG_FUNCTION, "dict_enter()"); +struct destroy_data { + void (*dtor_key)(void *tgt, void *data); + void (*dtor_value)(void *tgt, void *data); + void *data; +}; - hash = d->key2hash(key); - bucketpos = hash % DICTTABLESIZE; +static enum callback_status +destroy_cb(void *key, void *value, void *data) +{ + struct destroy_data *destroy_data = data; + if (destroy_data->dtor_key) + destroy_data->dtor_key(key, destroy_data->data); + if (destroy_data->dtor_value) + destroy_data->dtor_value(value, destroy_data->data); + return CBS_CONT; +} - assert(d); - newentry = malloc(sizeof(struct dict_entry)); - if (!newentry) { - perror("malloc"); - exit(1); +void +dict_destroy(struct dict *dict, + void (*dtor_key)(void *tgt, void *data), + void (*dtor_value)(void *tgt, void *data), + void *data) +{ + /* Some slots are unused (the corresponding keys and values + * are uninitialized), so we can't call dtors for them. + * Iterate DICT instead. */ + if (dtor_key != NULL || dtor_value != NULL) { + struct destroy_data destroy_data = { + dtor_key, dtor_value, data + }; + dict_each(dict, NULL, destroy_cb, &destroy_data); } - newentry->hash = hash; - newentry->key = key; - newentry->value = value; - newentry->next = NULL; - - entry = d->buckets[bucketpos]; - while (entry && entry->next) - entry = entry->next; + vect_destroy(&dict->keys, NULL, NULL); + vect_destroy(&dict->values, NULL, NULL); + vect_destroy(&dict->status, NULL, NULL); +} - if (entry) - entry->next = newentry; - else - d->buckets[bucketpos] = newentry; +static size_t +default_secondary_hash(size_t pos) +{ + return pos % 97 + 1; +} - debug(3, "new dict entry at %p[%d]: (%p,%p)", d, bucketpos, key, value); - return 0; +static size_t +small_secondary_hash(size_t pos) +{ + return 1; } -void * -dict_remove(Dict *d, void *key) -{ - assert(d != NULL); - debug(DEBUG_FUNCTION, "dict_remove(%p)", key); - - unsigned int hash = d->key2hash(key); - unsigned int bucketpos = hash % DICTTABLESIZE; - - struct dict_entry **entryp; - for (entryp = &d->buckets[bucketpos]; (*entryp) != NULL; - entryp = &(*entryp)->next) { - struct dict_entry *entry = *entryp; - if (hash != entry->hash) - continue; - if (d->key_cmp(key, entry->key) == 0) { - *entryp = entry->next; - void *value = entry->value; - free(entry); - return value; - } - } - return NULL; +static inline size_t +n(struct dict *dict) +{ + return vect_size(&dict->keys); } -void * -dict_find_entry(Dict *d, const void *key) +static inline size_t (* +hash2(struct dict *dict))(size_t) { - unsigned int hash; - unsigned int bucketpos; - struct dict_entry *entry; + if (dict->hash2 != NULL) + return dict->hash2; + else if (n(dict) < 100) + return small_secondary_hash; + else + return default_secondary_hash; +} - debug(DEBUG_FUNCTION, "dict_find_entry()"); +static void * +getkey(struct dict *dict, size_t pos) +{ + return ((unsigned char *)dict->keys.data) + + dict->keys.elt_size * pos; +} - hash = d->key2hash(key); - bucketpos = hash % DICTTABLESIZE; +static void * +getvalue(struct dict *dict, size_t pos) +{ + return ((unsigned char *)dict->values.data) + + dict->values.elt_size * pos; +} - assert(d); - for (entry = d->buckets[bucketpos]; entry; entry = entry->next) { - if (hash != entry->hash) { - continue; +static size_t +find_slot(struct dict *dict, const void *key, + int *foundp, int *should_rehash, size_t *pi) +{ + assert(n(dict) > 0); + size_t pos = dict->hash1(key) % n(dict); + size_t pos0 = -1; + size_t d = hash2(dict)(pos); + size_t i = 0; + *foundp = 0; + + /* We skip over any taken or erased slots. But we remember + * the first erased that we find, and if we don't find the key + * later, we return that position. */ + for (; bitp(dict, pos)->taken || bitp(dict, pos)->erased; + pos = (pos + d) % n(dict)) { + if (pos0 == (size_t)-1 && bitp(dict, pos)->erased) + pos0 = pos; + + /* If there is a loop, but we've seen an erased + * element, take that one. Otherwise give up. */ + if (++i > dict->size) { + if (pos0 != (size_t)-1) + break; + return (size_t)-1; } - if (!d->key_cmp(key, entry->key)) { + + if (bitp(dict, pos)->taken + && dict->eq(getkey(dict, pos), key)) { + *foundp = 1; break; } } - return entry ? entry->value : NULL; + + if (!*foundp && pos0 != (size_t)-1) + pos = pos0; + + /* If the hash table degraded into a linked list, request a + * rehash. */ + if (should_rehash != NULL) + *should_rehash = i > 10 && i > n(dict) / 10; + + if (pi != NULL) + *pi = i; + return pos; } -void -dict_apply_to_all(Dict *d, - void (*func) (void *key, void *value, void *data), void *data) { - int i; +static enum callback_status +rehash_move(void *key, void *value, void *data) +{ + if (dict_insert(data, key, value) < 0) + return CBS_STOP; + else + return CBS_CONT; +} + +static int +rehash(struct dict *dict, size_t nn) +{ + assert(nn != n(dict)); + int ret = -1; + + struct dict tmp; + dict_init(&tmp, dict->keys.elt_size, dict->values.elt_size, + dict->hash1, dict->eq, dict->hash2); + + /* To honor all invariants (so that we can safely call + * dict_destroy), we first make a request to _reserve_ enough + * room in all vectors. This has no observable effect on + * contents of vectors. */ + if (vect_reserve(&tmp.keys, nn) < 0 + || vect_reserve(&tmp.values, nn) < 0 + || vect_reserve(&tmp.status, nn) < 0) + goto done; + + /* Now that we know that there is enough size in vectors, we + * simply bump the size. */ + tmp.keys.size = nn; + tmp.values.size = nn; + size_t old_size = tmp.status.size; + tmp.status.size = nn; + memset(VECT_ELEMENT(&tmp.status, struct status_bits, old_size), + 0, (tmp.status.size - old_size) * tmp.status.elt_size); + + /* At this point, TMP is once more an empty dictionary with NN + * slots. Now move stuff from DICT to TMP. */ + if (dict_each(dict, NULL, rehash_move, &tmp) != NULL) + goto done; + + /* And now swap contents of DICT and TMP, and we are done. */ + { + struct dict tmp2 = *dict; + *dict = tmp; + tmp = tmp2; + } - debug(DEBUG_FUNCTION, "dict_apply_to_all()"); + ret = 0; - if (!d) { - return; +done: + /* We only want to release the containers, not the actual data + * that they hold, so it's fine if we don't pass any dtor. */ + dict_destroy(&tmp, NULL, NULL, NULL); + return ret; + +} + +static const size_t primes[] = { + 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, + 8191, 16381, 32749, 65521, 130981, 0 +}; + +static size_t +larger_size(size_t current) +{ + if (current == 0) + return primes[0]; + + if (current < primes[sizeof(primes)/sizeof(*primes) - 2]) { + size_t i; + for (i = 0; primes[i] != 0; ++i) + if (primes[i] > current) + return primes[i]; + abort(); } - for (i = 0; i < DICTTABLESIZE; i++) { - struct dict_entry *entry = d->buckets[i]; - while (entry) { - func(entry->key, entry->value, data); - entry = entry->next; + + /* We ran out of primes, so invent a new one. The following + * gives primes until about 17M elements (and then some more + * later). */ + return 2 * current + 6585; +} + +static size_t +smaller_size(size_t current) +{ + if (current <= primes[0]) + return primes[0]; + + if (current <= primes[sizeof(primes)/sizeof(*primes) - 2]) { + size_t i; + size_t prev = 0; + for (i = 0; primes[i] != 0; ++i) { + if (primes[i] >= current) + return prev; + prev = primes[i]; } + abort(); } + + return (current - 6585) / 2; } -/*****************************************************************************/ +int +dict_insert(struct dict *dict, void *key, void *value) +{ + if (n(dict) == 0 || dict->size > 0.7 * n(dict)) + rehash: + if (rehash(dict, larger_size(n(dict))) < 0) + return -1; + + int found; + int should_rehash; + size_t slot_n = find_slot(dict, key, &found, &should_rehash, NULL); + if (slot_n == (size_t)-1) + return -1; + if (found) + return 1; + assert(!bitp(dict, slot_n)->taken); + + /* If rehash was requested, do that, and retry. But just live + * with it for apparently sparse tables. No resizing can fix + * a rubbish hash. */ + if (should_rehash && dict->size > 0.3 * n(dict)) + goto rehash; + + memmove(getkey(dict, slot_n), key, dict->keys.elt_size); + memmove(getvalue(dict, slot_n), value, dict->values.elt_size); + + bitp(dict, slot_n)->taken = 1; + bitp(dict, slot_n)->erased = 0; + ++dict->size; + + return 0; +} -unsigned int -dict_key2hash_string(const void *key) +void * +dict_find(struct dict *dict, const void *key) { - const char *s = (const char *)key; - unsigned int total = 0, shift = 0; + if (dict->size == 0) + return NULL; + assert(n(dict) > 0); + + int found; + size_t slot_n = find_slot(dict, key, &found, NULL, NULL); + if (found) + return getvalue(dict, slot_n); + else + return NULL; +} - assert(key); - while (*s) { - total = total ^ ((*s) << shift); - shift += 5; - if (shift > 24) - shift -= 24; - s++; +int +dict_erase(struct dict *dict, const void *key, + void (*dtor_key)(void *tgt, void *data), + void (*dtor_value)(void *tgt, void *data), + void *data) +{ + int found; + size_t i; + size_t slot_n = find_slot(dict, key, &found, NULL, &i); + if (!found) + return -1; + + if (dtor_key != NULL) + dtor_key(getkey(dict, slot_n), data); + if (dtor_value != NULL) + dtor_value(getvalue(dict, slot_n), data); + + bitp(dict, slot_n)->taken = 0; + bitp(dict, slot_n)->erased = 1; + --dict->size; + + if (dict->size < 0.3 * n(dict)) { + size_t smaller = smaller_size(n(dict)); + if (smaller != n(dict)) + /* Don't mind if it fails when shrinking. */ + rehash(dict, smaller); } - return total; + + return 0; +} + +void * +dict_each(struct dict *dict, void *start_after, + enum callback_status (*cb)(void *, void *, void *), void *data) +{ + size_t i; + if (start_after != NULL) + i = ((start_after - dict->keys.data) / dict->keys.elt_size) + 1; + else + i = 0; + + for (; i < dict->keys.size; ++i) + if (bitp(dict, i)->taken && !bitp(dict, i)->erased) { + void *key = getkey(dict, i); + if (cb(key, getvalue(dict, i), data) != CBS_CONT) + return key; + } + + return NULL; +} + +size_t +dict_hash_int(const int *key) +{ + return (size_t)(*key * 2654435761U); } int -dict_key_cmp_string(const void *key1, const void *key2) +dict_eq_int(const int *key1, const int *key2) { - assert(key1); - assert(key2); - return strcmp((const char *)key1, (const char *)key2); + return *key1 == *key2; } -unsigned int -dict_key2hash_int(const void *key) +size_t +dict_hash_uint64(const uint64_t *key) { - return (unsigned long)key; + int const a = (int) *key; + int const b = (int) (*key >> 32); + return dict_hash_int (&a) ^ dict_hash_int (&b); } int -dict_key_cmp_int(const void *key1, const void *key2) +dict_eq_uint64(const uint64_t *key1, const uint64_t *key2) { - return key1 - key2; + return *key1 == *key2; } -Dict * -dict_clone2(Dict * old, void * (*key_clone)(void *, void *), - void * (*value_clone)(void *, void *), void * data) +size_t +dict_hash_string(const char **key) { - Dict *d; - int i; + size_t h = 5381; + const char *str = *key; + while (*str != 0) + h = h * 33 ^ *str++; + return h; +} + +int +dict_eq_string(const char **key1, const char **key2) +{ + return strcmp(*key1, *key2) == 0; +} - debug(DEBUG_FUNCTION, "dict_clone()"); +void +dict_dtor_string(const char **key, void *data) +{ + free((char *)*key); +} - d = malloc(sizeof(Dict)); - if (!d) { - perror("malloc()"); - exit(1); - } - memcpy(d, old, sizeof(Dict)); - for (i = 0; i < DICTTABLESIZE; i++) { /* better use memset()? */ - struct dict_entry *de_old; - struct dict_entry **de_new; - - de_old = old->buckets[i]; - de_new = &d->buckets[i]; - while (de_old) { - void * nkey, * nval; - *de_new = malloc(sizeof(struct dict_entry)); - if (!*de_new) { - perror("malloc()"); - exit(1); - } - memcpy(*de_new, de_old, sizeof(struct dict_entry)); - - /* The error detection is rather weak :-/ */ - nkey = key_clone(de_old->key, data); - if (nkey == NULL && de_old->key != NULL) { - perror("key_clone"); - err: - /* XXX Will this actually work? We - * simply memcpy the old dictionary - * over up there. */ - dict_clear(d); - free(de_new); - return NULL; - } +int +dict_clone_string(const char **tgt, const char **src, void *data) +{ + *tgt = strdup(*src); + return *tgt != NULL ? 0 : -1; +} - nval = value_clone(de_old->value, data); - if (nval == NULL && de_old->value != NULL) { - perror("value_clone"); - goto err; - } +#ifdef TEST +static enum callback_status +dump(int *key, int *value, void *data) +{ + char *seen = data; + assert(seen[*key] == 0); + seen[*key] = 1; + assert(*value == *key * 2 + 1); + return CBS_STOP; +} - (*de_new)->key = nkey; - (*de_new)->value = nval; - de_new = &(*de_new)->next; - de_old = de_old->next; - } - } - return d; +static size_t +dict_hash_int_silly(const int *key) +{ + return *key % 10; } -struct wrap_clone_cb +static void +verify(struct dict *di, size_t len, char *seen) { - void * (*key_clone)(void *); - void * (*value_clone)(void *); -}; + size_t ct = 0; + int *it; + for (it = NULL; (it = DICT_EACH(di, int, int, it, dump, seen)) != NULL;) + ct++; + assert(ct == len); + memset(seen, 0, len); +} -static void * -value_clone_1(void * arg, void * data) +static enum callback_status +fill_keys(int *key, int *value, void *data) { - return ((struct wrap_clone_cb *)data)->value_clone(arg); + int *array = data; + array[++array[0]] = *key; + return CBS_CONT; } -static void * -key_clone_1(void * arg, void * data) +static void +test1(void) { - return ((struct wrap_clone_cb *)data)->key_clone(arg); + struct dict di; + DICT_INIT(&di, int, int, dict_hash_int, dict_eq_int, NULL); + + char seen[100000] = {}; + size_t i; + for (i = 0; i < sizeof(seen); ++i) { + int key = i; + int value = 2 * i + 1; + DICT_INSERT(&di, &key, &value); + int *valp = DICT_FIND_REF(&di, &key, int); + assert(valp != NULL); + assert(*valp == value); + assert(dict_size(&di) == i + 1); + } + + verify(&di, sizeof(seen), seen); + + struct dict d2; + DICT_CLONE(&d2, &di, int, int, NULL, NULL, NULL, NULL, NULL); + DICT_DESTROY(&di, int, int, NULL, NULL, NULL); + verify(&d2, sizeof(seen), seen); + + /* Now we try to gradually erase all elements. We can't erase + * inside a DICT_EACH call, so copy first keys to a separate + * memory area first. */ + int keys[d2.size + 1]; + size_t ct = 0; + keys[0] = 0; + DICT_EACH(&d2, int, int, NULL, fill_keys, keys); + for (i = 0; i < (size_t)keys[0]; ++i) { + assert(DICT_ERASE(&d2, &keys[i + 1], int, + NULL, NULL, NULL) == 0); + ++ct; + } + assert(ct == sizeof(seen)); + DICT_DESTROY(&d2, int, int, NULL, NULL, NULL); +} + +static void +test_erase(void) +{ + int i; + + /* To test erase, we need a relatively bad hash function, so + * that there are some overlapping chains in the table. */ + struct dict d2; + DICT_INIT(&d2, int, int, dict_hash_int_silly, dict_eq_int, NULL); + const int limit = 500; + for (i = 0; i < limit; ++i) { + int key = 2 * i + 1; + int value = 2 * key + 1; + DICT_INSERT(&d2, &key, &value); + } + + /* Now we try to delete each of the keys, and verify that none + * of the chains was broken. */ + for (i = 0; i < limit; ++i) { + struct dict copy; + DICT_CLONE(©, &d2, int, int, NULL, NULL, NULL, NULL, NULL); + int key = 2 * i + 1; + DICT_ERASE(©, &key, int, NULL, NULL, NULL); + assert(dict_size(©) == dict_size(&d2) - 1); + + int j; + for (j = 0; j < limit; ++j) { + key = 2 * j + 1; + int *valp = DICT_FIND_REF(©, &key, int); + if (i != j) { + assert(valp != NULL); + assert(*valp == 2 * key + 1); + } else { + assert(valp == NULL); + } + } + + DICT_DESTROY(©, int, int, NULL, NULL, NULL); + } + DICT_DESTROY(&d2, int, int, NULL, NULL, NULL); } -Dict * -dict_clone(Dict * old, void * (*key_clone)(void *), - void * (*value_clone)(void *)) +int main(int argc, char *argv[]) { - struct wrap_clone_cb cb = { key_clone, value_clone }; - return dict_clone2(old, &key_clone_1, &value_clone_1, &cb); + test1(); + test_erase(); + return 0; } + +#endif @@ -1,9 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata - * Copyright (C) 2003,2004,2008,2009 Juan Cespedes - * Copyright (C) 2006 Ian Wienand - * Copyright (C) ???? Morten Eriksen <mortene@sim.no> + * Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,35 +18,236 @@ * 02110-1301 USA */ -#ifndef _DICT_H_ -#define _DICT_H_ +#ifndef DICT_H +#define DICT_H -/* - * Dictionary based on code by Morten Eriksen <mortene@sim.no>. - */ +#include <stddef.h> +#include <stdint.h> +#include <assert.h> +#include "vect.h" + +struct dict { + /* The invariant is that KEYS, VALUES and STATUS are of the + * same size. */ + struct vect keys; + struct vect values; + struct vect status; + size_t size; + + size_t (*hash1)(const void *); + int (*eq)(const void *, const void *); + size_t (*hash2)(size_t); +}; + +/* Initialize a dictionary DICT. The dictionary will hold keys of the + * size KEY_SIZE and values of the size VALUE_SIZE. HASH1 and HASH2 + * are, respectively, primary and secondary hashing functions. The + * latter may be NULL, in which case a default internal hash is used. + * EQ is a callback for comparing two keys. */ +void dict_init(struct dict *dict, + size_t key_size, size_t value_size, + size_t (*hash1)(const void *), + int (*eq)(const void *, const void *), + size_t (*hash2)(size_t)); + +/* Wrapper around dict_init. Initializes a dictionary DITCP which + * will hold keys of type KEY_TYPE and values of type VALUE_TYPE. + * Other arguments as above. */ +#define DICT_INIT(DICTP, KEY_TYPE, VALUE_TYPE, HASH1, EQ, HASH2) \ + ({ \ + /* Check that callbacks are typed properly. */ \ + size_t (*_hash1_callback)(const KEY_TYPE *) = HASH1; \ + int (*_eq_callback)(const KEY_TYPE *, const KEY_TYPE *) = EQ; \ + dict_init(DICTP, sizeof(KEY_TYPE), sizeof(VALUE_TYPE), \ + (size_t (*)(const void *))_hash1_callback, \ + (int (*)(const void *, const void *))_eq_callback, \ + HASH2); \ + }) + +/* Clone SOURCE to TARGET. For cloning slots, CLONE_KEY and + * CLONE_VALUE are called. These callbacks return 0 on success or a + * negative value on failure. If any of the callbacks is NULL, the + * default action is simple memmove. Returns 0 on success. If the + * cloning fails for any reason, already-cloned keys and values are + * destroyed again by DTOR_KEY and DTOR_VALUE callbacks (if non-NULL), + * and the function returns a negative value. DATA is passed to all + * callbacks verbatim. */ +int dict_clone(struct dict *target, const struct dict *source, + int (*clone_key)(void *tgt, const void *src, void *data), + void (*dtor_key)(void *tgt, void *data), + int (*clone_value)(void *tgt, const void *src, void *data), + void (*dtor_value)(void *tgt, void *data), + void *data); + +/* Clone SRC_DICTP, which holds KEY_TYPE-VALUE_TYPE pairs, into + * TGT_DICTP. Other arguments and return codes as above. */ +#define DICT_CLONE(TGT_DICTP, SRC_DICTP, KEY_TYPE, VALUE_TYPE, \ + CLONE_KEY, DTOR_KEY, CLONE_VALUE, DTOR_VALUE, DATA) \ + /* xxx GCC-ism necessary to get in the safety latches. */ \ + ({ \ + const struct dict *_source_d = (SRC_DICTP); \ + assert(_source_d->keys.elt_size == sizeof(KEY_TYPE)); \ + assert(_source_d->values.elt_size == sizeof(VALUE_TYPE)); \ + /* Check that callbacks are typed properly. */ \ + void (*_key_dtor_cb)(KEY_TYPE *, void *) = DTOR_KEY; \ + int (*_key_clone_cb)(KEY_TYPE *, const KEY_TYPE *, \ + void *) = CLONE_KEY; \ + void (*_value_dtor_cb)(VALUE_TYPE *, void *) = DTOR_VALUE; \ + int (*_value_clone_cb)(VALUE_TYPE *, const VALUE_TYPE *, \ + void *) = CLONE_VALUE; \ + dict_clone((TGT_DICTP), _source_d, \ + (int (*)(void *, const void *, \ + void *))_key_clone_cb, \ + (void (*)(void *, void *))_key_dtor_cb, \ + (int (*)(void *, const void *, \ + void *))_value_clone_cb, \ + (void (*)(void *, void *))_value_dtor_cb, \ + (DATA)); \ + }) + +/* Return number of key-value pairs stored in DICT. */ +size_t dict_size(const struct dict *dict); + +/* Emptiness predicate. */ +int dict_empty(const struct dict *dict); + +/* Insert into DICT a pair of KEY and VALUE. Returns 0 if insertion + * was successful, a negative value on error, or a positive value if + * this key is already present in the table. */ +int dict_insert(struct dict *dict, void *key, void *value); + +/* Insert into DICT a pair of KEY and VALUE. See dict_insert for + * details. In addition, make a check whether DICTP holds elements of + * the right size. */ +#define DICT_INSERT(DICTP, KEYP, VALUEP) \ + (assert((DICTP)->keys.elt_size == sizeof(*(KEYP))), \ + assert((DICTP)->values.elt_size == sizeof(*(VALUEP))), \ + dict_insert((DICTP), (KEYP), (VALUEP))) + +/* Find in DICT a value corresponding to KEY and return a pointer to + * it. Returns NULL if the key was not found. */ +void *dict_find(struct dict *dict, const void *key); + +/* Look into DICTP for a key *KEYP. Return a boolean indicating + * whether the key was found. */ +#define DICT_HAS_KEY(DICTP, KEYP) \ + (assert((DICTP)->keys.elt_size == sizeof(*(KEYP))), \ + dict_find((DICTP), (KEYP)) != NULL) + +/* Find in DICTP a value of type VALUE_TYPE corresponding to KEYP and + * return a pointer (VALUE_TYPE *) to it. Returns NULL if the key was + * not found. */ +#define DICT_FIND_REF(DICTP, KEYP, VALUE_TYPE) \ + (assert((DICTP)->keys.elt_size == sizeof(*(KEYP))), \ + (VALUE_TYPE *)dict_find((DICTP), (KEYP))) + +/* Find in DICTP a value of type VALUE_TYPE corresponding to KEYP and + * copy it to the memory pointed-to by VAR. Returns 0 on success, or + * a negative value if the key was not found. */ +#define DICT_FIND_VAL(DICTP, KEYP, VAR) \ + ({ \ + assert((DICTP)->keys.elt_size == sizeof(*(KEYP))); \ + assert((DICTP)->values.elt_size == sizeof((VAR))); \ + void *_ptr = dict_find((DICTP), (KEYP)); \ + if (_ptr != NULL) \ + memcpy((VAR), _ptr, (DICTP)->values.elt_size); \ + _ptr != NULL ? 0 : -1; \ + }) + +/* Erase from DICT the entry corresponding to KEY. Returns a negative + * value if the key was not found, or 0 on success. DTOR_KEY and + * DTOR_VALUE, if non-NULL, are called to destroy the erased + * value. */ +int dict_erase(struct dict *dict, const void *key, + void (*dtor_key)(void *tgt, void *data), + void (*dtor_value)(void *tgt, void *data), + void *data); + +/* Erase from DICTP a value of type VALUE_TYPE corresponding to + * KEYP. */ +#define DICT_ERASE(DICTP, KEYP, VALUE_TYPE, DTOR_KEY, DTOR_VALUE, DATA) \ + ({ \ + struct dict *_d = (DICTP); \ + assert(_d->keys.elt_size == sizeof(*KEYP)); \ + assert(_d->values.elt_size == sizeof(VALUE_TYPE)); \ + /* Check that callbacks are typed properly. */ \ + void (*_value_dtor_cb)(VALUE_TYPE *, void *) = DTOR_VALUE; \ + dict_erase(_d, (KEYP), (DTOR_KEY), \ + (void (*)(void *, void *))_value_dtor_cb, \ + (DATA)); \ + }) + +/* Destroy DICT. If KEY_DTOR is non-NULL, then it's called on each + * key stored in DICT. Similarly for VALUE_DTOR. DATA is passed to + * DTOR's verbatim. The memory pointed-to by DICT is not freed. */ +void dict_destroy(struct dict *dict, + void (*dtor_key)(void *tgt, void *data), + void (*dtor_value)(void *tgt, void *data), + void *data); + +/* Destroy DICTP, which holds keys of type KEY_TYPE and values of type + * VALUE_TYPE, using DTOR. */ +#define DICT_DESTROY(DICTP, KEY_TYPE, VALUE_TYPE, DTOR_KEY, DTOR_VALUE, DATA) \ + do { \ + struct dict *_d = (DICTP); \ + assert(_d->keys.elt_size == sizeof(KEY_TYPE)); \ + assert(_d->values.elt_size == sizeof(VALUE_TYPE)); \ + /* Check that callbacks are typed properly. */ \ + void (*_key_dtor_cb)(KEY_TYPE *, void *) = DTOR_KEY; \ + void (*_value_dtor_cb)(VALUE_TYPE *, void *) = DTOR_VALUE; \ + dict_destroy(_d, (void (*)(void *, void *))_key_dtor_cb, \ + (void (*)(void *, void *))_value_dtor_cb, \ + (DATA)); \ + } while (0) + +/* Iterate through DICT. See callback.h for notes on iteration + * interfaces. Callback arguments are key, value, DATA. Note that + * the iteration over DICT is more expensive than in other containers: + * while CB is only called for items present in the table, and is + * therefore O(number of elements), the iterator needs to go through + * all the table, which is proportional to O(size of table). + * START_AFTER and the returned iterator are key where the iteration + * stopped. */ +void *dict_each(struct dict *dict, void *start_after, + enum callback_status (*cb)(void *, void *, void *), void *data); + +#define DICT_EACH(DICTP, KEY_TYPE, VALUE_TYPE, START_AFTER, CB, DATA) \ + /* xxx GCC-ism necessary to get in the safety latches. */ \ + ({ \ + assert((DICTP)->keys.elt_size == sizeof(KEY_TYPE)); \ + assert((DICTP)->values.elt_size == sizeof(VALUE_TYPE)); \ + /* Check that CB is typed properly. */ \ + enum callback_status (*_cb)(KEY_TYPE *, VALUE_TYPE *, \ + void *) = CB; \ + KEY_TYPE *_start_after = (START_AFTER); \ + (KEY_TYPE *)dict_each((DICTP), _start_after, \ + (enum callback_status \ + (*)(void *, void *, void *))_cb, \ + (DATA)); \ + }) + +/* A callback for hashing integers. */ +size_t dict_hash_int(const int *key); + +/* An equality predicate callback for integers. */ +int dict_eq_int(const int *key1, const int *key2); + +/* A callback for hashing uint64_t. */ +size_t dict_hash_uint64(const uint64_t *key); -typedef struct dict Dict; +/* An equality predicate callback for uint64_t. */ +int dict_eq_uint64(const uint64_t *key1, const uint64_t *key2); -extern Dict *dict_init(unsigned int (*key2hash) (const void *), - int (*key_cmp) (const void *, const void *)); -extern void dict_clear(Dict *d); -extern int dict_enter(Dict *d, void *key, void *value); -extern void *dict_remove(Dict *d, void *key); -extern void *dict_find_entry(Dict *d, const void *key); -extern void dict_apply_to_all(Dict *d, - void (*func) (void *key, void *value, void *data), - void *data); +/* A callback for hashing NULL-terminated strings. */ +size_t dict_hash_string(const char **key); -extern unsigned int dict_key2hash_string(const void *key); -extern int dict_key_cmp_string(const void *key1, const void *key2); +/* An equality predicate callback for strings. */ +int dict_eq_string(const char **key1, const char **key2); -extern unsigned int dict_key2hash_int(const void *key); -extern int dict_key_cmp_int(const void *key1, const void *key2); +/* A dtor which calls 'free' on keys in a table. */ +void dict_dtor_string(const char **key, void *data); -extern Dict * dict_clone(Dict *old, void * (*key_clone)(void*), void * (*value_clone)(void*)); -extern Dict * dict_clone2(Dict * old, - void * (* key_clone)(void * key, void * data), - void * (* value_clone)(void * value, void * data), - void * data); +/* A cloner that calls 'strdup' on keys in a table. */ +int dict_clone_string(const char **tgt, const char **src, void *data); -#endif /* _DICT_H_ */ +#endif /* DICT_H */ diff --git a/dwarf_prototypes.c b/dwarf_prototypes.c new file mode 100644 index 0000000..9c36904 --- /dev/null +++ b/dwarf_prototypes.c @@ -0,0 +1,1050 @@ +/* Copyright Dima Kogan <dima@secretsauce.net> + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of version 2 of the GNU General Public License as published by the + * Free Software Foundation. + * + */ +#include <stdio.h> +#include <elfutils/libdwfl.h> +#include <dwarf.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> + +#include "config.h" +#include "prototype.h" +#include "type.h" +#include "param.h" +#include "dict.h" +#include "lens.h" +#include "lens_enum.h" +#include "value.h" +#include "expr.h" +#include "library.h" +#include "options.h" +#include "filter.h" +#include "debug.h" + +#define complain(die, format, ...) \ + debug(DEBUG_FUNCTION, "%s() die '%s' @ 0x%lx: " format, \ + __func__, dwarf_diename(die), dwarf_dieoffset(die), \ + ##__VA_ARGS__) + +#define NEXT_SIBLING(die) \ + int res = dwarf_siblingof(die, die); \ + if (res == 0) continue; /* sibling exists */ \ + if (res < 0) return false; /* error */ \ + break /* no sibling exists */ + +static struct arg_type_info *get_type(int *newly_allocated_info, + Dwarf_Die *type_die, + struct protolib *plib, + struct dict *type_dieoffset_hash); + + + + +// debugging functions to dump types that I already parsed +#ifdef DUMP_PROTOTYPES +static bool _dump_ltrace_tree(const struct arg_type_info *info, int indent) +{ + if (indent > 7) { + fprintf(stderr, "%*s%p ...\n", indent*4, "", (void*)info); + return true; + } + + if (info == NULL) { + fprintf(stderr, "%*s%p NULL\n", indent*4, "", (void*)info); + return true; + } + + switch (info->type) { + case ARGTYPE_VOID: + fprintf(stderr, "%*s%p void\n", indent*4, "", (void*)info); + break; + + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + case ARGTYPE_CHAR: + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + case ARGTYPE_FLOAT: + case ARGTYPE_DOUBLE: + fprintf(stderr, "%*s%p base\n", indent*4, "", (void*)info); + break; + + case ARGTYPE_ARRAY: + fprintf(stderr, "%*s%p array. elements not printed\n", indent*4, + "", (void*)info); + break; + + case ARGTYPE_POINTER: + fprintf(stderr, "%*s%p pointer to...\n", indent*4, + "", (void*)info); + _dump_ltrace_tree(info->u.ptr_info.info, indent+1); + break; + + case ARGTYPE_STRUCT: + fprintf(stderr, "%*s%p struct...\n", indent*4, + "", (void*)info); + struct struct_field + { + struct arg_type_info *info; + int own_info; + } *elements = (struct struct_field*)info->u.entries.data; + unsigned int i; + for(i=0; i<info->u.entries.size; i++) + _dump_ltrace_tree(elements[i].info, indent+1); + break; + + default: + fprintf(stderr, "%*s%p unknown type\n", indent*4, + "", (void*)info); + return false;; + } + + return true; +} + +static bool dump_ltrace_tree(const struct arg_type_info *info) +{ + return _dump_ltrace_tree(info, 0); +} +#endif + + +// pulls a numerical value out of a particular attribute in a die. Returns true +// if successful. The result is returned in *result. Note that this is cast to +// (uint64_t), regardless of the actual type of the input +static bool get_die_numeric(uint64_t *result, + Dwarf_Die *die, unsigned int attr_name) +{ + Dwarf_Attribute attr ; + + union { + Dwarf_Word udata; + Dwarf_Sword sdata; + Dwarf_Addr addr; + bool flag; + } u; + + if (dwarf_attr(die, attr_name, &attr) == NULL) + return false; + + unsigned int form = dwarf_whatform(&attr); + +#define PROCESS_NUMERIC(type) \ + if (dwarf_form ## type(&attr, &u.type) != 0) \ + return false; \ + *result = (uint64_t)u.type; \ + return true + + + switch (form) { + case DW_FORM_addr: + PROCESS_NUMERIC(addr); + + case DW_FORM_data1: + case DW_FORM_data2: + case DW_FORM_data4: + case DW_FORM_data8: + case DW_FORM_udata: + PROCESS_NUMERIC(udata); + + case DW_FORM_sdata: + PROCESS_NUMERIC(sdata); + + case DW_FORM_flag: + PROCESS_NUMERIC(flag); + + default: + complain(die, "Unknown numeric form %d for attr_name: %d", + form, attr_name); + return false; + } +#undef PROCESS_NUMERIC +} + +static bool get_integer_base_type(enum arg_type *type, int byte_size, + bool is_signed) +{ + // not using a switch() here because sizeof(int) == sizeof(long) on some + // architectures, and removing that case for those arches is a pain + if (byte_size == sizeof(char)) { + *type = ARGTYPE_CHAR; + return true; + } + + if (byte_size == sizeof(short)) { + *type = is_signed ? ARGTYPE_SHORT : ARGTYPE_USHORT; + return true; + } + + if (byte_size == sizeof(int)) { + *type = is_signed ? ARGTYPE_INT : ARGTYPE_UINT; + return true; + } + + if (byte_size == sizeof(long)) { + *type = is_signed ? ARGTYPE_LONG : ARGTYPE_ULONG; + return true; + } + + return false; +} + +// returns an ltrace ARGTYPE_XXX base type from the given die. If we dont +// support a particular type (or an error occurred), I regturn ARGTYPE_VOID +static enum arg_type get_base_type(Dwarf_Die *die) +{ + uint64_t encoding; + if (!get_die_numeric(&encoding, die, DW_AT_encoding)) + return ARGTYPE_VOID; + + if (encoding == DW_ATE_void) + return ARGTYPE_VOID; + + if (encoding == DW_ATE_signed_char || encoding == DW_ATE_unsigned_char) + return ARGTYPE_CHAR; + + uint64_t byte_size; + if (!get_die_numeric(&byte_size, die, DW_AT_byte_size)) + return ARGTYPE_VOID; + + if (encoding == DW_ATE_signed || + encoding == DW_ATE_unsigned || + encoding == DW_ATE_boolean) { + + bool is_signed = (encoding == DW_ATE_signed); + + enum arg_type type; + if (!get_integer_base_type(&type, (int)byte_size, is_signed)) { + complain(die, "Unknown integer base type. " + "Using 'void'"); + return ARGTYPE_VOID; + } + return type; + } + + if (encoding == DW_ATE_float) { + switch (byte_size) { + case sizeof(float): + return ARGTYPE_FLOAT; + + case sizeof(double): + return ARGTYPE_DOUBLE; + + default: + // things like long doubles. ltrace has no support yet, + // so I just say "void" + return ARGTYPE_VOID; + } + } + +#if 0 + if (encoding == DW_ATE_complex_float) { + switch (byte_size) { + case 2*sizeof(float): + return ARGTYPE_FLOAT; + + case 2*sizeof(double): + return ARGTYPE_DOUBLE; + + default: + // things like long doubles. ltrace has no support yet, + // so I just say "void" + return ARGTYPE_VOID; + } + } +#endif + + // Unknown encoding. I just say void + complain(die, "Unknown base type. Returning 'void'"); + return ARGTYPE_VOID; +} + +static bool get_type_die(Dwarf_Die *type_die, Dwarf_Die *die) +{ + Dwarf_Attribute attr; + return + dwarf_attr(die, DW_AT_type, &attr) != NULL && + dwarf_formref_die(&attr, type_die) != NULL; +} + + + +// type_dieoffset_hash dictionary callbacks +static size_t dwarf_die_hash(const void *x) +{ + return *(const Dwarf_Off*)x; +} +static int dwarf_die_eq(const void *a, const void *b) +{ + return *(const Dwarf_Off*)a == *(const Dwarf_Off*)b; +} + + +// returns a newly-allocated art_type_info*, or NULL on error +static struct arg_type_info *get_enum(Dwarf_Die *parent, + struct dict *type_dieoffset_hash) +{ + +#define CLEANUP_AND_RETURN_ERROR(ret) do { \ + if (dupkey != NULL) \ + free((void*)dupkey); \ + if (value != NULL) { \ + value_destroy(value); \ + free(value); \ + } \ + if (lens != NULL) { \ + lens_destroy(&lens->super); \ + free(lens); \ + } \ + if (result != NULL) { \ + type_destroy(result); \ + free(result); \ + } \ + dict_erase (type_dieoffset_hash, &die_offset, NULL, \ + NULL, NULL); \ + dict_insert(type_dieoffset_hash, &die_offset, \ + &(struct arg_type_info*){ \ + type_get_simple(ARGTYPE_VOID)}); \ + return ret; \ + } while (0) + + struct arg_type_info *result = NULL; + struct enum_lens *lens = NULL; + const char *dupkey = NULL; + struct value *value = NULL; + + Dwarf_Off die_offset = dwarf_dieoffset(parent); + + result = calloc(1, sizeof(struct arg_type_info)); + if (result == NULL) { + complain(parent, "alloc error"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + if (dict_insert(type_dieoffset_hash, &die_offset, &result) != 0) { + complain(parent, "Couldn't insert into cache dict"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + uint64_t byte_size; + if (!get_die_numeric(&byte_size, parent, DW_AT_byte_size)) { + // No byte size given, assume 'int' + result->type = ARGTYPE_INT; + } else { + if (!get_integer_base_type(&result->type, + (int)byte_size, true)) { + complain(parent, "Unknown integer base type. " + "Using 'int'"); + result->type = ARGTYPE_INT; + } + } + + lens = calloc(1, sizeof(struct enum_lens)); + if (lens == NULL) { + complain(parent, "alloc error"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + lens_init_enum(lens); + result->lens = &lens->super; + result->own_lens = 1; + + Dwarf_Die die; + if (dwarf_child(parent, &die) != 0) { + // empty enum. we're done + CLEANUP_AND_RETURN_ERROR(NULL); + } + + + while (1) { + complain(&die, "enum element: 0x%02x/'%s'", dwarf_tag(&die), + dwarf_diename(&die)); + + dupkey = NULL; + value = NULL; + + if (dwarf_tag(&die) != DW_TAG_enumerator) { + complain(&die, "Enums can have ONLY DW_TAG_enumerator " + "elements"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + if (!dwarf_hasattr(&die, DW_AT_const_value)) { + complain(&die, "Enums MUST have DW_AT_const_value " + "values"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + const char *key = dwarf_diename(&die); + if (key == NULL) { + complain(&die, "Enums must have a DW_AT_name key"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + dupkey = strdup(key); + if (dupkey == NULL) { + complain(&die, "Couldn't duplicate enum key"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + value = calloc(1, sizeof(struct value)); + if (value == NULL) { + complain(&die, "Couldn't alloc enum value"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + value_init_detached(value, NULL, type_get_simple(result->type), + 0); + uint64_t enum_value; + if (!get_die_numeric(&enum_value, &die, DW_AT_const_value)) { + complain(&die, "Couldn't get enum value"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + value_set_word(value, (long)enum_value); + + if (lens_enum_add(lens, dupkey, 1, value, 1)) { + complain(&die, "Couldn't add enum element"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + NEXT_SIBLING(&die); + } + + return result; + +#undef CLEANUP_AND_RETURN_ERROR +} + +// returns a newly-allocated art_type_info*, or NULL on error +static struct arg_type_info *get_array(Dwarf_Die *parent, + struct protolib *plib, + struct dict *type_dieoffset_hash) +{ + +#define CLEANUP_AND_RETURN_ERROR(ret) do { \ + if (length != NULL) { \ + expr_destroy(length); \ + free(length); \ + } \ + if (array_type != NULL && newly_allocated_array_type) { \ + type_destroy(array_type); \ + free(array_type); \ + } \ + if (result != NULL) { \ + type_destroy(result); \ + free(result); \ + } \ + dict_erase (type_dieoffset_hash, &die_offset, \ + NULL, NULL, NULL); \ + dict_insert(type_dieoffset_hash, &die_offset, \ + &(struct arg_type_info*){ \ + type_get_simple(ARGTYPE_VOID)}); \ + return ret; \ + } while (0) + + + struct arg_type_info *result = NULL; + struct expr_node *length = NULL; + struct arg_type_info *array_type = NULL; + int newly_allocated_array_type = 0; + + Dwarf_Off die_offset = dwarf_dieoffset(parent); + + result = calloc(1, sizeof(struct arg_type_info)); + if (result == NULL) { + complain(parent, "alloc error"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + Dwarf_Die type_die; + if (!get_type_die(&type_die, parent)) { + complain(parent, "Array has unknown type"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + if (dict_insert(type_dieoffset_hash, &die_offset, &result) != 0) { + complain(parent, "Couldn't insert into cache dict"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + array_type = get_type(&newly_allocated_array_type, + &type_die, plib, type_dieoffset_hash); + if (array_type == NULL) { + complain(parent, "Couldn't figure out array's type"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + Dwarf_Die subrange; + if (dwarf_child(parent, &subrange) != 0) { + complain(parent, + "Array must have a DW_TAG_subrange_type child, " + "but has none"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + Dwarf_Die next_subrange; + if (dwarf_siblingof(&subrange, &next_subrange) <= 0) { + complain(parent, + "Array must have exactly one DW_TAG_subrange_type " + "child"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + if (dwarf_hasattr(&subrange, DW_AT_lower_bound)) { + uint64_t lower_bound; + if (!get_die_numeric(&lower_bound, &subrange, + DW_AT_lower_bound)) { + complain(parent, "Couldn't read lower bound"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + if (lower_bound != 0) { + complain(parent, + "Array subrange has a nonzero lower bound. " + "Don't know what to do"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + } + + uint64_t N; + if (!dwarf_hasattr(&subrange, DW_AT_upper_bound)) { + // no upper bound is defined. This is probably a variable-width + // array, and I don't know how long it is. Let's say 0 to be + // safe + N = 0; + } + else + { + if (!get_die_numeric(&N, &subrange, DW_AT_upper_bound)) { + complain(parent, "Couldn't read upper bound"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + N++; + } + + // I'm not checking the subrange type. It should be some sort of + // integer, and I don't know what it would mean for it to be something + // else + length = calloc(1, sizeof(struct expr_node)); + if (length == NULL) { + complain(&subrange, "Couldn't alloc length expr"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + expr_init_const_word(length, N, type_get_simple(ARGTYPE_INT), 0); + + type_init_array(result, array_type, newly_allocated_array_type, + length, 1); + return result; + +#undef CLEANUP_AND_RETURN_ERROR +} + +// returns a newly-allocated art_type_info*, or NULL on error +static struct arg_type_info *get_structure(Dwarf_Die *parent, + struct protolib *plib, + struct dict *type_dieoffset_hash) +{ + +#define CLEANUP_AND_RETURN_ERROR(ret) do { \ + if (member_type != NULL && newly_allocated_member_type) { \ + type_destroy(member_type); \ + free(member_type); \ + } \ + if (result != NULL) { \ + type_destroy(result); \ + free(result); \ + } \ + dict_erase (type_dieoffset_hash, &die_offset, \ + NULL, NULL, NULL); \ + dict_insert(type_dieoffset_hash, &die_offset, \ + &(struct arg_type_info*){ \ + type_get_simple(ARGTYPE_VOID)}); \ + return ret; \ + } while (0) + + + struct arg_type_info *result = NULL; + struct arg_type_info *member_type = NULL; + int newly_allocated_member_type = 0; + + Dwarf_Off die_offset = dwarf_dieoffset(parent); + + result = calloc(1, sizeof(struct arg_type_info)); + if (result == NULL) { + complain(parent, "alloc error"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + type_init_struct(result); + if (dict_insert(type_dieoffset_hash, &die_offset, &result) != 0) { + complain(parent, "Couldn't insert into cache dict"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + Dwarf_Die die; + if (dwarf_child(parent, &die) != 0) { + // no elements; we're done + return result; + } + + while (1) { + member_type = NULL; + newly_allocated_member_type = 0; + + complain(&die, "member: 0x%02x", dwarf_tag(&die)); + + if (dwarf_tag(&die) != DW_TAG_member) { + complain(&die, "Structure can have ONLY DW_TAG_member"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + Dwarf_Die type_die; + if (!get_type_die(&type_die, &die)) { + complain(&die, "Couldn't get type of element"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + member_type = get_type(&newly_allocated_member_type, + &type_die, plib, type_dieoffset_hash); + if (member_type == NULL) { + complain(&die, "Couldn't parse type from DWARF data"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + if (type_struct_add(result, member_type, + newly_allocated_member_type) != 0) { + complain(&die, "Couldn't add type to struct"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + + NEXT_SIBLING(&die); + } + + return result; +#undef CLEANUP_AND_RETURN_ERROR +} + +// Reads the type in the die and returns the corresponding arg_type_info*. If +// this was newly allocated on the heap, *newly_allocated_info = true. If an +// error occurred, returns NULL +static struct arg_type_info *get_type(int *newly_allocated_result, + Dwarf_Die *type_die, + struct protolib *plib, + struct dict *type_dieoffset_hash) +{ + +#define CLEANUP_AND_RETURN_ERROR(ret) do { \ + if (pointee != NULL && newly_allocated_pointee) { \ + type_destroy(pointee); \ + free(pointee); \ + } \ + if (result != NULL && *newly_allocated_result) { \ + type_destroy(result); \ + free(result); \ + } \ + dict_erase (type_dieoffset_hash, &die_offset, \ + NULL, NULL, NULL); \ + dict_insert(type_dieoffset_hash, &die_offset, \ + &(struct arg_type_info*){ \ + type_get_simple(ARGTYPE_VOID)}); \ + return ret; \ + } while (0) + +#define DICT_INSERT_AND_CHECK(type_dieoffset_hash, die_offset, result) \ + do { \ + if (dict_insert(type_dieoffset_hash, \ + die_offset, result) != 0) { \ + complain(type_die, \ + "Couldn't insert into cache dict"); \ + CLEANUP_AND_RETURN_ERROR(NULL); \ + } \ + } while(0) + + struct arg_type_info *result = NULL; + struct arg_type_info *pointee = NULL; + int newly_allocated_pointee = 0; + + Dwarf_Off die_offset = dwarf_dieoffset(type_die); + + + // by default, we say we allocated nothing. I set this to true later, + // when I allocate memory + *newly_allocated_result = 0; + + struct arg_type_info **found_type = dict_find(type_dieoffset_hash, + &die_offset); + if (found_type != NULL) { + complain(type_die, "Read pre-computed type"); + return *found_type; + } + + const char *type_name = dwarf_diename(type_die); + if (type_name != NULL) { + + struct named_type *already_defined_type = + protolib_lookup_type(plib, type_name, true); + + if (already_defined_type != NULL) { + complain(type_die, + "Type '%s' defined in a .conf file. " + "Using that instead of DWARF", + type_name); + return already_defined_type->info; + } + } + + Dwarf_Die next_die; + + switch (dwarf_tag(type_die)) { + case DW_TAG_base_type: + complain(type_die, "Storing base type"); + result = type_get_simple(get_base_type(type_die)); + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + return result; + + case DW_TAG_subroutine_type: + case DW_TAG_inlined_subroutine: + // function pointers are stored as void*. If ltrace tries to + // dereference these, it'll get a segfault + complain(type_die, "Storing subroutine type"); + result = type_get_simple(ARGTYPE_VOID); + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + return result; + + case DW_TAG_pointer_type: + if (!get_type_die(&next_die, type_die)) { + // the pointed-to type isn't defined, so I report a + // void* + complain(type_die, "Storing void-pointer type"); + result = type_get_voidptr(); + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + return result; + } + + complain(type_die, "Storing pointer type"); + + *newly_allocated_result = 1; + result = calloc(1, sizeof(struct arg_type_info)); + if (result == NULL) { + complain(type_die, "alloc error"); + CLEANUP_AND_RETURN_ERROR(NULL); + } + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + pointee = get_type(&newly_allocated_pointee, + &next_die, plib, type_dieoffset_hash); + if (pointee == NULL) + CLEANUP_AND_RETURN_ERROR(NULL); + + type_init_pointer(result, pointee, newly_allocated_pointee); + return result; + + case DW_TAG_structure_type: + complain(type_die, "Storing struct type"); + *newly_allocated_result = 1; + + result = get_structure(type_die, plib, type_dieoffset_hash); + if (result == NULL) + CLEANUP_AND_RETURN_ERROR(NULL); + return result; + + + case DW_TAG_typedef: + case DW_TAG_const_type: + case DW_TAG_volatile_type: + // Various tags are simply pass-through, so I just keep going + if (get_type_die(&next_die, type_die)) { + complain(type_die, "Storing const/typedef type"); + + result = get_type(newly_allocated_result, &next_die, + plib, type_dieoffset_hash); + if (result == NULL) + CLEANUP_AND_RETURN_ERROR(NULL); + } else { + // no type. Use 'void'. Normally I'd think this is + // bogus, but stdio typedefs something to void + result = type_get_simple(ARGTYPE_VOID); + complain(type_die, "Storing void type"); + } + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + return result; + + case DW_TAG_enumeration_type: + // We have an enumeration. This has a base type, but has a + // particular lens to handle the enum + *newly_allocated_result = 1; + + complain(type_die, "Storing enum int"); + result = get_enum(type_die, type_dieoffset_hash); + if (result == NULL) + CLEANUP_AND_RETURN_ERROR(NULL); + return result; + + case DW_TAG_array_type: + *newly_allocated_result = 1; + + complain(type_die, "Storing array"); + result = get_array(type_die, plib, type_dieoffset_hash); + if (result == NULL) + CLEANUP_AND_RETURN_ERROR(NULL); + return result; + + case DW_TAG_union_type: + result = type_get_simple(ARGTYPE_VOID); + complain(type_die, "Storing union-as-void type"); + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + return result; + + default: + complain(type_die, "Unknown type tag 0x%x. Returning void", + dwarf_tag(type_die)); + result = type_get_simple(ARGTYPE_VOID); + DICT_INSERT_AND_CHECK(type_dieoffset_hash, &die_offset, &result); + return result; + } + +#undef DICT_INSERT_AND_CHECK +#undef CLEANUP_AND_RETURN_ERROR +} + +// fills in *proto with a prototype. Returns true on success +static bool get_prototype(struct prototype *result, + Dwarf_Die *subroutine, struct protolib *plib, + struct dict *type_dieoffset_hash) +{ + +#define CLEANUP_AND_RETURN_ERROR(ret) do { \ + if (argument_type != NULL && newly_allocated_argument_type) { \ + type_destroy(argument_type); \ + free(argument_type); \ + } \ + prototype_destroy(result); \ + return ret; \ + } while (0) + + + struct arg_type_info *argument_type = NULL; + int newly_allocated_argument_type = 0; + + prototype_init(result); + + // First, look at the return type. This is stored in a DW_AT_type tag in + // the subroutine DIE. If there is no such tag, this function returns + // void + Dwarf_Die return_type_die; + if (!get_type_die(&return_type_die, subroutine)) { + result->return_info = type_get_simple(ARGTYPE_VOID); + result->own_return_info = 0; + } else { + int newly_allocated_return_type; + result->return_info = get_type(&newly_allocated_return_type, + &return_type_die, plib, + type_dieoffset_hash); + if (result->return_info == NULL) { + complain(subroutine, "Couldn't get return type"); + CLEANUP_AND_RETURN_ERROR(false); + } + result->own_return_info = newly_allocated_return_type; + } + + + // Now look at the arguments + Dwarf_Die arg_die; + if (dwarf_child(subroutine, &arg_die) != 0) { + // no args. We're done + return true; + } + + while (1) { + if (dwarf_tag(&arg_die) == DW_TAG_formal_parameter) { + + complain(&arg_die, "arg: 0x%02x", dwarf_tag(&arg_die)); + + argument_type = NULL; + newly_allocated_argument_type = false; + + Dwarf_Die type_die; + if (!get_type_die(&type_die, &arg_die)) { + complain(&arg_die, "Couldn't get the argument " + "type die"); + CLEANUP_AND_RETURN_ERROR(false); + } + + + argument_type = get_type(&newly_allocated_argument_type, + &type_die, plib, + type_dieoffset_hash); + if (argument_type==NULL) { + complain(&arg_die, "Couldn't parse arg " + "type from DWARF data"); + CLEANUP_AND_RETURN_ERROR(false); + } + + struct param param; + param_init_type(¶m, argument_type, + newly_allocated_argument_type); + if (prototype_push_param(result, ¶m) <0) { + complain(&arg_die, "couldn't add argument to " + "the prototype"); + CLEANUP_AND_RETURN_ERROR(false); + } + +#ifdef DUMP_PROTOTYPES + fprintf(stderr, "Adding argument:\n"); + dump_ltrace_tree(argument_type); +#endif + } + + NEXT_SIBLING(&arg_die); + } + + return true; +#undef CLEANUP_AND_RETURN_ERROR +} + +static bool import_subprogram_name(struct protolib *plib, struct library *lib, + struct dict *type_dieoffset_hash, + Dwarf_Die *die, const char* function_name) +{ + if (!filter_matches_symbol(options.plt_filter, function_name, lib) && + !filter_matches_symbol(options.static_filter, function_name, lib) && + !filter_matches_symbol(options.export_filter, function_name, lib)) { + complain(die, "Prototype not requested by any filter"); + return true; + } + + complain(die, "subroutine_type: 0x%02x; function '%s'", + dwarf_tag(die), function_name); + + struct prototype *proto_already_there = + protolib_lookup_prototype(plib, function_name, false); + + if (proto_already_there != NULL) { + complain(die, "Prototype already exists. Skipping"); + return true; + } + + struct prototype proto; + if (!get_prototype(&proto, die, plib, type_dieoffset_hash)) { + complain(die, "couldn't get prototype"); + return false; + } + + const char *function_name_dup = strdup(function_name); + if (function_name_dup == NULL) { + complain(die, "couldn't strdup"); + prototype_destroy(&proto); + return false; + } + protolib_add_prototype(plib, function_name_dup, 1, &proto); + return true; +} + +static bool import_subprogram_die(struct protolib *plib, struct library *lib, + struct dict *type_dieoffset_hash, + Dwarf_Die *die) +{ + // If there is a linkage name, I use it (this is required for C++ code, + // in particular). + // + // I use the plain name regardless, since sometimes the exported symbol + // corresponds to the plain name, NOT the linkage name. For instance I + // see this on my Debian/sid amd64 box. In its libc, the linkage name of + // __nanosleep is __GI___nanosleep, but the export is __nanosleep + const char *function_name; + Dwarf_Attribute attr; + + if (dwarf_attr(die, DW_AT_linkage_name, &attr) != NULL && + (function_name = dwarf_formstring(&attr)) != NULL && + !import_subprogram_name(plib, lib, type_dieoffset_hash, die, + function_name)) { + return false; + } + + if ((function_name = dwarf_diename(die)) != NULL && + !import_subprogram_name(plib, lib, type_dieoffset_hash, die, + function_name)) { + return false; + } + + return true; +} + +static bool process_die_compileunit(struct protolib *plib, struct library *lib, + struct dict *type_dieoffset_hash, + Dwarf_Die *parent) +{ + complain(parent, "Processing compile unit"); + Dwarf_Die die; + if (dwarf_child(parent, &die) != 0) { + // no child nodes, so nothing to do + return true; + } + + while (1) { + if (dwarf_tag(&die) == DW_TAG_subprogram) + if (!import_subprogram_die(plib, lib, type_dieoffset_hash, + &die)) + complain(&die, "Error importing subprogram. " + "Skipping"); + + NEXT_SIBLING(&die); + } + + return true; +} + +static void import(struct protolib *plib, struct library *lib, + Dwfl_Module *dwfl_module) +{ + // A map from DIE addresses (Dwarf_Off) to type structures (struct + // arg_type_info*). This is created and filled in at the start of each + // import, and deleted when the import is complete + struct dict type_dieoffset_hash; + + dict_init(&type_dieoffset_hash, sizeof(Dwarf_Off), + sizeof(struct arg_type_info*), + dwarf_die_hash, dwarf_die_eq, NULL); + + Dwarf_Addr bias; + Dwarf_Die *die = NULL; + while ((die = dwfl_module_nextcu(dwfl_module, die, &bias)) != NULL) { + if (dwarf_tag(die) == DW_TAG_compile_unit) + process_die_compileunit(plib, lib, + &type_dieoffset_hash, die); + else + complain(die, "A DW_TAG_compile_unit die expected. " + "Skipping this one"); + } + + dict_destroy(&type_dieoffset_hash, NULL, NULL, NULL); +} + +bool import_DWARF_prototypes(struct library *lib) +{ + struct protolib *plib = lib->protolib; + + debug(DEBUG_FUNCTION, "Importing DWARF prototypes from '%s'", + lib->soname); + if (plib == NULL) { + + const char *soname_dup = strdup(lib->soname); + if (soname_dup == NULL) { + fprintf(stderr, "couldn't strdup"); + return false; + } + + plib = protolib_cache_default(&g_protocache, soname_dup, 1); + if (plib == NULL) { + fprintf(stderr, "Error loading protolib %s: %s.\n", + lib->soname, strerror(errno)); + } + } + + import(plib, lib, lib->dwfl_module); + lib->protolib = plib; + + return true; +} diff --git a/dwarf_prototypes.h b/dwarf_prototypes.h new file mode 100644 index 0000000..7eed214 --- /dev/null +++ b/dwarf_prototypes.h @@ -0,0 +1,31 @@ +/* + * This file is part of ltrace. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef DWARF_PROTOTYPES_H +#define DWARF_PROTOTYPES_H + +#include <stdbool.h> +#include <elfutils/libdwfl.h> + +#include "prototype.h" +#include "library.h" + +bool import_DWARF_prototypes(struct library *lib); + +#endif /* DWARF_PROTOTYPES_H */ diff --git a/etc/libacl.so.conf b/etc/libacl.so.conf new file mode 100644 index 0000000..6b277cd --- /dev/null +++ b/etc/libacl.so.conf @@ -0,0 +1,43 @@ +# See ltrace.conf(5) for description of syntax of this file. + +# sys/acl.h +int acl_add_perm(addr,uint); +int acl_calc_mask(addr); +int acl_clear_perms(addr); +int acl_copy_entry(addr,addr); +int acl_copy_ext(addr,addr,int); +addr acl_copy_int(addr); +int acl_create_entry(addr,addr); +int acl_delete_def_file(string); +int acl_delete_entry(addr,addr); +int acl_delete_perm(addr,uint); +addr acl_dup(addr); +int acl_free(addr); +addr acl_from_text(string); +int acl_get_entry(addr,int,addr); +addr acl_get_fd(int); +addr acl_get_file(string,int); +int acl_get_permset(addr,addr); +addr acl_get_qualifier(addr); +int acl_get_tag_type(addr,addr); +addr acl_init(int); +int acl_set_fd(int,addr); +int acl_set_file(string,int,addr); +int acl_set_permset(addr,addr); +int acl_set_qualifier(addr,addr); +int acl_set_tag_type(addr,int); +int acl_size(addr); +string acl_to_text(addr,addr); +int acl_valid(addr); + +# acl/libacl.h +int acl_check(addr,addr); +int acl_cmp(addr,addr); +int acl_entries(addr); +int acl_equiv_mode(addr,addr); +string acl_error(int); +int acl_extended_fd(int); +int acl_extended_file(string); +addr acl_from_mode(octal); +int acl_get_perm(addr,uint); +string acl_to_any_text(addr,string,char,int); diff --git a/etc/libc.so.conf b/etc/libc.so.conf new file mode 100644 index 0000000..48b3b01 --- /dev/null +++ b/etc/libc.so.conf @@ -0,0 +1,547 @@ +# See ltrace.conf(5) for description of syntax of this file. + +# XXX ltrace misses long double and long long support +typedef ldouble = double; +typedef llong = long; +typedef ullong = ulong; + +void __libc_start_main(hide(void*), hide(int), array(string, arg2)); + +# arpa/inet.h +typedef in_addr = struct(hex(uint)); +int inet_aton(string, +in_addr*); +hex(uint) inet_addr(string); +hex(uint) inet_network(string); +string inet_ntoa(in_addr); +in_addr inet_makeaddr(hex(int), hex(int)); +hex(uint) inet_lnaof(in_addr); +hex(uint) inet_netof(in_addr); + +# bfd.h +void bfd_init(); +int bfd_set_default_target(string); +addr bfd_scan_vma(string, addr, int); +addr bfd_openr(string,string); +int bfd_check_format(addr,int); + +# ctype.h +char tolower(char); +char toupper(char); +addr __ctype_b_loc(); +addr __ctype_tolower_loc(); +addr __ctype_toupper_loc(); +ulong __ctype_get_mb_cur_max(); + +# curses.h +int waddch(addr, char); +int mvprintw(int, int, format); +int wmove(addr, int, int); +int waddnstr(addr, string, int); +string tgoto(string, int, int); + +# dirent.h + +# We can't portably rely on DIR internals at all. Ideally this would +# be implemented in a per-OS config file, but even on Linux, we don't +# know whether there's a lock in the structure or not. Luckily the +# one interesting datum, file descriptor, we can access reliably. +# Having the structure half-defined like this is potentially +# problematic as structure size influences parameter passing. But +# POSIX always uses pointer to the structure, so it's fine. + +typedef DIR = struct(int); +typedef FILE = addr; + +# XXX We can't represent the following portably without having either +# uulong, or directly uint64_t.' + +typedef ino_t = ulong; +typedef ino_t64 = ulong; +typedef off_t = ulong; +typedef off_t64 = ulong; + +typedef dirent = struct(ino_t, hide(off_t), hide(ushort), hide(char), string(array(char, zero(256)))); +typedef dirent64 = struct(ino_t64, hide(off_t64), hide(ushort), hide(char), string(array(char, zero(256)))); + +dirent *readdir(DIR *); +dirent64 *readdir64(DIR *); +int closedir(DIR *); +DIR *opendir(string); +DIR *fdopendir(int); +int dirfd(DIR *); +void rewinddir(DIR *); +long telldir(DIR *); +void seekdir(DIR *, long); + +# dlfcn.h +addr dlopen(string, int); +string dlerror(); +addr dlsym(addr, string); +int dlclose(addr); + +# errno.h +addr __errno_location(); + +# fcntl.h +int open(string,int,octal); ; WARNING: 3rd argument may not be there +int open64(string,int,octal); ; WARNING: 3rd argument may not be there + +# fnmatch.h +int fnmatch(string, string, int); + +# getopt.h +int getopt_long(int,addr,string,addr,int*); +int getopt_long_only(int,addr,string,addr,addr); + +# grp.h +void endgrent(); +addr getgrnam(string); +void setgrent(); +addr getgrent(); + +# libintl.h +string __dcgettext(string,string,int); +string bindtextdomain(string, string); +string textdomain(string); + +# libio.h +char _IO_getc(file); +int _IO_putc(char,file); + +# locale.h +string setlocale(enum(LC_CTYPE=0, LC_NUMERIC=1, LC_TIME=2, LC_COLLATE=3, LC_MONETARY=4, LC_MESSAGES=5, LC_ALL=6, LC_PAPER=7, LC_NAME=8, LC_ADDRESS=9, LC_TELEPHONE=10, LC_MEASUREMENT=11, LC_IDENTIFICATION=12), string); + +# mcheck.h +void mtrace(); +void muntrace(); + +# mqueue.h +int mq_open(string, int, octal, addr); ; WARNING: 3rd and 4th arguments may not be there +int mq_close(int); +int mq_unlink(string); +int mq_getattr(int, addr); +int mq_setattr(int, addr, addr); +int mq_notify(int, addr); +int mq_send(int, string3, ulong, uint); +int mq_timedsend(int, string3, ulong, uint, addr); +long mq_receive(int, +string0, ulong, addr); +long mq_timedreceive(int, +string0, ulong, addr, addr); + +# netdb.h +void endhostent(); +void endnetent(); +void endnetgrent(); +void endprotoent(); +void endservent(); +void freeaddrinfo(addr); +string gai_strerror(int); +int getaddrinfo(string, string, addr, addr); +addr gethostbyaddr(string, uint, int); +addr gethostbyname(string); +addr gethostent(); +int getnameinfo(addr, uint, string, uint, string, uint, uint); +addr getnetbyaddr(uint, int); +addr getnetbyname(string); +addr getnetent(); +int getnetgrent(addr, addr, addr); +addr getprotobyname(string); +addr getprotobynumber(int); +addr getprotoent(); +addr getservbyname(string, string); +addr getservbyport(int, string); +addr getservent(); +void herror(string); +string hstrerror(int); +int rcmd(addr, ushort, string, string, string, addr); +int rcmd_af(addr, ushort, string, string, string, addr, int); +int rexec(addr, int, string, string, string, addr); +int rexec_af(addr, int, string, string, string, addr, int); +int rresvport (addr); +int rresvport_af (addr, int); +int ruserok(string, int, string, string); +int ruserok_af(string, int, string, string, int); +void sethostent(int); +void setnetent(int); +int setnetgrent(string); +void setprotoent(int); +void setservent(int); + +# netinet/in.h +uint ntohs(uint); + +# pcap.h +string pcap_lookupdev(addr); +addr pcap_open_live(string, int, int, int, addr); +int pcap_snapshot(addr); +int pcap_lookupnet(string, addr, addr, addr); +int pcap_compile(addr, addr, string, int, addr); + +# pwd.h +string getpass(string); +void endpwent(); +addr getpwnam(string); +void setpwent(); + +# readline/readline.h +string readline(string); + +# signal.h +typedef signum = enum(SIGHUP=1, SIGINT=2, SIGQUIT=3, SIGILL=4, SIGTRAP=5, SIGABRT=6, SIGBUS=7, SIGFPE=8, SIGKILL=9, SIGUSR1=10, SIGSEGV=11, SIGUSR2=12, SIGPIPE=13, SIGALRM=14, SIGTERM=15, SIGSTKFLT=16, SIGCHLD=17, SIGCONT=18, SIGSTOP=19, SIGTSTP=20, SIGTTIN=21, SIGTTOU=22, SIGURG=23, SIGXCPU=24, SIGXFSZ=25, SIGVTALRM=26, SIGPROF=27, SIGWINCH=28, SIGIO=29, SIGPWR=30, SIGSYS=31, SIGRTMIN_0=32, SIGRTMIN_1=33, SIGRTMIN_2=34, SIGRTMIN_3=35, SIGRTMIN_4=36, SIGRTMIN_5=37, SIGRTMIN_6=38, SIGRTMIN_7=39, SIGRTMIN_8=40, SIGRTMIN_9=41, SIGRTMIN_10=42, SIGRTMIN_11=43, SIGRTMIN_12=44, SIGRTMIN_13=45, SIGRTMIN_14=46, SIGRTMIN_15=47, SIGRTMIN_16=48, SIGRTMIN_17=49, SIGRTMIN_18=50, SIGRTMIN_19=51, SIGRTMIN_20=52, SIGRTMIN_21=53, SIGRTMIN_22=54, SIGRTMIN_23=55, SIGRTMIN_24=56, SIGRTMIN_25=57, SIGRTMIN_26=58, SIGRTMIN_27=59, SIGRTMIN_28=60, SIGRTMIN_29=61, SIGRTMIN_30=62, SIGRTMIN_31=63); +typedef sigset_t = bitvec(ulong); +# elm3 should be flags +typedef sigaction = struct(addr, sigset_t, hex(int), addr); +int kill(int, signum); +int sigemptyset(+sigset_t*); +int sigaddset(+sigset_t*, signum); +int sigdelset(+sigset_t*, signum); +int sigfillset(+sigset_t*); +int sigismember(sigset_t*, signum); +addr signal(signum,addr); +int sigaction(signum, sigaction*, +sigaction*); +int sigprocmask(enum(SIG_BLOCK=1, SIG_UNBLOCK=2, SIG_SETMASK=3), sigset_t*, +sigset_t*); +int sigpending(+sigset_t*); +int sigsuspend(sigset_t*); +int sigisemptyset(sigset_t*); +int sigorset(+sigset_t*, sigset_t*, sigset_t*); +int sigandset(+sigset_t*, sigset_t*, sigset_t*); + +# stdio.h +int fclose(file); +int feof(file); +int ferror(file); +int fflush(file); +char fgetc(file); +addr fgets(+string, int, file); +int fileno(file); +file fopen(string,string); +file fopen64(string,string); +file fdopen(int, string); +int fprintf(file,format); +int fputc(char,file); +int fputs(string,file); +ulong fread(addr,ulong,ulong,file); +ulong fread_unlocked(addr,ulong,ulong,file); +ulong fwrite(string,ulong,ulong,file); +ulong fwrite_unlocked(string,ulong,ulong,file); +int pclose(addr); +void perror(string); +addr popen(string, string); +int printf(format); +int puts(string); +int remove(string); +int snprintf(+string2,ulong,format); +int sprintf(+string,format); +string tempnam(string,string); +int vfprintf(file,string,addr); +int vsnprintf(+string2,ulong,string,addr); +int setvbuf(file,addr,int,ulong); +void setbuf(file,addr); +void setbuffer(file,addr,ulong); +void setlinebuf(file); +int rename(string,string); + +# xlocale.h +typedef locale_t = void*; +locale_t newlocale(hex(int), string, locale_t); + +# stdlib.h +long __strtol_internal(string, +string*, int); +ulong __strtoul_internal(string, +string*, int); + +double strtod(string, +string*); +float strtof(string, +string*); +ldouble strtold(string, +string*); + +double strtod_l(string, +string*, locale_t); +float strtof_l(string, +string*, locale_t); +ldouble strtold_l(string, +string*, locale_t); + +int atexit(addr); +addr bsearch(string, addr, ulong, ulong, addr); +addr calloc(ulong, ulong); +void exit(int); +void free(addr); +string getenv(string); +int putenv(string); +int setenv(string,string,int); +void unsetenv(string); +addr malloc(ulong); +void qsort(addr,ulong,ulong,addr); +addr realloc(addr,ulong); +int system(string); + +int rand(); +int rand_r(uint*); +void srand(uint); +long random(); +void srandom(uint); +void* initstate(uint, void*, ulong); +void* setstate(void*); +int random_r(void*, +int*); +int srandom_r(uint, void*); +int initstate_r(uint, void*, ulong, void*); +int setstate_r(void*, void*); +double drand48(); +double erand48(+array(ushort,3)*); +long lrand48(); +long nrand48(+array(ushort,3)*); +long mrand48(); +long jrand48(+array(ushort,3)*); +void srand48(long); +array(ushort,3)* seed48(array(ushort,3)*); +void lcong48(array(ushort,7)*); + +# string.h +void bcopy(addr,addr,ulong); +void bzero(addr,ulong); +string basename(string); +string index(string,char); +addr memchr(string,char,ulong); +addr memcpy(addr,string(array(char, arg3)*),ulong); +addr memmove(addr,string(array(char, arg3)*),ulong); +addr memset(addr,char,long); +string rindex(string,char); +addr stpcpy(addr,string); +int strcasecmp(string, string); +string strcat(string, string); +string strchr(string,char); +int strcoll(string,string); +ulong strlen(string); +int strcmp(string,string); +addr strcpy(addr,string); +addr strdup(string); +string strerror(int); +int strncmp(string,string,ulong); +addr strncpy(addr,string3,ulong); +string strrchr(string,char); +string strsep(addr,string); +ulong strspn(string,string); +ulong strcspn(string,string); +string strstr(string,string); +string strtok(string, string); + +# sys/ioctl.h +int ioctl(int, int, addr); + +# sys/socket.h +int socket(int,int,int); + +# sys/stat.h +int __fxstat(int,int,addr); +int __xstat(int,string,addr); +int __lxstat(int,string,addr); +int __fxstat64(int,int,addr); +int __xstat64(int,string,addr); +int __lxstat64(int,string,addr); +int chmod(string,octal); +int fchmod(int,octal); +int mkfifo(string,octal); +octal umask(octal); + +# sys/utsname.h +int uname(addr); + +# sys/vfs.h +int statfs(string,addr); + +# syslog.h +void closelog(); +void openlog(string,int,int); +void syslog(int,format); + +# term.h +int tputs(string, int, addr); + +# termios.h +int tcgetattr(int,addr); +int tcsetattr(int,int,addr); + +# time.h +string ctime(addr); +int gettimeofday(addr, addr); +addr gmtime(addr); +addr localtime(addr); +ulong strftime(+string2,ulong,string,addr); +long time(addr); +# XXX in fact (time_t, long), which may be (llong, long) on 32-bit +# arches. We don't have llong as of this writing. +typedef timespec = struct(long, long); +int nanosleep(timespec*, timespec*); + +# unistd.h +void _exit(int); +int access(string, int); +uint alarm(uint); +int chdir(string); +int chown(string,int,int); +int close(int); +string crypt(string,string); +int dup2(int,int); +int execlp(string,string,addr,addr,addr); +int execv(string,addr); +int fchdir(int); +int fork(); +int ftruncate(int,ulong); +string2 getcwd(addr,ulong); +int getdomainname(+string2,ulong); +int geteuid(); +int getegid(); +int getgid(); +int gethostname(+string2,ulong); +string getlogin(); +int getopt(int,addr,string); +int getpid(); +int getppid(); +int getuid(); +int getpgrp(); +int setpgrp(); +int getpgid(int); +int isatty(int); +int link(string,string); +int mkdir(string,octal); +long read(int, +string[retval], ulong); +int rmdir(string); +int seteuid(uint); +int setgid(int); +int sethostname(+string2,ulong); +int setpgid(int,int); +int setreuid(uint, uint); +int setuid(int); +uint sleep(uint); +int symlink(string,string); +int sync(); +int truncate(string,ulong); +string ttyname(int); +int unlink(string); +void usleep(uint); +long write(int, string3, ulong); +addr sbrk(long); +int getpagesize(); +long lseek(int,long,int); +int pipe(addr); + +# utmp.h +void endutent(); +addr getutent(); +void setutent(); + +# wchar.h +typedef wchar_t = string(uint); +typedef wint_t = string(int); +typedef wstring_t = string(array(uint, zero)*); +typedef wstring2_t = string(array(uint, zero(arg2))*); +typedef wstring3_t = string(array(uint, zero(arg3))*); + +int fwide(FILE*, int); +wint_t btowc(int); + +wint_t getwc(FILE *); +wint_t getwchar(); +wint_t fgetwc(FILE*); +wstring_t fgetws(+wstring2_t, int, FILE*); +wint_t ungetwc(wint_t, FILE*); + +wint_t fputwc(wchar_t, FILE*); +int fputws(wstring_t, FILE*); +wint_t putwc(wchar_t, FILE*); +wint_t putwchar(wchar_t); + +int wprintf(format(wstring_t)); +int fwprintf(FILE*, format(wstring_t)); +int swprintf(+wstring2_t, ulong, format(wstring_t)); +int vfwprintf(FILE*, wstring_t, addr); +int vwprintf(wstring_t, addr); +int vswprintf(+wstring2_t, ulong, wstring_t, addr); + +; int wscanf(const wchar_t *restrict, ...); +; int fwscanf(FILE *restrict, const wchar_t *restrict, ...); +; int swscanf(const wchar_t *restrict, const wchar_t *restrict, ...); +; int vfwscanf(FILE *restrict, const wchar_t *restrict, va_list); +; int vswscanf(const wchar_t *restrict, const wchar_t *restrict, va_list); +; int vwscanf(const wchar_t *restrict, va_list); + +int iswalnum(wint_t); +int iswalpha(wint_t); +int iswcntrl(wint_t); +int iswdigit(wint_t); +int iswgraph(wint_t); +int iswlower(wint_t); +int iswprint(wint_t); +int iswpunct(wint_t); +int iswspace(wint_t); +int iswupper(wint_t); +int iswxdigit(wint_t); +uint wctype(string); +int iswctype(wint_t, uint); + +ulong mbrlen(string, ulong, addr); +ulong mbrtowc(+wchar_t*, string[arg3], ulong, addr); +ulong mbsrtowcs(+wstring3_t, string*, ulong, addr); +ulong wctomb(+string0, wchar_t); +ulong wcrtomb(+string0, wchar_t, addr); +ulong wcsrtombs(+string3, wstring_t*, ulong, addr); +int mbsinit(addr); + +wint_t towlower(wint_t); +wint_t towupper(wint_t); + +wstring_t wcscat(wstring_t, wstring_t); +wstring_t wcsncat(wstring3_t, wstring_t, ulong); +wstring_t wcschr(wstring_t, wchar_t); +wstring_t wcsrchr(wstring_t, wchar_t); +int wcscmp(wstring_t, wstring_t); +int wcsncmp(wstring3_t, wstring3_t, ulong); +int wcscoll(wstring_t, wstring_t); +addr wcscpy(addr, wstring_t); +addr wcsncpy(addr, wstring_t, ulong); +ulong wcslen(wstring_t); + +wstring_t wcsstr(wstring_t, wstring_t); +wstring_t wcswcs(wstring_t, wstring_t); + +ulong wcscspn(wstring_t, wstring_t); +ulong wcsspn(wstring_t, wstring_t); +wstring_t wcspbrk(wstring_t, wstring_t); +wstring_t wcstok(wstring_t, wstring_t, +wstring_t*); + +ulong wcsftime(+wstring2_t, ulong, wstring_t, addr); + +double wcstod(wstring_t, +wstring_t*); +float wcstof(wstring_t, +wstring_t*); +ldouble wcstold(wstring_t, +wstring_t*); +long wcstol(wstring_t, +wstring_t*, int); +llong wcstoll(wstring_t, +wstring_t*, int); +ulong wcstoul(wstring_t, +wstring_t*, int); +ullong wcstoull(wstring_t, +wstring_t*, int); + +int wcwidth(wchar_t); +int wcswidth(wstring2_t, ulong); + +wstring_t wmemchr(wstring3_t, wchar_t, ulong); +int wmemcmp(wstring3_t, wstring3_t, ulong); +int wctob(wint_t); +wstring3_t wmemcpy(addr, wstring3_t, ulong); +wstring3_t wmemmove(addr, wstring3_t, ulong); +wstring3_t wmemset(addr, wchar_t, ulong); + +# sys/wait.h +int wait(addr); +int waitpid(int,addr,int); + +# other symbols not included above +long a64l(string); +string l64a(long); +void abort(); +int abs(int); +long labs(long); + +typedef mntent = struct(string, string, string, string, int, int); +int addmntent(file, mntent*); +int endmntent(file); +int __endmntent(file); +file setmntent(string,string); +file __setmntent(string,string); +mntent *getmntent(addr); +mntent *getmntent_r(file, +mntent*, string, int); +mntent *__getmntent_r(file, +mntent*, string, int); +string hasmntopt(mntent*, string); diff --git a/etc/libm.so.conf b/etc/libm.so.conf new file mode 100644 index 0000000..8159221 --- /dev/null +++ b/etc/libm.so.conf @@ -0,0 +1,426 @@ +# XXX ltrace misses long double and long long support +typedef ldouble = double; +typedef llong = long; + +# This should generally work, I'm not aware of any arch, where the +# parameter passing of complex arguments differs from that for +# structure of two floats. +typedef double_complex = struct(double, double); +typedef float_complex = struct(float, float); +typedef ldouble_complex = struct(ldouble, ldouble); + +double sin(double); +float sinf(float); +ldouble sinl(ldouble); + +double cos(double); +float cosf(float); +ldouble cosl(ldouble); + +void sincos(double, +double*, double*); +void sincosf(float, +float*, float*); +void sincosl(ldouble, +ldouble*, ldouble*); + +double tan(double); +float tanf(float); +ldouble tanl(ldouble); + +double asin(double); +float asinf(float); +ldouble asinl(ldouble); + +double acos(double); +float acosf(float); +ldouble acosl(ldouble); + +double atan(double); +float atanf(float); +ldouble atanl(ldouble); + +double atan2(double, double); +float atan2f(float, float); +ldouble atan2l(ldouble, ldouble); + +double sinh(double); +float sinhf(float); +ldouble sinhl(ldouble); + +double cosh(double); +float coshf(float); +ldouble coshl(ldouble); + +double tanh(double); +float tanhf(float); +ldouble tanhl(ldouble); + +double asinh(double); +float asinhf(float); +ldouble asinhl(ldouble); + +double acosh(double); +float acoshf(float); +ldouble acoshl(ldouble); + +double atanh(double); +float atanhf(float); +ldouble atanhl(ldouble); + +double_complex csin(double_complex); +float_complex csinf(float_complex); +ldouble_complex csinl(ldouble_complex); + +double_complex ccos(double_complex); +float_complex ccosf(float_complex); +ldouble_complex ccosl(ldouble_complex); + +double_complex ctan(double_complex); +float_complex ctanf(float_complex); +ldouble_complex ctanl(ldouble_complex); + +double_complex casin(double_complex); +float_complex casinf(float_complex); +ldouble_complex casinl(ldouble_complex); + +double_complex cacos(double_complex); +float_complex cacosf(float_complex); +ldouble_complex cacosl(ldouble_complex); + +double_complex catan(double_complex); +float_complex catanf(float_complex); +ldouble_complex catanl(ldouble_complex); + +double_complex csinh(double_complex); +float_complex csinhf(float_complex); +ldouble_complex csinhl(ldouble_complex); + +double_complex ccosh(double_complex); +float_complex ccoshf(float_complex); +ldouble_complex ccoshl(ldouble_complex); + +double_complex ctanh(double_complex); +float_complex ctanhf(float_complex); +ldouble_complex ctanhl(ldouble_complex); + +double_complex casinh(double_complex); +float_complex casinhf(float_complex); +ldouble_complex casinhl(ldouble_complex); + +double_complex cacosh(double_complex); +float_complex cacoshf(float_complex); +ldouble_complex cacoshl(ldouble_complex); + +double_complex catanh(double_complex); +float_complex catanhf(float_complex); +ldouble_complex catanhl(ldouble_complex); + +double creal(double_complex); +float crealf(float_complex); +ldouble creall(ldouble_complex); + +double cimag(double_complex); +float cimagf(float_complex); +ldouble cimagl(ldouble_complex); + +double round(double); +float roundf(float); +ldouble roundl(ldouble); + +long lround(double); +long lroundf(float); +long lroundl(ldouble); + +llong llround(double); +llong llroundf(float); +llong llroundl(ldouble); + +double trunc(double); +float truncf(float); +ldouble truncl(ldouble); + +double floor(double); +float floorf(float); +ldouble floorl(ldouble); + +double ceil(double); +float ceilf(float); +ldouble ceill(ldouble); + +double pow(double, double); +float powf(float, float); +ldouble powl(ldouble, ldouble); + +double_complex cpow(double_complex, double_complex); +float_complex cpowf(float_complex, float_complex); +ldouble_complex cpowl(ldouble_complex, ldouble_complex); + +double pow10(double); +float pow10f(float); +ldouble pow10l(ldouble); + +double sqrt(double); +float sqrtf(float); +ldouble sqrtl(ldouble); + +double_complex csqrt(double_complex); +float_complex csqrtf(float_complex); +ldouble_complex csqrtl(ldouble_complex); + +double cbrt(double); +float cbrtf(float); +ldouble cbrtl(ldouble); + +double log(double); +float logf(float); +ldouble logl(ldouble); + +double log10(double); +float log10f(float); +ldouble log10l(ldouble); + +double log2(double); +float log2f(float); +ldouble log2l(ldouble); + +double logb(double); +float logbf(float); +ldouble logbl(ldouble); + +int ilogb(double); +int ilogbf(float); +int ilogbl(ldouble); + +double log1p(double); +float log1pf(float); +ldouble log1pl(ldouble); + +double_complex clog(double_complex); +float_complex clogf(float_complex); +ldouble_complex clogl(ldouble_complex); + +double_complex clog10(double_complex); +float_complex clog10f(float_complex); +ldouble_complex clog10l(ldouble_complex); + +double gamma(double); +float gammaf(float); +ldouble gammal(ldouble); + +double lgamma(double); +float lgammaf(float); +ldouble lgammal(ldouble); + +double lgamma_r(double, +int*); +float lgammaf_r(float, +int*); +ldouble lgammal_r(ldouble, +int*); + +double tgamma(double); +float tgammaf(float); +ldouble tgammal(ldouble); + +double j0(double); +float j0f(float); +ldouble j0l(ldouble); + +double j1(double); +float j1f(float); +ldouble j1l(ldouble); + +double jn(int, double); +float jnf(int, float); +ldouble jnl(int, ldouble); + +double y0(double); +float y0f(float); +ldouble y0l(ldouble); + +double y1(double); +float y1f(float); +ldouble y1l(ldouble); + +double yn(int, double); +float ynf(int, float); +ldouble ynl(int, ldouble); + +double fdim(double, double); +float fdimf(float, float); +ldouble fdiml(ldouble, ldouble); + +double remainder(double, double); +float remainderf(float, float); +ldouble remainderl(ldouble, ldouble); + +double drem(double, double); +float dremf(float, float); +ldouble dreml(ldouble, ldouble); + +double nearbyint(double); +float nearbyintf(float); +ldouble nearbyintl(ldouble); + +double rint(double); +float rintf(float); +ldouble rintl(ldouble); + +long lrint(double); +long lrintf(float); +long lrintl(ldouble); + +llong llrint(double); +llong llrintf(float); +llong llrintl(ldouble); + +double exp(double); +float expf(float); +ldouble expl(ldouble); + +double exp10(double); +float exp10f(float); +ldouble exp10l(ldouble); + +double exp2(double); +float exp2f(float); +ldouble exp2l(ldouble); + +double expm1(double); +float expm1f(float); +ldouble expm1l(ldouble); + +double frexp(double, +int *); +float frexpf(float, +int *); +ldouble frexpl(ldouble, +int *); + +double ldexp(double, int); +float ldexpf(float, int); +ldouble ldexpl(ldouble, int); + +double_complex cexp(double_complex); +float_complex cexpf(float_complex); +ldouble_complex cexpl(ldouble_complex); + +double significand(double); +float significandf(float); +ldouble significandl(ldouble); + +int finite(double); +int finitef(float); +int finitel(ldouble); + +int isinf(double); +int isinff(float); +int isinfl(ldouble); + +int isnan(double); +int isnanf(float); +int isnanl(ldouble); + +double nan(string); +float nanf(string); +ldouble nanl(string); + +double fabs(double); +float fabsf(float); +ldouble fabsl(ldouble); + +double cabs(double_complex); +float cabsf(float_complex); +ldouble cabsl(ldouble_complex); + +double modf(double, +double *); +float modff(float, +float *); +ldouble modfl(ldouble, +ldouble *); + +double fmod(double, double); +float fmodf(float, float); +ldouble fmodl(ldouble, ldouble); + +double remquo(double, double, +int *); +float remquof(float, float, +int *); +ldouble remquol(ldouble, ldouble, +int *); + +double erf(double); +float erff(float); +ldouble erfl(ldouble); + +double erfc(double); +float erfcf(float); +ldouble erfcl(ldouble); + +double fmax(double, double); +float fmaxf(float, float); +ldouble fmaxl(ldouble, ldouble); + +double fmin(double, double); +float fminf(float, float); +ldouble fminl(ldouble, ldouble); + +double carg(double_complex); +float cargf(float_complex); +ldouble cargl(ldouble_complex); + +double hypot(double, double); +float hypotf(float, float); +ldouble hypotl(ldouble, ldouble); + +double scalb(double, double); +float scalbf(float, double); +ldouble scalbl(ldouble, double); + +double scalbn(double, int); +float scalbnf(float, int); +ldouble scalbnl(ldouble, int); + +double scalbln(double, long); +float scalblnf(float, long); +ldouble scalblnl(ldouble, long); + +double fma(double, double, double); +float fmaf(float, float, float); +ldouble fmal(ldouble, ldouble, ldouble); + +double_complex cproj(double_complex); +float_complex cprojf(float_complex); +ldouble_complex cprojl(ldouble_complex); + +double copysign(double, double); +float copysignf(float, float); +ldouble copysignl(ldouble, ldouble); + +double nextafter(double, double); +float nextafterf(float, float); +ldouble nextafterl(ldouble, ldouble); + +double nexttoward(double, ldouble); +float nexttowardf(float, ldouble); +ldouble nexttowardl(ldouble, ldouble); + +double_complex conj(double_complex); +float_complex conjf(float_complex); +ldouble_complex conjl(ldouble_complex); + +; 15: 000000000003c000 15 FUNC GLOBAL DEFAULT 13 __finitel@@GLIBC_2.2.5 +; 44: 0000000000027be0 286 FUNC GLOBAL DEFAULT 13 __clog10@@GLIBC_2.2.5 +; 50: 00000000000068d0 85 FUNC GLOBAL DEFAULT 13 feholdexcept@@GLIBC_2.2.5 +; 56: 0000000000028900 10 FUNC GLOBAL DEFAULT 13 __signbit@@GLIBC_2.2.5 +; 61: 0000000000006ae0 53 FUNC GLOBAL DEFAULT 13 feenableexcept@@GLIBC_2.2.5 +; 65: 0000000000006760 29 FUNC GLOBAL DEFAULT 13 fegetexceptflag@@GLIBC_2.2.5 +; 68: 0000000000006a60 52 FUNC GLOBAL DEFAULT 13 feupdateenv@@GLIBC_2.2.5 +; 75: 0000000000006840 25 FUNC GLOBAL DEFAULT 13 fetestexcept@@GLIBC_2.2.5 +; 89: 0000000000025500 80 FUNC GLOBAL DEFAULT 13 __fpclassify@@GLIBC_2.2.5 +; 99: 0000000000033370 310 FUNC GLOBAL DEFAULT 13 __clog10f@@GLIBC_2.2.5 +; 104: 000000000003b600 307 FUNC GLOBAL DEFAULT 13 __clog10l@@GLIBC_2.2.5 +; 127: 0000000000028560 29 FUNC GLOBAL DEFAULT 13 __finite@@GLIBC_2.2.5 +; 134: 0000000000006870 66 FUNC GLOBAL DEFAULT 13 fesetround@@GLIBC_2.2.5 +; 136: 0000000000006780 99 FUNC GLOBAL DEFAULT 13 feraiseexcept@@GLIBC_2.2.5 +; 146: 0000000000006aa0 49 FUNC GLOBAL DEFAULT 13 fedisableexcept@@GLIBC_2.2.5 +; 155: 0000000000006730 40 FUNC GLOBAL DEFAULT 13 feclearexcept@@GLIBC_2.2.5 +; 175: 0000000000006860 14 FUNC GLOBAL DEFAULT 13 fegetround@@GLIBC_2.2.5 +; 199: 0000000000006b20 16 FUNC GLOBAL DEFAULT 13 fegetexcept@@GLIBC_2.2.5 +; 213: 00000000000067f0 71 FUNC GLOBAL DEFAULT 13 fesetexceptflag@@GLIBC_2.2.5 +; 229: 00000000000068c0 9 FUNC GLOBAL DEFAULT 13 fegetenv@@GLIBC_2.2.5 +; 249: 0000000000006930 303 FUNC GLOBAL DEFAULT 13 fesetenv@@GLIBC_2.2.5 +; 256: 00000000000308c0 56 FUNC GLOBAL DEFAULT 13 __fpclassifyf@@GLIBC_2.2.5 +; 261: 0000000000039020 94 FUNC GLOBAL DEFAULT 13 __fpclassifyl@@GLIBC_2.2.5 +; 263: 0000000000033a80 8 FUNC GLOBAL DEFAULT 13 __signbitf@@GLIBC_2.2.5 +; 267: 000000000003c2f0 29 FUNC GLOBAL DEFAULT 13 __signbitl@@GLIBC_2.2.5 +; 318: 0000000000006720 3 FUNC WEAK DEFAULT 13 matherr@@GLIBC_2.2.5 +; 328: 00000000000337f0 18 FUNC GLOBAL DEFAULT 13 __finitef@@GLIBC_2.2.5 diff --git a/etc/syscalls.conf b/etc/syscalls.conf new file mode 100644 index 0000000..09d347d --- /dev/null +++ b/etc/syscalls.conf @@ -0,0 +1,146 @@ +# syscall.conf -- system call prototypes +# See ltrace.conf(5) for description of syntax of this file. + +# Special value used to indicate the *at functions should use the +# current working directory. +typedef at_dirfd_t = enum[int](AT_FDCWD=-100); + +addr brk(addr); +int close(int); +int execve(string,addr,addr); +void exit(int); +void exit_group(int); +int fork(); +int getcwd(+string2,ulong); +int getpid(); + +# XXX the last argument should be off_t +addr mmap(addr,ulong,int,int,int,long); + +int munmap(addr,ulong); +int open(string, hex(uint), oct(uint)); +int personality(uint); +long read(int,+string0,ulong); +int stat(string,addr); +octal umask(octal); +int uname(addr); +long write(int,string3,ulong); +int sync(); +int setxattr(string,string,addr,uint,int); +int lsetxattr(string,string,addr,uint,int); +int fsetxattr(int,string,addr,uint,int); +int getxattr(string,string,addr,uint); +int lgetxattr(string,string,addr,uint); +int fgetxattr(int,string,addr,uint); +int listxattr(string,addr,uint); +int llistxattr(string,addr,uint); +int flistxattr(int,addr,uint); +int removexattr(string,string); +int lremovexattr(string,string); +int fremovexattr(int,string); +int chdir(string); +int fchdir(int); +int chmod(string,octal); +int fchmod(int,octal); +int chown(string,int,int); +int fchown(int,int,int); +int lchown(string,int,int); +int chroot(string); +int dup(int); +int dup2(int,int); +int fdatasync(int); +int fsync(int); +int getpriority(int,int); +int setpriority(int,int,int); +int getrlimit(int,addr); +int setrlimit(int,addr); +int gettimeofday(addr,addr); +int settimeofday(addr,addr); +int setfsgid(int); +int setfsuid(int); +int getuid(); +int setuid(int); +int getgid(); +int setgid(int); +int getsid(int); +int setsid(int); +int setreuid(int,int); +int setregid(int,int); +int geteuid(); +int getegid(); +int setpgid(int,int); +int getresuid(addr,addr,addr); +int setresuid(int,int,int); +int getresgid(addr,addr,addr); +int setresgid(int,int,int); +int kill(int,int); +int link(string,string); +int madvise(addr,ulong,int); +int mkdir(string,octal); +int mknod(string,octal,int); +int msync(addr,ulong,int); +int nice(int); +int poll(addr,uint,int); +int readdir(uint,addr,uint); +int readlink(string,string,ulong); +int reboot(int,int,int,addr); +int rename(string,string); +int rmdir(string); +int sigaltstack(addr,addr); +int statfs(string,addr); +int fstatfs(int,addr); +int fstat(int,addr); +int lstat(string,addr); +int stime(addr); +int symlink(string, string); +int sysinfo(addr); +int syslog(int,string,int); +int truncate(string,long); +int ftruncate(int,long); +int mount(string,string,string,ulong,addr); +int umount(string); +int umount2(string,int); +int unlink(string); +int utime(string,addr); +long lseek(int,long,int); +addr signal(int,addr); +int sigaction(int,addr,addr); +int pause(); +int sigpending(addr); +int sigprocmask(int,addr,addr); +int sigqueue(int,int,addr); +int sigsuspend(addr); +int wait(addr); +int waitpid(int,addr,int); +ulong readv(int,addr,int); +ulong writev(int,addr,int); +int mprotect(addr,int,int); +int access(string,octal); +int getdents(uint, void *, uint); + +int openat(at_dirfd_t, string, hex(uint), oct(uint)); +int mknodat(at_dirfd_t, string, oct(uint), ushort) +int mkdirat(at_dirfd_t, string, oct(uint)); +int unlinkat(at_dirfd_t, string, hex(uint)); +int symlinkat(string, at_dirfd_t, string); +int linkat(at_dirfd_t, string, at_dirfd_t, string, hex(uint)); +int renameat(at_dirfd_t, string, at_dirfd_t, string); +int faccessat(at_dirfd_t, string, oct(uint), hex(uint)); +int fchmodat(at_dirfd_t, string, oct(uint), hex(uint)); +int fchownat(at_dirfd_t, string, int, int, hex(uint)); +int readlinkat(at_dirfd_t, string, +string[arg4], ulong); +int fstatat(at_dirfd_t, string, addr, hex(uint)); +int utimensat(at_dirfd_t, string, addr, hex(uint)); +int futimens(int, addr); +int futimesat(at_dirfd_t, string, addr); +addr shmat(int, addr, hex(uint)); +int shmdt(addr); + +typedef fid_type = enum(FILEID_ROOT=0, FILEID_INO32_GEN=1, FILEID_INO32_GEN_PARENT=2, FILEID_BTRFS_WITHOUT_PARENT=0x4d, FILEID_BTRFS_WITH_PARENT=0x4e, FILEID_BTRFS_WITH_PARENT_ROOT=0x4f, FILEID_UDF_WITHOUT_PARENT=0x51, FILEID_UDF_WITH_PARENT=0x52, FILEID_NILFS_WITHOUT_PARENT=0x61, FILEID_NILFS_WITH_PARENT=0x62); +typedef file_handle = struct(uint, fid_type, array(hex(char), elt1)*); +int name_to_handle_at(at_dirfd_t, string, file_handle, int*, hex(uint)); +int open_by_handle_at(at_dirfd_t, file_handle, hex(uint)); + +int newfstatat(at_dirfd_t, string, addr, hex(uint)); +int creat(string, oct(int)); +int ustat(ushort, addr); diff --git a/execute_program.c b/execute_program.c index b24f2ee..ba53ebf 100644 --- a/execute_program.c +++ b/execute_program.c @@ -22,10 +22,6 @@ */ #include "config.h" -#if defined(HAVE_LIBUNWIND) -#include <libunwind-ptrace.h> -#endif /* defined(HAVE_LIBUNWIND) */ - #include <stdio.h> #include <stdlib.h> #include <sys/types.h> @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,7 +21,6 @@ #include <string.h> #include <assert.h> #include <errno.h> -#include <error.h> #include <stdlib.h> #include "expr.h" @@ -172,6 +171,85 @@ expr_destroy(struct expr_node *node) abort(); } +static int +expr_alloc_and_clone(struct expr_node **retpp, struct expr_node *node, int own) +{ + *retpp = node; + if (own) { + *retpp = malloc(sizeof **retpp); + if (*retpp == NULL || expr_clone(*retpp, node) < 0) { + free(*retpp); + return -1; + } + } + return 0; +} + +int +expr_clone(struct expr_node *retp, const struct expr_node *node) +{ + *retp = *node; + + switch (node->kind) { + struct expr_node *nlhs; + struct expr_node *nrhs; + + case EXPR_OP_ARGNO: + case EXPR_OP_SELF: + return 0; + + case EXPR_OP_CONST: + return value_clone(&retp->u.value, &node->u.value); + + case EXPR_OP_NAMED: + if (node->u.name.own + && (retp->u.name.s = strdup(node->u.name.s)) == NULL) + return -1; + return 0; + + case EXPR_OP_INDEX: + if (expr_alloc_and_clone(&nlhs, node->lhs, node->own_lhs) < 0) + return -1; + + if (expr_alloc_and_clone(&nrhs, node->u.node.n, + node->u.node.own) < 0) { + if (nlhs != node->lhs) { + expr_destroy(nlhs); + free(nlhs); + } + return -1; + } + + retp->lhs = nlhs; + retp->u.node.n = nrhs; + return 0; + + case EXPR_OP_CALL2: + if (expr_alloc_and_clone(&nrhs, node->u.call.rhs, + node->u.call.own_rhs) < 0) + return -1; + retp->u.call.rhs = nrhs; + /* Fall through. */ + + case EXPR_OP_UP: + case EXPR_OP_CALL1: + if (expr_alloc_and_clone(&nlhs, node->lhs, node->own_lhs) < 0) { + if (node->kind == EXPR_OP_CALL2 + && node->u.call.own_rhs) { + expr_destroy(nrhs); + free(nrhs); + return -1; + } + } + + retp->lhs = nlhs; + return 0; + } + + assert(!"Invalid value of node kind"); + abort(); +} + int expr_is_compile_constant(struct expr_node *node) { @@ -327,12 +405,11 @@ expr_eval_constant(struct expr_node *node, long *valuep) struct expr_node * expr_self(void) { - static struct expr_node *node = NULL; - if (node == NULL) { - node = malloc(sizeof(*node)); - if (node == NULL) - error(1, errno, "malloc expr_self"); - expr_init_self(node); + static struct expr_node *nodep = NULL; + if (nodep == NULL) { + static struct expr_node node; + expr_init_self(&node); + nodep = &node; } - return node; + return nodep; } @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -125,6 +125,10 @@ void expr_init_cb2(struct expr_node *node, /* Release the data inside NODE. Doesn't free NODE itself. */ void expr_destroy(struct expr_node *node); +/* Copy expression NODE into the area pointed to by RETP. Return 0 on + * success or a negative value on failure. */ +int expr_clone(struct expr_node *retp, const struct expr_node *node); + /* Evaluate the expression NODE in context of VALUE. ARGUMENTS is a * dictionary of named and numbered values that NODE may use. Returns * 0 in case of success or a negative value on error. CONTEXT and @@ -27,18 +27,18 @@ #include "type.h" #ifdef ARCH_HAVE_FETCH_ARG -struct fetch_context *arch_fetch_arg_init(enum tof type, struct Process *proc, +struct fetch_context *arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info); -struct fetch_context *arch_fetch_arg_clone(struct Process *proc, +struct fetch_context *arch_fetch_arg_clone(struct process *proc, struct fetch_context *context); int arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep); int arch_fetch_retval(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep); void arch_fetch_arg_done(struct fetch_context *context); @@ -53,7 +53,7 @@ void arch_fetch_param_pack_end(struct fetch_context *context); #else /* Fall back to gimme_arg. */ -long gimme_arg(enum tof type, struct Process *proc, int arg_num, +long gimme_arg(enum tof type, struct process *proc, int arg_num, struct arg_type_info *info); struct fetch_context { @@ -61,14 +61,14 @@ struct fetch_context { }; struct fetch_context * -arch_fetch_arg_init(enum tof type, struct Process *proc, +arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { return calloc(sizeof(struct fetch_context), 1); } struct fetch_context * -arch_fetch_arg_clone(struct Process *proc, struct fetch_context *context) +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) { struct fetch_context *ret = malloc(sizeof(*ret)); if (ret == NULL) @@ -78,7 +78,7 @@ arch_fetch_arg_clone(struct Process *proc, struct fetch_context *context) int arch_fetch_arg_next(struct fetch_context *context, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { long l = gimme_arg(type, proc, context->argnum++, info); @@ -88,7 +88,7 @@ arch_fetch_arg_next(struct fetch_context *context, enum tof type, int arch_fetch_retval(struct fetch_context *context, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { long l = gimme_arg(type, proc, -1, info); @@ -118,21 +118,21 @@ arch_fetch_param_pack_end(struct fetch_context *context) #endif struct fetch_context * -fetch_arg_init(enum tof type, struct Process *proc, +fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { return arch_fetch_arg_init(type, proc, ret_info); } struct fetch_context * -fetch_arg_clone(struct Process *proc, struct fetch_context *context) +fetch_arg_clone(struct process *proc, struct fetch_context *context) { return arch_fetch_arg_clone(proc, context); } int fetch_arg_next(struct fetch_context *context, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { return arch_fetch_arg_next(context, type, proc, info, valuep); @@ -140,7 +140,7 @@ fetch_arg_next(struct fetch_context *context, enum tof type, int fetch_retval(struct fetch_context *context, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { return arch_fetch_retval(context, type, proc, info, valuep); @@ -38,24 +38,24 @@ struct fetch_context; /* Initialize argument fetching. Returns NULL on failure. RET_INFO * is the return type of the function. */ -struct fetch_context *fetch_arg_init(enum tof type, struct Process *proc, +struct fetch_context *fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info); /* Make a clone of context. */ -struct fetch_context *fetch_arg_clone(struct Process *proc, +struct fetch_context *fetch_arg_clone(struct process *proc, struct fetch_context *context); /* Load next argument. The function returns 0 on success or a * negative value on failure. The extracted value is stored in * *VALUEP. */ int fetch_arg_next(struct fetch_context *context, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep); /* Load return value. The function returns 0 on success or a negative * value on failure. The extracted value is stored in *VALUEP. */ int fetch_retval(struct fetch_context *context, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep); /* Destroy fetch context. CONTEXT shall be the same memory location @@ -74,15 +74,15 @@ void fetch_param_pack_end(struct fetch_context *context); /* The following callbacks have to be implemented in backend if arch.h * defines ARCH_HAVE_FETCH_ARG. These backend callbacks correspond to * above functions. */ -struct fetch_context *arch_fetch_arg_init(enum tof type, struct Process *proc, +struct fetch_context *arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info); -struct fetch_context *arch_fetch_arg_clone(struct Process *proc, +struct fetch_context *arch_fetch_arg_clone(struct process *proc, struct fetch_context *context); int arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep); int arch_fetch_retval(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep); void arch_fetch_arg_done(struct fetch_context *context); @@ -27,8 +27,7 @@ #include <sys/types.h> #include <regex.h> -struct library; -struct library_symbol; +#include "forward.h" enum filter_lib_matcher_type { /* Match by soname. */ @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -21,15 +21,20 @@ /* Important types defined in other header files are declared here. */ struct Event; -struct Process; struct arg_type_info; struct breakpoint; +struct dict; struct expr_node; +struct filter; struct library; struct library_symbol; struct ltelf; struct param; struct param_enum; +struct process; +struct protolib; +struct prototype; +struct timedelta; struct value; struct value_dict; -struct filter; +struct vect; @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2007, 2008, 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2007,2008,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -27,13 +27,11 @@ static ssize_t match_character_class(const char *glob, size_t length, size_t from) { - size_t i; - if (length > 0) - for (i = from + 2; i < length - 1 && glob[++i] != ':'; ) - ; - if (i >= length || glob[++i] != ']') + assert(length > 0); + const char *colon = memchr(glob + from + 2, ':', length - 1); + if (colon == NULL || colon[1] != ']') return -1; - return i; + return colon - glob; } static ssize_t @@ -63,14 +61,9 @@ match_brack(const char *glob, size_t length, size_t from, int *exclmp) } ++i; /* skip any character, including [ or ] */ - int escape = 0; for (; i < length; ++i) { char c = glob[i]; - if (escape) { - ++i; - escape = 0; - - } else if (c == '[' && glob[i + 1] == ':') { + if (c == '[' && glob[i + 1] == ':') { ssize_t j = match_character_class(glob, length, i); if (j < 0) goto fail; @@ -180,7 +173,7 @@ glob_to_regex(const char *glob, char **retp) goto fail; } *retp = buf; - return REG_NOERROR; + return 0; } int @@ -188,7 +181,7 @@ globcomp(regex_t *preg, const char *glob, int cflags) { char *regex = NULL; int status = glob_to_regex(glob, ®ex); - if (status != REG_NOERROR) + if (status != 0) return status; assert(regex != NULL); status = regcomp(preg, regex, cflags); @@ -18,8 +18,8 @@ * 02110-1301 USA */ -#ifndef _GLOB_H_ -#define _GLOB_H_ +#ifndef GLOB_H +#define GLOB_H #include <sys/types.h> #include <regex.h> @@ -29,4 +29,4 @@ * supported by globcomp. */ int globcomp(regex_t *preg, const char *glob, int cflags); -#endif /* _GLOB_H_ */ +#endif /* GLOB_H */ diff --git a/handle_event.c b/handle_event.c index 42a7a05..6fa7e98 100644 --- a/handle_event.c +++ b/handle_event.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Arnaud Patard, Mandriva SA * Copyright (C) 1998,2001,2002,2003,2004,2007,2008,2009 Juan Cespedes * Copyright (C) 2008 Luis Machado, IBM Corporation @@ -32,7 +32,7 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/time.h> +#include <stdbool.h> #include "backend.h" #include "breakpoint.h" @@ -40,6 +40,8 @@ #include "fetch.h" #include "library.h" #include "proc.h" +#include "prototype.h" +#include "summary.h" #include "value_dict.h" static void handle_signal(Event *event); @@ -54,20 +56,19 @@ static void handle_exec(Event *event); static void handle_breakpoint(Event *event); static void handle_new(Event *event); -static void callstack_push_syscall(Process *proc, int sysnum); -static void callstack_push_symfunc(Process *proc, - struct library_symbol *sym); +static void callstack_push_syscall(struct process *proc, int sysnum); +static void callstack_push_symfunc(struct process *proc, struct breakpoint *bp); /* XXX Stack maintenance should be moved to a dedicated module, or to * proc.c, and push/pop should be visible outside this module. For * now, because we need this in proc.c, this is non-static. */ -void callstack_pop(struct Process *proc); +void callstack_pop(struct process *proc); -static char * shortsignal(Process *proc, int signum); -static char * sysname(Process *proc, int sysnum); -static char * arch_sysname(Process *proc, int sysnum); +static char *shortsignal(struct process *proc, int signum); +static char *sysname(struct process *proc, int sysnum); +static char *arch_sysname(struct process *proc, int sysnum); static Event * -call_handler(Process * proc, Event * event) +call_handler(struct process *proc, Event *event) { assert(proc != NULL); @@ -114,70 +115,91 @@ handle_event(Event *event) case EVENT_NONE: debug(1, "event: none"); return; + case EVENT_SIGNAL: + assert(event->proc != NULL); debug(1, "[%d] event: signal (%s [%d])", event->proc->pid, shortsignal(event->proc, event->e_un.signum), event->e_un.signum); handle_signal(event); return; + case EVENT_EXIT: + assert(event->proc != NULL); debug(1, "[%d] event: exit (%d)", event->proc->pid, event->e_un.ret_val); handle_exit(event); return; + case EVENT_EXIT_SIGNAL: + assert(event->proc != NULL); debug(1, "[%d] event: exit signal (%s [%d])", event->proc->pid, shortsignal(event->proc, event->e_un.signum), event->e_un.signum); handle_exit_signal(event); return; + case EVENT_SYSCALL: + assert(event->proc != NULL); debug(1, "[%d] event: syscall (%s [%d])", event->proc->pid, sysname(event->proc, event->e_un.sysnum), event->e_un.sysnum); handle_syscall(event); return; + case EVENT_SYSRET: + assert(event->proc != NULL); debug(1, "[%d] event: sysret (%s [%d])", event->proc->pid, sysname(event->proc, event->e_un.sysnum), event->e_un.sysnum); handle_sysret(event); return; + case EVENT_ARCH_SYSCALL: + assert(event->proc != NULL); debug(1, "[%d] event: arch_syscall (%s [%d])", event->proc->pid, arch_sysname(event->proc, event->e_un.sysnum), event->e_un.sysnum); handle_arch_syscall(event); return; + case EVENT_ARCH_SYSRET: + assert(event->proc != NULL); debug(1, "[%d] event: arch_sysret (%s [%d])", event->proc->pid, arch_sysname(event->proc, event->e_un.sysnum), event->e_un.sysnum); handle_arch_sysret(event); return; + case EVENT_CLONE: case EVENT_VFORK: + assert(event->proc != NULL); debug(1, "[%d] event: clone (%u)", event->proc->pid, event->e_un.newpid); handle_clone(event); return; + case EVENT_EXEC: + assert(event->proc != NULL); debug(1, "[%d] event: exec()", event->proc->pid); handle_exec(event); return; + case EVENT_BREAKPOINT: + assert(event->proc != NULL); debug(1, "[%d] event: breakpoint %p", event->proc->pid, event->e_un.brk_addr); handle_breakpoint(event); return; + case EVENT_NEW: debug(1, "[%d] event: new process", event->e_un.newpid); @@ -229,26 +251,18 @@ pending_new_insert(pid_t pid) { } static void -pending_new_remove(pid_t pid) { - Pending_New *p, *pred; - +pending_new_remove(pid_t pid) +{ debug(DEBUG_FUNCTION, "pending_new_remove(%d)", pid); - p = pending_news; - pred = NULL; - if (p->pid == pid) { - pending_news = p->next; - free(p); - } else { - while (p) { - if (p->pid == pid) { - pred->next = p->next; - free(p); - } - pred = p; - p = p->next; + Pending_New **pp; + for (pp = &pending_news; *pp != NULL; pp = &(*pp)->next) + if ((*pp)->pid == pid) { + Pending_New *p = *pp; + *pp = p->next; + free(p); + return; } - } } static void @@ -256,53 +270,56 @@ handle_clone(Event *event) { debug(DEBUG_FUNCTION, "handle_clone(pid=%d)", event->proc->pid); - struct Process *proc = malloc(sizeof(*proc)); - if (proc == NULL) { - fail: + struct process *proc = malloc(sizeof(*proc)); + pid_t newpid = event->e_un.newpid; + if (proc == NULL + || process_clone(proc, event->proc, newpid) < 0) { free(proc); + proc = NULL; fprintf(stderr, - "Error during init of tracing process %d\n" - "This process won't be traced.\n", - event->proc->pid); - return; + "Couldn't initialize tracing of process %d.\n", + newpid); + + } else { + proc->parent = event->proc; + /* We save register values to the arch pointer, and + * these need to be per-thread. XXX arch_ptr should + * be retired in favor of fetch interface anyway. */ + proc->arch_ptr = NULL; } - if (process_clone(proc, event->proc, event->e_un.newpid) < 0) - goto fail; - proc->parent = event->proc; + if (pending_new(newpid)) { + pending_new_remove(newpid); + + if (proc != NULL) { + proc->event_handler = NULL; + if (event->proc->state == STATE_ATTACHED + && options.follow) + proc->state = STATE_ATTACHED; + else + proc->state = STATE_IGNORED; + } - /* We save register values to the arch pointer, and these need - to be per-thread. */ - proc->arch_ptr = NULL; + continue_process(newpid); - if (pending_new(proc->pid)) { - pending_new_remove(proc->pid); - /* XXX this used to be destroy_event_handler call, but - * I don't think we want to call that on a shared - * state. */ - proc->event_handler = NULL; - if (event->proc->state == STATE_ATTACHED && options.follow) - proc->state = STATE_ATTACHED; - else - proc->state = STATE_IGNORED; - continue_process(proc->pid); - } else { + } else if (proc != NULL) { proc->state = STATE_BEING_CREATED; } - if (event->type == EVENT_VFORK) + if (event->type != EVENT_VFORK) + continue_process(event->proc->pid); + else if (proc != NULL) continue_after_vfork(proc); else - continue_process(event->proc->pid); + continue_process(newpid); } static void -handle_new(Event * event) { - Process * proc; - +handle_new(Event *event) +{ debug(DEBUG_FUNCTION, "handle_new(pid=%d)", event->e_un.newpid); - proc = pid2proc(event->e_un.newpid); + struct process *proc = pid2proc(event->e_un.newpid); if (!proc) { pending_new_insert(event->e_un.newpid); } else { @@ -317,7 +334,8 @@ handle_new(Event * event) { } static char * -shortsignal(Process *proc, int signum) { +shortsignal(struct process *proc, int signum) +{ static char *signalent0[] = { #include "signalent.h" }; @@ -331,8 +349,7 @@ shortsignal(Process *proc, int signum) { debug(DEBUG_FUNCTION, "shortsignal(pid=%d, signum=%d)", proc->pid, signum); - if (proc->personality > sizeof signalents / sizeof signalents[0]) - abort(); + assert(proc->personality < sizeof signalents / sizeof signalents[0]); if (signum < 0 || signum >= nsignals[proc->personality]) { return "UNKNOWN_SIGNAL"; } else { @@ -341,53 +358,56 @@ shortsignal(Process *proc, int signum) { } static char * -sysname(Process *proc, int sysnum) { +sysname(struct process *proc, int sysnum) +{ static char result[128]; - static char *syscalent0[] = { + static char *syscallent0[] = { #include "syscallent.h" }; - static char *syscalent1[] = { + static char *syscallent1[] = { #include "syscallent1.h" }; - static char **syscalents[] = { syscalent0, syscalent1 }; - int nsyscals[] = { sizeof syscalent0 / sizeof syscalent0[0], - sizeof syscalent1 / sizeof syscalent1[0] + static char **syscallents[] = { syscallent0, syscallent1 }; + int nsyscalls[] = { + sizeof syscallent0 / sizeof syscallent0[0], + sizeof syscallent1 / sizeof syscallent1[0], }; debug(DEBUG_FUNCTION, "sysname(pid=%d, sysnum=%d)", proc->pid, sysnum); - if (proc->personality > sizeof syscalents / sizeof syscalents[0]) - abort(); - if (sysnum < 0 || sysnum >= nsyscals[proc->personality]) { + assert(proc->personality < sizeof syscallents / sizeof syscallents[0]); + if (sysnum < 0 || sysnum >= nsyscalls[proc->personality]) { sprintf(result, "SYS_%d", sysnum); return result; } else { - sprintf(result, "SYS_%s", - syscalents[proc->personality][sysnum]); - return result; + return syscallents[proc->personality][sysnum]; } } static char * -arch_sysname(Process *proc, int sysnum) { +arch_sysname(struct process *proc, int sysnum) +{ static char result[128]; - static char *arch_syscalent[] = { + static char *arch_syscallent[] = { #include "arch_syscallent.h" }; - int nsyscals = sizeof arch_syscalent / sizeof arch_syscalent[0]; + int nsyscalls = sizeof arch_syscallent / sizeof arch_syscallent[0]; debug(DEBUG_FUNCTION, "arch_sysname(pid=%d, sysnum=%d)", proc->pid, sysnum); - if (sysnum < 0 || sysnum >= nsyscals) { + if (sysnum < 0 || sysnum >= nsyscalls) { sprintf(result, "ARCH_%d", sysnum); return result; } else { - sprintf(result, "ARCH_%s", - arch_syscalent[sysnum]); + sprintf(result, "ARCH_%s", arch_syscallent[sysnum]); return result; } } +#ifndef HAVE_STRSIGNAL +# define strsignal(SIGNUM) "???" +#endif + static void handle_signal(Event *event) { debug(DEBUG_FUNCTION, "handle_signal(pid=%d, signum=%d)", event->proc->pid, event->e_un.signum); @@ -399,6 +419,84 @@ handle_signal(Event *event) { continue_after_signal(event->proc->pid, event->e_un.signum); } +static int +init_syscall_symbol(struct library_symbol *libsym, const char *name) +{ + static struct library syscall_lib; + + if (syscall_lib.protolib == NULL) { + struct protolib *protolib + = protolib_cache_load(&g_protocache, "syscalls", 0, 1); + if (protolib == NULL) { + fprintf(stderr, "Couldn't load system call prototypes:" + " %s.\n", strerror(errno)); + + /* Instead, get a fake one just so we can + * carry on, limping. */ + protolib = malloc(sizeof *protolib); + if (protolib == NULL) { + fprintf(stderr, "Couldn't even allocate a fake " + "prototype library: %s.\n", + strerror(errno)); + abort(); + } + protolib_init(protolib); + } + + assert(protolib != NULL); + if (library_init(&syscall_lib, LT_LIBTYPE_SYSCALL) < 0) { + fprintf(stderr, "Couldn't initialize system call " + "library: %s.\n", strerror(errno)); + abort(); + } + + library_set_soname(&syscall_lib, "SYS", 0); + syscall_lib.protolib = protolib; + } + + if (library_symbol_init(libsym, 0, name, 0, LS_TOPLT_NONE) < 0) + return -1; + + libsym->lib = &syscall_lib; + return 0; +} + +/* Account the unfinished functions on the call stack. */ +static void +account_current_callstack(struct process *proc) +{ + if (! options.summary) + return; + + struct timedelta spent[proc->callstack_depth]; + + size_t i; + for (i = 0; i < proc->callstack_depth; ++i) { + struct callstack_element *elem = &proc->callstack[i]; + spent[i] = calc_time_spent(elem->enter_time); + } + + for (i = 0; i < proc->callstack_depth; ++i) { + struct callstack_element *elem = &proc->callstack[i]; + struct library_symbol syscall, *libsym = NULL; + if (elem->is_syscall) { + const char *name = sysname(proc, elem->c_un.syscall); + if (init_syscall_symbol(&syscall, name) >= 0) + libsym = &syscall; + + } else { + libsym = elem->c_un.libfunc; + } + + if (libsym != NULL) { + summary_account_call(libsym, spent[i]); + + if (elem->is_syscall) + library_symbol_destroy(&syscall); + } + } +} + static void handle_exit(Event *event) { debug(DEBUG_FUNCTION, "handle_exit(pid=%d, status=%d)", event->proc->pid, event->e_un.ret_val); @@ -406,6 +504,8 @@ handle_exit(Event *event) { output_line(event->proc, "+++ exited (status %d) +++", event->e_un.ret_val); } + + account_current_callstack(event->proc); remove_process(event->proc); } @@ -416,35 +516,49 @@ handle_exit_signal(Event *event) { output_line(event->proc, "+++ killed by %s +++", shortsignal(event->proc, event->e_un.signum)); } + + account_current_callstack(event->proc); remove_process(event->proc); } static void -output_syscall(struct Process *proc, const char *name, enum tof tof, - void (*output)(enum tof, struct Process *, - struct library_symbol *)) +output_syscall(struct process *proc, const char *name, enum tof tof, + bool left, struct timedelta *spent) { + if (left) + assert(spent == NULL); + struct library_symbol syscall; - if (library_symbol_init(&syscall, 0, name, 0, LS_TOPLT_NONE) >= 0) { - (*output)(tof, proc, &syscall); + if (init_syscall_symbol(&syscall, name) >= 0) { + if (left) { + if (! options.summary) + output_left(tof, proc, &syscall); + } else if (options.summary) { + summary_account_call(&syscall, *spent); + } else { + output_right(tof, proc, &syscall, spent); + } + library_symbol_destroy(&syscall); } } static void -output_syscall_left(struct Process *proc, const char *name) +output_syscall_left(struct process *proc, const char *name) { - output_syscall(proc, name, LT_TOF_SYSCALL, &output_left); + output_syscall(proc, name, LT_TOF_SYSCALL, true, NULL); } static void -output_syscall_right(struct Process *proc, const char *name) +output_syscall_right(struct process *proc, const char *name, + struct timedelta *spent) { - output_syscall(proc, name, LT_TOF_SYSCALLR, &output_right); + output_syscall(proc, name, LT_TOF_SYSCALLR, false, spent); } static void -handle_syscall(Event *event) { +handle_syscall(Event *event) +{ debug(DEBUG_FUNCTION, "handle_syscall(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum); if (event->proc->state != STATE_IGNORED) { callstack_push_syscall(event->proc, event->e_un.sysnum); @@ -457,8 +571,9 @@ handle_syscall(Event *event) { } static void -handle_exec(Event * event) { - Process * proc = event->proc; +handle_exec(Event *event) +{ + struct process *proc = event->proc; /* Save the PID so that we can use it after unsuccessful * process_exec. */ @@ -473,24 +588,15 @@ handle_exec(Event * event) { } output_line(proc, "--- Called exec() ---"); + account_current_callstack(proc); + if (process_exec(proc) < 0) { fprintf(stderr, "couldn't reinitialize process %d after exec\n", pid); goto untrace; } - continue_process(proc->pid); - - /* After the exec, we expect to hit the first executable - * instruction. - * - * XXX TODO It would be nice to have this removed, but then we - * need to do that also for initial call to wait_for_proc in - * execute_program. In that case we could generate a - * EVENT_FIRST event or something, or maybe this could somehow - * be rolled into EVENT_NEW. */ - wait_for_proc(proc->pid); - continue_process(proc->pid); + continue_after_exec(proc); } static void @@ -507,77 +613,62 @@ handle_arch_syscall(Event *event) { continue_process(event->proc->pid); } -struct timeval current_time_spent; - static void -calc_time_spent(Process *proc) { - struct timeval tv; - struct timezone tz; - struct timeval diff; - struct callstack_element *elem; - - debug(DEBUG_FUNCTION, "calc_time_spent(pid=%d)", proc->pid); - elem = &proc->callstack[proc->callstack_depth - 1]; +handle_x_sysret(Event *event, char *(*name_cb)(struct process *, int)) +{ + debug(DEBUG_FUNCTION, "handle_x_sysret(pid=%d, sysnum=%d)", + event->proc->pid, event->e_un.sysnum); - gettimeofday(&tv, &tz); + unsigned d = event->proc->callstack_depth; + assert(d > 0); + struct callstack_element *elem = &event->proc->callstack[d - 1]; + assert(elem->is_syscall); - diff.tv_sec = tv.tv_sec - elem->time_spent.tv_sec; - if (tv.tv_usec >= elem->time_spent.tv_usec) { - diff.tv_usec = tv.tv_usec - elem->time_spent.tv_usec; - } else { - diff.tv_sec--; - diff.tv_usec = 1000000 + tv.tv_usec - elem->time_spent.tv_usec; - } - current_time_spent = diff; -} - -static void -handle_sysret(Event *event) { - debug(DEBUG_FUNCTION, "handle_sysret(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum); if (event->proc->state != STATE_IGNORED) { - if (opt_T || options.summary) { - calc_time_spent(event->proc); - } + struct timedelta spent = calc_time_spent(elem->enter_time); if (options.syscalls) output_syscall_right(event->proc, - sysname(event->proc, - event->e_un.sysnum)); + name_cb(event->proc, + event->e_un.sysnum), + &spent); - assert(event->proc->callstack_depth > 0); - unsigned d = event->proc->callstack_depth - 1; - assert(event->proc->callstack[d].is_syscall); callstack_pop(event->proc); } continue_after_syscall(event->proc, event->e_un.sysnum, 1); } static void -handle_arch_sysret(Event *event) { - debug(DEBUG_FUNCTION, "handle_arch_sysret(pid=%d, sysnum=%d)", event->proc->pid, event->e_un.sysnum); - if (event->proc->state != STATE_IGNORED) { - if (opt_T || options.summary) { - calc_time_spent(event->proc); - } - if (options.syscalls) - output_syscall_right(event->proc, - arch_sysname(event->proc, - event->e_un.sysnum)); - callstack_pop(event->proc); - } - continue_process(event->proc->pid); +handle_sysret(Event *event) +{ + handle_x_sysret(event, &sysname); +} + +static void +handle_arch_sysret(Event *event) +{ + handle_x_sysret(event, &arch_sysname); } static void -output_right_tos(struct Process *proc) +output_right_tos(struct process *proc) { size_t d = proc->callstack_depth; + assert(d > 0); struct callstack_element *elem = &proc->callstack[d - 1]; - if (proc->state != STATE_IGNORED) - output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc); + assert(! elem->is_syscall); + + if (proc->state != STATE_IGNORED) { + struct timedelta spent = calc_time_spent(elem->enter_time); + if (options.summary) + summary_account_call(elem->c_un.libfunc, spent); + else + output_right(LT_TOF_FUNCTIONR, proc, elem->c_un.libfunc, + &spent); + } } #ifndef ARCH_HAVE_SYMBOL_RET -void arch_symbol_ret(struct Process *proc, struct library_symbol *libsym) +void arch_symbol_ret(struct process *proc, struct library_symbol *libsym) { } #endif @@ -587,7 +678,7 @@ handle_breakpoint(Event *event) { int i, j; struct breakpoint *sbp; - Process *leader = event->proc->leader; + struct process *leader = event->proc->leader; void *brk_addr = event->e_un.brk_addr; /* The leader has terminated. */ @@ -602,15 +693,8 @@ handle_breakpoint(Event *event) for (i = event->proc->callstack_depth - 1; i >= 0; i--) { if (brk_addr == event->proc->callstack[i].return_addr) { - for (j = event->proc->callstack_depth - 1; j > i; j--) { + for (j = event->proc->callstack_depth - 1; j > i; j--) callstack_pop(event->proc); - } - if (event->proc->state != STATE_IGNORED) { - if (opt_T || options.summary) { - calc_time_spent(event->proc); - } - } - event->proc->return_addr = brk_addr; struct library_symbol *libsym = event->proc->callstack[i].c_un.libfunc; @@ -663,13 +747,14 @@ handle_breakpoint(Event *event) /* breakpoint_on_hit may delete its own breakpoint, so we have * to look it up again. */ if ((sbp = address2bpstruct(leader, brk_addr)) != NULL) { + if (event->proc->state != STATE_IGNORED && sbp->libsym != NULL) { event->proc->stack_pointer = get_stack_pointer(event->proc); - event->proc->return_addr = - get_return_addr(event->proc, event->proc->stack_pointer); - callstack_push_symfunc(event->proc, sbp->libsym); - output_left(LT_TOF_FUNCTION, event->proc, sbp->libsym); + callstack_push_symfunc(event->proc, sbp); + if (! options.summary) + output_left(LT_TOF_FUNCTION, event->proc, + sbp->libsym); } breakpoint_on_continue(sbp, event->proc); @@ -682,7 +767,8 @@ handle_breakpoint(Event *event) } static void -callstack_push_syscall(Process *proc, int sysnum) { +callstack_push_syscall(struct process *proc, int sysnum) +{ struct callstack_element *elem; debug(DEBUG_FUNCTION, "callstack_push_syscall(pid=%d, sysnum=%d)", proc->pid, sysnum); @@ -702,15 +788,17 @@ callstack_push_syscall(Process *proc, int sysnum) { proc->callstack_depth++; if (opt_T || options.summary) { struct timezone tz; - gettimeofday(&elem->time_spent, &tz); + gettimeofday(&elem->enter_time, &tz); } } static void -callstack_push_symfunc(Process *proc, struct library_symbol *sym) { +callstack_push_symfunc(struct process *proc, struct breakpoint *bp) +{ struct callstack_element *elem; - debug(DEBUG_FUNCTION, "callstack_push_symfunc(pid=%d, symbol=%s)", proc->pid, sym->name); + debug(DEBUG_FUNCTION, "callstack_push_symfunc(pid=%d, symbol=%s)", + proc->pid, bp->libsym->name); /* FIXME: not good -- should use dynamic allocation. 19990703 mortene. */ if (proc->callstack_depth == MAX_CALLDEPTH - 1) { fprintf(stderr, "%s: Error: call nesting too deep!\n", __func__); @@ -721,28 +809,43 @@ callstack_push_symfunc(Process *proc, struct library_symbol *sym) { elem = &proc->callstack[proc->callstack_depth++]; *elem = (struct callstack_element){}; elem->is_syscall = 0; - elem->c_un.libfunc = sym; + elem->c_un.libfunc = bp->libsym; + + struct breakpoint *rbp = NULL; + if (breakpoint_get_return_bp(&rbp, bp, proc) == 0 + && rbp != NULL) { + struct breakpoint *ext_rbp = insert_breakpoint(proc, rbp); + if (ext_rbp != rbp) { + breakpoint_destroy(rbp); + free(rbp); + rbp = ext_rbp; + } + } - elem->return_addr = proc->return_addr; - if (elem->return_addr) - insert_breakpoint(proc, elem->return_addr, NULL); + elem->return_addr = rbp != NULL ? rbp->addr : 0; if (opt_T || options.summary) { struct timezone tz; - gettimeofday(&elem->time_spent, &tz); + gettimeofday(&elem->enter_time, &tz); } } void -callstack_pop(struct Process *proc) +callstack_pop(struct process *proc) { struct callstack_element *elem; assert(proc->callstack_depth > 0); debug(DEBUG_FUNCTION, "callstack_pop(pid=%d)", proc->pid); elem = &proc->callstack[proc->callstack_depth - 1]; - if (!elem->is_syscall && elem->return_addr) - delete_breakpoint(proc, elem->return_addr); + if (!elem->is_syscall && elem->return_addr) { + struct breakpoint *bp + = address2bpstruct(proc->leader, elem->return_addr); + if (bp != NULL) { + breakpoint_on_hit(bp, proc); + delete_breakpoint(proc, bp); + } + } if (elem->fetch_context != NULL) fetch_arg_done(elem->fetch_context); diff --git a/lens_default.c b/lens_default.c index ed3d0e1..fb66b9d 100644 --- a/lens_default.c +++ b/lens_default.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2004,2007,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * Copyright (C) 2006 Steve Fink @@ -21,6 +21,8 @@ * 02110-1301 USA */ +#define _XOPEN_SOURCE /* For wcwidth from wchar.h. */ + #include <ctype.h> #include <stdlib.h> #include <assert.h> @@ -28,13 +30,15 @@ #include <stdarg.h> #include <stdio.h> #include <string.h> +#include <wchar.h> -#include "proc.h" -#include "lens_default.h" -#include "value.h" +#include "bits.h" #include "expr.h" +#include "lens_default.h" +#include "options.h" +#include "output.h" #include "type.h" -#include "common.h" +#include "value.h" #include "zero.h" #define READER(NAME, TYPE) \ @@ -122,13 +126,8 @@ acc_fprintf(int *countp, FILE *stream, const char *format, ...) } static int -format_char(FILE *stream, struct value *value, struct value_dict *arguments) +print_char(FILE *stream, int c) { - long lc; - if (value_extract_word(value, &lc, arguments) < 0) - return -1; - int c = (int)lc; - const char *fmt; switch (c) { case -1: @@ -172,13 +171,23 @@ format_char(FILE *stream, struct value *value, struct value_dict *arguments) } static int -format_naked_char(FILE *stream, struct value *value, - struct value_dict *arguments) +format_char(FILE *stream, struct value *value, struct value_dict *arguments) +{ + long lc; + if (value_extract_word(value, &lc, arguments) < 0) + return -1; + return print_char(stream, (int) lc); +} + +static int +format_naked(FILE *stream, struct value *value, + struct value_dict *arguments, + int (*what)(FILE *, struct value *, struct value_dict *)) { int written = 0; if (acc_fprintf(&written, stream, "'") < 0 || account_output(&written, - format_char(stream, value, arguments)) < 0 + what(stream, value, arguments)) < 0 || acc_fprintf(&written, stream, "'") < 0) return -1; @@ -255,11 +264,12 @@ format_struct(FILE *stream, struct value *value, struct value_dict *arguments) return written; } +static const char null_message[] = "nil"; int format_pointer(FILE *stream, struct value *value, struct value_dict *arguments) { if (value_is_zero(value, arguments)) - return fprintf(stream, "nil"); + return fprintf(stream, null_message); /* The following is for detecting recursion. We keep track of * the values that were already displayed. Each time a @@ -321,7 +331,7 @@ format_pointer(FILE *stream, struct value *value, struct value_dict *arguments) value_destroy(&element); done: - vect_popback(&pointers); + VECT_POPBACK(&pointers, struct value *, NULL, NULL); return o; } @@ -337,14 +347,14 @@ done: * OPEN, CLOSE, DELIM are opening and closing parenthesis and element * delimiter. */ -int +static int format_array(FILE *stream, struct value *value, struct value_dict *arguments, struct expr_node *length, size_t maxlen, int before, const char *open, const char *close, const char *delim) { /* We need "long" to be long enough to cover the whole address * space. */ - typedef char assert__long_enough_long[-(sizeof(long) < sizeof(void *))]; + (void)sizeof(char[1 - 2*(sizeof(long) < sizeof(void *))]); long l; if (expr_eval_word(length, value, arguments, &l) < 0) return -1; @@ -405,7 +415,8 @@ toplevel_format_lens(struct lens *lens, FILE *stream, case ARGTYPE_CHAR: if (int_fmt == INT_FMT_default) - return format_naked_char(stream, value, arguments); + return format_naked(stream, value, arguments, + &format_char); return format_integer(stream, value, int_fmt, arguments); case ARGTYPE_FLOAT: @@ -416,7 +427,9 @@ toplevel_format_lens(struct lens *lens, FILE *stream, return format_struct(stream, value, arguments); case ARGTYPE_POINTER: - if (value->type->u.array_info.elt_type->type != ARGTYPE_VOID) + if (value_is_zero(value, arguments)) + return fprintf(stream, null_message); + if (value->type->u.ptr_info.info->type != ARGTYPE_VOID) return format_pointer(stream, value, arguments); return format_integer(stream, value, INT_FMT_x, arguments); @@ -538,6 +551,51 @@ struct lens bool_lens = { .format_cb = bool_lens_format_cb, }; +static int +redispatch_as_array(struct lens *lens, FILE *stream, + struct value *value, struct value_dict *arguments, + int (*cb)(struct lens *, FILE *, + struct value *, struct value_dict *)) +{ + struct arg_type_info info[2]; + type_init_array(&info[1], value->type->u.ptr_info.info, 0, + expr_node_zero(), 0); + type_init_pointer(&info[0], &info[1], 0); + info->lens = lens; + info->own_lens = 0; + struct value tmp; + if (value_clone(&tmp, value) < 0) + return -1; + value_set_type(&tmp, info, 0); + int ret = cb(lens, stream, &tmp, arguments); + type_destroy(&info[0]); + type_destroy(&info[1]); + value_destroy(&tmp); + return ret; +} + +static int +format_wchar(FILE *stream, struct value *value, struct value_dict *arguments) +{ + long l; + if (value_extract_word(value, &l, arguments) < 0) + return -1; + wchar_t wc = (wchar_t) l; + char buf[MB_CUR_MAX + 1]; + + int c = wctomb(buf, wc); + if (c < 0) + return -1; + if (c == 1) + return print_char(stream, buf[0]); + + buf[c] = 0; + if (fprintf(stream, "%s", buf) < 0) + return -1; + + c = wcwidth(wc); + return c >= 0 ? c : 0; +} static int string_lens_format_cb(struct lens *lens, FILE *stream, @@ -550,39 +608,39 @@ string_lens_format_cb(struct lens *lens, FILE *stream, * I suspect people are so used to the char * C idiom, * that string(char *) might actually turn up. So * let's just support it. */ - if (value->type->u.ptr_info.info->type == ARGTYPE_CHAR) { - struct arg_type_info info[2]; - type_init_array(&info[1], - value->type->u.ptr_info.info, 0, - expr_node_zero(), 0); - type_init_pointer(&info[0], &info[1], 0); - info->lens = lens; - info->own_lens = 0; - struct value tmp; - if (value_clone(&tmp, value) < 0) - return -1; - value_set_type(&tmp, info, 0); - int ret = string_lens_format_cb(lens, stream, &tmp, - arguments); - type_destroy(&info[0]); - type_destroy(&info[1]); - value_destroy(&tmp); - return ret; - } - - /* fall-through */ + switch ((int) value->type->u.ptr_info.info->type) + case ARGTYPE_CHAR: + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + return redispatch_as_array(lens, stream, value, + arguments, + &string_lens_format_cb); + + /* Otherwise dispatch to whatever the default for the + * pointee is--most likely this will again be us. */ + /* Fall through. */ case ARGTYPE_VOID: case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: case ARGTYPE_STRUCT: + return toplevel_format_lens(lens, stream, value, + arguments, INT_FMT_default); + case ARGTYPE_SHORT: case ARGTYPE_INT: case ARGTYPE_LONG: case ARGTYPE_USHORT: case ARGTYPE_UINT: case ARGTYPE_ULONG: - return toplevel_format_lens(lens, stream, value, - arguments, INT_FMT_default); + if (value->parent != NULL && value->type->lens == NULL) + return format_wchar(stream, value, arguments); + else + return format_naked(stream, value, arguments, + &format_wchar); case ARGTYPE_CHAR: return format_char(stream, value, arguments); @@ -608,15 +666,6 @@ out_bits(FILE *stream, size_t low, size_t high) return fprintf(stream, "%zd-%zd", low, high); } -static unsigned -bitcount(unsigned u) -{ - int c = 0; - for (; u > 0; u &= u - 1) - c++; - return c; -} - static int bitvect_lens_format_cb(struct lens *lens, FILE *stream, struct value *value, struct value_dict *arguments) @@ -673,7 +722,7 @@ bitvect_lens_format_cb(struct lens *lens, FILE *stream, unsigned neg = bits > sz * 4 ? 0xff : 0x00; int o = 0; - if (acc_fprintf(&o, stream, "%s<", "~" + (neg == 0x00)) < 0) + if (acc_fprintf(&o, stream, "%s<", &"~"[neg == 0x00]) < 0) return -1; size_t bitno = 0; diff --git a/libltrace.c b/libltrace.c index 559edfa..0112c9f 100644 --- a/libltrace.c +++ b/libltrace.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -24,29 +24,33 @@ #include <sys/param.h> #include <sys/wait.h> #include <errno.h> +#include <limits.h> +#include <locale.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#include "backend.h" #include "common.h" #include "proc.h" +#include "prototype.h" #include "read_config_file.h" -#include "backend.h" +#include "summary.h" char *command = NULL; int exiting = 0; /* =1 if a SIGINT or SIGTERM has been received */ static enum callback_status -stop_non_p_processes(Process *proc, void *data) +stop_non_p_processes(struct process *proc, void *data) { int stop = 1; struct opt_p_t *it; for (it = opt_p; it != NULL; it = it->next) { - Process * p_proc = pid2proc(it->pid); + struct process *p_proc = pid2proc(it->pid); if (p_proc == NULL) { printf("stop_non_p_processes: %d terminated?\n", it->pid); continue; @@ -88,9 +92,8 @@ signal_exit(int sig) static void normal_exit(void) { - if (options.summary) { + if (options.summary) show_summary(); - } if (options.output) { fclose(options.output); options.output = NULL; @@ -98,7 +101,10 @@ normal_exit(void) } void -ltrace_init(int argc, char **argv) { +ltrace_init(int argc, char **argv) +{ + setlocale(LC_ALL, ""); + struct opt_p_t *opt_p_tmp; atexit(normal_exit); @@ -107,41 +113,20 @@ ltrace_init(int argc, char **argv) { argv = process_options(argc, argv); init_global_config(); - while (opt_F) { - /* If filename begins with ~, expand it to the user's home */ - /* directory. This does not correctly handle ~yoda, but that */ - /* isn't as bad as it seems because the shell will normally */ - /* be doing the expansion for us; only the hardcoded */ - /* ~/.ltrace.conf should ever use this code. */ - if (opt_F->filename[0] == '~') { - char path[PATH_MAX]; - char *home_dir = getenv("HOME"); - if (home_dir) { - strncpy(path, home_dir, PATH_MAX - 1); - path[PATH_MAX - 1] = '\0'; - strncat(path, opt_F->filename + 1, - PATH_MAX - strlen(path) - 1); - read_config_file(path); - } - } else { - read_config_file(opt_F->filename); - } - struct opt_F_t *next = opt_F->next; - if (opt_F->own_filename) - free(opt_F->filename); - free(opt_F); - opt_F = next; - } if (command) { /* Check that the binary ABI is supported before * calling execute_program. */ - struct ltelf lte = {}; - open_elf(<e, command); - do_close_elf(<e); + { + struct ltelf lte; + if (ltelf_init(<e, command) == 0) + ltelf_destroy(<e); + else + exit(EXIT_FAILURE); + } pid_t pid = execute_program(command, argv); - struct Process *proc = open_program(command, pid); + struct process *proc = open_program(command, pid); if (proc == NULL) { fprintf(stderr, "couldn't open program '%s': %s\n", command, strerror(errno)); @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2001,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -20,6 +20,8 @@ * 02110-1301 USA */ +#include "config.h" + #include <stdlib.h> #include <string.h> #include <assert.h> @@ -29,12 +31,36 @@ #include "callback.h" #include "debug.h" #include "dict.h" +#include "vect.h" #include "backend.h" // for arch_library_symbol_init, arch_library_init -#ifndef ARCH_HAVE_LIBRARY_DATA +static void +library_exported_names_init(struct library_exported_names *names); + +#ifndef OS_HAVE_LIBRARY_DATA +int +os_library_init(struct library *lib) +{ + return 0; +} + void +os_library_destroy(struct library *lib) +{ +} + +int +os_library_clone(struct library *retp, struct library *lib) +{ + return 0; +} +#endif + +#ifndef ARCH_HAVE_LIBRARY_DATA +int arch_library_init(struct library *lib) { + return 0; } void @@ -42,9 +68,30 @@ arch_library_destroy(struct library *lib) { } -void +int arch_library_clone(struct library *retp, struct library *lib) { + return 0; +} +#endif + +#ifndef OS_HAVE_LIBRARY_SYMBOL_DATA +int +os_library_symbol_init(struct library_symbol *libsym) +{ + return 0; +} + +void +os_library_symbol_destroy(struct library_symbol *libsym) +{ +} + +int +os_library_symbol_clone(struct library_symbol *retp, + struct library_symbol *libsym) +{ + return 0; } #endif @@ -68,46 +115,39 @@ arch_library_symbol_clone(struct library_symbol *retp, } #endif -unsigned int -target_address_hash(const void *key) +size_t +arch_addr_hash(const arch_addr_t *addr) { - /* XXX this assumes that key is passed by value. */ union { arch_addr_t addr; - unsigned int ints[sizeof(arch_addr_t) - / sizeof(unsigned int)]; - } u = { .addr = (arch_addr_t)key }; + int ints[sizeof(arch_addr_t) + / sizeof(unsigned int)]; + } u = { .addr = *addr }; size_t i; - unsigned int h = 0; + size_t h = 0; for (i = 0; i < sizeof(u.ints) / sizeof(*u.ints); ++i) - h ^= dict_key2hash_int((void *)(uintptr_t)u.ints[i]); + h ^= dict_hash_int(&u.ints[i]); return h; } int -target_address_cmp(const void *key1, const void *key2) +arch_addr_eq(const arch_addr_t *addr1, const arch_addr_t *addr2) { - /* XXX this assumes that key is passed by value. */ - arch_addr_t addr1 = (arch_addr_t)key1; - arch_addr_t addr2 = (arch_addr_t)key2; - return addr1 < addr2 ? 1 - : addr1 > addr2 ? -1 : 0; + return *addr1 == *addr2; } -/* If the other symbol owns the name, we need to make the copy, so - * that the life-times of the two symbols are not dependent on each - * other. */ -static int -strdup_if_owned(const char **retp, const char *str, int owned) +int +strdup_if(const char **retp, const char *str, int whether) { - if (!owned || str == NULL) { - *retp = str; - return 0; - } else { - *retp = strdup(str); - return *retp != NULL ? 0 : -1; + if (whether && str != NULL) { + str = strdup(str); + if (str == NULL) + return -1; } + + *retp = str; + return 0; } static void @@ -125,6 +165,7 @@ private_library_symbol_init(struct library_symbol *libsym, libsym->latent = latent; libsym->delayed = delayed; libsym->enter_addr = (void *)(uintptr_t)addr; + libsym->proto = NULL; } static void @@ -141,37 +182,55 @@ library_symbol_init(struct library_symbol *libsym, private_library_symbol_init(libsym, addr, name, own_name, type_of_plt, 0, 0); - /* If arch init fails, we've already set libsym->name and - * own_name. But we return failure, and the client code isn't - * supposed to call library_symbol_destroy in such a case. */ - return arch_library_symbol_init(libsym); + if (os_library_symbol_init(libsym) < 0) + /* We've already set libsym->name and own_name. But + * we return failure, and the client code isn't + * supposed to call library_symbol_destroy in such + * case. */ + return -1; + + if (arch_library_symbol_init(libsym) < 0) { + os_library_symbol_destroy(libsym); + return -1; + } + + return 0; } void library_symbol_destroy(struct library_symbol *libsym) { if (libsym != NULL) { - private_library_symbol_destroy(libsym); arch_library_symbol_destroy(libsym); + os_library_symbol_destroy(libsym); + private_library_symbol_destroy(libsym); } } int library_symbol_clone(struct library_symbol *retp, struct library_symbol *libsym) { + /* Make lifetimes of name stored at original independent of + * the one at the clone. */ const char *name; - if (strdup_if_owned(&name, libsym->name, libsym->own_name) < 0) + if (strdup_if(&name, libsym->name, libsym->own_name) < 0) return -1; private_library_symbol_init(retp, libsym->enter_addr, name, libsym->own_name, libsym->plt_type, libsym->latent, libsym->delayed); - if (arch_library_symbol_clone(retp, libsym) < 0) { + if (os_library_symbol_clone(retp, libsym) < 0) { + fail: private_library_symbol_destroy(retp); return -1; } + if (arch_library_symbol_clone(retp, libsym) < 0) { + os_library_symbol_destroy(retp); + goto fail; + } + return 0; } @@ -230,6 +289,7 @@ private_library_init(struct library *lib, enum library_type type) lib->base = 0; lib->entry = 0; lib->dyn_addr = 0; + lib->protolib = NULL; lib->soname = NULL; lib->own_soname = 0; @@ -238,37 +298,223 @@ private_library_init(struct library *lib, enum library_type type) lib->own_pathname = 0; lib->symbols = NULL; - lib->exported_names = NULL; + library_exported_names_init(&lib->exported_names); + lib->should_activate_latent = false; lib->type = type; + +#if defined(HAVE_LIBDW) + lib->dwfl_module = NULL; +#endif } -void +int library_init(struct library *lib, enum library_type type) { private_library_init(lib, type); - arch_library_init(lib); + + if (os_library_init(lib) < 0) + return -1; + + if (arch_library_init(lib) < 0) { + os_library_destroy(lib); + return -1; + } + + return 0; +} + + + + + +static void dtor_string(const char **tgt, void *data) +{ + free((char*)*tgt); +} +static int clone_vect(struct vect **to, const struct vect **from, void *data) +{ + *to = malloc(sizeof(struct vect)); + if (*to == NULL) + return -1; + + return + VECT_CLONE(*to, *from, const char*, + dict_clone_string, + dtor_string, + NULL); +} +static void dtor_vect(struct vect **tgt, void *data) +{ + VECT_DESTROY(*tgt, const char*, dtor_string, NULL); + free(*tgt); +} + +static void +library_exported_names_init(struct library_exported_names *names) +{ + DICT_INIT(&names->names, + const char*, uint64_t, + dict_hash_string, dict_eq_string, NULL); + DICT_INIT(&names->addrs, + uint64_t, struct vect*, + dict_hash_uint64, dict_eq_uint64, NULL); +} + +static void +library_exported_names_destroy(struct library_exported_names *names) +{ + DICT_DESTROY(&names->names, + const char*, uint64_t, + dtor_string, NULL, NULL); + DICT_DESTROY(&names->addrs, + uint64_t, struct vect*, + NULL, dtor_vect, NULL); } static int -library_exported_name_clone(struct library_exported_name *retp, - struct library_exported_name *exnm) +library_exported_names_clone(struct library_exported_names *retp, + const struct library_exported_names *names) +{ + return (DICT_CLONE(&retp->names, &names->names, + const char*, uint64_t, + dict_clone_string, dtor_string, + NULL, NULL, + NULL) < 0 || + DICT_CLONE(&retp->addrs, &names->addrs, + uint64_t, struct vect*, + NULL, NULL, + clone_vect, dtor_vect, + NULL) < 0) ? -1 : 0; +} + +int library_exported_names_push(struct library_exported_names *names, + uint64_t addr, char *name, + int own_name ) { - char *name = exnm->own_name ? strdup(exnm->name) : (char *)exnm->name; + // first, take ownership of the name, if it's not yet ours + if (!own_name) + name = strdup(name); if (name == NULL) return -1; - retp->name = name; - retp->own_name = exnm->own_name; + + // push to the name->addr map + int result = DICT_INSERT(&names->names, &name, &addr); + if (result > 0) { + // This symbol is already present in the table. This library has + // multiple copies of this symbol (probably corresponding to + // different symbol versions). I should handle this gracefully + // at some point, but for now I simply ignore later instances of + // any particular symbol + free(name); + return 0; + } + + if (result != 0) + return result; + + // push to the addr->names map + // I get the alias vector. If it doesn't yet exist, I make it + struct vect *aliases; + struct vect **paliases = DICT_FIND_REF(&names->addrs, + &addr, struct vect*); + + if (paliases == NULL) { + aliases = malloc(sizeof(struct vect)); + if (aliases == NULL) + return -1; + VECT_INIT(aliases, const char*); + result = DICT_INSERT(&names->addrs, &addr, &aliases); + assert(result <= 0); + if (result < 0) + return result; + } + else + aliases = *paliases; + + char *namedup = strdup(name); + if (namedup == NULL) + return -1; + + result = vect_pushback(aliases, &namedup); + if (result != 0) { + free(namedup); + return result; + } + return 0; } +struct library_exported_names_each_alias_context +{ + enum callback_status (*inner_cb)(const char *, void *); + const char *origname; + void *data; +}; +static enum callback_status +library_exported_names_each_alias_cb(const char **name, void *data) +{ + struct library_exported_names_each_alias_context *context = + (struct library_exported_names_each_alias_context*)data; + + // I do not report the original name we were asked about. Otherwise, any + // time the caller asks for aliases of symbol "sym", I'll always report + // "sym" along with any actual aliases + if (strcmp(*name, context->origname) == 0) + return CBS_CONT; + + return context->inner_cb(*name, context->data); +} + +const char** library_exported_names_each_alias( + struct library_exported_names *names, + const char *aliasname, + const char **name_start_after, + enum callback_status (*cb)(const char *, + void *), + void *data) +{ + // I have a symbol name. I look up its address, then get the list of + // aliased names + uint64_t *addr = DICT_FIND_REF(&names->names, + &aliasname, uint64_t); + if (addr == NULL) + return NULL; + + // OK. I have an address. Get the list of symbols at this address + struct vect **aliases = DICT_FIND_REF(&names->addrs, + addr, struct vect*); + assert(aliases != NULL); + + struct library_exported_names_each_alias_context context = + {.inner_cb = cb, + .origname = aliasname, + .data = data}; + return VECT_EACH(*aliases, const char*, name_start_after, + library_exported_names_each_alias_cb, &context); +} + +int library_exported_names_contains(struct library_exported_names *names, + const char *queryname) +{ + uint64_t *addr = DICT_FIND_REF(&names->names, + &queryname, uint64_t); + return (addr == NULL) ? 0 : 1; +} + + + + + int library_clone(struct library *retp, struct library *lib) { const char *soname = NULL; const char *pathname; - if (strdup_if_owned(&soname, lib->soname, lib->own_soname) < 0 - || strdup_if_owned(&pathname, - lib->pathname, lib->own_pathname) < 0) { + + /* Make lifetimes of strings stored at original independent of + * those at the clone. */ + if (strdup_if(&soname, lib->soname, lib->own_soname) < 0 + || strdup_if(&pathname, lib->pathname, lib->own_pathname) < 0) { if (lib->own_soname) free((char *)soname); return -1; @@ -277,9 +523,9 @@ library_clone(struct library *retp, struct library *lib) private_library_init(retp, lib->type); library_set_soname(retp, soname, lib->own_soname); library_set_pathname(retp, pathname, lib->own_pathname); - arch_library_clone(retp, lib); retp->key = lib->key; + retp->should_activate_latent = lib->should_activate_latent; /* Clone symbols. */ { @@ -290,6 +536,7 @@ library_clone(struct library *retp, struct library *lib) if (*nsymp == NULL || library_symbol_clone(*nsymp, it) < 0) { free(*nsymp); + *nsymp = NULL; fail: /* Release what we managed to allocate. */ library_destroy(retp); @@ -303,21 +550,24 @@ library_clone(struct library *retp, struct library *lib) } /* Clone exported names. */ - { - struct library_exported_name *it; - struct library_exported_name **nnamep = &retp->exported_names; - for (it = lib->exported_names; it != NULL; it = it->next) { - *nnamep = malloc(sizeof(**nnamep)); - if (*nnamep == NULL - || library_exported_name_clone(*nnamep, it) < 0) { - free(*nnamep); - goto fail; - } - nnamep = &(*nnamep)->next; - } - *nnamep = NULL; + if (library_exported_names_clone(&retp->exported_names, + &lib->exported_names) != 0) + goto fail; + + if (os_library_clone(retp, lib) < 0) + goto fail; + + if (arch_library_clone(retp, lib) < 0) { + os_library_destroy(retp); + goto fail; } +#if defined(HAVE_LIBDW) + /* Wipe DWFL_MODULE, leave it to proc_add_library to + * initialize. */ + lib->dwfl_module = NULL; +#endif + return 0; } @@ -328,6 +578,8 @@ library_destroy(struct library *lib) return; arch_library_destroy(lib); + os_library_destroy(lib); + library_set_soname(lib, NULL, 0); library_set_pathname(lib, NULL, 0); @@ -340,14 +592,7 @@ library_destroy(struct library *lib) } /* Release exported names. */ - struct library_exported_name *it; - for (it = lib->exported_names; it != NULL; ) { - struct library_exported_name *next = it->next; - if (it->own_name) - free((char *)it->name); - free(it); - it = next; - } + library_exported_names_destroy(&lib->exported_names); } void @@ -412,7 +657,7 @@ library_add_symbol(struct library *lib, struct library_symbol *first) } enum callback_status -library_named_cb(struct Process *proc, struct library *lib, void *name) +library_named_cb(struct process *proc, struct library *lib, void *name) { if (name == lib->soname || strcmp(lib->soname, (char *)name) == 0) @@ -422,7 +667,7 @@ library_named_cb(struct Process *proc, struct library *lib, void *name) } enum callback_status -library_with_key_cb(struct Process *proc, struct library *lib, void *keyp) +library_with_key_cb(struct process *proc, struct library *lib, void *keyp) { return lib->key == *(arch_addr_t *)keyp ? CBS_STOP : CBS_CONT; } @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2006 Paul Gilliam, IBM Corporation * * This program is free software; you can redistribute it and/or @@ -19,30 +19,45 @@ * 02110-1301 USA */ -#ifndef _LIBRARY_H_ -#define _LIBRARY_H_ +#ifndef LIBRARY_H +#define LIBRARY_H #include <stdint.h> +#include <stdbool.h> + +#if defined(HAVE_LIBDW) +# include <elfutils/libdwfl.h> +#endif + +#include "dict.h" #include "callback.h" +#include "forward.h" #include "sysdep.h" -struct Process; -struct library; - enum toplt { LS_TOPLT_NONE = 0, /* PLT not used for this symbol. */ LS_TOPLT_EXEC, /* PLT for this symbol is executable. */ }; /* Dict interface. */ -unsigned int target_address_hash(const void *key); -int target_address_cmp(const void *key1, const void *key2); +size_t arch_addr_hash(const arch_addr_t *addr); +int arch_addr_eq(const arch_addr_t *addr1, const arch_addr_t *addr2); -/* For handling -l. */ -struct library_exported_name { - struct library_exported_name *next; - const char *name; - int own_name : 1; + +/* For handling -l and for handling library export aliases (different symbol + * name, same address) + * + * This structure needs to + * - store (addr, name) tuples + * - be searchable by addr (when populating) + * - be searchable by name (when looking for aliases) + * - be enumeratable (by activate_latent_in()) + */ +struct library_exported_names { + // I store the data in several structures to facilitate different types + // of access + struct dict names; // maps a name to an address + struct dict addrs; // maps an address to a vect of names }; struct library_symbol { @@ -51,6 +66,11 @@ struct library_symbol { const char *name; arch_addr_t enter_addr; enum toplt plt_type; + + /* If this is non-NULL, this prototype is used instead of + * looking up one in LIB->protolib. */ + struct prototype *proto; + int own_name : 1; /* This is relevant for PLT symbols. Latent PLT symbols are @@ -67,6 +87,7 @@ struct library_symbol { int delayed : 1; struct arch_library_symbol_data arch; + struct os_library_symbol_data os; }; /* Init LIBSYM. NAME will be freed when LIBSYM is destroyed if @@ -116,6 +137,7 @@ enum callback_status library_symbol_delayed_cb(struct library_symbol *libsym, enum library_type { LT_LIBTYPE_MAIN, LT_LIBTYPE_DSO, + LT_LIBTYPE_SYSCALL, }; /* XXX we might consider sharing libraries across processes. Things @@ -148,8 +170,13 @@ struct library { /* List of names that this library implements, and that match * -l filter. Each time a new library is mapped, its list of * exports is examined, and corresponding PLT slots are - * enabled. */ - struct library_exported_name *exported_names; + * enabled. This data structure also keeps track of export + * addresses to find symbols with different names, but same + * addresses */ + struct library_exported_names exported_names; + + /* Prototype library associated with this library. */ + struct protolib *protolib; const char *soname; const char *pathname; @@ -158,12 +185,18 @@ struct library { char own_soname : 1; char own_pathname : 1; + bool should_activate_latent : 1; struct arch_library_data arch; + struct os_library_data os; + +#if defined(HAVE_LIBDW) + Dwfl_Module *dwfl_module; +#endif }; /* Init LIB. */ -void library_init(struct library *lib, enum library_type type); +int library_init(struct library *lib, enum library_type type); /* Initialize RETP to a library identical to LIB. Symbols are not * shared, but copied over. Returns 0 on success and a negative value @@ -195,7 +228,7 @@ void library_add_symbol(struct library *lib, struct library_symbol *sym); /* A function that can be used as proc_each_library callback. Looks * for a library with the name passed in DATA. PROC is ignored. */ -enum callback_status library_named_cb(struct Process *proc, +enum callback_status library_named_cb(struct process *proc, struct library *lib, void *name); /* A function that can be used as proc_each_library callback. Looks @@ -203,7 +236,7 @@ enum callback_status library_named_cb(struct Process *proc, * * NOTE: The key is passed as a POINTER to arch_addr_t (that * because in general, arch_addr_t doesn't fit in void*). */ -enum callback_status library_with_key_cb(struct Process *proc, +enum callback_status library_with_key_cb(struct process *proc, struct library *lib, void *keyp); /* XXX this should really be in backend.h (as on pmachata/revamp @@ -220,7 +253,42 @@ int arch_translate_address(struct ltelf *lte, arch_addr_t addr, arch_addr_t *ret); /* This is the same function as arch_translate_address, except it's * used at the point that we don't have ELF available anymore. */ -int arch_translate_address_dyn(struct Process *proc, +int arch_translate_address_dyn(struct process *proc, arch_addr_t addr, arch_addr_t *ret); -#endif /* _LIBRARY_H_ */ + +/* Pushes a name/address tuple to the list of a library's exports. Returns 0 on + * success + */ +int library_exported_names_push(struct library_exported_names *names, + uint64_t addr, char *name, + int own_name ); + +/* Iterates through the a library's export list, reporting each symbol that is + * an alias of the given 'aliasname' symbol. This 'aliasname' symbol itself is + * NOT reported, so if this symbol is unique, the callback is not called at all. + * + * If we want to iterate through the whole alias list, set + * name_start_after=NULL. If we want to start iterating immediately past a + * particular symbol name, pass a pointer to this symbol name in + * name_start_after. This must be a pointer in the internal dict, preferably + * returned by an earlier call to this function + * + * If the callback fails at any point, a pointer to the failing key is returned. + * On success, returns NULL. The returned pointer can be passed back to this + * function in name_start_after to resume skipping this element + */ +const char** library_exported_names_each_alias( + struct library_exported_names *names, + const char *aliasname, + const char **name_start_after, + enum callback_status (*cb)(const char *, + void *), + void *data); + +/* Returns 0 if the exported names list does not contain a given name, or 1 if + * it does */ +int library_exported_names_contains(struct library_exported_names *names, + const char *queryname); + +#endif /* LIBRARY_H */ diff --git a/ltrace-elf.c b/ltrace-elf.c index c571d2a..24d02f0 100644 --- a/ltrace-elf.c +++ b/ltrace-elf.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2006,2010,2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2006,2010,2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Zachary T Welch, CodeSourcery * Copyright (C) 2010 Joe Damato * Copyright (C) 1997,1998,2001,2004,2007,2008,2009 Juan Cespedes @@ -36,10 +36,12 @@ #include <gelf.h> #include <inttypes.h> #include <search.h> +#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <strings.h> #include <unistd.h> #include "backend.h" @@ -63,49 +65,50 @@ arch_elf_destroy(struct ltelf *lte) } #endif -int -default_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, - const char *a_name, GElf_Rela *rela, size_t ndx, - struct library_symbol **ret) +#ifndef OS_HAVE_ADD_PLT_ENTRY +enum plt_status +os_elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *a_name, GElf_Rela *rela, size_t ndx, + struct library_symbol **ret) { - char *name = strdup(a_name); - if (name == NULL) { - fail_message: - fprintf(stderr, "Couldn't create symbol for PLT entry: %s\n", - strerror(errno)); - fail: - free(name); - return -1; - } - - GElf_Addr addr = arch_plt_sym_val(lte, ndx, rela); - - struct library_symbol *libsym = malloc(sizeof(*libsym)); - if (libsym == NULL) - goto fail_message; - - /* XXX The double cast should be removed when - * arch_addr_t becomes integral type. */ - arch_addr_t taddr = (arch_addr_t) - (uintptr_t)(addr + lte->bias); - - if (library_symbol_init(libsym, taddr, name, 1, LS_TOPLT_EXEC) < 0) { - free(libsym); - goto fail; - } - - libsym->next = *ret; - *ret = libsym; - return 0; + return PLT_DEFAULT; } +#endif #ifndef ARCH_HAVE_ADD_PLT_ENTRY enum plt_status -arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, +arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, const char *a_name, GElf_Rela *rela, size_t ndx, struct library_symbol **ret) { - return plt_default; + return PLT_DEFAULT; +} +#endif + +#ifndef OS_HAVE_ADD_FUNC_ENTRY +enum plt_status +os_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret) +{ + if (GELF_ST_TYPE(sym->st_info) != STT_FUNC) { + *ret = NULL; + return PLT_OK; + } else { + return PLT_DEFAULT; + } +} +#endif + +#ifndef ARCH_HAVE_ADD_FUNC_ENTRY +enum plt_status +arch_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret) +{ + return PLT_DEFAULT; } #endif @@ -140,8 +143,9 @@ elf_get_section_if(struct ltelf *lte, Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr, return 0; } } - return -1; + *tgt_sec = NULL; + return 0; } static int @@ -202,23 +206,83 @@ elf_get_section_named(struct ltelf *lte, const char *name, &name_p, &data); } -static int -need_data(Elf_Data *data, GElf_Xword offset, GElf_Xword size) +static struct elf_each_symbol_t +each_symbol_in(Elf_Data *symtab, const char *strtab, size_t count, + unsigned i, + enum callback_status (*cb)(GElf_Sym *symbol, + const char *name, void *data), + void *data) +{ + for (; i < count; ++i) { + GElf_Sym sym; + if (gelf_getsym(symtab, i, &sym) == NULL) + return (struct elf_each_symbol_t){ i, -2 }; + + switch (cb(&sym, strtab + sym.st_name, data)) { + case CBS_FAIL: + return (struct elf_each_symbol_t){ i, -1 }; + case CBS_STOP: + return (struct elf_each_symbol_t){ i + 1, 0 }; + case CBS_CONT: + break; + } + } + + return (struct elf_each_symbol_t){ 0, 0 }; +} + +/* N.B.: gelf_getsym takes integer argument. Since negative values + * are invalid as indices, we can use the extra bit to encode which + * symbol table we are looking into. ltrace currently doesn't handle + * more than two symbol tables anyway, nor does it handle the xindex + * stuff. */ +struct elf_each_symbol_t +elf_each_symbol(struct ltelf *lte, unsigned start_after, + enum callback_status (*cb)(GElf_Sym *symbol, + const char *name, void *data), + void *data) +{ + unsigned index = start_after == 0 ? 0 : start_after >> 1; + + /* Go through static symbol table first. */ + if ((start_after & 0x1) == 0) { + struct elf_each_symbol_t st + = each_symbol_in(lte->symtab, lte->strtab, + lte->symtab_count, index, cb, data); + + /* If the iteration stopped prematurely, bail out. */ + if (st.restart != 0) + return ((struct elf_each_symbol_t) + { st.restart << 1, st.status }); + } + + struct elf_each_symbol_t st + = each_symbol_in(lte->dynsym, lte->dynstr, lte->dynsym_count, + index, cb, data); + if (st.restart != 0) + return ((struct elf_each_symbol_t) + { st.restart << 1 | 0x1, st.status }); + + return (struct elf_each_symbol_t){ 0, 0 }; +} + +int +elf_can_read_next(Elf_Data *data, GElf_Xword offset, GElf_Xword size) { assert(data != NULL); if (data->d_size < size || offset > data->d_size - size) { debug(1, "Not enough data to read %"PRId64"-byte value" " at offset %"PRId64".", size, offset); - return -1; + return 0; } - return 0; + return 1; } #define DEF_READER(NAME, SIZE) \ int \ NAME(Elf_Data *data, GElf_Xword offset, uint##SIZE##_t *retp) \ { \ - if (!need_data(data, offset, SIZE / 8) < 0) \ + if (!elf_can_read_next(data, offset, SIZE / 8)) \ return -1; \ \ if (data->d_buf == NULL) /* NODATA section */ { \ @@ -235,18 +299,73 @@ need_data(Elf_Data *data, GElf_Xword offset, GElf_Xword size) return 0; \ } +DEF_READER(elf_read_u8, 8) DEF_READER(elf_read_u16, 16) DEF_READER(elf_read_u32, 32) DEF_READER(elf_read_u64, 64) #undef DEF_READER +#define DEF_READER(NAME, SIZE) \ + int \ + NAME(Elf_Data *data, GElf_Xword *offset, uint##SIZE##_t *retp) \ + { \ + int rc = elf_read_u##SIZE(data, *offset, retp); \ + if (rc < 0) \ + return rc; \ + *offset += SIZE / 8; \ + return 0; \ + } + +DEF_READER(elf_read_next_u8, 8) +DEF_READER(elf_read_next_u16, 16) +DEF_READER(elf_read_next_u32, 32) +DEF_READER(elf_read_next_u64, 64) + +#undef DEF_READER + +int +elf_read_next_uleb128(Elf_Data *data, GElf_Xword *offset, uint64_t *retp) +{ + uint64_t result = 0; + int shift = 0; + int size = 8 * sizeof result; + + while (1) { + uint8_t byte; + if (elf_read_next_u8(data, offset, &byte) < 0) + return -1; + + uint8_t payload = byte & 0x7f; + result |= (uint64_t)payload << shift; + shift += 7; + if (shift > size && byte != 0x1) + return -1; + if ((byte & 0x80) == 0) + break; + } + + if (retp != NULL) + *retp = result; + return 0; +} + int -open_elf(struct ltelf *lte, const char *filename) +elf_read_uleb128(Elf_Data *data, GElf_Xword offset, uint64_t *retp) { + return elf_read_next_uleb128(data, &offset, retp); +} + +int +ltelf_init(struct ltelf *lte, const char *filename) +{ + memset(lte, 0, sizeof *lte); lte->fd = open(filename, O_RDONLY); - if (lte->fd == -1) + if (lte->fd == -1) { + fprintf(stderr, "Can't open %s: %s\n", filename, + strerror(errno)); return 1; + } elf_version(EV_CURRENT); @@ -293,9 +412,20 @@ open_elf(struct ltelf *lte, const char *filename) exit(EXIT_FAILURE); } + VECT_INIT(<e->plt_relocs, GElf_Rela); + return 0; } +void +ltelf_destroy(struct ltelf *lte) +{ + debug(DEBUG_FUNCTION, "close_elf()"); + elf_end(lte->elf); + close(lte->fd); + VECT_DESTROY(<e->plt_relocs, GElf_Rela, NULL, NULL); +} + static void read_symbol_table(struct ltelf *lte, const char *filename, Elf_Scn *scn, GElf_Shdr *shdr, const char *name, @@ -316,7 +446,7 @@ read_symbol_table(struct ltelf *lte, const char *filename, if (scn == NULL || gelf_getshdr(scn, &shdr2) == NULL) { fprintf(stderr, "Couldn't get header of section" " #%d from \"%s\": %s\n", - shdr2.sh_link, filename, elf_errmsg(-1)); + shdr->sh_link, filename, elf_errmsg(-1)); exit(EXIT_FAILURE); } @@ -333,13 +463,118 @@ read_symbol_table(struct ltelf *lte, const char *filename, } static int -do_init_elf(struct ltelf *lte, const char *filename) +rel_to_rela(struct ltelf *lte, const GElf_Rel *rel, GElf_Rela *rela) +{ + rela->r_offset = rel->r_offset; + rela->r_info = rel->r_info; + + Elf_Scn *sec; + GElf_Shdr shdr; + if (elf_get_section_covering(lte, rel->r_offset, &sec, &shdr) < 0 + || sec == NULL) + return -1; + + Elf_Data *data = elf_loaddata(sec, &shdr); + if (data == NULL) + return -1; + + GElf_Xword offset = rel->r_offset - shdr.sh_addr - data->d_off; + uint64_t value; + if (lte->ehdr.e_ident[EI_CLASS] == ELFCLASS32) { + uint32_t tmp; + if (elf_read_u32(data, offset, &tmp) < 0) + return -1; + value = tmp; + } else if (elf_read_u64(data, offset, &value) < 0) { + return -1; + } + + rela->r_addend = value; + return 0; +} + +int +elf_read_relocs(struct ltelf *lte, Elf_Scn *scn, GElf_Shdr *shdr, + struct vect *rela_vec) +{ + if (vect_reserve_additional(rela_vec, lte->ehdr.e_shnum) < 0) + return -1; + + Elf_Data *relplt = elf_loaddata(scn, shdr); + if (relplt == NULL) { + fprintf(stderr, "Couldn't load .rel*.plt data.\n"); + return -1; + } + + if ((shdr->sh_size % shdr->sh_entsize) != 0) { + fprintf(stderr, ".rel*.plt size (%" PRIx64 "d) not a multiple " + "of its sh_entsize (%" PRIx64 "d).\n", + shdr->sh_size, shdr->sh_entsize); + return -1; + } + + GElf_Xword relplt_count = shdr->sh_size / shdr->sh_entsize; + GElf_Xword i; + for (i = 0; i < relplt_count; ++i) { + GElf_Rela rela; + if (relplt->d_type == ELF_T_REL) { + GElf_Rel rel; + if (gelf_getrel(relplt, i, &rel) == NULL + || rel_to_rela(lte, &rel, &rela) < 0) + return -1; + + } else if (gelf_getrela(relplt, i, &rela) == NULL) { + return -1; + } + + if (VECT_PUSHBACK(rela_vec, &rela) < 0) + return -1; + } + + return 0; +} + +int +elf_load_dynamic_entry(struct ltelf *lte, int tag, GElf_Addr *valuep) +{ + Elf_Scn *scn; + GElf_Shdr shdr; + if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0 + || scn == NULL) { + fail: + fprintf(stderr, "Couldn't get SHT_DYNAMIC: %s\n", + elf_errmsg(-1)); + return -1; + } + + Elf_Data *data = elf_loaddata(scn, &shdr); + if (data == NULL) + goto fail; + + size_t j; + for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) { + GElf_Dyn dyn; + if (gelf_getdyn(data, j, &dyn) == NULL) + goto fail; + + if(dyn.d_tag == tag) { + *valuep = dyn.d_un.d_ptr; + return 0; + } + } + + return -1; +} + +static int +ltelf_read_elf(struct ltelf *lte, const char *filename) { int i; GElf_Addr relplt_addr = 0; GElf_Addr soname_offset = 0; + GElf_Xword relplt_size = 0; - debug(DEBUG_FUNCTION, "do_init_elf(filename=%s)", filename); + debug(DEBUG_FUNCTION, "ltelf_read_elf(filename=%s)", filename); debug(1, "Reading ELF from %s...", filename); for (i = 1; i < lte->ehdr.e_shnum; ++i) { @@ -398,7 +633,7 @@ do_init_elf(struct ltelf *lte, const char *filename) if (dyn.d_tag == DT_JMPREL) relplt_addr = dyn.d_un.d_ptr; else if (dyn.d_tag == DT_PLTRELSZ) - lte->relplt_size = dyn.d_un.d_val; + relplt_size = dyn.d_un.d_val; else if (dyn.d_tag == DT_SONAME) soname_offset = dyn.d_un.d_val; } @@ -431,14 +666,9 @@ do_init_elf(struct ltelf *lte, const char *filename) if (!relplt_addr || !lte->plt_addr) { debug(1, "%s has no PLT relocations", filename); - lte->relplt = NULL; - lte->relplt_count = 0; - } else if (lte->relplt_size == 0) { + } else if (relplt_size == 0) { debug(1, "%s has unknown PLT size", filename); - lte->relplt = NULL; - lte->relplt_count = 0; } else { - for (i = 1; i < lte->ehdr.e_shnum; ++i) { Elf_Scn *scn; GElf_Shdr shdr; @@ -451,12 +681,9 @@ do_init_elf(struct ltelf *lte, const char *filename) exit(EXIT_FAILURE); } if (shdr.sh_addr == relplt_addr - && shdr.sh_size == lte->relplt_size) { - lte->relplt = elf_getdata(scn, NULL); - lte->relplt_count = - shdr.sh_size / shdr.sh_entsize; - if (lte->relplt == NULL - || elf_getdata(scn, lte->relplt) != NULL) { + && shdr.sh_size == relplt_size) { + if (elf_read_relocs(lte, scn, &shdr, + <e->plt_relocs) < 0) { fprintf(stderr, "Couldn't get .rel*.plt" " data from \"%s\": %s\n", filename, elf_errmsg(-1)); @@ -472,9 +699,9 @@ do_init_elf(struct ltelf *lte, const char *filename) filename); exit(EXIT_FAILURE); } - - debug(1, "%s %zd PLT relocations", filename, lte->relplt_count); } + debug(1, "%s %zd PLT relocations", filename, + vect_size(<e->plt_relocs)); if (soname_offset != 0) lte->soname = lte->dynstr + soname_offset; @@ -482,53 +709,76 @@ do_init_elf(struct ltelf *lte, const char *filename) return 0; } -void -do_close_elf(struct ltelf *lte) +#ifndef ARCH_HAVE_GET_SYMINFO +int +arch_get_sym_info(struct ltelf *lte, const char *filename, + size_t sym_index, GElf_Rela *rela, GElf_Sym *sym) { - debug(DEBUG_FUNCTION, "do_close_elf()"); - arch_elf_destroy(lte); - elf_end(lte->elf); - close(lte->fd); + return gelf_getsym(lte->dynsym, + ELF64_R_SYM(rela->r_info), sym) != NULL ? 0 : -1; } +#endif int -elf_get_sym_info(struct ltelf *lte, const char *filename, - size_t sym_index, GElf_Rela *rela, GElf_Sym *sym) -{ - int i = sym_index; - GElf_Rel rel; - void *ret; - - if (lte->relplt->d_type == ELF_T_REL) { - ret = gelf_getrel(lte->relplt, i, &rel); - rela->r_offset = rel.r_offset; - rela->r_info = rel.r_info; - rela->r_addend = 0; - } else { - ret = gelf_getrela(lte->relplt, i, rela); +default_elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *a_name, GElf_Rela *rela, size_t ndx, + struct library_symbol **ret) +{ + char *name = strdup(a_name); + if (name == NULL) { + fail_message: + fprintf(stderr, "Couldn't create symbol for PLT entry: %s\n", + strerror(errno)); + fail: + free(name); + return -1; } - if (ret == NULL - || ELF64_R_SYM(rela->r_info) >= lte->dynsym_count - || gelf_getsym(lte->dynsym, ELF64_R_SYM(rela->r_info), - sym) == NULL) { - fprintf(stderr, - "Couldn't get relocation from \"%s\": %s\n", - filename, elf_errmsg(-1)); - exit(EXIT_FAILURE); + GElf_Addr addr = arch_plt_sym_val(lte, ndx, rela); + + struct library_symbol *libsym = malloc(sizeof(*libsym)); + if (libsym == NULL) + goto fail_message; + + /* XXX The double cast should be removed when + * arch_addr_t becomes integral type. */ + arch_addr_t taddr = (arch_addr_t) + (uintptr_t)(addr + lte->bias); + + if (library_symbol_init(libsym, taddr, name, 1, LS_TOPLT_EXEC) < 0) { + free(libsym); + goto fail; } + libsym->next = *ret; + *ret = libsym; return 0; } -#ifndef ARCH_HAVE_GET_SYMINFO int -arch_get_sym_info(struct ltelf *lte, const char *filename, - size_t sym_index, GElf_Rela *rela, GElf_Sym *sym) +elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *name, GElf_Rela *rela, size_t idx, + struct library_symbol **ret) { - return elf_get_sym_info(lte, filename, sym_index, rela, sym); + enum plt_status plts + = arch_elf_add_plt_entry(proc, lte, name, rela, idx, ret); + + if (plts == PLT_DEFAULT) + plts = os_elf_add_plt_entry(proc, lte, name, rela, idx, ret); + + switch (plts) { + case PLT_DEFAULT: + return default_elf_add_plt_entry(proc, lte, name, + rela, idx, ret); + case PLT_FAIL: + return -1; + case PLT_OK: + return 0; + } + + assert(! "Invalid return from X_elf_add_plt_entry!"); + abort(); } -#endif static void mark_chain_latent(struct library_symbol *libsym) @@ -539,53 +789,87 @@ mark_chain_latent(struct library_symbol *libsym) } } +static void +filter_symbol_chain(struct filter *filter, + struct library_symbol **libsymp, struct library *lib) +{ + assert(libsymp != NULL); + struct library_symbol **ptr = libsymp; + while (*ptr != NULL) { + if (filter_matches_symbol(filter, (*ptr)->name, lib)) { + ptr = &(*ptr)->next; + } else { + struct library_symbol *sym = *ptr; + *ptr = (*ptr)->next; + library_symbol_destroy(sym); + free(sym); + } + } +} + static int -populate_plt(struct Process *proc, const char *filename, - struct ltelf *lte, struct library *lib, - int latent_plts) +populate_plt(struct process *proc, const char *filename, + struct ltelf *lte, struct library *lib) { + const bool latent_plts = options.export_filter != NULL; + const size_t count = vect_size(<e->plt_relocs); + size_t i; - for (i = 0; i < lte->relplt_count; ++i) { - GElf_Rela rela; + for (i = 0; i < count; ++i) { + GElf_Rela *rela = VECT_ELEMENT(<e->plt_relocs, GElf_Rela, i); GElf_Sym sym; - if (arch_get_sym_info(lte, filename, i, &rela, &sym) < 0) + switch (arch_get_sym_info(lte, filename, i, rela, &sym)) { + default: + fprintf(stderr, + "Couldn't get relocation for symbol #%zd" + " from \"%s\": %s\n", + i, filename, elf_errmsg(-1)); + /* Fall through. */ + case 1: continue; /* Skip this entry. */ + case 0: + break; + } char const *name = lte->dynstr + sym.st_name; - - /* If the symbol wasn't matched, reject it, unless we - * need to keep latent PLT breakpoints for tracing - * exports. */ int matched = filter_matches_symbol(options.plt_filter, name, lib); - if (!matched && !latent_plts) - continue; struct library_symbol *libsym = NULL; - switch (arch_elf_add_plt_entry(proc, lte, name, - &rela, i, &libsym)) { - case plt_default: - if (default_elf_add_plt_entry(proc, lte, name, - &rela, i, &libsym) < 0) - /* fall-through */ - case plt_fail: - return -1; - /* fall-through */ - case plt_ok: - if (libsym != NULL) { - /* If we are adding those symbols just - * for tracing exports, mark them all - * latent. */ - if (!matched) - mark_chain_latent(libsym); - library_add_symbol(lib, libsym); - } + if (elf_add_plt_entry(proc, lte, name, rela, i, &libsym) < 0) + return -1; + + /* If we didn't match the PLT entry, filter the chain + * to only include the matching symbols (but include + * all if we are adding latent symbols) to allow + * backends to override the PLT symbol's name. */ + + if (! matched && ! latent_plts) + filter_symbol_chain(options.plt_filter, &libsym, lib); + + if (libsym != NULL) { + /* If we are adding those symbols just for + * tracing exports, mark them all latent. */ + if (! matched && latent_plts) + mark_chain_latent(libsym); + library_add_symbol(lib, libsym); } } return 0; } +void +delete_symbol_chain(struct library_symbol *libsym) +{ + while (libsym != NULL) { + struct library_symbol *tmp = libsym->next; + library_symbol_destroy(libsym); + free(libsym); + libsym = tmp; + } +} + /* When -x rules result in request to trace several aliases, we only * want to add such symbol once. The only way that those symbols * differ in is their name, e.g. in glibc you have __GI___libc_free, @@ -614,21 +898,17 @@ symbol_with_address(struct library_symbol *sym, void *addrptr) } static int -populate_this_symtab(struct Process *proc, const char *filename, +populate_this_symtab(struct process *proc, const char *filename, struct ltelf *lte, struct library *lib, - Elf_Data *symtab, const char *strtab, size_t size, - struct library_exported_name **names) + Elf_Data *symtab, const char *strtab, size_t count, + struct library_exported_names *names, + bool only_exported_names) { - /* If a valid NAMES is passed, we pass in *NAMES a list of - * symbol names that this library exports. */ - if (names != NULL) - *names = NULL; - /* Using sorted array would be arguably better, but this * should be well enough for the number of symbols that we * typically deal with. */ size_t num_symbols = 0; - struct unique_symbol *symbols = malloc(sizeof(*symbols) * size); + struct unique_symbol *symbols = malloc(sizeof(*symbols) * count); if (symbols == NULL) { fprintf(stderr, "couldn't insert symbols for -x: %s\n", strerror(errno)); @@ -639,28 +919,26 @@ populate_this_symtab(struct Process *proc, const char *filename, size_t i; for (i = 1; i < lte->ehdr.e_shnum; ++i) { Elf_Scn *scn = elf_getscn(lte->elf, i); - if (scn == NULL) - continue; GElf_Shdr shdr; - if (gelf_getshdr(scn, &shdr) == NULL) - continue; - secflags[i] = shdr.sh_flags; + if (scn == NULL || gelf_getshdr(scn, &shdr) == NULL) + secflags[i] = 0; + else + secflags[i] = shdr.sh_flags; } - for (i = 0; i < size; ++i) { + for (i = 0; i < count; ++i) { GElf_Sym sym; if (gelf_getsym(symtab, i, &sym) == NULL) { - fail: fprintf(stderr, "couldn't get symbol #%zd from %s: %s\n", i, filename, elf_errmsg(-1)); continue; } - /* XXX support IFUNC as well. */ - if (GELF_ST_TYPE(sym.st_info) != STT_FUNC - || sym.st_value == 0 - || sym.st_shndx == STN_UNDEF) + if (sym.st_value == 0 || sym.st_shndx == STN_UNDEF + /* Also ignore any special values besides direct + * section references. */ + || sym.st_shndx >= lte->ehdr.e_shnum) continue; /* Find symbol name and snip version. */ @@ -674,24 +952,23 @@ populate_this_symtab(struct Process *proc, const char *filename, name[len] = 0; /* If we are interested in exports, store this name. */ - char *name_copy = NULL; if (names != NULL) { - struct library_exported_name *export = NULL; - name_copy = strdup(name); - - if (name_copy == NULL - || (export = malloc(sizeof(*export))) == NULL) { - free(name_copy); + char *name_copy = strdup(name); + if (name_copy == NULL || + library_exported_names_push(names, + sym.st_value, + name_copy, 1) != 0) + { fprintf(stderr, "Couldn't store symbol %s. " "Tracing may be incomplete.\n", name); - } else { - export->name = name_copy; - export->own_name = 1; - export->next = *names; - *names = export; } } + /* If we're only dealing with the exported names list, there's + * nothing left to do with this symbol */ + if (only_exported_names) + continue; + /* If the symbol is not matched, skip it. We already * stored it to export list above. */ if (!filter_matches_symbol(options.static_filter, name, lib)) @@ -714,45 +991,94 @@ populate_this_symtab(struct Process *proc, const char *filename, continue; } - char *full_name; - int own_full_name = 1; - if (name_copy == NULL) { - full_name = strdup(name); - if (full_name == NULL) - goto fail; - } else { - full_name = name_copy; - own_full_name = 0; + char *full_name = strdup(name); + if (full_name == NULL) { + fprintf(stderr, "couldn't copy name of %s@%s: %s\n", + name, lib->soname, strerror(errno)); + continue; } - /* Look whether we already have a symbol for this - * address. If not, add this one. */ - struct unique_symbol key = { naddr, NULL }; - struct unique_symbol *unique - = lsearch(&key, symbols, &num_symbols, - sizeof(*symbols), &unique_symbol_cmp); - - if (unique->libsym == NULL) { - struct library_symbol *libsym = malloc(sizeof(*libsym)); - if (libsym == NULL - || library_symbol_init(libsym, naddr, - full_name, own_full_name, + struct library_symbol *libsym = NULL; + enum plt_status plts + = arch_elf_add_func_entry(proc, lte, &sym, + naddr, full_name, &libsym); + if (plts == PLT_DEFAULT) + plts = os_elf_add_func_entry(proc, lte, &sym, + naddr, full_name, &libsym); + + switch (plts) { + case PLT_DEFAULT:; + /* Put the default symbol to the chain. */ + struct library_symbol *tmp = malloc(sizeof *tmp); + if (tmp == NULL + || library_symbol_init(tmp, naddr, full_name, 1, LS_TOPLT_NONE) < 0) { - --num_symbols; - goto fail; + free(tmp); + + /* Either add the whole bunch, or none + * of it. Note that for PLT_FAIL we + * don't do this--it's the callee's + * job to clean up after itself before + * it bails out. */ + delete_symbol_chain(libsym); + libsym = NULL; + + case PLT_FAIL: + fprintf(stderr, "Couldn't add symbol %s@%s " + "for tracing.\n", name, lib->soname); + + break; } - unique->libsym = libsym; - unique->addr = naddr; - } else if (strlen(full_name) < strlen(unique->libsym->name)) { - library_symbol_set_name(unique->libsym, - full_name, own_full_name); + full_name = NULL; + tmp->next = libsym; + libsym = tmp; + break; - } else if (own_full_name) { - free(full_name); + case PLT_OK: + break; + } + + free(full_name); + + struct library_symbol *tmp; + for (tmp = libsym; tmp != NULL; ) { + /* Look whether we already have a symbol for + * this address. If not, add this one. If + * yes, look if we should pick the new symbol + * name. */ + + struct unique_symbol key = { tmp->enter_addr, NULL }; + struct unique_symbol *unique + = lsearch(&key, symbols, &num_symbols, + sizeof *symbols, &unique_symbol_cmp); + + if (unique->libsym == NULL) { + unique->libsym = tmp; + unique->addr = tmp->enter_addr; + tmp = tmp->next; + unique->libsym->next = NULL; + } else { + if (strlen(tmp->name) + < strlen(unique->libsym->name)) { + library_symbol_set_name + (unique->libsym, tmp->name, 1); + tmp->name = NULL; + } + struct library_symbol *next = tmp->next; + library_symbol_destroy(tmp); + free(tmp); + tmp = next; + } } } + /* If we're only dealing with the exported names list, there's nothing + * left to do */ + if (only_exported_names) + return 0; + + /* Now we do the union of this set of unique symbols with * what's already in the library. */ for (i = 0; i < num_symbols; ++i) { @@ -777,7 +1103,7 @@ populate_this_symtab(struct Process *proc, const char *filename, } static int -populate_symtab(struct Process *proc, const char *filename, +populate_symtab(struct process *proc, const char *filename, struct ltelf *lte, struct library *lib, int symtabs, int exports) { @@ -785,28 +1111,28 @@ populate_symtab(struct Process *proc, const char *filename, if (symtabs && lte->symtab != NULL && lte->strtab != NULL && (status = populate_this_symtab(proc, filename, lte, lib, lte->symtab, lte->strtab, - lte->symtab_count, NULL)) < 0) + lte->symtab_count, NULL, + false)) < 0) return status; /* Check whether we want to trace symbols implemented by this * library (-l). */ - struct library_exported_name **names = NULL; - if (exports) { - debug(DEBUG_FUNCTION, "-l matches %s", lib->soname); - names = &lib->exported_names; - } + struct library_exported_names *names = &lib->exported_names; + lib->should_activate_latent = exports != 0; + bool only_exported_names = symtabs == 0 && exports == 0; return populate_this_symtab(proc, filename, lte, lib, lte->dynsym, lte->dynstr, - lte->dynsym_count, names); + lte->dynsym_count, names, + only_exported_names); } static int -read_module(struct library *lib, struct Process *proc, +read_module(struct library *lib, struct process *proc, const char *filename, GElf_Addr bias, int main) { - struct ltelf lte = {}; - if (open_elf(<e, filename) < 0) + struct ltelf lte; + if (ltelf_init(<e, filename) < 0) return -1; /* XXX When we abstract ABI into a module, this should instead @@ -814,8 +1140,8 @@ read_module(struct library *lib, struct Process *proc, * * proc->abi = arch_get_abi(lte.ehdr); * - * The code in open_elf needs to be replaced by this logic. - * Be warned that libltrace.c calls open_elf as well to + * The code in ltelf_init needs to be replaced by this logic. + * Be warned that libltrace.c calls ltelf_init as well to * determine whether ABI is supported. This is to get * reasonable error messages when trying to run 64-bit binary * with 32-bit ltrace. It is desirable to preserve this. */ @@ -830,6 +1156,8 @@ read_module(struct library *lib, struct Process *proc, if (process_get_entry(proc, &entry, NULL) < 0) { fprintf(stderr, "Couldn't find entry of PIE %s\n", filename); + fail: + ltelf_destroy(<e); return -1; } /* XXX The double cast should be removed when @@ -854,26 +1182,25 @@ read_module(struct library *lib, struct Process *proc, fprintf(stderr, "Couldn't determine base address of %s\n", filename); - return -1; + goto fail; } } - if (do_init_elf(<e, filename) < 0) - return -1; + if (ltelf_read_elf(<e, filename) < 0) + goto fail; if (arch_elf_init(<e, lib) < 0) { fprintf(stderr, "Backend initialization failed.\n"); - return -1; + goto fail; } - int status = 0; if (lib == NULL) goto fail; /* Note that we set soname and pathname as soon as they are * allocated, so in case of further errors, this get released - * when LIB is release, which should happen in the caller when - * we return error. */ + * when LIB is released, which should happen in the caller + * when we return error. */ if (lib->pathname == NULL) { char *pathname = strdup(filename); @@ -888,8 +1215,10 @@ read_module(struct library *lib, struct Process *proc, goto fail; library_set_soname(lib, soname, 1); } else { - const char *soname = rindex(lib->pathname, '/') + 1; - if (soname == NULL) + const char *soname = rindex(lib->pathname, '/'); + if (soname != NULL) + soname += 1; + else soname = lib->pathname; library_set_soname(lib, soname, 0); } @@ -908,41 +1237,34 @@ read_module(struct library *lib, struct Process *proc, * arch_addr_t becomes integral type. */ lib->dyn_addr = (arch_addr_t)(uintptr_t)lte.dyn_addr; - /* There are two reasons that we need to inspect symbol tables - * or populate PLT entries. Either the user requested - * corresponding tracing features (respectively -x and -e), or - * they requested tracing exported symbols (-l). + /* There are several reasons that we need to inspect symbol tables or + * populate PLT entries. The user may have requested corresponding + * tracing features (respectively -x and -e), or they requested tracing + * exported symbols (-l). We also do this to resolve symbol aliases * - * In the latter case we need to keep even those PLT slots - * that are not requested by -e (but we keep them latent). We - * also need to inspect .dynsym to find what exports this - * library provide, to turn on existing latent PLT - * entries. */ + * In the case of -l, we need to keep even those PLT slots that are not + * requested by -e (but we keep them latent). We also need to inspect + * .dynsym to find what exports this library provide, to turn on + * existing latent PLT entries. */ int plts = filter_matches_library(options.plt_filter, lib); if ((plts || options.export_filter != NULL) - && populate_plt(proc, filename, <e, lib, - options.export_filter != NULL) < 0) + && populate_plt(proc, filename, <e, lib) < 0) goto fail; int exports = filter_matches_library(options.export_filter, lib); int symtabs = filter_matches_library(options.static_filter, lib); - if ((symtabs || exports) - && populate_symtab(proc, filename, <e, lib, - symtabs, exports) < 0) + if (populate_symtab(proc, filename, <e, lib, + symtabs, exports) < 0) goto fail; -done: - do_close_elf(<e); - return status; - -fail: - status = -1; - goto done; + arch_elf_destroy(<e); + ltelf_destroy(<e); + return 0; } int -ltelf_read_library(struct library *lib, struct Process *proc, +ltelf_read_library(struct library *lib, struct process *proc, const char *filename, GElf_Addr bias) { return read_module(lib, proc, filename, bias, 0); @@ -950,12 +1272,13 @@ ltelf_read_library(struct library *lib, struct Process *proc, struct library * -ltelf_read_main_binary(struct Process *proc, const char *path) +ltelf_read_main_binary(struct process *proc, const char *path) { struct library *lib = malloc(sizeof(*lib)); - if (lib == NULL) + if (lib == NULL || library_init(lib, LT_LIBTYPE_MAIN) < 0) { + free(lib); return NULL; - library_init(lib, LT_LIBTYPE_MAIN); + } library_set_pathname(lib, path, 0); /* There is a race between running the process and reading its @@ -965,14 +1288,13 @@ ltelf_read_main_binary(struct Process *proc, const char *path) * that. Presumably we could read the DSOs from the process * memory image, but that's not currently done. */ char *fname = pid2name(proc->pid); - if (fname == NULL) - return NULL; - if (read_module(lib, proc, fname, 0, 1) < 0) { + if (fname == NULL + || read_module(lib, proc, fname, 0, 1) < 0) { library_destroy(lib); free(lib); - return NULL; + lib = NULL; } - free(fname); + free(fname); return lib; } diff --git a/ltrace-elf.h b/ltrace-elf.h index 1a1321e..4a824c4 100644 --- a/ltrace-elf.h +++ b/ltrace-elf.h @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2006,2010,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2006,2010,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Zachary T Welch * Copyright (C) 2001,2004,2007,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand @@ -26,11 +26,11 @@ #include <gelf.h> #include <stdlib.h> -#include "sysdep.h" +#include <callback.h> -struct Process; -struct library; -struct library_symbol; +#include "forward.h" +#include "sysdep.h" +#include "vect.h" /* XXX Ok, the original idea was to separate the low-level ELF data * from the abstract "struct library" object, but we use some of the @@ -48,9 +48,11 @@ struct ltelf { GElf_Addr plt_addr; GElf_Word plt_flags; size_t plt_size; - Elf_Data *relplt; Elf_Data *plt_data; - size_t relplt_count; + + /* Vector of GElf_Rela with PLT relocations. */ + struct vect plt_relocs; + Elf_Data *symtab; const char *strtab; const char *soname; @@ -60,43 +62,59 @@ struct ltelf { size_t opd_size; GElf_Addr dyn_addr; size_t dyn_sz; - size_t relplt_size; GElf_Addr bias; GElf_Addr entry_addr; GElf_Addr base_addr; struct arch_ltelf_data arch; }; -int open_elf(struct ltelf *lte, const char *filename); -void do_close_elf(struct ltelf *lte); +int ltelf_init(struct ltelf *lte, const char *filename); +void ltelf_destroy(struct ltelf *lte); /* XXX is it possible to put breakpoints in VDSO and VSYSCALL * pseudo-libraries? For now we assume that all libraries can be * opened via a filesystem. BASE is ignored for ET_EXEC files. */ -int ltelf_read_library(struct library *lib, struct Process *proc, +int ltelf_read_library(struct library *lib, struct process *proc, const char *filename, GElf_Addr bias); /* Create a library object representing the main binary. The entry * point address is stored to *ENTRYP. */ -struct library *ltelf_read_main_binary(struct Process *proc, const char *path); +struct library *ltelf_read_main_binary(struct process *proc, const char *path); + +/* This is called for every PLT relocation R in ELF file LTE, that + * ltrace is about to add to a library constructed in process PROC. + * The corresponding PLT entry is for symbol called NAME, and it's + * I-th relocation in the file. *RET shall be initialized and + * symbol(s) corresponding to the given PLT entry will be added to the + * front. Returns 0 for success, or a negative value for failures. + * + * This calls os_elf_add_plt_entry and arch_elf_add_plt_entry in the + * background (see backend.h for documentation). The arch callback is + * called first. If it returns PLT_DEFAULT, the os callback is called + * next. If that returns PLT_DEFAULT, default_elf_add_plt_entry is + * called. */ +int elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *name, GElf_Rela *rela, size_t idx, + struct library_symbol **ret); /* Create a default PLT entry. This can be used instead (or in - * addition to) returning plt_default from arch_elf_add_plt_entry. + * addition to) returning PLT_DEFAULT from arch_elf_add_plt_entry. * RET shall be initialized, the created symbol will be added to the * beginning of the linked list at *RET. This function doesn't add * the symbol to LTE. arch_elf_add_plt_entry has the chance to adjust - * symbol internals to its liking, and then return either plt_default - * or plt_ok. */ -int default_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, + * symbol internals to its liking, and then return either PLT_DEFAULT + * or PLT_OK. */ +int default_elf_add_plt_entry(struct process *proc, struct ltelf *lte, const char *a_name, GElf_Rela *rela, size_t ndx, struct library_symbol **ret); -/* The base implementation of backend.h (arch_get_sym_info). - * See backend.h for details. */ -int elf_get_sym_info(struct ltelf *lte, const char *filename, - size_t sym_index, GElf_Rela *rela, GElf_Sym *sym); - Elf_Data *elf_loaddata(Elf_Scn *scn, GElf_Shdr *shdr); + +/* The following three look for sections based on various criteria. + * They return 0 if there was no error, or a negative value if there + * was. If the section was found, it is returned in *TGT_SEC, and the + * header is stored to TGT_SHDR. If it wasn't found, *TGT_SEC is set + * to NULL. */ int elf_get_section_covering(struct ltelf *lte, GElf_Addr addr, Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr); int elf_get_section_type(struct ltelf *lte, GElf_Word type, @@ -104,13 +122,51 @@ int elf_get_section_type(struct ltelf *lte, GElf_Word type, int elf_get_section_named(struct ltelf *lte, const char *name, Elf_Scn **tgt_sec, GElf_Shdr *tgt_shdr); -/* Read, respectively, 2, 4, or 8 bytes from Elf data at given OFFSET, - * and store it in *RETP. Returns 0 on success or a negative value if - * there's not enough data. */ +/* Iterate through all symbols in LTE. See callback.h for notes on + * iteration interfaces. START_AFTER is 0 in initial call. */ +struct elf_each_symbol_t { + unsigned restart; + int status; +} elf_each_symbol(struct ltelf *lte, unsigned start_after, + enum callback_status (*cb)(GElf_Sym *symbol, + const char *name, + void *data), + void *data); + +/* Read relocations from relocation section SCN with header SHDR and + * add them to RELA_VEC, which is a vector of GElf_Rela. Return 0 on + * success, or a negative value on failure. */ +int elf_read_relocs(struct ltelf *lte, Elf_Scn *scn, GElf_Shdr *shdr, + struct vect *rela_vec); + +/* Read a given DT_ TAG from LTE. Value is returned in *VALUEP. + * Returns 0 on success or a negative value on failure. */ +int elf_load_dynamic_entry(struct ltelf *lte, int tag, GElf_Addr *valuep); + +/* Read, respectively, 1, 2, 4, or 8 bytes from Elf data at given + * OFFSET, and store it in *RETP. Returns 0 on success or a negative + * value if there's not enough data. */ +int elf_read_u8(Elf_Data *data, GElf_Xword offset, uint8_t *retp); int elf_read_u16(Elf_Data *data, GElf_Xword offset, uint16_t *retp); int elf_read_u32(Elf_Data *data, GElf_Xword offset, uint32_t *retp); int elf_read_u64(Elf_Data *data, GElf_Xword offset, uint64_t *retp); +/* Read at most 64-bit quantity recorded in an ULEB128 variable-length + * encoding. */ +int elf_read_uleb128(Elf_Data *data, GElf_Xword offset, uint64_t *retp); + +/* These are same as above, but update *OFFSET with the width + * of read datum. */ +int elf_read_next_u8(Elf_Data *data, GElf_Xword *offset, uint8_t *retp); +int elf_read_next_u16(Elf_Data *data, GElf_Xword *offset, uint16_t *retp); +int elf_read_next_u32(Elf_Data *data, GElf_Xword *offset, uint32_t *retp); +int elf_read_next_u64(Elf_Data *data, GElf_Xword *offset, uint64_t *retp); +int elf_read_next_uleb128(Elf_Data *data, GElf_Xword *offset, uint64_t *retp); + +/* Return whether there's AMOUNT more bytes after OFFSET in DATA. */ +int elf_can_read_next(Elf_Data *data, GElf_Xword offset, GElf_Xword amount); + +void delete_symbol_chain(struct library_symbol *); #if __WORDSIZE == 32 #define PRI_ELF_ADDR PRIx32 #define GELF_ADDR_CAST(x) (void *)(uint32_t)(x) @@ -1,5 +1,5 @@ .\" -*-nroff-*- -.\" Copyright (c) 2012 Petr Machata, Red Hat Inc. +.\" Copyright (c) 2012, 2013, 2014 Petr Machata, Red Hat Inc. .\" Copyright (c) 1997-2005 Juan Cespedes <cespedes@debian.org> .\" .\" This program is free software; you can redistribute it and/or @@ -17,13 +17,65 @@ .\" Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA .\" 02110-1301 USA .\" -.TH LTRACE "1" "October 2012" "" "User Commands" +.TH LTRACE "1" "January 2013" "" "User Commands" .SH NAME ltrace \- A library call tracer .SH SYNOPSIS +.\" +.\" --------------------------------------------------------------------------- +.\" +.PP .B ltrace -.I "[-bCfghiLrStttV] [-a column] [-A maxelts] [-D level] [-e expr] [-l library_pattern] [-n nr] [-o filename] [-p pid] ... [-s strsize] [-u username] [-w count] [-x extern] ... [--align=column] [--debug=level] [--demangle] [--help] [--indent=nr] [--library=library_pattern] [--no-signals] [--output=filename] [--version] [--where=NR] [command [arg ...]]" +.\" +.\" What events to trace: +.\" +[\-e \fIfilter\fR|\-L] [\-l|\-\-library=\fIlibrary_pattern\fR] +[\-x \fIfilter\fR] [\-S] [\-b|\-\-no-signals] +.\" +.\" What to display with each event: +.\" +[\-i] [\-w|\-\-where=\fInr\fR] [\-r|\-t|\-tt|\-ttt] [\-T] +.\" +.\" Output formatting: +.\" +[[\-F|\-\-config] \fIpathlist\fR] +[\-A \fImaxelts\fR] [\-s \fIstrsize\fR] [\-C|\-\-demangle] +[\-a|\-\-align \fIcolumn\fR] [\-n|\-\-indent \fInr\fR] +[\-o|\-\-output \fIfilename\fR] +.\" +.\" Various: +.\" +[\-D|\-\-debug \fImask\fR] [\-u \fIusername\fR] +.\" +.\" What processes to trace: +.\" +[\-f] [\-p \fIpid\fR] [[\-\-] \fIcommand [arg ...]\fR] +.\" +.\" --------------------------------------------------------------------------- +.\" +.PP +.BR ltrace " \-c" +.\" +.\" What events to trace: +.\" +[\-e \fIfilter\fR|\-L] [\-l|\-\-library=\fIlibrary_pattern\fR] +[\-x \fIfilter\fR] [\-S] +.\" +.\" Output formatting: +.\" +[\-o|\-\-output \fIfilename\fR] +.\" +.\" What processes to trace: +.\" +[\-f] [\-p \fIpid\fR] [[\-\-] \fIcommand [arg ...]\fR] +.\" +.\" --------------------------------------------------------------------------- +.\" +.PP +.BR ltrace " \-V|\-\-version" +.PP +.BR ltrace " \-h|\-\-help" .SH DESCRIPTION .B ltrace @@ -37,156 +89,234 @@ It can also intercept and print the system calls executed by the program. Its use is very similar to .BR strace(1) . +.B ltrace +shows parameters of invoked functions and system calls. To determine +what arguments each function has, it needs external declaration of +function prototypes. Those are stored in files called \fIprototype +libraries\fR--see ltrace.conf(5) for details on the syntax of these +files. See the section \fBPROTOTYPE LIBRARY DISCOVERY\fR to learn how +\fBltrace\fR finds prototype libraries. + .SH OPTIONS -.TP -.I \-a, \-\-align column +.PP +.IP "\-a, \-\-align \fIcolumn" Align return values in a specific .IR column (default column is 5/8 of screen width). -.TP -.I \-A maxelts +.IP "\-A \fImaxelts" Maximum number of array elements to print before suppressing the rest with an ellipsis ("..."). This also limits number of recursive structure expansions. -.TP -.I \-b, \-\-no-signals -Disable printing of signals recieved by the traced process. -.TP -.I \-c -Count time and calls for each library call and report a summary on program exit. -.TP -.I \-C, \-\-demangle +.IP "\-b, \-\-no-signals" +Disable printing of signals received by the traced process. +.IP \-c +Count time and calls for each library call and report a summary on +program exit. +.IP "\-C, \-\-demangle" Decode (demangle) low-level symbol names into user-level names. Besides removing any initial underscore prefix used by the system, this makes C++ function names readable. -.TP -.I \-D, \-\-debug level -Show debugging output of -.B ltrace -itself. -.I level -must be a sum of some of the following numbers: -.RS -.TP -.B 01 -DEBUG_GENERAL. Shows helpful progress information -.TP -.B 010 -DEBUG_EVENT. Shows every event received by a traced program -.TP -.B 020 -DEBUG_PROCESS. Shows every action -.B ltrace -carries upon a traced process -.TP -.B 040 -DEBUG_FUNCTION. Shows every entry to internal functions -.RE -.TP -.I \-e filter -A qualifying expression which modifies which library calls to trace. -The format of the filter expression is described in the section -\fBFILTER EXPRESSIONS\fR. If more than one \-e option appears on the -command line, the library calls that match any of them are traced. If -no \-e is given, \fB@MAIN\fR is assumed as a default. -.TP -.I \-f +.IP "\-D, \-\-debug \fRmask\fI" +Show debugging output of \fBltrace\fR itself. \fImask\fR is a number +describing which debug messages should be displayed. Use the option +\-Dh to see what can be used, but note that currently the only +reliable debugmask is 77, which shows all debug messages. +.IP "\-e \fIfilter" +A qualifying expression which modifies which library calls (i.e. calls done +through PLT slots, which are typically calls from the main binary to a library, +or inter-library calls) to trace. Usage examples and the syntax description +appear below in sections \fBFILTER SPECIFICATIONS\fR and \fBFILTER +EXPRESSIONS\fR. If more than one \-e option appears on the command line, the +library calls that match any of them are traced. If no \-e is given, \fB@MAIN\fR +is assumed as a default. +.IP \-f Trace child processes as they are created by currently traced processes as a result of the fork(2) or clone(2) system calls. The new process is attached immediately. -.TP -.I \-F -Load an alternate config file. Normally, /etc/ltrace.conf and -~/.ltrace.conf will be read (the latter only if it exists). -Use this option to load the given file or files instead of -those two default files. -.TP -.I \-h, \-\-help +.IP "\-F, \-\-config \fIpathlist" +Contains a colon-separated list of paths. If a path refers to a +directory, that directory is considered when prototype libraries are +searched (see the section \fBPROTOTYPE LIBRARY DISCOVERY\fR). If it refers to +a file, that file is imported implicitly to all loaded prototype +libraries. +.IP "\-h, \-\-help" Show a summary of the options to ltrace and exit. -.TP -.I \-i +.IP \-i Print the instruction pointer at the time of the library call. -.TP -.I \-l, \-\-library library_pattern +.IP "\-l, \-\-library \fIlibrary_pattern" Display only calls to functions implemented by libraries that match .I library_pattern. -Multiple library patters can be specified with several instances of -this option. Syntax of library_pattern is described in section -\fBFILTER EXPRESSIONS\fR. +This is as if you specified one \-e for every symbol implemented in a +library specified by +.I library_pattern. +Multiple library patters can be specified with several instances of this option. +Usage examples and the syntax description of library_pattern appear below in +sections \fBFILTER SPECIFICATIONS\fR and \fBFILTER EXPRESSIONS\fR. Note that while this option selects calls that might be directed to the selected libraries, there's no actual guarantee that the call won't be directed elsewhere due to e.g. LD_PRELOAD or simply dependency ordering. If you want to make sure that symbols in given library are actually called, use \fB-x @\fIlibrary_pattern\fR instead. -.TP -.I \-L -When no -e option is given, don't assume the default action of -\fB@MAIN\fR. -.TP -.I \-n, \-\-indent nr -Indent trace output by -.I nr -number of spaces for each new nested call. Using this option makes -the program flow visualization easy to follow. -.TP -.I \-o, \-\-output filename -Write the trace output to the file -.I filename -rather than to stderr. -.TP -.I \-p pid -Attach to the process with the process ID -.I pid -and begin tracing. -.TP -.I \-r -Print a relative timestamp with each line of the trace. -This records the time difference between the beginning of -successive lines. -.TP -.I \-s strsize +.IP \-L +When no \-e option is given, don't assume the default action of +\fB@MAIN\fR. In practice this means that library calls will not be +traced. +.IP "\-n, \-\-indent \fInr" +Indent trace output by \fInr\fR spaces for each level of call +nesting. Using this option makes the program flow visualization easy +to follow. This indents uselessly also functions that never return, +such as service functions for throwing exceptions in the C++ runtime. +.IP "\-o, \-\-output \fIfilename" +Write the trace output to the file \fIfilename\fR rather than to +stderr. +.IP "\-p \fIpid" +Attach to the process with the process ID \fIpid\fR and begin tracing. +This option can be used together with passing a command to execute. +It is possible to attach to several processes by passing more than one +option \-p. +.IP \-r +Print a relative timestamp with each line of the trace. This records +the time difference between the beginning of successive lines. +.IP "\-s \fIstrsize" Specify the maximum string size to print (the default is 32). -.TP -.I \-S +.IP \-S Display system calls as well as library calls -.TP -.I \-t +.IP \-t Prefix each line of the trace with the time of day. -.TP -.I \-tt +.IP \-tt If given twice, the time printed will include the microseconds. -.TP -.I \-ttt +.IP \-ttt If given thrice, the time printed will include the microseconds and the leading portion will be printed as the number of seconds since the epoch. -.TP -.I \-T +.IP \-T Show the time spent inside each call. This records the time difference between the beginning and the end of each call. -.TP -.I \-u username +.IP "\-u \fIusername" Run command with the userid, groupid and supplementary groups of .IR username . This option is only useful when running as root and enables the correct execution of setuid and/or setgid binaries. -.TP -.I \-w, --where NR -Show backtrace of NR stack frames for each traced function. This option enabled -only if libunwind support was enabled at compile time. -.TP -.I \-x filter -A qualifying expression which modifies which symbol table entry points -to trace. The format of the filter expression is described in the -section \fBFILTER EXPRESSIONS\fR. If more than one \-x option appears -on the command line, the symbols that match any of them are traced. -No entry points are traced if no \-x is given. -.TP -.I \-V, \-\-version +.IP "\-w, \-\-where \fInr" +Show backtrace of \fInr\fR stack frames for each traced function. This +option enabled only if elfutils or libunwind support was enabled at compile +time. +.IP "\-x \fIfilter" +A qualifying expression which modifies which symbol table entry points to trace +(those are typically calls inside a library or main binary, though PLT calls, +traced by \-e, land on entry points as well). Usage examples and the syntax +description appear below in sections \fBFILTER SPECIFICATIONS\fR and \fBFILTER +EXPRESSIONS\fR. If more than one \-x option appears on the command line, the +symbols that match any of them are traced. No entry points are traced if no \-x +is given. +.IP "\-V, \-\-version" Show the version number of ltrace and exit. +.SH FILTER SPECIFICATIONS + +Filters are specified with the \-l, \-e and \-x options. In short they mean: +.IP "\fI\-x\fR is \'show me what calls these symbols (including local calls)\'" +.IP "\fI\-e\fR is \'show me what calls these symbols (inter-library calls only)\'" +.IP "\fI\-l\fR is \'show me what calls into this library\'" +.PP +.. +.de Vb \" Begin verbatim text +.ft CW +.nf +.ne \\$1 +.. +.de Ve \" End verbatim text +.ft R +.fi +.. +Suppose I have a library defined with this header \fBtstlib.h\fR: +.PP +.Vb 6 +\& void func_f_lib(void); +\& void func_g_lib(void); +.Ve +.PP +and this implementation \fBtstlib.c\fR: +.PP +.Vb 6 +\& #include "tstlib.h" +\& void func_f_lib(void) +\& { +\& func_g_lib(); +\& } +\& void func_g_lib(void) +\& { +\& } +.Ve +.PP +Suppose I have an executable that uses this library defined like this \fBtst.c\fR: +.PP +.Vb 6 +\& #include "tstlib.h" +\& void func_f_main(void) +\& { +\& } +\& void main(void) +\& { +\& func_f_main(); +\& func_f_lib(); +\& } +.Ve +.PP +If linking without \fB-Bsymbolic\fR, the internal \fBfunc_g_lib()\fR call uses +the PLT like external calls, and thus ltrace says: +.PP +.Vb 6 +\& \fB$ ltrace -x 'func*' -L ./tst\fR +\& func_f_main() = <void> +\& func_f_lib@tstlib.so( <unfinished ...> +\& func_g_lib@tstlib.so() = <void> +\& <... func_f_lib resumed> ) = <void> +\& +++ exited (status 163) +++ +.Ve + +.Vb 6 +\& \fB$ ltrace -e 'func*' ./tst\fR +\& tst->func_f_lib( <unfinished ...> +\& tstlib.so->func_g_lib() = <void> +\& <... func_f_lib resumed> ) = <void> +\& +++ exited (status 163) +++ +.Ve + +.Vb 6 +\& \fB$ ltrace -l tstlib.so ./tst\fR +\& tst->func_f_lib( <unfinished ...> +\& tstlib.so->func_g_lib() = <void> +\& <... func_f_lib resumed> ) = <void> +\& +++ exited (status 163) +++ +.Ve +.PP +By contrast, if linking with \fB-Bsymbolic\fR, then the internal +\fBfunc_g_lib()\fR call bypasses the PLT, and ltrace says: +.PP +.Vb 6 +\& \fB$ ltrace -x 'func*' -L ./tst\fR +\& func_f_main() = <void> +\& func_f_lib@tstlib.so( <unfinished ...> +\& func_g_lib@tstlib.so() = <void> +\& <... func_f_lib resumed> ) = <void> +\& +++ exited (status 163) +++ +.Ve + +.Vb 6 +\& \fB$ ltrace -e 'func*' ./tst\fR +\& tst->func_f_lib() = <void> +\& +++ exited (status 163) +++ +.Ve + +.Vb 6 +\& \fB$ ltrace -l tstlib.so ./tst\fR +\& tst->func_f_lib() = <void> +\& +++ exited (status 163) +++ +.Ve +.PP .SH FILTER EXPRESSIONS Filter expression is a chain of glob- or regexp-based rules that are @@ -235,7 +365,8 @@ path name. The first rule may lack a sign, in which case \fB+\fR is assumed. If, on the other hand, the first rule has a \fB-\fR sign, it is as if -there was another rule \fB@*\fR in front of it. +there was another rule \fB@\fR in front of it, which has the effect of +tracing complement of given rule. The above rules are used to construct the set of traced symbols. Each candidate symbol is passed through the chain of above rules. @@ -244,11 +375,61 @@ rule, it becomes \fImarked\fR, if it matches a \fB-\fR rule, it becomes \fIunmarked\fR again. If, after applying all rules, the symbol is \fImarked\fR, it will be traced. +.SH PROTOTYPE LIBRARY DISCOVERY + +When a library is mapped into the address space of a traced process, +ltrace needs to know what the prototypes are of functions that this +library implements. For purposes of ltrace, prototype really is a bit +more than just type signature: it's also formatting of individual +parameters and of return value. These prototypes are stored in files +called prototype libraries. + +After a library is mapped, ltrace finds out what its SONAME is. It +then looks for a file named SONAME.conf--e.g. protolib for libc.so.6 +would be in a file called libc.so.6.conf. When such file is found +(more about where ltrace looks for these files is below), ltrace reads +all prototypes stored therein. When a symbol table entry point (such +as those traced by \-x) is hit, the prototype is looked up in a +prototype library corresponding to the library where the hit occurred. +When a library call (such as those traced by \-e and \-l) is hit, the +prototype is looked up in all prototype libraries loaded for given +process. That is necessary, because a library call is traced in a PLT +table of a caller library, but the prototype is described at callee +library. + +If a library has no SONAME, basename of library file is considered +instead. For the main program binary, basename is considered as well +(e.g. protolib for /bin/echo would be called echo.conf). If a name +corresponding to soname (e.g. libc.so.6.conf) is not found, and the +module under consideration is a shared library, ltrace also tries +partial matches. Ltrace snips one period after another, retrying the +search, until either a protolib is found, or X.so is all that's left. +Thus libc.so.conf would be considered, but libc.conf not. + +When looking for a prototype library, ltrace potentially looks into +several directories. On Linux, those are $XDG_CONFIG_HOME/ltrace, +$HOME/.ltrace, \fIX\fR/ltrace for each \fIX\fR in $XDG_CONFIG_DIRS and +/usr/share/ltrace. If the environment variable XDG_CONFIG_HOME is not +defined, ltrace looks into $HOME/.config/ltrace instead. + +There's also a mechanism for loading legacy config files. If +$HOME/.ltrace.conf exists it is imported to every loaded prototype +library. Similarly for /etc/ltrace.conf. If both exist, both are +imported, and $HOME/.ltrace.conf is consulted before /etc/ltrace.conf. + +If \-F contains any directories, those are searched in precedence to +the above system directories, in the same order in which they are +mentioned in \-F. Any files passed in \-F are imported similarly to +above legacy config files, before them. + +See ltrace.conf(5) for details on the syntax of ltrace prototype +library files. + .SH BUGS It has most of the bugs stated in .BR strace(1) . .LP -It only works on Linux and in a small subset of architectures. +It only works on Linux and in some architectures. .LP .PP If you would like to report a bug, send a message to the mailing list diff --git a/ltrace.conf.5 b/ltrace.conf.5 index 957fe8b..1efd4fc 100644 --- a/ltrace.conf.5 +++ b/ltrace.conf.5 @@ -1,5 +1,5 @@ .\" -*-nroff-*- -.\" Copyright (c) 2012 Petr Machata, Red Hat Inc. +.\" Copyright (c) 2012, 2013 Petr Machata, Red Hat Inc. .\" Copyright (c) 1997-2005 Juan Cespedes <cespedes@debian.org> .\" .\" This program is free software; you can redistribute it and/or @@ -30,8 +30,9 @@ Ltrace needs this information to display function call arguments. Each line of a configuration file describes at most a single item. Lines composed entirely of white space are ignored, as are lines -starting with semicolon character (comment lines). Described items -can be either function prototypes, or definitions of type aliases. +starting with semicolon or hash characters (comment lines). Described +items can be either function prototypes, or definitions of type +aliases. .SH PROTOTYPES @@ -171,13 +172,16 @@ pointer to 256-bit bit vector. .RS The first form of the argument is canonical, the latter two are syntactic sugar. In the canonical form, the function argument is -formatted as string. The \fITYPE\fR shall be either a \fBchar*\fR, or -\fBarray(char,\fIEXPR\fB)\fR, or \fBarray(char,\fIEXPR\fB)*\fR. If an -array is given, the length will typically be a \fBzero\fR expression -(but doesn't have to be). Using argument that is plain array -(i.e. not a pointer to array) makes sense e.g. in C structs, in cases -like \fBstruct(string(array(char, \fR6\fB)))\fR, which describes the C -type \fBstruct {char \fRs\fB[\fR6\fB];}\fR. +formatted as string. The \fITYPE\fR can have either of the following +forms: \fIX\fB*\fR, or \fBarray(\fIX\fB,\fIEXPR\fB)\fR, or +\fBarray(\fIX\fB,\fIEXPR\fB)*\fR. \fIX\fR is either \fBchar\fR for +normal strings, or an integer type for wide-character strings. + +If an array is given, the length will typically be a \fBzero\fR +expression (but doesn't have to be). Using argument that is plain +array (i.e. not a pointer to array) makes sense e.g. in C structs, in +cases like \fBstruct(string(array(char, \fR6\fB)))\fR, which describes +the C type \fBstruct {char \fRs\fB[\fR6\fB];}\fR. Because simple C-like strings are pretty common, there are two shorthand forms. The first shorthand form (with brackets) means the @@ -221,7 +225,7 @@ such type, and later just use that name: .SH RECURSIVE STRUCTURES Ltrace allows you to express recursive structures. Such structures -are expanded to the depth described by the parameter -A. To declare a +are expanded to the depth described by the parameter \-A. To declare a recursive type, you first have to introduce the type to ltrace by using forward declaration. Then you can use the type in other type definitions in the usual way: @@ -401,7 +405,7 @@ ltrace configuration line. .br .B struct\fR S1 func_struct\fB(int \fRa\fB, struct \fRS2\fB, double \fRd\fB); .RS -.B struct(float,char,char)\fR func_struct_2\fB(int, struct(string(array(char, \fR6\fB)),float), double); +.B struct(float,char,char)\fR func_struct\fB(int, struct(string(array(char, \fR6\fB)),float), double); .RE .SH AUTHOR @@ -18,8 +18,8 @@ * 02110-1301 USA */ -#ifndef _LTRACE_H_ -#define _LTRACE_H_ +#ifndef LTRACE_H +#define LTRACE_H typedef enum Event_type Event_type; enum Event_type { @@ -44,7 +44,7 @@ enum Event_type { typedef struct Event Event; struct Event { struct Event * next; - struct Process * proc; + struct process *proc; Event_type type; union { int ret_val; /* EVENT_EXIT */ @@ -61,4 +61,4 @@ extern void ltrace_init(int argc, char **argv); extern void ltrace_add_callback(callback_func f, Event_type type); extern void ltrace_main(void); -#endif /* _LTRACE_H_ */ +#endif /* LTRACE_H */ diff --git a/memstream.c b/memstream.c new file mode 100644 index 0000000..19875e9 --- /dev/null +++ b/memstream.c @@ -0,0 +1,73 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +/* _GNU_SOURCE may be necessary for open_memstream visibility (see + * configure.ac), and there's no harm defining it just in case. */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> + +#include "config.h" +#include "memstream.h" + +int +memstream_init(struct memstream *memstream) +{ +#if HAVE_OPEN_MEMSTREAM + memstream->stream = open_memstream(&memstream->buf, + &memstream->size); +#else + memstream->stream = tmpfile(); + memstream->buf = NULL; +#endif + return memstream->stream != NULL ? 0 : -1; +} + +int +memstream_close(struct memstream *memstream) +{ +#if !defined(HAVE_OPEN_MEMSTREAM) || !HAVE_OPEN_MEMSTREAM + if (fseek(memstream->stream, 0, SEEK_END) < 0) { + fail: + fclose(memstream->stream); + return -1; + } + memstream->size = ftell(memstream->stream); + if (memstream->size == (size_t)-1) + goto fail; + memstream->buf = malloc(memstream->size); + if (memstream->buf == NULL) + goto fail; + + rewind(memstream->stream); + if (fread(memstream->buf, 1, memstream->size, memstream->stream) + < memstream->size) + goto fail; +#endif + + return fclose(memstream->stream) == 0 ? 0 : -1; +} + +void +memstream_destroy(struct memstream *memstream) +{ + free(memstream->buf); +} diff --git a/memstream.h b/memstream.h new file mode 100644 index 0000000..7360b61 --- /dev/null +++ b/memstream.h @@ -0,0 +1,40 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef MEMSTREAM_H +#define MEMSTREAM_H + +#include <stdio.h> + +/* Portability wrapper for platforms that don't have + * open_memstream. */ + +struct memstream +{ + FILE *stream; + char *buf; + size_t size; +}; + +int memstream_init(struct memstream *memstream); +int memstream_close(struct memstream *memstream); +void memstream_destroy(struct memstream *memstream); + +#endif /* MEMSTREAM_H */ @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012, 2013, 2014 Petr Machata, Red Hat Inc. * Copyright (C) 2009,2010 Joe Damato * Copyright (C) 1998,1999,2002,2003,2004,2007,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand @@ -26,6 +26,8 @@ #include "config.h" #include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> #include <assert.h> #include <errno.h> #include <fcntl.h> @@ -39,13 +41,7 @@ #include "common.h" #include "filter.h" #include "glob.h" - -#ifndef SYSCONFDIR -#define SYSCONFDIR "/etc" -#endif - -#define SYSTEM_CONFIG_FILE SYSCONFDIR "/ltrace.conf" -#define USER_CONFIG_FILE "~/.ltrace.conf" +#include "demangle.h" struct options_t options = { .align = DEFAULT_ALIGN, /* alignment column for results */ @@ -72,8 +68,8 @@ int opt_T = 0; /* show the time spent inside each call */ /* List of pids given to option -p: */ struct opt_p_t *opt_p = NULL; /* attach to process with a given pid */ -/* List of filenames give to option -F: */ -struct opt_F_t *opt_F = NULL; /* alternate configuration file(s) */ +/* Vector of struct opt_F_t. */ +struct vect opt_F; static void err_usage(void) { @@ -86,35 +82,35 @@ usage(void) { fprintf(stdout, "Usage: %s [option ...] [command [arg ...]]\n" "Trace library calls of a given program.\n\n" " -a, --align=COLUMN align return values in a secific column.\n" - " -A ARRAYLEN maximum number of array elements to print.\n" + " -A MAXELTS maximum number of array elements to print.\n" " -b, --no-signals don't print signals.\n" " -c count time and calls, and report a summary on exit.\n" # ifdef USE_DEMANGLE " -C, --demangle decode low-level symbol names into user-level names.\n" # endif - " -D, --debug=LEVEL enable debugging (see -Dh or --debug=help).\n" + " -D, --debug=MASK enable debugging (see -Dh or --debug=help).\n" " -Dh, --debug=help show help on debugging.\n" - " -e expr modify which events to trace.\n" + " -e FILTER modify which library calls to trace.\n" " -f trace children (fork() and clone()).\n" " -F, --config=FILE load alternate configuration file (may be repeated).\n" " -h, --help display this help and exit.\n" " -i print instruction pointer at time of library call.\n" - " -l, --library=FILE only trace symbols implemented by this library.\n" + " -l, --library=LIBRARY_PATTERN only trace symbols implemented by this library.\n" " -L do NOT display library calls.\n" " -n, --indent=NR indent output by NR spaces for each call level nesting.\n" - " -o, --output=FILE write the trace output to that file.\n" + " -o, --output=FILENAME write the trace output to file with given name.\n" " -p PID attach to the process with the process ID pid.\n" " -r print relative timestamps.\n" - " -s STRLEN specify the maximum string size to print.\n" - " -S display system calls.\n" + " -s STRSIZE specify the maximum string size to print.\n" + " -S trace system calls as well as library calls.\n" " -t, -tt, -ttt print absolute timestamps.\n" " -T show the time spent inside each call.\n" " -u USERNAME run command with the userid, groupid of username.\n" " -V, --version output version information and exit.\n" -#if defined(HAVE_LIBUNWIND) +#if defined(HAVE_UNWINDER) " -w, --where=NR print backtrace showing NR stack frames at most.\n" -#endif /* defined(HAVE_LIBUNWIND) */ - " -x NAME treat the global NAME like a library subroutine.\n" +#endif /* defined(HAVE_UNWINDER) */ + " -x FILTER modify which static functions to trace.\n" "\nReport bugs to ltrace-devel@lists.alioth.debian.org\n", progname); } @@ -132,6 +128,8 @@ usage_debug(void) { "\n" "Debugging options are mixed using bitwise-or.\n" "Note that the meanings and values are subject to change.\n" + "Also note that these values are used inconsistently in ltrace, and the\n" + "only debuglevel that you can rely on is -D77 that will show everything.\n" ); } @@ -204,10 +202,10 @@ compile_libname(const char *expr, const char *a_lib, int lib_re_p, regex_t lib_re; int status = (lib_re_p ? regcomp : globcomp)(&lib_re, lib, 0); - if (status != REG_NOERROR) { + if (status != 0) { char buf[100]; regerror(status, &lib_re, buf, sizeof buf); - fprintf(stderr, "Rule near '%s' will be ignored: %s.\n", + fprintf(stderr, "Couldn't compile '%s': %s.\n", expr, buf); return -1; } @@ -216,7 +214,7 @@ compile_libname(const char *expr, const char *a_lib, int lib_re_p, return 0; } -static void +static int add_filter_rule(struct filter *filt, const char *expr, enum filter_rule_type type, const char *a_sym, int sym_re_p, @@ -226,12 +224,10 @@ add_filter_rule(struct filter *filt, const char *expr, struct filter_lib_matcher *matcher = malloc(sizeof(*matcher)); if (rule == NULL || matcher == NULL) { - fprintf(stderr, "Rule near '%s' will be ignored: %s.\n", - expr, strerror(errno)); fail: free(rule); free(matcher); - return; + return -1; } regex_t symbol_re; @@ -246,7 +242,7 @@ add_filter_rule(struct filter *filt, const char *expr, if (status != 0) { char buf[100]; regerror(status, &symbol_re, buf, sizeof buf); - fprintf(stderr, "Rule near '%s' will be ignored: %s.\n", + fprintf(stderr, "Couldn't compile '%s': %s.\n", expr, buf); goto fail; } @@ -259,6 +255,7 @@ add_filter_rule(struct filter *filt, const char *expr, filter_rule_init(rule, type, matcher, symbol_re); filter_add_rule(filt, rule); + return 0; } static int @@ -291,7 +288,7 @@ parse_filter(struct filter *filt, char *expr, int operators) enum filter_rule_type type = FR_ADD; while (*expr != 0) { - size_t s = strcspn(expr, "-+@" + (operators ? 0 : 2)); + size_t s = strcspn(expr, &"-+@"[operators ? 0 : 2]); char *symname = expr; char *libname; char *next = expr + s + 1; @@ -310,7 +307,7 @@ parse_filter(struct filter *filt, char *expr, int operators) } else { assert(expr[s] == '@'); expr[s] = 0; - s = strcspn(next, "-+" + (operators ? 0 : 2)); + s = strcspn(next, &"-+"[operators ? 0 : 2]); if (s == 0) { libname = "*"; expr = next; @@ -382,7 +379,7 @@ parse_filter(struct filter *filt, char *expr, int operators) } static struct filter * -recursive_parse_chain(char *expr, int operators) +recursive_parse_chain(const char *orig, char *expr, int operators) { struct filter *filt = malloc(sizeof(*filt)); if (filt == NULL) { @@ -393,7 +390,7 @@ recursive_parse_chain(char *expr, int operators) filter_init(filt); if (parse_filter(filt, expr, operators) < 0) { - fprintf(stderr, "Filter '%s' will be ignored.\n", expr); + fprintf(stderr, "Filter '%s' will be ignored.\n", orig); free(filt); filt = NULL; } @@ -422,7 +419,7 @@ parse_filter_chain(const char *expr, struct filter **retp) if (str[0] == '!') str[0] = '-'; - *slist_chase_end(retp) = recursive_parse_chain(str, 1); + *slist_chase_end(retp) = recursive_parse_chain(expr, str, 1); free(str); } @@ -442,15 +439,89 @@ parse_int(const char *optarg, char opt, int min, int max) return (int)l; } +int +parse_colon_separated_list(const char *paths, struct vect *vec) +{ + /* PATHS contains a colon-separated list of directories and + * files to load. It's modeled after shell PATH variable, + * which doesn't allow escapes. PYTHONPATH in CPython behaves + * the same way. So let's follow suit, it makes things easier + * to us. */ + + char *clone = strdup(paths); + if (clone == NULL) { + fprintf(stderr, "Couldn't parse argument %s: %s.\n", + paths, strerror(errno)); + return -1; + } + + /* It's undesirable to use strtok, because we want the string + * "a::b" to have three elements. */ + char *tok = clone - 1; + char *end = clone + strlen(clone); + while (tok < end) { + ++tok; + size_t len = strcspn(tok, ":"); + tok[len] = 0; + + struct opt_F_t arg = { + .pathname = tok, + .own_pathname = tok == clone, + }; + if (VECT_PUSHBACK(vec, &arg) < 0) + /* Presumably this is not a deal-breaker. */ + fprintf(stderr, "Couldn't store component of %s: %s.\n", + paths, strerror(errno)); + + tok += len; + } + + return 0; +} + +void +opt_F_destroy(struct opt_F_t *entry) +{ + if (entry == NULL) + return; + if (entry->own_pathname) + free(entry->pathname); +} + +enum opt_F_kind +opt_F_get_kind(struct opt_F_t *entry) +{ + if (entry->kind == OPT_F_UNKNOWN) { + struct stat st; + if (lstat(entry->pathname, &st) < 0) { + fprintf(stderr, "Couldn't stat %s: %s\n", + entry->pathname, strerror(errno)); + entry->kind = OPT_F_BROKEN; + } else if (S_ISDIR(st.st_mode)) { + entry->kind = OPT_F_DIR; + } else if (S_ISREG(st.st_mode) || S_ISLNK(st.st_mode)) { + entry->kind = OPT_F_FILE; + } else { + fprintf(stderr, "%s is neither a regular file, " + "nor a directory.\n", entry->pathname); + entry->kind = OPT_F_BROKEN; + } + } + assert(entry->kind != OPT_F_UNKNOWN); + return entry->kind; +} + char ** process_options(int argc, char **argv) { + VECT_INIT(&opt_F, struct opt_F_t); + progname = argv[0]; options.output = stderr; options.no_signals = 0; -#if defined(HAVE_LIBUNWIND) +#if defined(HAVE_UNWINDER) options.bt_depth = -1; -#endif /* defined(HAVE_LIBUNWIND) */ +#endif /* defined(HAVE_UNWINDER) */ guess_cols(); @@ -459,6 +530,7 @@ process_options(int argc, char **argv) while (1) { int c; char *p; +#ifdef HAVE_GETOPT_LONG int option_index = 0; static struct option long_options[] = { {"align", 1, 0, 'a'}, @@ -466,28 +538,34 @@ process_options(int argc, char **argv) {"debug", 1, 0, 'D'}, # ifdef USE_DEMANGLE {"demangle", 0, 0, 'C'}, -#endif +# endif {"indent", 1, 0, 'n'}, {"help", 0, 0, 'h'}, {"library", 1, 0, 'l'}, {"output", 1, 0, 'o'}, {"version", 0, 0, 'V'}, {"no-signals", 0, 0, 'b'}, -#if defined(HAVE_LIBUNWIND) +# if defined(HAVE_UNWINDER) {"where", 1, 0, 'w'}, -#endif /* defined(HAVE_LIBUNWIND) */ +# endif /* defined(HAVE_UNWINDER) */ {0, 0, 0, 0} }; - c = getopt_long(argc, argv, "+cfhiLrStTVb" -# ifdef USE_DEMANGLE - "C" -# endif -#if defined(HAVE_LIBUNWIND) - "a:A:D:e:F:l:n:o:p:s:u:x:X:w:", long_options, -#else /* !defined(HAVE_LIBUNWIND) */ - "a:A:D:e:F:l:n:o:p:s:u:x:X:", long_options, #endif - &option_index); + + const char *opts = "+" +#ifdef USE_DEMANGLE + "C" +#endif +#if defined(HAVE_UNWINDER) + "w:" +#endif + "cfhiLrStTVba:A:D:e:F:l:n:o:p:s:u:x:"; + +#ifdef HAVE_GETOPT_LONG + c = getopt_long(argc, argv, opts, long_options, &option_index); +#else + c = getopt(argc, argv, opts); +#endif if (c == -1) { break; } @@ -529,23 +607,8 @@ process_options(int argc, char **argv) options.follow = 1; break; case 'F': - { - struct opt_F_t *tmp = malloc(sizeof(*tmp)); - if (tmp == NULL) { - fail: - fprintf(stderr, "%s\n", - strerror(errno)); - free(tmp); - exit(1); - } - tmp->filename = strdup(optarg); - if (tmp->filename == NULL) - goto fail; - tmp->own_filename = 1; - tmp->next = opt_F; - opt_F = tmp; - break; - } + parse_colon_separated_list(optarg, &opt_F); + break; case 'h': usage(); exit(0); @@ -558,7 +621,7 @@ process_options(int argc, char **argv) char buf[patlen + 2]; sprintf(buf, "@%s", optarg); *slist_chase_end(&options.export_filter) - = recursive_parse_chain(buf, 0); + = recursive_parse_chain(buf, buf, 0); break; } @@ -610,17 +673,19 @@ process_options(int argc, char **argv) options.user = optarg; break; case 'V': - printf("ltrace version " PACKAGE_VERSION ".\n" - "Copyright (C) 1997-2009 Juan Cespedes <cespedes@debian.org>.\n" - "This is free software; see the GNU General Public Licence\n" - "version 2 or later for copying conditions. There is NO warranty.\n"); + printf("ltrace " PACKAGE_VERSION "\n" + "Copyright (C) 2010-2013 Petr Machata, Red Hat Inc.\n" + "Copyright (C) 1997-2009 Juan Cespedes <cespedes@debian.org>.\n" + "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n" + "This is free software: you are free to change and redistribute it.\n" + "There is NO WARRANTY, to the extent permitted by law.\n"); exit(0); break; -#if defined(HAVE_LIBUNWIND) +#if defined(HAVE_UNWINDER) case 'w': options.bt_depth = parse_int(optarg, 'w', 1, 0); break; -#endif /* defined(HAVE_LIBUNWIND) */ +#endif /* defined(HAVE_UNWINDER) */ case 'x': parse_filter_chain(optarg, &options.static_filter); @@ -633,31 +698,6 @@ process_options(int argc, char **argv) argc -= optind; argv += optind; - if (!opt_F) { - opt_F = malloc(sizeof(struct opt_F_t)); - opt_F->next = malloc(sizeof(struct opt_F_t)); - opt_F->next->next = NULL; - opt_F->filename = USER_CONFIG_FILE; - opt_F->own_filename = 0; - opt_F->next->filename = SYSTEM_CONFIG_FILE; - opt_F->next->own_filename = 0; - } - /* Reverse the config file list since it was built by - * prepending, and it would make more sense to process the - * files in the order they were given. Probably it would make - * more sense to keep a tail pointer instead? */ - { - struct opt_F_t *egg = NULL; - struct opt_F_t *chicken; - while (opt_F) { - chicken = opt_F->next; - opt_F->next = egg; - egg = opt_F; - opt_F = chicken; - } - opt_F = egg; - } - /* If neither -e, nor -l, nor -L are used, set default -e. * Use @MAIN for now, as that's what ltrace used to have in * the past. XXX Maybe we should make this "*" instead. */ @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2009,2010 Joe Damato * Copyright (C) 1998,2002,2008 Juan Cespedes * Copyright (C) 2006 Ian Wienand @@ -21,10 +22,15 @@ * 02110-1301 USA */ +#ifndef OPTIONS_H +#define OPTIONS_H + #include <stdio.h> #include <sys/types.h> +#include <sys/time.h> #include "forward.h" +#include "vect.h" struct options_t { int align; /* -a: default alignment column for results */ @@ -39,9 +45,9 @@ struct options_t { size_t strlen; /* default maximum # of bytes printed in strings */ int follow; /* trace child processes */ int no_signals; /* don't print signals */ -#if defined(HAVE_LIBUNWIND) +#if defined(HAVE_UNWINDER) int bt_depth; /* how may levels of stack frames to show */ -#endif /* defined(HAVE_LIBUNWIND) */ +#endif /* defined(HAVE_UNWINDER) */ struct filter *plt_filter; struct filter *static_filter; @@ -63,15 +69,41 @@ struct opt_p_t { struct opt_p_t *next; }; -struct opt_F_t -{ - struct opt_F_t *next; - char *filename; - int own_filename : 1; +extern struct opt_p_t *opt_p; /* attach to process with a given pid */ + +enum opt_F_kind { + OPT_F_UNKNOWN = 0, + OPT_F_BROKEN, + OPT_F_FILE, + OPT_F_DIR, }; -extern struct opt_p_t *opt_p; /* attach to process with a given pid */ +struct opt_F_t { + char *pathname; + int own_pathname : 1; + enum opt_F_kind kind : 2; +}; -extern struct opt_F_t *opt_F; /* alternate configuration file(s) */ +/* If entry->kind is OPT_F_UNKNOWN, figure out whether it should be + * OPT_F_FILE or OPT_F_DIR, cache the result, and return it. Return + * OPT_F_BROKEN on failure. Error message will have been printed in + * that case. */ +enum opt_F_kind opt_F_get_kind(struct opt_F_t *entry); + +/* Destroy and release any memory associated with ENTRY (but don't + * free ENTRY itself). */ +void opt_F_destroy(struct opt_F_t *entry); + +/* PATHS contains colon-separated list of values, akin to enviroment + * variables PATH, PYTHONPATH, and others. No escaping is possible. + * The list is split and added to VEC, which shall be a vector + * initialized like VECT_INIT(VEC, struct opt_F_t); Returns 0 on + * success or a negative value on failure. */ +int parse_colon_separated_list(const char *paths, struct vect *vec); + +/* Vector of struct opt_F_t. */ +extern struct vect opt_F; extern char **process_options(int argc, char **argv); + +#endif /* OPTIONS_H */ @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Joe Damato * Copyright (C) 1997,1998,1999,2001,2002,2003,2004,2007,2008,2009 Juan Cespedes * Copyright (C) 2006 Paul Gilliam, IBM Corporation @@ -22,10 +22,6 @@ * 02110-1301 USA */ -/* glibc before 2.10, eglibc and uClibc all need _GNU_SOURCE defined - * for open_memstream to become visible. */ -#define _GNU_SOURCE - #include "config.h" #include <stdio.h> @@ -37,35 +33,42 @@ #include <unistd.h> #include <errno.h> #include <assert.h> +#include <inttypes.h> -#include "common.h" -#include "proc.h" +#include "output.h" +#include "demangle.h" +#include "fetch.h" +#include "lens_default.h" #include "library.h" +#include "memstream.h" +#include "options.h" +#include "param.h" +#include "proc.h" +#include "prototype.h" +#include "summary.h" #include "type.h" #include "value.h" #include "value_dict.h" -#include "param.h" -#include "fetch.h" -#include "lens_default.h" +#include "filter.h" +#include "debug.h" -/* TODO FIXME XXX: include in common.h: */ -extern struct timeval current_time_spent; - -Dict *dict_opt_c = NULL; +#if defined(HAVE_LIBDW) +#include "dwarf_prototypes.h" +#endif -static Process *current_proc = 0; +static struct process *current_proc = NULL; static size_t current_depth = 0; static int current_column = 0; static void -output_indent(struct Process *proc) +output_indent(struct process *proc) { int d = options.indent * (proc->callstack_depth - 1); current_column += fprintf(options.output, "%*s", d, ""); } static void -begin_of_line(Process *proc, int is_func, int indent) +begin_of_line(struct process *proc, int is_func, int indent) { current_column = 0; if (!proc) { @@ -78,11 +81,10 @@ begin_of_line(Process *proc, int is_func, int indent) } if (opt_r) { struct timeval tv; - struct timezone tz; static struct timeval old_tv = { 0, 0 }; struct timeval diff; - gettimeofday(&tv, &tz); + gettimeofday(&tv, NULL); if (old_tv.tv_sec == 0 && old_tv.tv_usec == 0) { old_tv.tv_sec = tv.tv_sec; @@ -98,16 +100,16 @@ begin_of_line(Process *proc, int is_func, int indent) old_tv.tv_sec = tv.tv_sec; old_tv.tv_usec = tv.tv_usec; current_column += fprintf(options.output, "%3lu.%06d ", - diff.tv_sec, (int)diff.tv_usec); + (unsigned long)diff.tv_sec, + (int)diff.tv_usec); } if (opt_t) { struct timeval tv; - struct timezone tz; - - gettimeofday(&tv, &tz); + gettimeofday(&tv, NULL); if (opt_t > 2) { current_column += fprintf(options.output, "%lu.%06d ", - tv.tv_sec, (int)tv.tv_usec); + (unsigned long)tv.tv_sec, + (int)tv.tv_usec); } else if (opt_t > 1) { struct tm *tmp = localtime(&tv.tv_sec); current_column += @@ -122,12 +124,15 @@ begin_of_line(Process *proc, int is_func, int indent) } } if (opt_i) { - if (is_func) + if (is_func) { + struct callstack_element *stel + = &proc->callstack[proc->callstack_depth - 1]; current_column += fprintf(options.output, "[%p] ", - proc->return_addr); - else + stel->return_addr); + } else { current_column += fprintf(options.output, "[%p] ", proc->instruction_pointer); + } } if (options.indent > 0 && indent) { output_indent(proc); @@ -137,78 +142,173 @@ begin_of_line(Process *proc, int is_func, int indent) static struct arg_type_info * get_unknown_type(void) { - static struct arg_type_info *info = NULL; - if (info == NULL) { - info = malloc(sizeof(*info)); - if (info == NULL) { - report_global_error("malloc: %s", strerror(errno)); - abort(); - } - *info = *type_get_simple(ARGTYPE_LONG); - info->lens = &guess_lens; - } - return info; + static struct arg_type_info *ret = NULL; + if (ret != NULL) + return ret; + + static struct arg_type_info info; + info = *type_get_simple(ARGTYPE_LONG); + info.lens = &guess_lens; + ret = &info; + return ret; } /* The default prototype is: long X(long, long, long, long). */ -static Function * +static struct prototype * build_default_prototype(void) { - Function *ret = malloc(sizeof(*ret)); - size_t i = 0; - if (ret == NULL) - goto err; - memset(ret, 0, sizeof(*ret)); + static struct prototype *ret = NULL; + if (ret != NULL) + return ret; - struct arg_type_info *unknown_type = get_unknown_type(); + static struct prototype proto; + prototype_init(&proto); - ret->return_info = unknown_type; - ret->own_return_info = 0; + struct arg_type_info *unknown_type = get_unknown_type(); + assert(unknown_type != NULL); + proto.return_info = unknown_type; + proto.own_return_info = 0; - ret->num_params = 4; - ret->params = malloc(sizeof(*ret->params) * ret->num_params); - if (ret->params == NULL) - goto err; + struct param unknown_param; + param_init_type(&unknown_param, unknown_type, 0); - for (i = 0; i < ret->num_params; ++i) - param_init_type(&ret->params[i], unknown_type, 0); + size_t i; + for (i = 0; i < 4; ++i) + if (prototype_push_param(&proto, &unknown_param) < 0) { + report_global_error("build_default_prototype: %s", + strerror(errno)); + prototype_destroy(&proto); + return NULL; + } + ret = &proto; return ret; +} -err: - report_global_error("malloc: %s", strerror(errno)); - if (ret->params != NULL) { - while (i-- > 0) - param_destroy(&ret->params[i]); - free(ret->params); +static bool +snip_period(char *buf) +{ + char *period = strrchr(buf, '.'); + if (period != NULL && strcmp(period, ".so") != 0) { + *period = 0; + return true; + } else { + return false; } +} + +struct lookup_prototype_alias_context +{ + struct library *lib; + struct prototype *result; // output +}; +static enum callback_status +lookup_prototype_alias_cb(const char *name, void *data) +{ + struct lookup_prototype_alias_context *context = + (struct lookup_prototype_alias_context*)data; - free(ret); + struct library *lib = context->lib; - return NULL; + context->result = + protolib_lookup_prototype(lib->protolib, name, + lib->type != LT_LIBTYPE_SYSCALL); + if (context->result != NULL) + return CBS_STOP; + + return CBS_CONT; } -static Function * -name2func(char const *name) { - Function *tmp; - const char *str1, *str2; +static struct prototype * +library_get_prototype(struct library *lib, const char *name) +{ + if (lib->protolib == NULL) { + size_t sz = strlen(lib->soname); + char buf[sz + 1]; + memcpy(buf, lib->soname, sz + 1); + + do { + if (protolib_cache_maybe_load(&g_protocache, buf, 0, + true, &lib->protolib) < 0) + return NULL; + } while (lib->protolib == NULL + && lib->type == LT_LIBTYPE_DSO + && snip_period(buf)); + +#if defined(HAVE_LIBDW) + // DWARF data fills in the gaps in the .conf files, so I don't + // check for lib->protolib==NULL here + if (lib->dwfl_module != NULL && + (filter_matches_library(options.plt_filter, lib ) || + filter_matches_library(options.static_filter, lib ) || + filter_matches_library(options.export_filter, lib ))) + import_DWARF_prototypes(lib); + else + debug(DEBUG_FUNCTION, + "Filter didn't match prototype '%s' in lib '%s'. " + "Not importing", + name, lib->soname); +#endif - for (tmp = list_of_functions; tmp != NULL; tmp = tmp->next) { - str1 = tmp->name; - str2 = name; - if (!strcmp(str1, str2)) - return tmp; + if (lib->protolib == NULL) + lib->protolib = protolib_cache_default(&g_protocache, + buf, 0); } + if (lib->protolib == NULL) + return NULL; + + struct prototype *result = + protolib_lookup_prototype(lib->protolib, name, + lib->type != LT_LIBTYPE_SYSCALL); + if (result != NULL) + return result; + + // prototype not found. Is it aliased? + struct lookup_prototype_alias_context context = {.lib = lib, + .result = NULL}; + library_exported_names_each_alias(&lib->exported_names, name, + NULL, lookup_prototype_alias_cb, + &context); + + // if found, the prototype is stored here, otherwise it's NULL + return context.result; +} + +struct find_proto_data { + const char *name; + struct prototype *ret; +}; + +static enum callback_status +find_proto_cb(struct process *proc, struct library *lib, void *d) +{ + struct find_proto_data *data = d; + data->ret = library_get_prototype(lib, data->name); + return CBS_STOP_IF(data->ret != NULL); +} - static Function *def = NULL; - if (def == NULL) - def = build_default_prototype(); +static struct prototype * +lookup_symbol_prototype(struct process *proc, struct library_symbol *libsym) +{ + if (libsym->proto != NULL) + return libsym->proto; + + struct library *lib = libsym->lib; + if (lib != NULL) { + struct find_proto_data data = { libsym->name }; + data.ret = library_get_prototype(lib, libsym->name); + if (data.ret == NULL + && libsym->plt_type == LS_TOPLT_EXEC) + proc_each_library(proc, NULL, find_proto_cb, &data); + if (data.ret != NULL) + return data.ret; + } - return def; + return build_default_prototype(); } void -output_line(struct Process *proc, const char *fmt, ...) +output_line(struct process *proc, const char *fmt, ...) { if (options.summary) return; @@ -248,7 +348,8 @@ output_error(FILE *stream) } static int -fetch_simple_param(enum tof type, Process *proc, struct fetch_context *context, +fetch_simple_param(enum tof type, struct process *proc, + struct fetch_context *context, struct value_dict *arguments, struct arg_type_info *info, int own, struct value *valuep) @@ -290,7 +391,8 @@ fetch_param_stop(struct value_dict *arguments, ssize_t *params_leftp) } static int -fetch_param_pack(enum tof type, Process *proc, struct fetch_context *context, +fetch_param_pack(enum tof type, struct process *proc, + struct fetch_context *context, struct value_dict *arguments, struct param *param, ssize_t *params_leftp) { @@ -343,7 +445,8 @@ fetch_param_pack(enum tof type, Process *proc, struct fetch_context *context, } static int -fetch_one_param(enum tof type, Process *proc, struct fetch_context *context, +fetch_one_param(enum tof type, struct process *proc, + struct fetch_context *context, struct value_dict *arguments, struct param *param, ssize_t *params_leftp) { @@ -371,15 +474,36 @@ fetch_one_param(enum tof type, Process *proc, struct fetch_context *context, abort(); } +struct fetch_one_param_data +{ + struct process *proc; + struct fetch_context *context; + struct value_dict *arguments; + ssize_t *params_leftp; + enum tof tof; +}; + +static enum callback_status +fetch_one_param_cb(struct prototype *proto, struct param *param, void *data) +{ + struct fetch_one_param_data *cb_data = data; + return CBS_STOP_IF(fetch_one_param(cb_data->tof, cb_data->proc, + cb_data->context, + cb_data->arguments, param, + cb_data->params_leftp) < 0); +} + static int -fetch_params(enum tof type, Process *proc, struct fetch_context *context, - struct value_dict *arguments, Function *func, ssize_t *params_leftp) +fetch_params(enum tof type, struct process *proc, + struct fetch_context *context, + struct value_dict *arguments, struct prototype *func, + ssize_t *params_leftp) { - size_t i; - for (i = 0; i < func->num_params; ++i) - if (fetch_one_param(type, proc, context, arguments, - &func->params[i], params_leftp) < 0) - return -1; + struct fetch_one_param_data cb_data + = { proc, context, arguments, params_leftp, type }; + if (prototype_each_param(func, NULL, + &fetch_one_param_cb, &cb_data) != NULL) + return -1; /* Implicit stop at the end of parameter list. */ fetch_param_stop(arguments, params_leftp); @@ -424,15 +548,11 @@ output_params(struct value_dict *arguments, size_t start, size_t end, } void -output_left(enum tof type, struct Process *proc, +output_left(enum tof type, struct process *proc, struct library_symbol *libsym) { - const char *function_name = libsym->name; - Function *func; + assert(! options.summary); - if (options.summary) { - return; - } if (current_proc) { fprintf(options.output, " <unfinished ...>\n"); current_column = 0; @@ -447,10 +567,10 @@ output_left(enum tof type, struct Process *proc, fprintf(options.output, "%s->", libsym->lib->soname)); - const char *name = function_name; + const char *name = libsym->name; #ifdef USE_DEMANGLE if (options.demangle) - name = my_demangle(function_name); + name = my_demangle(libsym->name); #endif if (account_output(¤t_column, fprintf(options.output, "%s", name)) < 0) @@ -467,17 +587,23 @@ output_left(enum tof type, struct Process *proc, account_output(¤t_column, fprintf(options.output, "(")); - func = name2func(function_name); + struct prototype *func = lookup_symbol_prototype(proc->leader, libsym); if (func == NULL) { + fail: account_output(¤t_column, fprintf(options.output, "???")); return; } struct fetch_context *context = fetch_arg_init(type, proc, func->return_info); + if (context == NULL) + goto fail; + struct value_dict *arguments = malloc(sizeof(*arguments)); - if (arguments == NULL) - return; + if (arguments == NULL) { + fetch_arg_done(context); + goto fail; + } val_dict_init(arguments); ssize_t params_left = -1; @@ -498,61 +624,99 @@ output_left(enum tof type, struct Process *proc, stel->out.need_delim = need_delim; } -void -output_right(enum tof type, struct Process *proc, struct library_symbol *libsym) +#if defined(HAVE_LIBDW) +/* Prints information about one frame of a thread. Called by + dwfl_getthread_frames in output_right. Returns 1 when done (max + number of frames reached). Returns -1 on error. Returns 0 on + success (if there are more frames in the thread, call us again). */ +static int +frame_callback (Dwfl_Frame *state, void *arg) { - const char *function_name = libsym->name; - Function *func = name2func(function_name); - if (func == NULL) - return; + Dwarf_Addr pc; + bool isactivation; - if (options.summary) { - struct opt_c_struct *st; - if (!dict_opt_c) { - dict_opt_c = - dict_init(dict_key2hash_string, - dict_key_cmp_string); - } - st = dict_find_entry(dict_opt_c, function_name); - if (!st) { - char *na; - st = malloc(sizeof(struct opt_c_struct)); - na = strdup(function_name); - if (!st || !na) { - perror("malloc()"); - exit(1); + int *frames = (int *) arg; + + if (!dwfl_frame_pc(state, &pc, &isactivation)) + return -1; + + if (!isactivation) + pc--; + + Dwfl *dwfl = dwfl_thread_dwfl(dwfl_frame_thread(state)); + Dwfl_Module *mod = dwfl_addrmodule(dwfl, pc); + const char *modname = NULL; + const char *symname = NULL; + GElf_Off off = 0; + if (mod != NULL) { + GElf_Sym sym; + modname = dwfl_module_info(mod, NULL, NULL, NULL, NULL, + NULL, NULL, NULL); + symname = dwfl_module_addrinfo(mod, pc, &off, &sym, + NULL, NULL, NULL); + } + + /* This mimics the output produced by libunwind below. */ + fprintf(options.output, " > %s(%s+0x%" PRIx64 ") [%" PRIx64 "]\n", + modname, symname, off, pc); + + /* See if we can extract the source line too and print it on + the next line if we can find it. */ + if (mod != NULL) { + Dwfl_Line *l = dwfl_module_getsrc(mod, pc); + if (l != NULL) { + int line, col; + line = col = -1; + const char *src = dwfl_lineinfo(l, NULL, &line, &col, + NULL, NULL); + if (src != NULL) { + fprintf(options.output, "\t%s", src); + if (line > 0) { + fprintf(options.output, ":%d", line); + if (col > 0) + fprintf(options.output, + ":%d", col); + } + fprintf(options.output, "\n"); } - st->count = 0; - st->tv.tv_sec = st->tv.tv_usec = 0; - dict_enter(dict_opt_c, na, st); - } - if (st->tv.tv_usec + current_time_spent.tv_usec > 1000000) { - st->tv.tv_usec += current_time_spent.tv_usec - 1000000; - st->tv.tv_sec++; - } else { - st->tv.tv_usec += current_time_spent.tv_usec; + } - st->count++; - st->tv.tv_sec += current_time_spent.tv_sec; + } -// fprintf(options.output, "%s <%lu.%06d>\n", function_name, -// current_time_spent.tv_sec, (int)current_time_spent.tv_usec); + /* Max number of frames to print reached? */ + if ((*frames)-- == 0) + return 1; + + return 0; +} +#endif /* defined(HAVE_LIBDW) */ + +void +output_right(enum tof type, struct process *proc, struct library_symbol *libsym, + struct timedelta *spent) +{ + assert(! options.summary); + + struct prototype *func = lookup_symbol_prototype(proc, libsym); + if (func == NULL) return; - } - if (current_proc && (current_proc != proc || - current_depth != proc->callstack_depth)) { + + if (current_proc != NULL + && (current_proc != proc + || current_depth != proc->callstack_depth)) { fprintf(options.output, " <unfinished ...>\n"); - current_proc = 0; + current_proc = NULL; } if (current_proc != proc) { begin_of_line(proc, type == LT_TOF_FUNCTIONR, 1); #ifdef USE_DEMANGLE current_column += fprintf(options.output, "<... %s resumed> ", - options.demangle ? my_demangle(function_name) : function_name); + options.demangle ? my_demangle(libsym->name) + : libsym->name); #else current_column += - fprintf(options.output, "<... %s resumed> ", function_name); + fprintf(options.output, "<... %s resumed> ", libsym->name); #endif } @@ -564,17 +728,17 @@ output_right(enum tof type, struct Process *proc, struct library_symbol *libsym) /* Fetch & enter into dictionary the retval first, so that * other values can use it in expressions. */ struct value retval; - int own_retval = 0; + bool own_retval = false; if (context != NULL) { value_init(&retval, proc, NULL, func->return_info, 0); - own_retval = 1; + own_retval = true; if (fetch_retval(context, type, proc, func->return_info, &retval) < 0) value_set_type(&retval, NULL, 0); else if (stel->arguments != NULL - && val_dict_push_named(stel->arguments, &retval, - "retval", 0) == 0) - own_retval = 0; + && val_dict_push_named(stel->arguments, &retval, + "retval", 0) == 0) + own_retval = false; } if (stel->arguments != NULL) @@ -595,25 +759,70 @@ output_right(enum tof type, struct Process *proc, struct library_symbol *libsym) value_destroy(&retval); if (opt_T) { + assert(spent != NULL); fprintf(options.output, " <%lu.%06d>", - current_time_spent.tv_sec, - (int)current_time_spent.tv_usec); + (unsigned long) spent->tm.tv_sec, + (int) spent->tm.tv_usec); } + fprintf(options.output, "\n"); #if defined(HAVE_LIBUNWIND) - if (options.bt_depth > 0) { + if (options.bt_depth > 0 + && proc->unwind_priv != NULL + && proc->unwind_as != NULL) { unw_cursor_t cursor; - unw_word_t ip, sp; + arch_addr_t ip, function_offset; + struct library *lib = NULL; int unwind_depth = options.bt_depth; char fn_name[100]; + const char *lib_name; + size_t distance; + /* Verify that we can safely cast arch_addr_t* to + * unw_word_t*. */ + (void)sizeof(char[1 - 2*(sizeof(unw_word_t) + != sizeof(arch_addr_t))]); unw_init_remote(&cursor, proc->unwind_as, proc->unwind_priv); while (unwind_depth) { - unw_get_reg(&cursor, UNW_REG_IP, &ip); - unw_get_reg(&cursor, UNW_REG_SP, &sp); - unw_get_proc_name(&cursor, fn_name, 100, NULL); - fprintf(options.output, "\t\t\t%s (ip = 0x%lx)\n", fn_name, (long) ip); + + int rc = unw_get_reg(&cursor, UNW_REG_IP, + (unw_word_t *) &ip); + if (rc < 0) { + fprintf(options.output, " > Error: %s\n", + unw_strerror(rc)); + goto cont; + } + + /* We are looking for the library with the base address + * closest to the current ip. */ + lib_name = "unmapped_area"; + distance = (size_t) -1; + lib = proc->libraries; + while (lib != NULL) { + /* N.B.: Assumes sizeof(size_t) == + * sizeof(arch_addr_t). + * Keyword: double cast. */ + if ((ip >= lib->base) && + ((size_t)(ip - lib->base) + < distance)) { + distance = ip - lib->base; + lib_name = lib->pathname; + } + lib = lib->next; + } + + rc = unw_get_proc_name(&cursor, fn_name, + sizeof(fn_name), + (unw_word_t *) &function_offset); + if (rc == 0 || rc == -UNW_ENOMEM) + fprintf(options.output, " > %s(%s+%p) [%p]\n", + lib_name, fn_name, function_offset, ip); + else + fprintf(options.output, " > %s(??\?) [%p]\n", + lib_name, ip); + + cont: if (unw_step(&cursor) <= 0) break; unwind_depth--; @@ -622,7 +831,25 @@ output_right(enum tof type, struct Process *proc, struct library_symbol *libsym) } #endif /* defined(HAVE_LIBUNWIND) */ - current_proc = 0; +#if defined(HAVE_LIBDW) + if (options.bt_depth > 0 && proc->leader->dwfl != NULL) { + int frames = options.bt_depth; + if (dwfl_getthread_frames(proc->leader->dwfl, proc->pid, + frame_callback, &frames) < 0) { + // Only print an error if we couldn't show anything. + // Otherwise just show there might be more... + if (frames == options.bt_depth) + fprintf(stderr, + "dwfl_getthread_frames tid %d: %s\n", + proc->pid, dwfl_errmsg(-1)); + else + fprintf(options.output, " > [...]\n"); + } + fprintf(options.output, "\n"); + } +#endif /* defined(HAVE_LIBDW) */ + + current_proc = NULL; current_column = 0; } @@ -640,18 +867,18 @@ delim_output(FILE *stream, int *need_delimp, o = writer(stream, data); } else { - char *buf; - size_t bufsz; - FILE *tmp = open_memstream(&buf, &bufsz); - o = writer(tmp, data); - fclose(tmp); - + struct memstream ms; + if (memstream_init(&ms) < 0) + return -1; + o = writer(ms.stream, data); + if (memstream_close(&ms) < 0) + o = -1; if (o > 0 && ((*need_delimp && account_output(&o, fprintf(stream, ", ")) < 0) - || fwrite(buf, 1, bufsz, stream) != bufsz)) + || fwrite(ms.buf, 1, ms.size, stream) != ms.size)) o = -1; - free(buf); + memstream_destroy(&ms); } if (o < 0) @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011, 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011, 2012, 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -18,17 +18,19 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ -#ifndef _OUTPUT_H_ -#define _OUTPUT_H_ + +#ifndef OUTPUT_H +#define OUTPUT_H #include "fetch.h" #include "forward.h" -void output_line(struct Process *proc, const char *fmt, ...); -void output_left(enum tof type, struct Process *proc, +void output_line(struct process *proc, const char *fmt, ...); +void output_left(enum tof type, struct process *proc, struct library_symbol *libsym); -void output_right(enum tof type, struct Process *proc, - struct library_symbol *libsym); +void output_right(enum tof type, struct process *proc, + struct library_symbol *libsym, + struct timedelta *spent); /* This function is for emitting lists of comma-separated strings. * @@ -55,4 +57,4 @@ void report_error(char const *file, unsigned line_no, const char *fmt, ...); void report_warning(char const *file, unsigned line_no, const char *fmt, ...); void report_global_error(const char *fmt, ...); -#endif /* _OUTPUT_H_ */ +#endif /* OUTPUT_H */ diff --git a/packaging/ltrace.spec b/packaging/ltrace.spec index 34b37f2..83fff07 100644 --- a/packaging/ltrace.spec +++ b/packaging/ltrace.spec @@ -31,6 +31,7 @@ cp %{SOURCE1001} . %build export CFLAGS="%{optflags} -Wall -Wno-unused-local-typedefs" +/bin/sh ./autogen.sh %configure --build=%{_target_cpu}-tizen-linux make @@ -45,6 +46,9 @@ rm -rf %{buildroot}/usr/share/doc/ltrace %{_bindir}/ltrace %{_mandir}/man?/ltrace.?.gz %{_mandir}/man?/ltrace.conf.?.gz -%config /etc/ltrace.conf +%config /usr/share/ltrace/syscalls.conf +%config /usr/share/ltrace/libc.so.conf +%config /usr/share/ltrace/libm.so.conf +%config /usr/share/ltrace/libacl.so.conf %changelog @@ -132,6 +132,8 @@ param_destroy(struct param *param) expr_destroy(¶m->u.pack.args[i]); free(param->u.pack.args); } + return; + case PARAM_FLAVOR_STOP: return; } @@ -69,7 +69,6 @@ enum param_status { * enumerator. */ struct param_enum; -/* int printf(string, pack(format, arg1)); */ struct param { enum param_flavor flavor; union { @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2004,2007,2008,2009 Juan Cespedes * Copyright (C) 2006 Steve Fink * Copyright (C) 2006 Ian Wienand @@ -22,7 +22,9 @@ */ #include <assert.h> +#include <stdint.h> #include <stdlib.h> +#include <string.h> #include "printf.h" #include "type.h" @@ -39,6 +41,7 @@ struct param_enum { char *format; char const *ptr; char const *end; + size_t width; }; static struct param_enum * @@ -47,12 +50,30 @@ param_printf_init(struct value *cb_args, size_t nargs, { assert(nargs == 1); - /* We expect a char array pointer. */ + struct process *proc = cb_args[0].inferior; + assert(proc != NULL); + + /* We expect a pointer to array. */ if (cb_args->type->type != ARGTYPE_POINTER - || cb_args->type->u.ptr_info.info->type != ARGTYPE_ARRAY - || (cb_args->type->u.ptr_info.info->u.array_info.elt_type->type - != ARGTYPE_CHAR)) + || cb_args->type->u.ptr_info.info->type != ARGTYPE_ARRAY) + return NULL; + + /* The element type should be either character (for narrow + * strings) or an integral type (for wide strings). */ + struct arg_type_info *et + = cb_args->type->u.ptr_info.info->u.array_info.elt_type; + switch (et->type) { + case ARGTYPE_CHAR: + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + break; + default: return NULL; + } struct param_enum *self = malloc(sizeof(*self)); if (self == NULL) { @@ -60,10 +81,12 @@ param_printf_init(struct value *cb_args, size_t nargs, free(self); return NULL; } + self->width = type_sizeof(proc, et); + if (self->width == (size_t) -1) + goto fail; if (value_init_deref(&self->array, cb_args) < 0) goto fail; - assert(self->array.type->type == ARGTYPE_ARRAY); self->format = (char *)value_get_data(&self->array, arguments); @@ -168,7 +191,7 @@ form_next_param(struct param_enum *self, type_init_pointer(infop, array, 1); } else if (format_type == ARGTYPE_POINTER) { - type_init_pointer(infop, elt_info, 1); + type_init_pointer(infop, elt_info, 0); } else { *infop = *type_get_simple(format_type); @@ -189,14 +212,29 @@ param_printf_next(struct param_enum *self, struct arg_type_info *infop, size_t len_buf_len = 0; struct lens *lens = NULL; - for (; self->ptr < self->end; ++self->ptr) { + for (; self->ptr < self->end; self->ptr += self->width) { + union { + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + char buf[0]; + } u; + memcpy(u.buf, self->ptr, self->width); + switch (self->width) { + case 1: u.u64 = u.u8; break; + case 2: u.u64 = u.u16; break; + case 4: u.u64 = u.u32; break; + } + uint64_t c = u.u64; + if (!self->percent) { - if (*self->ptr == '%') + if (c == '%') self->percent = 1; continue; } - switch (*self->ptr) { + switch (c) { case '#': case ' ': case '-': case '+': case 'I': case '\'': /* These are only important for formatting, @@ -214,7 +252,7 @@ param_printf_next(struct param_enum *self, struct arg_type_info *infop, = malloc(sizeof(*self->future_length)); if (self->future_length != NULL) { - ++self->ptr; + self->ptr += self->width; format_type = ARGTYPE_INT; break; } @@ -227,7 +265,7 @@ param_printf_next(struct param_enum *self, struct arg_type_info *infop, * this to attach the appropriate string * length expression. */ if (len_buf_len < sizeof(len_buf) - 1) - len_buf[len_buf_len++] = *self->ptr; + len_buf[len_buf_len++] = c; continue; case 'h': @@ -270,6 +308,7 @@ param_printf_next(struct param_enum *self, struct arg_type_info *infop, case 'x': case 'X': lens = &hex_lens; + /* Fall through. */ case 'u': uint: format_type = ARGTYPE_UINT; @@ -298,8 +337,7 @@ param_printf_next(struct param_enum *self, struct arg_type_info *infop, lng++; case 's': format_type = ARGTYPE_ARRAY; - /* XXX "ls" means wchar_t string. */ - elt_type = ARGTYPE_CHAR; + elt_type = lng == 0 ? ARGTYPE_CHAR : ARGTYPE_INT; self->percent = 0; lens = &string_lens; break; @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Joe Damato * Copyright (C) 1998,2009 Juan Cespedes * @@ -29,63 +29,63 @@ #include <stdlib.h> #include <string.h> -#if defined(HAVE_LIBUNWIND) -#include <libunwind.h> -#include <libunwind-ptrace.h> -#endif /* defined(HAVE_LIBUNWIND) */ - #include "backend.h" #include "breakpoint.h" #include "debug.h" #include "fetch.h" +#include "options.h" #include "proc.h" #include "value_dict.h" -#ifndef ARCH_HAVE_PROCESS_DATA +#if defined(HAVE_LIBDW) +# include "dwarf_prototypes.h" +#endif /* defined(HAVE_LIBDW) */ + +#ifndef OS_HAVE_PROCESS_DATA int -arch_process_init(struct Process *proc) +os_process_init(struct process *proc) { return 0; } void -arch_process_destroy(struct Process *proc) +os_process_destroy(struct process *proc) { } int -arch_process_clone(struct Process *retp, struct Process *proc) +os_process_clone(struct process *retp, struct process *proc) { return 0; } int -arch_process_exec(struct Process *proc) +os_process_exec(struct process *proc) { return 0; } #endif -#ifndef OS_HAVE_PROCESS_DATA +#ifndef ARCH_HAVE_PROCESS_DATA int -os_process_init(struct Process *proc) +arch_process_init(struct process *proc) { return 0; } void -os_process_destroy(struct Process *proc) +arch_process_destroy(struct process *proc) { } int -os_process_clone(struct Process *retp, struct Process *proc) +arch_process_clone(struct process *retp, struct process *proc) { return 0; } int -os_process_exec(struct Process *proc) +arch_process_exec(struct process *proc) { return 0; } @@ -93,25 +93,32 @@ os_process_exec(struct Process *proc) #ifndef ARCH_HAVE_DYNLINK_DONE void -arch_dynlink_done(struct Process *proc) +arch_dynlink_done(struct process *proc) { } #endif -static void add_process(struct Process *proc, int was_exec); -static void unlist_process(struct Process *proc); +static int add_process(struct process *proc, int was_exec); +static void unlist_process(struct process *proc); static void -destroy_unwind(struct Process *proc) +destroy_unwind(struct process *proc) { #if defined(HAVE_LIBUNWIND) - _UPT_destroy(proc->unwind_priv); - unw_destroy_addr_space(proc->unwind_as); + if (proc->unwind_priv != NULL) + _UPT_destroy(proc->unwind_priv); + if (proc->unwind_as != NULL) + unw_destroy_addr_space(proc->unwind_as); #endif /* defined(HAVE_LIBUNWIND) */ + +#if defined(HAVE_LIBDW) + if (proc->dwfl != NULL) + dwfl_end(proc->dwfl); +#endif /* defined(HAVE_LIBDW) */ } static int -process_bare_init(struct Process *proc, const char *filename, +process_bare_init(struct process *proc, const char *filename, pid_t pid, int was_exec) { if (!was_exec) { @@ -121,39 +128,67 @@ process_bare_init(struct Process *proc, const char *filename, if (proc->filename == NULL) { fail: free(proc->filename); - if (proc->breakpoints != NULL) - dict_clear(proc->breakpoints); + if (proc->breakpoints != NULL) { + dict_destroy(proc->breakpoints, + NULL, NULL, NULL); + free(proc->breakpoints); + proc->breakpoints = NULL; + } return -1; } } /* Add process so that we know who the leader is. */ proc->pid = pid; - add_process(proc, was_exec); - if (proc->leader == NULL) + if (add_process(proc, was_exec) < 0) + goto fail; + if (proc->leader == NULL) { + unlist_and_fail: + if (!was_exec) + unlist_process(proc); goto fail; + } if (proc->leader == proc) { - proc->breakpoints = dict_init(target_address_hash, - target_address_cmp); + proc->breakpoints = malloc(sizeof(*proc->breakpoints)); if (proc->breakpoints == NULL) - goto fail; + goto unlist_and_fail; + DICT_INIT(proc->breakpoints, + arch_addr_t, struct breakpoint *, + arch_addr_hash, arch_addr_eq, NULL); } else { proc->breakpoints = NULL; } #if defined(HAVE_LIBUNWIND) - proc->unwind_priv = _UPT_create(pid); - proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0); + if (options.bt_depth > 0) { + proc->unwind_priv = _UPT_create(pid); + proc->unwind_as = unw_create_addr_space(&_UPT_accessors, 0); + + if (proc->unwind_priv == NULL || proc->unwind_as == NULL) { + fprintf(stderr, + "Couldn't initialize unwinding " + "for process %d\n", proc->pid); + destroy_unwind(proc); + proc->unwind_priv = NULL; + proc->unwind_as = NULL; + } + } #endif /* defined(HAVE_LIBUNWIND) */ +#if defined(HAVE_LIBDW) + proc->dwfl = NULL; /* Initialize for leader only on first library. */ + proc->should_attach_dwfl = 1; /* should try to attach the DWFL data */ +#endif /* defined(HAVE_LIBDW) */ + return 0; } static void -process_bare_destroy(struct Process *proc, int was_exec) +process_bare_destroy(struct process *proc, int was_exec) { - dict_clear(proc->breakpoints); + dict_destroy(proc->breakpoints, NULL, NULL, NULL); + free(proc->breakpoints); if (!was_exec) { free(proc->filename); unlist_process(proc); @@ -162,7 +197,7 @@ process_bare_destroy(struct Process *proc, int was_exec) } static int -process_init_main(struct Process *proc) +process_init_main(struct process *proc) { if (breakpoints_init(proc) < 0) { fprintf(stderr, "failed to init breakpoints %d\n", @@ -174,7 +209,7 @@ process_init_main(struct Process *proc) } int -process_init(struct Process *proc, const char *filename, pid_t pid) +process_init(struct process *proc, const char *filename, pid_t pid) { if (process_bare_init(proc, filename, pid, 0) < 0) { fail: @@ -194,9 +229,11 @@ process_init(struct Process *proc, const char *filename, pid_t pid) goto fail; } - if (proc->leader != proc) - return 0; - if (process_init_main(proc) < 0) { + if (proc->leader != proc) { + proc->e_machine = proc->leader->e_machine; + proc->e_class = proc->leader->e_class; + get_arch_dep(proc); + } else if (process_init_main(proc) < 0) { process_bare_destroy(proc, 0); goto fail; } @@ -204,7 +241,7 @@ process_init(struct Process *proc, const char *filename, pid_t pid) } static enum callback_status -destroy_breakpoint_cb(struct Process *proc, struct breakpoint *bp, void *data) +destroy_breakpoint_cb(struct process *proc, struct breakpoint *bp, void *data) { breakpoint_destroy(bp); free(bp); @@ -212,10 +249,10 @@ destroy_breakpoint_cb(struct Process *proc, struct breakpoint *bp, void *data) } // XXX see comment in handle_event.c -void callstack_pop(struct Process *proc); +void callstack_pop(struct process *proc); static void -private_process_destroy(struct Process *proc, int was_exec) +private_process_destroy(struct process *proc, int was_exec) { /* Pop remaining stack elements. */ while (proc->callstack_depth > 0) { @@ -249,7 +286,8 @@ private_process_destroy(struct Process *proc, int was_exec) /* Breakpoints. */ if (proc->breakpoints != NULL) { proc_each_breakpoint(proc, NULL, destroy_breakpoint_cb, NULL); - dict_clear(proc->breakpoints); + dict_destroy(proc->breakpoints, NULL, NULL, NULL); + free(proc->breakpoints); proc->breakpoints = NULL; } @@ -257,7 +295,7 @@ private_process_destroy(struct Process *proc, int was_exec) } void -process_destroy(struct Process *proc) +process_destroy(struct process *proc) { arch_process_destroy(proc); os_process_destroy(proc); @@ -265,7 +303,7 @@ process_destroy(struct Process *proc) } int -process_exec(struct Process *proc) +process_exec(struct process *proc) { /* Call exec handlers first, before we destroy the main * state. */ @@ -284,11 +322,11 @@ process_exec(struct Process *proc) return 0; } -struct Process * +struct process * open_program(const char *filename, pid_t pid) { assert(pid != 0); - struct Process *proc = malloc(sizeof(*proc)); + struct process *proc = malloc(sizeof(*proc)); if (proc == NULL || process_init(proc, filename, pid) < 0) { free(proc); return NULL; @@ -297,41 +335,36 @@ open_program(const char *filename, pid_t pid) } struct clone_single_bp_data { - struct Process *old_proc; - struct Process *new_proc; - int error; + struct process *old_proc; + struct process *new_proc; }; -static void -clone_single_bp(void *key, void *value, void *u) +static enum callback_status +clone_single_bp(arch_addr_t *key, struct breakpoint **bpp, void *u) { - struct breakpoint *bp = value; + struct breakpoint *bp = *bpp; struct clone_single_bp_data *data = u; - /* Don't bother if there were errors anyway. */ - if (data->error != 0) - return; - struct breakpoint *clone = malloc(sizeof(*clone)); if (clone == NULL - || breakpoint_clone(clone, data->new_proc, - bp, data->old_proc) < 0) { + || breakpoint_clone(clone, data->new_proc, bp) < 0) { fail: free(clone); - data->error = -1; + return CBS_STOP; } if (proc_add_breakpoint(data->new_proc->leader, clone) < 0) { breakpoint_destroy(clone); goto fail; } + return CBS_CONT; } int -process_clone(struct Process *retp, struct Process *proc, pid_t pid) +process_clone(struct process *retp, struct process *proc, pid_t pid) { if (process_bare_init(retp, proc->filename, pid, 0) < 0) { fail1: - fprintf(stderr, "failed to clone process %d->%d : %s\n", + fprintf(stderr, "Failed to clone process %d to %d: %s\n", proc->pid, pid, strerror(errno)); return -1; } @@ -350,8 +383,12 @@ process_clone(struct Process *retp, struct Process *proc, pid_t pid) struct library **nlibp = &retp->libraries; for (lib = proc->leader->libraries; lib != NULL; lib = lib->next) { *nlibp = malloc(sizeof(**nlibp)); + if (*nlibp == NULL || library_clone(*nlibp, lib) < 0) { + free(*nlibp); + *nlibp = NULL; + fail2: process_bare_destroy(retp, 0); @@ -373,10 +410,10 @@ process_clone(struct Process *retp, struct Process *proc, pid_t pid) struct clone_single_bp_data data = { .old_proc = proc, .new_proc = retp, - .error = 0, }; - dict_apply_to_all(proc->leader->breakpoints, &clone_single_bp, &data); - if (data.error < 0) + if (DICT_EACH(proc->leader->breakpoints, + arch_addr_t, struct breakpoint *, NULL, + clone_single_bp, &data) != NULL) goto fail2; /* And finally the call stack. */ @@ -396,7 +433,7 @@ process_clone(struct Process *retp, struct Process *proc, pid_t pid) size_t j; fail3: for (j = 0; j < i; ++j) { - nctx = elem->fetch_context; + nctx = retp->callstack[j].fetch_context; fetch_arg_done(nctx); elem->fetch_context = NULL; } @@ -405,14 +442,13 @@ process_clone(struct Process *retp, struct Process *proc, pid_t pid) elem->fetch_context = nctx; } - struct value_dict *args = elem->arguments; - if (args != NULL) { + if (elem->arguments != NULL) { struct value_dict *nargs = malloc(sizeof(*nargs)); if (nargs == NULL - || val_dict_clone(nargs, args) < 0) { + || val_dict_clone(nargs, elem->arguments) < 0) { size_t j; for (j = 0; j < i; ++j) { - nargs = elem->arguments; + nargs = retp->callstack[j].arguments; val_dict_destroy(nargs); free(nargs); elem->arguments = NULL; @@ -456,20 +492,18 @@ process_clone(struct Process *retp, struct Process *proc, pid_t pid) static int open_one_pid(pid_t pid) { - Process *proc; - char *filename; debug(DEBUG_PROCESS, "open_one_pid(pid=%d)", pid); /* Get the filename first. Should the trace_pid fail, we can * easily free it, untracing is more work. */ - if ((filename = pid2name(pid)) == NULL - || trace_pid(pid) < 0) { + char *filename = pid2name(pid); + if (filename == NULL || trace_pid(pid) < 0) { fail: free(filename); return -1; } - proc = open_program(filename, pid); + struct process *proc = open_program(filename, pid); if (proc == NULL) goto fail; free(filename); @@ -479,12 +513,31 @@ open_one_pid(pid_t pid) } static enum callback_status -start_one_pid(Process * proc, void * data) +start_one_pid(struct process *proc, void *data) { continue_process(proc->pid); return CBS_CONT; } +static enum callback_status +is_main(struct process *proc, struct library *lib, void *data) +{ + return CBS_STOP_IF(lib->type == LT_LIBTYPE_MAIN); +} + +void +process_hit_start(struct process *proc) +{ + struct process *leader = proc->leader; + assert(leader != NULL); + + struct library *mainlib + = proc_each_library(leader, NULL, is_main, NULL); + assert(mainlib != NULL); + linkmap_init(leader, mainlib->dyn_addr); + arch_dynlink_done(leader); +} + void open_pid(pid_t pid) { @@ -495,7 +548,7 @@ open_pid(pid_t pid) return; /* First, see if we can attach the requested PID itself. */ - if (open_one_pid(pid)) { + if (open_one_pid(pid) < 0) { fprintf(stderr, "Cannot attach to pid %u: %s\n", pid, strerror(errno)); trace_fail_warning(pid); @@ -527,7 +580,7 @@ open_pid(pid_t pid) have_all = 1; for (i = 0; i < ntasks; ++i) if (pid2proc(tasks[i]) == NULL - && open_one_pid(tasks[i])) + && open_one_pid(tasks[i]) < 0) have_all = 0; free(tasks); @@ -537,40 +590,39 @@ open_pid(pid_t pid) old_ntasks = ntasks; } - struct Process *leader = pid2proc(pid)->leader; + struct process *leader = pid2proc(pid)->leader; /* XXX Is there a way to figure out whether _start has * actually already been hit? */ - arch_dynlink_done(leader); + process_hit_start(leader); /* Done. Continue everyone. */ each_task(leader, NULL, start_one_pid, NULL); } static enum callback_status -find_proc(Process * proc, void * data) +find_proc(struct process *proc, void *data) { - pid_t pid = (pid_t)(uintptr_t)data; - return proc->pid == pid ? CBS_STOP : CBS_CONT; + return CBS_STOP_IF(proc->pid == (pid_t)(uintptr_t)data); } -Process * -pid2proc(pid_t pid) { +struct process * +pid2proc(pid_t pid) +{ return each_process(NULL, &find_proc, (void *)(uintptr_t)pid); } -static Process * list_of_processes = NULL; +static struct process *list_of_processes = NULL; static void -unlist_process(Process * proc) +unlist_process(struct process *proc) { - Process *tmp; - if (list_of_processes == proc) { list_of_processes = list_of_processes->next; return; } + struct process *tmp; for (tmp = list_of_processes; ; tmp = tmp->next) { /* If the following assert fails, the process wasn't * in the list. */ @@ -583,17 +635,17 @@ unlist_process(Process * proc) } } -struct Process * -each_process(struct Process *start_after, - enum callback_status(*cb)(struct Process *proc, void *data), +struct process * +each_process(struct process *start_after, + enum callback_status(*cb)(struct process *proc, void *data), void *data) { - struct Process *it = start_after == NULL ? list_of_processes + struct process *it = start_after == NULL ? list_of_processes : start_after->next; while (it != NULL) { /* Callback might call remove_process. */ - struct Process *next = it->next; + struct process *next = it->next; switch ((*cb)(it, data)) { case CBS_FAIL: /* XXX handle me */ @@ -607,20 +659,20 @@ each_process(struct Process *start_after, return NULL; } -Process * -each_task(struct Process *proc, struct Process *start_after, - enum callback_status(*cb)(struct Process *proc, void *data), +struct process * +each_task(struct process *proc, struct process *start_after, + enum callback_status(*cb)(struct process *proc, void *data), void *data) { assert(proc != NULL); - struct Process *it = start_after == NULL ? proc->leader + struct process *it = start_after == NULL ? proc->leader : start_after->next; if (it != NULL) { - struct Process *leader = it->leader; + struct process *leader = it->leader; while (it != NULL && it->leader == leader) { /* Callback might call remove_process. */ - struct Process *next = it->next; + struct process *next = it->next; switch ((*cb)(it, data)) { case CBS_FAIL: /* XXX handle me */ @@ -635,20 +687,20 @@ each_task(struct Process *proc, struct Process *start_after, return NULL; } -static void -add_process(struct Process *proc, int was_exec) +static int +add_process(struct process *proc, int was_exec) { - Process ** leaderp = &list_of_processes; + struct process **leaderp = &list_of_processes; if (proc->pid) { pid_t tgid = process_leader(proc->pid); if (tgid == 0) /* Must have been terminated before we managed * to fully attach. */ - return; - if (tgid == proc->pid) + return -1; + if (tgid == proc->pid) { proc->leader = proc; - else { - Process * leader = pid2proc(tgid); + } else { + struct process *leader = pid2proc(tgid); proc->leader = leader; if (leader != NULL) leaderp = &leader->next; @@ -659,12 +711,13 @@ add_process(struct Process *proc, int was_exec) proc->next = *leaderp; *leaderp = proc; } + return 0; } void -change_process_leader(Process * proc, Process * leader) +change_process_leader(struct process *proc, struct process *leader) { - Process ** leaderp = &list_of_processes; + struct process **leaderp = &list_of_processes; if (proc->leader == leader) return; @@ -679,7 +732,7 @@ change_process_leader(Process * proc, Process * leader) } static enum callback_status -clear_leader(struct Process *proc, void *data) +clear_leader(struct process *proc, void *data) { debug(DEBUG_FUNCTION, "detach_task %d from leader %d", proc->pid, proc->leader->pid); @@ -688,7 +741,7 @@ clear_leader(struct Process *proc, void *data) } void -remove_process(Process *proc) +remove_process(struct process *proc) { debug(DEBUG_FUNCTION, "remove_proc(pid=%d)", proc->pid); @@ -702,7 +755,7 @@ remove_process(Process *proc) } void -install_event_handler(Process *proc, struct event_handler *handler) +install_event_handler(struct process *proc, struct event_handler *handler) { debug(DEBUG_FUNCTION, "install_event_handler(pid=%d, %p)", proc->pid, handler); assert(proc->event_handler == NULL); @@ -710,7 +763,7 @@ install_event_handler(Process *proc, struct event_handler *handler) } void -destroy_event_handler(Process * proc) +destroy_event_handler(struct process *proc) { struct event_handler *handler = proc->event_handler; debug(DEBUG_FUNCTION, "destroy_event_handler(pid=%d, %p)", proc->pid, handler); @@ -722,7 +775,7 @@ destroy_event_handler(Process * proc) } static int -breakpoint_for_symbol(struct library_symbol *libsym, struct Process *proc) +breakpoint_for_symbol(struct library_symbol *libsym, struct process *proc) { arch_addr_t bp_addr; assert(proc->leader == proc); @@ -749,9 +802,8 @@ breakpoint_for_symbol(struct library_symbol *libsym, struct Process *proc) * be also custom-allocated, and we would really need to swap * the two: delete the one now in the dictionary, swap values * around, and put the new breakpoint back in. */ - struct breakpoint *bp = dict_find_entry(proc->breakpoints, - bp_addr); - if (bp != NULL) { + struct breakpoint *bp; + if (DICT_FIND_VAL(proc->breakpoints, &bp_addr, &bp) == 0) { /* MIPS backend makes duplicate requests. This is * likely a bug in the backend. Currently there's no * point assigning more than one symbol to a @@ -795,11 +847,11 @@ breakpoint_for_symbol(struct library_symbol *libsym, struct Process *proc) static enum callback_status cb_breakpoint_for_symbol(struct library_symbol *libsym, void *data) { - return breakpoint_for_symbol(libsym, data) < 0 ? CBS_FAIL : CBS_CONT; + return CBS_STOP_IF(breakpoint_for_symbol(libsym, data) < 0); } static int -proc_activate_latent_symbol(struct Process *proc, +proc_activate_latent_symbol(struct process *proc, struct library_symbol *libsym) { assert(libsym->latent); @@ -809,7 +861,7 @@ proc_activate_latent_symbol(struct Process *proc, } int -proc_activate_delayed_symbol(struct Process *proc, +proc_activate_delayed_symbol(struct process *proc, struct library_symbol *libsym) { assert(libsym->delayed); @@ -818,25 +870,48 @@ proc_activate_delayed_symbol(struct Process *proc, return breakpoint_for_symbol(libsym, proc); } + +struct activate_latent_in_context +{ + struct process *proc; + struct library_exported_names *exported_names; +}; static enum callback_status -activate_latent_in(struct Process *proc, struct library *lib, void *data) +activate_latent_in_cb(struct library_symbol *libsym, void *data) { - struct library_exported_name *exported; - for (exported = data; exported != NULL; exported = exported->next) { - struct library_symbol *libsym = NULL; - while ((libsym = library_each_symbol(lib, libsym, - library_symbol_named_cb, - (void *)exported->name)) - != NULL) - if (libsym->latent - && proc_activate_latent_symbol(proc, libsym) < 0) - return CBS_FAIL; - } + struct activate_latent_in_context *ctx = + (struct activate_latent_in_context*)data; + + if (libsym->latent && + library_exported_names_contains(ctx->exported_names, + libsym->name) != 0) + proc_activate_latent_symbol(ctx->proc, libsym); + + return CBS_CONT; +} + +static enum callback_status +activate_latent_in(struct process *proc, struct library *lib, void *data) +{ + struct library_symbol *libsym = NULL; + + struct library_exported_names *exported_names = + (struct library_exported_names*)data; + + struct activate_latent_in_context ctx = + {.proc = proc, + .exported_names = exported_names}; + + if (library_each_symbol(lib, libsym, + activate_latent_in_cb, + &ctx) != NULL) + return CBS_FAIL; + return CBS_CONT; } void -proc_add_library(struct Process *proc, struct library *lib) +proc_add_library(struct process *proc, struct library *lib) { assert(lib->next == NULL); lib->next = proc->libraries; @@ -844,27 +919,94 @@ proc_add_library(struct Process *proc, struct library *lib) debug(DEBUG_PROCESS, "added library %s@%p (%s) to %d", lib->soname, lib->base, lib->pathname, proc->pid); +#if defined(HAVE_LIBDW) + Dwfl *dwfl = NULL; + Dwfl_Module *dwfl_module = NULL; + + /* Setup module tracking for libdwfl unwinding. */ + struct process *leader = proc->leader; + dwfl = leader->dwfl; + if (dwfl == NULL) { + static const Dwfl_Callbacks proc_callbacks = { + .find_elf = dwfl_linux_proc_find_elf, + .find_debuginfo = dwfl_standard_find_debuginfo + }; + dwfl = dwfl_begin(&proc_callbacks); + if (dwfl == NULL) + fprintf(stderr, + "Couldn't initialize libdwfl unwinding " + "for process %d: %s\n", leader->pid, + dwfl_errmsg (-1)); + } + + if (dwfl != NULL) { + dwfl_report_begin_add(dwfl); + dwfl_module = + dwfl_report_elf(dwfl, lib->soname, + lib->pathname, -1, + (GElf_Addr) lib->base, + false); + if (dwfl_module == NULL) + fprintf(stderr, + "dwfl_report_elf %s@%p (%s) %d: %s\n", + lib->soname, lib->base, lib->pathname, + proc->pid, dwfl_errmsg (-1)); + + dwfl_report_end(dwfl, NULL, NULL); + + if (options.bt_depth > 0) { + if (proc->should_attach_dwfl) { + int r = dwfl_linux_proc_attach(dwfl, + leader->pid, + true); + proc->should_attach_dwfl = 0; + if (r != 0) { + const char *msg; + dwfl_end(dwfl); + if (r < 0) + msg = dwfl_errmsg(-1); + else + msg = strerror(r); + fprintf(stderr, "Couldn't initialize " + "libdwfl (unwinding, prototype " + "import) for process %d: %s\n", + leader->pid, msg); + } + } + } + } + + lib->dwfl_module = dwfl_module; + leader->dwfl = dwfl; + +#endif /* defined(HAVE_LIBDW) */ + /* Insert breakpoints for all active (non-latent) symbols. */ struct library_symbol *libsym = NULL; while ((libsym = library_each_symbol(lib, libsym, cb_breakpoint_for_symbol, proc)) != NULL) - fprintf(stderr, "Couldn't insert breakpoint for %s to %d: %s.", - libsym->name, proc->pid, strerror(errno)); - - /* Look through export list of the new library and compare it - * with latent symbols of all libraries (including this - * library itself). */ - struct library *lib2 = NULL; - while ((lib2 = proc_each_library(proc, lib2, activate_latent_in, - lib->exported_names)) != NULL) fprintf(stderr, - "Couldn't activate latent symbols for %s in %d: %s.", + "Couldn't insert breakpoint for %s to %d: %s.\n", libsym->name, proc->pid, strerror(errno)); + + if (lib->should_activate_latent != 0) { + /* Look through export list of the new library and compare it + * with latent symbols of all libraries (including this + * library itself). */ + struct library *lib2 = NULL; + + while ((lib2 = proc_each_library(proc, lib2, activate_latent_in, + &lib->exported_names)) != NULL) + fprintf(stderr, + "Couldn't activate latent symbols " + "for %s in %d: %s.\n", + lib2->soname, proc->pid, strerror(errno)); + } } int -proc_remove_library(struct Process *proc, struct library *lib) +proc_remove_library(struct process *proc, struct library *lib) { struct library **libp; for (libp = &proc->libraries; *libp != NULL; libp = &(*libp)->next) @@ -876,13 +1018,15 @@ proc_remove_library(struct Process *proc, struct library *lib) } struct library * -proc_each_library(struct Process *proc, struct library *it, - enum callback_status (*cb)(struct Process *proc, +proc_each_library(struct process *proc, struct library *it, + enum callback_status (*cb)(struct process *proc, struct library *lib, void *data), void *data) { if (it == NULL) it = proc->libraries; + else + it = it->next; while (it != NULL) { struct library *next = it->next; @@ -903,7 +1047,7 @@ proc_each_library(struct Process *proc, struct library *it, } static void -check_leader(struct Process *proc) +check_leader(struct process *proc) { /* Only the group leader should be getting the breakpoints and * thus have ->breakpoint initialized. */ @@ -913,7 +1057,7 @@ check_leader(struct Process *proc) } int -proc_add_breakpoint(struct Process *proc, struct breakpoint *bp) +proc_add_breakpoint(struct process *proc, struct breakpoint *bp) { debug(DEBUG_FUNCTION, "proc_add_breakpoint(pid=%d, %s@%p)", proc->pid, breakpoint_name(bp), bp->addr); @@ -922,9 +1066,9 @@ proc_add_breakpoint(struct Process *proc, struct breakpoint *bp) /* XXX We might merge bp->libsym instead of the following * assert, but that's not necessary right now. Read the * comment in breakpoint_for_symbol. */ - assert(dict_find_entry(proc->breakpoints, bp->addr) == NULL); + assert(dict_find(proc->breakpoints, &bp->addr) == NULL); - if (dict_enter(proc->breakpoints, bp->addr, bp) < 0) { + if (DICT_INSERT(proc->breakpoints, &bp->addr, &bp) < 0) { fprintf(stderr, "couldn't enter breakpoint %s@%p to dictionary: %s\n", breakpoint_name(bp), bp->addr, strerror(errno)); @@ -935,67 +1079,50 @@ proc_add_breakpoint(struct Process *proc, struct breakpoint *bp) } void -proc_remove_breakpoint(struct Process *proc, struct breakpoint *bp) +proc_remove_breakpoint(struct process *proc, struct breakpoint *bp) { debug(DEBUG_FUNCTION, "proc_remove_breakpoint(pid=%d, %s@%p)", proc->pid, breakpoint_name(bp), bp->addr); check_leader(proc); - struct breakpoint *removed = dict_remove(proc->breakpoints, bp->addr); - assert(removed == bp); + int rc = DICT_ERASE(proc->breakpoints, &bp->addr, struct breakpoint *, + NULL, NULL, NULL); + assert(rc == 0); } -/* Dict doesn't support iteration restarts, so here's this contraption - * for now. XXX add restarts to dict. */ struct each_breakpoint_data { - void *start; - void *end; - struct Process *proc; - enum callback_status (*cb)(struct Process *proc, + struct process *proc; + enum callback_status (*cb)(struct process *proc, struct breakpoint *bp, void *data); void *cb_data; }; -static void -each_breakpoint_cb(void *key, void *value, void *d) +static enum callback_status +each_breakpoint_cb(arch_addr_t *key, struct breakpoint **bpp, void *d) { struct each_breakpoint_data *data = d; - if (data->end != NULL) - return; - if (data->start == key) - data->start = NULL; - - if (data->start == NULL) { - switch (data->cb(data->proc, value, data->cb_data)) { - case CBS_FAIL: - /* XXX handle me */ - case CBS_STOP: - data->end = key; - case CBS_CONT: - return; - } - } + return data->cb(data->proc, *bpp, data->cb_data); } -void * -proc_each_breakpoint(struct Process *proc, void *start, - enum callback_status (*cb)(struct Process *proc, +arch_addr_t * +proc_each_breakpoint(struct process *proc, arch_addr_t *start, + enum callback_status (*cb)(struct process *proc, struct breakpoint *bp, void *data), void *data) { struct each_breakpoint_data dd = { - .start = start, .proc = proc, .cb = cb, .cb_data = data, }; - dict_apply_to_all(proc->breakpoints, &each_breakpoint_cb, &dd); - return dd.end; + return DICT_EACH(proc->breakpoints, + arch_addr_t, struct breakpoint *, start, + &each_breakpoint_cb, &dd); } int -proc_find_symbol(struct Process *proc, struct library_symbol *sym, +proc_find_symbol(struct process *proc, struct library_symbol *sym, struct library **retlib, struct library_symbol **retsym) { struct library *lib = sym->lib; @@ -1021,7 +1148,7 @@ proc_find_symbol(struct Process *proc, struct library_symbol *sym, } struct library_symbol * -proc_each_symbol(struct Process *proc, struct library_symbol *start_after, +proc_each_symbol(struct process *proc, struct library_symbol *start_after, enum callback_status (*cb)(struct library_symbol *, void *), void *data) { @@ -1035,3 +1162,26 @@ proc_each_symbol(struct Process *proc, struct library_symbol *start_after, return NULL; } + +#define DEF_READER(NAME, SIZE) \ + int \ + NAME(struct process *proc, arch_addr_t addr, \ + uint##SIZE##_t *lp) \ + { \ + union { \ + uint##SIZE##_t dst; \ + char buf[0]; \ + } u; \ + if (umovebytes(proc, addr, &u.buf, sizeof(u.dst)) \ + != sizeof(u.dst)) \ + return -1; \ + *lp = u.dst; \ + return 0; \ + } + +DEF_READER(proc_read_8, 8) +DEF_READER(proc_read_16, 16) +DEF_READER(proc_read_32, 32) +DEF_READER(proc_read_64, 64) + +#undef DEF_READER @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2010,2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2010,2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Joe Damato * Copyright (C) 1998,2001,2008,2009 Juan Cespedes * @@ -20,15 +20,21 @@ * 02110-1301 USA */ -#ifndef _PROC_H_ -#define _PROC_H_ +#ifndef PROC_H +#define PROC_H #include "config.h" #include <sys/time.h> +#include <stdint.h> + +#if defined(HAVE_LIBDW) +# include <elfutils/libdwfl.h> +#endif #if defined(HAVE_LIBUNWIND) # include <libunwind.h> +# include <libunwind-ptrace.h> #endif /* defined(HAVE_LIBUNWIND) */ #include "ltrace.h" @@ -64,8 +70,8 @@ struct callstack_element { struct library_symbol * libfunc; } c_un; int is_syscall; - void * return_addr; - struct timeval time_spent; + arch_addr_t return_addr; + struct timeval enter_time; struct fetch_context *fetch_context; struct value_dict *arguments; struct output_state out; @@ -75,22 +81,19 @@ struct callstack_element { #define MAX_CALLDEPTH 64 /* XXX We would rather have this all organized a little differently, - * have Process for the whole group and Task for what's there for - * per-thread stuff. But for now this is the less invasive way of - * structuring it. */ -typedef struct Process Process; -struct Process { + * have struct process for the whole group and struct task (or struct + * lwp, struct thread) for what's there for per-thread stuff. But for + * now this is the less invasive way of structuring it. */ +struct process { enum process_state state; - Process * parent; /* needed by STATE_BEING_CREATED */ + struct process *parent; /* needed by STATE_BEING_CREATED */ char * filename; pid_t pid; /* Dictionary of breakpoints (which is a mapping * address->breakpoint). This is NULL for non-leader - * processes. XXX note that we store addresses (keys) by - * value. That assumes that arch_addr_t fits in host - * pointer. */ - Dict * breakpoints; + * processes. */ + struct dict *breakpoints; int mask_32bit; /* 1 if 64-bit ltrace is tracing 32-bit process */ unsigned int personality; @@ -106,7 +109,6 @@ struct Process { /* Arch-dependent: */ void * instruction_pointer; void * stack_pointer; /* To get return addr, args... */ - void * return_addr; void * arch_ptr; /* XXX We would like to replace this with a pointer to ABI @@ -116,10 +118,16 @@ struct Process { short e_machine; char e_class; - /* XXX this shoudl go to ARM's arch_process_data. */ -#ifdef __arm__ - int thumb_mode; /* ARM execution mode: 0: ARM, 1: Thumb */ -#endif +#if defined(HAVE_LIBDW) + /* Unwind info for leader, NULL for non-leader procs. */ + Dwfl *dwfl; + + /* Whether we still need to attach the DWARF library to this process. We + * try only once, and never again, regardless of whether we succeeded or + * not. 0 = shouldn't attach */ + int should_attach_dwfl; + +#endif /* defined(HAVE_LIBDW) */ #if defined(HAVE_LIBUNWIND) /* libunwind address space */ @@ -133,15 +141,15 @@ struct Process { /** * Process chaining. **/ - Process * next; + struct process *next; /* LEADER points to the leader thread of the POSIX.1 process. If X->LEADER == X, then X is the leader thread and the - Process structures chained by NEXT represent other threads, + process structures chained by NEXT represent other threads, up until, but not including, the next leader thread. LEADER may be NULL after the leader has already exited. In that case this process is waiting to be collected. */ - Process * leader; + struct process *leader; struct os_process_data os; struct arch_process_data arch; @@ -149,12 +157,12 @@ struct Process { /* Initialize a process given a path to binary FILENAME, with a PID, * and add the process to an internal chain of traced processes. */ -int process_init(struct Process *proc, const char *filename, pid_t pid); +int process_init(struct process *proc, const char *filename, pid_t pid); /* PROC underwent an exec. This is a bit like process_destroy * followed by process_init, except that some state is kept and the * process doesn't lose it's place in the list of processes. */ -int process_exec(struct Process *proc); +int process_exec(struct process *proc); /* Release any memory allocated for PROC (but not PROC itself). Does * NOT remove PROC from internal chain. @@ -162,96 +170,111 @@ int process_exec(struct Process *proc); * XXX clearly this init/destroy pair is different than others and * should be fixed. process_init should presumably be separate from * process_add. */ -void process_destroy(struct Process *proc); +void process_destroy(struct process *proc); -struct Process *open_program(const char *filename, pid_t pid); +struct process *open_program(const char *filename, pid_t pid); void open_pid(pid_t pid); -Process * pid2proc(pid_t pid); +struct process *pid2proc(pid_t pid); /* Clone the contents of PROC into the memory referenced by RETP. * Returns 0 on success or a negative value on failure. */ -int process_clone(struct Process *retp, struct Process *proc, pid_t pid); +int process_clone(struct process *retp, struct process *proc, pid_t pid); /* Iterate through the processes that ltrace currently traces. Tasks * are considered to be processes for the purpose of this iterator. * See callback.h for notes on iteration interfaces. */ -Process *each_process(Process *start_after, - enum callback_status (*cb)(struct Process *proc, - void *data), - void *data); +struct process *each_process(struct process *start_after, + enum callback_status (*cb)(struct process *proc, + void *data), + void *data); /* Iterate through list of tasks of given process PROC. See * callback.h for notes on iteration interfaces. */ -Process *each_task(struct Process *proc, struct Process *start_after, - enum callback_status (*cb)(struct Process *proc, - void *data), - void *data); +struct process *each_task(struct process *proc, struct process *start_after, + enum callback_status (*cb)(struct process *proc, + void *data), + void *data); + +void change_process_leader(struct process *proc, struct process *leader); -void change_process_leader(Process *proc, Process *leader); +/* Prepare those parts of process initialization that need to be done + * after _start is hit (i.e. after dynamic linking was done). */ +void process_hit_start(struct process *proc); /* Remove process from the list of traced processes, drop any events * in the event queue, destroy it and free memory. */ -void remove_process(struct Process *proc); +void remove_process(struct process *proc); -void install_event_handler(Process *proc, struct event_handler *handler); -void destroy_event_handler(Process *proc); +void install_event_handler(struct process *proc, struct event_handler *handler); +void destroy_event_handler(struct process *proc); /* Add a library LIB to the list of PROC's libraries. */ -void proc_add_library(struct Process *proc, struct library *lib); +void proc_add_library(struct process *proc, struct library *lib); /* Remove LIB from list of PROC's libraries. Returns 0 if the library * was found and unlinked, otherwise returns a negative value. */ -int proc_remove_library(struct Process *proc, struct library *lib); +int proc_remove_library(struct process *proc, struct library *lib); /* Clear a delayed flag. If a symbol is neither latent, nor delayed, * a breakpoint is inserted for it. Returns 0 if the activation was * successful or a negative value if it failed. Note that if a symbol * is both latent and delayed, this will not enable the corresponding * breakpoint. */ -int proc_activate_delayed_symbol(struct Process *proc, +int proc_activate_delayed_symbol(struct process *proc, struct library_symbol *libsym); /* Iterate through the libraries of PROC. See callback.h for notes on * iteration interfaces. */ -struct library *proc_each_library(struct Process *proc, struct library *start, - enum callback_status (*cb)(struct Process *p, +struct library *proc_each_library(struct process *proc, + struct library *start_after, + enum callback_status (*cb)(struct process *p, struct library *l, void *data), void *data); /* Insert BP into PROC. */ -int proc_add_breakpoint(struct Process *proc, struct breakpoint *bp); +int proc_add_breakpoint(struct process *proc, struct breakpoint *bp); /* Remove BP from PROC. This has no reason to fail in runtime. If it * does not find BP in PROC, it's hard error guarded by assertion. */ -void proc_remove_breakpoint(struct Process *proc, struct breakpoint *bp); +void proc_remove_breakpoint(struct process *proc, struct breakpoint *bp); /* Iterate through the breakpoints of PROC. See callback.h for notes * on iteration interfaces. */ -void *proc_each_breakpoint(struct Process *proc, void *start, - enum callback_status (*cb)(struct Process *proc, - struct breakpoint *bp, - void *data), - void *data); +arch_addr_t *proc_each_breakpoint(struct process *proc, arch_addr_t *start, + enum callback_status (*cb) + (struct process *proc, + struct breakpoint *bp, + void *data), + void *data); /* Iterate through the dynamic section at src_addr looking for D_TAG. * If tag is found, fill it's value in RET and return 0. * If tag is not found, return a negative value. */ -int proc_find_dynamic_entry_addr(struct Process *proc, arch_addr_t src_addr, +int proc_find_dynamic_entry_addr(struct process *proc, arch_addr_t src_addr, int d_tag, arch_addr_t *ret); /* Finds a symbol corresponding to LIBSYM in a process PROC. Returns * 0 and sets *RETLIB and *RETSYM if the corresponding pointer is * non-NULL. Returns a negative value when the symbols couldn't be * found. */ -int proc_find_symbol(struct Process *proc, struct library_symbol *sym, +int proc_find_symbol(struct process *proc, struct library_symbol *sym, struct library **retlib, struct library_symbol **retsym); /* Iterate through all symbols in all libraries of PROC. See * callback.h for notes on this interface. */ struct library_symbol *proc_each_symbol - (struct Process *proc, struct library_symbol *start_after, + (struct process *proc, struct library_symbol *start_after, enum callback_status (*cb)(struct library_symbol *, void *), void *data); -#endif /* _PROC_H_ */ +/* Read 8, 16, 32 or 64-bit quantity located at ADDR in PROC. The + * resulting value is stored in *LP. 0 is returned on success or a + * negative value on failure. This uses umovebytes under the hood + * (see backend.h). */ +int proc_read_8(struct process *proc, arch_addr_t addr, uint8_t *lp); +int proc_read_16(struct process *proc, arch_addr_t addr, uint16_t *lp); +int proc_read_32(struct process *proc, arch_addr_t addr, uint32_t *lp); +int proc_read_64(struct process *proc, arch_addr_t addr, uint64_t *lp); + +#endif /* PROC_H */ diff --git a/prototype.c b/prototype.c new file mode 100644 index 0000000..fa52ff3 --- /dev/null +++ b/prototype.c @@ -0,0 +1,708 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <alloca.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> + +#include "common.h" +#include "callback.h" +#include "param.h" +#include "prototype.h" +#include "type.h" +#include "options.h" +#include "read_config_file.h" +#include "backend.h" + +struct protolib_cache g_protocache; +static struct protolib legacy_typedefs; + +void +prototype_init(struct prototype *proto) +{ + VECT_INIT(&proto->params, struct param); + + proto->return_info = NULL; + proto->own_return_info = 0; +} + +static void +param_destroy_cb(struct param *param, void *data) +{ + param_destroy(param); +} + +void +prototype_destroy(struct prototype *proto) +{ + if (proto == NULL) + return; + if (proto->own_return_info) { + type_destroy(proto->return_info); + free(proto->return_info); + } + + VECT_DESTROY(&proto->params, struct param, ¶m_destroy_cb, NULL); +} + +int +prototype_push_param(struct prototype *proto, struct param *param) +{ + return VECT_PUSHBACK(&proto->params, param); +} + +size_t +prototype_num_params(struct prototype *proto) +{ + return vect_size(&proto->params); +} + +void +prototype_destroy_nth_param(struct prototype *proto, size_t n) +{ + assert(n < prototype_num_params(proto)); + VECT_ERASE(&proto->params, struct param, n, n+1, + ¶m_destroy_cb, NULL); +} + +struct param * +prototype_get_nth_param(struct prototype *proto, size_t n) +{ + assert(n < prototype_num_params(proto)); + return VECT_ELEMENT(&proto->params, struct param, n); +} + +struct each_param_data { + struct prototype *proto; + enum callback_status (*cb)(struct prototype *, struct param *, void *); + void *data; +}; + +static enum callback_status +each_param_cb(struct param *param, void *data) +{ + struct each_param_data *cb_data = data; + return (cb_data->cb)(cb_data->proto, param, cb_data->data); +} + +struct param * +prototype_each_param(struct prototype *proto, struct param *start_after, + enum callback_status (*cb)(struct prototype *, + struct param *, void *), + void *data) +{ + struct each_param_data cb_data = { proto, cb, data }; + return VECT_EACH(&proto->params, struct param, start_after, + &each_param_cb, &cb_data); +} + +void +named_type_init(struct named_type *named, + struct arg_type_info *info, int own_type) +{ + named->info = info; + named->own_type = own_type; + named->forward = 0; +} + +void +named_type_destroy(struct named_type *named) +{ + if (named->own_type) { + type_destroy(named->info); + free(named->info); + } +} + +void +protolib_init(struct protolib *plib) +{ + DICT_INIT(&plib->prototypes, char *, struct prototype, + dict_hash_string, dict_eq_string, NULL); + + DICT_INIT(&plib->named_types, char *, struct named_type, + dict_hash_string, dict_eq_string, NULL); + + VECT_INIT(&plib->imports, struct protolib *); + + plib->refs = 0; +} + +static void +destroy_prototype_cb(struct prototype *proto, void *data) +{ + prototype_destroy(proto); +} + +static void +destroy_named_type_cb(struct named_type *named, void *data) +{ + named_type_destroy(named); +} + +void +protolib_destroy(struct protolib *plib) +{ + assert(plib->refs == 0); + + VECT_DESTROY(&plib->imports, struct prototype *, NULL, NULL); + + DICT_DESTROY(&plib->prototypes, const char *, struct prototype, + dict_dtor_string, destroy_prototype_cb, NULL); + + DICT_DESTROY(&plib->named_types, const char *, struct named_type, + dict_dtor_string, destroy_named_type_cb, NULL); +} + +static struct protolib ** +each_import(struct protolib *plib, struct protolib **start_after, + enum callback_status (*cb)(struct protolib **, void *), void *data) +{ + assert(plib != NULL); + return VECT_EACH(&plib->imports, struct protolib *, + start_after, cb, data); +} + +static enum callback_status +is_or_imports(struct protolib **plibp, void *data) +{ + assert(plibp != NULL); + assert(*plibp != NULL); + struct protolib *import = data; + if (*plibp == import + || each_import(*plibp, NULL, &is_or_imports, import) != NULL) + return CBS_STOP; + else + return CBS_CONT; +} + +int +protolib_add_import(struct protolib *plib, struct protolib *import) +{ + assert(plib != NULL); + assert(import != NULL); + if (is_or_imports(&import, plib) == CBS_STOP) { + fprintf(stderr, "Recursive import rejected.\n"); + return -2; + } + + return VECT_PUSHBACK(&plib->imports, &import) < 0 ? -1 : 0; +} + +static int +bailout(const char *name, int own) +{ + int save_errno = errno; + if (own) + free((char *)name); + errno = save_errno; + return -1; +} + +int +protolib_add_prototype(struct protolib *plib, const char *name, int own_name, + struct prototype *proto) +{ + assert(plib != NULL); + if (strdup_if(&name, name, !own_name) < 0) + return -1; + if (DICT_INSERT(&plib->prototypes, &name, proto) < 0) + return bailout(name, own_name); + return 0; +} + +int +protolib_add_named_type(struct protolib *plib, const char *name, int own_name, + struct named_type *named) +{ + assert(plib != NULL); + if (strdup_if(&name, name, !own_name) < 0) + return -1; + if (DICT_INSERT(&plib->named_types, &name, named) < 0) + return bailout(name, own_name); + return 0; +} + +struct lookup { + const char *name; + struct dict *(*getter)(struct protolib *plib); + bool imports; + void *result; +}; + +static struct dict * +get_prototypes(struct protolib *plib) +{ + assert(plib != NULL); + return &plib->prototypes; +} + +static struct dict * +get_named_types(struct protolib *plib) +{ + assert(plib != NULL); + return &plib->named_types; +} + +static enum callback_status +protolib_lookup_rec(struct protolib **plibp, void *data) +{ + assert(plibp != NULL); + assert(*plibp != NULL); + struct lookup *lookup = data; + struct dict *dict = (*lookup->getter)(*plibp); + + lookup->result = dict_find(dict, &lookup->name); + if (lookup->result != NULL) + return CBS_STOP; + + if (lookup->imports && each_import(*plibp, NULL, &protolib_lookup_rec, + lookup) != NULL) { + assert(lookup->result != NULL); + return CBS_STOP; + } + + return CBS_CONT; +} + +static void * +protolib_lookup(struct protolib *plib, const char *name, + struct dict *(*getter)(struct protolib *), + bool imports) +{ + assert(plib != NULL); + struct lookup lookup = { name, getter, imports, NULL }; + if (protolib_lookup_rec(&plib, &lookup) == CBS_STOP) + assert(lookup.result != NULL); + else + assert(lookup.result == NULL); + return lookup.result; +} + +struct prototype * +protolib_lookup_prototype(struct protolib *plib, const char *name, bool imports) +{ + assert(plib != NULL); + return protolib_lookup(plib, name, &get_prototypes, imports); +} + +struct named_type * +protolib_lookup_type(struct protolib *plib, const char *name, bool imports) +{ + assert(plib != NULL); + return protolib_lookup(plib, name, &get_named_types, imports); +} + +static void +destroy_protolib_cb(struct protolib **plibp, void *data) +{ + assert(plibp != NULL); + + if (*plibp != NULL + && --(*plibp)->refs == 0) { + protolib_destroy(*plibp); + free(*plibp); + } +} + +void +protolib_cache_destroy(struct protolib_cache *cache) +{ + DICT_DESTROY(&cache->protolibs, const char *, struct protolib *, + dict_dtor_string, destroy_protolib_cb, NULL); +} + +struct load_config_data { + struct protolib_cache *self; + const char *key; + struct protolib *result; +}; + +static struct protolib * +consider_config_dir(struct protolib_cache *cache, + const char *path, const char *key) +{ + size_t len = sizeof ".conf"; + char *buf = alloca(strlen(path) + 1 + strlen(key) + len); + sprintf(buf, "%s/%s.conf", path, key); + + return protolib_cache_file(cache, buf, 0); +} + +static enum callback_status +consider_confdir_cb(struct opt_F_t *entry, void *d) +{ + if (opt_F_get_kind(entry) != OPT_F_DIR) + return CBS_CONT; + struct load_config_data *data = d; + + data->result = consider_config_dir(data->self, + entry->pathname, data->key); + return data->result != NULL ? CBS_STOP : CBS_CONT; +} + +static int +load_dash_F_dirs(struct protolib_cache *cache, + const char *key, struct protolib **retp) +{ + struct load_config_data data = {cache, key}; + + if (VECT_EACH(&opt_F, struct opt_F_t, NULL, + consider_confdir_cb, &data) == NULL) + /* Not found. That's fine. */ + return 0; + + if (data.result == NULL) + /* There were errors. */ + return -1; + + *retp = data.result; + return 0; +} + +static int +load_config(struct protolib_cache *cache, + const char *key, int private, struct protolib **retp) +{ + const char **dirs = NULL; + if (os_get_config_dirs(private, &dirs) < 0 + || dirs == NULL) + return -1; + + for (; *dirs != NULL; ++dirs) { + struct protolib *plib = consider_config_dir(cache, *dirs, key); + if (plib != NULL) { + *retp = plib; + break; + } + } + + return 0; +} + +static enum callback_status +import_legacy_file(char **fnp, void *data) +{ + struct protolib_cache *cache = data; + struct protolib *plib = protolib_cache_file(cache, *fnp, 1); + if (plib != NULL) { + /* The cache now owns the file name. */ + *fnp = NULL; + if (protolib_add_import(&cache->imports, plib) < 0) + return CBS_STOP; + } + + return CBS_CONT; +} + +static int +add_ltrace_conf(struct protolib_cache *cache) +{ + /* Look into private config directories for .ltrace.conf and + * into system config directories for ltrace.conf. If it's + * found, add it to implicit import module. */ + struct vect legacy_files; + VECT_INIT(&legacy_files, char *); + if (os_get_ltrace_conf_filenames(&legacy_files) < 0) { + vect_destroy(&legacy_files, NULL, NULL); + return -1; + } + + int ret = VECT_EACH(&legacy_files, char *, NULL, + import_legacy_file, cache) == NULL ? 0 : -1; + VECT_DESTROY(&legacy_files, char *, vect_dtor_string, NULL); + return ret; +} + +static enum callback_status +add_imports_cb(struct opt_F_t *entry, void *data) +{ + struct protolib_cache *self = data; + if (opt_F_get_kind(entry) != OPT_F_FILE) + return CBS_CONT; + + struct protolib *new_import + = protolib_cache_file(self, entry->pathname, 0); + + if (new_import == NULL + || protolib_add_import(&self->imports, new_import) < 0) + /* N.B. If new_import is non-NULL, it has been already + * cached. We don't therefore destroy it on + * failures. */ + return CBS_STOP; + + return CBS_CONT; +} + +int +protolib_cache_init(struct protolib_cache *cache, struct protolib *import) +{ + DICT_INIT(&cache->protolibs, char *, struct protolib *, + dict_hash_string, dict_eq_string, NULL); + protolib_init(&cache->imports); + + /* At this point the cache is consistent. This is important, + * because next we will use it to cache files that we load + * due to -F. + * + * But we are about to construct the implicit import module, + * which means this module can't be itself imported to the + * files that we load now. So remember that we are still + * bootstrapping. */ + cache->bootstrap = 1; + + if (protolib_add_import(&cache->imports, &legacy_typedefs) < 0 + || (import != NULL + && protolib_add_import(&cache->imports, import) < 0) + || add_ltrace_conf(cache) < 0 + || VECT_EACH(&opt_F, struct opt_F_t, NULL, + add_imports_cb, cache) != NULL) { + protolib_cache_destroy(cache); + return -1; + } + + cache->bootstrap = 0; + return 0; +} + +static enum callback_status +add_import_cb(struct protolib **importp, void *data) +{ + struct protolib *plib = data; + if (protolib_add_import(plib, *importp) < 0) + return CBS_STOP; + else + return CBS_CONT; +} + +static struct protolib * +build_default_config(struct protolib_cache *cache, const char *key) +{ + struct protolib *new_plib = malloc(sizeof(*new_plib)); + if (new_plib == NULL) { + fprintf(stderr, "Couldn't create config module %s: %s\n", + key, strerror(errno)); + return NULL; + } + + protolib_init(new_plib); + + /* If bootstrapping, copy over imports from implicit import + * module to new_plib. We can't reference the implicit + * import module itself, because new_plib will become part of + * this same implicit import module itself. */ + if ((cache->bootstrap && each_import(&cache->imports, NULL, + add_import_cb, new_plib) != NULL) + || (!cache->bootstrap + && protolib_add_import(new_plib, &cache->imports) < 0)) { + + fprintf(stderr, + "Couldn't add imports to config module %s: %s\n", + key, strerror(errno)); + protolib_destroy(new_plib); + free(new_plib); + return NULL; + } + + return new_plib; +} + +static void +attempt_to_cache(struct protolib_cache *cache, + const char *key, struct protolib *plib) +{ + if (protolib_cache_protolib(cache, key, 1, plib) == 0 + || plib == NULL) + /* Never mind failing to store a NULL. */ + return; + + /* Returning a protolib that hasn't been cached would leak + * that protolib, but perhaps it's less bad then giving up + * outright. At least print an error message. */ + fprintf(stderr, "Couldn't cache prototype library for %s\n", key); + free((void *) key); +} + +int +protolib_cache_maybe_load(struct protolib_cache *cache, + const char *key, int own_key, bool allow_private, + struct protolib **retp) +{ + if (DICT_FIND_VAL(&cache->protolibs, &key, retp) == 0) + return 0; + + if (strdup_if(&key, key, !own_key) < 0) { + fprintf(stderr, "Couldn't cache %s: %s\n", + key, strerror(errno)); + return -1; + } + + *retp = NULL; + if (load_dash_F_dirs(cache, key, retp) < 0 + || (*retp == NULL && allow_private + && load_config(cache, key, 1, retp) < 0) + || (*retp == NULL + && load_config(cache, key, 0, retp) < 0)) + { + fprintf(stderr, + "Error occurred when attempting to load a prototype " + "library for %s.\n", key); + if (!own_key) + free((void *) key); + return -1; + } + + if (*retp != NULL) + attempt_to_cache(cache, key, *retp); + else if (!own_key) + free((void *) key); + + return 0; +} + +struct protolib * +protolib_cache_load(struct protolib_cache *cache, + const char *key, int own_key, bool allow_private) +{ + struct protolib *plib; + if (protolib_cache_maybe_load(cache, key, own_key, + allow_private, &plib) < 0) + return NULL; + + if (plib == NULL) + plib = protolib_cache_default(cache, key, own_key); + + return plib; +} + +struct protolib * +protolib_cache_default(struct protolib_cache *cache, + const char *key, int own_key) +{ + if (strdup_if(&key, key, !own_key) < 0) { + fprintf(stderr, "Couldn't cache default %s: %s\n", + key, strerror(errno)); + return NULL; + } + + struct protolib *plib = build_default_config(cache, key); + + /* Whatever came out of this (even NULL), store it in + * the cache. */ + attempt_to_cache(cache, key, plib); + + return plib; +} + +struct protolib * +protolib_cache_file(struct protolib_cache *cache, + const char *filename, int own_filename) +{ + { + struct protolib *plib; + if (DICT_FIND_VAL(&cache->protolibs, &filename, &plib) == 0) + return plib; + } + + FILE *stream = fopen(filename, "r"); + if (stream == NULL) + return NULL; + + if (strdup_if(&filename, filename, !own_filename) < 0) { + fprintf(stderr, "Couldn't cache %s: %s\n", + filename, strerror(errno)); + fclose(stream); + return NULL; + } + + struct protolib *new_plib = build_default_config(cache, filename); + if (new_plib == NULL + || read_config_file(stream, filename, new_plib) < 0) { + fclose(stream); + if (own_filename) + free((char *) filename); + if (new_plib != NULL) { + protolib_destroy(new_plib); + free(new_plib); + } + return NULL; + } + + attempt_to_cache(cache, filename, new_plib); + fclose(stream); + return new_plib; +} + +int +protolib_cache_protolib(struct protolib_cache *cache, + const char *filename, int own_filename, + struct protolib *plib) +{ + if (strdup_if(&filename, filename, !own_filename) < 0) { + fprintf(stderr, "Couldn't cache %s: %s\n", + filename, strerror(errno)); + return -1; + } + + int rc = DICT_INSERT(&cache->protolibs, &filename, &plib); + if (rc < 0 && own_filename) + free((char *) filename); + if (rc == 0 && plib != NULL) + plib->refs++; + return rc; +} + +static void +destroy_global_config(void) +{ + protolib_cache_destroy(&g_protocache); + protolib_destroy(&legacy_typedefs); +} + +void +init_global_config(void) +{ + protolib_init(&legacy_typedefs); + + struct arg_type_info *ptr_info = type_get_voidptr(); + static struct named_type voidptr_type; + named_type_init(&voidptr_type, ptr_info, 0); + + /* Build legacy typedefs first. This is used by + * protolib_cache_init call below. */ + if (protolib_add_named_type(&legacy_typedefs, "addr", 0, + &voidptr_type) < 0 + || protolib_add_named_type(&legacy_typedefs, "file", 0, + &voidptr_type) < 0) { + fprintf(stderr, + "Couldn't initialize aliases `addr' and `file'.\n"); + + exit(1); + } + + if (protolib_cache_init(&g_protocache, NULL) < 0) { + fprintf(stderr, "Couldn't init prototype cache\n"); + exit(1); + } + + atexit(destroy_global_config); +} diff --git a/prototype.h b/prototype.h new file mode 100644 index 0000000..cbb6ed2 --- /dev/null +++ b/prototype.h @@ -0,0 +1,243 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef PROTOTYPE_H +#define PROTOTYPE_H + +#include <stdbool.h> + +#include "forward.h" +#include "dict.h" +#include "vect.h" + +/* Function prototype. */ +struct prototype { + /* Vector of struct param. */ + struct vect params; + + struct arg_type_info *return_info; + int own_return_info : 1; +}; + +/* Initialize a prototype PROTO. The name will be NAME, and the + * corresponding string will be owned and freed on destroy if + * OWN_NAME. */ +void prototype_init(struct prototype *proto); + +/* Destroy PROTO (but don't free the memory block pointed-to by + * PROTO). */ +void prototype_destroy(struct prototype *proto); + +/* Add new parameter PARAM to PROTO. The structure contents are + * copied and PARAM pointer itself is not owned by PROTO. */ +int prototype_push_param(struct prototype *proto, struct param *param); + +/* Return number of parameters of prototype. */ +size_t prototype_num_params(struct prototype *proto); + +/* Destroy N-th parameter from PROTO. N shall be smaller than the + * number of parameters. */ +void prototype_destroy_nth_param(struct prototype *proto, size_t n); + +/* Get N-th parameter of PROTO. N shall be smaller than the number of + * parameters. */ +struct param *prototype_get_nth_param(struct prototype *proto, size_t n); + +/* Iterate through the parameters of PROTO. See callback.h for notes + * on iteration interfaces. */ +struct param *prototype_each_param + (struct prototype *proto, struct param *start_after, + enum callback_status (*cb)(struct prototype *, struct param *, void *), + void *data); + +/* For storing type aliases. */ +struct named_type { + struct arg_type_info *info; + int forward : 1; + int own_type : 1; +}; + +/* Initialize a named type INFO, which, if OWN_TYPE, is destroyed when + * named_type_destroy is called. */ +void named_type_init(struct named_type *named, + struct arg_type_info *info, int own_type); + +void named_type_destroy(struct named_type *named); + +/* One prototype library. */ +struct protolib { + /* Other libraries to look through if the definition is not + * found here. Note that due to the way imports are stored, + * there is no way to distinguish where exactly (at which + * place of the config file) the import was made. */ + struct vect imports; + + /* Dictionary of name->struct prototype. */ + struct dict prototypes; + + /* Dictionary of name->struct named_type. */ + struct dict named_types; + + /* Reference count. */ + unsigned refs; +}; + +/* Initialize PLIB. */ +void protolib_init(struct protolib *plib); + +/* Destroy PLIB. */ +void protolib_destroy(struct protolib *plib); + +/* Push IMPORT to PLIB. Returns 0 on success or a negative value on + * failure. In particular, -2 is returned if mutual import is + * detected. */ +int protolib_add_import(struct protolib *plib, struct protolib *import); + +/* Add a prototype PROTO to PLIB. Returns 0 on success or a negative + * value on failure. NAME is owned and released on PLIB destruction + * if OWN_NAME. */ +int protolib_add_prototype(struct protolib *plib, + const char *name, int own_name, + struct prototype *proto); + +/* Add a named type NAMED to PLIB. Returns 0 on success or a negative + * value on failure. NAME is owned and released on PLIB destruction + * if OWN_NAME. NAMED _pointer_ is copied to PLIB. */ +int protolib_add_named_type(struct protolib *plib, + const char *name, int own_name, + struct named_type *named); + +/* Lookup prototype named NAME in PLIB. If none is found and IMPORTS + * is true, look recursively in each of the imports. Returns the + * corresponding prototype, or NULL if none was found. */ +struct prototype *protolib_lookup_prototype(struct protolib *plib, + const char *name, bool imports); + +/* Add a named type NAMED to PLIB. Returns 0 on success or a negative + * value on failure. */ +int protolib_add_type(struct protolib *plib, struct named_type *named); + +/* Lookup type named NAME in PLIB. If none is found and IMPORTS is + * true, look recursively in each of the imports. Returns the + * corresponding type, or NULL if none was found. */ +struct named_type *protolib_lookup_type(struct protolib *plib, + const char *name, bool imports); + +/* A cache of prototype libraries. Can load prototype libraries on + * demand. + * + * XXX ltrace should open one config per ABI, which maps long, int, + * etc. to uint32_t etc. It would also map char to either of + * {u,}int8_t. Other protolibs would have this as implicit import. + * That would mean that the cache needs ABI tagging--each ABI should + * have a separate prototype cache, because the types will potentially + * differ between the ABI's. protolib cache would then naturally be + * stored in the ABI object, when this is introduced. */ +struct protolib_cache { + /* Dictionary of filename->protolib*. */ + struct dict protolibs; + + /* Fake module for implicit imports. This is populated by all + * files coming from -F. When -F is empty, it also contains + * either $HOME/.ltrace.conf, or /etc/ltrace.conf (whichever + * comes first). */ + struct protolib imports; + + /* For tracking uses of cache during cache's own + * initialization. */ + int bootstrap : 1; +}; + +/* Initialize CACHE. Returns 0 on success or a negative value on + * failure. */ +int protolib_cache_init(struct protolib_cache *cache, + struct protolib *import); + +/* Destroy CACHE. */ +void protolib_cache_destroy(struct protolib_cache *cache); + +/* Get protolib corresponding to KEY from CACHE. KEY would typically + * be the soname of a library for which a protolib should be obtained. + * If none has been loaded yet, load a new protolib, cache and return + * it. Returns NULL for failures. + * + * Protolibs are loaded from a config directory. If -F contains + * directory names, those are checked first. Next, os_get_config_dirs + * callback is used to get a list of directories to look into. In the + * first round, if ALLOW_PRIVATE, ltrace looks in user's private + * directories. If the config file wasn't found, the second round is + * made through system directories. In each directory, ltrace looks + * and reads the file named KEY.conf. + * + * If the config file still wasn't found, an empty (but non-NULL) + * protolib is provided instead. That is augmented with the following + * imports: + * + * - Legacy typedefs + * - The IMPORT argument passed to protolib_cache_init, if non-NULL + * - $HOME/.ltrace.conf if available + * - @sysconfdir@/ltrace.conf if available + * - Any configure _files_ passed in -F + * + * This function returns either the loaded protolib, or NULL when + * there was an error. */ +struct protolib *protolib_cache_load(struct protolib_cache *cache, + const char *key, int own_key, + bool allow_private); + +/* This is similar to protolib_cache_load, except that if a protolib + * is not found NULL is returned instead of a default module. + * + * It returns 0 for success and a negative value for failure, and the + * actual return value is passed via *RET.*/ +int protolib_cache_maybe_load(struct protolib_cache *cache, + const char *key, int own_key, + bool allow_private, + struct protolib **ret); + +/* This is similar to protolib_cache_load, but instead of looking for + * the file to load in directories, the filename is given. */ +struct protolib *protolib_cache_file(struct protolib_cache *cache, + const char *filename, int own_filename); + +/* This caches a default module. This is what protolib_cache_load + * calls if it fails to find the actual protolib. Returns default + * protolib or NULL if there was an error. */ +struct protolib *protolib_cache_default(struct protolib_cache *cache, + const char *key, int own_key); + +/* This is similar to protolib_cache_file, but the library to cache is + * given in argument. Returns 0 on success or a negative value on + * failure. PLIB is thereafter owned by CACHE. */ +int protolib_cache_protolib(struct protolib_cache *cache, + const char *filename, int own_filename, + struct protolib *plib); + +/* Single global prototype cache. + * + * XXX Eventually each ABI should have its own cache. The idea is + * that there's one per-ABI config file that all others use for + * elementary typedefs (long, char, size_t). Ltrace then only deals + * in fixed-width integral types (and pointers etc.). */ +extern struct protolib_cache g_protocache; + +void init_global_config(void); + +#endif /* PROTOTYPE_H */ diff --git a/read_config_file.c b/read_config_file.c index e247436..e9c050d 100644 --- a/read_config_file.c +++ b/read_config_file.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 1998,1999,2003,2007,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * Copyright (C) 2006 Steve Fink @@ -21,13 +21,17 @@ * 02110-1301 USA */ +/* getline is POSIX.1-2008. It was originally a GNU extension, and + * chances are uClibc still needs _GNU_SOURCE, but for now try it this + * way. */ +#define _POSIX_C_SOURCE 200809L + #include "config.h" #include <string.h> #include <stdlib.h> #include <ctype.h> #include <errno.h> -#include <error.h> #include <assert.h> #include "common.h" @@ -35,29 +39,52 @@ #include "expr.h" #include "param.h" #include "printf.h" +#include "prototype.h" #include "zero.h" #include "type.h" #include "lens.h" #include "lens_default.h" #include "lens_enum.h" -static int line_no; -static char *filename; -struct typedef_node_t; +/* Lifted from GCC: The ctype functions are often implemented as + * macros which do lookups in arrays using the parameter as the + * offset. If the ctype function parameter is a char, then gcc will + * (appropriately) warn that a "subscript has type char". Using a + * (signed) char as a subscript is bad because you may get negative + * offsets and thus it is not 8-bit safe. The CTYPE_CONV macro + * ensures that the parameter is cast to an unsigned char when a char + * is passed in. When an int is passed in, the parameter is left + * alone so we don't lose EOF. */ + +#define CTYPE_CONV(CH) \ + (sizeof(CH) == sizeof(unsigned char) ? (int)(unsigned char)(CH) : (int)(CH)) + +struct locus +{ + const char *filename; + int line_no; +}; -static struct arg_type_info *parse_nonpointer_type(char **str, +static struct arg_type_info *parse_nonpointer_type(struct protolib *plib, + struct locus *loc, + char **str, struct param **extra_param, - size_t param_num, int *ownp, - struct typedef_node_t *td); -static struct arg_type_info *parse_type(char **str, struct param **extra_param, + size_t param_num, + int *ownp, int *forwardp); +static struct arg_type_info *parse_type(struct protolib *plib, + struct locus *loc, + char **str, struct param **extra_param, size_t param_num, int *ownp, - struct typedef_node_t *in_typedef); -static struct arg_type_info *parse_lens(char **str, struct param **extra_param, + int *forwardp); +static struct arg_type_info *parse_lens(struct protolib *plib, + struct locus *loc, + char **str, struct param **extra_param, size_t param_num, int *ownp, - struct typedef_node_t *in_typedef); -static int parse_enum(char **str, struct arg_type_info **retp, int *ownp); + int *forwardp); +static int parse_enum(struct protolib *plib, struct locus *loc, + char **str, struct arg_type_info **retp, int *ownp); -Function *list_of_functions = NULL; +struct prototype *list_of_functions = NULL; static int parse_arg_type(char **name, enum arg_type *ret) @@ -97,7 +124,7 @@ parse_arg_type(char **name, enum arg_type *ret) #undef KEYWORD ok: - if (isalnum(*rest)) + if (isalnum(CTYPE_CONV(*rest)) || *rest == '_') return -1; *name = rest; @@ -125,60 +152,29 @@ xstrndup(char *str, size_t len) { } static char * -parse_ident(char **str) { +parse_ident(struct locus *loc, char **str) +{ char *ident = *str; - if (!isalpha(**str) && **str != '_') { - report_error(filename, line_no, "bad identifier"); + if (!isalpha(CTYPE_CONV(**str)) && **str != '_') { + report_error(loc->filename, loc->line_no, "bad identifier"); return NULL; } - while (**str && (isalnum(**str) || **str == '_')) { + while (**str && (isalnum(CTYPE_CONV(**str)) || **str == '_')) { ++(*str); } return xstrndup(ident, *str - ident); } -/* - Returns position in string at the left parenthesis which starts the - function's argument signature. Returns NULL on error. -*/ -static char * -start_of_arg_sig(char *str) { - char *pos; - int stacked = 0; - - if (!strlen(str)) - return NULL; - - pos = &str[strlen(str)]; - do { - pos--; - if (pos < str) - return NULL; - while ((pos > str) && (*pos != ')') && (*pos != '(')) - pos--; - - if (*pos == ')') - stacked++; - else if (*pos == '(') - stacked--; - else - return NULL; - - } while (stacked > 0); - - return (stacked == 0) ? pos : NULL; -} - static int -parse_int(char **str, long *ret) +parse_int(struct locus *loc, char **str, long *ret) { char *end; long n = strtol(*str, &end, 0); if (end == *str) { - report_error(filename, line_no, "bad number"); + report_error(loc->filename, loc->line_no, "bad number"); return -1; } @@ -189,10 +185,10 @@ parse_int(char **str, long *ret) } static int -check_nonnegative(long l) +check_nonnegative(struct locus *loc, long l) { if (l < 0) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "expected non-negative value, got %ld", l); return -1; } @@ -200,11 +196,11 @@ check_nonnegative(long l) } static int -check_int(long l) +check_int(struct locus *loc, long l) { int i = l; if ((long)i != l) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "Number too large: %ld", l); return -1; } @@ -212,10 +208,10 @@ check_int(long l) } static int -parse_char(char **str, char expected) +parse_char(struct locus *loc, char **str, char expected) { if (**str != expected) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "expected '%c', got '%c'", expected, **str); return -1; } @@ -224,19 +220,20 @@ parse_char(char **str, char expected) return 0; } -static struct expr_node *parse_argnum(char **str, int *ownp, int zero); +static struct expr_node *parse_argnum(struct locus *loc, + char **str, int *ownp, int zero); static struct expr_node * -parse_zero(char **str, struct expr_node *ret, int *ownp) +parse_zero(struct locus *loc, char **str, int *ownp) { eat_spaces(str); if (**str == '(') { ++*str; int own; - struct expr_node *arg = parse_argnum(str, &own, 0); + struct expr_node *arg = parse_argnum(loc, str, &own, 0); if (arg == NULL) return NULL; - if (parse_char(str, ')') < 0) { + if (parse_char(loc, str, ')') < 0) { fail: expr_destroy(arg); free(arg); @@ -250,7 +247,6 @@ parse_zero(char **str, struct expr_node *ret, int *ownp) return ret; } else { - free(ret); *ownp = 0; return expr_node_zero(); } @@ -274,17 +270,17 @@ wrap_in_zero(struct expr_node **nodep) * N : The numeric value N */ static struct expr_node * -parse_argnum(char **str, int *ownp, int zero) +parse_argnum(struct locus *loc, char **str, int *ownp, int zero) { struct expr_node *expr = malloc(sizeof(*expr)); if (expr == NULL) return NULL; - if (isdigit(**str)) { + if (isdigit(CTYPE_CONV(**str))) { long l; - if (parse_int(str, &l) < 0 - || check_nonnegative(l) < 0 - || check_int(l) < 0) + if (parse_int(loc, str, &l) < 0 + || check_nonnegative(loc, l) < 0 + || check_int(loc, l) < 0) goto fail; expr_init_const_word(expr, l, type_get_simple(ARGTYPE_LONG), 0); @@ -296,7 +292,7 @@ parse_argnum(char **str, int *ownp, int zero) return expr; } else { - char *const name = parse_ident(str); + char *const name = parse_ident(loc, str); if (name == NULL) { fail_ident: free(name); @@ -307,7 +303,8 @@ parse_argnum(char **str, int *ownp, int zero) if (is_arg || strncmp(name, "elt", 3) == 0) { long l; char *num = name + 3; - if (parse_int(&num, &l) < 0 || check_int(l) < 0) + if (parse_int(loc, &num, &l) < 0 + || check_int(loc, l) < 0) goto fail_ident; if (is_arg) { @@ -335,13 +332,16 @@ parse_argnum(char **str, int *ownp, int zero) expr_init_named(expr, "retval", 0); } else if (strcmp(name, "zero") == 0) { - struct expr_node *ret = parse_zero(str, expr, ownp); + struct expr_node *ret + = parse_zero(loc, str, ownp); if (ret == NULL) goto fail_ident; + free(expr); + free(name); return ret; } else { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "Unknown length specifier: '%s'", name); goto fail_ident; } @@ -359,29 +359,11 @@ fail: return NULL; } -struct typedef_node_t { - char *name; - struct arg_type_info *info; - int own_type; - int forward : 1; - struct typedef_node_t *next; -} *typedefs = NULL; - -static struct typedef_node_t * -lookup_typedef(const char *name) -{ - struct typedef_node_t *node; - for (node = typedefs; node != NULL; node = node->next) - if (strcmp(name, node->name) == 0) - return node; - return NULL; -} - static struct arg_type_info * -parse_typedef_name(char **str) +parse_typedef_name(struct protolib *plib, char **str) { char *end = *str; - while (*end && (isalnum(*end) || *end == '_')) + while (*end && (isalnum(CTYPE_CONV(*end)) || *end == '_')) ++end; if (end == *str) return NULL; @@ -392,127 +374,91 @@ parse_typedef_name(char **str) *str += len; buf[len] = 0; - struct typedef_node_t *td = lookup_typedef(buf); - if (td == NULL) + struct named_type *nt = protolib_lookup_type(plib, buf, true); + if (nt == NULL) return NULL; - return td->info; + return nt->info; } -static void -insert_typedef(struct typedef_node_t *td) -{ - if (td == NULL) - return; - td->next = typedefs; - typedefs = td; -} - -static struct typedef_node_t * -new_typedef(char *name, struct arg_type_info *info, int own_type) -{ - struct typedef_node_t *binding = malloc(sizeof(*binding)); - binding->name = name; - binding->info = info; - binding->own_type = own_type; - binding->forward = 0; - binding->next = NULL; - return binding; -} - -static void -parse_typedef(char **str) +static int +parse_typedef(struct protolib *plib, struct locus *loc, char **str) { (*str) += strlen("typedef"); eat_spaces(str); - char *name = parse_ident(str); + char *name = parse_ident(loc, str); /* Look through the typedef list whether we already have a * forward of this type. If we do, it must be forward * structure. */ - struct typedef_node_t *forward = lookup_typedef(name); + struct named_type *forward = protolib_lookup_type(plib, name, true); if (forward != NULL && (forward->info->type != ARGTYPE_STRUCT || !forward->forward)) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "Redefinition of typedef '%s'", name); + err: free(name); - return; + return -1; } // Skip = sign eat_spaces(str); - if (parse_char(str, '=') < 0) { - free(name); - return; - } + if (parse_char(loc, str, '=') < 0) + goto err; eat_spaces(str); - struct typedef_node_t *this_td = new_typedef(name, NULL, 0); - this_td->info = parse_lens(str, NULL, 0, &this_td->own_type, this_td); + int fwd = 0; + int own = 0; + struct arg_type_info *info + = parse_lens(plib, loc, str, NULL, 0, &own, &fwd); + if (info == NULL) + goto err; - if (this_td->info == NULL) { - free(this_td); - free(name); - return; - } + struct named_type this_nt; + named_type_init(&this_nt, info, own); + this_nt.forward = fwd; if (forward == NULL) { - insert_typedef(this_td); - return; + if (protolib_add_named_type(plib, name, 1, &this_nt) < 0) { + named_type_destroy(&this_nt); + goto err; + } + return 0; } /* If we are defining a forward, make sure the definition is a * structure as well. */ - if (this_td->info->type != ARGTYPE_STRUCT) { - report_error(filename, line_no, + if (this_nt.info->type != ARGTYPE_STRUCT) { + report_error(loc->filename, loc->line_no, "Definition of forward '%s' must be a structure.", name); - if (this_td->own_type) { - type_destroy(this_td->info); - free(this_td->info); - } - free(this_td); - free(name); - return; + named_type_destroy(&this_nt); + goto err; } - /* Now move guts of the actual type over to the - * forward type. We can't just move pointers around, - * because references to forward must stay intact. */ - assert(this_td->own_type); + /* Now move guts of the actual type over to the forward type. + * We can't just move pointers around, because references to + * forward must stay intact. */ + assert(this_nt.own_type); type_destroy(forward->info); - *forward->info = *this_td->info; + *forward->info = *this_nt.info; forward->forward = 0; - free(this_td->info); + free(this_nt.info); free(name); - free(this_td); -} - -static void -destroy_fun(Function *fun) -{ - size_t i; - if (fun == NULL) - return; - if (fun->own_return_info) { - type_destroy(fun->return_info); - free(fun->return_info); - } - for (i = 0; i < fun->num_params; ++i) - param_destroy(&fun->params[i]); - free(fun->params); + return 0; } /* Syntax: struct ( type,type,type,... ) */ static int -parse_struct(char **str, struct arg_type_info *info, - struct typedef_node_t *in_typedef) +parse_struct(struct protolib *plib, struct locus *loc, + char **str, struct arg_type_info *info, + int *forwardp) { eat_spaces(str); if (**str == ';') { - if (in_typedef == NULL) { - report_error(filename, line_no, + if (forwardp == NULL) { + report_error(loc->filename, loc->line_no, "Forward struct can be declared only " "directly after a typedef."); return -1; @@ -521,11 +467,11 @@ parse_struct(char **str, struct arg_type_info *info, /* Forward declaration is currently handled as an * empty struct. */ type_init_struct(info); - in_typedef->forward = 1; + *forwardp = 1; return 0; } - if (parse_char(str, '(') < 0) + if (parse_char(loc, str, '(') < 0) return -1; eat_spaces(str); // Empty arg list with whitespace inside @@ -535,18 +481,18 @@ parse_struct(char **str, struct arg_type_info *info, while (1) { eat_spaces(str); if (**str == 0 || **str == ')') { - parse_char(str, ')'); + parse_char(loc, str, ')'); return 0; } /* Field delimiter. */ if (type_struct_size(info) > 0) - parse_char(str, ','); + parse_char(loc, str, ','); eat_spaces(str); int own; - struct arg_type_info *field = parse_lens(str, NULL, 0, &own, - NULL); + struct arg_type_info *field + = parse_lens(plib, loc, str, NULL, 0, &own, NULL); if (field == NULL || type_struct_add(info, field, own)) { type_destroy(info); return -1; @@ -554,31 +500,45 @@ parse_struct(char **str, struct arg_type_info *info, } } +/* Make a copy of INFO and set the *OWN bit if it's not already + * owned. */ static int -parse_string(char **str, struct arg_type_info **retp, int *ownp) +unshare_type_info(struct locus *loc, struct arg_type_info **infop, int *ownp) { - struct arg_type_info *info = malloc(sizeof(*info) * 2); - if (info == NULL) { - fail: - free(info); + if (*ownp) + return 0; + + struct arg_type_info *ninfo = malloc(sizeof(*ninfo)); + if (ninfo == NULL || type_clone(ninfo, *infop) < 0) { + report_error(loc->filename, loc->line_no, + "malloc: %s", strerror(errno)); + free(ninfo); return -1; } + *infop = ninfo; + *ownp = 1; + return 0; +} +static int +parse_string(struct protolib *plib, struct locus *loc, + char **str, struct arg_type_info **retp, int *ownp) +{ + struct arg_type_info *info = NULL; struct expr_node *length; int own_length; - int with_arg = 0; - if (isdigit(**str)) { + if (isdigit(CTYPE_CONV(**str))) { /* string0 is string[retval], length is zero(retval) * stringN is string[argN], length is zero(argN) */ long l; - if (parse_int(str, &l) < 0 - || check_int(l) < 0) - goto fail; + if (parse_int(loc, str, &l) < 0 + || check_int(loc, l) < 0) + return -1; struct expr_node *length_arg = malloc(sizeof(*length_arg)); if (length_arg == NULL) - goto fail; + return -1; if (l == 0) expr_init_named(length_arg, "retval", 0); @@ -589,7 +549,7 @@ parse_string(char **str, struct arg_type_info **retp, int *ownp) if (length == NULL) { expr_destroy(length_arg); free(length_arg); - goto fail; + return -1; } own_length = 1; @@ -599,28 +559,27 @@ parse_string(char **str, struct arg_type_info **retp, int *ownp) (*str)++; eat_spaces(str); - length = parse_argnum(str, &own_length, 1); + length = parse_argnum(loc, str, &own_length, 1); if (length == NULL) - goto fail; + return -1; eat_spaces(str); - parse_char(str, ']'); + parse_char(loc, str, ']'); } else if (**str == '(') { /* Usage of "string" as lens. */ ++*str; - free(info); - eat_spaces(str); - info = parse_type(str, NULL, 0, ownp, NULL); + info = parse_type(plib, loc, str, NULL, 0, ownp, NULL); if (info == NULL) - goto fail; + return -1; - eat_spaces(str); - parse_char(str, ')'); + length = NULL; + own_length = 0; - with_arg = 1; + eat_spaces(str); + parse_char(loc, str, ')'); } else { /* It was just a simple string after all. */ @@ -630,14 +589,35 @@ parse_string(char **str, struct arg_type_info **retp, int *ownp) } /* String is a pointer to array of chars. */ - if (!with_arg) { - type_init_array(&info[1], type_get_simple(ARGTYPE_CHAR), 0, + if (info == NULL) { + struct arg_type_info *info1 = malloc(sizeof(*info1)); + struct arg_type_info *info2 = malloc(sizeof(*info2)); + if (info1 == NULL || info2 == NULL) { + free(info1); + free(info2); + fail: + if (own_length) { + assert(length != NULL); + expr_destroy(length); + free(length); + } + return -1; + } + type_init_array(info2, type_get_simple(ARGTYPE_CHAR), 0, length, own_length); + type_init_pointer(info1, info2, 1); - type_init_pointer(&info[0], &info[1], 0); + info = info1; *ownp = 1; } + /* We'll need to set the lens, so unshare. */ + if (unshare_type_info(loc, &info, ownp) < 0) + /* If unshare_type_info failed, it must have been as a + * result of cloning attempt because *OWNP was 0. + * Thus we don't need to destroy INFO. */ + goto fail; + info->lens = &string_lens; info->own_lens = 0; @@ -646,15 +626,15 @@ parse_string(char **str, struct arg_type_info **retp, int *ownp) } static int -build_printf_pack(struct param **packp, size_t param_num) +build_printf_pack(struct locus *loc, struct param **packp, size_t param_num) { if (packp == NULL) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "'format' type in unexpected context"); return -1; } if (*packp != NULL) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "only one 'format' type per function supported"); return -1; } @@ -666,6 +646,7 @@ build_printf_pack(struct param **packp, size_t param_num) struct expr_node *node = malloc(sizeof(*node)); if (node == NULL) { free(*packp); + *packp = NULL; return -1; } @@ -683,39 +664,21 @@ try_parse_kwd(char **str, const char *kwd) { size_t len = strlen(kwd); if (strncmp(*str, kwd, len) == 0 - && !isalnum((*str)[len])) { + && !isalnum(CTYPE_CONV((*str)[len])) + && (*str)[len] != '_') { (*str) += len; return 0; } return -1; } -/* Make a copy of INFO and set the *OWN bit if it's not already - * owned. */ -static int -unshare_type_info(struct arg_type_info **infop, int *ownp) -{ - if (*ownp) - return 0; - - struct arg_type_info *ninfo = malloc(sizeof(*ninfo)); - if (ninfo == NULL) { - report_error(filename, line_no, - "malloc: %s", strerror(errno)); - return -1; - } - *ninfo = **infop; - *infop = ninfo; - *ownp = 1; - return 0; -} - -/* XXX extra_param and param_num are a kludge to get in +/* XXX EXTRA_PARAM and PARAM_NUM are a kludge to get in * backward-compatible support for "format" parameter type. The * latter is only valid if the former is non-NULL, which is only in * top-level context. */ static int -parse_alias(char **str, struct arg_type_info **retp, int *ownp, +parse_alias(struct protolib *plib, struct locus *loc, + char **str, struct arg_type_info **retp, int *ownp, struct param **extra_param, size_t param_num) { /* For backward compatibility, we need to support things like @@ -725,7 +688,7 @@ parse_alias(char **str, struct arg_type_info **retp, int *ownp, * "string" is syntax. */ if (strncmp(*str, "string", 6) == 0) { (*str) += 6; - return parse_string(str, retp, ownp); + return parse_string(plib, loc, str, retp, ownp); } else if (try_parse_kwd(str, "format") >= 0 && extra_param != NULL) { @@ -733,14 +696,14 @@ parse_alias(char **str, struct arg_type_info **retp, int *ownp, * "string", but it smuggles to the parameter list of * a function a "printf" argument pack with this * parameter as argument. */ - if (parse_string(str, retp, ownp) < 0) + if (parse_string(plib, loc, str, retp, ownp) < 0) return -1; - return build_printf_pack(extra_param, param_num); + return build_printf_pack(loc, extra_param, param_num); } else if (try_parse_kwd(str, "enum") >=0) { - return parse_enum(str, retp, ownp); + return parse_enum(plib, loc, str, retp, ownp); } else { *retp = NULL; @@ -750,24 +713,26 @@ parse_alias(char **str, struct arg_type_info **retp, int *ownp, /* Syntax: array ( type, N|argN ) */ static int -parse_array(char **str, struct arg_type_info *info) +parse_array(struct protolib *plib, struct locus *loc, + char **str, struct arg_type_info *info) { eat_spaces(str); - if (parse_char(str, '(') < 0) + if (parse_char(loc, str, '(') < 0) return -1; eat_spaces(str); int own; - struct arg_type_info *elt_info = parse_lens(str, NULL, 0, &own, NULL); + struct arg_type_info *elt_info + = parse_lens(plib, loc, str, NULL, 0, &own, NULL); if (elt_info == NULL) return -1; eat_spaces(str); - parse_char(str, ','); + parse_char(loc, str, ','); eat_spaces(str); int own_length; - struct expr_node *length = parse_argnum(str, &own_length, 0); + struct expr_node *length = parse_argnum(loc, str, &own_length, 0); if (length == NULL) { if (own) { type_destroy(elt_info); @@ -779,7 +744,7 @@ parse_array(char **str, struct arg_type_info *info) type_init_array(info, elt_info, own, length, own_length); eat_spaces(str); - parse_char(str, ')'); + parse_char(loc, str, ')'); return 0; } @@ -788,19 +753,20 @@ parse_array(char **str, struct arg_type_info *info) * enum<type> (keyname[=value],keyname[=value],... ) */ static int -parse_enum(char **str, struct arg_type_info **retp, int *ownp) +parse_enum(struct protolib *plib, struct locus *loc, char **str, + struct arg_type_info **retp, int *ownp) { /* Optional type argument. */ eat_spaces(str); if (**str == '[') { - parse_char(str, '['); + parse_char(loc, str, '['); eat_spaces(str); - *retp = parse_nonpointer_type(str, NULL, 0, ownp, 0); + *retp = parse_nonpointer_type(plib, loc, str, NULL, 0, ownp, 0); if (*retp == NULL) return -1; if (!type_is_integral((*retp)->type)) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "integral type required as enum argument"); fail: if (*ownp) { @@ -813,7 +779,7 @@ parse_enum(char **str, struct arg_type_info **retp, int *ownp) } eat_spaces(str); - if (parse_char(str, ']') < 0) + if (parse_char(loc, str, ']') < 0) goto fail; } else { @@ -822,16 +788,16 @@ parse_enum(char **str, struct arg_type_info **retp, int *ownp) } /* We'll need to set the lens, so unshare. */ - if (unshare_type_info(retp, ownp) < 0) + if (unshare_type_info(loc, retp, ownp) < 0) goto fail; eat_spaces(str); - if (parse_char(str, '(') < 0) + if (parse_char(loc, str, '(') < 0) goto fail; struct enum_lens *lens = malloc(sizeof(*lens)); if (lens == NULL) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "malloc enum lens: %s", strerror(errno)); return -1; } @@ -844,7 +810,7 @@ parse_enum(char **str, struct arg_type_info **retp, int *ownp) while (1) { eat_spaces(str); if (**str == 0 || **str == ')') { - parse_char(str, ')'); + parse_char(loc, str, ')'); return 0; } @@ -852,10 +818,10 @@ parse_enum(char **str, struct arg_type_info **retp, int *ownp) * syntax, where the enumeration can end in pending * comma? */ if (lens_enum_size(lens) > 0) - parse_char(str, ','); + parse_char(loc, str, ','); eat_spaces(str); - char *key = parse_ident(str); + char *key = parse_ident(loc, str); if (key == NULL) { err: free(key); @@ -865,7 +831,7 @@ parse_enum(char **str, struct arg_type_info **retp, int *ownp) if (**str == '=') { ++*str; eat_spaces(str); - if (parse_int(str, &last_val) < 0) + if (parse_int(loc, str, &last_val) < 0) goto err; } @@ -885,24 +851,25 @@ parse_enum(char **str, struct arg_type_info **retp, int *ownp) } static struct arg_type_info * -parse_nonpointer_type(char **str, struct param **extra_param, size_t param_num, - int *ownp, struct typedef_node_t *in_typedef) +parse_nonpointer_type(struct protolib *plib, struct locus *loc, + char **str, struct param **extra_param, size_t param_num, + int *ownp, int *forwardp) { + const char *orig_str = *str; enum arg_type type; if (parse_arg_type(str, &type) < 0) { - struct arg_type_info *simple; - if (parse_alias(str, &simple, ownp, extra_param, param_num) < 0) + struct arg_type_info *type; + if (parse_alias(plib, loc, str, &type, + ownp, extra_param, param_num) < 0) return NULL; - if (simple == NULL) - simple = parse_typedef_name(str); - if (simple != NULL) { - *ownp = 0; - return simple; - } + else if (type != NULL) + return type; - report_error(filename, line_no, - "unknown type around '%s'", *str); - return NULL; + *ownp = 0; + if ((type = parse_typedef_name(plib, str)) == NULL) + report_error(loc->filename, loc->line_no, + "unknown type around '%s'", orig_str); + return type; } /* For some types that's all we need. */ @@ -933,21 +900,21 @@ parse_nonpointer_type(char **str, struct param **extra_param, size_t param_num, struct arg_type_info *info = malloc(sizeof(*info)); if (info == NULL) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "malloc: %s", strerror(errno)); return NULL; } *ownp = 1; if (type == ARGTYPE_ARRAY) { - if (parse_array(str, info) < 0) { + if (parse_array(plib, loc, str, info) < 0) { fail: free(info); return NULL; } } else { assert(type == ARGTYPE_STRUCT); - if (parse_struct(str, info, in_typedef) < 0) + if (parse_struct(plib, loc, str, info, forwardp) < 0) goto fail; } @@ -981,12 +948,13 @@ name2lens(char **str, int *own_lensp) } static struct arg_type_info * -parse_type(char **str, struct param **extra_param, size_t param_num, int *ownp, - struct typedef_node_t *in_typedef) +parse_type(struct protolib *plib, struct locus *loc, char **str, + struct param **extra_param, size_t param_num, + int *ownp, int *forwardp) { struct arg_type_info *info - = parse_nonpointer_type(str, extra_param, - param_num, ownp, in_typedef); + = parse_nonpointer_type(plib, loc, str, extra_param, + param_num, ownp, forwardp); if (info == NULL) return NULL; @@ -999,7 +967,7 @@ parse_type(char **str, struct param **extra_param, size_t param_num, int *ownp, type_destroy(info); free(info); } - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "malloc: %s", strerror(errno)); return NULL; } @@ -1014,8 +982,9 @@ parse_type(char **str, struct param **extra_param, size_t param_num, int *ownp, } static struct arg_type_info * -parse_lens(char **str, struct param **extra_param, size_t param_num, int *ownp, - struct typedef_node_t *in_typedef) +parse_lens(struct protolib *plib, struct locus *loc, + char **str, struct param **extra_param, + size_t param_num, int *ownp, int *forwardp) { int own_lens; struct lens *lens = name2lens(str, &own_lens); @@ -1030,8 +999,8 @@ parse_lens(char **str, struct param **extra_param, size_t param_num, int *ownp, has_args = 0; info = type_get_simple(ARGTYPE_INT); *ownp = 0; - } else if (parse_char(str, '(') < 0) { - report_error(filename, line_no, + } else if (parse_char(loc, str, '(') < 0) { + report_error(loc->filename, loc->line_no, "expected type argument after the lens"); return NULL; } @@ -1039,8 +1008,8 @@ parse_lens(char **str, struct param **extra_param, size_t param_num, int *ownp, if (has_args) { eat_spaces(str); - info = parse_type(str, extra_param, param_num, ownp, - in_typedef); + info = parse_type(plib, loc, str, extra_param, param_num, + ownp, forwardp); if (info == NULL) { fail: if (own_lens && lens != NULL) @@ -1051,12 +1020,12 @@ parse_lens(char **str, struct param **extra_param, size_t param_num, int *ownp, if (lens != NULL && has_args) { eat_spaces(str); - parse_char(str, ')'); + parse_char(loc, str, ')'); } /* We can't modify shared types. Make a copy if we have a * lens. */ - if (lens != NULL && unshare_type_info(&info, ownp) < 0) + if (lens != NULL && unshare_type_info(loc, &info, ownp) < 0) goto fail; if (lens != NULL) { @@ -1068,23 +1037,6 @@ parse_lens(char **str, struct param **extra_param, size_t param_num, int *ownp, } static int -add_param(Function *fun, size_t *allocdp) -{ - size_t allocd = *allocdp; - /* XXX +1 is for the extra_param handling hack. */ - if ((fun->num_params + 1) >= allocd) { - allocd = allocd > 0 ? 2 * allocd : 8; - void *na = realloc(fun->params, sizeof(*fun->params) * allocd); - if (na == NULL) - return -1; - - fun->params = na; - *allocdp = allocd; - } - return 0; -} - -static int param_is_void(struct param *param) { return param->flavor == PARAM_FLAVOR_TYPE @@ -1094,64 +1046,83 @@ param_is_void(struct param *param) static struct arg_type_info * get_hidden_int(void) { - char *str = strdup("hide(int)"); - char *ptr = str; - assert(str != NULL); - int own; - struct arg_type_info *info = parse_lens(&ptr, NULL, 0, &own, NULL); - assert(info != NULL); - free(str); - return info; + static struct arg_type_info info, *pinfo = NULL; + if (pinfo != NULL) + return pinfo; + + info = *type_get_simple(ARGTYPE_INT); + info.lens = &blind_lens; + pinfo = &info; + + return pinfo; +} + +static enum callback_status +void_to_hidden_int(struct prototype *proto, struct param *param, void *data) +{ + struct locus *loc = data; + if (param_is_void(param)) { + report_warning(loc->filename, loc->line_no, + "void parameter assumed to be 'hide(int)'"); + + static struct arg_type_info *type = NULL; + if (type == NULL) + type = get_hidden_int(); + param_destroy(param); + param_init_type(param, type, 0); + } + return CBS_CONT; } -static Function * -process_line(char *buf) { +static int +process_line(struct protolib *plib, struct locus *loc, char *buf) +{ char *str = buf; - char *tmp; - line_no++; - debug(3, "Reading line %d of `%s'", line_no, filename); + debug(3, "Reading line %d of `%s'", loc->line_no, loc->filename); eat_spaces(&str); /* A comment or empty line. */ - if (*str == ';' || *str == 0 || *str == '\n') - return NULL; + if (*str == ';' || *str == 0 || *str == '\n' || *str == '#') + return 0; if (strncmp(str, "typedef", 7) == 0) { - parse_typedef(&str); - return NULL; + parse_typedef(plib, loc, &str); + return 0; } - Function *fun = calloc(1, sizeof(*fun)); - if (fun == NULL) { - report_error(filename, line_no, - "alloc function: %s", strerror(errno)); - return NULL; - } + struct prototype fun; + prototype_init(&fun); - fun->return_info = parse_lens(&str, NULL, 0, - &fun->own_return_info, NULL); - if (fun->return_info == NULL) { + struct param *extra_param = NULL; + char *proto_name = NULL; + int own; + fun.return_info = parse_lens(plib, loc, &str, NULL, 0, &own, NULL); + if (fun.return_info == NULL) { err: - debug(3, " Skipping line %d", line_no); - destroy_fun(fun); - return NULL; + debug(3, " Skipping line %d", loc->line_no); + + if (extra_param != NULL) { + param_destroy(extra_param); + free(extra_param); + } + + prototype_destroy(&fun); + free(proto_name); + return -1; } - debug(4, " return_type = %d", fun->return_info->type); + fun.own_return_info = own; + debug(4, " return_type = %d", fun.return_info->type); eat_spaces(&str); - tmp = start_of_arg_sig(str); - if (tmp == NULL) { - report_error(filename, line_no, "syntax error"); + proto_name = parse_ident(loc, &str); + if (proto_name == NULL) goto err; - } - *tmp = '\0'; - fun->name = strdup(str); - str = tmp + 1; - debug(3, " name = %s", fun->name); - size_t allocd = 0; - struct param *extra_param = NULL; + eat_spaces(&str); + if (parse_char(loc, &str, '(') < 0) + goto err; + debug(3, " name = %s", proto_name); int have_stop = 0; @@ -1162,33 +1133,35 @@ process_line(char *buf) { if (str[0] == '+') { if (have_stop == 0) { - if (add_param(fun, &allocd) < 0) - goto add_err; - param_init_stop - (&fun->params[fun->num_params++]); + struct param param; + param_init_stop(¶m); + if (prototype_push_param(&fun, ¶m) < 0) { + oom: + report_error(loc->filename, + loc->line_no, + "%s", strerror(errno)); + goto err; + } have_stop = 1; } str++; } - if (add_param(fun, &allocd) < 0) { - add_err: - report_error(filename, line_no, "(re)alloc params: %s", - strerror(errno)); - goto err; - } - int own; + size_t param_num = prototype_num_params(&fun) - have_stop; struct arg_type_info *type - = parse_lens(&str, &extra_param, - fun->num_params - have_stop, &own, NULL); + = parse_lens(plib, loc, &str, &extra_param, + param_num, &own, NULL); if (type == NULL) { - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "unknown argument type"); goto err; } - param_init_type(&fun->params[fun->num_params++], type, own); + struct param param; + param_init_type(¶m, type, own); + if (prototype_push_param(&fun, ¶m) < 0) + goto oom; eat_spaces(&str); if (*str == ',') { @@ -1199,7 +1172,7 @@ process_line(char *buf) { } else { if (str[strlen(str) - 1] == '\n') str[strlen(str) - 1] = '\0'; - report_error(filename, line_no, + report_error(loc->filename, loc->line_no, "syntax error around \"%s\"", str); goto err; } @@ -1216,84 +1189,49 @@ process_line(char *buf) { * latter is conservative, we can drop the argument * altogether, instead of fetching and then not showing it, * without breaking any observable behavior. */ - if (fun->num_params == 1 && param_is_void(&fun->params[0])) { + if (prototype_num_params(&fun) == 1 + && param_is_void(prototype_get_nth_param(&fun, 0))) { if (0) /* Don't show this warning. Pre-0.7.0 * ltrace.conf often used this idiom. This * should be postponed until much later, when * extant uses are likely gone. */ - report_warning(filename, line_no, + report_warning(loc->filename, loc->line_no, "sole void parameter ignored"); - param_destroy(&fun->params[0]); - fun->num_params = 0; + prototype_destroy_nth_param(&fun, 0); } else { - size_t i; - for (i = 0; i < fun->num_params; ++i) { - if (param_is_void(&fun->params[i])) { - report_warning - (filename, line_no, - "void parameter assumed to be " - "'hide(int)'"); - - static struct arg_type_info *type = NULL; - if (type == NULL) - type = get_hidden_int(); - param_destroy(&fun->params[i]); - param_init_type(&fun->params[i], type, 0); - } - } + prototype_each_param(&fun, NULL, void_to_hidden_int, loc); } if (extra_param != NULL) { - assert(fun->num_params < allocd); - memcpy(&fun->params[fun->num_params++], extra_param, - sizeof(*extra_param)); + prototype_push_param(&fun, extra_param); free(extra_param); + extra_param = NULL; } - return fun; -} - -void -init_global_config(void) -{ - struct arg_type_info *info = malloc(2 * sizeof(*info)); - if (info == NULL) - error(1, errno, "malloc in init_global_config"); - - memset(info, 0, 2 * sizeof(*info)); - info[0].type = ARGTYPE_POINTER; - info[0].u.ptr_info.info = &info[1]; - info[1].type = ARGTYPE_VOID; + if (protolib_add_prototype(plib, proto_name, 1, &fun) < 0) { + report_error(loc->filename, loc->line_no, + "couldn't add prototype: %s", + strerror(errno)); + goto err; + } - insert_typedef(new_typedef(strdup("addr"), info, 0)); - insert_typedef(new_typedef(strdup("file"), info, 1)); + return 0; } -void -read_config_file(char *file) { - FILE *stream; - char buf[1024]; - - filename = file; - stream = fopen(filename, "r"); - if (!stream) { - return; +int +read_config_file(FILE *stream, const char *path, struct protolib *plib) +{ + debug(DEBUG_FUNCTION, "Reading config file `%s'...", path); + + struct locus loc = { path, 0 }; + char *line = NULL; + size_t len = 0; + while (getline(&line, &len, stream) >= 0) { + loc.line_no++; + process_line(plib, &loc, line); } - debug(1, "Reading config file `%s'...", filename); - - line_no = 0; - while (fgets(buf, 1024, stream)) { - Function *tmp; - - tmp = process_line(buf); - - if (tmp) { - debug(2, "New function: `%s'", tmp->name); - tmp->next = list_of_functions; - list_of_functions = tmp; - } - } - fclose(stream); + free(line); + return 0; } diff --git a/read_config_file.h b/read_config_file.h index 7c60253..5a72a05 100644 --- a/read_config_file.h +++ b/read_config_file.h @@ -18,6 +18,11 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ +#ifndef READ_CONFIG_FILE_H +#define READ_CONFIG_FILE_H -extern void read_config_file(char *); -extern void init_global_config(void); +#include "forward.h" + +int read_config_file(FILE *stream, const char *name, struct protolib *plib); + +#endif /* READ_CONFIG_FILE_H */ @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2003,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -21,87 +22,180 @@ #include "config.h" +#include <sys/time.h> #include <stdio.h> #include <stdlib.h> -#include <sys/time.h> +#include <string.h> + +#include "summary.h" +#include "dict.h" +#include "library.h" +#include "options.h" + +struct entry_st { + const char *name; + unsigned count; + struct timeval tv; +}; -#include "common.h" +struct fill_struct_data { + struct vect entries; + unsigned tot_count; + unsigned long tot_usecs; +}; -static int num_entries = 0; -static struct entry_st { - char *name; +struct opt_c_struct { int count; struct timeval tv; -} *entries = NULL; +}; -static int tot_count = 0; -static unsigned long int tot_usecs = 0; +static struct dict *dict_opt_c; -static void fill_struct(void *key, void *value, void *data) +struct timedelta +calc_time_spent(struct timeval start) { - struct opt_c_struct *st = (struct opt_c_struct *)value; + struct timeval tv; + gettimeofday(&tv, NULL); - entries = realloc(entries, (num_entries + 1) * sizeof(struct entry_st)); - if (!entries) { - perror("realloc()"); - exit(1); + struct timeval diff; + diff.tv_sec = tv.tv_sec - start.tv_sec; + if (tv.tv_usec >= start.tv_usec) { + diff.tv_usec = tv.tv_usec - start.tv_usec; + } else { + diff.tv_sec--; + diff.tv_usec = 1000000 + tv.tv_usec - start.tv_usec; } - entries[num_entries].name = (char *)key; - entries[num_entries].count = st->count; - entries[num_entries].tv = st->tv; - tot_count += st->count; - tot_usecs += 1000000 * st->tv.tv_sec; - tot_usecs += st->tv.tv_usec; + struct timedelta ret = { diff }; + return ret; +} - num_entries++; +static enum callback_status +fill_struct(const char **namep, struct opt_c_struct *st, void *u) +{ + struct fill_struct_data *data = u; + struct entry_st entry = { *namep, st->count, st->tv }; + if (VECT_PUSHBACK(&data->entries, &entry) < 0) + return CBS_STOP; + + data->tot_count += st->count; + data->tot_usecs += 1000000 * st->tv.tv_sec; + data->tot_usecs += st->tv.tv_usec; + return CBS_CONT; } -static int compar(const void *a, const void *b) +static int +compar(const struct entry_st *en1, const struct entry_st *en2) { - struct entry_st *en1, *en2; + if (en2->tv.tv_sec - en1->tv.tv_sec) + return en2->tv.tv_sec - en1->tv.tv_sec; + else + return en2->tv.tv_usec - en1->tv.tv_usec; +} + +static enum callback_status +dump_one(struct entry_st *entry, void *u) +{ + struct fill_struct_data *data = u; + unsigned long long int c; + unsigned long long int p; + c = 1000000 * (int)entry->tv.tv_sec + + (int)entry->tv.tv_usec; + p = 100000 * c / data->tot_usecs + 5; + fprintf(options.output, "%3lu.%02lu %4d.%06d %11lu %9d %s\n", + (unsigned long int)(p / 1000), + (unsigned long int)((p / 10) % 100), + (int)entry->tv.tv_sec, (int)entry->tv.tv_usec, + (unsigned long int)(c / entry->count), + entry->count, +#ifdef USE_DEMANGLE + options.demangle ? my_demangle(entry->name) : +#endif + entry->name); - en1 = (struct entry_st *)a; - en2 = (struct entry_st *)b; + return CBS_CONT; +} - if (en2->tv.tv_sec - en1->tv.tv_sec) { - return (en2->tv.tv_sec - en1->tv.tv_sec); - } else { - return (en2->tv.tv_usec - en1->tv.tv_usec); +void +show_summary(void) +{ + struct fill_struct_data cdata = {}; + VECT_INIT(&cdata.entries, struct entry_st); + + if (dict_opt_c != NULL) { + DICT_EACH(dict_opt_c, const char *, struct opt_c_struct, NULL, + fill_struct, &cdata); + + VECT_QSORT(&cdata.entries, struct entry_st, &compar); } + + fprintf(options.output, + "%% time seconds usecs/call calls function\n"); + fprintf(options.output, + "------ ----------- ----------- --------- --------------------\n"); + + VECT_EACH(&cdata.entries, struct entry_st, NULL, dump_one, &cdata); + + fprintf(options.output, + "------ ----------- ----------- --------- --------------------\n"); + fprintf(options.output, "100.00 %4lu.%06lu %9d total\n", + cdata.tot_usecs / 1000000, + cdata.tot_usecs % 1000000, cdata.tot_count); + + vect_destroy(&cdata.entries, NULL, NULL); } -void show_summary(void) +static void +free_stringp_cb(const char **stringp, void *data) { - int i; - - num_entries = 0; - entries = NULL; - - dict_apply_to_all(dict_opt_c, fill_struct, NULL); - - qsort(entries, num_entries, sizeof(*entries), compar); - - fprintf(options.output, "%% time seconds usecs/call calls function\n"); - fprintf(options.output, "------ ----------- ----------- --------- --------------------\n"); - for (i = 0; i < num_entries; i++) { - unsigned long long int c; - unsigned long long int p; - c = 1000000 * (int)entries[i].tv.tv_sec + - (int)entries[i].tv.tv_usec; - p = 100000 * c / tot_usecs + 5; - fprintf(options.output, "%3lu.%02lu %4d.%06d %11lu %9d %s\n", - (unsigned long int)(p / 1000), - (unsigned long int)((p / 10) % 100), - (int)entries[i].tv.tv_sec, (int)entries[i].tv.tv_usec, - (unsigned long int)(c / entries[i].count), - entries[i].count, -#ifdef USE_DEMANGLE - options.demangle ? my_demangle(entries[i].name) : -#endif - entries[i].name); + free((char *)*stringp); +} + +void +summary_account_call(struct library_symbol *libsym, struct timedelta spent) +{ + assert(options.summary); + + if (dict_opt_c == NULL) { + dict_opt_c = malloc(sizeof(*dict_opt_c)); + if (dict_opt_c == NULL) { + oom: + fprintf(stderr, + "Can't allocate memory for " + "keeping track of -c.\n"); + free(dict_opt_c); + options.summary = 0; + return; + } + DICT_INIT(dict_opt_c, char *, struct opt_c_struct, + dict_hash_string, dict_eq_string, NULL); + } + + struct opt_c_struct *st = DICT_FIND_REF(dict_opt_c, &libsym->name, + struct opt_c_struct); + if (st == NULL) { + const char *na = strdup(libsym->name); + struct opt_c_struct new_st = {.count = 0, .tv = {0, 0}}; + if (na == NULL + || DICT_INSERT(dict_opt_c, &na, &new_st) < 0) { + free((char *) na); + DICT_DESTROY(dict_opt_c, const char *, + struct opt_c_struct, + free_stringp_cb, NULL, NULL); + goto oom; + } + st = DICT_FIND_REF(dict_opt_c, &libsym->name, + struct opt_c_struct); + assert(st != NULL); + } + + if (st->tv.tv_usec + spent.tm.tv_usec > 1000000) { + st->tv.tv_usec += spent.tm.tv_usec - 1000000; + st->tv.tv_sec++; + } else { + st->tv.tv_usec += spent.tm.tv_usec; } - fprintf(options.output, "------ ----------- ----------- --------- --------------------\n"); - fprintf(options.output, "100.00 %4lu.%06lu %9d total\n", tot_usecs / 1000000, - tot_usecs % 1000000, tot_count); + st->count++; + st->tv.tv_sec += spent.tm.tv_sec; + return; } diff --git a/summary.h b/summary.h new file mode 100644 index 0000000..ea9747a --- /dev/null +++ b/summary.h @@ -0,0 +1,35 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef SUMMARY_H +#define SUMMARY_H + +#include "forward.h" + +struct timedelta { + struct timeval tm; +}; + +struct timedelta calc_time_spent(struct timeval start); +void summary_account_call(struct library_symbol *libsym, + struct timedelta spent); +void show_summary(void); + +#endif /* SUMMARY_H */ diff --git a/sysdeps/linux-gnu/Makefile.am b/sysdeps/linux-gnu/Makefile.am index bfa67d6..ec26162 100644 --- a/sysdeps/linux-gnu/Makefile.am +++ b/sysdeps/linux-gnu/Makefile.am @@ -1,4 +1,5 @@ # This file is part of ltrace. +# Copyright (C) 2014 Petr Machata, Red Hat, Inc. # Copyright (C) 2010,2012 Marc Kleine-Budde, Pengutronix # # This program is free software; you can redistribute it and/or @@ -16,7 +17,8 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA -DIST_SUBDIRS = alpha arm cris ia64 m68k mipsel ppc s390 sparc x86 +DIST_SUBDIRS = aarch64 alpha arm cris ia64 m68k metag mips ppc s390 \ + sparc x86 SUBDIRS = \ $(HOST_CPU) @@ -24,11 +26,7 @@ SUBDIRS = \ noinst_LTLIBRARIES = \ ../libos.la -___libos_la_SOURCES = \ - events.c \ - trace.c \ - proc.c \ - breakpoint.c +___libos_la_SOURCES = events.c trace.c proc.c breakpoint.c hooks.c ___libos_la_LIBADD = \ libcpu.la diff --git a/sysdeps/linux-gnu/aarch64/Makefile.am b/sysdeps/linux-gnu/aarch64/Makefile.am new file mode 100644 index 0000000..0af4e6e --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/Makefile.am @@ -0,0 +1,25 @@ +# This file is part of ltrace. +# Copyright (C) 2014 Petr Machata, Red Hat, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +noinst_LTLIBRARIES = ../libcpu.la + +___libcpu_la_SOURCES = fetch.c plt.c regs.c trace.c + +noinst_HEADERS = arch.h ptrace.h signalent.h syscallent.h + +MAINTAINERCLEANFILES = Makefile.in diff --git a/sysdeps/linux-gnu/aarch64/arch.h b/sysdeps/linux-gnu/aarch64/arch.h new file mode 100644 index 0000000..4137613 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/arch.h @@ -0,0 +1,37 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ +#ifndef LTRACE_AARCH64_ARCH_H +#define LTRACE_AARCH64_ARCH_H + +/* | 31 21 | 20 5 | 4 0 | * + * | 1 1 0 1 0 1 0 0 0 0 1 | imm16 | 0 0 0 0 0 | */ +#define BREAKPOINT_VALUE { 0xd4, 0x20, 0, 0 } +#define BREAKPOINT_LENGTH 4 +#define DECR_PC_AFTER_BREAK 0 + +#define LT_ELFCLASS ELFCLASS64 +#define LT_ELF_MACHINE EM_AARCH64 + +#define ARCH_HAVE_FETCH_ARG +#define ARCH_ENDIAN_BIG +#define ARCH_HAVE_SIZEOF +#define ARCH_HAVE_ALIGNOF + +#endif /* LTRACE_AARCH64_ARCH_H */ diff --git a/sysdeps/linux-gnu/aarch64/fetch.c b/sysdeps/linux-gnu/aarch64/fetch.c new file mode 100644 index 0000000..2744df0 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/fetch.c @@ -0,0 +1,366 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> +#include <asm/ptrace.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> + +#include "fetch.h" +#include "proc.h" +#include "type.h" +#include "value.h" + +int aarch64_read_gregs(struct process *proc, struct user_pt_regs *regs); +int aarch64_read_fregs(struct process *proc, struct user_fpsimd_state *regs); + + +struct fetch_context +{ + struct user_pt_regs gregs; + struct user_fpsimd_state fpregs; + arch_addr_t nsaa; + unsigned ngrn; + unsigned nsrn; + arch_addr_t x8; +}; + +static int +context_init(struct fetch_context *context, struct process *proc) +{ + if (aarch64_read_gregs(proc, &context->gregs) < 0 + || aarch64_read_fregs(proc, &context->fpregs) < 0) + return -1; + + context->ngrn = 0; + context->nsrn = 0; + /* XXX double cast */ + context->nsaa = (arch_addr_t) (uintptr_t) context->gregs.sp; + context->x8 = 0; + + return 0; +} + +struct fetch_context * +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) +{ + struct fetch_context *ret = malloc(sizeof(*ret)); + if (ret == NULL) + return NULL; + return memcpy(ret, context, sizeof(*ret)); +} + +static void +fetch_next_gpr(struct fetch_context *context, unsigned char *buf) +{ + uint64_t u = context->gregs.regs[context->ngrn++]; + memcpy(buf, &u, 8); +} + +static int +fetch_gpr(struct fetch_context *context, struct value *value, size_t sz) +{ + if (sz < 8) + sz = 8; + + unsigned char *buf = value_reserve(value, sz); + if (buf == NULL) + return -1; + + size_t i; + for (i = 0; i < sz; i += 8) + fetch_next_gpr(context, buf + i); + + return 0; +} + +static void +fetch_next_sse(struct fetch_context *context, unsigned char *buf, size_t sz) +{ + __int128 u = context->fpregs.vregs[context->nsrn++]; + memcpy(buf, &u, sz); +} + +static int +fetch_sse(struct fetch_context *context, struct value *value, size_t sz) +{ + unsigned char *buf = value_reserve(value, sz); + if (buf == NULL) + return -1; + + fetch_next_sse(context, buf, sz); + return 0; +} + +static int +fetch_hfa(struct fetch_context *context, + struct value *value, struct arg_type_info *hfa_t, size_t count) +{ + size_t sz = type_sizeof(value->inferior, hfa_t); + unsigned char *buf = value_reserve(value, sz * count); + if (buf == NULL) + return -1; + + size_t i; + for (i = 0; i < count; ++i) { + fetch_next_sse(context, buf, sz); + buf += sz; + } + return 0; +} + +static int +fetch_stack(struct fetch_context *context, struct value *value, + size_t align, size_t sz) +{ + if (align < 8) + align = 8; + size_t amount = ((sz + align - 1) / align) * align; + + /* XXX double casts */ + uintptr_t sp = (uintptr_t) context->nsaa; + sp = ((sp + align - 1) / align) * align; + + value_in_inferior(value, (arch_addr_t) sp); + + sp += amount; + context->nsaa = (arch_addr_t) sp; + + return 0; +} + +enum convert_method { + CVT_ERR = -1, + CVT_NOP = 0, + CVT_BYREF, +}; + +enum fetch_method { + FETCH_NOP, + FETCH_STACK, + FETCH_GPR, + FETCH_SSE, + FETCH_HFA, +}; + +struct fetch_script { + enum convert_method c; + enum fetch_method f; + size_t sz; + struct arg_type_info *hfa_t; + size_t count; +}; + +static struct fetch_script +pass_arg(struct fetch_context const *context, + struct process *proc, struct arg_type_info *info) +{ + enum fetch_method cvt = CVT_NOP; + + size_t sz = type_sizeof(proc, info); + if (sz == (size_t) -1) + return (struct fetch_script) { CVT_ERR, FETCH_NOP, sz }; + + switch (info->type) { + case ARGTYPE_VOID: + return (struct fetch_script) { cvt, FETCH_NOP, sz }; + + case ARGTYPE_STRUCT: + case ARGTYPE_ARRAY:; + size_t count; + struct arg_type_info *hfa_t = type_get_hfa_type(info, &count); + if (hfa_t != NULL && count <= 4) { + if (context->nsrn + count <= 8) + return (struct fetch_script) + { cvt, FETCH_HFA, sz, hfa_t, count }; + return (struct fetch_script) + { cvt, FETCH_STACK, sz, hfa_t, count }; + } + + if (sz <= 16) { + size_t count = sz / 8; + if (context->ngrn + count <= 8) + return (struct fetch_script) + { cvt, FETCH_GPR, sz }; + } + + cvt = CVT_BYREF; + sz = 8; + /* Fall through. */ + + case ARGTYPE_POINTER: + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + case ARGTYPE_CHAR: + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + if (context->ngrn < 8 && sz <= 8) + return (struct fetch_script) { cvt, FETCH_GPR, sz }; + /* We don't support types wider than 8 bytes as of + * now. */ + assert(sz <= 8); + + return (struct fetch_script) { cvt, FETCH_STACK, sz }; + + case ARGTYPE_FLOAT: + case ARGTYPE_DOUBLE: + if (context->nsrn < 8) { + /* ltrace doesn't support float128. */ + assert(sz <= 8); + return (struct fetch_script) { cvt, FETCH_SSE, sz }; + } + + return (struct fetch_script) { cvt, FETCH_STACK, sz }; + } + + assert(! "Failed to allocate argument."); + abort(); +} + +static int +convert_arg(struct value *value, struct fetch_script how) +{ + switch (how.c) { + case CVT_NOP: + return 0; + case CVT_BYREF: + return value_pass_by_reference(value); + case CVT_ERR: + return -1; + } + + assert(! "Don't know how to convert argument."); + abort(); +} + +static int +fetch_arg(struct fetch_context *context, + struct process *proc, struct arg_type_info *info, + struct value *value, struct fetch_script how) +{ + if (convert_arg(value, how) < 0) + return -1; + + switch (how.f) { + case FETCH_NOP: + return 0; + + case FETCH_STACK: + if (how.hfa_t != NULL && how.count != 0 && how.count <= 8) + context->nsrn = 8; + return fetch_stack(context, value, + type_alignof(proc, info), how.sz); + + case FETCH_GPR: + return fetch_gpr(context, value, how.sz); + + case FETCH_SSE: + return fetch_sse(context, value, how.sz); + + case FETCH_HFA: + return fetch_hfa(context, value, how.hfa_t, how.count); + } + + assert(! "Don't know how to fetch argument."); + abort(); +} + +struct fetch_context * +arch_fetch_arg_init(enum tof type, struct process *proc, + struct arg_type_info *ret_info) +{ + struct fetch_context *context = malloc(sizeof *context); + if (context == NULL || context_init(context, proc) < 0) { + fail: + free(context); + return NULL; + } + + /* There's a provision in ARMv8 parameter passing convention + * for returning types that, if passed as first argument to a + * function, would be passed on stack. For those types, x8 + * contains an address where the return argument should be + * placed. The callee doesn't need to preserve the value of + * x8, so we need to fetch it now. + * + * To my knowledge, there are currently no types where this + * holds, but the code is here, utterly untested. */ + + struct fetch_script how = pass_arg(context, proc, ret_info); + if (how.c == CVT_ERR) + goto fail; + if (how.c == CVT_NOP && how.f == FETCH_STACK) { + /* XXX double cast. */ + context->x8 = (arch_addr_t) (uintptr_t) context->gregs.regs[8]; + /* See the comment above about the assert. */ + assert(! "Unexpected: first argument passed on stack."); + abort(); + } + + return context; +} + +int +arch_fetch_arg_next(struct fetch_context *context, enum tof type, + struct process *proc, struct arg_type_info *info, + struct value *value) +{ + return fetch_arg(context, proc, info, value, + pass_arg(context, proc, info)); +} + +int +arch_fetch_retval(struct fetch_context *context, enum tof type, + struct process *proc, struct arg_type_info *info, + struct value *value) +{ + if (context->x8 != 0) { + value_in_inferior(value, context->x8); + return 0; + } + + if (context_init(context, proc) < 0) + return -1; + + return fetch_arg(context, proc, info, value, + pass_arg(context, proc, info)); +} + +void +arch_fetch_arg_done(struct fetch_context *context) +{ + if (context != NULL) + free(context); +} + +size_t +arch_type_sizeof(struct process *proc, struct arg_type_info *arg) +{ + return (size_t) -2; +} + +size_t +arch_type_alignof(struct process *proc, struct arg_type_info *arg) +{ + return (size_t) -2; +} diff --git a/sysdeps/linux-gnu/aarch64/plt.c b/sysdeps/linux-gnu/aarch64/plt.c new file mode 100644 index 0000000..29dc4c9 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/plt.c @@ -0,0 +1,38 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gelf.h> + +#include "backend.h" +#include "proc.h" +#include "library.h" +#include "ltrace-elf.h" + +arch_addr_t +sym2addr(struct process *proc, struct library_symbol *sym) +{ + return sym->enter_addr; +} + +GElf_Addr +arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) +{ + return lte->plt_addr + 32 + ndx * 16; +} diff --git a/sysdeps/linux-gnu/aarch64/ptrace.h b/sysdeps/linux-gnu/aarch64/ptrace.h new file mode 100644 index 0000000..283c314 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/ptrace.h @@ -0,0 +1,22 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> +#include <asm/ptrace.h> diff --git a/sysdeps/linux-gnu/aarch64/regs.c b/sysdeps/linux-gnu/aarch64/regs.c new file mode 100644 index 0000000..461ec12 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/regs.c @@ -0,0 +1,131 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> +#include <asm/ptrace.h> +#include <linux/uio.h> +#include <assert.h> +#include <stdlib.h> +#include <stdio.h> + +#include "backend.h" +#include "proc.h" + +#define PC_OFF (32 * 4) + +int +aarch64_read_gregs(struct process *proc, struct user_pt_regs *regs) +{ + *regs = (struct user_pt_regs) {}; + struct iovec iovec; + iovec.iov_base = regs; + iovec.iov_len = sizeof *regs; + return ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, &iovec) < 0 + ? -1 : 0; +} + +int +aarch64_write_gregs(struct process *proc, struct user_pt_regs *regs) +{ + struct iovec iovec; + iovec.iov_base = regs; + iovec.iov_len = sizeof *regs; + return ptrace(PTRACE_SETREGSET, proc->pid, NT_PRSTATUS, &iovec) < 0 + ? -1 : 0; +} + +int +aarch64_read_fregs(struct process *proc, struct user_fpsimd_state *regs) +{ + *regs = (struct user_fpsimd_state) {}; + struct iovec iovec; + iovec.iov_base = regs; + iovec.iov_len = sizeof *regs; + return ptrace(PTRACE_GETREGSET, proc->pid, NT_FPREGSET, &iovec) < 0 + ? -1 : 0; +} + +arch_addr_t +get_instruction_pointer(struct process *proc) +{ + struct user_pt_regs regs; + if (aarch64_read_gregs(proc, ®s) < 0) { + fprintf(stderr, "get_instruction_pointer: " + "Couldn't read registers of %d.\n", proc->pid); + return 0; + } + + /* + char buf[128]; + sprintf(buf, "cat /proc/%d/maps", proc->pid); + system(buf); + */ + + /* XXX double cast */ + return (arch_addr_t) (uintptr_t) regs.pc; +} + +void +set_instruction_pointer(struct process *proc, arch_addr_t addr) +{ + struct user_pt_regs regs; + if (aarch64_read_gregs(proc, ®s) < 0) { + fprintf(stderr, "get_instruction_pointer: " + "Couldn't read registers of %d.\n", proc->pid); + return; + } + + /* XXX double cast */ + regs.pc = (uint64_t) (uintptr_t) addr; + + if (aarch64_write_gregs(proc, ®s) < 0) { + fprintf(stderr, "get_instruction_pointer: " + "Couldn't write registers of %d.\n", proc->pid); + return; + } +} + +arch_addr_t +get_stack_pointer(struct process *proc) +{ + struct user_pt_regs regs; + if (aarch64_read_gregs(proc, ®s) < 0) { + fprintf(stderr, "get_stack_pointer: " + "Couldn't read registers of %d.\n", proc->pid); + return 0; + } + + /* XXX double cast */ + return (arch_addr_t) (uintptr_t) regs.sp; +} + +arch_addr_t +get_return_addr(struct process *proc, arch_addr_t stack_pointer) +{ + struct user_pt_regs regs; + if (aarch64_read_gregs(proc, ®s) < 0) { + fprintf(stderr, "get_return_addr: " + "Couldn't read registers of %d.\n", proc->pid); + return 0; + } + + /* XXX double cast */ + return (arch_addr_t) (uintptr_t) regs.regs[30]; +} diff --git a/sysdeps/linux-gnu/aarch64/signalent.h b/sysdeps/linux-gnu/aarch64/signalent.h new file mode 100644 index 0000000..bf56ebc --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/signalent.h @@ -0,0 +1,52 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2006 Ian Wienand + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + "SIG_0", /* 0 */ + "SIGHUP", /* 1 */ + "SIGINT", /* 2 */ + "SIGQUIT", /* 3 */ + "SIGILL", /* 4 */ + "SIGTRAP", /* 5 */ + "SIGABRT", /* 6 */ + "SIGBUS", /* 7 */ + "SIGFPE", /* 8 */ + "SIGKILL", /* 9 */ + "SIGUSR1", /* 10 */ + "SIGSEGV", /* 11 */ + "SIGUSR2", /* 12 */ + "SIGPIPE", /* 13 */ + "SIGALRM", /* 14 */ + "SIGTERM", /* 15 */ + "SIGSTKFLT", /* 16 */ + "SIGCHLD", /* 17 */ + "SIGCONT", /* 18 */ + "SIGSTOP", /* 19 */ + "SIGTSTP", /* 20 */ + "SIGTTIN", /* 21 */ + "SIGTTOU", /* 22 */ + "SIGURG", /* 23 */ + "SIGXCPU", /* 24 */ + "SIGXFSZ", /* 25 */ + "SIGVTALRM", /* 26 */ + "SIGPROF", /* 27 */ + "SIGWINCH", /* 28 */ + "SIGIO", /* 29 */ + "SIGPWR", /* 30 */ + "SIGSYS", /* 31 */ diff --git a/sysdeps/linux-gnu/aarch64/syscallent.h b/sysdeps/linux-gnu/aarch64/syscallent.h new file mode 100644 index 0000000..aca8191 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/syscallent.h @@ -0,0 +1,1100 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + "io_setup", /* 0 */ + "io_destroy", /* 1 */ + "io_submit", /* 2 */ + "io_cancel", /* 3 */ + "io_getevents", /* 4 */ + "setxattr", /* 5 */ + "lsetxattr", /* 6 */ + "fsetxattr", /* 7 */ + "getxattr", /* 8 */ + "lgetxattr", /* 9 */ + "fgetxattr", /* 10 */ + "listxattr", /* 11 */ + "llistxattr", /* 12 */ + "flistxattr", /* 13 */ + "removexattr", /* 14 */ + "lremovexattr", /* 15 */ + "fremovexattr", /* 16 */ + "getcwd", /* 17 */ + "lookup_dcookie", /* 18 */ + "eventfd2", /* 19 */ + "epoll_create1", /* 20 */ + "epoll_ctl", /* 21 */ + "epoll_pwait", /* 22 */ + "dup", /* 23 */ + "dup3", /* 24 */ + "fcntl", /* 25 */ + "inotify_init1", /* 26 */ + "inotify_add_watch", /* 27 */ + "inotify_rm_watch", /* 28 */ + "ioctl", /* 29 */ + "ioprio_set", /* 30 */ + "ioprio_get", /* 31 */ + "flock", /* 32 */ + "mknodat", /* 33 */ + "mkdirat", /* 34 */ + "unlinkat", /* 35 */ + "symlinkat", /* 36 */ + "linkat", /* 37 */ + "renameat", /* 38 */ + "umount2", /* 39 */ + "mount", /* 40 */ + "pivot_root", /* 41 */ + "nfsservctl", /* 42 */ + "statfs", /* 43 */ + "fstatfs", /* 44 */ + "truncate", /* 45 */ + "ftruncate", /* 46 */ + "fallocate", /* 47 */ + "faccessat", /* 48 */ + "chdir", /* 49 */ + "fchdir", /* 50 */ + "chroot", /* 51 */ + "fchmod", /* 52 */ + "fchmodat", /* 53 */ + "fchownat", /* 54 */ + "fchown", /* 55 */ + "openat", /* 56 */ + "close", /* 57 */ + "vhangup", /* 58 */ + "pipe2", /* 59 */ + "quotactl", /* 60 */ + "getdents64", /* 61 */ + "lseek", /* 62 */ + "read", /* 63 */ + "write", /* 64 */ + "readv", /* 65 */ + "writev", /* 66 */ + "pread64", /* 67 */ + "pwrite64", /* 68 */ + "preadv", /* 69 */ + "pwritev", /* 70 */ + "sendfile", /* 71 */ + "pselect6", /* 72 */ + "ppoll", /* 73 */ + "signalfd4", /* 74 */ + "vmsplice", /* 75 */ + "splice", /* 76 */ + "tee", /* 77 */ + "readlinkat", /* 78 */ + "fstatat", /* 79 */ + "fstat", /* 80 */ + "sync", /* 81 */ + "fsync", /* 82 */ + "fdatasync", /* 83 */ + "sync_file_range", /* 84 */ + "timerfd_create", /* 85 */ + "timerfd_settime", /* 86 */ + "timerfd_gettime", /* 87 */ + "utimensat", /* 88 */ + "acct", /* 89 */ + "capget", /* 90 */ + "capset", /* 91 */ + "personality", /* 92 */ + "exit", /* 93 */ + "exit_group", /* 94 */ + "waitid", /* 95 */ + "set_tid_address", /* 96 */ + "unshare", /* 97 */ + "futex", /* 98 */ + "set_robust_list", /* 99 */ + "get_robust_list", /* 100 */ + "nanosleep", /* 101 */ + "getitimer", /* 102 */ + "setitimer", /* 103 */ + "kexec_load", /* 104 */ + "init_module", /* 105 */ + "delete_module", /* 106 */ + "timer_create", /* 107 */ + "timer_gettime", /* 108 */ + "timer_getoverrun", /* 109 */ + "timer_settime", /* 110 */ + "timer_delete", /* 111 */ + "clock_settime", /* 112 */ + "clock_gettime", /* 113 */ + "clock_getres", /* 114 */ + "clock_nanosleep", /* 115 */ + "syslog", /* 116 */ + "ptrace", /* 117 */ + "sched_setparam", /* 118 */ + "sched_setscheduler", /* 119 */ + "sched_getscheduler", /* 120 */ + "sched_getparam", /* 121 */ + "sched_setaffinity", /* 122 */ + "sched_getaffinity", /* 123 */ + "sched_yield", /* 124 */ + "sched_get_priority_max", /* 125 */ + "sched_get_priority_min", /* 126 */ + "sched_rr_get_interval", /* 127 */ + "restart_syscall", /* 128 */ + "kill", /* 129 */ + "tkill", /* 130 */ + "tgkill", /* 131 */ + "sigaltstack", /* 132 */ + "rt_sigsuspend", /* 133 */ + "rt_sigaction", /* 134 */ + "rt_sigprocmask", /* 135 */ + "rt_sigpending", /* 136 */ + "rt_sigtimedwait", /* 137 */ + "rt_sigqueueinfo", /* 138 */ + "rt_sigreturn", /* 139 */ + "setpriority", /* 140 */ + "getpriority", /* 141 */ + "reboot", /* 142 */ + "setregid", /* 143 */ + "setgid", /* 144 */ + "setreuid", /* 145 */ + "setuid", /* 146 */ + "setresuid", /* 147 */ + "getresuid", /* 148 */ + "setresgid", /* 149 */ + "getresgid", /* 150 */ + "setfsuid", /* 151 */ + "setfsgid", /* 152 */ + "times", /* 153 */ + "setpgid", /* 154 */ + "getpgid", /* 155 */ + "getsid", /* 156 */ + "setsid", /* 157 */ + "getgroups", /* 158 */ + "setgroups", /* 159 */ + "uname", /* 160 */ + "sethostname", /* 161 */ + "setdomainname", /* 162 */ + "getrlimit", /* 163 */ + "setrlimit", /* 164 */ + "getrusage", /* 165 */ + "umask", /* 166 */ + "prctl", /* 167 */ + "getcpu", /* 168 */ + "gettimeofday", /* 169 */ + "settimeofday", /* 170 */ + "adjtimex", /* 171 */ + "getpid", /* 172 */ + "getppid", /* 173 */ + "getuid", /* 174 */ + "geteuid", /* 175 */ + "getgid", /* 176 */ + "getegid", /* 177 */ + "gettid", /* 178 */ + "sysinfo", /* 179 */ + "mq_open", /* 180 */ + "mq_unlink", /* 181 */ + "mq_timedsend", /* 182 */ + "mq_timedreceive", /* 183 */ + "mq_notify", /* 184 */ + "mq_getsetattr", /* 185 */ + "msgget", /* 186 */ + "msgctl", /* 187 */ + "msgrcv", /* 188 */ + "msgsnd", /* 189 */ + "semget", /* 190 */ + "semctl", /* 191 */ + "semtimedop", /* 192 */ + "semop", /* 193 */ + "shmget", /* 194 */ + "shmctl", /* 195 */ + "shmat", /* 196 */ + "shmdt", /* 197 */ + "socket", /* 198 */ + "socketpair", /* 199 */ + "bind", /* 200 */ + "listen", /* 201 */ + "accept", /* 202 */ + "connect", /* 203 */ + "getsockname", /* 204 */ + "getpeername", /* 205 */ + "sendto", /* 206 */ + "recvfrom", /* 207 */ + "setsockopt", /* 208 */ + "getsockopt", /* 209 */ + "shutdown", /* 210 */ + "sendmsg", /* 211 */ + "recvmsg", /* 212 */ + "readahead", /* 213 */ + "brk", /* 214 */ + "munmap", /* 215 */ + "mremap", /* 216 */ + "add_key", /* 217 */ + "request_key", /* 218 */ + "keyctl", /* 219 */ + "clone", /* 220 */ + "execve", /* 221 */ + "mmap", /* 222 */ + "fadvise64", /* 223 */ + "swapon", /* 224 */ + "swapoff", /* 225 */ + "mprotect", /* 226 */ + "msync", /* 227 */ + "mlock", /* 228 */ + "munlock", /* 229 */ + "mlockall", /* 230 */ + "munlockall", /* 231 */ + "mincore", /* 232 */ + "madvise", /* 233 */ + "remap_file_pages", /* 234 */ + "mbind", /* 235 */ + "get_mempolicy", /* 236 */ + "set_mempolicy", /* 237 */ + "migrate_pages", /* 238 */ + "move_pages", /* 239 */ + "rt_tgsigqueueinfo", /* 240 */ + "perf_event_open", /* 241 */ + "accept4", /* 242 */ + "recvmmsg", /* 243 */ + "arch_specific_syscall", /* 244 */ + "245", /* 245 */ + "246", /* 246 */ + "247", /* 247 */ + "248", /* 248 */ + "249", /* 249 */ + "250", /* 250 */ + "251", /* 251 */ + "252", /* 252 */ + "253", /* 253 */ + "254", /* 254 */ + "255", /* 255 */ + "256", /* 256 */ + "257", /* 257 */ + "258", /* 258 */ + "259", /* 259 */ + "wait4", /* 260 */ + "prlimit64", /* 261 */ + "fanotify_init", /* 262 */ + "fanotify_mark", /* 263 */ + "name_to_handle_at", /* 264 */ + "open_by_handle_at", /* 265 */ + "clock_adjtime", /* 266 */ + "syncfs", /* 267 */ + "setns", /* 268 */ + "sendmmsg", /* 269 */ + "process_vm_readv", /* 270 */ + "process_vm_writev", /* 271 */ + "kcmp", /* 272 */ + "finit_module", /* 273 */ + "syscalls", /* 274 */ + "275", /* 275 */ + "276", /* 276 */ + "277", /* 277 */ + "278", /* 278 */ + "279", /* 279 */ + "280", /* 280 */ + "281", /* 281 */ + "282", /* 282 */ + "283", /* 283 */ + "284", /* 284 */ + "285", /* 285 */ + "286", /* 286 */ + "287", /* 287 */ + "288", /* 288 */ + "289", /* 289 */ + "290", /* 290 */ + "291", /* 291 */ + "292", /* 292 */ + "293", /* 293 */ + "294", /* 294 */ + "295", /* 295 */ + "296", /* 296 */ + "297", /* 297 */ + "298", /* 298 */ + "299", /* 299 */ + "300", /* 300 */ + "301", /* 301 */ + "302", /* 302 */ + "303", /* 303 */ + "304", /* 304 */ + "305", /* 305 */ + "306", /* 306 */ + "307", /* 307 */ + "308", /* 308 */ + "309", /* 309 */ + "310", /* 310 */ + "311", /* 311 */ + "312", /* 312 */ + "313", /* 313 */ + "314", /* 314 */ + "315", /* 315 */ + "316", /* 316 */ + "317", /* 317 */ + "318", /* 318 */ + "319", /* 319 */ + "320", /* 320 */ + "321", /* 321 */ + "322", /* 322 */ + "323", /* 323 */ + "324", /* 324 */ + "325", /* 325 */ + "326", /* 326 */ + "327", /* 327 */ + "328", /* 328 */ + "329", /* 329 */ + "330", /* 330 */ + "331", /* 331 */ + "332", /* 332 */ + "333", /* 333 */ + "334", /* 334 */ + "335", /* 335 */ + "336", /* 336 */ + "337", /* 337 */ + "338", /* 338 */ + "339", /* 339 */ + "340", /* 340 */ + "341", /* 341 */ + "342", /* 342 */ + "343", /* 343 */ + "344", /* 344 */ + "345", /* 345 */ + "346", /* 346 */ + "347", /* 347 */ + "348", /* 348 */ + "349", /* 349 */ + "350", /* 350 */ + "351", /* 351 */ + "352", /* 352 */ + "353", /* 353 */ + "354", /* 354 */ + "355", /* 355 */ + "356", /* 356 */ + "357", /* 357 */ + "358", /* 358 */ + "359", /* 359 */ + "360", /* 360 */ + "361", /* 361 */ + "362", /* 362 */ + "363", /* 363 */ + "364", /* 364 */ + "365", /* 365 */ + "366", /* 366 */ + "367", /* 367 */ + "368", /* 368 */ + "369", /* 369 */ + "370", /* 370 */ + "371", /* 371 */ + "372", /* 372 */ + "373", /* 373 */ + "374", /* 374 */ + "375", /* 375 */ + "376", /* 376 */ + "377", /* 377 */ + "378", /* 378 */ + "379", /* 379 */ + "380", /* 380 */ + "381", /* 381 */ + "382", /* 382 */ + "383", /* 383 */ + "384", /* 384 */ + "385", /* 385 */ + "386", /* 386 */ + "387", /* 387 */ + "388", /* 388 */ + "389", /* 389 */ + "390", /* 390 */ + "391", /* 391 */ + "392", /* 392 */ + "393", /* 393 */ + "394", /* 394 */ + "395", /* 395 */ + "396", /* 396 */ + "397", /* 397 */ + "398", /* 398 */ + "399", /* 399 */ + "400", /* 400 */ + "401", /* 401 */ + "402", /* 402 */ + "403", /* 403 */ + "404", /* 404 */ + "405", /* 405 */ + "406", /* 406 */ + "407", /* 407 */ + "408", /* 408 */ + "409", /* 409 */ + "410", /* 410 */ + "411", /* 411 */ + "412", /* 412 */ + "413", /* 413 */ + "414", /* 414 */ + "415", /* 415 */ + "416", /* 416 */ + "417", /* 417 */ + "418", /* 418 */ + "419", /* 419 */ + "420", /* 420 */ + "421", /* 421 */ + "422", /* 422 */ + "423", /* 423 */ + "424", /* 424 */ + "425", /* 425 */ + "426", /* 426 */ + "427", /* 427 */ + "428", /* 428 */ + "429", /* 429 */ + "430", /* 430 */ + "431", /* 431 */ + "432", /* 432 */ + "433", /* 433 */ + "434", /* 434 */ + "435", /* 435 */ + "436", /* 436 */ + "437", /* 437 */ + "438", /* 438 */ + "439", /* 439 */ + "440", /* 440 */ + "441", /* 441 */ + "442", /* 442 */ + "443", /* 443 */ + "444", /* 444 */ + "445", /* 445 */ + "446", /* 446 */ + "447", /* 447 */ + "448", /* 448 */ + "449", /* 449 */ + "450", /* 450 */ + "451", /* 451 */ + "452", /* 452 */ + "453", /* 453 */ + "454", /* 454 */ + "455", /* 455 */ + "456", /* 456 */ + "457", /* 457 */ + "458", /* 458 */ + "459", /* 459 */ + "460", /* 460 */ + "461", /* 461 */ + "462", /* 462 */ + "463", /* 463 */ + "464", /* 464 */ + "465", /* 465 */ + "466", /* 466 */ + "467", /* 467 */ + "468", /* 468 */ + "469", /* 469 */ + "470", /* 470 */ + "471", /* 471 */ + "472", /* 472 */ + "473", /* 473 */ + "474", /* 474 */ + "475", /* 475 */ + "476", /* 476 */ + "477", /* 477 */ + "478", /* 478 */ + "479", /* 479 */ + "480", /* 480 */ + "481", /* 481 */ + "482", /* 482 */ + "483", /* 483 */ + "484", /* 484 */ + "485", /* 485 */ + "486", /* 486 */ + "487", /* 487 */ + "488", /* 488 */ + "489", /* 489 */ + "490", /* 490 */ + "491", /* 491 */ + "492", /* 492 */ + "493", /* 493 */ + "494", /* 494 */ + "495", /* 495 */ + "496", /* 496 */ + "497", /* 497 */ + "498", /* 498 */ + "499", /* 499 */ + "500", /* 500 */ + "501", /* 501 */ + "502", /* 502 */ + "503", /* 503 */ + "504", /* 504 */ + "505", /* 505 */ + "506", /* 506 */ + "507", /* 507 */ + "508", /* 508 */ + "509", /* 509 */ + "510", /* 510 */ + "511", /* 511 */ + "512", /* 512 */ + "513", /* 513 */ + "514", /* 514 */ + "515", /* 515 */ + "516", /* 516 */ + "517", /* 517 */ + "518", /* 518 */ + "519", /* 519 */ + "520", /* 520 */ + "521", /* 521 */ + "522", /* 522 */ + "523", /* 523 */ + "524", /* 524 */ + "525", /* 525 */ + "526", /* 526 */ + "527", /* 527 */ + "528", /* 528 */ + "529", /* 529 */ + "530", /* 530 */ + "531", /* 531 */ + "532", /* 532 */ + "533", /* 533 */ + "534", /* 534 */ + "535", /* 535 */ + "536", /* 536 */ + "537", /* 537 */ + "538", /* 538 */ + "539", /* 539 */ + "540", /* 540 */ + "541", /* 541 */ + "542", /* 542 */ + "543", /* 543 */ + "544", /* 544 */ + "545", /* 545 */ + "546", /* 546 */ + "547", /* 547 */ + "548", /* 548 */ + "549", /* 549 */ + "550", /* 550 */ + "551", /* 551 */ + "552", /* 552 */ + "553", /* 553 */ + "554", /* 554 */ + "555", /* 555 */ + "556", /* 556 */ + "557", /* 557 */ + "558", /* 558 */ + "559", /* 559 */ + "560", /* 560 */ + "561", /* 561 */ + "562", /* 562 */ + "563", /* 563 */ + "564", /* 564 */ + "565", /* 565 */ + "566", /* 566 */ + "567", /* 567 */ + "568", /* 568 */ + "569", /* 569 */ + "570", /* 570 */ + "571", /* 571 */ + "572", /* 572 */ + "573", /* 573 */ + "574", /* 574 */ + "575", /* 575 */ + "576", /* 576 */ + "577", /* 577 */ + "578", /* 578 */ + "579", /* 579 */ + "580", /* 580 */ + "581", /* 581 */ + "582", /* 582 */ + "583", /* 583 */ + "584", /* 584 */ + "585", /* 585 */ + "586", /* 586 */ + "587", /* 587 */ + "588", /* 588 */ + "589", /* 589 */ + "590", /* 590 */ + "591", /* 591 */ + "592", /* 592 */ + "593", /* 593 */ + "594", /* 594 */ + "595", /* 595 */ + "596", /* 596 */ + "597", /* 597 */ + "598", /* 598 */ + "599", /* 599 */ + "600", /* 600 */ + "601", /* 601 */ + "602", /* 602 */ + "603", /* 603 */ + "604", /* 604 */ + "605", /* 605 */ + "606", /* 606 */ + "607", /* 607 */ + "608", /* 608 */ + "609", /* 609 */ + "610", /* 610 */ + "611", /* 611 */ + "612", /* 612 */ + "613", /* 613 */ + "614", /* 614 */ + "615", /* 615 */ + "616", /* 616 */ + "617", /* 617 */ + "618", /* 618 */ + "619", /* 619 */ + "620", /* 620 */ + "621", /* 621 */ + "622", /* 622 */ + "623", /* 623 */ + "624", /* 624 */ + "625", /* 625 */ + "626", /* 626 */ + "627", /* 627 */ + "628", /* 628 */ + "629", /* 629 */ + "630", /* 630 */ + "631", /* 631 */ + "632", /* 632 */ + "633", /* 633 */ + "634", /* 634 */ + "635", /* 635 */ + "636", /* 636 */ + "637", /* 637 */ + "638", /* 638 */ + "639", /* 639 */ + "640", /* 640 */ + "641", /* 641 */ + "642", /* 642 */ + "643", /* 643 */ + "644", /* 644 */ + "645", /* 645 */ + "646", /* 646 */ + "647", /* 647 */ + "648", /* 648 */ + "649", /* 649 */ + "650", /* 650 */ + "651", /* 651 */ + "652", /* 652 */ + "653", /* 653 */ + "654", /* 654 */ + "655", /* 655 */ + "656", /* 656 */ + "657", /* 657 */ + "658", /* 658 */ + "659", /* 659 */ + "660", /* 660 */ + "661", /* 661 */ + "662", /* 662 */ + "663", /* 663 */ + "664", /* 664 */ + "665", /* 665 */ + "666", /* 666 */ + "667", /* 667 */ + "668", /* 668 */ + "669", /* 669 */ + "670", /* 670 */ + "671", /* 671 */ + "672", /* 672 */ + "673", /* 673 */ + "674", /* 674 */ + "675", /* 675 */ + "676", /* 676 */ + "677", /* 677 */ + "678", /* 678 */ + "679", /* 679 */ + "680", /* 680 */ + "681", /* 681 */ + "682", /* 682 */ + "683", /* 683 */ + "684", /* 684 */ + "685", /* 685 */ + "686", /* 686 */ + "687", /* 687 */ + "688", /* 688 */ + "689", /* 689 */ + "690", /* 690 */ + "691", /* 691 */ + "692", /* 692 */ + "693", /* 693 */ + "694", /* 694 */ + "695", /* 695 */ + "696", /* 696 */ + "697", /* 697 */ + "698", /* 698 */ + "699", /* 699 */ + "700", /* 700 */ + "701", /* 701 */ + "702", /* 702 */ + "703", /* 703 */ + "704", /* 704 */ + "705", /* 705 */ + "706", /* 706 */ + "707", /* 707 */ + "708", /* 708 */ + "709", /* 709 */ + "710", /* 710 */ + "711", /* 711 */ + "712", /* 712 */ + "713", /* 713 */ + "714", /* 714 */ + "715", /* 715 */ + "716", /* 716 */ + "717", /* 717 */ + "718", /* 718 */ + "719", /* 719 */ + "720", /* 720 */ + "721", /* 721 */ + "722", /* 722 */ + "723", /* 723 */ + "724", /* 724 */ + "725", /* 725 */ + "726", /* 726 */ + "727", /* 727 */ + "728", /* 728 */ + "729", /* 729 */ + "730", /* 730 */ + "731", /* 731 */ + "732", /* 732 */ + "733", /* 733 */ + "734", /* 734 */ + "735", /* 735 */ + "736", /* 736 */ + "737", /* 737 */ + "738", /* 738 */ + "739", /* 739 */ + "740", /* 740 */ + "741", /* 741 */ + "742", /* 742 */ + "743", /* 743 */ + "744", /* 744 */ + "745", /* 745 */ + "746", /* 746 */ + "747", /* 747 */ + "748", /* 748 */ + "749", /* 749 */ + "750", /* 750 */ + "751", /* 751 */ + "752", /* 752 */ + "753", /* 753 */ + "754", /* 754 */ + "755", /* 755 */ + "756", /* 756 */ + "757", /* 757 */ + "758", /* 758 */ + "759", /* 759 */ + "760", /* 760 */ + "761", /* 761 */ + "762", /* 762 */ + "763", /* 763 */ + "764", /* 764 */ + "765", /* 765 */ + "766", /* 766 */ + "767", /* 767 */ + "768", /* 768 */ + "769", /* 769 */ + "770", /* 770 */ + "771", /* 771 */ + "772", /* 772 */ + "773", /* 773 */ + "774", /* 774 */ + "775", /* 775 */ + "776", /* 776 */ + "777", /* 777 */ + "778", /* 778 */ + "779", /* 779 */ + "780", /* 780 */ + "781", /* 781 */ + "782", /* 782 */ + "783", /* 783 */ + "784", /* 784 */ + "785", /* 785 */ + "786", /* 786 */ + "787", /* 787 */ + "788", /* 788 */ + "789", /* 789 */ + "790", /* 790 */ + "791", /* 791 */ + "792", /* 792 */ + "793", /* 793 */ + "794", /* 794 */ + "795", /* 795 */ + "796", /* 796 */ + "797", /* 797 */ + "798", /* 798 */ + "799", /* 799 */ + "800", /* 800 */ + "801", /* 801 */ + "802", /* 802 */ + "803", /* 803 */ + "804", /* 804 */ + "805", /* 805 */ + "806", /* 806 */ + "807", /* 807 */ + "808", /* 808 */ + "809", /* 809 */ + "810", /* 810 */ + "811", /* 811 */ + "812", /* 812 */ + "813", /* 813 */ + "814", /* 814 */ + "815", /* 815 */ + "816", /* 816 */ + "817", /* 817 */ + "818", /* 818 */ + "819", /* 819 */ + "820", /* 820 */ + "821", /* 821 */ + "822", /* 822 */ + "823", /* 823 */ + "824", /* 824 */ + "825", /* 825 */ + "826", /* 826 */ + "827", /* 827 */ + "828", /* 828 */ + "829", /* 829 */ + "830", /* 830 */ + "831", /* 831 */ + "832", /* 832 */ + "833", /* 833 */ + "834", /* 834 */ + "835", /* 835 */ + "836", /* 836 */ + "837", /* 837 */ + "838", /* 838 */ + "839", /* 839 */ + "840", /* 840 */ + "841", /* 841 */ + "842", /* 842 */ + "843", /* 843 */ + "844", /* 844 */ + "845", /* 845 */ + "846", /* 846 */ + "847", /* 847 */ + "848", /* 848 */ + "849", /* 849 */ + "850", /* 850 */ + "851", /* 851 */ + "852", /* 852 */ + "853", /* 853 */ + "854", /* 854 */ + "855", /* 855 */ + "856", /* 856 */ + "857", /* 857 */ + "858", /* 858 */ + "859", /* 859 */ + "860", /* 860 */ + "861", /* 861 */ + "862", /* 862 */ + "863", /* 863 */ + "864", /* 864 */ + "865", /* 865 */ + "866", /* 866 */ + "867", /* 867 */ + "868", /* 868 */ + "869", /* 869 */ + "870", /* 870 */ + "871", /* 871 */ + "872", /* 872 */ + "873", /* 873 */ + "874", /* 874 */ + "875", /* 875 */ + "876", /* 876 */ + "877", /* 877 */ + "878", /* 878 */ + "879", /* 879 */ + "880", /* 880 */ + "881", /* 881 */ + "882", /* 882 */ + "883", /* 883 */ + "884", /* 884 */ + "885", /* 885 */ + "886", /* 886 */ + "887", /* 887 */ + "888", /* 888 */ + "889", /* 889 */ + "890", /* 890 */ + "891", /* 891 */ + "892", /* 892 */ + "893", /* 893 */ + "894", /* 894 */ + "895", /* 895 */ + "896", /* 896 */ + "897", /* 897 */ + "898", /* 898 */ + "899", /* 899 */ + "900", /* 900 */ + "901", /* 901 */ + "902", /* 902 */ + "903", /* 903 */ + "904", /* 904 */ + "905", /* 905 */ + "906", /* 906 */ + "907", /* 907 */ + "908", /* 908 */ + "909", /* 909 */ + "910", /* 910 */ + "911", /* 911 */ + "912", /* 912 */ + "913", /* 913 */ + "914", /* 914 */ + "915", /* 915 */ + "916", /* 916 */ + "917", /* 917 */ + "918", /* 918 */ + "919", /* 919 */ + "920", /* 920 */ + "921", /* 921 */ + "922", /* 922 */ + "923", /* 923 */ + "924", /* 924 */ + "925", /* 925 */ + "926", /* 926 */ + "927", /* 927 */ + "928", /* 928 */ + "929", /* 929 */ + "930", /* 930 */ + "931", /* 931 */ + "932", /* 932 */ + "933", /* 933 */ + "934", /* 934 */ + "935", /* 935 */ + "936", /* 936 */ + "937", /* 937 */ + "938", /* 938 */ + "939", /* 939 */ + "940", /* 940 */ + "941", /* 941 */ + "942", /* 942 */ + "943", /* 943 */ + "944", /* 944 */ + "945", /* 945 */ + "946", /* 946 */ + "947", /* 947 */ + "948", /* 948 */ + "949", /* 949 */ + "950", /* 950 */ + "951", /* 951 */ + "952", /* 952 */ + "953", /* 953 */ + "954", /* 954 */ + "955", /* 955 */ + "956", /* 956 */ + "957", /* 957 */ + "958", /* 958 */ + "959", /* 959 */ + "960", /* 960 */ + "961", /* 961 */ + "962", /* 962 */ + "963", /* 963 */ + "964", /* 964 */ + "965", /* 965 */ + "966", /* 966 */ + "967", /* 967 */ + "968", /* 968 */ + "969", /* 969 */ + "970", /* 970 */ + "971", /* 971 */ + "972", /* 972 */ + "973", /* 973 */ + "974", /* 974 */ + "975", /* 975 */ + "976", /* 976 */ + "977", /* 977 */ + "978", /* 978 */ + "979", /* 979 */ + "980", /* 980 */ + "981", /* 981 */ + "982", /* 982 */ + "983", /* 983 */ + "984", /* 984 */ + "985", /* 985 */ + "986", /* 986 */ + "987", /* 987 */ + "988", /* 988 */ + "989", /* 989 */ + "990", /* 990 */ + "991", /* 991 */ + "992", /* 992 */ + "993", /* 993 */ + "994", /* 994 */ + "995", /* 995 */ + "996", /* 996 */ + "997", /* 997 */ + "998", /* 998 */ + "999", /* 999 */ + "1000", /* 1000 */ + "1001", /* 1001 */ + "1002", /* 1002 */ + "1003", /* 1003 */ + "1004", /* 1004 */ + "1005", /* 1005 */ + "1006", /* 1006 */ + "1007", /* 1007 */ + "1008", /* 1008 */ + "1009", /* 1009 */ + "1010", /* 1010 */ + "1011", /* 1011 */ + "1012", /* 1012 */ + "1013", /* 1013 */ + "1014", /* 1014 */ + "1015", /* 1015 */ + "1016", /* 1016 */ + "1017", /* 1017 */ + "1018", /* 1018 */ + "1019", /* 1019 */ + "1020", /* 1020 */ + "1021", /* 1021 */ + "1022", /* 1022 */ + "1023", /* 1023 */ + "open", /* 1024 */ + "link", /* 1025 */ + "unlink", /* 1026 */ + "mknod", /* 1027 */ + "chmod", /* 1028 */ + "chown", /* 1029 */ + "mkdir", /* 1030 */ + "rmdir", /* 1031 */ + "lchown", /* 1032 */ + "access", /* 1033 */ + "rename", /* 1034 */ + "readlink", /* 1035 */ + "symlink", /* 1036 */ + "utimes", /* 1037 */ + "stat", /* 1038 */ + "lstat", /* 1039 */ + "pipe", /* 1040 */ + "dup2", /* 1041 */ + "epoll_create", /* 1042 */ + "inotify_init", /* 1043 */ + "eventfd", /* 1044 */ + "signalfd", /* 1045 */ + "sendfile", /* 1046 */ + "ftruncate", /* 1047 */ + "truncate", /* 1048 */ + "stat", /* 1049 */ + "lstat", /* 1050 */ + "fstat", /* 1051 */ + "fcntl", /* 1052 */ + "fadvise64", /* 1053 */ + "newfstatat", /* 1054 */ + "fstatfs", /* 1055 */ + "statfs", /* 1056 */ + "lseek", /* 1057 */ + "mmap", /* 1058 */ + "alarm", /* 1059 */ + "getpgrp", /* 1060 */ + "pause", /* 1061 */ + "time", /* 1062 */ + "utime", /* 1063 */ + "creat", /* 1064 */ + "getdents", /* 1065 */ + "futimesat", /* 1066 */ + "select", /* 1067 */ + "poll", /* 1068 */ + "epoll_wait", /* 1069 */ + "ustat", /* 1070 */ + "vfork", /* 1071 */ + "oldwait4", /* 1072 */ + "recv", /* 1073 */ + "send", /* 1074 */ + "bdflush", /* 1075 */ + "umount", /* 1076 */ + "uselib", /* 1077 */ + "_sysctl", /* 1078 */ + "fork", /* 1079 */ diff --git a/sysdeps/linux-gnu/aarch64/trace.c b/sysdeps/linux-gnu/aarch64/trace.c new file mode 100644 index 0000000..4f31113 --- /dev/null +++ b/sysdeps/linux-gnu/aarch64/trace.c @@ -0,0 +1,84 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2014 Petr Machata, Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <asm/ptrace.h> +#include <string.h> +#include <stdio.h> +#include <errno.h> + +#include "backend.h" +#include "proc.h" + +void +get_arch_dep(struct process *proc) +{ +} + +int aarch64_read_gregs(struct process *proc, struct user_pt_regs *regs); + +/* The syscall instruction is: + * | 31 21 | 20 5 | 4 0 | + * | 1 1 0 1 0 1 0 0 | 0 0 0 | imm16 | 0 0 0 0 1 | */ +#define SVC_MASK 0xffe0001f +#define SVC_VALUE 0xd4000001 + +int +syscall_p(struct process *proc, int status, int *sysnum) +{ + if (WIFSTOPPED(status) + && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { + + struct user_pt_regs regs; + if (aarch64_read_gregs(proc, ®s) < 0) { + fprintf(stderr, "syscall_p: " + "Couldn't read registers of %d.\n", proc->pid); + return -1; + } + + errno = 0; + unsigned long insn = (unsigned long) ptrace(PTRACE_PEEKTEXT, + proc->pid, + regs.pc - 4, 0); + if (insn == -1UL && errno != 0) { + fprintf(stderr, "syscall_p: " + "Couldn't peek into %d: %s\n", proc->pid, + strerror(errno)); + return -1; + } + + insn &= 0xffffffffUL; + if ((insn & SVC_MASK) == SVC_VALUE) { + *sysnum = regs.regs[8]; + + size_t d1 = proc->callstack_depth - 1; + if (proc->callstack_depth > 0 + && proc->callstack[d1].is_syscall + && proc->callstack[d1].c_un.syscall == *sysnum) + return 2; + + return 1; + } + } + + return 0; +} diff --git a/sysdeps/linux-gnu/alpha/plt.c b/sysdeps/linux-gnu/alpha/plt.c index c7ce9fe..1185363 100644 --- a/sysdeps/linux-gnu/alpha/plt.c +++ b/sysdeps/linux-gnu/alpha/plt.c @@ -23,11 +23,13 @@ #include "common.h" GElf_Addr -arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { +arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) +{ return lte->plt_addr + ndx * 12 + 32; } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } diff --git a/sysdeps/linux-gnu/alpha/regs.c b/sysdeps/linux-gnu/alpha/regs.c index 0b46afd..9ccd8f2 100644 --- a/sysdeps/linux-gnu/alpha/regs.c +++ b/sysdeps/linux-gnu/alpha/regs.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2004,2008,2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -36,26 +37,25 @@ #endif void * -get_instruction_pointer(Process *proc) { +get_instruction_pointer(struct process *proc) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 64 /* REG_PC */ , 0); } void -set_instruction_pointer(Process *proc, void *addr) { +set_instruction_pointer(struct process *proc, void *addr) +{ ptrace(PTRACE_POKEUSER, proc->pid, 64 /* REG_PC */ , addr); } void * -get_stack_pointer(Process *proc) { +get_stack_pointer(struct process *proc) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 30 /* REG_FP */ , 0); } void * -get_return_addr(Process *proc, void *stack_pointer) { +get_return_addr(struct process *proc, void *stack_pointer) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 26 /* RA */ , 0); } - -void -set_return_addr(Process *proc, void *addr) { - ptrace(PTRACE_POKEUSER, proc->pid, 26 /* RA */ , addr); -} diff --git a/sysdeps/linux-gnu/alpha/trace.c b/sysdeps/linux-gnu/alpha/trace.c index a7c5e59..c6f7494 100644 --- a/sysdeps/linux-gnu/alpha/trace.c +++ b/sysdeps/linux-gnu/alpha/trace.c @@ -40,13 +40,15 @@ #endif void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ } /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { char *ip = get_instruction_pointer(proc) - 4; @@ -69,7 +71,8 @@ syscall_p(Process *proc, int status, int *sysnum) { } long -gimme_arg(enum tof type, Process *proc, int arg_num, struct arg_type_info *info) +gimme_arg(enum tof type, struct process *proc, int arg_num, + struct arg_type_info *info) { if (arg_num == -1) { /* return value */ return ptrace(PTRACE_PEEKUSER, proc->pid, 0 /* REG_R0 */ , 0); diff --git a/sysdeps/linux-gnu/arm/Makefile.am b/sysdeps/linux-gnu/arm/Makefile.am index 385424c..2c180c6 100644 --- a/sysdeps/linux-gnu/arm/Makefile.am +++ b/sysdeps/linux-gnu/arm/Makefile.am @@ -1,4 +1,5 @@ # This file is part of ltrace. +# Copyright (C) 2013 Petr Machata, Red Hat Inc. # Copyright (C) 2010 Marc Kleine-Budde, Pengutronix # # This program is free software; you can redistribute it and/or @@ -16,21 +17,11 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA -noinst_LTLIBRARIES = \ - ../libcpu.la +noinst_LTLIBRARIES = ../libcpu.la -___libcpu_la_SOURCES = \ - breakpoint.c \ - plt.c \ - regs.c \ - trace.c +___libcpu_la_SOURCES = breakpoint.c fetch.c plt.c regs.c trace.c -noinst_HEADERS = \ - arch.h \ - arch_syscallent.h \ - ptrace.h \ - signalent.h \ - syscallent.h +noinst_HEADERS = arch.h arch_syscallent.h ptrace.h regs.h signalent.h \ + syscallent.h -MAINTAINERCLEANFILES = \ - Makefile.in +MAINTAINERCLEANFILES = Makefile.in diff --git a/sysdeps/linux-gnu/arm/arch.h b/sysdeps/linux-gnu/arm/arch.h index 291443a..6d0d902 100644 --- a/sysdeps/linux-gnu/arm/arch.h +++ b/sysdeps/linux-gnu/arm/arch.h @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2004,2008 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -18,6 +19,11 @@ * 02110-1301 USA */ +#ifndef LTRACE_ARM_ARCH_H +#define LTRACE_ARM_ARCH_H + +#include <libelf.h> + #define ARCH_HAVE_ENABLE_BREAKPOINT 1 #define ARCH_HAVE_DISABLE_BREAKPOINT 1 @@ -31,7 +37,24 @@ #define LT_ELFCLASS ELFCLASS32 #define LT_ELF_MACHINE EM_ARM +#define ARCH_HAVE_SW_SINGLESTEP +#define ARCH_HAVE_FETCH_ARG +#define ARCH_HAVE_FETCH_PACK +#define ARCH_HAVE_SIZEOF +#define ARCH_HAVE_ALIGNOF #define ARCH_HAVE_BREAKPOINT_DATA struct arch_breakpoint_data { int thumb_mode; }; + +#define ARCH_HAVE_LTELF_DATA +struct arch_ltelf_data { + Elf_Data *jmprel_data; +}; + +#define ARCH_HAVE_LIBRARY_DATA +struct arch_library_data { + unsigned int hardfp:1; +}; + +#endif /* LTRACE_ARM_ARCH_H */ diff --git a/sysdeps/linux-gnu/arm/breakpoint.c b/sysdeps/linux-gnu/arm/breakpoint.c index 5748401..fcd43a7 100644 --- a/sysdeps/linux-gnu/arm/breakpoint.c +++ b/sysdeps/linux-gnu/arm/breakpoint.c @@ -92,16 +92,13 @@ arch_disable_breakpoint(pid_t pid, const struct breakpoint *sbp) } int -arch_breakpoint_init(struct Process *proc, struct breakpoint *sbp) +arch_breakpoint_init(struct process *proc, struct breakpoint *sbp) { - /* XXX That uintptr_t cast is there temporarily until - * arch_addr_t becomes integral type. */ - int thumb_mode = ((uintptr_t)sbp->addr) & 1; - if (thumb_mode) - sbp->addr = (void *)((uintptr_t)sbp->addr & ~1); - sbp->arch.thumb_mode = thumb_mode | proc->thumb_mode; - /* XXX This doesn't seem like it belongs here. */ - proc->thumb_mode = 0; + /* XXX double cast */ + sbp->arch.thumb_mode = ((uintptr_t)sbp->addr) & 1; + if (sbp->arch.thumb_mode) + /* XXX double cast */ + sbp->addr = (arch_addr_t)((uintptr_t)sbp->addr & ~1); return 0; } diff --git a/sysdeps/linux-gnu/arm/fetch.c b/sysdeps/linux-gnu/arm/fetch.c new file mode 100644 index 0000000..b500448 --- /dev/null +++ b/sysdeps/linux-gnu/arm/fetch.c @@ -0,0 +1,343 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> +#include <asm/ptrace.h> +#include <assert.h> +#include <elf.h> +#include <libelf.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> + +#include "backend.h" +#include "fetch.h" +#include "library.h" +#include "proc.h" +#include "ptrace.h" +#include "regs.h" +#include "type.h" +#include "value.h" + +enum { + /* How many (double) VFP registers the AAPCS uses for + * parameter passing. */ + NUM_VFP_REGS = 8, +}; + +struct fetch_context { + struct pt_regs regs; + + struct { + union { + double d[32]; + float s[64]; + }; + uint32_t fpscr; + } fpregs; + + /* VFP register allocation. ALLOC.S tracks whether the + * corresponding FPREGS.S register is taken, ALLOC.D the same + * for FPREGS.D. We only track 8 (16) registers, because + * that's what the ABI uses for parameter passing. */ + union { + int16_t d[NUM_VFP_REGS]; + int8_t s[NUM_VFP_REGS * 2]; + } alloc; + + unsigned ncrn; + arch_addr_t sp; + arch_addr_t nsaa; + arch_addr_t ret_struct; + + bool hardfp:1; + bool in_varargs:1; +}; + +static int +fetch_register_banks(struct process *proc, struct fetch_context *context) +{ + if (ptrace(PTRACE_GETREGS, proc->pid, NULL, &context->regs) == -1) + return -1; + + if (context->hardfp + && ptrace(PTRACE_GETVFPREGS, proc->pid, + NULL, &context->fpregs) == -1) + return -1; + + context->ncrn = 0; + context->nsaa = context->sp = get_stack_pointer(proc); + memset(&context->alloc, 0, sizeof(context->alloc)); + + return 0; +} + +struct fetch_context * +arch_fetch_arg_init(enum tof type, struct process *proc, + struct arg_type_info *ret_info) +{ + struct fetch_context *context = malloc(sizeof(*context)); + + { + struct process *mainp = proc; + while (mainp->libraries == NULL && mainp->parent != NULL) + mainp = mainp->parent; + context->hardfp = mainp->libraries->arch.hardfp; + } + + if (context == NULL + || fetch_register_banks(proc, context) < 0) { + free(context); + return NULL; + } + + if (ret_info->type == ARGTYPE_STRUCT + || ret_info->type == ARGTYPE_ARRAY) { + size_t sz = type_sizeof(proc, ret_info); + assert(sz != (size_t)-1); + if (sz > 4) { + /* XXX double cast */ + context->ret_struct + = (arch_addr_t)context->regs.uregs[0]; + context->ncrn++; + } + } + + return context; +} + +struct fetch_context * +arch_fetch_arg_clone(struct process *proc, + struct fetch_context *context) +{ + struct fetch_context *clone = malloc(sizeof(*context)); + if (clone == NULL) + return NULL; + *clone = *context; + return clone; +} + +/* 0 is success, 1 is failure, negative value is an error. */ +static int +pass_in_vfp(struct fetch_context *ctx, struct process *proc, + enum arg_type type, size_t count, struct value *valuep) +{ + assert(type == ARGTYPE_FLOAT || type == ARGTYPE_DOUBLE); + unsigned max = type == ARGTYPE_DOUBLE ? NUM_VFP_REGS : 2 * NUM_VFP_REGS; + if (count > max) + return 1; + + size_t i; + size_t j; + for (i = 0; i < max; ++i) { + for (j = i; j < i + count; ++j) + if ((type == ARGTYPE_DOUBLE && ctx->alloc.d[j] != 0) + || (type == ARGTYPE_FLOAT && ctx->alloc.s[j] != 0)) + goto next; + + /* Found COUNT consecutive unallocated registers at I. */ + const size_t sz = (type == ARGTYPE_FLOAT ? 4 : 8) * count; + unsigned char *data = value_reserve(valuep, sz); + if (data == NULL) + return -1; + + for (j = i; j < i + count; ++j) + if (type == ARGTYPE_DOUBLE) + ctx->alloc.d[j] = -1; + else + ctx->alloc.s[j] = -1; + + if (type == ARGTYPE_DOUBLE) + memcpy(data, ctx->fpregs.d + i, sz); + else + memcpy(data, ctx->fpregs.s + i, sz); + + return 0; + + next: + continue; + } + return 1; +} + +/* 0 is success, 1 is failure, negative value is an error. */ +static int +consider_vfp(struct fetch_context *ctx, struct process *proc, + struct arg_type_info *info, struct value *valuep) +{ + struct arg_type_info *float_info = NULL; + size_t hfa_size = 1; + if (info->type == ARGTYPE_FLOAT || info->type == ARGTYPE_DOUBLE) + float_info = info; + else + float_info = type_get_hfa_type(info, &hfa_size); + + if (float_info != NULL && hfa_size <= 4) + return pass_in_vfp(ctx, proc, float_info->type, + hfa_size, valuep); + return 1; +} + +int +arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, + struct process *proc, + struct arg_type_info *info, struct value *valuep) +{ + const size_t sz = type_sizeof(proc, info); + assert(sz != (size_t)-1); + + if (ctx->hardfp && !ctx->in_varargs) { + int rc; + if ((rc = consider_vfp(ctx, proc, info, valuep)) != 1) + return rc; + } + + /* IHI0042E_aapcs: If the argument requires double-word + * alignment (8-byte), the NCRN is rounded up to the next even + * register number. */ + const size_t al = type_alignof(proc, info); + assert(al != (size_t)-1); + if (al == 8) + ctx->ncrn = ((ctx->ncrn + 1) / 2) * 2; + + /* If the size in words of the argument is not more than r4 + * minus NCRN, the argument is copied into core registers, + * starting at the NCRN. */ + /* If the NCRN is less than r4 and the NSAA is equal to the + * SP, the argument is split between core registers and the + * stack. */ + + const size_t words = (sz + 3) / 4; + if (ctx->ncrn < 4 && ctx->nsaa == ctx->sp) { + unsigned char *data = value_reserve(valuep, words * 4); + if (data == NULL) + return -1; + size_t i; + for (i = 0; i < words && ctx->ncrn < 4; ++i) { + memcpy(data, &ctx->regs.uregs[ctx->ncrn++], 4); + data += 4; + } + const size_t rest = (words - i) * 4; + if (rest > 0) { + umovebytes(proc, ctx->nsaa, data, rest); + ctx->nsaa += rest; + } + return 0; + } + + assert(ctx->ncrn == 4); + + /* If the argument required double-word alignment (8-byte), + * then the NSAA is rounded up to the next double-word + * address. */ + if (al == 8) + /* XXX double cast. */ + ctx->nsaa = (arch_addr_t)((((uintptr_t)ctx->nsaa + 7) / 8) * 8); + else + ctx->nsaa = (arch_addr_t)((((uintptr_t)ctx->nsaa + 3) / 4) * 4); + + value_in_inferior(valuep, ctx->nsaa); + ctx->nsaa += sz; + + return 0; +} + +int +arch_fetch_retval(struct fetch_context *ctx, enum tof type, + struct process *proc, struct arg_type_info *info, + struct value *valuep) +{ + if (fetch_register_banks(proc, ctx) < 0) + return -1; + + if (ctx->hardfp && !ctx->in_varargs) { + int rc; + if ((rc = consider_vfp(ctx, proc, info, valuep)) != 1) + return rc; + } + + size_t sz = type_sizeof(proc, info); + assert(sz != (size_t)-1); + + switch (info->type) { + unsigned char *data; + + case ARGTYPE_VOID: + return 0; + + case ARGTYPE_FLOAT: + case ARGTYPE_DOUBLE: + if (ctx->hardfp && !ctx->in_varargs) { + unsigned char *data = value_reserve(valuep, sz); + if (data == NULL) + return -1; + memmove(data, &ctx->fpregs, sz); + return 0; + } + goto pass_in_registers; + + case ARGTYPE_ARRAY: + case ARGTYPE_STRUCT: + if (sz > 4) { + value_in_inferior(valuep, ctx->ret_struct); + return 0; + } + /* Fall through. */ + + case ARGTYPE_CHAR: + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + case ARGTYPE_POINTER: + pass_in_registers: + if ((data = value_reserve(valuep, sz)) == NULL) + return -1; + memmove(data, ctx->regs.uregs, sz); + return 0; + } + assert(info->type != info->type); + abort(); +} + +void +arch_fetch_arg_done(struct fetch_context *context) +{ + free(context); +} + +int +arch_fetch_param_pack_start(struct fetch_context *context, + enum param_pack_flavor ppflavor) +{ + if (ppflavor == PARAM_PACK_VARARGS) + context->in_varargs = true; + return 0; +} + +void +arch_fetch_param_pack_end(struct fetch_context *context) +{ + context->in_varargs = false; +} diff --git a/sysdeps/linux-gnu/arm/plt.c b/sysdeps/linux-gnu/arm/plt.c index 1b97705..9e9e37f 100644 --- a/sysdeps/linux-gnu/arm/plt.c +++ b/sysdeps/linux-gnu/arm/plt.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Zach Welch, CodeSourcery * Copyright (C) 2004,2008,2009 Juan Cespedes * @@ -20,20 +21,205 @@ */ #include <gelf.h> +#include <stdio.h> +#include <string.h> #include "proc.h" #include "library.h" #include "ltrace-elf.h" static int +get_hardfp(uint64_t abi_vfp_args) +{ + if (abi_vfp_args == 2) + fprintf(stderr, + "Tag_ABI_VFP_args value 2 (tool chain-specific " + "conventions) not supported.\n"); + return abi_vfp_args == 1; +} + +int +arch_elf_init(struct ltelf *lte, struct library *lib) +{ + GElf_Addr jmprel_addr; + Elf_Scn *jmprel_sec; + GElf_Shdr jmprel_shdr; + if (elf_load_dynamic_entry(lte, DT_JMPREL, &jmprel_addr) < 0 + || elf_get_section_covering(lte, jmprel_addr, + &jmprel_sec, &jmprel_shdr) < 0 + || jmprel_sec == NULL) + return -1; + + lte->arch.jmprel_data = elf_loaddata(jmprel_sec, &jmprel_shdr); + if (lte->arch.jmprel_data == NULL) + return -1; + + /* Nothing in this section is strictly critical. It's not + * that much of a deal if we fail to guess right whether the + * ABI is softfp or hardfp. */ + unsigned hardfp = 0; + + Elf_Scn *scn; + Elf_Data *data; + GElf_Shdr shdr; + if (elf_get_section_type(lte, SHT_ARM_ATTRIBUTES, &scn, &shdr) < 0 + || (scn != NULL && (data = elf_loaddata(scn, &shdr)) == NULL)) { + fprintf(stderr, + "Error when obtaining ARM attribute section: %s\n", + elf_errmsg(-1)); + goto done; + + } else if (scn != NULL && data != NULL) { + GElf_Xword offset = 0; + uint8_t version; + if (elf_read_next_u8(data, &offset, &version) < 0) { + goto done; + } else if (version != 'A') { + fprintf(stderr, "Unsupported ARM attribute section " + "version %d ('%c').\n", version, version); + goto done; + } + + do { + const char signature[] = "aeabi"; + /* N.B. LEN is including the length field + * itself. */ + uint32_t sec_len; + if (elf_read_u32(data, offset, &sec_len) < 0 + || !elf_can_read_next(data, offset, sec_len)) { + goto done; + } + const GElf_Xword next_offset = offset + sec_len; + offset += 4; + + if (sec_len < 4 + sizeof signature + || strcmp(signature, data->d_buf + offset) != 0) + goto skip; + offset += sizeof signature; + + const GElf_Xword offset0 = offset; + uint64_t tag; + uint32_t sub_len; + if (elf_read_next_uleb128(data, &offset, &tag) < 0 + || elf_read_next_u32(data, &offset, &sub_len) < 0 + || !elf_can_read_next(data, offset0, sub_len)) + goto done; + + if (tag != 1) + /* IHI0045D_ABI_addenda: "section and + * symbol attributes are deprecated + * [...] consumers are permitted to + * ignore them." */ + goto skip; + + while (offset < offset0 + sub_len) { + if (elf_read_next_uleb128(data, + &offset, &tag) < 0) + goto done; + + switch (tag) { + uint64_t v; + case 6: /* Tag_CPU_arch */ + case 7: /* Tag_CPU_arch_profile */ + case 8: /* Tag_ARM_ISA_use */ + case 9: /* Tag_THUMB_ISA_use */ + case 10: /* Tag_FP_arch */ + case 11: /* Tag_WMMX_arch */ + case 12: /* Tag_Advanced_SIMD_arch */ + case 13: /* Tag_PCS_config */ + case 14: /* Tag_ABI_PCS_R9_use */ + case 15: /* Tag_ABI_PCS_RW_data */ + case 16: /* Tag_ABI_PCS_RO_data */ + case 17: /* Tag_ABI_PCS_GOT_use */ + case 18: /* Tag_ABI_PCS_wchar_t */ + case 19: /* Tag_ABI_FP_rounding */ + case 20: /* Tag_ABI_FP_denormal */ + case 21: /* Tag_ABI_FP_exceptions */ + case 22: /* Tag_ABI_FP_user_exceptions */ + case 23: /* Tag_ABI_FP_number_model */ + case 24: /* Tag_ABI_align_needed */ + case 25: /* Tag_ABI_align_preserved */ + case 26: /* Tag_ABI_enum_size */ + case 27: /* Tag_ABI_HardFP_use */ + case 28: /* Tag_ABI_VFP_args */ + case 29: /* Tag_ABI_WMMX_args */ + case 30: /* Tag_ABI_optimization_goals */ + case 31: /* Tag_ABI_FP_optimization_goals */ + case 32: /* Tag_compatibility */ + case 34: /* Tag_CPU_unaligned_access */ + case 36: /* Tag_FP_HP_extension */ + case 38: /* Tag_ABI_FP_16bit_format */ + case 42: /* Tag_MPextension_use */ + case 70: /* Tag_MPextension_use as well */ + case 44: /* Tag_DIV_use */ + case 64: /* Tag_nodefaults */ + case 66: /* Tag_T2EE_use */ + case 68: /* Tag_Virtualization_use */ + uleb128: + if (elf_read_next_uleb128 + (data, &offset, &v) < 0) + goto done; + if (tag == 28) + hardfp = get_hardfp(v); + if (tag != 32) + continue; + + /* Tag 32 has two arguments, + * fall through. */ + + case 4: /* Tag_CPU_raw_name */ + case 5: /* Tag_CPU_name */ + case 65: /* Tag_also_compatible_with */ + case 67: /* Tag_conformance */ + ntbs: + offset += strlen(data->d_buf + + offset) + 1; + continue; + } + + /* Handle unknown tags in a generic + * manner, if possible. */ + if (tag <= 32) { + fprintf(stderr, + "Unknown tag %lld " + "at offset %#llx " + "of ARM attribute section.", + tag, offset); + goto skip; + } else if (tag % 2 == 0) { + goto uleb128; + } else { + goto ntbs; + } + } + + skip: + offset = next_offset; + + } while (elf_can_read_next(data, offset, 1)); + + } + +done: + lib->arch.hardfp = hardfp; + return 0; +} + +void +arch_elf_destroy(struct ltelf *lte) +{ +} + +static int arch_plt_entry_has_stub(struct ltelf *lte, size_t off) { - uint16_t op = *(uint16_t *)((char *)lte->relplt->d_buf + off); + char *buf = (char *) lte->arch.jmprel_data->d_buf; + uint16_t op = *(uint16_t *) (buf + off); return op == 0x4778; } GElf_Addr arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { - size_t start = lte->relplt->d_size + 12; + size_t start = lte->arch.jmprel_data->d_size + 12; size_t off = start + 20, i; for (i = 0; i < ndx; i++) off += arch_plt_entry_has_stub(lte, off) ? 16 : 12; @@ -43,6 +229,25 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } + +int +arch_library_init(struct library *lib) +{ + return 0; +} + +void +arch_library_destroy(struct library *lib) +{ +} + +int +arch_library_clone(struct library *retp, struct library *lib) +{ + retp->arch = lib->arch; + return 0; +} diff --git a/sysdeps/linux-gnu/arm/regs.c b/sysdeps/linux-gnu/arm/regs.c index 484de7f..e9e825e 100644 --- a/sysdeps/linux-gnu/arm/regs.c +++ b/sysdeps/linux-gnu/arm/regs.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2002,2004,2008,2009 Juan Cespedes * Copyright (C) 2009 Juan Cespedes * @@ -24,9 +25,11 @@ #include <sys/types.h> #include <sys/ptrace.h> #include <asm/ptrace.h> +#include <errno.h> #include "proc.h" #include "common.h" +#include "regs.h" #if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR)) # define PTRACE_PEEKUSER PTRACE_PEEKUSR @@ -36,45 +39,119 @@ # define PTRACE_POKEUSER PTRACE_POKEUSR #endif -#define off_pc ((void *)60) -#define off_lr ((void *)56) -#define off_sp ((void *)52) +int +arm_get_register(struct process *proc, enum arm_register reg, uint32_t *lp) +{ + errno = 0; + long l = ptrace(PTRACE_PEEKUSER, proc->pid, (void *)(reg * 4L), 0); + if (l == -1 && errno != 0) + return -1; + *lp = (uint32_t)l; + return 0; +} -void * -get_instruction_pointer(Process *proc) { - return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, off_pc, 0); +int +arm_set_register(struct process *proc, enum arm_register reg, uint32_t lp) +{ + return ptrace(PTRACE_PEEKUSER, proc->pid, + (void *)(reg * 4L), (void *)lp); } -void -set_instruction_pointer(Process *proc, void *addr) { - ptrace(PTRACE_POKEUSER, proc->pid, off_pc, addr); +int +arm_get_register_offpc(struct process *proc, enum arm_register reg, + uint32_t *lp) +{ + if (arm_get_register(proc, reg, lp) < 0) + return -1; + if (reg == ARM_REG_PC) + *lp += 8; + return 0; } -void * -get_stack_pointer(Process *proc) { - return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, off_sp, 0); +int +arm_get_shifted_register(struct process *proc, uint32_t inst, int carry, + arch_addr_t pc_val, uint32_t *lp) +{ + enum arm_register rm = BITS(inst, 0, 3); + unsigned long shifttype = BITS(inst, 5, 6); + + uint32_t shift; + if (BIT(inst, 4)) { + if (arm_get_register_offpc(proc, BITS(inst, 8, 11), &shift) < 0) + return -1; + shift &= 0xff; + } else { + shift = BITS(inst, 7, 11); + } + + uint32_t res; + if (rm == ARM_REG_PC) + /* xxx double cast */ + res = (uintptr_t)pc_val + (BIT(inst, 4) ? 12 : 8); + else if (arm_get_register(proc, rm, &res) < 0) + return -1; + + switch (shifttype) { + case 0: /* LSL */ + res = shift >= 32 ? 0 : res << shift; + break; + + case 1: /* LSR */ + res = shift >= 32 ? 0 : res >> shift; + break; + + case 2: /* ASR */ + if (shift >= 32) + shift = 31; + res = ((res & 0x80000000L) + ? ~((~res) >> shift) : res >> shift); + break; + + case 3: /* ROR/RRX */ + shift &= 31; + if (shift == 0) + res = (res >> 1) | (carry ? 0x80000000L : 0); + else + res = (res >> shift) | (res << (32 - shift)); + break; + } + + *lp = res & 0xffffffff; + return 0; } -/* really, this is given the *stack_pointer expecting - * a CISC architecture; in our case, we don't need that */ -void * -get_return_addr(Process *proc, void *stack_pointer) { - long addr = ptrace(PTRACE_PEEKUSER, proc->pid, off_lr, 0); - - /* Remember & unset the thumb mode bit. XXX This is really a - * bit of a hack, as we assume that the following - * insert_breakpoint call will be related to this address. - * This interface should really be get_return_breakpoint, or - * maybe install_return_breakpoint. */ - proc->thumb_mode = addr & 1; - if (proc->thumb_mode) - addr &= ~1; - - return (void *)addr; +static arch_addr_t +get_register_nocheck(struct process *proc, enum arm_register r) +{ + uint32_t reg; + if (arm_get_register(proc, r, ®) < 0) + /* XXX double cast. */ + return (arch_addr_t)-1; + /* XXX double cast. */ + return (arch_addr_t)(uintptr_t)reg; +} + +arch_addr_t +get_instruction_pointer(struct process *proc) +{ + return get_register_nocheck(proc, ARM_REG_PC); } void -set_return_addr(Process *proc, void *addr) { - long iaddr = (int)addr | proc->thumb_mode; - ptrace(PTRACE_POKEUSER, proc->pid, off_lr, (void *)iaddr); +set_instruction_pointer(struct process *proc, arch_addr_t addr) +{ + /* XXX double cast. */ + arm_set_register(proc, ARM_REG_PC, (uint32_t)addr); +} + +void * +get_stack_pointer(struct process *proc) +{ + return get_register_nocheck(proc, ARM_REG_SP); +} + +arch_addr_t +get_return_addr(struct process *proc, arch_addr_t stack_pointer) +{ + return get_register_nocheck(proc, ARM_REG_LR); } diff --git a/sysdeps/linux-gnu/arm/regs.h b/sysdeps/linux-gnu/arm/regs.h new file mode 100644 index 0000000..f9a5a86 --- /dev/null +++ b/sysdeps/linux-gnu/arm/regs.h @@ -0,0 +1,47 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#define SUBMASK(x) ((1L << ((x) + 1)) - 1) +#define BIT(obj,st) (((obj) >> (st)) & 1) +#define BITS(obj,st,fn) (((obj) >> (st)) & SUBMASK((fn) - (st))) +#define SBITS(obj,st,fn) \ + ((long) (BITS(obj,st,fn) | ((long) BIT(obj,fn) * ~ SUBMASK(fn - st)))) + +enum arm_register { + ARM_REG_R7 = 7, + ARM_REG_IP = 12, + ARM_REG_SP = 13, + ARM_REG_LR = 14, + ARM_REG_PC = 15, + ARM_REG_CPSR = 16, +}; + +/* Write value of register REG to *LP. Return 0 on success or a + * negative value on failure. */ +int arm_get_register(struct process *proc, enum arm_register reg, uint32_t *lp); + +/* Same as above, but if REG==ARM_REG_PC, it returns the value +8. */ +int arm_get_register_offpc(struct process *proc, enum arm_register reg, + uint32_t *lp); + +/* Same as arm_get_register, but shift is performed depending on + * instruction INST. */ +int arm_get_shifted_register(struct process *proc, uint32_t inst, int carry, + arch_addr_t pc, uint32_t *lp); diff --git a/sysdeps/linux-gnu/arm/trace.c b/sysdeps/linux-gnu/arm/trace.c index c528cfb..5e51e91 100644 --- a/sysdeps/linux-gnu/arm/trace.c +++ b/sysdeps/linux-gnu/arm/trace.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -29,10 +29,13 @@ #include <sys/ptrace.h> #include <asm/ptrace.h> -#include "proc.h" +#include "bits.h" #include "common.h" +#include "proc.h" #include "output.h" #include "ptrace.h" +#include "regs.h" +#include "type.h" #if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR)) # define PTRACE_PEEKUSER PTRACE_PEEKUSR @@ -42,13 +45,9 @@ # define PTRACE_POKEUSER PTRACE_POKEUSR #endif -#define off_r0 ((void *)0) -#define off_r7 ((void *)28) -#define off_ip ((void *)48) -#define off_pc ((void *)60) - void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ proc_archdep *a; if (!proc->arch_ptr) @@ -63,21 +62,28 @@ get_arch_dep(Process *proc) { * -1 on error. */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { - /* get the user's pc (plus 8) */ - unsigned pc = ptrace(PTRACE_PEEKUSER, proc->pid, off_pc, 0); + uint32_t pc, ip; + if (arm_get_register(proc, ARM_REG_PC, &pc) < 0 + || arm_get_register(proc, ARM_REG_IP, &ip) < 0) + return -1; + pc = pc - 4; + /* fetch the SWI instruction */ unsigned insn = ptrace(PTRACE_PEEKTEXT, proc->pid, (void *)pc, 0); - int ip = ptrace(PTRACE_PEEKUSER, proc->pid, off_ip, 0); if (insn == 0xef000000 || insn == 0x0f000000 || (insn & 0xffff0000) == 0xdf000000) { /* EABI syscall */ - *sysnum = ptrace(PTRACE_PEEKUSER, proc->pid, off_r7, 0); + uint32_t r7; + if (arm_get_register(proc, ARM_REG_R7, &r7) < 0) + return -1; + *sysnum = r7; } else if ((insn & 0xfff00000) == 0xef900000) { /* old ABI syscall */ *sysnum = insn & 0xfffff; @@ -103,46 +109,605 @@ syscall_p(Process *proc, int status, int *sysnum) { return 0; } -long -gimme_arg(enum tof type, Process *proc, int arg_num, struct arg_type_info *info) +static arch_addr_t +arm_branch_dest(const arch_addr_t pc, const uint32_t insn) { - proc_archdep *a = (proc_archdep *) proc->arch_ptr; + /* Bits 0-23 are signed immediate value. */ + return pc + ((((insn & 0xffffff) ^ 0x800000) - 0x800000) << 2) + 8; +} - if (arg_num == -1) { /* return value */ - return ptrace(PTRACE_PEEKUSER, proc->pid, off_r0, 0); - } +/* Addresses for calling Thumb functions have the bit 0 set. + Here are some macros to test, set, or clear bit 0 of addresses. */ +/* XXX double cast */ +#define IS_THUMB_ADDR(addr) ((uintptr_t)(addr) & 1) +#define MAKE_THUMB_ADDR(addr) ((arch_addr_t)((uintptr_t)(addr) | 1)) +#define UNMAKE_THUMB_ADDR(addr) ((arch_addr_t)((uintptr_t)(addr) & ~1)) - /* deal with the ARM calling conventions */ - if (type == LT_TOF_FUNCTION || type == LT_TOF_FUNCTIONR) { - if (arg_num < 4) { - if (a->valid && type == LT_TOF_FUNCTION) - return a->regs.uregs[arg_num]; - if (a->valid && type == LT_TOF_FUNCTIONR) - return a->func_arg[arg_num]; - return ptrace(PTRACE_PEEKUSER, proc->pid, - (void *)(4 * arg_num), 0); - } else { - return ptrace(PTRACE_PEEKDATA, proc->pid, - proc->stack_pointer + 4 * (arg_num - 4), - 0); +enum { + COND_ALWAYS = 0xe, + COND_NV = 0xf, + FLAG_C = 0x20000000, +}; + +static int +arm_get_next_pcs(struct process *proc, + const arch_addr_t pc, arch_addr_t next_pcs[2]) +{ + uint32_t this_instr; + uint32_t status; + if (proc_read_32(proc, pc, &this_instr) < 0 + || arm_get_register(proc, ARM_REG_CPSR, &status) < 0) + return -1; + + /* In theory, we sometimes don't even need to add any + * breakpoints at all. If the conditional bits of the + * instruction indicate that it should not be taken, then we + * can just skip it altogether without bothering. We could + * also emulate the instruction under the breakpoint. + * + * Here, we make it as simple as possible (though We Accept + * Patches). */ + int nr = 0; + + /* ARM can branch either relatively by using a branch + * instruction, or absolutely, by doing arbitrary arithmetic + * with PC as the destination. */ + const unsigned cond = BITS(this_instr, 28, 31); + const unsigned opcode = BITS(this_instr, 24, 27); + + if (cond == COND_NV) + switch (opcode) { + arch_addr_t addr; + case 0xa: + case 0xb: + /* Branch with Link and change to Thumb. */ + /* XXX double cast. */ + addr = (arch_addr_t) + ((uint32_t)arm_branch_dest(pc, this_instr) + | (((this_instr >> 24) & 0x1) << 1)); + next_pcs[nr++] = MAKE_THUMB_ADDR(addr); + break; } - } else if (type == LT_TOF_SYSCALL || type == LT_TOF_SYSCALLR) { - if (arg_num < 5) { - if (a->valid && type == LT_TOF_SYSCALL) - return a->regs.uregs[arg_num]; - if (a->valid && type == LT_TOF_SYSCALLR) - return a->sysc_arg[arg_num]; - return ptrace(PTRACE_PEEKUSER, proc->pid, - (void *)(4 * arg_num), 0); - } else { - return ptrace(PTRACE_PEEKDATA, proc->pid, - proc->stack_pointer + 4 * (arg_num - 5), - 0); + else + switch (opcode) { + uint32_t operand1, operand2, result = 0; + case 0x0: + case 0x1: /* data processing */ + case 0x2: + case 0x3: + if (BITS(this_instr, 12, 15) != ARM_REG_PC) + break; + + if (BITS(this_instr, 22, 25) == 0 + && BITS(this_instr, 4, 7) == 9) { /* multiply */ + invalid: + fprintf(stderr, + "Invalid update to pc in instruction.\n"); + break; + } + + /* BX <reg>, BLX <reg> */ + if (BITS(this_instr, 4, 27) == 0x12fff1 + || BITS(this_instr, 4, 27) == 0x12fff3) { + enum arm_register reg = BITS(this_instr, 0, 3); + /* XXX double cast: no need to go + * through tmp. */ + uint32_t tmp; + if (arm_get_register_offpc(proc, reg, &tmp) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)tmp; + return 0; + } + + /* Multiply into PC. */ + if (arm_get_register_offpc + (proc, BITS(this_instr, 16, 19), &operand1) < 0) + return -1; + + int c = (status & FLAG_C) ? 1 : 0; + if (BIT(this_instr, 25)) { + uint32_t immval = BITS(this_instr, 0, 7); + uint32_t rotate = 2 * BITS(this_instr, 8, 11); + operand2 = (((immval >> rotate) + | (immval << (32 - rotate))) + & 0xffffffff); + } else { + /* operand 2 is a shifted register. */ + if (arm_get_shifted_register + (proc, this_instr, c, pc, &operand2) < 0) + return -1; + } + + switch (BITS(this_instr, 21, 24)) { + case 0x0: /*and */ + result = operand1 & operand2; + break; + + case 0x1: /*eor */ + result = operand1 ^ operand2; + break; + + case 0x2: /*sub */ + result = operand1 - operand2; + break; + + case 0x3: /*rsb */ + result = operand2 - operand1; + break; + + case 0x4: /*add */ + result = operand1 + operand2; + break; + + case 0x5: /*adc */ + result = operand1 + operand2 + c; + break; + + case 0x6: /*sbc */ + result = operand1 - operand2 + c; + break; + + case 0x7: /*rsc */ + result = operand2 - operand1 + c; + break; + + case 0x8: + case 0x9: + case 0xa: + case 0xb: /* tst, teq, cmp, cmn */ + /* Only take the default branch. */ + result = 0; + break; + + case 0xc: /*orr */ + result = operand1 | operand2; + break; + + case 0xd: /*mov */ + /* Always step into a function. */ + result = operand2; + break; + + case 0xe: /*bic */ + result = operand1 & ~operand2; + break; + + case 0xf: /*mvn */ + result = ~operand2; + break; + } + + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)result; + break; + + case 0x4: + case 0x5: /* data transfer */ + case 0x6: + case 0x7: + /* Ignore if insn isn't load or Rn not PC. */ + if (!BIT(this_instr, 20) + || BITS(this_instr, 12, 15) != ARM_REG_PC) + break; + + if (BIT(this_instr, 22)) + goto invalid; + + /* byte write to PC */ + uint32_t base; + if (arm_get_register_offpc + (proc, BITS(this_instr, 16, 19), &base) < 0) + return -1; + + if (BIT(this_instr, 24)) { + /* pre-indexed */ + int c = (status & FLAG_C) ? 1 : 0; + uint32_t offset; + if (BIT(this_instr, 25)) { + if (arm_get_shifted_register + (proc, this_instr, c, + pc, &offset) < 0) + return -1; + } else { + offset = BITS(this_instr, 0, 11); + } + + if (BIT(this_instr, 23)) + base += offset; + else + base -= offset; + } + + /* XXX two double casts. */ + uint32_t next; + if (proc_read_32(proc, (arch_addr_t)base, &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + break; + + case 0x8: + case 0x9: /* block transfer */ + if (!BIT(this_instr, 20)) + break; + /* LDM */ + if (BIT(this_instr, 15)) { + /* Loading pc. */ + int offset = 0; + enum arm_register rn = BITS(this_instr, 16, 19); + uint32_t rn_val; + if (arm_get_register(proc, rn, &rn_val) < 0) + return -1; + + int pre = BIT(this_instr, 24); + if (BIT(this_instr, 23)) { + /* Bit U = up. */ + unsigned reglist + = BITS(this_instr, 0, 14); + offset = bitcount(reglist) * 4; + if (pre) + offset += 4; + } else if (pre) { + offset = -4; + } + + /* XXX double cast. */ + arch_addr_t addr + = (arch_addr_t)(rn_val + offset); + uint32_t next; + if (proc_read_32(proc, addr, &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + } + break; + + case 0xb: /* branch & link */ + case 0xa: /* branch */ + next_pcs[nr++] = arm_branch_dest(pc, this_instr); + break; + + case 0xc: + case 0xd: + case 0xe: /* coproc ops */ + case 0xf: /* SWI */ + break; + } + + /* Otherwise take the next instruction. */ + if (cond != COND_ALWAYS || nr == 0) + next_pcs[nr++] = pc + 4; + return 0; +} + +/* Return the size in bytes of the complete Thumb instruction whose + * first halfword is INST1. */ + +static int +thumb_insn_size (unsigned short inst1) +{ + if ((inst1 & 0xe000) == 0xe000 && (inst1 & 0x1800) != 0) + return 4; + else + return 2; +} + +static int +thumb_get_next_pcs(struct process *proc, + const arch_addr_t pc, arch_addr_t next_pcs[2]) +{ + uint16_t inst1; + uint32_t status; + if (proc_read_16(proc, pc, &inst1) < 0 + || arm_get_register(proc, ARM_REG_CPSR, &status) < 0) + return -1; + + int nr = 0; + + /* We currently ignore Thumb-2 conditional execution support + * (the IT instruction). No branches are allowed in IT block, + * and it's not legal to jump in the middle of it, so unless + * we need to singlestep through large swaths of code, which + * we currently don't, we can ignore them. */ + + if ((inst1 & 0xff00) == 0xbd00) { /* pop {rlist, pc} */ + /* Fetch the saved PC from the stack. It's stored + * above all of the other registers. */ + const unsigned offset = bitcount(BITS(inst1, 0, 7)) * 4; + uint32_t sp; + uint32_t next; + /* XXX two double casts */ + if (arm_get_register(proc, ARM_REG_SP, &sp) < 0 + || proc_read_32(proc, (arch_addr_t)(sp + offset), + &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + } else if ((inst1 & 0xf000) == 0xd000) { /* conditional branch */ + const unsigned long cond = BITS(inst1, 8, 11); + if (cond != 0x0f) { /* SWI */ + next_pcs[nr++] = pc + (SBITS(inst1, 0, 7) << 1); + if (cond == COND_ALWAYS) + return 0; + } + } else if ((inst1 & 0xf800) == 0xe000) { /* unconditional branch */ + next_pcs[nr++] = pc + (SBITS(inst1, 0, 10) << 1); + } else if (thumb_insn_size(inst1) == 4) { /* 32-bit instruction */ + unsigned short inst2; + if (proc_read_16(proc, pc + 2, &inst2) < 0) + return -1; + + if ((inst1 & 0xf800) == 0xf000 && (inst2 & 0x8000) == 0x8000) { + /* Branches and miscellaneous control instructions. */ + + if ((inst2 & 0x1000) != 0 + || (inst2 & 0xd001) == 0xc000) { + /* B, BL, BLX. */ + + const int imm1 = SBITS(inst1, 0, 10); + const unsigned imm2 = BITS(inst2, 0, 10); + const unsigned j1 = BIT(inst2, 13); + const unsigned j2 = BIT(inst2, 11); + + int32_t offset + = ((imm1 << 12) + (imm2 << 1)); + offset ^= ((!j2) << 22) | ((!j1) << 23); + + /* XXX double cast */ + uint32_t next = (uint32_t)(pc + offset); + /* For BLX make sure to clear the low bits. */ + if (BIT(inst2, 12) == 0) + next = next & 0xfffffffc; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + return 0; + } else if (inst1 == 0xf3de + && (inst2 & 0xff00) == 0x3f00) { + /* SUBS PC, LR, #imm8. */ + uint32_t next; + if (arm_get_register(proc, ARM_REG_LR, + &next) < 0) + return -1; + next -= inst2 & 0x00ff; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + return 0; + } else if ((inst2 & 0xd000) == 0x8000 + && (inst1 & 0x0380) != 0x0380) { + /* Conditional branch. */ + const int sign = SBITS(inst1, 10, 10); + const unsigned imm1 = BITS(inst1, 0, 5); + const unsigned imm2 = BITS(inst2, 0, 10); + const unsigned j1 = BIT(inst2, 13); + const unsigned j2 = BIT(inst2, 11); + + int32_t offset = (sign << 20) + + (j2 << 19) + (j1 << 18); + offset += (imm1 << 12) + (imm2 << 1); + next_pcs[nr++] = pc + offset; + if (BITS(inst1, 6, 9) == COND_ALWAYS) + return 0; + } + } else if ((inst1 & 0xfe50) == 0xe810) { + int load_pc = 1; + int offset; + const enum arm_register rn = BITS(inst1, 0, 3); + + if (BIT(inst1, 7) && !BIT(inst1, 8)) { + /* LDMIA or POP */ + if (!BIT(inst2, 15)) + load_pc = 0; + offset = bitcount(inst2) * 4 - 4; + } else if (!BIT(inst1, 7) && BIT(inst1, 8)) { + /* LDMDB */ + if (!BIT(inst2, 15)) + load_pc = 0; + offset = -4; + } else if (BIT(inst1, 7) && BIT(inst1, 8)) { + /* RFEIA */ + offset = 0; + } else if (!BIT(inst1, 7) && !BIT(inst1, 8)) { + /* RFEDB */ + offset = -8; + } else { + load_pc = 0; + } + + if (load_pc) { + uint32_t addr; + if (arm_get_register(proc, rn, &addr) < 0) + return -1; + arch_addr_t a = (arch_addr_t)(addr + offset); + uint32_t next; + if (proc_read_32(proc, a, &next) < 0) + return -1; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + } + } else if ((inst1 & 0xffef) == 0xea4f + && (inst2 & 0xfff0) == 0x0f00) { + /* MOV PC or MOVS PC. */ + const enum arm_register rn = BITS(inst2, 0, 3); + uint32_t next; + if (arm_get_register(proc, rn, &next) < 0) + return -1; + /* XXX double cast */ + next_pcs[nr++] = (arch_addr_t)next; + } else if ((inst1 & 0xff70) == 0xf850 + && (inst2 & 0xf000) == 0xf000) { + /* LDR PC. */ + const enum arm_register rn = BITS(inst1, 0, 3); + uint32_t base; + if (arm_get_register(proc, rn, &base) < 0) + return -1; + + int load_pc = 1; + if (rn == ARM_REG_PC) { + base = (base + 4) & ~(uint32_t)0x3; + if (BIT(inst1, 7)) + base += BITS(inst2, 0, 11); + else + base -= BITS(inst2, 0, 11); + } else if (BIT(inst1, 7)) { + base += BITS(inst2, 0, 11); + } else if (BIT(inst2, 11)) { + if (BIT(inst2, 10)) { + if (BIT(inst2, 9)) + base += BITS(inst2, 0, 7); + else + base -= BITS(inst2, 0, 7); + } + } else if ((inst2 & 0x0fc0) == 0x0000) { + const int shift = BITS(inst2, 4, 5); + const enum arm_register rm = BITS(inst2, 0, 3); + uint32_t v; + if (arm_get_register(proc, rm, &v) < 0) + return -1; + base += v << shift; + } else { + /* Reserved. */ + load_pc = 0; + } + + if (load_pc) { + /* xxx double casts */ + uint32_t next; + if (proc_read_32(proc, + (arch_addr_t)base, &next) < 0) + return -1; + next_pcs[nr++] = (arch_addr_t)next; + } + } else if ((inst1 & 0xfff0) == 0xe8d0 + && (inst2 & 0xfff0) == 0xf000) { + /* TBB. */ + const enum arm_register tbl_reg = BITS(inst1, 0, 3); + const enum arm_register off_reg = BITS(inst2, 0, 3); + + uint32_t table; + if (tbl_reg == ARM_REG_PC) + /* Regcache copy of PC isn't right yet. */ + /* XXX double cast */ + table = (uint32_t)pc + 4; + else if (arm_get_register(proc, tbl_reg, &table) < 0) + return -1; + + uint32_t offset; + if (arm_get_register(proc, off_reg, &offset) < 0) + return -1; + + table += offset; + uint8_t length; + /* XXX double cast */ + if (proc_read_8(proc, (arch_addr_t)table, &length) < 0) + return -1; + + next_pcs[nr++] = pc + 2 * length; + + } else if ((inst1 & 0xfff0) == 0xe8d0 + && (inst2 & 0xfff0) == 0xf010) { + /* TBH. */ + const enum arm_register tbl_reg = BITS(inst1, 0, 3); + const enum arm_register off_reg = BITS(inst2, 0, 3); + + uint32_t table; + if (tbl_reg == ARM_REG_PC) + /* Regcache copy of PC isn't right yet. */ + /* XXX double cast */ + table = (uint32_t)pc + 4; + else if (arm_get_register(proc, tbl_reg, &table) < 0) + return -1; + + uint32_t offset; + if (arm_get_register(proc, off_reg, &offset) < 0) + return -1; + + table += 2 * offset; + uint16_t length; + /* XXX double cast */ + if (proc_read_16(proc, (arch_addr_t)table, &length) < 0) + return -1; + + next_pcs[nr++] = pc + 2 * length; } - } else { - fprintf(stderr, "gimme_arg called with wrong arguments\n"); - exit(1); } + + /* Otherwise take the next instruction. */ + if (nr == 0) + next_pcs[nr++] = pc + thumb_insn_size(inst1); return 0; } + +enum sw_singlestep_status +arch_sw_singlestep(struct process *proc, struct breakpoint *sbp, + int (*add_cb)(arch_addr_t, struct sw_singlestep_data *), + struct sw_singlestep_data *add_cb_data) +{ + const arch_addr_t pc = get_instruction_pointer(proc); + + uint32_t cpsr; + if (arm_get_register(proc, ARM_REG_CPSR, &cpsr) < 0) + return SWS_FAIL; + + const unsigned thumb_p = BIT(cpsr, 5); + arch_addr_t next_pcs[2] = {}; + if ((thumb_p ? &thumb_get_next_pcs + : &arm_get_next_pcs)(proc, pc, next_pcs) < 0) + return SWS_FAIL; + + int i; + for (i = 0; i < 2; ++i) { + /* XXX double cast. */ + arch_addr_t target + = (arch_addr_t)(((uintptr_t)next_pcs[i]) | thumb_p); + if (next_pcs[i] != 0 && add_cb(target, add_cb_data) < 0) + return SWS_FAIL; + } + + debug(1, "PTRACE_CONT"); + ptrace(PTRACE_CONT, proc->pid, 0, 0); + return SWS_OK; +} + +size_t +arch_type_sizeof(struct process *proc, struct arg_type_info *info) +{ + if (proc == NULL) + return (size_t)-2; + + switch (info->type) { + case ARGTYPE_VOID: + return 0; + + case ARGTYPE_CHAR: + return 1; + + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + return 2; + + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + case ARGTYPE_POINTER: + return 4; + + case ARGTYPE_FLOAT: + return 4; + case ARGTYPE_DOUBLE: + return 8; + + case ARGTYPE_ARRAY: + case ARGTYPE_STRUCT: + /* Use default value. */ + return (size_t)-2; + + default: + assert(info->type != info->type); + abort(); + } +} + +size_t +arch_type_alignof(struct process *proc, struct arg_type_info *info) +{ + return arch_type_sizeof(proc, info); +} diff --git a/sysdeps/linux-gnu/breakpoint.c b/sysdeps/linux-gnu/breakpoint.c index fe336b1..c28d745 100644 --- a/sysdeps/linux-gnu/breakpoint.c +++ b/sysdeps/linux-gnu/breakpoint.c @@ -79,7 +79,7 @@ arch_enable_breakpoint(pid_t pid, struct breakpoint *sbp) #endif /* ARCH_HAVE_ENABLE_BREAKPOINT */ void -enable_breakpoint(Process *proc, struct breakpoint *sbp) +enable_breakpoint(struct process *proc, struct breakpoint *sbp) { debug(DEBUG_PROCESS, "enable_breakpoint: pid=%d, addr=%p, symbol=%s", proc->pid, sbp->addr, breakpoint_name(sbp)); @@ -127,7 +127,7 @@ arch_disable_breakpoint(pid_t pid, const struct breakpoint *sbp) #endif /* ARCH_HAVE_DISABLE_BREAKPOINT */ void -disable_breakpoint(Process *proc, struct breakpoint *sbp) +disable_breakpoint(struct process *proc, struct breakpoint *sbp) { debug(DEBUG_PROCESS, "disable_breakpoint: pid=%d, addr=%p, symbol=%s", proc->pid, sbp->addr, breakpoint_name(sbp)); diff --git a/sysdeps/linux-gnu/cris/plt.c b/sysdeps/linux-gnu/cris/plt.c index 427ae93..fcc18d2 100644 --- a/sysdeps/linux-gnu/cris/plt.c +++ b/sysdeps/linux-gnu/cris/plt.c @@ -28,7 +28,7 @@ GElf_Addr arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) return lte->plt_addr + 0x20 + (ndx * 26); } -void *sym2addr(Process *proc, struct library_symbol *sym) +void *sym2addr(struct process *proc, struct library_symbol *sym) { return sym->enter_addr; } diff --git a/sysdeps/linux-gnu/cris/regs.c b/sysdeps/linux-gnu/cris/regs.c index 7028b9e..4cf1fbf 100644 --- a/sysdeps/linux-gnu/cris/regs.c +++ b/sysdeps/linux-gnu/cris/regs.c @@ -37,22 +37,22 @@ # define PTRACE_POKEUSER PTRACE_POKEUSR #endif -void *get_instruction_pointer(Process *proc) +void *get_instruction_pointer(struct process *proc) { return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 4 * PT_PPC, 0); } -void set_instruction_pointer(Process *proc, void *addr) +void set_instruction_pointer(struct process *proc, void *addr) { ptrace(PTRACE_POKEUSER, proc->pid, 4 * PT_PPC, addr); } -void *get_stack_pointer(Process *proc) +void *get_stack_pointer(struct process *proc) { return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 4 * PT_USP, 0); } -void *get_return_addr(Process *proc, void *stack_pointer) +void *get_return_addr(struct process *proc, void *stack_pointer) { return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 4 * PT_SRP, 0); } diff --git a/sysdeps/linux-gnu/cris/trace.c b/sysdeps/linux-gnu/cris/trace.c index 98cb7d8..7d8cca6 100644 --- a/sysdeps/linux-gnu/cris/trace.c +++ b/sysdeps/linux-gnu/cris/trace.c @@ -40,14 +40,14 @@ # define PTRACE_POKEUSER PTRACE_POKEUSR #endif -void get_arch_dep(Process *proc) +void get_arch_dep(struct process *proc) { } /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ #define SYSCALL_INSN 0xe93d -int syscall_p(Process *proc, int status, int *sysnum) +int syscall_p(struct process *proc, int status, int *sysnum) { if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { @@ -71,7 +71,7 @@ int syscall_p(Process *proc, int status, int *sysnum) return 0; } -long gimme_arg(enum tof type, Process *proc, int arg_num, +long gimme_arg(enum tof type, struct process *proc, int arg_num, struct arg_type_info *info) { int pid = proc->pid; diff --git a/sysdeps/linux-gnu/events.c b/sysdeps/linux-gnu/events.c index 077a656..51c2cae 100644 --- a/sysdeps/linux-gnu/events.c +++ b/sysdeps/linux-gnu/events.c @@ -48,7 +48,7 @@ static Event * delayed_events = NULL; static Event * end_delayed_events = NULL; static enum callback_status -first (Process * proc, void * data) +first(struct process *proc, void *data) { return CBS_STOP; } @@ -83,12 +83,12 @@ each_qd_event(enum ecb_status (*pred)(Event *, void *), void * data) Event * event; for (event = prev; event != NULL; ) { switch ((*pred)(event, data)) { - case ecb_cont: + case ECB_CONT: prev = event; event = event->next; continue; - case ecb_deque: + case ECB_DEQUE: debug(DEBUG_FUNCTION, "dequeuing event %d for %d", event->type, event->proc != NULL ? event->proc->pid : -1); @@ -106,7 +106,7 @@ each_qd_event(enum ecb_status (*pred)(Event *, void *), void * data) end_delayed_events = NULL; /* fall-through */ - case ecb_yield: + case ECB_YIELD: return event; } } @@ -120,9 +120,9 @@ event_process_not_reenabling(Event * event, void * data) if (event->proc == NULL || event->proc->leader == NULL || event->proc->leader->event_handler == NULL) - return ecb_deque; + return ECB_DEQUE; else - return ecb_cont; + return ECB_CONT; } static Event * @@ -188,7 +188,7 @@ next_event(void) * now) the pain of figuring this out all over again. * Petr Machata 2011-11-22. */ int i = 0; - for (; i < 100 && process_status(pid) != ps_tracing_stop; ++i) { + for (; i < 100 && process_status(pid) != PS_TRACING_STOP; ++i) { debug(2, "waiting for %d to stop", pid); usleep(10000); } @@ -197,9 +197,10 @@ next_event(void) debug(DEBUG_EVENT, "event: NEW: pid=%d", pid); return &event; } + get_arch_dep(event.proc); debug(3, "event from pid %u", pid); - Process *leader = event.proc->leader; + struct process *leader = event.proc->leader; /* The process should be stopped after the waitpid call. But * when the whole thread group is terminated, we see @@ -313,7 +314,7 @@ next_event(void) actually seen this on an Itanium machine on RHEL 5, I don't remember the exact kernel version anymore. ia64-sigill.s in the test suite tests this. Petr Machata 2011-06-08. */ - void * break_address + arch_addr_t break_address = event.proc->instruction_pointer - DECR_PC_AFTER_BREAK; if ((stop_signal == SIGSEGV || stop_signal == SIGILL) && leader != NULL @@ -341,13 +342,13 @@ static enum ecb_status event_for_proc(struct Event *event, void *data) { if (event->proc == data) - return ecb_deque; + return ECB_DEQUE; else - return ecb_cont; + return ECB_CONT; } void -delete_events_for(struct Process *proc) +delete_events_for(struct process *proc) { struct Event *event; while ((event = each_qd_event(&event_for_proc, proc)) != NULL) diff --git a/sysdeps/linux-gnu/events.h b/sysdeps/linux-gnu/events.h index 3802aff..51ef309 100644 --- a/sysdeps/linux-gnu/events.h +++ b/sysdeps/linux-gnu/events.h @@ -26,16 +26,16 @@ /* Declarations for event que functions. */ enum ecb_status { - ecb_cont, /* The iteration should continue. */ - ecb_yield, /* The iteration should stop, yielding this + ECB_CONT, /* The iteration should continue. */ + ECB_YIELD, /* The iteration should stop, yielding this * event. */ - ecb_deque, /* Like ecb_stop, but the event should be removed + ECB_DEQUE, /* Like ECB_STOP, but the event should be removed * from the queue. */ }; struct Event *each_qd_event(enum ecb_status (*cb)(struct Event *event, void *data), void *data); -void delete_events_for(struct Process * proc); +void delete_events_for(struct process *proc); void enque_event(struct Event *event); #endif /* SYSDEPS_LINUX_GNU_EVENTS_H */ diff --git a/sysdeps/linux-gnu/hooks.c b/sysdeps/linux-gnu/hooks.c new file mode 100644 index 0000000..3fb3614 --- /dev/null +++ b/sysdeps/linux-gnu/hooks.c @@ -0,0 +1,213 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012, 2013 Petr Machata + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#define _POSIX_C_SOURCE 200809L +#include <sys/types.h> +#include <alloca.h> +#include <errno.h> +#include <pwd.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "backend.h" +#include "dict.h" +#include "options.h" +#include "sysdep.h" +#include "vect.h" + +static char * +append(const char *str1, const char *str2) +{ + char *ret = malloc(strlen(str1) + strlen(str2) + 2); + if (ret == NULL) + return ret; + strcpy(stpcpy(ret, str1), str2); + return ret; +} + +static void +add_dir(struct vect *dirs, const char *str1, const char *str2) +{ + char *dir = append(str1, str2); + if (dir != NULL + && VECT_PUSHBACK(dirs, &dir) < 0) + fprintf(stderr, + "Couldn't store candidate config directory %s%s: %s.\n", + str1, str2, strerror(errno)); +} + +static enum callback_status +add_dir_component_cb(struct opt_F_t *entry, void *data) +{ + struct vect *dirs = data; + if (opt_F_get_kind(entry) == OPT_F_DIR) + add_dir(dirs, entry->pathname, "/ltrace"); + return CBS_CONT; +} + +static void +destroy_opt_F_cb(struct opt_F_t *entry, void *data) +{ + opt_F_destroy(entry); +} + +static char *g_home_dir = NULL; + +int +os_get_config_dirs(int private, const char ***retp) +{ + /* Vector of char *. Contains first pointers to local paths, + * then NULL, then pointers to system paths, then another + * NULL. SYS_START points to the beginning of the second + * part. */ + static struct vect dirs; + static ssize_t sys_start = 0; + +again: + if (sys_start != 0) { + if (sys_start == -1) + return -1; + + if (retp != NULL) { + if (private) + *retp = VECT_ELEMENT(&dirs, const char *, 0); + else + *retp = VECT_ELEMENT(&dirs, const char *, + (size_t)sys_start); + } + + return 0; + } + + VECT_INIT(&dirs, char *); + + char *home = getenv("HOME"); + if (home == NULL) { + struct passwd *pwd = getpwuid(getuid()); + if (pwd != NULL) + home = pwd->pw_dir; + } + + size_t home_len = home != NULL ? strlen(home) : 0; + + /* The values coming from getenv and getpwuid may not be + * persistent. */ + if (home != NULL) { + free(g_home_dir); + g_home_dir = strdup(home); + if (g_home_dir != NULL) { + home = g_home_dir; + } else { + char *tmp = alloca(home_len + 1); + strcpy(tmp, home); + home = tmp; + } + } + + char *xdg_home = getenv("XDG_CONFIG_HOME"); + if (xdg_home == NULL && home != NULL) { + xdg_home = alloca(home_len + sizeof "/.config"); + sprintf(xdg_home, "%s/.config", home); + } + if (xdg_home != NULL) + add_dir(&dirs, xdg_home, "/ltrace"); + if (home != NULL) + add_dir(&dirs, home, "/.ltrace"); + + char *delim = NULL; + if (VECT_PUSHBACK(&dirs, &delim) < 0) { + fail: + /* This can't work :( */ + fprintf(stderr, + "Couldn't initialize list of config directories: %s.\n", + strerror(errno)); + VECT_DESTROY(&dirs, const char *, dict_dtor_string, NULL); + sys_start = -1; + return -1; + } + sys_start = vect_size(&dirs); + + /* """preference-ordered set of base directories to search for + * configuration files in addition to the $XDG_CONFIG_HOME + * base directory. The directories in $XDG_CONFIG_DIRS should + * be seperated with a colon ':'.""" */ + char *xdg_sys = getenv("XDG_CONFIG_DIRS"); + if (xdg_sys != NULL) { + struct vect v; + VECT_INIT(&v, struct opt_F_t); + if (parse_colon_separated_list(xdg_sys, &v) < 0 + || VECT_EACH(&v, struct opt_F_t, NULL, + add_dir_component_cb, &dirs) != NULL) + fprintf(stderr, + "Error processing $XDG_CONFIG_DIRS '%s': %s\n", + xdg_sys, strerror(errno)); + VECT_DESTROY(&v, struct opt_F_t, destroy_opt_F_cb, NULL); + } + + /* PKGDATADIR is passed via -D when compiling. */ + const char *pkgdatadir = PKGDATADIR; + if (pkgdatadir != NULL) + add_dir(&dirs, pkgdatadir, ""); + + if (VECT_PUSHBACK(&dirs, &delim) < 0) + goto fail; + + goto again; +} + +int +os_get_ltrace_conf_filenames(struct vect *retp) +{ + char *homepath = NULL; + char *syspath = NULL; + +#define FN ".ltrace.conf" + if (g_home_dir == NULL) + os_get_config_dirs(0, NULL); + + if (g_home_dir != NULL) { + homepath = malloc(strlen(g_home_dir) + 1 + sizeof FN); + if (homepath == NULL + || sprintf(homepath, "%s/%s", g_home_dir, FN) < 0) { + fail: + free(syspath); + free(homepath); + return -1; + } + } + + /* SYSCONFDIR is passed via -D when compiling. */ + const char *sysconfdir = SYSCONFDIR; + if (sysconfdir != NULL && *sysconfdir != '\0') { + /* No +1, we skip the initial period. */ + syspath = malloc(strlen(sysconfdir) + sizeof FN); + if (syspath == NULL + || sprintf(syspath, "%s/%s", sysconfdir, FN + 1) < 0) + goto fail; + } + + if (VECT_PUSHBACK(retp, &homepath) < 0 + || VECT_PUSHBACK(retp, &syspath) < 0) + goto fail; + + return 0; +} diff --git a/sysdeps/linux-gnu/ia64/fetch.c b/sysdeps/linux-gnu/ia64/fetch.c index 54dc5b8..171c7a2 100644 --- a/sysdeps/linux-gnu/ia64/fetch.c +++ b/sysdeps/linux-gnu/ia64/fetch.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2008,2009 Juan Cespedes * Copyright (C) 2006 Steve Fink * Copyright (C) 2006 Ian Wienand @@ -63,7 +63,7 @@ union cfm_t { }; static int -fetch_context_init(struct Process *proc, struct fetch_context *context) +fetch_context_init(struct process *proc, struct fetch_context *context) { context->slot_n = 0; context->flt = 8; @@ -76,7 +76,7 @@ fetch_context_init(struct Process *proc, struct fetch_context *context) } struct fetch_context * -arch_fetch_arg_init(enum tof type, struct Process *proc, +arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { struct fetch_context *context = malloc(sizeof(*context)); @@ -91,7 +91,7 @@ arch_fetch_arg_init(enum tof type, struct Process *proc, } struct fetch_context * -arch_fetch_arg_clone(struct Process *proc, +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) { struct fetch_context *clone = malloc(sizeof(*context)); @@ -102,7 +102,7 @@ arch_fetch_arg_clone(struct Process *proc, } int -allocate_stack_slot(struct fetch_context *ctx, struct Process *proc, +allocate_stack_slot(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep) { size_t al = type_alignof(proc, info); @@ -121,7 +121,7 @@ allocate_stack_slot(struct fetch_context *ctx, struct Process *proc, } static int -allocate_reg(struct fetch_context *ctx, struct Process *proc, +allocate_reg(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep) { if (ctx->slot_n >= 8) @@ -152,7 +152,7 @@ allocate_reg(struct fetch_context *ctx, struct Process *proc, } static int -copy_aggregate_part(struct fetch_context *ctx, struct Process *proc, +copy_aggregate_part(struct fetch_context *ctx, struct process *proc, unsigned char *buf, size_t size) { size_t slots = (size + 7) / 8; @@ -176,7 +176,7 @@ copy_aggregate_part(struct fetch_context *ctx, struct Process *proc, } static int -allocate_arg(struct fetch_context *ctx, struct Process *proc, +allocate_arg(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep) { size_t sz = type_sizeof(proc, info); @@ -213,7 +213,7 @@ fpreg_to_double (struct ia64_fpreg *fp) { } static int -allocate_float(struct fetch_context *ctx, struct Process *proc, +allocate_float(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep, int take_slot) { @@ -249,39 +249,8 @@ allocate_float(struct fetch_context *ctx, struct Process *proc, return 0; } -static enum arg_type -get_hfa_type(struct arg_type_info *info, size_t *countp) -{ - size_t n = type_aggregate_size(info); - if (n == (size_t)-1) - return ARGTYPE_VOID; - - enum arg_type type = ARGTYPE_VOID; - *countp = 0; - - while (n-- > 0) { - struct arg_type_info *emt = type_element(info, n); - - enum arg_type emt_type = emt->type; - size_t emt_count = 1; - if (emt_type == ARGTYPE_STRUCT || emt_type == ARGTYPE_ARRAY) - emt_type = get_hfa_type(emt, &emt_count); - - if (type == ARGTYPE_VOID) { - if (emt_type != ARGTYPE_FLOAT - && emt_type != ARGTYPE_DOUBLE) - return ARGTYPE_VOID; - type = emt_type; - } - if (emt_type != type) - return ARGTYPE_VOID; - *countp += emt_count; - } - return type; -} - static int -allocate_hfa(struct fetch_context *ctx, struct Process *proc, +allocate_hfa(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep, enum arg_type hfa_type, size_t hfa_count) { @@ -366,7 +335,7 @@ allocate_hfa(struct fetch_context *ctx, struct Process *proc, } static int -allocate_ret(struct fetch_context *ctx, struct Process *proc, +allocate_ret(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep) { size_t sz = type_sizeof(proc, info); @@ -380,10 +349,11 @@ allocate_ret(struct fetch_context *ctx, struct Process *proc, * floating-point registers, beginning with f8. */ if (info->type == ARGTYPE_STRUCT || info->type == ARGTYPE_ARRAY) { size_t hfa_size; - enum arg_type hfa_type = get_hfa_type(info, &hfa_size); - if (hfa_type != ARGTYPE_VOID && hfa_size <= 8) + struct arg_type_info *hfa_info + = type_get_hfa_type(info, &hfa_size); + if (hfa_info != NULL && hfa_size <= 8) return allocate_hfa(ctx, proc, info, valuep, - hfa_type, hfa_size); + hfa_info->type, hfa_size); } /* Integers and pointers are passed in r8. 128-bit integers @@ -405,11 +375,11 @@ allocate_ret(struct fetch_context *ctx, struct Process *proc, int arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { switch (info->type) { - enum arg_type hfa_type; + struct arg_type_info *hfa_info; size_t hfa_size; case ARGTYPE_VOID: @@ -421,10 +391,10 @@ arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, return allocate_float(ctx, proc, info, valuep, 1); case ARGTYPE_STRUCT: - hfa_type = get_hfa_type(info, &hfa_size); - if (hfa_type != ARGTYPE_VOID) + hfa_info = type_get_hfa_type(info, &hfa_size); + if (hfa_info != NULL) return allocate_hfa(ctx, proc, info, valuep, - hfa_type, hfa_size); + hfa_info->type, hfa_size); /* Fall through. */ case ARGTYPE_CHAR: case ARGTYPE_SHORT: @@ -446,7 +416,7 @@ arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, int arch_fetch_retval(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { if (fetch_context_init(proc, ctx) < 0) diff --git a/sysdeps/linux-gnu/ia64/plt.c b/sysdeps/linux-gnu/ia64/plt.c index a29488f..f6bc939 100644 --- a/sysdeps/linux-gnu/ia64/plt.c +++ b/sysdeps/linux-gnu/ia64/plt.c @@ -68,12 +68,13 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } int -arch_translate_address_dyn(struct Process *proc, +arch_translate_address_dyn(struct process *proc, arch_addr_t addr, arch_addr_t *ret) { errno = 0; diff --git a/sysdeps/linux-gnu/ia64/regs.c b/sysdeps/linux-gnu/ia64/regs.c index cbc2744..67873ce 100644 --- a/sysdeps/linux-gnu/ia64/regs.c +++ b/sysdeps/linux-gnu/ia64/regs.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -34,7 +34,8 @@ #include "common.h" void * -get_instruction_pointer(Process *proc) { +get_instruction_pointer(struct process *proc) +{ unsigned long ip = ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IIP, 0); unsigned long slot = (ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0) >> 41) & 3; @@ -43,7 +44,8 @@ get_instruction_pointer(Process *proc) { } void -set_instruction_pointer(Process *proc, void *addr) { +set_instruction_pointer(struct process *proc, void *addr) +{ unsigned long newip = (unsigned long)addr; unsigned long slot = (unsigned long)addr & 0xf; @@ -59,7 +61,8 @@ set_instruction_pointer(Process *proc, void *addr) { } void * -get_stack_pointer(Process *proc) { +get_stack_pointer(struct process *proc) +{ long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_R12, 0); if (l == -1 && errno) return NULL; @@ -67,14 +70,10 @@ get_stack_pointer(Process *proc) { } void * -get_return_addr(Process *proc, void *stack_pointer) { +get_return_addr(struct process *proc, void *stack_pointer) +{ long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_B0, 0); if (l == -1 && errno) return NULL; return (void *)l; } - -void -set_return_addr(Process *proc, void *addr) { - ptrace(PTRACE_POKEUSER, proc->pid, PT_B0, addr); -} diff --git a/sysdeps/linux-gnu/ia64/trace.c b/sysdeps/linux-gnu/ia64/trace.c index e608275..9e554ef 100644 --- a/sysdeps/linux-gnu/ia64/trace.c +++ b/sysdeps/linux-gnu/ia64/trace.c @@ -70,7 +70,8 @@ union cfm_t { }; int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { long l = ptrace(PTRACE_PEEKUSER, proc->pid, PT_CR_IPSR, 0); @@ -141,5 +142,6 @@ syscall_p(Process *proc, int status, int *sysnum) { } void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ } diff --git a/sysdeps/linux-gnu/m68k/fetch.c b/sysdeps/linux-gnu/m68k/fetch.c index f6d8a0b..24bd8f0 100644 --- a/sysdeps/linux-gnu/m68k/fetch.c +++ b/sysdeps/linux-gnu/m68k/fetch.c @@ -43,7 +43,7 @@ struct fetch_context }; static int -fetch_register_banks(struct Process *proc, struct fetch_context *context, +fetch_register_banks(struct process *proc, struct fetch_context *context, int floating) { if (ptrace(PTRACE_GETREGS, proc->pid, 0, &context->regs) < 0) @@ -57,7 +57,7 @@ fetch_register_banks(struct Process *proc, struct fetch_context *context, } struct fetch_context * -arch_fetch_arg_init(enum tof type, struct Process *proc, +arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { struct fetch_context *context = malloc(sizeof(*context)); @@ -92,7 +92,7 @@ arch_fetch_arg_init(enum tof type, struct Process *proc, } struct fetch_context * -arch_fetch_arg_clone(struct Process *proc, struct fetch_context *context) +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) { struct fetch_context *ret = malloc(sizeof(*ret)); if (ret == NULL) @@ -103,7 +103,7 @@ arch_fetch_arg_clone(struct Process *proc, struct fetch_context *context) int arch_fetch_arg_next(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { size_t sz = type_sizeof(proc, info); @@ -143,7 +143,7 @@ arch_fetch_arg_next(struct fetch_context *context, enum tof type, int arch_fetch_retval(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { if (fetch_register_banks(proc, context, type == LT_TOF_FUNCTIONR) < 0) diff --git a/sysdeps/linux-gnu/m68k/plt.c b/sysdeps/linux-gnu/m68k/plt.c index c1b37dd..dcd6878 100644 --- a/sysdeps/linux-gnu/m68k/plt.c +++ b/sysdeps/linux-gnu/m68k/plt.c @@ -30,6 +30,7 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } diff --git a/sysdeps/linux-gnu/m68k/regs.c b/sysdeps/linux-gnu/m68k/regs.c index 4afdfbb..e25aefb 100644 --- a/sysdeps/linux-gnu/m68k/regs.c +++ b/sysdeps/linux-gnu/m68k/regs.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2002,2004,2008,2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -36,26 +37,25 @@ #endif void * -get_instruction_pointer(Process *proc) { +get_instruction_pointer(struct process *proc) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 4 * PT_PC, 0); } void -set_instruction_pointer(Process *proc, void *addr) { +set_instruction_pointer(struct process *proc, void *addr) +{ ptrace(PTRACE_POKEUSER, proc->pid, 4 * PT_PC, addr); } void * -get_stack_pointer(Process *proc) { +get_stack_pointer(struct process *proc) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, 4 * PT_USP, 0); } void * -get_return_addr(Process *proc, void *stack_pointer) { +get_return_addr(struct process *proc, void *stack_pointer) +{ return (void *)ptrace(PTRACE_PEEKTEXT, proc->pid, stack_pointer, 0); } - -void -set_return_addr(Process *proc, void *addr) { - ptrace(PTRACE_POKETEXT, proc->pid, proc->stack_pointer, addr); -} diff --git a/sysdeps/linux-gnu/m68k/trace.c b/sysdeps/linux-gnu/m68k/trace.c index 311ffd5..01b5253 100644 --- a/sysdeps/linux-gnu/m68k/trace.c +++ b/sysdeps/linux-gnu/m68k/trace.c @@ -40,13 +40,15 @@ #endif void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ } /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ int depth; if (WIFSTOPPED(status) diff --git a/sysdeps/linux-gnu/metag/Makefile.am b/sysdeps/linux-gnu/metag/Makefile.am new file mode 100644 index 0000000..a79d2f7 --- /dev/null +++ b/sysdeps/linux-gnu/metag/Makefile.am @@ -0,0 +1,16 @@ +noinst_LTLIBRARIES = \ + ../libcpu.la + +___libcpu_la_SOURCES = \ + plt.c \ + regs.c \ + trace.c + +noinst_HEADERS = \ + arch.h \ + ptrace.h \ + signalent.h \ + syscallent.h + +MAINTAINERCLEANFILES = \ + Makefile.in diff --git a/sysdeps/linux-gnu/metag/arch.h b/sysdeps/linux-gnu/metag/arch.h new file mode 100644 index 0000000..2699c62 --- /dev/null +++ b/sysdeps/linux-gnu/metag/arch.h @@ -0,0 +1,28 @@ +/* + * This file is part of ltrace. + * Copyright (C) 1998,2004,2008 Juan Cespedes + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#define LT_ELFCLASS ELFCLASS32 +#define LT_ELF_MACHINE EM_METAG + +#define BREAKPOINT_VALUE { 0x01, 0x00, 0x40, 0xAF } +#define BREAKPOINT_LENGTH 4 +#define DECR_PC_AFTER_BREAK 0 +#define ARCH_ENDIAN_LITTLE +#define ARCH_HAVE_SW_SINGLESTEP diff --git a/sysdeps/linux-gnu/metag/plt.c b/sysdeps/linux-gnu/metag/plt.c new file mode 100644 index 0000000..2d479a4 --- /dev/null +++ b/sysdeps/linux-gnu/metag/plt.c @@ -0,0 +1,38 @@ +/* + * This file is part of ltrace. + * + * Copyright (C) 2013 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <gelf.h> + +#include "debug.h" +#include "proc.h" +#include "library.h" +#include "ltrace-elf.h" + +GElf_Addr +arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) +{ + return lte->plt_addr + ( ndx * 0x14 ) + 0x14; +} + +void +*sym2addr(struct process *proc, struct library_symbol *sym) +{ + return sym->enter_addr; +} diff --git a/sysdeps/linux-gnu/metag/ptrace.h b/sysdeps/linux-gnu/metag/ptrace.h new file mode 100644 index 0000000..7a41e4a --- /dev/null +++ b/sysdeps/linux-gnu/metag/ptrace.h @@ -0,0 +1,21 @@ +/* + * This file is part of ltrace. + * + * Copyright (C) 2013 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> diff --git a/sysdeps/linux-gnu/metag/regs.c b/sysdeps/linux-gnu/metag/regs.c new file mode 100644 index 0000000..d4a6f2f --- /dev/null +++ b/sysdeps/linux-gnu/metag/regs.c @@ -0,0 +1,89 @@ +/* + * This file is part of ltrace. + * + * Copyright (C) 2013 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/ptrace.h> +#include <linux/uio.h> +#include <asm/ptrace.h> + +#include "proc.h" +#include "common.h" + +arch_addr_t +get_instruction_pointer(struct process *proc) +{ + struct user_gp_regs regs; + struct iovec iov; + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, (long)&iov)) + return (void *)-1; + + return (void *)regs.pc; /* PC */ +} + +void +set_instruction_pointer(struct process *proc, arch_addr_t addr) +{ + struct user_gp_regs regs; + struct iovec iov; + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, (long)&iov)) + return; + + regs.pc = (unsigned long)addr; + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + ptrace(PTRACE_SETREGSET, proc->pid, NT_PRSTATUS, (long)&iov); +} + +arch_addr_t +get_stack_pointer(struct process *proc) +{ + struct user_gp_regs regs; + struct iovec iov; + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, (long)&iov)) + return (void *)-1; + + return (void *)regs.ax[0][0]; /* A0StP (A0.0) */ +} + +arch_addr_t +get_return_addr(struct process *proc, void *stack_pointer) +{ + struct user_gp_regs regs; + struct iovec iov; + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, (long)&iov)) + return (void *)-1; + + return (void *)regs.dx[4][1]; /* D1RtP (D1.4) */ +} diff --git a/sysdeps/linux-gnu/metag/signalent.h b/sysdeps/linux-gnu/metag/signalent.h new file mode 100644 index 0000000..80228b7 --- /dev/null +++ b/sysdeps/linux-gnu/metag/signalent.h @@ -0,0 +1,53 @@ +/* + * This file is part of ltrace. + * + * Copyright (C) 2013 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + "SIG_0", /* 0 */ + "SIGHUP", /* 1 */ + "SIGINT", /* 2 */ + "SIGQUIT", /* 3 */ + "SIGILL", /* 4 */ + "SIGTRAP", /* 5 */ + "SIGABRT", /* 6 */ + "SIGBUS", /* 7 */ + "SIGFPE", /* 8 */ + "SIGKILL", /* 9 */ + "SIGUSR1", /* 10 */ + "SIGSEGV", /* 11 */ + "SIGUSR2", /* 12 */ + "SIGPIPE", /* 13 */ + "SIGALRM", /* 14 */ + "SIGTERM", /* 15 */ + "SIGSTKFLT", /* 16 */ + "SIGCHLD", /* 17 */ + "SIGCONT", /* 18 */ + "SIGSTOP", /* 19 */ + "SIGTSTP", /* 20 */ + "SIGTTIN", /* 21 */ + "SIGTTOU", /* 22 */ + "SIGURG", /* 23 */ + "SIGXCPU", /* 24 */ + "SIGXFSZ", /* 25 */ + "SIGVTALRM", /* 26 */ + "SIGPROF", /* 27 */ + "SIGWINCH", /* 28 */ + "SIGIO", /* 29 */ + "SIGPWR", /* 30 */ + "SIGSYS", /* 31 */ + "SIGRTMIN", /* 32 */ diff --git a/sysdeps/linux-gnu/metag/syscallent.h b/sysdeps/linux-gnu/metag/syscallent.h new file mode 100644 index 0000000..447c550 --- /dev/null +++ b/sysdeps/linux-gnu/metag/syscallent.h @@ -0,0 +1,293 @@ +/* + * This file is part of ltrace. + * + * Copyright (C) 2013 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + "io_setup", /* 0 */ + "io_destroy", /* 1 */ + "io_submit", /* 2 */ + "io_cancel", /* 3 */ + "io_getevents", /* 4 */ + "setxattr", /* 5 */ + "lsetxattr", /* 6 */ + "fsetxattr", /* 7 */ + "getxattr", /* 8 */ + "lgetxattr", /* 9 */ + "fgetxattr", /* 10 */ + "listxattr", /* 11 */ + "llistxattr", /* 12 */ + "flistxattr", /* 13 */ + "removexattr", /* 14 */ + "lremovexattr", /* 15 */ + "fremovexattr", /* 16 */ + "getcwd", /* 17 */ + "lookup_dcookie", /* 18 */ + "eventfd2", /* 19 */ + "epoll_create1", /* 20 */ + "epoll_ctl", /* 21 */ + "epoll_pwait", /* 22 */ + "dup", /* 23 */ + "dup3", /* 24 */ + "fcntl64", /* 25 */ + "inotify_init1", /* 26 */ + "inotify_add_watch", /* 27 */ + "inotify_rm_watch", /* 28 */ + "ioctl", /* 29 */ + "ioprio_set", /* 30 */ + "ioprio_get", /* 31 */ + "flock", /* 32 */ + "mknodat", /* 33 */ + "mkdirat", /* 34 */ + "unlinkat", /* 35 */ + "symlinkat", /* 36 */ + "linkat", /* 37 */ + "renameat", /* 38 */ + "umount", /* 39 */ + "mount", /* 40 */ + "pivot_root", /* 41 */ + "42", /* 42 */ + "statfs64", /* 43 */ + "fstatfs64", /* 44 */ + "truncate64", /* 45 */ + "ftruncate64", /* 46 */ + "fallocate", /* 47 */ + "faccessat", /* 48 */ + "chdir", /* 49 */ + "fchdir", /* 50 */ + "chroot", /* 51 */ + "fchmod", /* 52 */ + "fchmodat", /* 53 */ + "fchownat", /* 54 */ + "fchown", /* 55 */ + "openat", /* 56 */ + "close", /* 57 */ + "vhangup", /* 58 */ + "pipe2", /* 59 */ + "quotactl", /* 60 */ + "getdents64", /* 61 */ + "llseek", /* 62 */ + "read", /* 63 */ + "write", /* 64 */ + "readv", /* 65 */ + "writev", /* 66 */ + "pread64", /* 67 */ + "pwrite64", /* 68 */ + "preadv", /* 69 */ + "pwritev", /* 70 */ + "sendfile64", /* 71 */ + "pselect6", /* 72 */ + "ppoll", /* 73 */ + "signalfd4", /* 74 */ + "vmsplice", /* 75 */ + "splice", /* 76 */ + "tee", /* 77 */ + "readlinkat", /* 78 */ + "fstatat64", /* 79 */ + "fstat64", /* 80 */ + "sync", /* 81 */ + "fsync", /* 82 */ + "fdatasync", /* 83 */ + "sync_file_range", /* 84 */ + "timerfd_create", /* 85 */ + "timerfd_settime", /* 86 */ + "timerfd_gettime", /* 87 */ + "utimensat", /* 88 */ + "acct", /* 89 */ + "capget", /* 90 */ + "capset", /* 91 */ + "personality", /* 92 */ + "exit", /* 93 */ + "exit_group", /* 94 */ + "waitid", /* 95 */ + "set_tid_address", /* 96 */ + "unshare", /* 97 */ + "futex", /* 98 */ + "set_robust_list", /* 99 */ + "get_robust_list", /* 100 */ + "nanosleep", /* 101 */ + "getitimer", /* 102 */ + "setitimer", /* 103 */ + "kexec_load", /* 104 */ + "init_module", /* 105 */ + "delete_module", /* 106 */ + "timer_create", /* 107 */ + "timer_gettime", /* 108 */ + "timer_getoverrun", /* 109 */ + "timer_settime", /* 110 */ + "timer_delete", /* 111 */ + "clock_settime", /* 112 */ + "clock_gettime", /* 113 */ + "clock_getres", /* 114 */ + "clock_nanosleep", /* 115 */ + "syslog", /* 116 */ + "ptrace", /* 117 */ + "sched_setparam", /* 118 */ + "sched_setscheduler", /* 119 */ + "sched_getscheduler", /* 120 */ + "sched_getparam", /* 121 */ + "sched_setaffinity", /* 122 */ + "sched_getaffinity", /* 123 */ + "sched_yield", /* 124 */ + "sched_get_priority_max", /* 125 */ + "sched_get_priority_min", /* 126 */ + "sched_rr_get_interval", /* 127 */ + "restart_syscall", /* 128 */ + "kill", /* 129 */ + "tkill", /* 130 */ + "tgkill", /* 131 */ + "sigaltstack", /* 132 */ + "rt_sigsuspend", /* 133 */ + "rt_sigaction", /* 134 */ + "rt_sigprocmask", /* 135 */ + "rt_sigpending", /* 136 */ + "rt_sigtimedwait", /* 137 */ + "rt_sigqueueinfo", /* 138 */ + "rt_sigreturn", /* 139 */ + "setpriority", /* 140 */ + "getpriority", /* 141 */ + "reboot", /* 142 */ + "setregid", /* 143 */ + "setgid", /* 144 */ + "setreuid", /* 145 */ + "setuid", /* 146 */ + "setresuid", /* 147 */ + "getresuid", /* 148 */ + "setresgid", /* 149 */ + "getresgid", /* 150 */ + "setfsuid", /* 151 */ + "setfsgid", /* 152 */ + "times", /* 153 */ + "setpgid", /* 154 */ + "getpgid", /* 155 */ + "getsid", /* 156 */ + "setsid", /* 157 */ + "getgroups", /* 158 */ + "setgroups", /* 159 */ + "newuname", /* 160 */ + "sethostname", /* 161 */ + "setdomainname", /* 162 */ + "getrlimit", /* 163 */ + "setrlimit", /* 164 */ + "getrusage", /* 165 */ + "umask", /* 166 */ + "prctl", /* 167 */ + "getcpu", /* 168 */ + "gettimeofday", /* 169 */ + "settimeofday", /* 170 */ + "adjtimex", /* 171 */ + "getpid", /* 172 */ + "getppid", /* 173 */ + "getuid", /* 174 */ + "geteuid", /* 175 */ + "getgid", /* 176 */ + "getegid", /* 177 */ + "gettid", /* 178 */ + "sysinfo", /* 179 */ + "mq_open", /* 180 */ + "mq_unlink", /* 181 */ + "mq_timedsend", /* 182 */ + "mq_timedreceive", /* 183 */ + "mq_notify", /* 184 */ + "mq_getsetattr", /* 185 */ + "msgget", /* 186 */ + "msgctl", /* 187 */ + "msgrcv", /* 188 */ + "msgsnd", /* 189 */ + "semget", /* 190 */ + "semctl", /* 191 */ + "semtimedop", /* 192 */ + "semop", /* 193 */ + "shmget", /* 194 */ + "shmctl", /* 195 */ + "shmat", /* 196 */ + "shmdt", /* 197 */ + "socket", /* 198 */ + "socketpair", /* 199 */ + "bind", /* 200 */ + "listen", /* 201 */ + "accept", /* 202 */ + "connect", /* 203 */ + "getsockname", /* 204 */ + "getpeername", /* 205 */ + "sendto", /* 206 */ + "recvfrom", /* 207 */ + "setsockopt", /* 208 */ + "getsockopt", /* 209 */ + "shutdown", /* 210 */ + "sendmsg", /* 211 */ + "recvmsg", /* 212 */ + "readahead", /* 213 */ + "brk", /* 214 */ + "munmap", /* 215 */ + "mremap", /* 216 */ + "add_key", /* 217 */ + "request_key", /* 218 */ + "keyctl", /* 219 */ + "clone", /* 220 */ + "execve", /* 221 */ + "mmap2", /* 222 */ + "fadvise64_64", /* 223 */ + "swapon", /* 224 */ + "swapoff", /* 225 */ + "mprotect", /* 226 */ + "msync", /* 227 */ + "mlock", /* 228 */ + "munlock", /* 229 */ + "mlockall", /* 230 */ + "munlockall", /* 231 */ + "mincore", /* 232 */ + "madvise", /* 233 */ + "remap_file_pages", /* 234 */ + "mbind", /* 235 */ + "get_mempolicy", /* 236 */ + "set_mempolicy", /* 237 */ + "migrate_pages", /* 238 */ + "move_pages", /* 239 */ + "rt_tgsigqueueinfo", /* 240 */ + "perf_event_open", /* 241 */ + "accept4", /* 242 */ + "recvmmsg", /* 243 */ + "244", /* 244 */ + "metag_setglobalbit", /* 245 */ + "metag_set_fpu_flags", /* 246 */ + "metag_set_tls", /* 247 */ + "metag_get_tls", /* 248 */ + "249", /* 249 */ + "250", /* 250 */ + "251", /* 251 */ + "252", /* 252 */ + "253", /* 253 */ + "254", /* 254 */ + "255", /* 255 */ + "256", /* 256 */ + "257", /* 257 */ + "258", /* 258 */ + "259", /* 259 */ + "wait4", /* 260 */ + "prlimit64", /* 261 */ + "fanotify_init", /* 262 */ + "fanotify_mark", /* 263 */ + "name_to_handle_at", /* 264 */ + "open_by_handle_at", /* 265 */ + "clock_adjtime", /* 266 */ + "syncfs", /* 267 */ + "setns", /* 268 */ + "sendmmsg", /* 269 */ + "process_vm_readv", /* 270 */ + "process_vm_writev", /* 271 */ + "kcmp", /* 272 */ diff --git a/sysdeps/linux-gnu/metag/trace.c b/sysdeps/linux-gnu/metag/trace.c new file mode 100644 index 0000000..ad5fffe --- /dev/null +++ b/sysdeps/linux-gnu/metag/trace.c @@ -0,0 +1,428 @@ +/* + * This file is part of ltrace. + * + * Copyright (C) 2013 Imagination Technologies Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <sys/ptrace.h> +#include <linux/uio.h> +#include <asm/ptrace.h> +#include <assert.h> + +#include "proc.h" +#include "common.h" + +#define METAG_INSN_SIZE 4 +#define N_UNITS 2 +#define REG_SIZE 4 + +/* unit codes */ +enum metag_unitnum { + METAG_UNIT_CT, /* 0x0 */ + METAG_UNIT_D0, + METAG_UNIT_D1, + METAG_UNIT_A0, + METAG_UNIT_A1, /* 0x4 */ + METAG_UNIT_PC, + METAG_UNIT_RA, + METAG_UNIT_TR, + METAG_UNIT_TT, /* 0x8 */ + METAG_UNIT_FX, + METAG_UNIT_MAX, +}; + +/** + \param proc The process that had an event. + + Called by \c next_event() right after the return from wait. + */ +void +get_arch_dep(struct process *proc) +{ + +} + +/** + \param proc Process that had event. + \param status From \c\ waitpid(). + \param sysnum 0-based syscall number. + \return 1 if syscall, 2 if sysret, 0 otherwise. + + Called by \c next_event() after the call to get_arch_dep(). + + */ +int +syscall_p(struct process *proc, int status, int *sysnum) +{ + if (WIFSTOPPED(status) + && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { + struct user_gp_regs regs; + struct iovec iov; + + /* Get GP registers. */ + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, + (long)&iov)) + return -1; + + /* Fetch the SWITCH instruction. */ + unsigned int insn = ptrace(PTRACE_PEEKTEXT, proc->pid, regs.pc, + 0); + *sysnum = regs.dx[0][1]; + + if (insn != 0xaf440001) { + /* Check if we're returning from the system call. */ + insn = ptrace(PTRACE_PEEKTEXT, proc->pid, regs.pc - 4, + 0); + if (insn == 0xaf440001) + return 2; + + return 0; + } + + if (*sysnum >= 0) + return 1; + } + return 0; +} + +/* 2-bit base unit (BU) mapping. */ +static enum metag_unitnum metag_bu_map[4] = { + METAG_UNIT_A1, + METAG_UNIT_D0, + METAG_UNIT_D1, + METAG_UNIT_A0, +}; + +static int +get_regval_from_unit(enum metag_unitnum unit, unsigned int reg, + struct user_gp_regs *regs) +{ + /* + * Check if reg has a sane value. + * We do have N_UNITS, each one having X registers + * and each register is REG_SIZE bytes. + */ + if ((unit == METAG_UNIT_A0) || (unit == METAG_UNIT_A1)) { + if (reg >= ((sizeof(regs->ax)/N_UNITS/REG_SIZE))) + goto bad_reg; + } else if ((unit == METAG_UNIT_D0) || (unit == METAG_UNIT_D1)) { + if (reg >= ((sizeof(regs->dx)/N_UNITS/REG_SIZE))) + goto bad_reg; + } + + switch(unit) { + case METAG_UNIT_A1: + return regs->ax[reg][1]; + case METAG_UNIT_D0: + return regs->dx[reg][0]; + case METAG_UNIT_D1: + return regs->dx[reg][1]; + case METAG_UNIT_A0: + return regs->ax[reg][0]; + /* We really shouldn't be here. */ + default: + assert(unit != unit); + abort(); + } + return 0; + +bad_reg: + fprintf(stderr, + "Reading from register %d of unit %d is not implemented.", + reg, unit); + return 0; + +} + +static int +metag_next_pcs(struct process *proc, uint32_t pc, uint32_t *newpc) +{ + uint32_t inst; + int nr = 0, imm, reg_val; + unsigned int unit = 0, reg; + struct user_gp_regs regs; + struct iovec iov; + + inst = ptrace(PTRACE_PEEKTEXT, proc->pid, pc, 0); + + if (inst == 0xa0fffffe) { /* NOP (Special branch instruction) */ + newpc[nr++] = pc + 4; + } else if ((inst & 0xff000000) == 0xa0000000) { + /* Matching 0xA 0x0 for opcode for B #S19 or B<cc> #S19. + * + * Potential Targets: + * - pc + #S19 * METAG_INSN_SIZE if R=1 or <CC> true + * - pc + 4 */ + imm = ((inst << 8) >> 13) * METAG_INSN_SIZE; + newpc[nr++] = pc + imm; + newpc[nr++] = pc + 4; + } else if ((inst & 0xff000000) == 0xac000000) { + /* Matching 0xA 0xC for opcode. + * JUMP BBx.r,#X16 or CALL BBx.r,#X16 + * + * pc = reg + #x16 (aligned) */ + imm = (inst >> 3) & 0xffff; + reg = (inst >> 19) & 0x1f; + unit = metag_bu_map[inst & 0x3]; + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, + NT_PRSTATUS, (long)&iov)) + goto ptrace_fail; + + reg_val = get_regval_from_unit(unit, reg, ®s); + newpc[nr++] = (reg_val + imm) & -METAG_INSN_SIZE; + } else if ((inst & 0xff000000) == 0xab000000) { + /* Matching 0xA 0xB for opcode. + * + * CALLR BBx.r,#S19 */ + imm = ((inst << 8) >> 13) * METAG_INSN_SIZE; + newpc[nr++] = pc + imm; + } else if ((inst & 0xff0001e0) == 0xa30000a0) { + /* + * Matching 0xA 0x3 for opcode and then + * Ud (bit 8-5) = 0x5 = METAG_UNIT_PC + * + * Potential MOV PC,.. or SWAP<cc> PC,.. or SWAP<cc> ..,PC + */ + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, + NT_PRSTATUS, (long)&iov)) + goto ptrace_fail; + + /* + * Maybe PC is the source register for a SWAP? + * bit9 = 1 and bit13-10(Us) == METAG_UNIT_PC + */ + if (((inst >> 9 ) & 0x1) && + (((inst >> 10) & 0xf) == METAG_UNIT_PC)) { + /* PC will get its value from the + * destination register. */ + reg = (inst >> 14) & 0x1f; + unit = (inst >> 5) & 0xf; + } else { /* PC is the destination register. + * Find the source register. */ + reg = (inst >> 19) & 0x1f; + unit = (inst >> 10) & 0xf; + } + + switch(unit) { + case METAG_UNIT_D0: + case METAG_UNIT_D1: + case METAG_UNIT_A0: + case METAG_UNIT_A1: + reg_val = get_regval_from_unit(unit, reg, ®s); + break; + case METAG_UNIT_PC: + reg_val = regs.pc; + break; + default: + goto unhandled; + } + newpc[nr++] = reg_val; + /* In case it is a conditional instruction. */ + newpc[nr++] = pc + 4; + } else if ((inst & 0xff00001f) == 0xc600000a){ + /* Matching 0xC 0x{4,6} for opcode + * and UD == 0x5 == METAG_UNIT_PC + * + * GETD PC, [A0.r + #S6] or + * GETD PC, [A0.r + A0.r] */ + unit = metag_bu_map[(inst >> 5) & 0x3]; + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + reg = (inst >> 14) & 0x1f; + imm = (inst << 18) >> 5; /* sign-extend it */ + if (ptrace(PTRACE_GETREGSET, proc->pid, + NT_PRSTATUS, (long)&iov)) + goto ptrace_fail; + reg_val = get_regval_from_unit(unit, reg, ®s) + imm; + /* See where reg_val actually points to. */ + newpc[nr++] = ptrace(PTRACE_PEEKTEXT, proc->pid, reg_val, 0); + } else if (((inst & 0xfe0001e0) == 0x840000a0) || /* ADDcc R, A, R */ + ((inst & 0xfe00003f) == 0x8600002a) || /* ADD R, A, #X8 */ + ((inst & 0xfe0001e0) == 0x8c0000a0) || /* SUBcc R, A, R */ + ((inst & 0xfe00003f) == 0x8e00002a) || /* SUB R, A, #X8 */ + ((inst & 0xf40001e0) == 0x040000a0) || /* ADDcc R, D, D */ + ((inst & 0xfe00003f) == 0x0600002a) || /* ADD R, D, #X8 */ + ((inst & 0xf40001e0) == 0x140000a0) || /* SUBcc R, D, D */ + ((inst & 0xf600003f) == 0x1600002a)) { /* SUB R, D, #X8 */ + + /* bits4-1(Ud) == METAG_UNIT_PC */ + + int src1, src2, pc_src1 = 0, pc_src2 = 0, is_aunit = 0; + int umask = 0, optype = 0; + + /* Look for O2R bit */ + if ((((inst >> 24) & 0x6) == 0x4) && (inst & 0x1)) + goto unhandled; + + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, + NT_PRSTATUS, (long)&iov)) + goto ptrace_fail; + + /* Figure out unit for source registers based on the opcode. */ + switch((inst >> 28) & 0xf) { + case 0: /* ADD<cc> Rx.r, Dx.r, De.r|#X8 */ + case 1: /* SUB<cc> Rx.r, Dx.r, De.r|#X8 */ + unit = METAG_UNIT_D0 + ((inst >> 24) & 0x1); + is_aunit = 0; + umask = 0x1f; + optype = (inst >> 28) & 0x1; + break; + case 8: + unit = METAG_UNIT_A0 + ((inst >> 24) & 0x1); + is_aunit = 1; + umask = 0xf; + optype = (inst >> 27) & 0x1; + break; + } + + /* Get pc bits (if any). */ + if (is_aunit) { + pc_src1 = (inst >> 18) & 0x1; + pc_src2 = (inst >> 13) & 0x1; + } + + /* Determine ADD|SUB format. Immediate or register ? */ + if ((inst >> 25) & 0x1) { /* ADD|SUB cc PC, X, #imm8 */ + src2 = (inst >> 6) & 0xff; /* so we can share code. */ + reg = (inst >> 14) & umask; + if (pc_src1) /* This can only be true for AU ops. */ + src1 = regs.pc; + else /* This covers both AU an DU ops. */ + src1 = get_regval_from_unit(unit, reg, ®s); + } else { /* ADD|SUB cc PC, X, X */ + if (pc_src1) + src1 = regs.pc; + else + src1 = get_regval_from_unit(unit, (inst >> 14) + & umask, ®s); + if (pc_src2) + src2 = regs.pc; + else + src2 = get_regval_from_unit(unit, (inst >> 9) + & umask, ®s); + } + + /* Construct the new PC. */ + if (optype) + /* SUB */ + newpc[nr++] = src1 - src2; + else /* ADD */ + newpc[nr++] = src1 + src2; + /* Conditional instruction so PC may not change. */ + newpc[nr++] = pc + 4; + } else { + newpc[nr++] = pc + 4; + } + + if (nr <= 0 || nr > 2) + goto fail; + if (nr == 2 && newpc[1] == 0) + goto fail; + + return nr; + +ptrace_fail: + fprintf(stderr, "Failed to read the registers pid=%d @ pc=0x%08x\n", + proc->pid, pc); + return 0; +unhandled: + fprintf(stderr, "Unhandled instruction: pc=0x%08x, inst=0x%08x\n", + pc, inst); + return 0; +fail: + fprintf(stderr, "nr=%d pc=0x%08x\n", nr, pc); + fprintf(stderr, "newpc=0x%08x 0x%08x\n", newpc[0], newpc[1]); + return 0; + +} + +enum sw_singlestep_status +arch_sw_singlestep(struct process *proc, struct breakpoint *bp, + int (*add_cb)(arch_addr_t, struct sw_singlestep_data *), + struct sw_singlestep_data *add_cb_data) +{ + arch_addr_t pc = get_instruction_pointer(proc); + uint32_t newpcs[2]; + int nr; + + nr = metag_next_pcs(proc, (uint32_t)pc, newpcs); + + while (nr-- > 0) { + arch_addr_t baddr = (arch_addr_t) newpcs[nr]; + if (dict_find(proc->leader->breakpoints, &baddr) != NULL) { + fprintf(stderr, "skip %p %p\n", baddr, add_cb_data); + continue; + } + + if (add_cb(baddr, add_cb_data) < 0) + return SWS_FAIL; + } + + ptrace(PTRACE_SYSCALL, proc->pid, 0, 0); + return SWS_OK; +} + +long +gimme_arg(enum tof type, struct process *proc, int arg_num, + struct arg_type_info *info) +{ + long ret; + struct user_gp_regs regs; + struct iovec iov; + + /* get GP registers */ + iov.iov_base = ®s; + iov.iov_len = sizeof(regs); + if (ptrace(PTRACE_GETREGSET, proc->pid, NT_PRSTATUS, (long)&iov)) + return 0; + + debug(2, "type %d arg %d arg",type, arg_num); + if (type == LT_TOF_FUNCTION || type == LT_TOF_SYSCALL) { + if (arg_num < 6) { + /* Args go backwards starting from D1Ar1 (D1.3) */ + ret = ((unsigned long *)®s.dx[3][1])[-arg_num]; + debug(2,"ret = %#lx",ret); + return ret; + } else { + return 0; + } + } + if (arg_num >= 0) { + fprintf(stderr,"args on return?"); + } + if (type == LT_TOF_FUNCTIONR || type == LT_TOF_SYSCALLR) { + return regs.dx[0][0]; /* D0Re0 (D0.0) */ + } + + fprintf(stderr, "gimme_arg called with wrong arguments\n"); + + return 0; +} diff --git a/sysdeps/linux-gnu/mips/Doxyfile b/sysdeps/linux-gnu/mips/Doxyfile new file mode 100644 index 0000000..1dda2c1 --- /dev/null +++ b/sysdeps/linux-gnu/mips/Doxyfile @@ -0,0 +1,293 @@ +# This file is part of ltrace. +# Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +# Doxyfile 1.5.1 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = ltrace-mips +PROJECT_NUMBER = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = NO +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = YES +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = YES +SORT_BY_SCOPE_NAME = YES +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = . +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py \ + *.C \ + *.CC \ + *.C++ \ + *.II \ + *.I++ \ + *.H \ + *.HH \ + *.H++ \ + *.CS \ + *.PHP \ + *.PHP3 \ + *.M \ + *.MM \ + *.PY +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = YES +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = NO +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = YES +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = YES +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = YES +CALLER_GRAPH = YES +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = NO +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/sysdeps/linux-gnu/mips/Makefile.am b/sysdeps/linux-gnu/mips/Makefile.am new file mode 100644 index 0000000..1fd8c2a --- /dev/null +++ b/sysdeps/linux-gnu/mips/Makefile.am @@ -0,0 +1,38 @@ +# This file is part of ltrace. +# Copyright (C) 2010 Marc Kleine-Budde, Pengutronix +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +noinst_LTLIBRARIES = \ + ../libcpu.la + +___libcpu_la_SOURCES = \ + plt.c \ + regs.c \ + trace.c + +noinst_HEADERS = \ + arch.h \ + mips.h \ + ptrace.h \ + signalent.h \ + syscallent.h + +EXTRA_DIST = \ + Doxyfile + +MAINTAINERCLEANFILES = \ + Makefile.in diff --git a/sysdeps/linux-gnu/mips/arch.h b/sysdeps/linux-gnu/mips/arch.h new file mode 100644 index 0000000..14572cd --- /dev/null +++ b/sysdeps/linux-gnu/mips/arch.h @@ -0,0 +1,76 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata + * Copyright (C) 2006 Eric Vaitl + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef LTRACE_MIPS_ARCH_H +#define LTRACE_MIPS_ARCH_H + +#include <stddef.h> +#include <gelf.h> + +#ifdef __MIPSEL__ +# define BREAKPOINT_VALUE { 0x0d, 0x00, 0x00, 0x00 } +# define ARCH_ENDIAN_LITTLE +#elif defined(__MIPSEB__) +# define BREAKPOINT_VALUE { 0x00, 0x00, 0x00, 0x0d } +# define ARCH_ENDIAN_BIG +#else +# error __MIPSEL__ or __MIPSEB__ must be defined +#endif + +#define BREAKPOINT_LENGTH 4 +#define DECR_PC_AFTER_BREAK 0 + +#define LT_ELFCLASS ELFCLASS32 +#define LT_ELF_MACHINE EM_MIPS + +#define ARCH_HAVE_LTELF_DATA +struct arch_ltelf_data { + size_t pltgot_addr; + size_t mips_local_gotno; + size_t mips_gotsym; +}; + +#define ARCH_HAVE_FIND_DL_DEBUG +#define ARCH_HAVE_GET_SYMINFO +#define ARCH_HAVE_DYNLINK_DONE +#define ARCH_HAVE_ADD_PLT_ENTRY +#define ARCH_HAVE_SW_SINGLESTEP +#define ARCH_HAVE_SYMBOL_RET + +#define ARCH_HAVE_LIBRARY_SYMBOL_DATA +enum mips_plt_type +{ + MIPS_PLT_UNRESOLVED, + MIPS_PLT_RESOLVED, +}; + +struct arch_library_symbol_data { + enum mips_plt_type type; + GElf_Addr resolved_addr; + GElf_Addr stub_addr; + + /* Set for FUNCs that have GOT entries but not PLT entries. */ + int gotonly : 1; + /* Set for FUNCs that have PLT entries that are always used. */ + int pltalways : 1; +}; + +#endif /* LTRACE_MIPS_ARCH_H */ diff --git a/sysdeps/linux-gnu/mips/mips.h b/sysdeps/linux-gnu/mips/mips.h new file mode 100644 index 0000000..0cbc715 --- /dev/null +++ b/sysdeps/linux-gnu/mips/mips.h @@ -0,0 +1,33 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2010 Arnaud Patard, Mandriva SA + * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#ifndef MIPS_h +#define MIPS_h +// asm/ptrace.h for these offsets. +#define off_v0 2 +#define off_pc 64 +#define off_a0 4 +#define off_a3 7 +#define off_lr 31 +#define off_fpr0 32 +#define off_sp 29 + +#endif // MIPS_h diff --git a/sysdeps/linux-gnu/mips/plt.c b/sysdeps/linux-gnu/mips/plt.c new file mode 100644 index 0000000..3adf2ec --- /dev/null +++ b/sysdeps/linux-gnu/mips/plt.c @@ -0,0 +1,423 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. + * Copyright (C) 2012 Edgar E. Iglesias, Axis Communications + * Copyright (C) 2008,2009 Juan Cespedes + * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <string.h> +#include <error.h> +#include <errno.h> +#include <gelf.h> +#include <sys/ptrace.h> + +#include "common.h" +#include "debug.h" +#include "proc.h" +#include "library.h" +#include "breakpoint.h" +#include "backend.h" + +/** + \addtogroup mips + @{ + */ + +/* Are we in pure CPIC mode (the non-PIC ABI extension)? */ +static inline int +mips_elf_is_cpic(unsigned int elf_flags) +{ + return (elf_flags & (EF_MIPS_PIC | EF_MIPS_CPIC)) == EF_MIPS_CPIC; +} + +/** + \param lte Structure containing link table entry information + \param ndx Index into .dynsym + \param rela Not used. + \return Address of GOT table entry + + MIPS ABI Supplement: + + DT_PLTGOT This member holds the address of the .got section. + + DT_MIPS_SYMTABNO This member holds the number of entries in the + .dynsym section. + + DT_MIPS_LOCAL_GOTNO This member holds the number of local global + offset table entries. + + DT_MIPS_GOTSYM This member holds the index of the first dyamic + symbol table entry that corresponds to an entry in the gobal offset + table. + + Called by read_elf when building the symbol table. + + */ +GElf_Addr +arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) +{ + debug(1,"plt_addr %zx ndx %#zx",lte->arch.pltgot_addr, ndx); + + if (mips_elf_is_cpic(lte->ehdr.e_flags)) { + /* Return a pointer into the PLT. */ + return lte->plt_addr + 16 * 2 + (ndx * 16); + } + + /* Return a pointer to a GOT entry. */ + return lte->arch.pltgot_addr + + sizeof(void *) * (lte->arch.mips_local_gotno + + (ndx - lte->arch.mips_gotsym)); +} +/** + \param proc The process to work on. + \param sym The library symbol. + \return What is at the got table address + + The return value should be the address to put the breakpoint at. + + On the mips the library_symbol.enter_addr is the .got addr for the + symbol and the breakpoint.addr is the actual breakpoint address. + + Other processors use a plt, the mips is "special" in that is uses + the .got for both function and data relocations. Prior to program + startup, return 0. + + \warning MIPS relocations are lazy. This means that the breakpoint + may move after the first call. Ltrace dictionary routines don't + have a delete and symbol is one to one with breakpoint, so if the + breakpoint changes I just add a new breakpoint for the new address. + */ +void * +sym2addr(struct process *proc, struct library_symbol *sym) +{ + long ret; + + if (sym->arch.pltalways + || (!sym->arch.gotonly && sym->plt_type == LS_TOPLT_NONE)) { + return sym->enter_addr; + } + + if(!proc->pid){ + return 0; + } + ret=ptrace(PTRACE_PEEKTEXT, proc->pid, sym->enter_addr, 0); + if(ret==-1){ + ret =0; + } + return (void *)ret;; +} + +/* Address of run time loader map, used for debugging. */ +#define DT_MIPS_RLD_MAP 0x70000016 +int +arch_find_dl_debug(struct process *proc, arch_addr_t dyn_addr, + arch_addr_t *ret) +{ + arch_addr_t rld_addr; + int r; + + /* MIPS puts the address of the r_debug structure into the + * DT_MIPS_RLD_MAP entry instead of into the DT_DEBUG entry. */ + r = proc_find_dynamic_entry_addr(proc, dyn_addr, + DT_MIPS_RLD_MAP, &rld_addr); + if (r == 0) { + if (umovebytes(proc, rld_addr, + ret, sizeof *ret) != sizeof *ret) { + r = -1; + } + } + return r; +} + + +/* + * MIPS doesn't have traditional got.plt entries with corresponding + * relocations. + * + * sym_index is an offset into the external GOT entries. Filter out + * stuff that are not functions. + */ +int +arch_get_sym_info(struct ltelf *lte, const char *filename, + size_t sym_index, GElf_Rela *rela, GElf_Sym *sym) +{ + if (mips_elf_is_cpic(lte->ehdr.e_flags)) { + return gelf_getsym(lte->dynsym, ELF64_R_SYM(rela->r_info), + sym) != NULL ? 0 : -1; + } + + /* Fixup the offset. */ + sym_index += lte->arch.mips_gotsym; + + if (gelf_getsym(lte->dynsym, sym_index, sym) == NULL) + return -1; + + if (ELF64_ST_TYPE(sym->st_info) != STT_FUNC) { + const char *name = lte->dynstr + sym->st_name; + debug(2, "sym %s not a function", name); + return 1; + } + + return 0; +} + +/** + MIPS ABI Supplement: + + DT_PLTGOT This member holds the address of the .got section. + + DT_MIPS_SYMTABNO This member holds the number of entries in the + .dynsym section. + + DT_MIPS_LOCAL_GOTNO This member holds the number of local global + offset table entries. + + DT_MIPS_GOTSYM This member holds the index of the first dyamic + symbol table entry that corresponds to an entry in the gobal offset + table. + + */ +int +arch_elf_init(struct ltelf *lte, struct library *lib) +{ + Elf_Scn *scn; + GElf_Shdr shdr; + + /* FIXME: for CPIC we should really scan both GOT tables + * to pick up relocations to external functions. Right now + * function pointers from the main binary to external functions + * can't be traced in CPIC mode. */ + if (mips_elf_is_cpic(lte->ehdr.e_flags)) { + return 0; /* We are already done. */ + } + + if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0 + || scn == NULL) { + fail: + error(0, 0, "Couldn't get SHT_DYNAMIC: %s", + elf_errmsg(-1)); + return -1; + } + + Elf_Data *data = elf_loaddata(scn, &shdr); + if (data == NULL) + goto fail; + + size_t j; + for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) { + GElf_Dyn dyn; + if (gelf_getdyn(data, j, &dyn) == NULL) + goto fail; + + if(dyn.d_tag == DT_PLTGOT) { + lte->arch.pltgot_addr = dyn.d_un.d_ptr; + } + if(dyn.d_tag == DT_MIPS_LOCAL_GOTNO){ + lte->arch.mips_local_gotno = dyn.d_un.d_val; + } + if(dyn.d_tag == DT_MIPS_GOTSYM){ + lte->arch.mips_gotsym = dyn.d_un.d_val; + } + } + + /* Tell the generic code how many dynamic trace:able symbols + * we've got. */ + lte->relplt_count = lte->dynsym_count - lte->arch.mips_gotsym; + return 0; +} + +void +arch_elf_destroy(struct ltelf *lte) +{ +} + +/* When functions return we check if the symbol needs an updated + breakpoint with the resolved address. */ +void arch_symbol_ret(struct process *proc, struct library_symbol *libsym) +{ + struct breakpoint *bp; + arch_addr_t resolved_addr; + struct process *leader = proc->leader; + + /* Only deal with unresolved symbols. */ + if (libsym->arch.type != MIPS_PLT_UNRESOLVED) + return; + + /* Get out if we are always using the PLT. */ + if (libsym->arch.pltalways) + return; + + resolved_addr = sym2addr(proc, libsym); + libsym->arch.resolved_addr = (uintptr_t) resolved_addr; + libsym->arch.type = MIPS_PLT_RESOLVED; + + if (libsym->arch.stub_addr == libsym->arch.resolved_addr) { + /* Prelinked symbol. No need to add new breakpoint. */ + return; + } + + bp = malloc(sizeof (*bp)); + if (bp == NULL) { + fprintf(stderr, "Failed to allocate bp for %s\n", + libsym->name); + return; + } + + if (breakpoint_init(bp, leader, resolved_addr, libsym) < 0) + goto err; + + if (proc_add_breakpoint(leader, bp) < 0) { + breakpoint_destroy(bp); + goto err; + } + + if (breakpoint_turn_on(bp, leader) < 0) { + proc_remove_breakpoint(leader, bp); + breakpoint_destroy(bp); + goto err; + } + return; +err: + free(bp); +} + +static enum callback_status +cb_enable_breakpoint_sym(struct library_symbol *libsym, void *data) +{ + struct process *proc = data; + arch_addr_t bp_addr; + + if (!libsym->arch.gotonly) + return CBS_CONT; + + /* Update state. */ + bp_addr = sym2addr(proc, libsym); + /* XXX The cast to uintptr_t should be removed when + * arch_addr_t becomes integral type. keywords: double cast. */ + libsym->arch.resolved_addr = (uintptr_t) bp_addr; + + if (libsym->arch.resolved_addr == 0) + /* FIXME: What does this mean? */ + return CBS_CONT; + + libsym->arch.type = MIPS_PLT_RESOLVED; + + /* Now, activate the symbol causing a breakpoint to be added. */ + if (proc_activate_delayed_symbol(proc, libsym) < 0) { + fprintf(stderr, "Failed to activate delayed sym %s\n", + libsym->name); + } + return CBS_CONT; +} + +static enum callback_status +cb_enable_breakpoint_lib(struct process *proc, struct library *lib, void *data) +{ + library_each_symbol(lib, NULL, cb_enable_breakpoint_sym, proc); + return CBS_CONT; +} + +void arch_dynlink_done(struct process *proc) +{ + proc_each_library(proc->leader, NULL, cb_enable_breakpoint_lib, NULL); +} + +enum plt_status +arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *a_name, GElf_Rela *rela, size_t ndx, + struct library_symbol **ret) +{ + char *name = NULL; + int sym_index = ndx + lte->arch.mips_gotsym; + + struct library_symbol *libsym = malloc(sizeof(*libsym)); + if (libsym == NULL) + return PLT_FAIL; + + GElf_Addr addr = arch_plt_sym_val(lte, sym_index, 0); + + name = strdup(a_name); + if (name == NULL) { + fprintf(stderr, "%s: failed %s(%#llx): %s\n", __func__, + name, addr, strerror(errno)); + goto fail; + } + + /* XXX The double cast should be removed when + * arch_addr_t becomes integral type. */ + if (library_symbol_init(libsym, + (arch_addr_t) (uintptr_t) addr, + name, 1, LS_TOPLT_EXEC) < 0) { + fprintf(stderr, "%s: failed %s : %llx\n", __func__, name, addr); + goto fail; + } + + arch_addr_t bp_addr = sym2addr(proc, libsym); + /* XXX This cast should be removed when + * arch_addr_t becomes integral type. keywords: double cast. */ + libsym->arch.stub_addr = (uintptr_t) bp_addr; + + if (bp_addr == 0) { + /* Function pointers without PLT entries. */ + libsym->plt_type = LS_TOPLT_NONE; + libsym->arch.gotonly = 1; + libsym->arch.type = MIPS_PLT_UNRESOLVED; + + /* Delay breakpoint activation until the symbol gets + * resolved. */ + libsym->delayed = 1; + } else if (mips_elf_is_cpic(lte->ehdr.e_flags)) { + libsym->arch.pltalways = 1; + } + + *ret = libsym; + return PLT_OK; + +fail: + free(name); + free(libsym); + return PLT_FAIL; +} + +int +arch_library_symbol_init(struct library_symbol *libsym) +{ + libsym->arch.pltalways = 0; + libsym->arch.gotonly = 0; + libsym->arch.type = MIPS_PLT_UNRESOLVED; + if (libsym->plt_type == LS_TOPLT_NONE) { + libsym->arch.type = MIPS_PLT_RESOLVED; + } + return 0; +} + +void +arch_library_symbol_destroy(struct library_symbol *libsym) +{ +} + +int +arch_library_symbol_clone(struct library_symbol *retp, + struct library_symbol *libsym) +{ + retp->arch = libsym->arch; + return 0; +} + +/**@}*/ diff --git a/sysdeps/linux-gnu/mips/ptrace.h b/sysdeps/linux-gnu/mips/ptrace.h new file mode 100644 index 0000000..00c8673 --- /dev/null +++ b/sysdeps/linux-gnu/mips/ptrace.h @@ -0,0 +1,21 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include <sys/ptrace.h> diff --git a/sysdeps/linux-gnu/mips/regs.c b/sysdeps/linux-gnu/mips/regs.c new file mode 100644 index 0000000..b143636 --- /dev/null +++ b/sysdeps/linux-gnu/mips/regs.c @@ -0,0 +1,97 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * Copyright (C) 2008,2009 Juan Cespedes + * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "config.h" + +#include <stddef.h> +#include <sys/types.h> +#include <sys/ptrace.h> +#include <asm/ptrace.h> + +#include "proc.h" +#include "common.h" +#include "mips.h" + +#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR)) +# define PTRACE_PEEKUSER PTRACE_PEEKUSR +#endif + +#if (!defined(PTRACE_POKEUSER) && defined(PTRACE_POKEUSR)) +# define PTRACE_POKEUSER PTRACE_POKEUSR +#endif + +/** + \addtogroup mips + @{ + */ + + +/** + \param proc The process to work on. + \return The current instruction pointer. + */ +void * +get_instruction_pointer(struct process *proc) +{ + return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, off_pc, 0); +} + +/** + \param proc The process to work on. + \param addr The address to set to. + + Called by \c continue_after_breakpoint(). + + \todo Our mips kernel ptrace doesn't support PTRACE_SINGLESTEP, so + we \c continue_process() after a breakpoint. Check if this is OK. + */ +void +set_instruction_pointer(struct process *proc, void *addr) +{ + ptrace(PTRACE_POKEUSER, proc->pid, off_pc, addr); +} + +/** + \param proc The process to work on. + \return The current stack pointer. + */ +void * +get_stack_pointer(struct process *proc) +{ + return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, off_sp, 0); +} + +/** + \param proc The process to work on. + \param stack_pointer The current stack pointer for proc + \return The current return address. + + Called by \c handle_breakpoint(). + + Mips uses r31 for the return address, so the stack_pointer is + unused. + */ +void * +get_return_addr(struct process *proc, void *stack_pointer) +{ + return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, off_lr, 0); +} diff --git a/sysdeps/linux-gnu/mips/signalent.h b/sysdeps/linux-gnu/mips/signalent.h new file mode 100644 index 0000000..d779af5 --- /dev/null +++ b/sysdeps/linux-gnu/mips/signalent.h @@ -0,0 +1,52 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2010 Arnaud Patard, Mandriva SA + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + "SIG_0", /* 0 */ + "SIGHUP", /* 1 */ + "SIGINT", /* 2 */ + "SIGQUIT", /* 3 */ + "SIGILL", /* 4 */ + "SIGTRAP", /* 5 */ + "SIGIOT", /* 6 */ + "SIGEMT", /* 7 */ + "SIGFPE", /* 8 */ + "SIGKILL", /* 9 */ + "SIGBUS", /* 10 */ + "SIGSEGV", /* 11 */ + "SIGSYS", /* 12 */ + "SIGPIPE", /* 13 */ + "SIGALRM", /* 14 */ + "SIGTERM", /* 15 */ + "SIGUSR1", /* 16 */ + "SIGUSR2", /* 17 */ + "SIGCHLD", /* 18 */ + "SIGPWR", /* 19 */ + "SIGWINCH", /* 20 */ + "SIGURG", /* 21 */ + "SIGIO", /* 22 */ + "SIGSTOP", /* 23 */ + "SIGTSTP", /* 24 */ + "SIGCONT", /* 25 */ + "SIGTTIN", /* 26 */ + "SIGTTOU", /* 27 */ + "SIGVTALRM", /* 28 */ + "SIGPROF", /* 29 */ + "SIGXCPU", /* 30 */ + "SIGXFSZ", /* 31 */ diff --git a/sysdeps/linux-gnu/mips/syscallent.h b/sysdeps/linux-gnu/mips/syscallent.h new file mode 100644 index 0000000..c0e5dd6 --- /dev/null +++ b/sysdeps/linux-gnu/mips/syscallent.h @@ -0,0 +1,368 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2010 Arnaud Patard, Mandriva SA + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + + "syscall", /* 0 */ + "exit", /* 1 */ + "fork", /* 2 */ + "read", /* 3 */ + "write", /* 4 */ + "open", /* 5 */ + "close", /* 6 */ + "waitpid", /* 7 */ + "creat", /* 8 */ + "link", /* 9 */ + "unlink", /* 10 */ + "execve", /* 11 */ + "chdir", /* 12 */ + "time", /* 13 */ + "mknod", /* 14 */ + "chmod", /* 15 */ + "lchown", /* 16 */ + "break", /* 17 */ + "unused18", /* 18 */ + "lseek", /* 19 */ + "getpid", /* 20 */ + "mount", /* 21 */ + "umount", /* 22 */ + "setuid", /* 23 */ + "getuid", /* 24 */ + "stime", /* 25 */ + "ptrace", /* 26 */ + "alarm", /* 27 */ + "unused28", /* 28 */ + "pause", /* 29 */ + "utime", /* 30 */ + "stty", /* 31 */ + "gtty", /* 32 */ + "access", /* 33 */ + "nice", /* 34 */ + "ftime", /* 35 */ + "sync", /* 36 */ + "kill", /* 37 */ + "rename", /* 38 */ + "mkdir", /* 39 */ + "rmdir", /* 40 */ + "dup", /* 41 */ + "pipe", /* 42 */ + "times", /* 43 */ + "prof", /* 44 */ + "brk", /* 45 */ + "setgid", /* 46 */ + "getgid", /* 47 */ + "signal", /* 48 */ + "geteuid", /* 49 */ + "getegid", /* 50 */ + "acct", /* 51 */ + "umount2", /* 52 */ + "lock", /* 53 */ + "ioctl", /* 54 */ + "fcntl", /* 55 */ + "mpx", /* 56 */ + "setpgid", /* 57 */ + "ulimit", /* 58 */ + "unused59", /* 59 */ + "umask", /* 60 */ + "chroot", /* 61 */ + "ustat", /* 62 */ + "dup2", /* 63 */ + "getppid", /* 64 */ + "getpgrp", /* 65 */ + "setsid", /* 66 */ + "sigaction", /* 67 */ + "sgetmask", /* 68 */ + "ssetmask", /* 69 */ + "setreuid", /* 70 */ + "setregid", /* 71 */ + "sigsuspend", /* 72 */ + "sigpending", /* 73 */ + "sethostname", /* 74 */ + "setrlimit", /* 75 */ + "getrlimit", /* 76 */ + "getrusage", /* 77 */ + "gettimeofday", /* 78 */ + "settimeofday", /* 79 */ + "getgroups", /* 80 */ + "setgroups", /* 81 */ + "reserved82", /* 82 */ + "symlink", /* 83 */ + "unused84", /* 84 */ + "readlink", /* 85 */ + "uselib", /* 86 */ + "swapon", /* 87 */ + "reboot", /* 88 */ + "readdir", /* 89 */ + "mmap", /* 90 */ + "munmap", /* 91 */ + "truncate", /* 92 */ + "ftruncate", /* 93 */ + "fchmod", /* 94 */ + "fchown", /* 95 */ + "getpriority", /* 96 */ + "setpriority", /* 97 */ + "profil", /* 98 */ + "statfs", /* 99 */ + "fstatfs", /* 100 */ + "ioperm", /* 101 */ + "socketcall", /* 102 */ + "syslog", /* 103 */ + "setitimer", /* 104 */ + "getitimer", /* 105 */ + "stat", /* 106 */ + "lstat", /* 107 */ + "fstat", /* 108 */ + "unused109", /* 109 */ + "iopl", /* 110 */ + "vhangup", /* 111 */ + "idle", /* 112 */ + "vm86", /* 113 */ + "wait4", /* 114 */ + "swapoff", /* 115 */ + "sysinfo", /* 116 */ + "ipc", /* 117 */ + "fsync", /* 118 */ + "sigreturn", /* 119 */ + "clone", /* 120 */ + "setdomainname", /* 121 */ + "uname", /* 122 */ + "modify_ldt", /* 123 */ + "adjtimex", /* 124 */ + "mprotect", /* 125 */ + "sigprocmask", /* 126 */ + "create_module", /* 127 */ + "init_module", /* 128 */ + "delete_module", /* 129 */ + "get_kernel_syms", /* 130 */ + "quotactl", /* 131 */ + "getpgid", /* 132 */ + "fchdir", /* 133 */ + "bdflush", /* 134 */ + "sysfs", /* 135 */ + "personality", /* 136 */ + "afs_syscall", /* 137 */ + "setfsuid", /* 138 */ + "setfsgid", /* 139 */ + "_llseek", /* 140 */ + "getdents", /* 141 */ + "_newselect", /* 142 */ + "flock", /* 143 */ + "msync", /* 144 */ + "readv", /* 145 */ + "writev", /* 146 */ + "cacheflush", /* 147 */ + "cachectl", /* 148 */ + "sysmips", /* 149 */ + "unused150", /* 150 */ + "getsid", /* 151 */ + "fdatasync", /* 152 */ + "_sysctl", /* 153 */ + "mlock", /* 154 */ + "munlock", /* 155 */ + "mlockall", /* 156 */ + "munlockall", /* 157 */ + "sched_setparam", /* 158 */ + "sched_getparam", /* 159 */ + "sched_setscheduler", /* 160 */ + "sched_getscheduler", /* 161 */ + "sched_yield", /* 162 */ + "sched_get_priority_max", /* 163 */ + "sched_get_priority_min", /* 164 */ + "sched_rr_get_interval", /* 165 */ + "nanosleep", /* 166 */ + "mremap", /* 167 */ + "accept", /* 168 */ + "bind", /* 169 */ + "connect", /* 170 */ + "getpeername", /* 171 */ + "getsockname", /* 172 */ + "getsockopt", /* 173 */ + "listen", /* 174 */ + "recv", /* 175 */ + "recvfrom", /* 176 */ + "recvmsg", /* 177 */ + "send", /* 178 */ + "sendmsg", /* 179 */ + "sendto", /* 180 */ + "setsockopt", /* 181 */ + "shutdown", /* 182 */ + "socket", /* 183 */ + "socketpair", /* 184 */ + "setresuid", /* 185 */ + "getresuid", /* 186 */ + "query_module", /* 187 */ + "poll", /* 188 */ + "nfsservctl", /* 189 */ + "setresgid", /* 190 */ + "getresgid", /* 191 */ + "prctl", /* 192 */ + "rt_sigreturn", /* 193 */ + "rt_sigaction", /* 194 */ + "rt_sigprocmask", /* 195 */ + "rt_sigpending", /* 196 */ + "rt_sigtimedwait", /* 197 */ + "rt_sigqueueinfo", /* 198 */ + "rt_sigsuspend", /* 199 */ + "pread64", /* 200 */ + "pwrite64", /* 201 */ + "chown", /* 202 */ + "getcwd", /* 203 */ + "capget", /* 204 */ + "capset", /* 205 */ + "sigaltstack", /* 206 */ + "sendfile", /* 207 */ + "getpmsg", /* 208 */ + "putpmsg", /* 209 */ + "mmap2", /* 210 */ + "truncate64", /* 211 */ + "ftruncate64", /* 212 */ + "stat64", /* 213 */ + "lstat64", /* 214 */ + "fstat64", /* 215 */ + "pivot_root", /* 216 */ + "mincore", /* 217 */ + "madvise", /* 218 */ + "getdents64", /* 219 */ + "fcntl64", /* 220 */ + "reserved221", /* 221 */ + "gettid", /* 222 */ + "readahead", /* 223 */ + "setxattr", /* 224 */ + "lsetxattr", /* 225 */ + "fsetxattr", /* 226 */ + "getxattr", /* 227 */ + "lgetxattr", /* 228 */ + "fgetxattr", /* 229 */ + "listxattr", /* 230 */ + "llistxattr", /* 231 */ + "flistxattr", /* 232 */ + "removexattr", /* 233 */ + "lremovexattr", /* 234 */ + "fremovexattr", /* 235 */ + "tkill", /* 236 */ + "sendfile64", /* 237 */ + "futex", /* 238 */ + "sched_setaffinity", /* 239 */ + "sched_getaffinity", /* 240 */ + "io_setup", /* 241 */ + "io_destroy", /* 242 */ + "io_getevents", /* 243 */ + "io_submit", /* 244 */ + "io_cancel", /* 245 */ + "exit_group", /* 246 */ + "lookup_dcookie", /* 247 */ + "epoll_create", /* 248 */ + "epoll_ctl", /* 249 */ + "epoll_wait", /* 250 */ + "remap_file_pages", /* 251 */ + "set_tid_address", /* 252 */ + "restart_syscall", /* 253 */ + "fadvise64", /* 254 */ + "statfs64", /* 255 */ + "fstatfs64", /* 256 */ + "timer_create", /* 257 */ + "timer_settime", /* 258 */ + "timer_gettime", /* 259 */ + "timer_getoverrun", /* 260 */ + "timer_delete", /* 261 */ + "clock_settime", /* 262 */ + "clock_gettime", /* 263 */ + "clock_getres", /* 264 */ + "clock_nanosleep", /* 265 */ + "tgkill", /* 266 */ + "utimes", /* 267 */ + "mbind", /* 268 */ + "get_mempolicy", /* 269 */ + "set_mempolicy", /* 270 */ + "mq_open", /* 271 */ + "mq_unlink", /* 272 */ + "mq_timedsend", /* 273 */ + "mq_timedreceive", /* 274 */ + "mq_notify", /* 275 */ + "mq_getsetattr", /* 276 */ + "vserver", /* 277 */ + "waitid", /* 278 */ + "279", /* 279 */ + "add_key", /* 280 */ + "request_key", /* 281 */ + "keyctl", /* 282 */ + "set_thread_area", /* 283 */ + "inotify_init", /* 284 */ + "inotify_add_watch", /* 285 */ + "inotify_rm_watch", /* 286 */ + "migrate_pages", /* 287 */ + "openat", /* 288 */ + "mkdirat", /* 289 */ + "mknodat", /* 290 */ + "fchownat", /* 291 */ + "futimesat", /* 292 */ + "fstatat64", /* 293 */ + "unlinkat", /* 294 */ + "renameat", /* 295 */ + "linkat", /* 296 */ + "symlinkat", /* 297 */ + "readlinkat", /* 298 */ + "fchmodat", /* 299 */ + "faccessat", /* 300 */ + "pselect6", /* 301 */ + "ppoll", /* 302 */ + "unshare", /* 303 */ + "splice", /* 304 */ + "sync_file_range", /* 305 */ + "tee", /* 306 */ + "vmsplice", /* 307 */ + "move_pages", /* 308 */ + "set_robust_list", /* 309 */ + "get_robust_list", /* 310 */ + "kexec_load", /* 311 */ + "getcpu", /* 312 */ + "epoll_pwait", /* 313 */ + "ioprio_set", /* 314 */ + "ioprio_get", /* 315 */ + "utimensat", /* 316 */ + "signalfd", /* 317 */ + "timerfd", /* 318 */ + "eventfd", /* 319 */ + "fallocate", /* 320 */ + "timerfd_create", /* 321 */ + "timerfd_gettime", /* 322 */ + "timerfd_settime", /* 323 */ + "signalfd4", /* 324 */ + "eventfd2", /* 325 */ + "epoll_create1", /* 326 */ + "dup3", /* 327 */ + "pipe2", /* 328 */ + "inotify_init1", /* 329 */ + "preadv", /* 330 */ + "pwritev", /* 331 */ + "rt_tgsigqueueinfo", /* 332 */ + "perf_event_open", /* 333 */ + "accept4", /* 334 */ + "recvmmsg", /* 335 */ + "fanotify_init", /* 336 */ + "fanotify_mark", /* 337 */ + "prlimit64", /* 338 */ + "name_to_handle_at", /* 339 */ + "open_by_handle_at", /* 340 */ + "clock_adjtime", /* 341 */ + "syncfs", /* 342 */ + "sendmmsg", /* 343 */ + "setns", /* 344 */ + "process_vm_readv", /* 345 */ + "process_vm_writev", /* 346 */ diff --git a/sysdeps/linux-gnu/mips/trace.c b/sysdeps/linux-gnu/mips/trace.c new file mode 100644 index 0000000..88e13ac --- /dev/null +++ b/sysdeps/linux-gnu/mips/trace.c @@ -0,0 +1,381 @@ +/* + * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. + * Copyright (C) 2012 Edgar E. Iglesias, Axis Communications + * Copyright (C) 2010 Arnaud Patard, Mandriva SA + * Copyright (C) 2008,2009 Juan Cespedes + * Copyright (C) 2006 Eric Vaitl, Cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + */ + +#include "config.h" + +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <sys/ptrace.h> +#include <asm/ptrace.h> +#include <assert.h> + +#include "backend.h" +#include "common.h" +#include "debug.h" +#include "mips.h" +#include "proc.h" +#include "type.h" + +#if (!defined(PTRACE_PEEKUSER) && defined(PTRACE_PEEKUSR)) +# define PTRACE_PEEKUSER PTRACE_PEEKUSR +#endif + +#if (!defined(PTRACE_POKEUSER) && defined(PTRACE_POKEUSR)) +# define PTRACE_POKEUSER PTRACE_POKEUSR +#endif + + +/** + \addtogroup mips Mips specific functions. + + These are the functions that it looks like I need to implement in + order to get ltrace to work on our target. + + @{ + */ + +/** + \param proc The process that had an event. + + Called by \c next_event() right after the return from wait. + + Most targets just return here. A couple use proc->arch_ptr for a + private data area. + */ +void +get_arch_dep(struct process *proc) +{ +} + +/** + \param proc Process that had event. + \param status From \c wait() + \param sysnum 0-based syscall number. + \return 1 if syscall, 2 if sysret, 0 otherwise. + + Called by \c next_event() after the call to get_arch_dep() + + It seems that the ptrace call trips twice on a system call, once + just before the system call and once when it returns. Both times, + the pc points at the instruction just after the mips "syscall" + instruction. + + There are several possiblities for system call sets, each is offset + by a base from the others. On our system, it looks like the base + for the system calls is 4000. + */ +int +syscall_p(struct process *proc, int status, int *sysnum) +{ + if (WIFSTOPPED(status) + && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { + /* get the user's pc (plus 8) */ + long pc = (long)get_instruction_pointer(proc); + /* fetch the SWI instruction */ + int insn = ptrace(PTRACE_PEEKTEXT, proc->pid, pc - 4, 0); + int num = ptrace(PTRACE_PEEKTEXT, proc->pid, pc - 8, 0); + + /* + On a mips, syscall looks like: + 24040fa1 li v0, 0x0fa1 # 4001 --> _exit syscall + 0000000c syscall + */ + if(insn!=0x0000000c){ + return 0; + } + + *sysnum = (num & 0xFFFF) - 4000; + /* if it is a syscall, return 1 or 2 */ + if (proc->callstack_depth > 0 && + proc->callstack[proc->callstack_depth - 1].is_syscall && + proc->callstack[proc->callstack_depth - 1].c_un.syscall == *sysnum) { + return 2; + } + + if (*sysnum >= 0) { + return 1; + } + } + return 0; +} + +/* Based on GDB code. */ +#define mips32_op(x) (x >> 26) +#define itype_op(x) (x >> 26) +#define itype_rs(x) ((x >> 21) & 0x1f) +#define itype_rt(x) ((x >> 16) & 0x1f) +#define itype_immediate(x) (x & 0xffff) + +#define jtype_op(x) (x >> 26) +#define jtype_target(x) (x & 0x03ffffff) + +#define rtype_op(x) (x >> 26) +#define rtype_rs(x) ((x >> 21) & 0x1f) +#define rtype_rt(x) ((x >> 16) & 0x1f) +#define rtype_rd(x) ((x >> 11) & 0x1f) +#define rtype_shamt(x) ((x >> 6) & 0x1f) +#define rtype_funct(x) (x & 0x3f) + +static int32_t +mips32_relative_offset (uint32_t inst) +{ + return ((itype_immediate(inst) ^ 0x8000) - 0x8000) << 2; +} + +int mips_next_pcs(struct process *proc, uint32_t pc, uint32_t *newpc) +{ + uint32_t inst, rx; + int op; + int rn; + int nr = 0; + + inst = ptrace(PTRACE_PEEKTEXT, proc->pid, pc, 0); + + if ((inst & 0xe0000000) != 0) { + /* Check for branches. */ + if (itype_op(inst) >> 2 == 5) { + /* BEQL, BNEL, BLEZL, BGTZL: bits 0101xx */ + op = (itype_op(inst) & 0x03); + switch (op) + { + case 0: /* BEQL */ + case 1: /* BNEL */ + case 2: /* BLEZL */ + case 3: /* BGTZL */ + newpc[nr++] = pc + 8; + newpc[nr++] = pc + 4 + + mips32_relative_offset(inst); + break; + default: + newpc[nr++] = pc + 4; + break; + } + } else if (itype_op(inst) == 17 && itype_rs(inst) == 8) { + /* Step over the branch. */ + newpc[nr++] = pc + 8; + newpc[nr++] = pc + mips32_relative_offset(inst) + 4; + } else { + newpc[nr++] = pc + 4; + } + } else { + /* Further subdivide into SPECIAL, REGIMM and other. */ + switch (op = itype_op(inst) & 0x07) + { + case 0: + op = rtype_funct(inst); + switch (op) + { + case 8: /* JR */ + case 9: /* JALR */ + rn = rtype_rs(inst); + + rx = ptrace(PTRACE_PEEKUSER,proc->pid, rn, 0); + newpc[nr++] = rx; + break; + default: + case 12: /* SYSCALL */ + newpc[nr++] = pc + 4; + break; + } + break; + case 1: + op = itype_rt(inst); + switch (op) + { + case 0: + case 1: + case 2: + case 3: + case 16: + case 17: + case 18: + case 19: + newpc[nr++] = pc + 8; + newpc[nr++] = pc + 4 + + mips32_relative_offset(inst); + break; + default: + newpc[nr++] = pc + 4; + break; + } + break; + case 2: /* J */ + case 3: /* JAL */ + rx = jtype_target(inst) << 2; + /* Upper four bits get never changed... */ + newpc[nr++] = rx + ((pc + 4) & ~0x0fffffff); + break; + case 4: /* BEQ */ + if (itype_rs(inst) == itype_rt(inst)) { + /* Compare the same reg for equality, always + * follow the branch. */ + newpc[nr++] = pc + 4 + + mips32_relative_offset(inst); + break; + } + /* Fall through. */ + default: + case 5: + case 6: + case 7: + /* Step over the branch. */ + newpc[nr++] = pc + 8; + newpc[nr++] = pc + mips32_relative_offset(inst) + 4; + break; + } + } + if (nr <= 0 || nr > 2) + goto fail; + if (nr == 2) { + if (newpc[1] == 0) + goto fail; + } + if (newpc[0] == 0) + goto fail; + + assert(nr == 1 || nr == 2); + return nr; + +fail: + printf("nr=%d pc=%x\n", nr, pc); + printf("pc=%x %x\n", newpc[0], newpc[1]); + return 0; +} + +enum sw_singlestep_status +arch_sw_singlestep(struct process *proc, struct breakpoint *bp, + int (*add_cb)(arch_addr_t, struct sw_singlestep_data *), + struct sw_singlestep_data *add_cb_data) +{ + uint32_t pc = (uint32_t) get_instruction_pointer(proc); + uint32_t newpcs[2]; + int nr; + + nr = mips_next_pcs(proc, pc, newpcs); + + while (nr-- > 0) { + arch_addr_t baddr = (arch_addr_t) newpcs[nr]; + /* Not sure what to do here. We've already got a bp? */ + if (DICT_HAS_KEY(proc->leader->breakpoints, &baddr)) { + fprintf(stderr, "skip %p %p\n", baddr, add_cb_data); + continue; + } + + if (add_cb(baddr, add_cb_data) < 0) + return SWS_FAIL; + } + + ptrace(PTRACE_SYSCALL, proc->pid, 0, 0); + return SWS_OK; +} + +/** + \param type Function/syscall call or return. + \param proc The process that had an event. + \param arg_num -1 for return value, + \return The argument to fetch. + + A couple of assumptions. + +- Type is LT_TOF_FUNCTIONR or LT_TOF_SYSCALLR if arg_num==-1. These + types are only used in calls for output_right(), which only uses -1 + for arg_num. +- Type is LT_TOF_FUNCTION or LT_TOF_SYSCALL for args 0...4. +- I'm only displaying the first 4 args (Registers a0..a3). Good + enough for now. + + Mips conventions seem to be: +- syscall parameters: r4...r9 +- syscall return: if(!a3){ return v0;} else{ errno=v0;return -1;} +- function call: r4..r7. Not sure how to get arg number 5. +- function return: v0 + +The argument registers are wiped by a call, so it is a mistake to ask +for arguments on a return. If ltrace does this, we will need to cache +arguments somewhere on the call. + +I'm not doing any floating point support here. + +*/ +long +gimme_arg(enum tof type, struct process *proc, int arg_num, + struct arg_type_info *info) +{ + long ret; + long addr; + debug(2,"type %d arg %d",type,arg_num); + if (arg_num == -1) { + if(type == LT_TOF_FUNCTIONR) { + return ptrace(PTRACE_PEEKUSER,proc->pid,off_v0,0); + } + if (type == LT_TOF_SYSCALLR) { + unsigned a3=ptrace(PTRACE_PEEKUSER, proc->pid,off_a3,0); + unsigned v0=ptrace(PTRACE_PEEKUSER, proc->pid,off_v0,0); + if(!a3){ + return v0; + } + return -1; + } + } + if (type == LT_TOF_FUNCTION || type == LT_TOF_SYSCALL) { + /* o32: float args are in f12 and f14 */ + if ((info->type == ARGTYPE_FLOAT) && (arg_num < 2)) { + ret=ptrace(PTRACE_PEEKUSER,proc->pid,off_fpr0+12+arg_num*2,0); + debug(2,"ret = %#lx",ret); + return ret; + } + if(arg_num <4){ + ret=ptrace(PTRACE_PEEKUSER,proc->pid,off_a0+arg_num,0); + debug(2,"ret = %#lx",ret); + return ret; + } else { + /* not sure it's going to work for something else than syscall */ + addr=ptrace(PTRACE_PEEKUSER,proc->pid,off_sp,0); + if (addr == -1) { + debug(2,"ret = %#lx",addr); + return addr; + } + ret = addr + 4*arg_num; + ret=ptrace(PTRACE_PEEKTEXT,proc->pid,addr,0); + debug(2,"ret = %#lx",ret); + return ret; + } + } + if (type == LT_TOF_FUNCTIONR || type == LT_TOF_SYSCALLR){ + addr=ptrace(PTRACE_PEEKUSER,proc->pid,off_sp,0); + if (addr == -1) { + debug(2,"ret = %#lx",addr); + return addr; + } + ret = addr + 4*arg_num; + ret=ptrace(PTRACE_PEEKTEXT,proc->pid,addr,0); + debug(2,"ret = %#lx",ret); + return ret; + } + fprintf(stderr, "gimme_arg called with wrong arguments\n"); + return 0; +} + +/**@}*/ diff --git a/sysdeps/linux-gnu/mksyscallent_mips b/sysdeps/linux-gnu/mksyscallent_mips new file mode 100755 index 0000000..f3961b4 --- /dev/null +++ b/sysdeps/linux-gnu/mksyscallent_mips @@ -0,0 +1,60 @@ +#!/usr/bin/awk -f +# This file is part of ltrace. +# Copyright (C) 2010 Arnaud Patard, Mandriva SA +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +# hack expression to generate arch/syscallent.h from <asm/unistd.h> +# It reads from stdin and writes to stdout +# It should work OK on i386,m68k,arm,ia64 +# It does NOT work in mips, s390 +# It is untested in other architectures + +BEGIN { + max=0; + FS="[ \t\n()+]+"; +} + +{ + #debug + #printf("/%s/%s/%s/%s/\n", $1, $2, $3, $4); + if ($2 ~ /__NR_Linux/ && $3 ~ /4000/) { + syscall=1; + } + if ($2 ~ /__NR_Linux_syscalls/) { + syscall=0; + } + if (syscall && ($1 ~ /^#define$/) && ($2 ~ /^__NR_/)) { + SYSCALL[$4]=substr($2,6); + if ($4 > max) { + max=$4; + } + } +} + +END { + for(i=0; i<=max; i++) { + if (!SYSCALL[i]) { + SYSCALL[i] = i; + } + pad = 32 - length(SYSCALL[i]); + if (pad<1) { + pad=1; + } + printf("\t\"%s\",%*s/* %d */\n", SYSCALL[i], pad, "", i); + } +} + diff --git a/sysdeps/linux-gnu/os.h b/sysdeps/linux-gnu/os.h index 62bf38b..60fd604 100644 --- a/sysdeps/linux-gnu/os.h +++ b/sysdeps/linux-gnu/os.h @@ -23,3 +23,17 @@ struct os_process_data { arch_addr_t debug_addr; int debug_state; }; + +#define OS_HAVE_LIBRARY_SYMBOL_DATA +struct os_library_symbol_data { + unsigned is_ifunc : 1; +}; + +#define OS_HAVE_BREAKPOINT_DATA +struct os_breakpoint_data { + /* For breakpoints that track return from IFUNC functions, we + * keep here the IFUNC symbol itself. */ + struct library_symbol *ret_libsym; +}; + +#define OS_HAVE_ADD_FUNC_ENTRY diff --git a/sysdeps/linux-gnu/ppc/arch.h b/sysdeps/linux-gnu/ppc/arch.h index fb8768a..4bad436 100644 --- a/sysdeps/linux-gnu/ppc/arch.h +++ b/sysdeps/linux-gnu/ppc/arch.h @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata + * Copyright (C) 2012,2013,2014 Petr Machata * Copyright (C) 2006 Paul Gilliam * Copyright (C) 2002,2004 Juan Cespedes * @@ -23,8 +23,8 @@ #define LTRACE_PPC_ARCH_H #include <gelf.h> +#include <stdbool.h> -#define BREAKPOINT_VALUE { 0x7f, 0xe0, 0x00, 0x08 } #define BREAKPOINT_LENGTH 4 #define DECR_PC_AFTER_BREAK 0 @@ -32,17 +32,51 @@ #define LT_ELF_MACHINE EM_PPC #ifdef __powerpc64__ // Says 'ltrace' is 64 bits, says nothing about target. -#define LT_ELFCLASS2 ELFCLASS64 -#define LT_ELF_MACHINE2 EM_PPC64 -#define ARCH_SUPPORTS_OPD +# define LT_ELFCLASS2 ELFCLASS64 +# define LT_ELF_MACHINE2 EM_PPC64 + +# ifdef __LITTLE_ENDIAN__ +# define BREAKPOINT_VALUE { 0x08, 0x00, 0xe0, 0x7f } +# define ARCH_ENDIAN_LITTLE +# else +# define BREAKPOINT_VALUE { 0x7f, 0xe0, 0x00, 0x08 } +# define ARCH_SUPPORTS_OPD +# define ARCH_ENDIAN_BIG +# endif + +# if !defined(_CALL_ELF) || _CALL_ELF < 2 +# define ARCH_SUPPORTS_OPD +# define STACK_FRAME_OVERHEAD 112 +# ifndef EF_PPC64_ABI +# define EF_PPC64_ABI 3 +# endif +# elif _CALL_ELF == 2 /* ELFv2 ABI */ +# define STACK_FRAME_OVERHEAD 32 +# else +# error Unsupported PowerPC64 ABI. +# endif /* CALL_ELF */ + +#else +# define BREAKPOINT_VALUE { 0x7f, 0xe0, 0x00, 0x08 } +# define ARCH_ENDIAN_BIG +# define STACK_FRAME_OVERHEAD 112 +# ifndef EF_PPC64_ABI +# define EF_PPC64_ABI 3 +# endif +#endif /* __powerpc64__ */ + +#ifdef _CALL_ELF +enum { ppc64_call_elf_abi = _CALL_ELF }; +#else +enum { ppc64_call_elf_abi = 0 }; #endif -#define ARCH_HAVE_ATOMIC_SINGLESTEP +#define ARCH_HAVE_SW_SINGLESTEP #define ARCH_HAVE_ADD_PLT_ENTRY +#define ARCH_HAVE_ADD_FUNC_ENTRY #define ARCH_HAVE_TRANSLATE_ADDRESS #define ARCH_HAVE_DYNLINK_DONE #define ARCH_HAVE_FETCH_ARG -#define ARCH_ENDIAN_BIG #define ARCH_HAVE_SIZEOF #define ARCH_HAVE_ALIGNOF @@ -55,7 +89,11 @@ struct arch_ltelf_data { Elf_Data *opd_data; GElf_Addr opd_base; GElf_Xword opd_size; - int secure_plt; + bool secure_plt : 1; + bool elfv2_abi : 1; + + Elf_Data *reladyn; + size_t reladyn_count; }; #define ARCH_HAVE_LIBRARY_DATA @@ -79,12 +117,33 @@ enum ppc64_plt_type { * corresponding PLT entry. The original is now saved in * RESOLVED_VALUE. */ PPC_PLT_RESOLVED, + + /* Very similar to PPC_PLT_UNRESOLVED, but for JMP_IREL + * slots. */ + PPC_PLT_IRELATIVE, + + /* Transitional state before the breakpoint is enabled. */ + PPC_PLT_NEED_UNRESOLVE, }; #define ARCH_HAVE_LIBRARY_SYMBOL_DATA +struct ppc_unresolve_data; struct arch_library_symbol_data { enum ppc64_plt_type type; - GElf_Addr resolved_value; + + /* State Contents + * + * PPC_DEFAULT N/A + * PPC64_PLT_STUB N/A + * PPC_PLT_UNRESOLVED PLT entry address. + * PPC_PLT_IRELATIVE Likewise. + * PPC_PLT_RESOLVED The original value the slot was resolved to. + * PPC_PLT_NEED_UNRESOLVE DATA. + */ + union { + GElf_Addr resolved_value; + struct ppc_unresolve_data *data; + }; /* Address of corresponding slot in .plt. */ GElf_Addr plt_slot_addr; @@ -92,7 +151,10 @@ struct arch_library_symbol_data { #define ARCH_HAVE_BREAKPOINT_DATA struct arch_breakpoint_data { - /* We need this just for arch_breakpoint_init. */ + /* This is where we hide symbol for IRELATIVE breakpoint for + * the first time that it hits. This is NULL for normal + * breakpoints. */ + struct library_symbol *irel_libsym; }; #define ARCH_HAVE_PROCESS_DATA diff --git a/sysdeps/linux-gnu/ppc/fetch.c b/sysdeps/linux-gnu/ppc/fetch.c index 9963a1e..c6cbd71 100644 --- a/sysdeps/linux-gnu/ppc/fetch.c +++ b/sysdeps/linux-gnu/ppc/fetch.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012, 2014 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -23,6 +23,7 @@ #include <stdlib.h> #include <string.h> #include <sys/ucontext.h> +#include <stdio.h> #include "backend.h" #include "fetch.h" @@ -30,9 +31,11 @@ #include "ptrace.h" #include "proc.h" #include "value.h" +#include "ltrace-elf.h" -static int allocate_gpr(struct fetch_context *ctx, struct Process *proc, - struct arg_type_info *info, struct value *valuep); +static int allocate_gpr(struct fetch_context *ctx, struct process *proc, + struct arg_type_info *info, struct value *valuep, + size_t off, bool is_hfa_type); /* Floating point registers have the same width on 32-bit as well as * 64-bit PPC, but <ucontext.h> presents a different API depending on @@ -55,18 +58,39 @@ struct fetch_context { arch_addr_t stack_pointer; int greg; int freg; - int ret_struct; + bool ret_struct; union { gregs32_t r32; gregs64_t r64; } regs; struct fpregs_t fpregs; - + int vgreg; }; +static bool +is_eligible_hfa(struct arg_type_info *info, + struct arg_type_info **hfa_infop, size_t *hfa_countp) +{ + size_t hfa_count; + struct arg_type_info *hfa_info = type_get_hfa_type(info, &hfa_count); + + if (hfa_info != NULL && hfa_count <= 8 + && (hfa_info->type == ARGTYPE_FLOAT + || hfa_info->type == ARGTYPE_DOUBLE)) { + + if (hfa_infop != NULL) + *hfa_infop = hfa_info; + if (hfa_countp != NULL) + *hfa_countp = hfa_count; + return true; + } + + return false; +} + static int -fetch_context_init(struct Process *proc, struct fetch_context *context) +fetch_context_init(struct process *proc, struct fetch_context *context) { context->greg = 3; context->freg = 1; @@ -74,7 +98,8 @@ fetch_context_init(struct Process *proc, struct fetch_context *context) if (proc->e_machine == EM_PPC) context->stack_pointer = proc->stack_pointer + 8; else - context->stack_pointer = proc->stack_pointer + 112; + context->stack_pointer = proc->stack_pointer + + STACK_FRAME_OVERHEAD; /* When ltrace is 64-bit, we might use PTRACE_GETREGS to * obtain 64-bit as well as 32-bit registers. But if we do it @@ -108,7 +133,7 @@ fetch_context_init(struct Process *proc, struct fetch_context *context) } struct fetch_context * -arch_fetch_arg_init(enum tof type, struct Process *proc, +arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { struct fetch_context *context = malloc(sizeof(*context)); @@ -118,21 +143,45 @@ arch_fetch_arg_init(enum tof type, struct Process *proc, return NULL; } + context->vgreg = context->greg; + /* Aggregates or unions of any length, and character strings * of length longer than 8 bytes, will be returned in a * storage buffer allocated by the caller. The caller will * pass the address of this buffer as a hidden first argument * in r3, causing the first explicit argument to be passed in - * r4. */ - context->ret_struct = ret_info->type == ARGTYPE_STRUCT; - if (context->ret_struct) + * r4. + */ + + context->ret_struct = false; + + if (ppc64_call_elf_abi == 2) { + /* With ELFv2 ABI, aggregates that consist + * (recursively) only of members of the same + * floating-point or vector type, are passed in a + * series of floating-point resp. vector registers. + * Additionally, when returning any aggregate of up to + * 16 bytes, general-purpose registers are used. */ + + if (ret_info->type == ARGTYPE_STRUCT + && ! is_eligible_hfa(ret_info, NULL, NULL) + && type_sizeof(proc, ret_info) > 16) { + + context->ret_struct = true; + context->greg++; + context->stack_pointer += 8; + } + + } else if (ret_info->type == ARGTYPE_STRUCT) { + context->ret_struct = true; context->greg++; + } return context; } struct fetch_context * -arch_fetch_arg_clone(struct Process *proc, +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) { struct fetch_context *clone = malloc(sizeof(*context)); @@ -143,8 +192,9 @@ arch_fetch_arg_clone(struct Process *proc, } static int -allocate_stack_slot(struct fetch_context *ctx, struct Process *proc, - struct arg_type_info *info, struct value *valuep) +allocate_stack_slot(struct fetch_context *ctx, struct process *proc, + struct arg_type_info *info, struct value *valuep, + bool is_hfa_type) { size_t sz = type_sizeof(proc, info); if (sz == (size_t)-1) @@ -152,10 +202,16 @@ allocate_stack_slot(struct fetch_context *ctx, struct Process *proc, size_t a = type_alignof(proc, info); size_t off = 0; - if (proc->e_machine == EM_PPC && a < 4) + if (proc->e_machine == EM_PPC && a < 4) { a = 4; - else if (proc->e_machine == EM_PPC64 && a < 8) + } else if (ppc64_call_elf_abi == 2) { + if (proc->e_machine == EM_PPC64 && sz == 4 && is_hfa_type) { + a = 4; + } else + a = 8; + } else if (proc->e_machine == EM_PPC64 && a < 8) { a = 8; + } /* XXX Remove the two double casts when arch_addr_t * becomes integral type. */ @@ -164,13 +220,13 @@ allocate_stack_slot(struct fetch_context *ctx, struct Process *proc, if (valuep != NULL) value_in_inferior(valuep, ctx->stack_pointer + off); - ctx->stack_pointer += sz; + ctx->stack_pointer += a; return 0; } static uint64_t -read_gpr(struct fetch_context *ctx, struct Process *proc, int reg_num) +read_gpr(struct fetch_context *ctx, struct process *proc, int reg_num) { if (proc->e_machine == EM_PPC) return ctx->regs.r32[reg_num]; @@ -215,20 +271,36 @@ align_small_int(unsigned char *buf, size_t w, size_t sz) } static int -allocate_gpr(struct fetch_context *ctx, struct Process *proc, - struct arg_type_info *info, struct value *valuep) +allocate_gpr(struct fetch_context *ctx, struct process *proc, + struct arg_type_info *info, struct value *valuep, + size_t off, bool is_hfa_type) { if (ctx->greg > 10) - return allocate_stack_slot(ctx, proc, info, valuep); + return allocate_stack_slot(ctx, proc, info, valuep, is_hfa_type); - int reg_num = ctx->greg++; - if (valuep == NULL) - return 0; + int reg_num = ctx->greg; size_t sz = type_sizeof(proc, info); if (sz == (size_t)-1) return -1; assert(sz == 1 || sz == 2 || sz == 4 || sz == 8); + + if (ppc64_call_elf_abi == 2) { + /* Consume the stack slot corresponding to this arg. */ + if ((sz + off) >= 8) + ctx->greg++; + + if (is_hfa_type) + ctx->stack_pointer += sz; + else + ctx->stack_pointer += 8; + } else { + ctx->greg++; + } + + if (valuep == NULL) + return 0; + if (value_reserve(valuep, sz) == NULL) return -1; @@ -240,13 +312,14 @@ allocate_gpr(struct fetch_context *ctx, struct Process *proc, u.i64 = read_gpr(ctx, proc, reg_num); if (proc->e_machine == EM_PPC) align_small_int(u.buf, 8, sz); - memcpy(value_get_raw_data(valuep), u.buf, sz); + memcpy(value_get_raw_data(valuep), u.buf + off, sz); return 0; } static int -allocate_float(struct fetch_context *ctx, struct Process *proc, - struct arg_type_info *info, struct value *valuep) +allocate_float(struct fetch_context *ctx, struct process *proc, + struct arg_type_info *info, struct value *valuep, + size_t off, bool is_hfa_type) { int pool = proc->e_machine == EM_PPC64 ? 13 : 8; if (ctx->freg <= pool) { @@ -257,8 +330,12 @@ allocate_float(struct fetch_context *ctx, struct Process *proc, } u = { .d = ctx->fpregs.fpregs[ctx->freg] }; ctx->freg++; + + if (!is_hfa_type) + ctx->vgreg++; + if (proc->e_machine == EM_PPC64) - allocate_gpr(ctx, proc, info, NULL); + allocate_gpr(ctx, proc, info, NULL, off, is_hfa_type); size_t sz = sizeof(double); if (info->type == ARGTYPE_FLOAT) { @@ -272,11 +349,113 @@ allocate_float(struct fetch_context *ctx, struct Process *proc, memcpy(value_get_raw_data(valuep), u.buf, sz); return 0; } - return allocate_stack_slot(ctx, proc, info, valuep); + return allocate_stack_slot(ctx, proc, info, valuep, is_hfa_type); } static int -allocate_argument(struct fetch_context *ctx, struct Process *proc, +allocate_hfa(struct fetch_context *ctx, struct process *proc, + struct arg_type_info *info, struct value *valuep, + enum arg_type hfa_type, size_t hfa_count) +{ + size_t sz = type_sizeof(proc, info); + if (sz == (size_t)-1) + return -1; + + /* There are two changes regarding structure return types: + * * heterogeneous float/vector structs are returned in + * (multiple) FP/vector registers, instead of via implicit + * reference. + * * small structs (up to 16 bytes) are return in one or two + * GPRs, instead of via implicit reference. + * + * Other structures (larger than 16 bytes, not heterogeneous) + * are still returned via implicit reference (i.e. a pointer + * to memory where to return the struct being passed in r3). + * Of course, whether or not an implicit reference pointer is + * present will shift the remaining arguments, so you need to + * get this right for ELFv2 in order to get the arguments + * correct. + * + * If an actual parameter is known to correspond to an HFA + * formal parameter, each element is passed in the next + * available floating-point argument register starting at fp1 + * until the fp13. The remaining elements of the aggregate are + * passed on the stack. + */ + size_t slot_off = 0; + + unsigned char *buf = value_reserve(valuep, sz); + if (buf == NULL) + return -1; + + struct arg_type_info *hfa_info = type_get_simple(hfa_type); + size_t hfa_sz = type_sizeof(proc, hfa_info); + + while (hfa_count > 0 && ctx->freg <= 13) { + struct value tmp; + value_init(&tmp, proc, NULL, hfa_info, 0); + int rc = allocate_float(ctx, proc, hfa_info, + &tmp, slot_off, true); + if (rc == 0) + memcpy(buf, value_get_data(&tmp, NULL), hfa_sz); + value_destroy(&tmp); + + if (rc < 0) + return -1; + + slot_off += hfa_sz; + buf += hfa_sz; + hfa_count--; + if (slot_off == 8) { + slot_off = 0; + ctx->vgreg++; + } + } + if (hfa_count == 0) + return 0; + + /* if no remaining FP, GPR corresponding to slot is used + * Mostly it is in part of r10. */ + if (ctx->vgreg == 10) { + while (ctx->vgreg <= 10) { + struct value tmp; + value_init(&tmp, proc, NULL, hfa_info, 0); + union { + uint64_t i64; + unsigned char buf[0]; + } u; + + u.i64 = read_gpr(ctx, proc, ctx->vgreg); + + memcpy(buf, u.buf + slot_off, hfa_sz); + slot_off += hfa_sz; + buf += hfa_sz; + hfa_count--; + ctx->stack_pointer += hfa_sz; + if (slot_off >= 8 ) { + slot_off = 0; + ctx->vgreg++; + } + value_destroy(&tmp); + } + } + + /* Remaining values are on stack */ + while (hfa_count > 0) { + struct value tmp; + value_init(&tmp, proc, NULL, hfa_info, 0); + + value_in_inferior(&tmp, ctx->stack_pointer); + memcpy(buf, value_get_data(&tmp, NULL), hfa_sz); + ctx->stack_pointer += hfa_sz; + buf += hfa_sz; + hfa_count--; + } + return 0; +} + +static int +allocate_argument(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep) { /* Floating point types and void are handled specially. */ @@ -287,12 +466,20 @@ allocate_argument(struct fetch_context *ctx, struct Process *proc, case ARGTYPE_FLOAT: case ARGTYPE_DOUBLE: - return allocate_float(ctx, proc, info, valuep); + return allocate_float(ctx, proc, info, valuep, + 8 - type_sizeof(proc,info), false); case ARGTYPE_STRUCT: if (proc->e_machine == EM_PPC) { if (value_pass_by_reference(valuep) < 0) return -1; + } else if (ppc64_call_elf_abi == 2) { + struct arg_type_info *hfa_info; + size_t hfa_count; + if (is_eligible_hfa(info, &hfa_info, &hfa_count)) { + return allocate_hfa(ctx, proc, info, valuep, + hfa_info->type, hfa_count); + } } else { /* PPC64: Fixed size aggregates and unions passed by * value are mapped to as many doublewords of the @@ -326,6 +513,7 @@ allocate_argument(struct fetch_context *ctx, struct Process *proc, size_t sz = type_sizeof(proc, valuep->type); if (sz == (size_t)-1) return -1; + size_t slots = (sz + width - 1) / width; /* Round up. */ unsigned char *buf = value_reserve(valuep, slots * width); if (buf == NULL) @@ -346,9 +534,11 @@ allocate_argument(struct fetch_context *ctx, struct Process *proc, struct arg_type_info *fp_info = type_get_fp_equivalent(valuep->type); if (fp_info != NULL) - rc = allocate_float(ctx, proc, fp_info, &val); + rc = allocate_float(ctx, proc, fp_info, &val, + 8-type_sizeof(proc,info), false); else - rc = allocate_gpr(ctx, proc, long_info, &val); + rc = allocate_gpr(ctx, proc, long_info, &val, + 0, false); if (rc >= 0) { memcpy(ptr, value_get_data(&val, NULL), width); @@ -363,6 +553,7 @@ allocate_argument(struct fetch_context *ctx, struct Process *proc, return rc; } +#ifndef __LITTLE_ENDIAN__ /* Small values need post-processing. */ if (sz < width) { switch (info->type) { @@ -394,13 +585,14 @@ allocate_argument(struct fetch_context *ctx, struct Process *proc, break; } } +#endif return 0; } int arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { return allocate_argument(ctx, proc, info, valuep); @@ -408,9 +600,12 @@ arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, int arch_fetch_retval(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { + if (fetch_context_init(proc, ctx) < 0) + return -1; + if (ctx->ret_struct) { assert(info->type == ARGTYPE_STRUCT); @@ -424,8 +619,6 @@ arch_fetch_retval(struct fetch_context *ctx, enum tof type, return 0; } - if (fetch_context_init(proc, ctx) < 0) - return -1; return allocate_argument(ctx, proc, info, valuep); } diff --git a/sysdeps/linux-gnu/ppc/plt.c b/sysdeps/linux-gnu/ppc/plt.c index f83f087..45ed7fb 100644 --- a/sysdeps/linux-gnu/ppc/plt.c +++ b/sysdeps/linux-gnu/ppc/plt.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Paul Gilliam * @@ -25,6 +25,7 @@ #include <errno.h> #include <inttypes.h> #include <assert.h> +#include <stdbool.h> #include <string.h> #include "proc.h" @@ -104,6 +105,28 @@ * through half the dynamic linker, we just let the thread run and hit * this breakpoint. When it hits, we know the PLT entry was resolved. * + * Another twist comes from tracing slots corresponding to + * R_PPC64_JMP_IREL relocations. These have no dedicated PLT entry. + * The calls are done directly from stubs, and the .plt entry + * (actually .iplt entry, these live in a special section) is resolved + * in advance before the binary starts. Because there's no PLT entry, + * we put the PLT breakpoints directly to the IFUNC resolver code, and + * then would like them to behave like ordinary PLT slots, including + * catching the point where these get resolved to unresolve them. So + * for the first call (which is the actual resolver call), we pretend + * that this breakpoint is artificial and has no associated symbol, + * and turn it on fully only after the first hit. Ideally we would + * trace that first call as well, but then the stepper, which tries to + * catch the point where the slot is resolved, would hit the return + * breakpoint and that's not currently handled well. + * + * On PPC32 with secure PLT, the address of IFUNC symbols in main + * binary actually isn't of the resolver, but of a PLT slot. We + * therefore have to locate the corresponding PLT relocation (which is + * of type R_PPC_IRELATIVE) and request that it be traced. The addend + * of that relocation is an address of resolver, and we request + * tracing of the xyz.IFUNC symbol there. + * * XXX TODO If we have hardware watch point, we might put a read watch * on .plt slot, and discover the offenders this way. I don't know * the details, but I assume at most a handful (like, one or two, if @@ -113,7 +136,11 @@ */ #define PPC_PLT_STUB_SIZE 16 -#define PPC64_PLT_STUB_SIZE 8 //xxx +#if _CALL_ELF != 2 +#define PPC64_PLT_STUB_SIZE 8 +#else +#define PPC64_PLT_STUB_SIZE 4 +#endif static inline int host_powerpc64() @@ -125,51 +152,6 @@ host_powerpc64() #endif } -int -read_target_4(struct Process *proc, arch_addr_t addr, uint32_t *lp) -{ - unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0); - if (l == -1UL && errno) - return -1; -#ifdef __powerpc64__ - l >>= 32; -#endif - *lp = l; - return 0; -} - -static int -read_target_8(struct Process *proc, arch_addr_t addr, uint64_t *lp) -{ - unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0); - if (l == -1UL && errno) - return -1; - if (host_powerpc64()) { - *lp = l; - } else { - unsigned long l2 = ptrace(PTRACE_PEEKTEXT, proc->pid, - addr + 4, 0); - if (l2 == -1UL && errno) - return -1; - *lp = ((uint64_t)l << 32) | l2; - } - return 0; -} - -int -read_target_long(struct Process *proc, arch_addr_t addr, uint64_t *lp) -{ - if (proc->e_machine == EM_PPC) { - uint32_t w; - int ret = read_target_4(proc, addr, &w); - if (ret >= 0) - *lp = (uint64_t)w; - return ret; - } else { - return read_target_8(proc, addr, lp); - } -} - static void mark_as_resolved(struct library_symbol *libsym, GElf_Addr value) { @@ -177,61 +159,99 @@ mark_as_resolved(struct library_symbol *libsym, GElf_Addr value) libsym->arch.resolved_value = value; } +static void +ppc32_delayed_symbol(struct library_symbol *libsym) +{ + /* arch_dynlink_done is called on attach as well. In that + * case some slots will have been resolved already. + * Unresolved PLT looks like this: + * + * <sleep@plt>: li r11,0 + * <sleep@plt+4>: b "resolve" + * + * "resolve" is another address in PLTGOT (the same block that + * all the PLT slots are it). When resolved, it looks either + * this way: + * + * <sleep@plt>: b 0xfea88d0 <sleep> + * + * Which is easy to detect. It can also look this way: + * + * <sleep@plt>: li r11,0 + * <sleep@plt+4>: b "dispatch" + * + * The "dispatch" address lies in PLTGOT as well. In current + * GNU toolchain, "dispatch" address is the same as PLTGOT + * address. We rely on this to figure out whether the address + * is resolved or not. */ + + uint32_t insn1 = libsym->arch.resolved_value >> 32; + uint32_t insn2 = (uint32_t) libsym->arch.resolved_value; + if ((insn1 & BRANCH_MASK) == B_INSN + || ((insn2 & BRANCH_MASK) == B_INSN + /* XXX double cast */ +#ifdef __LITTLE_ENDIAN__ + && (ppc_branch_dest(libsym->enter_addr + 4, insn1) + == (arch_addr_t) (long) libsym->lib->arch.pltgot_addr))) +#else + && (ppc_branch_dest(libsym->enter_addr + 4, insn2) + == (arch_addr_t) (long) libsym->lib->arch.pltgot_addr))) +#endif + { + mark_as_resolved(libsym, libsym->arch.resolved_value); + } +} + void -arch_dynlink_done(struct Process *proc) +arch_dynlink_done(struct process *proc) { - /* On PPC32 with BSS PLT, we need to enable delayed symbols. */ + /* We may need to activate delayed symbols. */ struct library_symbol *libsym = NULL; while ((libsym = proc_each_symbol(proc, libsym, library_symbol_delayed_cb, NULL))) { - if (read_target_8(proc, libsym->enter_addr, - &libsym->arch.resolved_value) < 0) { + if (proc_read_64(proc, libsym->enter_addr, + &libsym->arch.resolved_value) < 0) { fprintf(stderr, "couldn't read PLT value for %s(%p): %s\n", libsym->name, libsym->enter_addr, strerror(errno)); - return; + return; } - /* arch_dynlink_done is called on attach as well. In - * that case some slots will have been resolved - * already. Unresolved PLT looks like this: - * - * <sleep@plt>: li r11,0 - * <sleep@plt+4>: b "resolve" - * - * "resolve" is another address in PLTGOT (the same - * block that all the PLT slots are it). When - * resolved, it looks either this way: - * - * <sleep@plt>: b 0xfea88d0 <sleep> - * - * Which is easy to detect. It can also look this - * way: - * - * <sleep@plt>: li r11,0 - * <sleep@plt+4>: b "dispatch" - * - * The "dispatch" address lies in PLTGOT as well. In - * current GNU toolchain, "dispatch" address is the - * same as PLTGOT address. We rely on this to figure - * out whether the address is resolved or not. */ - uint32_t insn1 = libsym->arch.resolved_value >> 32; - uint32_t insn2 = (uint32_t)libsym->arch.resolved_value; - if ((insn1 & BRANCH_MASK) == B_INSN - || ((insn2 & BRANCH_MASK) == B_INSN - /* XXX double cast */ - && (ppc_branch_dest(libsym->enter_addr + 4, insn2) - == (void*)(long)libsym->lib->arch.pltgot_addr))) - mark_as_resolved(libsym, libsym->arch.resolved_value); + if (proc->e_machine == EM_PPC) + ppc32_delayed_symbol(libsym); if (proc_activate_delayed_symbol(proc, libsym) < 0) return; - /* XXX double cast */ - libsym->arch.plt_slot_addr - = (GElf_Addr)(uintptr_t)libsym->enter_addr; + if (proc->e_machine == EM_PPC) + /* XXX double cast */ + libsym->arch.plt_slot_addr + = (GElf_Addr) (uintptr_t) libsym->enter_addr; + } +} + +static bool +reloc_is_irelative(int machine, GElf_Rela *rela) +{ + bool irelative = false; + if (machine == EM_PPC64) { +#ifdef __LITTLE_ENDIAN__ +# ifdef R_PPC64_IRELATIVE + irelative = GELF_R_TYPE(rela->r_info) == R_PPC64_IRELATIVE; +# endif +#else +# ifdef R_PPC64_JMP_IREL + irelative = GELF_R_TYPE(rela->r_info) == R_PPC64_JMP_IREL; +# endif +#endif + } else { + assert(machine == EM_PPC); +#ifdef R_PPC_IRELATIVE + irelative = GELF_R_TYPE(rela->r_info) == R_PPC_IRELATIVE; +#endif } + return irelative; } GElf_Addr @@ -244,10 +264,28 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) } else if (lte->ehdr.e_machine == EM_PPC) { return rela->r_offset; + /* Beyond this point, we are on PPC64, but don't have stub + * symbols. */ + + } else if (reloc_is_irelative(lte->ehdr.e_machine, rela)) { + + /* Put JMP_IREL breakpoint to resolver, since there's + * no dedicated PLT entry. */ + + assert(rela->r_addend != 0); + /* XXX double cast */ + arch_addr_t res_addr = (arch_addr_t) (uintptr_t) rela->r_addend; + if (arch_translate_address(lte, res_addr, &res_addr) < 0) { + fprintf(stderr, "Couldn't OPD-translate IRELATIVE " + "resolver address.\n"); + return 0; + } + /* XXX double cast */ + return (GElf_Addr) (uintptr_t) res_addr; + } else { - /* If we get here, we don't have stub symbols. In - * that case we put brakpoints to PLT entries the same - * as the PPC32 secure PLT case does. */ + /* We put brakpoints to PLT entries the same as the + * PPC32 secure PLT case does. */ assert(lte->arch.plt_stub_vma != 0); return lte->arch.plt_stub_vma + PPC64_PLT_STUB_SIZE * ndx; } @@ -258,12 +296,13 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) * ourselves with bias, as the values in OPD have been resolved * already. */ int -arch_translate_address_dyn(struct Process *proc, +arch_translate_address_dyn(struct process *proc, arch_addr_t addr, arch_addr_t *ret) { if (proc->e_machine == EM_PPC64) { +#if _CALL_ELF != 2 uint64_t value; - if (read_target_8(proc, addr, &value) < 0) { + if (proc_read_64(proc, addr, &value) < 0) { fprintf(stderr, "dynamic .opd translation of %p: %s\n", addr, strerror(errno)); @@ -273,6 +312,7 @@ arch_translate_address_dyn(struct Process *proc, * arch_addr_t becomes integral type. */ *ret = (arch_addr_t)(uintptr_t)value; return 0; +#endif } *ret = addr; @@ -283,7 +323,8 @@ int arch_translate_address(struct ltelf *lte, arch_addr_t addr, arch_addr_t *ret) { - if (lte->ehdr.e_machine == EM_PPC64) { + if (lte->ehdr.e_machine == EM_PPC64 + && !lte->arch.elfv2_abi) { /* XXX The double cast should be removed when * arch_addr_t becomes integral type. */ GElf_Xword offset @@ -307,7 +348,8 @@ load_opd_data(struct ltelf *lte, struct library *lib) { Elf_Scn *sec; GElf_Shdr shdr; - if (elf_get_section_named(lte, ".opd", &sec, &shdr) < 0) { + if (elf_get_section_named(lte, ".opd", &sec, &shdr) < 0 + || sec == NULL) { fail: fprintf(stderr, "couldn't find .opd data\n"); return -1; @@ -324,7 +366,7 @@ load_opd_data(struct ltelf *lte, struct library *lib) } void * -sym2addr(struct Process *proc, struct library_symbol *sym) +sym2addr(struct process *proc, struct library_symbol *sym) { return sym->enter_addr; } @@ -335,8 +377,9 @@ get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot, Elf_Data *plt_data) Elf_Scn *ppcgot_sec = NULL; GElf_Shdr ppcgot_shdr; if (ppcgot != 0 - && elf_get_section_covering(lte, ppcgot, - &ppcgot_sec, &ppcgot_shdr) < 0) + && (elf_get_section_covering(lte, ppcgot, + &ppcgot_sec, &ppcgot_shdr) < 0 + || ppcgot_sec == NULL)) fprintf(stderr, "DT_PPC_GOT=%#"PRIx64", but no such section found\n", ppcgot); @@ -377,38 +420,6 @@ get_glink_vma(struct ltelf *lte, GElf_Addr ppcgot, Elf_Data *plt_data) } static int -load_dynamic_entry(struct ltelf *lte, int tag, GElf_Addr *valuep) -{ - Elf_Scn *scn; - GElf_Shdr shdr; - if (elf_get_section_type(lte, SHT_DYNAMIC, &scn, &shdr) < 0 - || scn == NULL) { - fail: - fprintf(stderr, "Couldn't get SHT_DYNAMIC: %s\n", - elf_errmsg(-1)); - return -1; - } - - Elf_Data *data = elf_loaddata(scn, &shdr); - if (data == NULL) - goto fail; - - size_t j; - for (j = 0; j < shdr.sh_size / shdr.sh_entsize; ++j) { - GElf_Dyn dyn; - if (gelf_getdyn(data, j, &dyn) == NULL) - goto fail; - - if(dyn.d_tag == tag) { - *valuep = dyn.d_un.d_ptr; - return 0; - } - } - - return -1; -} - -static int nonzero_data(Elf_Data *data) { /* We are not supposed to get here if there's no PLT. */ @@ -425,10 +436,28 @@ nonzero_data(Elf_Data *data) return 0; } +static enum callback_status +reloc_copy_if_irelative(GElf_Rela *rela, void *data) +{ + struct ltelf *lte = data; + + return CBS_STOP_IF(reloc_is_irelative(lte->ehdr.e_machine, rela) + && VECT_PUSHBACK(<e->plt_relocs, rela) < 0); +} + int arch_elf_init(struct ltelf *lte, struct library *lib) { + + /* Check for ABIv2 in ELF header processor specific flag. */ +#ifndef EF_PPC64_ABI + assert (! (lte->ehdr.e_flags & 3 ) == 2) +#else + lte->arch.elfv2_abi=((lte->ehdr.e_flags & EF_PPC64_ABI) == 2) ; +#endif + if (lte->ehdr.e_machine == EM_PPC64 + && !lte->arch.elfv2_abi && load_opd_data(lte, lib) < 0) return -1; @@ -445,23 +474,51 @@ arch_elf_init(struct ltelf *lte, struct library *lib) * value to something conspicuous. */ lib->arch.bss_plt_prelinked = -1; + /* On PPC64 and PPC32 secure, IRELATIVE relocations actually + * relocate .iplt section, and as such are stored in .rela.dyn + * (where all non-PLT relocations are stored) instead of + * .rela.plt. Add these to lte->plt_relocs. */ + + GElf_Addr rela, relasz; + Elf_Scn *rela_sec; + GElf_Shdr rela_shdr; + if ((lte->ehdr.e_machine == EM_PPC64 || lte->arch.secure_plt) + && elf_load_dynamic_entry(lte, DT_RELA, &rela) == 0 + && elf_load_dynamic_entry(lte, DT_RELASZ, &relasz) == 0 + && elf_get_section_covering(lte, rela, &rela_sec, &rela_shdr) == 0 + && rela_sec != NULL) { + + struct vect v; + VECT_INIT(&v, GElf_Rela); + int ret = elf_read_relocs(lte, rela_sec, &rela_shdr, &v); + if (ret >= 0 + && VECT_EACH(&v, GElf_Rela, NULL, + reloc_copy_if_irelative, lte) != NULL) + ret = -1; + + VECT_DESTROY(&v, GElf_Rela, NULL, NULL); + + if (ret < 0) + return ret; + } + if (lte->ehdr.e_machine == EM_PPC && lte->arch.secure_plt) { GElf_Addr ppcgot; - if (load_dynamic_entry(lte, DT_PPC_GOT, &ppcgot) < 0) { + if (elf_load_dynamic_entry(lte, DT_PPC_GOT, &ppcgot) < 0) { fprintf(stderr, "couldn't find DT_PPC_GOT\n"); return -1; } GElf_Addr glink_vma = get_glink_vma(lte, ppcgot, lte->plt_data); - assert(lte->relplt_size % 12 == 0); - size_t count = lte->relplt_size / 12; // size of RELA entry + size_t count = vect_size(<e->plt_relocs); lte->arch.plt_stub_vma = glink_vma - - (GElf_Addr)count * PPC_PLT_STUB_SIZE; + - (GElf_Addr) count * PPC_PLT_STUB_SIZE; debug(1, "stub_vma is %#" PRIx64, lte->arch.plt_stub_vma); } else if (lte->ehdr.e_machine == EM_PPC64) { GElf_Addr glink_vma; - if (load_dynamic_entry(lte, DT_PPC64_GLINK, &glink_vma) < 0) { + if (elf_load_dynamic_entry(lte, DT_PPC64_GLINK, + &glink_vma) < 0) { fprintf(stderr, "couldn't find DT_PPC64_GLINK\n"); return -1; } @@ -471,8 +528,8 @@ arch_elf_init(struct ltelf *lte, struct library *lib) } else { /* By exhaustion--PPC32 BSS. */ - if (load_dynamic_entry(lte, DT_PLTGOT, - &lib->arch.pltgot_addr) < 0) { + if (elf_load_dynamic_entry(lte, DT_PLTGOT, + &lib->arch.pltgot_addr) < 0) { fprintf(stderr, "couldn't find DT_PLTGOT\n"); return -1; } @@ -560,7 +617,7 @@ arch_elf_init(struct ltelf *lte, struct library *lib) } static int -read_plt_slot_value(struct Process *proc, GElf_Addr addr, GElf_Addr *valp) +read_plt_slot_value(struct process *proc, GElf_Addr addr, GElf_Addr *valp) { /* On PPC64, we read from .plt, which contains 8 byte * addresses. On PPC32 we read from .plt, which contains 4 @@ -568,8 +625,8 @@ read_plt_slot_value(struct Process *proc, GElf_Addr addr, GElf_Addr *valp) * either can change. */ uint64_t l; /* XXX double cast. */ - if (read_target_8(proc, (arch_addr_t)(uintptr_t)addr, &l) < 0) { - fprintf(stderr, "ptrace .plt slot value @%#" PRIx64": %s\n", + if (proc_read_64(proc, (arch_addr_t)(uintptr_t)addr, &l) < 0) { + debug(DEBUG_EVENT, "ptrace .plt slot value @%#" PRIx64": %s", addr, strerror(errno)); return -1; } @@ -579,14 +636,14 @@ read_plt_slot_value(struct Process *proc, GElf_Addr addr, GElf_Addr *valp) } static int -unresolve_plt_slot(struct Process *proc, GElf_Addr addr, GElf_Addr value) +unresolve_plt_slot(struct process *proc, GElf_Addr addr, GElf_Addr value) { /* We only modify plt_entry[0], which holds the resolved * address of the routine. We keep the TOC and environment * pointers intact. Hence the only adjustment that we need to * do is to IP. */ if (ptrace(PTRACE_POKETEXT, proc->pid, addr, value) < 0) { - fprintf(stderr, "failed to unresolve .plt slot: %s\n", + debug(DEBUG_EVENT, "failed to unresolve .plt slot: %s", strerror(errno)); return -1; } @@ -594,36 +651,156 @@ unresolve_plt_slot(struct Process *proc, GElf_Addr addr, GElf_Addr value) } enum plt_status -arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, +arch_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret) +{ +#ifndef PPC64_LOCAL_ENTRY_OFFSET + assert(! lte->arch.elfv2_abi); +#else + /* With ABIv2 st_other field contains an offset. */ + if (lte->arch.elfv2_abi) + addr += PPC64_LOCAL_ENTRY_OFFSET(sym->st_other); +#endif + + int st_info = GELF_ST_TYPE(sym->st_info); + + if ((lte->ehdr.e_machine != EM_PPC && sym->st_other == 0) + || lte->ehdr.e_type == ET_DYN + || (st_info == STT_FUNC && ! sym->st_other)) + return PLT_DEFAULT; + + if (st_info == STT_FUNC) { + /* Put the default symbol to the chain. + * The addr has already been updated with + * symbol offset */ + char *full_name = strdup(name); + if (full_name == NULL) { + fprintf(stderr, "couldn't copy name of %s: %s\n", + name, strerror(errno)); + free(full_name); + return PLT_FAIL; + } + struct library_symbol *libsym = malloc(sizeof *libsym); + if (libsym == NULL + || library_symbol_init(libsym, addr, full_name, 1, + LS_TOPLT_NONE) < 0) { + free(libsym); + delete_symbol_chain(libsym); + libsym = NULL; + fprintf(stderr, "Couldn't add symbol %s" + "for tracing.\n", name); + } + full_name = NULL; + libsym->next = *ret; + *ret = libsym; + return PLT_OK; + } + + bool ifunc = false; +#ifdef STT_GNU_IFUNC + ifunc = GELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC; +#endif + if (! ifunc) + return PLT_DEFAULT; + + size_t len = vect_size(<e->plt_relocs); + size_t i; + for (i = 0; i < len; ++i) { + GElf_Rela *rela = VECT_ELEMENT(<e->plt_relocs, GElf_Rela, i); + if (sym->st_value == arch_plt_sym_val(lte, i, rela)) { + + char *tmp_name = linux_append_IFUNC_to_name(name); + struct library_symbol *libsym = malloc(sizeof *libsym); + + /* XXX double cast. */ + arch_addr_t resolver_addr + = (arch_addr_t) (uintptr_t) rela->r_addend; + + if (tmp_name == NULL || libsym == NULL + || library_symbol_init(libsym, resolver_addr, + tmp_name, 1, + LS_TOPLT_EXEC) < 0) { + fail: + free(tmp_name); + free(libsym); + return PLT_FAIL; + } + + if (elf_add_plt_entry(proc, lte, name, rela, + i, ret) < 0) { + library_symbol_destroy(libsym); + goto fail; + } + + libsym->proto = linux_IFUNC_prototype(); + libsym->next = *ret; + *ret = libsym; + return PLT_OK; + } + } + + *ret = NULL; + return PLT_OK; +} + +struct ppc_unresolve_data { + struct ppc_unresolve_data *self; /* A canary. */ + GElf_Addr plt_entry_addr; + GElf_Addr plt_slot_addr; + GElf_Addr plt_slot_value; + bool is_irelative; +}; + +enum plt_status +arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, const char *a_name, GElf_Rela *rela, size_t ndx, struct library_symbol **ret) { - if (lte->ehdr.e_machine == EM_PPC) { - if (lte->arch.secure_plt) - return plt_default; + bool is_irelative = reloc_is_irelative(lte->ehdr.e_machine, rela); + char *name; + if (! is_irelative) { + name = strdup(a_name); + } else { + GElf_Addr addr = lte->ehdr.e_machine == EM_PPC64 + ? (GElf_Addr) rela->r_addend + : arch_plt_sym_val(lte, ndx, rela); + name = linux_elf_find_irelative_name(lte, addr); + } + + if (name == NULL) { + fail: + free(name); + return PLT_FAIL; + } - struct library_symbol *libsym = NULL; - if (default_elf_add_plt_entry(proc, lte, a_name, rela, ndx, - &libsym) < 0) - return plt_fail; + struct library_symbol *chain = NULL; + if (lte->ehdr.e_machine == EM_PPC) { + if (default_elf_add_plt_entry(proc, lte, name, rela, ndx, + &chain) < 0) + goto fail; - /* On PPC32 with BSS PLT, delay the symbol until - * dynamic linker is done. */ - assert(!libsym->delayed); - libsym->delayed = 1; + if (! lte->arch.secure_plt) { + /* On PPC32 with BSS PLT, delay the symbol + * until dynamic linker is done. */ + assert(!chain->delayed); + chain->delayed = 1; + } - *ret = libsym; - return plt_ok; + ok: + *ret = chain; + free(name); + return PLT_OK; } /* PPC64. If we have stubs, we return a chain of breakpoint * sites, one for each stub that corresponds to this PLT * entry. */ - struct library_symbol *chain = NULL; struct library_symbol **symp; for (symp = <e->arch.stubs; *symp != NULL; ) { struct library_symbol *sym = *symp; - if (strcmp(sym->name, a_name) != 0) { + if (strcmp(sym->name, name) != 0) { symp = &(*symp)->next; continue; } @@ -634,10 +811,8 @@ arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, chain = sym; } - if (chain != NULL) { - *ret = chain; - return plt_ok; - } + if (chain != NULL) + goto ok; /* We don't have stub symbols. Find corresponding .plt slot, * and check whether it contains the corresponding PLT address @@ -648,55 +823,62 @@ arch_elf_add_plt_entry(struct Process *proc, struct ltelf *lte, GElf_Addr plt_entry_addr = arch_plt_sym_val(lte, ndx, rela); GElf_Addr plt_slot_addr = rela->r_offset; + assert(plt_slot_addr >= lte->plt_addr || plt_slot_addr < lte->plt_addr + lte->plt_size); + /* Should avoid to do read if dynamic linker hasn't run yet + * or allow -1 a valid return code. */ GElf_Addr plt_slot_value; - if (read_plt_slot_value(proc, plt_slot_addr, &plt_slot_value) < 0) - return plt_fail; + if (read_plt_slot_value(proc, plt_slot_addr, &plt_slot_value) < 0) { + if (!lte->arch.elfv2_abi) + goto fail; + else + return PPC_PLT_UNRESOLVED; + } - char *name = strdup(a_name); struct library_symbol *libsym = malloc(sizeof(*libsym)); - if (name == NULL || libsym == NULL) { + if (libsym == NULL) { fprintf(stderr, "allocation for .plt slot: %s\n", strerror(errno)); - fail: - free(name); + fail2: free(libsym); - return plt_fail; + goto fail; } /* XXX The double cast should be removed when * arch_addr_t becomes integral type. */ if (library_symbol_init(libsym, - (arch_addr_t)(uintptr_t)plt_entry_addr, + (arch_addr_t) (uintptr_t) plt_entry_addr, name, 1, LS_TOPLT_EXEC) < 0) - goto fail; + goto fail2; libsym->arch.plt_slot_addr = plt_slot_addr; - if (plt_slot_value == plt_entry_addr || plt_slot_value == 0) { + if (! is_irelative + && (plt_slot_value == plt_entry_addr || plt_slot_value == 0)) { libsym->arch.type = PPC_PLT_UNRESOLVED; libsym->arch.resolved_value = plt_entry_addr; - } else { - /* Unresolve the .plt slot. If the binary was - * prelinked, this makes the code invalid, because in - * case of prelinked binary, the dynamic linker - * doesn't update .plt[0] and .plt[1] with addresses - * of the resover. But we don't care, we will never - * need to enter the resolver. That just means that - * we have to un-un-resolve this back before we - * detach. */ - - if (unresolve_plt_slot(proc, plt_slot_addr, plt_entry_addr) < 0) { - library_symbol_destroy(libsym); - goto fail; - } - mark_as_resolved(libsym, plt_slot_value); + /* Mark the symbol for later unresolving. We may not + * do this right away, as this is called by ltrace + * core for all symbols, and only later filtered. We + * only unresolve the symbol before the breakpoint is + * enabled. */ + + libsym->arch.type = PPC_PLT_NEED_UNRESOLVE; + libsym->arch.data = malloc(sizeof *libsym->arch.data); + if (libsym->arch.data == NULL) + goto fail2; + + libsym->arch.data->self = libsym->arch.data; + libsym->arch.data->plt_entry_addr = plt_entry_addr; + libsym->arch.data->plt_slot_addr = plt_slot_addr; + libsym->arch.data->plt_slot_value = plt_slot_value; + libsym->arch.data->is_irelative = is_irelative; } *ret = libsym; - return plt_ok; + return PLT_OK; } void @@ -712,7 +894,7 @@ arch_elf_destroy(struct ltelf *lte) } static void -dl_plt_update_bp_on_hit(struct breakpoint *bp, struct Process *proc) +dl_plt_update_bp_on_hit(struct breakpoint *bp, struct process *proc) { debug(DEBUG_PROCESS, "pid=%d dl_plt_update_bp_on_hit %s(%p)", proc->pid, breakpoint_name(bp), bp->addr); @@ -752,7 +934,7 @@ cb_on_all_stopped(struct process_stopping_handler *self) static enum callback_status cb_keep_stepping_p(struct process_stopping_handler *self) { - struct Process *proc = self->task_enabling_breakpoint; + struct process *proc = self->task_enabling_breakpoint; struct library_symbol *libsym = self->breakpoint_being_enabled->libsym; GElf_Addr value; @@ -799,7 +981,7 @@ cb_keep_stepping_p(struct process_stopping_handler *self) /* Install breakpoint to the address where the change takes * place. If we fail, then that just means that we'll have to * singlestep the next time around as well. */ - struct Process *leader = proc->leader; + struct process *leader = proc->leader; if (leader == NULL || leader->arch.dl_plt_update_bp != NULL) goto done; @@ -807,7 +989,7 @@ cb_keep_stepping_p(struct process_stopping_handler *self) * a store instruction, so moving the breakpoint one * instruction forward is safe. */ arch_addr_t addr = get_instruction_pointer(proc) + 4; - leader->arch.dl_plt_update_bp = insert_breakpoint(proc, addr, NULL); + leader->arch.dl_plt_update_bp = insert_breakpoint_at(proc, addr, NULL); if (leader->arch.dl_plt_update_bp == NULL) goto done; @@ -827,7 +1009,7 @@ done: } static void -jump_to_entry_point(struct Process *proc, struct breakpoint *bp) +jump_to_entry_point(struct process *proc, struct breakpoint *bp) { /* XXX The double cast should be removed when * arch_addr_t becomes integral type. */ @@ -837,10 +1019,19 @@ jump_to_entry_point(struct Process *proc, struct breakpoint *bp) } static void -ppc_plt_bp_continue(struct breakpoint *bp, struct Process *proc) +ppc_plt_bp_continue(struct breakpoint *bp, struct process *proc) { + /* If this is a first call through IREL breakpoint, enable the + * symbol so that it doesn't look like an artificial + * breakpoint anymore. */ + if (bp->libsym == NULL) { + assert(bp->arch.irel_libsym != NULL); + bp->libsym = bp->arch.irel_libsym; + bp->arch.irel_libsym = NULL; + } + switch (bp->libsym->arch.type) { - struct Process *leader; + struct process *leader; void (*on_all_stopped)(struct process_stopping_handler *); enum callback_status (*keep_stepping_p) (struct process_stopping_handler *); @@ -851,6 +1042,7 @@ ppc_plt_bp_continue(struct breakpoint *bp, struct Process *proc) assert(bp->libsym->lib->arch.bss_plt_prelinked == 0); /* Fall through. */ + case PPC_PLT_IRELATIVE: case PPC_PLT_UNRESOLVED: on_all_stopped = NULL; keep_stepping_p = NULL; @@ -877,11 +1069,16 @@ ppc_plt_bp_continue(struct breakpoint *bp, struct Process *proc) return; } +#if _CALL_ELF == 2 + continue_after_breakpoint(proc, bp); +#else jump_to_entry_point(proc, bp); continue_process(proc->pid); +#endif return; case PPC64_PLT_STUB: + case PPC_PLT_NEED_UNRESOLVE: /* These should never hit here. */ break; } @@ -899,7 +1096,7 @@ ppc_plt_bp_continue(struct breakpoint *bp, struct Process *proc) * detect both cases and do any fix-ups necessary to mend this * situation. */ static enum callback_status -detach_task_cb(struct Process *task, void *data) +detach_task_cb(struct process *task, void *data) { struct breakpoint *bp = data; @@ -919,7 +1116,7 @@ detach_task_cb(struct Process *task, void *data) } static void -ppc_plt_bp_retract(struct breakpoint *bp, struct Process *proc) +ppc_plt_bp_retract(struct breakpoint *bp, struct process *proc) { /* On PPC64, we rewrite .plt with PLT entry addresses. This * needs to be undone. Unfortunately, the program may have @@ -933,9 +1130,56 @@ ppc_plt_bp_retract(struct breakpoint *bp, struct Process *proc) } } -void +static void +ppc_plt_bp_install(struct breakpoint *bp, struct process *proc) +{ + /* This should not be an artificial breakpoint. */ + struct library_symbol *libsym = bp->libsym; + if (libsym == NULL) + libsym = bp->arch.irel_libsym; + assert(libsym != NULL); + + if (libsym->arch.type == PPC_PLT_NEED_UNRESOLVE) { + /* Unresolve the .plt slot. If the binary was + * prelinked, this makes the code invalid, because in + * case of prelinked binary, the dynamic linker + * doesn't update .plt[0] and .plt[1] with addresses + * of the resover. But we don't care, we will never + * need to enter the resolver. That just means that + * we have to un-un-resolve this back before we + * detach. */ + + struct ppc_unresolve_data *data = libsym->arch.data; + libsym->arch.data = NULL; + assert(data->self == data); + + GElf_Addr plt_slot_addr = data->plt_slot_addr; + GElf_Addr plt_slot_value = data->plt_slot_value; + GElf_Addr plt_entry_addr = data->plt_entry_addr; + + if (unresolve_plt_slot(proc, plt_slot_addr, + plt_entry_addr) == 0) { + if (! data->is_irelative) { + mark_as_resolved(libsym, plt_slot_value); + } else { + libsym->arch.type = PPC_PLT_IRELATIVE; + libsym->arch.resolved_value = plt_entry_addr; + } + } else { + fprintf(stderr, "Couldn't unresolve %s@%p. Not tracing" + " this symbol.\n", + breakpoint_name(bp), bp->addr); + proc_remove_breakpoint(proc, bp); + } + + free(data); + } +} + +int arch_library_init(struct library *lib) { + return 0; } void @@ -943,9 +1187,10 @@ arch_library_destroy(struct library *lib) { } -void +int arch_library_clone(struct library *retp, struct library *lib) { + return 0; } int @@ -954,13 +1199,22 @@ arch_library_symbol_init(struct library_symbol *libsym) /* We set type explicitly in the code above, where we have the * necessary context. This is for calls from ltrace-elf.c and * such. */ +#if _CALL_ELF == 2 + libsym->arch.type = PPC_PLT_UNRESOLVED; +#else libsym->arch.type = PPC_DEFAULT; +#endif return 0; } void arch_library_symbol_destroy(struct library_symbol *libsym) { + if (libsym->arch.type == PPC_PLT_NEED_UNRESOLVE) { + assert(libsym->arch.data->self == libsym->arch.data); + free(libsym->arch.data); + libsym->arch.data = NULL; + } } int @@ -975,8 +1229,10 @@ arch_library_symbol_clone(struct library_symbol *retp, * don't need PROC here, we can store the data in BP if it is of * interest to us. */ int -arch_breakpoint_init(struct Process *proc, struct breakpoint *bp) +arch_breakpoint_init(struct process *proc, struct breakpoint *bp) { + bp->arch.irel_libsym = NULL; + /* Artificial and entry-point breakpoints are plain. */ if (bp->libsym == NULL || bp->libsym->plt_type != LS_TOPLT_EXEC) return 0; @@ -994,8 +1250,17 @@ arch_breakpoint_init(struct Process *proc, struct breakpoint *bp) static struct bp_callbacks cbs = { .on_continue = ppc_plt_bp_continue, .on_retract = ppc_plt_bp_retract, + .on_install = ppc_plt_bp_install, }; breakpoint_set_callbacks(bp, &cbs); + + /* For JMP_IREL breakpoints, make the breakpoint look + * artificial by hiding the symbol. */ + if (bp->libsym->arch.type == PPC_PLT_IRELATIVE) { + bp->arch.irel_libsym = bp->libsym; + bp->libsym = NULL; + } + return 0; } @@ -1012,7 +1277,7 @@ arch_breakpoint_clone(struct breakpoint *retp, struct breakpoint *sbp) } int -arch_process_init(struct Process *proc) +arch_process_init(struct process *proc) { proc->arch.dl_plt_update_bp = NULL; proc->arch.handler = NULL; @@ -1020,19 +1285,31 @@ arch_process_init(struct Process *proc) } void -arch_process_destroy(struct Process *proc) +arch_process_destroy(struct process *proc) { } int -arch_process_clone(struct Process *retp, struct Process *proc) +arch_process_clone(struct process *retp, struct process *proc) { retp->arch = proc->arch; + + if (retp->arch.dl_plt_update_bp != NULL) { + /* Point it to the corresponding breakpoint in RETP. + * It must be there, this part of PROC has already + * been cloned to RETP. */ + retp->arch.dl_plt_update_bp + = address2bpstruct(retp, + retp->arch.dl_plt_update_bp->addr); + + assert(retp->arch.dl_plt_update_bp != NULL); + } + return 0; } int -arch_process_exec(struct Process *proc) +arch_process_exec(struct process *proc) { return arch_process_init(proc); } diff --git a/sysdeps/linux-gnu/ppc/regs.c b/sysdeps/linux-gnu/ppc/regs.c index 37f89a4..40d7e7a 100644 --- a/sysdeps/linux-gnu/ppc/regs.c +++ b/sysdeps/linux-gnu/ppc/regs.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2002,2008,2009 Juan Cespedes * Copyright (C) 2009 Juan Cespedes * Copyright (C) 2008 Luis Machado, IBM Corporation @@ -40,35 +41,26 @@ #endif void * -get_instruction_pointer(Process *proc) { +get_instruction_pointer(struct process *proc) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, sizeof(long)*PT_NIP, 0); } void -set_instruction_pointer(Process *proc, void *addr) +set_instruction_pointer(struct process *proc, void *addr) { if (ptrace(PTRACE_POKEUSER, proc->pid, sizeof(long)*PT_NIP, addr) != 0) error(0, errno, "set_instruction_pointer"); } void * -get_stack_pointer(Process *proc) { +get_stack_pointer(struct process *proc) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, sizeof(long)*PT_R1, 0); } void * -get_return_addr(Process *proc, void *stack_pointer) { +get_return_addr(struct process *proc, void *stack_pointer) +{ return (void *)ptrace(PTRACE_PEEKUSER, proc->pid, sizeof(long)*PT_LNK, 0); } - -void -set_return_addr(Process *proc, void *addr) { - ptrace(PTRACE_POKEUSER, proc->pid, sizeof(long)*PT_LNK, addr); -} - -/* Grab the value of CTR registers. */ -void * -get_count_register (Process *proc) { - return (void *) ptrace (PTRACE_PEEKUSER, proc->pid, - sizeof (long) * PT_CTR, 0); -} diff --git a/sysdeps/linux-gnu/ppc/trace.c b/sysdeps/linux-gnu/ppc/trace.c index 4357a1e..5aab538 100644 --- a/sysdeps/linux-gnu/ppc/trace.c +++ b/sysdeps/linux-gnu/ppc/trace.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2010,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2010,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2011 Andreas Schwab * Copyright (C) 2002,2004,2008,2009 Juan Cespedes * Copyright (C) 2008 Luis Machado, IBM Corporation @@ -49,7 +49,8 @@ #endif void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ #ifdef __powerpc64__ proc->mask_32bit = (proc->e_machine == EM_PPC); #endif @@ -59,13 +60,20 @@ get_arch_dep(Process *proc) { /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { long pc = (long)get_instruction_pointer(proc); +#ifndef __LITTLE_ENDIAN__ int insn = (int)ptrace(PTRACE_PEEKTEXT, proc->pid, pc - sizeof(long), 0); +#else + int insn = + (int)ptrace(PTRACE_PEEKTEXT, proc->pid, pc - sizeof(int), + 0); +#endif if (insn == SYSCALL_INSN) { *sysnum = @@ -84,18 +92,15 @@ syscall_p(Process *proc, int status, int *sysnum) { /* The atomic skip code is mostly taken from GDB. */ -/* In plt.h. XXX make this official interface. */ -int read_target_4(struct Process *proc, arch_addr_t addr, uint32_t *lp); - -int -arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, - int (*add_cb)(void *addr, void *data), - void *add_cb_data) +enum sw_singlestep_status +arch_sw_singlestep(struct process *proc, struct breakpoint *sbp, + int (*add_cb)(arch_addr_t, struct sw_singlestep_data *), + struct sw_singlestep_data *add_cb_data) { arch_addr_t ip = get_instruction_pointer(proc); struct breakpoint *other = address2bpstruct(proc->leader, ip); - debug(1, "arch_atomic_singlestep pid=%d addr=%p %s(%p)", + debug(1, "arch_sw_singlestep pid=%d addr=%p %s(%p)", proc->pid, ip, breakpoint_name(sbp), sbp->addr); /* If the original instruction was lwarx/ldarx, we can't @@ -107,15 +112,15 @@ arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, } u; if (other != NULL) { memcpy(u.buf, sbp->orig_value, BREAKPOINT_LENGTH); - } else if (read_target_4(proc, ip, &u.insn) < 0) { + } else if (proc_read_32(proc, ip, &u.insn) < 0) { fprintf(stderr, "couldn't read instruction at IP %p\n", ip); /* Do the normal singlestep. */ - return 1; + return SWS_HW; } if ((u.insn & LWARX_MASK) != LWARX_INSTRUCTION && (u.insn & LWARX_MASK) != LDARX_INSTRUCTION) - return 1; + return SWS_HW; debug(1, "singlestep over atomic block at %p", ip); @@ -125,10 +130,14 @@ arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, addr += 4; unsigned long l = ptrace(PTRACE_PEEKTEXT, proc->pid, addr, 0); if (l == (unsigned long)-1 && errno) - return -1; + return SWS_FAIL; uint32_t insn; #ifdef __powerpc64__ +# ifdef __LITTLE_ENDIAN__ + insn = (uint32_t) l ; +# else insn = l >> 32; +# endif #else insn = l; #endif @@ -140,7 +149,7 @@ arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, debug(1, "pid=%d, branch in atomic block from %p to %p", proc->pid, addr, branch_addr); if (add_cb(branch_addr, add_cb_data) < 0) - return -1; + return SWS_FAIL; } /* Assume that the atomic sequence ends with a @@ -157,22 +166,22 @@ arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, if (insn_count > 16) { fprintf(stderr, "[%d] couldn't find end of atomic block" " at %p\n", proc->pid, ip); - return -1; + return SWS_FAIL; } } /* Put the breakpoint to the next instruction. */ addr += 4; if (add_cb(addr, add_cb_data) < 0) - return -1; + return SWS_FAIL; debug(1, "PTRACE_CONT"); ptrace(PTRACE_CONT, proc->pid, 0, 0); - return 0; + return SWS_OK; } size_t -arch_type_sizeof(struct Process *proc, struct arg_type_info *info) +arch_type_sizeof(struct process *proc, struct arg_type_info *info) { if (proc == NULL) return (size_t)-2; @@ -215,7 +224,7 @@ arch_type_sizeof(struct Process *proc, struct arg_type_info *info) } size_t -arch_type_alignof(struct Process *proc, struct arg_type_info *info) +arch_type_alignof(struct process *proc, struct arg_type_info *info) { if (proc == NULL) return (size_t)-2; diff --git a/sysdeps/linux-gnu/proc.c b/sysdeps/linux-gnu/proc.c index 5adfb16..953fd86 100644 --- a/sysdeps/linux-gnu/proc.c +++ b/sysdeps/linux-gnu/proc.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Zachary T Welch, CodeSourcery * Copyright (C) 2010 Joe Damato * Copyright (C) 1998,2008,2009 Juan Cespedes @@ -177,44 +177,46 @@ process_status_cb(const char *line, const char *prefix, void *data) } while (0) switch (c) { - case 'Z': RETURN(ps_zombie); - case 't': RETURN(ps_tracing_stop); + case 'Z': RETURN(PS_ZOMBIE); + case 't': RETURN(PS_TRACING_STOP); case 'T': /* This can be either "T (stopped)" or, for older * kernels, "T (tracing stop)". */ if (!strcmp(status, "T (stopped)\n")) - RETURN(ps_stop); + RETURN(PS_STOP); else if (!strcmp(status, "T (tracing stop)\n")) - RETURN(ps_tracing_stop); + RETURN(PS_TRACING_STOP); else { fprintf(stderr, "Unknown process status: %s", status); - RETURN(ps_stop); /* Some sort of stop + RETURN(PS_STOP); /* Some sort of stop * anyway. */ } case 'D': - case 'S': RETURN(ps_sleeping); + case 'S': RETURN(PS_SLEEPING); } - RETURN(ps_other); + RETURN(PS_OTHER); #undef RETURN } enum process_status process_status(pid_t pid) { - enum process_status ret = ps_invalid; + enum process_status ret = PS_INVALID; FILE * file = open_status_file(pid); if (file != NULL) { each_line_starting(file, "State:\t", &process_status_cb, &ret); fclose(file); - if (ret == ps_invalid) - fprintf(stderr, "process_status %d: %s", pid, - strerror(errno)); - } else + if (ret == PS_INVALID) + fprintf(stderr, + "Couldn't determine status of process %d: %s\n", + pid, strerror(errno)); + } else { /* If the file is not present, the process presumably * exited already. */ - ret = ps_zombie; + ret = PS_ZOMBIE; + } return ret; } @@ -231,7 +233,7 @@ int process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n) { PROC_PID_FILE(fn, "/proc/%d/task", pid); - DIR * d = opendir(fn); + DIR *d = opendir(fn); if (d == NULL) return -1; @@ -243,7 +245,9 @@ process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n) struct dirent entry; struct dirent *result; if (readdir_r(d, &entry, &result) != 0) { + fail: free(tasks); + closedir(d); return -1; } if (result == NULL) @@ -254,14 +258,11 @@ process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n) alloc = alloc > 0 ? (2 * alloc) : 8; pid_t *ntasks = realloc(tasks, sizeof(*tasks) * alloc); - if (ntasks == NULL) { - free(tasks); - return -1; - } + if (ntasks == NULL) + goto fail; tasks = ntasks; } - if (n >= alloc) - abort(); + assert(n < alloc); tasks[n++] = npid; } } @@ -279,7 +280,7 @@ process_tasks(pid_t pid, pid_t **ret_tasks, size_t *ret_n) * ABI object, as theorized about somewhere on pmachata/revamp * branch. */ static void * -select_32_64(struct Process *proc, void *p32, void *p64) +select_32_64(struct process *proc, void *p32, void *p64) { if (sizeof(long) == 4 || proc->mask_32bit) return p32; @@ -288,7 +289,7 @@ select_32_64(struct Process *proc, void *p32, void *p64) } static int -fetch_dyn64(struct Process *proc, arch_addr_t *addr, Elf64_Dyn *ret) +fetch_dyn64(struct process *proc, arch_addr_t *addr, Elf64_Dyn *ret) { if (umovebytes(proc, *addr, ret, sizeof(*ret)) != sizeof(*ret)) return -1; @@ -297,7 +298,7 @@ fetch_dyn64(struct Process *proc, arch_addr_t *addr, Elf64_Dyn *ret) } static int -fetch_dyn32(struct Process *proc, arch_addr_t *addr, Elf64_Dyn *ret) +fetch_dyn32(struct process *proc, arch_addr_t *addr, Elf64_Dyn *ret) { Elf32_Dyn dyn; if (umovebytes(proc, *addr, &dyn, sizeof(dyn)) != sizeof(dyn)) @@ -311,14 +312,14 @@ fetch_dyn32(struct Process *proc, arch_addr_t *addr, Elf64_Dyn *ret) } static int (* -dyn_fetcher(struct Process *proc))(struct Process *, +dyn_fetcher(struct process *proc))(struct process *, arch_addr_t *, Elf64_Dyn *) { return select_32_64(proc, fetch_dyn32, fetch_dyn64); } int -proc_find_dynamic_entry_addr(struct Process *proc, arch_addr_t src_addr, +proc_find_dynamic_entry_addr(struct process *proc, arch_addr_t src_addr, int d_tag, arch_addr_t *ret) { debug(DEBUG_FUNCTION, "find_dynamic_entry()"); @@ -364,7 +365,7 @@ struct lt_link_map_32 LT_LINK_MAP(32); struct lt_link_map_64 LT_LINK_MAP(64); static int -fetch_lm64(struct Process *proc, arch_addr_t addr, +fetch_lm64(struct process *proc, arch_addr_t addr, struct lt_link_map_64 *ret) { if (umovebytes(proc, addr, ret, sizeof(*ret)) != sizeof(*ret)) @@ -373,7 +374,7 @@ fetch_lm64(struct Process *proc, arch_addr_t addr, } static int -fetch_lm32(struct Process *proc, arch_addr_t addr, +fetch_lm32(struct process *proc, arch_addr_t addr, struct lt_link_map_64 *ret) { struct lt_link_map_32 lm; @@ -390,7 +391,7 @@ fetch_lm32(struct Process *proc, arch_addr_t addr, } static int (* -lm_fetcher(struct Process *proc))(struct Process *, +lm_fetcher(struct process *proc))(struct process *, arch_addr_t, struct lt_link_map_64 *) { return select_32_64(proc, fetch_lm32, fetch_lm64); @@ -410,7 +411,7 @@ struct lt_r_debug_32 LT_R_DEBUG(32); struct lt_r_debug_64 LT_R_DEBUG(64); static int -fetch_rd64(struct Process *proc, arch_addr_t addr, +fetch_rd64(struct process *proc, arch_addr_t addr, struct lt_r_debug_64 *ret) { if (umovebytes(proc, addr, ret, sizeof(*ret)) != sizeof(*ret)) @@ -419,7 +420,7 @@ fetch_rd64(struct Process *proc, arch_addr_t addr, } static int -fetch_rd32(struct Process *proc, arch_addr_t addr, +fetch_rd32(struct process *proc, arch_addr_t addr, struct lt_r_debug_64 *ret) { struct lt_r_debug_32 rd; @@ -436,7 +437,7 @@ fetch_rd32(struct Process *proc, arch_addr_t addr, } static int (* -rdebug_fetcher(struct Process *proc))(struct Process *, +rdebug_fetcher(struct process *proc))(struct process *, arch_addr_t, struct lt_r_debug_64 *) { return select_32_64(proc, fetch_rd32, fetch_rd64); @@ -463,13 +464,13 @@ fetch_auxv32_entry(int fd, Elf64_auxv_t *ret) } static int (* -auxv_fetcher(struct Process *proc))(int, Elf64_auxv_t *) +auxv_fetcher(struct process *proc))(int, Elf64_auxv_t *) { return select_32_64(proc, fetch_auxv32_entry, fetch_auxv64_entry); } static void -crawl_linkmap(struct Process *proc, struct lt_r_debug_64 *dbg) +crawl_linkmap(struct process *proc, struct lt_r_debug_64 *dbg) { debug (DEBUG_FUNCTION, "crawl_linkmap()"); @@ -483,7 +484,7 @@ crawl_linkmap(struct Process *proc, struct lt_r_debug_64 *dbg) arch_addr_t addr = (arch_addr_t)(uintptr_t)dbg->r_map; while (addr != 0) { - struct lt_link_map_64 rlm; + struct lt_link_map_64 rlm = {}; if (lm_fetcher(proc)(proc, addr, &rlm) < 0) { debug(2, "Unable to read link map"); return; @@ -533,17 +534,20 @@ crawl_linkmap(struct Process *proc, struct lt_r_debug_64 *dbg) struct library *lib = malloc(sizeof(*lib)); if (lib == NULL) { fail: - if (lib != NULL) - library_destroy(lib); + free(lib); fprintf(stderr, "Couldn't load ELF object %s: %s\n", lib_name, strerror(errno)); continue; } - library_init(lib, LT_LIBTYPE_DSO); - if (ltelf_read_library(lib, proc, lib_name, rlm.l_addr) < 0) + if (library_init(lib, LT_LIBTYPE_DSO) < 0) goto fail; + if (ltelf_read_library(lib, proc, lib_name, rlm.l_addr) < 0) { + library_destroy(lib); + goto fail; + } + lib->key = key; proc_add_library(proc, lib); } @@ -551,7 +555,7 @@ crawl_linkmap(struct Process *proc, struct lt_r_debug_64 *dbg) } static int -load_debug_struct(struct Process *proc, struct lt_r_debug_64 *ret) +load_debug_struct(struct process *proc, struct lt_r_debug_64 *ret) { debug(DEBUG_FUNCTION, "load_debug_struct"); @@ -564,7 +568,7 @@ load_debug_struct(struct Process *proc, struct lt_r_debug_64 *ret) } static void -rdebug_bp_on_hit(struct breakpoint *bp, struct Process *proc) +rdebug_bp_on_hit(struct breakpoint *bp, struct process *proc) { debug(DEBUG_FUNCTION, "arch_check_dbg"); @@ -595,7 +599,7 @@ rdebug_bp_on_hit(struct breakpoint *bp, struct Process *proc) #ifndef ARCH_HAVE_FIND_DL_DEBUG int -arch_find_dl_debug(struct Process *proc, arch_addr_t dyn_addr, +arch_find_dl_debug(struct process *proc, arch_addr_t dyn_addr, arch_addr_t *ret) { return proc_find_dynamic_entry_addr(proc, dyn_addr, DT_DEBUG, ret); @@ -603,7 +607,7 @@ arch_find_dl_debug(struct Process *proc, arch_addr_t dyn_addr, #endif int -linkmap_init(struct Process *proc, arch_addr_t dyn_addr) +linkmap_init(struct process *proc, arch_addr_t dyn_addr) { debug(DEBUG_FUNCTION, "linkmap_init(%d, dyn_addr=%p)", proc->pid, dyn_addr); @@ -619,19 +623,29 @@ linkmap_init(struct Process *proc, arch_addr_t dyn_addr) return status; } + crawl_linkmap(proc, &rdbg); + /* XXX The double cast should be removed when * arch_addr_t becomes integral type. */ arch_addr_t addr = (arch_addr_t)(uintptr_t)rdbg.r_brk; if (arch_translate_address_dyn(proc, addr, &addr) < 0) return -1; - struct breakpoint *rdebug_bp = insert_breakpoint(proc, addr, NULL); - static struct bp_callbacks rdebug_callbacks = { - .on_hit = rdebug_bp_on_hit, - }; - rdebug_bp->cbs = &rdebug_callbacks; - - crawl_linkmap(proc, &rdbg); + struct breakpoint *rdebug_bp = insert_breakpoint_at(proc, addr, NULL); + if (rdebug_bp == NULL) { + /* This is not fatal, the tracing can continue with + * reduced functionality. */ + fprintf(stderr, + "Couldn't insert _r_debug breakpoint to %d: %s.\n" + "As a result of that, ltrace will not be able to " + "detect and trace\nnewly-loaded libraries.\n", + proc->pid, strerror(errno)); + } else { + static struct bp_callbacks rdebug_callbacks = { + .on_hit = rdebug_bp_on_hit, + }; + rdebug_bp->cbs = &rdebug_callbacks; + } return 0; } @@ -648,13 +662,13 @@ task_kill (pid_t pid, int sig) } void -process_removed(struct Process *proc) +process_removed(struct process *proc) { delete_events_for(proc); } int -process_get_entry(struct Process *proc, +process_get_entry(struct process *proc, arch_addr_t *entryp, arch_addr_t *interp_biasp) { @@ -706,7 +720,7 @@ process_get_entry(struct Process *proc, } int -os_process_init(struct Process *proc) +os_process_init(struct process *proc) { proc->os.debug_addr = 0; proc->os.debug_state = 0; @@ -714,19 +728,19 @@ os_process_init(struct Process *proc) } void -os_process_destroy(struct Process *proc) +os_process_destroy(struct process *proc) { } int -os_process_clone(struct Process *retp, struct Process *proc) +os_process_clone(struct process *retp, struct process *proc) { retp->os = proc->os; return 0; } int -os_process_exec(struct Process *proc) +os_process_exec(struct process *proc) { return 0; } diff --git a/sysdeps/linux-gnu/s390/arch.h b/sysdeps/linux-gnu/s390/arch.h index 0d412dc..de24b8f 100644 --- a/sysdeps/linux-gnu/s390/arch.h +++ b/sysdeps/linux-gnu/s390/arch.h @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2001 IBM Poughkeepsie, IBM Corporation * * This program is free software; you can redistribute it and/or @@ -25,6 +26,7 @@ #define ARCH_HAVE_FETCH_ARG #define ARCH_HAVE_SIZEOF #define ARCH_HAVE_ALIGNOF +#define ARCH_HAVE_ADD_PLT_ENTRY #define LT_ELFCLASS ELFCLASS32 #define LT_ELF_MACHINE EM_S390 diff --git a/sysdeps/linux-gnu/s390/fetch.c b/sysdeps/linux-gnu/s390/fetch.c index fa8f42d..4ad5951 100644 --- a/sysdeps/linux-gnu/s390/fetch.c +++ b/sysdeps/linux-gnu/s390/fetch.c @@ -23,6 +23,7 @@ #include <sys/ucontext.h> #include <assert.h> #include <errno.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -61,7 +62,8 @@ s390x(struct fetch_context *ctx) } static int -fetch_register_banks(struct Process *proc, struct fetch_context *ctx) +fetch_register_banks(struct process *proc, struct fetch_context *ctx, + bool syscall_enter) { ptrace_area parea; parea.len = sizeof(ctx->regs); @@ -72,24 +74,29 @@ fetch_register_banks(struct Process *proc, struct fetch_context *ctx) strerror(errno)); return -1; } + + if (syscall_enter) + ctx->regs.gprs[2] = ctx->regs.orig_gpr2; + return 0; } static int -fetch_context_init(struct Process *proc, struct fetch_context *context) +fetch_context_init(struct process *proc, struct fetch_context *context, + bool syscall_enter) { context->greg = 2; context->freg = 0; - return fetch_register_banks(proc, context); + return fetch_register_banks(proc, context, syscall_enter); } struct fetch_context * -arch_fetch_arg_init(enum tof type, struct Process *proc, +arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { struct fetch_context *context = malloc(sizeof(*context)); if (context == NULL - || fetch_context_init(proc, context) < 0) { + || fetch_context_init(proc, context, type == LT_TOF_SYSCALL) < 0) { fprintf(stderr, "arch_fetch_arg_init: %s\n", strerror(errno)); free(context); @@ -105,7 +112,7 @@ arch_fetch_arg_init(enum tof type, struct Process *proc, } struct fetch_context * -arch_fetch_arg_clone(struct Process *proc, +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) { struct fetch_context *clone = malloc(sizeof(*context)); @@ -116,7 +123,7 @@ arch_fetch_arg_clone(struct Process *proc, } static int -allocate_stack_slot(struct fetch_context *ctx, struct Process *proc, +allocate_stack_slot(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep, size_t sz) { @@ -148,7 +155,7 @@ copy_gpr(struct fetch_context *ctx, struct value *valuep, int regno) } static int -allocate_gpr(struct fetch_context *ctx, struct Process *proc, +allocate_gpr(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep, size_t sz) { @@ -160,7 +167,7 @@ allocate_gpr(struct fetch_context *ctx, struct Process *proc, } static int -allocate_gpr_pair(struct fetch_context *ctx, struct Process *proc, +allocate_gpr_pair(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep, size_t sz) { @@ -191,7 +198,7 @@ allocate_gpr_pair(struct fetch_context *ctx, struct Process *proc, } static int -allocate_fpr(struct fetch_context *ctx, struct Process *proc, +allocate_fpr(struct fetch_context *ctx, struct process *proc, struct arg_type_info *info, struct value *valuep, size_t sz) { @@ -212,7 +219,7 @@ allocate_fpr(struct fetch_context *ctx, struct Process *proc, int arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, - struct Process *proc, + struct process *proc, struct arg_type_info *info, struct value *valuep) { size_t sz = type_sizeof(proc, info); @@ -267,7 +274,7 @@ arch_fetch_arg_next(struct fetch_context *ctx, enum tof type, int arch_fetch_retval(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { if (info->type == ARGTYPE_STRUCT) { @@ -277,7 +284,7 @@ arch_fetch_retval(struct fetch_context *ctx, enum tof type, return 0; } - if (fetch_context_init(proc, ctx) < 0) + if (fetch_context_init(proc, ctx, false) < 0) return -1; return arch_fetch_arg_next(ctx, type, proc, info, valuep); } diff --git a/sysdeps/linux-gnu/s390/plt.c b/sysdeps/linux-gnu/s390/plt.c index 5f612e5..fb7ebcb 100644 --- a/sysdeps/linux-gnu/s390/plt.c +++ b/sysdeps/linux-gnu/s390/plt.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2004,2008,2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -19,9 +20,12 @@ */ #include <gelf.h> +#include <stdbool.h> + #include "proc.h" #include "common.h" #include "library.h" +#include "trace.h" GElf_Addr arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { @@ -29,6 +33,25 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } + +enum plt_status +arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *a_name, GElf_Rela *rela, size_t ndx, + struct library_symbol **ret) +{ +#ifdef R_390_IRELATIVE + bool irelative = GELF_R_TYPE(rela->r_info) == R_390_IRELATIVE; +#else + bool irelative = false; +#endif + + if (irelative) + return linux_elf_add_plt_entry_irelative(proc, lte, rela, + ndx, ret); + + return PLT_DEFAULT; +} diff --git a/sysdeps/linux-gnu/s390/regs.c b/sysdeps/linux-gnu/s390/regs.c index 0592ccd..51410d7 100644 --- a/sysdeps/linux-gnu/s390/regs.c +++ b/sysdeps/linux-gnu/s390/regs.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2002,2004,2008,2009 Juan Cespedes * Copyright (C) 2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand @@ -45,50 +46,52 @@ #define PSW_MASK 0x7fffffff #endif -void * -get_instruction_pointer(Process *proc) { +arch_addr_t +get_instruction_pointer(struct process *proc) +{ long ret = ptrace(PTRACE_PEEKUSER, proc->pid, PT_PSWADDR, 0) & PSW_MASK; #ifdef __s390x__ if (proc->mask_32bit) ret &= PSW_MASK31; #endif - return (void *)ret; + /* XXX double cast. */ + return (arch_addr_t)ret; } void -set_instruction_pointer(Process *proc, void *addr) { +set_instruction_pointer(struct process *proc, arch_addr_t addr) +{ #ifdef __s390x__ if (proc->mask_32bit) - addr = (void *)((long)addr & PSW_MASK31); + /* XXX double cast. */ + addr = (arch_addr_t)((uintptr_t)addr & PSW_MASK31); +#else + /* XXX double cast. */ + addr = (arch_addr_t)((uintptr_t)addr | ~PSW_MASK); #endif ptrace(PTRACE_POKEUSER, proc->pid, PT_PSWADDR, addr); } -void * -get_stack_pointer(Process *proc) { +arch_addr_t +get_stack_pointer(struct process *proc) +{ long ret = ptrace(PTRACE_PEEKUSER, proc->pid, PT_GPR15, 0) & PSW_MASK; #ifdef __s390x__ if (proc->mask_32bit) ret &= PSW_MASK31; #endif - return (void *)ret; + /* XXX double cast. */ + return (arch_addr_t)ret; } -void * -get_return_addr(Process *proc, void *stack_pointer) { +arch_addr_t +get_return_addr(struct process *proc, arch_addr_t stack_pointer) +{ long ret = ptrace(PTRACE_PEEKUSER, proc->pid, PT_GPR14, 0) & PSW_MASK; #ifdef __s390x__ if (proc->mask_32bit) ret &= PSW_MASK31; #endif - return (void *)ret; -} - -void -set_return_addr(Process *proc, void *addr) { -#ifdef __s390x__ - if (proc->mask_32bit) - addr = (void *)((long)addr & PSW_MASK31); -#endif - ptrace(PTRACE_POKEUSER, proc->pid, PT_GPR14, addr); + /* XXX double cast. */ + return (arch_addr_t)ret; } diff --git a/sysdeps/linux-gnu/s390/trace.c b/sysdeps/linux-gnu/s390/trace.c index b9e05ff..78b04c3 100644 --- a/sysdeps/linux-gnu/s390/trace.c +++ b/sysdeps/linux-gnu/s390/trace.c @@ -43,7 +43,8 @@ #endif void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ #ifdef __s390x__ unsigned long psw; @@ -64,7 +65,8 @@ get_arch_dep(Process *proc) { /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ long pc, opcode, offset_reg, scno, tmp; void *svc_addr; int gpr_offset[16] = { PT_GPR0, PT_GPR1, PT_ORIGGPR2, PT_GPR3, @@ -175,7 +177,7 @@ syscall_p(Process *proc, int status, int *sysnum) { } size_t -arch_type_sizeof(struct Process *proc, struct arg_type_info *info) +arch_type_sizeof(struct process *proc, struct arg_type_info *info) { if (proc == NULL) return (size_t)-2; @@ -217,7 +219,7 @@ arch_type_sizeof(struct Process *proc, struct arg_type_info *info) } size_t -arch_type_alignof(struct Process *proc, struct arg_type_info *info) +arch_type_alignof(struct process *proc, struct arg_type_info *info) { if (proc == NULL) return (size_t)-2; diff --git a/sysdeps/linux-gnu/sparc/plt.c b/sysdeps/linux-gnu/sparc/plt.c index 40bbabc..3d2e589 100644 --- a/sysdeps/linux-gnu/sparc/plt.c +++ b/sysdeps/linux-gnu/sparc/plt.c @@ -28,6 +28,7 @@ arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } diff --git a/sysdeps/linux-gnu/sparc/regs.c b/sysdeps/linux-gnu/sparc/regs.c index 5e5ad20..c474c83 100644 --- a/sysdeps/linux-gnu/sparc/regs.c +++ b/sysdeps/linux-gnu/sparc/regs.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -27,7 +28,8 @@ #include "common.h" void * -get_instruction_pointer(Process *proc) { +get_instruction_pointer(struct process *proc) +{ proc_archdep *a = (proc_archdep *) (proc->arch_ptr); if (a->valid) return (void *)a->regs.pc; @@ -35,14 +37,16 @@ get_instruction_pointer(Process *proc) { } void -set_instruction_pointer(Process *proc, void *addr) { +set_instruction_pointer(struct process *proc, void *addr) +{ proc_archdep *a = (proc_archdep *) (proc->arch_ptr); if (a->valid) a->regs.pc = (long)addr; } void * -get_stack_pointer(Process *proc) { +get_stack_pointer(struct process *proc) +{ proc_archdep *a = (proc_archdep *) (proc->arch_ptr); if (a->valid) return (void *)a->regs.u_regs[UREG_I5]; @@ -50,7 +54,8 @@ get_stack_pointer(Process *proc) { } void * -get_return_addr(Process *proc, void *stack_pointer) { +get_return_addr(struct process *proc, void *stack_pointer) +{ proc_archdep *a = (proc_archdep *) (proc->arch_ptr); unsigned int t; if (!a->valid) @@ -61,11 +66,3 @@ get_return_addr(Process *proc, void *stack_pointer) { return (void *)a->regs.u_regs[UREG_I6] + 12; return (void *)a->regs.u_regs[UREG_I6] + 8; } - -void -set_return_addr(Process *proc, void *addr) { - proc_archdep *a = (proc_archdep *) (proc->arch_ptr); - if (!a->valid) - return; - ptrace(PTRACE_POKETEXT, proc->pid, a->regs.u_regs[UREG_I6] + 8, addr); -} diff --git a/sysdeps/linux-gnu/sparc/trace.c b/sysdeps/linux-gnu/sparc/trace.c index e1725ff..078d406 100644 --- a/sysdeps/linux-gnu/sparc/trace.c +++ b/sysdeps/linux-gnu/sparc/trace.c @@ -31,7 +31,8 @@ #include "common.h" void -get_arch_dep(Process *proc) { +get_arch_dep(struct process *proc) +{ proc_archdep *a; if (!proc->arch_ptr) proc->arch_ptr = (void *)malloc(sizeof(proc_archdep)); @@ -43,7 +44,8 @@ get_arch_dep(Process *proc) { * Returns -1 otherwise */ int -syscall_p(Process *proc, int status, int *sysnum) { +syscall_p(struct process *proc, int status, int *sysnum) +{ if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { void *ip = get_instruction_pointer(proc); @@ -66,7 +68,8 @@ syscall_p(Process *proc, int status, int *sysnum) { } long -gimme_arg(enum tof type, Process *proc, int arg_num, struct arg_type_info *info) +gimme_arg(enum tof type, struct process *proc, int arg_num, + struct arg_type_info *info) { proc_archdep *a = (proc_archdep *) proc->arch_ptr; if (!a->valid) { diff --git a/sysdeps/linux-gnu/trace-defs.h b/sysdeps/linux-gnu/trace-defs.h index aa69b83..f2e0855 100644 --- a/sysdeps/linux-gnu/trace-defs.h +++ b/sysdeps/linux-gnu/trace-defs.h @@ -17,8 +17,8 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ -#ifndef _TRACE_DEFS_H_ -#define _TRACE_DEFS_H_ +#ifndef TRACE_DEFS_H +#define TRACE_DEFS_H #include <sys/ptrace.h> /* If the system headers did not provide the constants, hard-code the @@ -89,4 +89,4 @@ # define PTRACE_EVENT_EXIT 6 #endif -#endif /* _TRACE_DEFS_H_ */ +#endif /* TRACE_DEFS_H */ diff --git a/sysdeps/linux-gnu/trace.c b/sysdeps/linux-gnu/trace.c index e13b761..3fa2039 100644 --- a/sysdeps/linux-gnu/trace.c +++ b/sysdeps/linux-gnu/trace.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2007,2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2007,2011,2012,2013,2014 Petr Machata, Red Hat Inc. * Copyright (C) 2010 Joe Damato * Copyright (C) 1998,2002,2003,2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand @@ -24,29 +24,36 @@ #include "config.h" #include <asm/unistd.h> -#include <sys/types.h> -#include <sys/wait.h> #include <assert.h> #include <errno.h> +#include <gelf.h> +#include <inttypes.h> +#include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #ifdef HAVE_LIBSELINUX # include <selinux/selinux.h> #endif -#include "linux-gnu/trace.h" #include "linux-gnu/trace-defs.h" +#include "linux-gnu/trace.h" #include "backend.h" #include "breakpoint.h" #include "debug.h" #include "events.h" +#include "fetch.h" +#include "ltrace-elf.h" #include "options.h" #include "proc.h" +#include "prototype.h" #include "ptrace.h" #include "type.h" +#include "value.h" void trace_fail_warning(pid_t pid) @@ -111,7 +118,7 @@ trace_pid(pid_t pid) } void -trace_set_options(struct Process *proc) +trace_set_options(struct process *proc) { if (proc->tracesysgood & 0x80) return; @@ -148,8 +155,8 @@ static enum ecb_status event_for_pid(Event *event, void *data) { if (event->proc != NULL && event->proc->pid == (pid_t)(uintptr_t)data) - return ecb_yield; - return ecb_cont; + return ECB_YIELD; + return ECB_CONT; } static int @@ -206,7 +213,7 @@ add_task_info(struct pid_set *pids, pid_t pid) } static enum callback_status -task_stopped(struct Process *task, void *data) +task_stopped(struct process *task, void *data) { enum process_status st = process_status(task->pid); if (data != NULL) @@ -217,13 +224,13 @@ task_stopped(struct Process *task, void *data) * the meantime. This can happen when the whole thread group * is terminating. */ switch (st) { - case ps_invalid: - case ps_tracing_stop: - case ps_zombie: + case PS_INVALID: + case PS_TRACING_STOP: + case PS_ZOMBIE: return CBS_CONT; - case ps_sleeping: - case ps_stop: - case ps_other: + case PS_SLEEPING: + case PS_STOP: + case PS_OTHER: return CBS_STOP; } @@ -232,7 +239,7 @@ task_stopped(struct Process *task, void *data) /* Task is blocked if it's stopped, or if it's a vfork parent. */ static enum callback_status -task_blocked(struct Process *task, void *data) +task_blocked(struct process *task, void *data) { struct pid_set *pids = data; struct pid_task *task_info = get_task_info(pids, task->pid); @@ -246,7 +253,7 @@ task_blocked(struct Process *task, void *data) static Event *process_vfork_on_event(struct event_handler *super, Event *event); static enum callback_status -task_vforked(struct Process *task, void *data) +task_vforked(struct process *task, void *data) { if (task->event_handler != NULL && task->event_handler->on_event == &process_vfork_on_event) @@ -255,15 +262,15 @@ task_vforked(struct Process *task, void *data) } static int -is_vfork_parent(struct Process *task) +is_vfork_parent(struct process *task) { return each_task(task->leader, NULL, &task_vforked, NULL) != NULL; } static enum callback_status -send_sigstop(struct Process *task, void *data) +send_sigstop(struct process *task, void *data) { - struct Process *leader = task->leader; + struct process *leader = task->leader; struct pid_set *pids = data; /* Look for pre-existing task record, or add new. */ @@ -299,8 +306,8 @@ send_sigstop(struct Process *task, void *data) * vforked process. We set up event handler specially to hint * us. In that case parent is in D state, which we use to * weed out unnecessary looping. */ - if (st == ps_sleeping - && is_vfork_parent (task)) { + if (st == PS_SLEEPING + && is_vfork_parent(task)) { task_info->vforked = 1; return CBS_CONT; } @@ -321,20 +328,22 @@ send_sigstop(struct Process *task, void *data) breakpoint where IP points and let the process continue. After this the breakpoint can be retracted and the process detached. */ static void -ugly_workaround(struct Process *proc) +ugly_workaround(struct process *proc) { - void *ip = get_instruction_pointer(proc); - struct breakpoint *sbp = dict_find_entry(proc->leader->breakpoints, ip); - if (sbp != NULL) - enable_breakpoint(proc, sbp); - else - insert_breakpoint(proc, ip, NULL); + arch_addr_t ip = get_instruction_pointer(proc); + struct breakpoint *found; + if (DICT_FIND_VAL(proc->leader->breakpoints, &ip, &found) < 0) { + insert_breakpoint_at(proc, ip, NULL); + } else { + assert(found != NULL); + enable_breakpoint(proc, found); + } ptrace(PTRACE_CONT, proc->pid, 0, 0); } static void process_stopping_done(struct process_stopping_handler *self, - struct Process *leader) + struct process *leader) { debug(DEBUG_PROCESS, "process stopping done %d", self->task_enabling_breakpoint->pid); @@ -351,7 +360,7 @@ process_stopping_done(struct process_stopping_handler *self, if (self->exiting) { ugly_workaround: - self->state = psh_ugly_workaround; + self->state = PSH_UGLY_WORKAROUND; ugly_workaround(self->task_enabling_breakpoint); } else { switch ((self->ugly_workaround_p)(self)) { @@ -377,11 +386,11 @@ undo_breakpoint(Event *event, void *data) && event->proc->leader == data && event->type == EVENT_BREAKPOINT) set_instruction_pointer(event->proc, event->e_un.brk_addr); - return ecb_cont; + return ECB_CONT; } static enum callback_status -untrace_task(struct Process *task, void *data) +untrace_task(struct process *task, void *data) { if (task != data) untrace_pid(task->pid); @@ -389,7 +398,7 @@ untrace_task(struct Process *task, void *data) } static enum callback_status -remove_task(struct Process *task, void *data) +remove_task(struct process *task, void *data) { /* Don't untrace leader just yet. */ if (task != data) @@ -398,14 +407,14 @@ remove_task(struct Process *task, void *data) } static enum callback_status -retract_breakpoint_cb(struct Process *proc, struct breakpoint *bp, void *data) +retract_breakpoint_cb(struct process *proc, struct breakpoint *bp, void *data) { breakpoint_on_retract(bp, proc); return CBS_CONT; } static void -detach_process(struct Process *leader) +detach_process(struct process *leader) { each_qd_event(&undo_breakpoint, leader); disable_all_breakpoints(leader); @@ -414,7 +423,7 @@ detach_process(struct Process *leader) /* Now untrace the process, if it was attached to by -p. */ struct opt_p_t *it; for (it = opt_p; it != NULL; it = it->next) { - struct Process *proc = pid2proc(it->pid); + struct process *proc = pid2proc(it->pid); if (proc == NULL) continue; if (proc->leader == leader) { @@ -540,19 +549,13 @@ all_stops_accountable(struct pid_set *pids) return 1; } -/* The protocol is: 0 for success, negative for failure, positive if - * default singlestep is to be used. */ -int arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, - int (*add_cb)(void *addr, void *data), - void *add_cb_data); - -#ifndef ARCH_HAVE_ATOMIC_SINGLESTEP -int -arch_atomic_singlestep(struct Process *proc, struct breakpoint *sbp, - int (*add_cb)(void *addr, void *data), - void *add_cb_data) +#ifndef ARCH_HAVE_SW_SINGLESTEP +enum sw_singlestep_status +arch_sw_singlestep(struct process *proc, struct breakpoint *bp, + int (*add_cb)(arch_addr_t, struct sw_singlestep_data *), + struct sw_singlestep_data *data) { - return 1; + return SWS_HW; } #endif @@ -560,74 +563,84 @@ static Event *process_stopping_on_event(struct event_handler *super, Event *event); static void -remove_atomic_breakpoints(struct Process *proc) +remove_sw_breakpoints(struct process *proc) { struct process_stopping_handler *self = (void *)proc->leader->event_handler; assert(self != NULL); assert(self->super.on_event == process_stopping_on_event); - int ct = sizeof(self->atomic_skip_bp_addrs) - / sizeof(*self->atomic_skip_bp_addrs); + int ct = sizeof(self->sws_bps) / sizeof(*self->sws_bps); int i; for (i = 0; i < ct; ++i) - if (self->atomic_skip_bp_addrs[i] != 0) { - delete_breakpoint(proc, self->atomic_skip_bp_addrs[i]); - self->atomic_skip_bp_addrs[i] = 0; + if (self->sws_bps[i] != NULL) { + delete_breakpoint_at(proc, self->sws_bps[i]->addr); + self->sws_bps[i] = NULL; } } static void -atomic_singlestep_bp_on_hit(struct breakpoint *bp, struct Process *proc) +sw_singlestep_bp_on_hit(struct breakpoint *bp, struct process *proc) { - remove_atomic_breakpoints(proc); + remove_sw_breakpoints(proc); } +struct sw_singlestep_data { + struct process_stopping_handler *self; +}; + static int -atomic_singlestep_add_bp(void *addr, void *data) +sw_singlestep_add_bp(arch_addr_t addr, struct sw_singlestep_data *data) { - struct process_stopping_handler *self = data; - struct Process *proc = self->task_enabling_breakpoint; + struct process_stopping_handler *self = data->self; + struct process *proc = self->task_enabling_breakpoint; - int ct = sizeof(self->atomic_skip_bp_addrs) - / sizeof(*self->atomic_skip_bp_addrs); + int ct = sizeof(self->sws_bps) / sizeof(*self->sws_bps); int i; for (i = 0; i < ct; ++i) - if (self->atomic_skip_bp_addrs[i] == 0) { - self->atomic_skip_bp_addrs[i] = addr; + if (self->sws_bps[i] == NULL) { static struct bp_callbacks cbs = { - .on_hit = atomic_singlestep_bp_on_hit, + .on_hit = sw_singlestep_bp_on_hit, }; struct breakpoint *bp - = insert_breakpoint(proc, addr, NULL); + = insert_breakpoint_at(proc, addr, NULL); breakpoint_set_callbacks(bp, &cbs); + self->sws_bps[i] = bp; return 0; } - assert(!"Too many atomic singlestep breakpoints!"); + assert(!"Too many sw singlestep breakpoints!"); abort(); } static int singlestep(struct process_stopping_handler *self) { - struct Process *proc = self->task_enabling_breakpoint; - - int status = arch_atomic_singlestep(self->task_enabling_breakpoint, - self->breakpoint_being_enabled, - &atomic_singlestep_add_bp, self); + size_t i; + for (i = 0; i < sizeof(self->sws_bps) / sizeof(*self->sws_bps); ++i) + self->sws_bps[i] = NULL; + + struct sw_singlestep_data data = { self }; + switch (arch_sw_singlestep(self->task_enabling_breakpoint, + self->breakpoint_being_enabled, + &sw_singlestep_add_bp, &data)) { + case SWS_HW: + /* Otherwise do the default action: singlestep. */ + debug(1, "PTRACE_SINGLESTEP"); + if (ptrace(PTRACE_SINGLESTEP, + self->task_enabling_breakpoint->pid, 0, 0)) { + perror("PTRACE_SINGLESTEP"); + return -1; + } + return 0; - /* Propagate failure and success. */ - if (status <= 0) - return status; + case SWS_OK: + return 0; - /* Otherwise do the default action: singlestep. */ - debug(1, "PTRACE_SINGLESTEP"); - if (ptrace(PTRACE_SINGLESTEP, proc->pid, 0, 0)) { - perror("PTRACE_SINGLESTEP"); + case SWS_FAIL: return -1; } - return 0; + abort(); } static void @@ -639,27 +652,27 @@ post_singlestep(struct process_stopping_handler *self, if (*eventp != NULL && (*eventp)->type == EVENT_BREAKPOINT) *eventp = NULL; // handled - struct Process *proc = self->task_enabling_breakpoint; + struct process *proc = self->task_enabling_breakpoint; - remove_atomic_breakpoints(proc); + remove_sw_breakpoints(proc); self->breakpoint_being_enabled = NULL; } static void singlestep_error(struct process_stopping_handler *self) { - struct Process *teb = self->task_enabling_breakpoint; + struct process *teb = self->task_enabling_breakpoint; struct breakpoint *sbp = self->breakpoint_being_enabled; fprintf(stderr, "%d couldn't continue when handling %s (%p) at %p\n", teb->pid, breakpoint_name(sbp), sbp->addr, get_instruction_pointer(teb)); - delete_breakpoint(teb->leader, sbp->addr); + delete_breakpoint_at(teb->leader, sbp->addr); } static void pt_continue(struct process_stopping_handler *self) { - struct Process *teb = self->task_enabling_breakpoint; + struct process *teb = self->task_enabling_breakpoint; debug(1, "PTRACE_CONT"); ptrace(PTRACE_CONT, teb->pid, 0, 0); } @@ -675,12 +688,12 @@ static void disable_and(struct process_stopping_handler *self, void (*do_this)(struct process_stopping_handler *self)) { - struct Process *teb = self->task_enabling_breakpoint; + struct process *teb = self->task_enabling_breakpoint; debug(DEBUG_PROCESS, "all stopped, now singlestep/cont %d", teb->pid); if (self->breakpoint_being_enabled->enabled) disable_breakpoint(teb, self->breakpoint_being_enabled); (do_this)(self); - self->state = psh_singlestep; + self->state = PSH_SINGLESTEP; } void @@ -705,9 +718,9 @@ static Event * process_stopping_on_event(struct event_handler *super, Event *event) { struct process_stopping_handler *self = (void *)super; - struct Process *task = event->proc; - struct Process *leader = task->leader; - struct Process *teb = self->task_enabling_breakpoint; + struct process *task = event->proc; + struct process *leader = task->leader; + struct process *teb = self->task_enabling_breakpoint; debug(DEBUG_PROCESS, "process_stopping_on_event: pid %d; event type %d; state %d", @@ -733,11 +746,12 @@ process_stopping_on_event(struct event_handler *super, Event *event) if (event != NULL && event->type == EVENT_SYSRET) { debug(1, "%d LT_EV_SYSRET", event->proc->pid); event_to_queue = 0; - task_info->sysret = 1; + if (task_info != NULL) + task_info->sysret = 1; } switch (state) { - case psh_stopping: + case PSH_STOPPING: /* If everyone is stopped, singlestep. */ if (each_task(leader, NULL, &task_blocked, &self->pids) == NULL) { @@ -746,7 +760,7 @@ process_stopping_on_event(struct event_handler *super, Event *event) } break; - case psh_singlestep: + case PSH_SINGLESTEP: /* In singlestep state, breakpoint signifies that we * have now stepped, and can re-enable the breakpoint. */ if (event != NULL && task == teb) { @@ -801,13 +815,14 @@ process_stopping_on_event(struct event_handler *super, Event *event) break; psh_sinking: - state = self->state = psh_sinking; - case psh_sinking: + state = self->state = PSH_SINKING; + /* Fall through. */ + case PSH_SINKING: if (await_sigstop_delivery(&self->pids, task_info, event)) process_stopping_done(self, leader); break; - case psh_ugly_workaround: + case PSH_UGLY_WORKAROUND: if (event == NULL) break; if (event->type == EVENT_BREAKPOINT) { @@ -845,7 +860,7 @@ no(struct process_stopping_handler *self) } int -process_install_stopping_handler(struct Process *proc, struct breakpoint *sbp, +process_install_stopping_handler(struct process *proc, struct breakpoint *sbp, void (*as)(struct process_stopping_handler *), enum callback_status (*ks) (struct process_stopping_handler *), @@ -894,7 +909,7 @@ process_install_stopping_handler(struct Process *proc, struct breakpoint *sbp, } void -continue_after_breakpoint(Process *proc, struct breakpoint *sbp) +continue_after_breakpoint(struct process *proc, struct breakpoint *sbp) { debug(DEBUG_PROCESS, "continue_after_breakpoint: pid=%d, addr=%p", @@ -904,18 +919,11 @@ continue_after_breakpoint(Process *proc, struct breakpoint *sbp) if (sbp->enabled == 0) { continue_process(proc->pid); - } else { -#if defined __sparc__ || defined __ia64___ - /* we don't want to singlestep here */ + } else if (process_install_stopping_handler + (proc, sbp, NULL, NULL, NULL) < 0) { + perror("process_stopping_handler_create"); + /* Carry on not bothering to re-enable. */ continue_process(proc->pid); -#else - if (process_install_stopping_handler - (proc, sbp, NULL, NULL, NULL) < 0) { - perror("process_stopping_handler_create"); - /* Carry on not bothering to re-enable. */ - continue_process(proc->pid); - } -#endif } } @@ -937,8 +945,8 @@ static Event * ltrace_exiting_on_event(struct event_handler *super, Event *event) { struct ltrace_exiting_handler *self = (void *)super; - struct Process *task = event->proc; - struct Process *leader = task->leader; + struct process *task = event->proc; + struct process *leader = task->leader; debug(DEBUG_PROCESS, "ltrace_exiting_on_event: pid %d; event type %d", @@ -970,7 +978,7 @@ ltrace_exiting_destroy(struct event_handler *super) } static int -ltrace_exiting_install_handler(struct Process *proc) +ltrace_exiting_install_handler(struct process *proc) { /* Only install to leader. */ if (proc->leader != proc) @@ -1035,7 +1043,7 @@ ltrace_exiting_install_handler(struct Process *proc) struct process_vfork_handler { struct event_handler super; - void *bp_addr; + int vfork_bp_refd:1; }; static Event * @@ -1046,38 +1054,33 @@ process_vfork_on_event(struct event_handler *super, Event *event) event->proc->pid, event->type); struct process_vfork_handler *self = (void *)super; - struct breakpoint *sbp; + struct process *proc = event->proc; assert(self != NULL); switch (event->type) { case EVENT_BREAKPOINT: - /* Remember the vfork return breakpoint. */ - if (self->bp_addr == 0) - self->bp_addr = event->e_un.brk_addr; + /* We turn on the vfork return breakpoint (which + * should be the one that we have tripped over just + * now) one extra time, so that the vfork parent hits + * it as well. */ + if (!self->vfork_bp_refd) { + struct breakpoint *sbp = NULL; + DICT_FIND_VAL(proc->leader->breakpoints, + &event->e_un.brk_addr, &sbp); + assert(sbp != NULL); + breakpoint_turn_on(sbp, proc->leader); + self->vfork_bp_refd = 1; + } break; case EVENT_EXIT: case EVENT_EXIT_SIGNAL: case EVENT_EXEC: - /* Smuggle back in the vfork return breakpoint, so - * that our parent can trip over it once again. */ - if (self->bp_addr != 0) { - sbp = dict_find_entry(event->proc->leader->breakpoints, - self->bp_addr); - if (sbp != NULL) - assert(sbp->libsym == NULL); - /* We don't mind failing that, it's not a big - * deal to not display one extra vfork return. */ - insert_breakpoint(event->proc->parent, - self->bp_addr, NULL); - } - - continue_process(event->proc->parent->pid); - /* Remove the leader that we artificially set up * earlier. */ - change_process_leader(event->proc, event->proc); - destroy_event_handler(event->proc); + change_process_leader(proc, proc); + destroy_event_handler(proc); + continue_process(proc->parent->pid); default: ; @@ -1087,7 +1090,7 @@ process_vfork_on_event(struct event_handler *super, Event *event) } void -continue_after_vfork(struct Process *proc) +continue_after_vfork(struct process *proc) { debug(DEBUG_PROCESS, "continue_after_vfork: pid=%d", proc->pid); struct process_vfork_handler *handler = calloc(sizeof(*handler), 1); @@ -1116,7 +1119,7 @@ continue_after_vfork(struct Process *proc) } static int -is_mid_stopping(Process *proc) +is_mid_stopping(struct process *proc) { return proc != NULL && proc->event_handler != NULL @@ -1124,7 +1127,7 @@ is_mid_stopping(Process *proc) } void -continue_after_syscall(struct Process *proc, int sysnum, int ret_p) +continue_after_syscall(struct process *proc, int sysnum, int ret_p) { /* Don't continue if we are mid-stopping. */ if (ret_p && (is_mid_stopping(proc) || is_mid_stopping(proc->leader))) { @@ -1136,6 +1139,23 @@ continue_after_syscall(struct Process *proc, int sysnum, int ret_p) continue_process(proc->pid); } +void +continue_after_exec(struct process *proc) +{ + continue_process(proc->pid); + + /* After the exec, we expect to hit the first executable + * instruction. + * + * XXX TODO It would be nice to have this removed, but then we + * need to do that also for initial call to wait_for_proc in + * execute_program. In that case we could generate a + * EVENT_FIRST event or something, or maybe this could somehow + * be rolled into EVENT_NEW. */ + wait_for_proc(proc->pid); + continue_process(proc->pid); +} + /* If ltrace gets SIGINT, the processes directly or indirectly run by * ltrace get it too. We just have to wait long enough for the signal * to be delivered and the process terminated, which we notice and @@ -1152,7 +1172,7 @@ os_ltrace_exiting(void) { struct opt_p_t *it; for (it = opt_p; it != NULL; it = it->next) { - struct Process *proc = pid2proc(it->pid); + struct process *proc = pid2proc(it->pid); if (proc == NULL || proc->leader == NULL) continue; if (ltrace_exiting_install_handler(proc->leader) < 0) @@ -1174,7 +1194,8 @@ os_ltrace_exiting_sighandler(void) } size_t -umovebytes(Process *proc, void *addr, void *laddr, size_t len) { +umovebytes(struct process *proc, arch_addr_t addr, void *buf, size_t len) +{ union { long a; @@ -1194,11 +1215,11 @@ umovebytes(Process *proc, void *addr, void *laddr, size_t len) { started = 1; if (len - offset >= sizeof(long)) { - memcpy(laddr + offset, &a.c[0], sizeof(long)); + memcpy(buf + offset, &a.c[0], sizeof(long)); bytes_read += sizeof(long); } else { - memcpy(laddr + offset, &a.c[0], len - offset); + memcpy(buf + offset, &a.c[0], len - offset); bytes_read += (len - offset); } offset += sizeof(long); @@ -1206,3 +1227,292 @@ umovebytes(Process *proc, void *addr, void *laddr, size_t len) { return bytes_read; } + +struct irelative_name_data_t { + GElf_Addr addr; + const char *found_name; +}; + +static enum callback_status +irelative_name_cb(GElf_Sym *symbol, const char *name, void *d) +{ + struct irelative_name_data_t *data = d; + + if (symbol->st_value == data->addr) { + bool is_ifunc = false; +#ifdef STT_GNU_IFUNC + is_ifunc = GELF_ST_TYPE(symbol->st_info) == STT_GNU_IFUNC; +#endif + data->found_name = name; + + /* Keep looking, unless we found the actual IFUNC + * symbol. What we matched may have been a symbol + * denoting the resolver function, which would have + * the same address. */ + return CBS_STOP_IF(is_ifunc); + } + + return CBS_CONT; +} + +char * +linux_elf_find_irelative_name(struct ltelf *lte, GElf_Addr addr) +{ + struct irelative_name_data_t data = { addr, NULL }; + if (addr != 0 + && elf_each_symbol(lte, 0, + irelative_name_cb, &data).status < 0) + return NULL; + + const char *name; + if (data.found_name != NULL) { + name = data.found_name; + } else { +#define NAME "IREL." + /* NAME\0 + 0x + digits. */ + char *tmp_name = alloca(sizeof NAME + 2 + 16); + sprintf(tmp_name, NAME "%#" PRIx64, (uint64_t) addr); + name = tmp_name; +#undef NAME + } + + return strdup(name); +} + +enum plt_status +linux_elf_add_plt_entry_irelative(struct process *proc, struct ltelf *lte, + GElf_Rela *rela, size_t ndx, + struct library_symbol **ret) + +{ + char *name = linux_elf_find_irelative_name(lte, rela->r_addend); + int i = default_elf_add_plt_entry(proc, lte, name, rela, ndx, ret); + free(name); + return i < 0 ? PLT_FAIL : PLT_OK; +} + +struct prototype * +linux_IFUNC_prototype(void) +{ + static struct prototype ret; + if (ret.return_info == NULL) { + prototype_init(&ret); + ret.return_info = type_get_voidptr(); + ret.own_return_info = 0; + } + return &ret; +} + +int +os_library_symbol_init(struct library_symbol *libsym) +{ + libsym->os = (struct os_library_symbol_data){}; + return 0; +} + +void +os_library_symbol_destroy(struct library_symbol *libsym) +{ +} + +int +os_library_symbol_clone(struct library_symbol *retp, + struct library_symbol *libsym) +{ + retp->os = libsym->os; + return 0; +} + +char * +linux_append_IFUNC_to_name(const char *name) +{ +#define S ".IFUNC" + char *tmp_name = malloc(strlen(name) + sizeof S); + if (tmp_name == NULL) + return NULL; + sprintf(tmp_name, "%s%s", name, S); +#undef S + return tmp_name; +} + +enum plt_status +os_elf_add_func_entry(struct process *proc, struct ltelf *lte, + const GElf_Sym *sym, + arch_addr_t addr, const char *name, + struct library_symbol **ret) +{ + if (GELF_ST_TYPE(sym->st_info) == STT_FUNC) + return PLT_DEFAULT; + + bool ifunc = false; +#ifdef STT_GNU_IFUNC + ifunc = GELF_ST_TYPE(sym->st_info) == STT_GNU_IFUNC; +#endif + + if (ifunc) { + char *tmp_name = linux_append_IFUNC_to_name(name); + struct library_symbol *tmp = malloc(sizeof *tmp); + if (tmp_name == NULL || tmp == NULL) { + fail: + free(tmp_name); + free(tmp); + return PLT_FAIL; + } + + if (library_symbol_init(tmp, addr, tmp_name, 1, + LS_TOPLT_NONE) < 0) + goto fail; + tmp->proto = linux_IFUNC_prototype(); + tmp->os.is_ifunc = 1; + + *ret = tmp; + return PLT_OK; + } + + *ret = NULL; + return PLT_OK; +} + +static enum callback_status +libsym_at_address(struct library_symbol *libsym, void *addrp) +{ + arch_addr_t addr = *(arch_addr_t *)addrp; + return CBS_STOP_IF(addr == libsym->enter_addr); +} + +static void +ifunc_ret_hit(struct breakpoint *bp, struct process *proc) +{ + struct fetch_context *fetch = fetch_arg_init(LT_TOF_FUNCTION, proc, + type_get_voidptr()); + if (fetch == NULL) + return; + + struct breakpoint *nbp = NULL; + int own_libsym = 0; + struct library_symbol *libsym = NULL; + + struct value value; + value_init(&value, proc, NULL, type_get_voidptr(), 0); + size_t sz = value_size(&value, NULL); + union { + uint64_t u64; + uint32_t u32; + arch_addr_t a; + } u; + + if (fetch_retval(fetch, LT_TOF_FUNCTIONR, proc, + value.type, &value) < 0 + || sz > 8 /* Captures failure as well. */ + || value_extract_buf(&value, (void *) &u, NULL) < 0) { + fail: + fprintf(stderr, + "Couldn't trace the function " + "indicated by IFUNC resolver.\n"); + goto done; + } + + assert(sz == 4 || sz == 8); + /* XXX double casts below: */ + if (sz == 4) + u.a = (arch_addr_t)(uintptr_t)u.u32; + else + u.a = (arch_addr_t)(uintptr_t)u.u64; + if (arch_translate_address_dyn(proc, u.a, &u.a) < 0) { + fprintf(stderr, "Couldn't OPD-translate the address returned" + " by the IFUNC resolver.\n"); + goto done; + } + + assert(bp->os.ret_libsym != NULL); + + struct library *lib = bp->os.ret_libsym->lib; + assert(lib != NULL); + + /* Look if we already have a symbol with this address. + * Otherwise create a new one. */ + libsym = library_each_symbol(lib, NULL, libsym_at_address, &u.a); + if (libsym == NULL) { + libsym = malloc(sizeof *libsym); + char *name = strdup(bp->os.ret_libsym->name); + + if (libsym == NULL + || name == NULL + || library_symbol_init(libsym, u.a, name, 1, + LS_TOPLT_NONE) < 0) { + free(libsym); + free(name); + goto fail; + } + + /* Snip the .IFUNC token. */ + *strrchr(name, '.') = 0; + + own_libsym = 1; + library_add_symbol(lib, libsym); + } + + nbp = malloc(sizeof *bp); + if (nbp == NULL || breakpoint_init(nbp, proc, u.a, libsym) < 0) + goto fail; + + /* If there already is a breakpoint at that address, that is + * suspicious, but whatever. */ + struct breakpoint *pre_bp = insert_breakpoint(proc, nbp); + if (pre_bp == NULL) + goto fail; + if (pre_bp == nbp) { + /* PROC took our breakpoint, so these resources are + * not ours anymore. */ + nbp = NULL; + own_libsym = 0; + } + +done: + free(nbp); + if (own_libsym) { + library_symbol_destroy(libsym); + free(libsym); + } + fetch_arg_done(fetch); +} + +static int +create_ifunc_ret_bp(struct breakpoint **ret, + struct breakpoint *bp, struct process *proc) +{ + *ret = create_default_return_bp(proc); + if (*ret == NULL) + return -1; + static struct bp_callbacks cbs = { + .on_hit = ifunc_ret_hit, + }; + breakpoint_set_callbacks(*ret, &cbs); + + (*ret)->os.ret_libsym = bp->libsym; + + return 0; +} + +int +os_breakpoint_init(struct process *proc, struct breakpoint *bp) +{ + if (bp->libsym != NULL && bp->libsym->os.is_ifunc) { + static struct bp_callbacks cbs = { + .get_return_bp = create_ifunc_ret_bp, + }; + breakpoint_set_callbacks(bp, &cbs); + } + return 0; +} + +void +os_breakpoint_destroy(struct breakpoint *bp) +{ +} + +int +os_breakpoint_clone(struct breakpoint *retp, struct breakpoint *bp) +{ + return 0; +} diff --git a/sysdeps/linux-gnu/trace.h b/sysdeps/linux-gnu/trace.h index 88ac33d..1599738 100644 --- a/sysdeps/linux-gnu/trace.h +++ b/sysdeps/linux-gnu/trace.h @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -18,8 +18,8 @@ * 02110-1301 USA */ -#ifndef _LTRACE_LINUX_TRACE_H_ -#define _LTRACE_LINUX_TRACE_H_ +#ifndef LTRACE_LINUX_TRACE_H +#define LTRACE_LINUX_TRACE_H #include "proc.h" @@ -59,13 +59,13 @@ struct process_stopping_handler struct event_handler super; /* The task that is doing the re-enablement. */ - struct Process *task_enabling_breakpoint; + struct process *task_enabling_breakpoint; /* The pointer being re-enabled. */ struct breakpoint *breakpoint_being_enabled; - /* Artificial atomic skip breakpoint, if any needed. */ - void *atomic_skip_bp_addrs[2]; + /* Software singlestep breakpoints, if any needed. */ + struct breakpoint *sws_bps[2]; /* When all tasks are stopped, this callback gets called. */ void (*on_all_stopped)(struct process_stopping_handler *); @@ -84,17 +84,17 @@ struct process_stopping_handler enum { /* We are waiting for everyone to land in t/T. */ - psh_stopping = 0, + PSH_STOPPING = 0, /* We are doing the PTRACE_SINGLESTEP. */ - psh_singlestep, + PSH_SINGLESTEP, /* We are waiting for all the SIGSTOPs to arrive so * that we can sink them. */ - psh_sinking, + PSH_SINKING, /* This is for tracking the ugly workaround. */ - psh_ugly_workaround, + PSH_UGLY_WORKAROUND, } state; int exiting; @@ -108,7 +108,7 @@ struct process_stopping_handler * ON_ALL_STOPPED is LINUX_PTRACE_DISABLE_AND_SINGLESTEP, the default * for KEEP_STEPPING_P and UGLY_WORKAROUND_P is "no". */ int process_install_stopping_handler - (struct Process *proc, struct breakpoint *sbp, + (struct process *proc, struct breakpoint *sbp, void (*on_all_stopped)(struct process_stopping_handler *), enum callback_status (*keep_stepping_p) (struct process_stopping_handler *), @@ -118,4 +118,37 @@ int process_install_stopping_handler void linux_ptrace_disable_and_singlestep(struct process_stopping_handler *self); void linux_ptrace_disable_and_continue(struct process_stopping_handler *self); -#endif /* _LTRACE_LINUX_TRACE_H_ */ +/* When main binary needs to call an IFUNC function defined in the + * binary itself, a PLT entry is set up so that dynamic linker can get + * involved and resolve the symbol. But unlike other PLT relocation, + * this one can't rely on symbol table being available. So it doesn't + * reference the symbol by its name, but by its address, and + * correspondingly, has another type. When arch backend wishes to + * support these IRELATIVE relocations, it should override + * arch_elf_add_plt_entry and dispatch to this function for IRELATIVE + * relocations. + * + * This function behaves as arch_elf_add_plt_entry, except that it + * doesn't take name for a parameter, but instead looks up the name in + * symbol tables in LTE. */ +enum plt_status linux_elf_add_plt_entry_irelative(struct process *proc, + struct ltelf *lte, + GElf_Rela *rela, size_t ndx, + struct library_symbol **ret); + +/* Service routine of the above. Determines a name corresponding to + * ADDR, or invents a new one. Returns NULL on failures, otherwise it + * returns a malloc'd pointer that the caller is responsible for + * freeing. */ +char *linux_elf_find_irelative_name(struct ltelf *lte, GElf_Addr addr); + +/* Returns ${NAME}.IFUNC in a newly-malloc'd block, or NULL on + * failures. */ +char *linux_append_IFUNC_to_name(const char *name); + +/* Returns a statically allocated prototype that represents the + * prototype "void *()". Never fails. */ +struct prototype *linux_IFUNC_prototype(void); + + +#endif /* LTRACE_LINUX_TRACE_H */ diff --git a/sysdeps/linux-gnu/x86/arch.h b/sysdeps/linux-gnu/x86/arch.h index 329cfba..440020e 100644 --- a/sysdeps/linux-gnu/x86/arch.h +++ b/sysdeps/linux-gnu/x86/arch.h @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011, 2012 Petr Machata + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2006 Ian Wienand * Copyright (C) 2004 Juan Cespedes * @@ -19,6 +19,10 @@ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA */ +#ifndef LTRACE_X86_ARCH_H +#define LTRACE_X86_ARCH_H + +#include "vect.h" #define BREAKPOINT_VALUE {0xcc} #define BREAKPOINT_LENGTH 1 @@ -28,9 +32,18 @@ #define ARCH_HAVE_ALIGNOF #define ARCH_ENDIAN_LITTLE +#define ARCH_HAVE_ADD_PLT_ENTRY + +#define ARCH_HAVE_LTELF_DATA +struct arch_ltelf_data { + struct vect plt_map; +}; + #ifdef __x86_64__ #define LT_ELFCLASS ELFCLASS64 #define LT_ELF_MACHINE EM_X86_64 #endif #define LT_ELFCLASS2 ELFCLASS32 #define LT_ELF_MACHINE2 EM_386 + +#endif /* LTRACE_X86_ARCH_H */ diff --git a/sysdeps/linux-gnu/x86/fetch.c b/sysdeps/linux-gnu/x86/fetch.c index 4dab4cc..6868101 100644 --- a/sysdeps/linux-gnu/x86/fetch.c +++ b/sysdeps/linux-gnu/x86/fetch.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata + * Copyright (C) 2011,2012,2013 Petr Machata * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -58,9 +58,10 @@ struct fetch_context struct user_regs_struct iregs; struct user_fpregs_struct fpregs; - void *stack_pointer; + arch_addr_t stack_pointer; size_t ireg; /* Used-up integer registers. */ size_t freg; /* Used-up floating registers. */ + int machine; union { struct { @@ -137,14 +138,14 @@ allocate_stack_slot(struct fetch_context *context, struct value *valuep, size_t sz, size_t offset, size_t archw) { + assert(valuep != NULL); size_t a = type_alignof(valuep->inferior, valuep->type); if (a < archw) a = archw; context->stack_pointer = (void *)align((unsigned long)context->stack_pointer, a); - if (valuep != NULL) - value_in_inferior(valuep, context->stack_pointer); + value_in_inferior(valuep, context->stack_pointer); context->stack_pointer += sz; } @@ -238,21 +239,42 @@ allocate_integer(struct fetch_context *context, struct value *valuep, case POOL_SYSCALL: #ifdef __x86_64__ - switch (context->ireg) { - HANDLE(0, rdi); - HANDLE(1, rsi); - HANDLE(2, rdx); - HANDLE(3, r10); - HANDLE(4, r8); - HANDLE(5, r9); - default: - assert(!"More than six syscall arguments???"); - abort(); + if (context->machine == EM_X86_64) { + switch (context->ireg) { + HANDLE(0, rdi); + HANDLE(1, rsi); + HANDLE(2, rdx); + HANDLE(3, r10); + HANDLE(4, r8); + HANDLE(5, r9); + default: + assert(!"More than six syscall arguments???"); + abort(); + } } +#endif + if (context->machine == EM_386) { + +#ifdef __x86_64__ +# define HANDLE32(NUM, WHICH) HANDLE(NUM, r##WHICH) #else - i386_unreachable(); +# define HANDLE32(NUM, WHICH) HANDLE(NUM, e##WHICH) #endif + switch (context->ireg) { + HANDLE32(0, bx); + HANDLE32(1, cx); + HANDLE32(2, dx); + HANDLE32(3, si); + HANDLE32(4, di); + HANDLE32(5, bp); + default: + assert(!"More than six syscall arguments???"); + abort(); + } +#undef HANDLE32 + } + case POOL_RETVAL: switch (context->ireg) { #ifdef __x86_64__ @@ -323,14 +345,14 @@ allocate_class(enum arg_class cls, struct fetch_context *context, } static ssize_t -classify(struct Process *proc, struct fetch_context *context, - struct arg_type_info *info, struct value *valuep, enum arg_class classes[], +classify(struct process *proc, struct fetch_context *context, + struct arg_type_info *info, enum arg_class classes[], size_t sz, size_t eightbytes); /* This classifies one eightbyte part of an array or struct. */ static ssize_t -classify_eightbyte(struct Process *proc, struct fetch_context *context, - struct arg_type_info *info, struct value *valuep, +classify_eightbyte(struct process *proc, struct fetch_context *context, + struct arg_type_info *info, enum arg_class *classp, size_t start, size_t end, struct arg_type_info *(*getter)(struct arg_type_info *, size_t)) @@ -343,7 +365,7 @@ classify_eightbyte(struct Process *proc, struct fetch_context *context, size_t sz = type_sizeof(proc, info2); if (sz == (size_t)-1) return -1; - if (classify(proc, context, info2, valuep, &cls2, sz, 1) < 0) + if (classify(proc, context, info2, &cls2, sz, 1) < 0) return -1; if (cls == CLASS_NO) @@ -364,8 +386,8 @@ classify_eightbyte(struct Process *proc, struct fetch_context *context, /* This classifies small arrays and structs. */ static ssize_t -classify_eightbytes(struct Process *proc, struct fetch_context *context, - struct arg_type_info *info, struct value *valuep, +classify_eightbytes(struct process *proc, struct fetch_context *context, + struct arg_type_info *info, enum arg_class classes[], size_t elements, size_t eightbytes, struct arg_type_info *(*getter)(struct arg_type_info *, @@ -384,9 +406,9 @@ classify_eightbytes(struct Process *proc, struct fetch_context *context, } enum arg_class cls1, cls2; - if (classify_eightbyte(proc, context, info, valuep, &cls1, + if (classify_eightbyte(proc, context, info, &cls1, 0, start_2nd, getter) < 0 - || classify_eightbyte(proc, context, info, valuep, &cls2, + || classify_eightbyte(proc, context, info, &cls2, start_2nd, elements, getter) < 0) return -1; @@ -400,7 +422,7 @@ classify_eightbytes(struct Process *proc, struct fetch_context *context, return 2; } - return classify_eightbyte(proc, context, info, valuep, classes, + return classify_eightbyte(proc, context, info, classes, 0, elements, getter); } @@ -432,8 +454,8 @@ flatten_structure(struct arg_type_info *flattened, struct arg_type_info *info) } static ssize_t -classify(struct Process *proc, struct fetch_context *context, - struct arg_type_info *info, struct value *valuep, enum arg_class classes[], +classify(struct process *proc, struct fetch_context *context, + struct arg_type_info *info, enum arg_class classes[], size_t sz, size_t eightbytes) { switch (info->type) { @@ -474,7 +496,7 @@ classify(struct Process *proc, struct fetch_context *context, if (expr_eval_constant(info->u.array_info.length, &l) < 0) return -1; - return classify_eightbytes(proc, context, info, valuep, classes, + return classify_eightbytes(proc, context, info, classes, (size_t)l, eightbytes, get_array_field); @@ -492,7 +514,7 @@ classify(struct Process *proc, struct fetch_context *context, goto done; } ret = classify_eightbytes(proc, context, &flattened, - valuep, classes, + classes, type_struct_size(&flattened), eightbytes, type_struct_get); done: @@ -517,7 +539,7 @@ pass_by_reference(struct value *valuep, enum arg_class classes[]) } static ssize_t -classify_argument(struct Process *proc, struct fetch_context *context, +classify_argument(struct process *proc, struct fetch_context *context, struct arg_type_info *info, struct value *valuep, enum arg_class classes[], size_t *sizep) { @@ -541,11 +563,11 @@ classify_argument(struct Process *proc, struct fetch_context *context, return pass_by_reference(valuep, classes); } - return classify(proc, context, info, valuep, classes, sz, eightbytes); + return classify(proc, context, info, classes, sz, eightbytes); } static int -fetch_register_banks(struct Process *proc, struct fetch_context *context, +fetch_register_banks(struct process *proc, struct fetch_context *context, int floating) { if (ptrace(PTRACE_GETREGS, proc->pid, 0, &context->iregs) < 0) @@ -566,12 +588,21 @@ fetch_register_banks(struct Process *proc, struct fetch_context *context, static int arch_fetch_arg_next_32(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { size_t sz = type_sizeof(proc, info); if (sz == (size_t)-1) return -1; + if (value_reserve(valuep, sz) == NULL) + return -1; + + if (type == LT_TOF_SYSCALL || type == LT_TOF_SYSCALLR) { + int cls = allocate_integer(context, valuep, + sz, 0, POOL_SYSCALL); + assert(cls == CLASS_INTEGER); + return 0; + } allocate_stack_slot(context, valuep, sz, 0, 4); @@ -580,7 +611,7 @@ arch_fetch_arg_next_32(struct fetch_context *context, enum tof type, static int arch_fetch_retval_32(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { if (fetch_register_banks(proc, context, type == LT_TOF_FUNCTIONR) < 0) @@ -646,7 +677,7 @@ fetch_stack_pointer(struct fetch_context *context) struct fetch_context * arch_fetch_arg_init_32(struct fetch_context *context, - enum tof type, struct Process *proc, + enum tof type, struct process *proc, struct arg_type_info *ret_info) { context->stack_pointer = fetch_stack_pointer(context) + 4; @@ -673,7 +704,7 @@ arch_fetch_arg_init_32(struct fetch_context *context, struct fetch_context * arch_fetch_arg_init_64(struct fetch_context *ctx, enum tof type, - struct Process *proc, struct arg_type_info *ret_info) + struct process *proc, struct arg_type_info *ret_info) { /* The first stack slot holds a return address. */ ctx->stack_pointer = fetch_stack_pointer(ctx) + 8; @@ -698,12 +729,13 @@ arch_fetch_arg_init_64(struct fetch_context *ctx, enum tof type, } struct fetch_context * -arch_fetch_arg_init(enum tof type, struct Process *proc, +arch_fetch_arg_init(enum tof type, struct process *proc, struct arg_type_info *ret_info) { struct fetch_context *ctx = malloc(sizeof(*ctx)); if (ctx == NULL) return NULL; + ctx->machine = proc->e_machine; assert(type != LT_TOF_FUNCTIONR && type != LT_TOF_SYSCALLR); @@ -724,7 +756,7 @@ arch_fetch_arg_init(enum tof type, struct Process *proc, } struct fetch_context * -arch_fetch_arg_clone(struct Process *proc, struct fetch_context *context) +arch_fetch_arg_clone(struct process *proc, struct fetch_context *context) { struct fetch_context *ret = malloc(sizeof(*ret)); if (ret == NULL) @@ -734,7 +766,7 @@ arch_fetch_arg_clone(struct Process *proc, struct fetch_context *context) static int arch_fetch_pool_arg_next(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep, enum reg_pool pool) { enum arg_class classes[2]; @@ -776,7 +808,7 @@ arch_fetch_pool_arg_next(struct fetch_context *context, enum tof type, int arch_fetch_fun_retval(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { assert(type != LT_TOF_FUNCTION @@ -808,7 +840,7 @@ arch_fetch_fun_retval(struct fetch_context *context, enum tof type, int arch_fetch_arg_next(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { if (proc->e_machine == EM_386) @@ -832,7 +864,7 @@ arch_fetch_arg_next(struct fetch_context *context, enum tof type, int arch_fetch_retval(struct fetch_context *context, enum tof type, - struct Process *proc, struct arg_type_info *info, + struct process *proc, struct arg_type_info *info, struct value *valuep) { if (proc->e_machine == EM_386) diff --git a/sysdeps/linux-gnu/x86/plt.c b/sysdeps/linux-gnu/x86/plt.c index dc6f183..c860af6 100644 --- a/sysdeps/linux-gnu/x86/plt.c +++ b/sysdeps/linux-gnu/x86/plt.c @@ -1,5 +1,6 @@ /* * This file is part of ltrace. + * Copyright (C) 2013 Petr Machata, Red Hat Inc. * Copyright (C) 2004,2008,2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -19,16 +20,144 @@ */ #include <gelf.h> +#include <stdbool.h> + #include "proc.h" #include "common.h" #include "library.h" +#include "trace.h" + +static GElf_Addr +x86_plt_offset(uint32_t i) +{ + /* Skip the first PLT entry, which contains a stub to call the + * resolver. */ + return (i + 1) * 16; +} GElf_Addr -arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela * rela) { - return lte->plt_addr + (ndx + 1) * 16; +arch_plt_sym_val(struct ltelf *lte, size_t ndx, GElf_Rela *rela) +{ + uint32_t i = *VECT_ELEMENT(<e->arch.plt_map, uint32_t, ndx); + return x86_plt_offset(i) + lte->plt_addr; } void * -sym2addr(Process *proc, struct library_symbol *sym) { +sym2addr(struct process *proc, struct library_symbol *sym) +{ return sym->enter_addr; } + +enum plt_status +arch_elf_add_plt_entry(struct process *proc, struct ltelf *lte, + const char *a_name, GElf_Rela *rela, size_t ndx, + struct library_symbol **ret) +{ + bool irelative = false; + if (lte->ehdr.e_machine == EM_X86_64) { +#ifdef R_X86_64_IRELATIVE + irelative = GELF_R_TYPE(rela->r_info) == R_X86_64_IRELATIVE; +#endif + } else { + assert(lte->ehdr.e_machine == EM_386); +#ifdef R_386_IRELATIVE + irelative = GELF_R_TYPE(rela->r_info) == R_386_IRELATIVE; +#endif + } + + if (irelative) + return linux_elf_add_plt_entry_irelative(proc, lte, rela, + ndx, ret); + + return PLT_DEFAULT; +} + +int +arch_elf_init(struct ltelf *lte, struct library *lib) +{ + VECT_INIT(<e->arch.plt_map, unsigned int); + + /* IRELATIVE slots may make the whole situation a fair deal + * more complex. On x86{,_64}, the PLT slots are not + * presented in the order of the corresponding relocations, + * but in the order it which these symbols are in the symbol + * table. That's static symbol table, which may be stripped + * off, not dynsym--that doesn't contain IFUNC symbols at all. + * So we have to decode each PLT entry to figure out what + * entry it corresponds to. We need to interpret the PLT + * table to figure this out. + * + * On i386, the PLT entry format is as follows: + * + * 8048300: ff 25 0c a0 04 08 jmp *0x804a00c + * 8048306: 68 20 00 00 00 push $0x20 + * 804830b: e9 e0 ff ff ff jmp 80482f0 <_init+0x30> + * + * For PIE binaries it is the following: + * + * 410: ff a3 10 00 00 00 jmp *0x10(%ebx) + * 416: 68 00 00 00 00 push $0x0 + * 41b: e9 d0 ff ff ff jmp 3f0 <_init+0x30> + * + * On x86_64, it is: + * + * 400420: ff 25 f2 0b 20 00 jmpq *0x200bf2(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18> + * 400426: 68 00 00 00 00 pushq $0x0 + * 40042b: e9 e0 ff ff ff jmpq 400410 <_init+0x18> + * + * On i386, the argument to push is an offset of relocation to + * use. The first PLT slot has an offset of 0x0, the second + * 0x8, etc. On x86_64, it's directly the index that we are + * looking for. + */ + + /* Here we scan the PLT table and initialize a map of + * relocation->slot number in lte->arch.plt_map. */ + + size_t i; + for (i = 0; i < vect_size(<e->plt_relocs); ++i) { + + GElf_Addr offset = x86_plt_offset(i); + uint32_t reloc_arg = 0; + + uint8_t byte; + if (elf_read_next_u8(lte->plt_data, &offset, &byte) < 0 + || byte != 0xff + || elf_read_next_u8(lte->plt_data, &offset, &byte) < 0 + || (byte != 0xa3 && byte != 0x25)) + goto next; + + /* Skip immediate argument in the instruction. */ + offset += 4; + + if (elf_read_next_u8(lte->plt_data, &offset, &byte) < 0 + || byte != 0x68 + || elf_read_next_u32(lte->plt_data, + &offset, &reloc_arg) < 0) { + reloc_arg = 0; + goto next; + } + + if (lte->ehdr.e_machine == EM_386) { + if (reloc_arg % 8 != 0) { + reloc_arg = 0; + goto next; + } + reloc_arg /= 8; + } + + next: + if (VECT_PUSHBACK(<e->arch.plt_map, &reloc_arg) < 0) { + arch_elf_destroy(lte); + return -1; + } + } + + return 0; +} + +void +arch_elf_destroy(struct ltelf *lte) +{ + VECT_DESTROY(<e->arch.plt_map, uint32_t, NULL, NULL); +} diff --git a/sysdeps/linux-gnu/x86/regs.c b/sysdeps/linux-gnu/x86/regs.c index ca6470b..0a42c6e 100644 --- a/sysdeps/linux-gnu/x86/regs.c +++ b/sysdeps/linux-gnu/x86/regs.c @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 1998,2002,2004,2008,2009 Juan Cespedes * Copyright (C) 2006 Ian Wienand * @@ -56,7 +56,7 @@ conv_32(arch_addr_t val) } void * -get_instruction_pointer(struct Process *proc) +get_instruction_pointer(struct process *proc) { long int ret = ptrace(PTRACE_PEEKUSER, proc->pid, XIP, 0); if (proc->e_machine == EM_386) @@ -65,7 +65,7 @@ get_instruction_pointer(struct Process *proc) } void -set_instruction_pointer(struct Process *proc, arch_addr_t addr) +set_instruction_pointer(struct process *proc, arch_addr_t addr) { if (proc->e_machine == EM_386) addr = conv_32(addr); @@ -73,7 +73,7 @@ set_instruction_pointer(struct Process *proc, arch_addr_t addr) } void * -get_stack_pointer(struct Process *proc) +get_stack_pointer(struct process *proc) { long sp = ptrace(PTRACE_PEEKUSER, proc->pid, XSP, 0); if (sp == -1 && errno) { @@ -91,7 +91,7 @@ get_stack_pointer(struct Process *proc) } void * -get_return_addr(struct Process *proc, void *sp) +get_return_addr(struct process *proc, void *sp) { long a = ptrace(PTRACE_PEEKTEXT, proc->pid, sp, 0); if (a == -1 && errno) { @@ -107,10 +107,3 @@ get_return_addr(struct Process *proc, void *sp) ret = conv_32(ret); return ret; } - -void -set_return_addr(Process *proc, void *addr) { - if (proc->e_machine == EM_386) - addr = (void *)((long int)addr & 0xffffffff); - ptrace(PTRACE_POKETEXT, proc->pid, proc->stack_pointer, addr); -} diff --git a/sysdeps/linux-gnu/x86/trace.c b/sysdeps/linux-gnu/x86/trace.c index ed8bdb4..6a1a6a5 100644 --- a/sysdeps/linux-gnu/x86/trace.c +++ b/sysdeps/linux-gnu/x86/trace.c @@ -55,7 +55,7 @@ static const int x86_64 = 0; #endif void -get_arch_dep(struct Process *proc) +get_arch_dep(struct process *proc) { /* Unfortunately there are still remnants of mask_32bit uses * around. */ @@ -75,7 +75,7 @@ get_arch_dep(struct Process *proc) /* Returns 1 if syscall, 2 if sysret, 0 otherwise. */ int -syscall_p(struct Process *proc, int status, int *sysnum) +syscall_p(struct process *proc, int status, int *sysnum) { if (WIFSTOPPED(status) && WSTOPSIG(status) == (SIGTRAP | proc->tracesysgood)) { @@ -109,7 +109,7 @@ syscall_p(struct Process *proc, int status, int *sysnum) } size_t -arch_type_sizeof(struct Process *proc, struct arg_type_info *info) +arch_type_sizeof(struct process *proc, struct arg_type_info *info) { if (proc == NULL) return (size_t)-2; @@ -151,7 +151,7 @@ arch_type_sizeof(struct Process *proc, struct arg_type_info *info) } size_t -arch_type_alignof(struct Process *proc, struct arg_type_info *info) +arch_type_alignof(struct process *proc, struct arg_type_info *info) { if (proc == NULL) return (size_t)-2; diff --git a/sysdeps/sysdep.h b/sysdeps/sysdep.h index e8e287f..24c9341 100644 --- a/sysdeps/sysdep.h +++ b/sysdeps/sysdep.h @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -24,10 +24,11 @@ #include <arch.h> #ifndef ARCH_HAVE_ADDRESS_TYPES /* We should in general be able to trace 64-bit processes with 32-bit - * ltrace. (At least PPC has several PTRACE requests related to - * tracing 64-on-32, so presumably it should be possible.) But ltrace - * is currently hopelessly infested with using void* for host address. - * So keep with it, for now. */ + * ltrace (this is possible on PPC, and generally there should be no + * problem tracing x86_64 processes from x32 ltrace, though it isn't + * possible from i386 ltrace). But ltrace is currently hopelessly + * infested with using void* for host address. So keep with it, for + * now. */ typedef void *arch_addr_t; #endif @@ -38,16 +39,31 @@ struct arch_ltelf_data { }; #endif +#ifndef OS_HAVE_BREAKPOINT_DATA +struct os_breakpoint_data { +}; +#endif + #ifndef ARCH_HAVE_BREAKPOINT_DATA struct arch_breakpoint_data { }; #endif +#ifndef OS_HAVE_LIBRARY_SYMBOL_DATA +struct os_library_symbol_data { +}; +#endif + #ifndef ARCH_HAVE_LIBRARY_SYMBOL_DATA struct arch_library_symbol_data { }; #endif +#ifndef OS_HAVE_LIBRARY_DATA +struct os_library_data { +}; +#endif + #ifndef ARCH_HAVE_LIBRARY_DATA struct arch_library_data { }; diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am index f9b6c0d..d999b2e 100644 --- a/testsuite/Makefile.am +++ b/testsuite/Makefile.am @@ -38,6 +38,7 @@ BUILT_SOURCES = env.exp env.exp: Makefile rm -f env.exp echo set libelf_LD_LIBRARY_PATH '"$(libelf_LD_LIBRARY_PATH)"' >> $@ + echo set elfutils_LD_LIBRARY_PATH '"$(elfutils_LD_LIBRARY_PATH)"' >> $@ echo set libunwind_LD_LIBRARY_PATH '"$(libunwind_LD_LIBRARY_PATH)"' >> $@ CLEANFILES = *.o *.so *.log *.sum *.ltrace site.bak setval.tmp site.exp env.exp diff --git a/testsuite/lib/ltrace.exp b/testsuite/lib/ltrace.exp index cbb5602..9931794 100644 --- a/testsuite/lib/ltrace.exp +++ b/testsuite/lib/ltrace.exp @@ -1,5 +1,5 @@ # This file is part of ltrace. -# Copyright (C) 2012 Petr Machata, Red Hat Inc. +# Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. # Copyright (C) 2006 Yao Qi, IBM Corporation # # This program is free software; you can redistribute it and/or @@ -38,6 +38,18 @@ set LTRACE_OPTIONS {} set LTRACE_ARGS {} set LTRACE_TEMP_FILES {} +# Pre-8.5 TCL doesn't have lreverse. The following is taken from: +# http://www2.tcl.tk/17188 + +if {[info command lreverse] == ""} { + proc lreverse l { + set r {} + set i [llength $l] + while {[incr i -1]} {lappend r [lindex $l $i]} + lappend r [lindex $l 0] + } +} + # ltrace_compile SOURCE DEST TYPE OPTIONS # # Compile PUT(program under test) by native compiler. ltrace_compile runs @@ -218,12 +230,15 @@ proc ltrace_compile_shlib {sources dest options} { # # Arguments: # files List of files to delete. +# # Results: -# Each of the files is deleted. Returns nothing. +# Each of the files is deleted. Files are deleted in reverse +# order, so that directories are emptied and can be deleted +# without using -force. Returns nothing. proc WipeFiles {files} { verbose "WipeFiles: $files\n" - foreach f $files { + foreach f [lreverse $files] { file delete $f } } @@ -315,6 +330,37 @@ proc LtraceTempFile {pat} { return } +# ltraceNamedSource -- +# +# Create a file named FILENAME, and prime it with TEXT. If +# REMEMBERTEMP, add the file into LTRACE_TEMP_FILES, so that +# ltraceDone (or rather WipeFiles) erases it later. +# +# Arguments: +# filename Name of the file to create. +# +# text Contents of the new file. +# +# rememberTemp Whether to add filename to LTRACE_TEMP_FILES. +# +# Results: +# Returns $filename, which now refers to a file with contents +# given by TEXT. + +proc ltraceNamedSource {filename text {rememberTemp 1}} { + global LTRACE_TEMP_FILES + + set chan [open $filename w] + puts $chan $text + close $chan + + if $rememberTemp { + lappend LTRACE_TEMP_FILES $filename + } + + return $filename +} + # ltraceSource -- # # Create a temporary file with a given suffix and prime it with @@ -329,12 +375,22 @@ proc LtraceTempFile {pat} { # Returns file name of created file. proc ltraceSource {suffix text} { - set ret [LtraceTempFile "lt-XXXXXXXXXX.$suffix"] + return [ltraceNamedSource \ + [LtraceTempFile "lt-XXXXXXXXXX.$suffix"] $text 0] +} - set chan [open $ret w] - puts $chan $text - close $chan +# ltraceDir -- +# +# Create a temporary directory. +# +# Arguments: +# +# Results: +# Returns name of created directory. +proc ltraceDir {} { + set ret [LtraceTempFile "lt-XXXXXXXXXX.dir"] + file mkdir $ret return $ret } @@ -435,7 +491,9 @@ proc ltraceCompile {dest args} { set sources {} set objects {} foreach a $args { - if {[string match "-?*" $a]} { + if {[string match "-l*" $a]} { + lappend options "shlib=$a" + } elseif {[string match "-?*" $a]} { lappend options [string range $a 1 end] } elseif {[string match "*.so" $a]} { lappend options "shlib=$a" @@ -620,6 +678,37 @@ proc ltraceMatch {logfile patterns} { return } +# ltraceLibTest -- +# +# Generate a binary, a library (liblib.so) and a config file. +# Run the binary using ltraceRun, passing it -F to load the +# config file. +# +# Arguments: +# conf Contents of ltrace config file. +# +# cdecl Contents of header file. +# +# libcode Contents of library implementation file. +# +# maincode Contents of function "main". +# +# params Additional parameters to pass to ltraceRun. +# +# Results: +# +# Returns whatever ltraceRun returns. + +proc ltraceLibTest {conf cdecl libcode maincode {params ""}} { + set conffile [ltraceSource conf $conf] + set lib [ltraceCompile liblib.so [ltraceSource c [concat $cdecl $libcode]]] + set bin [ltraceCompile {} $lib \ + [ltraceSource c \ + [concat $cdecl "int main(void) {" $maincode "}"]]] + + return [eval [concat "ltraceRun -F $conffile " $params "-- $bin"]] +} + # # ltrace_options OPTIONS_LIST # Pass ltrace commandline options. @@ -653,6 +742,10 @@ proc ld_library_path { args } { if {[string length $libelf_LD_LIBRARY_PATH] > 0} { lappend ALL_LIBRARY_PATHS $libelf_LD_LIBRARY_PATH } + global elfutils_LD_LIBRARY_PATH + if {[string length $elfutils_LD_LIBRARY_PATH] > 0} { + lappend ALL_LIBRARY_PATHS $elfutils_LD_LIBRARY_PATH + } global libunwind_LD_LIBRARY_PATH if {[string length $libunwind_LD_LIBRARY_PATH] > 0} { lappend ALL_LIBRARY_PATHS $libunwind_LD_LIBRARY_PATH diff --git a/testsuite/ltrace.main/Makefile.am b/testsuite/ltrace.main/Makefile.am index ade68d6..12bbb9b 100644 --- a/testsuite/ltrace.main/Makefile.am +++ b/testsuite/ltrace.main/Makefile.am @@ -1,4 +1,4 @@ -# Copyright (C) 1992 - 2001, 2012 Free Software Foundation, Inc. +# Copyright (C) 1992 - 2001, 2012, 2013 Free Software Foundation, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,29 +15,12 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -EXTRA_DIST = \ - branch_func.c \ - branch_func.exp \ - filters.exp \ - hello-vfork.c \ - hello-vfork.exp \ - main.c \ - main.exp \ - main-internal.exp \ - main-lib.c \ - main-threaded.c \ - main-threaded.exp \ - main-vfork.c \ - main-vfork.exp \ - parameters.c \ - parameters.conf \ - parameters.exp \ - parameters-lib.c \ - parameters2.exp \ - signals.c \ - signals.exp \ - system_calls.c \ - system_calls.exp +EXTRA_DIST = branch_func.c branch_func.exp filters.exp hello-vfork.c \ + hello-vfork.exp main.c main.exp main-internal.exp main-lib.c \ + main-threaded.c main-threaded.exp main-vfork.c main-vfork.exp \ + parameters.c parameters.conf parameters.exp parameters-lib.c \ + parameters2.exp parameters3.exp signals.c signals.exp \ + system_calls.c system_calls.exp system_call_params.exp CLEANFILES = *.o *.so *.log *.sum *.ltrace setval.tmp \ main main-internal parameters signals system_calls diff --git a/testsuite/ltrace.main/branch_func.exp b/testsuite/ltrace.main/branch_func.exp index fec5700..c77c4c9 100644 --- a/testsuite/ltrace.main/branch_func.exp +++ b/testsuite/ltrace.main/branch_func.exp @@ -32,7 +32,7 @@ if { [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executabl # set options for ltrace. ltrace_options "-x" "func1" "-x" "func2" "-x" "func3" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.main/hello-vfork.exp b/testsuite/ltrace.main/hello-vfork.exp index 12c9ca3..c17d322 100644 --- a/testsuite/ltrace.main/hello-vfork.exp +++ b/testsuite/ltrace.main/hello-vfork.exp @@ -17,7 +17,7 @@ if { [ltrace_compile $srcdir/$subdir/$srcfile $objdir/$subdir/$binfile executabl # set options for ltrace. ltrace_options "-f" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.main/main-threaded.exp b/testsuite/ltrace.main/main-threaded.exp index 4f6c25d..4d5f478 100644 --- a/testsuite/ltrace.main/main-threaded.exp +++ b/testsuite/ltrace.main/main-threaded.exp @@ -21,7 +21,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" # set options for ltrace. ltrace_options "-l" "lib$testfile.so" "-f" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.main/main-vfork.exp b/testsuite/ltrace.main/main-vfork.exp index 989012d..57fa6e6 100644 --- a/testsuite/ltrace.main/main-vfork.exp +++ b/testsuite/ltrace.main/main-vfork.exp @@ -21,7 +21,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" # set options for ltrace. ltrace_options "-l" "lib$testfile.so" "-f" "-evfork" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.main/main.exp b/testsuite/ltrace.main/main.exp index 50c5353..8ad74f7 100644 --- a/testsuite/ltrace.main/main.exp +++ b/testsuite/ltrace.main/main.exp @@ -21,7 +21,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" # set options for ltrace. ltrace_options "-l" "libmain.so" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.main/parameters-hfa.exp b/testsuite/ltrace.main/parameters-hfa.exp new file mode 100644 index 0000000..5004f00 --- /dev/null +++ b/testsuite/ltrace.main/parameters-hfa.exp @@ -0,0 +1,474 @@ +# This file is part of ltrace. +# Copyright (C) 2014 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +# Test for a number of ways that HFA can be passed. + +ltraceMatch [ltraceLibTest { + struct(double) func_dbl_eqv(struct(double), struct(struct(double)), struct(struct(struct(double))), struct(struct(struct(struct(double))))); + struct(float) func_flt_eqv(struct(float), struct(struct(float)), struct(struct(struct(float))), struct(struct(struct(struct(float))))); + + struct(float,struct(float)) func_hfa_f2(struct(float,struct(float))); + struct(float,struct(float,struct(float))) func_hfa_f3(struct(float,struct(float,struct(float)))); + struct(float,struct(float,struct(float,struct(float)))) func_hfa_f4(struct(float,struct(float,struct(float,struct(float))))); + struct(float,struct(float,struct(float,struct(float,struct(float))))) func_hfa_f5(struct(float,struct(float,struct(float,struct(float,struct(float)))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))) func_hfa_f6(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))) func_hfa_f7(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))) func_hfa_f8(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))) func_hfa_f9(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))) func_hfa_f10(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))))) func_hfa_f11(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))))); + struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))))) func_hfa_f12(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))))))); + + struct(double,struct(double)) func_hfa_d2(struct(double,struct(double))); + struct(double,struct(double,struct(double))) func_hfa_d3(struct(double,struct(double,struct(double)))); + struct(double,struct(double,struct(double,struct(double)))) func_hfa_d4(struct(double,struct(double,struct(double,struct(double))))); + struct(double,struct(double,struct(double,struct(double,struct(double))))) func_hfa_d5(struct(double,struct(double,struct(double,struct(double,struct(double)))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))) func_hfa_d6(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))) func_hfa_d7(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))) func_hfa_d8(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))) func_hfa_d9(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))) func_hfa_d10(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))))) func_hfa_d11(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))))); + struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))))) func_hfa_d12(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))))))); +} { + struct dbl_eqv1 { double d; }; + struct dbl_eqv2 { struct dbl_eqv1 d; }; + struct dbl_eqv3 { struct dbl_eqv2 d; }; + struct dbl_eqv4 { struct dbl_eqv3 d; }; + + struct flt_eqv1 { float d; }; + struct flt_eqv2 { struct flt_eqv1 d; }; + struct flt_eqv3 { struct flt_eqv2 d; }; + struct flt_eqv4 { struct flt_eqv3 d; }; + + struct struct_hfa_f2 { float a; struct flt_eqv1 b; }; + struct struct_hfa_f3 { float a; struct struct_hfa_f2 b; }; + struct struct_hfa_f4 { float a; struct struct_hfa_f3 b; }; + struct struct_hfa_f5 { float a; struct struct_hfa_f4 b; }; + struct struct_hfa_f6 { float a; struct struct_hfa_f5 b; }; + struct struct_hfa_f7 { float a; struct struct_hfa_f6 b; }; + struct struct_hfa_f8 { float a; struct struct_hfa_f7 b; }; + struct struct_hfa_f9 { float a; struct struct_hfa_f8 b; }; + struct struct_hfa_f10 { float a; struct struct_hfa_f9 b; }; + struct struct_hfa_f11 { float a; struct struct_hfa_f10 b; }; + struct struct_hfa_f12 { float a; struct struct_hfa_f11 b; }; + + struct struct_hfa_d2 { double a; struct dbl_eqv1 b; }; + struct struct_hfa_d3 { double a; struct struct_hfa_d2 b; }; + struct struct_hfa_d4 { double a; struct struct_hfa_d3 b; }; + struct struct_hfa_d5 { double a; struct struct_hfa_d4 b; }; + struct struct_hfa_d6 { double a; struct struct_hfa_d5 b; }; + struct struct_hfa_d7 { double a; struct struct_hfa_d6 b; }; + struct struct_hfa_d8 { double a; struct struct_hfa_d7 b; }; + struct struct_hfa_d9 { double a; struct struct_hfa_d8 b; }; + struct struct_hfa_d10 { double a; struct struct_hfa_d9 b; }; + struct struct_hfa_d11 { double a; struct struct_hfa_d10 b; }; + struct struct_hfa_d12 { double a; struct struct_hfa_d11 b; }; + + struct dbl_eqv1 func_dbl_eqv(struct dbl_eqv1 a, struct dbl_eqv2 b, struct dbl_eqv3 c, struct dbl_eqv4 d); + struct flt_eqv1 func_flt_eqv(struct flt_eqv1 a, struct flt_eqv2 b, struct flt_eqv3 c, struct flt_eqv4 d); + + struct struct_hfa_f2 func_hfa_f2(struct struct_hfa_f2 e); + struct struct_hfa_f3 func_hfa_f3(struct struct_hfa_f3 e); + struct struct_hfa_f4 func_hfa_f4(struct struct_hfa_f4 e); + struct struct_hfa_f5 func_hfa_f5(struct struct_hfa_f5 e); + struct struct_hfa_f6 func_hfa_f6(struct struct_hfa_f6 e); + struct struct_hfa_f7 func_hfa_f7(struct struct_hfa_f7 e); + struct struct_hfa_f8 func_hfa_f8(struct struct_hfa_f8 e); + struct struct_hfa_f9 func_hfa_f9(struct struct_hfa_f9 e); + struct struct_hfa_f10 func_hfa_f10(struct struct_hfa_f10 e); + struct struct_hfa_f11 func_hfa_f11(struct struct_hfa_f11 e); + struct struct_hfa_f12 func_hfa_f12(struct struct_hfa_f12 e); + struct struct_hfa_d2 func_hfa_d2(struct struct_hfa_d2 e); + struct struct_hfa_d3 func_hfa_d3(struct struct_hfa_d3 e); + struct struct_hfa_d4 func_hfa_d4(struct struct_hfa_d4 e); + struct struct_hfa_d5 func_hfa_d5(struct struct_hfa_d5 e); + struct struct_hfa_d6 func_hfa_d6(struct struct_hfa_d6 e); + struct struct_hfa_d7 func_hfa_d7(struct struct_hfa_d7 e); + struct struct_hfa_d8 func_hfa_d8(struct struct_hfa_d8 e); + struct struct_hfa_d9 func_hfa_d9(struct struct_hfa_d9 e); + struct struct_hfa_d10 func_hfa_d10(struct struct_hfa_d10 e); + struct struct_hfa_d11 func_hfa_d11(struct struct_hfa_d11 e); + struct struct_hfa_d12 func_hfa_d12(struct struct_hfa_d12 e); +} { + struct dbl_eqv1 func_dbl_eqv(struct dbl_eqv1 a, struct dbl_eqv2 b, struct dbl_eqv3 c, struct dbl_eqv4 d) + { return (struct dbl_eqv1){ a.d + b.d.d + c.d.d.d + d.d.d.d.d }; } + + struct flt_eqv1 func_flt_eqv(struct flt_eqv1 a, struct flt_eqv2 b, struct flt_eqv3 c, struct flt_eqv4 d) + { return (struct flt_eqv1){ a.d + b.d.d + c.d.d.d + d.d.d.d.d }; } + + struct struct_hfa_f2 func_hfa_f2(struct struct_hfa_f2 e) { return e; } + struct struct_hfa_f3 func_hfa_f3(struct struct_hfa_f3 e) { return e; } + struct struct_hfa_f4 func_hfa_f4(struct struct_hfa_f4 e) { return e; } + struct struct_hfa_f5 func_hfa_f5(struct struct_hfa_f5 e) { return e; } + struct struct_hfa_f6 func_hfa_f6(struct struct_hfa_f6 e) { return e; } + struct struct_hfa_f7 func_hfa_f7(struct struct_hfa_f7 e) { return e; } + struct struct_hfa_f8 func_hfa_f8(struct struct_hfa_f8 e) { return e; } + struct struct_hfa_f9 func_hfa_f9(struct struct_hfa_f9 e) { return e; } + struct struct_hfa_f10 func_hfa_f10(struct struct_hfa_f10 e) { return e; } + struct struct_hfa_f11 func_hfa_f11(struct struct_hfa_f11 e) { return e; } + struct struct_hfa_f12 func_hfa_f12(struct struct_hfa_f12 e) { return e; } + struct struct_hfa_d2 func_hfa_d2(struct struct_hfa_d2 e) { return e; } + struct struct_hfa_d3 func_hfa_d3(struct struct_hfa_d3 e) { return e; } + struct struct_hfa_d4 func_hfa_d4(struct struct_hfa_d4 e) { return e; } + struct struct_hfa_d5 func_hfa_d5(struct struct_hfa_d5 e) { return e; } + struct struct_hfa_d6 func_hfa_d6(struct struct_hfa_d6 e) { return e; } + struct struct_hfa_d7 func_hfa_d7(struct struct_hfa_d7 e) { return e; } + struct struct_hfa_d8 func_hfa_d8(struct struct_hfa_d8 e) { return e; } + struct struct_hfa_d9 func_hfa_d9(struct struct_hfa_d9 e) { return e; } + struct struct_hfa_d10 func_hfa_d10(struct struct_hfa_d10 e) { return e; } + struct struct_hfa_d11 func_hfa_d11(struct struct_hfa_d11 e) { return e; } + struct struct_hfa_d12 func_hfa_d12(struct struct_hfa_d12 e) { return e; } +} { + func_dbl_eqv((struct dbl_eqv1){ 2.5 }, + (struct dbl_eqv2){ { 1.5 } }, + (struct dbl_eqv3){ { { 0.5 } } }, + (struct dbl_eqv4){ { { { -0.5 } } } }); + + func_flt_eqv((struct flt_eqv1){ 2.5 }, + (struct flt_eqv2){ { 1.5 } }, + (struct flt_eqv3){ { { 0.5 } } }, + (struct flt_eqv4){ { { { -0.5 } } } }); + + struct struct_hfa_f2 arg_func_hfa_f2 = { 1, { 2 } }; + func_hfa_f2(arg_func_hfa_f2); + struct struct_hfa_f3 arg_func_hfa_f3 = { 3, { 1, { 2 } } }; + func_hfa_f3(arg_func_hfa_f3); + struct struct_hfa_f4 arg_func_hfa_f4 = { 4, { 3, { 1, { 2 } } } }; + func_hfa_f4(arg_func_hfa_f4); + struct struct_hfa_f5 arg_func_hfa_f5 = { 5, { 4, { 3, { 1, { 2 } } } } }; + func_hfa_f5(arg_func_hfa_f5); + struct struct_hfa_f6 arg_func_hfa_f6 = { 6, { 5, { 4, { 3, { 1, { 2 } } } } } }; + func_hfa_f6(arg_func_hfa_f6); + struct struct_hfa_f7 arg_func_hfa_f7 = { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } }; + func_hfa_f7(arg_func_hfa_f7); + struct struct_hfa_f8 arg_func_hfa_f8 = { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } }; + func_hfa_f8(arg_func_hfa_f8); + struct struct_hfa_f9 arg_func_hfa_f9 = { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } }; + func_hfa_f9(arg_func_hfa_f9); + struct struct_hfa_f10 arg_func_hfa_f10 = { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } }; + func_hfa_f10(arg_func_hfa_f10); + struct struct_hfa_f11 arg_func_hfa_f11 = { 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } }; + func_hfa_f11(arg_func_hfa_f11); + struct struct_hfa_f12 arg_func_hfa_f12 = { 12, { 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } } }; + func_hfa_f12(arg_func_hfa_f12); + + struct struct_hfa_d2 arg_func_hfa_d2 = { 1, { 2 } }; + func_hfa_d2(arg_func_hfa_d2); + struct struct_hfa_d3 arg_func_hfa_d3 = { 3, { 1, { 2 } } }; + func_hfa_d3(arg_func_hfa_d3); + struct struct_hfa_d4 arg_func_hfa_d4 = { 4, { 3, { 1, { 2 } } } }; + func_hfa_d4(arg_func_hfa_d4); + struct struct_hfa_d5 arg_func_hfa_d5 = { 5, { 4, { 3, { 1, { 2 } } } } }; + func_hfa_d5(arg_func_hfa_d5); + struct struct_hfa_d6 arg_func_hfa_d6 = { 6, { 5, { 4, { 3, { 1, { 2 } } } } } }; + func_hfa_d6(arg_func_hfa_d6); + struct struct_hfa_d7 arg_func_hfa_d7 = { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } }; + func_hfa_d7(arg_func_hfa_d7); + struct struct_hfa_d8 arg_func_hfa_d8 = { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } }; + func_hfa_d8(arg_func_hfa_d8); + struct struct_hfa_d9 arg_func_hfa_d9 = { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } }; + func_hfa_d9(arg_func_hfa_d9); + struct struct_hfa_d10 arg_func_hfa_d10 = { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } }; + func_hfa_d10(arg_func_hfa_d10); + struct struct_hfa_d11 arg_func_hfa_d11 = { 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } }; + func_hfa_d11(arg_func_hfa_d11); + struct struct_hfa_d12 arg_func_hfa_d12 = { 12, { 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } } }; + func_hfa_d12(arg_func_hfa_d12); +}] { + { {func_dbl_eqv\({ 2.500* }, { { 1.50* } }, { { { 0.50* } } }, { { { { -0.50* } } } }\).*= { 4.00* }} == 1 } + { {func_flt_eqv\({ 2.500* }, { { 1.50* } }, { { { 0.50* } } }, { { { { -0.50* } } } }\).*= { 4.00* }} == 1 } + + { {func_hfa_f2\({ 1.000*, { 2.000* } }\).*= { 1.000*, { 2.000* } }} == 1 } + { {func_hfa_f3\({ 3.000*, { 1.000*, { 2.000* } } }\).*= { 3.000*, { 1.000*, { 2.000* } } }} == 1 } + { {func_hfa_f4\({ 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }\).*= { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }} == 1 } + { {func_hfa_f5\({ 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }\).*= { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }} == 1 } + { {func_hfa_f6\({ 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }\).*= { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }} == 1 } + { {func_hfa_f7\({ 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }\).*= { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }} == 1 } + { {func_hfa_f8\({ 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }\).*= { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }} == 1 } + { {func_hfa_f9\({ 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }\).*= { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }} == 1 } + { {func_hfa_f10\({ 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }\).*= { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }} == 1 } + { {func_hfa_f11\({ 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }\).*= { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }} == 1 } + { {func_hfa_f12\({ 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }\).*= { 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }} == 1 } + + { {func_hfa_d2\({ 1.000*, { 2.000* } }\).*= { 1.000*, { 2.000* } }} == 1 } + { {func_hfa_d3\({ 3.000*, { 1.000*, { 2.000* } } }\).*= { 3.000*, { 1.000*, { 2.000* } } }} == 1 } + { {func_hfa_d4\({ 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }\).*= { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }} == 1 } + { {func_hfa_d5\({ 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }\).*= { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }} == 1 } + { {func_hfa_d6\({ 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }\).*= { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }} == 1 } + { {func_hfa_d7\({ 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }\).*= { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }} == 1 } + { {func_hfa_d8\({ 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }\).*= { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }} == 1 } + { {func_hfa_d9\({ 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }\).*= { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }} == 1 } + { {func_hfa_d10\({ 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }\).*= { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }} == 1 } + { {func_hfa_d11\({ 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }\).*= { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }} == 1 } + { {func_hfa_d12\({ 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }\).*= { 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }} == 1 } +} + +ltraceMatch [ltraceLibTest { + typedef hfa2_t = struct(float, float); + typedef hda2_t = struct(double, double); + + void hfa2_n(hfa2_t, hfa2_t, hfa2_t, hfa2_t); + void hda2_n(hda2_t, hda2_t, hda2_t, hda2_t); + void hfa2_0(float, hfa2_t, hfa2_t, hfa2_t, hfa2_t); + void hda2_0(double, hda2_t, hda2_t, hda2_t, hda2_t); + void hfa2_1(hfa2_t, float, hfa2_t, hfa2_t, hfa2_t); + void hda2_1(hda2_t, double, hda2_t, hda2_t, hda2_t); + void hfa2_2(hfa2_t, hfa2_t, float, hfa2_t, hfa2_t); + void hda2_2(hda2_t, hda2_t, double, hda2_t, hda2_t); + void hfa2_3(hfa2_t, hfa2_t, hfa2_t, float, hfa2_t); + void hda2_3(hda2_t, hda2_t, hda2_t, double, hda2_t); + void hfa2_4(hfa2_t, hfa2_t, hfa2_t, hfa2_t, float); + void hda2_4(hda2_t, hda2_t, hda2_t, hda2_t, double); + + typedef hfa3_t = struct(float, float, float); + typedef hda3_t = struct(double, double, double); + + void hfa3_n(hfa3_t, hfa3_t, hfa3_t, hfa3_t); + void hda3_n(hda3_t, hda3_t, hda3_t, hda3_t); + void hfa3_0(float, hfa3_t, hfa3_t, hfa3_t, hfa3_t); + void hda3_0(double, hda3_t, hda3_t, hda3_t, hda3_t); + void hfa3_1(hfa3_t, float, hfa3_t, hfa3_t, hfa3_t); + void hda3_1(hda3_t, double, hda3_t, hda3_t, hda3_t); + void hfa3_2(hfa3_t, hfa3_t, float, hfa3_t, hfa3_t); + void hda3_2(hda3_t, hda3_t, double, hda3_t, hda3_t); + void hfa3_3(hfa3_t, hfa3_t, hfa3_t, float, hfa3_t); + void hda3_3(hda3_t, hda3_t, hda3_t, double, hda3_t); + void hfa3_4(hfa3_t, hfa3_t, hfa3_t, hfa3_t, float); + void hda3_4(hda3_t, hda3_t, hda3_t, hda3_t, double); + + typedef hfa4_t = struct(float, float, float, float); + typedef hda4_t = struct(double, double, double, double); + + void hfa4_n(hfa4_t, hfa4_t, hfa4_t, hfa4_t); + void hda4_n(hda4_t, hda4_t, hda4_t, hda4_t); + void hfa4_0(float, hfa4_t, hfa4_t, hfa4_t, hfa4_t); + void hda4_0(double, hda4_t, hda4_t, hda4_t, hda4_t); + void hfa4_1(hfa4_t, float, hfa4_t, hfa4_t, hfa4_t); + void hda4_1(hda4_t, double, hda4_t, hda4_t, hda4_t); + void hfa4_2(hfa4_t, hfa4_t, float, hfa4_t, hfa4_t); + void hda4_2(hda4_t, hda4_t, double, hda4_t, hda4_t); + void hfa4_3(hfa4_t, hfa4_t, hfa4_t, float, hfa4_t); + void hda4_3(hda4_t, hda4_t, hda4_t, double, hda4_t); + void hfa4_4(hfa4_t, hfa4_t, hfa4_t, hfa4_t, float); + void hda4_4(hda4_t, hda4_t, hda4_t, hda4_t, double); +} { + typedef struct { float a; float b; } hfa2_t; + typedef struct { double a; double b; } hda2_t; + + void hfa2_n(hfa2_t i, hfa2_t j, hfa2_t k, hfa2_t l); + void hda2_n(hda2_t i, hda2_t j, hda2_t k, hda2_t l); + void hfa2_0(float x, hfa2_t i, hfa2_t j, hfa2_t k, hfa2_t l); + void hda2_0(double x, hda2_t i, hda2_t j, hda2_t k, hda2_t l); + void hfa2_1(hfa2_t i, float x, hfa2_t j, hfa2_t k, hfa2_t l); + void hda2_1(hda2_t i, double x, hda2_t j, hda2_t k, hda2_t l); + void hfa2_2(hfa2_t i, hfa2_t j, float x, hfa2_t k, hfa2_t l); + void hda2_2(hda2_t i, hda2_t j, double x, hda2_t k, hda2_t l); + void hfa2_3(hfa2_t i, hfa2_t j, hfa2_t k, float x, hfa2_t l); + void hda2_3(hda2_t i, hda2_t j, hda2_t k, double x, hda2_t l); + void hfa2_4(hfa2_t i, hfa2_t j, hfa2_t k, hfa2_t l, float x); + void hda2_4(hda2_t i, hda2_t j, hda2_t k, hda2_t l, double x); + + typedef struct { float a; float b; float c; } hfa3_t; + typedef struct { double a; double b; double c; } hda3_t; + + void hfa3_n(hfa3_t i, hfa3_t j, hfa3_t k, hfa3_t l); + void hda3_n(hda3_t i, hda3_t j, hda3_t k, hda3_t l); + void hfa3_0(float x, hfa3_t i, hfa3_t j, hfa3_t k, hfa3_t l); + void hda3_0(double x, hda3_t i, hda3_t j, hda3_t k, hda3_t l); + void hfa3_1(hfa3_t i, float x, hfa3_t j, hfa3_t k, hfa3_t l); + void hda3_1(hda3_t i, double x, hda3_t j, hda3_t k, hda3_t l); + void hfa3_2(hfa3_t i, hfa3_t j, float x, hfa3_t k, hfa3_t l); + void hda3_2(hda3_t i, hda3_t j, double x, hda3_t k, hda3_t l); + void hfa3_3(hfa3_t i, hfa3_t j, hfa3_t k, float x, hfa3_t l); + void hda3_3(hda3_t i, hda3_t j, hda3_t k, double x, hda3_t l); + void hfa3_4(hfa3_t i, hfa3_t j, hfa3_t k, hfa3_t l, float x); + void hda3_4(hda3_t i, hda3_t j, hda3_t k, hda3_t l, double x); + + typedef struct { float a; float b; float c; float d; } hfa4_t; + typedef struct { double a; double b; double c; double d; } hda4_t; + + void hfa4_n(hfa4_t i, hfa4_t j, hfa4_t k, hfa4_t l); + void hda4_n(hda4_t i, hda4_t j, hda4_t k, hda4_t l); + void hfa4_0(float x, hfa4_t i, hfa4_t j, hfa4_t k, hfa4_t l); + void hda4_0(double x, hda4_t i, hda4_t j, hda4_t k, hda4_t l); + void hfa4_1(hfa4_t i, float x, hfa4_t j, hfa4_t k, hfa4_t l); + void hda4_1(hda4_t i, double x, hda4_t j, hda4_t k, hda4_t l); + void hfa4_2(hfa4_t i, hfa4_t j, float x, hfa4_t k, hfa4_t l); + void hda4_2(hda4_t i, hda4_t j, double x, hda4_t k, hda4_t l); + void hfa4_3(hfa4_t i, hfa4_t j, hfa4_t k, float x, hfa4_t l); + void hda4_3(hda4_t i, hda4_t j, hda4_t k, double x, hda4_t l); + void hfa4_4(hfa4_t i, hfa4_t j, hfa4_t k, hfa4_t l, float x); + void hda4_4(hda4_t i, hda4_t j, hda4_t k, hda4_t l, double x); +} { + void hfa2_n(hfa2_t i, hfa2_t j, hfa2_t k, hfa2_t l) {} + void hda2_n(hda2_t i, hda2_t j, hda2_t k, hda2_t l) {} + void hfa2_0(float x, hfa2_t i, hfa2_t j, hfa2_t k, hfa2_t l) {} + void hda2_0(double x, hda2_t i, hda2_t j, hda2_t k, hda2_t l) {} + void hfa2_1(hfa2_t i, float x, hfa2_t j, hfa2_t k, hfa2_t l) {} + void hda2_1(hda2_t i, double x, hda2_t j, hda2_t k, hda2_t l) {} + void hfa2_2(hfa2_t i, hfa2_t j, float x, hfa2_t k, hfa2_t l) {} + void hda2_2(hda2_t i, hda2_t j, double x, hda2_t k, hda2_t l) {} + void hfa2_3(hfa2_t i, hfa2_t j, hfa2_t k, float x, hfa2_t l) {} + void hda2_3(hda2_t i, hda2_t j, hda2_t k, double x, hda2_t l) {} + void hfa2_4(hfa2_t i, hfa2_t j, hfa2_t k, hfa2_t l, float x) {} + void hda2_4(hda2_t i, hda2_t j, hda2_t k, hda2_t l, double x) {} + + void hfa3_n(hfa3_t i, hfa3_t j, hfa3_t k, hfa3_t l) {} + void hda3_n(hda3_t i, hda3_t j, hda3_t k, hda3_t l) {} + void hfa3_0(float x, hfa3_t i, hfa3_t j, hfa3_t k, hfa3_t l) {} + void hda3_0(double x, hda3_t i, hda3_t j, hda3_t k, hda3_t l) {} + void hfa3_1(hfa3_t i, float x, hfa3_t j, hfa3_t k, hfa3_t l) {} + void hda3_1(hda3_t i, double x, hda3_t j, hda3_t k, hda3_t l) {} + void hfa3_2(hfa3_t i, hfa3_t j, float x, hfa3_t k, hfa3_t l) {} + void hda3_2(hda3_t i, hda3_t j, double x, hda3_t k, hda3_t l) {} + void hfa3_3(hfa3_t i, hfa3_t j, hfa3_t k, float x, hfa3_t l) {} + void hda3_3(hda3_t i, hda3_t j, hda3_t k, double x, hda3_t l) {} + void hfa3_4(hfa3_t i, hfa3_t j, hfa3_t k, hfa3_t l, float x) {} + void hda3_4(hda3_t i, hda3_t j, hda3_t k, hda3_t l, double x) {} + + void hfa4_n(hfa4_t i, hfa4_t j, hfa4_t k, hfa4_t l) {} + void hda4_n(hda4_t i, hda4_t j, hda4_t k, hda4_t l) {} + void hfa4_0(float x, hfa4_t i, hfa4_t j, hfa4_t k, hfa4_t l) {} + void hda4_0(double x, hda4_t i, hda4_t j, hda4_t k, hda4_t l) {} + void hfa4_1(hfa4_t i, float x, hfa4_t j, hfa4_t k, hfa4_t l) {} + void hda4_1(hda4_t i, double x, hda4_t j, hda4_t k, hda4_t l) {} + void hfa4_2(hfa4_t i, hfa4_t j, float x, hfa4_t k, hfa4_t l) {} + void hda4_2(hda4_t i, hda4_t j, double x, hda4_t k, hda4_t l) {} + void hfa4_3(hfa4_t i, hfa4_t j, hfa4_t k, float x, hfa4_t l) {} + void hda4_3(hda4_t i, hda4_t j, hda4_t k, double x, hda4_t l) {} + void hfa4_4(hfa4_t i, hfa4_t j, hfa4_t k, hfa4_t l, float x) {} + void hda4_4(hda4_t i, hda4_t j, hda4_t k, hda4_t l, double x) {} +} { + { + hfa2_t hfa1 = { 1, 2 }; + hfa2_t hfa2 = { 5, 6 }; + hfa2_t hfa3 = { 9, 10 }; + hfa2_t hfa4 = { 13, 14 }; + + hda2_t hda1 = { 1, 2 }; + hda2_t hda2 = { 5, 6 }; + hda2_t hda3 = { 9, 10 }; + hda2_t hda4 = { 13, 14 }; + + hfa2_n(hfa1, hfa2, hfa3, hfa4); + hda2_n(hda1, hda2, hda3, hda4); + hfa2_0(-1, hfa1, hfa2, hfa3, hfa4); + hda2_0(-1, hda1, hda2, hda3, hda4); + hfa2_1(hfa1, -1, hfa2, hfa3, hfa4); + hda2_1(hda1, -1, hda2, hda3, hda4); + hfa2_2(hfa1, hfa2, -1, hfa3, hfa4); + hda2_2(hda1, hda2, -1, hda3, hda4); + hfa2_3(hfa1, hfa2, hfa3, -1, hfa4); + hda2_3(hda1, hda2, hda3, -1, hda4); + hfa2_4(hfa1, hfa2, hfa3, hfa4, -1); + hda2_4(hda1, hda2, hda3, hda4, -1); + } + + { + hfa3_t hfa1 = { 1, 2, 3 }; + hfa3_t hfa2 = { 5, 6, 7 }; + hfa3_t hfa3 = { 9, 10, 11 }; + hfa3_t hfa4 = { 13, 14, 15 }; + + hda3_t hda1 = { 1, 2, 3 }; + hda3_t hda2 = { 5, 6, 7 }; + hda3_t hda3 = { 9, 10, 11 }; + hda3_t hda4 = { 13, 14, 15 }; + + hfa3_n(hfa1, hfa2, hfa3, hfa4); + hda3_n(hda1, hda2, hda3, hda4); + hfa3_0(-1, hfa1, hfa2, hfa3, hfa4); + hda3_0(-1, hda1, hda2, hda3, hda4); + hfa3_1(hfa1, -1, hfa2, hfa3, hfa4); + hda3_1(hda1, -1, hda2, hda3, hda4); + hfa3_2(hfa1, hfa2, -1, hfa3, hfa4); + hda3_2(hda1, hda2, -1, hda3, hda4); + hfa3_3(hfa1, hfa2, hfa3, -1, hfa4); + hda3_3(hda1, hda2, hda3, -1, hda4); + hfa3_4(hfa1, hfa2, hfa3, hfa4, -1); + hda3_4(hda1, hda2, hda3, hda4, -1); + } + + { + hfa4_t hfa1 = { 1, 2, 3, 4 }; + hfa4_t hfa2 = { 5, 6, 7, 8 }; + hfa4_t hfa3 = { 9, 10, 11, 12 }; + hfa4_t hfa4 = { 13, 14, 15, 16 }; + + hda4_t hda1 = { 1, 2, 3, 4 }; + hda4_t hda2 = { 5, 6, 7, 8 }; + hda4_t hda3 = { 9, 10, 11, 12 }; + hda4_t hda4 = { 13, 14, 15, 16 }; + + hfa4_n(hfa1, hfa2, hfa3, hfa4); + hda4_n(hda1, hda2, hda3, hda4); + hfa4_0(-1, hfa1, hfa2, hfa3, hfa4); + hda4_0(-1, hda1, hda2, hda3, hda4); + hfa4_1(hfa1, -1, hfa2, hfa3, hfa4); + hda4_1(hda1, -1, hda2, hda3, hda4); + hfa4_2(hfa1, hfa2, -1, hfa3, hfa4); + hda4_2(hda1, hda2, -1, hda3, hda4); + hfa4_3(hfa1, hfa2, hfa3, -1, hfa4); + hda4_3(hda1, hda2, hda3, -1, hda4); + hfa4_4(hfa1, hfa2, hfa3, hfa4, -1); + hda4_4(hda1, hda2, hda3, hda4, -1); + } + +}] { + { {hfa2_n\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hda2_n\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hfa2_0\(-1.00*, { 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hda2_0\(-1.00*, { 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hfa2_1\({ 1.00*, 2.00* }, -1.00*, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hda2_1\({ 1.00*, 2.00* }, -1.00*, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hfa2_2\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, -1.00*, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hda2_2\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, -1.00*, { 9.00*, 10.00* }, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hfa2_3\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, -1.00*, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hda2_3\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, -1.00*, { 13.00*, 14.00* }\).*= <void>} == 1} + { {hfa2_4\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }, -1.00*\).*= <void>} == 1} + { {hda2_4\({ 1.00*, 2.00* }, { 5.00*, 6.00* }, { 9.00*, 10.00* }, { 13.00*, 14.00* }, -1.00*\).*= <void>} == 1} + + { {hfa3_n\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hda3_n\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hfa3_0\(-1.00*, { 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hda3_0\(-1.00*, { 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hfa3_1\({ 1.00*, 2.00*, 3.00* }, -1.00*, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hda3_1\({ 1.00*, 2.00*, 3.00* }, -1.00*, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hfa3_2\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, -1.00*, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hda3_2\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, -1.00*, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hfa3_3\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, -1.00*, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hda3_3\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, -1.00*, { 13.00*, 14.00*, 15.00* }\).*= <void>} == 1} + { {hfa3_4\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }, -1.00*\).*= <void>} == 1} + { {hda3_4\({ 1.00*, 2.00*, 3.00* }, { 5.00*, 6.00*, 7.00* }, { 9.00*, 10.00*, 11.00* }, { 13.00*, 14.00*, 15.00* }, -1.00*\).*= <void>} == 1} + + { {hfa4_n\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hda4_n\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hfa4_0\(-1.00*, { 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hda4_0\(-1.00*, { 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hfa4_1\({ 1.00*, 2.00*, 3.00*, 4.00* }, -1.00*, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hda4_1\({ 1.00*, 2.00*, 3.00*, 4.00* }, -1.00*, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hfa4_2\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, -1.00*, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hda4_2\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, -1.00*, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hfa4_3\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, -1.00*, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hda4_3\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, -1.00*, { 13.00*, 14.00*, 15.00*, 16.00* }\).*= <void>} == 1} + { {hfa4_4\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }, -1.00*\).*= <void>} == 1} + { {hda4_4\({ 1.00*, 2.00*, 3.00*, 4.00* }, { 5.00*, 6.00*, 7.00*, 8.00* }, { 9.00*, 10.00*, 11.00*, 12.00* }, { 13.00*, 14.00*, 15.00*, 16.00* }, -1.00*\).*= <void>} == 1} +} + +ltraceDone diff --git a/testsuite/ltrace.main/parameters-lib.c b/testsuite/ltrace.main/parameters-lib.c index bf8cdfe..893db02 100644 --- a/testsuite/ltrace.main/parameters-lib.c +++ b/testsuite/ltrace.main/parameters-lib.c @@ -257,30 +257,6 @@ func_charp_string(char *p) { } -struct dbl_eqv1 { double d; }; -struct dbl_eqv2 { struct dbl_eqv1 d; }; -struct dbl_eqv3 { struct dbl_eqv2 d; }; -struct dbl_eqv4 { struct dbl_eqv3 d; }; - -struct flt_eqv1 { float d; }; -struct flt_eqv2 { struct flt_eqv1 d; }; -struct flt_eqv3 { struct flt_eqv2 d; }; -struct flt_eqv4 { struct flt_eqv3 d; }; - -struct dbl_eqv1 -func_dbl_eqv(struct dbl_eqv1 a, struct dbl_eqv2 b, - struct dbl_eqv3 c, struct dbl_eqv4 d) -{ - return (struct dbl_eqv1){ a.d + b.d.d + c.d.d.d + d.d.d.d.d }; -} - -struct flt_eqv1 -func_flt_eqv(struct flt_eqv1 a, struct flt_eqv2 b, - struct flt_eqv3 c, struct flt_eqv4 d) -{ - return (struct flt_eqv1){ a.d + b.d.d + c.d.d.d + d.d.d.d.d }; -} - struct struct_empty {}; struct struct_size1 { char a; }; struct struct_size2 { short a; }; @@ -317,161 +293,6 @@ func_struct_size8(struct struct_size8 e) return e; } -struct struct_hfa_f2 { float a; struct flt_eqv1 b; }; -struct struct_hfa_f2 -func_hfa_f2(struct struct_hfa_f2 e) -{ - return e; -} - -struct struct_hfa_f3 { float a; struct struct_hfa_f2 b; }; -struct struct_hfa_f3 -func_hfa_f3(struct struct_hfa_f3 e) -{ - return e; -} - -struct struct_hfa_f4 { float a; struct struct_hfa_f3 b; }; -struct struct_hfa_f4 -func_hfa_f4(struct struct_hfa_f4 e) -{ - return e; -} - -struct struct_hfa_f5 { float a; struct struct_hfa_f4 b; }; -struct struct_hfa_f5 -func_hfa_f5(struct struct_hfa_f5 e) -{ - return e; -} - -struct struct_hfa_f6 { float a; struct struct_hfa_f5 b; }; -struct struct_hfa_f6 -func_hfa_f6(struct struct_hfa_f6 e) -{ - return e; -} - -struct struct_hfa_f7 { float a; struct struct_hfa_f6 b; }; -struct struct_hfa_f7 -func_hfa_f7(struct struct_hfa_f7 e) -{ - return e; -} - -struct struct_hfa_f8 { float a; struct struct_hfa_f7 b; }; -struct struct_hfa_f8 -func_hfa_f8(struct struct_hfa_f8 e) -{ - return e; -} - -struct struct_hfa_f9 { float a; struct struct_hfa_f8 b; }; -struct struct_hfa_f9 -func_hfa_f9(struct struct_hfa_f9 e) -{ - return e; -} - -struct struct_hfa_f10 { float a; struct struct_hfa_f9 b; }; -struct struct_hfa_f10 -func_hfa_f10(struct struct_hfa_f10 e) -{ - return e; -} - -struct struct_hfa_f11 { float a; struct struct_hfa_f10 b; }; -struct struct_hfa_f11 -func_hfa_f11(struct struct_hfa_f11 e) -{ - return e; -} - -struct struct_hfa_f12 { float a; struct struct_hfa_f11 b; }; -struct struct_hfa_f12 -func_hfa_f12(struct struct_hfa_f12 e) -{ - return e; -} - - -struct struct_hfa_d2 { double a; struct dbl_eqv1 b; }; -struct struct_hfa_d2 -func_hfa_d2(struct struct_hfa_d2 e) -{ - return e; -} - -struct struct_hfa_d3 { double a; struct struct_hfa_d2 b; }; -struct struct_hfa_d3 -func_hfa_d3(struct struct_hfa_d3 e) -{ - return e; -} - -struct struct_hfa_d4 { double a; struct struct_hfa_d3 b; }; -struct struct_hfa_d4 -func_hfa_d4(struct struct_hfa_d4 e) -{ - return e; -} - -struct struct_hfa_d5 { double a; struct struct_hfa_d4 b; }; -struct struct_hfa_d5 -func_hfa_d5(struct struct_hfa_d5 e) -{ - return e; -} - -struct struct_hfa_d6 { double a; struct struct_hfa_d5 b; }; -struct struct_hfa_d6 -func_hfa_d6(struct struct_hfa_d6 e) -{ - return e; -} - -struct struct_hfa_d7 { double a; struct struct_hfa_d6 b; }; -struct struct_hfa_d7 -func_hfa_d7(struct struct_hfa_d7 e) -{ - return e; -} - -struct struct_hfa_d8 { double a; struct struct_hfa_d7 b; }; -struct struct_hfa_d8 -func_hfa_d8(struct struct_hfa_d8 e) -{ - return e; -} - -struct struct_hfa_d9 { double a; struct struct_hfa_d8 b; }; -struct struct_hfa_d9 -func_hfa_d9(struct struct_hfa_d9 e) -{ - return e; -} - -struct struct_hfa_d10 { double a; struct struct_hfa_d9 b; }; -struct struct_hfa_d10 -func_hfa_d10(struct struct_hfa_d10 e) -{ - return e; -} - -struct struct_hfa_d11 { double a; struct struct_hfa_d10 b; }; -struct struct_hfa_d11 -func_hfa_d11(struct struct_hfa_d11 e) -{ - return e; -} - -struct struct_hfa_d12 { double a; struct struct_hfa_d11 b; }; -struct struct_hfa_d12 -func_hfa_d12(struct struct_hfa_d12 e) -{ - return e; -} - void func_printf(char *format, ...) { diff --git a/testsuite/ltrace.main/parameters.c b/testsuite/ltrace.main/parameters.c index ff24a38..a3d8bb5 100644 --- a/testsuite/ltrace.main/parameters.c +++ b/testsuite/ltrace.main/parameters.c @@ -232,30 +232,6 @@ main () void func_charp_string(char *p); func_charp_string("null-terminated string"); - struct dbl_eqv1 { double d; }; - struct dbl_eqv2 { struct dbl_eqv1 d; }; - struct dbl_eqv3 { struct dbl_eqv2 d; }; - struct dbl_eqv4 { struct dbl_eqv3 d; }; - - struct flt_eqv1 { float d; }; - struct flt_eqv2 { struct flt_eqv1 d; }; - struct flt_eqv3 { struct flt_eqv2 d; }; - struct flt_eqv4 { struct flt_eqv3 d; }; - - struct dbl_eqv1 func_dbl_eqv(struct dbl_eqv1 a, struct dbl_eqv2 b, - struct dbl_eqv3 c, struct dbl_eqv4 d); - func_dbl_eqv((struct dbl_eqv1){ 2.5 }, - (struct dbl_eqv2){ { 1.5 } }, - (struct dbl_eqv3){ { { 0.5 } } }, - (struct dbl_eqv4){ { { { -0.5 } } } }); - - struct flt_eqv1 func_flt_eqv(struct flt_eqv1 a, struct flt_eqv2 b, - struct flt_eqv3 c, struct flt_eqv4 d); - func_flt_eqv((struct flt_eqv1){ 2.5 }, - (struct flt_eqv2){ { 1.5 } }, - (struct flt_eqv3){ { { 0.5 } } }, - (struct flt_eqv4){ { { { -0.5 } } } }); - struct struct_empty {}; struct struct_empty func_struct_empty(struct struct_empty e); func_struct_empty((struct struct_empty) {}); @@ -276,96 +252,5 @@ main () struct struct_size8 func_struct_size8(struct struct_size8 e); func_struct_size8((struct struct_size8){ 5, 6 }); - /* Test Itanium Homogeneous Floating-point Aggregates. */ - - struct struct_hfa_f2 { float a; struct flt_eqv1 b; }; - struct struct_hfa_f2 func_hfa_f2(struct struct_hfa_f2 e); - func_hfa_f2((struct struct_hfa_f2){ 1, { 2 } }); - - struct struct_hfa_f3 { float a; struct struct_hfa_f2 b; }; - struct struct_hfa_f3 func_hfa_f3(struct struct_hfa_f3 e); - func_hfa_f3((struct struct_hfa_f3){ 3, { 1, { 2 } } }); - - struct struct_hfa_f4 { float a; struct struct_hfa_f3 b; }; - struct struct_hfa_f4 func_hfa_f4(struct struct_hfa_f4 e); - func_hfa_f4((struct struct_hfa_f4){ 4, { 3, { 1, { 2 } } } }); - - struct struct_hfa_f5 { float a; struct struct_hfa_f4 b; }; - struct struct_hfa_f5 func_hfa_f5(struct struct_hfa_f5 e); - func_hfa_f5((struct struct_hfa_f5){ 5, { 4, { 3, { 1, { 2 } } } } }); - - struct struct_hfa_f6 { float a; struct struct_hfa_f5 b; }; - struct struct_hfa_f6 func_hfa_f6(struct struct_hfa_f6 e); - func_hfa_f6((struct struct_hfa_f6){ 6, { 5, { 4, { 3, { 1, { 2 } } } } } }); - - struct struct_hfa_f7 { float a; struct struct_hfa_f6 b; }; - struct struct_hfa_f7 func_hfa_f7(struct struct_hfa_f7 e); - func_hfa_f7((struct struct_hfa_f7){ 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } }); - - struct struct_hfa_f8 { float a; struct struct_hfa_f7 b; }; - struct struct_hfa_f8 func_hfa_f8(struct struct_hfa_f8 e); - func_hfa_f8((struct struct_hfa_f8){ 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } }); - - struct struct_hfa_f9 { float a; struct struct_hfa_f8 b; }; - struct struct_hfa_f9 func_hfa_f9(struct struct_hfa_f9 e); - func_hfa_f9((struct struct_hfa_f9){ 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } }); - - struct struct_hfa_f10 { float a; struct struct_hfa_f9 b; }; - struct struct_hfa_f10 func_hfa_f10(struct struct_hfa_f10 e); - func_hfa_f10((struct struct_hfa_f10){ 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } }); - - struct struct_hfa_f11 { float a; struct struct_hfa_f10 b; }; - struct struct_hfa_f11 func_hfa_f11(struct struct_hfa_f11 e); - func_hfa_f11((struct struct_hfa_f11){ 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } }); - - struct struct_hfa_f12 { float a; struct struct_hfa_f11 b; }; - struct struct_hfa_f12 func_hfa_f12(struct struct_hfa_f12 e); - func_hfa_f12((struct struct_hfa_f12){ 12, { 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } } }); - - - struct struct_hfa_d2 { double a; struct dbl_eqv1 b; }; - struct struct_hfa_d2 func_hfa_d2(struct struct_hfa_d2 e); - func_hfa_d2((struct struct_hfa_d2){ 1, { 2 } }); - - struct struct_hfa_d3 { double a; struct struct_hfa_d2 b; }; - struct struct_hfa_d3 func_hfa_d3(struct struct_hfa_d3 e); - func_hfa_d3((struct struct_hfa_d3){ 3, { 1, { 2 } } }); - - struct struct_hfa_d4 { double a; struct struct_hfa_d3 b; }; - struct struct_hfa_d4 func_hfa_d4(struct struct_hfa_d4 e); - func_hfa_d4((struct struct_hfa_d4){ 4, { 3, { 1, { 2 } } } }); - - struct struct_hfa_d5 { double a; struct struct_hfa_d4 b; }; - struct struct_hfa_d5 func_hfa_d5(struct struct_hfa_d5 e); - func_hfa_d5((struct struct_hfa_d5){ 5, { 4, { 3, { 1, { 2 } } } } }); - - struct struct_hfa_d6 { double a; struct struct_hfa_d5 b; }; - struct struct_hfa_d6 func_hfa_d6(struct struct_hfa_d6 e); - func_hfa_d6((struct struct_hfa_d6){ 6, { 5, { 4, { 3, { 1, { 2 } } } } } }); - - struct struct_hfa_d7 { double a; struct struct_hfa_d6 b; }; - struct struct_hfa_d7 func_hfa_d7(struct struct_hfa_d7 e); - func_hfa_d7((struct struct_hfa_d7){ 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } }); - - struct struct_hfa_d8 { double a; struct struct_hfa_d7 b; }; - struct struct_hfa_d8 func_hfa_d8(struct struct_hfa_d8 e); - func_hfa_d8((struct struct_hfa_d8){ 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } }); - - struct struct_hfa_d9 { double a; struct struct_hfa_d8 b; }; - struct struct_hfa_d9 func_hfa_d9(struct struct_hfa_d9 e); - func_hfa_d9((struct struct_hfa_d9){ 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } }); - - struct struct_hfa_d10 { double a; struct struct_hfa_d9 b; }; - struct struct_hfa_d10 func_hfa_d10(struct struct_hfa_d10 e); - func_hfa_d10((struct struct_hfa_d10){ 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } }); - - struct struct_hfa_d11 { double a; struct struct_hfa_d10 b; }; - struct struct_hfa_d11 func_hfa_d11(struct struct_hfa_d11 e); - func_hfa_d11((struct struct_hfa_d11){ 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } }); - - struct struct_hfa_d12 { double a; struct struct_hfa_d11 b; }; - struct struct_hfa_d12 func_hfa_d12(struct struct_hfa_d12 e); - func_hfa_d12((struct struct_hfa_d12){ 12, { 11, { 10, { 9, { 8, { 7, { 6, { 5, { 4, { 3, { 1, { 2 } } } } } } } } } } } }); - return 0; } diff --git a/testsuite/ltrace.main/parameters.conf b/testsuite/ltrace.main/parameters.conf index 066c012..890e071 100644 --- a/testsuite/ltrace.main/parameters.conf +++ b/testsuite/ltrace.main/parameters.conf @@ -32,34 +32,8 @@ void func_hide_struct(struct(hide(int), int, hide(int), hide(int), int, hide(int array(enum[long](A,B), 4) *func_short_enums(array(enum[short](A,B), 4)*); enum[long](A=-1) func_negative_enum(enum[short](A=-1), enum[ushort](A=-1), enum[int](A=-1), enum[uint](A=-1), enum[long](A=-1), enum[ulong](A=-1)); void func_charp_string(string(char *)); -struct(double) func_dbl_eqv(struct(double), struct(struct(double)), struct(struct(struct(double))), struct(struct(struct(struct(double))))); -struct(float) func_flt_eqv(struct(float), struct(struct(float)), struct(struct(struct(float))), struct(struct(struct(struct(float))))); struct() func_struct_empty(struct()); struct(char) func_struct_size1(struct(char)); struct(short) func_struct_size2(struct(short)); struct(int) func_struct_size4(struct(int)); struct(int,int) func_struct_size8(struct(int,int)); - -struct(float,struct(float)) func_hfa_f2(struct(float,struct(float))); -struct(float,struct(float,struct(float))) func_hfa_f3(struct(float,struct(float,struct(float)))); -struct(float,struct(float,struct(float,struct(float)))) func_hfa_f4(struct(float,struct(float,struct(float,struct(float))))); -struct(float,struct(float,struct(float,struct(float,struct(float))))) func_hfa_f5(struct(float,struct(float,struct(float,struct(float,struct(float)))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))) func_hfa_f6(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))) func_hfa_f7(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))) func_hfa_f8(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))) func_hfa_f9(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))) func_hfa_f10(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))))) func_hfa_f11(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))))); -struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float)))))))))))) func_hfa_f12(struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float,struct(float))))))))))))); - -struct(double,struct(double)) func_hfa_d2(struct(double,struct(double))); -struct(double,struct(double,struct(double))) func_hfa_d3(struct(double,struct(double,struct(double)))); -struct(double,struct(double,struct(double,struct(double)))) func_hfa_d4(struct(double,struct(double,struct(double,struct(double))))); -struct(double,struct(double,struct(double,struct(double,struct(double))))) func_hfa_d5(struct(double,struct(double,struct(double,struct(double,struct(double)))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))) func_hfa_d6(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))) func_hfa_d7(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))) func_hfa_d8(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))) func_hfa_d9(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))) func_hfa_d10(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))))) func_hfa_d11(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))))); -struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double)))))))))))) func_hfa_d12(struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double,struct(double))))))))))))); diff --git a/testsuite/ltrace.main/parameters.exp b/testsuite/ltrace.main/parameters.exp index e54086f..325e4d1 100644 --- a/testsuite/ltrace.main/parameters.exp +++ b/testsuite/ltrace.main/parameters.exp @@ -22,7 +22,7 @@ if { [ltrace_compile_shlib $libsrc $lib_sl debug ] != "" # set options for ltrace. ltrace_options "-l" "libparameters.so" "-F" "$srcdir/$subdir/parameters.conf" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. @@ -35,9 +35,6 @@ if [regexp {ELF from incompatible architecture} $exec_output] { return } -set xfail_spec {"arm*-*" } -set xfail_spec_arm {"arm*-*"} - # Verify the output set pattern "func_intptr(17)" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 @@ -63,7 +60,6 @@ set pattern "func_ushort(33, 34)" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 set pattern "func_float(3.40*, -3.40*).*= 3.40*" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -eval "setup_xfail $xfail_spec" set pattern "func_double(3.40*, -3.40*).*= -3.40*" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 set pattern "func_typedef(BLUE)" @@ -86,7 +82,6 @@ set pattern "func_work(\\\"x\\\")" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 set pattern "func_struct_2(17, { \\\"ABCDE\\\\\\\\0\\\", 0.250* }, 0.50*).*= { 0.250*, 'B', 'C' }" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -eval "setup_xfail $xfail_spec_arm" set pattern "<... func_call resumed> \\\"x\\\", \\\"y\\\")" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 @@ -144,12 +139,6 @@ ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 set pattern "func_charp_string(\\\"null-terminated string\\\")" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "func_dbl_eqv({ 2.500* }, { { 1.50* } }, { { { 0.50* } } }, { { { { -0.50* } } } }).*= { 4.00* }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_flt_eqv({ 2.500* }, { { 1.50* } }, { { { 0.50* } } }, { { { { -0.50* } } } }).*= { 4.00* }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - set pattern "func_struct_empty({ *}).*= { *}" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 @@ -164,69 +153,3 @@ ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 set pattern "func_struct_size8({ 5, 6 }).*= { 5, 6 }" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f2({ 1.000*, { 2.000* } }).*= { 1.000*, { 2.000* } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f3({ 3.000*, { 1.000*, { 2.000* } } }).*= { 3.000*, { 1.000*, { 2.000* } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f4({ 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }).*= { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f5({ 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }).*= { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f6({ 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }).*= { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f7({ 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }).*= { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f8({ 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }).*= { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f9({ 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }).*= { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f10({ 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }).*= { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f11({ 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }).*= { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_f12({ 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }).*= { 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d2({ 1.000*, { 2.000* } }).*= { 1.000*, { 2.000* } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d3({ 3.000*, { 1.000*, { 2.000* } } }).*= { 3.000*, { 1.000*, { 2.000* } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d4({ 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }).*= { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d5({ 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }).*= { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d6({ 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }).*= { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d7({ 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }).*= { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d8({ 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }).*= { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d9({ 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }).*= { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d10({ 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }).*= { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d11({ 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }).*= { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "func_hfa_d12({ 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }).*= { 12.000*, { 11.000*, { 10.000*, { 9.000*, { 8.000*, { 7.000*, { 6.000*, { 5.000*, { 4.000*, { 3.000*, { 1.000*, { 2.000* } } } } } } } } } } } }" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 diff --git a/testsuite/ltrace.main/parameters2.exp b/testsuite/ltrace.main/parameters2.exp index 8443fb6..9850079 100644 --- a/testsuite/ltrace.main/parameters2.exp +++ b/testsuite/ltrace.main/parameters2.exp @@ -1,5 +1,5 @@ # This file is part of ltrace. -# Copyright (C) 2012 Petr Machata, Red Hat Inc. +# Copyright (C) 2012, 2013, 2014 Petr Machata, Red Hat Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -27,6 +27,11 @@ ltraceMatch1 [ltraceRun -L -F [ltraceSource conf { }] -- $trivial] "error" == 0 ltraceMatch1 [ltraceRun -L -F [ltraceSource conf { + typedef char_blah = char; + void blah(char_blah); +}] -- $trivial] "error" == 0 + +ltraceMatch1 [ltraceRun -L -F [ltraceSource conf { typedef aa = int; typedef aa = int; }] -- $trivial] "error" != 0 @@ -119,20 +124,9 @@ ltraceMatch1 [ltraceRun -F $conf -e ll -A 5 \ } }]]] {->ll\({ 9, { 8, { 7, { 6, { 5, \.\.\. } } } } }\) *= <void>} == 1 -proc ltraceParamTest {conf cdecl libcode maincode match {params ""}} { - set conffile [ltraceSource conf $conf] - set lib [ltraceCompile liblib.so [ltraceSource c [concat $cdecl $libcode]]] - set bin [ltraceCompile {} $lib \ - [ltraceSource c \ - [concat $cdecl "int main(void) {" $maincode "}"]]] - - set command [concat "ltraceRun -F $conffile " $params "-- $bin"] - return [ltraceMatch [eval $command] $match] -} - # Test using lens in typedef. -ltraceParamTest { +ltraceMatch1 [ltraceLibTest { typedef hexptr = hex(uint*); void fun(hexptr); } { @@ -142,13 +136,11 @@ ltraceParamTest { } { unsigned u = 0x123; fun(&u); -} { - {{fun\(0x123\) *= <void>} == 1} -} +}] {fun\(0x123\) *= <void>} == 1 # Test support for bitvec lens. -ltraceParamTest { +ltraceMatch [ltraceLibTest { void fun(bitvec(uint)); void fun2(bitvec(array(char, 32)*)); } { @@ -167,7 +159,7 @@ ltraceParamTest { bytes[1] = 0xff; bytes[31] = 0x80; fun2(bytes); -} { +}] { {{fun\(<>\) *= <void>} == 1} {{fun\(<0-1,5,8>\) *= <void>} == 1} {{fun\(~<0>\) *= <void>} == 1} @@ -177,7 +169,7 @@ ltraceParamTest { # Test support for hex(float), hex(double). -ltraceParamTest { +ltraceMatch [ltraceLibTest { hex(float) hex_float(hex(float)); hex(double) hex_double(hex(double)); } { @@ -189,15 +181,28 @@ ltraceParamTest { } { hex_float(1.5); hex_double(1.5); -} { +}] { {{hex_float\(0x1.8p\+0\) *= 0x1.4p\+1} == 1} {{hex_double\(0x1.8p\+0\) *= 0x1.4p\+1} == 1} } +# Test that "addr" is recognized. + +ltraceMatch1 [ltraceLibTest { + void fun(addr); +} { + #include <stdint.h> + void fun(uintptr_t u); +} { + void fun(uintptr_t u) {} +} { + fun(0x1234); +}] {fun\(0x1234\) *= <void>} == 1 + # Test that -x fun can find "fun" prototype even if "fun" is in a # library. -ltraceParamTest { +ltraceMatch1 [ltraceLibTest { void fun(); } { void libfun(void); @@ -207,9 +212,63 @@ ltraceParamTest { } { libfun(); } { - {{fun@.*\(\)} == 1} -} { -L -x fun -} +}] {fun@.*\(\)} == 1 + + +# Test that %p format specifier does not crash + +ltraceMatch1 [ltraceLibTest { + void print_ptr(format); +} { + void print_ptr(const char *format, ...); +} { + void print_ptr(const char *format, ...) { } +} { + void *addr = (void *)0x42; + print_ptr("%p\n", addr); +}] {print_ptr\("%p\\n", 0x42\) *= <void>} == 1 + + +# Test that zero(EXPR) does not leak memory (needs valgrind) + +ltraceMatch1 [ltraceLibTest { + typedef String = string(array(char, zero(256))); + String *get_string(); +} { + char *get_string(); +} { + char *get_string() { + return "FOO"; + } +} { + get_string(); +}] {get_string\(\) *= "FOO"} == 1 + +# Test that void* NULL's are displayed as "nil" as well. + +ltraceMatch1 [ltraceLibTest { + addr somefunc(); +} { + void *somefunc(void); +} { + void *somefunc(void) { + return 0; + } +} { + somefunc(); +}] {somefunc\(\) *= nil} == 1 + +# Test that spaces in function name make no difference. + +ltraceMatch1 [ltraceLibTest { + void somefunc (); +} { + void somefunc(void); +} { + void somefunc(void) {} +} { + somefunc(); +}] {somefunc\(\)} == 1 ltraceDone diff --git a/testsuite/ltrace.main/parameters3.exp b/testsuite/ltrace.main/parameters3.exp new file mode 100644 index 0000000..693c219 --- /dev/null +++ b/testsuite/ltrace.main/parameters3.exp @@ -0,0 +1,73 @@ +# This file is part of ltrace. +# Copyright (C) 2012, 2014 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +# This tests lookup of per-library configure files. + +set dir [ltraceDir] + +ltraceNamedSource "$dir/liba.so.conf" { + void fun(); +} + +set liba [ltraceCompile liba.so [ltraceSource c { + void fun(void) {} +}]] + +set bin [ltraceCompile {} $liba [ltraceSource c { + int main(void) { + fun(); + } +}]] + +ltraceMatch1 [ltraceRun -F $dir -L -x fun -- $bin] {fun@liba.so\(\)} == 1 +ltraceMatch1 [ltraceRun -F $dir -e fun -- $bin] {fun\(\)} == 1 + +# This tests lookup of prototypes from non-leader threads. + +set bin [ltraceCompile {} $liba -lpthread [ltraceSource c { + #include <pthread.h> + #include <assert.h> + + void fun(); + + void * + start(void *arg) + { + fun(); + return NULL; + } + + int + main(int argc, char *argv[]) + { + pthread_t thr; + int rc = pthread_create(&thr, NULL, &start, NULL); + assert(rc == 0); + + void *retval; + rc = pthread_join(thr, &retval); + assert(rc == 0); + + return 0; + } +}]] + +ltraceMatch1 [ltraceRun -F $dir -f -L -x fun -- $bin] {fun@liba.so\(\)} == 1 +ltraceMatch1 [ltraceRun -F $dir -f -e fun -- $bin] {fun\(\)} == 1 + +ltraceDone diff --git a/testsuite/ltrace.main/signals.exp b/testsuite/ltrace.main/signals.exp index d812af6..97be127 100644 --- a/testsuite/ltrace.main/signals.exp +++ b/testsuite/ltrace.main/signals.exp @@ -14,7 +14,7 @@ if { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/ # set options for ltrace. ltrace_options "-L" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.main/system_call_params.exp b/testsuite/ltrace.main/system_call_params.exp new file mode 100644 index 0000000..2ccf840 --- /dev/null +++ b/testsuite/ltrace.main/system_call_params.exp @@ -0,0 +1,71 @@ +# This file is part of ltrace. +# Copyright (C) 2013, 2014 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +set bin [ltraceCompile {} [ltraceSource c { + #define _GNU_SOURCE + #include <sys/types.h> + #include <sys/stat.h> + #include <fcntl.h> + #include <unistd.h> + #include <sys/syscall.h> /* For SYS_xxx definitions */ + + #ifndef SYS_open + # if defined(__aarch64__) + # /* Linux doesn't actually implement SYS_open on AArch64, but for merely + # * recording the syscall, it's fine. */ + # define SYS_open 1024 + # else + # error SYS_open not available. + # endif + #endif + + int main(void) { + syscall(SYS_open, "/some/path", O_RDONLY); + write(1, "something", 10); + mount("source", "target", "filesystemtype", 0, 0); + } +}]] + +set dir [ltraceDir] +set conf [ltraceNamedSource "$dir/syscalls.conf" { + int open(string, int); + long write(int, string[arg3], ulong); + int mount(string, string, string, ulong, addr); +}] + +# When given the file directly via -F, ltrace should not use it for +# formatting system calls. The reason is that libraries are generally +# allowed to have functions with the same names as system calls +# (there's no interference between those two). In particular, +# readdir@SYS has a different prototype from readdir@libc. If a -F +# somelib.conf is passed, and syscalls.conf is not available, or +# doesn't list readdir, that would be taken from somelib.conf with a +# wrong prototype. + +ltraceMatch1 [ltraceRun -L -S -F $conf -- $bin] {^open@SYS\("/some/path"} == 0 + +# On the other hand, if -F somedir/ is given, we want to accept +# syscalls.conf found there. + +ltraceMatch [ltraceRun -L -S -F $dir -- $bin] { + {{^open@SYS\("/some/path"} == 1} + {{^write@SYS\(1, "something", 10\)} == 1} + {{^mount@SYS\("source", "target", "filesystemtype"} == 1} +} + +ltraceDone diff --git a/testsuite/ltrace.main/system_calls.exp b/testsuite/ltrace.main/system_calls.exp index 259ae25..1b64cb0 100644 --- a/testsuite/ltrace.main/system_calls.exp +++ b/testsuite/ltrace.main/system_calls.exp @@ -1,67 +1,146 @@ -# This file was written by Yao Qi <qiyao@cn.ibm.com>. +# This file is part of ltrace. +# Copyright (C) 2014 Petr Machata, Red Hat Inc. +# Copyright (C) 2006 Yao Qi <qiyao@cn.ibm.com>, IBM Corporation +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA -set testfile "system_calls" -set srcfile ${testfile}.c -set binfile ${testfile} +# Objectives: Verify that Ltrace can trace all the system calls in +# execution. Note that this test is necessarily noisy. Dynamic +# linker adds a bunch of system calls of its own. +set empty [ltraceCompile {} [ltraceSource c { + int main (void) { return 0; } +}]] -verbose "compiling source file now....." -# Build the shared libraries this test case needs. -if { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/${binfile}" executable {debug} ] != "" } { - send_user "Testcase compile failed, so all tests in this file will automatically fail.\n" +set bin [ltraceCompile {} [ltraceSource c { + #include <stdio.h> + #include <unistd.h> + #include <sys/syscall.h> + #include <sys/stat.h> + #include <errno.h> + #include <stdlib.h> + + int + main () + { + FILE* fp; + char s[]="system_calls"; + char buffer[1024]; + struct stat state; + + fp = fopen ("system_calls.tmp", "w"); + if (fp == NULL) + { + printf("Can not create system_calls.tmp\n"); + exit (0); + } + fwrite(s, sizeof(s), 1, fp); + fseek (fp, 0, SEEK_CUR); + fread(buffer, sizeof(s), 1, fp); + fclose(fp); + + getcwd (buffer, sizeof buffer); + chdir ("."); + symlink ("system_calls.tmp", "system_calls.link"); + remove("system_calls.link"); + rename ("system_calls.tmp", "system_calls.tmp1"); + stat ("system_calls.tmp", &state); + access ("system_calls.tmp", R_OK); + remove("system_calls.tmp1"); + + mkdir ("system_call_mkdir", 0777); + rmdir ("system_call_mkdir"); + + return 0; + } +}]] + +proc Calls {logfile} { + set fp [open $logfile] + set ret {} + + while {[gets $fp line] >= 0} { + if [regexp -- {^[a-zA-Z0-9]*@SYS} $line] { + set call [lindex [split $line @] 0] + dict incr ret $call + } + } + + close $fp + return $ret +} + +proc GetDefault {d key def} { + if {[dict exists $d $key]} { + return [dict get $d $key] + } else { + return $def + } } -# set options for ltrace. -ltrace_options "-S" +proc Diff {d1 d2} { + set keys [lsort -unique [concat [dict keys $d1] [dict keys $d2]]] + set ret {} + foreach key $keys { + set n1 [GetDefault $d1 $key 0] + set n2 [GetDefault $d2 $key 0] + set sum [expr $n1 - $n2] + if {[expr $sum != 0]} { + dict set ret $key $sum + } + } + return $ret +} + +proc Match {d patterns} { + foreach line $patterns { + set pattern [lindex $line 0] + set op [lindex $line 1] + set expect [lindex $line 2] -#Run PUT for ltarce. -set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] + set count 0 + foreach key [dict keys $d] { + if [regexp -- $pattern $key] { + incr count [dict get $d $key] + } + } -#check the output of this program. -verbose "ltrace runtest output: $exec_output\n" + set msgMain "$pattern was recorded $count times" -if [regexp {ELF from incompatible architecture} $exec_output] { - fail "32-bit ltrace can not perform on 64-bit PUTs and rebuild ltrace in 64 bit mode!" - return -} elseif [ regexp {Couldn't get .hash data} $exec_output ] { - fail "Couldn't get .hash data!" - return + if {[eval expr $count $op $expect]} { + pass $msgMain + } else { + fail "$msgMain, expected $op $expect" + } + } } +Match [Diff [Calls [ltraceRun -L -S -- $bin]] \ + [Calls [ltraceRun -L -S -- $empty]]] { + { {^write$} == 1 } + { {^unlink(at)?$} >= 2 } + { {^open(at)?$} == 1 } + { {^(new|f)?stat(64)?$} >= 1 } + { {^close$} == 1 } + { {^getcwd$} == 1 } + { {^chdir$} == 1 } + { {^symlink(at)?$} == 1 } + { {^f?access(at)?$} == 1 } + { {^rename(at)?$} == 1 } + { {^mkdir(at)?$} == 1 } +} -set pattern "SYS_munmap" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 2 -set pattern "SYS_write" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_unlink" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "SYS_brk" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_open" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_(new)?fstat" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 egrep -set pattern "SYS_(old_)?mmap" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 egrep -set pattern "SYS_close" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - -set pattern "SYS_getcwd" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_chdir" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_symlink" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_unlink" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_(new)?stat" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 egrep -set pattern "SYS_access" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_rename" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_mkdir" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 -set pattern "SYS_rmdir" -ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 +ltraceDone diff --git a/testsuite/ltrace.minor/Makefile.am b/testsuite/ltrace.minor/Makefile.am index af6cfde..1ff1e4e 100644 --- a/testsuite/ltrace.minor/Makefile.am +++ b/testsuite/ltrace.minor/Makefile.am @@ -15,32 +15,18 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -EXTRA_DIST = \ - attach-process.exp \ - count-record.c \ - count-record.exp \ - demangle.cpp \ - demangle.exp \ - demangle.h \ - demangle-lib.cpp \ - libdl-simple.c \ - libdl-simple.exp \ - libdl-simple-lib.c \ - print-instruction-pointer.c \ - print-instruction-pointer.exp \ - time-record.c \ - time-record-T.exp \ - time-record-tt.exp \ - time-record-ttt.exp \ - trace-clone.c \ - trace-clone.exp \ - trace-exec.exp \ - trace-fork.c \ - trace-fork.exp +EXTRA_DIST = attach-process.exp count-record.c count-record.exp \ + demangle.cpp demangle.exp demangle.h demangle-lib.cpp \ + libdl-simple.c libdl-simple.exp libdl-simple-lib.c \ + print-instruction-pointer.c print-instruction-pointer.exp \ + time-record.c time-record-T.exp time-record-tt.exp \ + time-record-ttt.exp trace-clone.c trace-clone.exp \ + trace-exec.exp trace-fork.c trace-fork.exp \ + trace-irelative.exp wchar.exp -CLEANFILES = *.o *.so *.log *.sum *.ltrace setval.tmp \ - attach-process count-record demangle print-instruction-pointer \ - time-record-T time-record-tt time-record-ttt trace-clone \ - trace-exec trace-exec1 trace-fork libdl-simple +CLEANFILES = *.o *.so *.log *.sum *.ltrace setval.tmp attach-process \ + count-record demangle print-instruction-pointer time-record-T \ + time-record-tt time-record-ttt trace-clone trace-exec \ + trace-exec1 trace-fork libdl-simple MAINTAINERCLEANFILES = Makefile.in diff --git a/testsuite/ltrace.minor/attach-process-dlopen.exp b/testsuite/ltrace.minor/attach-process-dlopen.exp new file mode 100644 index 0000000..ac61347 --- /dev/null +++ b/testsuite/ltrace.minor/attach-process-dlopen.exp @@ -0,0 +1,49 @@ +# This file is part of ltrace. +# Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +set libll [ltraceCompile libll.so [ltraceSource c { + #include <unistd.h> + void sym(void) { + sleep(1); + } +}]] + +spawn [ltraceCompile exe -ldl [ltraceSource c { + #include <unistd.h> + #include <dlfcn.h> + #include <assert.h> + #include <stdio.h> + #include <string.h> + int main(int argc, char **argv) { + assert(argc == 2); + #define FN "libll.so" + char buf[strlen(argv[1]) + 1 + sizeof FN]; + sprintf(buf, "%s/%s", argv[1], FN); + void *libll = dlopen(buf, RTLD_LAZY | RTLD_LOCAL); + assert(libll != NULL); + sleep(1); + void (*sym)(void) = dlsym(libll, "sym"); + assert(sym != NULL); + sym(); + return 0; + } +}]] $objdir/$subdir + +ltraceMatch1 [ltraceRun -e* -p [exp_pid]] {libll.so->sleep} == 1 + +ltraceDone diff --git a/testsuite/ltrace.minor/demangle-lib.cpp b/testsuite/ltrace.minor/demangle-lib.cpp index af66474..3091f31 100644 --- a/testsuite/ltrace.minor/demangle-lib.cpp +++ b/testsuite/ltrace.minor/demangle-lib.cpp @@ -1,5 +1,4 @@ #include<stddef.h> -#include<cstdlib> #include<iostream> #include<cstdlib> #include"demangle.h" diff --git a/testsuite/ltrace.minor/trace-clone.c b/testsuite/ltrace.minor/trace-clone.c index db1936d..ded930c 100644 --- a/testsuite/ltrace.minor/trace-clone.c +++ b/testsuite/ltrace.minor/trace-clone.c @@ -8,6 +8,7 @@ #include <sys/types.h> #include <stdlib.h> #include <sched.h> +#include <unistd.h> int child () { @@ -22,7 +23,8 @@ typedef int (* myfunc)(); int main () { pid_t pid; - static char stack[STACK_SIZE]; + static __attribute__ ((aligned (16))) char stack[STACK_SIZE]; + #ifdef __ia64__ pid = __clone2((myfunc)&child, stack, STACK_SIZE, CLONE_FS, NULL); #else diff --git a/testsuite/ltrace.minor/trace-exec.exp b/testsuite/ltrace.minor/trace-exec.exp index 7a953de..57260f8 100644 --- a/testsuite/ltrace.minor/trace-exec.exp +++ b/testsuite/ltrace.minor/trace-exec.exp @@ -1,5 +1,5 @@ # This file is part of ltrace. -# Copyright (C) 2012 Petr Machata, Red Hat Inc. +# Copyright (C) 2012, 2013 Petr Machata, Red Hat Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -16,22 +16,30 @@ # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA # 02110-1301 USA -ltraceMatch [ltraceRun -xmain -- [ltraceCompile {} [ltraceSource c { +set bin1 [ltraceCompile {} [ltraceSource c { #include <unistd.h> #include <stdlib.h> int main(int argc, char ** argv) { execl(argv[1], argv[1], NULL); abort(); } -}]] [ltraceCompile {} [ltraceSource c { +}]] + +set bin2 [ltraceCompile {} [ltraceSource c { #include <stdio.h> int main(void) { return puts("Hello, World."); } -}]]] { +}]] + +ltraceMatch [ltraceRun -xmain -- $bin1 $bin2] { {{^execl\(} == 1} {{^puts\(.*\) .*= 14} == 1} {{^main\(} == 2} } +ltraceMatch [ltraceRun -c -- $bin1 $bin2] { + {{exec} > 0} +} + ltraceDone diff --git a/testsuite/ltrace.minor/trace-irelative.exp b/testsuite/ltrace.minor/trace-irelative.exp new file mode 100644 index 0000000..3bd9c41 --- /dev/null +++ b/testsuite/ltrace.minor/trace-irelative.exp @@ -0,0 +1,68 @@ +# This file is part of ltrace. +# Copyright (C) 2013 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +proc do_tests {bin appendage} { + ltraceMatch1 [ltraceRun -e *xyz -- $bin] {xyz\(} == 2 + + # If the actual entry point that xyz resolves to is not traced, it + # should get xyz's name. But we don't mind if somebody implements + # the late lookup and actually names the symbol properly. + + set log [ltraceRun -L -x xyz -- $bin] + ltraceMatch1 $log [format {xyz\.IFUNC%s\(\)} $appendage] == 1 + ltraceMatch1 $log [format {(xyz|abc)%s\(} $appendage] == 2 + + # If we request abc's tracing explicitly, than it definitely needs + # to be presented as abc, not as xyz. + + set log [ltraceRun -L -x xyz+abc -- $bin] + ltraceMatch1 $log [format {xyz\.IFUNC%s\(\)} $appendage] == 1 + ltraceMatch1 $log [format {abc%s\(} $appendage] == 2 +} + +set src [ltraceSource c { + int abc (int a) { return a + 1; } + + __asm__(".type xyz, \%gnu_indirect_function"); + + int xyz (int a); + extern void *xyz_ifunc(void) __asm__("xyz"); + extern void *xyz_ifunc(void) { + return &abc; + } + + int + main(int argc, char *argv[]) + { + return xyz (xyz (argc)); + } +}] + +foreach ext {{} .pie} { + set bin1 [ltraceCompile $ext $src] + do_tests $bin1 "" +} + +set lib [ltraceCompile lib.so $src] + +foreach ext {{} .pie} { + set bin2 [ltraceCompile $ext $lib [ltraceSource c {/* Empty. */}]] + do_tests $bin2 @lib.so +} + +ltraceDone diff --git a/testsuite/ltrace.minor/wchar.exp b/testsuite/ltrace.minor/wchar.exp new file mode 100644 index 0000000..1200f9f --- /dev/null +++ b/testsuite/ltrace.minor/wchar.exp @@ -0,0 +1,214 @@ +# This file is part of ltrace. +# Copyright (C) 2013 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +set bin [ltraceCompile {} [ltraceSource c { + #include <sys/time.h> + #include <assert.h> + #include <locale.h> + #include <stdio.h> + #include <stdlib.h> + #include <string.h> + #include <time.h> + #include <wchar.h> + #include <wctype.h> + + int main(int argc, char *argv[]) + { + setlocale(LC_ALL, ""); + + FILE *rd_stream, *wr_stream, *wr_stream_2; + { + int fds[2]; + pipe (fds); + rd_stream = fdopen (fds[0], "r"); + wr_stream = fdopen (fds[1], "w"); + wr_stream_2 = fdopen (fds[1], "w"); + } + + wcslen(L"Вот это да!"); + + fprintf(wr_stream_2, "something %s\n", "something"); + fprintf(wr_stream_2, "something %ls\n", L"что-то"); + + fputwc(L'Ф', wr_stream); + putwc(L'Д', wr_stream); + fflush(wr_stream); + fgetwc(rd_stream); + ungetwc(getwc(rd_stream), rd_stream); + + fputws(L"Что-то.\n", wr_stream); + fflush(wr_stream); + wchar_t wbuf[64]; + fgetws(wbuf, 64, rd_stream); + + fwprintf(wr_stream, L"Какое-то %ls %s.\n", L"что-то", "something"); + swprintf(wbuf, 64, L"zwölf große %ls %zd", L"Boxkämpfe", wcslen(wbuf)); + + int i = iswalnum(L'1'); + assert(!!i); + i = iswalpha(L'A'); + assert(!!i); + i = iswcntrl(L'\t'); + assert(!!i); + i = iswdigit(L'1'); + assert(!!i); + i = iswgraph(L'='); + assert(!!i); + i = iswlower(L'ц'); + assert(!!i); + i = iswupper(L'Ц'); + assert(!!i); + i = iswprint(L'☻'); + assert(!!i); + i = iswpunct(L'•'); + assert(!!i); + i = iswspace(L'\t'); + assert(!!i); + i = iswxdigit(L'A'); + assert(!!i); + + i = mbrlen("что", sizeof "что", NULL); + assert(i == 2); + wchar_t wc; + i = mbrtowc(&wc, "что", sizeof "что", NULL); + + const char *str = "что"; + i = mbsrtowcs(wbuf, &str, 64, NULL); + assert(i >= 0); + + i = towlower(towupper(L'ы')) == L'ы'; + assert(!!i); + + char buf[64] = {}; + wctomb(buf, L'ư'); + wcrtomb(buf, L'ơ', NULL); + + wbuf[0] = 0; + i = wcscmp(wcschr(wcsncat(wcscat(wbuf, L"žluťoučký "), + L"kůň", 64), L'ů'), L"ůň"); + assert(i == 0); + i = wcsncmp(wbuf, L"žluťák", 4); + assert(i == 0); + + i = wcscoll(wcscpy(wbuf, L"/ˈɪŋɡlɪʃ/"), L"/dɔɪ̯ʧ/"); + assert(i != 0); + i = wcsspn(wbuf, L"/"); + assert(i == 1); + i = wcscspn(wbuf, L"ˈ"); + assert(i == 1); + *wcsrchr(wcspbrk(wbuf, L"ɪ"), L'ɪ') = L'i'; + + struct timeval tv; + gettimeofday(&tv, NULL); + struct tm *tm = gmtime(&tv.tv_sec); + wbuf[0] = L'\0'; + wcsftime(wbuf, 64, L"«%F • %T»", tm); + { const wchar_t *ptr = wbuf; wcsrtombs(buf, &ptr, 64, NULL); } + { wchar_t *ptr = NULL; wcstod(wcsstr(wbuf, L"•") + 2, &ptr); } + wcsncpy(wbuf, L"1234•", 64); + { wchar_t *ptr = NULL; wcstof(wbuf, &ptr); } + { wchar_t *ptr = NULL; wcstold(wbuf, &ptr); } + { wchar_t *ptr = NULL; wcstol(wbuf, &ptr, 10); } + { wchar_t *ptr = NULL; wcstoll(wbuf, &ptr, 10); } + { wchar_t *ptr = NULL; wcstoul(wbuf, &ptr, 10); } + { wchar_t *ptr = NULL; wcstoull(wbuf, &ptr, 10); } + i = wmemcmp(wmemchr(wbuf, L'•', 64), L"•", 2); + assert(i == 0); + + i = wcswidth(L"你好") + wcwidth(L'你') + wctob(L'1');; + assert(i == 6 + '1'); + + i = iswctype(L'Ш', wctype("alpha")); + assert(!!i); + + wmemcpy(wbuf, L"Dobrý den", 6); + wmemmove(wbuf, L" ", 2); + { wchar_t *ptr = NULL; wmemset(wcstok(wbuf, L" ", &ptr), L'я', 5); } + + return 0; + } +}]] + +ltraceMatch [ltraceRun -F $srcdir/../etc/ -- $bin] { + {{^fprintf\(.*, "something %s\\n", "something"\)} == 1} + {{^fprintf\(.*, "something %ls\\n", "что-то"\)} == 1} + {{^fputwc\('Ф', .*\).*= 'Ф'} == 1} + {{^putwc\('Д', .*\).*= 'Д'} == 1} + {{^fgetwc\(.*\).*= 'Ф'} == 1} + {{^getwc\(.*\).*= 'Д'} == 1} + {{^ungetwc\('Д', .*\).*= 'Д'} == 1} + {{^fputws\("Что-то.\\n", .*\)} == 1} + {{^fgetws\("ДЧто-то.\\n", 64, .*\).*= "ДЧто-то.\\n"} == 1} + {{^fwprintf\(.*, "Какое-то %ls %s.\\n", "что-то", "something"\).*= 27} == 1} + {{^wcslen\("ДЧто-то.\\n"\).*= 9} == 1} + {{^swprintf\("zwölf große Boxkämpfe 9", 64, "zwölf große %ls %zd", "Boxkämpfe", 9\).*= 23} == 1} + {{^iswalnum\('1'\).*= 8} == 1} + {{^iswalpha\('A'\).*= 1024} == 1} + {{^iswcntrl\('\\t'\).*= 2} == 1} + {{^iswdigit\('1'\).*= 1} == 1} + {{^iswgraph\('='\).*= 32768} == 1} + {{^iswlower\('ц'\).*= 1} == 1} + {{^iswupper\('Ц'\).*= 1} == 1} + {{^iswprint\('☻'\).*= 1} == 1} + {{^iswpunct\('•'\).*= 1} == 1} + {{^iswspace\('\\t'\).*= 8192} == 1} + {{^iswxdigit\('A'\).*= 4096} == 1} + {{^mbrtowc\('ч', ".*", 7, nil\)} == 1} + {{^mbsrtowcs\("что", nil, 64, nil\).*= 3} == 1} + {{^towupper\('ы'\).*= 'Ы'} == 1} + {{^towlower\('Ы'\).*= 'ы'} == 1} + {{^wctomb\(".*", 'ư'\)} == 1} + {{^wcrtomb\(".*", 'ơ', nil\)} == 1} + {{^wcscat\("", "žluťoučký "\).*= "žluťoučký "} == 1} + {{^wcsncat\("žluťoučký ", "kůň", 64\).*= "žluťoučký kůň"} == 1} + {{^wcschr\("žluťoučký kůň", 'ů'\).*= "ůň"} == 1} + {{^wcscmp\("ůň", "ůň"\).*= 0} == 1} + {{^wcsncmp\("žluť", "žluť", 4\).*= 0} == 1} + {{^wcscpy\(.*, "/ˈɪŋɡlɪʃ/"\).*= .*} == 1} + {{^wcscoll\("/ˈɪŋɡlɪʃ/", "/dɔɪ̯ʧ/"\).*= 10} == 1} + {{^wcsspn\("/ˈɪŋɡlɪʃ/", "/"\).*= 1} == 1} + {{^wcscspn\("/ˈɪŋɡlɪʃ/", "ˈ"\).*= 1} == 1} + {{^wcspbrk\("/ˈɪŋɡlɪʃ/", "ɪ"\).*= "ɪŋɡlɪʃ/"} == 1} + {{^wcsrchr\("ɪŋɡlɪʃ/", 'ɪ'\).*= "ɪʃ/"} == 1} + {{^gettimeofday\(.*, nil\).*= 0} == 1} + {{^gmtime\(.*\).*= .*} == 1} + {{^wcsftime\("«.* • .*»", 64, "«%F • %T»", .*\)} == 1} + {{^wcsrtombs\(".*", nil, 64, nil\)} == 1} + {{^wcsstr\("«.* • .*»", "•"\).*= "• .*»"} == 1} + {{^wcstod\(".*»", ".*»"\).*= [0-9]+} == 1} + {{^wcsncpy\(.*, "1234•", 64\).*= .*} == 1} + {{^wcstof\("1234•", "•"\).*= 1234} == 1} + {{^wcstold\("1234•", "•"\).*= 1234} == 1} + {{^wcstol\("1234•", "•", 10\).*= 1234} == 1} + {{^wcstoll\("1234•", "•", 10\).*= 1234} == 1} + {{^wcstoul\("1234•", "•", 10\).*= 1234} == 1} + {{^wcstoull\("1234•", "•", 10\).*= 1234} == 1} + {{^wmemchr\("1234•", '•', 64\).*= "•"} == 1} + {{^wmemcmp\("•", "•", 2\).*= 0} == 1} + {{^wcswidth\("你好", .*\).*= 4} == 1} + {{^wcwidth\('你'\).*= 2} == 1} + {{^wctob\('1'\).*= 49} == 1} + {{^wctype\("alpha"\).*= .*} == 1} + {{^iswctype\('Ш', .*\).*= 1} == 1} + {{^wmemcpy\(.*, "Dobrý ", 6\).*= "Dobrý "} == 1} + {{^wmemmove\(.*, " ", 2\).*= " "} == 1} + {{^wcstok\(" brý ", " ", ""\).*= "brý"} == 1} + {{^wmemset\(.*, 'я', 5\).*= "яяяяя"} == 1} +} + +ltraceDone diff --git a/testsuite/ltrace.torture/Makefile.am b/testsuite/ltrace.torture/Makefile.am index daa772f..5a45265 100644 --- a/testsuite/ltrace.torture/Makefile.am +++ b/testsuite/ltrace.torture/Makefile.am @@ -15,15 +15,9 @@ # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # -EXTRA_DIST = \ - ia64-sigill.exp \ - ia64-sigill.s \ - ppc-lwarx.c \ - ppc-lwarx.exp \ - signals.c \ - signals.exp \ - vfork-thread.c \ - vfork-thread.exp +EXTRA_DIST = arm-singlestep.exp ia64-sigill.exp ia64-sigill.s \ + ppc-lwarx.c ppc-lwarx.exp signals.c signals.exp \ + vfork-thread.c vfork-thread.exp CLEANFILES = *.o *.so *.log *.sum *.ltrace setval.tmp \ signals diff --git a/testsuite/ltrace.torture/arm-singlestep.exp b/testsuite/ltrace.torture/arm-singlestep.exp new file mode 100644 index 0000000..0d633d9 --- /dev/null +++ b/testsuite/ltrace.torture/arm-singlestep.exp @@ -0,0 +1,44 @@ +# This file is part of ltrace. +# Copyright (C) 2013 Petr Machata, Red Hat Inc. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 2 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA +# 02110-1301 USA + +if {![istarget arm*-*]} { + unsupported "arm-specific test" + return +} + +set exe [ltraceCompile {} [ltraceSource c { + int puc(void) { return 0; } + + int bar(void); + int baz(void); + __asm__ (" .type bar, %function\n" + "bar: \n" + " b puc \n" + " .type baz, %function\n" + "baz: \n" + " b puc \n"); + + int main(void) { return bar() + baz(); } +}]] + +ltraceMatch [ltraceRun -L -xbar+baz $exe] { + {{bar} == 1} + {{baz} == 1} +} + +ltraceDone diff --git a/testsuite/ltrace.torture/ia64-sigill.exp b/testsuite/ltrace.torture/ia64-sigill.exp index c4cf5a2..a99b0d6 100644 --- a/testsuite/ltrace.torture/ia64-sigill.exp +++ b/testsuite/ltrace.torture/ia64-sigill.exp @@ -11,7 +11,7 @@ if { [istarget ia64-*] } then { send_user "Testcase compile failed, so all tests in this file will automatically fail\n." } - # Run PUT for ltarce. + # Run PUT for ltrace. set exec_output [ltrace_runtest $srcdir/$subdir $srcdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.torture/ppc-lwarx.exp b/testsuite/ltrace.torture/ppc-lwarx.exp index bc2eba4..caea316 100644 --- a/testsuite/ltrace.torture/ppc-lwarx.exp +++ b/testsuite/ltrace.torture/ppc-lwarx.exp @@ -1,5 +1,5 @@ # This file is part of ltrace. -# Copyright (C) 2012 Petr Machata, Red Hat Inc. +# Copyright (C) 2012, 2014 Petr Machata, Red Hat Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License as @@ -33,7 +33,7 @@ if { [istarget powerpc*-*] } then { # set options for ltrace. ltrace_options "-x" "atomic_add" "-e" "!atoi" - # Run PUT for ltarce. + # Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. @@ -46,10 +46,10 @@ if { [istarget powerpc*-*] } then { return } - set pattern "atomic_add(.*, 5,.*)" + set pattern "atomic_add(.*, 5\\b.*" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - set pattern "atomic_add(.*, 10,.*)" + set pattern "atomic_add(.*, 10\\b.*" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 - set pattern "atomic_add(.*, 15,.*)" + set pattern "atomic_add(.*, 15\\b.*" ltrace_verify_output ${objdir}/${subdir}/${testfile}.ltrace $pattern 1 } diff --git a/testsuite/ltrace.torture/signals.exp b/testsuite/ltrace.torture/signals.exp index bf10bc9..321409b 100644 --- a/testsuite/ltrace.torture/signals.exp +++ b/testsuite/ltrace.torture/signals.exp @@ -14,7 +14,7 @@ if { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/ # Set options for ltrace. ltrace_options "-L" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. diff --git a/testsuite/ltrace.torture/vfork-thread.exp b/testsuite/ltrace.torture/vfork-thread.exp index bd01319..91eccb2 100644 --- a/testsuite/ltrace.torture/vfork-thread.exp +++ b/testsuite/ltrace.torture/vfork-thread.exp @@ -13,7 +13,7 @@ if { [ ltrace_compile "${srcdir}/${subdir}/${testfile}.c" "${objdir}/${subdir}/ ltrace_options "-f" -# Run PUT for ltarce. +# Run PUT for ltrace. set exec_output [ltrace_runtest $objdir/$subdir $objdir/$subdir/$binfile] # Check the output of this program. @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 2007,2008 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -59,6 +59,19 @@ type_get_simple(enum arg_type type) abort(); } +struct arg_type_info * +type_get_voidptr(void) +{ + struct arg_type_info *void_info = type_get_simple(ARGTYPE_VOID); + static struct arg_type_info *ret; + if (ret == NULL) { + static struct arg_type_info ptr_info; + type_init_pointer(&ptr_info, void_info, 0); + ret = &ptr_info; + } + return ret; +} + static void type_init_common(struct arg_type_info *info, enum arg_type type) { @@ -92,11 +105,7 @@ struct arg_type_info * type_struct_get(struct arg_type_info *info, size_t idx) { assert(info->type == ARGTYPE_STRUCT); - struct struct_field *field = VECT_ELEMENT(&info->u.entries, - struct struct_field, idx); - if (field == NULL) - return NULL; - return field->info; + return VECT_ELEMENT(&info->u.entries, struct struct_field, idx)->info; } size_t @@ -123,7 +132,7 @@ type_struct_destroy(struct arg_type_info *info) } static int -layout_struct(struct Process *proc, struct arg_type_info *info, +layout_struct(struct process *proc, struct arg_type_info *info, size_t *sizep, size_t *alignmentp, size_t *offsetofp) { size_t sz = 0; @@ -253,11 +262,100 @@ type_destroy(struct arg_type_info *info) } } +static int +type_alloc_and_clone(struct arg_type_info **retpp, + struct arg_type_info *info, int own) +{ + *retpp = info; + if (own) { + *retpp = malloc(sizeof **retpp); + if (*retpp == NULL || type_clone(*retpp, info) < 0) { + free(*retpp); + return -1; + } + } + return 0; +} + +static enum callback_status +clone_struct_add_field(const struct struct_field *field, void *data) +{ + struct arg_type_info *retp = data; + struct arg_type_info *info; + if (type_alloc_and_clone(&info, field->info, field->own_info) < 0) { + fail: + if (info != field->info) + free(info); + return CBS_STOP; + } + + if (type_struct_add(retp, info, field->own_info) < 0) { + if (field->own_info) + type_destroy(info); + goto fail; + } + + return CBS_CONT; +} + +int +type_clone(struct arg_type_info *retp, const struct arg_type_info *info) +{ + switch (info->type) { + case ARGTYPE_STRUCT: + type_init_struct(retp); + if (VECT_EACH_CST(&info->u.entries, struct struct_field, NULL, + clone_struct_add_field, retp) != NULL) { + type_destroy(retp); + return -1; + } + break; + + case ARGTYPE_ARRAY:; + struct arg_type_info *elt_type; + if (type_alloc_and_clone(&elt_type, info->u.array_info.elt_type, + info->u.array_info.own_info) < 0) + return -1; + + assert(!info->u.array_info.own_length); // XXXXXXX + type_init_array(retp, elt_type, info->u.array_info.own_info, + info->u.array_info.length, + info->u.array_info.own_length); + break; + + case ARGTYPE_POINTER:; + struct arg_type_info *ninfo; + if (type_alloc_and_clone(&ninfo, info->u.ptr_info.info, + info->u.ptr_info.own_info) < 0) + return -1; + type_init_pointer(retp, ninfo, info->u.ptr_info.own_info); + break; + + case ARGTYPE_VOID: + case ARGTYPE_INT: + case ARGTYPE_UINT: + case ARGTYPE_LONG: + case ARGTYPE_ULONG: + case ARGTYPE_CHAR: + case ARGTYPE_SHORT: + case ARGTYPE_USHORT: + case ARGTYPE_FLOAT: + case ARGTYPE_DOUBLE: + *retp = *info; + break; + } + + assert(!info->own_lens); + retp->lens = info->lens; + retp->own_lens = info->own_lens; + return 0; +} + #ifdef ARCH_HAVE_SIZEOF -size_t arch_type_sizeof(struct Process *proc, struct arg_type_info * arg); +size_t arch_type_sizeof(struct process *proc, struct arg_type_info *arg); #else size_t -arch_type_sizeof(struct Process *proc, struct arg_type_info * arg) +arch_type_sizeof(struct process *proc, struct arg_type_info *arg) { /* Use default value. */ return (size_t)-2; @@ -265,10 +363,10 @@ arch_type_sizeof(struct Process *proc, struct arg_type_info * arg) #endif #ifdef ARCH_HAVE_ALIGNOF -size_t arch_type_alignof(struct Process *proc, struct arg_type_info * arg); +size_t arch_type_alignof(struct process *proc, struct arg_type_info *arg); #else size_t -arch_type_alignof(struct Process *proc, struct arg_type_info * arg) +arch_type_alignof(struct process *proc, struct arg_type_info *arg) { /* Use default value. */ return (size_t)-2; @@ -289,7 +387,7 @@ align(size_t sz, size_t alignment) } size_t -type_sizeof(struct Process *proc, struct arg_type_info *type) +type_sizeof(struct process *proc, struct arg_type_info *type) { size_t arch_size = arch_type_sizeof(proc, type); if (arch_size != (size_t)-2) @@ -359,7 +457,7 @@ type_sizeof(struct Process *proc, struct arg_type_info *type) #define alignof(field,st) ((size_t) ((char*) &st.field - (char*) &st)) size_t -type_alignof(struct Process *proc, struct arg_type_info *type) +type_alignof(struct process *proc, struct arg_type_info *type) { size_t arch_alignment = arch_type_alignof(proc, type); if (arch_alignment != (size_t)-2) @@ -412,7 +510,7 @@ type_alignof(struct Process *proc, struct arg_type_info *type) } size_t -type_offsetof(struct Process *proc, struct arg_type_info *type, size_t emt) +type_offsetof(struct process *proc, struct arg_type_info *type, size_t emt) { assert(type->type == ARGTYPE_STRUCT || type->type == ARGTYPE_ARRAY); @@ -568,3 +666,39 @@ type_get_fp_equivalent(struct arg_type_info *info) } abort(); } + +struct arg_type_info * +type_get_hfa_type(struct arg_type_info *info, size_t *countp) +{ + assert(info != NULL); + if (info->type != ARGTYPE_STRUCT + && info->type != ARGTYPE_ARRAY) + return NULL; + + size_t n = type_aggregate_size(info); + if (n == (size_t)-1) + return NULL; + + struct arg_type_info *ret = NULL; + *countp = 0; + + while (n-- > 0) { + struct arg_type_info *emt = type_element(info, n); + + size_t emt_count = 1; + if (emt->type == ARGTYPE_STRUCT || emt->type == ARGTYPE_ARRAY) + emt = type_get_hfa_type(emt, &emt_count); + if (emt == NULL) + return NULL; + if (ret == NULL) { + if (emt->type != ARGTYPE_FLOAT + && emt->type != ARGTYPE_DOUBLE) + return NULL; + ret = emt; + } + if (emt->type != ret->type) + return NULL; + *countp += emt_count; + } + return ret; +} @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * Copyright (C) 1997-2009 Juan Cespedes * * This program is free software; you can redistribute it and/or @@ -70,6 +70,7 @@ struct arg_type_info { * struct, or pointer. Each call with the same TYPE yields the same * arg_type_info pointer. */ struct arg_type_info *type_get_simple(enum arg_type type); +struct arg_type_info *type_get_voidptr(void); /* Initialize INFO so it becomes ARGTYPE_STRUCT. The created * structure contains no fields. Use type_struct_add to populate the @@ -110,12 +111,16 @@ void type_init_pointer(struct arg_type_info *info, * itself. */ void type_destroy(struct arg_type_info *info); +/* Copy type INFO into the area pointed to by RETP. Return 0 on + * success or a negative value on failure. */ +int type_clone(struct arg_type_info *retp, const struct arg_type_info *info); + /* Compute a size of given type. Return (size_t)-1 for error. */ -size_t type_sizeof(struct Process *proc, struct arg_type_info *type); +size_t type_sizeof(struct process *proc, struct arg_type_info *type); /* Compute an alignment necessary for elements of this type. Return * (size_t)-1 for error. */ -size_t type_alignof(struct Process *proc, struct arg_type_info *type); +size_t type_alignof(struct process *proc, struct arg_type_info *type); /* Align value SZ to ALIGNMENT and return the result. */ size_t align(size_t sz, size_t alignment); @@ -126,7 +131,7 @@ struct arg_type_info *type_element(struct arg_type_info *type, size_t elt); /* Compute an offset of EMT-th element of type TYPE. This works for * arrays and structures. Return (size_t)-1 for error. */ -size_t type_offsetof(struct Process *proc, +size_t type_offsetof(struct process *proc, struct arg_type_info *type, size_t elt); /* Whether TYPE is an integral type as defined by the C standard. */ @@ -142,4 +147,13 @@ int type_is_signed(enum arg_type type); * type. */ struct arg_type_info *type_get_fp_equivalent(struct arg_type_info *info); +/* If INFO is homogeneous floating-point aggregate, return the + * corresponding floating point type, and set *COUNTP to number of + * fields of the structure. Otherwise return NULL. INFO is a HFA if + * it's an aggregate whose each field is either a HFA, or a + * floating-point type. */ +struct arg_type_info *type_get_hfa_type(struct arg_type_info *info, + size_t *countp); + + #endif /* TYPE_H */ @@ -29,7 +29,7 @@ #include "backend.h" static void -value_common_init(struct value *valp, struct Process *inferior, +value_common_init(struct value *valp, struct process *inferior, struct value *parent, struct arg_type_info *type, int own_type) { @@ -43,7 +43,7 @@ value_common_init(struct value *valp, struct Process *inferior, } void -value_init(struct value *valp, struct Process *inferior, struct value *parent, +value_init(struct value *valp, struct process *inferior, struct value *parent, struct arg_type_info *type, int own_type) { assert(inferior != NULL); @@ -189,15 +189,30 @@ int value_clone(struct value *retp, const struct value *val) { *retp = *val; + + if (val->own_type) { + retp->type = malloc(sizeof(struct arg_type_info)); + if (type_clone (retp->type, val->type) < 0) { + free(retp->type); + return -1; + } + } + if (val->where == VAL_LOC_COPY) { assert(val->inferior != NULL); size_t size = type_sizeof(val->inferior, val->type); - if (size == (size_t)-1) + if (size == (size_t)-1) { + fail: + if (retp->own_type) { + type_destroy(retp->type); + free(retp->type); + } return -1; + } retp->u.address = malloc(size); if (retp->u.address == NULL) - return -1; + goto fail; memcpy(retp->u.address, val->u.address, size); } @@ -284,7 +299,7 @@ value_init_deref(struct value *ret_val, struct value *valp) /* We need "long" to be long enough to hold platform * pointers. */ - typedef char assert__long_enough_long[-(sizeof(l) < sizeof(void *))]; + (void)sizeof(char[1 - 2*(sizeof(l) < sizeof(void *))]); value_common_init(ret_val, valp->inferior, valp, valp->type->u.ptr_info.info, 0); @@ -46,7 +46,7 @@ enum value_location_t { struct value { struct arg_type_info *type; - struct Process *inferior; + struct process *inferior; struct value *parent; size_t size; union { @@ -63,7 +63,7 @@ struct value { * value, in case of compound types. It may be NULL. TYPE is a type * of the value. It may be NULL if the type is not yet known. If * OWN_TYPE, the passed-in type is owned and released by value. */ -void value_init(struct value *value, struct Process *inferior, +void value_init(struct value *value, struct process *inferior, struct value *parent, struct arg_type_info *type, int own_type); diff --git a/value_dict.h b/value_dict.h index f75ee37..4991dd3 100644 --- a/value_dict.h +++ b/value_dict.h @@ -18,8 +18,8 @@ * 02110-1301 USA */ -#ifndef ARGS_H -#define ARGS_H +#ifndef VALUE_DICT_H +#define VALUE_DICT_H #include "forward.h" #include "vect.h" @@ -64,4 +64,4 @@ struct value *val_dict_get_name(struct value_dict *dict, const char *name); * itself (the pointer) is not freed. */ void val_dict_destroy(struct value_dict *dict); -#endif /* ARGS_H */ +#endif /* VALUE_DICT_H */ @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -129,9 +129,31 @@ vect_pushback(struct vect *vec, void *eltp) } void -vect_popback(struct vect *vec) +vect_erase(struct vect *vec, size_t start, size_t end, + void (*dtor)(void *emt, void *data), void *data) { - vec->size--; + assert(start < vect_size(vec) || start == 0); + assert(end <= vect_size(vec)); + + /* First, destroy the elements that are to be erased. */ + if (dtor != NULL) { + size_t i; + for (i = start; i < end; ++i) + dtor(slot(vec, i), data); + } + + /* Now move the tail forward and adjust size. */ + memmove(slot(vec, start), slot(vec, end), + slot(vec, vec->size) - slot(vec, end)); + vec->size -= end - start; +} + +void +vect_popback(struct vect *vec, + void (*dtor)(void *emt, void *data), void *data) +{ + assert(vect_size(vec) > 0); + vect_erase(vec, vect_size(vec)-1, vect_size(vec), dtor, data); } void @@ -140,12 +162,8 @@ vect_destroy(struct vect *vec, void (*dtor)(void *emt, void *data), void *data) if (vec == NULL) return; - if (dtor != NULL) { - size_t i; - size_t sz = vect_size(vec); - for (i = 0; i < sz; ++i) - dtor(slot(vec, i), data); - } + vect_erase(vec, 0, vect_size(vec), dtor, data); + assert(vect_size(vec) == 0); free(vec->data); } @@ -170,3 +188,23 @@ vect_each(struct vect *vec, void *start_after, return NULL; } + +void +vect_qsort(struct vect *vec, int (*compar)(const void *, const void *)) +{ + qsort(vec->data, vec->size, vec->elt_size, compar); +} + +const void * +vect_each_cst(const struct vect *vec, const void *start_after, + enum callback_status (*cb)(const void *, void *), void *data) +{ + return vect_each((struct vect *)vec, (void *)start_after, + (void *)cb, data); +} + +void +vect_dtor_string(char **key, void *data) +{ + free(*key); +} @@ -1,6 +1,6 @@ /* * This file is part of ltrace. - * Copyright (C) 2011,2012 Petr Machata, Red Hat Inc. + * Copyright (C) 2011,2012,2013 Petr Machata, Red Hat Inc. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as @@ -96,8 +96,32 @@ int vect_empty(const struct vect *vec); * operation was successful, or negative value on error. */ int vect_pushback(struct vect *vec, void *eltp); -/* Drop last element of VECP. */ -void vect_popback(struct vect *vec); +/* Drop last element of VECP. This is like calling + * vect_erase(VEC, vect_size(VEC)-1, vect_size(VEC), DTOR, DATA); */ +void vect_popback(struct vect *vec, + void (*dtor)(void *emt, void *data), void *data); + +#define VECT_POPBACK(VECP, ELT_TYPE, DTOR, DATA) \ + do \ + VECT_ERASE((VECP), ELT_TYPE, \ + vect_size(VECP) - 1, vect_size(VECP), \ + DTOR, DATA); \ + while (0) + +/* Drop elements START (inclusive) to END (non-inclusive) of VECP. If + * DTOR is non-NULL, it is called on each of the removed elements. + * DATA is passed verbatim to DTOR. */ +void vect_erase(struct vect *vec, size_t start, size_t end, + void (*dtor)(void *emt, void *data), void *data); + +#define VECT_ERASE(VECP, ELT_TYPE, START, END, DTOR, DATA) \ + do { \ + assert((VECP)->elt_size == sizeof(ELT_TYPE)); \ + /* Check that DTOR is typed properly. */ \ + void (*_dtor_callback)(ELT_TYPE *, void *) = DTOR; \ + vect_erase((VECP), (START), (END), \ + (void (*)(void *, void *))_dtor_callback, DATA); \ + } while (0) /* Copy element referenced by ELTP to the end of VEC. See * vect_pushback for details. In addition, make a check whether VECP @@ -140,11 +164,48 @@ void *vect_each(struct vect *vec, void *start_after, assert((VECP)->elt_size == sizeof(ELT_TYPE)); \ /* Check that CB is typed properly. */ \ enum callback_status (*_cb)(ELT_TYPE *, void *) = CB; \ - ELT_TYPE *start_after = (START_AFTER); \ - (ELT_TYPE *)vect_each((VECP), start_after, \ + ELT_TYPE *_start_after = (START_AFTER); \ + (ELT_TYPE *)vect_each((VECP), _start_after, \ (enum callback_status \ (*)(void *, void *))_cb, \ DATA); \ }) +/* Iterate through vector VEC. See callback.h for notes on iteration + * interfaces. */ +const void *vect_each_cst(const struct vect *vec, const void *start_after, + enum callback_status (*cb)(const void *, void *), + void *data); + +#define VECT_EACH_CST(VECP, ELT_TYPE, START_AFTER, CB, DATA) \ + /* xxx GCC-ism necessary to get in the safety latches. */ \ + ({ \ + assert((VECP)->elt_size == sizeof(ELT_TYPE)); \ + /* Check that CB is typed properly. */ \ + enum callback_status (*_cb)(const ELT_TYPE *, void *) = CB; \ + const ELT_TYPE *start_after = (START_AFTER); \ + (const ELT_TYPE *)vect_each_cst((VECP), start_after, \ + (enum callback_status \ + (*)(const void *, \ + void *))_cb, \ + DATA); \ + }) + +/* Call qsort on elements of VECT, with COMPAR as a comparison + * function. */ +void vect_qsort(struct vect *vec, int (*compar)(const void *, const void *)); + +#define VECT_QSORT(VECP, ELT_TYPE, COMPAR) \ + do { \ + assert((VECP)->elt_size == sizeof(ELT_TYPE)); \ + /* Check that CB is typed properly. */ \ + int (*_compar)(const ELT_TYPE *, const ELT_TYPE *) = COMPAR; \ + vect_qsort((VECP), \ + (int (*)(const void *, const void *))_compar); \ + } while (0) + + +/* A dtor which calls 'free' on elements of a vector. */ +void vect_dtor_string(char **key, void *data); + #endif /* VECT_H */ @@ -18,7 +18,6 @@ * 02110-1301 USA */ -#include <error.h> #include <errno.h> #include "zero.h" @@ -93,13 +92,12 @@ build_zero_w_arg(struct expr_node *expr, int own) struct expr_node * expr_node_zero(void) { - static struct expr_node *node = NULL; - if (node == NULL) { - node = malloc(sizeof(*node)); - if (node == NULL) - error(1, errno, "malloc expr_node_zero"); - expr_init_cb1(node, &zero1_callback, + static struct expr_node *nodep = NULL; + if (nodep == NULL) { + static struct expr_node node; + expr_init_cb1(&node, &zero1_callback, expr_self(), 0, (void *)-1); + nodep = &node; } - return node; + return nodep; } |