capstone icon indicating copy to clipboard operation
capstone copied to clipboard

Pseudo Instructions and their operands

Open wxrdnx opened this issue 6 months ago • 4 comments

In Capstone, the operands for pseudo-instructions are change along with mnenonic changes. For example, on Aarch64, the operand details of the pseudo-instruction cmp x0, x1 is

$ cstool -d aarch64 1f0001eb                   
 0  1f 00 01 eb  cmp	x0, x1
	ID: 1182 (subs)
	Is alias: 1510 (cmp) with ALIAS operand set
	op_count: 2
		operands[0].type: REG = x0
		operands[0].access: READ
		operands[1].type: REG = x1
		operands[1].access: READ
	Update-flags: True
	Registers read: x0 x1
	Registers modified: nzcv

However, cmp x0, x1 is merely a pseudo-instruction that should be expanded to subs xzr, x0, x1. Therefore, the op_count should be 3 instead of 2.

Furthermore, the operand details are also changed along with their mnemonic form, and this causes a problem: some operators are actually read/written "implicitly", but capstone's disassembly does not report this. As an example, the ret pseudo-instruction in Aarch64 is basically ret x30, but capstone returns the following

cstool -d aarch64 c0035fd6            
 0  c0 03 5f d6  ret	
	ID: 865 (ret)
	Is alias: 1588 (ret) with ALIAS operand set
	Groups: jump return

In reality, the link register x30 should be read, but Capstone does not report this fact, as the "implicit" operand of the ret pseudo-instruction is striped away.

Similar observations can be found in other architectures as well. For example, on RISC-V, the pseudo-instruction jal 0x4 is expanded to jal x1, 0x4, but Capstone returns

$ cstool -d riscv64 ef004000                
 0  ef 00 40 00  jal	4
	ID: 206 (jal)
	op_count: 1
		operands[0].type: IMM = 0x4
		operands[0].access: READ

	Groups: call

From my understanding, Capstone handles pseudo-instructions in all architectures this way.

This feature create minor problems if we merely use Capstone to disassemble opcodes, but it can cause some headaches if we aim to analyze the instruction's opcodes in depth.

Is there any workaround for this?

wxrdnx avatar Jun 14 '25 10:06 wxrdnx

For most Auto-Sync architectures (see the table here: https://github.com/capstone-engine/capstone/issues/2015) you can add the -r flag to cstool to disassemble the instruction details of the "real" instruction.

Here are the two examples for AArch64:

cstool -d -r aarch64 c0035fd6   
 0  c0 03 5f d6  ret	
	ID: 887 (ret)
	Is alias: 1620 (ret) with REAL operand set
	op_count: 1
		operands[0].type: REG = x30
		operands[0].access: READ
	Registers read: x30
	Groups: jump return 

cstool -d -r aarch64 1f0001eb  
 0  1f 00 01 eb  cmp	x0, x1
	ID: 1210 (subs)
	Is alias: 1542 (cmp) with REAL operand set
	op_count: 3
		operands[0].type: REG = xzr
		operands[0].access: WRITE
		operands[1].type: REG = x0
		operands[1].access: READ
		operands[2].type: REG = x1
		operands[2].access: READ
	Update-flags: True
	Registers read: x0 x1
	Registers modified: nzcv xzr

In code you can enable this behavior with the CS_OPT_DETAIL_REAL option.

Note though this doesn't work for RISCV, because it wasn't refactored yet. And switching between the details is a new feature for updated modules only (SystemZ and LoongArch are still missing, although they are updated).

That said, ret should still have x30 in the implicit read list. This is a bug and I'll fix it.

Rot127 avatar Jun 14 '25 12:06 Rot127

btw, there is no option yet to also print the "real" instruction asm text (instead of the alias). But it isn't too complicated to implement this. It just isn't high up on the priority list currently.

Rot127 avatar Jun 14 '25 12:06 Rot127

Thanks for the reply!

Do you happen to know when this feature might be available for RISC-V? I've learned that you are switching to SAIL instead of LLVM for the Auto-sync, and it looks like the Auto-sync capstone-autosync-sail is almost complete. Any idea when RISC-V Auto-synced files will be merged into the next branch?

wxrdnx avatar Jun 15 '25 08:06 wxrdnx

The RISCV PR is a prototype. We will test if it performs better than the current module. Then either merge it into next or into an experimental branch (based on next).

The biggest problem with the SAIL RISCV module is that it currently still has linear runtime. O(n) where n is the number of RISCV instructions (the LLVM modules have something around O(log(n))).

When it is done depends how much time @moste00 finds to work on it. Currently it is disassembling instructions but they are not yet tested.

Rot127 avatar Jun 15 '25 11:06 Rot127

Thanks for the reply again!

One last question: May I ask what makes the time complexity O(log(n)) in LLVM? This sound a little bit odd to me because from my understanding, in order to decode n instructions, you should at least traverse all the n instructions, so shouldn't it be O(n)?

wxrdnx avatar Jun 16 '25 12:06 wxrdnx

The LLVM disassembler essentially implements a state machine, checking bit fields and properties. So for a 32bit wide instruction it maybe checks (in practice) 2-5 fields within it to identify the instruction. In the theoretical worst case it needs to check every of the 32 bits to decide which instruction it is.

I described the decoding in more detail here.

The experimental RISCV module on the other hand has many hundred if cases which essentially do:

if ((bytes & mask_I1) == unique_instruction_bytes) { found } ; if ((bytes & mask_I2) == unique_instruction_bytes) { ... } etc.. So this is linear.

Rot127 avatar Jun 16 '25 14:06 Rot127