Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/CITest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt

@Rot127 Rot127 Jul 24, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in order to run this test you need to add a step in CITest.yaml.
Its a little more annoying than on the next branch because here it was never refactored.
Just adding a simple new step below the cstest one should be enough.

Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
14 changes: 8 additions & 6 deletions arch/PowerPC/PPCInstPrinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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->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 (c == '+') {
insn->detail->ppc.bh = PPC_BH_PLUS;
} else if (strrchr(insn->mnemonic, '-') != NULL) {
} else if (c == '-') {
insn->detail->ppc.bh = PPC_BH_MINUS;
}

if (strrchr(insn->mnemonic, '.') != NULL) {
} else if (c == '.') {
insn->detail->ppc.update_cr0 = true;
}
}
Expand Down
3 changes: 3 additions & 0 deletions cs.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions suite/cstest/issues.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions tests/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
90 changes: 90 additions & 0 deletions tests/test_ppc_iter_detail.c
Original file line number Diff line number Diff line change
@@ -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 <stdio.h>

#include <capstone/platform.h>
#include <capstone/capstone.h>

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;
}
Loading