/*
 * This is modified version of Simple PT dumper by Andi Kleen
 * https://github.com/andikleen/simple-pt
 */

/*
 * Copyright (c) 2015, Intel Corporation
 * Author: Andi Kleen
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * 2. 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.
 *
 * 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 HOLDER 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.
*/

#define _GNU_SOURCE 1
#include <sys/mman.h>
#include <sys/fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>

#include "defs.h"
#include "map.h"

#define BIT(x) (1U << (x))

typedef unsigned long long u64;

static FILE *out_fp;

static char psb[16] = {
	0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82,
	0x02, 0x82, 0x02, 0x82, 0x02, 0x82, 0x02, 0x82
};

#define LEFT(x) ((end - p) >= (x))

/* Caller must have checked length */
static u64 get_ip_val(unsigned char **pp, unsigned char *end, int len, uint64_t *last_ip)
{
	unsigned char *p = *pp;
	u64 v = *last_ip;
	int i;
	unsigned shift = 0;

	if (len == 0) {
		*last_ip = 0;
		return 0; /* out of context */
	}
	if (len < 4) {
		if (!LEFT(len)) {
			*last_ip = 0;
			return 0; /* XXX error */
		}
		for (i = 0; i < len; i++, shift += 16, p += 2) {
			uint64_t b = *(uint16_t *)p;
			v = (v & ~(0xffffULL << shift)) | (b << shift);
		}
		v = ((int64_t)(v << (64 - 48))) >> (64 - 48); /* sign extension */
	} else {
		return 0; /* XXX error */
	}
	*pp = p;
	*last_ip = v;
	return v;
}

static char *get_ip_symbol(u64 ip, char *buf, int bufsize)
{
	struct syment *sm;
	ulong offset;

	sm = value_search(ip, &offset);

	if (sm) {
		if (!offset)
			sprintf(buf, "%s", sm->name);
		else
			sprintf(buf, "%s+%lu", sm->name, offset);

		return buf;
	}

	return NULL;
}

/* Caller must have checked length */
static u64 get_val(unsigned char **pp, int len)
{
	unsigned char *p = *pp;
	u64 v = 0;
	int i;
	unsigned shift = 0;

	for (i = 0; i < len; i++, shift += 8)
		v |= ((uint64_t)(*p++)) << shift;
	*pp = p;
	return v;
}

static void print_unknown(unsigned char *p, unsigned char *end, unsigned char *map)
{
	fprintf(out_fp, "unknown packet: ");
	unsigned len = end - p;
	int i;
	if (len > 16)
		len = 16;
	for (i = 0; i < len; i++)
		fprintf(out_fp, "%02x ", p[i]);
	fprintf(out_fp, "\n");
}

static void print_tnt_byte(unsigned char v, int max)
{
	int i;
	for (i = max - 1; i >= 0; i--)
		if (v & BIT(i))
			fprintf(out_fp, "T");
		else
			fprintf(out_fp, "N");
}

static void print_tnt_stop(unsigned char v)
{
	int j;
	for (j = 7; j >= 0; j--) {
		if (v & BIT(j))
			break;
	}
	print_tnt_byte(v, j);
}

static void print_multi_tnt(unsigned char *p, int len)
{
	int i;

	for (i = len - 1; i >= 0 && p[i] == 0; i--)
		;
	if (i >= 0) {
		print_tnt_stop(p[i]);
		i--;
	} else {
		fprintf(out_fp, "??? no stop bit");
		return;
	}
	for (; i >= 0; i--)
		print_tnt_byte(p[i], 8);
}

void decode_buffer(unsigned char *map, size_t len)
{
	unsigned char *end = map + len;
	unsigned char *p;
	size_t skipped = 0;
	size_t overflow = 0;
	uint64_t last_ip = 0;
	u64 ip;
	char buf[64];

	for (p = map; p < end; ) {
		unsigned char *prev = p;
		/* look for PSB */
		p = memmem(p, end - p, psb, 16);
		if (!p) {
			p = end;
			break;
		}
		skipped += p - prev;
		while (p < end) {
			fprintf(out_fp, "%lx\t", p - map);

			if (*p == 2 && LEFT(2)) {
				if (p[1] == 0xa3 && LEFT(8)) { /* long TNT */
					fprintf(out_fp, "tnt64 ");
					print_multi_tnt(p + 2, 6);
					fprintf(out_fp, "\n");
					p += 8;
					continue;
				}
				if (p[1] == 0x43 && LEFT(8)) { /* PIP */
					p += 2;
					fprintf(out_fp, "pip\t%llx\n", (get_val(&p, 6) >> 1) << 5);
					continue;
				}
				if (p[1] == 3 && LEFT(4) && p[3] == 0) { /* CBR */
					fprintf(out_fp, "cbr\t%u\n", p[2]);
					p += 4;
					continue;
				}
				if (p[1] == 0b10000011) {
					fprintf(out_fp, "tracestop\n");
					p += 2;
					continue;
				}
				if (p[1] == 0b11110011 && LEFT(8)) { /* OVF */
					fprintf(out_fp, "ovf\n");
					p += 8;
					overflow++;
					continue;
				}
				if (p[1] == 0x82 && LEFT(16) && !memcmp(p, psb, 16)) { /* PSB */
					fprintf(out_fp, "psb\n");
					p += 16;
					continue;
				}
				if (p[1] == 0b100011) { /* PSBEND */
					fprintf(out_fp, "psbend\n");
					p += 2;
					continue;
				}
				/* MNT */
				if (p[1] == 0b11000011 && LEFT(11) && p[2] == 0b10001000) {
					fprintf(out_fp, "mnt\t%llx\n",
						p[3] |
						((u64)p[4] << 8) |
						((u64)p[5] << 16) |
						((u64)p[6] << 24) |
						((u64)p[7] << 32) |
						((u64)p[8] << 40) |
						((u64)p[9] << 48) |
						((u64)p[10] << 56));
					p += 10;
					continue;
				}
				/* TMA */
				if (p[1] == 0b01110011 && LEFT(7)) {
					fprintf(out_fp, "tma\tctc=%u fc=%u\n",
							p[2] | (p[3] << 8),
							p[5] | ((p[6] & 1) << 8));
					p += 7;
					continue;
				}
				/* VMCS */
				if (p[1] == 0b11001000 && LEFT(7)) {
					fprintf(out_fp, "vmcs\t%llx\n",
						((u64)p[2] << 12) |
						((u64)p[3] << 20) |
						((u64)p[4] << 28) |
						((u64)p[5] << 36) |
						((u64)p[6] << 44));
					continue;
				}
			}

			if ((*p & BIT(0)) == 0) {
				if (*p == 0) { /* PAD */
					fprintf(out_fp, "pad\n");
					p++;
					continue;
				}
				fprintf(out_fp, "tnt8 ");
				print_tnt_stop(*p >> 1);
				fprintf(out_fp, "\n");
				p++;
				continue;
			}

			char *name = NULL;
			switch (*p & 0x1f) {
			case 0xd:
				name = "tip";
				break;
			case 0x11:
				name = "tip.pge";
				break;
			case 0x1:
				name = "tip.pgd";
				break;
			case 0x1d:
				name = "fup";
				break;
			}
			if (name) {
				int ipl = *p >> 5;
				p++;
				ip = get_ip_val(&p, end, ipl, &last_ip);
				fprintf(out_fp, "%s\t%d: %llx", name, ipl, ip);
				if (get_ip_symbol(ip, buf, sizeof(buf)))
					fprintf(out_fp, " <%s>\n", buf);
				else
					fprintf(out_fp, "\n");
				continue;
			}
			if (*p == 0x99 && LEFT(2)) { /* MODE */
				if ((p[1] >> 5) == 1) {
					fprintf(out_fp, "mode.tsx");
					if (p[1] & BIT(0))
						fprintf(out_fp, " intx");
					if (p[1] & BIT(1))
						fprintf(out_fp, " txabort");
					fprintf(out_fp, "\n");
					p += 2;
					continue;
				} else if ((p[1] >> 5) == 0) {
					fprintf(out_fp, "mode.exec");
					fprintf(out_fp, " lma=%d", (p[1] & BIT(0)));
					fprintf(out_fp, " cs.d=%d", !!(p[1] & BIT(1)));
					fprintf(out_fp, "\n");
					p += 2;
					continue;
				}
			}

			if (*p == 0x19 && LEFT(8)) {  /* TSC */
				p++;
				fprintf(out_fp, "tsc\t%llu\n", get_val(&p, 7));
				continue;
			}
			if (*p == 0b01011001 && LEFT(2)) { /* MTC */
				fprintf(out_fp, "mtc\t%u\n", p[1]);
				p += 2;
				continue;
			}
			if ((*p & 3) == 3) { /* CYC */
				u64 cyc = *p >> 2;
				unsigned shift = 4;
				if ((*p & 4) && LEFT(1)) {
					do {
						p++;
						cyc |= (*p >> 1) << shift;
						shift += 7;
					} while ((*p & 1) && LEFT(1));
				}
				fprintf(out_fp, "cyc\t%llu\n", cyc);
				p++;
				continue;
			}

			print_unknown(p, end, map);
			break;
		}
	}
	if (p < end || skipped)
		fprintf(out_fp, "%lu bytes undecoded\n", (end - p) + skipped);
	if (overflow)
		fprintf(out_fp, "%lu overflows\n", overflow);
}

int fastdecode(char *in, char *out)
{
	size_t len;
	unsigned char *map = mapfile(in, &len);
	if (!map) {
		fprintf(fp, "Cannot map file: %s\n", in);
		return FALSE;
	}

	if ((out_fp = fopen(out, "w")) == NULL) {
		fprintf(fp, "Cannot open file: %s\n", out);
		return FALSE;
	}

	decode_buffer(map, len);
	unmapfile(map, len);
	fclose(out_fp);

	return TRUE;
}

