From 5fd83771641d15c418f747bd343ba6738d3875f7 Mon Sep 17 00:00:00 2001 From: Cameron Katri Date: Sun, 9 May 2021 14:20:58 -0400 Subject: Import macOS userland adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106 --- system_cmds/trace.tproj/trace.1 | 380 +++++ system_cmds/trace.tproj/trace.c | 2941 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 3321 insertions(+) create mode 100644 system_cmds/trace.tproj/trace.1 create mode 100644 system_cmds/trace.tproj/trace.c (limited to 'system_cmds/trace.tproj') diff --git a/system_cmds/trace.tproj/trace.1 b/system_cmds/trace.tproj/trace.1 new file mode 100644 index 0000000..afbd280 --- /dev/null +++ b/system_cmds/trace.tproj/trace.1 @@ -0,0 +1,380 @@ +.\" Copyright (c) 2010, Apple Inc. All rights reserved. +.\" +.Dd April 3, 2015 +.Dt TRACE 1 +.Os "Mac OS X" +.Sh NAME +.Nm trace +.Nd configure, record, and display kernel trace events +.Sh SYNOPSIS +.Bl -hang -compact -width "trace -" +.\" +.It Nm Fl d +.Op Fl a Ar pid | Fl x Ar pid +.\" +.It Nm Fl e +.Op Fl c Ar class Oo Fl p Ar end-class Oc Oo Fl s Ar subclass Oc +.Op Fl a Ar pid | Fl x Ar pid +.Op Fl k Ar code +.Op Fl P +.Op Fl T Ar filter-file +.\" +.It Nm Fl E +.Op Fl c Ar class Oo Fl p Ar end-class Oc Oo Fl s Ar subclass Oc +.Op Fl a Ar pid | Fl x Ar pid +.Op Fl k Ar code +.Op Fl P +.Op Fl T Ar tracefilter +.Ar command +.Op Ar argument ... +.\" +.It Nm Fl h +.\" +.It Nm Fl i +.Op Fl b Ar num-events +.\" +.It Nm Fl g +.\" +.It Nm Fl l Ar rawfile +.\" +.It Nm Fl L Ar rawfile +.Op Fl S Ar secs +.\" +.It Nm Fl n +.\" +.It Nm Fl r +.\" +.It Nm Fl R Ar rawfile +.Op Fl X +.Op Fl F Ar frequency +.Op Fl o Ar outfile +.Op Fl N +.Op Ar codesfile ... +.\" +.It Nm Fl t +.Op Fl o Ar outfile +.Op Fl N +.Op Ar codesfile ... +.El +.Sh DESCRIPTION +.Nm +initializes and configures the kernel trace subsystem. Trace events can +be recorded to an in-memory buffer or logged directly to a file, and +optionally converted to a human-readable, plain-text format. +.Pp +.Nm +operates according to the command flag specified. +.Pp +NOTE: +.Nm +is obsolete. The command you probably want is +.Xr ktrace 1 +.Sh COMMANDS +.Bl -tag -width Ds +.It Fl h +Print a succinct help message to +.Xr stdout 4 . +.\" +.\" ## trace -i ## +.It Fl i Op Fl b Ar num-events +.Pp +Initialize the kernel trace buffer. This command is required before +tracing. +.Bl -tag -width Ds +.It Fl b Ar num-events +Set the number of events that can be stored in the kernel trace buffer. +.Ar num-events +is a decimal number of events. The default (and minimum) value is 8192 +event records per logical CPU. No more than half of available +memory may be used by trace buffers, though running with a buffer this +large is not recommended. +.El +.\" +.\" ## trace -g ## +.It Fl g +Print the current kernel trace buffer settings to +.Xr stdout 4 . +.\" +.\" ## trace -d ## +.It Fl d Op Fl a Ar pid | Fl x Ar pid +.Pp +By default, disable collection of events. This command does not remove +the kernel trace buffer. +.Bl -tag -width Ds +.It Fl a Ar pid +Disable event collection for the process identified by +.Ar pid . +.It Fl x Ar pid +Stop excluding +.Ar pid +from the trace. +This reenables event collection of events for. +.Ar pid . +.El +.\" +.\" ## trace -r ## +.It Fl r +Remove the kernel trace buffer. +.\" +.\" ## trace -n ## +.It Fl n +Disable ring buffer mode. When set, the trace buffer will fill to capacity +and then stop collecting events. Ring buffer mode is sometimes called +"head," "stop," or "no-wrap" mode. By default, the trace buffer will +wrap around, overwriting previous events. The default behavior is +sometimes called windowed or wrap-around mode. +.\" +.\" ## trace -e ## +.Bk -words +.It Xo Fl e +.Op Fl c Ar class Oo Fl p Ar end-class Oc Oo Fl s Ar subclass Oc +.Op Fl a Ar pid | Fl x Ar pid +.Op Fl k Ar code ... +.Op Fl P +.Op Fl T Ar filter-file +.Ek +.Xc +.Pp +Enable collection of events. By default, all events are collected. +.Pp +Options can be used to restrict collection to specific classes, class +ranges, subclasses, and codes. Classes and subclasses can be specified +any number of times, but only 4 codes can be filtered at once. Values +can be specified in hex (0x...), decimal, or octal (0...). +.Bl -tag -width " " +.It Fl c Ar class +Restrict collection to the events with the specified +.Ar class . +This option can be specified any number of times to collect events from +more classes. +.Bl -tag -width " " +.It Fl p Ar end-class +Provide an ending class to restrict collection to events in an inclusive +range of classes between +.Ar class +and +.Ar end-class . +.It Fl s Ar subclass +Restrict collection to the events with the specified +.Ar subclass . +.El +.It Fl a Ar pid +Restrict collection to to those events emitted by the process identified +by +.Ar pid . +.It Fl x Ar pid +Exclude events emitted by the process identified by +.Ar pid . +.It Fl k Ar code +Restrict collection to events with the specified +.Ar code . +This option can be specified up to 4 times, and applies to all classes +being collected. +.It Fl T Ar filter-file +Restrict collection to events specified in the provided +.Ar filter-file . +See +.Sx TRACEFILTER FORMAT +for details. +.It Xo Fl P +Restrict collection to PPT events. This special collection of trace +events provides insight into system performance. +.Xc +.El +.\" +.\" ## trace -E ## +.Bk -words +.It Xo Fl E +.Op Fl c Ar class Oo Fl p Ar end-class Oc Oo Fl s Ar subclass Oc +.Op Fl a Ar pid | Fl x Ar pid +.Op Fl k Ar code ... +.Op Fl P +.Op Fl T Ar filter-file +.Ar command Op args ... +.Ek +.Xc +.Pp +Execute +.Ar command +with trace collection enabled for events emitted by the process. See +.Fl e +for more information. +.\" +.\" ## trace -t ## +.It Fl t Oo Fl o Ar outfile Oc Oo Fl N Oc Oo Ar codesfile ... Oc +.Pp +Extract events from the kernel trace buffer and print them in a formatted, +plain-text representation. Additional code files can be specified to +help +.Nm +find the names of unknown events. For more information on the formatting +process, see +.Sx TRACE CODES FORMAT . +.Bl -tag -width Ds +.It Fl o Ar outfile +Output the plain-text events to +.Ar outfile . +.It Fl N +Ignore the system-wide trace codes file when getting names of events. +Additional trace codes files specified will still be used. +.El +.\" +.\" ## trace -l ## +.It Fl l Ar rawfile +.Pp +Empty the current kernel trace buffer into +.Ar rawfile +in a binary format. If +.Ar rawfile +is +.Li - , +the file will be written to +.Xr stdout 4 . +.\" +.\" ## trace -L ## +.It Fl L Ar rawfile Fl S Ar seconds +.Pp +Copy the current trace buffer to +.Ar rawfile +and send all future trace events to +.Ar rawfile . +.Bl -tag -width Ds +.It Fl S Ar seconds +After +.Ar seconds +have elapsed, stop recording and exit. If +.Ar rawfile +is +.Li - , +the file will be written to +.Xr stdout 4 . +.El +.\" +.\" ## trace -R ## +.It Fl R Ar rawfile Oo Fl o Ar outfile Oc Oo Fl N Oc Oo Fl F Ar frequency Oc Oo Fl X Oc Op Ar codesfile ... +.Pp +Read events from +.Ar rawfile +and print them in a human-readable format. +.Bl -tag -width " " +.It Fl F Ar frequency +If +.Ar rawfile +does not contain clock frequency information, it can be specified with +.Fl F . +.It Fl X +Force the binary format to be interpreted as 32-bit, as opposed to +matching the width of the system running +.Nm . +.El +.Pp +See +.Fl t +for additional options. +.El +.Sh TRACE CODES FORMAT +Event classes, subclasses, and codes are matched to names using a trace +codes file. Any events that cannot be matched will be referred to by +their debugid in hex. +.Pp +The system-wide trace codes file is located at +.Pa /usr/share/misc/trace.codes . +Additional files are typically stored in +.Pa /usr/local/share/misc . +.Pp +A code file consists of a list of tracepoints, one per line, with the +tracepoint's debugid (component, subclass, and code) in hex, followed by +a tab, followed by the tracepoint's name. +.Pp +For instance, the MSC_mach_msg_trap tracepoint is defined by +.Pp +.Dl 0x010c007c MSC_mach_msg_trap +.Pp +This describes the tracepoint with the following info: +.Pp +.Bl -column -offset indent "Subclass" "MSC_mach_msg_trap" +.\" is this right? We should refer to the shifting and kdebug.h +.It Sy Name Ta MSC_mach_msg_trap +.It Sy Class Ta 0x01 Ta (Mach events) +.It Sy Subclass Ta 0x0c Ta (Mach system calls) +.It Sy Code Ta 0x007c Ta (msg_trap) +.El +.Pp +See +.\" this absolute path no longer exists thanks to SDKs. :P +.Pa /usr/include/sys/kdebug.h +for class and subclass values. +.Sh TRACEFILTER FORMAT +A tracefilter description file consists of a list of class and subclass +filters in hex, one per line, which are applied as if they were passed +with +.Fl c +and +.Fl s . +Pass +.Fl v +to see what classes and subclasses are being set. +.Pp +Comment lines start with +.Ql # , +class filter lines start with +.Ql C , +and subclass filter lines start with +.Ql S +and include the class they apply to. +.Pp +For example, to trace Mach events (class 1): +.Pp +.Dl C 0x01 +.Pp +And to trace Mach system calls (class 1, subclass 13): +.Pp +.Dl S 0x010C +.Pp +.Sh EXAMPLES +.Nm +usually requires multiple invocations in order to set up the trace +buffers, enable the correct events, and collect the events. A typical +session with trace is: +.Pp +.Dl trace -i +.Dl trace -e -c 1 -s 31 +.Dl sleep 1 +.Dl trace -d +.Dl trace -t +.Pp +This initializes the trace buffers to their default values, enables the +mach_msg_trap subclass of the MACH_SysCall class, waits for one second, +then disables tracing and prints it to +.Xr stdout 4 . +This is useful for investigating isolated issues or gaining some +understanding about a kernel subsystem. If a specific execuable should +be traced, with the events saved for later analysis, the sequence of +commands would be: +.Pp +.Dl trace -i +.Dl trace -E -c 0x4 ./my_prog +.Dl trace -d +.Dl trace -l tracefile +.Dl trace -R tracefile +.Pp +This initializes the trace buffers, enables all events in the BSC_SysCall class and runs +.Li my_prog , +disables tracing, collects events into +.Li tracefile , +and finally prints those events out in a human-readable form. +.Sh CAVEATS +Almost all +.Nm +command flags require superuser (root) privileges. +.Pp +After failures, the trace buffers usually need to be re-initialized. +.Pp +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr trace 4 , +.Xr fs_usage 1 , +.Xr sc_usage 1 , +.Xr latency 1 , +.Xr top 1 diff --git a/system_cmds/trace.tproj/trace.c b/system_cmds/trace.tproj/trace.c new file mode 100644 index 0000000..f85b336 --- /dev/null +++ b/system_cmds/trace.tproj/trace.c @@ -0,0 +1,2941 @@ +/* + cc -I/System/Library/Frameworks/System.framework/Versions/B/PrivateHeaders -arch x86_64 -arch i386 -O -o trace trace.c +*/ + +/* + * NOTE: There exists another copy of this file in the kernel_tools. Changes + * made here may also need to be made there. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef KERNEL_PRIVATE +#define KERNEL_PRIVATE +#include +#undef KERNEL_PRIVATE +#else +#include +#endif /*KERNEL_PRIVATE*/ +#include + +#include +#include + +int nbufs = 0; +int enable_flag=0; +int execute_flag=0; +int logRAW_flag=0; +int LogRAW_flag=0; +int readRAW_flag = 0; +int disable_flag=0; +int init_flag=0; +int kval_flag=0; +int remove_flag=0; +int bufset_flag=0; +int bufget_flag=0; +int filter_flag=0; +int filter_file_flag=0; +int filter_alloced=0; +int trace_flag=0; +int nowrap_flag=0; +int freerun_flag=0; +int verbose_flag=0; +int usage_flag=0; +int pid_flag=0; +int pid_exflag=0; +int ppt_flag=0; +int done_with_args=0; +int no_default_codes_flag=0; + +unsigned int value1=0; +unsigned int value2=0; +unsigned int value3=0; +unsigned int value4=0; + +pid_t pid=0; +int reenable=0; + +int force_32bit_exec = 0; +int frequency = 0; + +int mib[6]; +size_t needed; + +char *logfile = (char *)0; /* This file is trace format */ +char *RAW_file = (char *)0; +FILE *output_file; +int output_fd; + +extern char **environ; + +uint8_t* type_filter_bitmap; + +#define SIZE_4KB (4 * (1 << 10)) + +#define DBG_FUNC_ALL (DBG_FUNC_START | DBG_FUNC_END) +#define DBG_FUNC_MASK 0xfffffffc +#define SHORT_HELP 1 +#define LONG_HELP 0 + +#define CSC_MASK 0xffff0000 + +#define BSC_exit 0x040c0004 +#define BSC_thread_terminate 0x040c05a4 +#define MACH_SCHEDULED 0x01400000 +#define MACH_MAKERUNNABLE 0x01400018 +#define MACH_STKHANDOFF 0x01400008 + +#define EMPTYSTRING "" +#define UNKNOWN "unknown" + +char tmpcommand[MAXCOMLEN]; + +int total_threads = 0; +int nthreads = 0; +kd_threadmap *mapptr = 0; + +kd_cpumap_header* cpumap_header = NULL; +kd_cpumap* cpumap = NULL; + +/* + If NUMPARMS changes from the kernel, + then PATHLENGTH will also reflect the change + This is for the vfslookup entries that + return pathnames +*/ +#define NUMPARMS 23 +#define PATHLENGTH (NUMPARMS*sizeof(long)) + + +#define US_TO_SLEEP 50000 +#define BASE_EVENTS 500000 + +mach_timebase_info_data_t mach_timebase; +double divisor; + +typedef struct { + uint32_t debugid; + char *debug_string; +} code_type_t; + +code_type_t* codesc = 0; +size_t codesc_idx = 0; // Index into first empty codesc entry + + + +typedef struct event *event_t; + +struct event { + event_t ev_next; + + uint64_t ev_thread; + uint32_t ev_debugid; + uint64_t ev_timestamp; +}; + +typedef struct lookup *lookup_t; + +struct lookup { + lookup_t lk_next; + + uint64_t lk_thread; + uint64_t lk_dvp; + int64_t *lk_pathptr; + int64_t lk_pathname[NUMPARMS + 1]; +}; + +typedef struct threadmap *threadmap_t; + +struct threadmap { + threadmap_t tm_next; + + uint64_t tm_thread; + uint64_t tm_pthread; + boolean_t tm_deleteme; + char tm_command[MAXCOMLEN + 1]; +}; + +#define HASH_SIZE 1024 +#define HASH_MASK 1023 + +event_t event_hash[HASH_SIZE]; +lookup_t lookup_hash[HASH_SIZE]; +threadmap_t threadmap_hash[HASH_SIZE]; + +event_t event_freelist; +lookup_t lookup_freelist; +threadmap_t threadmap_freelist; +threadmap_t threadmap_temp; + + +#define SBUFFER_SIZE (128 * 4096) +char sbuffer[SBUFFER_SIZE]; + +int secs_to_run = 0; +int use_current_buf = 0; + + +kbufinfo_t bufinfo = {0, 0, 0, 0}; + +int codenum = 0; +int codeindx_cache = 0; + +static void quit(char *); +static int match_debugid(unsigned int, char *, int *); +static void usage(int short_help); +static int argtoi(int flag, char *req, char *str, int base); +static int parse_codefile(const char *filename); +static void codesc_find_dupes(void); +static int read_command_map(int, uint32_t); +static void read_cpu_map(int); +static void find_thread_command(kd_buf *, char **); +static void create_map_entry(uint64_t, char *); +static void getdivisor(); +static unsigned long argtoul(); + +static void set_enable(int); +static void set_remove(); +static void set_nowrap(); +static void set_pidcheck(int, int); +static void set_pidexclude(int, int); +static void set_numbufs(int); +static void set_freerun(); +static void get_bufinfo(kbufinfo_t *); +static int get_ktrace_state(void); +static void set_init(); +static void set_kval_list(); +static void readtrace(char *); +static void log_trace(); +static void Log_trace(); +static void read_trace(); +static void signal_handler(int); +static void signal_handler_RAW(int); +static void delete_thread_entry(uint64_t); +static void find_and_insert_tmp_map_entry(uint64_t, char *); +static void create_tmp_map_entry(uint64_t, uint64_t); +static void find_thread_name(uint64_t, char **, boolean_t); +static void execute_process(char * const argv[]); + +static int writetrace(int); +static int write_command_map(int); +static int debugid_compar(const void *, const void *); + +static threadmap_t find_thread_entry(uint64_t); + +static void saw_filter_class(uint8_t class); +static void saw_filter_end_range(uint8_t end_class); +static void saw_filter_subclass(uint8_t subclass); +static void filter_done_parsing(void); + +static void set_filter(void); +static void set_filter_class(uint8_t class); +static void set_filter_range(uint8_t class, uint8_t end); +static void set_filter_subclass(uint8_t class, uint8_t subclass); + +static void parse_filter_file(char *filename); + +static void quit_args(const char *fmt, ...) __printflike(1, 2); + +#ifndef KERN_KDWRITETR +#define KERN_KDWRITETR 17 +#endif + +#ifndef KERN_KDWRITEMAP +#define KERN_KDWRITEMAP 18 +#endif + +#ifndef F_FLUSH_DATA +#define F_FLUSH_DATA 40 +#endif + +#ifndef RAW_VERSION1 +typedef struct { + int version_no; + int thread_count; + uint64_t TOD_secs; + uint32_t TOD_usecs; +} RAW_header; + +#define RAW_VERSION0 0x55aa0000 +#define RAW_VERSION1 0x55aa0101 +#endif + +#define ARRAYSIZE(x) ((int)(sizeof(x) / sizeof(*x))) + +#define EXTRACT_CLASS_LOW(debugid) ( (uint8_t) ( ((debugid) & 0xFF00 ) >> 8 ) ) +#define EXTRACT_SUBCLASS_LOW(debugid) ( (uint8_t) ( ((debugid) & 0xFF ) ) ) + +#define ENCODE_CSC_LOW(class, subclass) \ + ( (uint16_t) ( ((class) & 0xff) << 8 ) | ((subclass) & 0xff) ) + +RAW_header raw_header; + + + +void set_enable(int val) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDENABLE; +#ifdef KDEBUG_ENABLE_PPT + if (ppt_flag && val) { + mib[3] = KDEBUG_ENABLE_PPT; + } else { + mib[3] = val; + } +#else + mib[3] = val; +#endif + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 4, NULL, &needed, NULL, 0) < 0) { + if (errno == EINVAL) { + quit_args("trace facility failure, KERN_KDENABLE: trace buffer is uninitialized\n"); + } + quit_args("trace facility failure, KERN_KDENABLE: %s\n", strerror(errno)); + } +} + +void set_remove(void) +{ + extern int errno; + + errno = 0; + + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDREMOVE; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, NULL, &needed, NULL, 0) < 0) + { + if (errno == EBUSY) + quit("the trace facility is currently in use...\n fs_usage, sc_usage, trace, and latency use this feature.\n\n"); + else + quit_args("trace facility failure, KERN_KDREMOVE: %s\n", strerror(errno)); + } +} + +void set_numbufs(int nbufs) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDSETBUF; + mib[3] = nbufs; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 4, NULL, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDSETBUF: %s\n", strerror(errno)); + + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDSETUP; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, NULL, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDSETUP: %s\n", strerror(errno)); +} + +void set_nowrap(void) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDEFLAGS; + mib[3] = KDBG_NOWRAP; + mib[4] = 0; + mib[5] = 0; /* no flags */ + if (sysctl(mib, 4, NULL, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KDBG_NOWRAP: %s\n", strerror(errno)); + +} + +void set_pidcheck(int pid, int on_off_flag) +{ + kd_regtype kr; + + kr.type = KDBG_TYPENONE; + kr.value1 = pid; + kr.value2 = on_off_flag; + needed = sizeof(kd_regtype); + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDPIDTR; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, &kr, &needed, NULL, 0) < 0) + { + if (errno == EACCES) + { + quit_args("trace facility failure, setting pid filter: %s\n", + strerror(errno)); + } + else if (on_off_flag == 1 && errno == ESRCH) + { + set_remove(); + quit_args("trace facility failure, setting pid filter: " + "pid %d does not exist\n", pid); + } + else + { + quit_args("trace facility failure, KERN_KDPIDTR: %s\n", strerror(errno)); + } + } +} + +void set_pidexclude(int pid, int on_off_flag) +{ + kd_regtype kr; + + kr.type = KDBG_TYPENONE; + kr.value1 = pid; + kr.value2 = on_off_flag; + needed = sizeof(kd_regtype); + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDPIDEX; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, &kr, &needed, NULL, 0) < 0) + { + if (on_off_flag == 1) + { + printf ("pid %d does not exist\n", pid); + set_remove(); + exit(2); + } + } +} + +void set_freerun(void) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDEFLAGS; + mib[3] = KDBG_FREERUN; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 4, NULL, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KDBG_FREERUN: %s\n", strerror(errno)); +} + +static int get_ktrace_state(void) +{ + int state; + size_t state_size = sizeof(state); + int err = sysctlbyname("ktrace.state", &state, &state_size, NULL, 0); + if (err) { + fprintf(stderr, "error: could not query ktrace.state sysctl (%d: %s)\n", errno, strerror(errno)); + exit(1); + } + return state; +} + +void get_bufinfo(kbufinfo_t *val) +{ + needed = sizeof (*val); + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDGETBUF; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, val, &needed, 0, 0) < 0) + quit_args("trace facility failure, KERN_KDGETBUF: %s\n", strerror(errno)); +} + +void set_init(void) +{ + kd_regtype kr; + + kr.type = KDBG_RANGETYPE; + kr.value1 = 0; + kr.value2 = -1; + needed = sizeof(kd_regtype); + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDSETREG; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, &kr, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDSETREG (rangetype): %s\n", strerror(errno)); + + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDSETUP; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, NULL, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDSETUP: %s\n", strerror(errno)); +} + +static void +set_filter(void) +{ + errno = 0; + int mib[] = { CTL_KERN, KERN_KDEBUG, KERN_KDSET_TYPEFILTER }; + size_t needed = KDBG_TYPEFILTER_BITMAP_SIZE; + + if(sysctl(mib, ARRAYSIZE(mib), type_filter_bitmap, &needed, NULL, 0)) { + quit_args("trace facility failure, KERN_KDSET_TYPEFILTER: %s\n", strerror(errno)); + } +} + +void set_kval_list(void) +{ + kd_regtype kr; + + kr.type = KDBG_VALCHECK; + kr.value1 = value1; + kr.value2 = value2; + kr.value3 = value3; + kr.value4 = value4; + needed = sizeof(kd_regtype); + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDSETREG; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, &kr, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDSETREG (valcheck): %s\n", strerror(errno)); +} + + +void readtrace(char *buffer) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDREADTR; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + + if (sysctl(mib, 3, buffer, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDREADTR: %s\n", strerror(errno)); +} + + +int writetrace(int fd) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDWRITETR; + mib[3] = fd; + mib[4] = 0; + mib[5] = 0; + + if (sysctl(mib, 4, NULL, &needed, NULL, 0) < 0) + return 1; + + return 0; +} + + +int write_command_map(int fd) +{ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDWRITEMAP; + mib[3] = fd; + mib[4] = 0; + mib[5] = 0; + + if (sysctl(mib, 4, NULL, &needed, NULL, 0) < 0) { + if (errno == ENODATA) { + if (verbose_flag) { + printf("Cannot write thread map -- this is not fatal\n"); + } + } else { + return 1; + } + } + + return 0; +} + + +static +lookup_t handle_lookup_event(uint64_t thread, int debugid, kd_buf *kdp) +{ + lookup_t lkp; + int hashid; + boolean_t first_record = FALSE; + + hashid = thread & HASH_MASK; + + if (debugid & DBG_FUNC_START) + first_record = TRUE; + + for (lkp = lookup_hash[hashid]; lkp; lkp = lkp->lk_next) { + if (lkp->lk_thread == thread) + break; + } + if (lkp == NULL) { + if (first_record == FALSE) + return (0); + + if ((lkp = lookup_freelist)) + lookup_freelist = lkp->lk_next; + else + lkp = (lookup_t)malloc(sizeof(struct lookup)); + + lkp->lk_thread = thread; + + lkp->lk_next = lookup_hash[hashid]; + lookup_hash[hashid] = lkp; + } + + if (first_record == TRUE) { + lkp->lk_pathptr = lkp->lk_pathname; + lkp->lk_dvp = kdp->arg1; + } else { + if (lkp->lk_pathptr > &lkp->lk_pathname[NUMPARMS-4]) + return (lkp); + + *lkp->lk_pathptr++ = kdp->arg1; + } + *lkp->lk_pathptr++ = kdp->arg2; + *lkp->lk_pathptr++ = kdp->arg3; + *lkp->lk_pathptr++ = kdp->arg4; + *lkp->lk_pathptr = 0; + + return (lkp); +} + + +static +void delete_lookup_event(uint64_t thread, lookup_t lkp_to_delete) +{ + lookup_t lkp; + lookup_t lkp_prev; + int hashid; + + hashid = thread & HASH_MASK; + + if ((lkp = lookup_hash[hashid])) { + if (lkp == lkp_to_delete) + lookup_hash[hashid] = lkp->lk_next; + else { + lkp_prev = lkp; + + for (lkp = lkp->lk_next; lkp; lkp = lkp->lk_next) { + if (lkp == lkp_to_delete) { + lkp_prev->lk_next = lkp->lk_next; + break; + } + lkp_prev = lkp; + } + } + if (lkp) { + lkp->lk_next = lookup_freelist; + lookup_freelist = lkp; + } + } +} + + +static +void insert_start_event(uint64_t thread, int debugid, uint64_t now) +{ + event_t evp; + int hashid; + + hashid = thread & HASH_MASK; + + for (evp = event_hash[hashid]; evp; evp = evp->ev_next) { + if (evp->ev_thread == thread && evp->ev_debugid == debugid) + break; + } + if (evp == NULL) { + if ((evp = event_freelist)) + event_freelist = evp->ev_next; + else + evp = (event_t)malloc(sizeof(struct event)); + + evp->ev_thread = thread; + evp->ev_debugid = debugid; + + evp->ev_next = event_hash[hashid]; + event_hash[hashid] = evp; + } + evp->ev_timestamp = now; +} + + +static +uint64_t consume_start_event(uint64_t thread, int debugid, uint64_t now) +{ + event_t evp; + event_t evp_prev; + int hashid; + uint64_t elapsed = 0; + + hashid = thread & HASH_MASK; + + if ((evp = event_hash[hashid])) { + if (evp->ev_thread == thread && evp->ev_debugid == debugid) + event_hash[hashid] = evp->ev_next; + else { + evp_prev = evp; + + for (evp = evp->ev_next; evp; evp = evp->ev_next) { + + if (evp->ev_thread == thread && evp->ev_debugid == debugid) { + evp_prev->ev_next = evp->ev_next; + break; + } + evp_prev = evp; + } + } + if (evp) { + elapsed = now - evp->ev_timestamp; + + evp->ev_next = event_freelist; + event_freelist = evp; + } + } + return (elapsed); +} + +void +log_trace(void) +{ + int fd = -1; + int ret = 0; + char *buffer; + uint32_t buffer_size = 1000000 * sizeof(kd_buf); + + if (logfile[0] == '-' && logfile[1] == '\0') { + fd = STDOUT_FILENO; + } else { + fd = open(logfile, O_TRUNC | O_WRONLY | O_CREAT, 0777); + } + + if (fd == -1) { + perror("Can't open logfile"); + exit(1); + } + get_bufinfo(&bufinfo); + + if (bufinfo.nolog != 1) { + reenable = 1; + set_enable(0); /* disable logging*/ + } + get_bufinfo(&bufinfo); + + if (verbose_flag) { + if (bufinfo.flags & KDBG_WRAPPED) + printf("Buffer has wrapped\n"); + else + printf("Buffer has not wrapped\n"); + } + + ret = write_command_map(fd); + if (ret) { + close(fd); + perror("failed to write logfile"); + exit(1); + } + + buffer = malloc(buffer_size); + if (buffer == NULL) { + quit("can't allocate memory for events\n"); + } + + for (;;) { + needed = buffer_size; + + readtrace(buffer); + + if (needed == 0) { + break; + } + + write(fd, buffer, needed * sizeof(kd_buf)); + } + + free(buffer); + + close(fd); +} + +/* + * Why does this function exist? + * trace -L needs millisecond level wait times. + * When this code is running remotely, the mach_timebase_info_t data may + * be from a device with a different timebase. This code avoids using + * mach_absolute_time(), so that time calculations come out correct both + * locally and remotely. + */ +static uint64_t current_millis() { + struct timeval time; + gettimeofday(&time, NULL); + return (time.tv_sec * 1000) + (time.tv_usec / 1000); +} + +void +Log_trace(void) +{ + size_t len; + int num_cpus = 0; + int fd; + uint64_t current_ms; + uint64_t ending_ms = 0; + uint64_t last_time_written; + uint32_t ms_to_run; + + if ((fd = open(logfile, O_TRUNC|O_WRONLY|O_CREAT, 0777)) == -1) { + perror("Can't open logfile"); + exit(1); + } + if (use_current_buf == 0) { + /* + * grab the number of cpus and scale the buffer size + */ + mib[0] = CTL_HW; + mib[1] = HW_NCPU; + mib[2] = 0; + len = sizeof(num_cpus); + + sysctl(mib, 2, &num_cpus, &len, NULL, 0); + + if (!bufset_flag) + nbufs = BASE_EVENTS * num_cpus; + + set_remove(); + set_numbufs(nbufs); + set_init(); + + if (filter_flag) + set_filter(); + + if (kval_flag) + set_kval_list(); + } + + if (use_current_buf == 0) + set_enable(1); + + if (write_command_map(fd)) { + quit("can't write tracefile header\n"); + } + + last_time_written = current_millis(); + + if (secs_to_run) { + ms_to_run = secs_to_run * 1000; + ending_ms = last_time_written + ms_to_run; + } else + ms_to_run = 0; + + while (LogRAW_flag) { + needed = ms_to_run; + + if (writetrace(fd)) { + perror("KDWRITETR returned error"); + + /* Clean up and exit in case of write fail */ + break; + } + + if (needed) { + current_ms = current_millis(); + + printf("wrote %d events - elapsed time = %.1f secs\n", + (int)needed, (double)(current_ms - last_time_written) / 1000.0); + + last_time_written = current_ms; + } + + if (secs_to_run) { + current_ms = current_millis(); + + if (current_ms > ending_ms) + break; + + ms_to_run = (uint32_t)(ending_ms - current_ms); + + if (ms_to_run == 0) + break; + } + } + set_enable(0); + set_numbufs(0); + set_remove(); + + close(fd); +} + + +void read_trace(void) +{ + char *buffer; + uint32_t buffer_size; + kd_buf *kd; + int fd; + int firsttime = 1; + int lines = 0; + int io_lines = 0; + uint64_t bias = 0; + uint32_t count_of_names; + double last_event_time = 0.0; + time_t trace_time; + + if (!readRAW_flag) { + get_bufinfo(&bufinfo); + + if (bufinfo.nolog != 1) { + reenable = 1; + set_enable(0); /* disable logging*/ + } + if (verbose_flag) { + if (bufinfo.flags & KDBG_WRAPPED) + printf("Buffer has wrapped\n"); + else + printf("Buffer has not wrapped\n"); + } + fd = 0; + count_of_names = 0; + + } else { + fd = open(RAW_file, O_RDONLY); + + if (fd < 0) { + perror("Can't open file"); + exit(1); + } + if (read(fd, &raw_header, sizeof(RAW_header)) != sizeof(RAW_header)) { + perror("read failed"); + exit(2); + } + if (raw_header.version_no != RAW_VERSION1) { + raw_header.version_no = RAW_VERSION0; + raw_header.TOD_secs = time((long *)0); + raw_header.TOD_usecs = 0; + + lseek(fd, (off_t)0, SEEK_SET); + + if (read(fd, &raw_header.thread_count, sizeof(int)) != sizeof(int)) { + perror("read failed"); + exit(2); + } + } else if (raw_header.version_no == RAW_VERSION1) { +#if defined(__ILP32__) + /* + * If the raw trace file was written by armv7k, the 64-bit alignment + * of TOD_secs causes RAW_header to be 24 bytes. If we only read 20 + * bytes, the next 4 bytes might be a legitimate thread_id, but it might + * also be 0 or a leaked kernel pointer from an armv7k trace file. For + * both those cases, consume the 4 bytes and look for the thread map + * after it. + */ + if (sizeof(raw_header) == 20) { + uint32_t alignment_garbage; + + if (read(fd, &alignment_garbage, sizeof(alignment_garbage)) != sizeof(alignment_garbage)) { + perror("read failed"); + exit(2); + } + + if ((alignment_garbage == 0) || (alignment_garbage >= 0x80000000)) { + if (verbose_flag) { + printf("Skipping 4 bytes to find valid thread map\n"); + } + } else { + /* oops, go back to where we were */ + lseek(fd, -(off_t)sizeof(alignment_garbage), SEEK_CUR); + } + } +#endif + } + count_of_names = raw_header.thread_count; + trace_time = (time_t) (raw_header.TOD_secs); + + printf("%s\n", ctime(&trace_time)); + } + buffer_size = 1000000 * sizeof(kd_buf); + buffer = malloc(buffer_size); + + if (buffer == (char *) 0) + quit("can't allocate memory for tracing info\n"); + + kd = (kd_buf *)(uintptr_t)buffer; + + read_command_map(fd, count_of_names); + read_cpu_map(fd); + + for (;;) { + uint32_t count; + uint64_t now = 0; + uint64_t prev; + uint64_t prevdelta = 0; + uint32_t cpunum = 0; + uint64_t thread; + double x = 0.0; + double y = 0.0; + double event_elapsed_time = 0; + kd_buf *kdp; + lookup_t lkp; + boolean_t ending_event; + int i; + int debugid; + int debugid_base; + int dmsgindex; + char dbgmessge[80]; + char outbuf[32]; + char *command; + + if (!readRAW_flag) { + needed = buffer_size; + + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDREADTR; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; + if (sysctl(mib, 3, buffer, &needed, NULL, 0) < 0) + quit_args("trace facility failure, KERN_KDREADTR: %s\n", strerror(errno)); + + if (needed == 0) + break; + count = (uint32_t)needed; + + } else { + uint32_t bytes_read; + + bytes_read = (uint32_t)read(fd, buffer, buffer_size); + + if (bytes_read == -1) { + perror("read failed"); + exit(2); + } + count = bytes_read / sizeof(kd_buf); + + if (count == 0) + break; + } + for (kdp = &kd[0], i = 0; i < count; i++, kdp++) { + + prev = now; + debugid = kdp->debugid; + debugid_base = debugid & DBG_FUNC_MASK; + now = kdp->timestamp & KDBG_TIMESTAMP_MASK; + cpunum = kdbg_get_cpu(kdp); + + /* + * Is this event from an IOP? If so, there will be no + * thread command, label it with the symbolic IOP name + */ + if (cpumap && (cpunum < cpumap_header->cpu_count) && (cpumap[cpunum].flags & KDBG_CPUMAP_IS_IOP)) { + command = cpumap[cpunum].name; + } else { + find_thread_command(kdp, &command); + } + + /* + * The internal use TRACE points clutter the output. + * Print them only if in verbose mode. + */ + if (!verbose_flag) + { + /* Is this entry of Class DBG_TRACE */ + if ((debugid >> 24) == DBG_TRACE) { + if (((debugid >> 16) & 0xff) != DBG_TRACE_INFO) + continue; + } + } + + if (firsttime) + bias = now; + now -= bias; + + thread = kdp->arg5; + + if (lines == 64 || firsttime) + { + prevdelta = now - prevdelta; + + if (firsttime) + firsttime = 0; + else { + x = (double)prevdelta; + x /= divisor; + + fprintf(output_file, "\n\nNumber of microsecs since in last page %8.1f\n", x); + } + prevdelta = now; + + /* + * Output description row to output file (make sure to format correctly for 32-bit and 64-bit) + */ + fprintf(output_file, +#ifdef __LP64__ + " AbsTime(Us) Delta debugid arg1 arg2 arg3 arg4 thread cpu# command\n\n" +#else + " AbsTime(Us) Delta debugid arg1 arg2 arg3 arg4 thread cpu# command\n\n" +#endif + ); + + lines = 0; + + if (io_lines > 15000) { + fcntl(output_fd, F_FLUSH_DATA, 0); + + io_lines = 0; + } + } + lkp = 0; + + if (debugid_base == VFS_LOOKUP) { + lkp = handle_lookup_event(thread, debugid, kdp); + + if ( !lkp || !(debugid & DBG_FUNC_END)) + continue; + } + + x = (double)now; + x /= divisor; + + if (last_event_time) + y = x - last_event_time; + else + y = x; + last_event_time = x; + ending_event = FALSE; + + if ( !lkp) { + int t_debugid; + uint64_t t_thread; + + if ((debugid & DBG_FUNC_START) || debugid == MACH_MAKERUNNABLE) { + + if (debugid_base != BSC_thread_terminate && debugid_base != BSC_exit) { + + if (debugid == MACH_MAKERUNNABLE) + t_thread = kdp->arg1; + else + t_thread = thread; + + insert_start_event(t_thread, debugid_base, now); + } + + } else if ((debugid & DBG_FUNC_END) || debugid == MACH_STKHANDOFF || debugid == MACH_SCHEDULED) { + + if (debugid == MACH_STKHANDOFF || debugid == MACH_SCHEDULED) { + t_debugid = MACH_MAKERUNNABLE; + t_thread = kdp->arg2; + } else { + t_debugid = debugid_base; + t_thread = thread; + } + event_elapsed_time = (double)consume_start_event(t_thread, t_debugid, now); + event_elapsed_time /= divisor; + ending_event = TRUE; + + if (event_elapsed_time == 0 && (debugid == MACH_STKHANDOFF || debugid == MACH_SCHEDULED)) + ending_event = FALSE; + } + } + if (ending_event) { + char *ch; + + sprintf(&outbuf[0], "(%-10.1f)", event_elapsed_time); + /* + * fix that right paren + */ + ch = &outbuf[11]; + + if (*ch != ')') { + ch = strchr (&outbuf[0], ')'); + } + if (ch) + { + *ch = ' '; + --ch; + + while (ch != &outbuf[0]) + { + if (*ch == ' ') + --ch; + else + { + *(++ch) = ')'; + break; + } + } + } + } + if (match_debugid(debugid_base, dbgmessge, &dmsgindex)) { + if (ending_event) + fprintf(output_file, "%13.1f %10.1f%s %-28x ", x, y, outbuf, debugid_base); + else + fprintf(output_file, "%13.1f %10.1f %-28x ", x, y, debugid_base); + } else { + if (ending_event) + fprintf(output_file, "%13.1f %10.1f%s %-28.28s ", x, y, outbuf, dbgmessge); + else + fprintf(output_file, "%13.1f %10.1f %-28.28s ", x, y, dbgmessge); + } + if (lkp) { + char *strptr; + int len; + + strptr = (char *)lkp->lk_pathname; + + /* + * print the tail end of the pathname + */ + len = (int)strlen(strptr); + if (len > 51) + len -= 51; + else + len = 0; +#if defined(__LP64__) || defined(__arm64__) + + fprintf(output_file, "%-16llx %-51s %-16" PRIx64 " %-2d %s\n", (uint64_t)lkp->lk_dvp, &strptr[len], thread, cpunum, command); +#else + fprintf(output_file, "%-8x %-51s %-8" PRIx64 " %-2d %s\n", (unsigned int)lkp->lk_dvp, &strptr[len], thread, cpunum, command); +#endif + delete_lookup_event(thread, lkp); + } else if (debugid == TRACE_INFO_STRING) { +#if defined(__LP64__) || defined(__arm64__) + fprintf(output_file, "%-32s%-36s %-16" PRIx64 " %-2d %s\n", (char *) &kdp->arg1, "", thread, cpunum, command); +#else + fprintf(output_file, "%-16s%-46s %-8" PRIx64 " %-2d %s\n", (char *) &kdp->arg1, "", thread, cpunum, command); +#endif + } else { +#if defined(__LP64__) || defined(__arm64__) + fprintf(output_file, "%-16" PRIx64 " %-16" PRIx64 " %-16" PRIx64 " %-16" PRIx64 " %-16" PRIx64 " %-2d %s\n", + (uint64_t)kdp->arg1, (uint64_t)kdp->arg2, (uint64_t)kdp->arg3, (uint64_t)kdp->arg4, thread, cpunum, command); +#else + fprintf(output_file, "%-8" PRIx64 " %-8" PRIx64 " %-8" PRIx64 " %-8" PRIx64 " %-8" PRIx64 " %-2d %s\n", + (uint64_t)kdp->arg1, (uint64_t)kdp->arg2, (uint64_t)kdp->arg3, (uint64_t)kdp->arg4, thread, cpunum, command); +#endif + } + lines++; + io_lines++; + } + } + if (reenable == 1) + set_enable(1); /* re-enable kernel logging */ +} + + + +void signal_handler(int sig) +{ + ptrace(PT_KILL, pid, (caddr_t)0, 0); + /* + * child is gone; no need to disable the pid + */ + exit(2); +} + + +void signal_handler_RAW(int sig) +{ + LogRAW_flag = 0; +} + + +int main (int argc, char* argv[], char *envp[]) +{ + extern char *optarg; + extern int optind; + int ch; + int i; + char *output_filename = NULL; + unsigned int parsed_arg; + + for (i = 1; i < argc; i++) { + if (strcmp("-X", argv[i]) == 0) { + force_32bit_exec = 1; + break; + } + } + if (force_32bit_exec) { + if (0 != reexec_to_match_lp64ness(FALSE)) { + fprintf(stderr, "Could not re-execute: %d\n", errno); + exit(1); + } + } +#if !defined(__arm64__) + else { + if (0 != reexec_to_match_kernel()) { + fprintf(stderr, "Could not re-execute: %d\n", errno); + exit(1); + } + } +#endif + if (setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_PASSIVE) < 0) { + printf("setiopolicy failed\n"); + exit(1); + } + output_file = stdout; + output_fd = 1; + + while ((ch = getopt(argc, argv, "hedEk:irb:gc:p:s:tR:L:l:S:F:a:x:Xnfvo:PT:N")) != EOF) + { + switch(ch) + { + case 'h': /* help */ + usage_flag=1; + break; + case 'S': + secs_to_run = argtoi('S', "decimal number", optarg, 10); + break; + case 'a': /* set tracing on a pid */ + pid_flag=1; + pid = argtoi('a', "decimal number", optarg, 10); + break; + case 'x': /* exclude a pid from tracing */ + pid_exflag=1; + pid = argtoi('x', "decimal number", optarg, 10); + break; + case 'v': + verbose_flag=1; + break; + case 'l': + logRAW_flag = 1; + logfile = optarg; + break; + case 'L': + LogRAW_flag = 1; + logfile = optarg; + signal(SIGINT, signal_handler_RAW); + break; + case 'e': + enable_flag = 1; + break; + case 'i': + init_flag = 1; + break; + case 'E': + execute_flag = 1; + break; + case 'd': + disable_flag = 1; + break; + case 'k': + if (kval_flag == 0) + value1 = (unsigned int) argtoul('k', "hex number", optarg, 16); + else if (kval_flag == 1) + value2 = (unsigned int) argtoul('k', "hex number", optarg, 16); + else if (kval_flag == 2) + value3 = (unsigned int) argtoul('k', "hex number", optarg, 16); + else if (kval_flag == 3) + value4 = (unsigned int) argtoul('k', "hex number", optarg, 16); + else + { + fprintf(stderr, "A maximum of four values can be specified with -k\n"); + usage(SHORT_HELP); + } + kval_flag++; + break; + case 'r': + remove_flag = 1; + break; + case 'g': + bufget_flag = 1; + break; + case 't': + trace_flag = 1; + break; + case 'R': + readRAW_flag = 1; + RAW_file = optarg; + break; + case 'n': + nowrap_flag = 1; + break; + case 'f': + freerun_flag = 1; + break; + case 'b': + bufset_flag = 1; + nbufs = argtoi('b', "decimal number", optarg, 10); + break; + case 'c': + filter_flag = 1; + parsed_arg = argtoi('c', "decimal, hex, or octal number", optarg, 0); + if (parsed_arg > 0xFF) + quit_args("argument '-c %s' parsed as %u, " + "class value must be 0-255\n", optarg, parsed_arg); + saw_filter_class(parsed_arg); + break; + case 's': + filter_flag = 1; + parsed_arg = argtoi('s', "decimal, hex, or octal number", optarg, 0); + if (parsed_arg > 0xFF) + quit_args("argument '-s %s' parsed as %u, " + "subclass value must be 0-255\n", optarg, parsed_arg); + saw_filter_subclass(parsed_arg); + break; + case 'p': + filter_flag = 1; + parsed_arg = argtoi('p', "decimal, hex, or octal number", optarg, 0); + if (parsed_arg > 0xFF) + quit_args("argument '-p %s' parsed as %u, " + "end range value must be 0-255\n", optarg, parsed_arg); + saw_filter_end_range(parsed_arg); + break; + case 'P': + ppt_flag = 1; + break; + case 'o': + output_filename = optarg; + break; + case 'F': + frequency = argtoi('F', "decimal number", optarg, 10); + break; + case 'X': + break; + case 'N': + no_default_codes_flag = 1; + break; + case 'T': + filter_flag = 1; + + // Flush out any unclosed -c argument + filter_done_parsing(); + + parse_filter_file(optarg); + break; + default: + usage(SHORT_HELP); + } + } + argc -= optind; + + if (!no_default_codes_flag) + { + if (verbose_flag) + printf("Adding default code file /usr/share/misc/trace.codes. Use '-N' to skip this.\n"); + parse_codefile("/usr/share/misc/trace.codes"); + } + + if (argc) + { + if (!execute_flag) + { + while (argc--) + { + const char *cfile = argv[optind++]; + if (verbose_flag) printf("Adding code file %s \n", cfile); + parse_codefile(cfile); + } + } + } + else + { + if (execute_flag) + quit_args("-E flag needs an executable to launch\n"); + } + + if (usage_flag) + usage(LONG_HELP); + + getdivisor(); + + if (pid_flag && pid_exflag) + quit_args("Can't use both -a and -x flag together\n"); + + if (kval_flag && filter_flag) + quit_args("Cannot use -k flag with -c, -s, or -p\n"); + + if (output_filename && !trace_flag && !readRAW_flag) + quit_args("When using 'o' option, must use the 't' or 'R' option too\n"); + + filter_done_parsing(); + + done_with_args = 1; + + if (LogRAW_flag) { + get_bufinfo(&bufinfo); + int ktrace_state = get_ktrace_state(); + + /* + * Only use the current kdebug configuration when foreground + * tracing is enabled. Both checks are necessary because the + * background tool might have enabled tracing, but as soon as we + * try to write a header, that configuration is removed for us. + */ + if ((ktrace_state == 1) && (bufinfo.nolog == 0)) { + use_current_buf = 1; + } + } + + if (disable_flag) + { + if (pid_flag) + { + set_pidcheck(pid, 0); /* disable pid check for given pid */ + exit(0); + } + else if (pid_exflag) + { + set_pidexclude(pid, 0); /* disable pid exclusion for given pid */ + exit(0); + } + set_enable(0); + exit(0); + } + + if (remove_flag) + { + set_remove(); + exit(0); + } + + if (bufset_flag ) + { + if (!init_flag && !LogRAW_flag) + { + fprintf(stderr,"The -b flag must be used with the -i flag\n"); + exit(1); + } + set_numbufs(nbufs); + } + + if (nowrap_flag) + set_nowrap(); + + if (freerun_flag) + set_freerun(); + + if (bufget_flag) + { + printf("The kernel tracing settings are:\n"); + + /* determine the state of ktrace */ + int state = get_ktrace_state(); + + /* get the name of the last process to configure ktrace */ + char execname[20] = { 0 }; + size_t execname_size = sizeof(execname); + int err = sysctlbyname("ktrace.configured_by", &execname, &execname_size, NULL, 0); + if (err) { + fprintf(stderr, "error: could not query ktrace.configured_by sysctl (%d: %s)\n", errno, strerror(errno)); + exit(1); + } + + printf("\tTracing is "); + switch (state) { + case 0: + printf("off"); + break; + case 1: + printf("active (foreground)"); + break; + case 2: + printf("active (background)"); + break; + default: + printf("in an invalid state"); + break; + } + printf("\n"); + + printf("\tLast configured by \"%s\"\n", execname[0] == '\0' ? "" : execname); + + /* get kdebug info */ + + get_bufinfo(&bufinfo); + + printf("The kernel buffer settings are:\n"); + + if (bufinfo.flags & KDBG_BUFINIT) + printf("\tKernel buffer is initialized\n"); + else + printf("\tKernel buffer is not initialized\n"); + + printf("\t number of buf entries = %d\n", bufinfo.nkdbufs); + + if (verbose_flag) + { + if (bufinfo.flags & KDBG_MAPINIT) + printf("\tKernel thread map is initialized\n"); + else + printf("\tKernel thread map is not initialized\n"); + printf("\t number of thread entries = %d\n", bufinfo.nkdthreads); + } + + if (bufinfo.nolog) + printf("\tBuffer logging is disabled\n"); + else + printf("\tBuffer logging is enabled\n"); + + if (verbose_flag) + printf("\tkernel flags = 0x%x\n", bufinfo.flags); + + if (bufinfo.flags & KDBG_NOWRAP) + printf("\tKernel buffer wrap is disabled\n"); + else + printf("\tKernel buffer wrap is enabled\n"); + + if (bufinfo.flags & KDBG_RANGECHECK) + printf("\tCollection within a range is enabled\n"); + else + printf("\tCollection within a range is disabled\n"); + + if (bufinfo.flags & KDBG_VALCHECK) + printf("\tCollecting specific code values is enabled\n"); + else + printf("\tCollecting specific code values is disabled\n"); + + if (bufinfo.flags & KDBG_TYPEFILTER_CHECK) + printf("\tCollection based on a filter is enabled\n"); + else + printf("\tCollection based on a filter is disabled\n"); + + if (bufinfo.flags & KDBG_PIDCHECK) + printf("\tCollection based on pid is enabled\n"); + else + printf("\tCollection based on pid is disabled\n"); + + if (bufinfo.flags & KDBG_PIDEXCLUDE) + printf("\tCollection based on pid exclusion is enabled\n"); + else + printf("\tCollection based on pid exclusion is disabled\n"); + + if (bufinfo.bufid == -1) + printf("\tKernel buffer is not controlled by any process.\n"); + else + printf("\tKernel buffer is controlled by proc id [%d]\n", bufinfo.bufid); + + + if (bufinfo.flags & KDBG_TYPEFILTER_CHECK) { + if (verbose_flag) { + bool (^should_print)(uint8_t*) = ^bool(uint8_t* ptr) { + for (uint32_t i=0; i<32; ++i) { + if (ptr[i] > 0) return true; + } + + return false; + }; + + uint8_t* typefilter = (uint8_t*)kdebug_typefilter(); + if (typefilter) { + bool header = false; + + // Reduce noise, only print lines that are allowing events. + for (uint32_t tclass = 0; tclass < 0x100; ++tclass) { + uint8_t* base = &typefilter[tclass * 32]; + if (should_print(base)) { + if (!header) { + header = true; + printf("\tTypefilter:\n"); + printf("%18s ",""); + for (uint32_t tsubclass=0; tsubclass<32; ++tsubclass) { + printf("%02x ", tsubclass * 8); + } + printf("\n"); + printf("%18s ",""); + for (uint32_t tsubclass=0; tsubclass<32; ++tsubclass) { + printf("---"); + } + printf("\n"); + } + printf("%16s%02x: ", "", tclass); + for (uint32_t tsubclass=0; tsubclass<32; ++tsubclass) { + printf("%02X ", typefilter[(tclass * 32) + tsubclass]); + } + printf("\n"); + } + } + } + } + } + } + + if (init_flag) + set_init(); + + if (filter_flag) + set_filter(); + + if (kval_flag) + set_kval_list(); + + if (execute_flag) + { + fprintf(stderr, "Starting program: %s\n", argv[optind]); + fflush(stdout); + fflush(stderr); + + execute_process(&(argv[optind])); + + exit(0); + } + else if (enable_flag) + { + if (pid_flag) + set_pidcheck(pid, 1); + else if (pid_exflag) + set_pidexclude(pid, 1); + set_enable(1); + } + + if (output_filename) + { + if (((output_fd = open(output_filename, O_CREAT | O_TRUNC | O_WRONLY | O_APPEND, 0644)) < 0 ) || + !(output_file = fdopen(output_fd, "w"))) + { + fprintf(stderr, "Cannot open file \"%s\" for writing.\n", output_filename); + usage(SHORT_HELP); + } + setbuffer(output_file, &sbuffer[0], SBUFFER_SIZE); + + if (fcntl(output_fd, F_NOCACHE, 1) < 0) + { + /* Not fatal */ + fprintf(stderr, "Warning: setting F_NOCACHE on %s, failed\n", output_filename); + } + } + if (!LogRAW_flag && !logRAW_flag) + setbuffer(output_file, &sbuffer[0], SBUFFER_SIZE); + + if (trace_flag || readRAW_flag) + read_trace(); + else if (LogRAW_flag) + Log_trace(); + else if (logRAW_flag) + log_trace(); + + exit(0); + +} /* end main */ + +static void +execute_process(char * const argv[]) +{ + int status = 0; + int rc = 0; + posix_spawnattr_t spawn_attrs; + + assert(argv); + + /* ensure that the process being spawned starts suspended */ + rc = posix_spawnattr_init(&spawn_attrs); + if (rc != 0) { + quit_args("Failed to initialize spawn attrs: %s\n", strerror(rc)); + } + rc = posix_spawnattr_setflags(&spawn_attrs, + POSIX_SPAWN_START_SUSPENDED); + if (rc != 0) { + quit_args("Unable to start process suspended: %s\n", strerror(rc)); + } + + /* spawn the process with the rest of the arguments */ + rc = posix_spawnp(&pid, argv[0], NULL, &spawn_attrs, argv, environ); + if (rc != 0) { + quit_args("Unabled to start process: %s\n", strerror(rc)); + } + + signal(SIGINT, signal_handler); + set_pidcheck(pid, 1); + set_enable(1); + + /* start the child process */ + rc = kill(pid, SIGCONT); + if (rc != 0) { + perror("Failed to continue child process:"); + exit(EX_OSERR); + } + + rc = waitpid(pid, &status, 0); + if (rc == -1) { + perror("Failed to wait for process: "); + } +} + +static void +quit_args(const char *fmt, ...) +{ + char buffer[1024]; + + if (reenable == 1) + { + reenable = 0; + set_enable(1); /* re-enable kernel logging */ + } + + va_list args; + + va_start (args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + + fprintf(stderr, "trace error: %s", buffer); + + va_end(args); + + if (!done_with_args) + usage(SHORT_HELP); + + exit(1); +} + + +void +quit(char *s) +{ + if (reenable == 1) + { + reenable = 0; + set_enable(1); /* re-enable kernel logging */ + } + + printf("trace: "); + if (s) + printf("%s", s); + exit(1); +} + +static void +usage(int short_help) +{ + + if (short_help) + { + (void)fprintf(stderr, " usage: trace -h [-v]\n"); + (void)fprintf(stderr, " usage: trace -i [-b numbufs]\n"); + (void)fprintf(stderr, " usage: trace -g\n"); + (void)fprintf(stderr, " usage: trace -d [-a pid | -x pid ]\n"); + (void)fprintf(stderr, " usage: trace -r\n"); + (void)fprintf(stderr, " usage: trace -n\n"); + + (void)fprintf(stderr, + " usage: trace -e [ -c class [[-s subclass]... | -p class ]]... | \n"); + (void)fprintf(stderr, + " [-k code | -k code | -k code | -k code] [-P] [-T tracefilter] \n"); + (void)fprintf(stderr, + " [-a pid | -x pid] \n\n"); + + (void)fprintf(stderr, + " usage: trace -E [ -c class [[-s subclass]... | -p class ]]... | \n"); + (void)fprintf(stderr, + " [-k code | -k code | -k code | -k code] [-P] [-T tracefilter] \n"); + (void)fprintf(stderr, + " executable_path [optional args to executable] \n\n"); + + (void)fprintf(stderr, + " usage: trace -L RawFilename [-S SecsToRun]\n"); + (void)fprintf(stderr, + " usage: trace -l RawFilename\n"); + (void)fprintf(stderr, + " usage: trace -R RawFilename [-X] [-F frequency] [-o OutputFilename] [-N] [ExtraCodeFilename1 ExtraCodeFilename2 ...]\n"); + (void)fprintf(stderr, + " usage: trace -t [-o OutputFilename] [-N] [ExtraCodeFilename1 ExtraCodeFilename2 ...]\n"); + (void)fprintf(stderr, + " Trace will import /usr/share/misc/trace.codes as a default codefile unless -N is specified. Extra codefiles specified are used in addition to the default codefile.\n"); + exit(1); + } + + + /* Only get here if printing long usage info */ + (void)fprintf(stderr, "usage: trace -h [-v]\n"); + (void)fprintf(stderr, "\tPrint this long command help.\n\n"); + (void)fprintf(stderr, "\t -v Print extra information about tracefilter and code files.\n\n"); + + (void)fprintf(stderr, "usage: trace -i [-b numbufs]\n"); + (void)fprintf(stderr, "\tInitialize the kernel trace buffer.\n\n"); + (void)fprintf(stderr, "\t-b numbufs The number of trace elements the kernel buffer\n"); + (void)fprintf(stderr, "\t can hold is set to numbufs. Use with the -i flag.\n"); + (void)fprintf(stderr, "\t Enter a decimal value.\n\n"); + + (void)fprintf(stderr, "usage: trace -g\n"); + (void)fprintf(stderr, "\tGet the kernel buffer settings.\n\n"); + + (void)fprintf(stderr, "usage: trace -d [-a pid | -x pid]\n"); + (void)fprintf(stderr, "\tDisable/stop collection of kernel trace elements.\n\n"); + (void)fprintf(stderr, "\t -a pid Disable/stop collection for this process only.\n\n"); + (void)fprintf(stderr, "\t -x pid Disable/stop exclusion of this process only.\n\n"); + + (void)fprintf(stderr, "usage: trace -r\n"); + (void)fprintf(stderr, "\tRemove the kernel trace buffer. Set controls to default.\n\n"); + + (void)fprintf(stderr, "usage: trace -n\n"); + (void)fprintf(stderr, "\tDisables kernel buffer wrap around.\n\n"); + + (void)fprintf(stderr, + "usage: trace -e [ -c class [[-s subclass]... | -p class ]]... |\n"); + (void)fprintf(stderr, + " [-k code | -k code | -k code | -k code] [-P] [-T tracefilter]\n"); + (void) fprintf(stderr, + " [-a pid | -x pid]\n\n"); + (void)fprintf(stderr, "\t Enable/start collection of kernel trace elements. \n\n"); + (void)fprintf(stderr, "\t By default, trace collects all tracepoints. \n"); + (void)fprintf(stderr, "\t The following arguments may be used to restrict collection \n"); + (void)fprintf(stderr, "\t to a limited set of tracepoints. \n\n"); + (void)fprintf(stderr, "\t Multiple classes can be specified by repeating -c. \n"); + (void)fprintf(stderr, "\t Multiple subclasses can be specified by repeating -s after -c. \n"); + (void)fprintf(stderr, "\t Classes, subclasses, and class ranges can be entered \n"); + (void)fprintf(stderr, "\t in hex (0xXX), decimal (XX), or octal (0XX). \n\n"); + (void)fprintf(stderr, "\t -c class Restrict trace collection to given class. \n\n"); + (void)fprintf(stderr, "\t -p class Restrict trace collection to given class range. \n"); + (void)fprintf(stderr, "\t Must provide class with -c first. \n\n"); + (void)fprintf(stderr, "\t -s subclass Restrict trace collection to given subclass. \n"); + (void)fprintf(stderr, "\t Must provide class with -c first. \n\n"); + (void)fprintf(stderr, "\t -a pid Restrict trace collection to the given process.\n\n"); + (void)fprintf(stderr, "\t -x pid Exclude the given process from trace collection.\n\n"); + (void)fprintf(stderr, "\t -k code Restrict trace collection up to four specific codes.\n"); + (void)fprintf(stderr, "\t Enter codes in hex (0xXXXXXXXX). \n\n"); + (void)fprintf(stderr, "\t -P Enable restricted PPT trace points only.\n\n"); + (void)fprintf(stderr, "\t -T tracefilter Read class and subclass restrictions from a \n"); + (void)fprintf(stderr, "\t tracefilter description file. \n"); + (void)fprintf(stderr, "\t Run trace -h -v for more info on this file. \n\n"); + + (void)fprintf(stderr, + "usage: trace -E [ -c class [[-s subclass]... | -p class ]]... |\n"); + (void)fprintf(stderr, + " [-k code | -k code | -k code | -k code] [-P] [-T tracefilter]\n"); + (void)fprintf(stderr, + " executable_path [optional args to executable] \n\n"); + (void)fprintf(stderr, "\tLaunch the given executable and enable/start\n"); + (void)fprintf(stderr, "\tcollection of kernel trace elements for that process.\n"); + (void)fprintf(stderr, "\tSee -e(enable) flag for option descriptions.\n\n"); + + (void)fprintf(stderr, "usage: trace -t [-o OutputFilename] [-N] [ExtraCodeFilename1 ExtraCodeFilename2 ...] \n"); + (void)fprintf(stderr, "\tCollect the kernel buffer trace data and print it.\n\n"); + (void)fprintf(stderr, "\t -N Do not import /usr/share/misc/trace.codes (for raw hex tracing or supplying an alternate set of codefiles)\n"); + (void)fprintf(stderr, "\t -o OutputFilename Print trace output to OutputFilename. Default is stdout.\n\n"); + + (void)fprintf(stderr, + "usage: trace -R RawFilename [-X] [-F frequency] [-o OutputFilename] [-N] [ExtraCodeFilename1 ExtraCodeFilename2 ...] \n"); + (void)fprintf(stderr, "\tRead raw trace file and print it.\n\n"); + (void)fprintf(stderr, "\t -X Force trace to interpret trace data as 32 bit. \n"); + (void)fprintf(stderr, "\t Default is to match the bit width of the current system. \n"); + (void)fprintf(stderr, "\t -N Do not import /usr/share/misc/trace.codes (for raw hex tracing or supplying an alternate set of codefiles)\n"); + (void)fprintf(stderr, "\t -F frequency Specify the frequency of the clock used to timestamp entries in RawFilename.\n\t Use command \"sysctl hw.tbfrequency\" on the target device, to get target frequency.\n"); + (void)fprintf(stderr, "\t -o OutputFilename Print trace output to OutputFilename. Default is stdout.\n\n"); + + (void)fprintf(stderr, + "usage: trace -L RawFilename [-S SecsToRun]\n"); + (void)fprintf(stderr, "\tContinuously collect the kernel buffer trace data in the raw format \n"); + (void)fprintf(stderr, "\tand write it to RawFilename. \n"); + + (void)fprintf(stderr, "\t-L implies -r -i if tracing isn't currently enabled.\n"); + (void)fprintf(stderr, "\tOptions passed to -e(enable) are also accepted by -L. (except -a -x -P)\n\n"); + (void)fprintf(stderr, "\t -S SecsToRun Specify the number of seconds to collect trace data.\n\n"); + + (void)fprintf(stderr, + "usage: trace -l RawFilename\n"); + (void)fprintf(stderr, "\tCollect the existing kernel buffer trace data in the raw format.\n\n"); + + if (verbose_flag) { + (void)fprintf(stderr, + "Code file: \n" + "\t A code file consists of a list of tracepoints, one per line, \n" + "\t with one tracepoint code in hex, followed by a tab, \n" + "\t followed by the tracepoint's name. \n\n" + + "\t Example tracepoint: \n" + "\t 0x010c007c\tMSC_mach_msg_trap \n" + "\t This describes the tracepoint with the following info: \n" + "\t Name: MSC_mach_msg_trap \n" + "\t Class: 0x01 (Mach events) \n" + "\t Subclass: 0x0c (Mach system calls) \n" + "\t Code: 0x007c (Mach syscall number 31) \n\n" + + "\t See /usr/include/sys/kdebug.h for the currently defined \n" + "\t class and subclass values. \n" + "\t See /usr/share/misc/trace.codes for the currently allocated \n" + "\t system tracepoints in trace code file format. \n" + "\t This codefile is useful with the -R argument to trace. \n" + "\n"); + + (void)fprintf(stderr, + "Tracefilter description file: \n" + "\t A tracefilter description file consists of a list of \n" + "\t class and subclass filters in hex, one per line, \n" + "\t which are applied as if they were passed with -c and -s. \n" + "\t Pass -v to see what classes and subclasses are being set. \n\n" + + "\t File syntax: \n" + "\t Class filter: \n" + "\t C 0xXX \n" + "\t Subclass filter (includes class): \n" + "\t S 0xXXXX \n" + "\t Comment: \n" + "\t # This is a comment \n\n" + + "\t For example, to trace Mach events (class 1):\n" + "\t C 0x01 \n" + "\t or to trace Mach system calls (class 1 subclass 13): \n" + "\t S 0x010C \n" + "\n"); + } + + exit(1); +} + + +static int +argtoi(int flag, char *req, char *str, int base) +{ + char *cp; + int ret; + + ret = (int)strtol(str, &cp, base); + if (cp == str || *cp) + errx(EINVAL, "-%c flag requires a %s", flag, req); + return (ret); +} + +static unsigned long +argtoul(int flag, char *req, char *str, int base) +{ + char *cp; + unsigned long ret; + + ret = (int)strtoul(str, &cp, base); + if (cp == str || *cp) + errx(EINVAL, "-%c flag requires a %s", flag, req); + return (ret); +} + +/* + * comparison function for qsort + * sort by debugid + */ +int +debugid_compar(const void *p1, const void *p2) +{ + const code_type_t *q1 = (const code_type_t *)p1; + const code_type_t *q2 = (const code_type_t *)p2; + + if (q1->debugid > q2->debugid) + return (1); + else if (q1->debugid == q2->debugid) + return (0); + else + return (-1); +} + +/* + * Filter args parsing state machine: + * + * Allowed args: + * -c -p + * -c -s (-s)* + * -c (-c)* + * every -c goes back to start + * + * Valid transitions: + * start -> class (first -c) + * class -> range (-c -p) + * class -> sub (-c -s) + * class -> class (-c -c) + * range -> class (-c -p -c) + * sub -> class (-c -s -c) + * * -> start (on filter_done_parsing) + * + * Need to call filter_done_parsing after + * calling saw_filter_* + * to flush out any class flag waiting to see if + * there is a -s flag coming up + */ + + +// What type of flag did I last see? +enum { + FILTER_MODE_START, + FILTER_MODE_CLASS, + FILTER_MODE_CLASS_RANGE, + FILTER_MODE_SUBCLASS +} filter_mode = FILTER_MODE_START; + +uint8_t filter_current_class = 0; +uint8_t filter_current_subclass = 0; +uint8_t filter_current_class_range = 0; + +static void +saw_filter_class(uint8_t class) +{ + switch(filter_mode) { + case FILTER_MODE_START: + case FILTER_MODE_CLASS_RANGE: + case FILTER_MODE_SUBCLASS: + filter_mode = FILTER_MODE_CLASS; + filter_current_class = class; + filter_current_subclass = 0; + filter_current_class_range = 0; + // the case of a lone -c is taken care of + // by filter_done_parsing + break; + case FILTER_MODE_CLASS: + filter_mode = FILTER_MODE_CLASS; + // set old class, remember new one + set_filter_class(filter_current_class); + filter_current_class = class; + filter_current_subclass = 0; + filter_current_class_range = 0; + break; + default: + quit_args("invalid case in saw_filter_class\n"); + } +} + +static void +saw_filter_end_range(uint8_t end_class) +{ + switch(filter_mode) { + case FILTER_MODE_CLASS: + filter_mode = FILTER_MODE_CLASS_RANGE; + filter_current_class_range = end_class; + set_filter_range(filter_current_class, filter_current_class_range); + break; + case FILTER_MODE_START: + quit_args("must provide '-c class' before '-p 0x%x'\n", + end_class); + case FILTER_MODE_CLASS_RANGE: + quit_args("extra range end '-p 0x%x'" + " for class '-c 0x%x'\n", + end_class, filter_current_class); + case FILTER_MODE_SUBCLASS: + quit_args("cannot provide both range end '-p 0x%x'" + " and subclass '-s 0x%x'" + " for class '-c 0x%x'\n", + end_class, filter_current_subclass, + filter_current_class); + default: + quit_args("invalid case in saw_filter_end_range\n"); + } +} + +static void +saw_filter_subclass(uint8_t subclass) +{ + switch(filter_mode) { + case FILTER_MODE_CLASS: + case FILTER_MODE_SUBCLASS: + filter_mode = FILTER_MODE_SUBCLASS; + filter_current_subclass = subclass; + set_filter_subclass(filter_current_class, filter_current_subclass); + break; + case FILTER_MODE_START: + quit_args("must provide '-c class'" + " before subclass '-s 0x%x'\n", subclass); + case FILTER_MODE_CLASS_RANGE: + quit_args("cannot provide both range end '-p 0x%x'" + " and subclass '-s 0x%x'" + " for the same class '-c 0x%x'\n", + filter_current_class_range, + subclass, filter_current_class); + default: + quit_args("invalid case in saw_filter_subclass\n"); + } +} + +static void +filter_done_parsing(void) +{ + switch(filter_mode) { + case FILTER_MODE_CLASS: + // flush out the current class + set_filter_class(filter_current_class); + filter_mode = FILTER_MODE_START; + filter_current_class = 0; + filter_current_subclass = 0; + filter_current_class_range = 0; + break; + case FILTER_MODE_SUBCLASS: + case FILTER_MODE_START: + case FILTER_MODE_CLASS_RANGE: + filter_mode = FILTER_MODE_START; + filter_current_class = 0; + filter_current_subclass = 0; + filter_current_class_range = 0; + break; + default: + quit_args("invalid case in filter_done_parsing\n"); + } +} + +/* Tell set_filter_subclass not to print every. single. subclass. */ +static boolean_t setting_class = FALSE; +static boolean_t setting_range = FALSE; + +static void +set_filter_subclass(uint8_t class, uint8_t subclass) +{ + if (!filter_alloced) { + type_filter_bitmap = (uint8_t *) calloc(1, KDBG_TYPEFILTER_BITMAP_SIZE); + if (type_filter_bitmap == NULL) + quit_args("Could not allocate type_filter_bitmap.\n"); + filter_alloced = 1; + } + + uint16_t csc = ENCODE_CSC_LOW(class, subclass); + + if (verbose_flag && !setting_class) + printf("tracing subclass: 0x%4.4x\n", csc); + + if (verbose_flag && isset(type_filter_bitmap, csc)) + printf("class %u (0x%2.2x), subclass %u (0x%2.2x) set twice.\n", + class, class, subclass, subclass); + + setbit(type_filter_bitmap, csc); +} + +static void +set_filter_class(uint8_t class) +{ + if (verbose_flag && !setting_range) + printf("tracing class: 0x%2.2x\n", class); + + setting_class = TRUE; + + for (int i = 0; i < 256; i++) + set_filter_subclass(class, i); + + setting_class = FALSE; +} + +static void +set_filter_range(uint8_t class, uint8_t end) +{ + if (verbose_flag) + printf("tracing range: 0x%2.2x - 0x%2.2x\n", class, end); + + setting_range = TRUE; + + for (int i = class; i <= end; i++) + set_filter_class(i); + + setting_range = FALSE; +} + +/* + * Syntax of filter file: + * Hexadecimal numbers only + * Class: + * C 0xXX + * Subclass (includes class): + * S 0xXXXX + * Comment: + * # + * TBD: Class ranges? + * TBD: K for -k flag? + */ + +static void +parse_filter_file(char *filename) +{ + FILE* file; + uint32_t current_line = 0; + uint32_t parsed_arg = 0; + int rval; + + char line[256]; + + if ( (file = fopen(filename, "r")) == NULL ) { + quit_args("Failed to open filter description file %s: %s\n", + filename, strerror(errno)); + } + + if (verbose_flag) + printf("Parsing typefilter file: %s\n", filename); + + while( fgets(line, sizeof(line), file) != NULL ) { + current_line++; + + switch (line[0]) { + case 'C': + rval = sscanf(line, "C 0x%x\n", &parsed_arg); + if (rval != 1) + quit_args("invalid line %d of file %s: %s\n", + current_line, filename, line); + if (parsed_arg > 0xFF) + quit_args("line %d of file %s: %s\n" + "parsed as 0x%x, " + "class value must be 0x0-0xFF\n", + current_line, filename, line, parsed_arg); + set_filter_class((uint8_t)parsed_arg); + break; + case 'S': + rval = sscanf(line, "S 0x%x\n", &parsed_arg); + if (rval != 1) + quit_args("invalid line %d of file %s: %s\n", + current_line, filename, line); + if (parsed_arg > 0xFFFF) + quit_args("line %d of file %s: %s\n" + "parsed as 0x%x, " + "value must be 0x0-0xFFFF\n", + current_line, filename, line, parsed_arg); + set_filter_subclass(EXTRACT_CLASS_LOW(parsed_arg), + EXTRACT_SUBCLASS_LOW(parsed_arg)); + break; + case '#': + // comment + break; + case '\n': + // empty line + break; + case '\0': + // end of file + break; + default: + quit_args("Invalid filter description file: %s\n" + "could not parse line %d: %s\n", + filename, current_line, line); + } + } + + fclose(file); +} + +/* + * Find the debugid code in the list and return its index + */ +static int +binary_search(code_type_t *list, int lowbound, int highbound, unsigned int code) +{ + int low, high, mid; + int tries = 0; + + low = lowbound; + high = highbound; + + while (1) + { + mid = (low + high) / 2; + + tries++; + + if (low > high) + return (-1); /* failed */ + else if ( low + 1 >= high) + { + /* We have a match */ + if (list[high].debugid == code) + return(high); + else if (list[low].debugid == code) + return(low); + else + return(-1); /* search failed */ + } + else if (code < list[mid].debugid) + high = mid; + else + low = mid; + } +} + + +static int +parse_codefile(const char *filename) +{ + int fd; + int j, line; + size_t count; + struct stat stat_buf; + size_t file_size; + char *file_addr, *endp; + + if ((fd = open(filename, O_RDONLY, 0)) == -1) + { + printf("Failed to open code description file %s\n",filename); + return(-1); + } + + if (fstat(fd, &stat_buf) == -1) + { + printf("Error: Can't fstat file: %s\n", filename); + return(-1); + } + + /* + * For some reason mapping files with zero size fails + * so it has to be handled specially. + */ + file_size = (size_t)stat_buf.st_size; + + if (stat_buf.st_size != 0) + { + file_addr = mmap(0, file_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_FILE, fd, 0); + if (file_addr == MAP_FAILED) + { + printf("Error: Can't map file: %s\n", filename); + close(fd); + return(-1); + } + } + else + { + // Skip empty files + close(fd); + return(0); + } + close(fd); + + + /* + * If we get here, we have mapped the file + * and we are ready to parse it. Count + * the newlines to get total number of codes. + */ + + for (count = 0, j=1; j < file_size; j++) + { + if (file_addr[j] == '\n') + count++; + } + + if (count == 0) + { + printf("Error: No codes in %s\n", filename); + return(-1); + } + + /* + * Fudge the count to accomodate the last line in the file - + * in case it doesn't end in a newline. + */ + count++; + + /* Grow the size of codesc to store new entries. */ + size_t total_count = codesc_idx + count; + code_type_t *new_codesc = (code_type_t *)realloc(codesc, (total_count) * sizeof(code_type_t)); + + if (new_codesc == NULL) { + printf("Failed to grow/allocate buffer. Skipping file %s\n", filename); + return (-1); + } + codesc = new_codesc; + bzero((char *)(codesc + codesc_idx), count * sizeof(code_type_t)); + + for (line = 1, j = 0; j < file_size && codesc_idx < total_count; + codesc_idx++) + { + /* Skip blank lines */ + while (j < file_size && file_addr[j] == '\n') + { + j++; + line++; + } + + /* Skip leading whitespace */ + while (file_addr[j] == ' ' || file_addr[j] == '\t') + j++; + + /* Get the debugid code */ + codesc[codesc_idx].debugid = (uint32_t)strtoul(file_addr + j, &endp, 16); + j = (int)(endp - file_addr); + + if (codesc[codesc_idx].debugid == 0) + { + /* We didn't find a debugid code - skip this line */ + if (verbose_flag) + printf("Error: while parsing line %d, skip\n", line); + while (j < file_size && file_addr[j] != '\n') + j++; + codesc_idx--; + line++; + continue; + } + + /* Skip whitespace */ + while (j < file_size && (file_addr[j] == ' ' || file_addr[j] == '\t')) + { + j++; + } + + if (j >= file_size) + { + break; + } + + /* Get around old file that had count at the beginning */ + if (file_addr[j] == '\n') + { + /* missing debugid string - skip */ + if (verbose_flag) + { + printf("Error: while parsing line %d, (0x%x) skip\n", line, + codesc[codesc_idx].debugid); + } + + j++; + codesc_idx--; + line++; + continue; + } + + if (j >= file_size) + { + break; + } + + /* Next is the debugid string terminated by a newline */ + codesc[codesc_idx].debug_string = &file_addr[j]; + + /* Null out the newline terminator */ + while (j < file_size && file_addr[j] != '\n') + { + j++; + } + + if (j >= file_size) + { + break; + } + + file_addr[j] = '\0'; /* File must be read-write */ + j++; + line++; + codenum++; /*Index into codesc is 0 to codenum-1 */ + } + + if (verbose_flag) + { + printf("Parsed %d codes in %s\n", codenum, filename); + printf("[%6d] 0x%8x %s\n", 0, codesc[0].debugid, codesc[0].debug_string); + printf("[%6d] 0x%8x %s\n\n", codenum-1, codesc[codenum-1].debugid, codesc[codenum-1].debug_string); + } + + /* sort */ + qsort((void *)codesc, codesc_idx, sizeof(code_type_t), debugid_compar); + + if (verbose_flag) + { + printf("Sorted %zd codes in %s\n", codesc_idx, filename); + printf("lowbound [%6d]: 0x%8x %s\n", 0, codesc[0].debugid, codesc[0].debug_string); + printf("highbound [%6zd]: 0x%8x %s\n\n", codesc_idx - 1, codesc[codesc_idx - 1].debugid, codesc[codesc_idx - 1].debug_string); + } + codesc_find_dupes(); + +#if 0 + /* Dump the codefile */ + int i; + for (i = 0; i < codesc_idx; i++) + printf("[%d] 0x%x %s\n",i+1, codesc[i].debugid, codesc[i].debug_string); +#endif + return(0); +} + +static void +codesc_find_dupes(void) +{ + boolean_t found_dupes = FALSE; + if (codesc_idx == 0) + { + return; + } + uint32_t last_debugid = codesc[0].debugid; + for(int i = 1; i < codesc_idx; i++) + { + if(codesc[i].debugid == last_debugid) + { + found_dupes = TRUE; + if (verbose_flag) { + fprintf(stderr, "WARNING: The debugid 0x%"PRIx32" (%s) has already been defined as '%s'.\n", codesc[i].debugid, codesc[i].debug_string, codesc[i - 1].debug_string); + } + } + last_debugid = codesc[i].debugid; + } + if (found_dupes) + { + fprintf(stderr, "WARNING: One or more duplicate entries found in your codefiles, which will lead to unpredictable decoding behavior. Re-run with -v for more info\n"); + } +} + +int +match_debugid(unsigned int xx, char * debugstr, int * yy) +{ + int indx; + + if (codenum == 0) + return(-1); + + if (codesc[codeindx_cache].debugid != xx) + indx = binary_search(codesc, 0, (codenum-1), xx); + else + indx = codeindx_cache; + + if (indx == -1) + return(indx); /* match failed */ + else { + bcopy(&codesc[indx].debug_string[0], debugstr,80); + *yy = indx; + codeindx_cache = indx; + return(0); /* match success */ + } +} + +void +read_cpu_map(int fd) +{ + if (cpumap_header) { + free(cpumap_header); + cpumap_header = NULL; + cpumap = NULL; + } + + /* + * To fit in the padding space of a VERSION1 file, the max possible + * cpumap size is one page. + */ + cpumap_header = malloc(PAGE_SIZE); + + if (readRAW_flag) { + /* + * cpu maps exist in a RAW_VERSION1+ header only + */ + if (raw_header.version_no == RAW_VERSION1) { + off_t cpumap_position = lseek(fd, 0, SEEK_CUR); + /* cpumap is part of the last 4KB of padding in the preamble */ + size_t padding_bytes = SIZE_4KB - (cpumap_position & (SIZE_4KB - 1)); + + if (read(fd, cpumap_header, padding_bytes) == padding_bytes) { + if (cpumap_header->version_no == RAW_VERSION1) { + cpumap = (kd_cpumap*)&cpumap_header[1]; + } + } + } + } else { + int mib[3]; + + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDCPUMAP; + + size_t temp = PAGE_SIZE; + if (sysctl(mib, 3, cpumap_header, &temp, NULL, 0) == 0) { + if (PAGE_SIZE >= temp) { + if (cpumap_header->version_no == RAW_VERSION1) { + cpumap = (kd_cpumap*)&cpumap_header[1]; + } + } + } + } + + if (!cpumap) { + printf("Can't read the cpu map -- this is not fatal\n"); + free(cpumap_header); + cpumap_header = NULL; + } else if (verbose_flag) { + /* Dump the initial cpumap */ + printf("\nCPU\tName\n"); + for (int i = 0; i < cpumap_header->cpu_count; i++) { + printf ("%2d\t%s\n", cpumap[i].cpu_id, cpumap[i].name); + } + printf("\n"); + } +} + +int +read_command_map(int fd, uint32_t count) +{ + int i; + size_t size; + int mib[6]; + + if (readRAW_flag) { + total_threads = count; + size = count * sizeof(kd_threadmap); + } else { + get_bufinfo(&bufinfo); + + total_threads = bufinfo.nkdthreads; + size = bufinfo.nkdthreads * sizeof(kd_threadmap); + } + mapptr = 0; + nthreads = total_threads * 2; + + if (verbose_flag) + printf("Size of map table is %d, thus %d entries\n", (int)size, total_threads); + + if (size) { + if ((mapptr = (kd_threadmap *) malloc(size))) + bzero (mapptr, size); + else + { + if (verbose_flag) + printf("Thread map is not initialized -- this is not fatal\n"); + return(0); + } + } + if (readRAW_flag) { + if (read(fd, mapptr, size) != size) { + if (verbose_flag) + printf("Can't read the thread map -- this is not fatal\n"); + free(mapptr); + mapptr = 0; + + return (int)size; + } + } else { + /* Now read the threadmap */ + mib[0] = CTL_KERN; + mib[1] = KERN_KDEBUG; + mib[2] = KERN_KDTHRMAP; + mib[3] = 0; + mib[4] = 0; + mib[5] = 0; /* no flags */ + if (sysctl(mib, 3, mapptr, &size, NULL, 0) < 0) + { + /* This is not fatal -- just means I cant map command strings */ + if (verbose_flag) + printf("Can't read the thread map -- this is not fatal\n"); + free(mapptr); + mapptr = 0; + return(0); + } + } + for (i = 0; i < total_threads; i++) { + if (mapptr[i].thread) + create_map_entry(mapptr[i].thread, &mapptr[i].command[0]); + } + + if (verbose_flag) { + /* Dump the initial map */ + + printf("Size of maptable returned is %ld, thus %ld entries\n", size, (size/sizeof(kd_threadmap))); + + printf("Thread Command\n"); + for (i = 0; i < total_threads; i++) { + printf ("0x%" PRIx64 " %s\n", + (uint64_t)mapptr[i].thread, + mapptr[i].command); + } + } + + return (int)size; +} + +void +create_map_entry(uint64_t thread, char *command) +{ + threadmap_t tme; + int hashid; + + if ((tme = threadmap_freelist)) + threadmap_freelist = tme->tm_next; + else + tme = (threadmap_t)malloc(sizeof(struct threadmap)); + + tme->tm_thread = thread; + tme->tm_deleteme = FALSE; + + (void)strncpy (tme->tm_command, command, MAXCOMLEN); + tme->tm_command[MAXCOMLEN] = '\0'; + + hashid = thread & HASH_MASK; + + tme->tm_next = threadmap_hash[hashid]; + threadmap_hash[hashid] = tme; +} + +void +delete_thread_entry(uint64_t thread) +{ + threadmap_t tme = 0; + threadmap_t tme_prev; + int hashid; + + hashid = thread & HASH_MASK; + + if ((tme = threadmap_hash[hashid])) { + if (tme->tm_thread == thread) + threadmap_hash[hashid] = tme->tm_next; + else { + tme_prev = tme; + + for (tme = tme->tm_next; tme; tme = tme->tm_next) { + if (tme->tm_thread == thread) { + tme_prev->tm_next = tme->tm_next; + break; + } + tme_prev = tme; + } + } + if (tme) { + tme->tm_next = threadmap_freelist; + threadmap_freelist = tme; + } + } +} + +void +find_and_insert_tmp_map_entry(uint64_t pthread, char *command) +{ + threadmap_t tme = 0; + threadmap_t tme_prev; + int hashid; + + if ((tme = threadmap_temp)) { + if (tme->tm_pthread == pthread) + threadmap_temp = tme->tm_next; + else { + tme_prev = tme; + + for (tme = tme->tm_next; tme; tme = tme->tm_next) { + if (tme->tm_pthread == pthread) { + tme_prev->tm_next = tme->tm_next; + break; + } + tme_prev = tme; + } + } + if (tme) { + (void)strncpy (tme->tm_command, command, MAXCOMLEN); + tme->tm_command[MAXCOMLEN] = '\0'; + + delete_thread_entry(tme->tm_thread); + + hashid = tme->tm_thread & HASH_MASK; + + tme->tm_next = threadmap_hash[hashid]; + threadmap_hash[hashid] = tme; + } + } +} + +void +create_tmp_map_entry(uint64_t thread, uint64_t pthread) +{ + threadmap_t tme; + + if ((tme = threadmap_freelist)) + threadmap_freelist = tme->tm_next; + else + tme = (threadmap_t)malloc(sizeof(struct threadmap)); + + tme->tm_thread = thread; + tme->tm_pthread = pthread; + tme->tm_deleteme = FALSE; + tme->tm_command[0] = '\0'; + + tme->tm_next = threadmap_temp; + threadmap_temp = tme; +} + + +threadmap_t +find_thread_entry(uint64_t thread) +{ + threadmap_t tme; + int hashid; + + hashid = thread & HASH_MASK; + + for (tme = threadmap_hash[hashid]; tme; tme = tme->tm_next) { + if (tme->tm_thread == thread) + return (tme); + } + return (0); +} + +void +find_thread_name(uint64_t thread, char **command, boolean_t deleteme) +{ + threadmap_t tme; + + if ((tme = find_thread_entry(thread))) { + *command = tme->tm_command; + + if (deleteme == TRUE) + tme->tm_deleteme = deleteme; + } else + *command = EMPTYSTRING; +} + +void +find_thread_command(kd_buf *kbufp, char **command) +{ + uint64_t thread; + threadmap_t tme; + int debugid_base; + + *command = EMPTYSTRING; + + thread = kbufp->arg5; + debugid_base = kbufp->debugid & DBG_FUNC_MASK; + + if (debugid_base == BSC_exit || debugid_base == MACH_STKHANDOFF) { + /* + * Mark entry as invalid and return temp command pointer + */ + if ((tme = find_thread_entry(thread))) { + + strncpy(tmpcommand, tme->tm_command, MAXCOMLEN); + *command = tmpcommand; + + if (debugid_base == BSC_exit || tme->tm_deleteme == TRUE) + delete_thread_entry(thread); + } + } + else if (debugid_base == TRACE_DATA_NEWTHREAD) { + /* + * Save the create thread data + */ + create_tmp_map_entry(kbufp->arg1, kbufp->arg5); + } + else if (debugid_base == TRACE_STRING_NEWTHREAD) { + /* + * process new map entry + */ + find_and_insert_tmp_map_entry(kbufp->arg5, (char *)&kbufp->arg1); + } + else if (debugid_base == TRACE_STRING_EXEC) { + + delete_thread_entry(kbufp->arg5); + + create_map_entry(kbufp->arg5, (char *)&kbufp->arg1); + } + else + find_thread_name(thread, command, (debugid_base == BSC_thread_terminate)); +} + +static void +getdivisor(void) +{ + (void) mach_timebase_info (&mach_timebase); + + if (frequency == 0) { + divisor = ( (double)mach_timebase.denom / (double)mach_timebase.numer) * 1000; + } else + divisor = (double)frequency / 1000000; + + if (verbose_flag) + printf("divisor = %g\n", divisor); +} -- cgit v1.2.3-56-ge451