Linux 0.12 OS. math - ea.c

这个.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;
}

你可能感兴趣的:(Linux 0.12 OS. math - ea.c)