Implement disassembler
This commit is contained in:
parent
9cc01bb6f7
commit
f5a84fd4d0
10
makefile
10
makefile
|
@ -29,7 +29,7 @@ core:
|
||||||
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
||||||
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
||||||
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
||||||
-D VB_DIRECT_READ=testRead
|
-D VB_DIRECT_READ=testRead -D VB_DIRECT_LINK=testLink
|
||||||
# Clang generic
|
# Clang generic
|
||||||
@emcc core/vb.c -I core -c -o /dev/null -O3 \
|
@emcc core/vb.c -I core -c -o /dev/null -O3 \
|
||||||
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
||||||
|
@ -40,7 +40,13 @@ core:
|
||||||
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
-D VB_DIRECT_EXCEPTION=textException -D VB_DIRECT_EXECUTE=textExecute \
|
||||||
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
-D VB_DIRECT_FETCH=testFetch -D VB_DIRECT_FRAME=testFrame \
|
||||||
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
-D VB_DIRECT_WRITE=testWrite -D VB_DIRECT_SAMPLES=testSamples \
|
||||||
-D VB_DIRECT_READ=testRead
|
-D VB_DIRECT_READ=testRead -D VB_DIRECT_LINK=testLink
|
||||||
|
|
||||||
|
.PHONY: util
|
||||||
|
util:
|
||||||
|
# GCC generic
|
||||||
|
@gcc util/vbu.c -I core -I util -c -o /dev/null -O3 \
|
||||||
|
-Werror -std=c90 -Wall -Wextra -Wpedantic -fno-strict-aliasing
|
||||||
|
|
||||||
.PHONY: wasm
|
.PHONY: wasm
|
||||||
wasm:
|
wasm:
|
||||||
|
|
|
@ -0,0 +1,691 @@
|
||||||
|
/* This file is included into vb.c and cannot be compiled on its own. */
|
||||||
|
#ifdef VBUAPI
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
|
/* Operand types */
|
||||||
|
#define DASM_BCOND 0
|
||||||
|
#define DASM_DISP9 1
|
||||||
|
#define DASM_DISP26 2
|
||||||
|
#define DASM_IMM5S 3
|
||||||
|
#define DASM_IMM5U 4
|
||||||
|
#define DASM_IMM16S 5
|
||||||
|
#define DASM_IMM16U 6
|
||||||
|
#define DASM_JMP 7
|
||||||
|
#define DASM_MEMORY 8
|
||||||
|
#define DASM_REG1 9
|
||||||
|
#define DASM_REG2 10
|
||||||
|
#define DASM_SETF 11
|
||||||
|
#define DASM_SYSTEM 12
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************** Lookup Data ********************************/
|
||||||
|
|
||||||
|
/* Instruction descriptor */
|
||||||
|
typedef struct {
|
||||||
|
char mnemonic[8]; /* Mnemonic, uppercase */
|
||||||
|
uint8_t operandsLength; /* Number of operands */
|
||||||
|
uint8_t operands[3]; /* Operand types */
|
||||||
|
} OpDef;
|
||||||
|
|
||||||
|
/* Instruction descriptors */
|
||||||
|
static const OpDef OPDEFS[] = {
|
||||||
|
{ "MOV" , 2, { DASM_REG1, DASM_REG2 } }, /* 000000 */
|
||||||
|
{ "ADD" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SUB" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "CMP" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SHL" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SHR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "JMP" , 1, { DASM_JMP } },
|
||||||
|
{ "SAR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MUL" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "DIV" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MULU" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "DIVU" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "OR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "AND" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "XOR" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "NOT" , 1, { DASM_REG1 } },
|
||||||
|
|
||||||
|
{ "MOV" , 2, { DASM_IMM5S, DASM_REG2 } }, /* 010000 */
|
||||||
|
{ "ADD" , 2, { DASM_IMM5S, DASM_REG2 } },
|
||||||
|
{ "" , 0, { 0 } }, /* SETF */
|
||||||
|
{ "CMP" , 2, { DASM_IMM5S, DASM_REG2 } },
|
||||||
|
{ "SHL" , 2, { DASM_IMM5U, DASM_REG2 } },
|
||||||
|
{ "SHR" , 2, { DASM_IMM5U, DASM_REG2 } },
|
||||||
|
{ "CLI" , 0, { 0 } },
|
||||||
|
{ "SAR" , 2, { DASM_IMM5U, DASM_REG2 } },
|
||||||
|
{ "TRAP" , 1, { DASM_IMM5U } },
|
||||||
|
{ "RETI" , 0, { 0 } },
|
||||||
|
{ "HALT" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "LDSR" , 2, { DASM_REG2, DASM_SYSTEM } },
|
||||||
|
{ "STSR" , 2, { DASM_SYSTEM, DASM_REG2 } },
|
||||||
|
{ "SEI" , 0, { 0 } },
|
||||||
|
{ "" , 0, { 0 } }, /* Bit string */
|
||||||
|
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */ /* 100000 */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "" , 0, { 0 } }, /* BCOND */
|
||||||
|
{ "MOVEA", 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "ADDI" , 3, { DASM_IMM16S, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "JR" , 1, { DASM_DISP26 } },
|
||||||
|
{ "JAL" , 1, { DASM_DISP26 } },
|
||||||
|
{ "ORI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "ANDI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "XORI" , 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MOVHI", 3, { DASM_IMM16U, DASM_REG1, DASM_REG2 } },
|
||||||
|
|
||||||
|
{ "LD.B" , 2, { DASM_MEMORY, DASM_REG2 } }, /* 110000 */
|
||||||
|
{ "LD.H" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "LD.W" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "ST.B" , 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "ST.H" , 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "ST.W" , 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "IN.B" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "IN.H" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "CAXI" , 0, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "IN.W" , 2, { DASM_MEMORY, DASM_REG2 } },
|
||||||
|
{ "OUT.B", 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "OUT.H", 2, { DASM_REG2, DASM_MEMORY } },
|
||||||
|
{ "" , 0, { 0 } }, /* Floating-point/Nintendo */
|
||||||
|
{ "OUT.W", 2, { DASM_REG2, DASM_MEMORY } }
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Bit string instruction descriptors */
|
||||||
|
static const OpDef OPDEFS_BITSTRING[] = {
|
||||||
|
{ "SCH0BSU", 0, { 0 } }, /* 00000 */
|
||||||
|
{ "SCH0BSD", 0, { 0 } },
|
||||||
|
{ "SCH1BSU", 0, { 0 } },
|
||||||
|
{ "SCH1BSD", 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "ORBSU" , 0, { 0 } },
|
||||||
|
{ "ANDBSU" , 0, { 0 } },
|
||||||
|
{ "XORBSU" , 0, { 0 } },
|
||||||
|
{ "MOVBSU" , 0, { 0 } },
|
||||||
|
{ "ORNBSU" , 0, { 0 } },
|
||||||
|
{ "ANDNBSU", 0, { 0 } },
|
||||||
|
{ "XORNBSU", 0, { 0 } },
|
||||||
|
{ "NOTBSU" , 0, { 0 } }
|
||||||
|
/* +16 invalid opcodes */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Floating-point/Nintendo instruction descriptors */
|
||||||
|
static const OpDef OPDEFS_FLOATENDO[] = {
|
||||||
|
{ "CMPF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "---" , 0, { 0 } },
|
||||||
|
{ "CVT.WS" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "CVT.SW" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "ADDF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "SUBF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MULF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "DIVF.S" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "XB" , 1, { DASM_REG2 } },
|
||||||
|
{ "XH" , 1, { DASM_REG2 } },
|
||||||
|
{ "REV" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "TRNC.SW", 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
{ "MPYHW" , 2, { DASM_REG1, DASM_REG2 } },
|
||||||
|
/* +51 invalid opcodes */
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Invalid instruction descriptors */
|
||||||
|
static const OpDef OPDEF_ILLEGAL = { "---", 0, { 0 } };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Select the display text for a condition */
|
||||||
|
static char* dasmCondition(VBU_DasmConfig *config, int cond) {
|
||||||
|
switch (cond) {
|
||||||
|
case 0: return "V" ;
|
||||||
|
case 1: return config->conditionCL == VBU_C ? "C" : "L";
|
||||||
|
case 2: return config->conditionEZ == VBU_E ? "E" : "Z";
|
||||||
|
case 3: return "NH";
|
||||||
|
case 4: return "N" ;
|
||||||
|
case 5: return "T" ;
|
||||||
|
case 6: return "LT";
|
||||||
|
case 7: return "LE";
|
||||||
|
case 8: return "NV";
|
||||||
|
case 9: return config->conditionCL == VBU_C ? "NC" : "NL";
|
||||||
|
case 10: return config->conditionEZ == VBU_E ? "NE" : "NZ";
|
||||||
|
case 11: return "H" ;
|
||||||
|
case 12: return "P" ;
|
||||||
|
case 13: return "F" ;
|
||||||
|
case 14: return "GE";
|
||||||
|
}
|
||||||
|
return "GT";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a number as hexadecimal */
|
||||||
|
static void dasmToHex(char *dest, VBU_DasmConfig *config,
|
||||||
|
int minDigits, int32_t value) {
|
||||||
|
char format[9];
|
||||||
|
char *prefix = "";
|
||||||
|
char *suffix = "";
|
||||||
|
switch (config->hexNotation) {
|
||||||
|
case VBU_0X : prefix = "0x"; break;
|
||||||
|
case VBU_DOLLAR: prefix = "$" ; break;
|
||||||
|
case VBU_H : suffix = "h" ; break;
|
||||||
|
}
|
||||||
|
sprintf(format, "%%s%%0%d%s%%s", minDigits,
|
||||||
|
config->hexCase == VBU_LOWER ? "x" : "X");
|
||||||
|
sprintf(dest, format, prefix, value, suffix);
|
||||||
|
if (config->hexNotation != VBU_H || *dest <= '9')
|
||||||
|
return;
|
||||||
|
*dest++ = '0';
|
||||||
|
sprintf(dest, format, prefix, value, suffix);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Convert a string to lowercase */
|
||||||
|
static void dasmToLower(char *str) {
|
||||||
|
for (; *str; str++) {
|
||||||
|
if (*str >= 'A' && *str <= 'Z')
|
||||||
|
*str += 'a' - 'A';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a program register */
|
||||||
|
static void dasmProgram(char *dest, VBU_DasmConfig *config, int id) {
|
||||||
|
char *text = NULL;
|
||||||
|
if (config->programNotation != VBU_NUMBERS) {
|
||||||
|
switch (id) {
|
||||||
|
case 2: text = "HP"; break;
|
||||||
|
case 3: text = "SP"; break;
|
||||||
|
case 4: text = "GP"; break;
|
||||||
|
case 5: text = "TP"; break;
|
||||||
|
case 31: text = "LP"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text == NULL)
|
||||||
|
sprintf(dest, "R%d", id);
|
||||||
|
else strcpy(dest, text);
|
||||||
|
if (config->programCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**************************** Operand Formatters *****************************/
|
||||||
|
|
||||||
|
/* Format the condition operand of a split BCOND instruction */
|
||||||
|
static void dasmOpBCOND(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int cond = line->code[1] >> 2 & 15;
|
||||||
|
if (config->conditionNotation == VBU_NUMBERS)
|
||||||
|
sprintf(dest, "%d", cond);
|
||||||
|
else {
|
||||||
|
strcpy(dest, dasmCondition(config, cond));
|
||||||
|
if (config->conditionCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 9-bit displacement operand */
|
||||||
|
static void dasmOpDisp9(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int32_t disp = SignExtend((int32_t) line->code[1] << 8 | line->code[0], 9);
|
||||||
|
sprintf(dest, config->hexCase == VBU_LOWER ?
|
||||||
|
"%08x" : "%08X", line->address + disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 26-bit displacement operand */
|
||||||
|
static void dasmOpDisp26(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int32_t disp = SignExtend(
|
||||||
|
(int32_t) line->code[1] << 24 |
|
||||||
|
(int32_t) line->code[0] << 16 |
|
||||||
|
(int32_t) line->code[3] << 8 |
|
||||||
|
line->code[2]
|
||||||
|
, 26);
|
||||||
|
sprintf(dest, config->hexCase == VBU_LOWER ?
|
||||||
|
"%08x" : "%08X", line->address + disp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 5-bit sign-extended immediate operand */
|
||||||
|
static void dasmOpImm5S(char *dest, VBU_DasmLine *line) {
|
||||||
|
sprintf(dest, "%d", SignExtend(line->code[0], 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 5-bit zero-filled immediate operand */
|
||||||
|
static void dasmOpImm5U(char *dest, VBU_DasmLine *line) {
|
||||||
|
sprintf(dest, "%d", line->code[0] & 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 16-bit sign-extended immediate operand */
|
||||||
|
static void dasmOpImm16S(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int32_t imm = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
|
||||||
|
if (imm >= -256 && imm <= 256) {
|
||||||
|
sprintf(dest, "%d", imm);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (imm < 0) {
|
||||||
|
*dest++ = '-';
|
||||||
|
imm = -imm;
|
||||||
|
}
|
||||||
|
dasmToHex(dest, config, 0, imm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a 16-bit zero-filled immediate operand */
|
||||||
|
static void dasmOpImm16U(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
uint16_t imm = (uint16_t) line->code[3] << 8 | line->code[2];
|
||||||
|
dasmToHex(dest, config, 4, imm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format the operand of a JMP instruction */
|
||||||
|
static void dasmOpJMP(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, line->code[0] & 31);
|
||||||
|
strcat(dest, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a memory operand */
|
||||||
|
static void dasmOpMemory(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int reg1 = line->code[0] & 31;
|
||||||
|
int32_t disp = (int16_t) ((int16_t) line->code[3] << 8 | line->code[2]);
|
||||||
|
|
||||||
|
/* Displacement of zero */
|
||||||
|
if (disp == 0) {
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, reg1);
|
||||||
|
strcat(dest, "]");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inside */
|
||||||
|
if (config->memoryNotation == VBU_INSIDE) {
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, reg1);
|
||||||
|
dest += strlen(dest);
|
||||||
|
if (disp < 0) {
|
||||||
|
strcpy(dest, " - ");
|
||||||
|
disp = -disp;
|
||||||
|
} else strcpy(dest, " + ");
|
||||||
|
dest += 3;
|
||||||
|
if (disp <= 256)
|
||||||
|
sprintf(dest, "%d", disp);
|
||||||
|
else dasmToHex(dest, config, 0, disp);
|
||||||
|
strcat(dest, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Outside */
|
||||||
|
else {
|
||||||
|
if (disp < 0) {
|
||||||
|
*dest++ = '-';
|
||||||
|
disp = -disp;
|
||||||
|
}
|
||||||
|
if (disp <= 256)
|
||||||
|
sprintf(dest, "%d", disp);
|
||||||
|
else dasmToHex(dest, config, 0, disp);
|
||||||
|
dest += strlen(dest);
|
||||||
|
*dest++ = '[';
|
||||||
|
dasmProgram(dest, config, reg1);
|
||||||
|
strcat(dest, "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a source register operand */
|
||||||
|
static void dasmOpReg1(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
dasmProgram(dest, config, line->code[0] & 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a destination register operand */
|
||||||
|
static void dasmOpReg2(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
dasmProgram(dest, config,
|
||||||
|
((uint16_t) line->code[1] << 8 | line->code[0]) >> 5 & 31);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format the condition operand of a split SETF instruction */
|
||||||
|
static void dasmOpSETF(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int cond = line->code[0] & 15;
|
||||||
|
if (config->conditionNotation == VBU_NUMBERS)
|
||||||
|
sprintf(dest, "%d", cond);
|
||||||
|
else {
|
||||||
|
strcpy(dest, dasmCondition(config, cond));
|
||||||
|
if (config->conditionCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format a system register operand */
|
||||||
|
static void dasmOpSystem(char*dest, VBU_DasmConfig*config, VBU_DasmLine*line) {
|
||||||
|
int id = line->code[0] & 31;
|
||||||
|
char *text = NULL;
|
||||||
|
if (config->systemNotation != VBU_NUMBERS) {
|
||||||
|
switch (id) {
|
||||||
|
case 0: text = "EIPC" ; break;
|
||||||
|
case 1: text = "EIPSW"; break;
|
||||||
|
case 2: text = "FEPC" ; break;
|
||||||
|
case 3: text = "FEPSW"; break;
|
||||||
|
case 4: text = "ECR" ; break;
|
||||||
|
case 5: text = "PSW" ; break;
|
||||||
|
case 6: text = "PIR" ; break;
|
||||||
|
case 7: text = "TKCW" ; break;
|
||||||
|
case 24: text = "CHCW" ; break;
|
||||||
|
case 25: text = "ADTRE"; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (text == NULL) {
|
||||||
|
sprintf(dest, "%d", id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
strcpy(dest, text);
|
||||||
|
if (config->programCase == VBU_LOWER)
|
||||||
|
dasmToLower(dest);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Module Functions ******************************/
|
||||||
|
|
||||||
|
/* Prepare an OpDef for a BCOND instruction */
|
||||||
|
static OpDef* dasmBCOND(VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine *line, OpDef *opdef) {
|
||||||
|
int cond; /* Condition code */
|
||||||
|
char *text; /* Mnemonic text */
|
||||||
|
|
||||||
|
/* Split notation */
|
||||||
|
if (config->bcondNotation == VBU_SPLIT) {
|
||||||
|
strcpy(opdef->mnemonic, "BCOND");
|
||||||
|
opdef->operandsLength = 2;
|
||||||
|
opdef->operands[0] = DASM_BCOND;
|
||||||
|
opdef->operands[1] = DASM_DISP9;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Joined notation */
|
||||||
|
cond = line->code[1] >> 1 & 15;
|
||||||
|
switch (cond) {
|
||||||
|
case 0: text = "BV" ; break;
|
||||||
|
case 1: text = config->conditionCL == VBU_C ? "BC" : "BL"; break;
|
||||||
|
case 2: text = config->conditionEZ == VBU_E ? "BE" : "BZ"; break;
|
||||||
|
case 3: text = "BNH"; break;
|
||||||
|
case 4: text = "BN" ; break;
|
||||||
|
case 5: text = "BR" ; break;
|
||||||
|
case 6: text = "BLT"; break;
|
||||||
|
case 7: text = "BLE"; break;
|
||||||
|
case 8: text = "BNV"; break;
|
||||||
|
case 9: text = config->conditionCL == VBU_C ? "BNC" : "BNL"; break;
|
||||||
|
case 10: text = config->conditionEZ == VBU_E ? "BNE" : "BNZ"; break;
|
||||||
|
case 11: text = "BH" ; break;
|
||||||
|
case 12: text = "BP" ; break;
|
||||||
|
case 13: text = "NOP"; break;
|
||||||
|
case 14: text = "BGE"; break;
|
||||||
|
default: text = "BGT";
|
||||||
|
}
|
||||||
|
strcpy(opdef->mnemonic, text);
|
||||||
|
opdef->operandsLength = cond != 13;
|
||||||
|
opdef->operands[0] = DASM_DISP9;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure an output buffer matches the required size */
|
||||||
|
static VBU_DasmLine* dasmGrow(
|
||||||
|
VBU_DasmLine **lines, size_t *size, size_t target, unsigned index) {
|
||||||
|
if (*size < target) {
|
||||||
|
while (*size < target)
|
||||||
|
*size *= 2;
|
||||||
|
*lines = VBU_REALLOC(*lines, *size);
|
||||||
|
}
|
||||||
|
return *lines == NULL ? NULL : &(*lines)[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine the size of an instruction */
|
||||||
|
static uint32_t dasmInstSize(VB *sim, uint32_t address) {
|
||||||
|
unsigned opcode = vbRead(sim, address, VB_U16) >> 10 & 63;
|
||||||
|
return opcode < 0x20 || opcode == 0x32 || opcode == 0x36 ? 2 : 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Format an operand */
|
||||||
|
static void dasmOperand(char *dest, VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine *line, uint8_t type) {
|
||||||
|
switch (type) {
|
||||||
|
case DASM_BCOND : dasmOpBCOND (dest, config, line); break;
|
||||||
|
case DASM_DISP9 : dasmOpDisp9 (dest, config, line); break;
|
||||||
|
case DASM_DISP26: dasmOpDisp26(dest, config, line); break;
|
||||||
|
case DASM_IMM5S : dasmOpImm5S (dest, line); break;
|
||||||
|
case DASM_IMM5U : dasmOpImm5U (dest, line); break;
|
||||||
|
case DASM_IMM16S: dasmOpImm16S(dest, config, line); break;
|
||||||
|
case DASM_IMM16U: dasmOpImm16U(dest, config, line); break;
|
||||||
|
case DASM_JMP : dasmOpJMP (dest, config, line); break;
|
||||||
|
case DASM_MEMORY: dasmOpMemory(dest, config, line); break;
|
||||||
|
case DASM_REG1 : dasmOpReg1 (dest, config, line); break;
|
||||||
|
case DASM_REG2 : dasmOpReg2 (dest, config, line); break;
|
||||||
|
case DASM_SETF : dasmOpSETF (dest, config, line); break;
|
||||||
|
case DASM_SYSTEM: dasmOpSystem(dest, config, line); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prepare an OpDef for a SETF instruction */
|
||||||
|
static OpDef* dasmSETF(VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine *line, OpDef *opdef) {
|
||||||
|
|
||||||
|
/* Split notation */
|
||||||
|
if (config->bcondNotation == VBU_SPLIT) {
|
||||||
|
strcpy(opdef->mnemonic, "SETF");
|
||||||
|
opdef->operandsLength = 2;
|
||||||
|
opdef->operands[0] = DASM_SETF;
|
||||||
|
opdef->operands[1] = DASM_REG2;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Joined notation */
|
||||||
|
sprintf(opdef->mnemonic, "SETF%s",
|
||||||
|
dasmCondition(config, line->code[1] >> 1 & 15));
|
||||||
|
opdef->operandsLength = 1;
|
||||||
|
opdef->operands[0] = DASM_REG2;
|
||||||
|
return opdef;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disassemble one instruction */
|
||||||
|
static int dasmLine(VB *sim, uint32_t *address, VBU_DasmConfig *config,
|
||||||
|
VBU_DasmLine **lines, size_t *size, size_t *offset, unsigned index) {
|
||||||
|
char *dest; /* Text output pointer */
|
||||||
|
char *format; /* Hexadecimal format string */
|
||||||
|
VBU_DasmLine *line; /* Output line */
|
||||||
|
size_t more; /* Number of bytes to output */
|
||||||
|
OpDef *opdef; /* Instruction descriptor */
|
||||||
|
OpDef special; /* Special-case descriptor */
|
||||||
|
char tAddress[9]; /* Address display text */
|
||||||
|
char tCode[4][3]; /* Code display text */
|
||||||
|
char tMnemonic[8]; /* Mnemonic display text */
|
||||||
|
char tOperands[3][15]; /* Operands display text */
|
||||||
|
unsigned x; /* Scratch and iterator */
|
||||||
|
|
||||||
|
/* Process non-text members */
|
||||||
|
line = &(*lines)[index];
|
||||||
|
line->address = *address;
|
||||||
|
line->codeLength = dasmInstSize(sim, *address);
|
||||||
|
line->isPC = vbGetProgramCounter(sim) - *address < line->codeLength;
|
||||||
|
for (x = 0; x < line->codeLength; x++, (*address)++)
|
||||||
|
line->code[x] = vbRead(sim, *address, VB_U8);
|
||||||
|
|
||||||
|
/* Do not process text members */
|
||||||
|
if (config == NULL) {
|
||||||
|
line->text.address = NULL;
|
||||||
|
line->text.mnemonic = NULL;
|
||||||
|
line->text.operandsLength = 0;
|
||||||
|
for (x = 0; x < line->codeLength; x++)
|
||||||
|
line->text.code[x] = NULL;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Select instruction descriptor */
|
||||||
|
x = line->code[1] >> 2 & 63;
|
||||||
|
switch (x) {
|
||||||
|
case 0x12: /* SETF */
|
||||||
|
opdef = dasmSETF(config, line, &special);
|
||||||
|
break;
|
||||||
|
case 0x1F: /* Bit string */
|
||||||
|
x = line->code[0] & 31;
|
||||||
|
opdef = (OpDef *) (x < 16 ? &OPDEFS_BITSTRING[x] : &OPDEF_ILLEGAL);
|
||||||
|
break;
|
||||||
|
case 0x20: case 0x21: case 0x22: case 0x23: /* BCOND */
|
||||||
|
case 0x24: case 0x25: case 0x26: case 0x27:
|
||||||
|
opdef = dasmBCOND(config, line, &special);
|
||||||
|
break;
|
||||||
|
case 0x3E: /* Floating-point/Nintendo */
|
||||||
|
x = line->code[3] >> 2 & 63;
|
||||||
|
opdef = (OpDef *) (x < 13 ? &OPDEFS_FLOATENDO[x] : &OPDEF_ILLEGAL);
|
||||||
|
break;
|
||||||
|
default: opdef = (OpDef *) &OPDEFS[x]; /* Other */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Address */
|
||||||
|
format = config->hexCase == VBU_LOWER ? "%08x" : "%08X";
|
||||||
|
sprintf(tAddress, format, line->address);
|
||||||
|
more = 9;
|
||||||
|
|
||||||
|
/* Code */
|
||||||
|
format = config->hexCase == VBU_LOWER ? "%02x" : "%02X";
|
||||||
|
for (x = 0; x < line->codeLength; x++) {
|
||||||
|
sprintf(tCode[x], format, (int) line->code[x]);
|
||||||
|
more += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonic */
|
||||||
|
strcpy(tMnemonic, opdef->mnemonic);
|
||||||
|
if (config->mnemonicCase == VBU_LOWER)
|
||||||
|
dasmToLower(tMnemonic);
|
||||||
|
more += strlen(tMnemonic) + 1;
|
||||||
|
|
||||||
|
/* Operands */
|
||||||
|
line->text.operandsLength = opdef->operandsLength;
|
||||||
|
for (x = 0; x < opdef->operandsLength; x++) {
|
||||||
|
dasmOperand(tOperands[x], config, line, opdef->operands[x]);
|
||||||
|
more += strlen(tOperands[x]) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grow the text buffer */
|
||||||
|
line = dasmGrow(lines, size, *offset + more, index);
|
||||||
|
if (line == NULL)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
/* Working variables */
|
||||||
|
dest = &((char *) *lines)[*offset];
|
||||||
|
*offset += more;
|
||||||
|
|
||||||
|
/* Address */
|
||||||
|
strcpy(dest, tAddress);
|
||||||
|
line->text.address = dest;
|
||||||
|
dest += 9;
|
||||||
|
|
||||||
|
/* Code */
|
||||||
|
for (x = 0; x < line->codeLength; x++) {
|
||||||
|
strcpy(dest, tCode[x]);
|
||||||
|
line->text.code[x] = dest;
|
||||||
|
dest += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mnemonic */
|
||||||
|
strcpy(dest, tMnemonic);
|
||||||
|
line->text.mnemonic = dest;
|
||||||
|
dest += strlen(dest) + 1;
|
||||||
|
|
||||||
|
/* Operands */
|
||||||
|
for (x = 0; x < opdef->operandsLength; x++) {
|
||||||
|
strcpy(dest, tOperands[
|
||||||
|
config->operandOrder == VBU_DEST_FIRST ?
|
||||||
|
opdef->operandsLength - 1 - x :
|
||||||
|
x
|
||||||
|
]);
|
||||||
|
line->text.operands[x] = dest;
|
||||||
|
dest += strlen(dest) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Library Functions *****************************/
|
||||||
|
|
||||||
|
/* Disassemble from a simulation */
|
||||||
|
static VBU_DasmLine* dasmDisassemble(VB *sim, uint32_t address,
|
||||||
|
VBU_DasmConfig *config, unsigned length, int line) {
|
||||||
|
uint32_t addr; /* Working address */
|
||||||
|
uint32_t *circle = NULL; /* Circular address buffer */
|
||||||
|
VBU_DasmLine *lines = NULL; /* Output line buffer */
|
||||||
|
size_t offset; /* Offset into lines buffer */
|
||||||
|
uint32_t pc; /* Program counter */
|
||||||
|
size_t size; /* Number of bytes in lines */
|
||||||
|
unsigned x; /* Iterator */
|
||||||
|
|
||||||
|
/* Nothing to disassemble */
|
||||||
|
if (length == 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* Establish a circular buffer */
|
||||||
|
if (line > 0) {
|
||||||
|
circle = VBU_REALLOC(NULL, (size_t) (line + 1) * sizeof (uint32_t));
|
||||||
|
if (circle == NULL)
|
||||||
|
goto catch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Begin decoding from at least 10 lines before first/reference */
|
||||||
|
addr = (address & 0xFFFFFFFE) +
|
||||||
|
(int32_t) ((line < 0 ? -line : 0) - 10) * 4;
|
||||||
|
|
||||||
|
/* Locate the address of the line containing the reference address */
|
||||||
|
pc = vbGetProgramCounter(sim);
|
||||||
|
for (x = 0;;) {
|
||||||
|
|
||||||
|
/* Track the address in the circular buffer */
|
||||||
|
if (line > 0) {
|
||||||
|
circle[x] = addr;
|
||||||
|
if (++x > (size_t) line)
|
||||||
|
x = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check if the instruction contains the reference address */
|
||||||
|
size = dasmInstSize(sim, addr);
|
||||||
|
if (address - addr < size)
|
||||||
|
break;
|
||||||
|
addr += pc - addr < size ? pc - addr : size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Address of first line is in the circular buffer */
|
||||||
|
if (line > 0) {
|
||||||
|
addr = circle[x];
|
||||||
|
circle = VBU_REALLOC(circle, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keep decoding until the first line of output */
|
||||||
|
else for (; line < 0; line++)
|
||||||
|
addr += dasmInstSize(sim, addr);
|
||||||
|
|
||||||
|
/* Working variables */
|
||||||
|
size = length * sizeof (VBU_DasmLine);
|
||||||
|
lines = VBU_REALLOC(NULL, size);
|
||||||
|
offset = size;
|
||||||
|
if (lines == NULL)
|
||||||
|
goto catch;
|
||||||
|
|
||||||
|
/* Process output lines */
|
||||||
|
for (x = 0; x < length; x++) {
|
||||||
|
if (dasmLine(sim, &addr, config, &lines, &size, &offset, x))
|
||||||
|
goto catch;
|
||||||
|
}
|
||||||
|
return lines;
|
||||||
|
|
||||||
|
/* Exception handler */
|
||||||
|
catch:
|
||||||
|
circle = VBU_REALLOC(circle, 0);
|
||||||
|
lines = VBU_REALLOC(lines , 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* VBUAPI */
|
|
@ -0,0 +1,65 @@
|
||||||
|
#ifndef VBUAPI
|
||||||
|
#define VBUAPI
|
||||||
|
#endif
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <vbu.h>
|
||||||
|
|
||||||
|
/* Memory management */
|
||||||
|
#ifndef VBU_REALLOC
|
||||||
|
#include <stdlib.h>
|
||||||
|
#define VBU_REALLOC realloc
|
||||||
|
#else
|
||||||
|
extern void* VBU_REALLOC(void *, size_t);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***************************** Library Functions *****************************/
|
||||||
|
|
||||||
|
/* Sign-extend an integer of variable width */
|
||||||
|
static int32_t SignExtend(int32_t value, int32_t bits) {
|
||||||
|
#ifndef VB_SIGNED_PROPAGATE
|
||||||
|
value &= ~((uint32_t) 0xFFFFFFFF << bits);
|
||||||
|
bits = (int32_t) 1 << (bits - (int32_t) 1);
|
||||||
|
return (value ^ bits) - bits;
|
||||||
|
#else
|
||||||
|
return value << (32 - bits) >> (32 - bits);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************** Sub-Modules ********************************/
|
||||||
|
|
||||||
|
#include "disassembler.c"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************* API Commands ********************************/
|
||||||
|
|
||||||
|
/* Initialize disassembler options with default settings */
|
||||||
|
VBUAPI VBU_DasmConfig* vbuDasmInit(VBU_DasmConfig *config) {
|
||||||
|
config->bcondNotation = VBU_JOINED;
|
||||||
|
config->conditionCase = VBU_LOWER;
|
||||||
|
config->conditionCL = VBU_L;
|
||||||
|
config->conditionEZ = VBU_Z;
|
||||||
|
config->conditionNotation = VBU_NAMES;
|
||||||
|
config->hexCase = VBU_UPPER;
|
||||||
|
config->hexNotation = VBU_0X;
|
||||||
|
config->memoryNotation = VBU_OUTSIDE;
|
||||||
|
config->mnemonicCase = VBU_UPPER;
|
||||||
|
config->operandOrder = VBU_DEST_LAST;
|
||||||
|
config->programCase = VBU_LOWER;
|
||||||
|
config->programNotation = VBU_NAMES;
|
||||||
|
config->setfNotation = VBU_SPLIT;
|
||||||
|
config->systemCase = VBU_LOWER;
|
||||||
|
config->systemNotation = VBU_NAMES;
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disassemble from a simulation */
|
||||||
|
VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address,
|
||||||
|
VBU_DasmConfig *config, unsigned length, int line) {
|
||||||
|
return dasmDisassemble(sim, address, config, length, line);
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
#ifndef VBU_H_
|
||||||
|
#define VBU_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef VBUAPI
|
||||||
|
#define VBUAPI extern
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Header includes */
|
||||||
|
#include <vb.h>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/********************************* Constants *********************************/
|
||||||
|
|
||||||
|
/* Disassembler options */
|
||||||
|
#define VBU_0X 0
|
||||||
|
#define VBU_ABSOLUTE 0
|
||||||
|
#define VBU_C 1
|
||||||
|
#define VBU_DEST_FIRST 1
|
||||||
|
#define VBU_DEST_LAST 0
|
||||||
|
#define VBU_DOLLAR 1
|
||||||
|
#define VBU_E 0
|
||||||
|
#define VBU_H 2
|
||||||
|
#define VBU_INSIDE 1
|
||||||
|
#define VBU_JOINED 0
|
||||||
|
#define VBU_L 0
|
||||||
|
#define VBU_LOWER 1
|
||||||
|
#define VBU_NAMES 1
|
||||||
|
#define VBU_NUMBERS 0
|
||||||
|
#define VBU_OUTSIDE 0
|
||||||
|
#define VBU_RELATIVE 1
|
||||||
|
#define VBU_SPLIT 1
|
||||||
|
#define VBU_UPPER 0
|
||||||
|
#define VBU_Z 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*********************************** Types ***********************************/
|
||||||
|
|
||||||
|
/* Disassembler text options */
|
||||||
|
typedef struct { /* Defaults listed first */
|
||||||
|
uint8_t bcondNotation; /* JOINED, SPLIT */
|
||||||
|
uint8_t branchNotation; /* ABSOLUTE, RELATIVE */
|
||||||
|
uint8_t conditionCase; /* LOWER, UPPER */
|
||||||
|
uint8_t conditionCL; /* L, C */
|
||||||
|
uint8_t conditionEZ; /* Z, E */
|
||||||
|
uint8_t conditionNotation; /* NAMES, NUMBERS */
|
||||||
|
uint8_t hexCase; /* UPPER, LOWER */
|
||||||
|
uint8_t hexNotation; /* 0X, H, DOLLAR */
|
||||||
|
uint8_t memoryNotation; /* OUTSIDE, INSIDE */
|
||||||
|
uint8_t mnemonicCase; /* UPPER, LOWER */
|
||||||
|
uint8_t operandOrder; /* DEST_LAST, DEST_FIRST */
|
||||||
|
uint8_t programCase; /* LOWER, UPPER */
|
||||||
|
uint8_t programNotation; /* NAMES, NUMBERS */
|
||||||
|
uint8_t setfNotation; /* SPLIT, JOINED */
|
||||||
|
uint8_t systemCase; /* LOWER, UPPER */
|
||||||
|
uint8_t systemNotation; /* NAMES, NUMBERS */
|
||||||
|
} VBU_DasmConfig;
|
||||||
|
|
||||||
|
/* Disassembler output line */
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/* Parsed */
|
||||||
|
uint32_t address; /* Memory address */
|
||||||
|
uint16_t code[4]; /* Instruction bytes */
|
||||||
|
uint8_t codeLength; /* Number of items in code */
|
||||||
|
uint8_t isPC; /* Set if PC is on this line */
|
||||||
|
|
||||||
|
/* Display text */
|
||||||
|
struct {
|
||||||
|
char *address; /* Memory address */
|
||||||
|
char *code[4]; /* Instruction bytes */
|
||||||
|
char *mnemonic; /* Instruction mnemonic */
|
||||||
|
char *operands[3]; /* Instruction operands */
|
||||||
|
uint8_t operandsLength; /* Number of items in operands */
|
||||||
|
} text;
|
||||||
|
|
||||||
|
} VBU_DasmLine;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/******************************* API Commands ********************************/
|
||||||
|
|
||||||
|
VBUAPI VBU_DasmConfig* vbuDasmInit (VBU_DasmConfig *config);
|
||||||
|
VBUAPI VBU_DasmLine* vbuDisassemble(VB *sim, uint32_t address, VBU_DasmConfig *config, unsigned length, int line);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VBU_H_ */
|
Loading…
Reference in New Issue