EFLAGS description and implementation

EFLAGS description and implementation.

Introduction


EFLAGS是一个32位寄存器,由:一组状态标志位,一个控制标志位,一组系统标志位和系统保留位组成。

状态标志位

状态标志位简称为:OSZAPC,它们分别是:

  • Carry Flag (bit0):如果算术运算在结果的MSb(most significant bit最高有效)之外产生进位或借位,该标志值1,反之置0。该标志用于表示无符号整数运算的溢出条件;
  • Parity Flag (bit2):如果运算结果的LSB中含有偶数个1的时候,该标志置1,反之置0;
  • (least significant bit最低有效位,

    最低有效位the least significant bitlsb)是指一个二进制数字中的第0位(即最低位),具有权值为2^0,可以用它来检测数的奇偶性。与之相反的称之为最高有效位。在大端序中,lsb指最右边的位。

    最低有效位代表二进制数中的最小的单位,可以用来指示数字很小的变化

    )
  • Adjust Flag (bit4):如果运算结果的bit3上产生进位或借位,该标志置1,反之置0。该标志只用于BCD运算中;
  • Zero Flag (bit6):如果运算结果为0,该标志置1,反之置0;
  • Sign Flag (bit7):永远和运算结果的MSb相同。即1表示负数,0表示正数。所以,其实在CPU内部看来,数字本身是不分正负的,所谓的有符号数和无符号数类型,只不过是是否把SF表示和运算结果一起考虑的问题了。例如:0xFFFFFFFF,如果把这个数和SF一起考虑,这个数就是-1,如果不和SF一起考虑,这个数就是32位能表示的最大值;
  • Overflow Flag (bit11):如果运算结果超过了目的操作数可以表示的最大正数或最小负数,则该标志置1,反之置0。该标志只用来表示有符号数的溢出状况。

上面这组状态标志位可以使得CPU的一个算术运算为三种不同的数据类型产生表达不同的结果:

  • 如果把算术运算对待成是针对无符号数的:那么CF标志表示溢出;
  • 如果把算术运算对待成是针对有符号数的:那么OF标志表示溢出;
  • 如果把算术运算对待成是针对BCD数的:那么AF标志表示溢出;

判断OF的方法 由于OF是用来判断有符号数溢出的,因此我们只考虑有符号数的情况。以一字节数据为例,数值范围是-128 ~ 127:

对于加法操作

  • 如果操作数的MSb不同,即表示一正一负进行运算,则“最差”结果是-128 + 127 = -1,即FF。此时是不会造成溢出的,因此OF在MSb不同是为0的时候,无论如何不会被置位;
  • 如果操作数的MSb相同,如果和与两个加数的MSb不同,则代表发生了溢出的情况;

对于减法操作

  • 如果操作数的MSb不同,即表示一正一负进行运算,-128 - 1和127 - (-1)都会带来溢出结果,而判断的方法是差和被减数的MSb不同,在差在被减数的MSb上进行了借位;
  • 如果操作数的MSb相同,减法操作是不会带来算术溢出的,最差的结果无非是128 - 128或者-127 - (-127);

在实现上,通过两个宏来判断OF的置位条件:

控制标志位

Direction Flag (bit10):作为唯一的一个控制标志,DF用来控制字符串指令的方向。当置1时,字符串指令从高地址向低地址执行;反之则从低地址向高地址执行;

系统标志位

  • Trap Flag (bit8):在调试模式中,启用Single Step则置1,反之置0;
  • Interrupt Enable Flag (bit9):置1时,CPU响应可屏蔽中断,反之禁止可屏蔽中断;
  • IOPL (bit12-13):指定当前任务可以访问I/O地址的最低权限。CPL必须小于等于IOPL才可以访问I/O地址。该指令只能在CPL等于0的时候,通过POPF或者IRET指令修改;
  • Nested Task Flag (bit14):暂时不清楚
  • Resume Flag (bit16):暂时不清楚
  • Virtual-8086 Mode Flag (bit17):置1启用Virtual8086模式,置0返回保护模式;
  • Alignment Check Flag (bit18):当该标志和CR0.AM同时为1时,启用内存访问的对齐检查,二者有一个置0,则不进行检查。且该检查只有在CPL=3,且CPU处于保护模式或V8086模式时,才有用;
  • Virutal Interrupt Flag (bit19):暂时不清楚
  • Virtual Interrupt Pending Flag (bit20):暂时不清楚
  • Identification Flag (bit21):置1,CPU支持cpuid指令,反之不支持;

Implementation

由于有些标志位的修改会影响到系统的运行模式,因此实现上,分成不同组,只是单纯读写EFLAGS寄存器,不会影响系统运行状态的分成一组,影响系统运行状态的分成另外一组。

不影响系统状态或CPU工作模式的标志位

为了方便对EFLAGS中的每一个标志位进行读写,AVM定义了一组宏。每一个标志位都有5个基本操作,用

#define DECLARE_EFLAGS_ACCESSOR(name, bitnum) \
    bit32u get_##name(); \
    avm_bool getB_##name(); \
    void assert_##name(); \
    void clear_##name(); \
    void set_##name(avm_bool val);

这个宏在v_cpu类内部使用,每个方法的说明:

方法含义
get_##name屏蔽掉除了name标志之外的其他标志后,EFLAGS的值。
getB_##name单纯的判定name标志是否置位。
assert_##name保证name标志一定置位。
clear_##name清除name标志。
set_##name将name标志设定成val。

在实现上,读取标志用宏IMPLEMENT_EFLAGS_ACCESSOR,写入标志用宏IMPLEMENT_SET_EFLAGS_ACCESSOR

#define IMPLEMENT_EFLAG_ACCESSOR(name, bitnum) \
    bit32u v_cpu::get_##name() { \
        return eflags & (1 << bitnum); \
    } \
    avm_bool getB_##name() { \
        return (eflags >> bitnum) & 1; \
    } 

#define IMPLEMENT_SET_EFLAGS_ACCESSOR(name, bitnum) \
    void v_cpu::assert_##name() { \
        eflags |= (1 << bitnum); \
    } \
    void v_cpu::clear_##name() { \
        eflag &= ~(1 << bitnum); \
    } \
    void v_cpu::set_##name(avm_bool val) { \
        eflags = (eflags & ~(1 << bitnum)) | ((val) << bitnum); \
    }

影响系统状态或CPU工作模式的标志位

  • IOPL
    #define DECLARE_EFLAG_ACCESSOR_IOPL(bitnum) \
        void set_IOPL(bit32u val); \
        bit32u get_IOPL(); 
    
    #define IMPLEMENT_SET_EFLAGS_ACCESSOR_IOPL(bitnum) \
        void v_cpu::set_IOPL(bit32u val) { \
            eflags = (eflags & ~(0x3 << bitnum)) | ((0x03 & val) << bitnum); \
        } \
        bit32u v_cpu::get_IOPL() { \
            return 3 & (eflags >> bitnum); \
        }
  • TF, IF & RF Unimplement yet
  • AC Unimplement yet
  • VM Unimplement yet

算数标志位的缓释评估

v_cpu.h中,用一组特殊的宏ArithmeticalFlag来定义算术标志位的操作。和DECLARE_EFLAGS_ACCESSOR不同的是,多了两个方法:get_##flag##Lazyforce_##flag

缓释评估的基本原理:

用一个结构来记录算数运算的操作数、结果和具体的算数指令:avm_lf_flags_entry(定义在lazy_flags.h中),定义如下:

struct avm_lf_flags_entry {
    avm_address op1;
    avm_address op2;
    avm_address result;
    unsigned instr;
};

v_cpu内部,用一个该类型的对象oszapc,来记录虚拟cpu执行的最后一条算数指令的信息。

v_cpu内部,单独用一个整型变量lf_flags_status来记录算数指令带来的EFLAGS寄存器算数标志位的变化,可以把lf_flags_status看成一个位图,特定的二进制bit对应特定的算数标志位,当特定的算数标志位需要根据实际的算数指令更新的时候,特定标志位对应的bit会被置1,否则被置0。因此每一条算数指令最后,都会使用一个相应的宏,来更新lf_flags_status的值。那么当使用get_##flags或者get_B##flags获取某一个标志位的时候,先判断lf_flags_status中的特定标志是否被置1,如果置1,则调用缓释评估函数get_##flag##Lazy,根据实际的算数指令结果获取相应的标志位,否则,直接读取EFLAGS中的值。

所有的get_##flag##Lazy的基本结构是:

avm_bool v_cpu::get_##flag##Lazy() {
    unsigned flag;

    switch(_instruction type_) {
        case instr1:
            flag = ;
        break;
        case instr2:
            flag = ;
        break;
        
        ...
        case instrN:
            flag = ;
        break;
        default:
            EXCEPTION();
    }

    return flag;
}

所有的算数操作类型在lazy_flags.h中通过宏定义,之后用统一的接口进行定义。

avm_lf_flags_entry的统一接口:所有的算数指令使用一组统一的接口来操作CPU内部的oszapc对象,它们是:

 

只有当ADC,SBB等需要使用算数标志位的指令被执行到时,才会调用相应的get_##flag##Lazy来根据最近一次的算数运算来获取相应的标志位的值。

两个操作数的情况: 通用宏SET_FLAGS_OSZAPC_SIZE(size, lf_op1, lf_op2, lf_result, ins) size分成8, 16, 32

op1操作数和一个结果: SET_FLAGS_OSZAPC_S1_SIZE(size, lf_op1, lf_result, ins) size分成8, 16, 32

op2操作数和一个结果: SET_FLAGS_OSZAPC_S2_SIZE(size, lf_op1, lf_result, ins) size分成8, 16, 32

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值