这个.c档可以计算出操作数的内存地址,当然既然内存地址那必须是内存操作数。浮点协处理器内存操作数包括短整(m16j)、整数(m32j)、长整(m64j)、单精度实数(m32r)、双精度实数(m64r)、临时实数(m80r)。
要读懂ea.c的两个函数,需要先学习Intel指令解析的一些内容,建议阅读Intel spec的第6、7章节。
一 r/m字段
/* 计算出info结构体里成员的地址偏移量,为了能方便的通过 reg字段来访问寄存器内容。 */ static int __regoffset[] = { offsetof(struct info,___eax), offsetof(struct info,___ecx), offsetof(struct info,___edx), offsetof(struct info,___ebx), offsetof(struct info,___esp), offsetof(struct info,___ebp), offsetof(struct info,___esi), offsetof(struct info,___edi) }; /* 通过r/m字段值(0~7)对寄存器进行访问 */ #define REG(x) (*(long *)(__regoffset[(x)]+(char *) info))
这里面有个offset的function,它被定义在include/stddef.h里面
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
现代内核里面貌似也能经常见到,哈哈,个人觉得非常帅气的用法。
二 code部分
就只有两个function,一个是ea,就是通过它来获取内存操作数地址的,一个是sib,专门对‘displacement(base,index,scale)’这样的格式进行解析。
I. ea
char * ea(struct info * info, unsigned short code) { /* 对于浮点指令,我们只看第2个字节,它被称为ModRM字节, mod: The mod field combines with the r/m field to form 32 possible values representing 8 general registers and 24 indexing modes reg: The reg field specifies either a register number or three more bits of opcode information, the meaning of the reg field is determined by the first opcode byte of the instruction r/m: The r/m field can specify a register as the location of an operand, or it can be combined with the mod field to form the addressing-mode encoding 这里的reg字段在浮点指令中是opcode的辅助字段,我们需要 了解的是mod和r/m字段,通过Intel spec里面的速查表可以很快 的知道它们每个值代表的含义。 */ unsigned char mod,rm; long * tmp = &EAX; int offset = 0; /* 最终加上的地址偏移 */ mod = (code >> 6) & 3; /* ModRM(bit7~6)*/ rm = code & 7; /* ModRM(bit2~0)*/ if (rm == 4 && mod != 3) /* SIB byte */ return sib(info,mod); if (rm == 5 && !mod) { /* displacement,这种情况通常是label地址 */ offset = get_fs_long((unsigned long *) EIP); EIP += 4; /* 将eip移到下一条执行的指令 */ I387.foo = offset; /* 保存这次操作数的地址,[选择子]:[段偏移] */ I387.fos = 0x17; /* 注意,操作数是在用户空间 */ return (char *) offset; } tmp = & REG(rm); /* 基地址,对应的就是一个寄存器 */ switch (mod) { case 0: offset = 0; break; case 1: offset = (signed char) get_fs_byte((char *) EIP); /* 1个字节的disp */ EIP++; break; case 2: offset = (signed) get_fs_long((unsigned long *) EIP); /* 4个字节的disp */ EIP += 4; break; case 3: /* 注意,浮点协处理器指令的操作数有是寄存器操作数的情况, 比如fstsw %ax,将状态字内容拷贝到ax寄存器中,但这里默认不支持 */ math_abort(info,1<<(SIGILL-1)); /* 发送非法指令信号 */ } I387.foo = offset; /* 应该是offset + *tmp,指向数据的地址 */ I387.fos = 0x17; return offset + (char *) *tmp; }
II. sib
static char * sib(struct info * info, int mod) { /* 浮点指令的第3个字节,它被称为SIB字节, 形如:displacement(base,index,scale)的汇编格式, sf: It specifies the scale factor index: It specifies the register number of the index register base: It specifies the register number of the base register 通过Intel spec里面的速查表可以很快的知道它们每个值代表的含义, 最后的结果是 base + index*scale + displacement。 */ unsigned char sf,index,base; /* 'ss' to 'sf' */ long offset = 0; base = get_fs_byte((char *) EIP); /* 先把SIB字节取出来 */ EIP++; /* 记得更新eip */ sf = base >> 6; /* scale(bit7~6)*/ index = (base >> 3) & 7; /* index(bit5~3)*/ base &= 7; /* base(bit2~0)*/ if (index == 4) /* none */ offset = 0; else offset = REG(index); offset <<= sf; /* 乘上对应的scale */ if (mod || base != 5) /* the '[*]' heading in column 5 of SIB values means a disp32 with no base if mod is 00, EBP otherwise */ offset += REG(base); /* 对于mod=0,base=5的情况在下面会处理, 对于r/m=4的情况比较复杂,我们需要利用汇编来测试出结果, 1. 对于base是ebp的情况,会将形如(%ebp,index,scale)汇编格式变成 disp8(%ebp,index,scale)的格式,致使mod=1. 2. 对于没有base的情况,mod统一为0,也即形如[disp](,index,scale) 这样的汇编格式([disp]可以表示无/8位/32位)变成 disp32(,index,scale)的格式。 */ if (mod == 1) { /* disp 是8位的 */ offset += (signed char) get_fs_byte((char *) EIP); EIP++; } else if (mod == 2 || base == 5) { /* mod=0, base=5, [disp32](,index,scale) */ offset += (signed) get_fs_long((unsigned long *) EIP); EIP += 4; } I387.foo = offset; /* 保存当前操作数的地址,[选择子]:[段偏移] */ I387.fos = 0x17; /*注意,操作数在用户空间 */ return (char *) offset; }