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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/* SPDX-FileCopyrightText: © 2022-2024 Decompollaborate */
/* SPDX-License-Identifier: MIT */
#include "instructions/RabbitizerInstruction.h"
#include <assert.h>
#include <string.h>
#include <stdio.h>
#include "common/RabbitizerConfig.h"
size_t RabbitizerInstruction_getSizeForBufferInstrDisasm(const RabbitizerInstruction *self, size_t immOverrideLength,
int extraLJust) {
size_t totalSize = 0;
size_t opcodeNameLength;
opcodeNameLength = strlen(RabbitizerInstrId_getOpcodeName(self->uniqueId));
totalSize += opcodeNameLength;
totalSize += RabbitizerInstrSuffix_getSizeForBuffer(self, self->descriptor->instrSuffix);
if (self->descriptor->operands[0] == RAB_OPERAND_ALL_INVALID) {
// There are no operands
return totalSize;
}
if (RabbitizerConfig_Cfg.misc.opcodeLJust > 0) {
totalSize += RabbitizerConfig_Cfg.misc.opcodeLJust;
}
if (extraLJust > 0) {
totalSize += extraLJust;
}
totalSize++;
totalSize += RabbitizerInstruction_getSizeForBufferOperandsDisasm(self, immOverrideLength);
return totalSize;
}
size_t RabbitizerInstruction_disassembleInstruction(const RabbitizerInstruction *self, char *dst,
const char *immOverride, size_t immOverrideLength, int extraLJust) {
size_t totalSize = 0;
const char *opcodeName = RabbitizerInstrId_getOpcodeName(self->uniqueId);
RABUTILS_BUFFER_CPY(dst, totalSize, opcodeName);
RABUTILS_BUFFER_ADVANCE(dst, totalSize,
RabbitizerInstrSuffix_processSuffix(self, dst, self->descriptor->instrSuffix));
if (self->descriptor->operands[0] == RAB_OPERAND_ALL_INVALID) {
// There are no operands
*dst = '\0';
return totalSize;
}
RABUTILS_BUFFER_ADVANCE(
dst, totalSize,
RabbitizerUtils_CharFill(dst, RabbitizerConfig_Cfg.misc.opcodeLJust + extraLJust - totalSize, ' '));
RABUTILS_BUFFER_WRITE_CHAR(dst, totalSize, ' ');
RABUTILS_BUFFER_ADVANCE(dst, totalSize,
RabbitizerInstruction_disassembleOperands(self, dst, immOverride, immOverrideLength));
*dst = '\0';
return totalSize;
}
size_t RabbitizerInstruction_getSizeForBufferDataDisasm(UNUSED const RabbitizerInstruction *self, int extraLJust) {
size_t totalSize = 0;
int tempLJust;
totalSize += strlen(".word");
tempLJust = RabbitizerConfig_Cfg.misc.opcodeLJust + extraLJust;
tempLJust = RAB_MAX(tempLJust, 0);
totalSize += tempLJust;
totalSize += 11;
return totalSize;
}
size_t RabbitizerInstruction_disassembleAsData(const RabbitizerInstruction *self, char *dst, int extraLJust) {
size_t totalSize = 0;
RABUTILS_BUFFER_CPY(dst, totalSize, ".word");
RABUTILS_BUFFER_ADVANCE(
dst, totalSize,
RabbitizerUtils_CharFill(dst, RabbitizerConfig_Cfg.misc.opcodeLJust + extraLJust - totalSize, ' '));
RABUTILS_BUFFER_SPRINTF(dst, totalSize, " 0x%08X", RabbitizerInstruction_getRaw(self));
return totalSize;
}
bool RabbitizerInstruction_mustDisasmAsData(const RabbitizerInstruction *self) {
switch (self->uniqueId) {
case RABBITIZER_INSTR_ID_cpu_break:
if (RabbitizerConfig_Cfg.toolchainTweaks.sn64DivFix) {
return true;
}
break;
case RABBITIZER_INSTR_ID_cpu_trunc_w_s:
case RABBITIZER_INSTR_ID_cpu_cvt_w_s:
if (self->category == RABBITIZER_INSTRCAT_R5900) {
switch (RAB_INSTR_FLAGS_GET_r5900DisasmAsData(self)) {
case RAB_TRINARY_VAL_TRUE:
return true;
case RAB_TRINARY_VAL_FALSE:
break;
case RAB_TRINARY_VAL_NONE:
if (RabbitizerConfig_Cfg.toolchainTweaks.gnuMode) {
/**
* Due to the R5900's FPU being non properly complaint, the instruction cvt.w.s always
* behaves as trunc.w.s because EE can only do round-to-zero.
*
* Assemblers like GAS workaround this issue by decoding cvt.w.s as trunc.w.s, but other
* assemblers just use trunc.w.s and cvt.w.s as-is.
*
* Here's some reading about the binutils rationale:
* - https://sourceware.org/legacy-ml/binutils/2012-11/msg00360.html
* - https://sourceware.org/pipermail/binutils/2013-January/079863.html
*
* Because of this, building with GAS with the -march=r5900 flag produces:
* - trunc.w.s is built as the cvt.w.s instruction.
* - cvt.w.s errors complaining as not being supported by the processor.
*
* To ensure the produced disassembly will still match when built with GAS, we decode this
* two instructions as .word
*/
return true;
}
break;
}
}
break;
case RABBITIZER_INSTR_ID_r4000allegrex_lvl_q:
case RABBITIZER_INSTR_ID_r4000allegrex_lvr_q:
if (true) {
/*
* These two instructions were removed from the specification
* because they do not work properly due to a hardware bug.
* This is mentioned in the VFPU Users manual, section 2.4, page 16.
* https://github.com/Decompollaborate/rabbitizer/files/11356333/VFPU-Users_Manual-English.pdf
* and the Technical Notes 200503-10 on the PSP Developer Network.
*
* Because of this early errata, compiler/assembler support
* is a bit wonky.
* Different compilers/versions either added or removed support
* at different points in time, which is kinda problematic for
* us since we can't have a one single way to express this
* instruction in generated assembly.
*
* This forces us to emit this instruction as a `.word`
* directive to avoid users not being able to assemble code
* using this instruction.
*
* Currently there's no way to change this behavior.
* This may change in the future if there's enough interest.
*
* Btw, the hardware bug happens because this instruction
* shares representation with `ldc1`, so executing these
* bits performs the effect of both `lvl.q`/`lvr.q` and `ldc1`.
*
* For more discussion see
* https://github.com/Decompollaborate/rabbitizer/issues/85
*/
return true;
}
break;
case RABBITIZER_INSTR_ID_r5900_vclipw:
switch (RAB_INSTR_FLAGS_GET_r5900DisasmAsData(self)) {
case RAB_TRINARY_VAL_TRUE:
return true;
case RAB_TRINARY_VAL_FALSE:
break;
case RAB_TRINARY_VAL_NONE:
if (RabbitizerConfig_Cfg.toolchainTweaks.gnuMode) {
// The vclipw instruction has variants that are undocumented (vclipw.xy, vclipw.z) and don't
// assemble in gnu as
return true;
}
break;
}
break;
case RABBITIZER_INSTR_ID_r5900_vsqrt:
switch (RAB_INSTR_FLAGS_GET_r5900DisasmAsData(self)) {
case RAB_TRINARY_VAL_TRUE:
return true;
case RAB_TRINARY_VAL_FALSE:
break;
case RAB_TRINARY_VAL_NONE:
if (RabbitizerConfig_Cfg.toolchainTweaks.gnuMode) {
// The vclipw instruction seems to be representable in multiple ways, and we only disassemble
// one of them
return true;
}
break;
}
break;
default:
break;
}
if (!RabbitizerInstruction_isValid(self)) {
return true;
}
return false;
}
size_t RabbitizerInstruction_getSizeForBuffer(const RabbitizerInstruction *self, size_t immOverrideLength,
int extraLJust) {
if (!RabbitizerInstruction_isValid(self) || RabbitizerInstruction_mustDisasmAsData(self)) {
size_t totalSize = RabbitizerInstruction_getSizeForBufferDataDisasm(self, extraLJust);
if (RabbitizerConfig_Cfg.misc.unknownInstrComment) {
totalSize += 40;
totalSize += 3; // " # "
totalSize += RabbitizerInstruction_getSizeForBufferInstrDisasm(self, immOverrideLength, extraLJust);
totalSize += strlen(" # 00000000"); // " # %08X"
totalSize += strlen(" <InstrIdType: %s>");
totalSize += strlen(RabInstrIdType_getName(self->instrIdType));
}
return totalSize;
}
return RabbitizerInstruction_getSizeForBufferInstrDisasm(self, immOverrideLength, extraLJust);
}
size_t RabbitizerInstruction_disassemble(const RabbitizerInstruction *self, char *dst, const char *immOverride,
size_t immOverrideLength, int extraLJust) {
assert(dst != NULL);
if (!RabbitizerInstruction_isValid(self) || RabbitizerInstruction_mustDisasmAsData(self)) {
size_t totalSize = 0;
RABUTILS_BUFFER_ADVANCE(dst, totalSize, RabbitizerInstruction_disassembleAsData(self, dst, extraLJust));
if (RabbitizerConfig_Cfg.misc.unknownInstrComment) {
uint32_t validBits;
RABUTILS_BUFFER_ADVANCE(dst, totalSize, RabbitizerUtils_CharFill(dst, 40 - totalSize, ' '));
RABUTILS_BUFFER_WRITE_CHAR(dst, totalSize, ' ');
RABUTILS_BUFFER_WRITE_CHAR(dst, totalSize, '#');
RABUTILS_BUFFER_WRITE_CHAR(dst, totalSize, ' ');
RABUTILS_BUFFER_ADVANCE(
dst, totalSize,
RabbitizerInstruction_disassembleInstruction(self, dst, immOverride, immOverrideLength, extraLJust));
validBits = RabbitizerInstruction_getValidBits(self);
RABUTILS_BUFFER_SPRINTF(dst, totalSize, " # %08X", ((~validBits) & self->word));
RABUTILS_BUFFER_SPRINTF(dst, totalSize, " <InstrIdType: %s>", RabInstrIdType_getName(self->instrIdType));
}
return totalSize;
}
return RabbitizerInstruction_disassembleInstruction(self, dst, immOverride, immOverrideLength, extraLJust);
}