Proposal for improving x86 eflags/fpuflags
The current set of eflags is incomplete; it doesn't include any of the System/IOPL flags. It also includes access specifiers that aren't particularly valuable for semantic analysis (e.g., X86_EFLAGS_PRIOR* that indicate reserved bits should be set to their prior value). There are also register/access combinations that aren't used by any instructions that I could find in the latest SDM[1] or in the datafiles from Xed v2024.11.04 (e.g., X86_EFLAGS_SET_IF).
It's not possible to fix these within the confines of the current implementation because the X86_EFLAGS macros already use 58 of the available 64 bits, macros can't be removed, and the unused/unusable ones can't be set to a dummy value (this could lead to redundant switch cases in user code). We're left with a need for a new implementation. Here is my proposal based on what Zydis does (code comments omitted for brevity).
// shift step could be 1?
#define CS_CPUFLAG_CF (1UL << 0)
#define CS_CPUFLAG_PF (1UL << 2)
#define CS_CPUFLAG_AF (1UL << 4)
#define CS_CPUFLAG_ZF (1UL << 6)
#define CS_CPUFLAG_SF (1UL << 7)
#define CS_CPUFLAG_TF (1UL << 8)
#define CS_CPUFLAG_IF (1UL << 9)
#define CS_CPUFLAG_DF (1UL << 10)
#define CS_CPUFLAG_OF (1UL << 11)
#define CS_CPUFLAG_IOPL (1UL << 12)
#define CS_CPUFLAG_NT (1UL << 14)
#define CS_CPUFLAG_RF (1UL << 16)
#define CS_CPUFLAG_VM (1UL << 17)
#define CS_CPUFLAG_AC (1UL << 18)
#define CS_CPUFLAG_VIF (1UL << 19)
#define CS_CPUFLAG_VIP (1UL << 20)
#define CS_CPUFLAG_ID (1UL << 21)
#define CS_FPUFLAG_C0 (1UL << 0)
#define CS_FPUFLAG_C1 (1UL << 1)
#define CS_FPUFLAG_C2 (1UL << 2)
#define CS_FPUFLAG_C3 (1UL << 3)
typedef struct cs_cpu_flag_state {
uint32_t tested;
uint32_t modified;
uint32_t set_0;
uint32_t set_1;
uint32_t undefined;
} cs_cpu_flag_state;
typedef struct cs_cpu_flags {
cs_cpu_flag_state eflags;
cs_cpu_flag_state fpu_flags;
} cs_cpu_flags;
typedef struct cs_x86 {
// ...
union {
/// EFLAGS updated by this instruction.
/// This can be formed from OR combination of X86_EFLAGS_* symbols in x86.h
uint64_t eflags;
/// FPU_FLAGS updated by this instruction.
/// This can be formed from OR combination of X86_FPU_FLAGS_* symbols in x86.h
uint64_t fpu_flags;
/// Version 2 of all cpu flags updated by this instruction.
cs_cpu_flags const* flags;
};
// ...
} cs_x86;
As long as sizeof(void*) == sizeof(uint64_t), this wouldn't be an ABI break.
The hard part will be determining how to fill out both flag inputs. One possibility would be to simply duplicate the information in X86MappingInsnOp.inc.
// X86Mapping.c
typedef struct insn_op {
uint64_t flags; // how this instruction update EFLAGS(arithmetic instructions) of FPU FLAGS(for FPU instructions)
uint8_t access[6];
cs_cpu_flags flags_v2;
} insn_op;
// X86MappingInsnOp.inc
{ /* X86_COM_FIr, X86_INS_FCOMI: fcomi */
{X86_EFLAGS_MODIFY_CF | X86_EFLAGS_MODIFY_PF | X86_EFLAGS_RESET_AF | X86_EFLAGS_MODIFY_ZF | X86_EFLAGS_RESET_SF | X86_EFLAGS_MODIFY_OF},
{ CS_AC_READ | CS_AC_WRITE, 0 },
{
{// CPU
{}, // tested
{CS_CPUFLAG_CF | CS_CPUFLAG_PF | CS_CPUFLAG_ZF}, // modified
{CS_CPUFLAG_AF | CS_CPUFLAG_SF | CS_CPUFLAG_OF}, // set_0
{}, // set_1
{}, // undefined
},
{// FPU
{}, // tested
{}, // modified
{}, // set_0
{CS_FPUFLAG_C1}, // set_1
{}, // undefined
}
},
},
Of course, nearly all instructions wouldn't need to completely fill out the FPU part. This is a "maximal" example that also solves https://github.com/capstone-engine/capstone/pull/2680.
There are lots of details left out like how to manage moving this information into the frontend. But my goal is to start a conversation on how we might be able to do this without needing to wait for a big rewrite like moving to Zydis (ping https://github.com/capstone-engine/capstone/discussions/2505).
Here is the complete set of flags and specifiers from Xed:
Note: pop and ah are really mod, 0 is reset, and 1 is set.
eflags
----------------------------------
ac = {mod, pop, tst}
af = {0, ah, mod, pop, tst, u}
cf = {0, 1, ah, mod, pop, tst, u}
df = {0, 1, mod, pop, tst}
id = {mod, pop, tst}
if = {0, mod, pop, tst}
iopl = {mod, pop, tst}
nt = {mod, pop, tst}
of = {0, mod, pop, tst, u}
pf = {0, ah, mod, pop, tst, u}
rf = {0, mod, pop, tst}
sf = {0, ah, mod, pop, tst, u}
tf = {0, mod, pop, tst}
vif = {mod, pop, tst}
vip = {mod, pop, tst}
vm = {0, mod, pop, tst}
zf = {ah, mod, pop, tst, u}
fpu flags
----------------------------------
fc0 = {mod, u}
fc1 = {mod, u}
fc2 = {mod, u}
fc3 = {mod, u}
[1] Intel 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4 June 2025
As long as sizeof(void*) == sizeof(uint64_t), this wouldn't be an ABI break.
This is an assumption we cannot make. One of the tasks left for the Beta is making Capstone work again on 32bit machines.
union
I wouldn't add them to the union in the details. Just adding a new member is fine I think. People can access it if they need to but still stick with the old flags. I think we could also wrap these new flags in some CAPSTONE_EXTENDED_X86_FLAGS include guards (also in the .inc files). So people can build with it or without it.
The hard part will be determining how to fill out both flag inputs.
I don't think this needs to be complicated. Just keep the old flags and add the new ones. Especially if it can be build with optionally people can save the space if they want to.
The hard part is to actually add all those flags correctly to the .inc files. Doing it manually is way way too error prone for thousands of instructions. So it needs to be automated somehow. Are you aware of any source providing this info in a machine readable manner?
As long as sizeof(void*) == sizeof(uint64_t), this wouldn't be an ABI break.
This is an assumption we cannot make. One of the tasks left for the Beta is making Capstone work again on 32bit machines.
Ah, I didn't know that.
union
I wouldn't add them to the union in the details. Just adding a new member is fine I think. People can access it if they need to but still stick with the old flags. I think we could also wrap these new flags in some
CAPSTONE_EXTENDED_X86_FLAGSinclude guards (also in the.incfiles). So people can build with it or without it.
I think that's a good stopgap that would be an opt-in for increasing the size of the x86 structure. In that case, I'd not make it a pointer to a cs_cpu_flags to ensure there doesn't need to be a memory allocation somewhere.
The hard part is to actually add all those flags correctly to the
.incfiles. Doing it manually is way way too error prone for thousands of instructions. So it needs to be automated somehow.
I gave this quite a bit of thought, but somehow never wrote it in my proposal. My plan is to transform the existing flags in X86MappingInsnOp* like I showed in the example and then add others as they are needed/discovered. That minimizes the changes needed for this proposal, maintains the existing level of semantic information, and allows for future expansion. I have some PRs coming that would fill in missing information for several instructions (those changes are the motivation for this proposal since they need to add flags that aren't in Capstone). Realistically, filling in the missing flag information for every instruction in Capstone is too big of a job- even if it could be automated somehow. If I'm the first person to propose this level of change, then it's unlikely other users would be improved by having a complete set of flag semantics.
Are you aware of any source providing this info in a machine readable manner?
A quick search found this Go package for parsing the xed data files, but I know nothing about Go.
One additional item is how to update the testing infrastructure. I think having it check both forms would be best.
I showed in the example and then add others as they are needed/discovered. filling in the missing flag information for every instruction in Capstone is too big of a job- even if it could be automated somehow.
I am not sure this is an ok target tbh. People come to Capstone expecting this information being there. And if it isn't, it is considered a bug. Also, from my own experience I know it is annoying to debug something and then figure it is Capstone missing X. I wouldn't want this to be the default for a new feature. Although, this is the current state of the flags, it can't be a justification to have it for the new flags as well.
IMO we should either ensure we have a generator adding all flags reliably, or wait until we made the decision on Zydis.
cc @kabeor @wtdcode @jiegec @wargio
I've been able to successfully import the flag information from Zydis with the structure I suggested above. I'll make a PR so we can directly comment on code choices. There are going to have to be really big changes to the instruction printers to handle the both the old and new data. I'll definitely want to work closely with all of you to make that a smooth transition.