536 lines
15 KiB
C
536 lines
15 KiB
C
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdbool.h>
|
|
#include <assert.h>
|
|
|
|
#include <sys/mman.h>
|
|
#include <linux/perf_event.h>
|
|
|
|
#include <intel-pt.h>
|
|
#include <libipt-sb.h>
|
|
#include <pevent.h>
|
|
|
|
#include <caml/mlvalues.h>
|
|
#include <caml/custom.h>
|
|
#include <caml/fail.h>
|
|
#include <caml/memory.h>
|
|
#include <caml/callback.h>
|
|
#include <caml/alloc.h>
|
|
|
|
/*** INTEROP CODE ***/
|
|
|
|
/*$
|
|
open Magic_trace_lib_cinaps_helpers.Trace_decoding_interop ;;
|
|
|
|
gen_c_enum event_kinds ;;
|
|
gen_c_enum decode_result ;;
|
|
gen_c_record_enum event_packet ;;
|
|
gen_c_record_enum add_section_packet ;;
|
|
gen_c_record_enum decoding_config ;;
|
|
gen_c_record_enum mmap ;;
|
|
gen_c_record_enum trace_meta ;;
|
|
gen_c_record_enum setup_info ;;
|
|
*/
|
|
enum event_kind {
|
|
event_kind_none = -1,
|
|
event_kind_other = 0,
|
|
event_kind_call = 1,
|
|
event_kind_ret = 2,
|
|
event_kind_start_trace = 3,
|
|
event_kind_end_trace = 4,
|
|
event_kind_end_trace_syscall = 5,
|
|
event_kind_install_handler = 6,
|
|
event_kind_raise_exception = 7,
|
|
event_kind_decode_error = 8,
|
|
event_kind_jump = 9,
|
|
};
|
|
|
|
enum decode_result {
|
|
decode_result_none = -1,
|
|
decode_result_end_of_trace = 0,
|
|
decode_result_event = 1,
|
|
decode_result_add_section = 2,
|
|
};
|
|
|
|
enum event_field {
|
|
event_field_pid /* int */,
|
|
event_field_tid /* int */,
|
|
event_field_kind /* Event_kind.t */,
|
|
event_field_addr /* int */,
|
|
event_field_time /* int */,
|
|
event_field_isid /* int */,
|
|
event_field_symbol_begin /* int */,
|
|
event_field_symbol_end /* int */,
|
|
};
|
|
|
|
enum add_section_field {
|
|
add_section_field_filename /* string */,
|
|
add_section_field_offset /* int */,
|
|
add_section_field_size /* int */,
|
|
add_section_field_vaddr /* int */,
|
|
add_section_field_isid /* int */,
|
|
};
|
|
|
|
enum config_field {
|
|
config_field_sideband_filename /* string */,
|
|
config_field_pt_data_fd /* int */,
|
|
config_field_setup_info /* Manual_perf.Setup_info.t */,
|
|
};
|
|
|
|
enum mmap_field {
|
|
mmap_field_vaddr /* int */,
|
|
mmap_field_length /* int */,
|
|
mmap_field_offset /* int */,
|
|
mmap_field_filename /* string */,
|
|
};
|
|
|
|
enum trace_meta_field {
|
|
trace_meta_field_time_shift /* int */,
|
|
trace_meta_field_time_mult /* int */,
|
|
trace_meta_field_time_zero /* int */,
|
|
trace_meta_field_max_nonturbo_ratio /* int */,
|
|
};
|
|
|
|
enum setup_info_field {
|
|
setup_info_field_initial_maps /* Mmap.t list */,
|
|
setup_info_field_trace_meta /* Trace_meta.t */,
|
|
setup_info_field_pid /* int */,
|
|
};
|
|
/*$*/
|
|
|
|
|
|
unsigned char TODO_ASM_INSTALL[] = { 0 };
|
|
|
|
/*** MAIN ***/
|
|
|
|
struct pending_section {
|
|
char *filename;
|
|
uint64_t offset;
|
|
uint64_t size;
|
|
uint64_t vaddr;
|
|
uint64_t isid;
|
|
|
|
struct pending_section *next;
|
|
};
|
|
|
|
struct decoding_state {
|
|
// passed
|
|
int pt_fd;
|
|
|
|
// internal to the decoding state
|
|
uint8_t *pt_mmap;
|
|
size_t pt_length;
|
|
uint64_t tsc;
|
|
|
|
struct pt_insn_decoder *decoder;
|
|
struct pt_image_section_cache *iscache;
|
|
struct pt_sb_session *session;
|
|
|
|
struct pev_config pev_config;
|
|
|
|
enum event_kind pending_event_kind;
|
|
bool last_was_syscall;
|
|
|
|
bool pt_synced;
|
|
int saved_status;
|
|
|
|
struct pending_section *pending_section_head;
|
|
|
|
// These values are only used temporarily during decode calls
|
|
value *event;
|
|
value *add_section;
|
|
enum decode_result pending_decode_result;
|
|
};
|
|
|
|
static void destroy_decoding_state(struct decoding_state *s) {
|
|
if (!s) return;
|
|
|
|
if (s->pt_mmap) munmap(s->pt_mmap, s->pt_length);
|
|
if (s->decoder) pt_insn_free_decoder(s->decoder);
|
|
if (s->iscache) pt_iscache_free(s->iscache);
|
|
if (s->session) pt_sb_free(s->session);
|
|
free(s);
|
|
}
|
|
|
|
/*** EVENT HANDLERS ***/
|
|
|
|
static void commit_event(struct decoding_state *s) {
|
|
uint64_t timestamp_ns;
|
|
// This only fails if we call it wrong
|
|
assert(pev_time_from_tsc(×tamp_ns, s->tsc, &(s->pev_config)) == 0);
|
|
Store_field(*s->event, event_field_time, Val_long(timestamp_ns));
|
|
s->pending_decode_result = decode_result_event;
|
|
}
|
|
|
|
static int handle_event(struct decoding_state *s, const struct pt_event *event) {
|
|
int error = 0;
|
|
if (event->has_tsc) {
|
|
s->tsc = event->tsc;
|
|
}
|
|
|
|
enum event_kind kind = event_kind_none;
|
|
uint64_t ip = 0;
|
|
switch (event->type) {
|
|
case ptev_enabled:
|
|
kind = event_kind_start_trace;
|
|
ip = event->variant.enabled.ip;
|
|
break;
|
|
case ptev_disabled:
|
|
kind = s->last_was_syscall ? event_kind_end_trace_syscall : event_kind_end_trace;
|
|
ip = event->variant.disabled.ip;
|
|
break;
|
|
case ptev_async_disabled:
|
|
kind = event_kind_end_trace;
|
|
ip = event->variant.async_disabled.ip;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
if(kind != event_kind_none) {
|
|
Store_field(*s->event, event_field_kind, Val_int(kind));
|
|
if(ip) Store_field(*s->event, event_field_addr, Val_int(ip));
|
|
Store_field(*s->event, event_field_isid, Val_int(0));
|
|
commit_event(s);
|
|
}
|
|
|
|
|
|
struct pt_image *image = NULL;
|
|
error = pt_sb_event(s->session, &image, event, sizeof(*event), stdout,
|
|
0);
|
|
// ptsbp_verbose | ptsbp_filename | ptsbp_file_offset | ptsbp_tsc);
|
|
if (error < 0) return error;
|
|
|
|
if (image) {
|
|
return pt_insn_set_image(s->decoder, image);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static void handle_instruction(struct decoding_state *s, const struct pt_insn *insn) {
|
|
uint64_t ip = insn->ip;
|
|
uint64_t prev_sym_start = Long_val(Field(*s->event, event_field_symbol_begin));
|
|
uint64_t prev_sym_end = Long_val(Field(*s->event, event_field_symbol_end));
|
|
|
|
if(s->pending_event_kind != event_kind_none) {
|
|
Store_field(*s->event, event_field_kind, Val_int(s->pending_event_kind));
|
|
Store_field(*s->event, event_field_addr, Val_int(ip));
|
|
Store_field(*s->event, event_field_isid, Val_int(insn->isid));
|
|
commit_event(s);
|
|
s->pending_event_kind = event_kind_none;
|
|
}
|
|
|
|
enum event_kind kind = event_kind_none;
|
|
switch (insn->iclass) {
|
|
case ptic_call:
|
|
kind = event_kind_call;
|
|
break;
|
|
case ptic_return:
|
|
kind = event_kind_ret;
|
|
break;
|
|
case ptic_jump: case ptic_cond_jump: case ptic_far_jump:
|
|
// do not report a jump as an event if we remain in the address space of the current
|
|
// symbol
|
|
if (prev_sym_start <= ip && ip < prev_sym_end)
|
|
break;
|
|
kind = event_kind_jump;
|
|
break;
|
|
case ptic_far_return: // sysreturn
|
|
case ptic_far_call: // syscall
|
|
case ptic_other:
|
|
/* TODO: handle checking exception handlers */
|
|
// fallthrough
|
|
default:
|
|
break;
|
|
}
|
|
s->last_was_syscall = (insn->iclass == ptic_far_call);
|
|
s->pending_event_kind = kind;
|
|
}
|
|
|
|
|
|
/*** LIBIPT BOILERPLATE ***/
|
|
// https://github.com/intel/libipt/blob/master/doc/howto_libipt.md
|
|
|
|
static void pop_pending_section(struct decoding_state *s) {
|
|
if(s->pending_section_head != NULL) {
|
|
struct pending_section *popped = s->pending_section_head;
|
|
s->pending_section_head = popped->next;
|
|
|
|
Store_field(*s->add_section, add_section_field_filename, caml_copy_string(popped->filename));
|
|
Store_field(*s->add_section, add_section_field_offset, Val_long(popped->offset));
|
|
Store_field(*s->add_section, add_section_field_size, Val_long(popped->size));
|
|
Store_field(*s->add_section, add_section_field_vaddr, Val_long(popped->vaddr));
|
|
Store_field(*s->add_section, add_section_field_isid, Val_int(popped->isid));
|
|
s->pending_decode_result = decode_result_add_section;
|
|
|
|
free(popped->filename);
|
|
free(popped);
|
|
}
|
|
}
|
|
|
|
static int handle_events(struct decoding_state *s, int status) {
|
|
while (status & pts_event_pending) {
|
|
struct pt_event event;
|
|
|
|
status = pt_insn_event(s->decoder, &event, sizeof(event));
|
|
if (status < 0) break;
|
|
|
|
handle_event(s, &event);
|
|
if (s->pending_decode_result != decode_result_none) break;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
// Turn this to true to enable debug printing for decoding issues
|
|
static const bool debug_decoding = false;
|
|
|
|
static int decode_until_event(struct decoding_state *s) {
|
|
uint64_t offset;
|
|
int status = s->saved_status;
|
|
|
|
pop_pending_section(s);
|
|
if (s->pending_decode_result != decode_result_none) return 0;
|
|
|
|
if(!s->pt_synced) {
|
|
status = pt_insn_sync_forward(s->decoder);
|
|
if(debug_decoding) {
|
|
pt_insn_get_offset(s->decoder, &offset);
|
|
printf("1 %d %lx\n", status, offset); fflush(stdout);
|
|
}
|
|
if (status == -pte_eos) {
|
|
s->pending_decode_result = decode_result_end_of_trace;
|
|
return 0;
|
|
}
|
|
if (status < 0) return status;
|
|
s->pt_synced = true;
|
|
}
|
|
|
|
for (;;) {
|
|
struct pt_insn insn;
|
|
status = handle_events(s, status);
|
|
if(debug_decoding) {
|
|
printf("2 %s %d\n", pt_errstr(-status), s->pending_decode_result); fflush(stdout);
|
|
}
|
|
if (status < 0) goto decode_error;
|
|
if (s->pending_decode_result != decode_result_none) break;
|
|
pop_pending_section(s);
|
|
if (s->pending_decode_result != decode_result_none) break;
|
|
|
|
status = pt_insn_next(s->decoder, &insn, sizeof(insn));
|
|
if(debug_decoding) {
|
|
printf("3 %s %d\n", pt_errstr(-status), s->pending_decode_result); fflush(stdout);
|
|
}
|
|
if (insn.iclass != ptic_error) {
|
|
handle_instruction(s, &insn);
|
|
}
|
|
if (status < 0) goto decode_error;
|
|
if (s->pending_decode_result != decode_result_none) break;
|
|
}
|
|
|
|
s->saved_status = status;
|
|
return status;
|
|
|
|
decode_error:
|
|
if(debug_decoding) {
|
|
pt_insn_get_offset(s->decoder, &offset);
|
|
printf("error %s at %lx\n", pt_errstr(-status), offset); fflush(stdout);
|
|
}
|
|
Store_field(*s->event, event_field_kind, Val_int(event_kind_decode_error));
|
|
commit_event(s);
|
|
s->pt_synced = false;
|
|
s->saved_status = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void do_add_section_callback(void *s_void, const char *filename, uint64_t offset,
|
|
uint64_t size, uint64_t vaddr, int isid) {
|
|
if (filename[0] != '/') return;
|
|
struct decoding_state *s = s_void;
|
|
|
|
struct pending_section *section = malloc(sizeof(*section));
|
|
section->filename = strdup(filename);
|
|
section->offset = offset;
|
|
section->size = size;
|
|
section->vaddr = vaddr;
|
|
section->isid = isid;
|
|
section->next = s->pending_section_head;
|
|
s->pending_section_head = section;
|
|
}
|
|
|
|
static int add_initial_images(struct decoding_state *s, value mmap_list, uint32_t pid) {
|
|
CAMLparam1(mmap_list);
|
|
CAMLlocal3(list, cur, filename_val);
|
|
|
|
int error = 0;
|
|
struct pt_sb_context *context;
|
|
error = pt_sb_get_context_by_pid(&context, s->session, pid);
|
|
if(error < 0) CAMLreturn(error);
|
|
|
|
for (list = mmap_list; Is_block(list); list = Field(list, 1)) {
|
|
cur = Field(list, 0);
|
|
filename_val = Field(cur, mmap_field_filename);
|
|
const char *filename = String_val(filename_val);
|
|
uint64_t
|
|
vaddr = Long_val(Field(cur, mmap_field_vaddr)),
|
|
size = Long_val(Field(cur, mmap_field_length)),
|
|
offset = Long_val(Field(cur, mmap_field_offset));
|
|
if (filename[0] != '/') continue;
|
|
|
|
error = pt_sb_ctx_mmap(s->session, context, filename, offset, size, vaddr);
|
|
if (error < 0) break;
|
|
}
|
|
|
|
CAMLreturn(error);
|
|
}
|
|
|
|
static int setup_decoding(struct decoding_state *s, value setup_info, value sb_filename) {
|
|
CAMLparam2(setup_info, sb_filename);
|
|
int error;
|
|
|
|
// mmap
|
|
off_t pt_length = lseek(s->pt_fd, 0, SEEK_END);
|
|
if (pt_length == -1) CAMLreturn (-1);
|
|
s->pt_length = pt_length;
|
|
s->pt_mmap = mmap(NULL, pt_length, PROT_READ, MAP_SHARED, s->pt_fd, 0);
|
|
|
|
if (s->pt_mmap == MAP_FAILED) CAMLreturn (-1);
|
|
|
|
value trace_meta = Field(setup_info, setup_info_field_trace_meta);
|
|
|
|
// instruction decoder
|
|
struct pt_config config;
|
|
|
|
memset(&config, 0, sizeof(config));
|
|
config.size = sizeof(config);
|
|
config.begin = s->pt_mmap;
|
|
config.end = s->pt_mmap + s->pt_length;
|
|
config.flags.variant.insn.enable_tick_events = 1;
|
|
|
|
config.nom_freq = Long_val(Field(trace_meta, trace_meta_field_max_nonturbo_ratio));
|
|
|
|
s->decoder = pt_insn_alloc_decoder(&config);
|
|
if (!s->decoder) CAMLreturn (-pte_bad_config);
|
|
|
|
// image cache
|
|
s->iscache = pt_iscache_alloc(NULL);
|
|
if (!s->iscache) CAMLreturn (-1);
|
|
pt_iscache_set_file_hook(s->iscache, do_add_section_callback, s);
|
|
|
|
// pevent sideband
|
|
s->session = pt_sb_alloc(s->iscache);
|
|
if (!s->session) CAMLreturn(-1);
|
|
|
|
struct pt_sb_pevent_config pevent;
|
|
memset(&pevent, 0, sizeof(pevent));
|
|
pevent.primary = 1;
|
|
pevent.size = sizeof(pevent);
|
|
pevent.kernel_start = UINT64_MAX;
|
|
pevent.filename = String_val(sb_filename);
|
|
pevent.begin = pevent.end = 0;
|
|
pevent.sample_type = s->pev_config.sample_type =
|
|
PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME;
|
|
|
|
pevent.time_shift = s->pev_config.time_shift =
|
|
Long_val(Field(trace_meta, trace_meta_field_time_shift));
|
|
pevent.time_mult = s->pev_config.time_mult =
|
|
Long_val(Field(trace_meta, trace_meta_field_time_mult));
|
|
pevent.time_zero = s->pev_config.time_zero =
|
|
Long_val(Field(trace_meta, trace_meta_field_time_zero));
|
|
|
|
// Use these for debugging with libipt tooling
|
|
// printf("--cpu 6/85/7 --cpuid-0x15.ebx %u --cpuid-0x15.eax %u --nom-freq %u --mtc-freq %u\n",
|
|
// config.cpuid_0x15_ebx, config.cpuid_0x15_eax, config.nom_freq, config.mtc_freq);
|
|
// printf("--pevent:sample-type %lu --pevent:time-shift %u --pevent:time-mult %u --pevent:time-zero %lu --pevent:primary %s\n",
|
|
// pevent.sample_type, pevent.time_shift, pevent.time_mult, pevent.time_zero, pevent.filename);
|
|
|
|
error = pt_sb_alloc_pevent_decoder(s->session, &pevent);
|
|
if (error < 0) CAMLreturn (error);
|
|
|
|
error = pt_sb_init_decoders(s->session);
|
|
if (error < 0) CAMLreturn (error);
|
|
|
|
intnat pid = Long_val(Field(setup_info, setup_info_field_pid));
|
|
error = add_initial_images(s, Field(setup_info, setup_info_field_initial_maps), pid);
|
|
if (error < 0) CAMLreturn (error);
|
|
|
|
CAMLreturn (error);
|
|
}
|
|
|
|
/*** OCAML STUBS ***/
|
|
|
|
#define Decoding_state_val(v) (*((struct decoding_state **) Data_custom_val(v)))
|
|
|
|
static void finalize_decoding_state(value v) {
|
|
destroy_decoding_state(Decoding_state_val(v));
|
|
}
|
|
|
|
|
|
static struct custom_operations decoding_state_ops =
|
|
{ .identifier = "com.janestreet.magic-trace.decoding_state"
|
|
, .finalize = finalize_decoding_state
|
|
, .compare = custom_compare_default
|
|
, .compare_ext = custom_compare_ext_default
|
|
, .hash = custom_hash_default
|
|
, .serialize = custom_serialize_default
|
|
, .deserialize = custom_deserialize_default
|
|
, .fixed_length = custom_fixed_length_default
|
|
};
|
|
|
|
|
|
CAMLprim value magic_pt_init_decoder_stub(value config) {
|
|
CAMLparam1(config);
|
|
CAMLlocal1(v);
|
|
|
|
struct decoding_state *state = malloc(sizeof(*state));
|
|
v = caml_alloc_custom(&decoding_state_ops, sizeof(state), 0, 1);
|
|
Decoding_state_val(v) = state;
|
|
|
|
memset(state, 0, sizeof(*state));
|
|
state->pt_fd = Int_val(Field(config, config_field_pt_data_fd));
|
|
state->pending_event_kind = event_kind_none;
|
|
state->last_was_syscall = false;
|
|
state->pt_synced = false;
|
|
state->pending_section_head = NULL;
|
|
state->saved_status = 0;
|
|
pev_config_init(&state->pev_config);
|
|
|
|
int status = setup_decoding(state,
|
|
Field(config, config_field_setup_info),
|
|
Field(config, config_field_sideband_filename));
|
|
|
|
if(status < 0) {
|
|
destroy_decoding_state(state);
|
|
caml_failwith(pt_errstr(-status));
|
|
}
|
|
|
|
CAMLreturn(v);
|
|
}
|
|
|
|
CAMLprim value magic_pt_run_decoder_stub(value state_v, value event, value add_section) {
|
|
CAMLparam3(state_v, event, add_section);
|
|
|
|
struct decoding_state *state = Decoding_state_val(state_v);
|
|
|
|
// We need to shove these in the state instead of using another parameter because of the
|
|
// add_section callback only having access to the state.
|
|
state->event = &event;
|
|
state->add_section = &add_section;
|
|
state->pending_decode_result = decode_result_none;
|
|
|
|
int result = decode_until_event(state);
|
|
|
|
state->event = NULL;
|
|
state->add_section = NULL;
|
|
|
|
if(result < 0) {
|
|
caml_failwith(pt_errstr(-result));
|
|
}
|
|
|
|
enum decode_result decode_result = state->pending_decode_result;
|
|
assert(decode_result >= 0);
|
|
CAMLreturn(Val_int(decode_result));
|
|
}
|