/*
 * Copyright (c) 2013-2023, Intel Corporation
 * SPDX-License-Identifier: BSD-3-Clause
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  * Neither the name of Intel Corporation nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#if defined(FEATURE_ELF)
# include "load_elf.h"
#endif /* defined(FEATURE_ELF) */

#include "pt_cpu.h"
#include "pt_version.h"

#include "intel-pt.h"

#if defined(FEATURE_SIDEBAND)
#  include "libipt-sb.h"
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <errno.h>

#include <xed-interface.h>


/* The type of decoder to be used. */
enum ptxed_decoder_type {
	pdt_insn_decoder,
	pdt_block_decoder
};

/* The decoder to use. */
struct ptxed_decoder {
	/* The decoder type. */
	enum ptxed_decoder_type type;

	/* The actual decoder. */
	union {
		/* If @type == pdt_insn_decoder */
		struct pt_insn_decoder *insn;

		/* If @type == pdt_block_decoder */
		struct pt_block_decoder *block;
	} variant;

	/* The image section cache. */
	struct pt_image_section_cache *iscache;

#if defined(FEATURE_SIDEBAND)
	/* The sideband session. */
	struct pt_sb_session *session;

#if defined(FEATURE_PEVENT)
	/* The perf event sideband decoder configuration. */
	struct pt_sb_pevent_config pevent;
#endif /* defined(FEATURE_PEVENT) */
#endif /* defined(FEATURE_SIDEBAND) */
};

/* A collection of options. */
struct ptxed_options {
#if defined(FEATURE_SIDEBAND)
	/* Sideband dump flags. */
	uint32_t sb_dump_flags;
#endif
	/* Do not print the instruction. */
	uint32_t dont_print_insn:1;

	/* Remain as quiet as possible - excluding error messages. */
	uint32_t quiet:1;

	/* Print statistics (overrides quiet). */
	uint32_t print_stats:1;

	/* Print information about section loads and unloads. */
	uint32_t track_image:1;

	/* Track blocks in the output.
	 *
	 * This only applies to the block decoder.
	 */
	uint32_t track_blocks:1;

	/* Print in AT&T format. */
	uint32_t att_format:1;

	/* Print the offset into the trace file. */
	uint32_t print_offset:1;

	/* Print the current timestamp. */
	uint32_t print_time:1;

	/* Print the raw bytes for an insn. */
	uint32_t print_raw_insn:1;

	/* Perform checks. */
	uint32_t check:1;

	/* Print the time stamp of events. */
	uint32_t print_event_time:1;

	/* Print the ip of events. */
	uint32_t print_event_ip:1;

	/* Request tick events. */
	uint32_t enable_tick_events:1;

#if defined(FEATURE_SIDEBAND)
	/* Print sideband warnings. */
	uint32_t print_sb_warnings:1;
#endif
};

/* A collection of flags selecting which stats to collect/print. */
enum ptxed_stats_flag {
	/* Collect number of instructions. */
	ptxed_stat_insn		= (1 << 0),

	/* Collect number of blocks. */
	ptxed_stat_blocks	= (1 << 1)
};

/* A collection of statistics. */
struct ptxed_stats {
	/* The number of instructions. */
	uint64_t insn;

	/* The number of blocks.
	 *
	 * This only applies to the block decoder.
	 */
	uint64_t blocks;

	/* A collection of flags saying which statistics to collect/print. */
	uint32_t flags;
};

static int ptxed_have_decoder(const struct ptxed_decoder *decoder)
{
	/* It suffices to check for one decoder in the variant union. */
	return decoder && decoder->variant.insn;
}

static int ptxed_init_decoder(struct ptxed_decoder *decoder)
{
	if (!decoder)
		return -pte_internal;

	memset(decoder, 0, sizeof(*decoder));
	decoder->type = pdt_block_decoder;

	decoder->iscache = pt_iscache_alloc(NULL);
	if (!decoder->iscache)
		return -pte_nomem;

#if defined(FEATURE_SIDEBAND)
	decoder->session = pt_sb_alloc(decoder->iscache);
	if (!decoder->session) {
		pt_iscache_free(decoder->iscache);
		return -pte_nomem;
	}

#if defined(FEATURE_PEVENT)
	memset(&decoder->pevent, 0, sizeof(decoder->pevent));
	decoder->pevent.size = sizeof(decoder->pevent);
	decoder->pevent.kernel_start = UINT64_MAX;
	decoder->pevent.time_mult = 1;
#endif /* defined(FEATURE_PEVENT) */
#endif /* defined(FEATURE_SIDEBAND) */

	return 0;
}

static void ptxed_free_decoder(struct ptxed_decoder *decoder)
{
	if (!decoder)
		return;

	switch (decoder->type) {
	case pdt_insn_decoder:
		pt_insn_free_decoder(decoder->variant.insn);
		break;

	case pdt_block_decoder:
		pt_blk_free_decoder(decoder->variant.block);
		break;
	}

#if defined(FEATURE_SIDEBAND)
	pt_sb_free(decoder->session);
#endif

	pt_iscache_free(decoder->iscache);
}

static void help(const char *name)
{
	printf("usage: %s [<options>]\n\n", name);
	printf("options:\n");
	printf("  --help|-h                            this text.\n");
	printf("  --version                            display version information and exit.\n");
	printf("  --att                                print instructions in att format.\n");
	printf("  --no-inst                            do not print instructions (only addresses).\n");
	printf("  --quiet|-q                           do not print anything (except errors).\n");
	printf("  --offset                             print the offset into the trace file.\n");
	printf("  --time                               print the current timestamp.\n");
	printf("  --raw-insn                           print the raw bytes of each instruction.\n");
	printf("  --check                              perform checks (expensive).\n");
	printf("  --iscache-limit <size>               set the image section cache limit to <size> bytes.\n");
	printf("  --event:time                         print the tsc for events if available.\n");
	printf("  --event:ip                           print the ip of events if available.\n");
	printf("  --event:tick                         request tick events.\n");
	printf("  --filter:addr<n>_cfg <cfg>           set IA32_RTIT_CTL.ADDRn_CFG to <cfg>.\n");
	printf("  --filter:addr<n>_a <base>            set IA32_RTIT_ADDRn_A to <base>.\n");
	printf("  --filter:addr<n>_b <limit>           set IA32_RTIT_ADDRn_B to <limit>.\n");
	printf("  --stat                               print statistics (even when quiet).\n");
	printf("                                       collects all statistics unless one or more are selected.\n");
	printf("  --stat:insn                          collect number of instructions.\n");
	printf("  --stat:blocks                        collect number of blocks.\n");
#if defined(FEATURE_SIDEBAND)
	printf("  --sb:compact | --sb                  show sideband records in compact format.\n");
	printf("  --sb:verbose                         show sideband records in verbose format.\n");
	printf("  --sb:filename                        show the filename on sideband records.\n");
	printf("  --sb:offset                          show the offset on sideband records.\n");
	printf("  --sb:time                            show the time on sideband records.\n");
	printf("  --sb:switch                          print the new image name on context switches.\n");
	printf("  --sb:warn                            show sideband warnings.\n");
#if defined(FEATURE_PEVENT)
	printf("  --pevent:primary/secondary <file>[:<from>[-<to>]]\n");
	printf("                              load a perf_event sideband stream from <file>.\n");
	printf("                              an optional offset or range can be given.\n");
	printf("  --pevent:sample-type <val>  set perf_event_attr.sample_type to <val> (default: 0).\n");
	printf("  --pevent:time-zero <val>    set perf_event_mmap_page.time_zero to <val> (default: 0).\n");
	printf("  --pevent:time-shift <val>   set perf_event_mmap_page.time_shift to <val> (default: 0).\n");
	printf("  --pevent:time-mult <val>    set perf_event_mmap_page.time_mult to <val> (default: 1).\n");
	printf("  --pevent:tsc-offset <val>   show perf events <val> ticks earlier.\n");
	printf("  --pevent:kernel-start <val> the start address of the kernel.\n");
	printf("  --pevent:sysroot <path>     prepend <path> to sideband filenames.\n");
#if defined(FEATURE_ELF)
	printf("  --pevent:kcore <file>       load the kernel from a core dump.\n");
#endif /* defined(FEATURE_ELF) */
	printf("  --pevent:vdso-x64 <file>    use <file> as 64-bit vdso.\n");
	printf("  --pevent:vdso-x32 <file>    use <file> as x32 vdso.\n");
	printf("  --pevent:vdso-ia32 <file>   use <file> as 32-bit vdso.\n");
#endif /* defined(FEATURE_PEVENT) */
#endif /* defined(FEATURE_SIDEBAND) */
	printf("  --verbose|-v                         print various information (even when quiet).\n");
	printf("  --pt <file>[:<from>[-<to>]]          load the processor trace data from <file>.\n");
	printf("                                       an optional offset or range can be given.\n");
#if defined(FEATURE_ELF)
	printf("  --elf <<file>[:<base>]               load an ELF from <file> at address <base>.\n");
	printf("                                       use the default load address if <base> is omitted.\n");
#endif /* defined(FEATURE_ELF) */
	printf("  --raw <file>[:<from>[-<to>]]:<base>  load a raw binary from <file> at address <base>.\n");
	printf("                                       an optional offset or range can be given.\n");
	printf("  --cpu none|auto|f/m[/s]              set cpu to the given value and decode according to:\n");
	printf("                                         none     spec (default)\n");
	printf("                                         auto     current cpu\n");
	printf("                                         f/m[/s]  family/model[/stepping]\n");
	printf("  --mtc-freq <n>                       set the MTC frequency (IA32_RTIT_CTL[17:14]) to <n>.\n");
	printf("  --nom-freq <n>                       set the nominal frequency (MSR_PLATFORM_INFO[15:8]) to <n>.\n");
	printf("  --cpuid-0x15.eax                     set the value of cpuid[0x15].eax.\n");
	printf("  --cpuid-0x15.ebx                     set the value of cpuid[0x15].ebx.\n");
	printf("  --insn-decoder                       use the instruction flow decoder.\n");
	printf("  --block-decoder                      use the block decoder (default).\n");
	printf("  --block:show-blocks                  show blocks in the output.\n");
	printf("  --block:end-on-call                  set the end-on-call block decoder flag.\n");
	printf("  --block:end-on-jump                  set the end-on-jump block decoder flag.\n");
	printf("\n");
#if defined(FEATURE_ELF)
	printf("You must specify at least one binary or ELF file (--raw|--elf).\n");
#else /* defined(FEATURE_ELF) */
	printf("You must specify at least one binary file (--raw).\n");
#endif /* defined(FEATURE_ELF) */
	printf("You must specify exactly one processor trace file (--pt).\n");
}

static int extract_base(char *arg, uint64_t *base)
{
	char *sep, *rest;

	sep = strrchr(arg, ':');
	if (sep) {
		uint64_t num;

		if (!sep[1])
			return 0;

		errno = 0;
		num = strtoull(sep+1, &rest, 0);
		if (errno || *rest)
			return 0;

		*base = num;
		*sep = 0;
		return 1;
	}

	return 0;
}

static int parse_range(const char *arg, uint64_t *begin, uint64_t *end)
{
	char *rest;

	if (!arg || !*arg)
		return 0;

	errno = 0;
	*begin = strtoull(arg, &rest, 0);
	if (errno)
		return -1;

	if (!*rest)
		return 1;

	if (*rest != '-')
		return -1;

	*end = strtoull(rest+1, &rest, 0);
	if (errno || *rest)
		return -1;

	return 2;
}

/* Preprocess a filename argument.
 *
 * A filename may optionally be followed by a file offset or a file range
 * argument separated by ':'.  Split the original argument into the filename
 * part and the offset/range part.
 *
 * If no end address is specified, set @size to zero.
 * If no offset is specified, set @offset to zero.
 *
 * Returns zero on success, a negative error code otherwise.
 */
static int preprocess_filename(char *filename, uint64_t *offset, uint64_t *size)
{
	uint64_t begin, end;
	char *range;
	int parts;

	if (!filename || !offset || !size)
		return -pte_internal;

	/* Search from the end as the filename may also contain ':'. */
	range = strrchr(filename, ':');
	if (!range) {
		*offset = 0ull;
		*size = 0ull;

		return 0;
	}

	/* Let's try to parse an optional range suffix.
	 *
	 * If we can, remove it from the filename argument.
	 * If we can not, assume that the ':' is part of the filename, e.g. a
	 * drive letter on Windows.
	 */
	parts = parse_range(range + 1, &begin, &end);
	if (parts <= 0) {
		*offset = 0ull;
		*size = 0ull;

		return 0;
	}

	if (parts == 1) {
		*offset = begin;
		*size = 0ull;

		*range = 0;

		return 0;
	}

	if (parts == 2) {
		if (end <= begin)
			return -pte_invalid;

		*offset = begin;
		*size = end - begin;

		*range = 0;

		return 0;
	}

	return -pte_internal;
}

static int load_file(uint8_t **buffer, size_t *psize, const char *filename,
		     uint64_t offset, uint64_t size, const char *prog)
{
	uint8_t *content;
	size_t read;
	FILE *file;
	long fsize, begin, end;
	int errcode;

	if (!buffer || !psize || !filename || !prog) {
		fprintf(stderr, "%s: internal error.\n", prog ? prog : "");
		return -1;
	}

	errno = 0;
	file = fopen(filename, "rb");
	if (!file) {
		fprintf(stderr, "%s: failed to open %s: %d.\n",
			prog, filename, errno);
		return -1;
	}

	errcode = fseek(file, 0, SEEK_END);
	if (errcode) {
		fprintf(stderr, "%s: failed to determine size of %s: %d.\n",
			prog, filename, errno);
		goto err_file;
	}

	fsize = ftell(file);
	if (fsize < 0) {
		fprintf(stderr, "%s: failed to determine size of %s: %d.\n",
			prog, filename, errno);
		goto err_file;
	}

	begin = (long) offset;
	if (((uint64_t) begin != offset) || (fsize <= begin)) {
		fprintf(stderr,
			"%s: bad offset 0x%" PRIx64 " into %s.\n",
			prog, offset, filename);
		goto err_file;
	}

	end = fsize;
	if (size) {
		uint64_t range_end;

		range_end = offset + size;
		if ((uint64_t) end < range_end) {
			fprintf(stderr,
				"%s: bad range 0x%" PRIx64 " in %s.\n",
				prog, range_end, filename);
			goto err_file;
		}

		end = (long) range_end;
	}

	fsize = end - begin;

	content = malloc((size_t) fsize);
	if (!content) {
		fprintf(stderr, "%s: failed to allocated memory %s.\n",
			prog, filename);
		goto err_file;
	}

	errcode = fseek(file, begin, SEEK_SET);
	if (errcode) {
		fprintf(stderr, "%s: failed to load %s: %d.\n",
			prog, filename, errno);
		goto err_content;
	}

	read = fread(content, (size_t) fsize, 1u, file);
	if (read != 1) {
		fprintf(stderr, "%s: failed to load %s: %d.\n",
			prog, filename, errno);
		goto err_content;
	}

	fclose(file);

	*buffer = content;
	*psize = (size_t) fsize;

	return 0;

err_content:
	free(content);

err_file:
	fclose(file);
	return -1;
}

static int load_pt(struct pt_config *config, char *arg, const char *prog)
{
	uint64_t foffset, fsize;
	uint8_t *buffer;
	size_t size;
	int errcode;

	errcode = preprocess_filename(arg, &foffset, &fsize);
	if (errcode < 0) {
		fprintf(stderr, "%s: bad file %s: %s.\n", prog, arg,
			pt_errstr(pt_errcode(errcode)));
		return -1;
	}

	errcode = load_file(&buffer, &size, arg, foffset, fsize, prog);
	if (errcode < 0)
		return errcode;

	config->begin = buffer;
	config->end = buffer + size;

	return 0;
}

static int load_raw(struct pt_image_section_cache *iscache,
		    struct pt_image *image, char *arg, const char *prog)
{
	uint64_t base, foffset, fsize;
	int isid, errcode, has_base;

	has_base = extract_base(arg, &base);
	if (has_base <= 0) {
		fprintf(stderr, "%s: failed to parse base address"
			"from '%s'.\n", prog, arg);
		return -1;
	}

	errcode = preprocess_filename(arg, &foffset, &fsize);
	if (errcode < 0) {
		fprintf(stderr, "%s: bad file %s: %s.\n", prog, arg,
			pt_errstr(pt_errcode(errcode)));
		return -1;
	}

	if (!fsize)
		fsize = UINT64_MAX;

	isid = pt_iscache_add_file(iscache, arg, foffset, fsize, base);
	if (isid < 0) {
		fprintf(stderr, "%s: failed to add %s at 0x%" PRIx64 ": %s.\n",
			prog, arg, base, pt_errstr(pt_errcode(isid)));
		return -1;
	}

	errcode = pt_image_add_cached(image, iscache, isid, NULL);
	if (errcode < 0) {
		fprintf(stderr, "%s: failed to add %s at 0x%" PRIx64 ": %s.\n",
			prog, arg, base, pt_errstr(pt_errcode(errcode)));
		return -1;
	}

	return 0;
}

static xed_machine_mode_enum_t translate_mode(enum pt_exec_mode mode)
{
	switch (mode) {
	case ptem_unknown:
		return XED_MACHINE_MODE_INVALID;

	case ptem_16bit:
		return XED_MACHINE_MODE_LEGACY_16;

	case ptem_32bit:
		return XED_MACHINE_MODE_LEGACY_32;

	case ptem_64bit:
		return XED_MACHINE_MODE_LONG_64;
	}

	return XED_MACHINE_MODE_INVALID;
}

static const char *visualize_iclass(enum pt_insn_class iclass)
{
	switch (iclass) {
	case ptic_error:
		return "unknown/error";

	case ptic_other:
		return "other";

	case ptic_call:
		return "near call";

	case ptic_return:
		return "near return";

	case ptic_jump:
		return "near jump";

	case ptic_cond_jump:
		return "cond jump";

	case ptic_far_call:
		return "far call";

	case ptic_far_return:
		return "far return";

	case ptic_far_jump:
		return "far jump";

	case ptic_ptwrite:
		return "ptwrite";
	}

	return "undefined";
}

static void check_insn_iclass(const xed_inst_t *inst,
			      const struct pt_insn *insn, uint64_t offset)
{
	xed_category_enum_t category;
	xed_iclass_enum_t iclass;

	if (!inst || !insn) {
		printf("[internal error]\n");
		return;
	}

	category = xed_inst_category(inst);
	iclass = xed_inst_iclass(inst);

	switch (insn->iclass) {
	case ptic_error:
		break;

	case ptic_ptwrite:
	case ptic_other:
		switch (category) {
		default:
			return;

		case XED_CATEGORY_CALL:
		case XED_CATEGORY_RET:
		case XED_CATEGORY_UNCOND_BR:
		case XED_CATEGORY_SYSCALL:
		case XED_CATEGORY_SYSRET:
			break;

		case XED_CATEGORY_COND_BR:
			switch (iclass) {
			case XED_ICLASS_XBEGIN:
			case XED_ICLASS_XEND:
				return;

			default:
				break;
			}
			break;

		case XED_CATEGORY_INTERRUPT:
			switch (iclass) {
			case XED_ICLASS_BOUND:
				return;

			default:
				break;
			}
			break;
		}
		break;

	case ptic_call:
		if (iclass == XED_ICLASS_CALL_NEAR)
			return;

		break;

	case ptic_return:
		if (iclass == XED_ICLASS_RET_NEAR)
			return;

		break;

	case ptic_jump:
		if (iclass == XED_ICLASS_JMP)
			return;

		break;

	case ptic_cond_jump:
		if (category == XED_CATEGORY_COND_BR)
			return;

		break;

	case ptic_far_call:
		switch (iclass) {
		default:
			break;

		case XED_ICLASS_CALL_FAR:
		case XED_ICLASS_INT:
		case XED_ICLASS_INT1:
		case XED_ICLASS_INT3:
		case XED_ICLASS_INTO:
		case XED_ICLASS_SYSCALL:
		case XED_ICLASS_SYSCALL_AMD:
		case XED_ICLASS_SYSENTER:
		case XED_ICLASS_VMCALL:
			return;
		}
		break;

	case ptic_far_return:
		switch (iclass) {
		default:
			break;

		case XED_ICLASS_RET_FAR:
		case XED_ICLASS_IRET:
		case XED_ICLASS_IRETD:
		case XED_ICLASS_IRETQ:
		case XED_ICLASS_SYSRET:
		case XED_ICLASS_SYSRET64:
		case XED_ICLASS_SYSRET_AMD:
		case XED_ICLASS_SYSEXIT:
		case XED_ICLASS_VMLAUNCH:
		case XED_ICLASS_VMRESUME:
			return;
		}
		break;

	case ptic_far_jump:
		if (iclass == XED_ICLASS_JMP_FAR)
			return;

		break;
	}

	/* If we get here, @insn->iclass doesn't match XED's classification. */
	printf("[%" PRIx64 ", %" PRIx64 ": iclass error: iclass: %s, "
	       "xed iclass: %s, category: %s]\n", offset, insn->ip,
	       visualize_iclass(insn->iclass), xed_iclass_enum_t2str(iclass),
	       xed_category_enum_t2str(category));

}

static void check_insn_decode(xed_decoded_inst_t *inst,
			      const struct pt_insn *insn, uint64_t offset)
{
	xed_error_enum_t errcode;

	if (!inst || !insn) {
		printf("[internal error]\n");
		return;
	}

	xed_decoded_inst_set_mode(inst, translate_mode(insn->mode),
				  XED_ADDRESS_WIDTH_INVALID);

	/* Decode the instruction (again).
	 *
	 * We may have decoded the instruction already for printing.  In this
	 * case, we will decode it twice.
	 *
	 * The more common use-case, however, is to check the instruction class
	 * while not printing instructions since the latter is too expensive for
	 * regular use with long traces.
	 */
	errcode = xed_decode(inst, insn->raw, insn->size);
	if (errcode != XED_ERROR_NONE) {
		printf("[%" PRIx64 ", %" PRIx64 ": xed error: (%u) %s]\n",
		       offset, insn->ip, errcode,
		       xed_error_enum_t2str(errcode));
		return;
	}

	if (!xed_decoded_inst_valid(inst)) {
		printf("[%" PRIx64 ", %" PRIx64 ": xed error: "
		       "invalid instruction]\n", offset, insn->ip);
		return;
	}
}

static void check_insn(const struct pt_insn *insn, uint64_t offset)
{
	xed_decoded_inst_t inst;

	if (!insn) {
		printf("[internal error]\n");
		return;
	}

	if (insn->isid <= 0)
		printf("[%" PRIx64 ", %" PRIx64 ": check error: "
		       "bad isid]\n", offset, insn->ip);

	xed_decoded_inst_zero(&inst);
	check_insn_decode(&inst, insn, offset);

	/* We need a valid instruction in order to do further checks.
	 *
	 * Invalid instructions have already been diagnosed.
	 */
	if (!xed_decoded_inst_valid(&inst))
		return;

	check_insn_iclass(xed_decoded_inst_inst(&inst), insn, offset);
}

static void print_raw_insn(const struct pt_insn *insn)
{
	uint8_t length, idx;

	if (!insn) {
		printf("[internal error]");
		return;
	}

	length = insn->size;
	if (sizeof(insn->raw) < length)
		length = sizeof(insn->raw);

	for (idx = 0; idx < length; ++idx)
		printf(" %02x", insn->raw[idx]);

	for (; idx < pt_max_insn_size; ++idx)
		printf("   ");
}

static void xed_print_insn(const xed_decoded_inst_t *inst, uint64_t ip,
			   const struct ptxed_options *options)
{
	xed_print_info_t pi;
	char buffer[256];
	xed_bool_t ok;

	if (!inst || !options) {
		printf(" [internal error]");
		return;
	}

	if (options->print_raw_insn) {
		xed_uint_t length, i;

		length = xed_decoded_inst_get_length(inst);
		for (i = 0; i < length; ++i)
			printf(" %02x", xed_decoded_inst_get_byte(inst, i));

		for (; i < pt_max_insn_size; ++i)
			printf("   ");
	}

	xed_init_print_info(&pi);
	pi.p = inst;
	pi.buf = buffer;
	pi.blen = sizeof(buffer);
	pi.runtime_address = ip;

	if (options->att_format)
		pi.syntax = XED_SYNTAX_ATT;

	ok = xed_format_generic(&pi);
	if (!ok) {
		printf(" [xed print error]");
		return;
	}

	printf("  %s", buffer);
}

static void print_insn(const struct pt_insn *insn, xed_state_t *xed,
		       const struct ptxed_options *options, uint64_t offset,
		       uint64_t time)
{
	if (!insn || !options) {
		printf("[internal error]\n");
		return;
	}

	if (options->print_offset)
		printf("%016" PRIx64 "  ", offset);

	if (options->print_time)
		printf("%016" PRIx64 "  ", time);

	if (insn->speculative)
		printf("? ");

	printf("%016" PRIx64, insn->ip);

	if (!options->dont_print_insn) {
		xed_machine_mode_enum_t mode;
		xed_decoded_inst_t inst;
		xed_error_enum_t errcode;

		mode = translate_mode(insn->mode);

		xed_state_set_machine_mode(xed, mode);
		xed_decoded_inst_zero_set_mode(&inst, xed);

		errcode = xed_decode(&inst, insn->raw, insn->size);
		switch (errcode) {
		case XED_ERROR_NONE:
			xed_print_insn(&inst, insn->ip, options);
			break;

		default:
			print_raw_insn(insn);

			printf(" [xed decode error: (%u) %s]", errcode,
			       xed_error_enum_t2str(errcode));
			break;
		}
	}

	printf("\n");
}

static const char *print_exec_mode(enum pt_exec_mode mode)
{
	switch (mode) {
	case ptem_unknown:
		return "<unknown>";

	case ptem_16bit:
		return "16-bit";

	case ptem_32bit:
		return "32-bit";

	case ptem_64bit:
		return "64-bit";
	}

	return "<invalid>";
}

static void print_event(const struct pt_event *event,
			const struct ptxed_options *options, uint64_t offset)
{
	if (!event || !options) {
		printf("[internal error]\n");
		return;
	}

	printf("[");

	if (options->print_offset)
		printf("%016" PRIx64 "  ", offset);

	if (options->print_event_time && event->has_tsc)
		printf("%016" PRIx64 "  ", event->tsc);

	switch (event->type) {
	case ptev_enabled:
		printf("%s", event->variant.enabled.resumed ? "resumed" :
		       "enabled");

		if (options->print_event_ip)
			printf(", ip: %016" PRIx64, event->variant.enabled.ip);
		break;

	case ptev_disabled:
		printf("disabled");

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.disabled.ip);
		break;

	case ptev_async_disabled:
		printf("disabled");

		if (options->print_event_ip) {
			printf(", at: %016" PRIx64,
			       event->variant.async_disabled.at);

			if (!event->ip_suppressed)
				printf(", ip: %016" PRIx64,
				       event->variant.async_disabled.ip);
		}
		break;

	case ptev_async_branch:
		printf("interrupt");

		if (options->print_event_ip) {
			printf(", from: %016" PRIx64,
			       event->variant.async_branch.from);

			if (!event->ip_suppressed)
				printf(", to: %016" PRIx64,
				       event->variant.async_branch.to);
		}
		break;

	case ptev_paging:
		printf("paging, cr3: %016" PRIx64 "%s",
		       event->variant.paging.cr3,
		       event->variant.paging.non_root ? ", nr" : "");
		break;

	case ptev_async_paging:
		printf("paging, cr3: %016" PRIx64 "%s",
		       event->variant.async_paging.cr3,
		       event->variant.async_paging.non_root ? ", nr" : "");

		if (options->print_event_ip)
			printf(", ip: %016" PRIx64,
			       event->variant.async_paging.ip);
		break;

	case ptev_overflow:
		printf("overflow");

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.overflow.ip);
		break;

	case ptev_exec_mode:
		printf("exec mode: %s",
		       print_exec_mode(event->variant.exec_mode.mode));

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64,
			       event->variant.exec_mode.ip);
		break;

	case ptev_tsx:
		if (event->variant.tsx.aborted)
			printf("aborted");
		else if (event->variant.tsx.speculative)
			printf("begin transaction");
		else
			printf("committed");

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.tsx.ip);
		break;

	case ptev_stop:
		printf("stopped");
		break;

	case ptev_vmcs:
		printf("vmcs, base: %016" PRIx64, event->variant.vmcs.base);
		break;

	case ptev_async_vmcs:
		printf("vmcs, base: %016" PRIx64,
		       event->variant.async_vmcs.base);

		if (options->print_event_ip)
			printf(", ip: %016" PRIx64,
			       event->variant.async_vmcs.ip);
		break;

	case ptev_exstop:
		printf("exstop");

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.exstop.ip);
		break;

	case ptev_mwait:
		printf("mwait %" PRIx32 " %" PRIx32,
		       event->variant.mwait.hints, event->variant.mwait.ext);

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.mwait.ip);
		break;

	case ptev_pwre:
		printf("pwre c%u.%u", (event->variant.pwre.state + 1) & 0xf,
		       (event->variant.pwre.sub_state + 1) & 0xf);

		if (event->variant.pwre.hw)
			printf(" hw");
		break;


	case ptev_pwrx:
		printf("pwrx ");

		if (event->variant.pwrx.interrupt)
			printf("int: ");

		if (event->variant.pwrx.store)
			printf("st: ");

		if (event->variant.pwrx.autonomous)
			printf("hw: ");

		printf("c%u (c%u)", (event->variant.pwrx.last + 1) & 0xf,
		       (event->variant.pwrx.deepest + 1) & 0xf);
		break;

	case ptev_ptwrite:
		printf("ptwrite: %" PRIx64, event->variant.ptwrite.payload);

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.ptwrite.ip);
		break;

	case ptev_tick:
		printf("tick");

		if (options->print_event_ip && !event->ip_suppressed)
			printf(", ip: %016" PRIx64, event->variant.tick.ip);
		break;

	case ptev_cbr:
		printf("cbr: %x", event->variant.cbr.ratio);
		break;

	case ptev_mnt:
		printf("mnt: %" PRIx64, event->variant.mnt.payload);
		break;
	}

	printf("]\n");
}

static void diagnose(struct ptxed_decoder *decoder, uint64_t ip,
		     const char *errtype, int errcode)
{
	int err;
	uint64_t pos;

	err = -pte_internal;
	pos = 0ull;

	switch (decoder->type) {
	case pdt_insn_decoder:
		err = pt_insn_get_offset(decoder->variant.insn, &pos);
		break;

	case pdt_block_decoder:
		err = pt_blk_get_offset(decoder->variant.block, &pos);
		break;
	}

	if (err < 0) {
		printf("could not determine offset: %s\n",
		       pt_errstr(pt_errcode(err)));
		printf("[?, %" PRIx64 ": %s: %s]\n", ip, errtype,
		       pt_errstr(pt_errcode(errcode)));
	} else
		printf("[%" PRIx64 ", %" PRIx64 ": %s: %s]\n", pos,
		       ip, errtype, pt_errstr(pt_errcode(errcode)));
}

#if defined(FEATURE_SIDEBAND)

static int ptxed_sb_event(struct ptxed_decoder *decoder,
			  const struct pt_event *event,
			  const struct ptxed_options *options)
{
	struct pt_image *image;
	int errcode;

	if (!decoder || !event || !options)
		return -pte_internal;

	image = NULL;
	errcode = pt_sb_event(decoder->session, &image, event, sizeof(*event),
			      stdout, options->sb_dump_flags);
	if (errcode < 0)
		return errcode;

	if (!image)
		return 0;

	switch (decoder->type) {
	case pdt_insn_decoder:
		return pt_insn_set_image(decoder->variant.insn, image);

	case pdt_block_decoder:
		return pt_blk_set_image(decoder->variant.block, image);
	}

	return -pte_internal;
}

#endif /* defined(FEATURE_SIDEBAND) */

static int drain_events_insn(struct ptxed_decoder *decoder, uint64_t *time,
			     int status, const struct ptxed_options *options)
{
	struct pt_insn_decoder *ptdec;
	int errcode;

	if (!decoder || !time || !options)
		return -pte_internal;

	ptdec = decoder->variant.insn;

	while (status & pts_event_pending) {
		struct pt_event event;
		uint64_t offset;

		offset = 0ull;
		if (options->print_offset) {
			errcode = pt_insn_get_offset(ptdec, &offset);
			if (errcode < 0)
				return errcode;
		}

		status = pt_insn_event(ptdec, &event, sizeof(event));
		if (status < 0)
			return status;

		*time = event.tsc;

		if (!options->quiet && !event.status_update)
			print_event(&event, options, offset);

#if defined(FEATURE_SIDEBAND)
		errcode = ptxed_sb_event(decoder, &event, options);
		if (errcode < 0)
			return errcode;
#endif /* defined(FEATURE_SIDEBAND) */
	}

	return status;
}

static void decode_insn(struct ptxed_decoder *decoder,
			const struct ptxed_options *options,
			struct ptxed_stats *stats)
{
	struct pt_insn_decoder *ptdec;
	xed_state_t xed;
	uint64_t offset, sync, time;

	if (!decoder || !options) {
		printf("[internal error]\n");
		return;
	}

	xed_state_zero(&xed);

	ptdec = decoder->variant.insn;
	offset = 0ull;
	sync = 0ull;
	time = 0ull;
	for (;;) {
		struct pt_insn insn;
		int status;

		/* Initialize the IP - we use it for error reporting. */
		insn.ip = 0ull;

		status = pt_insn_sync_forward(ptdec);
		if (status < 0) {
			uint64_t new_sync;
			int errcode;

			if (status == -pte_eos)
				break;

			diagnose(decoder, insn.ip, "sync error", status);

			/* Let's see if we made any progress.  If we haven't,
			 * we likely never will.  Bail out.
			 *
			 * We intentionally report the error twice to indicate
			 * that we tried to re-sync.  Maybe it even changed.
			 */
			errcode = pt_insn_get_offset(ptdec, &new_sync);
			if (errcode < 0 || (new_sync <= sync))
				break;

			sync = new_sync;
			continue;
		}

		for (;;) {
			status = drain_events_insn(decoder, &time, status,
						   options);
			if (status < 0)
				break;

			if (status & pts_eos) {
				if (!(status & pts_ip_suppressed) &&
				    !options->quiet)
					printf("[end of trace]\n");

				status = -pte_eos;
				break;
			}

			if (options->print_offset || options->check) {
				int errcode;

				errcode = pt_insn_get_offset(ptdec, &offset);
				if (errcode < 0)
					break;
			}

			status = pt_insn_next(ptdec, &insn, sizeof(insn));
			if (status < 0) {
				/* Even in case of errors, we may have succeeded
				 * in decoding the current instruction.
				 */
				if (insn.iclass != ptic_error) {
					if (!options->quiet)
						print_insn(&insn, &xed, options,
							   offset, time);
					if (stats)
						stats->insn += 1;

					if (options->check)
						check_insn(&insn, offset);
				}
				break;
			}

			if (!options->quiet)
				print_insn(&insn, &xed, options, offset, time);

			if (stats)
				stats->insn += 1;

			if (options->check)
				check_insn(&insn, offset);
		}

		/* We shouldn't break out of the loop without an error. */
		if (!status)
			status = -pte_internal;

		/* We're done when we reach the end of the trace stream. */
		if (status == -pte_eos)
			break;

		diagnose(decoder, insn.ip, "error",  status);
	}
}

static int xed_next_ip(uint64_t *pip, const xed_decoded_inst_t *inst,
		       uint64_t ip)
{
	xed_uint_t length, disp_width;

	if (!pip || !inst)
		return -pte_internal;

	length = xed_decoded_inst_get_length(inst);
	if (!length) {
		printf("[xed error: failed to determine instruction length]\n");
		return -pte_bad_insn;
	}

	ip += length;

	/* If it got a branch displacement it must be a branch.
	 *
	 * This includes conditional branches for which we don't know whether
	 * they were taken.  The next IP won't be used in this case as a
	 * conditional branch ends a block.  The next block will start with the
	 * correct IP.
	 */
	disp_width = xed_decoded_inst_get_branch_displacement_width(inst);
	if (disp_width)
		ip += (uint64_t) (int64_t)
			xed_decoded_inst_get_branch_displacement(inst);

	*pip = ip;
	return 0;
}

static int block_fetch_insn(struct pt_insn *insn, const struct pt_block *block,
			    uint64_t ip, struct pt_image_section_cache *iscache)
{
	if (!insn || !block)
		return -pte_internal;

	/* We can't read from an empty block. */
	if (!block->ninsn)
		return -pte_invalid;

	memset(insn, 0, sizeof(*insn));
	insn->mode = block->mode;
	insn->isid = block->isid;
	insn->ip = ip;

	/* The last instruction in a block may be truncated. */
	if ((ip == block->end_ip) && block->truncated) {
		if (!block->size || (sizeof(insn->raw) < (size_t) block->size))
			return -pte_bad_insn;

		insn->size = block->size;
		memcpy(insn->raw, block->raw, insn->size);
	} else {
		int size;

		size = pt_iscache_read(iscache, insn->raw, sizeof(insn->raw),
				       insn->isid, ip);
		if (size < 0)
			return size;

		insn->size = (uint8_t) size;
	}

	return 0;
}

static void diagnose_block(struct ptxed_decoder *decoder,
			   const char *errtype, int errcode,
			   const struct pt_block *block)
{
	uint64_t ip;
	int err;

	if (!decoder || !block) {
		printf("ptxed: internal error");
		return;
	}

	/* Determine the IP at which to report the error.
	 *
	 * Depending on the type of error, the IP varies between that of the
	 * last instruction in @block or the next instruction outside of @block.
	 *
	 * When the block is empty, we use the IP of the block itself,
	 * i.e. where the first instruction should have been.
	 */
	if (!block->ninsn)
		ip = block->ip;
	else {
		ip = block->end_ip;

		switch (errcode) {
		case -pte_nomap:
		case -pte_bad_insn: {
			struct pt_insn insn;
			xed_decoded_inst_t inst;
			xed_error_enum_t xederr;

			/* Decode failed when trying to fetch or decode the next
			 * instruction.  Since indirect or conditional branches
			 * end a block and don't cause an additional fetch, we
			 * should be able to reach that IP from the last
			 * instruction in @block.
			 *
			 * We ignore errors and fall back to the IP of the last
			 * instruction.
			 */
			err = block_fetch_insn(&insn, block, ip,
					       decoder->iscache);
			if (err < 0)
				break;

			xed_decoded_inst_zero(&inst);
			xed_decoded_inst_set_mode(&inst,
						  translate_mode(insn.mode),
						  XED_ADDRESS_WIDTH_INVALID);

			xederr = xed_decode(&inst, insn.raw, insn.size);
			if (xederr != XED_ERROR_NONE)
				break;

			(void) xed_next_ip(&ip, &inst, insn.ip);
		}
			break;

		default:
			break;
		}
	}

	diagnose(decoder, ip, errtype, errcode);
}

static void print_block(struct ptxed_decoder *decoder,
			const struct pt_block *block,
			const struct ptxed_options *options,
			const struct ptxed_stats *stats,
			uint64_t offset, uint64_t time)
{
	xed_machine_mode_enum_t mode;
	xed_state_t xed;
	uint64_t ip;
	uint16_t ninsn;

	if (!block || !options) {
		printf("[internal error]\n");
		return;
	}

	if (options->track_blocks) {
		printf("[block");
		if (stats)
			printf(" %" PRIx64, stats->blocks);
		printf("]\n");
	}

	mode = translate_mode(block->mode);
	xed_state_init2(&xed, mode, XED_ADDRESS_WIDTH_INVALID);

	/* There's nothing to do for empty blocks. */
	ninsn = block->ninsn;
	if (!ninsn)
		return;

	ip = block->ip;
	for (;;) {
		struct pt_insn insn;
		xed_decoded_inst_t inst;
		xed_error_enum_t xederrcode;
		int errcode;

		if (options->print_offset)
			printf("%016" PRIx64 "  ", offset);

		if (options->print_time)
			printf("%016" PRIx64 "  ", time);

		if (block->speculative)
			printf("? ");

		printf("%016" PRIx64, ip);

		errcode = block_fetch_insn(&insn, block, ip, decoder->iscache);
		if (errcode < 0) {
			printf(" [fetch error: %s]\n",
			       pt_errstr(pt_errcode(errcode)));
			break;
		}

		xed_decoded_inst_zero_set_mode(&inst, &xed);

		xederrcode = xed_decode(&inst, insn.raw, insn.size);
		if (xederrcode != XED_ERROR_NONE) {
			print_raw_insn(&insn);

			printf(" [xed decode error: (%u) %s]\n", xederrcode,
			       xed_error_enum_t2str(xederrcode));
			break;
		}

		if (!options->dont_print_insn)
			xed_print_insn(&inst, insn.ip, options);

		printf("\n");

		ninsn -= 1;
		if (!ninsn)
			break;

		errcode = xed_next_ip(&ip, &inst, ip);
		if (errcode < 0) {
			diagnose(decoder, ip, "reconstruct error", errcode);
			break;
		}
	}

	/* Decode should have brought us to @block->end_ip. */
	if (ip != block->end_ip)
		diagnose(decoder, ip, "reconstruct error", -pte_nosync);
}

static void check_block(const struct pt_block *block,
			struct pt_image_section_cache *iscache,
			uint64_t offset)
{
	struct pt_insn insn;
	xed_decoded_inst_t inst;
	uint64_t ip;
	uint16_t ninsn;
	int errcode;

	if (!block) {
		printf("[internal error]\n");
		return;
	}

	/* There's nothing to check for an empty block. */
	ninsn = block->ninsn;
	if (!ninsn)
		return;

	if (block->isid <= 0)
		printf("[%" PRIx64 ", %" PRIx64 ": check error: "
		       "bad isid]\n", offset, block->ip);

	ip = block->ip;
	do {
		errcode = block_fetch_insn(&insn, block, ip, iscache);
		if (errcode < 0) {
			printf("[%" PRIx64 ", %" PRIx64 ": fetch error: %s]\n",
			       offset, ip, pt_errstr(pt_errcode(errcode)));
			return;
		}

		xed_decoded_inst_zero(&inst);
		check_insn_decode(&inst, &insn, offset);

		/* We need a valid instruction in order to do further checks.
		 *
		 * Invalid instructions have already been diagnosed.
		 */
		if (!xed_decoded_inst_valid(&inst))
			return;

		errcode = xed_next_ip(&ip, &inst, ip);
		if (errcode < 0) {
			printf("[%" PRIx64 ", %" PRIx64 ": error: %s]\n",
			       offset, ip, pt_errstr(pt_errcode(errcode)));
			return;
		}
	} while (--ninsn);

	/* We reached the end of the block.  Both @insn and @inst refer to the
	 * last instruction in @block.
	 *
	 * Check that we reached the end IP of the block.
	 */
	if (insn.ip != block->end_ip) {
		printf("[%" PRIx64 ", %" PRIx64 ": error: did not reach end: %"
		       PRIx64 "]\n", offset, insn.ip, block->end_ip);
	}

	/* Check the last instruction's classification, if available. */
	insn.iclass = block->iclass;
	if (insn.iclass)
		check_insn_iclass(xed_decoded_inst_inst(&inst), &insn, offset);
}

static int drain_events_block(struct ptxed_decoder *decoder, uint64_t *time,
			      int status, const struct ptxed_options *options)
{
	struct pt_block_decoder *ptdec;
	int errcode;

	if (!decoder || !time || !options)
		return -pte_internal;

	ptdec = decoder->variant.block;

	while (status & pts_event_pending) {
		struct pt_event event;
		uint64_t offset;

		offset = 0ull;
		if (options->print_offset) {
			errcode = pt_blk_get_offset(ptdec, &offset);
			if (errcode < 0)
				return errcode;
		}

		status = pt_blk_event(ptdec, &event, sizeof(event));
		if (status < 0)
			return status;

		*time = event.tsc;

		if (!options->quiet && !event.status_update)
			print_event(&event, options, offset);

#if defined(FEATURE_SIDEBAND)
		errcode = ptxed_sb_event(decoder, &event, options);
		if (errcode < 0)
			return errcode;
#endif /* defined(FEATURE_SIDEBAND) */
	}

	return status;
}

static void decode_block(struct ptxed_decoder *decoder,
			 const struct ptxed_options *options,
			 struct ptxed_stats *stats)
{
	struct pt_image_section_cache *iscache;
	struct pt_block_decoder *ptdec;
	uint64_t offset, sync, time;

	if (!decoder || !options) {
		printf("[internal error]\n");
		return;
	}

	iscache = decoder->iscache;
	ptdec = decoder->variant.block;
	offset = 0ull;
	sync = 0ull;
	time = 0ull;
	for (;;) {
		struct pt_block block;
		int status;

		/* Initialize IP and ninsn - we use it for error reporting. */
		block.ip = 0ull;
		block.ninsn = 0u;

		status = pt_blk_sync_forward(ptdec);
		if (status < 0) {
			uint64_t new_sync;
			int errcode;

			if (status == -pte_eos)
				break;

			diagnose_block(decoder, "sync error", status, &block);

			/* Let's see if we made any progress.  If we haven't,
			 * we likely never will.  Bail out.
			 *
			 * We intentionally report the error twice to indicate
			 * that we tried to re-sync.  Maybe it even changed.
			 */
			errcode = pt_blk_get_offset(ptdec, &new_sync);
			if (errcode < 0 || (new_sync <= sync))
				break;

			sync = new_sync;
			continue;
		}

		for (;;) {
			status = drain_events_block(decoder, &time, status,
						    options);
			if (status < 0)
				break;

			if (status & pts_eos) {
				if (!(status & pts_ip_suppressed) &&
				    !options->quiet)
					printf("[end of trace]\n");

				status = -pte_eos;
				break;
			}

			if (options->print_offset || options->check) {
				int errcode;

				errcode = pt_blk_get_offset(ptdec, &offset);
				if (errcode < 0)
					break;
			}

			status = pt_blk_next(ptdec, &block, sizeof(block));
			if (status < 0) {
				/* Even in case of errors, we may have succeeded
				 * in decoding some instructions.
				 */
				if (block.ninsn) {
					if (stats) {
						stats->insn += block.ninsn;
						stats->blocks += 1;
					}

					if (!options->quiet)
						print_block(decoder, &block,
							    options, stats,
							    offset, time);

					if (options->check)
						check_block(&block, iscache,
							    offset);
				}
				break;
			}

			if (stats) {
				stats->insn += block.ninsn;
				stats->blocks += 1;
			}

			if (!options->quiet)
				print_block(decoder, &block, options, stats,
					    offset, time);

			if (options->check)
				check_block(&block, iscache, offset);
		}

		/* We shouldn't break out of the loop without an error. */
		if (!status)
			status = -pte_internal;

		/* We're done when we reach the end of the trace stream. */
		if (status == -pte_eos)
			break;

		diagnose_block(decoder, "error", status, &block);
	}
}

static void decode(struct ptxed_decoder *decoder,
		   const struct ptxed_options *options,
		   struct ptxed_stats *stats)
{
	if (!decoder) {
		printf("[internal error]\n");
		return;
	}

	switch (decoder->type) {
	case pdt_insn_decoder:
		decode_insn(decoder, options, stats);
		break;

	case pdt_block_decoder:
		decode_block(decoder, options, stats);
		break;
	}
}

static int alloc_decoder(struct ptxed_decoder *decoder,
			 const struct pt_config *conf, struct pt_image *image,
			 const struct ptxed_options *options, const char *prog)
{
	struct pt_config config;
	int errcode;

	if (!decoder || !conf || !options || !prog)
		return -pte_internal;

	config = *conf;

	switch (decoder->type) {
	case pdt_insn_decoder:
		if (options->enable_tick_events)
			config.flags.variant.insn.enable_tick_events = 1;

		decoder->variant.insn = pt_insn_alloc_decoder(&config);
		if (!decoder->variant.insn) {
			fprintf(stderr,
				"%s: failed to create decoder.\n", prog);
			return -pte_nomem;
		}

		errcode = pt_insn_set_image(decoder->variant.insn, image);
		if (errcode < 0) {
			fprintf(stderr, "%s: failed to set image.\n", prog);
			return errcode;
		}

		break;

	case pdt_block_decoder:
		if (options->enable_tick_events)
			config.flags.variant.block.enable_tick_events = 1;

		decoder->variant.block = pt_blk_alloc_decoder(&config);
		if (!decoder->variant.block) {
			fprintf(stderr,
				"%s: failed to create  decoder.\n", prog);
			return -pte_nomem;
		}

		errcode = pt_blk_set_image(decoder->variant.block, image);
		if (errcode < 0) {
			fprintf(stderr, "%s: failed to set image.\n", prog);
			return errcode;
		}

		break;
	}

	return 0;
}

static void print_stats(struct ptxed_stats *stats)
{
	if (!stats) {
		printf("[internal error]\n");
		return;
	}

	if (stats->flags & ptxed_stat_insn)
		printf("insn: %" PRIu64 ".\n", stats->insn);

	if (stats->flags & ptxed_stat_blocks)
		printf("blocks:\t%" PRIu64 ".\n", stats->blocks);
}

#if defined(FEATURE_SIDEBAND)

static int ptxed_print_error(int errcode, const char *filename,
			     uint64_t offset, void *priv)
{
	const struct ptxed_options *options;
	const char *errstr, *severity;

	options = (struct ptxed_options *) priv;
	if (!options)
		return -pte_internal;

	if (errcode >= 0 && !options->print_sb_warnings)
		return 0;

	if (!filename)
		filename = "<unknown>";

	severity = errcode < 0 ? "error" : "warning";

	errstr = errcode < 0
		? pt_errstr(pt_errcode(errcode))
		: pt_sb_errstr((enum pt_sb_error_code) errcode);

	if (!errstr)
		errstr = "<unknown error>";

	printf("[%s:%016" PRIx64 " sideband %s: %s]\n", filename, offset,
	       severity, errstr);

	return 0;
}

static int ptxed_print_switch(const struct pt_sb_context *context, void *priv)
{
	struct pt_image *image;
	const char *name;

	if (!priv)
		return -pte_internal;

	image = pt_sb_ctx_image(context);
	if (!image)
		return -pte_internal;

	name = pt_image_name(image);
	if (!name)
		name = "<unknown>";

	printf("[context: %s]\n", name);

	return 0;
}

#if defined(FEATURE_PEVENT)

static int ptxed_sb_pevent(struct ptxed_decoder *decoder, char *filename,
			   const char *prog)
{
	struct pt_sb_pevent_config config;
	uint64_t foffset, fsize, fend;
	int errcode;

	if (!decoder || !prog) {
		fprintf(stderr, "%s: internal error.\n", prog ? prog : "?");
		return -1;
	}

	errcode = preprocess_filename(filename, &foffset, &fsize);
	if (errcode < 0) {
		fprintf(stderr, "%s: bad file %s: %s.\n", prog, filename,
			pt_errstr(pt_errcode(errcode)));
		return -1;
	}

	if (SIZE_MAX < foffset) {
		fprintf(stderr,
			"%s: bad offset: 0x%" PRIx64 ".\n", prog, foffset);
		return -1;
	}

	config = decoder->pevent;
	config.filename = filename;
	config.begin = (size_t) foffset;
	config.end = 0;

	if (fsize) {
		fend = foffset + fsize;
		if ((fend <= foffset) || (SIZE_MAX < fend)) {
			fprintf(stderr,
				"%s: bad range: 0x%" PRIx64 "-0x%" PRIx64 ".\n",
				prog, foffset, fend);
			return -1;
		}

		config.end = (size_t) fend;
	}

	errcode = pt_sb_alloc_pevent_decoder(decoder->session, &config);
	if (errcode < 0) {
		fprintf(stderr, "%s: error loading %s: %s.\n", prog, filename,
			pt_errstr(pt_errcode(errcode)));
		return -1;
	}

	return 0;
}

#endif /* defined(FEATURE_PEVENT) */
#endif /* defined(FEATURE_SIDEBAND) */

static int get_arg_uint64(uint64_t *value, const char *option, const char *arg,
			  const char *prog)
{
	char *rest;

	if (!value || !option || !prog) {
		fprintf(stderr, "%s: internal error.\n", prog ? prog : "?");
		return 0;
	}

	if (!arg || arg[0] == 0 || (arg[0] == '-' && arg[1] == '-')) {
		fprintf(stderr, "%s: %s: missing argument.\n", prog, option);
		return 0;
	}

	errno = 0;
	*value = strtoull(arg, &rest, 0);
	if (errno || *rest) {
		fprintf(stderr, "%s: %s: bad argument: %s.\n", prog, option,
			arg);
		return 0;
	}

	return 1;
}

static int get_arg_uint32(uint32_t *value, const char *option, const char *arg,
			  const char *prog)
{
	uint64_t val;

	if (!get_arg_uint64(&val, option, arg, prog))
		return 0;

	if (val > UINT32_MAX) {
		fprintf(stderr, "%s: %s: value too big: %s.\n", prog, option,
			arg);
		return 0;
	}

	*value = (uint32_t) val;

	return 1;
}

#if defined(FEATURE_SIDEBAND) && defined(FEATURE_PEVENT)

static int get_arg_uint16(uint16_t *value, const char *option, const char *arg,
			  const char *prog)
{
	uint64_t val;

	if (!get_arg_uint64(&val, option, arg, prog))
		return 0;

	if (val > UINT16_MAX) {
		fprintf(stderr, "%s: %s: value too big: %s.\n", prog, option,
			arg);
		return 0;
	}

	*value = (uint16_t) val;

	return 1;
}

#endif /* defined(FEATURE_SIDEBAND) && defined(FEATURE_PEVENT) */

static int get_arg_uint8(uint8_t *value, const char *option, const char *arg,
			 const char *prog)
{
	uint64_t val;

	if (!get_arg_uint64(&val, option, arg, prog))
		return 0;

	if (val > UINT8_MAX) {
		fprintf(stderr, "%s: %s: value too big: %s.\n", prog, option,
			arg);
		return 0;
	}

	*value = (uint8_t) val;

	return 1;
}

static int ptxed_addr_cfg(struct pt_config *config, uint8_t filter,
			  const char *option, const char *arg, const char *prog)
{
	uint64_t addr_cfg;

	if (!config || !option || !arg || !prog) {
		fprintf(stderr, "%s: internal error.\n", prog ? prog : "?");
		return 0;
	}

	if (!get_arg_uint64(&addr_cfg, option, arg, prog))
		return 0;

	if (15 < addr_cfg) {
		fprintf(stderr, "%s: %s: value too big: %s.\n", prog, option,
			arg);
		return 0;
	}

	/* Make sure the shift doesn't overflow. */
	if (15 < filter) {
		fprintf(stderr, "%s: internal error.\n", prog);
		return 0;
	}

	addr_cfg <<= (filter * 4);

	config->addr_filter.config.addr_cfg |= addr_cfg;

	return 1;
}

extern int main(int argc, char *argv[])
{
	struct ptxed_decoder decoder;
	struct ptxed_options options;
	struct ptxed_stats stats;
	struct pt_config config;
	struct pt_image *image;
	const char *prog;
	int errcode, i;

	if (!argc) {
		help("");
		return 1;
	}

	prog = argv[0];
	image = NULL;

	memset(&options, 0, sizeof(options));
	memset(&stats, 0, sizeof(stats));

	pt_config_init(&config);

	errcode = ptxed_init_decoder(&decoder);
	if (errcode < 0) {
		fprintf(stderr,
			"%s: error initializing decoder: %s.\n", prog,
			pt_errstr(pt_errcode(errcode)));
		goto err;
	}

#if defined(FEATURE_SIDEBAND)
	pt_sb_notify_error(decoder.session, ptxed_print_error, &options);
#endif

	image = pt_image_alloc(NULL);
	if (!image) {
		fprintf(stderr, "%s: failed to allocate image.\n", prog);
		goto err;
	}

	for (i = 1; i < argc;) {
		char *arg;

		arg = argv[i++];

		if (strcmp(arg, "--help") == 0 || strcmp(arg, "-h") == 0) {
			help(prog);
			goto out;
		}
		if (strcmp(arg, "--version") == 0) {
			pt_print_tool_version(prog);
			goto out;
		}
		if (strcmp(arg, "--pt") == 0) {
			if (argc <= i) {
				fprintf(stderr,
					"%s: --pt: missing argument.\n", prog);
				goto out;
			}
			arg = argv[i++];

			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: duplicate pt sources: %s.\n",
					prog, arg);
				goto err;
			}

			if (config.cpu.vendor) {
				errcode = pt_cpu_errata(&config.errata,
							&config.cpu);
				if (errcode < 0)
					printf("[0, 0: config error: %s]\n",
					       pt_errstr(pt_errcode(errcode)));
			}

			errcode = load_pt(&config, arg, prog);
			if (errcode < 0)
				goto err;

			errcode = alloc_decoder(&decoder, &config, image,
						&options, prog);
			if (errcode < 0)
				goto err;

			continue;
		}
		if (strcmp(arg, "--raw") == 0) {
			if (argc <= i) {
				fprintf(stderr,
					"%s: --raw: missing argument.\n", prog);
				goto out;
			}
			arg = argv[i++];

			errcode = load_raw(decoder.iscache, image, arg, prog);
			if (errcode < 0) {
				fprintf(stderr, "%s: --raw: failed to load "
					"'%s'.\n", prog, arg);
				goto err;
			}

			continue;
		}
#if defined(FEATURE_ELF)
		if (strcmp(arg, "--elf") == 0) {
			uint64_t base;

			if (argc <= i) {
				fprintf(stderr,
					"%s: --elf: missing argument.\n", prog);
				goto out;
			}
			arg = argv[i++];
			base = 0ull;
			errcode = extract_base(arg, &base);
			if (errcode < 0)
				goto err;

			errcode = load_elf(decoder.iscache, image, arg, base,
					   prog, options.track_image);
			if (errcode < 0)
				goto err;

			continue;
		}
#endif /* defined(FEATURE_ELF) */
		if (strcmp(arg, "--att") == 0) {
			options.att_format = 1;
			continue;
		}
		if (strcmp(arg, "--no-inst") == 0) {
			options.dont_print_insn = 1;
			continue;
		}
		if (strcmp(arg, "--quiet") == 0 || strcmp(arg, "-q") == 0) {
			options.quiet = 1;
			continue;
		}
		if (strcmp(arg, "--offset") == 0) {
			options.print_offset = 1;
			continue;
		}
		if (strcmp(arg, "--time") == 0) {
			options.print_time = 1;
			continue;
		}
		if (strcmp(arg, "--raw-insn") == 0) {
			options.print_raw_insn = 1;

			continue;
		}
		if (strcmp(arg, "--event:time") == 0) {
			options.print_event_time = 1;

			continue;
		}
		if (strcmp(arg, "--event:ip") == 0) {
			options.print_event_ip = 1;

			continue;
		}
		if (strcmp(arg, "--event:tick") == 0) {
			options.enable_tick_events = 1;

			continue;
		}
		if (strcmp(arg, "--filter:addr0_cfg") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!ptxed_addr_cfg(&config, 0, arg, argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr0_a") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr0_a, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr0_b") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr0_b, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr1_cfg") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!ptxed_addr_cfg(&config, 1, arg, argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr1_a") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr1_a, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr1_b") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr1_b, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr2_cfg") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!ptxed_addr_cfg(&config, 2, arg, argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr2_a") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr2_a, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr2_b") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr2_b, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr3_cfg") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!ptxed_addr_cfg(&config, 3, arg, argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr3_a") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr3_a, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--filter:addr3_b") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before --pt.\n",
					prog, arg);
				goto err;
			}

			if (!get_arg_uint64(&config.addr_filter.addr3_b, arg,
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--check") == 0) {
			options.check = 1;
			continue;
		}
		if (strcmp(arg, "--iscache-limit") == 0) {
			uint64_t limit;

			if (!get_arg_uint64(&limit, arg, argv[i++], prog))
				goto err;

			errcode = pt_iscache_set_limit(decoder.iscache, limit);
			if (errcode < 0) {
				fprintf(stderr, "%s: error setting iscache "
					"limit: %s.\n", prog,
					pt_errstr(pt_errcode(errcode)));
				goto err;
			}

			continue;
		}
		if (strcmp(arg, "--stat") == 0) {
			options.print_stats = 1;
			continue;
		}
		if (strcmp(arg, "--stat:insn") == 0) {
			options.print_stats = 1;
			stats.flags |= ptxed_stat_insn;
			continue;
		}
		if (strcmp(arg, "--stat:blocks") == 0) {
			options.print_stats = 1;
			stats.flags |= ptxed_stat_blocks;
			continue;
		}
#if defined(FEATURE_SIDEBAND)
		if ((strcmp(arg, "--sb:compact") == 0) ||
		    (strcmp(arg, "--sb") == 0)) {
			options.sb_dump_flags &= ~(uint32_t) ptsbp_verbose;
			options.sb_dump_flags |= (uint32_t) ptsbp_compact;
			continue;
		}
		if (strcmp(arg, "--sb:verbose") == 0) {
			options.sb_dump_flags &= ~(uint32_t) ptsbp_compact;
			options.sb_dump_flags |= (uint32_t) ptsbp_verbose;
			continue;
		}
		if (strcmp(arg, "--sb:filename") == 0) {
			options.sb_dump_flags |= (uint32_t) ptsbp_filename;
			continue;
		}
		if (strcmp(arg, "--sb:offset") == 0) {
			options.sb_dump_flags |= (uint32_t) ptsbp_file_offset;
			continue;
		}
		if (strcmp(arg, "--sb:time") == 0) {
			options.sb_dump_flags |= (uint32_t) ptsbp_tsc;
			continue;
		}
		if (strcmp(arg, "--sb:switch") == 0) {
			pt_sb_notify_switch(decoder.session, ptxed_print_switch,
					    &options);
			continue;
		}
		if (strcmp(arg, "--sb:warn") == 0) {
			options.print_sb_warnings = 1;
			continue;
		}
#if defined(FEATURE_PEVENT)
		if (strcmp(arg, "--pevent:primary") == 0) {
			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:primary: "
					"missing argument.\n", prog);
				goto err;
			}

			decoder.pevent.primary = 1;
			errcode = ptxed_sb_pevent(&decoder, arg, prog);
			if (errcode < 0)
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:secondary") == 0) {
			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:secondary: "
					"missing argument.\n", prog);
				goto err;
			}

			decoder.pevent.primary = 0;
			errcode = ptxed_sb_pevent(&decoder, arg, prog);
			if (errcode < 0)
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:sample-type") == 0) {
			if (!get_arg_uint64(&decoder.pevent.sample_type,
					    "--pevent:sample-type",
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:time-zero") == 0) {
			if (!get_arg_uint64(&decoder.pevent.time_zero,
					    "--pevent:time-zero",
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:time-shift") == 0) {
			if (!get_arg_uint16(&decoder.pevent.time_shift,
					    "--pevent:time-shift",
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:time-mult") == 0) {
			if (!get_arg_uint32(&decoder.pevent.time_mult,
					    "--pevent:time-mult",
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:tsc-offset") == 0) {
			if (!get_arg_uint64(&decoder.pevent.tsc_offset,
					    "--pevent:tsc-offset",
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:kernel-start") == 0) {
			if (!get_arg_uint64(&decoder.pevent.kernel_start,
					    "--pevent:kernel-start",
					    argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--pevent:sysroot") == 0) {
			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:sysroot: "
					"missing argument.\n", prog);
				goto err;
			}

			decoder.pevent.sysroot = arg;
			continue;
		}
#if defined(FEATURE_ELF)
		if (strcmp(arg, "--pevent:kcore") == 0) {
			struct pt_image *kernel;
			uint64_t base;

			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:kcore: "
					"missing argument.\n", prog);
				goto err;
			}

			base = 0ull;
			errcode = extract_base(arg, &base);
			if (errcode < 0)
				goto err;

			kernel = pt_sb_kernel_image(decoder.session);

			errcode = load_elf(decoder.iscache, kernel, arg, base,
					   prog, options.track_image);
			if (errcode < 0)
				goto err;

			continue;
		}
#endif /* defined(FEATURE_ELF) */
		if (strcmp(arg, "--pevent:vdso-x64") == 0) {
			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:vdso-x64: "
					"missing argument.\n", prog);
				goto err;
			}

			decoder.pevent.vdso_x64 = arg;
			continue;
		}
		if (strcmp(arg, "--pevent:vdso-x32") == 0) {
			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:vdso-x32: "
					"missing argument.\n", prog);
				goto err;
			}

			decoder.pevent.vdso_x32 = arg;
			continue;
		}
		if (strcmp(arg, "--pevent:vdso-ia32") == 0) {
			arg = argv[i++];
			if (!arg) {
				fprintf(stderr, "%s: --pevent:vdso-ia32: "
					"missing argument.\n", prog);
				goto err;
			}

			decoder.pevent.vdso_ia32 = arg;
			continue;
		}
#endif /* defined(FEATURE_PEVENT) */
#endif /* defined(FEATURE_SIDEBAND) */
		if (strcmp(arg, "--cpu") == 0) {
			/* override cpu information before the decoder
			 * is initialized.
			 */
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify cpu before the pt source file.\n",
					prog);
				goto err;
			}
			if (argc <= i) {
				fprintf(stderr,
					"%s: --cpu: missing argument.\n", prog);
				goto out;
			}
			arg = argv[i++];

			if (strcmp(arg, "auto") == 0) {
				errcode = pt_cpu_read(&config.cpu);
				if (errcode < 0) {
					fprintf(stderr,
						"%s: error reading cpu: %s.\n",
						prog,
						pt_errstr(pt_errcode(errcode)));
					return 1;
				}
				continue;
			}

			if (strcmp(arg, "none") == 0) {
				memset(&config.cpu, 0, sizeof(config.cpu));
				continue;
			}

			errcode = pt_cpu_parse(&config.cpu, arg);
			if (errcode < 0) {
				fprintf(stderr,
					"%s: cpu must be specified as f/m[/s]\n",
					prog);
				goto err;
			}
			continue;
		}
		if (strcmp(arg, "--mtc-freq") == 0) {
			if (!get_arg_uint8(&config.mtc_freq, "--mtc-freq",
					   argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--nom-freq") == 0) {
			if (!get_arg_uint8(&config.nom_freq, "--nom-freq",
					   argv[i++], prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--cpuid-0x15.eax") == 0) {
			if (!get_arg_uint32(&config.cpuid_0x15_eax,
					    "--cpuid-0x15.eax", argv[i++],
					    prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--cpuid-0x15.ebx") == 0) {
			if (!get_arg_uint32(&config.cpuid_0x15_ebx,
					    "--cpuid-0x15.ebx", argv[i++],
					    prog))
				goto err;

			continue;
		}
		if (strcmp(arg, "--verbose") == 0 || strcmp(arg, "-v") == 0) {
			options.track_image = 1;
			continue;
		}

		if (strcmp(arg, "--insn-decoder") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before the pt "
					"source file.\n", arg, prog);
				goto err;
			}

			decoder.type = pdt_insn_decoder;
			continue;
		}

		if (strcmp(arg, "--block-decoder") == 0) {
			if (ptxed_have_decoder(&decoder)) {
				fprintf(stderr,
					"%s: please specify %s before the pt "
					"source file.\n", arg, prog);
				goto err;
			}

			decoder.type = pdt_block_decoder;
			continue;
		}

		if (strcmp(arg, "--block:show-blocks") == 0) {
			options.track_blocks = 1;
			continue;
		}

		if (strcmp(arg, "--block:end-on-call") == 0) {
			config.flags.variant.block.end_on_call = 1;
			continue;
		}

		if (strcmp(arg, "--block:end-on-jump") == 0) {
			config.flags.variant.block.end_on_jump = 1;
			continue;
		}

		fprintf(stderr, "%s: unknown option: %s.\n", prog, arg);
		goto err;
	}

	if (!ptxed_have_decoder(&decoder)) {
		fprintf(stderr, "%s: no pt file.\n", prog);
		goto err;
	}

	xed_tables_init();

	/* If we didn't select any statistics, select them all depending on the
	 * decoder type.
	 */
	if (options.print_stats && !stats.flags) {
		stats.flags |= ptxed_stat_insn;

		if (decoder.type == pdt_block_decoder)
			stats.flags |= ptxed_stat_blocks;
	}

#if defined(FEATURE_SIDEBAND)
	errcode = pt_sb_init_decoders(decoder.session);
	if (errcode < 0) {
		fprintf(stderr,
			"%s: error initializing sideband decoders: %s.\n",
			prog, pt_errstr(pt_errcode(errcode)));
		goto err;
	}
#endif /* defined(FEATURE_SIDEBAND) */

	decode(&decoder, &options, options.print_stats ? &stats : NULL);

	if (options.print_stats)
		print_stats(&stats);

out:
	ptxed_free_decoder(&decoder);
	pt_image_free(image);
	free(config.begin);
	return 0;

err:
	ptxed_free_decoder(&decoder);
	pt_image_free(image);
	free(config.begin);
	return 1;
}
