diff --git a/.github/workflows/CITest.yml b/.github/workflows/CITest.yml index 6fd6250950..28614975e0 100644 --- a/.github/workflows/CITest.yml +++ b/.github/workflows/CITest.yml @@ -197,6 +197,11 @@ jobs: python -c "import capstone;print(capstone.debug())" | grep Cython BUILD_TESTS=no make tests + - name: run python binding test (cmake) + if: matrix.config.enable-asan == 'OFF' && startsWith(matrix.config.build-system, 'cmake') + run: | + ctest --test-dir build --output-on-failure + Windows: runs-on: ${{ matrix.config.os }} name: ${{ matrix.config.name }} diff --git a/CMakeLists.txt b/CMakeLists.txt index b6654504f8..f53c3b1589 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -772,12 +772,22 @@ endif() if(CAPSTONE_BUILD_TESTS) set(CMAKE_FOLDER "Tests") + set(TESTS_SKIP_CMP test_skipdata test_iter) enable_testing() foreach(TSRC ${TEST_SOURCES}) string(REGEX REPLACE ".c$" "" TBIN ${TSRC}) add_executable(${TBIN} "tests/${TSRC}") target_link_libraries(${TBIN} PRIVATE capstone) - add_test(NAME "capstone_${TBIN}" COMMAND ${TBIN}) + if (${TBIN} IN_LIST TESTS_SKIP_CMP) + add_test(NAME "capstone_${TBIN}" COMMAND ${TBIN}) + else () + set(TST_PY ../bindings/python/${TBIN}.py) + if(EXISTS ${TST_PY}) + add_test(NAME "capstone_${TBIN}" COMMAND python3 ../bindings/stdout_cmp.py -f1 ./${TBIN} -f2 python3 ${TST_PY}) + else() + MESSAGE("TEST ${TST_PY} does not exist. Skip.") + endif() + endif () endforeach() if(CAPSTONE_ARM_SUPPORT) set(ARM_REGRESS_TEST test_arm_regression.c) diff --git a/bindings/stdout_cmp.py b/bindings/stdout_cmp.py new file mode 100644 index 0000000000..d430377050 --- /dev/null +++ b/bindings/stdout_cmp.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import argparse +import difflib +import re +import subprocess +import sys + + +def stdout_cmp(f1, f2): + def lines_run(xs): + out = subprocess.run(xs, stdout=subprocess.PIPE, check=True).stdout.decode() + lines = out.splitlines(keepends=True) + return out, [re.sub(r'([\t ])', '', ln) for ln in lines] + + out1, lns1 = lines_run(f1) + out2, lns2 = lines_run(f2) + dif = list(difflib.unified_diff(lns1, lns2)) + return len(dif) == 0, dif + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description='Comparing the standard output of two threads') + parser.add_argument("-f1", nargs='+') + parser.add_argument("-f2", nargs='+') + argv = parser.parse_args(sys.argv[1:]) + res, dif = stdout_cmp(argv.f1, argv.f2) + if not res: + print('\n'.join(dif)) + exit(1) + exit(0) diff --git a/cstool/cstool.c b/cstool/cstool.c index 160dbb7981..5114a42261 100644 --- a/cstool/cstool.c +++ b/cstool/cstool.c @@ -124,6 +124,8 @@ static struct { { "hppa20wbe", CS_ARCH_HPPA, CS_MODE_HPPA_20W | CS_MODE_BIG_ENDIAN }, { "loongarch32", CS_ARCH_LOONGARCH, CS_MODE_LOONGARCH32 }, { "loongarch64", CS_ARCH_LOONGARCH, CS_MODE_LOONGARCH64 }, + { "xtensa", CS_ARCH_XTENSA, CS_MODE_XTENSA }, + { "xtensabe", CS_ARCH_XTENSA, CS_MODE_XTENSA | CS_MODE_BIG_ENDIAN }, { NULL } }; @@ -349,6 +351,11 @@ static void usage(char *prog) printf(" loongarch64 LoongArch64\n"); } + if (cs_support(CS_ARCH_XTENSA)) { + printf(" xtensa Xtensa\n"); + printf(" xtensabe Xtensa + big endian\n"); + } + printf("\nExtra options:\n"); printf(" -d show detailed information of the instructions\n"); printf(" -r show detailed information of the real instructions (even for alias)\n"); @@ -430,6 +437,9 @@ static void print_details(csh handle, cs_arch arch, cs_mode md, cs_insn *ins) case CS_ARCH_LOONGARCH: print_insn_detail_loongarch(handle, ins); break; + case CS_ARCH_XTENSA: + print_insn_detail_xtensa(handle, ins); + break; default: break; } @@ -578,6 +588,10 @@ int main(int argc, char **argv) printf("loongarch=1 "); } + if (cs_support(CS_ARCH_XTENSA)) { + printf("xtensa=1 "); + } + printf("\n"); return 0; case 'h': diff --git a/cstool/cstool.h b/cstool/cstool.h index d0d73d1e89..10304a2705 100644 --- a/cstool/cstool.h +++ b/cstool/cstool.h @@ -22,5 +22,6 @@ void print_insn_detail_tricore(csh handle, cs_insn *ins); void print_insn_detail_alpha(csh handle, cs_insn *ins); void print_insn_detail_hppa(csh handle, cs_insn *ins); void print_insn_detail_loongarch(csh handle, cs_insn *ins); +#include "cstool_xtensa.inc" #endif //CAPSTONE_CSTOOL_CSTOOL_H_ diff --git a/cstool/cstool_xtensa.inc b/cstool/cstool_xtensa.inc new file mode 100644 index 0000000000..941e5e68e4 --- /dev/null +++ b/cstool/cstool_xtensa.inc @@ -0,0 +1,62 @@ +#include +#include + +static inline void print_insn_detail_xtensa(csh handle, cs_insn *ins) +{ + int i; + cs_regs regs_read, regs_write; + uint8_t regs_read_count, regs_write_count; + + // detail can be NULL on "data" instruction if SKIPDATA option is turned ON + if (ins->detail == NULL) + return; + + cs_xtensa *detail = &(ins->detail->xtensa); + + if (detail->op_count) + printf("\top_count: %u\n", detail->op_count); + + for (i = 0; i < detail->op_count; i++) { + cs_xtensa_op *op = &(detail->operands[i]); + if (op->type & CS_OP_REG) + printf("\t\toperands[%u].type: REG = %s\n", i, + cs_reg_name(handle, op->reg)); + else if (op->type & CS_OP_IMM) + printf("\t\toperands[%u].type: IMM = 0x%" PRIx32 "\n", + i, op->imm); + else if (op->type & CS_OP_MEM) + printf("\t\toperands[%u].type: MEM\n" + "\t\t\t.mem.base: REG = %s\n" + "\t\t\t.mem.disp: 0x%" PRIx8 "\n", + i, cs_reg_name(handle, op->mem.base), + op->mem.disp); + if (op->access & CS_AC_READ) + printf("\t\t\t.access: READ\n"); + else if (op->access & CS_AC_WRITE) + printf("\t\t\t.access: WRITE\n"); + else if (op->access & (CS_AC_READ | CS_AC_WRITE)) + printf("\t\t\t.access: READ | WRITE\n"); + } + // Print out all registers accessed by this instruction (either implicit or + // explicit) + if (!cs_regs_access(handle, ins, regs_read, ®s_read_count, + regs_write, ®s_write_count)) { + if (regs_read_count) { + printf("\tRegisters read:"); + for (i = 0; i < regs_read_count; i++) { + printf(" %s", + cs_reg_name(handle, regs_read[i])); + } + printf("\n"); + } + + if (regs_write_count) { + printf("\tRegisters modified:"); + for (i = 0; i < regs_write_count; i++) { + printf(" %s", + cs_reg_name(handle, regs_write[i])); + } + printf("\n"); + } + } +} diff --git a/suite/cstest/include/factory.h b/suite/cstest/include/factory.h index d3c36dba76..5c038c14c5 100644 --- a/suite/cstest/include/factory.h +++ b/suite/cstest/include/factory.h @@ -27,5 +27,6 @@ char *get_detail_tricore(csh *handle, cs_mode mode, cs_insn *ins); char *get_detail_alpha(csh *handle, cs_mode mode, cs_insn *ins); char *get_detail_hppa(csh *handle, cs_mode mode, cs_insn *ins); char *get_detail_loongarch(csh *handle, cs_mode mode, cs_insn *ins); +char *get_detail_xtensa(csh *handle, cs_mode mode, cs_insn *ins); #endif /* FACTORY_H */ diff --git a/suite/cstest/src/capstone_test.c b/suite/cstest/src/capstone_test.c index 3cf3c0f88d..abd9286131 100644 --- a/suite/cstest/src/capstone_test.c +++ b/suite/cstest/src/capstone_test.c @@ -208,6 +208,9 @@ int set_function(int arch) case CS_ARCH_LOONGARCH: function = get_detail_loongarch; break; + case CS_ARCH_XTENSA: + function = get_detail_xtensa; + break; default: return -1; } diff --git a/suite/cstest/src/main.c b/suite/cstest/src/main.c index 5da0e4998e..dd182a74e7 100644 --- a/suite/cstest/src/main.c +++ b/suite/cstest/src/main.c @@ -24,6 +24,7 @@ static single_dict arches[] = { {"CS_ARCH_ALPHA", CS_ARCH_ALPHA}, {"CS_ARCH_HPPA", CS_ARCH_HPPA}, {"CS_ARCH_LOONGARCH", CS_ARCH_LOONGARCH}, + {"CS_ARCH_XTENSA", CS_ARCH_XTENSA}, }; static single_dict modes[] = { @@ -78,6 +79,7 @@ static single_dict arches[] = { {"CS_MODE_HPPA_11", CS_MODE_HPPA_11}, {"CS_MODE_LOONGARCH32", CS_MODE_LOONGARCH32}, {"CS_MODE_LOONGARCH64", CS_MODE_LOONGARCH64}, + {"CS_MODE_XTENSA", CS_MODE_XTENSA}, }; static double_dict options[] = { @@ -139,6 +141,7 @@ static single_dict arches[] = { {"CS_MODE_HPPA_11", CS_OPT_MODE, CS_MODE_HPPA_11}, {"CS_MODE_LOONGARCH32", CS_OPT_MODE, CS_MODE_LOONGARCH32}, {"CS_MODE_LOONGARCH64", CS_OPT_MODE, CS_MODE_LOONGARCH64}, + {"CS_MODE_XTNESA", CS_OPT_MODE, CS_MODE_XTENSA}, }; static int counter; diff --git a/suite/cstest/src/xtensa_detail.c b/suite/cstest/src/xtensa_detail.c new file mode 100644 index 0000000000..f9bc170102 --- /dev/null +++ b/suite/cstest/src/xtensa_detail.c @@ -0,0 +1,67 @@ +#include "factory.h" + +char *get_detail_xtensa(csh *p_handle, cs_mode mode, cs_insn *ins) +{ + cs_xtensa *detail; + int i; + cs_regs regs_read, regs_write; + uint8_t regs_read_count, regs_write_count; + + if (ins->detail == NULL) + return NULL; + + char *result = (char *)calloc(1024, sizeof(char)); + csh handle = *p_handle; + detail = &(ins->detail->xtensa); + +#define printf(...) add_str(&result, __VA_ARGS__) + + if (detail->op_count) + printf("\top_count: %u\n", detail->op_count); + + for (i = 0; i < detail->op_count; i++) { + cs_xtensa_op *op = &(detail->operands[i]); + if (op->type & CS_OP_REG) + printf("\t\toperands[%u].type: REG = %s\n", i, + cs_reg_name(handle, op->reg)); + else if (op->type & CS_OP_IMM) + printf("\t\toperands[%u].type: IMM = 0x%" PRIx32 "\n", + i, op->imm); + else if (op->type & CS_OP_MEM) + printf("\t\toperands[%u].type: MEM\n" + "\t\t\t.mem.base: REG = %s\n" + "\t\t\t.mem.disp: 0x%" PRIx8 "\n", + i, cs_reg_name(handle, op->mem.base), + op->mem.disp); + if (op->access & CS_AC_READ) + printf("\t\t\t.access: READ\n"); + else if (op->access & CS_AC_WRITE) + printf("\t\t\t.access: WRITE\n"); + else if (op->access & (CS_AC_READ | CS_AC_WRITE)) + printf("\t\t\t.access: READ | WRITE\n"); + } + // Print out all registers accessed by this instruction (either implicit or + // explicit) + if (!cs_regs_access(handle, ins, regs_read, ®s_read_count, + regs_write, ®s_write_count)) { + if (regs_read_count) { + printf("\tRegisters read:"); + for (i = 0; i < regs_read_count; i++) { + printf(" %s", + cs_reg_name(handle, regs_read[i])); + } + printf("\n"); + } + + if (regs_write_count) { + printf("\tRegisters modified:"); + for (i = 0; i < regs_write_count; i++) { + printf(" %s", + cs_reg_name(handle, regs_write[i])); + } + printf("\n"); + } + } + + return result; +} diff --git a/tests/test_xtensa.c b/tests/test_xtensa.c new file mode 100644 index 0000000000..c31bdb2f98 --- /dev/null +++ b/tests/test_xtensa.c @@ -0,0 +1,100 @@ +/* Capstone Disassembler Engine */ +/// Capstone xtensa test, by billow + +#include + +#include +#include +#include "../cstool/cstool_xtensa.inc" + +struct platform { + cs_arch arch; + cs_mode mode; + unsigned char *code; + size_t size; + char *comment; +}; + +static csh handle; + +static void print_string_hex(const char *comment, unsigned char *str, + size_t len) +{ + unsigned char *c; + + printf("%s", comment); + for (c = str; c < str + len; c++) { + printf("0x%02x ", *c & 0xff); + } + + printf("\n"); +} + +static void test() +{ +#define TEST_CODE "\x60\x51\x60\x32\x51\x02" + + struct platform platforms[] = { + { + CS_ARCH_XTENSA, + CS_MODE_LITTLE_ENDIAN, + (unsigned char *)TEST_CODE, + sizeof(TEST_CODE) - 1, + "Xtensa", + }, + }; + + uint64_t address = 0x1000; + cs_insn *insn; + int i; + size_t count; + + for (i = 0; i < sizeof(platforms) / sizeof(platforms[0]); i++) { + cs_err err = + cs_open(platforms[i].arch, platforms[i].mode, &handle); + if (err) { + printf("Failed on cs_open() with error returned: %u\n", + err); + continue; + } + + cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); + + count = cs_disasm(handle, platforms[i].code, platforms[i].size, + address, 0, &insn); + if (count) { + size_t j; + + printf("****************\n"); + printf("Platform: %s\n", platforms[i].comment); + print_string_hex("Code:", platforms[i].code, + platforms[i].size); + printf("Disasm:\n"); + + for (j = 0; j < count; j++) { + printf("0x%" PRIx64 ":\t%s\t%s\n", + insn[j].address, insn[j].mnemonic, + insn[j].op_str); + print_insn_detail_xtensa(handle, &insn[j]); + } + + // free memory allocated by cs_disasm() + cs_free(insn, count); + } else { + printf("****************\n"); + printf("Platform: %s\n", platforms[i].comment); + print_string_hex("Code:", platforms[i].code, + platforms[i].size); + printf("ERROR: Failed to disasm given code!\n"); + } + + cs_close(&handle); + } +} + +int main() +{ + test(); + + return 0; +}