Commit 156d2238 authored by Ingo Molnar's avatar Ingo Molnar

Merge tag 'perf-core-for-mingo' of...

Merge tag 'perf-core-for-mingo' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/core

Pull perf/core improvements and fixes from Arnaldo Carvalho de Melo:

User visible fixes:

 - Handle spaces in file names obtained from /proc/pid/maps (Marcin Ślusarz)

New features:

 - Improved support for Java, using the JVMTI agent library to do jitdumps
   that then will be inserted in synthesized PERF_RECORD_MMAP2 events via
   'perf inject' pointed to synthesized ELF files stored in ~/.debug and
   keyed with build-ids, to allow symbol resolution and even annotation with
   source line info, see the changeset comments to see how to use it (Stephane Eranian)

Documentation changes:

 - Document mmore variables in the 'perf config' man page (Taeung Song)

Infrastructure changes:

 - Improve a bit the 'make -C tools/perf build-test' output (Arnaldo Carvalho de Melo)

 - Do 'build-test' in parallel, using 'make -j' (Arnaldo Carvalho de Melo)

 - Fix handling of 'clean' in multi-target make invokations for parallell builds (Jiri Olsa)
Signed-off-by: default avatarArnaldo Carvalho de Melo <acme@redhat.com>
Signed-off-by: default avatarIngo Molnar <mingo@kernel.org>
parents d0af1c05 598b7c69
......@@ -46,6 +46,7 @@ FEATURE_TESTS_BASIC := \
libpython \
libpython-version \
libslang \
libcrypto \
libunwind \
pthread-attr-setaffinity-np \
stackprotector-all \
......@@ -87,6 +88,7 @@ FEATURE_DISPLAY ?= \
libperl \
libpython \
libslang \
libcrypto \
libunwind \
libdw-dwarf-unwind \
zlib \
......
......@@ -23,6 +23,7 @@ FILES= \
test-libpython.bin \
test-libpython-version.bin \
test-libslang.bin \
test-libcrypto.bin \
test-libunwind.bin \
test-libunwind-debug-frame.bin \
test-pthread-attr-setaffinity-np.bin \
......@@ -105,6 +106,9 @@ $(OUTPUT)test-libaudit.bin:
$(OUTPUT)test-libslang.bin:
$(BUILD) -I/usr/include/slang -lslang
$(OUTPUT)test-libcrypto.bin:
$(BUILD) -lcrypto
$(OUTPUT)test-gtk2.bin:
$(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null)
......
......@@ -129,6 +129,10 @@
# include "test-bpf.c"
#undef main
#define main main_test_libcrypto
# include "test-libcrypto.c"
#undef main
int main(int argc, char *argv[])
{
main_test_libpython();
......@@ -158,6 +162,7 @@ int main(int argc, char *argv[])
main_test_lzma();
main_test_get_cpuid();
main_test_bpf();
main_test_libcrypto();
return 0;
}
#include <openssl/sha.h>
#include <openssl/md5.h>
int main(void)
{
MD5_CTX context;
unsigned char md[MD5_DIGEST_LENGTH + SHA_DIGEST_LENGTH];
unsigned char dat[] = "12345";
MD5_Init(&context);
MD5_Update(&context, &dat[0], sizeof(dat));
MD5_Final(&md[0], &context);
SHA1(&dat[0], sizeof(dat), &md[0]);
return 0;
}
......@@ -296,6 +296,149 @@ hist.*::
and 'baz' to 50.00% for each, while 'absolute' would show their
current overhead (33.33%).
ui.*::
ui.show-headers::
This option controls display of column headers (like 'Overhead' and 'Symbol')
in 'report' and 'top'. If this option is false, they are hidden.
This option is only applied to TUI.
call-graph.*::
When sub-commands 'top' and 'report' work with -g/—-children
there're options in control of call-graph.
call-graph.record-mode::
The record-mode can be 'fp' (frame pointer), 'dwarf' and 'lbr'.
The value of 'dwarf' is effective only if perf detect needed library
(libunwind or a recent version of libdw).
'lbr' only work for cpus that support it.
call-graph.dump-size::
The size of stack to dump in order to do post-unwinding. Default is 8192 (byte).
When using dwarf into record-mode, the default size will be used if omitted.
call-graph.print-type::
The print-types can be graph (graph absolute), fractal (graph relative),
flat and folded. This option controls a way to show overhead for each callchain
entry. Suppose a following example.
Overhead Symbols
........ .......
40.00% foo
|
---foo
|
|--50.00%--bar
| main
|
--50.00%--baz
main
This output is a 'fractal' format. The 'foo' came from 'bar' and 'baz' exactly
half and half so 'fractal' shows 50.00% for each
(meaning that it assumes 100% total overhead of 'foo').
The 'graph' uses absolute overhead value of 'foo' as total so each of
'bar' and 'baz' callchain will have 20.00% of overhead.
If 'flat' is used, single column and linear exposure of call chains.
'folded' mean call chains are displayed in a line, separated by semicolons.
call-graph.order::
This option controls print order of callchains. The default is
'callee' which means callee is printed at top and then followed by its
caller and so on. The 'caller' prints it in reverse order.
If this option is not set and report.children or top.children is
set to true (or the equivalent command line option is given),
the default value of this option is changed to 'caller' for the
execution of 'perf report' or 'perf top'. Other commands will
still default to 'callee'.
call-graph.sort-key::
The callchains are merged if they contain same information.
The sort-key option determines a way to compare the callchains.
A value of 'sort-key' can be 'function' or 'address'.
The default is 'function'.
call-graph.threshold::
When there're many callchains it'd print tons of lines. So perf omits
small callchains under a certain overhead (threshold) and this option
control the threshold. Default is 0.5 (%). The overhead is calculated
by value depends on call-graph.print-type.
call-graph.print-limit::
This is a maximum number of lines of callchain printed for a single
histogram entry. Default is 0 which means no limitation.
report.*::
report.percent-limit::
This one is mostly the same as call-graph.threshold but works for
histogram entries. Entries having an overhead lower than this
percentage will not be printed. Default is '0'. If percent-limit
is '10', only entries which have more than 10% of overhead will be
printed.
report.queue-size::
This option sets up the maximum allocation size of the internal
event queue for ordering events. Default is 0, meaning no limit.
report.children::
'Children' means functions called from another function.
If this option is true, 'perf report' cumulates callchains of children
and show (accumulated) total overhead as well as 'Self' overhead.
Please refer to the 'perf report' manual. The default is 'true'.
report.group::
This option is to show event group information together.
Example output with this turned on, notice that there is one column
per event in the group, ref-cycles and cycles:
# group: {ref-cycles,cycles}
# ========
#
# Samples: 7K of event 'anon group { ref-cycles, cycles }'
# Event count (approx.): 6876107743
#
# Overhead Command Shared Object Symbol
# ................ ....... ................. ...................
#
99.84% 99.76% noploop noploop [.] main
0.07% 0.00% noploop ld-2.15.so [.] strcmp
0.03% 0.00% noploop [kernel.kallsyms] [k] timerqueue_del
top.*::
top.children::
Same as 'report.children'. So if it is enabled, the output of 'top'
command will have 'Children' overhead column as well as 'Self' overhead
column by default.
The default is 'true'.
man.*::
man.viewer::
This option can assign a tool to view manual pages when 'help'
subcommand was invoked. Supported tools are 'man', 'woman'
(with emacs client) and 'konqueror'. Default is 'man'.
New man viewer tool can be also added using 'man.<tool>.cmd'
or use different path using 'man.<tool>.path' config option.
pager.*::
pager.<subcommand>::
When the subcommand is run on stdio, determine whether it uses
pager or not based on this value. Default is 'unspecified'.
kmem.*::
kmem.default::
This option decides which allocator is to be analyzed if neither
'--slab' nor '--page' option is used. Default is 'slab'.
record.*::
record.build-id::
This option can be 'cache', 'no-cache' or 'skip'.
'cache' is to post-process data and save/update the binaries into
the build-id cache (in ~/.debug). This is the default.
But if this option is 'no-cache', it will not update the build-id cache.
'skip' skips post-processing and does not update the cache.
SEE ALSO
--------
linkperf:perf[1]
......@@ -53,6 +53,13 @@ include::itrace.txt[]
--strip::
Use with --itrace to strip out non-synthesized events.
-j::
--jit::
Process jitdump files by injecting the mmap records corresponding to jitted
functions. This option also generates the ELF images for each jitted function
found in the jitdumps files captured in the input perf.data file. Use this option
if you are monitoring environment using JIT runtimes, such as Java, DART or V8.
SEE ALSO
--------
linkperf:perf-record[1], linkperf:perf-report[1], linkperf:perf-archive[1]
......@@ -68,6 +68,20 @@ all tags TAGS:
$(print_msg)
$(make)
ifdef MAKECMDGOALS
has_clean := 0
ifneq ($(filter clean,$(MAKECMDGOALS)),)
has_clean := 1
endif # clean
ifeq ($(has_clean),1)
rest := $(filter-out clean,$(MAKECMDGOALS))
ifneq ($(rest),)
$(rest): clean
endif # rest
endif # has_clean
endif # MAKECMDGOALS
#
# The clean target is not really parallel, don't print the jobs info:
#
......@@ -85,7 +99,7 @@ clean:
# make -C tools/perf -f tests/make
#
build-test:
@$(MAKE) SHUF=1 -f tests/make REUSE_FEATURES_DUMP=1 MK=Makefile --no-print-directory tarpkg out
@$(MAKE) SHUF=1 -f tests/make REUSE_FEATURES_DUMP=1 MK=Makefile SET_PARALLEL=1 --no-print-directory tarpkg out
#
# All other targets get passed through:
......
......@@ -58,6 +58,9 @@ include config/utilities.mak
#
# Define NO_LIBBIONIC if you do not want bionic support
#
# Define NO_LIBCRYPTO if you do not want libcrypto (openssl) support
# used for generating build-ids for ELFs generated by jitdump.
#
# Define NO_LIBDW_DWARF_UNWIND if you do not want libdw support
# for dwarf backtrace post unwind.
#
......
......@@ -17,6 +17,7 @@
#include "util/build-id.h"
#include "util/data.h"
#include "util/auxtrace.h"
#include "util/jit.h"
#include <subcmd/parse-options.h>
......@@ -29,6 +30,7 @@ struct perf_inject {
bool sched_stat;
bool have_auxtrace;
bool strip;
bool jit_mode;
const char *input_name;
struct perf_data_file output;
u64 bytes_written;
......@@ -71,6 +73,15 @@ static int perf_event__repipe_oe_synth(struct perf_tool *tool,
return perf_event__repipe_synth(tool, event);
}
#ifdef HAVE_LIBELF_SUPPORT
static int perf_event__drop_oe(struct perf_tool *tool __maybe_unused,
union perf_event *event __maybe_unused,
struct ordered_events *oe __maybe_unused)
{
return 0;
}
#endif
static int perf_event__repipe_op2_synth(struct perf_tool *tool,
union perf_event *event,
struct perf_session *session
......@@ -234,6 +245,27 @@ static int perf_event__repipe_mmap(struct perf_tool *tool,
return err;
}
#ifdef HAVE_LIBELF_SUPPORT
static int perf_event__jit_repipe_mmap(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
struct machine *machine)
{
struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
u64 n = 0;
/*
* if jit marker, then inject jit mmaps and generate ELF images
*/
if (!jit_process(inject->session, &inject->output, machine,
event->mmap.filename, sample->pid, &n)) {
inject->bytes_written += n;
return 0;
}
return perf_event__repipe_mmap(tool, event, sample, machine);
}
#endif
static int perf_event__repipe_mmap2(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
......@@ -247,6 +279,27 @@ static int perf_event__repipe_mmap2(struct perf_tool *tool,
return err;
}
#ifdef HAVE_LIBELF_SUPPORT
static int perf_event__jit_repipe_mmap2(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
struct machine *machine)
{
struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
u64 n = 0;
/*
* if jit marker, then inject jit mmaps and generate ELF images
*/
if (!jit_process(inject->session, &inject->output, machine,
event->mmap2.filename, sample->pid, &n)) {
inject->bytes_written += n;
return 0;
}
return perf_event__repipe_mmap2(tool, event, sample, machine);
}
#endif
static int perf_event__repipe_fork(struct perf_tool *tool,
union perf_event *event,
struct perf_sample *sample,
......@@ -664,6 +717,23 @@ static int __cmd_inject(struct perf_inject *inject)
return ret;
}
#ifdef HAVE_LIBELF_SUPPORT
static int
jit_validate_events(struct perf_session *session)
{
struct perf_evsel *evsel;
/*
* check that all events use CLOCK_MONOTONIC
*/
evlist__for_each(session->evlist, evsel) {
if (evsel->attr.use_clockid == 0 || evsel->attr.clockid != CLOCK_MONOTONIC)
return -1;
}
return 0;
}
#endif
int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused)
{
struct perf_inject inject = {
......@@ -703,7 +773,7 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused)
};
int ret;
const struct option options[] = {
struct option options[] = {
OPT_BOOLEAN('b', "build-ids", &inject.build_ids,
"Inject build-ids into the output stream"),
OPT_STRING('i', "input", &inject.input_name, "file",
......@@ -713,6 +783,7 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused)
OPT_BOOLEAN('s', "sched-stat", &inject.sched_stat,
"Merge sched-stat and sched-switch for getting events "
"where and how long tasks slept"),
OPT_BOOLEAN('j', "jit", &inject.jit_mode, "merge jitdump files into perf.data file"),
OPT_INCR('v', "verbose", &verbose,
"be more verbose (show build ids, etc)"),
OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, "file",
......@@ -729,7 +800,9 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused)
"perf inject [<options>]",
NULL
};
#ifndef HAVE_LIBELF_SUPPORT
set_option_nobuild(options, 'j', "jit", "NO_LIBELF=1", true);
#endif
argc = parse_options(argc, argv, options, inject_usage, 0);
/*
......@@ -755,6 +828,36 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused)
if (inject.session == NULL)
return -1;
if (inject.build_ids) {
/*
* to make sure the mmap records are ordered correctly
* and so that the correct especially due to jitted code
* mmaps. We cannot generate the buildid hit list and
* inject the jit mmaps at the same time for now.
*/
inject.tool.ordered_events = true;
inject.tool.ordering_requires_timestamps = true;
}
#ifdef HAVE_LIBELF_SUPPORT
if (inject.jit_mode) {
/*
* validate event is using the correct clockid
*/
if (jit_validate_events(inject.session)) {
fprintf(stderr, "error, jitted code must be sampled with perf record -k 1\n");
return -1;
}
inject.tool.mmap2 = perf_event__jit_repipe_mmap2;
inject.tool.mmap = perf_event__jit_repipe_mmap;
inject.tool.ordered_events = true;
inject.tool.ordering_requires_timestamps = true;
/*
* JIT MMAP injection injects all MMAP events in one go, so it
* does not obey finished_round semantics.
*/
inject.tool.finished_round = perf_event__drop_oe;
}
#endif
ret = symbol__init(&inject.session->header.env);
if (ret < 0)
goto out_delete;
......
......@@ -404,6 +404,17 @@ ifndef NO_LIBAUDIT
endif
endif
ifndef NO_LIBCRYPTO
ifneq ($(feature-libcrypto), 1)
msg := $(warning No libcrypto.h found, disables jitted code injection, please install libssl-devel or libssl-dev);
NO_LIBCRYPTO := 1
else
CFLAGS += -DHAVE_LIBCRYPTO_SUPPORT
EXTLIBS += -lcrypto
$(call detected,CONFIG_CRYPTO)
endif
endif
ifdef NO_NEWT
NO_SLANG=1
endif
......
ARCH=$(shell uname -m)
ifeq ($(ARCH), x86_64)
JARCH=amd64
endif
ifeq ($(ARCH), armv7l)
JARCH=armhf
endif
ifeq ($(ARCH), armv6l)
JARCH=armhf
endif
ifeq ($(ARCH), aarch64)
JARCH=aarch64
endif
ifeq ($(ARCH), ppc64)
JARCH=powerpc
endif
ifeq ($(ARCH), ppc64le)
JARCH=powerpc
endif
DESTDIR=/usr/local
VERSION=1
REVISION=0
AGE=0
LN=ln -sf
RM=rm
SLIBJVMTI=libjvmti.so.$(VERSION).$(REVISION).$(AGE)
VLIBJVMTI=libjvmti.so.$(VERSION)
SLDFLAGS=-shared -Wl,-soname -Wl,$(VLIBJVMTI)
SOLIBEXT=so
# The following works at least on fedora 23, you may need the next
# line for other distros.
JDIR=$(shell alternatives --display java | tail -1 | cut -d' ' -f 5 | sed 's%/jre/bin/java.%%g')
#JDIR=$(shell /usr/sbin/update-java-alternatives -l | head -1 | cut -d ' ' -f 3)
# -lrt required in 32-bit mode for clock_gettime()
LIBS=-lelf -lrt
INCDIR=-I $(JDIR)/include -I $(JDIR)/include/linux
TARGETS=$(SLIBJVMTI)
SRCS=libjvmti.c jvmti_agent.c
OBJS=$(SRCS:.c=.o)
SOBJS=$(OBJS:.o=.lo)
OPT=-O2 -g -Werror -Wall
CFLAGS=$(INCDIR) $(OPT)
all: $(TARGETS)
.c.o:
$(CC) $(CFLAGS) -c $*.c
.c.lo:
$(CC) -fPIC -DPIC $(CFLAGS) -c $*.c -o $*.lo
$(OBJS) $(SOBJS): Makefile jvmti_agent.h ../util/jitdump.h
$(SLIBJVMTI): $(SOBJS)
$(CC) $(CFLAGS) $(SLDFLAGS) -o $@ $(SOBJS) $(LIBS)
$(LN) $@ libjvmti.$(SOLIBEXT)
clean:
$(RM) -f *.o *.so.* *.so *.lo
install:
-mkdir -p $(DESTDIR)/lib
install -m 755 $(SLIBJVMTI) $(DESTDIR)/lib/
(cd $(DESTDIR)/lib; $(LN) $(SLIBJVMTI) $(VLIBJVMTI))
(cd $(DESTDIR)/lib; $(LN) $(SLIBJVMTI) libjvmti.$(SOLIBEXT))
ldconfig
.SUFFIXES: .c .S .o .lo
/*
* jvmti_agent.c: JVMTI agent interface
*
* Adapted from the Oprofile code in opagent.c:
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Copyright 2007 OProfile authors
* Jens Wilke
* Daniel Hansel
* Copyright IBM Corporation 2007
*/
#include <sys/types.h>
#include <sys/stat.h> /* for mkdir() */
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <sys/mman.h>
#include <syscall.h> /* for gettid() */
#include <err.h>
#include "jvmti_agent.h"
#include "../util/jitdump.h"
#define JIT_LANG "java"
static char jit_path[PATH_MAX];
static void *marker_addr;
/*
* padding buffer
*/
static const char pad_bytes[7];
static inline pid_t gettid(void)
{
return (pid_t)syscall(__NR_gettid);
}
static int get_e_machine(struct jitheader *hdr)
{
ssize_t sret;
char id[16];
int fd, ret = -1;
int m = -1;
struct {
uint16_t e_type;
uint16_t e_machine;
} info;
fd = open("/proc/self/exe", O_RDONLY);
if (fd == -1)
return -1;
sret = read(fd, id, sizeof(id));
if (sret != sizeof(id))
goto error;
/* check ELF signature */
if (id[0] != 0x7f || id[1] != 'E' || id[2] != 'L' || id[3] != 'F')
goto error;
sret = read(fd, &info, sizeof(info));
if (sret != sizeof(info))
goto error;
m = info.e_machine;
if (m < 0)
m = 0; /* ELF EM_NONE */
hdr->elf_mach = m;
ret = 0;
error:
close(fd);
return ret;
}
#define NSEC_PER_SEC 1000000000
static int perf_clk_id = CLOCK_MONOTONIC;
static inline uint64_t
timespec_to_ns(const struct timespec *ts)
{
return ((uint64_t) ts->tv_sec * NSEC_PER_SEC) + ts->tv_nsec;
}
static inline uint64_t
perf_get_timestamp(void)
{
struct timespec ts;
int ret;
ret = clock_gettime(perf_clk_id, &ts);
if (ret)
return 0;
return timespec_to_ns(&ts);
}
static int
debug_cache_init(void)
{
char str[32];
char *base, *p;
struct tm tm;
time_t t;
int ret;
time(&t);
localtime_r(&t, &tm);
base = getenv("JITDUMPDIR");
if (!base)
base = getenv("HOME");
if (!base)
base = ".";
strftime(str, sizeof(str), JIT_LANG"-jit-%Y%m%d", &tm);
snprintf(jit_path, PATH_MAX - 1, "%s/.debug/", base);
ret = mkdir(jit_path, 0755);
if (ret == -1) {
if (errno != EEXIST) {
warn("jvmti: cannot create jit cache dir %s", jit_path);
return -1;
}
}
snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit", base);
ret = mkdir(jit_path, 0755);
if (ret == -1) {
if (errno != EEXIST) {
warn("cannot create jit cache dir %s", jit_path);
return -1;
}
}
snprintf(jit_path, PATH_MAX - 1, "%s/.debug/jit/%s.XXXXXXXX", base, str);
p = mkdtemp(jit_path);
if (p != jit_path) {
warn("cannot create jit cache dir %s", jit_path);
return -1;
}
return 0;
}
static int
perf_open_marker_file(int fd)
{
long pgsz;
pgsz = sysconf(_SC_PAGESIZE);
if (pgsz == -1)
return -1;
/*
* we mmap the jitdump to create an MMAP RECORD in perf.data file.
* The mmap is captured either live (perf record running when we mmap)
* or in deferred mode, via /proc/PID/maps
* the MMAP record is used as a marker of a jitdump file for more meta
* data info about the jitted code. Perf report/annotate detect this
* special filename and process the jitdump file.
*
* mapping must be PROT_EXEC to ensure it is captured by perf record
* even when not using -d option
*/
marker_addr = mmap(NULL, pgsz, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
return (marker_addr == MAP_FAILED) ? -1 : 0;
}
static void
perf_close_marker_file(void)
{
long pgsz;
if (!marker_addr)
return;
pgsz = sysconf(_SC_PAGESIZE);
if (pgsz == -1)
return;
munmap(marker_addr, pgsz);
}
void *jvmti_open(void)
{
int pad_cnt;
char dump_path[PATH_MAX];
struct jitheader header;
int fd;
FILE *fp;
/*
* check if clockid is supported
*/
if (!perf_get_timestamp())
warnx("jvmti: kernel does not support %d clock id", perf_clk_id);
memset(&header, 0, sizeof(header));
debug_cache_init();
/*
* jitdump file name
*/
snprintf(dump_path, PATH_MAX, "%s/jit-%i.dump", jit_path, getpid());
fd = open(dump_path, O_CREAT|O_TRUNC|O_RDWR, 0666);
if (fd == -1)
return NULL;
/*
* create perf.data maker for the jitdump file
*/
if (perf_open_marker_file(fd)) {
warnx("jvmti: failed to create marker file");
return NULL;
}
fp = fdopen(fd, "w+");
if (!fp) {
warn("jvmti: cannot create %s", dump_path);
close(fd);
goto error;
}
warnx("jvmti: jitdump in %s", dump_path);
if (get_e_machine(&header)) {
warn("get_e_machine failed\n");
goto error;
}
header.magic = JITHEADER_MAGIC;
header.version = JITHEADER_VERSION;
header.total_size = sizeof(header);
header.pid = getpid();
/* calculate amount of padding '\0' */
pad_cnt = PADDING_8ALIGNED(header.total_size);
header.total_size += pad_cnt;
header.timestamp = perf_get_timestamp();
if (!fwrite(&header, sizeof(header), 1, fp)) {
warn("jvmti: cannot write dumpfile header");
goto error;
}
/* write padding '\0' if necessary */
if (pad_cnt && !fwrite(pad_bytes, pad_cnt, 1, fp)) {
warn("jvmti: cannot write dumpfile header padding");
goto error;
}
return fp;
error:
fclose(fp);
return NULL;
}
int
jvmti_close(void *agent)
{
struct jr_code_close rec;
FILE *fp = agent;
if (!fp) {
warnx("jvmti: incalid fd in close_agent");
return -1;
}
rec.p.id = JIT_CODE_CLOSE;
rec.p.total_size = sizeof(rec);
rec.p.timestamp = perf_get_timestamp();
if (!fwrite(&rec, sizeof(rec), 1, fp))
return -1;
fclose(fp);
fp = NULL;
perf_close_marker_file();
return 0;
}
int
jvmti_write_code(void *agent, char const *sym,
uint64_t vma, void const *code, unsigned int const size)
{
static int code_generation = 1;
struct jr_code_load rec;
size_t sym_len;
size_t padding_count;
FILE *fp = agent;
int ret = -1;
/* don't care about 0 length function, no samples */
if (size == 0)
return 0;
if (!fp) {
warnx("jvmti: invalid fd in write_native_code");
return -1;
}
sym_len = strlen(sym) + 1;
rec.p.id = JIT_CODE_LOAD;
rec.p.total_size = sizeof(rec) + sym_len;
padding_count = PADDING_8ALIGNED(rec.p.total_size);
rec.p. total_size += padding_count;
rec.p.timestamp = perf_get_timestamp();
rec.code_size = size;
rec.vma = vma;
rec.code_addr = vma;
rec.pid = getpid();
rec.tid = gettid();
if (code)
rec.p.total_size += size;
/*
* If JVM is multi-threaded, nultiple concurrent calls to agent
* may be possible, so protect file writes
*/
flockfile(fp);
/*
* get code index inside lock to avoid race condition
*/
rec.code_index = code_generation++;
ret = fwrite_unlocked(&rec, sizeof(rec), 1, fp);
fwrite_unlocked(sym, sym_len, 1, fp);
if (padding_count)
fwrite_unlocked(pad_bytes, padding_count, 1, fp);
if (code)
fwrite_unlocked(code, size, 1, fp);
funlockfile(fp);
ret = 0;
return ret;
}
int
jvmti_write_debug_info(void *agent, uint64_t code, const char *file,
jvmti_line_info_t *li, int nr_lines)
{
struct jr_code_debug_info rec;
size_t sret, len, size, flen;
size_t padding_count;
uint64_t addr;
const char *fn = file;
FILE *fp = agent;
int i;
/*
* no entry to write
*/
if (!nr_lines)
return 0;
if (!fp) {
warnx("jvmti: invalid fd in write_debug_info");
return -1;
}
flen = strlen(file) + 1;
rec.p.id = JIT_CODE_DEBUG_INFO;
size = sizeof(rec);
rec.p.timestamp = perf_get_timestamp();
rec.code_addr = (uint64_t)(uintptr_t)code;
rec.nr_entry = nr_lines;
/*
* on disk source line info layout:
* uint64_t : addr
* int : line number
* int : column discriminator
* file[] : source file name
* padding : pad to multiple of 8 bytes
*/
size += nr_lines * sizeof(struct debug_entry);
size += flen * nr_lines;
/*
* pad to 8 bytes
*/
padding_count = PADDING_8ALIGNED(size);
rec.p.total_size = size + padding_count;
/*
* If JVM is multi-threaded, nultiple concurrent calls to agent
* may be possible, so protect file writes
*/
flockfile(fp);
sret = fwrite_unlocked(&rec, sizeof(rec), 1, fp);
if (sret != 1)
goto error;
for (i = 0; i < nr_lines; i++) {
addr = (uint64_t)li[i].pc;
len = sizeof(addr);
sret = fwrite_unlocked(&addr, len, 1, fp);
if (sret != 1)
goto error;
len = sizeof(li[0].line_number);
sret = fwrite_unlocked(&li[i].line_number, len, 1, fp);
if (sret != 1)
goto error;
len = sizeof(li[0].discrim);
sret = fwrite_unlocked(&li[i].discrim, len, 1, fp);
if (sret != 1)
goto error;
sret = fwrite_unlocked(fn, flen, 1, fp);
if (sret != 1)
goto error;
}
if (padding_count)
sret = fwrite_unlocked(pad_bytes, padding_count, 1, fp);
if (sret != 1)
goto error;
funlockfile(fp);
return 0;
error:
funlockfile(fp);
return -1;
}
#ifndef __JVMTI_AGENT_H__
#define __JVMTI_AGENT_H__
#include <sys/types.h>
#include <stdint.h>
#include <jvmti.h>
#define __unused __attribute__((unused))
#if defined(__cplusplus)
extern "C" {
#endif
typedef struct {
unsigned long pc;
int line_number;
int discrim; /* discriminator -- 0 for now */
} jvmti_line_info_t;
void *jvmti_open(void);
int jvmti_close(void *agent);
int jvmti_write_code(void *agent, char const *symbol_name,
uint64_t vma, void const *code,
const unsigned int code_size);
int jvmti_write_debug_info(void *agent,
uint64_t code,
const char *file,
jvmti_line_info_t *li,
int nr_lines);
#if defined(__cplusplus)
}
#endif
#endif /* __JVMTI_H__ */
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <err.h>
#include <jvmti.h>
#include <jvmticmlr.h>
#include <limits.h>
#include "jvmti_agent.h"
static int has_line_numbers;
void *jvmti_agent;
static jvmtiError
do_get_line_numbers(jvmtiEnv *jvmti, void *pc, jmethodID m, jint bci,
jvmti_line_info_t *tab, jint *nr)
{
jint i, lines = 0;
jint nr_lines = 0;
jvmtiLineNumberEntry *loc_tab = NULL;
jvmtiError ret;
ret = (*jvmti)->GetLineNumberTable(jvmti, m, &nr_lines, &loc_tab);
if (ret != JVMTI_ERROR_NONE)
return ret;
for (i = 0; i < nr_lines; i++) {
if (loc_tab[i].start_location < bci) {
tab[lines].pc = (unsigned long)pc;
tab[lines].line_number = loc_tab[i].line_number;
tab[lines].discrim = 0; /* not yet used */
lines++;
} else {
break;
}
}
(*jvmti)->Deallocate(jvmti, (unsigned char *)loc_tab);
*nr = lines;
return JVMTI_ERROR_NONE;
}
static jvmtiError
get_line_numbers(jvmtiEnv *jvmti, const void *compile_info, jvmti_line_info_t **tab, int *nr_lines)
{
const jvmtiCompiledMethodLoadRecordHeader *hdr;
jvmtiCompiledMethodLoadInlineRecord *rec;
jvmtiLineNumberEntry *lne = NULL;
PCStackInfo *c;
jint nr, ret;
int nr_total = 0;
int i, lines_total = 0;
if (!(tab && nr_lines))
return JVMTI_ERROR_NULL_POINTER;
/*
* Phase 1 -- get the number of lines necessary
*/
for (hdr = compile_info; hdr != NULL; hdr = hdr->next) {
if (hdr->kind == JVMTI_CMLR_INLINE_INFO) {
rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr;
for (i = 0; i < rec->numpcs; i++) {
c = rec->pcinfo + i;
nr = 0;
/*
* unfortunately, need a tab to get the number of lines!
*/
ret = (*jvmti)->GetLineNumberTable(jvmti, c->methods[0], &nr, &lne);
if (ret == JVMTI_ERROR_NONE) {
/* free what was allocated for nothing */
(*jvmti)->Deallocate(jvmti, (unsigned char *)lne);
nr_total += (int)nr;
}
}
}
}
if (nr_total == 0)
return JVMTI_ERROR_NOT_FOUND;
/*
* Phase 2 -- allocate big enough line table
*/
*tab = malloc(nr_total * sizeof(**tab));
if (!*tab)
return JVMTI_ERROR_OUT_OF_MEMORY;
for (hdr = compile_info; hdr != NULL; hdr = hdr->next) {
if (hdr->kind == JVMTI_CMLR_INLINE_INFO) {
rec = (jvmtiCompiledMethodLoadInlineRecord *)hdr;
for (i = 0; i < rec->numpcs; i++) {
c = rec->pcinfo + i;
nr = 0;
ret = do_get_line_numbers(jvmti, c->pc,
c->methods[0],
c->bcis[0],
*tab + lines_total,
&nr);
if (ret == JVMTI_ERROR_NONE)
lines_total += nr;
}
}
}
*nr_lines = lines_total;
return JVMTI_ERROR_NONE;
}
static void JNICALL
compiled_method_load_cb(jvmtiEnv *jvmti,
jmethodID method,
jint code_size,
void const *code_addr,
jint map_length,
jvmtiAddrLocationMap const *map,
const void *compile_info)
{
jvmti_line_info_t *line_tab = NULL;
jclass decl_class;
char *class_sign = NULL;
char *func_name = NULL;
char *func_sign = NULL;
char *file_name= NULL;
char fn[PATH_MAX];
uint64_t addr = (uint64_t)(uintptr_t)code_addr;
jvmtiError ret;
int nr_lines = 0; /* in line_tab[] */
size_t len;
ret = (*jvmti)->GetMethodDeclaringClass(jvmti, method,
&decl_class);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: cannot get declaring class");
return;
}
if (has_line_numbers && map && map_length) {
ret = get_line_numbers(jvmti, compile_info, &line_tab, &nr_lines);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: cannot get line table for method");
nr_lines = 0;
}
}
ret = (*jvmti)->GetSourceFileName(jvmti, decl_class, &file_name);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: cannot get source filename ret=%d", ret);
goto error;
}
ret = (*jvmti)->GetClassSignature(jvmti, decl_class,
&class_sign, NULL);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: getclassignature failed");
goto error;
}
ret = (*jvmti)->GetMethodName(jvmti, method, &func_name,
&func_sign, NULL);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: failed getmethodname");
goto error;
}
/*
* Assume path name is class hierarchy, this is a common practice with Java programs
*/
if (*class_sign == 'L') {
int j, i = 0;
char *p = strrchr(class_sign, '/');
if (p) {
/* drop the 'L' prefix and copy up to the final '/' */
for (i = 0; i < (p - class_sign); i++)
fn[i] = class_sign[i+1];
}
/*
* append file name, we use loops and not string ops to avoid modifying
* class_sign which is used later for the symbol name
*/
for (j = 0; i < (PATH_MAX - 1) && file_name && j < strlen(file_name); j++, i++)
fn[i] = file_name[j];
fn[i] = '\0';
} else {
/* fallback case */
strcpy(fn, file_name);
}
/*
* write source line info record if we have it
*/
if (jvmti_write_debug_info(jvmti_agent, addr, fn, line_tab, nr_lines))
warnx("jvmti: write_debug_info() failed");
len = strlen(func_name) + strlen(class_sign) + strlen(func_sign) + 2;
{
char str[len];
snprintf(str, len, "%s%s%s", class_sign, func_name, func_sign);
if (jvmti_write_code(jvmti_agent, str, addr, code_addr, code_size))
warnx("jvmti: write_code() failed");
}
error:
(*jvmti)->Deallocate(jvmti, (unsigned char *)func_name);
(*jvmti)->Deallocate(jvmti, (unsigned char *)func_sign);
(*jvmti)->Deallocate(jvmti, (unsigned char *)class_sign);
(*jvmti)->Deallocate(jvmti, (unsigned char *)file_name);
free(line_tab);
}
static void JNICALL
code_generated_cb(jvmtiEnv *jvmti,
char const *name,
void const *code_addr,
jint code_size)
{
uint64_t addr = (uint64_t)(unsigned long)code_addr;
int ret;
ret = jvmti_write_code(jvmti_agent, name, addr, code_addr, code_size);
if (ret)
warnx("jvmti: write_code() failed for code_generated");
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved __unused)
{
jvmtiEventCallbacks cb;
jvmtiCapabilities caps1;
jvmtiJlocationFormat format;
jvmtiEnv *jvmti = NULL;
jint ret;
jvmti_agent = jvmti_open();
if (!jvmti_agent) {
warnx("jvmti: open_agent failed");
return -1;
}
/*
* Request a JVMTI interface version 1 environment
*/
ret = (*jvm)->GetEnv(jvm, (void *)&jvmti, JVMTI_VERSION_1);
if (ret != JNI_OK) {
warnx("jvmti: jvmti version 1 not supported");
return -1;
}
/*
* acquire method_load capability, we require it
* request line numbers (optional)
*/
memset(&caps1, 0, sizeof(caps1));
caps1.can_generate_compiled_method_load_events = 1;
ret = (*jvmti)->AddCapabilities(jvmti, &caps1);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: acquire compiled_method capability failed");
return -1;
}
ret = (*jvmti)->GetJLocationFormat(jvmti, &format);
if (ret == JVMTI_ERROR_NONE && format == JVMTI_JLOCATION_JVMBCI) {
memset(&caps1, 0, sizeof(caps1));
caps1.can_get_line_numbers = 1;
caps1.can_get_source_file_name = 1;
ret = (*jvmti)->AddCapabilities(jvmti, &caps1);
if (ret == JVMTI_ERROR_NONE)
has_line_numbers = 1;
}
memset(&cb, 0, sizeof(cb));
cb.CompiledMethodLoad = compiled_method_load_cb;
cb.DynamicCodeGenerated = code_generated_cb;
ret = (*jvmti)->SetEventCallbacks(jvmti, &cb, sizeof(cb));
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: cannot set event callbacks");
return -1;
}
ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: setnotification failed for method_load");
return -1;
}
ret = (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE,
JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);
if (ret != JVMTI_ERROR_NONE) {
warnx("jvmti: setnotification failed on code_generated");
return -1;
}
return 0;
}
JNIEXPORT void JNICALL
Agent_OnUnload(JavaVM *jvm __unused)
{
int ret;
ret = jvmti_close(jvmti_agent);
if (ret)
errx(1, "Error: op_close_agent()");
}
......@@ -80,6 +80,7 @@ make_no_libaudit := NO_LIBAUDIT=1
make_no_libbionic := NO_LIBBIONIC=1
make_no_auxtrace := NO_AUXTRACE=1
make_no_libbpf := NO_LIBBPF=1
make_no_libcrypto := NO_LIBCRYPTO=1
make_tags := tags
make_cscope := cscope
make_help := help
......@@ -103,6 +104,7 @@ make_minimal := NO_LIBPERL=1 NO_LIBPYTHON=1 NO_NEWT=1 NO_GTK2=1
make_minimal += NO_DEMANGLE=1 NO_LIBELF=1 NO_LIBUNWIND=1 NO_BACKTRACE=1
make_minimal += NO_LIBNUMA=1 NO_LIBAUDIT=1 NO_LIBBIONIC=1
make_minimal += NO_LIBDW_DWARF_UNWIND=1 NO_AUXTRACE=1 NO_LIBBPF=1
make_minimal += NO_LIBCRYPTO=1
# $(run) contains all available tests
run := make_pure
......@@ -111,6 +113,9 @@ run := make_pure
# disable features detection
ifeq ($(MK),Makefile)
run += make_clean_all
MAKE_F := $(MAKE)
else
MAKE_F := $(MAKE) -f $(MK)
endif
run += make_python_perf_so
run += make_debug
......@@ -270,12 +275,12 @@ endif
MAKEFLAGS := --no-print-directory
clean := @(cd $(PERF); make -s -f $(MK) $(O_OPT) clean >/dev/null)
clean := @(cd $(PERF); $(MAKE_F) -s $(O_OPT) clean >/dev/null)
$(run):
$(call clean)
@TMP_DEST=$$(mktemp -d); \
cmd="cd $(PERF) && make -f $(MK) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST $($@)"; \
cmd="cd $(PERF) && $(MAKE_F) $($@) $(PARALLEL_OPT) $(O_OPT) DESTDIR=$$TMP_DEST"; \
printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \
( eval $$cmd ) >> $@ 2>&1; \
echo " test: $(call test,$@)" >> $@ 2>&1; \
......@@ -286,7 +291,7 @@ $(run_O):
$(call clean)
@TMP_O=$$(mktemp -d); \
TMP_DEST=$$(mktemp -d); \
cmd="cd $(PERF) && make -f $(MK) $(PARALLEL_OPT) O=$$TMP_O DESTDIR=$$TMP_DEST $($(patsubst %_O,%,$@))"; \
cmd="cd $(PERF) && $(MAKE_F) $($(patsubst %_O,%,$@)) $(PARALLEL_OPT) O=$$TMP_O DESTDIR=$$TMP_DEST"; \
printf "%*.*s: %s\n" $(max_width) $(max_width) "$@" "$$cmd" && echo $$cmd > $@ && \
( eval $$cmd ) >> $@ 2>&1 && \
echo " test: $(call test_O,$@)" >> $@ 2>&1; \
......
......@@ -105,8 +105,14 @@ libperf-y += scripting-engines/
libperf-$(CONFIG_ZLIB) += zlib.o
libperf-$(CONFIG_LZMA) += lzma.o
libperf-y += demangle-java.o
libperf-$(CONFIG_LIBELF) += jitdump.o
libperf-$(CONFIG_LIBELF) += genelf.o
libperf-$(CONFIG_LIBELF) += genelf_debug.o
CFLAGS_config.o += -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))"
# avoid compiler warnings in 32-bit mode
CFLAGS_genelf_debug.o += -Wno-packed
$(OUTPUT)util/parse-events-flex.c: util/parse-events.l $(OUTPUT)util/parse-events-bison.c
$(call rule_mkdir)
......
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include "util.h"
#include "debug.h"
#include "symbol.h"
#include "demangle-java.h"
enum {
MODE_PREFIX = 0,
MODE_CLASS = 1,
MODE_FUNC = 2,
MODE_TYPE = 3,
MODE_CTYPE = 3, /* class arg */
};
#define BASE_ENT(c, n) [c - 'A']=n
static const char *base_types['Z' - 'A' + 1] = {
BASE_ENT('B', "byte" ),
BASE_ENT('C', "char" ),
BASE_ENT('D', "double" ),
BASE_ENT('F', "float" ),
BASE_ENT('I', "int" ),
BASE_ENT('J', "long" ),
BASE_ENT('S', "short" ),
BASE_ENT('Z', "bool" ),
};
/*
* demangle Java symbol between str and end positions and stores
* up to maxlen characters into buf. The parser starts in mode.
*
* Use MODE_PREFIX to process entire prototype till end position
* Use MODE_TYPE to process return type if str starts on return type char
*
* Return:
* success: buf
* error : NULL
*/
static char *
__demangle_java_sym(const char *str, const char *end, char *buf, int maxlen, int mode)
{
int rlen = 0;
int array = 0;
int narg = 0;
const char *q;
if (!end)
end = str + strlen(str);
for (q = str; q != end; q++) {
if (rlen == (maxlen - 1))
break;
switch (*q) {
case 'L':
if (mode == MODE_PREFIX || mode == MODE_CTYPE) {
if (mode == MODE_CTYPE) {
if (narg)
rlen += scnprintf(buf + rlen, maxlen - rlen, ", ");
narg++;
}
rlen += scnprintf(buf + rlen, maxlen - rlen, "class ");
if (mode == MODE_PREFIX)
mode = MODE_CLASS;
} else
buf[rlen++] = *q;
break;
case 'B':
case 'C':
case 'D':
case 'F':
case 'I':
case 'J':
case 'S':
case 'Z':
if (mode == MODE_TYPE) {
if (narg)
rlen += scnprintf(buf + rlen, maxlen - rlen, ", ");
rlen += scnprintf(buf + rlen, maxlen - rlen, "%s", base_types[*q - 'A']);
while (array--)
rlen += scnprintf(buf + rlen, maxlen - rlen, "[]");
array = 0;
narg++;
} else
buf[rlen++] = *q;
break;
case 'V':
if (mode == MODE_TYPE) {
rlen += scnprintf(buf + rlen, maxlen - rlen, "void");
while (array--)
rlen += scnprintf(buf + rlen, maxlen - rlen, "[]");
array = 0;
} else
buf[rlen++] = *q;
break;
case '[':
if (mode != MODE_TYPE)
goto error;
array++;
break;
case '(':
if (mode != MODE_FUNC)
goto error;
buf[rlen++] = *q;
mode = MODE_TYPE;
break;
case ')':
if (mode != MODE_TYPE)
goto error;
buf[rlen++] = *q;
narg = 0;
break;
case ';':
if (mode != MODE_CLASS && mode != MODE_CTYPE)
goto error;
/* safe because at least one other char to process */
if (isalpha(*(q + 1)))
rlen += scnprintf(buf + rlen, maxlen - rlen, ".");
if (mode == MODE_CLASS)
mode = MODE_FUNC;
else if (mode == MODE_CTYPE)
mode = MODE_TYPE;
break;
case '/':
if (mode != MODE_CLASS && mode != MODE_CTYPE)
goto error;
rlen += scnprintf(buf + rlen, maxlen - rlen, ".");
break;
default :
buf[rlen++] = *q;
}
}
buf[rlen] = '\0';
return buf;
error:
return NULL;
}
/*
* Demangle Java function signature (openJDK, not GCJ)
* input:
* str: string to parse. String is not modified
* flags: comobination of JAVA_DEMANGLE_* flags to modify demangling
* return:
* if input can be demangled, then a newly allocated string is returned.
* if input cannot be demangled, then NULL is returned
*
* Note: caller is responsible for freeing demangled string
*/
char *
java_demangle_sym(const char *str, int flags)
{
char *buf, *ptr;
char *p;
size_t len, l1 = 0;
if (!str)
return NULL;
/* find start of retunr type */
p = strrchr(str, ')');
if (!p)
return NULL;
/*
* expansion factor estimated to 3x
*/
len = strlen(str) * 3 + 1;
buf = malloc(len);
if (!buf)
return NULL;
buf[0] = '\0';
if (!(flags & JAVA_DEMANGLE_NORET)) {
/*
* get return type first
*/
ptr = __demangle_java_sym(p + 1, NULL, buf, len, MODE_TYPE);
if (!ptr)
goto error;
/* add space between return type and function prototype */
l1 = strlen(buf);
buf[l1++] = ' ';
}
/* process function up to return type */
ptr = __demangle_java_sym(str, p + 1, buf + l1, len - l1, MODE_PREFIX);
if (!ptr)
goto error;
return buf;
error:
free(buf);
return NULL;
}
#ifndef __PERF_DEMANGLE_JAVA
#define __PERF_DEMANGLE_JAVA 1
/*
* demangle function flags
*/
#define JAVA_DEMANGLE_NORET 0x1 /* do not process return type */
char * java_demangle_sym(const char *str, int flags);
#endif /* __PERF_DEMANGLE_JAVA */
......@@ -282,7 +282,7 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool,
strcpy(execname, "");
/* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %x:%x %u %s\n",
n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %x:%x %u %[^\n]\n",
&event->mmap2.start, &event->mmap2.len, prot,
&event->mmap2.pgoff, &event->mmap2.maj,
&event->mmap2.min,
......
/*
* genelf.c
* Copyright (C) 2014, Google, Inc
*
* Contributed by:
* Stephane Eranian <eranian@gmail.com>
*
* Released under the GPL v2. (and only v2, not any later version)
*/
#include <sys/types.h>
#include <stdio.h>
#include <getopt.h>
#include <stddef.h>
#include <libelf.h>
#include <string.h>
#include <stdlib.h>
#include <inttypes.h>
#include <limits.h>
#include <fcntl.h>
#include <err.h>
#include <dwarf.h>
#include "perf.h"
#include "genelf.h"
#include "../util/jitdump.h"
#define JVMTI
#define BUILD_ID_URANDOM /* different uuid for each run */
#ifdef HAVE_LIBCRYPTO
#define BUILD_ID_MD5
#undef BUILD_ID_SHA /* does not seem to work well when linked with Java */
#undef BUILD_ID_URANDOM /* different uuid for each run */
#ifdef BUILD_ID_SHA
#include <openssl/sha.h>
#endif
#ifdef BUILD_ID_MD5
#include <openssl/md5.h>
#endif
#endif
typedef struct {
unsigned int namesz; /* Size of entry's owner string */
unsigned int descsz; /* Size of the note descriptor */
unsigned int type; /* Interpretation of the descriptor */
char name[0]; /* Start of the name+desc data */
} Elf_Note;
struct options {
char *output;
int fd;
};
static char shd_string_table[] = {
0,
'.', 't', 'e', 'x', 't', 0, /* 1 */
'.', 's', 'h', 's', 't', 'r', 't', 'a', 'b', 0, /* 7 */
'.', 's', 'y', 'm', 't', 'a', 'b', 0, /* 17 */
'.', 's', 't', 'r', 't', 'a', 'b', 0, /* 25 */
'.', 'n', 'o', 't', 'e', '.', 'g', 'n', 'u', '.', 'b', 'u', 'i', 'l', 'd', '-', 'i', 'd', 0, /* 33 */
'.', 'd', 'e', 'b', 'u', 'g', '_', 'l', 'i', 'n', 'e', 0, /* 52 */
'.', 'd', 'e', 'b', 'u', 'g', '_', 'i', 'n', 'f', 'o', 0, /* 64 */
'.', 'd', 'e', 'b', 'u', 'g', '_', 'a', 'b', 'b', 'r', 'e', 'v', 0, /* 76 */
};
static struct buildid_note {
Elf_Note desc; /* descsz: size of build-id, must be multiple of 4 */
char name[4]; /* GNU\0 */
char build_id[20];
} bnote;
static Elf_Sym symtab[]={
/* symbol 0 MUST be the undefined symbol */
{ .st_name = 0, /* index in sym_string table */
.st_info = ELF_ST_TYPE(STT_NOTYPE),
.st_shndx = 0, /* for now */
.st_value = 0x0,
.st_other = ELF_ST_VIS(STV_DEFAULT),
.st_size = 0,
},
{ .st_name = 1, /* index in sym_string table */
.st_info = ELF_ST_BIND(STB_LOCAL) | ELF_ST_TYPE(STT_FUNC),
.st_shndx = 1,
.st_value = 0, /* for now */
.st_other = ELF_ST_VIS(STV_DEFAULT),
.st_size = 0, /* for now */
}
};
#ifdef BUILD_ID_URANDOM
static void
gen_build_id(struct buildid_note *note,
unsigned long load_addr __maybe_unused,
const void *code __maybe_unused,
size_t csize __maybe_unused)
{
int fd;
size_t sz = sizeof(note->build_id);
ssize_t sret;
fd = open("/dev/urandom", O_RDONLY);
if (fd == -1)
err(1, "cannot access /dev/urandom for builid");
sret = read(fd, note->build_id, sz);
close(fd);
if (sret != (ssize_t)sz)
memset(note->build_id, 0, sz);
}
#endif
#ifdef BUILD_ID_SHA
static void
gen_build_id(struct buildid_note *note,
unsigned long load_addr __maybe_unused,
const void *code,
size_t csize)
{
if (sizeof(note->build_id) < SHA_DIGEST_LENGTH)
errx(1, "build_id too small for SHA1");
SHA1(code, csize, (unsigned char *)note->build_id);
}
#endif
#ifdef BUILD_ID_MD5
static void
gen_build_id(struct buildid_note *note, unsigned long load_addr, const void *code, size_t csize)
{
MD5_CTX context;
if (sizeof(note->build_id) < 16)
errx(1, "build_id too small for MD5");
MD5_Init(&context);
MD5_Update(&context, &load_addr, sizeof(load_addr));
MD5_Update(&context, code, csize);
MD5_Final((unsigned char *)note->build_id, &context);
}
#endif
/*
* fd: file descriptor open for writing for the output file
* load_addr: code load address (could be zero, just used for buildid)
* sym: function name (for native code - used as the symbol)
* code: the native code
* csize: the code size in bytes
*/
int
jit_write_elf(int fd, uint64_t load_addr, const char *sym,
const void *code, int csize,
void *debug, int nr_debug_entries)
{
Elf *e;
Elf_Data *d;
Elf_Scn *scn;
Elf_Ehdr *ehdr;
Elf_Shdr *shdr;
char *strsym = NULL;
int symlen;
int retval = -1;
if (elf_version(EV_CURRENT) == EV_NONE) {
warnx("ELF initialization failed");
return -1;
}
e = elf_begin(fd, ELF_C_WRITE, NULL);
if (!e) {
warnx("elf_begin failed");
goto error;
}
/*
* setup ELF header
*/
ehdr = elf_newehdr(e);
if (!ehdr) {
warnx("cannot get ehdr");
goto error;
}
ehdr->e_ident[EI_DATA] = GEN_ELF_ENDIAN;
ehdr->e_ident[EI_CLASS] = GEN_ELF_CLASS;
ehdr->e_machine = GEN_ELF_ARCH;
ehdr->e_type = ET_DYN;
ehdr->e_entry = GEN_ELF_TEXT_OFFSET;
ehdr->e_version = EV_CURRENT;
ehdr->e_shstrndx= 2; /* shdr index for section name */
/*
* setup text section
*/
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
goto error;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
goto error;
}
d->d_align = 16;
d->d_off = 0LL;
d->d_buf = (void *)code;
d->d_type = ELF_T_BYTE;
d->d_size = csize;
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
goto error;
}
shdr->sh_name = 1;
shdr->sh_type = SHT_PROGBITS;
shdr->sh_addr = GEN_ELF_TEXT_OFFSET;
shdr->sh_flags = SHF_EXECINSTR | SHF_ALLOC;
shdr->sh_entsize = 0;
/*
* setup section headers string table
*/
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
goto error;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
goto error;
}
d->d_align = 1;
d->d_off = 0LL;
d->d_buf = shd_string_table;
d->d_type = ELF_T_BYTE;
d->d_size = sizeof(shd_string_table);
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
goto error;
}
shdr->sh_name = 7; /* offset of '.shstrtab' in shd_string_table */
shdr->sh_type = SHT_STRTAB;
shdr->sh_flags = 0;
shdr->sh_entsize = 0;
/*
* setup symtab section
*/
symtab[1].st_size = csize;
symtab[1].st_value = GEN_ELF_TEXT_OFFSET;
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
goto error;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
goto error;
}
d->d_align = 8;
d->d_off = 0LL;
d->d_buf = symtab;
d->d_type = ELF_T_SYM;
d->d_size = sizeof(symtab);
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
goto error;
}
shdr->sh_name = 17; /* offset of '.symtab' in shd_string_table */
shdr->sh_type = SHT_SYMTAB;
shdr->sh_flags = 0;
shdr->sh_entsize = sizeof(Elf_Sym);
shdr->sh_link = 4; /* index of .strtab section */
/*
* setup symbols string table
* 2 = 1 for 0 in 1st entry, 1 for the 0 at end of symbol for 2nd entry
*/
symlen = 2 + strlen(sym);
strsym = calloc(1, symlen);
if (!strsym) {
warnx("cannot allocate strsym");
goto error;
}
strcpy(strsym + 1, sym);
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
goto error;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
goto error;
}
d->d_align = 1;
d->d_off = 0LL;
d->d_buf = strsym;
d->d_type = ELF_T_BYTE;
d->d_size = symlen;
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
goto error;
}
shdr->sh_name = 25; /* offset in shd_string_table */
shdr->sh_type = SHT_STRTAB;
shdr->sh_flags = 0;
shdr->sh_entsize = 0;
/*
* setup build-id section
*/
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
goto error;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
goto error;
}
/*
* build-id generation
*/
gen_build_id(&bnote, load_addr, code, csize);
bnote.desc.namesz = sizeof(bnote.name); /* must include 0 termination */
bnote.desc.descsz = sizeof(bnote.build_id);
bnote.desc.type = NT_GNU_BUILD_ID;
strcpy(bnote.name, "GNU");
d->d_align = 4;
d->d_off = 0LL;
d->d_buf = &bnote;
d->d_type = ELF_T_BYTE;
d->d_size = sizeof(bnote);
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
goto error;
}
shdr->sh_name = 33; /* offset in shd_string_table */
shdr->sh_type = SHT_NOTE;
shdr->sh_addr = 0x0;
shdr->sh_flags = SHF_ALLOC;
shdr->sh_size = sizeof(bnote);
shdr->sh_entsize = 0;
if (debug && nr_debug_entries) {
retval = jit_add_debug_info(e, load_addr, debug, nr_debug_entries);
if (retval)
goto error;
} else {
if (elf_update(e, ELF_C_WRITE) < 0) {
warnx("elf_update 4 failed");
goto error;
}
}
retval = 0;
error:
(void)elf_end(e);
free(strsym);
return retval;
}
#ifndef JVMTI
static unsigned char x86_code[] = {
0xBB, 0x2A, 0x00, 0x00, 0x00, /* movl $42, %ebx */
0xB8, 0x01, 0x00, 0x00, 0x00, /* movl $1, %eax */
0xCD, 0x80 /* int $0x80 */
};
static struct options options;
int main(int argc, char **argv)
{
int c, fd, ret;
while ((c = getopt(argc, argv, "o:h")) != -1) {
switch (c) {
case 'o':
options.output = optarg;
break;
case 'h':
printf("Usage: genelf -o output_file [-h]\n");
return 0;
default:
errx(1, "unknown option");
}
}
fd = open(options.output, O_CREAT|O_TRUNC|O_RDWR, 0666);
if (fd == -1)
err(1, "cannot create file %s", options.output);
ret = jit_write_elf(fd, "main", x86_code, sizeof(x86_code));
close(fd);
if (ret != 0)
unlink(options.output);
return ret;
}
#endif
#ifndef __GENELF_H__
#define __GENELF_H__
/* genelf.c */
extern int jit_write_elf(int fd, uint64_t code_addr, const char *sym,
const void *code, int csize,
void *debug, int nr_debug_entries);
/* genelf_debug.c */
extern int jit_add_debug_info(Elf *e, uint64_t code_addr,
void *debug, int nr_debug_entries);
#if defined(__arm__)
#define GEN_ELF_ARCH EM_ARM
#define GEN_ELF_ENDIAN ELFDATA2LSB
#define GEN_ELF_CLASS ELFCLASS32
#elif defined(__aarch64__)
#define GEN_ELF_ARCH EM_AARCH64
#define GEN_ELF_ENDIAN ELFDATA2LSB
#define GEN_ELF_CLASS ELFCLASS64
#elif defined(__x86_64__)
#define GEN_ELF_ARCH EM_X86_64
#define GEN_ELF_ENDIAN ELFDATA2LSB
#define GEN_ELF_CLASS ELFCLASS64
#elif defined(__i386__)
#define GEN_ELF_ARCH EM_386
#define GEN_ELF_ENDIAN ELFDATA2LSB
#define GEN_ELF_CLASS ELFCLASS32
#elif defined(__ppcle__)
#define GEN_ELF_ARCH EM_PPC
#define GEN_ELF_ENDIAN ELFDATA2LSB
#define GEN_ELF_CLASS ELFCLASS64
#elif defined(__powerpc__)
#define GEN_ELF_ARCH EM_PPC64
#define GEN_ELF_ENDIAN ELFDATA2MSB
#define GEN_ELF_CLASS ELFCLASS64
#elif defined(__powerpcle__)
#define GEN_ELF_ARCH EM_PPC64
#define GEN_ELF_ENDIAN ELFDATA2LSB
#define GEN_ELF_CLASS ELFCLASS64
#else
#error "unsupported architecture"
#endif
#if GEN_ELF_CLASS == ELFCLASS64
#define elf_newehdr elf64_newehdr
#define elf_getshdr elf64_getshdr
#define Elf_Ehdr Elf64_Ehdr
#define Elf_Shdr Elf64_Shdr
#define Elf_Sym Elf64_Sym
#define ELF_ST_TYPE(a) ELF64_ST_TYPE(a)
#define ELF_ST_BIND(a) ELF64_ST_BIND(a)
#define ELF_ST_VIS(a) ELF64_ST_VISIBILITY(a)
#else
#define elf_newehdr elf32_newehdr
#define elf_getshdr elf32_getshdr
#define Elf_Ehdr Elf32_Ehdr
#define Elf_Shdr Elf32_Shdr
#define Elf_Sym Elf32_Sym
#define ELF_ST_TYPE(a) ELF32_ST_TYPE(a)
#define ELF_ST_BIND(a) ELF32_ST_BIND(a)
#define ELF_ST_VIS(a) ELF32_ST_VISIBILITY(a)
#endif
/* The .text section is directly after the ELF header */
#define GEN_ELF_TEXT_OFFSET sizeof(Elf_Ehdr)
#endif
This diff is collapsed.
#ifndef __JIT_H__
#define __JIT_H__
#include <data.h>
extern int jit_process(struct perf_session *session,
struct perf_data_file *output,
struct machine *machine,
char *filename,
pid_t pid,
u64 *nbytes);
extern int jit_inject_record(const char *filename);
#endif /* __JIT_H__ */
This diff is collapsed.
/*
* jitdump.h: jitted code info encapsulation file format
*
* Adapted from OProfile GPLv2 support jidump.h:
* Copyright 2007 OProfile authors
* Jens Wilke
* Daniel Hansel
* Copyright IBM Corporation 2007
*/
#ifndef JITDUMP_H
#define JITDUMP_H
#include <sys/time.h>
#include <time.h>
#include <stdint.h>
/* JiTD */
#define JITHEADER_MAGIC 0x4A695444
#define JITHEADER_MAGIC_SW 0x4454694A
#define PADDING_8ALIGNED(x) ((((x) + 7) & 7) ^ 7)
#define JITHEADER_VERSION 1
enum jitdump_flags_bits {
JITDUMP_FLAGS_MAX_BIT,
};
#define JITDUMP_FLAGS_RESERVED (JITDUMP_FLAGS_MAX_BIT < 64 ? \
(~((1ULL << JITDUMP_FLAGS_MAX_BIT) - 1)) : 0)
struct jitheader {
uint32_t magic; /* characters "jItD" */
uint32_t version; /* header version */
uint32_t total_size; /* total size of header */
uint32_t elf_mach; /* elf mach target */
uint32_t pad1; /* reserved */
uint32_t pid; /* JIT process id */
uint64_t timestamp; /* timestamp */
uint64_t flags; /* flags */
};
enum jit_record_type {
JIT_CODE_LOAD = 0,
JIT_CODE_MOVE = 1,
JIT_CODE_DEBUG_INFO = 2,
JIT_CODE_CLOSE = 3,
JIT_CODE_MAX,
};
/* record prefix (mandatory in each record) */
struct jr_prefix {
uint32_t id;
uint32_t total_size;
uint64_t timestamp;
};
struct jr_code_load {
struct jr_prefix p;
uint32_t pid;
uint32_t tid;
uint64_t vma;
uint64_t code_addr;
uint64_t code_size;
uint64_t code_index;
};
struct jr_code_close {
struct jr_prefix p;
};
struct jr_code_move {
struct jr_prefix p;
uint32_t pid;
uint32_t tid;
uint64_t vma;
uint64_t old_code_addr;
uint64_t new_code_addr;
uint64_t code_size;
uint64_t code_index;
};
struct debug_entry {
uint64_t addr;
int lineno; /* source line number starting at 1 */
int discrim; /* column discriminator, 0 is default */
const char name[0]; /* null terminated filename, \xff\0 if same as previous entry */
};
struct jr_code_debug_info {
struct jr_prefix p;
uint64_t code_addr;
uint64_t nr_entry;
struct debug_entry entries[0];
};
union jr_entry {
struct jr_code_debug_info info;
struct jr_code_close close;
struct jr_code_load load;
struct jr_code_move move;
struct jr_prefix prefix;
};
static inline struct debug_entry *
debug_entry_next(struct debug_entry *ent)
{
void *a = ent + 1;
size_t l = strlen(ent->name) + 1;
return a + l;
}
static inline char *
debug_entry_file(struct debug_entry *ent)
{
void *a = ent + 1;
return a;
}
#endif /* !JITDUMP_H */
......@@ -6,6 +6,7 @@
#include <inttypes.h>
#include "symbol.h"
#include "demangle-java.h"
#include "machine.h"
#include "vdso.h"
#include <symbol/kallsyms.h>
......@@ -1077,6 +1078,8 @@ int dso__load_sym(struct dso *dso, struct map *map,
demangle_flags = DMGL_PARAMS | DMGL_ANSI;
demangled = bfd_demangle(NULL, elf_name, demangle_flags);
if (demangled == NULL)
demangled = java_demangle_sym(elf_name, JAVA_DEMANGLE_NORET);
if (demangled != NULL)
elf_name = demangled;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment