mirror of
https://github.com/torvalds/linux.git
synced 2024-12-23 11:21:33 +00:00
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:
commit
156d223865
@ -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;
|
||||
}
|
||||
|
17
tools/build/feature/test-libcrypto.c
Normal file
17
tools/build/feature/test-libcrypto.c
Normal 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;
|
||||
}
|
@ -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
|
||||
|
76
tools/perf/jvmti/Makefile
Normal file
76
tools/perf/jvmti/Makefile
Normal 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
|
465
tools/perf/jvmti/jvmti_agent.c
Normal file
465
tools/perf/jvmti/jvmti_agent.c
Normal 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;
|
||||
}
|
36
tools/perf/jvmti/jvmti_agent.h
Normal file
36
tools/perf/jvmti/jvmti_agent.h
Normal 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
304
tools/perf/jvmti/libjvmti.c
Normal 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()");
|
||||
}
|
@ -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)
|
||||
|
199
tools/perf/util/demangle-java.c
Normal file
199
tools/perf/util/demangle-java.c
Normal 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;
|
||||
}
|
10
tools/perf/util/demangle-java.h
Normal file
10
tools/perf/util/demangle-java.h
Normal 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 */
|
@ -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
449
tools/perf/util/genelf.c
Normal 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
67
tools/perf/util/genelf.h
Normal 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
|
610
tools/perf/util/genelf_debug.c
Normal file
610
tools/perf/util/genelf_debug.c
Normal 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
15
tools/perf/util/jit.h
Normal 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
672
tools/perf/util/jitdump.c
Normal 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
124
tools/perf/util/jitdump.h
Normal 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 */
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user