diff --git a/.github/workflows/CITest.yml b/.github/workflows/CITest.yml index 90a761c51..4896416ac 100644 --- a/.github/workflows/CITest.yml +++ b/.github/workflows/CITest.yml @@ -89,6 +89,12 @@ jobs: python cstest_report.py -D -t build/cstest -d ../MC; python cstest_report.py -D -t build/cstest -f issues.cs; cd ..; + - name: test_ppc_iter_detail + shell: 'script -q -e -c "bash -e {0}"' + run: | + export LD_LIBRARY_PATH="${PWD}:${LD_LIBRARY_PATH:-}" + if [ -x build/test_ppc_iter_detail ]; then ./build/test_ppc_iter_detail; else ./tests/test_ppc_iter_detail; fi + Windows: runs-on: ${{ matrix.config.os }} name: ${{ matrix.config.name }} diff --git a/CMakeLists.txt b/CMakeLists.txt index 94e37057f..8b531278a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -298,7 +298,7 @@ if(CAPSTONE_PPC_SUPPORT) arch/PowerPC/PPCGenRegisterInfo.inc arch/PowerPC/PPCGenInstrInfo.inc ) - set(TEST_SOURCES ${TEST_SOURCES} test_ppc.c) + set(TEST_SOURCES ${TEST_SOURCES} test_ppc.c test_ppc_iter_detail.c) endif() if(CAPSTONE_X86_SUPPORT) diff --git a/arch/PowerPC/PPCInstPrinter.c b/arch/PowerPC/PPCInstPrinter.c index 1e654ba6e..4ddc20c64 100644 --- a/arch/PowerPC/PPCInstPrinter.c +++ b/arch/PowerPC/PPCInstPrinter.c @@ -79,14 +79,16 @@ void PPC_post_printer(csh ud, cs_insn *insn, char *insn_asm, MCInst *mci) if (((cs_struct *)ud)->detail != CS_OPT_ON) return; - // check if this insn has branch hint - if (strrchr(insn->mnemonic, '+') != NULL && !strstr(insn_asm, ".+")) { - insn->detail->ppc.bh = PPC_BH_PLUS; - } else if (strrchr(insn->mnemonic, '-') != NULL) { - insn->detail->ppc.bh = PPC_BH_MINUS; - } + // insn->mnemonic is not filled yet; the record-form '.' and the branch + // hint '+'/'-' are always the last char of the mnemonic token of insn_asm + size_t n = strcspn(insn_asm, " \t"); + char c = n ? insn_asm[n - 1] : 0; - if (strrchr(insn->mnemonic, '.') != NULL) { + if (c == '+') { + insn->detail->ppc.bh = PPC_BH_PLUS; + } else if (c == '-') { + insn->detail->ppc.bh = PPC_BH_MINUS; + } else if (c == '.') { insn->detail->ppc.update_cr0 = true; } } diff --git a/cs.c b/cs.c index 1b3b65d7d..460779dfd 100644 --- a/cs.c +++ b/cs.c @@ -581,6 +581,9 @@ static void fill_insn(struct cs_struct *handle, cs_insn *insn, char *buffer, MCI // we might skip some redundant bytes in front in the case of X86 memcpy(insn->bytes, code + insn->size - copy_size, copy_size); insn->op_str[0] = '\0'; + // mnemonic is filled below, after the post printer runs: post printers + // must derive any checks from insn_asm, never from insn->mnemonic + insn->mnemonic[0] = '\0'; insn->size = copy_size; // alias instruction might have ID saved in OpcodePub diff --git a/suite/cstest/issues.cs b/suite/cstest/issues.cs index 51c98fd66..f6675a34e 100644 --- a/suite/cstest/issues.cs +++ b/suite/cstest/issues.cs @@ -1118,3 +1118,15 @@ !# MOVSXD r16, m32 (memory form, 0x66 prefix) !# CS_ARCH_X86, CS_MODE_64, CS_OPT_DETAIL 0x0: 0x66, 0x63, 0x20 == movsxd sp, dword ptr [rax] + +!# issue PPC record form sets Update-CR0 +!# CS_ARCH_PPC, CS_MODE_64 | CS_MODE_BIG_ENDIAN, CS_OPT_DETAIL +0x7c,0x85,0x32,0x15 == add. r4, r5, r6 ; Update-CR0: True + +!# issue PPC branch hint plus +!# CS_ARCH_PPC, CS_MODE_64 | CS_MODE_BIG_ENDIAN, CS_OPT_DETAIL +0x40,0xe2,0x00,0x10 == bne+ 0x10 ; Branch hint: 1 + +!# issue PPC branch hint minus +!# CS_ARCH_PPC, CS_MODE_64 | CS_MODE_BIG_ENDIAN, CS_OPT_DETAIL +0x40,0xc2,0x00,0x10 == bne- 0x10 ; Branch hint: 2 diff --git a/tests/Makefile b/tests/Makefile index b9e90c6d0..f74d19992 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -84,6 +84,7 @@ endif ifneq (,$(findstring powerpc,$(CAPSTONE_ARCHS))) CFLAGS += -DCAPSTONE_HAS_POWERPC SOURCES += test_ppc.c +SOURCES += test_ppc_iter_detail.c endif ifneq (,$(findstring sparc,$(CAPSTONE_ARCHS))) CFLAGS += -DCAPSTONE_HAS_SPARC diff --git a/tests/test_ppc_iter_detail.c b/tests/test_ppc_iter_detail.c new file mode 100644 index 000000000..45623084c --- /dev/null +++ b/tests/test_ppc_iter_detail.c @@ -0,0 +1,90 @@ +/* Capstone Disassembly Engine */ +/* By phix33, 2026 */ + +// Regression test: update_cr0 and bh (branch hint) must be derived from the +// current instruction only. PPC_post_printer used to read insn->mnemonic, +// which the alias early-return paths in PPC_printInst (mr, slwi/srwi, sldi, +// dcbt/dcbtst, dcbf) never fill before it runs. When one cs_insn buffer is +// reused across cs_disasm_iter() calls - the documented usage pattern - those +// aliases inherited update_cr0/bh from the previously decoded instruction. + +#include + +#include +#include + +struct step { + const char *code; // 4 bytes, big-endian + const char *asm_text; + bool update_cr0; + ppc_bh bh; +}; + +// Each alias is decoded right after an instruction that legitimately sets +// update_cr0 or a branch hint, into the same reused cs_insn. +static const struct step steps[] = { + { "\x7c\x85\x32\x15", "add. r4, r5, r6", true, PPC_BH_INVALID }, + { "\x7c\x29\x0b\x78", "mr r9, r1", false, PPC_BH_INVALID }, + { "\x7c\x85\x32\x15", "add. r4, r5, r6", true, PPC_BH_INVALID }, + { "\x54\xa4\x18\x38", "slwi r4, r5, 3", false, PPC_BH_INVALID }, + { "\x7c\x85\x32\x15", "add. r4, r5, r6", true, PPC_BH_INVALID }, + { "\x78\xa4\x1f\x24", "sldi r4, r5, 3", false, PPC_BH_INVALID }, + { "\x7c\x85\x32\x15", "add. r4, r5, r6", true, PPC_BH_INVALID }, + { "\x7c\x04\x2a\x2c", "dcbt r4, r5", false, PPC_BH_INVALID }, + { "\x7c\x85\x32\x15", "add. r4, r5, r6", true, PPC_BH_INVALID }, + { "\x7c\x04\x28\xac", "dcbf r4, r5", false, PPC_BH_INVALID }, + // non-alias path after a record form: was already correct + { "\x7c\x85\x32\x15", "add. r4, r5, r6", true, PPC_BH_INVALID }, + { "\x7c\x85\x32\x14", "add r4, r5, r6", false, PPC_BH_INVALID }, + // branch hints must not leak into the aliases either + { "\x40\xe2\x00\x10", "bne+ 0x1010", false, PPC_BH_PLUS }, + { "\x7c\x29\x0b\x78", "mr r9, r1", false, PPC_BH_INVALID }, + { "\x40\xc2\x00\x10", "bne- 0x1010", false, PPC_BH_MINUS }, + { "\x7c\x04\x2a\x2c", "dcbt r4, r5", false, PPC_BH_INVALID }, +}; + +int main(void) +{ + csh handle; + cs_insn *insn; + int i, errors = 0; + + if (cs_open(CS_ARCH_PPC, CS_MODE_64 | CS_MODE_BIG_ENDIAN, &handle) != CS_ERR_OK) { + printf("ERROR: Failed to initialize engine!\n"); + return 1; + } + cs_option(handle, CS_OPT_DETAIL, CS_OPT_ON); + + insn = cs_malloc(handle); + + for (i = 0; i < (int)(sizeof(steps) / sizeof(steps[0])); i++) { + const uint8_t *code = (const uint8_t *)steps[i].code; + size_t size = 4; + uint64_t address = 0x1000; + + if (!cs_disasm_iter(handle, &code, &size, &address, insn)) { + printf("ERROR: failed to decode '%s'\n", steps[i].asm_text); + errors++; + continue; + } + + bool ok = insn->detail->ppc.update_cr0 == steps[i].update_cr0 && + insn->detail->ppc.bh == steps[i].bh; + printf("%s\t%s: update_cr0=%u (expected %u), bh=%u (expected %u)\n", + ok ? "OK " : "FAIL", steps[i].asm_text, + insn->detail->ppc.update_cr0, steps[i].update_cr0, + insn->detail->ppc.bh, steps[i].bh); + if (!ok) + errors++; + } + + cs_free(insn, 1); + cs_close(&handle); + + if (errors) { + printf("%d test(s) failed\n", errors); + return 1; + } + printf("all tests passed\n"); + return 0; +}