mirror of
https://github.com/xen-project/xen.git
synced 2026-10-29 18:50:05 +01:00
To unify certain common sanity checks, checks are done very early in processing based only on the top-level type. Unfortunately, when TRC_HVM_EMUL was introduced, it broke some of the assumptions about how the top-level types worked. Namely, traces of this type will show up outside of HVM contexts: in idle domains and in PV domains. Make an explicit exception for TRC_HVM_EMUL types in a number of places: - Pass the record info pointer to toplevel_assert_check, so that it can exclude TRC_HVM_EMUL records from idle and vcpu data_mode checks - Don't attempt to set the vcpu data_type in hvm_process for TRC_HVM_EMUL records. Signed-off-by: George Dunlap <george.dunlap@cloud.com> Acked-by: Andrew Cooper <andrew.cooper3@citrix.com>
10983 lines
312 KiB
C
10983 lines
312 KiB
C
/*
|
|
* xenalyze.c: Analyzing xentrace output
|
|
*
|
|
* Written by George Dunlap.
|
|
*
|
|
* Copyright (c) 2006-2007, XenSource Inc.
|
|
* Copyright (c) 2007-2008, Citrix Systems R&D Ltd, UK
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
* more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* this program; If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
#define _XOPEN_SOURCE 600
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdbool.h>
|
|
#include <argp.h>
|
|
#include <inttypes.h>
|
|
#include <limits.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <xen/trace.h>
|
|
#include "analyze.h"
|
|
#include "mread.h"
|
|
#include "pv.h"
|
|
#include <errno.h>
|
|
#include <strings.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
struct mread_ctrl;
|
|
|
|
|
|
#define DEFAULT_CPU_HZ 2400000000LL
|
|
#define QHZ_FROM_HZ(_hz) (((_hz) << 10)/ 1000000000)
|
|
|
|
#define ADDR_SPACE_BITS 48
|
|
#define DEFAULT_SAMPLE_SIZE 1024
|
|
#define DEFAULT_SAMPLE_MAX 1024*1024*32
|
|
#define DEFAULT_INTERVAL_LENGTH 1000
|
|
|
|
struct array_struct {
|
|
unsigned long long *values;
|
|
int count;
|
|
};
|
|
|
|
#define warn_once(_x...) \
|
|
do { \
|
|
static int _w=1; \
|
|
if ( _w ) { \
|
|
_w=0; \
|
|
fprintf(warn, ##_x); \
|
|
} \
|
|
} while(0) \
|
|
|
|
/* -- Global variables -- */
|
|
struct {
|
|
int fd;
|
|
struct mread_ctrl *mh;
|
|
struct symbol_struct * symbols;
|
|
char * symbol_file;
|
|
char * trace_file;
|
|
int output_defined;
|
|
off_t file_size;
|
|
struct {
|
|
off_t update_offset;
|
|
int pipe[2];
|
|
FILE* out;
|
|
int pid;
|
|
} progress;
|
|
} G = {
|
|
.fd=-1,
|
|
.symbols = NULL,
|
|
.symbol_file = NULL,
|
|
.trace_file = NULL,
|
|
.output_defined = 0,
|
|
.file_size = 0,
|
|
.progress = { .update_offset = 0 },
|
|
};
|
|
|
|
/*
|
|
Kinds of errors:
|
|
Unexpected values
|
|
- RIP with information in high bits (not all 0 or 1)
|
|
- exit reason too high
|
|
Unexpected record layout
|
|
- x64 bit set in PIO,PV_PTWR_EMULATION_PAE,
|
|
- Unknown minor type (PV_PTWR_EMULATION, RUNSTATE_CHANGE
|
|
- Wrong record size
|
|
- More than one bit set in evt.main field
|
|
Unexpected sequences
|
|
- wake tsc tracking
|
|
- TSC dependency loop
|
|
- Mismatch between non-running old event states
|
|
- Runstate continue while running on another pcpu
|
|
- lost_record_end seen in non-lost pcpu
|
|
- Unexpected non-CPU_CHANGE record during new_pcpu scan
|
|
- record tsc < interval start tsc
|
|
- lost_record tsc !> order tsc
|
|
Limited resources
|
|
- interrupt interval slots
|
|
- record cpu > MAX_CPUS
|
|
Algorithm asserts
|
|
- Duplicate CR3/domain values
|
|
- Logic holes
|
|
- domain runstates
|
|
- runstate / tsc skew
|
|
- vcpu_{prev,next}_update p->current{==,!=}null
|
|
- vcpu start conditions
|
|
- lost_cpu count higher than # of seen cpus / < 0
|
|
- lost cpu has non-null p->current
|
|
Symbol file
|
|
-file doesn't open
|
|
-file not ordered
|
|
System
|
|
- short read
|
|
- malloc failed
|
|
Args
|
|
- Invalid cpu_hz value / suffix
|
|
- No trace file
|
|
- Can't open trace file
|
|
*/
|
|
enum error_level {
|
|
ERR_NONE=0,
|
|
ERR_STRICT, /* Be unreasonably picky */
|
|
ERR_WARN, /* Something midly unexpected */
|
|
ERR_SANITY, /* Sanity checks: RIP with info in high bits */
|
|
ERR_RECORD, /* Something that keeps you from processing the record */
|
|
ERR_FILE, /* Probably caused by a corrupt file */
|
|
ERR_LIMIT, /* Exceeded limits; data will be lost */
|
|
ERR_MAX_TOLERABLE=ERR_LIMIT,
|
|
/* -- Unrecoverable past this point -- */
|
|
ERR_ASSERT, /* Algoritm assert */
|
|
ERR_SYSTEM, /* System error: cannot allocate memory, short read, &c */
|
|
};
|
|
|
|
int verbosity = 5;
|
|
|
|
struct {
|
|
unsigned
|
|
scatterplot_interrupt_eip:1,
|
|
scatterplot_unpin_promote:1,
|
|
scatterplot_cr3_switch:1,
|
|
scatterplot_wake_to_halt:1,
|
|
scatterplot_io:1,
|
|
scatterplot_vmexit_eip:1,
|
|
scatterplot_runstate:1,
|
|
scatterplot_runstate_time:1,
|
|
scatterplot_pcpu:1,
|
|
scatterplot_extint_cycles:1,
|
|
scatterplot_rdtsc:1,
|
|
scatterplot_irq:1,
|
|
histogram_interrupt_eip:1,
|
|
interval_mode:1,
|
|
dump_all:1,
|
|
dump_raw_process:1,
|
|
dump_raw_reads:1,
|
|
dump_no_processing:1,
|
|
dump_ipi_latency:1,
|
|
dump_trace_volume_on_lost_record:1,
|
|
dump_show_power_states:1,
|
|
with_cr3_enumeration:1,
|
|
with_pio_enumeration:1,
|
|
with_mmio_enumeration:1,
|
|
with_interrupt_eip_enumeration:1,
|
|
show_default_domain_summary:1,
|
|
mmio_enumeration_skip_vga:1,
|
|
progress:1,
|
|
svm_mode:1,
|
|
summary:1,
|
|
report_pcpu:1,
|
|
tsc_loop_fatal:1,
|
|
summary_info;
|
|
long long cpu_qhz, cpu_hz;
|
|
int scatterplot_interrupt_vector;
|
|
int scatterplot_extint_cycles_vector;
|
|
int scatterplot_io_port;
|
|
int histogram_interrupt_vector;
|
|
unsigned long long histogram_interrupt_increment;
|
|
int interrupt_eip_enumeration_vector;
|
|
int default_guest_paging_levels;
|
|
int sample_size, sample_max;
|
|
enum error_level tolerance; /* Tolerate up to this level of error */
|
|
struct {
|
|
tsc_t cycles;
|
|
/* Used if interval is specified in seconds to delay calculating
|
|
* time_interval until all arguments have been processed (specifically,
|
|
* cpu_hz). */
|
|
unsigned msec;
|
|
enum {
|
|
INTERVAL_CR3_SCHEDULE_TIME,
|
|
INTERVAL_CR3_SCHEDULE_ORDERED,
|
|
INTERVAL_CR3_SHORT_SUMMARY,
|
|
INTERVAL_DOMAIN_TOTAL_TIME,
|
|
INTERVAL_DOMAIN_SHORT_SUMMARY,
|
|
INTERVAL_DOMAIN_GUEST_INTERRUPT,
|
|
INTERVAL_DOMAIN_GRANT_MAPS
|
|
} output;
|
|
enum {
|
|
INTERVAL_MODE_CUSTOM,
|
|
INTERVAL_MODE_ARRAY,
|
|
INTERVAL_MODE_LIST
|
|
} mode;
|
|
enum {
|
|
INTERVAL_CHECK_NONE,
|
|
INTERVAL_CHECK_CR3,
|
|
INTERVAL_CHECK_DOMAIN
|
|
} check;
|
|
/* Options for specific interval output types */
|
|
union {
|
|
struct array_struct array;
|
|
};
|
|
int count;
|
|
} interval;
|
|
} opt = {
|
|
.scatterplot_interrupt_eip=0,
|
|
.scatterplot_unpin_promote=0,
|
|
.scatterplot_cr3_switch=0,
|
|
.scatterplot_wake_to_halt=0,
|
|
.scatterplot_vmexit_eip=0,
|
|
.scatterplot_runstate=0,
|
|
.scatterplot_runstate_time=0,
|
|
.scatterplot_pcpu=0,
|
|
.scatterplot_extint_cycles=0,
|
|
.scatterplot_rdtsc=0,
|
|
.scatterplot_irq=0,
|
|
.histogram_interrupt_eip=0,
|
|
.dump_all = 0,
|
|
.dump_raw_process = 0,
|
|
.dump_raw_reads = 0,
|
|
.dump_no_processing = 0,
|
|
.dump_ipi_latency = 0,
|
|
.dump_trace_volume_on_lost_record = 0,
|
|
.dump_show_power_states = 0,
|
|
.with_cr3_enumeration = 0,
|
|
.with_pio_enumeration = 1,
|
|
.with_mmio_enumeration = 0,
|
|
.with_interrupt_eip_enumeration = 0,
|
|
.show_default_domain_summary = 0,
|
|
.mmio_enumeration_skip_vga = 1,
|
|
.progress = 0,
|
|
.svm_mode = 0,
|
|
.summary = 0,
|
|
.report_pcpu = 0,
|
|
.tsc_loop_fatal = 0,
|
|
.cpu_hz = DEFAULT_CPU_HZ,
|
|
/* Pre-calculate a multiplier that makes the rest of the
|
|
* calculations easier */
|
|
.cpu_qhz = QHZ_FROM_HZ(DEFAULT_CPU_HZ),
|
|
.default_guest_paging_levels = 2,
|
|
.sample_size = DEFAULT_SAMPLE_SIZE,
|
|
.sample_max = DEFAULT_SAMPLE_MAX,
|
|
.tolerance = ERR_SANITY,
|
|
.interval = { .msec = DEFAULT_INTERVAL_LENGTH },
|
|
};
|
|
|
|
FILE *warn = NULL;
|
|
|
|
/* -- Summary data -- */
|
|
struct cycle_framework {
|
|
tsc_t first_tsc, last_tsc, total_cycles;
|
|
};
|
|
|
|
struct interval_element {
|
|
int count;
|
|
long long cycles;
|
|
long long instructions;
|
|
};
|
|
|
|
struct cycle_summary {
|
|
int event_count, count, sample_size;
|
|
unsigned long long cycles;
|
|
long long *sample;
|
|
struct interval_element interval;
|
|
};
|
|
|
|
/* -- Symbol list information -- */
|
|
#define SYMBOL_ENTRIES_PER_STRUCT 1023
|
|
#define SYMBOL_NAME_SIZE 124
|
|
struct symbol_struct {
|
|
int count;
|
|
struct {
|
|
unsigned long long addr;
|
|
char name[SYMBOL_NAME_SIZE];
|
|
} symbols[SYMBOL_ENTRIES_PER_STRUCT];
|
|
struct symbol_struct *next;
|
|
};
|
|
|
|
void error(enum error_level l, struct record_info *ri);
|
|
|
|
void parse_symbol_file(char *fn) {
|
|
unsigned long long last_addr = 0;
|
|
FILE * symbol_file;
|
|
struct symbol_struct ** p=&G.symbols;
|
|
|
|
if((symbol_file=fopen(fn, "rb"))==NULL) {
|
|
fprintf(stderr, "Could not open symbol file %s\n", fn);
|
|
perror("open");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
while(!feof(symbol_file)) {
|
|
/* Allocate a new struct if we need it */
|
|
if(!*p) {
|
|
*p = malloc(sizeof(**p));
|
|
if(!*p) {
|
|
fprintf(stderr, "Malloc failed!\n");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
(*p)->count=0;
|
|
(*p)->next=NULL;
|
|
}
|
|
|
|
/* FIXME -- use SYMBOL_NAME_SIZE */
|
|
/* FIXME -- use regexp. This won't work for symbols with spaces (yes they exist) */
|
|
(*p)->symbols[(*p)->count].addr = 0xDEADBEEF;
|
|
if ( fscanf(symbol_file, "%llx %128s",
|
|
&(*p)->symbols[(*p)->count].addr,
|
|
(*p)->symbols[(*p)->count].name) == 0 )
|
|
break;
|
|
|
|
|
|
if( ((*p)->symbols[(*p)->count].addr > 0)
|
|
&& ((*p)->symbols[(*p)->count].addr < last_addr) ) {
|
|
fprintf(stderr, "Symbol file not properly ordered: %llx %s < %llx!\n",
|
|
(*p)->symbols[(*p)->count].addr,
|
|
(*p)->symbols[(*p)->count].name,
|
|
last_addr);
|
|
/* Could be recovered from; just free existing strings and set symbols to NULL */
|
|
error(ERR_ASSERT, NULL);
|
|
} else
|
|
last_addr = (*p)->symbols[(*p)->count].addr;
|
|
|
|
(*p)->count++;
|
|
|
|
/* If this struct is full, point to the next. It will be allocated
|
|
if needed. */
|
|
if((*p)->count == SYMBOL_ENTRIES_PER_STRUCT) {
|
|
p=&((*p)->next);
|
|
}
|
|
}
|
|
|
|
fclose(symbol_file);
|
|
}
|
|
|
|
/* WARNING not thread safe */
|
|
char * find_symbol(unsigned long long addr) {
|
|
struct symbol_struct * p=G.symbols;
|
|
int i;
|
|
const char * lastname="ZERO";
|
|
unsigned long long offset=addr;
|
|
static char name[144];
|
|
|
|
if(!p) {
|
|
name[0]=0;
|
|
return name;
|
|
}
|
|
|
|
while(1) {
|
|
if(!p)
|
|
goto finish;
|
|
for(i=0; i<p->count; i++) {
|
|
if(p->symbols[i].addr > addr)
|
|
goto finish;
|
|
else {
|
|
lastname=p->symbols[i].name;
|
|
offset=addr - p->symbols[i].addr;
|
|
}
|
|
}
|
|
p=p->next;
|
|
}
|
|
finish:
|
|
snprintf(name, 144, "(%s +%llx)",
|
|
lastname, offset);
|
|
return name;
|
|
}
|
|
|
|
/* -- Eip list data -- */
|
|
enum {
|
|
EIP_LIST_TYPE_NONE=0,
|
|
EIP_LIST_TYPE_MAX
|
|
};
|
|
|
|
struct eip_list_struct {
|
|
struct eip_list_struct *next;
|
|
unsigned long long eip;
|
|
struct cycle_summary summary;
|
|
int type;
|
|
void * extra;
|
|
};
|
|
|
|
struct {
|
|
void (*update)(struct eip_list_struct *, void *);
|
|
void (*new)(struct eip_list_struct *, void *);
|
|
void (*dump)(struct eip_list_struct *);
|
|
} eip_list_type[EIP_LIST_TYPE_MAX] = {
|
|
[EIP_LIST_TYPE_NONE] = {
|
|
.update=NULL,
|
|
.new=NULL,
|
|
.dump=NULL },
|
|
};
|
|
|
|
|
|
/* --- HVM class of events --- */
|
|
|
|
/*
|
|
* -- Algorithms --
|
|
*
|
|
* Interrupt Wake-to-halt detection
|
|
*
|
|
* Purpose: To correlate device interrupts to vcpu runtime.
|
|
*
|
|
* Diagram:
|
|
* ...
|
|
* blocked -> runnable <- set to waking
|
|
* ...
|
|
* runnable -> running
|
|
* inj_virq A <- Note "waking" interrupt
|
|
* vmenter <- Start tsc of "wake-to-halt" interval.
|
|
Turn off 'waking'.
|
|
* ...
|
|
* inj_virq B <- Note alternate interrupt
|
|
* vmenter <- Start tsc of "interrupt-to-halt" interval
|
|
* ...
|
|
* vmexit <- End tsc of "x-to-halt" interval
|
|
* running -> blocked <- Process
|
|
*
|
|
* The "waking" interrupts we want to sub-classify into
|
|
* "wake-only" (when interrupt was the only interrupt from wake to halt) and
|
|
* "wake-all" (whether this was the only interrupt or not).
|
|
*/
|
|
|
|
/* VMX data */
|
|
#define EXIT_REASON_EXCEPTION_NMI 0
|
|
#define EXIT_REASON_EXTERNAL_INTERRUPT 1
|
|
#define EXIT_REASON_TRIPLE_FAULT 2
|
|
#define EXIT_REASON_INIT 3
|
|
#define EXIT_REASON_SIPI 4
|
|
#define EXIT_REASON_IO_SMI 5
|
|
#define EXIT_REASON_OTHER_SMI 6
|
|
#define EXIT_REASON_PENDING_VIRT_INTR 7
|
|
#define EXIT_REASON_PENDING_VIRT_NMI 8
|
|
#define EXIT_REASON_TASK_SWITCH 9
|
|
#define EXIT_REASON_CPUID 10
|
|
#define EXIT_REASON_GETSEC 11
|
|
#define EXIT_REASON_HLT 12
|
|
#define EXIT_REASON_INVD 13
|
|
#define EXIT_REASON_INVLPG 14
|
|
#define EXIT_REASON_RDPMC 15
|
|
#define EXIT_REASON_RDTSC 16
|
|
#define EXIT_REASON_RSM 17
|
|
#define EXIT_REASON_VMCALL 18
|
|
#define EXIT_REASON_VMCLEAR 19
|
|
#define EXIT_REASON_VMLAUNCH 20
|
|
#define EXIT_REASON_VMPTRLD 21
|
|
#define EXIT_REASON_VMPTRST 22
|
|
#define EXIT_REASON_VMREAD 23
|
|
#define EXIT_REASON_VMRESUME 24
|
|
#define EXIT_REASON_VMWRITE 25
|
|
#define EXIT_REASON_VMXOFF 26
|
|
#define EXIT_REASON_VMXON 27
|
|
#define EXIT_REASON_CR_ACCESS 28
|
|
#define EXIT_REASON_DR_ACCESS 29
|
|
#define EXIT_REASON_IO_INSTRUCTION 30
|
|
#define EXIT_REASON_MSR_READ 31
|
|
#define EXIT_REASON_MSR_WRITE 32
|
|
#define EXIT_REASON_INVALID_GUEST_STATE 33
|
|
#define EXIT_REASON_MSR_LOADING 34
|
|
#define EXIT_REASON_MWAIT_INSTRUCTION 36
|
|
#define EXIT_REASON_MONITOR_TRAP_FLAG 37
|
|
#define EXIT_REASON_MONITOR_INSTRUCTION 39
|
|
#define EXIT_REASON_PAUSE_INSTRUCTION 40
|
|
#define EXIT_REASON_MCE_DURING_VMENTRY 41
|
|
#define EXIT_REASON_TPR_BELOW_THRESHOLD 43
|
|
#define EXIT_REASON_APIC_ACCESS 44
|
|
#define EXIT_REASON_ACCESS_GDTR_OR_IDTR 46
|
|
#define EXIT_REASON_ACCESS_LDTR_OR_TR 47
|
|
#define EXIT_REASON_EPT_VIOLATION 48
|
|
#define EXIT_REASON_EPT_MISCONFIG 49
|
|
#define EXIT_REASON_INVEPT 50
|
|
#define EXIT_REASON_RDTSCP 51
|
|
#define EXIT_REASON_VMX_PREEMPTION_TIMER_EXPIRED 52
|
|
#define EXIT_REASON_INVVPID 53
|
|
#define EXIT_REASON_WBINVD 54
|
|
#define EXIT_REASON_XSETBV 55
|
|
|
|
#define HVM_VMX_EXIT_REASON_MAX (EXIT_REASON_XSETBV+1)
|
|
|
|
const char * hvm_vmx_exit_reason_name[HVM_VMX_EXIT_REASON_MAX] = {
|
|
[EXIT_REASON_EXCEPTION_NMI]="EXCEPTION_NMI",
|
|
[EXIT_REASON_EXTERNAL_INTERRUPT]="EXTERNAL_INTERRUPT",
|
|
[EXIT_REASON_TRIPLE_FAULT]="TRIPLE_FAULT",
|
|
[EXIT_REASON_INIT]="INIT",
|
|
[EXIT_REASON_SIPI]="SIPI",
|
|
[EXIT_REASON_IO_SMI]="IO_SMI",
|
|
[EXIT_REASON_OTHER_SMI]="OTHER_SMI",
|
|
[EXIT_REASON_PENDING_VIRT_INTR]="PENDING_VIRT_INTR",
|
|
[EXIT_REASON_PENDING_VIRT_NMI]="PENDING_VIRT_NMI",
|
|
[EXIT_REASON_TASK_SWITCH]="TASK_SWITCH",
|
|
[EXIT_REASON_CPUID]="CPUID",
|
|
[EXIT_REASON_GETSEC]="GETSEC",
|
|
[EXIT_REASON_HLT]="HLT",
|
|
[EXIT_REASON_INVD]="INVD",
|
|
[EXIT_REASON_INVLPG]="INVLPG",
|
|
[EXIT_REASON_RDPMC]="RDPMC",
|
|
[EXIT_REASON_RDTSC]="RDTSC",
|
|
[EXIT_REASON_RSM]="RSM",
|
|
[EXIT_REASON_VMCALL]="VMCALL",
|
|
[EXIT_REASON_VMCLEAR]="VMCLEAR",
|
|
[EXIT_REASON_VMLAUNCH]="VMLAUNCH",
|
|
[EXIT_REASON_VMPTRLD]="VMPTRLD",
|
|
[EXIT_REASON_VMPTRST]="VMPTRST",
|
|
[EXIT_REASON_VMREAD]="VMREAD",
|
|
[EXIT_REASON_VMRESUME]="VMRESUME",
|
|
[EXIT_REASON_VMWRITE]="VMWRITE",
|
|
[EXIT_REASON_VMXOFF]="VMXOFF",
|
|
[EXIT_REASON_VMXON]="VMXON",
|
|
[EXIT_REASON_CR_ACCESS]="CR_ACCESS",
|
|
[EXIT_REASON_DR_ACCESS]="DR_ACCESS",
|
|
[EXIT_REASON_IO_INSTRUCTION]="IO_INSTRUCTION",
|
|
[EXIT_REASON_MSR_READ]="MSR_READ",
|
|
[EXIT_REASON_MSR_WRITE]="MSR_WRITE",
|
|
[EXIT_REASON_INVALID_GUEST_STATE]="INVALID_GUEST_STATE",
|
|
[EXIT_REASON_MSR_LOADING]="MSR_LOADING",
|
|
[EXIT_REASON_MWAIT_INSTRUCTION]="MWAIT_INSTRUCTION",
|
|
[EXIT_REASON_MONITOR_TRAP_FLAG]="MONITOR_TRAP_FLAG",
|
|
[EXIT_REASON_MONITOR_INSTRUCTION]="MONITOR_INSTRUCTION",
|
|
[EXIT_REASON_PAUSE_INSTRUCTION]="PAUSE_INSTRUCTION",
|
|
[EXIT_REASON_MCE_DURING_VMENTRY]="MCE_DURING_VMENTRY",
|
|
[EXIT_REASON_TPR_BELOW_THRESHOLD]="TPR_BELOW_THRESHOLD",
|
|
[EXIT_REASON_APIC_ACCESS]="APIC_ACCESS",
|
|
[EXIT_REASON_EPT_VIOLATION]="EPT_VIOLATION",
|
|
[EXIT_REASON_EPT_MISCONFIG]="EPT_MISCONFIG",
|
|
[EXIT_REASON_INVEPT]="INVEPT",
|
|
[EXIT_REASON_RDTSCP]="RDTSCP",
|
|
[EXIT_REASON_VMX_PREEMPTION_TIMER_EXPIRED]="VMX_PREEMPTION_TIMER_EXPIRED",
|
|
[EXIT_REASON_INVVPID]="INVVPID",
|
|
[EXIT_REASON_WBINVD]="WBINVD",
|
|
[EXIT_REASON_XSETBV]="XSETBV",
|
|
};
|
|
|
|
/* SVM data */
|
|
enum VMEXIT_EXITCODE
|
|
{
|
|
/* control register read exitcodes */
|
|
VMEXIT_CR0_READ = 0,
|
|
VMEXIT_CR1_READ = 1,
|
|
VMEXIT_CR2_READ = 2,
|
|
VMEXIT_CR3_READ = 3,
|
|
VMEXIT_CR4_READ = 4,
|
|
VMEXIT_CR5_READ = 5,
|
|
VMEXIT_CR6_READ = 6,
|
|
VMEXIT_CR7_READ = 7,
|
|
VMEXIT_CR8_READ = 8,
|
|
VMEXIT_CR9_READ = 9,
|
|
VMEXIT_CR10_READ = 10,
|
|
VMEXIT_CR11_READ = 11,
|
|
VMEXIT_CR12_READ = 12,
|
|
VMEXIT_CR13_READ = 13,
|
|
VMEXIT_CR14_READ = 14,
|
|
VMEXIT_CR15_READ = 15,
|
|
|
|
/* control register write exitcodes */
|
|
VMEXIT_CR0_WRITE = 16,
|
|
VMEXIT_CR1_WRITE = 17,
|
|
VMEXIT_CR2_WRITE = 18,
|
|
VMEXIT_CR3_WRITE = 19,
|
|
VMEXIT_CR4_WRITE = 20,
|
|
VMEXIT_CR5_WRITE = 21,
|
|
VMEXIT_CR6_WRITE = 22,
|
|
VMEXIT_CR7_WRITE = 23,
|
|
VMEXIT_CR8_WRITE = 24,
|
|
VMEXIT_CR9_WRITE = 25,
|
|
VMEXIT_CR10_WRITE = 26,
|
|
VMEXIT_CR11_WRITE = 27,
|
|
VMEXIT_CR12_WRITE = 28,
|
|
VMEXIT_CR13_WRITE = 29,
|
|
VMEXIT_CR14_WRITE = 30,
|
|
VMEXIT_CR15_WRITE = 31,
|
|
|
|
/* debug register read exitcodes */
|
|
VMEXIT_DR0_READ = 32,
|
|
VMEXIT_DR1_READ = 33,
|
|
VMEXIT_DR2_READ = 34,
|
|
VMEXIT_DR3_READ = 35,
|
|
VMEXIT_DR4_READ = 36,
|
|
VMEXIT_DR5_READ = 37,
|
|
VMEXIT_DR6_READ = 38,
|
|
VMEXIT_DR7_READ = 39,
|
|
VMEXIT_DR8_READ = 40,
|
|
VMEXIT_DR9_READ = 41,
|
|
VMEXIT_DR10_READ = 42,
|
|
VMEXIT_DR11_READ = 43,
|
|
VMEXIT_DR12_READ = 44,
|
|
VMEXIT_DR13_READ = 45,
|
|
VMEXIT_DR14_READ = 46,
|
|
VMEXIT_DR15_READ = 47,
|
|
|
|
/* debug register write exitcodes */
|
|
VMEXIT_DR0_WRITE = 48,
|
|
VMEXIT_DR1_WRITE = 49,
|
|
VMEXIT_DR2_WRITE = 50,
|
|
VMEXIT_DR3_WRITE = 51,
|
|
VMEXIT_DR4_WRITE = 52,
|
|
VMEXIT_DR5_WRITE = 53,
|
|
VMEXIT_DR6_WRITE = 54,
|
|
VMEXIT_DR7_WRITE = 55,
|
|
VMEXIT_DR8_WRITE = 56,
|
|
VMEXIT_DR9_WRITE = 57,
|
|
VMEXIT_DR10_WRITE = 58,
|
|
VMEXIT_DR11_WRITE = 59,
|
|
VMEXIT_DR12_WRITE = 60,
|
|
VMEXIT_DR13_WRITE = 61,
|
|
VMEXIT_DR14_WRITE = 62,
|
|
VMEXIT_DR15_WRITE = 63,
|
|
|
|
/* processor exception exitcodes (VMEXIT_EXCP[0-31]) */
|
|
VMEXIT_EXCEPTION_DE = 64, /* divide-by-zero-error */
|
|
VMEXIT_EXCEPTION_DB = 65, /* debug */
|
|
VMEXIT_EXCEPTION_NMI = 66, /* non-maskable-interrupt */
|
|
VMEXIT_EXCEPTION_BP = 67, /* breakpoint */
|
|
VMEXIT_EXCEPTION_OF = 68, /* overflow */
|
|
VMEXIT_EXCEPTION_BR = 69, /* bound-range */
|
|
VMEXIT_EXCEPTION_UD = 70, /* invalid-opcode*/
|
|
VMEXIT_EXCEPTION_NM = 71, /* device-not-available */
|
|
VMEXIT_EXCEPTION_DF = 72, /* double-fault */
|
|
VMEXIT_EXCEPTION_09 = 73, /* unsupported (reserved) */
|
|
VMEXIT_EXCEPTION_TS = 74, /* invalid-tss */
|
|
VMEXIT_EXCEPTION_NP = 75, /* segment-not-present */
|
|
VMEXIT_EXCEPTION_SS = 76, /* stack */
|
|
VMEXIT_EXCEPTION_GP = 77, /* general-protection */
|
|
VMEXIT_EXCEPTION_PF = 78, /* page-fault */
|
|
VMEXIT_EXCEPTION_15 = 79, /* reserved */
|
|
VMEXIT_EXCEPTION_MF = 80, /* x87 floating-point exception-pending */
|
|
VMEXIT_EXCEPTION_AC = 81, /* alignment-check */
|
|
VMEXIT_EXCEPTION_MC = 82, /* machine-check */
|
|
VMEXIT_EXCEPTION_XF = 83, /* simd floating-point */
|
|
|
|
/* exceptions 20-31 (exitcodes 84-95) are reserved */
|
|
|
|
/* ...and the rest of the #VMEXITs */
|
|
VMEXIT_INTR = 96,
|
|
VMEXIT_NMI = 97,
|
|
VMEXIT_SMI = 98,
|
|
VMEXIT_INIT = 99,
|
|
VMEXIT_VINTR = 100,
|
|
VMEXIT_CR0_SEL_WRITE = 101,
|
|
VMEXIT_IDTR_READ = 102,
|
|
VMEXIT_GDTR_READ = 103,
|
|
VMEXIT_LDTR_READ = 104,
|
|
VMEXIT_TR_READ = 105,
|
|
VMEXIT_IDTR_WRITE = 106,
|
|
VMEXIT_GDTR_WRITE = 107,
|
|
VMEXIT_LDTR_WRITE = 108,
|
|
VMEXIT_TR_WRITE = 109,
|
|
VMEXIT_RDTSC = 110,
|
|
VMEXIT_RDPMC = 111,
|
|
VMEXIT_PUSHF = 112,
|
|
VMEXIT_POPF = 113,
|
|
VMEXIT_CPUID = 114,
|
|
VMEXIT_RSM = 115,
|
|
VMEXIT_IRET = 116,
|
|
VMEXIT_SWINT = 117,
|
|
VMEXIT_INVD = 118,
|
|
VMEXIT_PAUSE = 119,
|
|
VMEXIT_HLT = 120,
|
|
VMEXIT_INVLPG = 121,
|
|
VMEXIT_INVLPGA = 122,
|
|
VMEXIT_IOIO = 123,
|
|
VMEXIT_MSR = 124,
|
|
VMEXIT_TASK_SWITCH = 125,
|
|
VMEXIT_FERR_FREEZE = 126,
|
|
VMEXIT_SHUTDOWN = 127,
|
|
VMEXIT_VMRUN = 128,
|
|
VMEXIT_VMMCALL = 129,
|
|
VMEXIT_VMLOAD = 130,
|
|
VMEXIT_VMSAVE = 131,
|
|
VMEXIT_STGI = 132,
|
|
VMEXIT_CLGI = 133,
|
|
VMEXIT_SKINIT = 134,
|
|
VMEXIT_RDTSCP = 135,
|
|
VMEXIT_ICEBP = 136,
|
|
VMEXIT_WBINVD = 137,
|
|
VMEXIT_MONITOR = 138,
|
|
VMEXIT_MWAIT = 139,
|
|
VMEXIT_MWAIT_CONDITIONAL= 140,
|
|
VMEXIT_NPF = 1024, /* nested paging fault */
|
|
VMEXIT_INVALID = -1
|
|
};
|
|
|
|
#define HVM_SVM_EXIT_REASON_MAX 1025
|
|
const char * hvm_svm_exit_reason_name[HVM_SVM_EXIT_REASON_MAX] = {
|
|
/* 0-15 */
|
|
"VMEXIT_CR0_READ",
|
|
"VMEXIT_CR1_READ",
|
|
"VMEXIT_CR2_READ",
|
|
"VMEXIT_CR3_READ",
|
|
"VMEXIT_CR4_READ",
|
|
"VMEXIT_CR5_READ",
|
|
"VMEXIT_CR6_READ",
|
|
"VMEXIT_CR7_READ",
|
|
"VMEXIT_CR8_READ",
|
|
"VMEXIT_CR9_READ",
|
|
"VMEXIT_CR10_READ",
|
|
"VMEXIT_CR11_READ",
|
|
"VMEXIT_CR12_READ",
|
|
"VMEXIT_CR13_READ",
|
|
"VMEXIT_CR14_READ",
|
|
"VMEXIT_CR15_READ",
|
|
/* 16-31 */
|
|
"VMEXIT_CR0_WRITE",
|
|
"VMEXIT_CR1_WRITE",
|
|
"VMEXIT_CR2_WRITE",
|
|
"VMEXIT_CR3_WRITE",
|
|
"VMEXIT_CR4_WRITE",
|
|
"VMEXIT_CR5_WRITE",
|
|
"VMEXIT_CR6_WRITE",
|
|
"VMEXIT_CR7_WRITE",
|
|
"VMEXIT_CR8_WRITE",
|
|
"VMEXIT_CR9_WRITE",
|
|
"VMEXIT_CR10_WRITE",
|
|
"VMEXIT_CR11_WRITE",
|
|
"VMEXIT_CR12_WRITE",
|
|
"VMEXIT_CR13_WRITE",
|
|
"VMEXIT_CR14_WRITE",
|
|
"VMEXIT_CR15_WRITE",
|
|
/* 32-47 */
|
|
"VMEXIT_DR0_READ",
|
|
"VMEXIT_DR1_READ",
|
|
"VMEXIT_DR2_READ",
|
|
"VMEXIT_DR3_READ",
|
|
"VMEXIT_DR4_READ",
|
|
"VMEXIT_DR5_READ",
|
|
"VMEXIT_DR6_READ",
|
|
"VMEXIT_DR7_READ",
|
|
"VMEXIT_DR8_READ",
|
|
"VMEXIT_DR9_READ",
|
|
"VMEXIT_DR10_READ",
|
|
"VMEXIT_DR11_READ",
|
|
"VMEXIT_DR12_READ",
|
|
"VMEXIT_DR13_READ",
|
|
"VMEXIT_DR14_READ",
|
|
"VMEXIT_DR15_READ",
|
|
/* 48-63 */
|
|
"VMEXIT_DR0_WRITE",
|
|
"VMEXIT_DR1_WRITE",
|
|
"VMEXIT_DR2_WRITE",
|
|
"VMEXIT_DR3_WRITE",
|
|
"VMEXIT_DR4_WRITE",
|
|
"VMEXIT_DR5_WRITE",
|
|
"VMEXIT_DR6_WRITE",
|
|
"VMEXIT_DR7_WRITE",
|
|
"VMEXIT_DR8_WRITE",
|
|
"VMEXIT_DR9_WRITE",
|
|
"VMEXIT_DR10_WRITE",
|
|
"VMEXIT_DR11_WRITE",
|
|
"VMEXIT_DR12_WRITE",
|
|
"VMEXIT_DR13_WRITE",
|
|
"VMEXIT_DR14_WRITE",
|
|
"VMEXIT_DR15_WRITE",
|
|
/* 64-83 */
|
|
"VMEXIT_EXCEPTION_DE",
|
|
"VMEXIT_EXCEPTION_DB",
|
|
"VMEXIT_EXCEPTION_NMI",
|
|
"VMEXIT_EXCEPTION_BP",
|
|
"VMEXIT_EXCEPTION_OF",
|
|
"VMEXIT_EXCEPTION_BR",
|
|
"VMEXIT_EXCEPTION_UD",
|
|
"VMEXIT_EXCEPTION_NM",
|
|
"VMEXIT_EXCEPTION_DF",
|
|
"VMEXIT_EXCEPTION_09",
|
|
"VMEXIT_EXCEPTION_TS",
|
|
"VMEXIT_EXCEPTION_NP",
|
|
"VMEXIT_EXCEPTION_SS",
|
|
"VMEXIT_EXCEPTION_GP",
|
|
"VMEXIT_EXCEPTION_PF",
|
|
"VMEXIT_EXCEPTION_15",
|
|
"VMEXIT_EXCEPTION_MF",
|
|
"VMEXIT_EXCEPTION_AC",
|
|
"VMEXIT_EXCEPTION_MC",
|
|
"VMEXIT_EXCEPTION_XF",
|
|
/* 84-95 */
|
|
"VMEXIT_EXCEPTION_20",
|
|
"VMEXIT_EXCEPTION_21",
|
|
"VMEXIT_EXCEPTION_22",
|
|
"VMEXIT_EXCEPTION_23",
|
|
"VMEXIT_EXCEPTION_24",
|
|
"VMEXIT_EXCEPTION_25",
|
|
"VMEXIT_EXCEPTION_26",
|
|
"VMEXIT_EXCEPTION_27",
|
|
"VMEXIT_EXCEPTION_28",
|
|
"VMEXIT_EXCEPTION_29",
|
|
"VMEXIT_EXCEPTION_30",
|
|
"VMEXIT_EXCEPTION_31",
|
|
/* 96-99 */
|
|
"VMEXIT_INTR",
|
|
"VMEXIT_NMI",
|
|
"VMEXIT_SMI",
|
|
"VMEXIT_INIT",
|
|
/* 100-109 */
|
|
"VMEXIT_VINTR",
|
|
"VMEXIT_CR0_SEL_WRITE",
|
|
"VMEXIT_IDTR_READ",
|
|
"VMEXIT_GDTR_READ",
|
|
"VMEXIT_LDTR_READ",
|
|
"VMEXIT_TR_READ",
|
|
"VMEXIT_IDTR_WRITE",
|
|
"VMEXIT_GDTR_WRITE",
|
|
"VMEXIT_LDTR_WRITE",
|
|
"VMEXIT_TR_WRITE",
|
|
/* 110-119 */
|
|
"VMEXIT_RDTSC",
|
|
"VMEXIT_RDPMC",
|
|
"VMEXIT_PUSHF",
|
|
"VMEXIT_POPF",
|
|
"VMEXIT_CPUID",
|
|
"VMEXIT_RSM",
|
|
"VMEXIT_IRET",
|
|
"VMEXIT_SWINT",
|
|
"VMEXIT_INVD",
|
|
"VMEXIT_PAUSE",
|
|
/* 120-129 */
|
|
"VMEXIT_HLT",
|
|
"VMEXIT_INVLPG",
|
|
"VMEXIT_INVLPGA",
|
|
"VMEXIT_IOIO",
|
|
"VMEXIT_MSR",
|
|
"VMEXIT_TASK_SWITCH",
|
|
"VMEXIT_FERR_FREEZE",
|
|
"VMEXIT_SHUTDOWN",
|
|
"VMEXIT_VMRUN",
|
|
"VMEXIT_VMMCALL",
|
|
/* 130-139 */
|
|
"VMEXIT_VMLOAD",
|
|
"VMEXIT_VMSAVE",
|
|
"VMEXIT_STGI",
|
|
"VMEXIT_CLGI",
|
|
"VMEXIT_SKINIT",
|
|
"VMEXIT_RDTSCP",
|
|
"VMEXIT_ICEBP",
|
|
"VMEXIT_WBINVD",
|
|
"VMEXIT_MONITOR",
|
|
"VMEXIT_MWAIT",
|
|
/* 140 */
|
|
"VMEXIT_MWAIT_CONDITIONAL",
|
|
[VMEXIT_NPF] = "VMEXIT_NPF", /* nested paging fault */
|
|
};
|
|
|
|
|
|
#if ( HVM_VMX_EXIT_REASON_MAX > HVM_SVM_EXIT_REASON_MAX )
|
|
# define HVM_EXIT_REASON_MAX HVM_VMX_EXIT_REASON_MAX
|
|
# error - Strange!
|
|
#else
|
|
# define HVM_EXIT_REASON_MAX HVM_SVM_EXIT_REASON_MAX
|
|
#endif
|
|
|
|
/* General hvm information */
|
|
#define SPURIOUS_APIC_VECTOR 0xff
|
|
#define ERROR_APIC_VECTOR 0xfe
|
|
#define INVALIDATE_TLB_VECTOR 0xfd
|
|
#define EVENT_CHECK_VECTOR 0xfc
|
|
#define CALL_FUNCTION_VECTOR 0xfb
|
|
#define THERMAL_APIC_VECTOR 0xfa
|
|
#define LOCAL_TIMER_VECTOR 0xf9
|
|
|
|
#define EXTERNAL_INTERRUPT_MAX 256
|
|
|
|
/* Stringify numbers */
|
|
const char * hvm_extint_vector_name[EXTERNAL_INTERRUPT_MAX] = {
|
|
[SPURIOUS_APIC_VECTOR] = "SPURIOS_APIC",
|
|
[ERROR_APIC_VECTOR] = "ERROR_APIC",
|
|
[INVALIDATE_TLB_VECTOR]= "INVALIDATE_TLB",
|
|
[EVENT_CHECK_VECTOR]= "EVENT_CHECK",
|
|
[CALL_FUNCTION_VECTOR]= "CALL_FUNCTION",
|
|
[THERMAL_APIC_VECTOR]= "THERMAL_APIC",
|
|
[LOCAL_TIMER_VECTOR] = "LOCAL_TIMER",
|
|
};
|
|
|
|
#define HVM_TRAP_MAX 20
|
|
|
|
const char * hvm_trap_name[HVM_TRAP_MAX] = {
|
|
[0] = "Divide",
|
|
[1] = "RESERVED",
|
|
[2] = "NMI",
|
|
[3] = "Breakpoint",
|
|
[4] = "Overflow",
|
|
[5] = "BOUND",
|
|
[6] = "Invalid Op",
|
|
[7] = "Coprocessor not present",
|
|
[8] = "Double Fault",
|
|
[9] = "Coprocessor segment overrun",
|
|
[10] = "TSS",
|
|
[11] = "Segment not present",
|
|
[12] = "Stack-segment fault",
|
|
[13] = "GP",
|
|
[14] = "Page fault",
|
|
[15] = "RESERVED",
|
|
[16] = "FPU",
|
|
[17] = "Alignment check",
|
|
[18] = "Machine check",
|
|
[19] = "SIMD",
|
|
};
|
|
|
|
|
|
enum {
|
|
HVM_EVENT_HANDLER_NONE = 0,
|
|
HVM_EVENT_HANDLER_PF_XEN = 1,
|
|
HVM_EVENT_HANDLER_PF_INJECT,
|
|
HVM_EVENT_HANDLER_INJ_EXC,
|
|
HVM_EVENT_HANDLER_INJ_VIRQ,
|
|
HVM_EVENT_HANDLER_REINJ_VIRQ,
|
|
HVM_EVENT_HANDLER_IO_READ,
|
|
HVM_EVENT_HANDLER_IO_WRITE,
|
|
HVM_EVENT_HANDLER_CR_READ, /* 8 */
|
|
HVM_EVENT_HANDLER_CR_WRITE,
|
|
HVM_EVENT_HANDLER_DR_READ,
|
|
HVM_EVENT_HANDLER_DR_WRITE,
|
|
HVM_EVENT_HANDLER_MSR_READ,
|
|
HVM_EVENT_HANDLER_MSR_WRITE,
|
|
HVM_EVENT_HANDLER_CPUID,
|
|
HVM_EVENT_HANDLER_INTR,
|
|
HVM_EVENT_HANDLER_NMI, /* 16 */
|
|
HVM_EVENT_HANDLER_SMI,
|
|
HVM_EVENT_HANDLER_VMCALL,
|
|
HVM_EVENT_HANDLER_HLT,
|
|
HVM_EVENT_HANDLER_INVLPG,
|
|
HVM_EVENT_HANDLER_MCE,
|
|
HVM_EVENT_HANDLER_IO_ASSIST,
|
|
HVM_EVENT_HANDLER_MMIO_ASSIST,
|
|
HVM_EVENT_HANDLER_CLTS,
|
|
HVM_EVENT_HANDLER_LMSW,
|
|
HVM_EVENT_RDTSC,
|
|
HVM_EVENT_INTR_WINDOW=0x20, /* Oops... skipped 0x1b-1f */
|
|
HVM_EVENT_NPF,
|
|
HVM_EVENT_REALMODE_EMULATE,
|
|
HVM_EVENT_TRAP,
|
|
HVM_EVENT_TRAP_DEBUG,
|
|
HVM_EVENT_VLAPIC,
|
|
HVM_EVENT_HANDLER_MAX
|
|
};
|
|
const char * hvm_event_handler_name[HVM_EVENT_HANDLER_MAX] = {
|
|
"(no handler)",
|
|
"pf_xen",
|
|
"pf_inject",
|
|
"inj_exc",
|
|
"inj_virq",
|
|
"reinj_virq",
|
|
"io_read",
|
|
"io_write",
|
|
"cr_read", /* 8 */
|
|
"cr_write",
|
|
"dr_read",
|
|
"dr_write",
|
|
"msr_read",
|
|
"msr_write",
|
|
"cpuid",
|
|
"intr",
|
|
"nmi", /* 16 */
|
|
"smi",
|
|
"vmcall",
|
|
"hlt",
|
|
"invlpg",
|
|
"mce",
|
|
"io_assist",
|
|
"mmio_assist",
|
|
"clts", /* 24 */
|
|
"lmsw",
|
|
"rdtsc",
|
|
[HVM_EVENT_INTR_WINDOW]="intr_window",
|
|
"npf",
|
|
"realmode_emulate",
|
|
"trap",
|
|
"trap_debug",
|
|
"vlapic"
|
|
};
|
|
|
|
enum {
|
|
HVM_VOL_VMENTRY,
|
|
HVM_VOL_VMEXIT,
|
|
HVM_VOL_HANDLER,
|
|
HVM_VOL_EMUL,
|
|
HVM_VOL_MAX
|
|
};
|
|
|
|
enum {
|
|
GUEST_INTERRUPT_CASE_NONE,
|
|
/* This interrupt woke, no other interrupts until halt */
|
|
GUEST_INTERRUPT_CASE_WAKE_TO_HALT_ALONE,
|
|
/* This interrupt woke, maybe another interrupt before halt */
|
|
GUEST_INTERRUPT_CASE_WAKE_TO_HALT_ANY,
|
|
/* Time from interrupt (running) to halt */
|
|
GUEST_INTERRUPT_CASE_INTERRUPT_TO_HALT,
|
|
GUEST_INTERRUPT_CASE_MAX,
|
|
};
|
|
|
|
const char *guest_interrupt_case_name[] = {
|
|
[GUEST_INTERRUPT_CASE_WAKE_TO_HALT_ALONE]="wake to halt alone",
|
|
/* This interrupt woke, maybe another interrupt before halt */
|
|
[GUEST_INTERRUPT_CASE_WAKE_TO_HALT_ANY] ="wake to halt any ",
|
|
/* Time from interrupt (running) to halt */
|
|
[GUEST_INTERRUPT_CASE_INTERRUPT_TO_HALT] ="intr to halt ",
|
|
};
|
|
|
|
const char *hvm_vol_name[HVM_VOL_MAX] = {
|
|
[HVM_VOL_VMENTRY]="vmentry",
|
|
[HVM_VOL_VMEXIT] ="vmexit",
|
|
[HVM_VOL_HANDLER]="handler",
|
|
[HVM_VOL_EMUL]="emul",
|
|
};
|
|
|
|
enum {
|
|
HYPERCALL_set_trap_table = 0,
|
|
HYPERCALL_mmu_update,
|
|
HYPERCALL_set_gdt,
|
|
HYPERCALL_stack_switch,
|
|
HYPERCALL_set_callbacks,
|
|
HYPERCALL_fpu_taskswitch,
|
|
HYPERCALL_sched_op_compat,
|
|
HYPERCALL_platform_op,
|
|
HYPERCALL_set_debugreg,
|
|
HYPERCALL_get_debugreg,
|
|
HYPERCALL_update_descriptor,
|
|
HYPERCALL_memory_op=12,
|
|
HYPERCALL_multicall,
|
|
HYPERCALL_update_va_mapping,
|
|
HYPERCALL_set_timer_op,
|
|
HYPERCALL_event_channel_op_compat,
|
|
HYPERCALL_xen_version,
|
|
HYPERCALL_console_io,
|
|
HYPERCALL_physdev_op_compat,
|
|
HYPERCALL_grant_table_op,
|
|
HYPERCALL_vm_assist,
|
|
HYPERCALL_update_va_mapping_otherdomain,
|
|
HYPERCALL_iret,
|
|
HYPERCALL_vcpu_op,
|
|
HYPERCALL_set_segment_base,
|
|
HYPERCALL_mmuext_op,
|
|
HYPERCALL_acm_op,
|
|
HYPERCALL_nmi_op,
|
|
HYPERCALL_sched_op,
|
|
HYPERCALL_callback_op,
|
|
HYPERCALL_xenoprof_op,
|
|
HYPERCALL_event_channel_op,
|
|
HYPERCALL_physdev_op,
|
|
HYPERCALL_hvm_op,
|
|
HYPERCALL_sysctl,
|
|
HYPERCALL_domctl,
|
|
HYPERCALL_kexec_op,
|
|
HYPERCALL_MAX
|
|
};
|
|
|
|
const char *hypercall_name[HYPERCALL_MAX] = {
|
|
[HYPERCALL_set_trap_table]="set_trap_table",
|
|
[HYPERCALL_mmu_update]="mmu_update",
|
|
[HYPERCALL_set_gdt]="set_gdt",
|
|
[HYPERCALL_stack_switch]="stack_switch",
|
|
[HYPERCALL_set_callbacks]="set_callbacks",
|
|
[HYPERCALL_fpu_taskswitch]="fpu_taskswitch",
|
|
[HYPERCALL_sched_op_compat]="sched_op(compat)",
|
|
[HYPERCALL_platform_op]="platform_op",
|
|
[HYPERCALL_set_debugreg]="set_debugreg",
|
|
[HYPERCALL_get_debugreg]="get_debugreg",
|
|
[HYPERCALL_update_descriptor]="update_descriptor",
|
|
[HYPERCALL_memory_op]="memory_op",
|
|
[HYPERCALL_multicall]="multicall",
|
|
[HYPERCALL_update_va_mapping]="update_va_mapping",
|
|
[HYPERCALL_set_timer_op]="set_timer_op",
|
|
[HYPERCALL_event_channel_op_compat]="evtchn_op(compat)",
|
|
[HYPERCALL_xen_version]="xen_version",
|
|
[HYPERCALL_console_io]="console_io",
|
|
[HYPERCALL_physdev_op_compat]="physdev_op(compat)",
|
|
[HYPERCALL_grant_table_op]="grant_table_op",
|
|
[HYPERCALL_vm_assist]="vm_assist",
|
|
[HYPERCALL_update_va_mapping_otherdomain]="update_va_mapping_otherdomain",
|
|
[HYPERCALL_iret]="iret",
|
|
[HYPERCALL_vcpu_op]="vcpu_op",
|
|
[HYPERCALL_set_segment_base]="set_segment_base",
|
|
[HYPERCALL_mmuext_op]="mmuext_op",
|
|
[HYPERCALL_acm_op]="acm_op",
|
|
[HYPERCALL_nmi_op]="nmi_op",
|
|
[HYPERCALL_sched_op]="sched_op",
|
|
[HYPERCALL_callback_op]="callback_op",
|
|
[HYPERCALL_xenoprof_op]="xenoprof_op",
|
|
[HYPERCALL_event_channel_op]="evtchn_op",
|
|
[HYPERCALL_physdev_op]="physdev_op",
|
|
[HYPERCALL_hvm_op]="hvm_op",
|
|
[HYPERCALL_sysctl]="sysctl",
|
|
[HYPERCALL_domctl]="domctl",
|
|
[HYPERCALL_kexec_op]="kexec_op"
|
|
};
|
|
|
|
enum {
|
|
PF_XEN_EMUL_LVL_0,
|
|
PF_XEN_EMUL_LVL_1,
|
|
PF_XEN_EMUL_LVL_2,
|
|
PF_XEN_EMUL_LVL_3,
|
|
PF_XEN_EMUL_LVL_4,
|
|
PF_XEN_EMUL_EARLY_UNSHADOW,
|
|
PF_XEN_EMUL_SET_CHANGED,
|
|
PF_XEN_EMUL_SET_UNCHANGED,
|
|
PF_XEN_EMUL_SET_FLUSH,
|
|
PF_XEN_EMUL_SET_ERROR,
|
|
PF_XEN_EMUL_PROMOTE,
|
|
PF_XEN_EMUL_DEMOTE,
|
|
PF_XEN_EMUL_PREALLOC_UNPIN,
|
|
PF_XEN_EMUL_PREALLOC_UNHOOK,
|
|
PF_XEN_EMUL_MAX,
|
|
};
|
|
|
|
const char * pf_xen_emul_name[PF_XEN_EMUL_MAX] = {
|
|
[PF_XEN_EMUL_LVL_0]="non-linmap",
|
|
[PF_XEN_EMUL_LVL_1]="linmap l1",
|
|
[PF_XEN_EMUL_LVL_2]="linmap l2",
|
|
[PF_XEN_EMUL_LVL_3]="linmap l3",
|
|
[PF_XEN_EMUL_LVL_4]="linmap l4",
|
|
[PF_XEN_EMUL_EARLY_UNSHADOW]="early unshadow",
|
|
[PF_XEN_EMUL_SET_UNCHANGED]="set unchanged",
|
|
[PF_XEN_EMUL_SET_CHANGED]="set changed",
|
|
[PF_XEN_EMUL_SET_FLUSH]="set changed",
|
|
[PF_XEN_EMUL_SET_ERROR]="set changed",
|
|
[PF_XEN_EMUL_PROMOTE]="promote",
|
|
[PF_XEN_EMUL_DEMOTE]="demote",
|
|
[PF_XEN_EMUL_PREALLOC_UNPIN]="unpin",
|
|
[PF_XEN_EMUL_PREALLOC_UNHOOK]="unhook",
|
|
};
|
|
|
|
/* Rio only */
|
|
enum {
|
|
PF_XEN_NON_EMUL_VA_USER,
|
|
PF_XEN_NON_EMUL_VA_KERNEL,
|
|
PF_XEN_NON_EMUL_EIP_USER,
|
|
PF_XEN_NON_EMUL_EIP_KERNEL,
|
|
PF_XEN_NON_EMUL_MAX,
|
|
};
|
|
|
|
const char * pf_xen_non_emul_name[PF_XEN_NON_EMUL_MAX] = {
|
|
[PF_XEN_NON_EMUL_VA_USER]="va user",
|
|
[PF_XEN_NON_EMUL_VA_KERNEL]="va kernel",
|
|
[PF_XEN_NON_EMUL_EIP_USER]="eip user",
|
|
[PF_XEN_NON_EMUL_EIP_KERNEL]="eip kernel",
|
|
};
|
|
|
|
enum {
|
|
PF_XEN_FIXUP_PREALLOC_UNPIN,
|
|
PF_XEN_FIXUP_PREALLOC_UNHOOK,
|
|
PF_XEN_FIXUP_UNSYNC,
|
|
PF_XEN_FIXUP_OOS_ADD,
|
|
PF_XEN_FIXUP_OOS_EVICT,
|
|
PF_XEN_FIXUP_PROMOTE,
|
|
PF_XEN_FIXUP_UPDATE_ONLY,
|
|
PF_XEN_FIXUP_WRMAP,
|
|
PF_XEN_FIXUP_BRUTE_FORCE,
|
|
PF_XEN_FIXUP_MAX,
|
|
};
|
|
|
|
const char * pf_xen_fixup_name[PF_XEN_FIXUP_MAX] = {
|
|
[PF_XEN_FIXUP_PREALLOC_UNPIN] = "unpin",
|
|
[PF_XEN_FIXUP_PREALLOC_UNHOOK] = "unhook",
|
|
[PF_XEN_FIXUP_UNSYNC] = "unsync",
|
|
[PF_XEN_FIXUP_OOS_ADD] = "oos-add",
|
|
[PF_XEN_FIXUP_OOS_EVICT] = "oos-evict",
|
|
[PF_XEN_FIXUP_PROMOTE] = "promote",
|
|
[PF_XEN_FIXUP_UPDATE_ONLY] = "update",
|
|
[PF_XEN_FIXUP_WRMAP] = "wrmap",
|
|
[PF_XEN_FIXUP_BRUTE_FORCE] = "wrmap-bf",
|
|
};
|
|
|
|
enum {
|
|
PF_XEN_NOT_SHADOW = 1,
|
|
PF_XEN_FAST_PROPAGATE,
|
|
PF_XEN_FAST_MMIO,
|
|
PF_XEN_FALSE_FAST_PATH,
|
|
PF_XEN_MMIO,
|
|
PF_XEN_FIXUP,
|
|
PF_XEN_DOMF_DYING,
|
|
PF_XEN_EMULATE,
|
|
PF_XEN_EMULATE_UNSHADOW_USER,
|
|
PF_XEN_EMULATE_UNSHADOW_EVTINJ,
|
|
PF_XEN_EMULATE_UNSHADOW_UNHANDLED,
|
|
PF_XEN_LAST_FAULT=PF_XEN_EMULATE_UNSHADOW_UNHANDLED,
|
|
PF_XEN_NON_EMULATE,
|
|
PF_XEN_NO_HANDLER,
|
|
PF_XEN_MAX,
|
|
};
|
|
|
|
#define SHADOW_WRMAP_BF 12
|
|
#define SHADOW_PREALLOC_UNPIN 13
|
|
#define SHADOW_RESYNC_FULL 14
|
|
#define SHADOW_RESYNC_ONLY 15
|
|
|
|
const char * pf_xen_name[PF_XEN_MAX] = {
|
|
[PF_XEN_NOT_SHADOW]="propagate",
|
|
[PF_XEN_FAST_PROPAGATE]="fast propagate",
|
|
[PF_XEN_FAST_MMIO]="fast mmio",
|
|
[PF_XEN_FALSE_FAST_PATH]="false fast path",
|
|
[PF_XEN_MMIO]="mmio",
|
|
[PF_XEN_FIXUP]="fixup",
|
|
[PF_XEN_DOMF_DYING]="dom dying",
|
|
[PF_XEN_EMULATE]="emulate",
|
|
[PF_XEN_EMULATE_UNSHADOW_USER]="unshadow:user-mode",
|
|
[PF_XEN_EMULATE_UNSHADOW_EVTINJ]="unshadow:evt inj",
|
|
[PF_XEN_EMULATE_UNSHADOW_UNHANDLED]="unshadow:unhandled instr",
|
|
[PF_XEN_NON_EMULATE]="fixup|mmio",
|
|
[PF_XEN_NO_HANDLER]="(no handler)",
|
|
};
|
|
|
|
#define CORR_VA_INVALID (0ULL-1)
|
|
|
|
enum {
|
|
NONPF_MMIO_APIC,
|
|
NONPF_MMIO_NPF,
|
|
NONPF_MMIO_UNKNOWN,
|
|
NONPF_MMIO_MAX
|
|
};
|
|
|
|
struct mmio_info {
|
|
unsigned long long gpa;
|
|
unsigned long long va; /* Filled only by shadow */
|
|
unsigned data;
|
|
unsigned data_valid:1, is_write:1;
|
|
};
|
|
|
|
struct pf_xen_extra {
|
|
unsigned long long va;
|
|
union {
|
|
unsigned flags;
|
|
struct {
|
|
unsigned flag_set_ad:1,
|
|
flag_set_a:1,
|
|
flag_shadow_l1_get_ref:1,
|
|
flag_shadow_l1_put_ref:1,
|
|
flag_l2_propagate:1,
|
|
flag_set_changed:1,
|
|
flag_set_flush:1,
|
|
flag_set_error:1,
|
|
flag_demote:1,
|
|
flag_promote:1,
|
|
flag_wrmap:1,
|
|
flag_wrmap_guess_found:1,
|
|
flag_wrmap_brute_force:1,
|
|
flag_early_unshadow:1,
|
|
flag_emulation_2nd_pt_written:1,
|
|
flag_emulation_last_failed:1,
|
|
flag_emulate_full_pt:1,
|
|
flag_prealloc_unhook:1,
|
|
flag_unsync:1,
|
|
flag_oos_fixup_add:1,
|
|
flag_oos_fixup_evict:1;
|
|
};
|
|
}; /* Miami + ; fixup & emulate */
|
|
unsigned int error_code; /* Rio only */
|
|
|
|
/* Calculated */
|
|
int pf_case; /* Rio */
|
|
|
|
/* MMIO only */
|
|
unsigned long long gpa;
|
|
unsigned int data;
|
|
|
|
/* Emulate only */
|
|
unsigned long long gl1e; /* Miami + */
|
|
unsigned long long wval; /* Miami */
|
|
unsigned long long corresponding_va;
|
|
unsigned int pt_index[5], pt_is_lo;
|
|
int pt_level;
|
|
|
|
/* Other */
|
|
unsigned long long gfn;
|
|
|
|
/* Flags */
|
|
unsigned corr_valid:1,
|
|
corr_is_kernel:1,
|
|
va_is_kernel:1;
|
|
};
|
|
|
|
struct pcpu_info;
|
|
|
|
#define GUEST_INTERRUPT_MAX 350
|
|
#define FAKE_VECTOR 349
|
|
#define CR_MAX 9
|
|
#define RESYNCS_MAX 17
|
|
#define PF_XEN_FIXUP_UNSYNC_RESYNC_MAX 2
|
|
|
|
struct hvm_data;
|
|
|
|
struct hvm_summary_handler_node {
|
|
void (*handler)(struct hvm_data *, void* data);
|
|
void *data;
|
|
struct hvm_summary_handler_node *next;
|
|
};
|
|
|
|
struct hvm_data {
|
|
/* Summary information */
|
|
int init;
|
|
int vmexit_valid;
|
|
int summary_info;
|
|
struct vcpu_data *v; /* up-pointer */
|
|
|
|
/* SVM / VMX compatibility. FIXME - should be global */
|
|
const char ** exit_reason_name;
|
|
int exit_reason_max;
|
|
struct hvm_summary_handler_node *exit_reason_summary_handler_list[HVM_EXIT_REASON_MAX];
|
|
|
|
/* Information about particular exit reasons */
|
|
struct {
|
|
struct cycle_summary exit_reason[HVM_EXIT_REASON_MAX];
|
|
int extint[EXTERNAL_INTERRUPT_MAX+1];
|
|
int *extint_histogram;
|
|
struct cycle_summary trap[HVM_TRAP_MAX];
|
|
struct cycle_summary pf_xen[PF_XEN_MAX];
|
|
struct cycle_summary pf_xen_emul[PF_XEN_EMUL_MAX];
|
|
struct cycle_summary pf_xen_emul_early_unshadow[5];
|
|
struct cycle_summary pf_xen_non_emul[PF_XEN_NON_EMUL_MAX];
|
|
struct cycle_summary pf_xen_fixup[PF_XEN_FIXUP_MAX];
|
|
struct cycle_summary pf_xen_fixup_unsync_resync[PF_XEN_FIXUP_UNSYNC_RESYNC_MAX+1];
|
|
struct cycle_summary cr_write[CR_MAX];
|
|
struct cycle_summary cr3_write_resyncs[RESYNCS_MAX+1];
|
|
struct cycle_summary vmcall[HYPERCALL_MAX+1];
|
|
struct cycle_summary generic[HVM_EVENT_HANDLER_MAX];
|
|
struct cycle_summary mmio[NONPF_MMIO_MAX];
|
|
struct hvm_gi_struct {
|
|
int count;
|
|
struct cycle_summary runtime[GUEST_INTERRUPT_CASE_MAX];
|
|
/* OK, not summary info, but still... */
|
|
int is_wake;
|
|
tsc_t start_tsc;
|
|
} guest_interrupt[GUEST_INTERRUPT_MAX + 1];
|
|
/* IPI Latency */
|
|
struct cycle_summary ipi_latency;
|
|
int ipi_count[256];
|
|
struct {
|
|
struct io_address *mmio, *pio;
|
|
} io;
|
|
} summary;
|
|
|
|
/* In-flight accumulation information */
|
|
struct {
|
|
union {
|
|
struct {
|
|
unsigned port:31,
|
|
is_write:1;
|
|
unsigned int val;
|
|
} io;
|
|
struct pf_xen_extra pf_xen;
|
|
struct {
|
|
unsigned cr;
|
|
unsigned long long val;
|
|
int repromote;
|
|
} cr_write;
|
|
struct {
|
|
unsigned addr;
|
|
unsigned long long val;
|
|
} msr;
|
|
struct {
|
|
unsigned int event;
|
|
uint32_t d[4];
|
|
} generic;
|
|
struct {
|
|
unsigned eax;
|
|
} vmcall;
|
|
struct {
|
|
unsigned vec;
|
|
} intr;
|
|
};
|
|
/* MMIO gets its separate area, since many exits may use it */
|
|
struct mmio_info mmio;
|
|
}inflight;
|
|
int resyncs;
|
|
void (*post_process)(struct hvm_data *);
|
|
tsc_t exit_tsc, arc_cycles, entry_tsc;
|
|
unsigned long long rip;
|
|
unsigned exit_reason, event_handler;
|
|
unsigned int short_summary_done:1, prealloc_unpin:1, wrmap_bf:1;
|
|
|
|
/* Immediate processing */
|
|
void *d;
|
|
|
|
/* Wake-to-halt detection. See comment above. */
|
|
struct {
|
|
unsigned waking:1;
|
|
/* Wake vector: keep track of time from vmentry until:
|
|
next halt, or next interrupt */
|
|
int vector, interrupts, interrupts_wanting_tsc;
|
|
} w2h;
|
|
|
|
/* Historical info */
|
|
tsc_t last_rdtsc;
|
|
};
|
|
|
|
enum {
|
|
HVM_SHORT_SUMMARY_EMULATE,
|
|
HVM_SHORT_SUMMARY_UNSYNC,
|
|
HVM_SHORT_SUMMARY_FIXUP,
|
|
HVM_SHORT_SUMMARY_MMIO,
|
|
HVM_SHORT_SUMMARY_PROPAGATE,
|
|
HVM_SHORT_SUMMARY_CR3,
|
|
HVM_SHORT_SUMMARY_VMCALL,
|
|
HVM_SHORT_SUMMARY_INTERRUPT,
|
|
HVM_SHORT_SUMMARY_HLT,
|
|
HVM_SHORT_SUMMARY_OTHER,
|
|
HVM_SHORT_SUMMARY_MAX,
|
|
};
|
|
|
|
const char *hvm_short_summary_name[HVM_SHORT_SUMMARY_MAX] = {
|
|
[HVM_SHORT_SUMMARY_EMULATE] ="emulate",
|
|
[HVM_SHORT_SUMMARY_UNSYNC] ="unsync",
|
|
[HVM_SHORT_SUMMARY_FIXUP] ="fixup",
|
|
[HVM_SHORT_SUMMARY_MMIO] ="mmio",
|
|
[HVM_SHORT_SUMMARY_PROPAGATE]="propagate",
|
|
[HVM_SHORT_SUMMARY_CR3] ="cr3",
|
|
[HVM_SHORT_SUMMARY_VMCALL] ="vmcall",
|
|
[HVM_SHORT_SUMMARY_INTERRUPT]="intr",
|
|
[HVM_SHORT_SUMMARY_HLT] ="hlt",
|
|
[HVM_SHORT_SUMMARY_OTHER] ="other",
|
|
};
|
|
|
|
struct hvm_short_summary_struct {
|
|
struct cycle_summary s[HVM_SHORT_SUMMARY_MAX];
|
|
};
|
|
|
|
void init_hvm_data(struct hvm_data *h, struct vcpu_data *v) {
|
|
int i;
|
|
|
|
if(h->init)
|
|
return;
|
|
|
|
h->v = v;
|
|
|
|
h->init = 1;
|
|
|
|
if(opt.histogram_interrupt_eip) {
|
|
int count = ((1ULL<<ADDR_SPACE_BITS)/opt.histogram_interrupt_increment);
|
|
size_t size = count * sizeof(int);
|
|
h->summary.extint_histogram = malloc(size);
|
|
if(h->summary.extint_histogram)
|
|
bzero(h->summary.extint_histogram, size);
|
|
else {
|
|
fprintf(stderr, "FATAL: Could not allocate %zd bytes for interrupt histogram!\n",
|
|
size);
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
}
|
|
for(i=0; i<GUEST_INTERRUPT_MAX+1; i++)
|
|
h->summary.guest_interrupt[i].count=0;
|
|
}
|
|
|
|
void set_hvm_exit_reason_data(struct hvm_data *h, unsigned event) {
|
|
if (((event & ~TRC_64_FLAG) == TRC_HVM_SVM_EXIT) ||
|
|
opt.svm_mode) {
|
|
opt.svm_mode = 1;
|
|
h->exit_reason_max = HVM_SVM_EXIT_REASON_MAX;
|
|
h->exit_reason_name = hvm_svm_exit_reason_name;
|
|
} else {
|
|
h->exit_reason_max = HVM_VMX_EXIT_REASON_MAX;
|
|
h->exit_reason_name = hvm_vmx_exit_reason_name;
|
|
}
|
|
}
|
|
|
|
/* PV data */
|
|
enum {
|
|
PV_HYPERCALL=1,
|
|
PV_TRAP=3,
|
|
PV_PAGE_FAULT,
|
|
PV_FORCED_INVALID_OP,
|
|
PV_EMULATE_PRIVOP,
|
|
PV_EMULATE_4GB,
|
|
PV_MATH_STATE_RESTORE,
|
|
PV_PAGING_FIXUP,
|
|
PV_GDT_LDT_MAPPING_FAULT,
|
|
PV_PTWR_EMULATION,
|
|
PV_PTWR_EMULATION_PAE,
|
|
PV_HYPERCALL_V2 = 13,
|
|
PV_HYPERCALL_SUBCALL = 14,
|
|
PV_MAX
|
|
};
|
|
|
|
const char *pv_name[PV_MAX] = {
|
|
[PV_HYPERCALL]="hypercall",
|
|
[PV_TRAP]="trap",
|
|
[PV_PAGE_FAULT]="page_fault",
|
|
[PV_FORCED_INVALID_OP]="forced_invalid_op",
|
|
[PV_EMULATE_PRIVOP]="emulate privop",
|
|
[PV_EMULATE_4GB]="emulate 4g",
|
|
[PV_MATH_STATE_RESTORE]="math state restore",
|
|
[PV_PAGING_FIXUP]="paging fixup",
|
|
[PV_GDT_LDT_MAPPING_FAULT]="gdt/ldt mapping fault",
|
|
[PV_PTWR_EMULATION]="ptwr",
|
|
[PV_PTWR_EMULATION_PAE]="ptwr(pae)",
|
|
[PV_HYPERCALL_V2]="hypercall",
|
|
[PV_HYPERCALL_SUBCALL]="hypercall (subcall)",
|
|
};
|
|
|
|
#define PV_HYPERCALL_MAX 56
|
|
#define PV_TRAP_MAX 20
|
|
|
|
struct pv_data {
|
|
unsigned summary_info:1;
|
|
int count[PV_MAX];
|
|
int hypercall_count[PV_HYPERCALL_MAX];
|
|
int trap_count[PV_TRAP_MAX];
|
|
};
|
|
|
|
/* Sched data */
|
|
enum {
|
|
RUNSTATE_RUNNING=0,
|
|
RUNSTATE_RUNNABLE,
|
|
RUNSTATE_BLOCKED,
|
|
RUNSTATE_OFFLINE,
|
|
RUNSTATE_LOST,
|
|
RUNSTATE_QUEUED,
|
|
RUNSTATE_INIT,
|
|
RUNSTATE_MAX
|
|
};
|
|
|
|
int runstate_graph[RUNSTATE_MAX] =
|
|
{
|
|
[RUNSTATE_BLOCKED]=0,
|
|
[RUNSTATE_OFFLINE]=1,
|
|
[RUNSTATE_RUNNABLE]=2,
|
|
[RUNSTATE_RUNNING]=3,
|
|
[RUNSTATE_LOST]=-1,
|
|
[RUNSTATE_QUEUED]=-2,
|
|
[RUNSTATE_INIT]=-2,
|
|
};
|
|
|
|
const char * runstate_name[RUNSTATE_MAX]={
|
|
[RUNSTATE_RUNNING]= "running",
|
|
[RUNSTATE_RUNNABLE]="runnable",
|
|
[RUNSTATE_BLOCKED]= "blocked", /* to be blocked */
|
|
[RUNSTATE_OFFLINE]= "offline",
|
|
[RUNSTATE_QUEUED]= "queued",
|
|
[RUNSTATE_INIT]= "init",
|
|
[RUNSTATE_LOST]= "lost",
|
|
};
|
|
|
|
enum {
|
|
RUNNABLE_STATE_INVALID,
|
|
RUNNABLE_STATE_WAKE,
|
|
RUNNABLE_STATE_PREEMPT,
|
|
RUNNABLE_STATE_OTHER,
|
|
RUNNABLE_STATE_MAX
|
|
};
|
|
|
|
const char * runnable_state_name[RUNNABLE_STATE_MAX]={
|
|
[RUNNABLE_STATE_INVALID]="invalid", /* Should never show up */
|
|
[RUNNABLE_STATE_WAKE]="wake",
|
|
[RUNNABLE_STATE_PREEMPT]="preempt",
|
|
[RUNNABLE_STATE_OTHER]="other",
|
|
};
|
|
|
|
/* Memory data */
|
|
enum {
|
|
MEM_PAGE_GRANT_MAP = 1,
|
|
MEM_PAGE_GRANT_UNMAP,
|
|
MEM_PAGE_GRANT_TRANSFER,
|
|
MEM_SET_P2M_ENTRY,
|
|
MEM_DECREASE_RESERVATION,
|
|
MEM_POD_POPULATE = 16,
|
|
MEM_POD_ZERO_RECLAIM,
|
|
MEM_POD_SUPERPAGE_SPLINTER,
|
|
MEM_MAX
|
|
};
|
|
|
|
const char *mem_name[MEM_MAX] = {
|
|
[MEM_PAGE_GRANT_MAP] = "grant-map",
|
|
[MEM_PAGE_GRANT_UNMAP] = "grant-unmap",
|
|
[MEM_PAGE_GRANT_TRANSFER] = "grant-transfer",
|
|
[MEM_SET_P2M_ENTRY] = "set-p2m",
|
|
[MEM_DECREASE_RESERVATION] = "decrease-reservation",
|
|
[MEM_POD_POPULATE] = "pod-populate",
|
|
[MEM_POD_ZERO_RECLAIM] = "pod-zero-reclaim",
|
|
[MEM_POD_SUPERPAGE_SPLINTER] = "pod-superpage-splinter",
|
|
};
|
|
|
|
/* Per-unit information. */
|
|
|
|
struct cr3_value_struct {
|
|
struct cr3_value_struct * next;
|
|
struct cr3_value_struct * gnext;
|
|
unsigned long long gmfn;
|
|
int cr3_id;
|
|
unsigned long long first_time, last_time, run_time;
|
|
struct cycle_summary total_time, guest_time, hv_time;
|
|
int switch_count, flush_count;
|
|
|
|
struct hvm_short_summary_struct hvm;
|
|
|
|
struct {
|
|
int now;
|
|
int count;
|
|
} prealloc_unpin;
|
|
|
|
struct {
|
|
unsigned callback:1;
|
|
unsigned flush_count, switch_count;
|
|
unsigned fixup_user, emulate_corr_user;
|
|
} destroy;
|
|
};
|
|
|
|
#ifndef MAX_CPUS
|
|
#define MAX_CPUS 256
|
|
#endif
|
|
typedef uint32_t cpu_mask_t;
|
|
|
|
#define IDLE_DOMAIN 32767
|
|
#define DEFAULT_DOMAIN 32768
|
|
|
|
#define MAX_VLAPIC_LIST 8
|
|
struct vlapic_struct {
|
|
struct {
|
|
struct outstanding_ipi {
|
|
tsc_t first_tsc;
|
|
int vec, count;
|
|
int injected, valid;
|
|
} list[MAX_VLAPIC_LIST];
|
|
} outstanding;
|
|
};
|
|
|
|
struct vcpu_data {
|
|
int vid;
|
|
struct domain_data *d; /* up-pointer */
|
|
unsigned activated:1, delayed_init:1;
|
|
|
|
int guest_paging_levels;
|
|
|
|
/* Schedule info */
|
|
struct {
|
|
int state;
|
|
int runnable_state; /* Only valid when state==RUNSTATE_RUNNABLE */
|
|
tsc_t tsc;
|
|
/* TSC skew detection/correction */
|
|
struct last_oldstate_struct {
|
|
int wrong, actual, pid;
|
|
tsc_t tsc;
|
|
} last_oldstate;
|
|
/* Performance counters */
|
|
unsigned long long p1_start, p2_start;
|
|
} runstate;
|
|
struct pcpu_info *p;
|
|
tsc_t pcpu_tsc;
|
|
|
|
/* Hardware tracking */
|
|
struct {
|
|
long long val;
|
|
tsc_t start_time;
|
|
struct cr3_value_struct *data;
|
|
} cr3;
|
|
|
|
/* IPI latency tracking */
|
|
struct vlapic_struct vlapic;
|
|
|
|
/* Summary info */
|
|
struct cycle_framework f;
|
|
struct cycle_summary runstates[RUNSTATE_MAX];
|
|
struct cycle_summary runnable_states[RUNNABLE_STATE_MAX];
|
|
struct cycle_summary cpu_affinity_all,
|
|
cpu_affinity_pcpu[MAX_CPUS];
|
|
enum {
|
|
VCPU_DATA_NONE=0,
|
|
VCPU_DATA_HVM,
|
|
VCPU_DATA_PV
|
|
} data_type;
|
|
union {
|
|
struct hvm_data hvm;
|
|
struct pv_data pv;
|
|
};
|
|
};
|
|
|
|
enum {
|
|
DOMAIN_RUNSTATE_BLOCKED=0,
|
|
DOMAIN_RUNSTATE_PARTIAL_RUN,
|
|
DOMAIN_RUNSTATE_FULL_RUN,
|
|
DOMAIN_RUNSTATE_PARTIAL_CONTENTION,
|
|
DOMAIN_RUNSTATE_CONCURRENCY_HAZARD,
|
|
DOMAIN_RUNSTATE_FULL_CONTENTION,
|
|
DOMAIN_RUNSTATE_LOST,
|
|
DOMAIN_RUNSTATE_MAX
|
|
};
|
|
|
|
const char * domain_runstate_name[] = {
|
|
[DOMAIN_RUNSTATE_BLOCKED]="blocked",
|
|
[DOMAIN_RUNSTATE_PARTIAL_RUN]="partial run",
|
|
[DOMAIN_RUNSTATE_FULL_RUN]="full run",
|
|
[DOMAIN_RUNSTATE_PARTIAL_CONTENTION]="partial contention",
|
|
[DOMAIN_RUNSTATE_CONCURRENCY_HAZARD]="concurrency_hazard",
|
|
[DOMAIN_RUNSTATE_FULL_CONTENTION]="full_contention",
|
|
[DOMAIN_RUNSTATE_LOST]="lost",
|
|
};
|
|
|
|
enum {
|
|
POD_RECLAIM_CONTEXT_UNKNOWN=0,
|
|
POD_RECLAIM_CONTEXT_FAULT,
|
|
POD_RECLAIM_CONTEXT_BALLOON,
|
|
POD_RECLAIM_CONTEXT_MAX
|
|
};
|
|
|
|
const char * pod_reclaim_context_name[] = {
|
|
[POD_RECLAIM_CONTEXT_UNKNOWN]="unknown",
|
|
[POD_RECLAIM_CONTEXT_FAULT]="fault",
|
|
[POD_RECLAIM_CONTEXT_BALLOON]="balloon",
|
|
};
|
|
|
|
#define POD_ORDER_MAX 4
|
|
|
|
struct domain_data {
|
|
struct domain_data *next;
|
|
int did;
|
|
struct vcpu_data *vcpu[MAX_CPUS];
|
|
|
|
int max_vid;
|
|
|
|
int runstate;
|
|
tsc_t runstate_tsc;
|
|
struct cycle_summary total_time;
|
|
struct cycle_summary runstates[DOMAIN_RUNSTATE_MAX];
|
|
struct cr3_value_struct *cr3_value_head;
|
|
struct eip_list_struct *emulate_eip_list;
|
|
struct eip_list_struct *interrupt_eip_list;
|
|
|
|
int guest_interrupt[GUEST_INTERRUPT_MAX+1];
|
|
struct hvm_short_summary_struct hvm_short;
|
|
struct {
|
|
int done[MEM_MAX];
|
|
int done_interval[MEM_MAX];
|
|
|
|
int done_for[MEM_MAX];
|
|
int done_for_interval[MEM_MAX];
|
|
} memops;
|
|
|
|
struct {
|
|
int reclaim_order[POD_ORDER_MAX];
|
|
int reclaim_context[POD_RECLAIM_CONTEXT_MAX];
|
|
int reclaim_context_order[POD_RECLAIM_CONTEXT_MAX][POD_ORDER_MAX];
|
|
/* FIXME: Do a full cycle summary */
|
|
int populate_order[POD_ORDER_MAX];
|
|
} pod;
|
|
};
|
|
|
|
struct domain_data * domain_list=NULL;
|
|
|
|
struct domain_data default_domain;
|
|
|
|
enum {
|
|
TOPLEVEL_GEN=0,
|
|
TOPLEVEL_SCHED,
|
|
TOPLEVEL_DOM0OP,
|
|
TOPLEVEL_HVM,
|
|
TOPLEVEL_MEM,
|
|
TOPLEVEL_PV,
|
|
TOPLEVEL_SHADOW,
|
|
TOPLEVEL_HW,
|
|
TOPLEVEL_MAX=TOPLEVEL_HW+1,
|
|
};
|
|
|
|
const char * toplevel_name[TOPLEVEL_MAX] = {
|
|
[TOPLEVEL_GEN]="gen",
|
|
[TOPLEVEL_SCHED]="sched",
|
|
[TOPLEVEL_DOM0OP]="dom0op",
|
|
[TOPLEVEL_HVM]="hvm",
|
|
[TOPLEVEL_MEM]="mem",
|
|
[TOPLEVEL_PV]="pv",
|
|
[TOPLEVEL_SHADOW]="shadow",
|
|
[TOPLEVEL_HW]="hw",
|
|
};
|
|
|
|
struct trace_volume {
|
|
unsigned long long toplevel[TOPLEVEL_MAX];
|
|
unsigned long long sched_verbose;
|
|
unsigned long long hvm[HVM_VOL_MAX];
|
|
} volume;
|
|
|
|
#define UPDATE_VOLUME(_p,_x,_s) \
|
|
do { \
|
|
(_p)->volume.total._x += _s; \
|
|
(_p)->volume.last_buffer._x += _s; \
|
|
} while(0)
|
|
|
|
void volume_clear(struct trace_volume *vol)
|
|
{
|
|
bzero(vol, sizeof(*vol));
|
|
}
|
|
|
|
void volume_summary(struct trace_volume *vol)
|
|
{
|
|
int j, k;
|
|
for(j=0; j<TOPLEVEL_MAX; j++)
|
|
if(vol->toplevel[j]) {
|
|
printf(" %-6s: %10lld\n",
|
|
toplevel_name[j], vol->toplevel[j]);
|
|
switch(j) {
|
|
case TOPLEVEL_SCHED:
|
|
if(vol->sched_verbose)
|
|
printf(" +-verbose: %10lld\n",
|
|
vol->sched_verbose);
|
|
break;
|
|
case TOPLEVEL_HVM:
|
|
for(k=0; k<HVM_VOL_MAX; k++) {
|
|
if(vol->hvm[k])
|
|
printf(" +-%-7s: %10lld\n",
|
|
hvm_vol_name[k], vol->hvm[k]);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct pcpu_info {
|
|
/* Information about this pcpu */
|
|
unsigned active:1, summary:1;
|
|
int pid;
|
|
|
|
/* Information related to scanning thru the file */
|
|
tsc_t first_tsc, last_tsc, order_tsc;
|
|
off_t file_offset;
|
|
off_t next_cpu_change_offset;
|
|
struct record_info ri;
|
|
int last_cpu_change_pid;
|
|
int power_state;
|
|
|
|
/* Information related to tsc skew detection / correction */
|
|
struct {
|
|
tsc_t offset;
|
|
cpu_mask_t downstream; /* To detect cycles in dependencies */
|
|
} tsc_skew;
|
|
|
|
/* Information related to domain tracking */
|
|
struct vcpu_data * current;
|
|
struct {
|
|
unsigned active:1,
|
|
domain_valid:1,
|
|
seen_valid_schedule:1; /* Seen an actual schedule since lost records */
|
|
unsigned did:16,vid:16;
|
|
tsc_t tsc;
|
|
} lost_record;
|
|
|
|
/* Record volume */
|
|
struct {
|
|
tsc_t buffer_first_tsc,
|
|
buffer_dom0_runstate_tsc,
|
|
buffer_dom0_runstate_cycles[RUNSTATE_MAX];
|
|
int buffer_dom0_runstate;
|
|
unsigned buffer_size;
|
|
struct trace_volume total, last_buffer;
|
|
} volume;
|
|
|
|
/* Time report */
|
|
struct {
|
|
tsc_t tsc;
|
|
struct cycle_summary idle, running, lost;
|
|
} time;
|
|
};
|
|
|
|
void __fill_in_record_info(struct pcpu_info *p);
|
|
|
|
#define INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX 10
|
|
|
|
struct {
|
|
int max_active_pcpu;
|
|
off_t last_epoch_offset;
|
|
int early_eof;
|
|
int lost_cpus;
|
|
tsc_t now;
|
|
struct cycle_framework f;
|
|
tsc_t buffer_trace_virq_tsc;
|
|
struct pcpu_info pcpu[MAX_CPUS];
|
|
|
|
struct {
|
|
int id;
|
|
/* Invariant: head null => tail null; head !null => tail valid */
|
|
struct cr3_value_struct *head, **tail;
|
|
} cr3;
|
|
|
|
struct {
|
|
tsc_t start_tsc;
|
|
/* Information about specific interval output types */
|
|
union {
|
|
struct {
|
|
struct interval_element ** values;
|
|
int count;
|
|
} array;
|
|
struct {
|
|
struct interval_list *head, **tail;
|
|
} list;
|
|
struct cr3_value_struct *cr3;
|
|
struct {
|
|
struct domain_data *d;
|
|
int guest_vector[INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX];
|
|
} domain;
|
|
};
|
|
} interval;
|
|
} P = { 0 };
|
|
|
|
/* Function prototypes */
|
|
char * pcpu_string(int pcpu);
|
|
void pcpu_string_draw(struct pcpu_info *p);
|
|
void process_generic(struct record_info *ri);
|
|
void dump_generic(FILE *f, struct record_info *ri);
|
|
ssize_t __read_record(struct trace_record *rec, off_t offset);
|
|
void error(enum error_level l, struct record_info *ri);
|
|
void update_io_address(struct io_address ** list, unsigned int pa, int dir,
|
|
tsc_t arc_cycles, unsigned int va);
|
|
int check_extra_words(struct record_info *ri, int expected_size, const char *record);
|
|
int vcpu_set_data_type(struct vcpu_data *v, int type);
|
|
|
|
void cpumask_init(cpu_mask_t *c) {
|
|
*c = 0UL;
|
|
}
|
|
|
|
void cpumask_clear(cpu_mask_t *c, int cpu) {
|
|
*c &= ~(1UL << cpu);
|
|
}
|
|
|
|
void cpumask_set(cpu_mask_t *c, int cpu) {
|
|
*c |= (1UL << cpu);
|
|
}
|
|
|
|
int cpumask_isset(const cpu_mask_t *c, int cpu) {
|
|
if(*c & (1UL<<cpu))
|
|
return 1;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void cpumask_union(cpu_mask_t *d, const cpu_mask_t *s) {
|
|
*d |= *s;
|
|
}
|
|
|
|
/* -- Time code -- */
|
|
|
|
void cycles_to_time(unsigned long long c, struct time_struct *t) {
|
|
t->time = ((c - P.f.first_tsc) << 10) / opt.cpu_qhz;
|
|
t->s = t->time / 1000000000;
|
|
t->ns = t->time - (t->s * 1000000000);
|
|
}
|
|
|
|
void abs_cycles_to_time(unsigned long long ac, struct time_struct *t) {
|
|
if(ac > P.f.first_tsc) {
|
|
/* t->time = ((ac - P.f.first_tsc) * 1000) / (opt.cpu_hz / 1000000 ); */
|
|
/* t->s = t->time / 1000000000; */
|
|
/* t->ns = t->time % 1000000000; */
|
|
t->time = ((ac - P.f.first_tsc) << 10) / opt.cpu_qhz;
|
|
t->s = t->time / 1000000000;
|
|
t->ns = t->time - (t->s * 1000000000);
|
|
} else {
|
|
t->time = t->s = t->ns = 0;
|
|
}
|
|
}
|
|
|
|
tsc_t abs_cycles_to_global(unsigned long long ac) {
|
|
if(ac > P.f.first_tsc)
|
|
return ac - P.f.first_tsc;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
void scatterplot_vs_time(tsc_t atsc, long long y) {
|
|
struct time_struct t;
|
|
|
|
abs_cycles_to_time(atsc, &t);
|
|
|
|
printf("%u.%09u %lld\n", t.s, t.ns, y);
|
|
}
|
|
|
|
/* -- Summary Code -- */
|
|
|
|
/* With compliments to "Numerical Recipes in C", which provided the algorithm
|
|
* and basic template for this function. */
|
|
long long percentile(long long * A, int N, int ple) {
|
|
int I, J, L, R, K;
|
|
|
|
long long X, W;
|
|
|
|
/* No samples! */
|
|
if ( N == 0 )
|
|
return 0;
|
|
|
|
/* Find K, the element # we want */
|
|
K=N*ple/100;
|
|
|
|
/* Set the left and right boundaries of the current search space */
|
|
L=0; R=N-1;
|
|
|
|
while(L < R) {
|
|
/* X: The value to order everything higher / lower than */
|
|
X=A[K];
|
|
|
|
/* Starting at the left and the right... */
|
|
I=L;
|
|
J=R;
|
|
|
|
do {
|
|
/* Find the first element on the left that is out-of-order w/ X */
|
|
while(A[I]<X)
|
|
I++;
|
|
/* Find the first element on the right that is out-of-order w/ X */
|
|
while(X<A[J])
|
|
J--;
|
|
|
|
/* If we found something out-of-order */
|
|
if(I<=J) {
|
|
/* Switch the values */
|
|
W=A[I];
|
|
A[I]=A[J];
|
|
A[J]=W;
|
|
|
|
/* And move on */
|
|
I++; J--;
|
|
}
|
|
} while (I <= J); /* Keep going until our pointers meet or pass */
|
|
|
|
/* Re-adjust L and R, based on which element we're looking for */
|
|
if(J<K)
|
|
L=I;
|
|
if(K<I)
|
|
R=J;
|
|
}
|
|
|
|
return A[K];
|
|
}
|
|
|
|
float weighted_percentile(float * A, /* values */
|
|
unsigned long long * w, /* weights */
|
|
int N, /* total */
|
|
int ple) /* percentile */
|
|
{
|
|
int L, R, I, J, K;
|
|
unsigned long long L_weight, R_weight, I_weight, J_weight,
|
|
K_weight, N_weight;
|
|
|
|
float X, t1;
|
|
unsigned long long t2;
|
|
|
|
/* Calculate total weight */
|
|
N_weight=0;
|
|
|
|
for(I=0; I<N; I++) {
|
|
assert(w[I]!=0);
|
|
N_weight += w[I];
|
|
}
|
|
|
|
/* Find K_weight, the target weight we want */
|
|
K_weight = N_weight * ple / 100;
|
|
|
|
/* Set the left and right boundaries of the current search space */
|
|
L=0;
|
|
L_weight = 0;
|
|
R=N-1;
|
|
R_weight = N_weight - w[R];
|
|
|
|
/* Search between L and R, narrowing down until we're done */
|
|
while(L < R) {
|
|
/* Chose an ordering value from right in the middle */
|
|
K = (L + R) >> 1;
|
|
/* X: The value to order everything higher / lower than */
|
|
X=A[K];
|
|
|
|
/* Starting at the left and the right... */
|
|
I=L; I_weight = L_weight;
|
|
J=R; J_weight = R_weight;
|
|
|
|
do {
|
|
/* Find the first element on the left that is out-of-order w/ X */
|
|
while(A[I]<X) {
|
|
I_weight += w[I];
|
|
I++;
|
|
}
|
|
/* Find the first element on the right that is out-of-order w/ X */
|
|
while(X<A[J]) {
|
|
J_weight -= w[J];
|
|
J--;
|
|
}
|
|
|
|
/* If we actually found something... */
|
|
if(I<=J) {
|
|
/* Switch the values */
|
|
t1=A[I];
|
|
A[I]=A[J];
|
|
A[J]=t1;
|
|
|
|
t2=w[I];
|
|
w[I]=w[J];
|
|
w[J]=t2;
|
|
|
|
/* And move in */
|
|
I_weight += w[I];
|
|
I++;
|
|
|
|
J_weight -= w[J];
|
|
J--;
|
|
}
|
|
} while (I <= J); /* Keep going until our pointers meet or pass */
|
|
|
|
/* Re-adjust L and R, based on which element we're looking for */
|
|
if(J_weight<K_weight) {
|
|
L=I;
|
|
L_weight = I_weight;
|
|
}
|
|
if(K_weight<I_weight) {
|
|
R=J;
|
|
R_weight = J_weight;
|
|
}
|
|
}
|
|
|
|
return A[L];
|
|
}
|
|
|
|
long long self_weighted_percentile(long long * A,
|
|
int N, /* total */
|
|
int ple) /* percentile */
|
|
{
|
|
int L, R, I, J, K;
|
|
long long L_weight, R_weight, I_weight, J_weight,
|
|
K_weight, N_weight;
|
|
|
|
long long X, t1;
|
|
|
|
/* Calculate total weight */
|
|
N_weight=0;
|
|
|
|
for(I=0; I<N; I++) {
|
|
if(A[I] < 0)
|
|
fprintf(warn, "%s: Value %lld less than zero!\n",
|
|
__func__, A[I]);
|
|
assert(A[I]!=0);
|
|
N_weight += A[I];
|
|
}
|
|
|
|
/* Find K_weight, the target weight we want */
|
|
K_weight = N_weight * ple / 100;
|
|
|
|
/* Set the left and right boundaries of the current search space */
|
|
L=0;
|
|
L_weight = 0;
|
|
R=N-1;
|
|
R_weight = N_weight - A[R];
|
|
|
|
/* Search between L and R, narrowing down until we're done */
|
|
while(L < R) {
|
|
/* Chose an ordering value from right in the middle */
|
|
K = (L + R) >> 1;
|
|
/* X: The value to order everything higher / lower than */
|
|
X=A[K];
|
|
|
|
/* Starting at the left and the right... */
|
|
I=L; I_weight = L_weight;
|
|
J=R; J_weight = R_weight;
|
|
|
|
do {
|
|
/* Find the first element on the left that is out-of-order w/ X */
|
|
while(A[I]<X) {
|
|
I_weight += A[I];
|
|
I++;
|
|
}
|
|
/* Find the first element on the right that is out-of-order w/ X */
|
|
while(X<A[J]) {
|
|
J_weight -= A[J];
|
|
J--;
|
|
}
|
|
|
|
/* If we actually found something... */
|
|
if(I<=J) {
|
|
/* Switch the values */
|
|
t1=A[I];
|
|
A[I]=A[J];
|
|
A[J]=t1;
|
|
|
|
/* And move in */
|
|
I_weight += A[I];
|
|
I++;
|
|
|
|
J_weight -= A[J];
|
|
J--;
|
|
}
|
|
} while (I <= J); /* Keep going until our pointers meet or pass */
|
|
|
|
/* Re-adjust L and R, based on which element we're looking for */
|
|
if(J_weight<K_weight) {
|
|
L=I;
|
|
L_weight = I_weight;
|
|
}
|
|
if(K_weight<I_weight) {
|
|
R=J;
|
|
R_weight = J_weight;
|
|
}
|
|
}
|
|
|
|
return A[L];
|
|
}
|
|
|
|
static inline double __cycles_percent(long long cycles, long long total) {
|
|
return (double)(cycles*100) / total;
|
|
}
|
|
|
|
static inline double __summary_percent(struct cycle_summary *s,
|
|
struct cycle_framework *f) {
|
|
return __cycles_percent(s->cycles, f->total_cycles);
|
|
}
|
|
|
|
static inline double summary_percent_global(struct cycle_summary *s) {
|
|
return __summary_percent(s, &P.f);
|
|
}
|
|
|
|
static inline void update_cycles(struct cycle_summary *s, long long c) {
|
|
s->event_count++;
|
|
|
|
if (!c)
|
|
return;
|
|
|
|
if(opt.sample_size) {
|
|
if (s->count >= s->sample_size
|
|
&& (s->count == 0
|
|
|| opt.sample_max == 0
|
|
|| s->sample_size < opt.sample_max)) {
|
|
int new_size;
|
|
void * new_sample = NULL;
|
|
|
|
new_size = s->sample_size << 1;
|
|
|
|
if (new_size == 0)
|
|
new_size = opt.sample_size;
|
|
|
|
if (opt.sample_max != 0 && new_size > opt.sample_max)
|
|
new_size = opt.sample_max;
|
|
|
|
new_sample = realloc(s->sample, sizeof(*s->sample) * new_size);
|
|
|
|
if (new_sample) {
|
|
s->sample = new_sample;
|
|
s->sample_size = new_size;
|
|
}
|
|
}
|
|
|
|
if (s->count < s->sample_size) {
|
|
s->sample[s->count]=c;
|
|
} else {
|
|
/*
|
|
* If we run out of space for samples, start taking only a
|
|
* subset of samples.
|
|
*/
|
|
int lap, index;
|
|
lap = (s->count/s->sample_size)+1;
|
|
index =s->count % s->sample_size;
|
|
if((index - (lap/3))%lap == 0) {
|
|
s->sample[index]=c;
|
|
}
|
|
}
|
|
}
|
|
s->count++;
|
|
s->cycles += c;
|
|
|
|
s->interval.count++;
|
|
s->interval.cycles += c;
|
|
}
|
|
|
|
static inline void clear_interval_cycles(struct interval_element *e) {
|
|
e->cycles = 0;
|
|
e->count = 0;
|
|
e->instructions = 0;
|
|
}
|
|
|
|
static inline void print_cpu_affinity(struct cycle_summary *s, const char *p) {
|
|
if(s->count) {
|
|
long long avg;
|
|
|
|
avg = s->cycles / s->count;
|
|
|
|
if ( opt.sample_size ) {
|
|
long long p5, p50, p95;
|
|
int data_size = s->count;
|
|
if(data_size > s->sample_size)
|
|
data_size = s->sample_size;
|
|
|
|
p50 = percentile(s->sample, data_size, 50);
|
|
p5 = percentile(s->sample, data_size, 5);
|
|
p95 = percentile(s->sample, data_size, 95);
|
|
|
|
printf("%s: %7d %6lld {%6lld|%6lld|%6lld}\n",
|
|
p, s->count, avg, p5, p50, p95);
|
|
} else {
|
|
printf("%s: %7d %6lld\n",
|
|
p, s->count, avg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void print_cycle_percent_summary(struct cycle_summary *s,
|
|
tsc_t total, char *p) {
|
|
if(s->count) {
|
|
long long avg;
|
|
double percent, seconds;
|
|
|
|
avg = s->cycles / s->count;
|
|
|
|
seconds = ((double)s->cycles) / opt.cpu_hz;
|
|
|
|
percent = ((double)(s->cycles * 100)) / total;
|
|
|
|
if ( opt.sample_size ) {
|
|
long long p5, p50, p95;
|
|
int data_size = s->count;
|
|
|
|
if(data_size > s->sample_size)
|
|
data_size = s->sample_size;
|
|
|
|
p50 = self_weighted_percentile(s->sample, data_size, 50);
|
|
p5 = self_weighted_percentile(s->sample, data_size, 5);
|
|
p95 = self_weighted_percentile(s->sample, data_size, 95);
|
|
|
|
printf("%s: %7d %5.2lfs %5.2lf%% %6lld {%6lld|%6lld|%6lld}\n",
|
|
p, s->count,
|
|
seconds,
|
|
percent,
|
|
avg, p5, p50, p95);
|
|
} else {
|
|
printf("%s: %7d %5.2lfs %5.2lf%% %6lld\n",
|
|
p, s->count,
|
|
seconds,
|
|
percent,
|
|
avg);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline void print_cycle_summary(struct cycle_summary *s,
|
|
const char *p) {
|
|
if(s->count) {
|
|
long long avg;
|
|
|
|
avg = s->cycles / s->count;
|
|
|
|
if ( opt.sample_size ) {
|
|
long long p5, p50, p95;
|
|
int data_size = s->count;
|
|
|
|
if(data_size > s->sample_size)
|
|
data_size = s->sample_size;
|
|
|
|
p50 = self_weighted_percentile(s->sample, data_size, 50);
|
|
p5 = self_weighted_percentile(s->sample, data_size, 5);
|
|
p95 = self_weighted_percentile(s->sample, data_size, 95);
|
|
|
|
printf("%s: %7d %5.2lfs %6lld {%6lld|%6lld|%6lld}\n",
|
|
p, s->count, ((double)s->cycles)/opt.cpu_hz,
|
|
avg, p5, p50, p95);
|
|
} else {
|
|
printf("%s: %7d %5.2lfs %6lld\n",
|
|
p, s->count, ((double)s->cycles)/opt.cpu_hz, avg);
|
|
}
|
|
}
|
|
}
|
|
|
|
#define PRINT_SUMMARY(_s, _p...) \
|
|
do { \
|
|
if((_s).event_count) { \
|
|
if ( opt.sample_size ) { \
|
|
unsigned long long p5, p50, p95; \
|
|
int data_size=(_s).count; \
|
|
if(data_size > (_s).sample_size) \
|
|
data_size=(_s).sample_size; \
|
|
p50=percentile((_s).sample, data_size, 50); \
|
|
p5=percentile((_s).sample, data_size, 5); \
|
|
p95=percentile((_s).sample, data_size, 95); \
|
|
printf(_p); \
|
|
printf(" %7d %5.2lfs %5.2lf%% %5lld cyc {%5lld|%5lld|%5lld}\n", \
|
|
(_s).event_count, \
|
|
((double)(_s).cycles)/opt.cpu_hz, \
|
|
summary_percent_global(&(_s)), \
|
|
(_s).count ? (_s).cycles / (_s).count:0, \
|
|
p5, p50, p95); \
|
|
} else { \
|
|
printf(_p); \
|
|
printf(" %7d %5.2lfs %5.2lf%% %5lld cyc\n", \
|
|
(_s).event_count, \
|
|
((double)(_s).cycles)/opt.cpu_hz, \
|
|
summary_percent_global(&(_s)), \
|
|
(_s).count ? (_s).cycles / (_s).count:0); \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
#define INTERVAL_DESC_MAX 31
|
|
struct interval_list {
|
|
struct interval_element *elem;
|
|
struct interval_list *next;
|
|
char desc[INTERVAL_DESC_MAX+1]; /* +1 for the null terminator */
|
|
};
|
|
|
|
void __interval_cycle_percent_output(struct interval_element *e, tsc_t cycles) {
|
|
printf(" %.02lf",
|
|
__cycles_percent(e->cycles, cycles));
|
|
clear_interval_cycles(e);
|
|
}
|
|
|
|
void interval_cycle_percent_output(struct interval_element *e) {
|
|
__interval_cycle_percent_output(e, opt.interval.cycles);
|
|
}
|
|
|
|
void interval_time_output(void) {
|
|
struct time_struct t;
|
|
abs_cycles_to_time(P.interval.start_tsc, &t);
|
|
|
|
printf("%u.%09u", t.s, t.ns);
|
|
}
|
|
|
|
void interval_table_output(void) {
|
|
int i;
|
|
|
|
interval_time_output();
|
|
|
|
if(opt.interval.mode == INTERVAL_MODE_ARRAY) {
|
|
for(i=0; i<P.interval.array.count; i++) {
|
|
struct interval_element *e = P.interval.array.values[i];
|
|
if(e) {
|
|
interval_cycle_percent_output(e);
|
|
} else {
|
|
printf(" 0.0");
|
|
}
|
|
}
|
|
} else if(opt.interval.mode == INTERVAL_MODE_LIST) {
|
|
struct interval_list *p;
|
|
for(p = P.interval.list.head; p; p = p->next)
|
|
interval_cycle_percent_output(p->elem);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void interval_table_tail(void) {
|
|
struct interval_list *p;
|
|
|
|
printf("time");
|
|
|
|
for(p=P.interval.list.head; p; p = p->next)
|
|
printf(" %s", p->desc);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
void interval_table_alloc(int count) {
|
|
P.interval.array.count = count;
|
|
P.interval.array.values = malloc(count * sizeof(struct interval_list *));
|
|
|
|
if(!P.interval.array.values) {
|
|
fprintf(stderr, "Malloc failed!\n");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(P.interval.array.values, count*sizeof(struct interval_list *));
|
|
}
|
|
|
|
void interval_list_add(struct interval_element *e, char *desc) {
|
|
struct interval_list *p;
|
|
|
|
fprintf(warn, "%s: Adding element '%s'\n", __func__, desc);
|
|
|
|
if((p=malloc(sizeof(*p)))==NULL) {
|
|
fprintf(stderr, "malloc() failed.\n");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(p, sizeof(*p));
|
|
|
|
p->elem = e;
|
|
strncpy(p->desc, desc, INTERVAL_DESC_MAX);
|
|
|
|
p->next=NULL;
|
|
|
|
if(P.interval.list.head)
|
|
*P.interval.list.tail = p;
|
|
else
|
|
P.interval.list.head = p;
|
|
P.interval.list.tail = &p->next;
|
|
}
|
|
|
|
void interval_cr3_schedule_time_header(void) {
|
|
if( opt.interval.mode == INTERVAL_MODE_ARRAY ) {
|
|
int i;
|
|
|
|
printf("time");
|
|
for(i=0; i<opt.interval.array.count; i++) {
|
|
printf(" %llx", opt.interval.array.values[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
/* Can't see into the future, so no header if cr3 values are
|
|
not specified. */
|
|
}
|
|
|
|
void interval_cr3_value_check(struct cr3_value_struct *cr3) {
|
|
if( opt.interval.mode == INTERVAL_MODE_ARRAY ) {
|
|
int i;
|
|
|
|
for(i=0; i<opt.interval.array.count; i++) {
|
|
if(cr3->gmfn == opt.interval.array.values[i]) {
|
|
if(P.interval.array.values[i]) {
|
|
fprintf(stderr, "Fatal: duplicate cr3 value %llx!\n",
|
|
cr3->gmfn);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
fprintf(stderr, "%s: found gmfn %llx\n",
|
|
__func__, cr3->gmfn);
|
|
|
|
P.interval.array.values[i] = &cr3->total_time.interval;
|
|
}
|
|
}
|
|
} else if(opt.interval.mode == INTERVAL_MODE_LIST) {
|
|
char desc[32];
|
|
snprintf(desc, 32, "%llx", cr3->gmfn);
|
|
interval_list_add(&cr3->total_time.interval, desc);
|
|
} else {
|
|
/* Custom */
|
|
if(cr3->gmfn == opt.interval.array.values[0])
|
|
P.interval.cr3 = cr3;
|
|
}
|
|
}
|
|
|
|
int cr3_time_compare(const void *_a, const void *_b) {
|
|
struct cr3_value_struct *a=*(typeof(&a))_a;
|
|
struct cr3_value_struct *b=*(typeof(&a))_b;
|
|
|
|
if(a->total_time.interval.cycles < b->total_time.interval.cycles)
|
|
return 1;
|
|
else if(b->total_time.interval.cycles == a->total_time.interval.cycles) {
|
|
if(a->total_time.interval.count < b->total_time.interval.count)
|
|
return 1;
|
|
else if(a->total_time.interval.count == b->total_time.interval.count)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
} else
|
|
return -1;
|
|
}
|
|
|
|
void interval_cr3_schedule_ordered_output(void) {
|
|
struct cr3_value_struct *p;
|
|
int i;
|
|
|
|
struct cr3_value_struct **qsort_array;
|
|
int N=0;
|
|
|
|
for(p=P.cr3.head; p; p=p->gnext)
|
|
N++;
|
|
|
|
if(!N)
|
|
return;
|
|
|
|
qsort_array = malloc(N * sizeof(struct eip_list_struct *));
|
|
|
|
for(i=0, p=P.cr3.head; p; p=p->gnext, i++)
|
|
qsort_array[i]=p;
|
|
|
|
qsort(qsort_array, N, sizeof(struct eip_list_struct *),
|
|
cr3_time_compare);
|
|
|
|
interval_time_output();
|
|
|
|
for(i=0; i<N; i++) {
|
|
p = qsort_array[i];
|
|
/* Rounding down means this will get ..1]% */
|
|
if(p->total_time.interval.cycles > 0) {
|
|
printf(" %8llx: %.02lf %c\n",
|
|
p->gmfn,
|
|
__cycles_percent(p->total_time.interval.cycles,
|
|
opt.interval.cycles),
|
|
(p->first_time > P.interval.start_tsc)?'*':' ');
|
|
}
|
|
clear_interval_cycles(&p->total_time.interval);
|
|
}
|
|
|
|
free(qsort_array);
|
|
}
|
|
|
|
void interval_cr3_short_summary_header(void) {
|
|
int i;
|
|
|
|
printf("time guest");
|
|
for(i=0; i<HVM_SHORT_SUMMARY_MAX; i++)
|
|
printf(" %s", hvm_short_summary_name[i]);
|
|
printf("\n");
|
|
}
|
|
|
|
void interval_cr3_short_summary_output(void) {
|
|
struct cycle_summary *hss_array;
|
|
int i;
|
|
|
|
if(P.interval.cr3) {
|
|
struct cr3_value_struct *p = P.interval.cr3;
|
|
|
|
interval_time_output();
|
|
|
|
hss_array = p->hvm.s;
|
|
|
|
printf(" %.02lf",
|
|
__cycles_percent(p->total_time.interval.cycles,
|
|
opt.interval.cycles));
|
|
|
|
for(i=0; i<HVM_SHORT_SUMMARY_MAX; i++)
|
|
__interval_cycle_percent_output(&hss_array[i].interval,
|
|
p->total_time.interval.cycles);
|
|
|
|
clear_interval_cycles(&p->total_time.interval);
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void interval_domain_value_check(struct domain_data *d) {
|
|
if( opt.interval.mode == INTERVAL_MODE_ARRAY ) {
|
|
int i;
|
|
|
|
for(i=0; i<opt.interval.array.count; i++) {
|
|
if(d->did == opt.interval.array.values[i]) {
|
|
if(P.interval.array.values[i]) {
|
|
fprintf(stderr, "Fatal: duplicate domain value %d!\n",
|
|
d->did);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
|
|
P.interval.array.values[i] = &d->total_time.interval;
|
|
}
|
|
}
|
|
} else if(opt.interval.mode == INTERVAL_MODE_LIST) {
|
|
char desc[32];
|
|
snprintf(desc, 32, "%d", d->did);
|
|
interval_list_add(&d->total_time.interval, desc);
|
|
} else {
|
|
if(d->did == opt.interval.array.values[0])
|
|
P.interval.domain.d = d;
|
|
}
|
|
}
|
|
|
|
void interval_domain_short_summary_header(void) {
|
|
int i;
|
|
|
|
printf("time running");
|
|
for(i=0; i<HVM_SHORT_SUMMARY_MAX; i++)
|
|
printf(" %s", hvm_short_summary_name[i]);
|
|
printf("\n");
|
|
}
|
|
|
|
void interval_domain_short_summary_output(void) {
|
|
|
|
if(P.interval.domain.d) {
|
|
struct domain_data *d;
|
|
int i;
|
|
|
|
d=P.interval.domain.d;
|
|
|
|
interval_time_output();
|
|
|
|
interval_cycle_percent_output(&d->total_time.interval);
|
|
|
|
for(i=0; i<HVM_SHORT_SUMMARY_MAX; i++)
|
|
interval_cycle_percent_output(&d->hvm_short.s[i].interval);
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void interval_domain_guest_interrupt(struct hvm_data *h, int vector) {
|
|
struct domain_data *d = h->v->d;
|
|
int i;
|
|
|
|
/* Check to see if this vector is in the "print list" */
|
|
for(i=0; i<INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX; i++) {
|
|
if(P.interval.domain.guest_vector[i] == 0) {
|
|
P.interval.domain.guest_vector[i] = vector;
|
|
break;
|
|
}
|
|
if(P.interval.domain.guest_vector[i] == vector)
|
|
break;
|
|
}
|
|
|
|
if(i == INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX) {
|
|
fprintf(stderr, "FATAL: used up all %d guest interrupt slots!\n",
|
|
INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX);
|
|
error(ERR_LIMIT, NULL);
|
|
} else {
|
|
d->guest_interrupt[vector]++;
|
|
}
|
|
}
|
|
|
|
void interval_domain_guest_interrupt_tail(void) {
|
|
int i;
|
|
|
|
printf("time running");
|
|
for(i=0; i<INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX; i++) {
|
|
if(P.interval.domain.guest_vector[i] == 0)
|
|
break;
|
|
printf(" %d", P.interval.domain.guest_vector[i]);
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void interval_domain_guest_interrupt_output(void) {
|
|
|
|
if(P.interval.domain.d) {
|
|
struct domain_data *d;
|
|
int i;
|
|
|
|
d=P.interval.domain.d;
|
|
|
|
interval_time_output();
|
|
|
|
for(i=0; i<INTERVAL_DOMAIN_GUEST_INTERRUPT_MAX; i++) {
|
|
int v = P.interval.domain.guest_vector[i];
|
|
|
|
if(v == 0)
|
|
break;
|
|
|
|
printf(" %d", d->guest_interrupt[v]);
|
|
|
|
d->guest_interrupt[v]=0;
|
|
}
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
}
|
|
|
|
void interval_domain_grant_maps_output(void) {
|
|
|
|
if(P.interval.domain.d) {
|
|
struct domain_data *d;
|
|
|
|
d=P.interval.domain.d;
|
|
|
|
interval_time_output();
|
|
|
|
printf(" %d", d->memops.done_for_interval[MEM_PAGE_GRANT_MAP]);
|
|
|
|
d->memops.done_for_interval[MEM_PAGE_GRANT_MAP] = 0;
|
|
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/* General interval gateways */
|
|
|
|
void interval_callback(void) {
|
|
/* First, see if we're in generic mode. */
|
|
switch(opt.interval.mode) {
|
|
case INTERVAL_MODE_LIST:
|
|
case INTERVAL_MODE_ARRAY:
|
|
interval_table_output();
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch(opt.interval.output) {
|
|
case INTERVAL_CR3_SCHEDULE_ORDERED:
|
|
interval_cr3_schedule_ordered_output();
|
|
break;
|
|
case INTERVAL_CR3_SHORT_SUMMARY:
|
|
interval_cr3_short_summary_output();
|
|
break;
|
|
case INTERVAL_DOMAIN_SHORT_SUMMARY:
|
|
interval_domain_short_summary_output();
|
|
break;
|
|
case INTERVAL_DOMAIN_GUEST_INTERRUPT:
|
|
interval_domain_guest_interrupt_output();
|
|
break;
|
|
case INTERVAL_DOMAIN_GRANT_MAPS:
|
|
interval_domain_grant_maps_output();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void interval_header(void) {
|
|
switch(opt.interval.output) {
|
|
case INTERVAL_CR3_SHORT_SUMMARY:
|
|
interval_cr3_short_summary_header();
|
|
break;
|
|
case INTERVAL_DOMAIN_SHORT_SUMMARY:
|
|
interval_domain_short_summary_header();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void interval_tail(void) {
|
|
if(opt.interval.mode == INTERVAL_MODE_LIST) {
|
|
interval_table_tail();
|
|
return;
|
|
}
|
|
|
|
switch(opt.interval.output) {
|
|
case INTERVAL_DOMAIN_GUEST_INTERRUPT:
|
|
interval_domain_guest_interrupt_tail();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* -- Eip list data -- */
|
|
|
|
void update_eip(struct eip_list_struct **head, unsigned long long eip,
|
|
unsigned long long cycles, int type, void * extra) {
|
|
struct eip_list_struct *p, **last=head;
|
|
|
|
for(p=*head; p; last = (&p->next), p=p->next)
|
|
if(p->eip >= eip)
|
|
break;
|
|
|
|
if(!p || p->eip != eip) {
|
|
p=malloc(sizeof(*p));
|
|
if(!p) {
|
|
perror("malloc failed");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(p, sizeof(*p));
|
|
|
|
p->eip=eip;
|
|
p->type = type;
|
|
|
|
if(eip_list_type[type].new) {
|
|
eip_list_type[type].new(p, extra);
|
|
}
|
|
p->next = *last;
|
|
*last=p;
|
|
} else if(p->type != type) {
|
|
fprintf(stderr, "WARNING, mixed types! %d %d\n", p->type, type);
|
|
} else if(eip_list_type[type].update) {
|
|
eip_list_type[type].update(p, extra);
|
|
}
|
|
|
|
update_cycles(&p->summary, cycles);
|
|
}
|
|
|
|
int eip_compare(const void *_a, const void *_b) {
|
|
struct eip_list_struct *a=*(typeof(&a))_a;
|
|
struct eip_list_struct *b=*(typeof(&a))_b;
|
|
|
|
if(a->summary.cycles < b->summary.cycles)
|
|
return 1;
|
|
else if(b->summary.cycles == a->summary.cycles) {
|
|
if(a->summary.count < b->summary.count)
|
|
return 1;
|
|
else if(a->summary.count == b->summary.count)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
} else
|
|
return -1;
|
|
}
|
|
|
|
void dump_eip(struct eip_list_struct *head) {
|
|
struct eip_list_struct *p;
|
|
int i;
|
|
int total = 0;
|
|
|
|
struct eip_list_struct **qsort_array;
|
|
int N=0;
|
|
|
|
for(p=head; p; p=p->next)
|
|
{
|
|
total += p->summary.event_count;
|
|
N++;
|
|
}
|
|
|
|
if(!N)
|
|
return;
|
|
|
|
qsort_array = malloc(N * sizeof(struct eip_list_struct *));
|
|
|
|
for(i=0, p=head; p; p=p->next, i++)
|
|
qsort_array[i]=p;
|
|
|
|
qsort(qsort_array, N, sizeof(struct eip_list_struct *),
|
|
eip_compare);
|
|
|
|
/* WARNING: don't use N after this point unless you copy this variable */
|
|
#if 0
|
|
if(opt.summary_eip_limit && opt.summary_eip_limit < N)
|
|
N=opt.summary_eip_limit;
|
|
#endif
|
|
|
|
printf(" Total samples: %d\n", total);
|
|
|
|
for(i=0; i<N; i++) {
|
|
p = qsort_array[i];
|
|
if ( p->summary.cycles )
|
|
PRINT_SUMMARY(p->summary, " %12llx%-45s: ",
|
|
p->eip,
|
|
find_symbol(p->eip));
|
|
else
|
|
{
|
|
printf(" %12llx%-45s: ",
|
|
p->eip,
|
|
find_symbol(p->eip));
|
|
printf(" %7d %5.2lf%%\n",
|
|
p->summary.event_count,
|
|
((double)p->summary.event_count*100)/total);
|
|
}
|
|
|
|
|
|
if(eip_list_type[p->type].dump) {
|
|
eip_list_type[p->type].dump(p);
|
|
}
|
|
}
|
|
|
|
free(qsort_array);
|
|
}
|
|
|
|
/* -- HVM code -- */
|
|
struct hvm_pf_xen_record {
|
|
//unsigned vcpu:16, domain:16;
|
|
union {
|
|
struct {
|
|
unsigned long long va;
|
|
unsigned int error_code;
|
|
} x64;
|
|
struct {
|
|
unsigned int va;
|
|
unsigned int error_code;
|
|
} x32;
|
|
};
|
|
};
|
|
|
|
void hvm_update_short_summary(struct hvm_data *h, int element) {
|
|
struct vcpu_data *v = h->v;
|
|
|
|
if(v->cr3.data)
|
|
update_cycles(&v->cr3.data->hvm.s[element], h->arc_cycles);
|
|
|
|
update_cycles(&v->d->hvm_short.s[element], h->arc_cycles);
|
|
|
|
h->short_summary_done=1;
|
|
}
|
|
|
|
void hvm_short_summary(struct hvm_short_summary_struct *hss,
|
|
tsc_t total, const char *prefix) {
|
|
char desc[80];
|
|
int i;
|
|
|
|
for(i=0; i<HVM_SHORT_SUMMARY_MAX; i++) {
|
|
snprintf(desc, 80, "%s%s", prefix, hvm_short_summary_name[i]);
|
|
print_cycle_percent_summary(hss->s + i, total, desc);
|
|
}
|
|
}
|
|
|
|
/* Wrapper to try to make sure this is only called once per
|
|
* call site, rather than walking through the list each time */
|
|
#define hvm_set_summary_handler(_h, _s, _d) \
|
|
do { \
|
|
static int done=0; \
|
|
int ret; \
|
|
if(!done) { \
|
|
if ((ret=__hvm_set_summary_handler(_h, _s, _d))) \
|
|
fprintf(stderr, "%s: hvm_set_summary_handler returned %d\n", \
|
|
__func__, ret); \
|
|
done=1; \
|
|
} \
|
|
} while(0)
|
|
|
|
int __hvm_set_summary_handler(struct hvm_data *h, void (*s)(struct hvm_data *h, void*d), void*d) {
|
|
/* Set summary handler */
|
|
if(h->exit_reason < h->exit_reason_max)
|
|
{
|
|
struct hvm_summary_handler_node *p, **q;
|
|
|
|
/* Find the end of the list, checking to make sure there are no
|
|
* duplicates along the way */
|
|
q=&h->exit_reason_summary_handler_list[h->exit_reason];
|
|
p = *q;
|
|
while(p)
|
|
{
|
|
if(p->handler == s && p->data == d)
|
|
{
|
|
fprintf(stderr, "%s: Unexpected duplicate handler %p,%p\n",
|
|
__func__, s, d);
|
|
error(ERR_STRICT, NULL);
|
|
return -EBUSY;
|
|
}
|
|
q=&p->next;
|
|
p=*q;
|
|
}
|
|
|
|
assert(p==NULL);
|
|
|
|
/* Insert the new handler */
|
|
p=malloc(sizeof(*p));
|
|
if (!p) {
|
|
fprintf(stderr, "%s: Malloc failed!\n", __func__);
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
p->handler=s;
|
|
p->data = d;
|
|
p->next=*q;
|
|
*q=p;
|
|
return 0;
|
|
}
|
|
return -EINVAL;
|
|
}
|
|
|
|
void hvm_generic_postprocess(struct hvm_data *h);
|
|
|
|
static int hvm_set_postprocess(struct hvm_data *h, void (*s)(struct hvm_data *h))
|
|
{
|
|
if ( h->post_process == NULL
|
|
|| h->post_process == hvm_generic_postprocess )
|
|
{
|
|
h->post_process = s;
|
|
return 0;
|
|
}
|
|
else
|
|
return 1;
|
|
}
|
|
|
|
#define SIGN_EXTENDED_BITS (~((1ULL<<48)-1))
|
|
#define HIGH_BIT(_v) ((_v) & (1ULL<<47))
|
|
static inline int is_valid_addr64(unsigned long long va)
|
|
{
|
|
if(HIGH_BIT(va))
|
|
return ((va & SIGN_EXTENDED_BITS) == SIGN_EXTENDED_BITS);
|
|
else
|
|
return ((va & SIGN_EXTENDED_BITS) == 0);
|
|
}
|
|
|
|
void hvm_pf_xen_summary(struct hvm_data *h, void *d) {
|
|
int i,j, k;
|
|
|
|
printf(" page_fault\n");
|
|
for(i=0; i<PF_XEN_MAX; i++)
|
|
{
|
|
if( pf_xen_name[i] )
|
|
{
|
|
PRINT_SUMMARY(h->summary.pf_xen[i],
|
|
" %-25s ", pf_xen_name[i]);
|
|
}
|
|
else
|
|
{
|
|
PRINT_SUMMARY(h->summary.pf_xen[i],
|
|
" [%23d] ", i);
|
|
}
|
|
switch(i){
|
|
case PF_XEN_NON_EMULATE:
|
|
for(j=0; j<PF_XEN_NON_EMUL_MAX; j++)
|
|
PRINT_SUMMARY(h->summary.pf_xen_non_emul[j],
|
|
" *%-13s ", pf_xen_non_emul_name[j]);
|
|
break;
|
|
case PF_XEN_EMULATE:
|
|
for(j=0; j<PF_XEN_EMUL_MAX; j++) {
|
|
PRINT_SUMMARY(h->summary.pf_xen_emul[j],
|
|
" *%-13s ", pf_xen_emul_name[j]);
|
|
if(j == PF_XEN_EMUL_EARLY_UNSHADOW) {
|
|
int k;
|
|
for(k=0; k<5; k++) {
|
|
PRINT_SUMMARY(h->summary.pf_xen_emul_early_unshadow[k],
|
|
" +[%d] ", k);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case PF_XEN_FIXUP:
|
|
for(j=0; j<PF_XEN_FIXUP_MAX; j++) {
|
|
PRINT_SUMMARY(h->summary.pf_xen_fixup[j],
|
|
" *%-13s ", pf_xen_fixup_name[j]);
|
|
if(j == PF_XEN_FIXUP_UNSYNC ) {
|
|
for(k=0; k<PF_XEN_FIXUP_UNSYNC_RESYNC_MAX; k++) {
|
|
PRINT_SUMMARY(h->summary.pf_xen_fixup_unsync_resync[k],
|
|
" +[%3d] ", k);
|
|
}
|
|
PRINT_SUMMARY(h->summary.pf_xen_fixup_unsync_resync[k],
|
|
" +[max] ");
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void pf_preprocess(struct pf_xen_extra *e, int guest_paging_levels)
|
|
{
|
|
switch(guest_paging_levels) {
|
|
/* Select a subfield of _bits bits starting at bit _shift from _x */
|
|
#define _SUBFIELD(_bits, _shift, _x) \
|
|
(((_x)>>(_shift)) & ((1ULL<<(_bits))-1))
|
|
case 4:
|
|
/* Verify sign-extension */
|
|
if((HIGH_BIT(e->va)
|
|
&&((e->va & SIGN_EXTENDED_BITS) != SIGN_EXTENDED_BITS))
|
|
|| (!HIGH_BIT(e->va)
|
|
&& ((e->va & SIGN_EXTENDED_BITS) != 0))) {
|
|
fprintf(warn, "Strange, va %llx not properly sign extended for 4-level pagetables\n",
|
|
e->va);
|
|
}
|
|
e->pt_index[4]=_SUBFIELD(9,39,e->va);
|
|
e->pt_index[3]=_SUBFIELD(9,30,e->va);
|
|
e->pt_index[2]=_SUBFIELD(9,21,e->va);
|
|
e->pt_index[1]=_SUBFIELD(9,12,e->va);
|
|
/* These are only useful for the linear-pagetable code */
|
|
e->pt_index[0]=_SUBFIELD(9,3,e->va);
|
|
if(e->va & 0x4)
|
|
e->pt_is_lo=0;
|
|
break;
|
|
case 3:
|
|
e->pt_index[3]=_SUBFIELD(2,30,e->va);
|
|
e->pt_index[2]=_SUBFIELD(9,21,e->va);
|
|
e->pt_index[1]=_SUBFIELD(9,12,e->va);
|
|
/* These are only useful for the linear-pagetable code */
|
|
e->pt_index[0]=_SUBFIELD(9,3,e->va);
|
|
if(e->va & 0x4)
|
|
e->pt_is_lo=0;
|
|
break;
|
|
case 2:
|
|
e->pt_index[2]=_SUBFIELD(10,22,e->va);
|
|
e->pt_index[1]=_SUBFIELD(10,12,e->va);
|
|
/* This is only useful for the linear pagetable code */
|
|
e->pt_index[0]=_SUBFIELD(10,2,e->va);
|
|
break;
|
|
case 0:
|
|
break;
|
|
default:
|
|
fprintf(warn, "Don't know how to handle %d-level pagetables\n",
|
|
guest_paging_levels);
|
|
}
|
|
|
|
e->corresponding_va = CORR_VA_INVALID;
|
|
e->pt_level = 0;
|
|
|
|
/* Detect accesses to Windows linear pagetables */
|
|
switch(guest_paging_levels)
|
|
{
|
|
case 2:
|
|
if(e->pt_index[2] == 768) {
|
|
if(e->pt_index[1] == 768)
|
|
{
|
|
e->pt_level = 2;
|
|
e->corresponding_va=((1UL<<22)-1)
|
|
| e->pt_index[0]<<22;
|
|
}
|
|
else
|
|
{
|
|
e->pt_level = 1;
|
|
e->corresponding_va = ((1UL<<12)-1)
|
|
| e->pt_index[1]<<22
|
|
| e->pt_index[0]<<12;
|
|
}
|
|
}
|
|
break;
|
|
case 3:
|
|
if(e->pt_index[3]==3 && (e->pt_index[2]>>2==0))
|
|
{
|
|
if(e->pt_index[2]==3 && e->pt_index[1]>>2==0)
|
|
{
|
|
if(e->pt_index[1] == 3 && e->pt_index[0]>>2==0)
|
|
{
|
|
e->pt_level = 3;
|
|
e->corresponding_va=((1UL<<30)-1)
|
|
| e->pt_index[0]<<30;
|
|
}
|
|
else
|
|
{
|
|
e->pt_level = 2;
|
|
e->corresponding_va=((1UL<<21)-1)
|
|
| e->pt_index[1]<<30
|
|
| e->pt_index[2]<<21;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e->pt_level = 1;
|
|
e->corresponding_va = ((1UL<<12)-1)
|
|
| e->pt_index[0]<<12
|
|
| e->pt_index[1]<<21
|
|
| e->pt_index[2]<<30;
|
|
}
|
|
}
|
|
break;
|
|
case 4:
|
|
if(e->pt_index[4] == 0x1ed)
|
|
{
|
|
if(e->pt_index[3] == 0x1ed)
|
|
{
|
|
if(e->pt_index[2] == 0x1ed)
|
|
{
|
|
if(e->pt_index[1] == 0x1ed)
|
|
{
|
|
e->pt_level = 4;
|
|
e->corresponding_va = ((1ULL<<39)-1)
|
|
| (unsigned long long)e->pt_index[0]<<39;
|
|
}
|
|
else
|
|
{
|
|
e->pt_level = 3;
|
|
e->corresponding_va = ((1ULL<<30)-1)
|
|
| (unsigned long long)e->pt_index[0]<<30
|
|
| (unsigned long long)e->pt_index[1]<<39;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e->pt_level = 2;
|
|
e->corresponding_va = ((1ULL<<21)-1)
|
|
| (unsigned long long)e->pt_index[0]<<21
|
|
| (unsigned long long)e->pt_index[1]<<30
|
|
| (unsigned long long)e->pt_index[2]<<39;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
e->pt_level = 1;
|
|
e->corresponding_va = ((1ULL<<12)-1)
|
|
| (unsigned long long)e->pt_index[0]<<12
|
|
| (unsigned long long)e->pt_index[1]<<21
|
|
| (unsigned long long)e->pt_index[2]<<30
|
|
| (unsigned long long)e->pt_index[3]<<39;
|
|
}
|
|
|
|
if(HIGH_BIT(e->corresponding_va))
|
|
e->corresponding_va |= SIGN_EXTENDED_BITS;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void hvm_pf_xen_preprocess(unsigned event, struct hvm_data *h) {
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
struct mmio_info *m = &h->inflight.mmio;
|
|
struct hvm_pf_xen_record *r = (typeof(r))h->d;
|
|
|
|
if(event == TRC_HVM_PF_XEN64)
|
|
{
|
|
if(!is_valid_addr64(r->x64.va))
|
|
fprintf(warn, "%s: invalid va %llx",
|
|
__func__, r->x64.va);
|
|
e->va = r->x64.va;
|
|
e->error_code = r->x64.error_code;
|
|
}
|
|
else
|
|
{
|
|
e->va = r->x32.va;
|
|
e->error_code = r->x32.error_code;
|
|
}
|
|
|
|
if(m->data_valid)
|
|
e->pf_case = PF_XEN_MMIO;
|
|
else
|
|
{
|
|
pf_preprocess(e, h->v->guest_paging_levels);
|
|
|
|
/* On rio traces, we try to infer emulation by looking for accesses
|
|
in the linear pagetable */
|
|
if(e->pt_level > 0)
|
|
e->pf_case = PF_XEN_EMULATE;
|
|
else
|
|
e->pf_case = PF_XEN_NON_EMULATE;
|
|
}
|
|
}
|
|
|
|
static inline int is_kernel(int paging_levels, unsigned long long va) {
|
|
switch(paging_levels) {
|
|
case 2:
|
|
case 3:
|
|
if(va & 0x80000000)
|
|
return 1;
|
|
else
|
|
return 0;
|
|
break;
|
|
case 4:
|
|
if(HIGH_BIT(va))
|
|
return 1;
|
|
else return 0;
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
void hvm_pf_xen_postprocess(struct hvm_data *h) {
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
|
|
if(opt.summary_info) {
|
|
if(e->pf_case)
|
|
update_cycles(&h->summary.pf_xen[e->pf_case],
|
|
h->arc_cycles);
|
|
else
|
|
fprintf(warn, "Strange, pf_case 0!\n");
|
|
switch(e->pf_case)
|
|
{
|
|
case PF_XEN_EMULATE:
|
|
update_eip(&h->v->d->emulate_eip_list,
|
|
h->rip,
|
|
h->arc_cycles,
|
|
0, NULL);
|
|
break;
|
|
case PF_XEN_NON_EMULATE:
|
|
if(is_kernel(h->v->guest_paging_levels, h->rip))
|
|
update_cycles(&h->summary.pf_xen_non_emul[PF_XEN_NON_EMUL_EIP_KERNEL],
|
|
h->arc_cycles);
|
|
else
|
|
update_cycles(&h->summary.pf_xen_non_emul[PF_XEN_NON_EMUL_EIP_USER],
|
|
h->arc_cycles);
|
|
if(is_kernel(h->v->guest_paging_levels, e->va))
|
|
update_cycles(&h->summary.pf_xen_non_emul[PF_XEN_NON_EMUL_VA_KERNEL],
|
|
h->arc_cycles);
|
|
|
|
else
|
|
update_cycles(&h->summary.pf_xen_non_emul[PF_XEN_NON_EMUL_VA_USER],
|
|
h->arc_cycles);
|
|
}
|
|
|
|
/* Set summary handler */
|
|
hvm_set_summary_handler(h, hvm_pf_xen_summary, NULL);
|
|
}
|
|
}
|
|
|
|
void hvm_pf_xen_process(struct record_info *ri, struct hvm_data *h) {
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
|
|
if(ri->event == TRC_HVM_PF_XEN64
|
|
&& h->v->guest_paging_levels != 4)
|
|
fprintf(warn, "Strange, PF_XEN64 but guest_paging_levels %d!\n",
|
|
h->v->guest_paging_levels);
|
|
else if(ri->event == TRC_HVM_PF_XEN
|
|
&& h->v->guest_paging_levels == 4)
|
|
fprintf(warn, "Strange, PF_XEN but guest_paging_levels %d!\n",
|
|
h->v->guest_paging_levels);
|
|
|
|
hvm_pf_xen_preprocess(ri->event, h);
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
if(e->pf_case == PF_XEN_EMULATE)
|
|
printf("]%s pf_xen:emulate va %llx ec %x level %d corr %llx e->pt_index[%d %d %d %d %d]\n",
|
|
ri->dump_header, e->va, e->error_code,
|
|
e->pt_level, e->corresponding_va,
|
|
e->pt_index[0], e->pt_index[1], e->pt_index[2],
|
|
e->pt_index[3],
|
|
e->pt_index[4]);
|
|
else
|
|
printf("]%s pf_xen va %llx ec %x e->pt_index[%d %d %d %d %d]\n",
|
|
ri->dump_header, e->va, e->error_code,
|
|
e->pt_index[0], e->pt_index[1], e->pt_index[2],
|
|
e->pt_index[3],
|
|
e->pt_index[4]);
|
|
}
|
|
|
|
if ( hvm_set_postprocess(h, hvm_pf_xen_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
const char * hvm_vlapic_icr_dest_shorthand_name[4] = {
|
|
"dest_field", "self", "all-inc", "all-exc"
|
|
};
|
|
|
|
void hvm_vlapic_vmentry_cleanup(struct vcpu_data *v, tsc_t tsc)
|
|
{
|
|
int i;
|
|
|
|
struct vlapic_struct *vla = &v->vlapic;
|
|
|
|
for(i=0; i<MAX_VLAPIC_LIST; i++)
|
|
{
|
|
unsigned long long lat=0;
|
|
struct outstanding_ipi *o = vla->outstanding.list + i;
|
|
|
|
if(!(o->valid && o->injected))
|
|
continue;
|
|
|
|
if(tsc >= o->first_tsc)
|
|
lat = tsc - o->first_tsc;
|
|
else
|
|
fprintf(warn, "Strange, vec %d first_tsc %lld > ri->tsc %lld!\n",
|
|
o->vec, o->first_tsc, tsc);
|
|
|
|
if(opt.dump_ipi_latency
|
|
|| (opt.dump_all && o->count > 1)) {
|
|
struct time_struct t;
|
|
cycles_to_time(lat, &t);
|
|
printf(" [vla] d%dv%d vec %d ipis %d, latency %lld (%u.%09u s)\n",
|
|
v->d->did, v->vid, o->vec, o->count, lat,
|
|
t.s, t.ns);
|
|
}
|
|
|
|
#if 0
|
|
/* FIXME: make general somehow */
|
|
if(opt.summary_info)
|
|
{
|
|
update_cycles(&h->summary.ipi_latency, lat);
|
|
h->summary.ipi_count[vla->outstanding_ipis]++;
|
|
}
|
|
#endif
|
|
|
|
o->vec = o->count = o->injected = o->valid = o->first_tsc = 0;
|
|
}
|
|
}
|
|
|
|
void hvm_vlapic_clear(struct vlapic_struct *vla)
|
|
{
|
|
bzero(vla, sizeof(*vla));
|
|
}
|
|
|
|
struct outstanding_ipi *find_vec(struct vlapic_struct *vla, int vec)
|
|
{
|
|
struct outstanding_ipi *o = NULL;
|
|
int i;
|
|
|
|
/* Find the entry for this vector, or the first empty one. */
|
|
for(i=0; i<MAX_VLAPIC_LIST; i++)
|
|
{
|
|
if(vla->outstanding.list[i].valid && vla->outstanding.list[i].vec == vec)
|
|
{
|
|
o = vla->outstanding.list + i;
|
|
break;
|
|
} else if(!vla->outstanding.list[i].valid && !o)
|
|
o = vla->outstanding.list + i;
|
|
}
|
|
|
|
if(o && !o->valid) {
|
|
o->vec = vec;
|
|
o->valid = 1;
|
|
}
|
|
|
|
return o;
|
|
}
|
|
|
|
void ipi_send(struct vcpu_data *ov, int vec)
|
|
{
|
|
struct vlapic_struct *vla;
|
|
struct outstanding_ipi *o = NULL;
|
|
|
|
if(ov->runstate.state == RUNSTATE_LOST) {
|
|
if(opt.dump_all)
|
|
fprintf(warn, "%s: v%d in state RUNSTATE_LOST, not counting ipi\n",
|
|
__func__, ov->vid);
|
|
return;
|
|
}
|
|
|
|
vla = &ov->vlapic;
|
|
|
|
o = find_vec(vla, vec);
|
|
|
|
if(!o)
|
|
{
|
|
fprintf(warn, "%s: Couldn't find an open slot!\n",
|
|
__func__);
|
|
return;
|
|
}
|
|
|
|
if(!o->first_tsc)
|
|
o->first_tsc = P.now;
|
|
|
|
if(opt.dump_all && o->count == 0 && o->injected)
|
|
printf(" [vla] Pre-injection\n");
|
|
|
|
o->count++;
|
|
|
|
if((opt.dump_all)
|
|
#if 0
|
|
&& (ov->runstate.state != RUNSTATE_RUNNING
|
|
|| ov->hvm.vmexit_valid)
|
|
#endif
|
|
)
|
|
printf(" [vla] d%dv%d vec %d state %s (outstanding ipis %d)\n",
|
|
ov->d->did, ov->vid,
|
|
o->vec,
|
|
runstate_name[ov->runstate.state],
|
|
o->count);
|
|
}
|
|
|
|
void hvm_vlapic_icr_handler(struct hvm_data *h)
|
|
{
|
|
struct mmio_info *m = &h->inflight.mmio;
|
|
union {
|
|
unsigned int val;
|
|
struct {
|
|
unsigned vec:8,
|
|
delivery_mode:3,
|
|
dest_mode:1,
|
|
delivery_status:1,
|
|
_res1:1,
|
|
level:1,
|
|
trigger:1,
|
|
_res2:2,
|
|
dest_shorthand:2;
|
|
};
|
|
} icr = { .val = m->data };
|
|
|
|
if(m->is_write) {
|
|
if(opt.dump_all) {
|
|
printf(" [vla] d%dv%d icr vec %d %s\n",
|
|
h->v->d->did, h->v->vid,
|
|
icr.vec,
|
|
hvm_vlapic_icr_dest_shorthand_name[icr.dest_shorthand]);
|
|
}
|
|
|
|
if(icr.dest_shorthand == 3)
|
|
{
|
|
struct vcpu_data *ov, *v = h->v;
|
|
struct domain_data *d = v->d;
|
|
int i;
|
|
|
|
for(i=0; i<MAX_CPUS; i++)
|
|
{
|
|
ov = d->vcpu[i];
|
|
if(!ov || ov == v)
|
|
continue;
|
|
|
|
ipi_send(ov, icr.vec);
|
|
|
|
}
|
|
} else if(icr.dest_shorthand != 1) {
|
|
#if 0
|
|
fprintf(warn, "Strange, vlapic icr %s vec %d!\n",
|
|
hvm_vlapic_icr_dest_shorthand_name[icr.dest_shorthand],
|
|
icr.vec);
|
|
#endif
|
|
}
|
|
} else {
|
|
/* Read */
|
|
if(opt.dump_all) {
|
|
printf(" [vla] d%dv%d icr status %s\n",
|
|
h->v->d->did, h->v->vid,
|
|
icr.delivery_status?"pending":"idle");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void hvm_vlapic_inject(struct vcpu_data *v, int vec)
|
|
{
|
|
struct vlapic_struct *vla = &v->vlapic;
|
|
struct outstanding_ipi *o = NULL;
|
|
|
|
o = find_vec(vla, vec);
|
|
|
|
if(o) {
|
|
if(opt.dump_all)
|
|
printf(" [vla] d%dv%d vec %d injecting\n",
|
|
v->d->did, v->vid, vec);
|
|
o->injected=1;
|
|
} else {
|
|
fprintf(stderr, "%s: Couldn't find an open ipi slot!\n",
|
|
__func__);
|
|
}
|
|
}
|
|
|
|
void hvm_vlapic_eoi_handler(struct hvm_data *h) {
|
|
if(opt.dump_all)
|
|
printf(" [vla] d%dv%d eoi\n",
|
|
h->v->d->did, h->v->vid);
|
|
}
|
|
|
|
void hvm_vlapic_handler(struct hvm_data *h)
|
|
{
|
|
struct mmio_info *m = &h->inflight.mmio;
|
|
|
|
switch(m->gpa) {
|
|
case 0xfee00300:
|
|
hvm_vlapic_icr_handler(h);
|
|
break;
|
|
case 0xfee000b0:
|
|
hvm_vlapic_eoi_handler(h);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/* Also called by shadow_mmio_postprocess */
|
|
#define MMIO_VGA_START (0xa0000)
|
|
#define MMIO_VGA_END (0xbffff)
|
|
void enumerate_mmio(struct hvm_data *h)
|
|
{
|
|
struct mmio_info *m = &h->inflight.mmio;
|
|
|
|
/* Skip vga area */
|
|
if ( opt.mmio_enumeration_skip_vga
|
|
&& m->gpa >= MMIO_VGA_START
|
|
&& m->gpa < MMIO_VGA_END)
|
|
{
|
|
warn_once("WARNING: Not enumerationg MMIO in VGA range. Use --mmio-enumeration-skip-vga=0 to override.\n");
|
|
return;
|
|
}
|
|
|
|
if ( m->data_valid )
|
|
update_io_address(&h->summary.io.mmio, m->gpa, m->is_write, h->arc_cycles, m->va);
|
|
}
|
|
|
|
void hvm_mmio_summary(struct hvm_data *h, void *data)
|
|
{
|
|
long reason=(long)data;
|
|
|
|
PRINT_SUMMARY(h->summary.mmio[reason],
|
|
" mmio ");
|
|
}
|
|
|
|
void hvm_mmio_assist_postprocess(struct hvm_data *h)
|
|
{
|
|
long reason;
|
|
|
|
switch(h->exit_reason)
|
|
{
|
|
case VMEXIT_NPF:
|
|
case EXIT_REASON_EPT_VIOLATION:
|
|
reason=NONPF_MMIO_NPF;
|
|
hvm_set_summary_handler(h, hvm_mmio_summary, (void *)reason);
|
|
break;
|
|
case EXIT_REASON_APIC_ACCESS:
|
|
reason=NONPF_MMIO_APIC;
|
|
hvm_set_summary_handler(h, hvm_mmio_summary, (void *)reason);
|
|
break;
|
|
default:
|
|
{
|
|
static int warned = 0;
|
|
if (!warned)
|
|
{
|
|
fprintf(warn, "%s: Strange, MMIO with unexpected exit reason %d\n",
|
|
__func__, h->exit_reason);
|
|
warned=1;
|
|
}
|
|
reason=NONPF_MMIO_UNKNOWN;
|
|
hvm_set_summary_handler(h, hvm_mmio_summary, (void *)reason);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(opt.summary_info)
|
|
{
|
|
update_cycles(&h->summary.mmio[reason],
|
|
h->arc_cycles);
|
|
}
|
|
|
|
if ( opt.with_mmio_enumeration )
|
|
enumerate_mmio(h);
|
|
}
|
|
|
|
#define HVM_IO_ASSIST_WRITE 0x200
|
|
void hvm_mmio_assist_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct mmio_info *m = &h->inflight.mmio;
|
|
union {
|
|
struct {
|
|
unsigned int gpa;
|
|
unsigned int data;
|
|
} x32;
|
|
struct {
|
|
unsigned long long gpa;
|
|
unsigned int data;
|
|
} x64;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
union {
|
|
unsigned event;
|
|
struct {
|
|
unsigned minor:8,
|
|
x64:1,
|
|
write:2;
|
|
};
|
|
} mevt = { .event = ri->event };
|
|
|
|
if(mevt.x64) {
|
|
m->gpa = r->x64.gpa;
|
|
m->data = r->x64.data;
|
|
if(ri->extra_words*(sizeof(unsigned int))==sizeof(r->x64))
|
|
m->data_valid=1;
|
|
} else {
|
|
m->gpa = r->x32.gpa;
|
|
m->data = r->x32.data;
|
|
if(ri->extra_words*(sizeof(unsigned int))==sizeof(r->x32))
|
|
m->data_valid=1;
|
|
}
|
|
|
|
m->is_write = mevt.write;
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
if(m->data_valid)
|
|
printf("]%s mmio_assist %c gpa %llx data %x\n",
|
|
ri->dump_header,
|
|
mevt.write?'w':'r',
|
|
m->gpa, m->data);
|
|
else
|
|
printf("]%s mmio_assist %c gpa %llx (no data)\n", ri->dump_header,
|
|
mevt.write?'w':'r', m->gpa);
|
|
}
|
|
|
|
if((m->gpa & 0xfffff000) == 0xfee00000)
|
|
hvm_vlapic_handler(h);
|
|
|
|
/* Catch MMIOs that don't go through the shadow code; tolerate
|
|
* failures to set (probably shadow_mmio) */
|
|
hvm_set_postprocess(h, hvm_mmio_assist_postprocess);
|
|
}
|
|
|
|
void hvm_inj_virq_process(struct record_info *ri, struct hvm_data *h) {
|
|
struct {
|
|
int vector, fake;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
if(opt.dump_all) {
|
|
printf(" %s inj_virq vec %u %s\n",
|
|
ri->dump_header,
|
|
r->vector, r->fake?"fake":"real");
|
|
}
|
|
|
|
if(opt.summary_info)
|
|
{
|
|
int vector = r->vector;
|
|
|
|
if(vector >= GUEST_INTERRUPT_MAX)
|
|
vector = GUEST_INTERRUPT_MAX;
|
|
h->summary.guest_interrupt[vector].count++;
|
|
|
|
if(opt.interval.output == INTERVAL_DOMAIN_GUEST_INTERRUPT)
|
|
interval_domain_guest_interrupt(h, vector);
|
|
}
|
|
|
|
/* If we're waking, make this the wake vector */
|
|
if(r->vector < GUEST_INTERRUPT_MAX ) {
|
|
int vector = r->vector;
|
|
if ( h->w2h.waking && h->w2h.vector == 0 ) {
|
|
if(h->summary.guest_interrupt[vector].start_tsc) {
|
|
fprintf(warn, "Strange, d%dv%d waking && wake_vector 0 but vec %d start_tsc %lld!\n",
|
|
h->v->d->did, h->v->vid,
|
|
vector,
|
|
h->summary.guest_interrupt[vector].start_tsc);
|
|
error(ERR_WARN, NULL);
|
|
}
|
|
if(h->w2h.interrupts)
|
|
fprintf(warn, "Strange, waking && wake_vector 0 but interrupts_this_wait_to_halt %d!\n",
|
|
h->w2h.interrupts);
|
|
|
|
if(opt.dump_all)
|
|
printf(" [w2h] d%dv%d Setting wake_vector %d\n",
|
|
h->v->d->did, h->v->vid, vector);
|
|
|
|
/* In svm mode, vector information is invalid */
|
|
if ( opt.svm_mode && r->fake )
|
|
h->w2h.vector = FAKE_VECTOR;
|
|
else
|
|
h->w2h.vector = vector;
|
|
h->summary.guest_interrupt[vector].is_wake = 1;
|
|
}
|
|
|
|
if( h->summary.guest_interrupt[vector].start_tsc == 0 ) {
|
|
/* Note that we want start_tsc set at the next vmentry */
|
|
h->summary.guest_interrupt[vector].start_tsc = 1;
|
|
h->w2h.interrupts_wanting_tsc++;
|
|
h->w2h.interrupts++;
|
|
|
|
if(opt.dump_all)
|
|
printf(" [w2h] d%dv%d Starting vec %d\n",
|
|
h->v->d->did, h->v->vid, vector);
|
|
}
|
|
}
|
|
|
|
hvm_vlapic_inject(h->v, r->vector);
|
|
}
|
|
|
|
/* I/O Handling */
|
|
struct io_address {
|
|
struct io_address *next;
|
|
unsigned int pa;
|
|
unsigned int va;
|
|
struct cycle_summary summary[2];
|
|
};
|
|
|
|
void update_io_address(struct io_address ** list, unsigned int pa, int dir,
|
|
tsc_t arc_cycles, unsigned int va) {
|
|
struct io_address *p, *q=NULL;
|
|
|
|
/* Keep list in order */
|
|
for(p=*list; p && (p->pa != pa) && (p->pa < pa); q=p, p=p->next);
|
|
|
|
/* If we didn't find it, make a new element. */
|
|
if(!p || (p->pa != pa)) {
|
|
if((p=malloc(sizeof(*p)))==NULL) {
|
|
fprintf(stderr, "malloc() failed.\n");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(p, sizeof(*p));
|
|
|
|
p->pa=pa;
|
|
p->va=va;
|
|
|
|
/* If we stopped in the middle or at the end, add it in */
|
|
if(q) {
|
|
p->next=q->next;
|
|
q->next=p;
|
|
} else {
|
|
/* Otherwise, we stopped after the first element; put it at the beginning */
|
|
p->next = *list;
|
|
*list = p;
|
|
}
|
|
}
|
|
update_cycles(&p->summary[dir], arc_cycles);
|
|
}
|
|
|
|
void hvm_io_address_summary(struct io_address *list, const char * s) {
|
|
if(!list)
|
|
return;
|
|
|
|
printf("%s\n", s);
|
|
|
|
for(; list; list=list->next) {
|
|
if ( list->va )
|
|
{
|
|
PRINT_SUMMARY(list->summary[0], "%8x@%8x:[r] ", list->pa, list->va);
|
|
PRINT_SUMMARY(list->summary[1], "%8x@%8x:[w] ", list->pa, list->va);
|
|
}
|
|
else
|
|
{
|
|
PRINT_SUMMARY(list->summary[0], "%8x:[r] ", list->pa);
|
|
PRINT_SUMMARY(list->summary[1], "%8x:[w] ", list->pa);
|
|
}
|
|
}
|
|
}
|
|
|
|
void hvm_io_write_postprocess(struct hvm_data *h)
|
|
{
|
|
if(opt.with_pio_enumeration)
|
|
update_io_address(&h->summary.io.pio, h->inflight.io.port, 1, h->arc_cycles, 0);
|
|
}
|
|
|
|
void hvm_io_read_postprocess(struct hvm_data *h)
|
|
{
|
|
if(opt.with_pio_enumeration)
|
|
update_io_address(&h->summary.io.pio, h->inflight.io.port, 0, h->arc_cycles, 0);
|
|
if(opt.scatterplot_io && h->inflight.io.port == opt.scatterplot_io_port)
|
|
scatterplot_vs_time(h->exit_tsc, P.now - h->exit_tsc);
|
|
}
|
|
|
|
void hvm_io_assist_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
union {
|
|
struct {
|
|
unsigned int port;
|
|
unsigned int data;
|
|
} x32;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
union {
|
|
unsigned event;
|
|
struct {
|
|
unsigned minor:8,
|
|
x64:1,
|
|
write:2;
|
|
};
|
|
} mevt = { .event = ri->event };
|
|
|
|
if(mevt.x64) {
|
|
fprintf(stderr, "FATAL: Unexpected 64-bit PIO\n");
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
|
|
h->inflight.io.port = r->x32.port;
|
|
h->inflight.io.val = r->x32.data;
|
|
|
|
if(mevt.write) {
|
|
h->inflight.io.is_write = 1;
|
|
if ( hvm_set_postprocess(h, hvm_io_write_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
} else {
|
|
h->inflight.io.is_write = 0;
|
|
if ( hvm_set_postprocess(h, hvm_io_read_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
printf(" %s io %s port %x val %x\n",
|
|
ri->dump_header,
|
|
mevt.write?"write":"read",
|
|
r->x32.port,
|
|
r->x32.data);
|
|
}
|
|
}
|
|
|
|
/* cr_write */
|
|
/* CR3 list */
|
|
void cr3_switch(unsigned long long val, struct hvm_data *h) {
|
|
struct vcpu_data *v = h->v;
|
|
/* Really only need absolute tsc here. Later change to global time. */
|
|
unsigned long long now = P.now;
|
|
unsigned long long gmfn = val >> 12;
|
|
|
|
if ( !h->init )
|
|
return;
|
|
|
|
if(opt.with_cr3_enumeration) {
|
|
if(v->cr3.data) {
|
|
struct cr3_value_struct *cur = v->cr3.data;
|
|
unsigned long long cycles = now - v->cr3.start_time;
|
|
|
|
if(opt.summary_info)
|
|
update_cycles(&cur->total_time, cycles);
|
|
|
|
cur->last_time = now;
|
|
}
|
|
|
|
if(gmfn) {
|
|
struct cr3_value_struct *p, **last=&v->d->cr3_value_head;
|
|
|
|
/* Always add to tail, so that we get consistent interval
|
|
ouptut as the number of cr3s grow */
|
|
for(p=*last; p; last = (&p->next), p=p->next)
|
|
if(p->gmfn == gmfn)
|
|
break;
|
|
|
|
if(!p) {
|
|
if((p=malloc(sizeof(*p)))==NULL) {
|
|
fprintf(stderr, "malloc() failed.\n");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(p, sizeof(*p));
|
|
|
|
p->gmfn = gmfn;
|
|
p->cr3_id = P.cr3.id;
|
|
p->first_time = now;
|
|
|
|
p->next=*last;
|
|
*last=p;
|
|
|
|
p->gnext = NULL;
|
|
if(P.cr3.head)
|
|
*P.cr3.tail = p;
|
|
else
|
|
P.cr3.head = p;
|
|
P.cr3.tail = &p->gnext;
|
|
|
|
P.cr3.id++;
|
|
|
|
/* Add to the interval list if appropriate */
|
|
if(opt.interval.check == INTERVAL_CHECK_CR3
|
|
&& v->d->did != DEFAULT_DOMAIN)
|
|
interval_cr3_value_check(p);
|
|
}
|
|
|
|
if(p->prealloc_unpin.now) {
|
|
fprintf(warn, "Re-promoting previously unpinned cr3 %llx!\n",
|
|
p->gmfn);
|
|
p->prealloc_unpin.now = 0;
|
|
h->inflight.cr_write.repromote = 1;
|
|
}
|
|
|
|
/* Accounting for new toplevel */
|
|
v->cr3.start_time = now;
|
|
p->switch_count++;
|
|
if(p->destroy.callback)
|
|
p->destroy.switch_count++;
|
|
v->cr3.data = p;
|
|
} else {
|
|
v->cr3.data = NULL;
|
|
}
|
|
|
|
if (opt.scatterplot_cr3_switch) {
|
|
scatterplot_vs_time(h->exit_tsc,
|
|
v->cr3.data ? (v->cr3.data->cr3_id) : 0);
|
|
}
|
|
} else {
|
|
if (opt.scatterplot_cr3_switch)
|
|
scatterplot_vs_time(h->exit_tsc, gmfn);
|
|
}
|
|
|
|
v->cr3.val = val;
|
|
};
|
|
|
|
void cr3_prealloc_unpin(struct vcpu_data *v, unsigned long long gmfn) {
|
|
struct cr3_value_struct *cr3;
|
|
|
|
/* Look for it in the list */
|
|
for(cr3 = v->d->cr3_value_head; cr3; cr3=cr3->next)
|
|
if(cr3->gmfn == gmfn)
|
|
break;
|
|
|
|
if(!cr3)
|
|
return;
|
|
|
|
if(cr3->prealloc_unpin.now)
|
|
fprintf(warn, "Strange, gmfn %llx multiple unpins w/o access!\n",
|
|
gmfn);
|
|
|
|
cr3->prealloc_unpin.now = 1;
|
|
cr3->prealloc_unpin.count++;
|
|
|
|
if(opt.dump_all)
|
|
printf(" cr3 %llx unpinned %d times\n",
|
|
gmfn, cr3->prealloc_unpin.count);
|
|
}
|
|
|
|
int cr3_compare_start(const void *_a, const void *_b) {
|
|
struct cr3_value_struct *a=*(typeof(&a))_a;
|
|
struct cr3_value_struct *b=*(typeof(&a))_b;
|
|
|
|
if(a->first_time > b->first_time)
|
|
return 1;
|
|
else if(b->first_time == a->first_time)
|
|
return 0;
|
|
else
|
|
return -1;
|
|
}
|
|
|
|
void cr3_dump_list(struct cr3_value_struct *head){
|
|
struct cr3_value_struct *p;
|
|
struct cr3_value_struct **qsort_array;
|
|
int i, N=0;
|
|
|
|
if(!head)
|
|
return;
|
|
|
|
/* Count the number of elements */
|
|
for(p=head; p; p=p->next)
|
|
N++;
|
|
|
|
/* Alloc a struct of the right size */
|
|
qsort_array = malloc(N * sizeof(struct eip_list_struct *));
|
|
|
|
/* Point the array into it */
|
|
for(i=0, p=head; p; p=p->next, i++)
|
|
qsort_array[i]=p;
|
|
|
|
/* Sort the array by time */
|
|
qsort(qsort_array, N, sizeof(struct eip_list_struct *),
|
|
cr3_compare_start);
|
|
|
|
/* WARNING: don't use N after this point unless you copy this variable */
|
|
#if 0
|
|
if(opt.summary_eip_limit && opt.summary_eip_limit < N)
|
|
N=opt.summary_eip_limit;
|
|
#endif
|
|
|
|
/* Now print the results */
|
|
printf(" cr3 values:\n");
|
|
for(i=0; i<N; i++) {
|
|
char desc[30];
|
|
struct time_struct first, last;
|
|
|
|
p = qsort_array[i];
|
|
|
|
abs_cycles_to_time(p->first_time, &first);
|
|
abs_cycles_to_time(p->last_time, &last);
|
|
|
|
|
|
snprintf(desc, 30, " %8llx (id %d)", p->gmfn, p->cr3_id);
|
|
print_cycle_summary(&p->total_time, desc);
|
|
snprintf(desc, 30, " guest");
|
|
print_cycle_percent_summary(&p->guest_time, p->run_time, desc);
|
|
snprintf(desc, 30, " hv ");
|
|
print_cycle_percent_summary(&p->hv_time, p->run_time, desc);
|
|
|
|
hvm_short_summary(&p->hvm, p->run_time, " + ");
|
|
printf(" Seen: %4u.%09u-%4u.%09u switch %d flush %d\n",
|
|
first.s, first.ns,
|
|
last.s, last.ns,
|
|
p->switch_count, p->flush_count);
|
|
if(p->destroy.callback)
|
|
printf(" destroy: flush %u switch %u fixup %u emulate %u\n",
|
|
p->destroy.flush_count,
|
|
p->destroy.switch_count,
|
|
p->destroy.fixup_user,
|
|
p->destroy.emulate_corr_user);
|
|
}
|
|
|
|
free(qsort_array);
|
|
}
|
|
|
|
void hvm_cr3_write_summary(struct hvm_data *h) {
|
|
int j;
|
|
|
|
for(j=0; j<RESYNCS_MAX; j++)
|
|
PRINT_SUMMARY(h->summary.cr3_write_resyncs[j],
|
|
" *[%3d] ", j);
|
|
PRINT_SUMMARY(h->summary.cr3_write_resyncs[j],
|
|
" *[MAX] ");
|
|
}
|
|
|
|
void hvm_cr_write_summary(struct hvm_data *h, void *data)
|
|
{
|
|
long cr=(long)data;
|
|
|
|
PRINT_SUMMARY(h->summary.cr_write[cr],
|
|
" cr%ld ", cr);
|
|
if ( cr==3 )
|
|
hvm_cr3_write_summary(h);
|
|
}
|
|
|
|
void hvm_cr_write_postprocess(struct hvm_data *h)
|
|
{
|
|
if(h->inflight.cr_write.cr == 3) {
|
|
struct vcpu_data *v = h->v;
|
|
unsigned long long new_val = h->inflight.cr_write.val;
|
|
unsigned long long oval;
|
|
int flush=0;
|
|
|
|
if(v->cr3.val) {
|
|
oval = v->cr3.val;
|
|
|
|
if(new_val == oval) {
|
|
if(v->cr3.data) {
|
|
v->cr3.data->flush_count++;
|
|
if(v->cr3.data->destroy.callback)
|
|
v->cr3.data->destroy.flush_count++;
|
|
}
|
|
flush=1;
|
|
}
|
|
}
|
|
|
|
if(opt.summary_info) {
|
|
int resyncs = h->resyncs;
|
|
|
|
if(resyncs > RESYNCS_MAX)
|
|
resyncs = RESYNCS_MAX;
|
|
|
|
update_cycles(&h->summary.cr3_write_resyncs[resyncs],
|
|
h->arc_cycles);
|
|
|
|
update_cycles(&h->summary.cr_write[3],
|
|
h->arc_cycles);
|
|
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_CR3);
|
|
}
|
|
|
|
if(!flush)
|
|
cr3_switch(new_val, h);
|
|
} else {
|
|
if(opt.summary_info)
|
|
{
|
|
if(h->inflight.cr_write.cr < CR_MAX)
|
|
update_cycles(&h->summary.cr_write[h->inflight.cr_write.cr],
|
|
h->arc_cycles);
|
|
|
|
}
|
|
}
|
|
|
|
/* Set summary handler */
|
|
/* FIXME - deal with cr_read_summary */
|
|
if(h->exit_reason < h->exit_reason_max)
|
|
{
|
|
/* Want a different "set" for each cr */
|
|
switch(h->inflight.cr_write.cr)
|
|
{
|
|
#define case_cr(_x) \
|
|
case (_x): \
|
|
hvm_set_summary_handler(h, hvm_cr_write_summary, (void *)(_x)); \
|
|
break
|
|
case_cr(0);
|
|
case_cr(1);
|
|
case_cr(2);
|
|
case_cr(3);
|
|
case_cr(4);
|
|
case_cr(5);
|
|
case_cr(6);
|
|
case_cr(7);
|
|
case_cr(8);
|
|
case_cr(9);
|
|
case_cr(10);
|
|
case_cr(11);
|
|
case_cr(12);
|
|
case_cr(13);
|
|
case_cr(14);
|
|
case_cr(15);
|
|
#undef case_cr
|
|
default:
|
|
fprintf(stderr, "Unexpected cr: %d\n", h->inflight.cr_write.cr);
|
|
error(ERR_SANITY, NULL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void hvm_cr_write_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
union {
|
|
struct {
|
|
unsigned cr;
|
|
unsigned int val;
|
|
} x32;
|
|
struct {
|
|
unsigned cr;
|
|
unsigned long long val;
|
|
} __attribute__((packed)) x64;
|
|
} *r = (typeof(r))h->d;
|
|
unsigned cr;
|
|
unsigned long long val;
|
|
|
|
if(ri->event & TRC_64_FLAG) {
|
|
h->inflight.cr_write.cr = cr = r->x64.cr;
|
|
h->inflight.cr_write.val = val = r->x64.val;
|
|
} else {
|
|
h->inflight.cr_write.cr = cr = r->x32.cr;
|
|
h->inflight.cr_write.val = val = r->x32.val;
|
|
}
|
|
|
|
/* In vmx, in real mode, cr accesses may cause EXNMI vmexits.
|
|
* Account them under that heading; otherwise, complain */
|
|
if ( hvm_set_postprocess(h, hvm_cr_write_postprocess) )
|
|
fprintf(warn, "%s: Strange, h->postprocess already set!\n",
|
|
__func__);
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
if(cr == 3 && h->v->cr3.val) {
|
|
printf("]%s cr_write cr3 val %llx oval %llx %s\n",
|
|
ri->dump_header,
|
|
val,
|
|
h->v->cr3.val,
|
|
(h->v->cr3.val == val)?"flush":"switch");
|
|
} else {
|
|
printf(" %s cr_write cr%d val %llx\n",
|
|
ri->dump_header,
|
|
cr, val);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* msr_write */
|
|
void hvm_msr_write_summary(struct hvm_data *h, void *d)
|
|
{
|
|
}
|
|
|
|
void hvm_msr_write_postprocess(struct hvm_data *h)
|
|
{
|
|
if(opt.summary_info) {
|
|
}
|
|
|
|
/* Set summary handler */
|
|
hvm_set_summary_handler(h, hvm_msr_write_summary, NULL);
|
|
}
|
|
|
|
void hvm_msr_write_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
unsigned int addr;
|
|
unsigned long long val;
|
|
} __attribute__((packed)) *r = (typeof(r))h->d;
|
|
|
|
if(check_extra_words(ri, sizeof(*r), "msr_write"))
|
|
return;
|
|
|
|
h->inflight.msr.addr = r->addr;
|
|
h->inflight.msr.val = r->val;
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
printf(" %s msr_write addr %x val %llx\n",
|
|
ri->dump_header,
|
|
r->addr, r->val);
|
|
}
|
|
|
|
if ( hvm_set_postprocess(h, hvm_msr_write_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
/* msr_read */
|
|
void hvm_msr_read_summary(struct hvm_data *h, void *d)
|
|
{
|
|
}
|
|
|
|
void hvm_msr_read_postprocess(struct hvm_data *h)
|
|
{
|
|
if(opt.summary_info) {
|
|
}
|
|
|
|
/* Set summary handler */
|
|
hvm_set_summary_handler(h, hvm_msr_read_summary, NULL);
|
|
}
|
|
|
|
void hvm_msr_read_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
unsigned int addr;
|
|
unsigned long long val;
|
|
} __attribute__((packed)) *r = (typeof(r))h->d;
|
|
|
|
if(check_extra_words(ri, sizeof(*r), "msr_read"))
|
|
return;
|
|
|
|
h->inflight.msr.addr = r->addr;
|
|
h->inflight.msr.val = r->val;
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
printf(" %s msr_read addr %x val %llx\n",
|
|
ri->dump_header,
|
|
r->addr, r->val);
|
|
}
|
|
|
|
if ( hvm_set_postprocess(h, hvm_msr_read_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void hvm_vmcall_summary(struct hvm_data *h, void *d)
|
|
{
|
|
int i;
|
|
|
|
for ( i=0; i<HYPERCALL_MAX ; i++)
|
|
{
|
|
PRINT_SUMMARY(h->summary.vmcall[i],
|
|
" [%10s] ", hypercall_name[i]);
|
|
}
|
|
PRINT_SUMMARY(h->summary.vmcall[HYPERCALL_MAX],
|
|
" [%10s] ", "max");
|
|
}
|
|
|
|
void hvm_vmcall_postprocess(struct hvm_data *h)
|
|
{
|
|
unsigned eax = h->inflight.vmcall.eax ;
|
|
|
|
if(opt.summary)
|
|
{
|
|
if ( eax < HYPERCALL_MAX )
|
|
update_cycles(&h->summary.vmcall[eax],
|
|
h->arc_cycles);
|
|
else
|
|
update_cycles(&h->summary.vmcall[HYPERCALL_MAX],
|
|
h->arc_cycles);
|
|
hvm_set_summary_handler(h, hvm_vmcall_summary, NULL);
|
|
}
|
|
}
|
|
|
|
void hvm_vmcall_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
unsigned int eax;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
if(opt.dump_all) {
|
|
if(r->eax < HYPERCALL_MAX)
|
|
printf(" %s vmcall %2x (%s)\n",
|
|
ri->dump_header,
|
|
r->eax,
|
|
hypercall_name[r->eax]);
|
|
else
|
|
printf(" %s vmcall %2x\n",
|
|
ri->dump_header,
|
|
r->eax);
|
|
}
|
|
|
|
h->inflight.vmcall.eax = r->eax;
|
|
|
|
if ( hvm_set_postprocess(h, hvm_vmcall_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void hvm_inj_exc_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
unsigned vec, ec;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
if(r->vec < HVM_TRAP_MAX)
|
|
printf(" %3u.%09u %s inj_exc trap %s ec %x\n",
|
|
ri->t.s, ri->t.ns, pcpu_string(ri->cpu),
|
|
hvm_trap_name[r->vec], r->ec);
|
|
else
|
|
printf(" %3u.%09u %s inj_exc trap %u ec %x\n",
|
|
ri->t.s, ri->t.ns, pcpu_string(ri->cpu),
|
|
r->vec, r->ec);
|
|
}
|
|
|
|
}
|
|
|
|
void hvm_intr_summary(struct hvm_data *h, void *d)
|
|
{
|
|
int i;
|
|
|
|
for(i=0; i<EXTERNAL_INTERRUPT_MAX; i++)
|
|
if(h->summary.extint[i])
|
|
{
|
|
if(hvm_extint_vector_name[i])
|
|
printf(" %10s(%3d): %d\n",
|
|
hvm_extint_vector_name[i],
|
|
i,
|
|
h->summary.extint[i]);
|
|
else
|
|
printf(" [%3d]: %d\n",
|
|
i,
|
|
h->summary.extint[i]);
|
|
}
|
|
if(h->summary.extint[EXTERNAL_INTERRUPT_MAX])
|
|
printf(" Other: : %d\n",
|
|
h->summary.extint[EXTERNAL_INTERRUPT_MAX]);
|
|
}
|
|
|
|
|
|
void hvm_intr_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
unsigned vec = *(unsigned *)h->d;
|
|
|
|
/* Vector is difficult to get in SVM mode */
|
|
if ( opt.svm_mode )
|
|
vec = 0;
|
|
|
|
if( (h->rip >> ADDR_SPACE_BITS) != 00
|
|
&& (h->rip >> ADDR_SPACE_BITS) != ((0ULL-1)>> ADDR_SPACE_BITS) ) {
|
|
fprintf(stderr, "Unexpected rip %llx (shift %llx)\n",
|
|
h->rip,
|
|
h->rip >> ADDR_SPACE_BITS);
|
|
error(ERR_RECORD, NULL);
|
|
/* Can process with strange rip */
|
|
}
|
|
|
|
h->inflight.intr.vec = vec;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
if ( vec < EXTERNAL_INTERRUPT_MAX &&
|
|
hvm_extint_vector_name[vec] )
|
|
printf(" %s intr vec %s(%x)\n",
|
|
ri->dump_header,
|
|
hvm_extint_vector_name[vec],
|
|
vec);
|
|
else
|
|
printf(" %s intr vec %x\n",
|
|
ri->dump_header, vec);
|
|
}
|
|
|
|
if(opt.scatterplot_interrupt_eip
|
|
&& vec == opt.scatterplot_interrupt_vector)
|
|
{
|
|
struct time_struct t;
|
|
/* Truncate to 40 bits */
|
|
unsigned long long rip = h->rip & ((1ULL << ADDR_SPACE_BITS)-1);
|
|
|
|
/* Want absolute tsc to global tsc */
|
|
abs_cycles_to_time(h->exit_tsc, &t);
|
|
printf("d%dv%d %u.%09u %lld\n",
|
|
h->v->d->did, h->v->vid,
|
|
t.s, t.ns,
|
|
rip);
|
|
}
|
|
|
|
if(opt.histogram_interrupt_eip
|
|
&& vec == opt.histogram_interrupt_vector)
|
|
{
|
|
/* Truncate to 40 bits */
|
|
unsigned long long rip = h->rip & ((1ULL << ADDR_SPACE_BITS)-1);
|
|
unsigned index = rip / opt.histogram_interrupt_increment;
|
|
|
|
h->summary.extint_histogram[index]++;
|
|
}
|
|
|
|
if(opt.with_interrupt_eip_enumeration
|
|
&& vec == opt.interrupt_eip_enumeration_vector)
|
|
{
|
|
/* Truncate to 40 bits */
|
|
unsigned long long rip = h->rip & ((1ULL << ADDR_SPACE_BITS)-1);
|
|
|
|
/* Want absolute tsc to global tsc */
|
|
update_eip(&h->v->d->interrupt_eip_list, rip, 0, 0, NULL);
|
|
}
|
|
|
|
/* Disable generic postprocessing */
|
|
/* FIXME: Do the summary stuff in a post-processor */
|
|
h->post_process = NULL;
|
|
|
|
if(opt.summary_info) {
|
|
if(opt.summary)
|
|
hvm_set_summary_handler(h, hvm_intr_summary, NULL);
|
|
|
|
if(vec < EXTERNAL_INTERRUPT_MAX)
|
|
h->summary.extint[vec]++;
|
|
else
|
|
h->summary.extint[EXTERNAL_INTERRUPT_MAX]++;
|
|
}
|
|
}
|
|
|
|
|
|
void hvm_intr_window_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
uint32_t vector;
|
|
uint32_t source;
|
|
int32_t intr;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
const char *intsrc_name[] = {
|
|
"none",
|
|
"pic",
|
|
"lapic",
|
|
"nmi",
|
|
"mce",
|
|
"vector"
|
|
};
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s intr_window vec %u src %u(%s) ",
|
|
ri->dump_header,
|
|
(unsigned)r->vector,
|
|
(unsigned)r->source,
|
|
r->source < 6 ? intsrc_name[r->source]: "?");
|
|
|
|
if ( r->intr > 0 )
|
|
printf("intr %x\n",
|
|
(unsigned)r->intr);
|
|
else
|
|
printf("intr #\n");
|
|
}
|
|
}
|
|
|
|
void hvm_pf_inject_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
union {
|
|
struct {
|
|
unsigned ec;
|
|
unsigned int cr2;
|
|
} x32;
|
|
struct {
|
|
unsigned ec;
|
|
unsigned long long cr2;
|
|
} __attribute__((packed)) x64;
|
|
} *r = (typeof(r))h->d;
|
|
unsigned int ec;
|
|
unsigned long long cr2;
|
|
int is_64 = 0;
|
|
|
|
if(ri->event & TRC_64_FLAG) {
|
|
is_64 = 1;
|
|
cr2 = r->x64.cr2;
|
|
ec = r->x64.ec;
|
|
} else {
|
|
cr2 = r->x32.cr2;
|
|
ec = r->x32.ec;
|
|
}
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %3u.%09u %s pf_inject%s guest_cr2 %llx guest_ec %x\n",
|
|
ri->t.s, ri->t.ns, pcpu_string(ri->cpu),
|
|
is_64?"64":"",
|
|
cr2, ec);
|
|
}
|
|
}
|
|
|
|
void hvm_generic_postprocess_init(struct record_info *ri, struct hvm_data *h);
|
|
|
|
void hvm_npf_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
uint64_t gpa;
|
|
uint64_t mfn;
|
|
uint32_t qualification;
|
|
uint32_t p2mt;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
if ( opt.dump_all )
|
|
printf(" %s npf gpa %llx q %x mfn %llx t %d\n",
|
|
ri->dump_header,
|
|
(unsigned long long)r->gpa, r->qualification,
|
|
(unsigned long long)r->mfn, r->p2mt);
|
|
|
|
if ( opt.summary_info )
|
|
hvm_generic_postprocess_init(ri, h);
|
|
}
|
|
|
|
void hvm_rdtsc_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
unsigned long long tsc;
|
|
} *r = (typeof(r))h->d;
|
|
|
|
if ( opt.dump_all )
|
|
printf(" %s rdtsc %llx %lld %s\n",
|
|
ri->dump_header,
|
|
(unsigned long long)r->tsc,
|
|
(unsigned long long)r->tsc,
|
|
h->last_rdtsc > r->tsc ? "BACKWARDS" : "");
|
|
|
|
if ( opt.scatterplot_rdtsc )
|
|
{
|
|
struct time_struct t;
|
|
|
|
abs_cycles_to_time(ri->tsc, &t);
|
|
|
|
printf("%dv%d %u.%09u %llu\n",
|
|
h->v->d->did, h->v->vid,
|
|
t.s, t.ns,
|
|
r->tsc);
|
|
}
|
|
|
|
h->last_rdtsc = r->tsc;
|
|
}
|
|
|
|
void hvm_generic_summary(struct hvm_data *h, void *data)
|
|
{
|
|
long evt = (long)data;
|
|
|
|
assert(evt < HVM_EVENT_HANDLER_MAX);
|
|
|
|
PRINT_SUMMARY(h->summary.generic[evt],
|
|
" %s ", hvm_event_handler_name[evt]);
|
|
|
|
}
|
|
|
|
void hvm_generic_postprocess_init(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
if ( h->post_process != hvm_generic_postprocess )
|
|
fprintf(warn, "%s: Strange, h->postprocess set!\n",
|
|
__func__);
|
|
h->inflight.generic.event = ri->event;
|
|
bcopy(h->d, h->inflight.generic.d, sizeof(unsigned int) * 4);
|
|
}
|
|
|
|
void hvm_generic_postprocess(struct hvm_data *h)
|
|
{
|
|
long evt = 0;
|
|
static unsigned registered[HVM_EVENT_HANDLER_MAX] = { 0 };
|
|
|
|
if ( h->inflight.generic.event )
|
|
evt = (h->inflight.generic.event - TRC_HVM_HANDLER)
|
|
& ~(TRC_64_FLAG|HVM_IO_ASSIST_WRITE);
|
|
else {
|
|
static unsigned warned[HVM_EXIT_REASON_MAX] = { 0 };
|
|
/* Some exits we don't expect a handler; just return */
|
|
if(opt.svm_mode)
|
|
{
|
|
switch(h->exit_reason)
|
|
{
|
|
case VMEXIT_VINTR: /* Equivalent of PENDING_VIRT_INTR */
|
|
case VMEXIT_PAUSE:
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(h->exit_reason)
|
|
{
|
|
/* These just need us to go through the return path */
|
|
case EXIT_REASON_PENDING_VIRT_INTR:
|
|
case EXIT_REASON_TPR_BELOW_THRESHOLD:
|
|
case EXIT_REASON_PAUSE_INSTRUCTION:
|
|
/* Not much to log now; may need later */
|
|
case EXIT_REASON_WBINVD:
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ( !warned[h->exit_reason] )
|
|
{
|
|
/* If we aren't a known exception, warn and log results */
|
|
fprintf(warn, "%s: Strange, exit %x(%s) missing a handler\n",
|
|
__func__, h->exit_reason,
|
|
(h->exit_reason > h->exit_reason_max)
|
|
? "[clipped]"
|
|
: h->exit_reason_name[h->exit_reason]);
|
|
warned[h->exit_reason]=1;
|
|
|
|
/*
|
|
* NB that we don't return here; the result will be that `evt`
|
|
* will be "0", and there will be a "(no handler)" entry for the
|
|
* given VMEXIT.
|
|
*
|
|
* This does mean that if two different exits have no HVM
|
|
* handlers, that only the first one will accumulate data;
|
|
* if accumulating a separate "(no handler)" data for more
|
|
* than one exit reason is needed, we'll have to make Yet
|
|
* Another Array. But for now, since we try to avoid
|
|
* it happening, just tolerate it.
|
|
*/
|
|
}
|
|
}
|
|
|
|
if ( evt >= HVM_EVENT_HANDLER_MAX || evt < 0)
|
|
{
|
|
fprintf(warn, "%s: invalid hvm event %lx(%x)\n",
|
|
__func__, evt, h->inflight.generic.event);
|
|
error(ERR_RECORD, NULL);
|
|
return;
|
|
}
|
|
|
|
if(opt.summary_info) {
|
|
/* NB that h->exit_reason may be 0, so we offset by 1 */
|
|
if ( registered[evt] )
|
|
{
|
|
static unsigned warned[HVM_EXIT_REASON_MAX] = { 0 };
|
|
if ( registered[evt] != h->exit_reason+1 )
|
|
{
|
|
if ( !warned[h->exit_reason] )
|
|
{
|
|
fprintf(warn, "%s: HVM evt %lx in %x and %x!\n",
|
|
__func__, evt, registered[evt]-1, h->exit_reason);
|
|
warned[h->exit_reason]=1;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int ret;
|
|
if((ret=__hvm_set_summary_handler(h, hvm_generic_summary, (void *)evt)))
|
|
fprintf(stderr, "%s: hvm_set_summary_handler returned %d\n",
|
|
__func__, ret);
|
|
registered[evt]=h->exit_reason+1;
|
|
}
|
|
|
|
update_cycles(&h->summary.generic[evt],
|
|
h->arc_cycles);
|
|
|
|
/* HLT checked at hvm_vmexit_close() */
|
|
}
|
|
}
|
|
|
|
void hvm_generic_dump(struct record_info *ri, const char * prefix)
|
|
{
|
|
struct {
|
|
unsigned vcpu:16, domain:16;
|
|
unsigned d[4];
|
|
} *cr = (typeof(cr))ri->d;
|
|
|
|
const char *evt_string;
|
|
char evt_number[256];
|
|
int i, evt, is_64 = 0;
|
|
|
|
evt = ri->event - TRC_HVM_HANDLER;
|
|
|
|
if(evt & TRC_64_FLAG) {
|
|
evt &= ~(TRC_64_FLAG);
|
|
is_64=1;
|
|
}
|
|
|
|
if(evt < HVM_EVENT_HANDLER_MAX)
|
|
{
|
|
evt_string = hvm_event_handler_name[evt];
|
|
}
|
|
else
|
|
{
|
|
snprintf(evt_number, 256, "hvm_handler %d", evt);
|
|
evt_string = evt_number;
|
|
}
|
|
|
|
printf("%s%s %s%s [",
|
|
prefix,
|
|
ri->dump_header,
|
|
evt_string,
|
|
is_64?"64":"");
|
|
|
|
for(i=0; i<ri->extra_words; i++) {
|
|
printf(" %x", ri->d[i]);
|
|
}
|
|
|
|
printf(" ]\n");
|
|
}
|
|
|
|
void hvm_handler_process(struct record_info *ri, struct hvm_data *h) {
|
|
/* Wait for first vmexit to initialize */
|
|
if(!h->init)
|
|
{
|
|
if(opt.dump_all)
|
|
hvm_generic_dump(ri,"!");
|
|
return;
|
|
}
|
|
|
|
h->d = ri->d;
|
|
|
|
/* Handle things that don't need a vmexit */
|
|
switch(ri->event) {
|
|
default:
|
|
goto needs_vmexit;
|
|
/* Records about changing guest state */
|
|
case TRC_HVM_PF_INJECT:
|
|
case TRC_HVM_PF_INJECT64:
|
|
hvm_pf_inject_process(ri, h);
|
|
break;
|
|
case TRC_HVM_REINJ_VIRQ:
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %3u.%09u %s inj_virq vec %u\n",
|
|
ri->t.s, ri->t.ns, pcpu_string(ri->cpu),
|
|
*(unsigned*)h->d);
|
|
}
|
|
break;
|
|
case TRC_HVM_INJ_EXC:
|
|
hvm_inj_exc_process(ri, h);
|
|
break;
|
|
case TRC_HVM_INJ_VIRQ:
|
|
hvm_inj_virq_process(ri, h);
|
|
break;
|
|
case TRC_HVM_INTR_WINDOW:
|
|
hvm_intr_window_process(ri, h);
|
|
break;
|
|
case TRC_HVM_OP_DESTROY_PROC:
|
|
if(h->v->cr3.data) {
|
|
struct cr3_value_struct *cur = h->v->cr3.data;
|
|
if(cur->destroy.callback)
|
|
fprintf(warn, "Strange, double callback for cr3 gmfn %llx!\n",
|
|
cur->gmfn);
|
|
cur->destroy.callback = 1;
|
|
} else if(opt.with_cr3_enumeration) {
|
|
fprintf(warn, "Warning: destroy_proc: don't know current cr3\n");
|
|
}
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %3u.%09u %s destroy_proc cur_cr3 %llx\n",
|
|
ri->t.s, ri->t.ns, pcpu_string(ri->cpu), h->v->cr3.val);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return;
|
|
|
|
needs_vmexit:
|
|
/* Wait for the next vmexit */
|
|
if(!h->vmexit_valid)
|
|
{
|
|
if(opt.dump_all)
|
|
hvm_generic_dump(ri,"!");
|
|
return;
|
|
}
|
|
|
|
/* Keep generic "event handler" info */
|
|
h->event_handler = ri->event - TRC_HVM_HANDLER;
|
|
|
|
switch(ri->event) {
|
|
/* Records adding to the vmexit reason */
|
|
case TRC_HVM_INTR:
|
|
hvm_intr_process(ri, h);
|
|
break;
|
|
case TRC_HVM_PF_XEN:
|
|
case TRC_HVM_PF_XEN64:
|
|
hvm_pf_xen_process(ri, h);
|
|
break;
|
|
case TRC_HVM_IOPORT_READ:
|
|
case TRC_HVM_IOPORT_WRITE:
|
|
hvm_io_assist_process(ri, h);
|
|
break;
|
|
case TRC_HVM_IOMEM_READ:
|
|
case TRC_HVM_IOMEM_WRITE:
|
|
case TRC_HVM_IOMEM_READ|TRC_64_FLAG:
|
|
case TRC_HVM_IOMEM_WRITE|TRC_64_FLAG:
|
|
hvm_mmio_assist_process(ri, h);
|
|
break;
|
|
case TRC_HVM_CR_WRITE:
|
|
case TRC_HVM_CR_WRITE64:
|
|
hvm_cr_write_process(ri, h);
|
|
break;
|
|
case TRC_HVM_MSR_WRITE:
|
|
hvm_msr_write_process(ri, h);
|
|
break;
|
|
case TRC_HVM_MSR_READ:
|
|
hvm_msr_read_process(ri, h);
|
|
break;
|
|
case TRC_HVM_VMMCALL:
|
|
hvm_vmcall_process(ri, h);
|
|
break;
|
|
case TRC_HVM_NPF:
|
|
hvm_npf_process(ri, h);
|
|
break;
|
|
case TRC_HVM_RDTSC:
|
|
hvm_rdtsc_process(ri, h);
|
|
break;
|
|
case TRC_HVM_DR_READ:
|
|
case TRC_HVM_DR_WRITE:
|
|
case TRC_HVM_CPUID:
|
|
case TRC_HVM_SMI:
|
|
case TRC_HVM_HLT:
|
|
case TRC_HVM_INVLPG:
|
|
case TRC_HVM_INVLPG64:
|
|
case TRC_HVM_MCE:
|
|
case TRC_HVM_CLTS:
|
|
case TRC_HVM_LMSW:
|
|
case TRC_HVM_LMSW64:
|
|
case TRC_HVM_NMI:
|
|
case TRC_HVM_REALMODE_EMULATE:
|
|
case TRC_HVM_TRAP:
|
|
case TRC_HVM_TRAP_DEBUG:
|
|
case TRC_HVM_CR_READ:
|
|
case TRC_HVM_CR_READ64:
|
|
default:
|
|
if(opt.dump_all)
|
|
hvm_generic_dump(ri, "]");
|
|
if(opt.summary_info)
|
|
hvm_generic_postprocess_init(ri, h);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void vcpu_next_update(struct pcpu_info *p, struct vcpu_data *next, tsc_t tsc);
|
|
void vcpu_prev_update(struct pcpu_info *p, struct vcpu_data *prev,
|
|
tsc_t tsc, int new_runstate);
|
|
struct vcpu_data * vcpu_find(int did, int vid);
|
|
void lose_vcpu(struct vcpu_data *v, tsc_t tsc);
|
|
|
|
int domain_runstate(struct domain_data *d) {
|
|
int i;
|
|
int runstates[RUNSTATE_MAX];
|
|
int ret=-1;
|
|
int max_vcpus = 0;
|
|
|
|
if(d->did == DEFAULT_DOMAIN)
|
|
return 0;
|
|
|
|
for(i=0; i<RUNSTATE_MAX; i++)
|
|
runstates[i]=0;
|
|
|
|
for(i=0; i<=d->max_vid; i++)
|
|
if(d->vcpu[i] && d->vcpu[i]->runstate.state != RUNSTATE_INIT) {
|
|
max_vcpus++;
|
|
runstates[d->vcpu[i]->runstate.state]++;
|
|
}
|
|
|
|
if(runstates[RUNSTATE_LOST] == max_vcpus)
|
|
ret=DOMAIN_RUNSTATE_LOST;
|
|
else if(runstates[RUNSTATE_RUNNING])
|
|
{
|
|
if(runstates[RUNSTATE_RUNNABLE])
|
|
ret=DOMAIN_RUNSTATE_CONCURRENCY_HAZARD;
|
|
else if(runstates[RUNSTATE_BLOCKED]||runstates[RUNSTATE_OFFLINE])
|
|
ret= DOMAIN_RUNSTATE_PARTIAL_RUN;
|
|
else
|
|
ret= DOMAIN_RUNSTATE_FULL_RUN;
|
|
}
|
|
else if(runstates[RUNSTATE_RUNNABLE])
|
|
{
|
|
if(runstates[RUNSTATE_BLOCKED]||runstates[RUNSTATE_OFFLINE])
|
|
ret= DOMAIN_RUNSTATE_PARTIAL_CONTENTION;
|
|
else
|
|
ret= DOMAIN_RUNSTATE_FULL_CONTENTION;
|
|
}
|
|
else if(runstates[RUNSTATE_BLOCKED]||runstates[RUNSTATE_OFFLINE])
|
|
{
|
|
ret= DOMAIN_RUNSTATE_BLOCKED;
|
|
} else {
|
|
fprintf(warn, "Strange, no meaningful runstates for d%d!\n",
|
|
d->did);
|
|
}
|
|
|
|
if ( ret < 0 )
|
|
{
|
|
printf(" Max vid: %d (max_vcpus %d)\n", d->max_vid, max_vcpus);
|
|
for(i=0; i<=d->max_vid; i++)
|
|
if(d->vcpu[i])
|
|
fprintf(warn, " v%d: %s\n",
|
|
i, runstate_name[d->vcpu[i]->runstate.state]);
|
|
|
|
for(i=0; i<RUNSTATE_MAX; i++)
|
|
fprintf(warn, " %s: %d\n",
|
|
runstate_name[i], runstates[i]);
|
|
}
|
|
|
|
if(ret >= 0)
|
|
return ret;
|
|
|
|
error(ERR_ASSERT, NULL);
|
|
return -1; /* Never happens */
|
|
}
|
|
|
|
static inline void runstate_update(struct vcpu_data *v, int new_runstate,
|
|
tsc_t tsc)
|
|
{
|
|
struct domain_data *d = v->d;
|
|
|
|
if ( opt.scatterplot_runstate )
|
|
{
|
|
struct time_struct t;
|
|
|
|
abs_cycles_to_time(tsc, &t);
|
|
|
|
printf("%dv%d %u.%09u %d\n",
|
|
d->did, v->vid,
|
|
t.s, t.ns,
|
|
runstate_graph[v->runstate.state]);
|
|
printf("%dv%d %u.%09u %d\n",
|
|
d->did, v->vid,
|
|
t.s, t.ns,
|
|
runstate_graph[new_runstate]);
|
|
}
|
|
|
|
if(v->runstate.tsc > 0 && v->runstate.tsc < tsc) {
|
|
update_cycles(v->runstates + v->runstate.state, tsc - v->runstate.tsc);
|
|
|
|
if ( opt.scatterplot_runstate_time )
|
|
{
|
|
struct time_struct t, dt;
|
|
|
|
abs_cycles_to_time(tsc, &t);
|
|
cycles_to_time(tsc - v->runstate.tsc, &dt);
|
|
|
|
printf("%dv%d %u.%09u %u.%09u\n",
|
|
d->did, v->vid,
|
|
t.s, t.ns,
|
|
dt.s, dt.ns);
|
|
}
|
|
|
|
if(v->runstate.state == RUNSTATE_RUNNING)
|
|
update_cycles(&v->d->total_time, tsc - v->runstate.tsc);
|
|
|
|
if(v->runstate.state == RUNSTATE_RUNNABLE)
|
|
update_cycles(v->runnable_states + v->runstate.runnable_state, tsc - v->runstate.tsc);
|
|
|
|
/* How much did dom0 run this buffer? */
|
|
if(v->d->did == 0) {
|
|
int i;
|
|
for(i=0; i<MAX_CPUS; i++) {
|
|
struct pcpu_info * p = P.pcpu + i;
|
|
tsc_t start_tsc;
|
|
if(!p->active)
|
|
continue;
|
|
start_tsc = (p->volume.buffer_first_tsc > v->runstate.tsc) ?
|
|
p->volume.buffer_first_tsc :
|
|
v->runstate.tsc;
|
|
p->volume.buffer_dom0_runstate_cycles[v->runstate.state]
|
|
+= tsc - start_tsc;
|
|
#if 0
|
|
printf(" - updated p%d dom0_runstate %s to %lld cycles (+%lld)\n",
|
|
p->pid, runstate_name[v->runstate.state],
|
|
p->volume.buffer_dom0_runstate_cycles[v->runstate.state],
|
|
tsc - start_tsc);
|
|
#endif
|
|
p->volume.buffer_dom0_runstate = new_runstate;
|
|
p->volume.buffer_dom0_runstate_tsc = tsc;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Detect "runnable" states */
|
|
if ( new_runstate == RUNSTATE_RUNNABLE )
|
|
{
|
|
switch(v->runstate.state)
|
|
{
|
|
case RUNSTATE_RUNNING:
|
|
v->runstate.runnable_state=RUNNABLE_STATE_PREEMPT;
|
|
break;
|
|
case RUNSTATE_BLOCKED:
|
|
case RUNSTATE_OFFLINE:
|
|
v->runstate.runnable_state=RUNNABLE_STATE_WAKE;
|
|
break;
|
|
default:
|
|
v->runstate.runnable_state=RUNNABLE_STATE_OTHER;
|
|
break;
|
|
}
|
|
} else
|
|
v->runstate.runnable_state=RUNNABLE_STATE_INVALID;
|
|
|
|
v->runstate.state = new_runstate;
|
|
v->runstate.tsc = tsc;
|
|
|
|
/* Determine the domain runstate */
|
|
if(d->runstate_tsc > 0 && d->runstate_tsc < tsc)
|
|
update_cycles(d->runstates + d->runstate, tsc - d->runstate_tsc);
|
|
|
|
d->runstate = domain_runstate(d);
|
|
|
|
d->runstate_tsc = tsc;
|
|
}
|
|
|
|
void hvm_vmexit_process(struct record_info *ri, struct hvm_data *h,
|
|
struct vcpu_data *v) {
|
|
struct {
|
|
union {
|
|
struct {
|
|
unsigned int exit_reason;
|
|
unsigned long long rip;
|
|
} __attribute__((packed)) x64;
|
|
struct {
|
|
unsigned int exit_reason;
|
|
unsigned int eip;
|
|
} x32;
|
|
};
|
|
} *r;
|
|
|
|
if ( ri->event & TRC_64_FLAG )
|
|
{
|
|
if (check_extra_words(ri, sizeof(r->x64), "vmexit"))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
if (check_extra_words(ri, sizeof(r->x32), "vmexit"))
|
|
return;
|
|
}
|
|
|
|
r = (typeof(r))ri->d;
|
|
|
|
if(!h->exit_reason_name)
|
|
set_hvm_exit_reason_data(h, ri->event);
|
|
|
|
h->vmexit_valid=1;
|
|
bzero(&h->inflight, sizeof(h->inflight));
|
|
|
|
if(ri->event & TRC_64_FLAG) {
|
|
if(v->guest_paging_levels != 4)
|
|
{
|
|
if ( verbosity >= 6 )
|
|
fprintf(warn, "%s: VMEXIT64, but guest_paging_levels %d. Switching to 4.\n",
|
|
__func__, v->guest_paging_levels);
|
|
v->guest_paging_levels = 4;
|
|
}
|
|
if(!is_valid_addr64(r->x64.rip))
|
|
fprintf(warn, "%s: invalid va %llx\n",
|
|
__func__, r->x64.rip);
|
|
h->rip = r->x64.rip;
|
|
h->exit_reason = r->x64.exit_reason;
|
|
} else {
|
|
if(v->guest_paging_levels == 4)
|
|
{
|
|
int new_paging_levels = opt.default_guest_paging_levels;
|
|
|
|
if(new_paging_levels == 4)
|
|
new_paging_levels = 2; /* Wild guess */
|
|
|
|
if ( verbosity >= 6 )
|
|
fprintf(warn, "%s: VMEXIT, but guest_paging_levels %d. Switching to %d(default).\n",
|
|
__func__, v->guest_paging_levels, new_paging_levels);
|
|
|
|
v->guest_paging_levels = new_paging_levels;
|
|
}
|
|
h->rip = r->x32.eip;
|
|
h->exit_reason = r->x32.exit_reason;
|
|
}
|
|
|
|
if(opt.scatterplot_vmexit_eip)
|
|
scatterplot_vs_time(ri->tsc, h->rip);
|
|
|
|
if(h->exit_reason > h->exit_reason_max)
|
|
{
|
|
fprintf(warn, "h->exit_reason %x > exit_reason_max %x!\n",
|
|
(unsigned int)h->exit_reason,
|
|
(unsigned int)h->exit_reason_max);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
|
|
if(opt.dump_all) {
|
|
if ( h->exit_reason < h->exit_reason_max
|
|
&& h->exit_reason_name[h->exit_reason] != NULL)
|
|
printf("]%s vmexit exit_reason %s eip %llx%s\n",
|
|
ri->dump_header,
|
|
h->exit_reason_name[h->exit_reason],
|
|
h->rip,
|
|
find_symbol(h->rip));
|
|
else
|
|
printf("]%s vmexit exit_reason %x eip %llx%s\n",
|
|
ri->dump_header,
|
|
h->exit_reason,
|
|
h->rip,
|
|
find_symbol(h->rip));
|
|
}
|
|
|
|
if(h->v->cr3.data && h->entry_tsc) {
|
|
update_cycles(&h->v->cr3.data->guest_time,
|
|
ri->tsc - h->entry_tsc);
|
|
h->v->cr3.data->run_time += (ri->tsc - h->entry_tsc);
|
|
}
|
|
|
|
h->exit_tsc = ri->tsc;
|
|
h->entry_tsc = 0;
|
|
h->resyncs = 0;
|
|
h->prealloc_unpin = 0;
|
|
h->wrmap_bf = 0;
|
|
h->short_summary_done = 0;
|
|
|
|
h->post_process = hvm_generic_postprocess;
|
|
h->inflight.generic.event = 0;
|
|
}
|
|
|
|
void hvm_close_vmexit(struct hvm_data *h, tsc_t tsc) {
|
|
|
|
if(h->exit_tsc) {
|
|
if(h->exit_tsc > tsc)
|
|
h->arc_cycles = 0;
|
|
else {
|
|
h->arc_cycles = tsc - h->exit_tsc;
|
|
|
|
if(opt.summary_info) {
|
|
update_cycles(&h->summary.exit_reason[h->exit_reason],
|
|
h->arc_cycles);
|
|
h->summary_info = 1;
|
|
}
|
|
|
|
if ( opt.scatterplot_extint_cycles
|
|
&& h->exit_reason == EXIT_REASON_EXTERNAL_INTERRUPT
|
|
&& h->inflight.intr.vec == opt.scatterplot_extint_cycles_vector )
|
|
{
|
|
struct time_struct t;
|
|
|
|
abs_cycles_to_time(tsc, &t);
|
|
|
|
printf("d%dv%d %u.%09u %lld\n",
|
|
h->v->d->did,
|
|
h->v->vid,
|
|
t.s, t.ns,
|
|
h->arc_cycles);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(h->post_process)
|
|
(h->post_process)(h);
|
|
|
|
if(h->arc_cycles) {
|
|
if(opt.summary_info && !h->short_summary_done) {
|
|
switch(h->event_handler) {
|
|
case HVM_EVENT_HANDLER_VMCALL:
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_VMCALL);
|
|
break;
|
|
case HVM_EVENT_HANDLER_INTR:
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_INTERRUPT);
|
|
break;
|
|
case HVM_EVENT_HANDLER_HLT:
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_HLT);
|
|
break;
|
|
default:
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_OTHER);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if(h->v->cr3.data) {
|
|
h->v->cr3.data->run_time += h->arc_cycles;
|
|
|
|
if(opt.summary_info)
|
|
update_cycles(&h->v->cr3.data->hv_time,
|
|
h->arc_cycles);
|
|
}
|
|
}
|
|
|
|
h->exit_tsc = 0;
|
|
h->vmexit_valid = 0;
|
|
h->post_process = NULL;
|
|
|
|
}
|
|
|
|
void hvm_vmentry_process(struct record_info *ri, struct hvm_data *h) {
|
|
if(!h->init)
|
|
{
|
|
if(opt.dump_all)
|
|
printf("!%s vmentry\n",
|
|
ri->dump_header);
|
|
return;
|
|
}
|
|
|
|
/* Vista bug
|
|
* This has to be done here because irqs are injected on the path out
|
|
* to vmexit. */
|
|
hvm_vlapic_vmentry_cleanup(h->v, ri->tsc);
|
|
|
|
if(h->w2h.waking && opt.dump_all)
|
|
printf(" [w2h] d%dv%d Finishing waking\n",
|
|
h->v->d->did, h->v->vid);
|
|
|
|
h->w2h.waking = 0;
|
|
|
|
if ( h->w2h.interrupts_wanting_tsc ) {
|
|
int i;
|
|
for(i=0; i<GUEST_INTERRUPT_MAX; i++)
|
|
{
|
|
if ( h->summary.guest_interrupt[i].start_tsc == 1 )
|
|
{
|
|
if(opt.dump_all)
|
|
printf(" [w2h] d%dv%d Setting vec %d tsc to %lld\n",
|
|
h->v->d->did, h->v->vid, i, ri->tsc);
|
|
h->summary.guest_interrupt[i].start_tsc = ri->tsc;
|
|
h->w2h.interrupts_wanting_tsc--;
|
|
if ( h->w2h.interrupts_wanting_tsc == 0 )
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!h->vmexit_valid)
|
|
{
|
|
if(opt.dump_all)
|
|
printf("!%s vmentry\n",
|
|
ri->dump_header);
|
|
return;
|
|
}
|
|
|
|
if(opt.dump_all) {
|
|
unsigned long long arc_cycles = ri->tsc - h->exit_tsc;
|
|
printf("]%s vmentry cycles %lld %s\n",
|
|
ri->dump_header, arc_cycles, (arc_cycles>10000)?"!":"");
|
|
}
|
|
|
|
hvm_close_vmexit(h, ri->tsc);
|
|
h->entry_tsc = ri->tsc;
|
|
}
|
|
|
|
void hvm_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
struct vcpu_data *v = p->current;
|
|
struct hvm_data *h = &v->hvm;
|
|
|
|
assert(p->current);
|
|
|
|
/* HVM_EMUL types show up in all contexts */
|
|
if(ri->evt.sub != 0x4) {
|
|
if(vcpu_set_data_type(p->current, VCPU_DATA_HVM))
|
|
return;
|
|
}
|
|
|
|
switch ( ri->evt.sub ) {
|
|
case 2: /* HVM_HANDLER */
|
|
UPDATE_VOLUME(p, hvm[HVM_VOL_HANDLER], ri->size);
|
|
hvm_handler_process(ri, h);
|
|
break;
|
|
case 4: /* HVM_EMUL */
|
|
UPDATE_VOLUME(p, hvm[HVM_VOL_EMUL], ri->size);
|
|
warn_once("WARNING: We don't yet analyze HVM_EMUL events.\n");
|
|
/* FIXME: Collect analysis on this */
|
|
break;
|
|
default:
|
|
switch(ri->event) {
|
|
case TRC_HVM_VMX_EXIT:
|
|
case TRC_HVM_VMX_EXIT64:
|
|
case TRC_HVM_SVM_EXIT:
|
|
case TRC_HVM_SVM_EXIT64:
|
|
UPDATE_VOLUME(p, hvm[HVM_VOL_VMEXIT], ri->size);
|
|
hvm_vmexit_process(ri, h, v);
|
|
break;
|
|
case TRC_HVM_VMENTRY:
|
|
UPDATE_VOLUME(p, hvm[HVM_VOL_VMENTRY], ri->size);
|
|
hvm_vmentry_process(ri, &p->current->hvm);
|
|
break;
|
|
default:
|
|
fprintf(warn, "Unknown hvm event: %x\n", ri->event);
|
|
}
|
|
}
|
|
}
|
|
|
|
void hvm_summary(struct hvm_data *h) {
|
|
int i;
|
|
|
|
if(!h->summary_info)
|
|
return;
|
|
|
|
printf("Exit reasons:\n");
|
|
for(i=0; i<h->exit_reason_max; i++) {
|
|
struct hvm_summary_handler_node *p;
|
|
|
|
if ( h->exit_reason_name[i] )
|
|
PRINT_SUMMARY(h->summary.exit_reason[i],
|
|
" %-20s ", h->exit_reason_name[i]);
|
|
else
|
|
PRINT_SUMMARY(h->summary.exit_reason[i],
|
|
" %20d ", i);
|
|
|
|
p=h->exit_reason_summary_handler_list[i];
|
|
while(p)
|
|
{
|
|
p->handler(h, p->data);
|
|
p=p->next;
|
|
}
|
|
}
|
|
|
|
printf("Guest interrupt counts:\n");
|
|
for(i=0; i<GUEST_INTERRUPT_MAX; i++)
|
|
if(h->summary.guest_interrupt[i].count) {
|
|
int j;
|
|
printf(" [%3d] %d\n",
|
|
i, h->summary.guest_interrupt[i].count);
|
|
for(j=1; j<GUEST_INTERRUPT_CASE_MAX; j++) {
|
|
char desc[80];
|
|
snprintf(desc, 80, " * %s", guest_interrupt_case_name[j]);
|
|
print_cycle_summary(h->summary.guest_interrupt[i].runtime+j, desc);
|
|
}
|
|
}
|
|
if(h->summary.guest_interrupt[i].count)
|
|
printf(" [%d+] %d\n",
|
|
i, h->summary.guest_interrupt[i].count);
|
|
|
|
if(opt.histogram_interrupt_eip)
|
|
{
|
|
unsigned max = ((1ULL<<ADDR_SPACE_BITS)/opt.histogram_interrupt_increment);
|
|
printf("Interrupt eip histogram:\n");
|
|
for(i=0; i<max; i++)
|
|
if(h->summary.extint_histogram[i])
|
|
{
|
|
printf("[%llx-%llx]: %d\n",
|
|
opt.histogram_interrupt_increment * i,
|
|
(opt.histogram_interrupt_increment * (i+1)) - 1,
|
|
h->summary.extint_histogram[i]);
|
|
}
|
|
}
|
|
|
|
PRINT_SUMMARY(h->summary.ipi_latency,
|
|
"IPI latency \n");
|
|
for(i=0; i<256; i++)
|
|
if(h->summary.ipi_count[i])
|
|
printf(" [%3d] %10d\n",
|
|
i, h->summary.ipi_count[i]);
|
|
hvm_io_address_summary(h->summary.io.pio, "IO address summary:");
|
|
hvm_io_address_summary(h->summary.io.mmio, "MMIO address summary:");
|
|
}
|
|
|
|
/* ---- Shadow records ---- */
|
|
union shadow_event
|
|
{
|
|
unsigned event;
|
|
struct {
|
|
unsigned minor:8,
|
|
paging_levels:4;
|
|
};
|
|
};
|
|
|
|
/* WARNING - not thread safe */
|
|
#define FLAGSTRING(_name, _char) \
|
|
if(e->flag_ ## _name) \
|
|
flagstring[i] = _char; \
|
|
i++;
|
|
|
|
char * flag_string(struct pf_xen_extra *e)
|
|
{
|
|
static char flagstring[32];
|
|
int i=0;
|
|
|
|
for(i=0; i<32; i++)
|
|
flagstring[i]='-';
|
|
|
|
i=0;
|
|
|
|
if(e->flag_set_ad)
|
|
flagstring[i]='d';
|
|
else if(e->flag_set_a)
|
|
flagstring[i]='a';
|
|
i++;
|
|
|
|
FLAGSTRING(shadow_l1_get_ref, 'g');
|
|
FLAGSTRING(shadow_l1_put_ref, 'p');
|
|
//FLAGSTRING(l2_propagate, '2');
|
|
FLAGSTRING(demote, 'D');
|
|
FLAGSTRING(promote, 'P');
|
|
FLAGSTRING(wrmap, 'w');
|
|
FLAGSTRING(wrmap_guess_found, 'G');
|
|
//FLAGSTRING(wrmap_brute_force, 'b');
|
|
FLAGSTRING(early_unshadow, 'e');
|
|
FLAGSTRING(prealloc_unhook, 'H');
|
|
FLAGSTRING(unsync, 'u');
|
|
FLAGSTRING(oos_fixup_add, 'a');
|
|
FLAGSTRING(oos_fixup_evict, 'x');
|
|
|
|
flagstring[i]=0;
|
|
|
|
return flagstring;
|
|
}
|
|
|
|
void shadow_emulate_postprocess(struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
|
|
if ( opt.summary_info )
|
|
{
|
|
update_eip(&h->v->d->emulate_eip_list,
|
|
h->rip,
|
|
h->arc_cycles,
|
|
0, NULL);
|
|
update_cycles(&h->summary.pf_xen[PF_XEN_EMULATE], h->arc_cycles);
|
|
update_cycles(&h->summary.pf_xen_emul[e->pt_level], h->arc_cycles);
|
|
if(h->prealloc_unpin)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_PREALLOC_UNPIN], h->arc_cycles);
|
|
if(e->flag_prealloc_unhook)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_PREALLOC_UNHOOK], h->arc_cycles);
|
|
if(e->flag_early_unshadow)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_EARLY_UNSHADOW], h->arc_cycles);
|
|
if(e->flag_set_changed)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_SET_CHANGED], h->arc_cycles);
|
|
else
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_SET_UNCHANGED], h->arc_cycles);
|
|
if(e->flag_set_flush)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_SET_FLUSH], h->arc_cycles);
|
|
if(e->flag_set_error)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_SET_ERROR], h->arc_cycles);
|
|
if(e->flag_promote)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_PROMOTE], h->arc_cycles);
|
|
if(e->flag_demote)
|
|
update_cycles(&h->summary.pf_xen_emul[PF_XEN_EMUL_DEMOTE], h->arc_cycles);
|
|
/* more summary info */
|
|
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_EMULATE);
|
|
}
|
|
|
|
if(opt.scatterplot_unpin_promote) {
|
|
if(e->flag_early_unshadow)
|
|
scatterplot_vs_time(h->exit_tsc, -10);
|
|
if(h->prealloc_unpin)
|
|
scatterplot_vs_time(h->exit_tsc, 0);
|
|
if(e->flag_promote) {
|
|
if(opt.with_cr3_enumeration) {
|
|
if(h->v->cr3.data)
|
|
scatterplot_vs_time(h->exit_tsc, h->v->cr3.data->cr3_id);
|
|
} else
|
|
scatterplot_vs_time(h->exit_tsc, 2);
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
void shadow_emulate_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
union {
|
|
/* for PAE, guest_l1e may be 64 while guest_va may be 32;
|
|
so put it first for alignment sake. */
|
|
struct {
|
|
unsigned gl1e, write_val;
|
|
unsigned va;
|
|
unsigned flags:29, emulation_count:3;
|
|
} gpl2;
|
|
struct {
|
|
unsigned long long gl1e, write_val;
|
|
unsigned va;
|
|
unsigned flags:29, emulation_count:3;
|
|
} gpl3;
|
|
struct {
|
|
unsigned long long gl1e, write_val;
|
|
unsigned long long va;
|
|
unsigned flags:29, emulation_count:3;
|
|
} gpl4;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
union shadow_event sevt = { .event = ri->event };
|
|
int rec_gpl = sevt.paging_levels + 2;
|
|
|
|
if ( rec_gpl != h->v->guest_paging_levels )
|
|
{
|
|
fprintf(warn, "%s: record paging levels %d, guest paging levels %d. Switching.\n",
|
|
__func__, rec_gpl, h->v->guest_paging_levels);
|
|
h->v->guest_paging_levels = rec_gpl;
|
|
}
|
|
|
|
/* Fill in extended information */
|
|
switch(rec_gpl)
|
|
{
|
|
case 2:
|
|
if(sizeof(r->gpl2) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl2), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl2.va;
|
|
e->flags = r->gpl2.flags;
|
|
e->gl1e = r->gpl2.gl1e;
|
|
e->wval = r->gpl2.write_val;
|
|
break;
|
|
case 3:
|
|
if(sizeof(r->gpl3) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl3), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl3.va;
|
|
e->flags = r->gpl3.flags;
|
|
e->gl1e = r->gpl3.gl1e;
|
|
e->wval = r->gpl3.write_val;
|
|
break;
|
|
case 4:
|
|
if(sizeof(r->gpl4) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl4), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl4.va;
|
|
e->flags = r->gpl4.flags;
|
|
e->gl1e = r->gpl4.gl1e;
|
|
e->wval = r->gpl4.write_val;
|
|
break;
|
|
}
|
|
|
|
pf_preprocess(e,rec_gpl);
|
|
|
|
if(opt.dump_all)
|
|
printf("]%s emulate va %llx gl1e %8llx wval %8llx flags %s(%x) pt_level %d corr %8llx\n",
|
|
ri->dump_header,
|
|
e->va,
|
|
e->gl1e, e->wval,
|
|
flag_string(e), e->flags,
|
|
e->pt_level, e->corresponding_va);
|
|
|
|
if ( hvm_set_postprocess(h, shadow_emulate_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
struct shadow_emulate_other {
|
|
unsigned long long gfn, va;
|
|
};
|
|
|
|
#define SHADOW_OTHER_LOGS_GFN_NOT_GMFN 1
|
|
|
|
void shadow_parse_other(struct record_info *ri,
|
|
struct shadow_emulate_other *o,
|
|
struct hvm_data *h) {
|
|
union {
|
|
/* for PAE, guest_l1e may be 64 while guest_va may be 32;
|
|
so put it first for alignment sake. */
|
|
#if SHADOW_OTHER_LOGS_GFN_NOT_GMFN
|
|
/* D'OH! Accidentally used mfn_t in the struct, so gmfns are always
|
|
64-bit... :-/ */
|
|
struct {
|
|
unsigned int gfn, va;
|
|
} gpl2;
|
|
#endif
|
|
struct {
|
|
unsigned long long gfn;
|
|
unsigned int va;
|
|
} gpl3;
|
|
struct {
|
|
unsigned long long gfn, va;
|
|
} gpl4;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
|
|
union shadow_event sevt = { .event = ri->event };
|
|
int rec_gpl = sevt.paging_levels + 2;
|
|
|
|
if ( rec_gpl != h->v->guest_paging_levels )
|
|
{
|
|
fprintf(warn, "%s: record paging levels %d, guest paging levels %d. Switching.\n",
|
|
__func__, rec_gpl, h->v->guest_paging_levels);
|
|
h->v->guest_paging_levels = rec_gpl;
|
|
}
|
|
|
|
switch(rec_gpl)
|
|
{
|
|
#if SHADOW_OTHER_LOGS_GFN_NOT_GMFN
|
|
case 2:
|
|
if(sizeof(r->gpl2) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl2), rec_gpl,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
o->va = r->gpl2.va;
|
|
o->gfn = r->gpl2.gfn;
|
|
break;
|
|
#else
|
|
case 2:
|
|
/* FALLTHRU */
|
|
#endif
|
|
case 3:
|
|
if(sizeof(r->gpl3) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl3), rec_gpl,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
o->va = r->gpl3.va;
|
|
o->gfn = r->gpl3.gfn;
|
|
break;
|
|
case 4:
|
|
if(sizeof(r->gpl4) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl4), rec_gpl,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
o->va = r->gpl4.va;
|
|
o->gfn = r->gpl4.gfn;
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
void shadow_unsync_postprocess(struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
|
|
if(h->resyncs > 1)
|
|
fprintf(warn, "Strange, %d resyncs for an unsync!\n",
|
|
h->resyncs);
|
|
|
|
if(opt.summary_info) {
|
|
update_cycles(&h->summary.pf_xen[PF_XEN_EMULATE_UNSYNC],
|
|
h->arc_cycles);
|
|
if(h->resyncs <= 1)
|
|
update_cycles(&h->summary.pf_xen_unsync[h->resyncs],
|
|
h->arc_cycles);
|
|
}
|
|
}
|
|
|
|
|
|
void shadow_unsync_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
struct shadow_emulate_other r;
|
|
|
|
shadow_parse_other(ri, &r, h);
|
|
|
|
e->gmfn = r.gmfn;
|
|
e->va = r.va;
|
|
|
|
pf_preprocess(e, h->v->guest_paging_levels);
|
|
|
|
if(opt.dump_all)
|
|
printf("]%s shadow unsync gmfn %llx va %llx pt_level %d corr %llx\n",
|
|
ri->dump_header,
|
|
e->gmfn,
|
|
e->va,
|
|
e->pt_level,
|
|
e->corresponding_va);
|
|
|
|
if ( hvm_set_postprocess(h, shadow_unsync_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
#endif
|
|
|
|
void shadow_fault_generic_postprocess(struct hvm_data *h);
|
|
|
|
void shadow_emulate_other_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
struct shadow_emulate_other r;
|
|
union shadow_event sevt = { .event = ri->event };
|
|
|
|
shadow_parse_other(ri, &r, h);
|
|
|
|
e->gfn = r.gfn;
|
|
e->va = r.va;
|
|
e->pf_case = sevt.minor;
|
|
|
|
pf_preprocess(e, h->v->guest_paging_levels);
|
|
|
|
if(opt.dump_all)
|
|
printf("]%s shadow %s gfn %llx va %llx\n",
|
|
ri->dump_header,
|
|
pf_xen_name[sevt.minor],
|
|
e->gfn,
|
|
e->va);
|
|
|
|
if ( hvm_set_postprocess(h, shadow_fault_generic_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void shadow_fixup_postprocess(struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
|
|
if ( opt.summary_info )
|
|
{
|
|
update_cycles(&h->summary.pf_xen[PF_XEN_FIXUP], h->arc_cycles);
|
|
if(h->prealloc_unpin) {
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_PREALLOC_UNPIN], h->arc_cycles);
|
|
}
|
|
if(e->flag_unsync) {
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_UNSYNC], h->arc_cycles);
|
|
if(h->resyncs < PF_XEN_FIXUP_UNSYNC_RESYNC_MAX)
|
|
update_cycles(&h->summary.pf_xen_fixup_unsync_resync[h->resyncs],
|
|
h->arc_cycles);
|
|
else
|
|
update_cycles(&h->summary.pf_xen_fixup_unsync_resync[PF_XEN_FIXUP_UNSYNC_RESYNC_MAX],
|
|
h->arc_cycles);
|
|
}
|
|
if(e->flag_oos_fixup_add)
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_OOS_ADD], h->arc_cycles);
|
|
if(e->flag_oos_fixup_evict)
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_OOS_EVICT], h->arc_cycles);
|
|
if(e->flag_promote)
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_PROMOTE], h->arc_cycles);
|
|
if(e->flag_wrmap) {
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_WRMAP], h->arc_cycles);
|
|
if(e->flag_wrmap_brute_force || h->wrmap_bf)
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_BRUTE_FORCE], h->arc_cycles);
|
|
} else if(e->flag_wrmap_brute_force || h->wrmap_bf) {
|
|
fprintf(warn, "Strange: wrmap_bf but not wrmap!\n");
|
|
}
|
|
|
|
|
|
if(!(e->flag_promote || h->prealloc_unpin || e->flag_unsync))
|
|
update_cycles(&h->summary.pf_xen_fixup[PF_XEN_FIXUP_UPDATE_ONLY], h->arc_cycles);
|
|
/* more summary info */
|
|
|
|
if(e->flag_unsync)
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_UNSYNC);
|
|
else
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_FIXUP);
|
|
}
|
|
|
|
if(opt.scatterplot_unpin_promote) {
|
|
if(h->prealloc_unpin)
|
|
scatterplot_vs_time(h->exit_tsc, 0);
|
|
if(e->flag_promote) {
|
|
if(opt.with_cr3_enumeration) {
|
|
if(h->v->cr3.data)
|
|
scatterplot_vs_time(h->exit_tsc, h->v->cr3.data->cr3_id);
|
|
} else
|
|
scatterplot_vs_time(h->exit_tsc, 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
void shadow_fixup_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
union {
|
|
/* for PAE, guest_l1e may be 64 while guest_va may be 32;
|
|
so put it first for alignment sake. */
|
|
struct {
|
|
unsigned int gl1e, va, flags;
|
|
} gpl2;
|
|
struct {
|
|
unsigned long long gl1e;
|
|
unsigned int va, flags;
|
|
} gpl3;
|
|
struct {
|
|
unsigned long long gl1e, va;
|
|
unsigned int flags;
|
|
} gpl4;
|
|
} *r = (typeof(r))ri->d;
|
|
union shadow_event sevt = { .event = ri->event };
|
|
int rec_gpl = sevt.paging_levels + 2;
|
|
|
|
if ( rec_gpl != h->v->guest_paging_levels )
|
|
{
|
|
fprintf(warn, "%s: record paging levels %d, guest paging levels %d. Switching.\n",
|
|
__func__, rec_gpl, h->v->guest_paging_levels);
|
|
h->v->guest_paging_levels = rec_gpl;
|
|
}
|
|
|
|
switch(rec_gpl)
|
|
{
|
|
case 2:
|
|
if(sizeof(r->gpl2) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl2), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl2.va;
|
|
e->flags = r->gpl2.flags;
|
|
e->gl1e = r->gpl2.gl1e;
|
|
break;
|
|
case 3:
|
|
if(sizeof(r->gpl3) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl3), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl3.va;
|
|
e->flags = r->gpl3.flags;
|
|
e->gl1e = r->gpl3.gl1e;
|
|
break;
|
|
case 4:
|
|
if(sizeof(r->gpl4) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl4), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl4.va;
|
|
e->flags = r->gpl4.flags;
|
|
e->gl1e = r->gpl4.gl1e;
|
|
break;
|
|
}
|
|
|
|
pf_preprocess(e,rec_gpl);
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
if ( e->flag_unsync )
|
|
printf("]%s fixup:unsync va %llx gl1e %llx corr %llx flags (%x)%s\n",
|
|
ri->dump_header,
|
|
e->va, e->gl1e,
|
|
e->corresponding_va,
|
|
e->flags,
|
|
flag_string(e));
|
|
else
|
|
printf("]%s fixup va %llx gl1e %llx flags (%x)%s\n",
|
|
ri->dump_header,
|
|
e->va, e->gl1e, e->flags,
|
|
flag_string(e));
|
|
}
|
|
|
|
if ( hvm_set_postprocess(h, shadow_fixup_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void shadow_mmio_postprocess(struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
if ( opt.summary_info )
|
|
{
|
|
if(e->pf_case)
|
|
update_cycles(&h->summary.pf_xen[e->pf_case],
|
|
h->arc_cycles);
|
|
else
|
|
fprintf(warn, "Strange, pf_case 0!\n");
|
|
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_MMIO);
|
|
}
|
|
|
|
if(opt.with_mmio_enumeration)
|
|
enumerate_mmio(h);
|
|
}
|
|
|
|
void shadow_mmio_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
struct mmio_info *m = &h->inflight.mmio;
|
|
union {
|
|
/* for PAE, guest_l1e may be 64 while guest_va may be 32;
|
|
so put it first for alignment sake. */
|
|
struct {
|
|
unsigned int va;
|
|
} gpl2;
|
|
struct {
|
|
unsigned long long va;
|
|
} gpl4;
|
|
} *r = (typeof(r))ri->d;
|
|
union shadow_event sevt = { .event = ri->event };
|
|
int rec_gpl = sevt.paging_levels + 2;
|
|
|
|
if ( rec_gpl != h->v->guest_paging_levels )
|
|
{
|
|
fprintf(warn, "%s: record paging levels %d, guest paging levels %d. Switching.\n",
|
|
__func__, rec_gpl, h->v->guest_paging_levels);
|
|
h->v->guest_paging_levels = rec_gpl;
|
|
}
|
|
|
|
switch(rec_gpl)
|
|
{
|
|
case 2:
|
|
case 3:
|
|
if(sizeof(r->gpl2) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl2), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = m->va = r->gpl2.va;
|
|
break;
|
|
case 4:
|
|
if(sizeof(r->gpl4) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl4), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = m->va = r->gpl4.va;
|
|
break;
|
|
}
|
|
|
|
if(opt.dump_all)
|
|
printf("]%s %smmio va %llx\n",
|
|
ri->dump_header,
|
|
(e->pf_case==PF_XEN_FAST_MMIO)?"fast ":"",
|
|
e->va);
|
|
|
|
if ( hvm_set_postprocess(h, shadow_mmio_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void shadow_propagate_postprocess(struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
|
|
if ( opt.summary_info )
|
|
{
|
|
if(e->pf_case)
|
|
update_cycles(&h->summary.pf_xen[e->pf_case],
|
|
h->arc_cycles);
|
|
else
|
|
fprintf(warn, "Strange, pf_case 0!\n");
|
|
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_PROPAGATE);
|
|
}
|
|
}
|
|
|
|
void shadow_propagate_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
union {
|
|
/* for PAE, guest_l1e may be 64 while guest_va may be 32;
|
|
so put it first for alignment sake. */
|
|
struct {
|
|
unsigned int gl1e, va, flags;
|
|
} gpl2;
|
|
struct {
|
|
unsigned long long gl1e;
|
|
unsigned int va, flags;
|
|
} gpl3;
|
|
struct {
|
|
unsigned long long gl1e, va;
|
|
unsigned int flags;
|
|
} gpl4;
|
|
} *r = (typeof(r))ri->d;
|
|
union shadow_event sevt = { .event = ri->event };
|
|
int rec_gpl = sevt.paging_levels + 2;
|
|
|
|
if ( rec_gpl != h->v->guest_paging_levels )
|
|
{
|
|
fprintf(warn, "%s: record paging levels %d, guest paging levels %d. Switching.\n",
|
|
__func__, rec_gpl, h->v->guest_paging_levels);
|
|
h->v->guest_paging_levels = rec_gpl;
|
|
}
|
|
|
|
switch(rec_gpl)
|
|
{
|
|
case 2:
|
|
if(sizeof(r->gpl2) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl2), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl2.va;
|
|
e->flags = r->gpl2.flags;
|
|
e->gl1e = r->gpl2.gl1e;
|
|
break;
|
|
case 3:
|
|
if(sizeof(r->gpl3) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl3), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl3.va;
|
|
e->flags = r->gpl3.flags;
|
|
e->gl1e = r->gpl3.gl1e;
|
|
break;
|
|
case 4:
|
|
if(sizeof(r->gpl4) != ri->extra_words * 4)
|
|
{
|
|
fprintf(warn, "%s: expected %zd bytes for %d-level guest, got %d!\n",
|
|
__func__, sizeof(r->gpl4), h->v->guest_paging_levels,
|
|
ri->extra_words * 4);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
e->va = r->gpl4.va;
|
|
e->flags = r->gpl4.flags;
|
|
e->gl1e = r->gpl4.gl1e;
|
|
break;
|
|
}
|
|
|
|
if(opt.dump_all)
|
|
printf("]%s propagate va %llx gl1e %llx flags (%x)%s\n",
|
|
ri->dump_header,
|
|
e->va, e->gl1e, e->flags,
|
|
flag_string(e));
|
|
|
|
if ( hvm_set_postprocess(h, shadow_propagate_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void shadow_fault_generic_dump(unsigned int event, uint32_t *d,
|
|
const char *prefix, const char * dump_header)
|
|
{
|
|
const char *evt_string;
|
|
char evt_number[10];
|
|
union shadow_event sevt = { .event = event };
|
|
int i;
|
|
|
|
if(sevt.minor < PF_XEN_MAX && pf_xen_name[sevt.minor])
|
|
{
|
|
evt_string = pf_xen_name[sevt.minor];
|
|
}
|
|
else
|
|
{
|
|
snprintf(evt_number, 10, "%d", sevt.minor);
|
|
evt_string = evt_number;
|
|
}
|
|
|
|
printf("%s%s shadow %s gl %d [",
|
|
prefix,
|
|
dump_header,
|
|
evt_string,
|
|
sevt.paging_levels);
|
|
|
|
for(i=0; i<4; i++)
|
|
{
|
|
printf(" %x", d[i]);
|
|
}
|
|
|
|
printf(" ]\n");
|
|
}
|
|
|
|
void shadow_fault_generic_postprocess(struct hvm_data *h)
|
|
{
|
|
struct pf_xen_extra *e = &h->inflight.pf_xen;
|
|
if ( e->pf_case < PF_XEN_NOT_SHADOW || e->pf_case > PF_XEN_LAST_FAULT )
|
|
{
|
|
fprintf(warn, "%s: Strange, unexpected case %d\n",
|
|
__func__, e->pf_case);
|
|
return;
|
|
}
|
|
|
|
if(opt.summary_info) {
|
|
update_cycles(&h->summary.pf_xen[e->pf_case],
|
|
h->arc_cycles);
|
|
|
|
hvm_update_short_summary(h, HVM_SHORT_SUMMARY_PROPAGATE);
|
|
}
|
|
}
|
|
|
|
void shadow_fault_generic_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
union shadow_event sevt = { .event = ri->event };
|
|
|
|
/* pf-case traces, vs others */
|
|
h->inflight.generic.event = ri->event;
|
|
bcopy(ri->d, h->inflight.generic.d, sizeof(unsigned int) * 4);
|
|
|
|
if(opt.dump_all)
|
|
shadow_fault_generic_dump(h->inflight.generic.event,
|
|
h->inflight.generic.d,
|
|
"]", ri->dump_header);
|
|
|
|
h->inflight.pf_xen.pf_case = sevt.minor;
|
|
if ( hvm_set_postprocess(h, shadow_fault_generic_postprocess) )
|
|
fprintf(warn, "%s: Strange, postprocess already set\n", __func__);
|
|
}
|
|
|
|
void shadow_resync_process(struct record_info *ri, struct hvm_data *h)
|
|
{
|
|
struct {
|
|
unsigned long long gfn;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if(opt.dump_all)
|
|
printf(" %s oos resync %s gfn %llx\n",
|
|
ri->dump_header,
|
|
(ri->event == TRC_SHADOW_RESYNC_FULL)?"full":"only",
|
|
r->gfn);
|
|
|
|
h->resyncs++;
|
|
}
|
|
|
|
void shadow_prealloc_unpin_process(struct record_info *ri, struct hvm_data *h) {
|
|
struct {
|
|
unsigned long long gfn;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if(opt.dump_all)
|
|
printf(" %s prealloc-unpin gfn %llx\n",
|
|
ri->dump_header, r->gfn);
|
|
|
|
if(h->prealloc_unpin)
|
|
fprintf(warn, "Strange, more than one prealloc_unpin per arc!\n");
|
|
|
|
h->prealloc_unpin = 1;
|
|
|
|
if(opt.with_cr3_enumeration)
|
|
cr3_prealloc_unpin(h->v, r->gfn);
|
|
}
|
|
|
|
void shadow_wrmap_bf_process(struct record_info *ri, struct hvm_data *h) {
|
|
struct {
|
|
unsigned long long gfn;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if(opt.dump_all)
|
|
printf(" %s wrmap-bf gfn %llx\n",
|
|
ri->dump_header, r->gfn);
|
|
|
|
h->wrmap_bf = 1;
|
|
}
|
|
|
|
void shadow_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
struct hvm_data *h;
|
|
|
|
union shadow_event sevt = { .event = ri->event };
|
|
|
|
int gpl = sevt.paging_levels + 2;
|
|
|
|
assert(p->current);
|
|
if(vcpu_set_data_type(p->current, VCPU_DATA_HVM))
|
|
return;
|
|
|
|
h = &p->current->hvm;
|
|
|
|
if(!h->init || !h->vmexit_valid)
|
|
{
|
|
if(opt.dump_all)
|
|
shadow_fault_generic_dump(ri->event,
|
|
ri->d,
|
|
"!", ri->dump_header);
|
|
return;
|
|
}
|
|
|
|
if(sevt.minor <= PF_XEN_NOT_SHADOW) {
|
|
if(p->current->guest_paging_levels != gpl)
|
|
{
|
|
fprintf(warn, "%s: Changing guest paging levels to %d\n",
|
|
__func__, gpl);
|
|
p->current->guest_paging_levels = gpl;
|
|
}
|
|
}
|
|
|
|
if(sevt.minor <= PF_XEN_LAST_FAULT) {
|
|
h->inflight.pf_xen.pf_case = sevt.minor;
|
|
if(opt.summary) {
|
|
hvm_set_summary_handler(h, hvm_pf_xen_summary, NULL);
|
|
}
|
|
}
|
|
|
|
/* FIXME - mask out paging levels */
|
|
switch(sevt.minor)
|
|
{
|
|
case PF_XEN_NOT_SHADOW:
|
|
shadow_propagate_process(ri, h);
|
|
break;
|
|
case PF_XEN_EMULATE:
|
|
shadow_emulate_process(ri, h);
|
|
break;
|
|
case PF_XEN_FIXUP:
|
|
shadow_fixup_process(ri, h);
|
|
break;
|
|
case PF_XEN_MMIO:
|
|
case PF_XEN_FAST_MMIO:
|
|
shadow_mmio_process(ri, h);
|
|
break;
|
|
case PF_XEN_EMULATE_UNSHADOW_USER:
|
|
case PF_XEN_EMULATE_UNSHADOW_EVTINJ:
|
|
case PF_XEN_EMULATE_UNSHADOW_UNHANDLED:
|
|
shadow_emulate_other_process(ri, h);
|
|
break;
|
|
#if 0
|
|
case PF_XEN_EMULATE_UNSYNC:
|
|
shadow_unsync_process(ri, h);
|
|
break;
|
|
#endif
|
|
case SHADOW_RESYNC_FULL:
|
|
case SHADOW_RESYNC_ONLY:
|
|
shadow_resync_process(ri, h);
|
|
break;
|
|
case SHADOW_PREALLOC_UNPIN:
|
|
shadow_prealloc_unpin_process(ri, h);
|
|
break;
|
|
case SHADOW_WRMAP_BF:
|
|
shadow_wrmap_bf_process(ri, h);
|
|
break;
|
|
default:
|
|
if(sevt.minor <= PF_XEN_LAST_FAULT) {
|
|
shadow_fault_generic_process(ri, h);
|
|
} else {
|
|
warn_once("Warning: processing shadow as generic\n");
|
|
process_generic(ri);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ---- PV guests ---- */
|
|
union pv_event {
|
|
unsigned event;
|
|
struct {
|
|
unsigned minor:8,
|
|
x64:1,
|
|
unused1:3,
|
|
sub:4,
|
|
main:12,
|
|
unused:4;
|
|
};
|
|
};
|
|
|
|
void pv_hypercall_process(struct record_info *ri, struct pv_data *pv) {
|
|
union {
|
|
struct {
|
|
uint32_t eip, eax;
|
|
} x32;
|
|
struct {
|
|
uint64_t eip;
|
|
uint32_t eax;
|
|
} x64;
|
|
} * r = (typeof(r)) ri->d;
|
|
union pv_event pevt = { .event = ri->event };
|
|
unsigned long long eip;
|
|
unsigned int eax;
|
|
|
|
if(pevt.x64) {
|
|
eip = r->x64.eip;
|
|
eax = r->x64.eax;
|
|
} else {
|
|
eip = r->x32.eip;
|
|
eax = r->x32.eax;
|
|
}
|
|
|
|
if(opt.summary_info) {
|
|
if(eax < PV_HYPERCALL_MAX)
|
|
pv->hypercall_count[eax]++;
|
|
}
|
|
|
|
if(opt.dump_all) {
|
|
if(eax < HYPERCALL_MAX)
|
|
printf(" %s hypercall %2x (%s) eip %llx\n",
|
|
ri->dump_header, eax,
|
|
hypercall_name[eax], eip);
|
|
else
|
|
printf(" %s hypercall %x eip %llx\n",
|
|
ri->dump_header, eax, eip);
|
|
}
|
|
}
|
|
|
|
void pv_trap_process(struct record_info *ri, struct pv_data *pv) {
|
|
union {
|
|
struct {
|
|
unsigned int eip;
|
|
unsigned trapnr:15,
|
|
use_error_code:1,
|
|
error_code:16;
|
|
} x32;
|
|
struct {
|
|
unsigned long long eip;
|
|
unsigned trapnr:15,
|
|
use_error_code:1,
|
|
error_code:16;
|
|
} x64;
|
|
} * r = (typeof(r)) ri->d;
|
|
union pv_event pevt = { .event = ri->event };
|
|
unsigned long long eip;
|
|
unsigned trapnr, use_ec, ec;
|
|
|
|
if(pevt.x64) {
|
|
eip = r->x64.eip;
|
|
trapnr = r->x64.trapnr;
|
|
use_ec = r->x64.use_error_code;
|
|
ec = r->x64.error_code;
|
|
} else {
|
|
eip = r->x32.eip;
|
|
trapnr = r->x32.trapnr;
|
|
use_ec = r->x32.use_error_code;
|
|
ec = r->x32.error_code;
|
|
}
|
|
|
|
if(opt.summary_info) {
|
|
if(trapnr < PV_TRAP_MAX)
|
|
pv->trap_count[trapnr]++;
|
|
}
|
|
|
|
if(opt.dump_all) {
|
|
printf(" %s trap %x eip %llx",
|
|
ri->dump_header, trapnr, eip);
|
|
if(use_ec)
|
|
printf(" ec %x\n", ec);
|
|
else
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void pv_ptwr_emulation_process(struct record_info *ri, struct pv_data *pv) {
|
|
union pv_event pevt = { .event = ri->event };
|
|
union {
|
|
/* gpl2 is deprecated */
|
|
struct {
|
|
unsigned long long pte;
|
|
unsigned int addr, eip;
|
|
} gpl3;
|
|
struct {
|
|
unsigned long long pte;
|
|
unsigned long long addr, eip;
|
|
} gpl4;
|
|
} *r = (typeof(r))ri->d;
|
|
struct {
|
|
unsigned long long pte, addr, eip;
|
|
} e;
|
|
|
|
switch ( pevt.minor ) {
|
|
case PV_PTWR_EMULATION_PAE:
|
|
if ( pevt.x64 )
|
|
{
|
|
fprintf(warn, "Strange: PV_PTWR_EMULATION, but x64! %x\n",
|
|
ri->event);
|
|
error(ERR_RECORD, ri);
|
|
}
|
|
e.pte = r->gpl3.pte;
|
|
e.addr = r->gpl3.addr;
|
|
e.eip = r->gpl3.eip;
|
|
break;
|
|
case PV_PTWR_EMULATION:
|
|
if ( !pevt.x64 )
|
|
{
|
|
fprintf(warn, "Strange: PV_PTWR_EMULATION, but !x64! %x\n",
|
|
ri->event);
|
|
error(ERR_RECORD, ri);
|
|
}
|
|
e.pte = r->gpl4.pte;
|
|
e.addr = r->gpl4.addr;
|
|
e.eip = r->gpl4.eip;
|
|
break;
|
|
default:
|
|
fprintf(warn, "ERROR: Unknown PV_PTRW minor type %d!\n",
|
|
pevt.minor);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s ptwr l1e %llx eip %llx addr %llx\n",
|
|
ri->dump_header,
|
|
e.pte, e.eip, e.addr);
|
|
}
|
|
}
|
|
|
|
void pv_generic_process(struct record_info *ri, struct pv_data *pv) {
|
|
union pv_event pevt = { .event = ri->event };
|
|
if ( opt.dump_all ) {
|
|
if(pevt.minor < PV_MAX && pv_name[pevt.minor])
|
|
printf(" %s %s",
|
|
ri->dump_header,
|
|
pv_name[pevt.minor]);
|
|
else
|
|
printf(" %s PV-%d ",
|
|
ri->dump_header, pevt.minor);
|
|
if (ri->extra_words) {
|
|
int i;
|
|
printf("[ ");
|
|
for(i=0; i<ri->extra_words; i++) {
|
|
printf("%x ", (unsigned)ri->d[i]);
|
|
}
|
|
printf("]");
|
|
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void pv_summary(struct pv_data *pv) {
|
|
int i, j;
|
|
|
|
if(!pv->summary_info)
|
|
return;
|
|
|
|
printf("PV events:\n");
|
|
for(i=0; i<PV_MAX; i++) {
|
|
int count;
|
|
|
|
count = pv->count[i];
|
|
if (i == PV_HYPERCALL_V2)
|
|
count += pv->count[PV_HYPERCALL_SUBCALL];
|
|
|
|
if (count == 0)
|
|
continue;
|
|
|
|
printf(" %s %d\n", pv_name[i], count);
|
|
|
|
switch(i) {
|
|
case PV_HYPERCALL:
|
|
case PV_HYPERCALL_V2:
|
|
for(j=0; j<PV_HYPERCALL_MAX; j++) {
|
|
if(pv->hypercall_count[j])
|
|
printf(" %-29s[%2d]: %6d\n",
|
|
hypercall_name[j],
|
|
j,
|
|
pv->hypercall_count[j]);
|
|
}
|
|
break;
|
|
case PV_TRAP:
|
|
for(j=0; j<PV_TRAP_MAX; j++) {
|
|
if(pv->trap_count[j])
|
|
printf(" [%d] %d\n",
|
|
j, pv->trap_count[j]);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *grant_table_op_str[] = {
|
|
"map_grant_ref", "unmap_grant_ref", "setup_table", "dump_table",
|
|
"transfer", "copy", "query_size", "unmap_and_replace",
|
|
"set_version", "get_status_frames", "get_version", "swap_grant_ref",
|
|
};
|
|
|
|
static const char *vcpu_op_str[] = {
|
|
"initialise", "up", "down", "is_up", "get_runstate_info",
|
|
"register_runstate_memory_area", "set_periodic_timer",
|
|
"stop_periodic_timer", "set_singleshot_timer", "stop_singleshot_timer",
|
|
"register_vcpu_info", "send_nmi", "get_physid",
|
|
"register_vcpu_time_memory_area",
|
|
};
|
|
|
|
static const char *sched_op_str[] = {
|
|
"yield", "block", "shutdown", "poll", "remote_shutdown", "shutdown_code",
|
|
"watchdog",
|
|
};
|
|
|
|
static const char *cmd_to_str(const char *strings[], size_t n, uint32_t cmd)
|
|
{
|
|
static char buf[32];
|
|
|
|
if (cmd < n)
|
|
return strings[cmd];
|
|
|
|
snprintf(buf, sizeof(buf), "unknown (%d)", cmd);
|
|
return buf;
|
|
}
|
|
|
|
#define CMD_TO_STR(op) \
|
|
static const char * op ## _to_str(uint32_t cmd) { \
|
|
return cmd_to_str(op ## _str, ARRAY_SIZE(op ## _str), cmd); \
|
|
}
|
|
|
|
CMD_TO_STR(grant_table_op);
|
|
CMD_TO_STR(vcpu_op);
|
|
CMD_TO_STR(sched_op);
|
|
|
|
void pv_hypercall_gather_args(const struct record_info *ri, uint64_t *args)
|
|
{
|
|
int i, word;
|
|
|
|
/* Missing arguments are zeroed. */
|
|
memset(args, 0, 6 * sizeof(uint64_t));
|
|
|
|
for (i = 0, word = 1; i < 6 && word < ri->extra_words; i++) {
|
|
int present = pv_hypercall_arg_present(ri, i);
|
|
|
|
switch (present) {
|
|
case ARG_32BIT:
|
|
args[i] = ri->d[word];
|
|
break;
|
|
case ARG_64BIT:
|
|
args[i] = ((uint64_t)ri->d[word + 1] << 32) | ri->d[word];
|
|
break;
|
|
}
|
|
|
|
/* Skip over any words for this argument. */
|
|
word += present;
|
|
}
|
|
}
|
|
|
|
static void pv_hypercall_print_args(const struct record_info *ri)
|
|
{
|
|
int i, word;
|
|
|
|
for (i = 0, word = 1; i < 6 && word < ri->extra_words; i++) {
|
|
int present = pv_hypercall_arg_present(ri, i);
|
|
|
|
switch (present) {
|
|
case ARG_MISSING:
|
|
printf(" ??");
|
|
break;
|
|
case ARG_32BIT:
|
|
printf(" %08x", ri->d[word]);
|
|
break;
|
|
case ARG_64BIT:
|
|
printf(" %016"PRIu64"", ((uint64_t)ri->d[word + 1] << 32) | ri->d[word]);
|
|
break;
|
|
}
|
|
|
|
word += present;
|
|
}
|
|
}
|
|
|
|
void pv_hypercall_v2_process(struct record_info *ri, struct pv_data *pv,
|
|
const char *indent)
|
|
{
|
|
int op = pv_hypercall_op(ri);
|
|
|
|
if(opt.summary_info) {
|
|
if(op < PV_HYPERCALL_MAX)
|
|
pv->hypercall_count[op]++;
|
|
}
|
|
|
|
if(opt.dump_all) {
|
|
uint64_t args[6];
|
|
|
|
if(op < HYPERCALL_MAX)
|
|
printf(" %s%s hypercall %2x (%s)",
|
|
ri->dump_header, indent, op, hypercall_name[op]);
|
|
else
|
|
printf(" %s%s hypercall %2x",
|
|
ri->dump_header, indent, op);
|
|
|
|
switch(op) {
|
|
case HYPERCALL_mmu_update:
|
|
pv_hypercall_gather_args(ri, args);
|
|
printf(" %d updates%s", (uint32_t)args[1] & ~MMU_UPDATE_PREEMPTED,
|
|
(args[1] & MMU_UPDATE_PREEMPTED) ? " (preempted)" : "");
|
|
break;
|
|
case HYPERCALL_multicall:
|
|
pv_hypercall_gather_args(ri, args);
|
|
printf(" %d calls", (uint32_t)args[1]);
|
|
break;
|
|
case HYPERCALL_grant_table_op:
|
|
pv_hypercall_gather_args(ri, args);
|
|
printf(" %s %d ops", grant_table_op_to_str(args[0]), (uint32_t)args[2]);
|
|
break;
|
|
case HYPERCALL_vcpu_op:
|
|
pv_hypercall_gather_args(ri, args);
|
|
printf(" %s vcpu %d", vcpu_op_to_str(args[0]), (uint32_t)args[1]);
|
|
break;
|
|
case HYPERCALL_mmuext_op:
|
|
pv_hypercall_gather_args(ri, args);
|
|
printf(" %d ops", (uint32_t)args[1]);
|
|
break;
|
|
case HYPERCALL_sched_op:
|
|
pv_hypercall_gather_args(ri, args);
|
|
printf(" %s", sched_op_to_str(args[0]));
|
|
break;
|
|
default:
|
|
pv_hypercall_print_args(ri);
|
|
break;
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
void pv_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
struct vcpu_data *v = p->current;
|
|
struct pv_data *pv = &v->pv;
|
|
|
|
union pv_event pevt = { .event = ri->event };
|
|
|
|
if(vcpu_set_data_type(p->current, VCPU_DATA_PV))
|
|
return;
|
|
|
|
if(opt.summary_info) {
|
|
pv->summary_info=1;
|
|
|
|
if(pevt.minor == PV_PTWR_EMULATION_PAE)
|
|
pv->count[PV_PTWR_EMULATION]++;
|
|
else
|
|
pv->count[pevt.minor]++;
|
|
}
|
|
|
|
switch(pevt.minor)
|
|
{
|
|
case PV_HYPERCALL:
|
|
pv_hypercall_process(ri, pv);
|
|
break;
|
|
case PV_TRAP:
|
|
pv_trap_process(ri, pv);
|
|
break;
|
|
case PV_PTWR_EMULATION:
|
|
case PV_PTWR_EMULATION_PAE:
|
|
pv_ptwr_emulation_process(ri, pv);
|
|
break;
|
|
case PV_HYPERCALL_V2:
|
|
pv_hypercall_v2_process(ri, pv, "");
|
|
break;
|
|
case PV_HYPERCALL_SUBCALL:
|
|
pv_hypercall_v2_process(ri, pv, " ");
|
|
break;
|
|
default:
|
|
pv_generic_process(ri, pv);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* ---- Schedule ---- */
|
|
struct vcpu_data * vcpu_create(struct domain_data *d, int vid)
|
|
{
|
|
struct vcpu_data *v;
|
|
|
|
assert(d->vcpu[vid] == NULL);
|
|
|
|
fprintf(warn, "Creating vcpu %d for dom %d\n", vid, d->did);
|
|
|
|
if((v=malloc(sizeof(*v)))==NULL)
|
|
{
|
|
fprintf(stderr, "%s: malloc %zd failed!\n", __func__, sizeof(*d));
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(v, sizeof(*v));
|
|
|
|
v->vid = vid;
|
|
v->d = d;
|
|
v->p = NULL;
|
|
v->runstate.state = RUNSTATE_INIT;
|
|
v->runstate.last_oldstate.wrong = RUNSTATE_INIT;
|
|
|
|
d->vcpu[vid] = v;
|
|
|
|
assert(v == v->d->vcpu[v->vid]);
|
|
|
|
if(vid > d->max_vid)
|
|
d->max_vid = vid;
|
|
|
|
return v;
|
|
}
|
|
|
|
/* Called by both domain_create and sched_default_domain_init */
|
|
void domain_init(struct domain_data *d, int did)
|
|
{
|
|
bzero(d, sizeof(*d));
|
|
|
|
d->did = did;
|
|
d->next = NULL;
|
|
|
|
if(opt.interval.check == INTERVAL_CHECK_DOMAIN)
|
|
interval_domain_value_check(d);
|
|
}
|
|
|
|
struct domain_data * domain_create(int did)
|
|
{
|
|
struct domain_data *d;
|
|
|
|
fprintf(warn, "Creating domain %d\n", did);
|
|
|
|
if((d=malloc(sizeof(*d)))==NULL)
|
|
{
|
|
fprintf(stderr, "%s: malloc %zd failed!\n", __func__, sizeof(*d));
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
/* Initialize domain & vcpus */
|
|
domain_init(d, did);
|
|
|
|
return d;
|
|
}
|
|
|
|
struct domain_data * domain_find(int did)
|
|
{
|
|
struct domain_data *d, *n, **q;
|
|
|
|
/* Look for domain, keeping track of the last pointer so we can add
|
|
a domain if we need to. */
|
|
for ( d = domain_list, q=&domain_list ;
|
|
d && (d->did < did) ;
|
|
q = &d->next, d=d->next ) ;
|
|
|
|
if(d && d->did == did)
|
|
return d;
|
|
|
|
/* Make a new domain */
|
|
n = domain_create(did);
|
|
|
|
/* Insert it into the list */
|
|
n->next = d;
|
|
*q = n;
|
|
|
|
return n;
|
|
}
|
|
|
|
struct vcpu_data * vcpu_find(int did, int vid)
|
|
{
|
|
struct domain_data *d;
|
|
struct vcpu_data *v;
|
|
|
|
d = domain_find(did);
|
|
|
|
v = d->vcpu[vid];
|
|
|
|
if(!v)
|
|
v = vcpu_create(d, vid);
|
|
|
|
return v;
|
|
}
|
|
|
|
void pcpu_runstate_update(struct pcpu_info *p, tsc_t tsc)
|
|
{
|
|
if ( p->time.tsc )
|
|
{
|
|
if ( p->current->d->did == IDLE_DOMAIN )
|
|
update_cycles(&p->time.idle, tsc - p->time.tsc);
|
|
else
|
|
update_cycles(&p->time.running, tsc - p->time.tsc);
|
|
p->time.tsc = 0;
|
|
}
|
|
}
|
|
|
|
void vcpu_prev_update(struct pcpu_info *p, struct vcpu_data *prev,
|
|
tsc_t tsc, int new_runstate)
|
|
{
|
|
assert(prev == prev->d->vcpu[prev->vid]);
|
|
|
|
if(prev->p != p)
|
|
{
|
|
fprintf(warn, "Strange, sched_switch on pcpu %d, prev->pcpu %d!\n",
|
|
p->pid, prev->p->pid);
|
|
prev->runstate.tsc = 0;
|
|
goto set;
|
|
}
|
|
|
|
//assert(p->current);
|
|
|
|
if ( !p->current )
|
|
{
|
|
fprintf(warn, "%s: FATAL: p->current NULL!\n", __func__);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
|
|
if(p->current != prev)
|
|
{
|
|
fprintf(warn, "Strange, sched_switch prev d%dv%d, pcpu %d current d%dv%d!\n",
|
|
prev->d->did, prev->vid,
|
|
p->pid, p->current->d->did, p->current->vid);
|
|
prev->runstate.tsc = 0;
|
|
goto set;
|
|
}
|
|
|
|
if(prev->runstate.state != RUNSTATE_RUNNING)
|
|
{
|
|
fprintf(warn, "Strange, prev d%dv%d not running!\n",
|
|
prev->d->did, prev->vid);
|
|
prev->runstate.tsc = 0;
|
|
goto set;
|
|
}
|
|
|
|
set:
|
|
pcpu_runstate_update(p, tsc);
|
|
p->current = NULL;
|
|
pcpu_string_draw(p);
|
|
runstate_update(prev, new_runstate, tsc);
|
|
}
|
|
|
|
void vcpu_next_update(struct pcpu_info *p, struct vcpu_data *next, tsc_t tsc)
|
|
{
|
|
assert(next == next->d->vcpu[next->vid]);
|
|
//assert(p->current == NULL);
|
|
|
|
if ( p->current != NULL )
|
|
{
|
|
if ( p->lost_record.seen_valid_schedule == 0 )
|
|
{
|
|
fprintf(warn, "%s: p->current non-NULL, but seen_valid_schedule 0. Ignoring.\n",
|
|
__func__);
|
|
runstate_update(p->current, RUNSTATE_LOST, tsc);
|
|
p->current = NULL;
|
|
}
|
|
else
|
|
{
|
|
fprintf(warn, "%s: FATAL: p->current not NULL! (d%dv%d, runstate %s)\n",
|
|
__func__,
|
|
p->current->d->did,
|
|
p->current->vid,
|
|
runstate_name[p->current->runstate.state]);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
}
|
|
|
|
if(next->activated)
|
|
{
|
|
/* We may get lost records at start-of-day, so ignore
|
|
setting runstate of default vcpus */
|
|
if(next->runstate.state == RUNSTATE_RUNNING
|
|
&& next->d->did != DEFAULT_DOMAIN)
|
|
{
|
|
fprintf(warn, "Strange, next d%dv%d already running on proc %d!\n",
|
|
next->d->did, next->vid,
|
|
next->p->pid);
|
|
next->runstate.tsc = 0;
|
|
}
|
|
|
|
/* If we're moving from one pcpu to another, record change & update tsc */
|
|
if(next->p != p) {
|
|
if(next->pcpu_tsc)
|
|
{
|
|
update_cycles(&next->cpu_affinity_all, tsc - next->pcpu_tsc);
|
|
update_cycles(&next->cpu_affinity_pcpu[p->pid], tsc - next->pcpu_tsc);
|
|
}
|
|
next->pcpu_tsc = tsc;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
next->guest_paging_levels = opt.default_guest_paging_levels;
|
|
next->activated = 1;
|
|
next->pcpu_tsc = tsc;
|
|
}
|
|
|
|
runstate_update(next, RUNSTATE_RUNNING, tsc);
|
|
|
|
if ( opt.scatterplot_pcpu
|
|
&& next->d->did != IDLE_DOMAIN
|
|
&& next->d->did != DEFAULT_DOMAIN )
|
|
{
|
|
struct time_struct t;
|
|
|
|
abs_cycles_to_time(tsc, &t);
|
|
|
|
if ( next->p )
|
|
printf("%dv%d %u.%09u %d\n",
|
|
next->d->did, next->vid,
|
|
t.s, t.ns,
|
|
next->p->pid);
|
|
printf("%dv%d %u.%09u %d\n",
|
|
next->d->did, next->vid,
|
|
t.s, t.ns,
|
|
p->pid);
|
|
}
|
|
|
|
next->p = p;
|
|
p->current = next;
|
|
pcpu_string_draw(p);
|
|
p->time.tsc = tsc;
|
|
p->lost_record.seen_valid_schedule = 1;
|
|
}
|
|
|
|
/*
|
|
* If the vcpu in question is in state INIT, we're fixing up from something
|
|
* like start-of-day. Update what we can.
|
|
*/
|
|
void vcpu_start(struct pcpu_info *p, struct vcpu_data *v,
|
|
int old_runstate, int new_runstate, tsc_t ri_tsc) {
|
|
tsc_t tsc;
|
|
|
|
/*
|
|
*
|
|
* Cases:
|
|
* running -> running:
|
|
* v -> running, using p->first_tsc
|
|
* {runnable, blocked} -> running:
|
|
* v -> running, using ri->tsc
|
|
* running -> {runnable, blocked}:
|
|
* Leave v INIT, but clear p->current in case another vcpu is scheduled
|
|
* blocked -> runnable:
|
|
* Leave INIT, and also leave p->current, since we still don't know who's scheduled here
|
|
*/
|
|
|
|
/*
|
|
* NB that a vcpu won't come out of INIT until it starts running somewhere.
|
|
* If this event is pcpu that has already seen a scheduling event, p->current
|
|
* should be null; if this is the first scheduling event on this pcpu,
|
|
* p->current should be the default domain.
|
|
*/
|
|
if( old_runstate == RUNSTATE_RUNNING ) {
|
|
if ( !p->current || p->current->d->did != DEFAULT_DOMAIN) {
|
|
fprintf(stderr, "Strange, p->current not default domain!\n");
|
|
error(ERR_FILE, NULL);
|
|
return;
|
|
|
|
}
|
|
|
|
if(!p->first_tsc) {
|
|
fprintf(stderr, "Strange, p%d first_tsc 0!\n", p->pid);
|
|
error(ERR_FILE, NULL);
|
|
}
|
|
|
|
if(p->first_tsc <= p->current->runstate.tsc) {
|
|
fprintf(stderr, "Strange, first_tsc %llx < default_domain runstate tsc %llx!\n",
|
|
p->first_tsc,
|
|
p->current->runstate.tsc);
|
|
error(ERR_FILE, NULL);
|
|
}
|
|
|
|
/* Change default domain to 'queued' */
|
|
runstate_update(p->current, RUNSTATE_QUEUED, p->first_tsc);
|
|
|
|
/*
|
|
* Set current to NULL, so that if another vcpu (not in INIT)
|
|
* is scheduled here, we don't trip over the check in
|
|
* vcpu_next_update()
|
|
*/
|
|
p->current = NULL;
|
|
}
|
|
|
|
/* TSC skew at start-of-day is hard to deal with. Don't
|
|
* bring a vcpu out of INIT until it's seen to be actually
|
|
* running somewhere. */
|
|
if ( new_runstate != RUNSTATE_RUNNING ) {
|
|
if ( !v->delayed_init ) {
|
|
fprintf(warn, "First schedule for d%dv%d doesn't take us into a running state; leaving in INIT\n",
|
|
v->d->did, v->vid);
|
|
v->delayed_init = 1;
|
|
}
|
|
|
|
return;
|
|
} else if ( v->delayed_init ) {
|
|
fprintf(warn, "d%dv%d RUNSTATE_RUNNING detected, leaving INIT",
|
|
v->d->did, v->vid);
|
|
v->delayed_init = 0;
|
|
}
|
|
|
|
tsc = ri_tsc;
|
|
if ( old_runstate == RUNSTATE_RUNNING ) {
|
|
/* FIXME: Copy over data from the default domain this interval */
|
|
fprintf(warn, "Using first_tsc for d%dv%d (%lld cycles)\n",
|
|
v->d->did, v->vid, p->last_tsc - p->first_tsc);
|
|
|
|
tsc = p->first_tsc;
|
|
}
|
|
|
|
/* Simulate the time since the first tsc */
|
|
runstate_update(v, RUNSTATE_RUNNING, tsc);
|
|
p->time.tsc = tsc;
|
|
p->current = v;
|
|
pcpu_string_draw(p);
|
|
v->p = p;
|
|
}
|
|
|
|
void sched_runstate_process(struct pcpu_info *p)
|
|
{
|
|
enum {
|
|
CHANGE=0,
|
|
CONTINUE
|
|
} type;
|
|
struct vcpu_data *v;
|
|
struct record_info *ri = &p->ri;
|
|
struct {
|
|
unsigned vcpu:16, dom:16;
|
|
unsigned long long p1, p2;
|
|
} __attribute__((packed)) * r = (typeof(r))ri->d;
|
|
union {
|
|
unsigned int event;
|
|
struct {
|
|
unsigned lo:4,
|
|
new_runstate:4,
|
|
old_runstate:4,
|
|
sub:4,
|
|
main:12,
|
|
unused:4;
|
|
};
|
|
} _sevt = { .event = ri->event };
|
|
struct {
|
|
int new_runstate, old_runstate;
|
|
} sevt;
|
|
int perfctrs;
|
|
struct last_oldstate_struct last_oldstate;
|
|
|
|
switch(_sevt.lo)
|
|
{
|
|
case 1:
|
|
type = CHANGE;
|
|
sevt.new_runstate = _sevt.new_runstate;
|
|
sevt.old_runstate = _sevt.old_runstate;
|
|
break;
|
|
case 2:
|
|
type = CONTINUE;
|
|
sevt.new_runstate = sevt.old_runstate = RUNSTATE_RUNNING;
|
|
break;
|
|
default:
|
|
fprintf(warn, "FATAL: Unexpected runstate change type %d!\n",
|
|
_sevt.lo);
|
|
error(ERR_RECORD, NULL);
|
|
return;
|
|
}
|
|
|
|
perfctrs = (ri->extra_words == 5);
|
|
|
|
if(opt.dump_all) {
|
|
if( perfctrs ) {
|
|
printf(" %s %s {%lld,%lld} d%uv%u %s->%s\n",
|
|
ri->dump_header,
|
|
type?"runstate_continue":"runstate_change",
|
|
r->p1, r->p2,
|
|
r->dom, r->vcpu,
|
|
runstate_name[sevt.old_runstate],
|
|
runstate_name[sevt.new_runstate]);
|
|
} else {
|
|
printf(" %s %s d%uv%u %s->%s\n",
|
|
ri->dump_header,
|
|
type?"runstate_continue":"runstate_change",
|
|
r->dom, r->vcpu,
|
|
runstate_name[sevt.old_runstate],
|
|
runstate_name[sevt.new_runstate]);
|
|
}
|
|
}
|
|
|
|
/* Sanity check: expected transitions */
|
|
if ( type == CHANGE )
|
|
{
|
|
if( (sevt.new_runstate == RUNSTATE_RUNNING
|
|
&& sevt.old_runstate != RUNSTATE_RUNNABLE)
|
|
|| (sevt.new_runstate == RUNSTATE_BLOCKED
|
|
&& sevt.old_runstate == RUNSTATE_RUNNABLE ) )
|
|
{
|
|
fprintf(warn, "Strange, d%dv%d unexpected runstate transition %s->%s\n",
|
|
r->dom, r->vcpu,
|
|
runstate_name[sevt.old_runstate],
|
|
runstate_name[sevt.new_runstate]);
|
|
}
|
|
}
|
|
|
|
if(r->vcpu > MAX_CPUS)
|
|
{
|
|
fprintf(warn, "%s: vcpu %u > MAX_VCPUS %d!\n",
|
|
__func__, r->vcpu, MAX_CPUS);
|
|
return;
|
|
}
|
|
|
|
v = vcpu_find(r->dom, r->vcpu);
|
|
|
|
/* We want last_oldstate reset every time; so copy the last one and use
|
|
* that locally, clobbering the one in the vcpu struct. If it needs to
|
|
* be reset, it will be reset below. */
|
|
last_oldstate = v->runstate.last_oldstate;
|
|
v->runstate.last_oldstate.wrong = RUNSTATE_INIT;
|
|
|
|
/* Handle all "start-of-day" issues in one place. This can be
|
|
* done before any of the other tracks or sanity checks. */
|
|
if ( v->runstate.state == RUNSTATE_INIT ) {
|
|
vcpu_start(p, v, sevt.old_runstate, sevt.new_runstate, ri->tsc);
|
|
return;
|
|
}
|
|
|
|
/* Close vmexits when the putative reason for blocking / &c stops.
|
|
* This way, we don't account cpu contention to some other overhead. */
|
|
if(sevt.new_runstate == RUNSTATE_RUNNABLE
|
|
&& v->data_type == VCPU_DATA_HVM
|
|
&& v->hvm.vmexit_valid) {
|
|
hvm_close_vmexit(&v->hvm, ri->tsc);
|
|
}
|
|
|
|
/* Track waking state */
|
|
if ( v->data_type == VCPU_DATA_HVM && v->runstate.state != RUNSTATE_LOST ) {
|
|
if ( sevt.new_runstate == RUNSTATE_RUNNABLE
|
|
&& sevt.old_runstate == RUNSTATE_BLOCKED )
|
|
{
|
|
/* Hmm... want to make sure we're not in some weird
|
|
vmexit state... have to look later. */
|
|
if(opt.dump_all)
|
|
printf(" [w2h] d%dv%d Setting waking\n", v->d->did, v->vid);
|
|
v->hvm.w2h.waking = 1;
|
|
}
|
|
else if ( sevt.new_runstate != RUNSTATE_RUNNING
|
|
|| sevt.old_runstate != RUNSTATE_RUNNABLE )
|
|
{
|
|
if( v->hvm.w2h.waking
|
|
&& sevt.old_runstate == RUNSTATE_RUNNING
|
|
&& sevt.new_runstate != RUNSTATE_OFFLINE )
|
|
{
|
|
/* NB: This is printed a lot unnecessairly when there is TSC skew */
|
|
if ( sevt.old_runstate != v->runstate.state )
|
|
fprintf(warn, "Strange, unexpected waking transition for d%dv%d: %s -> %s\n",
|
|
v->d->did, v->vid,
|
|
runstate_name[sevt.old_runstate],
|
|
runstate_name[sevt.new_runstate]);
|
|
v->hvm.w2h.waking = 0;
|
|
}
|
|
|
|
/* Close wake-to-halt summary */
|
|
/* FIXME: Need to think about handling preemption. */
|
|
if (sevt.new_runstate == RUNSTATE_BLOCKED
|
|
&& sevt.old_runstate == RUNSTATE_RUNNING
|
|
&& v->hvm.w2h.interrupts ) {
|
|
int i;
|
|
for(i=0; i<GUEST_INTERRUPT_MAX; i++) {
|
|
struct hvm_gi_struct *g=v->hvm.summary.guest_interrupt + i;
|
|
tsc_t start_tsc = g->start_tsc;
|
|
if(start_tsc) {
|
|
tsc_t t = (start_tsc == 1) ? 0 : ri->tsc - start_tsc;
|
|
if(opt.dump_all)
|
|
printf(" [w2h] Halting vec %d is_wake %d time %lld\n",
|
|
i,
|
|
g->is_wake,
|
|
t);
|
|
|
|
if(opt.scatterplot_wake_to_halt
|
|
&& t
|
|
&& g->is_wake)
|
|
scatterplot_vs_time(ri->tsc, t);
|
|
|
|
if(opt.summary && t) {
|
|
if(g->is_wake) {
|
|
if(v->hvm.w2h.interrupts==1)
|
|
update_cycles(&g->runtime[GUEST_INTERRUPT_CASE_WAKE_TO_HALT_ALONE],
|
|
t);
|
|
update_cycles(&g->runtime[GUEST_INTERRUPT_CASE_WAKE_TO_HALT_ANY],
|
|
t);
|
|
} else {
|
|
update_cycles(&g->runtime[GUEST_INTERRUPT_CASE_INTERRUPT_TO_HALT],
|
|
t);
|
|
}
|
|
}
|
|
g->start_tsc = 0;
|
|
g->is_wake = 0;
|
|
}
|
|
}
|
|
v->hvm.w2h.interrupts = 0;
|
|
v->hvm.w2h.vector = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Sanity checks / tsc skew detection */
|
|
if( v->runstate.state != sevt.old_runstate
|
|
&& v->runstate.state != RUNSTATE_INIT )
|
|
{
|
|
if(v->runstate.state == RUNSTATE_LOST) {
|
|
if( sevt.new_runstate == RUNSTATE_RUNNING )
|
|
goto update;
|
|
else if(opt.dump_all)
|
|
fprintf(warn, "%s: d%dv%d in runstate lost, not updating to %s\n",
|
|
__func__, v->d->did, v->vid,
|
|
runstate_name[sevt.new_runstate]);
|
|
goto no_update;
|
|
} else if (last_oldstate.wrong == sevt.new_runstate
|
|
&& last_oldstate.actual == sevt.old_runstate) {
|
|
tsc_t lag, old_offset;
|
|
struct pcpu_info *p2;
|
|
|
|
if(ri->tsc < last_oldstate.tsc) {
|
|
fprintf(warn, "WARNING: new tsc %lld < detected runstate tsc %lld! Not updating\n",
|
|
ri->tsc, last_oldstate.tsc);
|
|
goto no_update;
|
|
}
|
|
|
|
p2 = P.pcpu + last_oldstate.pid;
|
|
|
|
lag = ri->tsc
|
|
- last_oldstate.tsc;
|
|
|
|
old_offset = p2->tsc_skew.offset;
|
|
|
|
cpumask_union(&p2->tsc_skew.downstream, &p->tsc_skew.downstream);
|
|
cpumask_set(&p2->tsc_skew.downstream, p->pid);
|
|
|
|
if(cpumask_isset(&p2->tsc_skew.downstream, p2->pid)) {
|
|
if ( opt.tsc_loop_fatal )
|
|
{
|
|
fprintf(stderr, "FATAL: tsc skew dependency loop detected!\n");
|
|
error(ERR_FILE, NULL);
|
|
}
|
|
else
|
|
{
|
|
int i;
|
|
fprintf(warn, "Tsc skew dependency loop detected! Resetting...\n");
|
|
for ( i=0; i<=P.max_active_pcpu; i++)
|
|
{
|
|
struct pcpu_info *p = P.pcpu + i;
|
|
|
|
p->tsc_skew.offset = 0;
|
|
cpumask_init(&p->tsc_skew.downstream);
|
|
}
|
|
goto no_update;
|
|
}
|
|
}
|
|
|
|
p2->tsc_skew.offset += lag * 2;
|
|
|
|
fprintf(warn, "TSC skew detected p%d->p%d, %lld cycles. Changing p%d offset from %lld to %lld\n",
|
|
p->pid, p2->pid, lag,
|
|
p2->pid,
|
|
old_offset,
|
|
p2->tsc_skew.offset);
|
|
|
|
goto no_update;
|
|
} else {
|
|
fprintf(warn, "runstate_change old_runstate %s, d%dv%d runstate %s. Possible tsc skew.\n",
|
|
runstate_name[sevt.old_runstate],
|
|
v->d->did, v->vid,
|
|
runstate_name[v->runstate.state]);
|
|
|
|
v->runstate.last_oldstate.wrong = sevt.old_runstate;
|
|
v->runstate.last_oldstate.actual = v->runstate.state;
|
|
v->runstate.last_oldstate.tsc = ri->tsc;
|
|
v->runstate.last_oldstate.pid = p->pid;
|
|
|
|
if ( v->runstate.state == RUNSTATE_RUNNING )
|
|
{
|
|
fprintf(warn, " Not updating.\n");
|
|
goto no_update;
|
|
}
|
|
goto update;
|
|
}
|
|
fprintf(stderr, "FATAL: Logic hole in %s\n", __func__);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
|
|
update:
|
|
/* Actually update the runstate. Special things to do if we're starting
|
|
* or stopping actually running on a physical cpu. */
|
|
if ( type == CONTINUE )
|
|
{
|
|
/* Continue running. First, do some sanity checks */
|
|
if ( v->runstate.state == RUNSTATE_LOST ) {
|
|
fprintf(warn, "WARNING: continue with d%dv%d in RUNSTATE_LOST. Resetting current.\n",
|
|
v->d->did, v->vid);
|
|
if ( p->current )
|
|
vcpu_prev_update(p, p->current, ri->tsc, RUNSTATE_LOST);
|
|
vcpu_next_update(p, v, ri->tsc);
|
|
}
|
|
else if( v->runstate.state != RUNSTATE_RUNNING ) {
|
|
/* This should never happen. */
|
|
fprintf(warn, "FATAL: sevt.old_runstate running, but d%dv%d runstate %s!\n",
|
|
v->d->did, v->vid, runstate_name[v->runstate.state]);
|
|
error(ERR_FILE, NULL);
|
|
} else if ( v->p != p ) {
|
|
fprintf(warn, "FATAL: continue on p%d, but d%dv%d p%d!\n",
|
|
p->pid, v->d->did, v->vid,
|
|
v->p ? v->p->pid : -1);
|
|
error(ERR_FILE, NULL);
|
|
}
|
|
|
|
runstate_update(v, RUNSTATE_RUNNING, ri->tsc);
|
|
}
|
|
else if ( sevt.old_runstate == RUNSTATE_RUNNING
|
|
|| v->runstate.state == RUNSTATE_RUNNING )
|
|
{
|
|
/*
|
|
* Cases:
|
|
* old running, v running:
|
|
* normal (prev update p, lost record check)
|
|
* v running, old ! running:
|
|
* tsc skew (prev update v->p, lost record check)
|
|
* old running, v init:
|
|
start-of-day (fake update, prev p, lost record)
|
|
* old running, v !{running,init}:
|
|
* # (should never happen)
|
|
*/
|
|
if( sevt.old_runstate == RUNSTATE_RUNNING ) {
|
|
if( v->runstate.state != RUNSTATE_RUNNING
|
|
&& v->runstate.state != RUNSTATE_LOST ) {
|
|
/* This should never happen. */
|
|
fprintf(warn, "FATAL: sevt.old_runstate running, but d%dv%d runstate %s!\n",
|
|
v->d->did, v->vid, runstate_name[v->runstate.state]);
|
|
error(ERR_FILE, NULL);
|
|
}
|
|
|
|
vcpu_prev_update(p, v, ri->tsc, sevt.new_runstate);
|
|
} else {
|
|
vcpu_prev_update(v->p, v, ri->tsc, sevt.new_runstate);
|
|
}
|
|
|
|
if(P.lost_cpus && v->d->did != IDLE_DOMAIN) {
|
|
if(opt.dump_all)
|
|
fprintf(warn, "%s: %d lost cpus, setting d%dv%d runstate to RUNSTATE_LOST\n",
|
|
__func__, P.lost_cpus, v->d->did, v->vid);
|
|
lose_vcpu(v, ri->tsc);
|
|
}
|
|
}
|
|
else if ( sevt.new_runstate == RUNSTATE_RUNNING )
|
|
{
|
|
if(perfctrs) {
|
|
v->runstate.p1_start = r->p1;
|
|
v->runstate.p2_start = r->p2;
|
|
}
|
|
|
|
vcpu_next_update(p, v, ri->tsc);
|
|
}
|
|
else
|
|
{
|
|
runstate_update(v, sevt.new_runstate, ri->tsc);
|
|
}
|
|
|
|
no_update:
|
|
return;
|
|
}
|
|
|
|
void dump_sched_switch(struct record_info *ri)
|
|
{
|
|
struct {
|
|
unsigned int prev_dom, prev_vcpu, next_dom, next_vcpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_switch prev d%uv%u next d%uv%u\n",
|
|
ri->dump_header, r->prev_dom, r->prev_vcpu,
|
|
r->next_dom, r->next_vcpu);
|
|
}
|
|
|
|
void sched_switch_process(struct pcpu_info *p)
|
|
{
|
|
struct vcpu_data *prev, *next;
|
|
struct record_info *ri = &p->ri;
|
|
struct {
|
|
unsigned int prev_dom, prev_vcpu, next_dom, next_vcpu;
|
|
} * r = (typeof(r))ri->d;
|
|
|
|
if(opt.dump_all)
|
|
dump_sched_switch(ri);
|
|
|
|
if(r->prev_vcpu > MAX_CPUS)
|
|
{
|
|
fprintf(warn, "%s: prev_vcpu %u > MAX_VCPUS %d!\n",
|
|
__func__, r->prev_vcpu, MAX_CPUS);
|
|
return;
|
|
}
|
|
|
|
if(r->next_vcpu > MAX_CPUS)
|
|
{
|
|
fprintf(warn, "%s: next_vcpu %u > MAX_VCPUS %d!\n",
|
|
__func__, r->next_vcpu, MAX_CPUS);
|
|
return;
|
|
}
|
|
|
|
prev = vcpu_find(r->prev_dom, r->prev_vcpu);
|
|
next = vcpu_find(r->next_dom, r->next_vcpu);
|
|
|
|
vcpu_prev_update(p, prev, ri->tsc, RUNSTATE_QUEUED); /* FIXME */
|
|
|
|
vcpu_next_update(p, next, ri->tsc);
|
|
}
|
|
|
|
void sched_default_vcpu_activate(struct pcpu_info *p)
|
|
{
|
|
struct vcpu_data *v = default_domain.vcpu[p->pid];
|
|
|
|
if(!v)
|
|
v = vcpu_create(&default_domain, p->pid);
|
|
|
|
assert(v == v->d->vcpu[v->vid]);
|
|
|
|
v->activated = 1;
|
|
v->guest_paging_levels = opt.default_guest_paging_levels;
|
|
v->p = p;
|
|
v->runstate.state = RUNSTATE_RUNNING;
|
|
|
|
p->current = v;
|
|
pcpu_string_draw(p);
|
|
}
|
|
|
|
void sched_default_domain_init(void)
|
|
{
|
|
struct domain_data *d = &default_domain;
|
|
|
|
domain_init(d, DEFAULT_DOMAIN);
|
|
}
|
|
|
|
void runstate_clear(tsc_t * runstate_cycles)
|
|
{
|
|
int i;
|
|
for(i=0; i<RUNSTATE_MAX; i++)
|
|
runstate_cycles[i]=0;
|
|
}
|
|
|
|
void runstate_summary(tsc_t * runstate_cycles)
|
|
{
|
|
int i;
|
|
for(i=0; i<RUNSTATE_MAX; i++)
|
|
if(runstate_cycles[i]) {
|
|
struct time_struct t;
|
|
cycles_to_time(runstate_cycles[i], &t);
|
|
printf(" %s: %u.%09u s\n",
|
|
runstate_name[i], t.s, t.ns);
|
|
}
|
|
}
|
|
|
|
void sched_summary_vcpu(struct vcpu_data *v)
|
|
{
|
|
int i;
|
|
char desc[30];
|
|
|
|
/* FIXME: Update all records like this */
|
|
if ( v->pcpu_tsc )
|
|
{
|
|
update_cycles(&v->cpu_affinity_all, P.f.last_tsc - v->pcpu_tsc);
|
|
update_cycles(&v->cpu_affinity_pcpu[v->p->pid], P.f.last_tsc - v->pcpu_tsc);
|
|
}
|
|
|
|
printf(" Runstates:\n");
|
|
for(i=0; i<RUNSTATE_MAX; i++) {
|
|
snprintf(desc,30, " %8s", runstate_name[i]);
|
|
print_cycle_summary(v->runstates+i, desc);
|
|
if ( i==RUNSTATE_RUNNABLE )
|
|
{
|
|
int j;
|
|
for(j=0; j<RUNNABLE_STATE_MAX; j++) {
|
|
if ( j == RUNNABLE_STATE_INVALID )
|
|
continue;
|
|
snprintf(desc,30, " %8s", runnable_state_name[j]);
|
|
print_cycle_summary(v->runnable_states+j, desc);
|
|
}
|
|
}
|
|
}
|
|
print_cpu_affinity(&v->cpu_affinity_all, " cpu affinity");
|
|
for ( i = 0; i < MAX_CPUS ; i++)
|
|
{
|
|
snprintf(desc,30, " [%d]", i);
|
|
print_cpu_affinity(v->cpu_affinity_pcpu+i, desc);
|
|
}
|
|
}
|
|
|
|
void sched_summary_domain(struct domain_data *d)
|
|
{
|
|
int i;
|
|
char desc[30];
|
|
|
|
printf(" Runstates:\n");
|
|
for(i=0; i<DOMAIN_RUNSTATE_MAX; i++) {
|
|
snprintf(desc,30, " %8s", domain_runstate_name[i]);
|
|
print_cycle_summary(d->runstates+i, desc);
|
|
}
|
|
}
|
|
|
|
void dump_sched_vcpu_action(struct record_info *ri, const char *action)
|
|
{
|
|
struct {
|
|
unsigned int domid, vcpuid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s %s d%uv%u\n", ri->dump_header, action, r->domid, r->vcpuid);
|
|
}
|
|
|
|
void sched_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
if(ri->evt.sub == 0xf) {
|
|
switch(ri->event)
|
|
{
|
|
case TRC_SCHED_SWITCH:
|
|
sched_switch_process(p);
|
|
break;
|
|
default:
|
|
process_generic(&p->ri);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if(ri->evt.sub == 1) {
|
|
/* TRC_SCHED_MIN */
|
|
sched_runstate_process(p);
|
|
} else if (ri->evt.sub == 8) {
|
|
/* TRC_SCHED_VERBOSE */
|
|
switch(ri->event)
|
|
{
|
|
case TRC_SCHED_DOM_ADD:
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_init_domain d%u\n", ri->dump_header, r->domid);
|
|
}
|
|
break;
|
|
case TRC_SCHED_DOM_REM:
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_destroy_domain d%u\n", ri->dump_header, r->domid);
|
|
}
|
|
break;
|
|
case TRC_SCHED_SLEEP:
|
|
if(opt.dump_all)
|
|
dump_sched_vcpu_action(ri, "vcpu_sleep");
|
|
break;
|
|
case TRC_SCHED_WAKE:
|
|
if(opt.dump_all)
|
|
dump_sched_vcpu_action(ri, "vcpu_wake");
|
|
break;
|
|
case TRC_SCHED_YIELD:
|
|
if(opt.dump_all)
|
|
dump_sched_vcpu_action(ri, "vcpu_yield");
|
|
break;
|
|
case TRC_SCHED_BLOCK:
|
|
if(opt.dump_all)
|
|
dump_sched_vcpu_action(ri, "vcpu_block");
|
|
break;
|
|
case TRC_SCHED_SHUTDOWN:
|
|
case TRC_SCHED_SHUTDOWN_CODE:
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid, reason;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s %s d%uv%u, reason = %u\n", ri->dump_header,
|
|
ri->event == TRC_SCHED_SHUTDOWN ? "sched_shutdown" :
|
|
"sched_shutdown_code", r->domid, r->vcpuid, r->reason);
|
|
}
|
|
break;
|
|
case TRC_SCHED_ADJDOM:
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_adjust d%u\n", ri->dump_header, r->domid);
|
|
}
|
|
break;
|
|
case TRC_SCHED_SWITCH:
|
|
if(opt.dump_all)
|
|
dump_sched_switch(ri);
|
|
break;
|
|
case TRC_SCHED_SWITCH_INFPREV:
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid, runtime;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_switch prev d%uv%d, run for %u.%uus\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->runtime / 1000, r->runtime % 1000);
|
|
}
|
|
break;
|
|
case TRC_SCHED_SWITCH_INFNEXT:
|
|
if(opt.dump_all)
|
|
{
|
|
struct {
|
|
unsigned int domid, vcpuid, rsince;
|
|
int slice;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_switch next d%uv%u", ri->dump_header,
|
|
r->domid, r->vcpuid);
|
|
if ( r->rsince != 0 )
|
|
printf(", was runnable for %u.%uus", r->rsince / 1000,
|
|
r->rsince % 1000);
|
|
if ( r->slice > 0 )
|
|
printf(", next slice %u.%uus", r->slice / 1000,
|
|
r->slice % 1000);
|
|
printf("\n");
|
|
}
|
|
break;
|
|
case TRC_SCHED_SWITCH_INFCONT:
|
|
if(opt.dump_all)
|
|
{
|
|
struct {
|
|
unsigned int domid, vcpuid, rsince;
|
|
int slice;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s sched_switch continue d%uv%u, run for %u.%uus",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->rsince / 1000, r->rsince % 1000);
|
|
if ( r->slice > 0 )
|
|
printf(", next slice %u.%uus", r->slice / 1000,
|
|
r->slice % 1000);
|
|
printf("\n");
|
|
}
|
|
break;
|
|
case TRC_SCHED_CTL:
|
|
case TRC_SCHED_S_TIMER_FN:
|
|
case TRC_SCHED_T_TIMER_FN:
|
|
case TRC_SCHED_DOM_TIMER_FN:
|
|
break;
|
|
default:
|
|
process_generic(&p->ri);
|
|
}
|
|
} else if(ri->evt.sub == 2) {
|
|
/* TRC_SCHED_CLASS */
|
|
switch(ri->event)
|
|
{
|
|
/* CREDIT (TRC_CSCHED_xxx) */
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 1): /* SCHED_TASKLET */
|
|
if(opt.dump_all)
|
|
printf(" %s csched:sched_tasklet\n", ri->dump_header);
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 2): /* ACCOUNT_START */
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 3): /* ACCOUNT_STOP */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid, actv_cnt;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:acct_%s d%uv%u, active_vcpus %u\n",
|
|
ri->dump_header,
|
|
ri->event == TRC_SCHED_CLASS_EVT(CSCHED, 2) ?
|
|
"start" : "stop",
|
|
r->domid, r->vcpuid,
|
|
r->actv_cnt);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 4): /* STOLEN_VCPU */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int peer_cpu, domid, vcpuid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:stolen_vcpu d%uv%u from cpu %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->peer_cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 5): /* PICKED_CPU */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid, cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:pick_cpu %u for d%uv%u\n",
|
|
ri->dump_header, r->cpu, r->domid, r->vcpuid);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 6): /* TICKLE */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:runq_tickle, cpu %u\n",
|
|
ri->dump_header, r->cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 7): /* BOOST_START */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched: d%uv%u boosted\n",
|
|
ri->dump_header, r->domid, r->vcpuid);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 8): /* BOOST_END */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid, vcpuid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched: d%uv%u unboosted\n",
|
|
ri->dump_header, r->domid, r->vcpuid);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 9): /* SCHEDULE */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int cpu:16, tasklet:8, idle:8;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:schedule cpu %u%s%s\n",
|
|
ri->dump_header, r->cpu,
|
|
r->tasklet ? ", tasklet scheduled" : "",
|
|
r->idle ? ", idle" : ", busy");
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 10): /* RATELIMIT */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
unsigned int runtime;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:ratelimit, d%uv%u run only %u.%uus\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->runtime / 1000, r->runtime % 1000);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED, 11): /* STEAL_CHECK */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int peer_cpu, check;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched:load_balance %s %u\n",
|
|
ri->dump_header, r->check ? "checking" : "skipping",
|
|
r->peer_cpu);
|
|
}
|
|
break;
|
|
/* CREDIT 2 (TRC_CSCHED2_xxx) */
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 1): /* TICK */
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 4): /* CREDIT_ADD */
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 2): /* RUNQ_POS */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16, pos;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:runq_insert d%uv%u, position %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->pos);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 3): /* CREDIT_BURN */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
int credit, budget, delta;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:burn_credits d%uv%u, credit = %d, ",
|
|
ri->dump_header, r->domid, r->vcpuid, r->credit);
|
|
if ( r->budget != INT_MIN )
|
|
printf("budget = %d, ", r->budget);
|
|
printf("delta = %d\n", r->delta);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 5): /* TICKLE_CHECK */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
int credit, score;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:tickle_check d%uv%u, credit = %d, score = %d\n\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->credit, r->score);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 6): /* TICKLE */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int cpu:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:runq_tickle cpu %u\n",
|
|
ri->dump_header, r->cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 7): /* CREDIT_RESET */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
int credit_start, credit_end;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:reset_credits d%uv%u, "
|
|
"credit_start = %d, credit_end = %d\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->credit_start, r->credit_end);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 8): /* SCHED_TASKLET */
|
|
if(opt.dump_all)
|
|
printf(" %s csched2:sched_tasklet\n", ri->dump_header);
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 9): /* UPDATE_LOAD */
|
|
if(opt.dump_all)
|
|
printf(" %s csched2:update_load\n", ri->dump_header);
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 10): /* RUNQ_ASSIGN */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
unsigned int rqi:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:runq_assign d%uv%u on rq# %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->rqi);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 11): /* UPDATE_VCPU_LOAD */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
uint64_t vcpuload;
|
|
unsigned int vcpuid:16, domid:16;
|
|
unsigned int shift;
|
|
} *r = (typeof(r))ri->d;
|
|
double vcpuload;
|
|
|
|
vcpuload = (r->vcpuload * 100.0) / (1ULL << r->shift);
|
|
|
|
printf(" %s csched2:update_vcpu_load d%uv%u, "
|
|
"vcpu_load = %4.3f%% (%"PRIu64")\n",
|
|
ri->dump_header, r->domid, r->vcpuid, vcpuload,
|
|
r->vcpuload);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 12): /* UPDATE_RUNQ_LOAD */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
uint64_t rq_avgload, b_avgload;
|
|
unsigned int rq_load:16, rq_id:8, shift:8;
|
|
} *r = (typeof(r))ri->d;
|
|
double avgload, b_avgload;
|
|
|
|
avgload = (r->rq_avgload * 100.0) / (1ULL << r->shift);
|
|
b_avgload = (r->b_avgload * 100.0) / (1ULL << r->shift);
|
|
|
|
printf(" %s csched2:update_rq_load rq# %u, load = %u, "
|
|
"avgload = %4.3f%% (%"PRIu64"), "
|
|
"b_avgload = %4.3f%% (%"PRIu64")\n",
|
|
ri->dump_header, r->rq_id, r->rq_load,
|
|
avgload, r->rq_avgload, b_avgload, r->b_avgload);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 13): /* TICKLE_NEW */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16, processor;
|
|
int credit;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:runq_tickle_new d%uv%u, "
|
|
"processor = %u, credit = %d\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->processor, r->credit);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 14): /* RUNQ_MAX_WEIGHT */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned rqi:16, max_weight:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:update_max_weight rq# %u, max_weight = %u\n",
|
|
ri->dump_header, r->rqi, r->max_weight);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 15): /* MIGRATE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned vcpuid:16, domid:16;
|
|
unsigned rqi:16, trqi:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:migrate d%uv%u rq# %u --> rq# %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->rqi, r->trqi);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 16): /* LOAD_CHECK */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned lrqi:16, orqi:16;
|
|
unsigned load_delta;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:load_balance_check lrq# %u, orq# %u, "
|
|
"delta = %u\n",
|
|
ri->dump_header, r->lrqi, r->orqi, r->load_delta);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 17): /* LOAD_BALANCE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint64_t lb_avgload, ob_avgload;
|
|
unsigned lrqi:16, orqi:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:load_balance_begin lrq# %u, "
|
|
"avg_load = %"PRIu64" -- orq# %u, avg_load = %"PRIu64"\n",
|
|
ri->dump_header, r->lrqi, r->lb_avgload,
|
|
r->orqi, r->ob_avgload);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 19): /* PICKED_CPU */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint64_t b_avgload;
|
|
unsigned vcpuid:16, domid:16;
|
|
unsigned rqi:16, cpu:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:picked_cpu d%uv%u, rq# %u, cpu %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->rqi, r->cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 20): /* RUNQ_CANDIDATE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned vcpuid:16, domid:16;
|
|
unsigned tickled_cpu;
|
|
int credit;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:runq_candidate d%uv%u, credit = %d, ",
|
|
ri->dump_header, r->domid, r->vcpuid, r->credit);
|
|
if (r->tickled_cpu == (unsigned)-1)
|
|
printf("no cpu was tickled\n");
|
|
else
|
|
printf("cpu %u was tickled\n", r->tickled_cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 21): /* SCHEDULE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned cpu:16, rqi:16;
|
|
unsigned tasklet:8, idle:8, smt_idle:8, tickled:8;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:schedule cpu %u, rq# %u%s%s%s%s\n",
|
|
ri->dump_header, r->cpu, r->rqi,
|
|
r->tasklet ? ", tasklet scheduled" : "",
|
|
r->idle ? ", idle" : ", busy",
|
|
r->idle ? (r->smt_idle ? ", SMT idle" : ", SMT busy") : "",
|
|
r->tickled ? ", tickled" : ", not tickled");
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 22): /* RATELIMIT */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
unsigned int runtime;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:ratelimit, d%uv%u run only %u.%uus\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->runtime / 1000, r->runtime % 1000);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(CSCHED2, 23): /* RUNQ_CAND_CHECK */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s csched2:runq_cand_check d%uv%u\n",
|
|
ri->dump_header, r->domid, r->vcpuid);
|
|
}
|
|
break;
|
|
/* RTDS (TRC_RTDS_xxx) */
|
|
case TRC_SCHED_CLASS_EVT(RTDS, 1): /* TICKLE */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int cpu:16;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s rtds:runq_tickle cpu %u\n",
|
|
ri->dump_header, r->cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(RTDS, 2): /* RUNQ_PICK */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
uint64_t cur_dl, cur_bg;
|
|
} __attribute__((packed)) *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s rtds:runq_pick d%uv%u, deadline = %"PRIu64", "
|
|
"budget = %"PRIu64"\n", ri->dump_header,
|
|
r->domid, r->vcpuid, r->cur_dl, r->cur_bg);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(RTDS, 3): /* BUDGET_BURN */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
uint64_t cur_bg;
|
|
int delta;
|
|
} __attribute__((packed)) *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s rtds:burn_budget d%uv%u, budget = %"PRIu64", "
|
|
"delta = %d\n", ri->dump_header, r->domid,
|
|
r->vcpuid, r->cur_bg, r->delta);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(RTDS, 4): /* BUDGET_REPLENISH */
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int vcpuid:16, domid:16;
|
|
uint64_t cur_dl, cur_bg;
|
|
} __attribute__((packed)) *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s rtds:repl_budget d%uv%u, deadline = %"PRIu64", "
|
|
"budget = %"PRIu64"\n", ri->dump_header,
|
|
r->domid, r->vcpuid, r->cur_dl, r->cur_bg);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(RTDS, 5): /* SCHED_TASKLET */
|
|
if(opt.dump_all)
|
|
printf(" %s rtds:sched_tasklet\n", ri->dump_header);
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(RTDS, 6): /* SCHEDULE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
unsigned cpu:16, tasklet:8, idle:4, tickled:4;
|
|
} __attribute__((packed)) *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s rtds:schedule cpu %u, %s%s%s\n",
|
|
ri->dump_header, r->cpu,
|
|
r->tasklet ? ", tasklet scheduled" : "",
|
|
r->idle ? ", idle" : ", busy",
|
|
r->tickled ? ", tickled" : ", not tickled");
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(SNULL, 1): /* PICKED_CPU */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint16_t vcpuid, domid;
|
|
uint32_t new_cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s null:picked_cpu d%uv%u, cpu %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->new_cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(SNULL, 2): /* VCPU_ASSIGN */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint16_t vcpuid, domid;
|
|
uint32_t cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s null:vcpu_assign d%uv%u to cpu %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(SNULL, 3): /* VCPU_DEASSIGN */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint16_t vcpuid, domid;
|
|
uint32_t cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s null:vcpu_deassign d%uv%u from cpu %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid, r->cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(SNULL, 4): /* MIGRATE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint16_t vcpuid, domid;
|
|
uint16_t cpu, new_cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s null:migrate d%uv%u, cpu %u, new_cpu %u\n",
|
|
ri->dump_header, r->domid, r->vcpuid,
|
|
r->cpu, r->new_cpu);
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(SNULL, 5): /* SCHEDULE */
|
|
if (opt.dump_all) {
|
|
struct {
|
|
uint16_t tasklet, cpu;
|
|
int16_t vcpuid, domid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s null:schedule cpu %u%s",
|
|
ri->dump_header, r->cpu,
|
|
r->tasklet ? ", tasklet scheduled" : "");
|
|
if (r->vcpuid != -1)
|
|
printf(", vcpu d%uv%d\n", r->domid, r->vcpuid);
|
|
else
|
|
printf(", no vcpu\n");
|
|
}
|
|
break;
|
|
case TRC_SCHED_CLASS_EVT(SNULL, 6): /* TASKLET */
|
|
if (opt.dump_all)
|
|
printf(" %s null:sched_tasklet\n", ri->dump_header);
|
|
break;
|
|
default:
|
|
process_generic(ri);
|
|
}
|
|
} else {
|
|
UPDATE_VOLUME(p, sched_verbose, ri->size);
|
|
process_generic(&p->ri);
|
|
}
|
|
}
|
|
|
|
/* ---- Memory ---- */
|
|
void mem_summary_domain(struct domain_data *d) {
|
|
int i, j;
|
|
|
|
printf(" Grant table ops:\n");
|
|
|
|
printf(" Done by:\n");
|
|
for(i=0; i<MEM_MAX; i++)
|
|
if(d->memops.done[i])
|
|
printf(" %-14s: %d\n",
|
|
mem_name[i],
|
|
d->memops.done[i]);
|
|
|
|
printf(" Done for:\n");
|
|
for(i=0; i<MEM_MAX; i++)
|
|
if(d->memops.done_for[i])
|
|
printf(" %-14s: %d\n",
|
|
mem_name[i],
|
|
d->memops.done_for[i]);
|
|
|
|
printf(" Populate-on-demand:\n");
|
|
printf(" Populated:\n");
|
|
for(i=0; i<4; i++)
|
|
{
|
|
if ( d->pod.populate_order[i] )
|
|
printf(" [%d] %d\n", i,
|
|
d->pod.populate_order[i]);
|
|
}
|
|
printf(" Reclaim order:\n");
|
|
for(i=0; i<4; i++)
|
|
{
|
|
if ( d->pod.reclaim_order[i] )
|
|
printf(" [%d] %d\n", i,
|
|
d->pod.reclaim_order[i]);
|
|
}
|
|
printf(" Reclaim contexts:\n");
|
|
for(j=0; j<POD_RECLAIM_CONTEXT_MAX; j++)
|
|
{
|
|
if ( d->pod.reclaim_context[j] )
|
|
{
|
|
printf(" * [%s] %d\n",
|
|
pod_reclaim_context_name[j],
|
|
d->pod.reclaim_context[j]);
|
|
for(i=0; i<4; i++)
|
|
{
|
|
if ( d->pod.reclaim_context_order[j][i] )
|
|
printf(" [%d] %d\n", i,
|
|
d->pod.reclaim_context_order[j][i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int p2m_canonical_order(int order)
|
|
{
|
|
if ( order % 9
|
|
|| (order / 9) > 2 )
|
|
{
|
|
fprintf(warn, "%s: Strange, non-canonical order %d\n",
|
|
__func__, order);
|
|
order = 4;
|
|
} else {
|
|
order /= 9;
|
|
}
|
|
return order;
|
|
}
|
|
|
|
void mem_pod_zero_reclaim_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
int context = POD_RECLAIM_CONTEXT_UNKNOWN;
|
|
struct vcpu_data *v = p->current;
|
|
|
|
struct {
|
|
uint64_t gfn, mfn;
|
|
uint32_t d, order;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( v && v->hvm.vmexit_valid )
|
|
{
|
|
switch(v->hvm.exit_reason)
|
|
{
|
|
case EXIT_REASON_EPT_VIOLATION:
|
|
case EXIT_REASON_EXCEPTION_NMI:
|
|
context = POD_RECLAIM_CONTEXT_FAULT;
|
|
break;
|
|
case EXIT_REASON_VMCALL:
|
|
context = POD_RECLAIM_CONTEXT_BALLOON;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s pod_zero_reclaim d%d o%d g %llx m %llx ctx %s\n",
|
|
ri->dump_header,
|
|
r->d, r->order,
|
|
(unsigned long long)r->gfn, (unsigned long long)r->mfn,
|
|
pod_reclaim_context_name[context]);
|
|
|
|
}
|
|
|
|
if ( opt.summary_info )
|
|
{
|
|
struct domain_data *d;
|
|
|
|
if ( v && (d=v->d) )
|
|
{
|
|
int order;
|
|
|
|
order = p2m_canonical_order(r->order);
|
|
|
|
d->pod.reclaim_order[order]++;
|
|
d->pod.reclaim_context[context]++;
|
|
d->pod.reclaim_context_order[context][order]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void mem_pod_populate_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
struct {
|
|
uint64_t gfn, mfn;
|
|
uint32_t d, order;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s pod_populate d%d o%d g %llx m %llx\n",
|
|
ri->dump_header,
|
|
r->d, r->order,
|
|
(unsigned long long)r->gfn, (unsigned long long)r->mfn);
|
|
}
|
|
|
|
if ( opt.summary_info )
|
|
{
|
|
struct vcpu_data *v = p->current;
|
|
struct domain_data *d;
|
|
|
|
if ( v && (d=v->d) )
|
|
{
|
|
int order;
|
|
|
|
order = p2m_canonical_order(r->order);
|
|
|
|
d->pod.populate_order[order]++;
|
|
}
|
|
}
|
|
}
|
|
|
|
void mem_pod_superpage_splinter_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
struct {
|
|
uint64_t gfn;
|
|
uint32_t d, order;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s pod_spage_splinter d%d o%d g %"PRIx64"\n",
|
|
ri->dump_header,
|
|
r->d, r->order, r->gfn);
|
|
}
|
|
}
|
|
|
|
void mem_page_grant(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
struct {
|
|
unsigned domain;
|
|
} *r = (typeof(r))ri->d;
|
|
union pv_event pevt = { .event = ri->event };
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s %s domain %u\n", ri->dump_header, mem_name[pevt.minor], r->domain);
|
|
}
|
|
}
|
|
void mem_set_p2m_entry_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
struct {
|
|
uint64_t gfn, mfn;
|
|
uint32_t p2mt;
|
|
uint16_t d, order;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s set_p2m_entry d%u o%u t %u g %llx m %llx\n",
|
|
ri->dump_header,
|
|
r->d, r->order,
|
|
r->p2mt,
|
|
(unsigned long long)r->gfn, (unsigned long long)r->mfn);
|
|
}
|
|
}
|
|
|
|
void mem_decrease_reservation_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
struct {
|
|
uint64_t gfn;
|
|
uint32_t d, order;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s decrease_reservation d%d o%d g %llx\n",
|
|
ri->dump_header,
|
|
r->d, r->order,
|
|
(unsigned long long)r->gfn);
|
|
}
|
|
}
|
|
|
|
void mem_process(struct pcpu_info *p) {
|
|
struct record_info *ri = &p->ri;
|
|
struct {
|
|
int dom;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
int minor = ri->evt.minor;
|
|
|
|
switch ( minor )
|
|
{
|
|
case MEM_PAGE_GRANT_MAP:
|
|
case MEM_PAGE_GRANT_UNMAP:
|
|
case MEM_PAGE_GRANT_TRANSFER:
|
|
mem_page_grant(p);
|
|
break;
|
|
case MEM_SET_P2M_ENTRY:
|
|
mem_set_p2m_entry_process(p);
|
|
break;
|
|
case MEM_DECREASE_RESERVATION:
|
|
mem_decrease_reservation_process(p);
|
|
break;
|
|
case MEM_POD_POPULATE:
|
|
mem_pod_populate_process(p);
|
|
break;
|
|
case MEM_POD_ZERO_RECLAIM:
|
|
mem_pod_zero_reclaim_process(p);
|
|
break;
|
|
case MEM_POD_SUPERPAGE_SPLINTER:
|
|
mem_pod_superpage_splinter_process(p);
|
|
break;
|
|
default:
|
|
if(opt.dump_all) {
|
|
dump_generic(stdout, ri);
|
|
}
|
|
|
|
if(opt.summary_info && minor < MEM_MAX) {
|
|
struct domain_data *d;
|
|
|
|
if(p->current) {
|
|
if (p->current->d) {
|
|
p->current->d->memops.done[minor]++;
|
|
p->current->d->memops.done_interval[minor]++;
|
|
}
|
|
if((d=domain_find(r->dom))) {
|
|
d->memops.done_for[minor]++;
|
|
d->memops.done_for_interval[minor]++;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/* ---- PM ---- */
|
|
#define CSTATE_MAX 5
|
|
#define CSTATE_INVALID ((CSTATE_MAX)+1)
|
|
void pm_process(struct pcpu_info *p) {
|
|
struct record_info *ri = &p->ri;
|
|
|
|
switch ( ri->event )
|
|
{
|
|
case TRC_PM_FREQ_CHANGE:
|
|
if (opt.dump_all )
|
|
printf(" %s pm_freq_change o%d n%d\n",
|
|
ri->dump_header,
|
|
ri->d[0],
|
|
ri->d[1]);
|
|
break;
|
|
case TRC_PM_IDLE_ENTRY:
|
|
if (opt.dump_all )
|
|
printf(" %s pm_idle_start c%d\n",
|
|
ri->dump_header,
|
|
ri->d[0]);
|
|
if ( ri->d[0] <= CSTATE_MAX )
|
|
{
|
|
p->power_state=ri->d[0];
|
|
pcpu_string_draw(p);
|
|
}
|
|
break;
|
|
case TRC_PM_IDLE_EXIT:
|
|
if (opt.dump_all )
|
|
printf(" %s pm_idle_end c%d\n",
|
|
ri->dump_header,
|
|
ri->d[0]);
|
|
if ( p->power_state != ri->d[0]
|
|
&& p->power_state != CSTATE_INVALID )
|
|
printf("Strange, pm_idle_end %d, power_state %d!\n",
|
|
ri->d[0], p->power_state);
|
|
p->power_state = 0;
|
|
pcpu_string_draw(p);
|
|
break;
|
|
default:
|
|
if(opt.dump_all) {
|
|
dump_generic(stdout, ri);
|
|
}
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* IRQ related stuff
|
|
*/
|
|
|
|
#define MAX_VECTOR 256
|
|
int global_vector_used[256] = {0};
|
|
struct pci_dev {
|
|
uint8_t bus;
|
|
uint8_t devfn;
|
|
int vector_used[MAX_VECTOR];
|
|
struct pci_dev *next;
|
|
} *pdev_list;
|
|
|
|
#define MAX_IRQ 512
|
|
struct irq_desc {
|
|
enum {
|
|
IRQ_NONE,
|
|
IRQ_MSI,
|
|
IRQ_GSI
|
|
} type;
|
|
struct pci_dev *dev;
|
|
} irq_table[MAX_IRQ];
|
|
|
|
struct pci_dev * pdev_find(uint8_t bus, uint8_t devfn)
|
|
{
|
|
struct pci_dev *d, *n, **q;
|
|
|
|
/* Look for domain, keeping track of the last pointer so we can add
|
|
a domain if we need to. */
|
|
for ( d = pdev_list, q=&pdev_list ;
|
|
d && ( (d->bus < bus)
|
|
|| (d->bus == bus && d->devfn < devfn) ) ;
|
|
q = &d->next, d=d->next ) ;
|
|
|
|
if(d && d->bus == bus && d->devfn == devfn)
|
|
return d;
|
|
|
|
/* Make a new domain */
|
|
fprintf(warn, "Creating pdev %02x:%02x.%x\n", bus, devfn>>4, devfn&3);
|
|
|
|
if((n=malloc(sizeof(*n)))==NULL)
|
|
{
|
|
fprintf(stderr, "%s: malloc %zd failed!\n", __func__, sizeof(*n));
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
bzero(n, sizeof(*n));
|
|
|
|
n->bus=bus;
|
|
n->devfn=devfn;
|
|
|
|
/* Insert it into the list */
|
|
n->next = d;
|
|
*q = n;
|
|
|
|
return n;
|
|
}
|
|
|
|
void irq_process(struct pcpu_info *p) {
|
|
struct record_info *ri = &p->ri;
|
|
|
|
switch ( ri->event )
|
|
{
|
|
case TRC_HW_IRQ_BIND_VECTOR:
|
|
{
|
|
struct {
|
|
int irq, vec;
|
|
unsigned mask[4];
|
|
} *r = (typeof(r))ri->d;
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s irq_bind_vector irq %x vec %x mask %04x %04x %04x %04x\n",
|
|
ri->dump_header,
|
|
r->irq, r->vec,
|
|
r->mask[3],
|
|
r->mask[2],
|
|
r->mask[1],
|
|
r->mask[0]);
|
|
}
|
|
break;
|
|
}
|
|
case TRC_HW_IRQ_HANDLED:
|
|
{
|
|
struct {
|
|
int irq, start_tsc, end_tsc;
|
|
} *r = (typeof(r))ri->d;
|
|
int arctime;
|
|
|
|
arctime = r->end_tsc - r->start_tsc;
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s irq_handled irq %x %d (%d,%d)\n",
|
|
ri->dump_header,
|
|
r->irq, arctime, r->start_tsc, r->end_tsc);
|
|
}
|
|
if ( opt.scatterplot_irq )
|
|
{
|
|
struct time_struct t;
|
|
|
|
abs_cycles_to_time(ri->tsc, &t);
|
|
|
|
printf("i%x %u.%09u %d\n",
|
|
(unsigned)r->irq,
|
|
t.s, t.ns,
|
|
p->pid);
|
|
}
|
|
break;
|
|
}
|
|
case TRC_HW_IRQ_ASSIGN_VECTOR:
|
|
{
|
|
struct {
|
|
int irq, vec;
|
|
unsigned mask[4];
|
|
} *r = (typeof(r))ri->d;
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s irq_assign_vector irq %x vec %x mask %04x %04x %04x %04x\n",
|
|
ri->dump_header,
|
|
r->irq, r->vec,
|
|
r->mask[3],
|
|
r->mask[2],
|
|
r->mask[1],
|
|
r->mask[0]);
|
|
}
|
|
if ( r->irq < MAX_IRQ
|
|
&& r->vec < MAX_VECTOR )
|
|
{
|
|
if ( irq_table[r->irq].type == IRQ_MSI )
|
|
{
|
|
if(global_vector_used[r->vec])
|
|
fprintf(warn, " Vector collision on global table!\n");
|
|
global_vector_used[r->vec]=1;
|
|
}
|
|
if( irq_table[r->irq].dev )
|
|
{
|
|
struct pci_dev * pdev=irq_table[r->irq].dev;
|
|
|
|
if(pdev->vector_used[r->vec])
|
|
fprintf(warn, " Vector collision on %02x.%02x!\n",
|
|
pdev->bus, pdev->devfn);
|
|
pdev->vector_used[r->vec]=1;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case TRC_HW_IRQ_MOVE_CLEANUP_DELAY:
|
|
{
|
|
struct {
|
|
int irq, vec, cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s irq_move_cleanup_delay irq %x vec %x cpu %d\n",
|
|
ri->dump_header,
|
|
r->irq, r->vec, r->cpu);
|
|
}
|
|
break;
|
|
}
|
|
case TRC_HW_IRQ_MOVE_CLEANUP:
|
|
{
|
|
struct {
|
|
int irq;
|
|
int vec;
|
|
int cpu;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s irq_move_cleanup irq %x vec %x cpu %d\n",
|
|
ri->dump_header,
|
|
r->irq, r->vec, r->cpu);
|
|
}
|
|
if ( r->irq < MAX_IRQ
|
|
&& r->vec < MAX_VECTOR )
|
|
{
|
|
if ( irq_table[r->irq].type == IRQ_MSI )
|
|
{
|
|
if(!global_vector_used[r->vec])
|
|
fprintf(warn," Strange, cleanup on non-used vector\n");
|
|
global_vector_used[r->vec]=0;
|
|
}
|
|
if ( irq_table[r->irq].dev )
|
|
{
|
|
struct pci_dev * pdev=irq_table[r->irq].dev;
|
|
|
|
if(!pdev->vector_used[r->vec])
|
|
fprintf(warn," Strange, cleanup on non-used vector\n");
|
|
pdev->vector_used[r->vec]=0;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case TRC_HW_IRQ_UNMAPPED_VECTOR:
|
|
{
|
|
struct {
|
|
int vec;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
if ( opt.dump_all )
|
|
{
|
|
printf(" %s irq_unmapped_vector vec %x\n",
|
|
ri->dump_header,
|
|
r->vec);
|
|
}
|
|
break;
|
|
}
|
|
case TRC_HW_IRQ_CLEAR_VECTOR:
|
|
case TRC_HW_IRQ_MOVE_FINISH :
|
|
default:
|
|
if(opt.dump_all) {
|
|
dump_generic(stdout, ri);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
#define TRC_HW_SUB_PM 1
|
|
#define TRC_HW_SUB_IRQ 2
|
|
void hw_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
switch(ri->evt.sub)
|
|
{
|
|
case TRC_HW_SUB_PM:
|
|
pm_process(p);
|
|
break;
|
|
case TRC_HW_SUB_IRQ:
|
|
irq_process(p);
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
#define TRC_DOM0_SUB_DOMOPS 1
|
|
void dom0_process(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
|
|
switch(ri->evt.sub)
|
|
{
|
|
case TRC_DOM0_SUB_DOMOPS:
|
|
if(opt.dump_all) {
|
|
struct {
|
|
unsigned int domid;
|
|
} *r = (typeof(r))ri->d;
|
|
|
|
printf(" %s %s domain d%u\n", ri->dump_header,
|
|
ri->event == TRC_DOM0_DOM_ADD ? "creating" : "destroying",
|
|
r->domid);
|
|
}
|
|
break;
|
|
default:
|
|
process_generic(&p->ri);
|
|
}
|
|
}
|
|
|
|
/* ---- Base ----- */
|
|
void dump_generic(FILE * f, struct record_info *ri)
|
|
{
|
|
int i;
|
|
|
|
fprintf(f, "]%s %7x(%x:%x:%x) %u [",
|
|
ri->dump_header,
|
|
ri->event,
|
|
ri->evt.main,
|
|
ri->evt.sub,
|
|
ri->evt.minor,
|
|
ri->extra_words);
|
|
|
|
for(i=0; i<ri->extra_words; i++) {
|
|
fprintf(f, " %x", ri->d[i]);
|
|
}
|
|
|
|
fprintf(f, " ]\n");
|
|
}
|
|
|
|
void dump_raw(const char * s, struct record_info *ri)
|
|
{
|
|
int i;
|
|
|
|
if(ri->rec.cycle_flag)
|
|
printf("%s %7x %d %14lld [",
|
|
s, ri->event, ri->extra_words, ri->tsc);
|
|
else
|
|
printf("%s %7x %d %14s [",
|
|
s, ri->event, ri->extra_words, "-");
|
|
|
|
for(i=0; i<7; i++) {
|
|
if ( i < ri->extra_words )
|
|
printf(" %8x", ri->d[i]);
|
|
else
|
|
printf(" ");
|
|
}
|
|
|
|
printf(" ] | ");
|
|
|
|
for (i=0; i<8; i++) {
|
|
printf(" %08x", ri->rec.raw[i]);
|
|
}
|
|
|
|
printf(" |\n");
|
|
}
|
|
|
|
void error(enum error_level l, struct record_info *ri)
|
|
{
|
|
if ( l > opt.tolerance )
|
|
{
|
|
if ( ri )
|
|
dump_generic(warn, ri);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int check_extra_words(struct record_info *ri,
|
|
int expected_size,
|
|
const char *record)
|
|
{
|
|
static int off_by_one = 0;
|
|
int expected_extra = expected_size / sizeof(unsigned int);
|
|
|
|
if(ri->extra_words != expected_extra
|
|
&& !(off_by_one && ri->extra_words == expected_extra + 1) )
|
|
{
|
|
if ( !off_by_one && ri->extra_words == expected_extra + 1 )
|
|
{
|
|
fprintf(warn, "Detected off-by-one bug; relaxing expectations\n");
|
|
off_by_one=1;
|
|
}
|
|
else {
|
|
fprintf(warn, "ERROR: %s extra_words %d, expected %d!\n",
|
|
record,
|
|
ri->extra_words, expected_extra);
|
|
error(ERR_RECORD, ri);
|
|
return 1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void process_generic(struct record_info *ri) {
|
|
|
|
error(ERR_STRICT, ri);
|
|
|
|
if(opt.dump_all) {
|
|
dump_generic(stdout, ri);
|
|
}
|
|
}
|
|
|
|
int vcpu_set_data_type(struct vcpu_data *v, int type)
|
|
{
|
|
if (v->data_type == VCPU_DATA_NONE )
|
|
{
|
|
v->data_type = type;
|
|
switch(type)
|
|
{
|
|
case VCPU_DATA_HVM:
|
|
init_hvm_data(&v->hvm, v);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
assert(v->data_type == type);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void lose_vcpu(struct vcpu_data *v, tsc_t tsc)
|
|
{
|
|
if(v->data_type == VCPU_DATA_HVM)
|
|
v->hvm.vmexit_valid=0;
|
|
runstate_update(v, RUNSTATE_LOST, tsc);
|
|
hvm_vlapic_clear(&v->vlapic);
|
|
|
|
if(v->data_type == VCPU_DATA_HVM) {
|
|
int i;
|
|
if(opt.dump_all)
|
|
printf(" [w2h] Clearing w2h state for d%dv%d\n",
|
|
v->d->did, v->vid);
|
|
v->hvm.w2h.interrupts=0;
|
|
v->hvm.w2h.vector=0;
|
|
v->hvm.w2h.waking = 0;
|
|
for(i=0; i<GUEST_INTERRUPT_MAX; i++) {
|
|
if(opt.dump_all && v->hvm.summary.guest_interrupt[i].start_tsc) {
|
|
printf(" Interrupt %d clearing start_tsc %lld\n",
|
|
i, v->hvm.summary.guest_interrupt[i].start_tsc);
|
|
}
|
|
v->hvm.summary.guest_interrupt[i].start_tsc = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct lost_record_struct {
|
|
int lost_records;
|
|
unsigned did:16,vid:16;
|
|
tsc_t first_tsc;
|
|
};
|
|
|
|
void process_lost_records(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
struct lost_record_struct *r = (typeof(r))ri->d;
|
|
tsc_t first_tsc; /* TSC of first record that was lost */
|
|
|
|
/* Sanity checks */
|
|
if(ri->extra_words != 4)
|
|
{
|
|
fprintf(warn, "FATAL: Lost record has unexpected extra words %d!\n",
|
|
ri->extra_words);
|
|
error(ERR_RECORD, ri);
|
|
return;
|
|
}
|
|
|
|
first_tsc = r->first_tsc;
|
|
|
|
if(opt.dump_all)
|
|
{
|
|
if(p->current)
|
|
printf(" %s lost_records count %d d%uv%u (cur d%dv%d) first_tsc %lld\n",
|
|
ri->dump_header, r->lost_records,
|
|
r->did, r->vid,
|
|
p->current->d->did, p->current->vid,
|
|
r->first_tsc);
|
|
else
|
|
printf(" %s lost_records count %d d%uv%u (cur X) first_tsc %lld\n",
|
|
ri->dump_header, r->lost_records,
|
|
r->did, r->vid,
|
|
r->first_tsc);
|
|
}
|
|
|
|
#if 0
|
|
if(opt.dump_trace_volume_on_lost_record)
|
|
volume_summary(&p->volume.last_buffer);
|
|
#endif
|
|
|
|
if ( p->current ) {
|
|
|
|
hvm_vlapic_clear(&p->current->vlapic);
|
|
if(p->current->data_type == VCPU_DATA_HVM) {
|
|
p->current->hvm.vmexit_valid=0;
|
|
cr3_switch(0, &p->current->hvm);
|
|
}
|
|
|
|
/* We may lose scheduling records; so we need to:
|
|
* - Point all records until now to the next schedule in the
|
|
* "default" domain
|
|
* - Make sure there are no warnings / strangeness with the
|
|
* current vcpu (if it gets scheduled elsewhere).
|
|
*/
|
|
vcpu_prev_update(p, p->current, first_tsc, RUNSTATE_LOST);
|
|
}
|
|
#if 0
|
|
vcpu_next_update(p, default_domain.vcpu[p->pid], first_tsc);
|
|
if(p->current->data_type == VCPU_DATA_HVM) {
|
|
p->current->hvm.vmexit_valid=0;
|
|
}
|
|
#endif
|
|
|
|
/* The lost record trace is processed early -- i.e.,
|
|
* After the last good record, rather than when the next
|
|
* record is processed. Between the time it's processed and
|
|
* the time it actually went in, the vcpu may be scheduled on
|
|
* other processors. So we can't switch vcpus until the first
|
|
* TSC'd record after the lost record. */
|
|
if(!p->lost_record.active) {
|
|
P.lost_cpus++;
|
|
if(P.lost_cpus > P.max_active_pcpu + 1) {
|
|
fprintf(warn, "ERROR: P.lost_cpus %d > P.max_active_pcpu + 1 %d!\n",
|
|
P.lost_cpus, P.max_active_pcpu + 1);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
} else
|
|
fprintf(warn, "Strange, lost record for pcpu %d, but lost_record still active!\n",
|
|
p->pid);
|
|
|
|
p->lost_record.active = 1;
|
|
p->lost_record.tsc = first_tsc;
|
|
pcpu_string_draw(p);
|
|
|
|
{
|
|
/* Any vcpu which is not actively running may be scheduled on the
|
|
* lost cpu. To avoid mis-accounting, we need to reset */
|
|
struct domain_data *d;
|
|
int i;
|
|
for(d=domain_list ; d; d=d->next)
|
|
{
|
|
if(d->did != DEFAULT_DOMAIN) {
|
|
for(i=0; i<MAX_CPUS; i++)
|
|
if(d->vcpu[i] &&
|
|
d->vcpu[i]->runstate.state != RUNSTATE_RUNNING) {
|
|
if(opt.dump_all)
|
|
fprintf(warn, "%s: setting d%dv%d to RUNSTATE_LOST\n",
|
|
__func__, d->did, i);
|
|
lose_vcpu(d->vcpu[i], first_tsc);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
p->lost_record.domain_valid=1;
|
|
p->lost_record.did=r->did;
|
|
p->lost_record.vid=r->vid;
|
|
}
|
|
|
|
|
|
void process_lost_records_end(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri = &p->ri;
|
|
struct lost_record_struct *r = (typeof(r))ri->d;
|
|
|
|
if(!p->lost_record.active) {
|
|
fprintf(warn, "FATAL: lost_records_end but pid %d not lost!\n",
|
|
p->pid);
|
|
error(ERR_FILE, NULL);
|
|
return;
|
|
}
|
|
|
|
/* Lost records. If this is the first record on a pcpu after the loss,
|
|
* Update the information. */
|
|
if(ri->tsc > p->lost_record.tsc)
|
|
{
|
|
if(opt.dump_all)
|
|
printf(" %s lost_records end ---\n",
|
|
pcpu_string(p->pid));
|
|
|
|
update_cycles(&p->time.lost, ri->tsc - p->lost_record.tsc);
|
|
|
|
if(p->lost_record.domain_valid) {
|
|
int did = p->lost_record.did,
|
|
vid = p->lost_record.vid;
|
|
|
|
if(opt.dump_all)
|
|
printf(" %s lost_records end d%dv%d---\n",
|
|
pcpu_string(p->pid),
|
|
did, vid);
|
|
if(p->current)
|
|
{
|
|
fprintf(warn, "FATAL: lost_record valid (d%dv%d), but current d%dv%d!\n",
|
|
did, vid,
|
|
p->current->d->did, p->current->vid);
|
|
error(ERR_FILE, NULL);
|
|
return;
|
|
}
|
|
|
|
if(opt.dump_all)
|
|
fprintf(warn, "Changing p%d current to d%dv%d\n",
|
|
p->pid, did, vid);
|
|
vcpu_next_update(p,
|
|
vcpu_find(did, vid),
|
|
ri->tsc);
|
|
p->lost_record.domain_valid=0;
|
|
p->lost_record.seen_valid_schedule=0; /* Let next vcpu_next_update know that
|
|
this one was inferred */
|
|
} else {
|
|
if(opt.dump_all)
|
|
printf(" %s lost_records end (domain invalid)---\n",
|
|
pcpu_string(p->pid));
|
|
}
|
|
|
|
|
|
p->lost_record.active = 0;
|
|
pcpu_string_draw(p);
|
|
P.lost_cpus--;
|
|
if(P.lost_cpus < 0) {
|
|
fprintf(warn, "ERROR: lost_cpus fell below 0 for pcpu %d!\n",
|
|
p->pid);
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
void base_process(struct pcpu_info *p) {
|
|
struct record_info *ri = &p->ri;
|
|
switch(ri->event)
|
|
{
|
|
case TRC_TRACE_WRAP_BUFFER:
|
|
break;
|
|
case TRC_LOST_RECORDS:
|
|
process_lost_records(p);
|
|
break;
|
|
case TRC_LOST_RECORDS_END:
|
|
process_lost_records_end(p);
|
|
break;
|
|
default:
|
|
process_generic(ri);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* Non-compat only */
|
|
void record_order_insert(struct pcpu_info *new);
|
|
void record_order_remove(struct pcpu_info *rem);
|
|
void record_order_bubble(struct pcpu_info *last);
|
|
|
|
struct cpu_change_data {
|
|
int cpu;
|
|
unsigned window_size;
|
|
};
|
|
|
|
void activate_early_eof(void) {
|
|
struct pcpu_info *p;
|
|
int i;
|
|
|
|
fprintf(warn, "Short cpu_change window, activating early_eof\n");
|
|
|
|
P.early_eof = 1;
|
|
|
|
for(i=0; i<=P.max_active_pcpu; i++) {
|
|
p = P.pcpu + i;
|
|
if(p->active && p->file_offset > P.last_epoch_offset) {
|
|
fprintf(warn, " deactivating pid %d\n",
|
|
p->pid);
|
|
p->active = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
off_t scan_for_new_pcpu(off_t offset) {
|
|
ssize_t r;
|
|
struct trace_record rec;
|
|
struct cpu_change_data *cd;
|
|
|
|
r=__read_record(&rec, offset);
|
|
|
|
if(r==0)
|
|
return 0;
|
|
|
|
if(rec.event != TRC_TRACE_CPU_CHANGE
|
|
|| rec.cycle_flag)
|
|
{
|
|
fprintf(stderr, "%s: Unexpected record event %x!\n",
|
|
__func__, rec.event);
|
|
error(ERR_ASSERT, NULL); /* Actually file, but can't recover */
|
|
}
|
|
|
|
cd = (typeof(cd))rec.u.notsc.data;
|
|
|
|
if ( cd->cpu > MAX_CPUS )
|
|
{
|
|
fprintf(stderr, "%s: cpu %d exceeds MAX_CPU %d!\n",
|
|
__func__, cd->cpu, MAX_CPUS);
|
|
/* FIXME: Figure out if we could handle this more gracefully */
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
|
|
if(cd->cpu > P.max_active_pcpu || !P.pcpu[cd->cpu].active) {
|
|
struct pcpu_info *p = P.pcpu + cd->cpu;
|
|
|
|
fprintf(warn, "%s: Activating pcpu %d at offset %lld\n",
|
|
__func__, cd->cpu, (unsigned long long)offset);
|
|
|
|
p->active = 1;
|
|
/* Process this cpu_change record first */
|
|
p->ri.rec = rec;
|
|
p->ri.size = r;
|
|
__fill_in_record_info(p);
|
|
|
|
p->file_offset = offset;
|
|
p->next_cpu_change_offset = offset;
|
|
|
|
record_order_insert(p);
|
|
|
|
offset += r + cd->window_size;
|
|
|
|
sched_default_vcpu_activate(p);
|
|
|
|
if ( cd->cpu > P.max_active_pcpu )
|
|
P.max_active_pcpu = cd->cpu;
|
|
|
|
return offset;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Conceptually, when we reach a cpu_change record that's not for our pcpu,
|
|
* we want to scan forward through the file until we reach one that's for us.
|
|
* However, looping through involves reading the file, which we'd rather
|
|
* do in one place. Because cpu_change records don't include a tsc,
|
|
* the same pcpu will be processed repeatedly until the cpu_change
|
|
* equals p->pid.
|
|
*
|
|
* There are two additional things we need to do in this algorithm:
|
|
* + Detect new pcpus as they come online
|
|
* + De-activate pcpus which don't have any more records
|
|
*
|
|
* Detecting new pcpus which are less than P.max_active_pcpu is straight-
|
|
* forward: when max_active_pcpu is searching for its next cpu window,
|
|
* it will pass by the new cpu's window, and can activate it then.
|
|
*
|
|
* Detecting new pcpus greater than P.max_active_pcpu is a little harder;
|
|
* When max_active_pcpu is scanning for its next cpu window, after it's found
|
|
* it, we need to scan one more window forward to see if its' an already-active
|
|
* pcpu; if not, activate it.
|
|
*
|
|
* We also need to deal with truncated files, where records from one pcpu may
|
|
* be present but not from another pcpu due to lack of disk space. The best
|
|
* thing to do is to find the last "epoch" and essentially truncate the file
|
|
* to that.
|
|
*/
|
|
void deactivate_pcpu(struct pcpu_info *p)
|
|
{
|
|
if ( p->current )
|
|
{
|
|
pcpu_runstate_update(p, p->last_tsc);
|
|
|
|
fprintf(warn, "%s: setting d%dv%d to state LOST\n",
|
|
__func__, p->current->d->did,
|
|
p->current->vid);
|
|
lose_vcpu(p->current, p->last_tsc);
|
|
}
|
|
p->active = 0;
|
|
|
|
record_order_remove(p);
|
|
|
|
if ( p->pid == P.max_active_pcpu )
|
|
{
|
|
int i, max_active_pcpu = -1;
|
|
for(i=0; i<=P.max_active_pcpu; i++)
|
|
{
|
|
if(!P.pcpu[i].active)
|
|
continue;
|
|
|
|
max_active_pcpu = i;
|
|
}
|
|
P.max_active_pcpu = max_active_pcpu;
|
|
fprintf(warn, "%s: Setting max_active_pcpu to %d\n",
|
|
__func__, max_active_pcpu);
|
|
}
|
|
|
|
}
|
|
|
|
/* Helper function to process tsc-related record info */
|
|
void process_record_tsc(tsc_t order_tsc, struct record_info *ri)
|
|
{
|
|
/* Find the first tsc set */
|
|
if(ri->tsc && ri->tsc >= P.f.first_tsc) {
|
|
/* We use the order_tsc to account for the second processing of
|
|
* a lost record. */
|
|
tsc_t tsc = order_tsc;
|
|
|
|
if(P.f.first_tsc == 0) {
|
|
P.f.first_tsc = tsc;
|
|
if ( opt.interval_mode ) {
|
|
P.interval.start_tsc = tsc;
|
|
}
|
|
} else {
|
|
if ( opt.interval_mode ) {
|
|
if(P.interval.start_tsc > tsc) {
|
|
fprintf(warn, "FATAL: order_tsc %lld < interval.start_tsc %lld!\n",
|
|
tsc, P.interval.start_tsc);
|
|
error(ERR_FILE, NULL);
|
|
} else {
|
|
while ( tsc - P.interval.start_tsc > opt.interval.cycles ) {
|
|
interval_callback();
|
|
P.interval.start_tsc += opt.interval.cycles;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
P.f.last_tsc=tsc;
|
|
|
|
P.f.total_cycles = P.f.last_tsc - P.f.first_tsc;
|
|
|
|
P.now = tsc;
|
|
}
|
|
}
|
|
|
|
/* Standardized part of dump output */
|
|
void create_dump_header(struct record_info *ri, struct pcpu_info *p)
|
|
{
|
|
char * c;
|
|
int len, r;
|
|
|
|
len = DUMP_HEADER_MAX;
|
|
c = ri->dump_header;
|
|
|
|
abs_cycles_to_time(ri->tsc, &ri->t);
|
|
|
|
if ( ri->t.time )
|
|
{
|
|
r=snprintf(c, len, "%3u.%09u", ri->t.s, ri->t.ns);
|
|
c+=r;
|
|
len-=r;
|
|
}
|
|
else
|
|
{
|
|
r=snprintf(c,
|
|
len,
|
|
" ");
|
|
c+=r;
|
|
len-=r;
|
|
}
|
|
|
|
r = snprintf(c, len, " %s", pcpu_string(ri->cpu));
|
|
c+=r;
|
|
len-=r;
|
|
|
|
if ( p->current )
|
|
{
|
|
r = snprintf(c, len, " d%dv%d", p->current->d->did, p->current->vid);
|
|
c+=r;
|
|
len-=r;
|
|
}
|
|
else
|
|
{
|
|
r = snprintf(c, len, " d?v?");
|
|
c+=r;
|
|
len-=r;
|
|
}
|
|
}
|
|
|
|
int find_toplevel_event(struct record_info *ri)
|
|
{
|
|
int toplevel=0, i, count;
|
|
|
|
for(i=0, count=0; i<TOPLEVEL_MAX; i++)
|
|
if(ri->evt.main & (1UL<<i))
|
|
{
|
|
toplevel=i;
|
|
count++;
|
|
}
|
|
|
|
/* Sanity check: One and only one bit should be set */
|
|
if(count != 1)
|
|
{
|
|
fprintf(warn, "FATAL: unexpected number bits(%d) in evt.main! event %x main %x sub %x minor %x\n",
|
|
count,
|
|
ri->event,
|
|
ri->evt.main, ri->evt.sub, ri->evt.minor);
|
|
error(ERR_RECORD, NULL);
|
|
return -1;
|
|
}
|
|
|
|
return toplevel;
|
|
}
|
|
|
|
|
|
void process_cpu_change(struct pcpu_info *p) {
|
|
struct record_info *ri = &p->ri;
|
|
struct cpu_change_data *r = (typeof(r))ri->d;
|
|
|
|
if(opt.dump_all && verbosity >= 6) {
|
|
printf("]%s cpu_change this-cpu %u record-cpu %u window_size %u(0x%08x)\n",
|
|
ri->dump_header, p->pid, r->cpu, r->window_size,
|
|
r->window_size);
|
|
}
|
|
|
|
/* File sanity check */
|
|
if(p->file_offset != p->next_cpu_change_offset) {
|
|
fprintf(warn, "Strange, pcpu %d expected offset %llx, actual %llx!\n",
|
|
p->pid, (unsigned long long)p->next_cpu_change_offset,
|
|
(unsigned long long)p->file_offset);
|
|
}
|
|
|
|
if(r->cpu > MAX_CPUS)
|
|
{
|
|
fprintf(stderr, "FATAL: cpu %d > MAX_CPUS %d.\n",
|
|
r->cpu, MAX_CPUS);
|
|
/* Actually file, but takes some work to skip */
|
|
error(ERR_ASSERT, NULL);
|
|
}
|
|
|
|
/* Detect beginning of new "epoch" while scanning thru file */
|
|
if((p->last_cpu_change_pid > r->cpu)
|
|
&& (p->file_offset > P.last_epoch_offset)) {
|
|
P.last_epoch_offset = p->file_offset;
|
|
}
|
|
|
|
/* If that pcpu has never been activated, activate it. */
|
|
if(!P.pcpu[r->cpu].active && P.pcpu[r->cpu].file_offset == 0)
|
|
{
|
|
struct pcpu_info * p2 = P.pcpu + r->cpu;
|
|
|
|
p2->active = 1;
|
|
if(r->cpu > P.max_active_pcpu)
|
|
P.max_active_pcpu = r->cpu;
|
|
|
|
/* Taking this record as the first record should make everything
|
|
* run swimmingly. */
|
|
p2->ri = *ri;
|
|
p2->ri.cpu = r->cpu;
|
|
p2->ri.d = p2->ri.rec.u.notsc.data;
|
|
p2->file_offset = p->file_offset;
|
|
p2->next_cpu_change_offset = p->file_offset;
|
|
|
|
fprintf(warn, "%s: Activating pcpu %d at offset %lld\n",
|
|
__func__, r->cpu, (unsigned long long)p->file_offset);
|
|
|
|
record_order_insert(p2);
|
|
|
|
sched_default_vcpu_activate(p2);
|
|
}
|
|
|
|
p->last_cpu_change_pid = r->cpu;
|
|
|
|
/* If this isn't the cpu we're looking for, skip the whole bunch */
|
|
if(p->pid != r->cpu)
|
|
{
|
|
p->file_offset += ri->size + r->window_size;
|
|
p->next_cpu_change_offset = p->file_offset;
|
|
|
|
if(p->file_offset > G.file_size) {
|
|
activate_early_eof();
|
|
} else if(P.early_eof && p->file_offset > P.last_epoch_offset) {
|
|
fprintf(warn, "%s: early_eof activated, pcpu %d past last_epoch_offset %llx, deactivating.\n",
|
|
__func__, p->pid, (unsigned long long)P.last_epoch_offset);
|
|
deactivate_pcpu(p);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Track information about dom0 scheduling and records */
|
|
if(opt.dump_trace_volume_on_lost_record) {
|
|
tsc_t cycles;
|
|
struct time_struct t;
|
|
|
|
/* Update dom0 runstates */
|
|
cycles = (p->volume.buffer_first_tsc > p->volume.buffer_dom0_runstate_tsc) ?
|
|
p->volume.buffer_first_tsc :
|
|
p->volume.buffer_dom0_runstate_tsc;
|
|
p->volume.buffer_dom0_runstate_cycles[p->volume.buffer_dom0_runstate]
|
|
+= ri->tsc - cycles;
|
|
|
|
printf(" - updated p%d dom0_runstate %s to %lld cycles (+%lld)\n",
|
|
p->pid, runstate_name[p->volume.buffer_dom0_runstate],
|
|
p->volume.buffer_dom0_runstate_cycles[p->volume.buffer_dom0_runstate],
|
|
ri->tsc - cycles);
|
|
|
|
/* print info */
|
|
cycles = ri->tsc - p->volume.buffer_first_tsc;
|
|
cycles_to_time(cycles, &t);
|
|
printf("Buffer time: %u.%09u (%lld cycles)\n",
|
|
t.s, t.ns, cycles);
|
|
if(p->volume.buffer_size)
|
|
printf("Rate: %lld cycles / byte\n",
|
|
cycles / p->volume.buffer_size);
|
|
if(P.buffer_trace_virq_tsc)
|
|
{
|
|
cycles = ri->tsc - P.buffer_trace_virq_tsc;
|
|
cycles_to_time(cycles, &t);
|
|
printf("trace_virq latency: %u.%09u (%lld cycles)\n",
|
|
t.s, t.ns, cycles);
|
|
P.buffer_trace_virq_tsc = 0;
|
|
}
|
|
else
|
|
{
|
|
printf("No trace_virq record found.\n");
|
|
}
|
|
printf("Dom0 runstates this buffer:\n");
|
|
runstate_summary(p->volume.buffer_dom0_runstate_cycles);
|
|
volume_summary(&p->volume.last_buffer);
|
|
|
|
/* reset info */
|
|
p->volume.buffer_first_tsc = 0;
|
|
p->volume.buffer_size = r->window_size;
|
|
runstate_clear(p->volume.buffer_dom0_runstate_cycles);
|
|
volume_clear(&p->volume.last_buffer);
|
|
}
|
|
|
|
p->file_offset += ri->size;
|
|
p->next_cpu_change_offset = p->file_offset + r->window_size;
|
|
|
|
if(p->next_cpu_change_offset > G.file_size)
|
|
activate_early_eof();
|
|
else if(p->pid == P.max_active_pcpu)
|
|
scan_for_new_pcpu(p->next_cpu_change_offset);
|
|
|
|
}
|
|
}
|
|
|
|
struct tl_assert_mask {
|
|
unsigned p_current:1,
|
|
not_idle_domain:1;
|
|
int vcpu_data_mode;
|
|
};
|
|
static struct tl_assert_mask tl_assert_checks[TOPLEVEL_MAX] = {
|
|
[TRC_HVM_MAIN]={ .p_current=1, .not_idle_domain=1, .vcpu_data_mode=VCPU_DATA_HVM },
|
|
[TRC_SHADOW_MAIN]={ .p_current=1, .not_idle_domain=1, .vcpu_data_mode=VCPU_DATA_HVM },
|
|
[TRC_PV_MAIN]={ .p_current=1, .not_idle_domain=1, .vcpu_data_mode=VCPU_DATA_PV },
|
|
};
|
|
|
|
/* There are a lot of common assumptions for the various processing
|
|
* routines. Check them all in one place, doing something else if
|
|
* they don't pass. */
|
|
int toplevel_assert_check(int toplevel, struct record_info *ri, struct pcpu_info *p)
|
|
{
|
|
struct tl_assert_mask mask;
|
|
bool is_hvm_emul = (toplevel == TOPLEVEL_HVM) && (ri->evt.sub == 0x4);
|
|
|
|
mask = tl_assert_checks[toplevel];
|
|
|
|
if (mask.p_current && p->current == NULL)
|
|
{
|
|
fprintf(warn, "WARNING: p->current null! Not processing\n");
|
|
goto fail;
|
|
}
|
|
|
|
if( mask.not_idle_domain && !is_hvm_emul)
|
|
{
|
|
/* Can't do this check w/o first doing above check */
|
|
assert(mask.p_current);
|
|
|
|
if ( p->current->d->did == IDLE_DOMAIN) {
|
|
fprintf(warn, "WARNING: Unexpected record for idle domain! Not processing\n");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if ( mask.vcpu_data_mode )
|
|
{
|
|
struct vcpu_data *v;
|
|
assert(mask.p_current);
|
|
|
|
v = p->current;
|
|
|
|
if ( ! (v->data_type == VCPU_DATA_NONE
|
|
|| v->data_type == mask.vcpu_data_mode
|
|
|| is_hvm_emul) )
|
|
{
|
|
/* This may happen for track_dirty_vram, which causes a SHADOW_WRMAP_BF trace f/ dom0 */
|
|
fprintf(warn, "WARNING: Unexpected vcpu data type for d%dv%d on proc %d! Expected %d got %d. Not processing\n",
|
|
v->d->did, v->vid, p->pid,
|
|
mask.vcpu_data_mode,
|
|
v->data_type);
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
|
|
fail:
|
|
dump_generic(warn, &p->ri);
|
|
return 0;
|
|
}
|
|
|
|
void process_record(struct pcpu_info *p) {
|
|
struct record_info *ri = &p->ri;
|
|
int toplevel;
|
|
|
|
/* Process only TRC_TRACE_CPU_CHANGE */
|
|
if(ri->event == TRC_TRACE_CPU_CHANGE) {
|
|
process_cpu_change(p);
|
|
return;
|
|
}
|
|
|
|
if ( opt.dump_no_processing )
|
|
goto out;
|
|
|
|
p->summary = 1;
|
|
|
|
if( opt.dump_raw_process )
|
|
dump_raw("* ", ri);
|
|
|
|
process_record_tsc(p->order_tsc, ri);
|
|
|
|
if(opt.dump_all)
|
|
create_dump_header(ri, p);
|
|
|
|
|
|
toplevel = find_toplevel_event(ri);
|
|
if ( toplevel < 0 )
|
|
return;
|
|
|
|
/* Unify toplevel assertions */
|
|
if ( toplevel_assert_check(toplevel, ri, p) )
|
|
{
|
|
switch(toplevel) {
|
|
case TRC_GEN_MAIN:
|
|
base_process(p);
|
|
break;
|
|
case TRC_SCHED_MAIN:
|
|
sched_process(p);
|
|
break;
|
|
case TRC_HVM_MAIN:
|
|
hvm_process(p);
|
|
break;
|
|
case TRC_SHADOW_MAIN:
|
|
shadow_process(p);
|
|
break;
|
|
case TRC_PV_MAIN:
|
|
pv_process(p);
|
|
break;
|
|
case TRC_MEM_MAIN:
|
|
mem_process(p);
|
|
break;
|
|
case TRC_HW_MAIN:
|
|
hw_process(p);
|
|
break;
|
|
case TRC_DOM0OP_MAIN:
|
|
dom0_process(p);
|
|
break;
|
|
default:
|
|
process_generic(ri);
|
|
}
|
|
}
|
|
|
|
UPDATE_VOLUME(p, toplevel[toplevel], ri->size);
|
|
|
|
if(!p->volume.buffer_first_tsc)
|
|
p->volume.buffer_first_tsc = ri->tsc;
|
|
|
|
out:
|
|
/* Lost records gets processed twice */
|
|
if(ri->event != TRC_LOST_RECORDS)
|
|
p->file_offset += ri->size;
|
|
}
|
|
|
|
static inline ssize_t get_rec_size(struct trace_record *rec) {
|
|
ssize_t s;
|
|
|
|
s = sizeof(uint32_t);
|
|
|
|
if(rec->cycle_flag)
|
|
s += sizeof(tsc_t);
|
|
|
|
s += rec->extra_words * sizeof(uint32_t);
|
|
|
|
return s;
|
|
}
|
|
|
|
#define STDIN 0
|
|
|
|
void progress_child_exec(void) {
|
|
fclose(stdin);
|
|
dup2(G.progress.pipe[0], STDIN);
|
|
|
|
execlp("zenity", "zenity", "--progress", "--auto-close", "--title",
|
|
"Analyzing", "--text", G.trace_file, "--auto-kill", NULL);
|
|
}
|
|
|
|
void progress_init(void) {
|
|
int pid;
|
|
|
|
if (pipe(G.progress.pipe) < 0)
|
|
perror("pipe");
|
|
|
|
if(!(pid = fork())) {
|
|
progress_child_exec();
|
|
|
|
fprintf(stderr, "%s: exec failed (%s), disabling progress bar\n",
|
|
__func__, strerror(errno));
|
|
opt.progress = 0;
|
|
exit(1);
|
|
} else if( pid < 0 ) {
|
|
fprintf(stderr, "%s: could not fork: %s, disabling progress bar\n",
|
|
__func__, strerror(errno));
|
|
opt.progress = 0;
|
|
}
|
|
|
|
if( (G.progress.out = fdopen(G.progress.pipe[1], "w")) == NULL ) {
|
|
fprintf(stderr, "%s: could not fdopen pipe: %s, disabling progress bar\n",
|
|
__func__, strerror(errno));
|
|
opt.progress = 0;
|
|
}
|
|
|
|
}
|
|
|
|
void progress_update(off_t offset) {
|
|
long long p;
|
|
|
|
p = ( offset * 100 ) / G.file_size;
|
|
|
|
fprintf(G.progress.out, "%lld\n", p);
|
|
fflush(G.progress.out);
|
|
|
|
p += 1;
|
|
|
|
G.progress.update_offset = ( G.file_size * p ) / 100;
|
|
|
|
#if 0
|
|
fprintf(stderr, "Progress: %lld %% Next update_offset: %lld\n",
|
|
p-1,
|
|
G.progress.update_offset);
|
|
#endif
|
|
}
|
|
|
|
void progress_finish(void) {
|
|
int pid;
|
|
|
|
fprintf(G.progress.out, "100\n");
|
|
fflush(G.progress.out);
|
|
fclose(G.progress.out);
|
|
|
|
wait(NULL);
|
|
|
|
if(!(pid = fork())) {
|
|
/* Child */
|
|
char text[128];
|
|
|
|
snprintf(text, 128, "Finished analyzing %s",
|
|
G.trace_file);
|
|
execlp("zenity", "zenity", "--info", "--text", text, NULL);
|
|
}
|
|
}
|
|
|
|
ssize_t __read_record(struct trace_record *rec, off_t offset)
|
|
{
|
|
ssize_t r, rsize;
|
|
|
|
r=mread64(G.mh, rec, sizeof(*rec), offset);
|
|
|
|
if(r < 0) {
|
|
/* Read error */
|
|
perror("read");
|
|
fprintf(stderr, "offset %llx\n", (unsigned long long)offset);
|
|
return 0;
|
|
} else if(r==0) {
|
|
/* End-of-file */
|
|
return 0;
|
|
} else if(r < sizeof(uint32_t)) {
|
|
/* Full header not read */
|
|
fprintf(stderr, "%s: short read (%zd bytes)\n",
|
|
__func__, r);
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
rsize=get_rec_size(rec);
|
|
|
|
if(r < rsize) {
|
|
/* Full record not read */
|
|
fprintf(stderr, "%s: short read (%zd, expected %zd)\n",
|
|
__func__, r, rsize);
|
|
return 0;
|
|
}
|
|
|
|
return rsize;
|
|
}
|
|
|
|
void __fill_in_record_info(struct pcpu_info *p)
|
|
{
|
|
struct record_info *ri;
|
|
tsc_t tsc=0;
|
|
|
|
ri = &p->ri;
|
|
|
|
ri->event = ri->rec.event;
|
|
ri->extra_words = ri->rec.extra_words;
|
|
|
|
if(ri->rec.cycle_flag) {
|
|
tsc = (((tsc_t)ri->rec.u.tsc.tsc_hi) << 32)
|
|
| ri->rec.u.tsc.tsc_lo;
|
|
|
|
tsc += p->tsc_skew.offset;
|
|
|
|
ri->tsc = tsc;
|
|
ri->d = ri->rec.u.tsc.data;
|
|
|
|
if(p->first_tsc == 0)
|
|
p->first_tsc = tsc;
|
|
|
|
/* We process lost record twice: once at the first_tsc,
|
|
once at the time it was placed in the log */
|
|
if(ri->event == TRC_LOST_RECORDS && ri->extra_words == 4) {
|
|
struct lost_record_struct *r = (typeof(r))ri->d;
|
|
p->order_tsc = r->first_tsc + p->tsc_skew.offset;
|
|
} else
|
|
p->order_tsc = tsc;
|
|
|
|
p->last_tsc = tsc;
|
|
} else {
|
|
ri->tsc = p->last_tsc;
|
|
ri->d = ri->rec.u.notsc.data;
|
|
}
|
|
|
|
if ( opt.dump_raw_reads ) {
|
|
char s[256];
|
|
snprintf(s, 256, "R p%2d o%016llx ",
|
|
p->pid, (unsigned long long)p->file_offset);
|
|
dump_raw(s, ri);
|
|
}
|
|
|
|
/* Updated tracing uses CPU_CHANGE. If we hit one of these,
|
|
* it will process very next (since the tsc isn't updated), and
|
|
* we'll skip forward appropriately. */
|
|
ri->cpu = p->pid;
|
|
}
|
|
|
|
ssize_t read_record(struct pcpu_info * p) {
|
|
off_t * offset;
|
|
struct record_info *ri;
|
|
|
|
offset = &p->file_offset;
|
|
ri = &p->ri;
|
|
|
|
ri->size = __read_record(&ri->rec, *offset);
|
|
if(ri->size)
|
|
{
|
|
__fill_in_record_info(p);
|
|
}
|
|
else
|
|
{
|
|
fprintf(warn, "%s: read returned zero, deactivating pcpu %d\n",
|
|
__func__, p->pid);
|
|
deactivate_pcpu(p);
|
|
}
|
|
|
|
return ri->size;
|
|
}
|
|
|
|
/*
|
|
* This function gets called for every record when doing dump. Try to
|
|
* make it efficient by changing the minimum amount from the last
|
|
* call. Do this by:
|
|
* - Keeping track of the last pcpu called, so we can just set that to -
|
|
* - Keeping track of how many pcpus we've "drawn", and only "drawing" new ones
|
|
* - Updating the current one
|
|
*
|
|
* FIXME: Need to deal with pcpu states changing...
|
|
*
|
|
* WARNING not thread-safe
|
|
*/
|
|
|
|
char __pcpu_string[MAX_CPUS+1] = { 0 };
|
|
void pcpu_string_draw(struct pcpu_info *p)
|
|
{
|
|
char *s = __pcpu_string;
|
|
int i=p->pid;
|
|
|
|
if(p->lost_record.active)
|
|
s[i]='l';
|
|
else if (!p->current)
|
|
s[i]=' ';
|
|
else if (p->current->d->did == DEFAULT_DOMAIN)
|
|
s[i]='.';
|
|
else if (p->current->d->did == IDLE_DOMAIN)
|
|
{
|
|
if ( opt.dump_show_power_states )
|
|
s[i]=p->power_state+'0';
|
|
else
|
|
s[i]='-';
|
|
}
|
|
else
|
|
s[i]='|';
|
|
}
|
|
|
|
char * pcpu_string(int pcpu)
|
|
{
|
|
char *s = __pcpu_string;
|
|
static int max_active_pcpu=-1, last_pcpu=-1;
|
|
|
|
assert(P.max_active_pcpu < MAX_CPUS);
|
|
assert(pcpu <= P.max_active_pcpu);
|
|
|
|
if(last_pcpu >= 0)
|
|
pcpu_string_draw(P.pcpu+last_pcpu);
|
|
|
|
if(P.max_active_pcpu > max_active_pcpu)
|
|
{
|
|
int i;
|
|
for(i=max_active_pcpu + 1; i<= P.max_active_pcpu; i++)
|
|
pcpu_string_draw(P.pcpu+i);
|
|
max_active_pcpu=P.max_active_pcpu;
|
|
}
|
|
|
|
s[pcpu]='x';
|
|
last_pcpu = pcpu;
|
|
|
|
return s;
|
|
}
|
|
|
|
/* Null terminated */
|
|
struct pcpu_info *record_order[MAX_CPUS+1] = { 0 };
|
|
|
|
/* In the case of identical tsc values, the old algorithm would favor the
|
|
* pcpu with the lowest number. By default the new algorithm favors the
|
|
* pcpu which has been processed most recently.
|
|
*
|
|
* I think the second way is better; but it's good to be able to use the
|
|
* old ordering, at very lest to verify that there are no (other) ordering
|
|
* differences. Enabling the below flag will cause the insertion / bubble
|
|
* routines to order by pcpu id as well as tsc, preserving the old order. */
|
|
//#define PRESERVE_PCPU_ORDERING
|
|
|
|
/* Steady state:
|
|
* + Entire list is in order, except (potentially) for the first entry
|
|
* + last is pointing to the first entry.
|
|
*/
|
|
void record_order_bubble(struct pcpu_info *last)
|
|
{
|
|
int i;
|
|
|
|
/* Find the pcpu to "bubble". This is usually the
|
|
* first one, but if other pcpus have been activated, it may
|
|
* not be. */
|
|
for(i=0; record_order[i] && record_order[i]!=last; i++);
|
|
|
|
assert(record_order[i]);
|
|
|
|
/* Now bubble it down */
|
|
for( ;
|
|
record_order[i+1]
|
|
&& ( record_order[i+1]->order_tsc < last->order_tsc
|
|
#ifdef PRESERVE_PCPU_ORDERING
|
|
|| ( record_order[i+1]->order_tsc == last->order_tsc
|
|
&& record_order[i+1]->pid < last->pid )
|
|
#endif
|
|
) ;
|
|
i++)
|
|
record_order[i]=record_order[i+1];
|
|
record_order[i]=last;
|
|
}
|
|
|
|
void record_order_insert(struct pcpu_info *new)
|
|
{
|
|
int i;
|
|
struct pcpu_info *p=NULL, *t=NULL;
|
|
|
|
/* Sanity check: Make sure it's not already in there */
|
|
for(i=0; record_order[i]; i++)
|
|
assert(record_order[i]!=new);
|
|
|
|
/* Find where to insert it */
|
|
for(i=0;
|
|
record_order[i]
|
|
&& ( record_order[i]->order_tsc < new->order_tsc
|
|
#ifdef PRESERVE_PCPU_ORDERING
|
|
|| ( record_order[i]->order_tsc == new->order_tsc
|
|
&& record_order[i]->pid < new->pid )
|
|
#endif
|
|
) ;
|
|
i++)
|
|
;
|
|
|
|
/* And insert it */
|
|
for( p=new; p ; i++)
|
|
{
|
|
t=record_order[i];
|
|
record_order[i]=p;
|
|
p=t;
|
|
}
|
|
}
|
|
|
|
void record_order_remove(struct pcpu_info *rem)
|
|
{
|
|
int i;
|
|
|
|
/* Find where the record is */
|
|
for(i=0; record_order[i] && record_order[i]!=rem; i++)
|
|
;
|
|
|
|
/* Sanity check: Make sure it's actually there! */
|
|
assert(record_order[i]);
|
|
|
|
/* And move everyone forward */
|
|
for(; (record_order[i]=record_order[i+1]); i++)
|
|
;
|
|
}
|
|
|
|
struct pcpu_info * choose_next_record(void)
|
|
{
|
|
struct pcpu_info *min_p=NULL;
|
|
|
|
min_p=record_order[0];
|
|
|
|
if(opt.progress && min_p && min_p->file_offset >= G.progress.update_offset)
|
|
progress_update(min_p->file_offset);
|
|
|
|
/* If there are active pcpus, make sure we chose one */
|
|
assert(min_p || (P.max_active_pcpu==-1));
|
|
|
|
return min_p;
|
|
}
|
|
|
|
void process_records(void) {
|
|
while(1) {
|
|
struct pcpu_info *p = NULL;
|
|
|
|
if(!(p=choose_next_record()))
|
|
return;
|
|
|
|
process_record(p);
|
|
|
|
/* Lost records gets processed twice. */
|
|
if(p->ri.event == TRC_LOST_RECORDS) {
|
|
p->ri.event = TRC_LOST_RECORDS_END;
|
|
if(p->ri.tsc > p->order_tsc)
|
|
p->order_tsc = p->ri.tsc;
|
|
else {
|
|
fprintf(warn, "Strange, lost_record ri->tsc %lld !> p->order_tsc %lld!\n",
|
|
p->ri.tsc, p->order_tsc);
|
|
error(ERR_FILE, NULL);
|
|
}
|
|
}
|
|
else
|
|
read_record(p);
|
|
|
|
/* Update this pcpu in the processing order */
|
|
if ( p->active )
|
|
record_order_bubble(p);
|
|
}
|
|
}
|
|
|
|
void vcpu_summary(struct vcpu_data *v)
|
|
{
|
|
printf("-- v%d --\n", v->vid);
|
|
sched_summary_vcpu(v);
|
|
switch(v->data_type) {
|
|
case VCPU_DATA_HVM:
|
|
hvm_summary(&v->hvm);
|
|
break;
|
|
case VCPU_DATA_PV:
|
|
pv_summary(&v->pv);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void domain_summary(void)
|
|
{
|
|
struct domain_data * d;
|
|
int i;
|
|
|
|
if(opt.show_default_domain_summary) {
|
|
d = &default_domain;
|
|
printf("|-- Default domain --|\n");
|
|
|
|
for( i = 0; i < MAX_CPUS ; i++ )
|
|
{
|
|
if(d->vcpu[i])
|
|
vcpu_summary(d->vcpu[i]);
|
|
}
|
|
}
|
|
|
|
for ( d = domain_list ; d ; d=d->next )
|
|
{
|
|
int i;
|
|
printf("|-- Domain %d --|\n", d->did);
|
|
|
|
sched_summary_domain(d);
|
|
|
|
mem_summary_domain(d);
|
|
|
|
for( i = 0; i < MAX_CPUS ; i++ )
|
|
{
|
|
if(d->vcpu[i])
|
|
vcpu_summary(d->vcpu[i]);
|
|
}
|
|
|
|
printf("Emulate eip list\n");
|
|
dump_eip(d->emulate_eip_list);
|
|
|
|
if ( opt.with_interrupt_eip_enumeration )
|
|
{
|
|
printf("Interrupt eip list (vector %d)\n",
|
|
opt.interrupt_eip_enumeration_vector);
|
|
dump_eip(d->interrupt_eip_list);
|
|
}
|
|
|
|
cr3_dump_list(d->cr3_value_head);
|
|
}
|
|
}
|
|
|
|
char * stringify_cpu_hz(long long cpu_hz);
|
|
|
|
void summary(void) {
|
|
int i;
|
|
printf("Total time: %.2lf seconds (using cpu speed %s)\n",
|
|
((double)(P.f.total_cycles))/opt.cpu_hz,
|
|
stringify_cpu_hz(opt.cpu_hz));
|
|
printf("--- Log volume summary ---\n");
|
|
for(i=0; i<MAX_CPUS; i++)
|
|
{
|
|
struct pcpu_info *p = P.pcpu+i;
|
|
if(!p->summary)
|
|
continue;
|
|
printf(" - cpu %d -\n", i);
|
|
volume_summary(&p->volume.total);
|
|
}
|
|
domain_summary();
|
|
}
|
|
|
|
void report_pcpu(void) {
|
|
int i, active=0;
|
|
|
|
for(i=0; i<MAX_CPUS; i++)
|
|
{
|
|
struct pcpu_info *p = P.pcpu+i;
|
|
if(!p->summary)
|
|
continue;
|
|
printf("pcpu %d\n", i);
|
|
|
|
print_cycle_summary(&p->time.running, " running");
|
|
print_cycle_summary(&p->time.idle, " idle");
|
|
print_cycle_summary(&p->time.lost, " lost");
|
|
|
|
if ( p->time.running.count )
|
|
active++;
|
|
}
|
|
printf("Total active cpus: %d\n", active);
|
|
|
|
}
|
|
|
|
void init_pcpus(void) {
|
|
int i=0;
|
|
off_t offset = 0;
|
|
|
|
for(i=0; i<MAX_CPUS; i++)
|
|
{
|
|
P.pcpu[i].pid=i;
|
|
P.pcpu[i].lost_record.seen_valid_schedule=1;
|
|
P.pcpu[i].power_state=CSTATE_INVALID;
|
|
}
|
|
|
|
P.max_active_pcpu = -1;
|
|
|
|
sched_default_domain_init();
|
|
|
|
/* Scan through the cpu_change recs until we see a duplicate */
|
|
do {
|
|
offset = scan_for_new_pcpu(offset);
|
|
|
|
if(!offset) {
|
|
fprintf(warn, "%s: through first trace write, done for now.\n",
|
|
__func__);
|
|
}
|
|
} while(offset);
|
|
|
|
}
|
|
|
|
enum {
|
|
OPT_NULL=0,
|
|
/* Dumping info */
|
|
OPT_DUMP_RAW_READS,
|
|
OPT_DUMP_RAW_PROCESS,
|
|
OPT_DUMP_NO_PROCESSING,
|
|
OPT_DUMP_IPI_LATENCY,
|
|
OPT_DUMP_TRACE_VOLUME_ON_LOST_RECORD,
|
|
OPT_DUMP_SHOW_POWER_STATES,
|
|
/* Extra tracking functionality */
|
|
OPT_WITH_CR3_ENUMERATION,
|
|
OPT_WITH_PIO_ENUMERATION,
|
|
OPT_WITH_MMIO_ENUMERATION,
|
|
OPT_WITH_INTERRUPT_EIP_ENUMERATION,
|
|
OPT_SCATTERPLOT_INTERRUPT_EIP,
|
|
OPT_SCATTERPLOT_UNPIN_PROMOTE,
|
|
OPT_SCATTERPLOT_CR3_SWITCH,
|
|
OPT_SCATTERPLOT_WAKE_TO_HALT,
|
|
OPT_SCATTERPLOT_IO,
|
|
OPT_SCATTERPLOT_VMEXIT_EIP,
|
|
OPT_SCATTERPLOT_RUNSTATE,
|
|
OPT_SCATTERPLOT_RUNSTATE_TIME,
|
|
OPT_SCATTERPLOT_PCPU,
|
|
OPT_SCATTERPLOT_EXTINT_CYCLES,
|
|
OPT_SCATTERPLOT_RDTSC,
|
|
OPT_SCATTERPLOT_IRQ,
|
|
OPT_HISTOGRAM_INTERRUPT_EIP,
|
|
/* Interval options */
|
|
OPT_INTERVAL_CR3_SCHEDULE_TIME,
|
|
OPT_INTERVAL_CR3_SCHEDULE_TIME_ALL,
|
|
OPT_INTERVAL_CR3_SCHEDULE_ORDERED,
|
|
OPT_INTERVAL_CR3_SHORT_SUMMARY,
|
|
OPT_INTERVAL_DOMAIN_TOTAL_TIME,
|
|
OPT_INTERVAL_DOMAIN_TOTAL_TIME_ALL,
|
|
OPT_INTERVAL_DOMAIN_SHORT_SUMMARY,
|
|
OPT_INTERVAL_DOMAIN_GUEST_INTERRUPT,
|
|
OPT_INTERVAL_DOMAIN_GRANT_MAPS,
|
|
/* Summary info */
|
|
OPT_SHOW_DEFAULT_DOMAIN_SUMMARY,
|
|
OPT_MMIO_ENUMERATION_SKIP_VGA,
|
|
OPT_SAMPLE_SIZE,
|
|
OPT_SAMPLE_MAX,
|
|
OPT_REPORT_PCPU,
|
|
/* Guest info */
|
|
OPT_DEFAULT_GUEST_PAGING_LEVELS,
|
|
OPT_SYMBOL_FILE,
|
|
/* Hardware info */
|
|
OPT_SVM_MODE,
|
|
OPT_CPU_HZ,
|
|
/* Misc */
|
|
OPT_PROGRESS,
|
|
OPT_TOLERANCE,
|
|
OPT_TSC_LOOP_FATAL,
|
|
/* Specific letters */
|
|
OPT_DUMP_ALL='a',
|
|
OPT_INTERVAL_LENGTH='i',
|
|
OPT_SUMMARY='s',
|
|
};
|
|
|
|
enum {
|
|
OPT_GROUP_SUMMARY=1,
|
|
OPT_GROUP_DUMP,
|
|
OPT_GROUP_INTERVAL,
|
|
OPT_GROUP_EXTRA,
|
|
OPT_GROUP_GUEST,
|
|
OPT_GROUP_HARDWARE
|
|
};
|
|
|
|
#define xstr(x) str(x)
|
|
#define str(x) #x
|
|
|
|
#define GHZ 1000000000LL
|
|
#define MHZ 1000000LL
|
|
#define KHZ 1000LL
|
|
|
|
void parse_cpu_hz(char * arg) {
|
|
float hz_base;
|
|
char * next_ptr;
|
|
|
|
hz_base=strtof(arg, &next_ptr);
|
|
if(next_ptr == arg) {
|
|
fprintf(stderr, "Invalid cpu_hz %s\n", arg);
|
|
exit(1);
|
|
}
|
|
switch(*next_ptr) {
|
|
case '\0':
|
|
opt.cpu_hz=(long long)hz_base;
|
|
break;
|
|
case 'G':
|
|
opt.cpu_hz= hz_base * GHZ;
|
|
break;
|
|
case 'M':
|
|
opt.cpu_hz=hz_base * MHZ;
|
|
break;
|
|
case 'K':
|
|
opt.cpu_hz=hz_base * KHZ;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Unknown suffix %c\n", *next_ptr);
|
|
exit(1);
|
|
}
|
|
/* Just a convenient pre-calculation */
|
|
opt.cpu_qhz = QHZ_FROM_HZ(opt.cpu_hz);
|
|
}
|
|
|
|
/* WARNING not thread-safe */
|
|
char * stringify_cpu_hz(long long cpu_hz) {
|
|
static char cpu_string[20], suffix;
|
|
float hz;
|
|
|
|
if(cpu_hz > GHZ) {
|
|
hz = (float)cpu_hz / GHZ;
|
|
suffix = 'G';
|
|
} else if(cpu_hz > MHZ) {
|
|
hz = (float)cpu_hz / MHZ;
|
|
suffix = 'M';
|
|
} else if(cpu_hz > KHZ) {
|
|
hz = (float)cpu_hz / KHZ;
|
|
suffix = 'k';
|
|
} else {
|
|
hz = cpu_hz;
|
|
suffix = ' ';
|
|
}
|
|
|
|
snprintf(cpu_string, 20, "%1.2lf %cHz", hz, suffix);
|
|
|
|
return cpu_string;
|
|
}
|
|
|
|
int parse_array(char *arg, struct array_struct *a) {
|
|
char *p, *q;
|
|
int n=1, i;
|
|
|
|
/* Count the number of commas (and thus the number of elements) */
|
|
for(p=arg; *p; p++)
|
|
if(*p == ',')
|
|
n++;
|
|
|
|
fprintf(warn, "%s: Found %d elements\n", __func__, n);
|
|
fflush(warn);
|
|
a->count = n;
|
|
a->values = malloc(n * sizeof(unsigned long long));
|
|
|
|
if(!a->values) {
|
|
fprintf(stderr, "Malloc failed!\n");
|
|
error(ERR_SYSTEM, NULL);
|
|
}
|
|
|
|
/* Now parse the elements */
|
|
p = q = arg;
|
|
for(i=0; i<n; i++) {
|
|
a->values[i] = strtoull(p, &q, 0);
|
|
if(p == q) {
|
|
fprintf(stderr, "Bad format: %s\n", q);
|
|
return -1;
|
|
}
|
|
fprintf(warn, "%s: Found element 0x%llx (%lld)\n",
|
|
__func__, a->values[i],
|
|
a->values[i]);
|
|
fflush(warn);
|
|
if(*q == ',')
|
|
q++;
|
|
else if(*q != '\0') {
|
|
fprintf(stderr, "Bad format: %s\n", q);
|
|
return -1;
|
|
}
|
|
p=q;
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
error_t cmd_parser(int key, char *arg, struct argp_state *state)
|
|
{
|
|
switch (key)
|
|
{
|
|
/* Dump group */
|
|
case OPT_DUMP_ALL:
|
|
opt.dump_all = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
case OPT_DUMP_RAW_READS:
|
|
opt.dump_raw_reads = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
case OPT_DUMP_NO_PROCESSING:
|
|
opt.dump_no_processing = 1;
|
|
opt.dump_raw_reads = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
case OPT_DUMP_RAW_PROCESS:
|
|
opt.dump_raw_process = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
case OPT_DUMP_IPI_LATENCY:
|
|
opt.dump_ipi_latency = 1;
|
|
break;
|
|
case OPT_DUMP_TRACE_VOLUME_ON_LOST_RECORD:
|
|
opt.dump_trace_volume_on_lost_record = 1;
|
|
break;
|
|
case OPT_DUMP_SHOW_POWER_STATES:
|
|
opt.dump_show_power_states = 1;
|
|
break;
|
|
/* Extra group */
|
|
case OPT_WITH_CR3_ENUMERATION:
|
|
opt.with_cr3_enumeration=1;
|
|
break;
|
|
case OPT_WITH_PIO_ENUMERATION:
|
|
opt.with_pio_enumeration=1;
|
|
break;
|
|
case OPT_WITH_MMIO_ENUMERATION:
|
|
opt.with_mmio_enumeration=1;
|
|
break;
|
|
case OPT_SHOW_DEFAULT_DOMAIN_SUMMARY:
|
|
opt.show_default_domain_summary=1;
|
|
break;
|
|
case OPT_SAMPLE_SIZE:
|
|
{
|
|
char * inval;
|
|
opt.sample_size = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
break;
|
|
}
|
|
case OPT_SAMPLE_MAX:
|
|
{
|
|
char * inval;
|
|
opt.sample_max = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
break;
|
|
}
|
|
case OPT_MMIO_ENUMERATION_SKIP_VGA:
|
|
{
|
|
char * inval;
|
|
opt.mmio_enumeration_skip_vga = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
break;
|
|
}
|
|
case OPT_SCATTERPLOT_INTERRUPT_EIP:
|
|
{
|
|
char * inval;
|
|
G.output_defined = 1;
|
|
opt.scatterplot_interrupt_eip=1;
|
|
opt.scatterplot_interrupt_vector = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case OPT_WITH_INTERRUPT_EIP_ENUMERATION:
|
|
{
|
|
char * inval;
|
|
opt.with_interrupt_eip_enumeration=1;
|
|
opt.interrupt_eip_enumeration_vector = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case OPT_SCATTERPLOT_UNPIN_PROMOTE:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_unpin_promote=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_CR3_SWITCH:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_cr3_switch=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_WAKE_TO_HALT:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_wake_to_halt=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_VMEXIT_EIP:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_vmexit_eip=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_EXTINT_CYCLES:
|
|
{
|
|
char * inval;
|
|
G.output_defined = 1;
|
|
opt.scatterplot_extint_cycles=1;
|
|
opt.scatterplot_extint_cycles_vector = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case OPT_SCATTERPLOT_RDTSC:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_rdtsc=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_IRQ:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_irq=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_IO:
|
|
{
|
|
char * inval;
|
|
G.output_defined = 1;
|
|
opt.scatterplot_io=1;
|
|
opt.scatterplot_io_port = (int)strtol(arg, &inval, 0);
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case OPT_SCATTERPLOT_RUNSTATE:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_runstate=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_RUNSTATE_TIME:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_runstate_time=1;
|
|
break;
|
|
case OPT_SCATTERPLOT_PCPU:
|
|
G.output_defined = 1;
|
|
opt.scatterplot_pcpu=1;
|
|
break;
|
|
case OPT_HISTOGRAM_INTERRUPT_EIP:
|
|
{
|
|
char * inval, *p;
|
|
|
|
opt.histogram_interrupt_eip=1;
|
|
opt.histogram_interrupt_vector = (int)strtol(arg, &inval, 0);
|
|
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
|
|
p = inval;
|
|
|
|
if(*p == ',')
|
|
opt.histogram_interrupt_increment = (unsigned long long)strtoull(p+1, &inval, 0);
|
|
else
|
|
opt.histogram_interrupt_increment = 0x1000000;
|
|
|
|
printf("Making histogram of eips at interrupt %d, increment %llx\n",
|
|
opt.histogram_interrupt_vector,
|
|
opt.histogram_interrupt_increment);
|
|
}
|
|
break;
|
|
|
|
case OPT_INTERVAL_LENGTH:
|
|
{
|
|
char * inval;
|
|
|
|
opt.interval.msec = (unsigned) (strtof(arg, &inval) * 1000);
|
|
|
|
if ( inval == arg )
|
|
argp_usage(state);
|
|
|
|
break;
|
|
}
|
|
|
|
case OPT_INTERVAL_CR3_SCHEDULE_TIME:
|
|
{
|
|
if(parse_array(arg, &opt.interval.array) < 0)
|
|
goto usage;
|
|
interval_table_alloc(opt.interval.array.count);
|
|
opt.interval.output = INTERVAL_CR3_SCHEDULE_TIME;
|
|
opt.interval.check = INTERVAL_CHECK_CR3;
|
|
opt.interval.mode = INTERVAL_MODE_ARRAY;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
opt.with_cr3_enumeration = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
usage:
|
|
fprintf(stderr, "Invalid input for cr3_schedule_time\n");
|
|
argp_usage(state);
|
|
break;
|
|
}
|
|
|
|
case OPT_INTERVAL_CR3_SCHEDULE_TIME_ALL:
|
|
opt.interval.output = INTERVAL_CR3_SCHEDULE_TIME;
|
|
opt.interval.check = INTERVAL_CHECK_CR3;
|
|
opt.interval.mode = INTERVAL_MODE_LIST;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
opt.with_cr3_enumeration = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
|
|
case OPT_INTERVAL_CR3_SCHEDULE_ORDERED:
|
|
opt.interval.output = INTERVAL_CR3_SCHEDULE_ORDERED;
|
|
opt.interval.check = INTERVAL_CHECK_CR3;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
opt.with_cr3_enumeration = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
|
|
case OPT_INTERVAL_CR3_SHORT_SUMMARY:
|
|
{
|
|
if(parse_array(arg, &opt.interval.array) < 0
|
|
|| opt.interval.array.count != 1)
|
|
goto usage;
|
|
opt.interval.output = INTERVAL_CR3_SHORT_SUMMARY;
|
|
opt.interval.check = INTERVAL_CHECK_CR3;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
opt.with_cr3_enumeration = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
}
|
|
|
|
case OPT_INTERVAL_DOMAIN_TOTAL_TIME:
|
|
{
|
|
if(parse_array(arg, &opt.interval.array) < 0)
|
|
goto idtt_usage;
|
|
interval_table_alloc(opt.interval.array.count);
|
|
opt.interval.output = INTERVAL_DOMAIN_TOTAL_TIME;
|
|
opt.interval.check = INTERVAL_CHECK_DOMAIN;
|
|
opt.interval.mode = INTERVAL_MODE_ARRAY;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
idtt_usage:
|
|
fprintf(stderr, "Invalid input for domain_total_time\n");
|
|
argp_usage(state);
|
|
break;
|
|
}
|
|
|
|
case OPT_INTERVAL_DOMAIN_TOTAL_TIME_ALL:
|
|
opt.interval.output = INTERVAL_DOMAIN_TOTAL_TIME;
|
|
opt.interval.check = INTERVAL_CHECK_DOMAIN;
|
|
opt.interval.mode = INTERVAL_MODE_LIST;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
|
|
case OPT_INTERVAL_DOMAIN_SHORT_SUMMARY:
|
|
{
|
|
if((parse_array(arg, &opt.interval.array) < 0)
|
|
|| opt.interval.array.count != 1)
|
|
argp_usage(state);
|
|
|
|
opt.interval.output = INTERVAL_DOMAIN_SHORT_SUMMARY;
|
|
opt.interval.check = INTERVAL_CHECK_DOMAIN;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
}
|
|
|
|
case OPT_INTERVAL_DOMAIN_GUEST_INTERRUPT:
|
|
{
|
|
if((parse_array(arg, &opt.interval.array) < 0)
|
|
|| opt.interval.array.count != 1)
|
|
argp_usage(state);
|
|
|
|
opt.interval.output = INTERVAL_DOMAIN_GUEST_INTERRUPT;
|
|
opt.interval.check = INTERVAL_CHECK_DOMAIN;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
}
|
|
|
|
case OPT_INTERVAL_DOMAIN_GRANT_MAPS:
|
|
{
|
|
if((parse_array(arg, &opt.interval.array) < 0)
|
|
|| opt.interval.array.count != 1)
|
|
argp_usage(state);
|
|
|
|
opt.interval.output = INTERVAL_DOMAIN_GRANT_MAPS;
|
|
opt.interval.check = INTERVAL_CHECK_DOMAIN;
|
|
opt.interval_mode = 1;
|
|
opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
}
|
|
|
|
/* Summary group */
|
|
case OPT_SUMMARY:
|
|
opt.summary = 1;
|
|
opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
case OPT_REPORT_PCPU:
|
|
opt.report_pcpu = 1;
|
|
//opt.summary_info = 1;
|
|
G.output_defined = 1;
|
|
break;
|
|
/* Guest info group */
|
|
case OPT_DEFAULT_GUEST_PAGING_LEVELS:
|
|
{
|
|
char *inval;
|
|
opt.default_guest_paging_levels = (int)strtol(arg, &inval, 0);
|
|
if ( inval == arg )
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case OPT_SYMBOL_FILE:
|
|
/* FIXME - strcpy */
|
|
G.symbol_file = arg;
|
|
break;
|
|
/* Hardware info group */
|
|
case OPT_SVM_MODE:
|
|
opt.svm_mode = 1;
|
|
break;
|
|
case OPT_CPU_HZ:
|
|
parse_cpu_hz(arg);
|
|
break;
|
|
break;
|
|
|
|
case OPT_TOLERANCE:
|
|
{
|
|
char * inval;
|
|
|
|
opt.tolerance = (int)strtol(arg, &inval, 0);
|
|
|
|
if( inval == arg )
|
|
argp_usage(state);
|
|
|
|
if ( opt.tolerance > ERR_MAX_TOLERABLE )
|
|
{
|
|
fprintf(stderr, "ERROR: Max tolerable error %d\n",
|
|
ERR_MAX_TOLERABLE);
|
|
exit(1);
|
|
}
|
|
|
|
printf("Tolerating errors at or below %d\n",
|
|
opt.tolerance);
|
|
}
|
|
break;
|
|
|
|
case OPT_PROGRESS:
|
|
opt.progress = 1;
|
|
break;
|
|
|
|
case OPT_TSC_LOOP_FATAL:
|
|
opt.tsc_loop_fatal = 1;
|
|
break;
|
|
|
|
case ARGP_KEY_ARG:
|
|
{
|
|
/* FIXME - strcpy */
|
|
if (state->arg_num == 0)
|
|
G.trace_file = arg;
|
|
else
|
|
argp_usage(state);
|
|
}
|
|
break;
|
|
case ARGP_KEY_END:
|
|
{
|
|
if(opt.interval_mode) {
|
|
opt.interval.cycles = ( opt.interval.msec * opt.cpu_hz ) / 1000 ;
|
|
interval_header();
|
|
}
|
|
|
|
if(!G.output_defined)
|
|
{
|
|
fprintf(stderr, "No output defined, using summary.\n");
|
|
opt.summary = 1;
|
|
opt.summary_info = 1;
|
|
}
|
|
fprintf(stderr, "Using %s hardware-assisted virtualization.\n",
|
|
opt.svm_mode?"SVM":"VMX");
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return ARGP_ERR_UNKNOWN;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct argp_option cmd_opts[] = {
|
|
/* Dump group */
|
|
{ .name = "dump-all",
|
|
.key = OPT_DUMP_ALL,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Dump all records as they come in.", },
|
|
|
|
{ .name = "dump-raw-reads",
|
|
.key = OPT_DUMP_RAW_READS,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Dump raw data as it's read from disk. Useful mainly for debugging the analysis tool.", },
|
|
|
|
{ .name = "dump-no-processing",
|
|
.key = OPT_DUMP_NO_PROCESSING,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Don't do any processing on records other than cpu changes. Implies dump-raw-reads (or you wouldn't get anything).", },
|
|
|
|
{ .name = "dump-raw-process",
|
|
.key = OPT_DUMP_RAW_PROCESS,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Dump raw data as it's processed. Useful mainly for debugging the analysis tool.", },
|
|
|
|
{ .name = "dump-ipi-latency",
|
|
.key = OPT_DUMP_IPI_LATENCY,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Dump IPI latency info as IPIs are delivered (vector 0xd1 only).", },
|
|
|
|
{ .name = "dump-trace-volume-on-lost-record",
|
|
.key = OPT_DUMP_TRACE_VOLUME_ON_LOST_RECORD,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Dump the volume of trace types in the previous cpu buffer when a lost record is created.", },
|
|
|
|
{ .name = "dump-show-power-states",
|
|
.key = OPT_DUMP_SHOW_POWER_STATES,
|
|
.group = OPT_GROUP_DUMP,
|
|
.doc = "Show the power-state of the physical cpu when dumping output.", },
|
|
|
|
/* Extra processing group */
|
|
{ .name = "with-cr3-enumeration",
|
|
.key = OPT_WITH_CR3_ENUMERATION,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Keep track of per-cr3 values", },
|
|
|
|
{ .name = "with-pio-enumeration",
|
|
.key = OPT_WITH_PIO_ENUMERATION,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Report summary info on indiviaul IO addresses", },
|
|
|
|
{ .name = "with-mmio-enumeration",
|
|
.key = OPT_WITH_MMIO_ENUMERATION,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Report summary info on indiviaul MMIO addresses.", },
|
|
|
|
{ .name = "with-interrupt-eip-enumeration",
|
|
.key = OPT_WITH_INTERRUPT_EIP_ENUMERATION,
|
|
.arg = "vector",
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Report a summary on eips interrupted by specified vector.", },
|
|
|
|
{ .name = "scatterplot-interrupt-eip",
|
|
.key = OPT_SCATTERPLOT_INTERRUPT_EIP,
|
|
.arg = "vector",
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of eips as a function of time.", },
|
|
|
|
{ .name = "scatterplot-extint-cycles",
|
|
.key = OPT_SCATTERPLOT_EXTINT_CYCLES,
|
|
.arg = "vector",
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output a scatterplot of vmexit cycles for external interrupts of the given vector as a function of time.", },
|
|
|
|
{ .name = "scatterplot-unpin-promote",
|
|
.key = OPT_SCATTERPLOT_UNPIN_PROMOTE,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of unpins and promotions. If " \
|
|
"--with-cr3-enumeration is included, promotions include current cr3.", },
|
|
|
|
{ .name = "scatterplot-cr3-switch",
|
|
.key = OPT_SCATTERPLOT_CR3_SWITCH,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of cr3 switches.", },
|
|
|
|
{ .name = "scatterplot-wake-to-halt",
|
|
.key = OPT_SCATTERPLOT_WAKE_TO_HALT,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of wake-to-halt.", },
|
|
|
|
{ .name = "scatterplot-vmexit-eip",
|
|
.key = OPT_SCATTERPLOT_VMEXIT_EIP,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of vmexit eips.", },
|
|
|
|
{ .name = "scatterplot-io",
|
|
.key = OPT_SCATTERPLOT_IO,
|
|
.arg = "port",
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of io latencies for givein address as a function of time.", },
|
|
|
|
{ .name = "scatterplot-runstate",
|
|
.key = OPT_SCATTERPLOT_RUNSTATE,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of runstate.", },
|
|
|
|
{ .name = "scatterplot-runstate-time",
|
|
.key = OPT_SCATTERPLOT_RUNSTATE_TIME,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of time in a runstate.", },
|
|
|
|
{ .name = "scatterplot-pcpu",
|
|
.key = OPT_SCATTERPLOT_PCPU,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of which pcpu vcpus are run on.", },
|
|
|
|
{ .name = "scatterplot-rdtsc",
|
|
.key = OPT_SCATTERPLOT_RDTSC,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of rdtsc values.", },
|
|
|
|
{ .name = "scatterplot-irq",
|
|
.key = OPT_SCATTERPLOT_IRQ,
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output scatterplot of irqs on pcpus.", },
|
|
|
|
{ .name = "histogram-interrupt-eip",
|
|
.key = OPT_HISTOGRAM_INTERRUPT_EIP,
|
|
.arg = "vector[,increment]",
|
|
.group = OPT_GROUP_EXTRA,
|
|
.doc = "Output histograms of eips.", },
|
|
|
|
{ .name = "interval",
|
|
.key = OPT_INTERVAL_LENGTH,
|
|
.arg = "sec",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Interval length to do time-based graphs, in seconds", },
|
|
|
|
{ .name = "interval-cr3-schedule-time",
|
|
.key = OPT_INTERVAL_CR3_SCHEDULE_TIME,
|
|
.arg = "gmfn[,gmfn...]",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with the listed cr3 value(s) every interval.", },
|
|
|
|
{ .name = "interval-cr3-schedule-time-all",
|
|
.key = OPT_INTERVAL_CR3_SCHEDULE_TIME_ALL,
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with all cr3 values every interval.", },
|
|
|
|
{ .name = "interval-cr3-schedule-ordered",
|
|
.key = OPT_INTERVAL_CR3_SCHEDULE_ORDERED,
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print summary with the top 10 cr3 values every interval.", },
|
|
|
|
{ .name = "interval-cr3-short-summary",
|
|
.key = OPT_INTERVAL_CR3_SHORT_SUMMARY,
|
|
.arg = "gmfn",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with the hvm short summary of cr3 value every interval.", },
|
|
|
|
{ .name = "interval-domain-total-time",
|
|
.key = OPT_INTERVAL_DOMAIN_TOTAL_TIME,
|
|
.arg = "domain[,domain...]",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with the listed domain(s) total runtime every interval.", },
|
|
|
|
{ .name = "interval-domain-total-time-all",
|
|
.key = OPT_INTERVAL_DOMAIN_TOTAL_TIME_ALL,
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with all domains every interval.", },
|
|
|
|
{ .name = "interval-domain-short-summary",
|
|
.key = OPT_INTERVAL_DOMAIN_SHORT_SUMMARY,
|
|
.arg = "domain-id",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with the hvm short summary of given domain every interval.", },
|
|
|
|
{ .name = "interval-domain-guest-interrupt",
|
|
.key = OPT_INTERVAL_DOMAIN_GUEST_INTERRUPT,
|
|
.arg = "domain-id",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with the guest interrupt count of given domain every interval.", },
|
|
|
|
{ .name = "interval-domain-grant-maps",
|
|
.key = OPT_INTERVAL_DOMAIN_GRANT_MAPS,
|
|
.arg = "domain-id",
|
|
.group = OPT_GROUP_INTERVAL,
|
|
.doc = "Print a csv with the grant maps done on behalf of a given domain every interval.", },
|
|
|
|
/* Summary group */
|
|
{ .name = "show-default-domain-summary",
|
|
.key = OPT_SHOW_DEFAULT_DOMAIN_SUMMARY,
|
|
.group = OPT_GROUP_SUMMARY,
|
|
.doc = "Show default domain information on summary", },
|
|
|
|
{ .name = "mmio-enumeration-skip-vga",
|
|
.key = OPT_MMIO_ENUMERATION_SKIP_VGA,
|
|
.arg = "[0|1]",
|
|
.group = OPT_GROUP_SUMMARY,
|
|
.doc = "Control whether we enumerate MMIO accesses to the VGA area, which can be extremely high during boot. Default: 0", },
|
|
|
|
{ .name = "sample-size",
|
|
.key = OPT_SAMPLE_SIZE,
|
|
.arg = "size",
|
|
.group = OPT_GROUP_SUMMARY,
|
|
.doc = "Start with [size] samples for percentile purposes. Enter 0 to" \
|
|
"disable. Default 1024.", },
|
|
|
|
{ .name = "sample-max",
|
|
.key = OPT_SAMPLE_MAX,
|
|
.arg = "size",
|
|
.group = OPT_GROUP_SUMMARY,
|
|
.doc = "Do not allow sample to grow beyond [size] samples for percentile"\
|
|
" purposes. Enter 0 for no limit.", },
|
|
|
|
{ .name = "summary",
|
|
.key = OPT_SUMMARY,
|
|
.group = OPT_GROUP_SUMMARY,
|
|
.doc = "Output a summary", },
|
|
|
|
{ .name = "report-pcpu",
|
|
.key = OPT_REPORT_PCPU,
|
|
.group = OPT_GROUP_SUMMARY,
|
|
.doc = "Report utilization for pcpus", },
|
|
|
|
/* Guest info */
|
|
{ .name = "default-guest-paging-levels",
|
|
.key = OPT_DEFAULT_GUEST_PAGING_LEVELS,
|
|
.group = OPT_GROUP_GUEST,
|
|
.arg = "L",
|
|
.doc = "Default guest paging levels. Mainly necessary for Rio, as Miami traces include guest paging levels where appropriate.", },
|
|
|
|
{ .name = "symbol-file",
|
|
.key = OPT_SYMBOL_FILE,
|
|
.group = OPT_GROUP_GUEST,
|
|
.arg = "filename",
|
|
.doc = "A symbol file for interpreting guest eips.", },
|
|
|
|
/* Hardware info */
|
|
{ .name = "cpu-hz",
|
|
.key = OPT_CPU_HZ,
|
|
.group = OPT_GROUP_HARDWARE,
|
|
.arg = "HZ",
|
|
.doc = "Cpu speed of the tracing host, used to convert tsc into seconds.", },
|
|
|
|
{ .name = "progress",
|
|
.key = OPT_PROGRESS,
|
|
.doc = "Progress dialog. Requires the zenity (GTK+) executable.", },
|
|
|
|
{ .name = "tsc-loop-fatal",
|
|
.key = OPT_TSC_LOOP_FATAL,
|
|
.doc = "Stop processing and exit if tsc skew tracking detects a dependency loop.", },
|
|
|
|
{ .name = "tolerance",
|
|
.key = OPT_TOLERANCE,
|
|
.arg = "errlevel",
|
|
.doc = "Sets tolerance for errors found in the file. Default is 3; max is 6.", },
|
|
|
|
|
|
{ 0 },
|
|
};
|
|
|
|
const struct argp parser_def = {
|
|
.options = cmd_opts,
|
|
.parser = cmd_parser,
|
|
.args_doc = "[trace file]",
|
|
.doc = "",
|
|
};
|
|
|
|
const char *argp_program_bug_address = "George Dunlap <george.dunlap@eu.citrix.com>";
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
|
/* Start with warn at stderr. */
|
|
warn = stderr;
|
|
|
|
argp_parse(&parser_def, argc, argv, 0, NULL, NULL);
|
|
|
|
if (G.trace_file == NULL)
|
|
exit(1);
|
|
|
|
if ( (G.fd = open(G.trace_file, O_RDONLY)) < 0) {
|
|
perror("open");
|
|
error(ERR_SYSTEM, NULL);
|
|
} else {
|
|
struct stat s;
|
|
fstat(G.fd, &s);
|
|
G.file_size = s.st_size;
|
|
}
|
|
|
|
if ( (G.mh = mread_init(G.fd)) == NULL )
|
|
perror("mread");
|
|
|
|
if (G.symbol_file != NULL)
|
|
parse_symbol_file(G.symbol_file);
|
|
|
|
if(opt.dump_all)
|
|
warn = stdout;
|
|
|
|
init_pcpus();
|
|
|
|
if(opt.progress)
|
|
progress_init();
|
|
|
|
process_records();
|
|
|
|
if(opt.interval_mode)
|
|
interval_tail();
|
|
|
|
if(opt.summary)
|
|
summary();
|
|
|
|
if(opt.report_pcpu)
|
|
report_pcpu();
|
|
|
|
if(opt.progress)
|
|
progress_finish();
|
|
|
|
return 0;
|
|
}
|
|
/*
|
|
* Local variables:
|
|
* mode: C
|
|
* c-set-style: "BSD"
|
|
* c-basic-offset: 4
|
|
* tab-width: 4
|
|
* indent-tabs-mode: nil
|
|
* End:
|
|
*/
|