/*
* Copyright (C) 2013 Paul Woegerer <paul_woegerer@mentor.com>
* Copyright (C) 2015 Antoine Busque <abusque@efficios.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#define _LGPL_SOURCE
#define _GNU_SOURCE
#include <link.h>
#include <limits.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <lttng/ust-elf.h>
#include "lttng-tracer-core.h"
#include "lttng-ust-statedump.h"
#define TRACEPOINT_DEFINE
#define TRACEPOINT_CREATE_PROBES
#define TP_SESSION_CHECK
#include "lttng-ust-statedump-provider.h"
struct dl_iterate_data {
void *owner;
int exec_found;
};
struct bin_info_data {
void *owner;
void *base_addr_ptr;
const char *resolved_path;
char *dbg_file;
uint8_t *build_id;
uint64_t memsz;
size_t build_id_len;
int vdso;
uint32_t crc;
uint8_t is_pic;
uint8_t has_build_id;
uint8_t has_debug_link;
};
typedef void (*tracepoint_cb)(struct lttng_session *session, void *priv);
/*
* Trace statedump event into all sessions owned by the caller thread
* for which statedump is pending.
*/
static
int trace_statedump_event(tracepoint_cb tp_cb, void *owner, void *priv)
{
struct cds_list_head *sessionsp;
struct lttng_session *session;
sessionsp = _lttng_get_sessions();
cds_list_for_each_entry(session, sessionsp, node) {
if (session->owner != owner)
continue;
if (!session->statedump_pending)
continue;
tp_cb(session, priv);
}
return 0;
}
static
void trace_bin_info_cb(struct lttng_session *session, void *priv)
{
struct bin_info_data *bin_data = (struct bin_info_data *) priv;
tracepoint(lttng_ust_statedump, bin_info,
session, bin_data->base_addr_ptr,
bin_data->resolved_path, bin_data->memsz,
bin_data->is_pic, bin_data->has_build_id,
bin_data->has_debug_link);
}
static
void trace_build_id_cb(struct lttng_session *session, void *priv)
{
struct bin_info_data *bin_data = (struct bin_info_data *) priv;
tracepoint(lttng_ust_statedump, build_id,
session, bin_data->base_addr_ptr,
bin_data->build_id, bin_data->build_id_len);
}
static
void trace_debug_link_cb(struct lttng_session *session, void *priv)
{
struct bin_info_data *bin_data = (struct bin_info_data *) priv;
tracepoint(lttng_ust_statedump, debug_link,
session, bin_data->base_addr_ptr,
bin_data->dbg_file, bin_data->crc);
}
static
void trace_start_cb(struct lttng_session *session, void *priv)
{
tracepoint(lttng_ust_statedump, start, session);
}
static
void trace_end_cb(struct lttng_session *session, void *priv)
{
tracepoint(lttng_ust_statedump, end, session);
}
static
int get_elf_info(struct bin_info_data *bin_data)
{
struct lttng_ust_elf *elf;
int ret = 0, found;
elf = lttng_ust_elf_create(bin_data->resolved_path);
if (!elf) {
ret = -1;
goto end;
}
ret = lttng_ust_elf_get_memsz(elf, &bin_data->memsz);
if (ret) {
goto end;
}
found = 0;
ret = lttng_ust_elf_get_build_id(elf, &bin_data->build_id,
&bin_data->build_id_len,
&found);
if (ret) {
goto end;
}
bin_data->has_build_id = !!found;
found = 0;
ret = lttng_ust_elf_get_debug_link(elf, &bin_data->dbg_file,
&bin_data->crc,
&found);
if (ret) {
goto end;
}
bin_data->has_debug_link = !!found;
bin_data->is_pic = lttng_ust_elf_is_pic(elf);
end:
lttng_ust_elf_destroy(elf);
return ret;
}
static
int trace_baddr(struct bin_info_data *bin_data)
{
int ret = 0;
if (!bin_data->vdso) {
ret = get_elf_info(bin_data);
if (ret) {
goto end;
}
} else {
bin_data->memsz = 0;
bin_data->has_build_id = 0;
bin_data->has_debug_link = 0;
}
ret = trace_statedump_event(trace_bin_info_cb, bin_data->owner,
bin_data);
if (ret) {
goto end;
}
if (bin_data->has_build_id) {
ret = trace_statedump_event(
trace_build_id_cb, bin_data->owner, bin_data);
free(bin_data->build_id);
if (ret) {
goto end;
}
}
if (bin_data->has_debug_link) {
ret = trace_statedump_event(
trace_debug_link_cb, bin_data->owner, bin_data);
free(bin_data->dbg_file);
if (ret) {
goto end;
}
}
end:
return ret;
}
static
int trace_statedump_start(void *owner)
{
return trace_statedump_event(trace_start_cb, owner, NULL);
}
static
int trace_statedump_end(void *owner)
{
return trace_statedump_event(trace_end_cb, owner, NULL);
}
static
int extract_bin_info_events(struct dl_phdr_info *info, size_t size, void *_data)
{
int j, ret = 0;
struct dl_iterate_data *data = _data;
/*
* UST lock nests within dynamic loader lock.
*
* Hold this lock across handling of the entire module to
* protect memory allocation at early process start, due to
* interactions with libc-wrapper lttng malloc instrumentation.
*/
if (ust_lock()) {
goto end;
}
for (j = 0; j < info->dlpi_phnum; j++) {
struct bin_info_data bin_data;
char resolved_path[PATH_MAX];
void *base_addr_ptr;
if (info->dlpi_phdr[j].p_type != PT_LOAD)
continue;
/* Calculate virtual memory address of the loadable segment */
base_addr_ptr = (void *) info->dlpi_addr +
info->dlpi_phdr[j].p_vaddr;
if ((info->dlpi_name == NULL || info->dlpi_name[0] == 0)) {
/*
* Only the first phdr without a dlpi_name
* encountered is considered as the program
* executable. The rest are vdsos.
*/
if (!data->exec_found) {
ssize_t path_len;
data->exec_found = 1;
/*
* Use /proc/self/exe to resolve the
* executable's full path.
*/
path_len = readlink("/proc/self/exe",
resolved_path,
PATH_MAX - 1);
if (path_len <= 0)
break;
resolved_path[path_len] = '\0';
bin_data.vdso = 0;
} else {
snprintf(resolved_path, PATH_MAX - 1, "[vdso]");
bin_data.vdso = 1;
}
} else {
/*
* For regular dl_phdr_info entries check if
* the path to the binary really exists. If not,
* treat as vdso and use dlpi_name as 'path'.
*/
if (!realpath(info->dlpi_name, resolved_path)) {
snprintf(resolved_path, PATH_MAX - 1, "[%s]",
info->dlpi_name);
bin_data.vdso = 1;
} else {
bin_data.vdso = 0;
}
}
bin_data.owner = data->owner;
bin_data.base_addr_ptr = base_addr_ptr;
bin_data.resolved_path = resolved_path;
ret = trace_baddr(&bin_data);
break;
}
end:
ust_unlock();
return ret;
}
/*
* Generate a statedump of base addresses of all shared objects loaded
* by the traced application, as well as for the application's
* executable itself.
*/
static
int do_baddr_statedump(void *owner)
{
struct dl_iterate_data data;
if (getenv("LTTNG_UST_WITHOUT_BADDR_STATEDUMP"))
return 0;
data.owner = owner;
data.exec_found = 0;
/*
* Iterate through the list of currently loaded shared objects and
* generate events for loadable segments using
* extract_bin_info_events.
*/
dl_iterate_phdr(extract_bin_info_events, &data);
return 0;
}
/*
* Generate a statedump of a given traced application. A statedump is
* delimited by start and end events. For a given (process, session)
* pair, begin/end events are serialized and will match. However, in a
* session, statedumps from different processes may be
* interleaved. The vpid context should be used to identify which
* events belong to which process.
*/
int do_lttng_ust_statedump(void *owner)
{
trace_statedump_start(owner);
do_baddr_statedump(owner);
trace_statedump_end(owner);
return 0;
}
void lttng_ust_statedump_init(void)
{
__tracepoints__init();
__tracepoints__ptrs_init();
__lttng_events_init__lttng_ust_statedump();
}
void lttng_ust_statedump_destroy(void)
{
__lttng_events_exit__lttng_ust_statedump();
__tracepoints__ptrs_destroy();
__tracepoints__destroy();
}