aboutsummaryrefslogtreecommitdiff
path: root/deps/lightrec/disassembler.c
blob: 06fcec9d69fa0ba76e34d5d20cd4b683df07f8bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
/*
 * Copyright (C) 2014-2020 Paul Cercueil <paul@crapouillou.net>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

#include "config.h"

#if ENABLE_DISASSEMBLER
#include <dis-asm.h>
#endif
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "debug.h"
#include "disassembler.h"
#include "lightrec-private.h"
#include "memmanager.h"

static bool is_unconditional_jump(const struct opcode *op)
{
	switch (op->i.op) {
	case OP_SPECIAL:
		return op->r.op == OP_SPECIAL_JR || op->r.op == OP_SPECIAL_JALR;
	case OP_J:
	case OP_JAL:
		return true;
	case OP_BEQ:
	case OP_BLEZ:
		return op->i.rs == op->i.rt;
	case OP_REGIMM:
		return (op->r.rt == OP_REGIMM_BGEZ ||
			op->r.rt == OP_REGIMM_BGEZAL) && op->i.rs == 0;
	default:
		return false;
	}
}

static bool is_syscall(const struct opcode *op)
{
	return (op->i.op == OP_SPECIAL && (op->r.op == OP_SPECIAL_SYSCALL ||
					   op->r.op == OP_SPECIAL_BREAK)) ||
		(op->i.op == OP_CP0 && (op->r.rs == OP_CP0_MTC0 ||
					op->r.rs == OP_CP0_CTC0) &&
		 (op->r.rd == 12 || op->r.rd == 13));
}

void lightrec_free_opcode_list(struct lightrec_state *state, struct opcode *list)
{
	struct opcode *next;

	while (list) {
		next = list->next;
		lightrec_free(state, MEM_FOR_IR, sizeof(*list), list);
		list = next;
	}
}

struct opcode * lightrec_disassemble(struct lightrec_state *state,
				     const u32 *src, unsigned int *len)
{
	struct opcode *head = NULL;
	bool stop_next = false;
	struct opcode *curr, *last;
	unsigned int i;

	for (i = 0, last = NULL; ; i++, last = curr) {
		curr = lightrec_calloc(state, MEM_FOR_IR, sizeof(*curr));
		if (!curr) {
			pr_err("Unable to allocate memory\n");
			lightrec_free_opcode_list(state, head);
			return NULL;
		}

		if (!last)
			head = curr;
		else
			last->next = curr;

		/* TODO: Take care of endianness */
		curr->opcode = LE32TOH(*src++);
		curr->offset = i;

		/* NOTE: The block disassembly ends after the opcode that
		 * follows an unconditional jump (delay slot) */
		if (stop_next || is_syscall(curr))
			break;
		else if (is_unconditional_jump(curr))
			stop_next = true;
	}

	if (len)
		*len = (i + 1) * sizeof(u32);

	return head;
}

unsigned int lightrec_cycles_of_opcode(union code code)
{
	switch (code.i.op) {
	case OP_META_REG_UNLOAD:
	case OP_META_SYNC:
		return 0;
	default:
		return 2;
	}
}

#if ENABLE_DISASSEMBLER
void lightrec_print_disassembly(const struct block *block,
				const u32 *code, unsigned int length)
{
	struct disassemble_info info;
	unsigned int i;

	memset(&info, 0, sizeof(info));
	init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf);

	info.buffer = (bfd_byte *) code;
	info.buffer_vma = (bfd_vma)(uintptr_t) code;
	info.buffer_length = length;
	info.flavour = bfd_target_unknown_flavour;
	info.arch = bfd_arch_mips;
	info.mach = bfd_mach_mips3000;
	disassemble_init_for_target(&info);

	for (i = 0; i < length; i += 4) {
		void print_insn_little_mips(bfd_vma, struct disassemble_info *);
		putc('\t', stdout);
		print_insn_little_mips((bfd_vma)(uintptr_t) code++, &info);
		putc('\n', stdout);
	}
}
#endif