perf/core improvements and fixes:

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:
 
 - Document mmore variables in the 'perf config' man page (Taeung Song)
 
 Infrastructure:
 
 - Improve a bit the 'make -C tools/perf build-test' output (Arnaldo Carvalho de Melo)
 
 - Do 'build-test' in parallell, using 'make -j' (Arnaldo Carvalho de Melo)
 
 - Fix handling of 'clean' in multi-target make invokations for parallell builds (Jiri Olsa)
 
 Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABAgAGBQJWtMxMAAoJENZQFvNTUqpA2xIP/RRbfNraOhXub8Crc6kZBY6z
 Rr3tZHH3K8JG96xELLEr35VyyXPv1vPLXXvzev9/6ZJC6/DTt/hWoOfHa6JBpvND
 gp6z9lw1Q/7OMyxxsjtUs434eWCabNl3vNI+K907+gsx2BEZ7PCrW8Ck1oxiyPKT
 dq1htrnp1935+JHJqT5VcKcBGTq2oOtZbrQ0qvld5/hHU8a+RL5Yrp70AmN3w1ux
 wr5keuOGujqs5Vsh5WjvByoov6mdoNAzq8kPB3nQceVag3FL04YckDQ8gGG0WNQ7
 czqljQNt74Nddkpm1501hymxDRcRxqxmEKpef9YaYR6i0f0U9Aq0Jnu/p7LOjn0l
 OA1NdbkARfLHW3IKfABZt3Mf3Z4kSPSQMzXn7Ru3i23v94PZ66tKzIFGmyn8lhts
 ndze4h03GmVCZKH1IkJczJeOhuuf4pgVF6Bgouku2kb4fFS9L7MmuijDteltzbhN
 Z6n7XiDDFmLyCFFV719raLMwfWNRCd1bR+ANGd4kj2vp/SNNmVuwHM6AdTbdwJBE
 8RnAaHncQkFH+Sc7u1nYo74p0HSGpQeQOGs+b/+yoYu2j66GBy4BWhUEgCc6E9QY
 tknZwPAnRoo75FsLatqcMJN8MGnJ/gIEU1YyOFK0Z5fAu5fxCgQT5vgaGd+wLljZ
 pOKxw/JKaBFbC50kNJZi
 =rdTD
 -----END PGP SIGNATURE-----

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: Arnaldo Carvalho de Melo <acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
Ingo Molnar 2016-02-09 10:38:40 +01:00
commit 156d223865
26 changed files with 3357 additions and 7 deletions

View File

@ -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 \

View File

@ -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)

View File

@ -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;
}

View File

@ -0,0 +1,17 @@
#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;
}

View File

@ -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]

View File

@ -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]

View File

@ -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:

View File

@ -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.
#

View File

@ -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;

View File

@ -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

76
tools/perf/jvmti/Makefile Normal file
View File

@ -0,0 +1,76 @@
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

View File

@ -0,0 +1,465 @@
/*
* 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;
}

View File

@ -0,0 +1,36 @@
#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__ */

304
tools/perf/jvmti/libjvmti.c Normal file
View File

@ -0,0 +1,304 @@
#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()");
}

View File

@ -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; \

View File

@ -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)

View File

@ -0,0 +1,199 @@
#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;
}

View File

@ -0,0 +1,10 @@
#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 */

View File

@ -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,

449
tools/perf/util/genelf.c Normal file
View File

@ -0,0 +1,449 @@
/*
* 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

67
tools/perf/util/genelf.h Normal file
View File

@ -0,0 +1,67 @@
#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

View File

@ -0,0 +1,610 @@
/*
* genelf_debug.c
* Copyright (C) 2015, Google, Inc
*
* Contributed by:
* Stephane Eranian <eranian@google.com>
*
* Released under the GPL v2.
*
* based on GPLv2 source code from Oprofile
* @remark Copyright 2007 OProfile authors
* @author Philippe Elie
*/
#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 BUFFER_EXT_DFL_SIZE (4 * 1024)
typedef uint32_t uword;
typedef uint16_t uhalf;
typedef int32_t sword;
typedef int16_t shalf;
typedef uint8_t ubyte;
typedef int8_t sbyte;
struct buffer_ext {
size_t cur_pos;
size_t max_sz;
void *data;
};
static void
buffer_ext_dump(struct buffer_ext *be, const char *msg)
{
size_t i;
warnx("DUMP for %s", msg);
for (i = 0 ; i < be->cur_pos; i++)
warnx("%4zu 0x%02x", i, (((char *)be->data)[i]) & 0xff);
}
static inline int
buffer_ext_add(struct buffer_ext *be, void *addr, size_t sz)
{
void *tmp;
size_t be_sz = be->max_sz;
retry:
if ((be->cur_pos + sz) < be_sz) {
memcpy(be->data + be->cur_pos, addr, sz);
be->cur_pos += sz;
return 0;
}
if (!be_sz)
be_sz = BUFFER_EXT_DFL_SIZE;
else
be_sz <<= 1;
tmp = realloc(be->data, be_sz);
if (!tmp)
return -1;
be->data = tmp;
be->max_sz = be_sz;
goto retry;
}
static void
buffer_ext_init(struct buffer_ext *be)
{
be->data = NULL;
be->cur_pos = 0;
be->max_sz = 0;
}
static inline size_t
buffer_ext_size(struct buffer_ext *be)
{
return be->cur_pos;
}
static inline void *
buffer_ext_addr(struct buffer_ext *be)
{
return be->data;
}
struct debug_line_header {
// Not counting this field
uword total_length;
// version number (2 currently)
uhalf version;
// relative offset from next field to
// program statement
uword prolog_length;
ubyte minimum_instruction_length;
ubyte default_is_stmt;
// line_base - see DWARF 2 specs
sbyte line_base;
// line_range - see DWARF 2 specs
ubyte line_range;
// number of opcode + 1
ubyte opcode_base;
/* follow the array of opcode args nr: ubytes [nr_opcode_base] */
/* follow the search directories index, zero terminated string
* terminated by an empty string.
*/
/* follow an array of { filename, LEB128, LEB128, LEB128 }, first is
* the directory index entry, 0 means current directory, then mtime
* and filesize, last entry is followed by en empty string.
*/
/* follow the first program statement */
} __attribute__((packed));
/* DWARF 2 spec talk only about one possible compilation unit header while
* binutils can handle two flavours of dwarf 2, 32 and 64 bits, this is not
* related to the used arch, an ELF 32 can hold more than 4 Go of debug
* information. For now we handle only DWARF 2 32 bits comp unit. It'll only
* become a problem if we generate more than 4GB of debug information.
*/
struct compilation_unit_header {
uword total_length;
uhalf version;
uword debug_abbrev_offset;
ubyte pointer_size;
} __attribute__((packed));
#define DW_LNS_num_opcode (DW_LNS_set_isa + 1)
/* field filled at run time are marked with -1 */
static struct debug_line_header const default_debug_line_header = {
.total_length = -1,
.version = 2,
.prolog_length = -1,
.minimum_instruction_length = 1, /* could be better when min instruction size != 1 */
.default_is_stmt = 1, /* we don't take care about basic block */
.line_base = -5, /* sensible value for line base ... */
.line_range = -14, /* ... and line range are guessed statically */
.opcode_base = DW_LNS_num_opcode
};
static ubyte standard_opcode_length[] =
{
0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1
};
#if 0
{
[DW_LNS_advance_pc] = 1,
[DW_LNS_advance_line] = 1,
[DW_LNS_set_file] = 1,
[DW_LNS_set_column] = 1,
[DW_LNS_fixed_advance_pc] = 1,
[DW_LNS_set_isa] = 1,
};
#endif
/* field filled at run time are marked with -1 */
static struct compilation_unit_header default_comp_unit_header = {
.total_length = -1,
.version = 2,
.debug_abbrev_offset = 0, /* we reuse the same abbrev entries for all comp unit */
.pointer_size = sizeof(void *)
};
static void emit_uword(struct buffer_ext *be, uword data)
{
buffer_ext_add(be, &data, sizeof(uword));
}
static void emit_string(struct buffer_ext *be, const char *s)
{
buffer_ext_add(be, (void *)s, strlen(s) + 1);
}
static void emit_unsigned_LEB128(struct buffer_ext *be,
unsigned long data)
{
do {
ubyte cur = data & 0x7F;
data >>= 7;
if (data)
cur |= 0x80;
buffer_ext_add(be, &cur, 1);
} while (data);
}
static void emit_signed_LEB128(struct buffer_ext *be, long data)
{
int more = 1;
int negative = data < 0;
int size = sizeof(long) * CHAR_BIT;
while (more) {
ubyte cur = data & 0x7F;
data >>= 7;
if (negative)
data |= - (1 << (size - 7));
if ((data == 0 && !(cur & 0x40)) ||
(data == -1l && (cur & 0x40)))
more = 0;
else
cur |= 0x80;
buffer_ext_add(be, &cur, 1);
}
}
static void emit_extended_opcode(struct buffer_ext *be, ubyte opcode,
void *data, size_t data_len)
{
buffer_ext_add(be, (char *)"", 1);
emit_unsigned_LEB128(be, data_len + 1);
buffer_ext_add(be, &opcode, 1);
buffer_ext_add(be, data, data_len);
}
static void emit_opcode(struct buffer_ext *be, ubyte opcode)
{
buffer_ext_add(be, &opcode, 1);
}
static void emit_opcode_signed(struct buffer_ext *be,
ubyte opcode, long data)
{
buffer_ext_add(be, &opcode, 1);
emit_signed_LEB128(be, data);
}
static void emit_opcode_unsigned(struct buffer_ext *be, ubyte opcode,
unsigned long data)
{
buffer_ext_add(be, &opcode, 1);
emit_unsigned_LEB128(be, data);
}
static void emit_advance_pc(struct buffer_ext *be, unsigned long delta_pc)
{
emit_opcode_unsigned(be, DW_LNS_advance_pc, delta_pc);
}
static void emit_advance_lineno(struct buffer_ext *be, long delta_lineno)
{
emit_opcode_signed(be, DW_LNS_advance_line, delta_lineno);
}
static void emit_lne_end_of_sequence(struct buffer_ext *be)
{
emit_extended_opcode(be, DW_LNE_end_sequence, NULL, 0);
}
static void emit_set_file(struct buffer_ext *be, unsigned long idx)
{
emit_opcode_unsigned(be, DW_LNS_set_file, idx);
}
static void emit_lne_define_filename(struct buffer_ext *be,
const char *filename)
{
buffer_ext_add(be, (void *)"", 1);
/* LNE field, strlen(filename) + zero termination, 3 bytes for: the dir entry, timestamp, filesize */
emit_unsigned_LEB128(be, strlen(filename) + 5);
emit_opcode(be, DW_LNE_define_file);
emit_string(be, filename);
/* directory index 0=do not know */
emit_unsigned_LEB128(be, 0);
/* last modification date on file 0=do not know */
emit_unsigned_LEB128(be, 0);
/* filesize 0=do not know */
emit_unsigned_LEB128(be, 0);
}
static void emit_lne_set_address(struct buffer_ext *be,
void *address)
{
emit_extended_opcode(be, DW_LNE_set_address, &address, sizeof(unsigned long));
}
static ubyte get_special_opcode(struct debug_entry *ent,
unsigned int last_line,
unsigned long last_vma)
{
unsigned int temp;
unsigned long delta_addr;
/*
* delta from line_base
*/
temp = (ent->lineno - last_line) - default_debug_line_header.line_base;
if (temp >= default_debug_line_header.line_range)
return 0;
/*
* delta of addresses
*/
delta_addr = (ent->addr - last_vma) / default_debug_line_header.minimum_instruction_length;
/* This is not sufficient to ensure opcode will be in [0-256] but
* sufficient to ensure when summing with the delta lineno we will
* not overflow the unsigned long opcode */
if (delta_addr <= 256 / default_debug_line_header.line_range) {
unsigned long opcode = temp +
(delta_addr * default_debug_line_header.line_range) +
default_debug_line_header.opcode_base;
return opcode <= 255 ? opcode : 0;
}
return 0;
}
static void emit_lineno_info(struct buffer_ext *be,
struct debug_entry *ent, size_t nr_entry,
unsigned long code_addr)
{
size_t i;
/*
* Machine state at start of a statement program
* address = 0
* file = 1
* line = 1
* column = 0
* is_stmt = default_is_stmt as given in the debug_line_header
* basic block = 0
* end sequence = 0
*/
/* start state of the state machine we take care of */
unsigned long last_vma = code_addr;
char const *cur_filename = NULL;
unsigned long cur_file_idx = 0;
int last_line = 1;
emit_lne_set_address(be, (void *)code_addr);
for (i = 0; i < nr_entry; i++, ent = debug_entry_next(ent)) {
int need_copy = 0;
ubyte special_opcode;
/*
* check if filename changed, if so add it
*/
if (!cur_filename || strcmp(cur_filename, ent->name)) {
emit_lne_define_filename(be, ent->name);
cur_filename = ent->name;
emit_set_file(be, ++cur_file_idx);
need_copy = 1;
}
special_opcode = get_special_opcode(ent, last_line, last_vma);
if (special_opcode != 0) {
last_line = ent->lineno;
last_vma = ent->addr;
emit_opcode(be, special_opcode);
} else {
/*
* lines differ, emit line delta
*/
if (last_line != ent->lineno) {
emit_advance_lineno(be, ent->lineno - last_line);
last_line = ent->lineno;
need_copy = 1;
}
/*
* addresses differ, emit address delta
*/
if (last_vma != ent->addr) {
emit_advance_pc(be, ent->addr - last_vma);
last_vma = ent->addr;
need_copy = 1;
}
/*
* add new row to matrix
*/
if (need_copy)
emit_opcode(be, DW_LNS_copy);
}
}
}
static void add_debug_line(struct buffer_ext *be,
struct debug_entry *ent, size_t nr_entry,
unsigned long code_addr)
{
struct debug_line_header * dbg_header;
size_t old_size;
old_size = buffer_ext_size(be);
buffer_ext_add(be, (void *)&default_debug_line_header,
sizeof(default_debug_line_header));
buffer_ext_add(be, &standard_opcode_length, sizeof(standard_opcode_length));
// empty directory entry
buffer_ext_add(be, (void *)"", 1);
// empty filename directory
buffer_ext_add(be, (void *)"", 1);
dbg_header = buffer_ext_addr(be) + old_size;
dbg_header->prolog_length = (buffer_ext_size(be) - old_size) -
offsetof(struct debug_line_header, minimum_instruction_length);
emit_lineno_info(be, ent, nr_entry, code_addr);
emit_lne_end_of_sequence(be);
dbg_header = buffer_ext_addr(be) + old_size;
dbg_header->total_length = (buffer_ext_size(be) - old_size) -
offsetof(struct debug_line_header, version);
}
static void
add_debug_abbrev(struct buffer_ext *be)
{
emit_unsigned_LEB128(be, 1);
emit_unsigned_LEB128(be, DW_TAG_compile_unit);
emit_unsigned_LEB128(be, DW_CHILDREN_yes);
emit_unsigned_LEB128(be, DW_AT_stmt_list);
emit_unsigned_LEB128(be, DW_FORM_data4);
emit_unsigned_LEB128(be, 0);
emit_unsigned_LEB128(be, 0);
emit_unsigned_LEB128(be, 0);
}
static void
add_compilation_unit(struct buffer_ext *be,
size_t offset_debug_line)
{
struct compilation_unit_header *comp_unit_header;
size_t old_size = buffer_ext_size(be);
buffer_ext_add(be, &default_comp_unit_header,
sizeof(default_comp_unit_header));
emit_unsigned_LEB128(be, 1);
emit_uword(be, offset_debug_line);
comp_unit_header = buffer_ext_addr(be) + old_size;
comp_unit_header->total_length = (buffer_ext_size(be) - old_size) -
offsetof(struct compilation_unit_header, version);
}
static int
jit_process_debug_info(uint64_t code_addr,
void *debug, int nr_debug_entries,
struct buffer_ext *dl,
struct buffer_ext *da,
struct buffer_ext *di)
{
struct debug_entry *ent = debug;
int i;
for (i = 0; i < nr_debug_entries; i++) {
ent->addr = ent->addr - code_addr;
ent = debug_entry_next(ent);
}
add_compilation_unit(di, buffer_ext_size(dl));
add_debug_line(dl, debug, nr_debug_entries, 0);
add_debug_abbrev(da);
if (0) buffer_ext_dump(da, "abbrev");
return 0;
}
int
jit_add_debug_info(Elf *e, uint64_t code_addr, void *debug, int nr_debug_entries)
{
Elf_Data *d;
Elf_Scn *scn;
Elf_Shdr *shdr;
struct buffer_ext dl, di, da;
int ret;
buffer_ext_init(&dl);
buffer_ext_init(&di);
buffer_ext_init(&da);
ret = jit_process_debug_info(code_addr, debug, nr_debug_entries, &dl, &da, &di);
if (ret)
return -1;
/*
* setup .debug_line section
*/
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
return -1;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
return -1;
}
d->d_align = 1;
d->d_off = 0LL;
d->d_buf = buffer_ext_addr(&dl);
d->d_type = ELF_T_BYTE;
d->d_size = buffer_ext_size(&dl);
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
return -1;
}
shdr->sh_name = 52; /* .debug_line */
shdr->sh_type = SHT_PROGBITS;
shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */
shdr->sh_flags = 0;
shdr->sh_entsize = 0;
/*
* setup .debug_info section
*/
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
return -1;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
return -1;
}
d->d_align = 1;
d->d_off = 0LL;
d->d_buf = buffer_ext_addr(&di);
d->d_type = ELF_T_BYTE;
d->d_size = buffer_ext_size(&di);
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
return -1;
}
shdr->sh_name = 64; /* .debug_info */
shdr->sh_type = SHT_PROGBITS;
shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */
shdr->sh_flags = 0;
shdr->sh_entsize = 0;
/*
* setup .debug_abbrev section
*/
scn = elf_newscn(e);
if (!scn) {
warnx("cannot create section");
return -1;
}
d = elf_newdata(scn);
if (!d) {
warnx("cannot get new data");
return -1;
}
d->d_align = 1;
d->d_off = 0LL;
d->d_buf = buffer_ext_addr(&da);
d->d_type = ELF_T_BYTE;
d->d_size = buffer_ext_size(&da);
d->d_version = EV_CURRENT;
shdr = elf_getshdr(scn);
if (!shdr) {
warnx("cannot get section header");
return -1;
}
shdr->sh_name = 76; /* .debug_info */
shdr->sh_type = SHT_PROGBITS;
shdr->sh_addr = 0; /* must be zero or == sh_offset -> dynamic object */
shdr->sh_flags = 0;
shdr->sh_entsize = 0;
/*
* now we update the ELF image with all the sections
*/
if (elf_update(e, ELF_C_WRITE) < 0) {
warnx("elf_update debug failed");
return -1;
}
return 0;
}

15
tools/perf/util/jit.h Normal file
View File

@ -0,0 +1,15 @@
#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__ */

672
tools/perf/util/jitdump.c Normal file
View File

@ -0,0 +1,672 @@
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <inttypes.h>
#include <byteswap.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "util.h"
#include "event.h"
#include "debug.h"
#include "evlist.h"
#include "symbol.h"
#include "strlist.h"
#include <elf.h>
#include "session.h"
#include "jit.h"
#include "jitdump.h"
#include "genelf.h"
#include "../builtin.h"
struct jit_buf_desc {
struct perf_data_file *output;
struct perf_session *session;
struct machine *machine;
union jr_entry *entry;
void *buf;
uint64_t sample_type;
size_t bufsize;
FILE *in;
bool needs_bswap; /* handles cross-endianess */
void *debug_data;
size_t nr_debug_entries;
uint32_t code_load_count;
u64 bytes_written;
struct rb_root code_root;
char dir[PATH_MAX];
};
struct debug_line_info {
unsigned long vma;
unsigned int lineno;
/* The filename format is unspecified, absolute path, relative etc. */
char const filename[0];
};
struct jit_tool {
struct perf_tool tool;
struct perf_data_file output;
struct perf_data_file input;
u64 bytes_written;
};
#define hmax(a, b) ((a) > (b) ? (a) : (b))
#define get_jit_tool(t) (container_of(tool, struct jit_tool, tool))
static int
jit_emit_elf(char *filename,
const char *sym,
uint64_t code_addr,
const void *code,
int csize,
void *debug,
int nr_debug_entries)
{
int ret, fd;
if (verbose > 0)
fprintf(stderr, "write ELF image %s\n", filename);
fd = open(filename, O_CREAT|O_TRUNC|O_WRONLY, 0644);
if (fd == -1) {
pr_warning("cannot create jit ELF %s: %s\n", filename, strerror(errno));
return -1;
}
ret = jit_write_elf(fd, code_addr, sym, (const void *)code, csize, debug, nr_debug_entries);
close(fd);
if (ret)
unlink(filename);
return ret;
}
static void
jit_close(struct jit_buf_desc *jd)
{
if (!(jd && jd->in))
return;
funlockfile(jd->in);
fclose(jd->in);
jd->in = NULL;
}
static int
jit_open(struct jit_buf_desc *jd, const char *name)
{
struct jitheader header;
struct jr_prefix *prefix;
ssize_t bs, bsz = 0;
void *n, *buf = NULL;
int ret, retval = -1;
jd->in = fopen(name, "r");
if (!jd->in)
return -1;
bsz = hmax(sizeof(header), sizeof(*prefix));
buf = malloc(bsz);
if (!buf)
goto error;
/*
* protect from writer modifying the file while we are reading it
*/
flockfile(jd->in);
ret = fread(buf, sizeof(header), 1, jd->in);
if (ret != 1)
goto error;
memcpy(&header, buf, sizeof(header));
if (header.magic != JITHEADER_MAGIC) {
if (header.magic != JITHEADER_MAGIC_SW)
goto error;
jd->needs_bswap = true;
}
if (jd->needs_bswap) {
header.version = bswap_32(header.version);
header.total_size = bswap_32(header.total_size);
header.pid = bswap_32(header.pid);
header.elf_mach = bswap_32(header.elf_mach);
header.timestamp = bswap_64(header.timestamp);
header.flags = bswap_64(header.flags);
}
if (verbose > 2)
pr_debug("version=%u\nhdr.size=%u\nts=0x%llx\npid=%d\nelf_mach=%d\n",
header.version,
header.total_size,
(unsigned long long)header.timestamp,
header.pid,
header.elf_mach);
if (header.flags & JITDUMP_FLAGS_RESERVED) {
pr_err("jitdump file contains invalid or unsupported flags 0x%llx\n",
(unsigned long long)header.flags & JITDUMP_FLAGS_RESERVED);
goto error;
}
bs = header.total_size - sizeof(header);
if (bs > bsz) {
n = realloc(buf, bs);
if (!n)
goto error;
bsz = bs;
buf = n;
/* read extra we do not know about */
ret = fread(buf, bs - bsz, 1, jd->in);
if (ret != 1)
goto error;
}
/*
* keep dirname for generating files and mmap records
*/
strcpy(jd->dir, name);
dirname(jd->dir);
return 0;
error:
funlockfile(jd->in);
fclose(jd->in);
return retval;
}
static union jr_entry *
jit_get_next_entry(struct jit_buf_desc *jd)
{
struct jr_prefix *prefix;
union jr_entry *jr;
void *addr;
size_t bs, size;
int id, ret;
if (!(jd && jd->in))
return NULL;
if (jd->buf == NULL) {
size_t sz = getpagesize();
if (sz < sizeof(*prefix))
sz = sizeof(*prefix);
jd->buf = malloc(sz);
if (jd->buf == NULL)
return NULL;
jd->bufsize = sz;
}
prefix = jd->buf;
/*
* file is still locked at this point
*/
ret = fread(prefix, sizeof(*prefix), 1, jd->in);
if (ret != 1)
return NULL;
if (jd->needs_bswap) {
prefix->id = bswap_32(prefix->id);
prefix->total_size = bswap_32(prefix->total_size);
prefix->timestamp = bswap_64(prefix->timestamp);
}
id = prefix->id;
size = prefix->total_size;
bs = (size_t)size;
if (bs < sizeof(*prefix))
return NULL;
if (id >= JIT_CODE_MAX) {
pr_warning("next_entry: unknown prefix %d, skipping\n", id);
return NULL;
}
if (bs > jd->bufsize) {
void *n;
n = realloc(jd->buf, bs);
if (!n)
return NULL;
jd->buf = n;
jd->bufsize = bs;
}
addr = ((void *)jd->buf) + sizeof(*prefix);
ret = fread(addr, bs - sizeof(*prefix), 1, jd->in);
if (ret != 1)
return NULL;
jr = (union jr_entry *)jd->buf;
switch(id) {
case JIT_CODE_DEBUG_INFO:
if (jd->needs_bswap) {
uint64_t n;
jr->info.code_addr = bswap_64(jr->info.code_addr);
jr->info.nr_entry = bswap_64(jr->info.nr_entry);
for (n = 0 ; n < jr->info.nr_entry; n++) {
jr->info.entries[n].addr = bswap_64(jr->info.entries[n].addr);
jr->info.entries[n].lineno = bswap_32(jr->info.entries[n].lineno);
jr->info.entries[n].discrim = bswap_32(jr->info.entries[n].discrim);
}
}
break;
case JIT_CODE_CLOSE:
break;
case JIT_CODE_LOAD:
if (jd->needs_bswap) {
jr->load.pid = bswap_32(jr->load.pid);
jr->load.tid = bswap_32(jr->load.tid);
jr->load.vma = bswap_64(jr->load.vma);
jr->load.code_addr = bswap_64(jr->load.code_addr);
jr->load.code_size = bswap_64(jr->load.code_size);
jr->load.code_index= bswap_64(jr->load.code_index);
}
jd->code_load_count++;
break;
case JIT_CODE_MOVE:
if (jd->needs_bswap) {
jr->move.pid = bswap_32(jr->move.pid);
jr->move.tid = bswap_32(jr->move.tid);
jr->move.vma = bswap_64(jr->move.vma);
jr->move.old_code_addr = bswap_64(jr->move.old_code_addr);
jr->move.new_code_addr = bswap_64(jr->move.new_code_addr);
jr->move.code_size = bswap_64(jr->move.code_size);
jr->move.code_index = bswap_64(jr->move.code_index);
}
break;
case JIT_CODE_MAX:
default:
return NULL;
}
return jr;
}
static int
jit_inject_event(struct jit_buf_desc *jd, union perf_event *event)
{
ssize_t size;
size = perf_data_file__write(jd->output, event, event->header.size);
if (size < 0)
return -1;
jd->bytes_written += size;
return 0;
}
static int jit_repipe_code_load(struct jit_buf_desc *jd, union jr_entry *jr)
{
struct perf_sample sample;
union perf_event *event;
struct perf_tool *tool = jd->session->tool;
uint64_t code, addr;
uintptr_t uaddr;
char *filename;
struct stat st;
size_t size;
u16 idr_size;
const char *sym;
uint32_t count;
int ret, csize;
pid_t pid, tid;
struct {
u32 pid, tid;
u64 time;
} *id;
pid = jr->load.pid;
tid = jr->load.tid;
csize = jr->load.code_size;
addr = jr->load.code_addr;
sym = (void *)((unsigned long)jr + sizeof(jr->load));
code = (unsigned long)jr + jr->load.p.total_size - csize;
count = jr->load.code_index;
idr_size = jd->machine->id_hdr_size;
event = calloc(1, sizeof(*event) + idr_size);
if (!event)
return -1;
filename = event->mmap2.filename;
size = snprintf(filename, PATH_MAX, "%s/jitted-%d-%u.so",
jd->dir,
pid,
count);
size++; /* for \0 */
size = PERF_ALIGN(size, sizeof(u64));
uaddr = (uintptr_t)code;
ret = jit_emit_elf(filename, sym, addr, (const void *)uaddr, csize, jd->debug_data, jd->nr_debug_entries);
if (jd->debug_data && jd->nr_debug_entries) {
free(jd->debug_data);
jd->debug_data = NULL;
jd->nr_debug_entries = 0;
}
if (ret) {
free(event);
return -1;
}
if (stat(filename, &st))
memset(&st, 0, sizeof(stat));
event->mmap2.header.type = PERF_RECORD_MMAP2;
event->mmap2.header.misc = PERF_RECORD_MISC_USER;
event->mmap2.header.size = (sizeof(event->mmap2) -
(sizeof(event->mmap2.filename) - size) + idr_size);
event->mmap2.pgoff = GEN_ELF_TEXT_OFFSET;
event->mmap2.start = addr;
event->mmap2.len = csize;
event->mmap2.pid = pid;
event->mmap2.tid = tid;
event->mmap2.ino = st.st_ino;
event->mmap2.maj = major(st.st_dev);
event->mmap2.min = minor(st.st_dev);
event->mmap2.prot = st.st_mode;
event->mmap2.flags = MAP_SHARED;
event->mmap2.ino_generation = 1;
id = (void *)((unsigned long)event + event->mmap.header.size - idr_size);
if (jd->sample_type & PERF_SAMPLE_TID) {
id->pid = pid;
id->tid = tid;
}
if (jd->sample_type & PERF_SAMPLE_TIME)
id->time = jr->load.p.timestamp;
/*
* create pseudo sample to induce dso hit increment
* use first address as sample address
*/
memset(&sample, 0, sizeof(sample));
sample.pid = pid;
sample.tid = tid;
sample.time = id->time;
sample.ip = addr;
ret = perf_event__process_mmap2(tool, event, &sample, jd->machine);
if (ret)
return ret;
ret = jit_inject_event(jd, event);
/*
* mark dso as use to generate buildid in the header
*/
if (!ret)
build_id__mark_dso_hit(tool, event, &sample, NULL, jd->machine);
return ret;
}
static int jit_repipe_code_move(struct jit_buf_desc *jd, union jr_entry *jr)
{
struct perf_sample sample;
union perf_event *event;
struct perf_tool *tool = jd->session->tool;
char *filename;
size_t size;
struct stat st;
u16 idr_size;
int ret;
pid_t pid, tid;
struct {
u32 pid, tid;
u64 time;
} *id;
pid = jr->move.pid;
tid = jr->move.tid;
idr_size = jd->machine->id_hdr_size;
/*
* +16 to account for sample_id_all (hack)
*/
event = calloc(1, sizeof(*event) + 16);
if (!event)
return -1;
filename = event->mmap2.filename;
size = snprintf(filename, PATH_MAX, "%s/jitted-%d-%"PRIu64,
jd->dir,
pid,
jr->move.code_index);
size++; /* for \0 */
if (stat(filename, &st))
memset(&st, 0, sizeof(stat));
size = PERF_ALIGN(size, sizeof(u64));
event->mmap2.header.type = PERF_RECORD_MMAP2;
event->mmap2.header.misc = PERF_RECORD_MISC_USER;
event->mmap2.header.size = (sizeof(event->mmap2) -
(sizeof(event->mmap2.filename) - size) + idr_size);
event->mmap2.pgoff = GEN_ELF_TEXT_OFFSET;
event->mmap2.start = jr->move.new_code_addr;
event->mmap2.len = jr->move.code_size;
event->mmap2.pid = pid;
event->mmap2.tid = tid;
event->mmap2.ino = st.st_ino;
event->mmap2.maj = major(st.st_dev);
event->mmap2.min = minor(st.st_dev);
event->mmap2.prot = st.st_mode;
event->mmap2.flags = MAP_SHARED;
event->mmap2.ino_generation = 1;
id = (void *)((unsigned long)event + event->mmap.header.size - idr_size);
if (jd->sample_type & PERF_SAMPLE_TID) {
id->pid = pid;
id->tid = tid;
}
if (jd->sample_type & PERF_SAMPLE_TIME)
id->time = jr->load.p.timestamp;
/*
* create pseudo sample to induce dso hit increment
* use first address as sample address
*/
memset(&sample, 0, sizeof(sample));
sample.pid = pid;
sample.tid = tid;
sample.time = id->time;
sample.ip = jr->move.new_code_addr;
ret = perf_event__process_mmap2(tool, event, &sample, jd->machine);
if (ret)
return ret;
ret = jit_inject_event(jd, event);
if (!ret)
build_id__mark_dso_hit(tool, event, &sample, NULL, jd->machine);
return ret;
}
static int jit_repipe_debug_info(struct jit_buf_desc *jd, union jr_entry *jr)
{
void *data;
size_t sz;
if (!(jd && jr))
return -1;
sz = jr->prefix.total_size - sizeof(jr->info);
data = malloc(sz);
if (!data)
return -1;
memcpy(data, &jr->info.entries, sz);
jd->debug_data = data;
/*
* we must use nr_entry instead of size here because
* we cannot distinguish actual entry from padding otherwise
*/
jd->nr_debug_entries = jr->info.nr_entry;
return 0;
}
static int
jit_process_dump(struct jit_buf_desc *jd)
{
union jr_entry *jr;
int ret;
while ((jr = jit_get_next_entry(jd))) {
switch(jr->prefix.id) {
case JIT_CODE_LOAD:
ret = jit_repipe_code_load(jd, jr);
break;
case JIT_CODE_MOVE:
ret = jit_repipe_code_move(jd, jr);
break;
case JIT_CODE_DEBUG_INFO:
ret = jit_repipe_debug_info(jd, jr);
break;
default:
ret = 0;
continue;
}
}
return ret;
}
static int
jit_inject(struct jit_buf_desc *jd, char *path)
{
int ret;
if (verbose > 0)
fprintf(stderr, "injecting: %s\n", path);
ret = jit_open(jd, path);
if (ret)
return -1;
ret = jit_process_dump(jd);
jit_close(jd);
if (verbose > 0)
fprintf(stderr, "injected: %s (%d)\n", path, ret);
return 0;
}
/*
* File must be with pattern .../jit-XXXX.dump
* where XXXX is the PID of the process which did the mmap()
* as captured in the RECORD_MMAP record
*/
static int
jit_detect(char *mmap_name, pid_t pid)
{
char *p;
char *end = NULL;
pid_t pid2;
if (verbose > 2)
fprintf(stderr, "jit marker trying : %s\n", mmap_name);
/*
* get file name
*/
p = strrchr(mmap_name, '/');
if (!p)
return -1;
/*
* match prefix
*/
if (strncmp(p, "/jit-", 5))
return -1;
/*
* skip prefix
*/
p += 5;
/*
* must be followed by a pid
*/
if (!isdigit(*p))
return -1;
pid2 = (int)strtol(p, &end, 10);
if (!end)
return -1;
/*
* pid does not match mmap pid
* pid==0 in system-wide mode (synthesized)
*/
if (pid && pid2 != pid)
return -1;
/*
* validate suffix
*/
if (strcmp(end, ".dump"))
return -1;
if (verbose > 0)
fprintf(stderr, "jit marker found: %s\n", mmap_name);
return 0;
}
int
jit_process(struct perf_session *session,
struct perf_data_file *output,
struct machine *machine,
char *filename,
pid_t pid,
u64 *nbytes)
{
struct perf_evsel *first;
struct jit_buf_desc jd;
int ret;
/*
* first, detect marker mmap (i.e., the jitdump mmap)
*/
if (jit_detect(filename, pid))
return -1;
memset(&jd, 0, sizeof(jd));
jd.session = session;
jd.output = output;
jd.machine = machine;
/*
* track sample_type to compute id_all layout
* perf sets the same sample type to all events as of now
*/
first = perf_evlist__first(session->evlist);
jd.sample_type = first->attr.sample_type;
*nbytes = 0;
ret = jit_inject(&jd, filename);
if (!ret)
*nbytes = jd.bytes_written;
return ret;
}

124
tools/perf/util/jitdump.h Normal file
View File

@ -0,0 +1,124 @@
/*
* 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 */

View File

@ -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 @@ new_symbol:
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;
}