/*
* This file is part of ltrace.
* 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
* 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
*/
#include "config.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>
#include <errno.h>
#include <assert.h>
#include <inttypes.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"
static struct process *current_proc = NULL;
static size_t current_depth = 0;
static int current_column = 0;
static void
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(struct process *proc, int is_func, int indent)
{
current_column = 0;
if (!proc) {
return;
}
if ((options.output != stderr) && (opt_p || options.follow)) {
current_column += fprintf(options.output, "%u ", proc->pid);
} else if (options.follow) {
current_column += fprintf(options.output, "[pid %u] ", proc->pid);
}
if (opt_r) {
struct timeval tv;
static struct timeval old_tv = { 0, 0 };
struct timeval diff;
gettimeofday(&tv, NULL);
if (old_tv.tv_sec == 0 && old_tv.tv_usec == 0) {
old_tv.tv_sec = tv.tv_sec;
old_tv.tv_usec = tv.tv_usec;
}
diff.tv_sec = tv.tv_sec - old_tv.tv_sec;
if (tv.tv_usec >= old_tv.tv_usec) {
diff.tv_usec = tv.tv_usec - old_tv.tv_usec;
} else {
diff.tv_sec--;
diff.tv_usec = 1000000 + tv.tv_usec - old_tv.tv_usec;
}
old_tv.tv_sec = tv.tv_sec;
old_tv.tv_usec = tv.tv_usec;
current_column += fprintf(options.output, "%3lu.%06d ",
(unsigned long)diff.tv_sec,
(int)diff.tv_usec);
}
if (opt_t) {
struct timeval tv;
gettimeofday(&tv, NULL);
if (opt_t > 2) {
current_column += fprintf(options.output, "%lu.%06d ",
(unsigned long)tv.tv_sec,
(int)tv.tv_usec);
} else if (opt_t > 1) {
struct tm *tmp = localtime(&tv.tv_sec);
current_column +=
fprintf(options.output, "%02d:%02d:%02d.%06d ",
tmp->tm_hour, tmp->tm_min, tmp->tm_sec,
(int)tv.tv_usec);
} else {
struct tm *tmp = localtime(&tv.tv_sec);
current_column += fprintf(options.output, "%02d:%02d:%02d ",
tmp->tm_hour, tmp->tm_min,
tmp->tm_sec);
}
}
if (opt_i) {
if (is_func) {
struct callstack_element *stel
= &proc->callstack[proc->callstack_depth - 1];
current_column += fprintf(options.output, "[%p] ",
stel->return_addr);
} else {
current_column += fprintf(options.output, "[%p] ",
proc->instruction_pointer);
}
}
if (options.indent > 0 && indent) {
output_indent(proc);
}
}
static struct arg_type_info *
get_unknown_type(void)
{
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 struct prototype *
build_default_prototype(void)
{
static struct prototype *ret = NULL;
if (ret != NULL)
return ret;
static struct prototype proto;
prototype_init(&proto);
struct arg_type_info *unknown_type = get_unknown_type();
assert(unknown_type != NULL);
proto.return_info = unknown_type;
proto.own_return_info = 0;
struct param unknown_param;
param_init_type(&unknown_param, 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;
}
static bool
snip_period(char *buf)
{
char *period = strrchr(buf, '.');
if (period != NULL && strcmp(period, ".so") != 0) {
*period = 0;
return true;
} else {
return false;
}
}
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 (lib->protolib == NULL)
lib->protolib = protolib_cache_default(&g_protocache,
buf, 0);
}
if (lib->protolib == NULL)
return NULL;
return protolib_lookup_prototype(lib->protolib, name,
lib->type != LT_LIBTYPE_SYSCALL);
}
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 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 build_default_prototype();
}
void
output_line(struct process *proc, const char *fmt, ...)
{
if (options.summary)
return;
if (current_proc != NULL) {
if (current_proc->callstack[current_depth].return_addr)
fprintf(options.output, " <unfinished ...>\n");
else
fprintf(options.output, " <no return ...>\n");
}
current_proc = NULL;
if (fmt == NULL)
return;
begin_of_line(proc, 0, 0);
va_list args;
va_start(args, fmt);
vfprintf(options.output, fmt, args);
fprintf(options.output, "\n");
va_end(args);
current_column = 0;
}
static void
tabto(int col) {
if (current_column < col) {
fprintf(options.output, "%*s", col - current_column, "");
}
}
static int
output_error(FILE *stream)
{
return fprintf(stream, "?");
}
static int
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)
{
/* Arrays decay into pointers per C standard. We check for
* this here, because here we also capture arrays that come
* from parameter packs. */
if (info->type == ARGTYPE_ARRAY) {
struct arg_type_info *tmp = malloc(sizeof(*tmp));
if (tmp != NULL) {
type_init_pointer(tmp, info, own);
tmp->lens = info->lens;
info = tmp;
own = 1;
}
}
struct value value;
value_init(&value, proc, NULL, info, own);
if (fetch_arg_next(context, type, proc, info, &value) < 0)
return -1;
if (val_dict_push_next(arguments, &value) < 0) {
value_destroy(&value);
return -1;
}
if (valuep != NULL)
*valuep = value;
return 0;
}
static void
fetch_param_stop(struct value_dict *arguments, ssize_t *params_leftp)
{
if (*params_leftp == -1)
*params_leftp = val_dict_count(arguments);
}
static int
fetch_param_pack(enum tof type, struct process *proc,
struct fetch_context *context,
struct value_dict *arguments, struct param *param,
ssize_t *params_leftp)
{
struct param_enum *e = param_pack_init(param, arguments);
if (e == NULL)
return -1;
int ret = 0;
while (1) {
int insert_stop = 0;
struct arg_type_info *info = malloc(sizeof(*info));
if (info == NULL
|| param_pack_next(param, e, info, &insert_stop) < 0) {
fail:
free(info);
ret = -1;
break;
}
if (insert_stop)
fetch_param_stop(arguments, params_leftp);
if (info->type == ARGTYPE_VOID) {
type_destroy(info);
free(info);
break;
}
struct value val;
if (fetch_simple_param(type, proc, context, arguments,
info, 1, &val) < 0)
goto fail;
int stop = 0;
switch (param_pack_stop(param, e, &val)) {
case PPCB_ERR:
goto fail;
case PPCB_STOP:
stop = 1;
case PPCB_CONT:
break;
}
if (stop)
break;
}
param_pack_done(param, e);
return ret;
}
static int
fetch_one_param(enum tof type, struct process *proc,
struct fetch_context *context,
struct value_dict *arguments, struct param *param,
ssize_t *params_leftp)
{
switch (param->flavor) {
int rc;
case PARAM_FLAVOR_TYPE:
return fetch_simple_param(type, proc, context, arguments,
param->u.type.type, 0, NULL);
case PARAM_FLAVOR_PACK:
if (fetch_param_pack_start(context,
param->u.pack.ppflavor) < 0)
return -1;
rc = fetch_param_pack(type, proc, context, arguments,
param, params_leftp);
fetch_param_pack_end(context);
return rc;
case PARAM_FLAVOR_STOP:
fetch_param_stop(arguments, params_leftp);
return 0;
}
assert(!"Invalid param flavor!");
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, struct process *proc,
struct fetch_context *context,
struct value_dict *arguments, struct prototype *func,
ssize_t *params_leftp)
{
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);
return 0;
}
struct format_argument_data
{
struct value *value;
struct value_dict *arguments;
};
static int
format_argument_cb(FILE *stream, void *ptr)
{
struct format_argument_data *data = ptr;
int o = format_argument(stream, data->value, data->arguments);
if (o < 0)
o = output_error(stream);
return o;
}
static int
output_params(struct value_dict *arguments, size_t start, size_t end,
int *need_delimp)
{
size_t i;
for (i = start; i < end; ++i) {
struct value *value = val_dict_get_num(arguments, i);
if (value == NULL)
return -1;
struct format_argument_data data = { value, arguments };
int o = delim_output(options.output, need_delimp,
format_argument_cb, &data);
if (o < 0)
return -1;
current_column += o;
}
return 0;
}
void
output_left(enum tof type, struct process *proc,
struct library_symbol *libsym)
{
assert(! options.summary);
if (current_proc) {
fprintf(options.output, " <unfinished ...>\n");
current_column = 0;
}
current_proc = proc;
current_depth = proc->callstack_depth;
begin_of_line(proc, type == LT_TOF_FUNCTION, 1);
if (!options.hide_caller && libsym->lib != NULL
&& libsym->plt_type != LS_TOPLT_NONE)
/* We don't terribly mind failing this. */
account_output(¤t_column,
fprintf(options.output, "%s->",
libsym->lib->soname));
const char *name = libsym->name;
#ifdef USE_DEMANGLE
if (options.demangle)
name = my_demangle(libsym->name);
#endif
if (account_output(¤t_column,
fprintf(options.output, "%s", name)) < 0)
return;
if (libsym->lib != NULL
&& libsym->lib->type != LT_LIBTYPE_MAIN
&& libsym->plt_type == LS_TOPLT_NONE
&& account_output(¤t_column,
fprintf(options.output, "@%s",
libsym->lib->soname)) < 0)
/* We do mind failing this though. */
return;
account_output(¤t_column, fprintf(options.output, "("));
struct prototype *func = lookup_symbol_prototype(proc, 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) {
fetch_arg_done(context);
goto fail;
}
val_dict_init(arguments);
ssize_t params_left = -1;
int need_delim = 0;
if (fetch_params(type, proc, context, arguments, func, ¶ms_left) < 0
|| output_params(arguments, 0, params_left, &need_delim) < 0) {
val_dict_destroy(arguments);
fetch_arg_done(context);
arguments = NULL;
context = NULL;
}
struct callstack_element *stel
= &proc->callstack[proc->callstack_depth - 1];
stel->fetch_context = context;
stel->arguments = arguments;
stel->out.params_left = params_left;
stel->out.need_delim = need_delim;
}
#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)
{
Dwarf_Addr pc;
bool isactivation;
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");
}
}
}
/* 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 != NULL
&& (current_proc != proc
|| current_depth != proc->callstack_depth)) {
fprintf(options.output, " <unfinished ...>\n");
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(libsym->name)
: libsym->name);
#else
current_column +=
fprintf(options.output, "<... %s resumed> ", libsym->name);
#endif
}
struct callstack_element *stel
= &proc->callstack[proc->callstack_depth - 1];
struct fetch_context *context = stel->fetch_context;
/* Fetch & enter into dictionary the retval first, so that
* other values can use it in expressions. */
struct value retval;
bool own_retval = false;
if (context != NULL) {
value_init(&retval, proc, NULL, func->return_info, 0);
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 = false;
}
if (stel->arguments != NULL)
output_params(stel->arguments, stel->out.params_left,
val_dict_count(stel->arguments),
&stel->out.need_delim);
current_column += fprintf(options.output, ") ");
tabto(options.align - 1);
fprintf(options.output, "= ");
if (context != NULL && retval.type != NULL) {
struct format_argument_data data = { &retval, stel->arguments };
format_argument_cb(options.output, &data);
}
if (own_retval)
value_destroy(&retval);
if (opt_T) {
assert(spent != NULL);
fprintf(options.output, " <%lu.%06d>",
(unsigned long) spent->tm.tv_sec,
(int) spent->tm.tv_usec);
}
fprintf(options.output, "\n");
#if defined(HAVE_LIBUNWIND)
if (options.bt_depth > 0
&& proc->unwind_priv != NULL
&& proc->unwind_as != NULL) {
unw_cursor_t cursor;
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) {
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--;
}
fprintf(options.output, "\n");
}
#endif /* defined(HAVE_LIBUNWIND) */
#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;
}
int
delim_output(FILE *stream, int *need_delimp,
int (*writer)(FILE *stream, void *data),
void *data)
{
int o;
/* If we don't need a delimiter, then we don't need to go
* through a temporary stream. It's all the same whether
* WRITER emits anything or not. */
if (!*need_delimp) {
o = writer(stream, data);
} else {
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(ms.buf, 1, ms.size, stream) != ms.size))
o = -1;
memstream_destroy(&ms);
}
if (o < 0)
return -1;
*need_delimp = *need_delimp || o > 0;
return o;
}
int
account_output(int *countp, int c)
{
if (c > 0)
*countp += c;
return c;
}
static void
do_report(const char *filename, unsigned line_no, const char *severity,
const char *fmt, va_list args)
{
char buf[128];
vsnprintf(buf, sizeof(buf), fmt, args);
buf[sizeof(buf) - 1] = 0;
if (filename != NULL)
output_line(0, "%s:%d: %s: %s",
filename, line_no, severity, buf);
else
output_line(0, "%s: %s", severity, buf);
}
void
report_error(const char *filename, unsigned line_no, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
do_report(filename, line_no, "error", fmt, args);
va_end(args);
}
void
report_warning(const char *filename, unsigned line_no, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
do_report(filename, line_no, "warning", fmt, args);
va_end(args);
}
void
report_global_error(const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
do_report(NULL, 0, "error", fmt, args);
va_end(args);
}