您的位置:首页 > 其它

程序的机器级表示

2014-08-18 16:05 351 查看

1, 操作数提示符:

三种:

立即数:常数值, 在ATT格式的汇编代码中, 书写格式是 $ + 整数, 如:$-123 $0X12

寄存器:如:32位的%eax 16位的%ax 8位的%al

存储器引用:

Imm(Eb) 表示地址为:Eb+Imm

Imm(Eb, Ei) 地址为:Eb(基址)+ Ei(变址) + Imm(立即数偏移),

Imm(Eb, Ei, s) 地址为: Eb + Ei * s + Imm

2, 字节传送指令:

mov 同等传送, 即俩者的大小一致 (如:movb byte, movew word, movel longword(DW))

movs/ movz 不同等传送, 即俩者的大小可以不一致, 如:movsbl 表示将源byte传送到目的longword, 用1 或0补充剩余位(取决于源byte的最高位是否为1), movzbl 与movsbl相似, 用0补充剩余位

push & pop

3, 特殊指令:

lea: 直接上例:

lea  4(%eax) %ebx   //表示 %ebx 里面的值为%eax + 4 。       
而 mov 4(%eax) %ebx  //表示 %ebx里面的值为【eax+4】这个地址的值


其实, 复杂的来说, lea 表示获取源地址的偏移地址, 对于上例来说, 偏移地址当然是%eax + 4 咯。

int fun (unsigned x) 
{
    int val = 0;
    while (x) {
         val ^= x;
         x >>= 1;
    }
    return val & 0X1;
}
这个函数是测试x二进制里面1的个数的奇偶, 奇返回1, 偶返回0;

理解, 第一着重于移位操作, 每次移位 都使前一位移动至后一位(即使得n位与n-1位之间的运算成为可能), 第二 着重于异或操作,对于1bit ^ 1bit来说, 恰好是查看这俩者的1的总个数是否为偶, 第三着重于第一位, 每次移位操作 都使得前一位都能跟当前第一位进行异或操作, 而这一位的结果是这一位上的1的个数是否为偶。loop, 测试前前一位。。。。。 again and again until it ends.

int fun (unsigned x) {
     int val = 0;
     int i;
     for (i = 0; i< 32; i++) {
           val = (val <<1) | (x & 0x1);
           x >>= 1;
     }
     return val;
}
这是获取x的位反转制造出来的镜像

4, switch指令较条件跳转的优越性

条件跳转, 在asm中, 是以类似cmp + jne 等指令实现的。 对于N种情况来说, 就意味着要N对cmp-jne这些指令。

switch对于条件转换来说, 是利用了跳转表来进行的(其实就是地址偏移),例如情况为100-106, 即只需要计算当前值-100 的大小(k), 跳转到jt【k】所指的位置即可。

其实这个也说明了某种问题, 也就是对于疏散的情况集合, 对于跳转表的使用会不会添加空白无用的区域浪费呢?

to be continued.......

5, Union的作用:

类似struct可以存放数据, 不同在于, Union所有数据共用内存, 即Union的大小为数据中大小最大的, 而不是大小之和。

用处: 类型转换。。。注意小端法读取数据

6, 为什么要数据对齐:

条件:1,平台获取数据都是以固定字节数获取的。 2,有些数据不规则,如:struct{int a, char c;}A; 其标准大小是5Bytes

导致的情况: 比如固定字节数为8, 不对齐数据就意味着第一次读取, 获得 A1 以及A2的部分数据, 为了读取A2的值, 必须读取两次,以及需要进行高低位的修补。。。。

所以数据对齐, 是一种以空间换时间的策略。

这也是为什么 sizeof(A)!= 5 而是8(test in VS2013)。

测试:

struct A{
int a;
char c;
int b;
};
void testSizeOf()
{
	printf("size of A is %d\n", sizeof(A));

	A k = { 1, 'c', 2 };
	printf("A:\na: %d\nc: %c\nb: %d\n\n", k.a, k.c, k.b);
	printf("A:\na: %x\nc: %x\nb: %x\n\n", k, k, k);         //其中%x表示十六进制整数。   科普:\x 表示其后面两位为十六进制数,常用于printf cout等。
}

结果为:
size of A is 12
A:
a: 1
c:  c
b: 2

A:
a:  1
c:   cccccc63         // 63是 'c'的十六进制ASCII字符表示形式
b:  2


表明:

1, 对于结构体k来说, 执行printf多次输出,其过程类似于文件流读取(读即前进)。

2, 结构体A中, c占用的字节为4, 其中多余3字节作为补充。

注:char *c 对象大小为4, 因为里面装的是地址。。。。。 void *v 大小亦是4,同理。

这也表明了数据位置排列的重要性。一般来说 应按照从大到小排列。 因为这样可以缩小对齐要求的字节数,如4->>2。(???若是从小到大, 会怎样? 会呵呵。。。)

7, 理解指针:

1) 指针指向函数:

int func(int a, int *b){...}

typedef int (*fp) (int a, int *b);

fp = func;
int result = fp(a, &b);
其实函数调用call也是移动到某一指令, 跟指针有差别吗?

2)指针跟数组无异:

int a
, *(a+1) = a [1];

3) 改变类型后 寻址的区别:

char *cp;
(int*)cp + 7  => (cp, 4, 7)的位置
(int*)(cp+7) =>  (cp, 1, 7)的位置


8, 存储器的越界引用以及缓冲区溢出:

常见的做法是 类似于char chs[8], strcpy(chs, "1234...........");导致缓冲区溢出, 覆盖其他地址的值(被保存的寄存器的值, 以及返回地址)

防范:

1, 栈随机化: 即同一个程序, 运行时的栈的位置不固定。
2, 栈破坏检测: 即在保存寄存器附近放置哨兵值, 程序运行后检查哨兵值是否发生改变, 若有, 立即终止程序。当然 这意味着哨兵值必须保存俩处, 而另一处唯一的保障是,只读,不能被修改, 似乎有点薄弱。。。。
3, 设置不能执行区域机制, 限制可执行区域。

总结(自我感觉):
1, 每次调用其他函数是, 会将参数列表反序压栈 + 返回地址。 接着是%ebp %ebx(或许这个跟参数有关, 因为每次push ebx之后在函数结尾都需要pop ebx 意味着ebx是一个需要保存的值, 不能改变, 这个岂不是传值参数传递不影响参数的值的依据?) 如果此时有申请缓冲区(char buffer【8】)则在此后面开辟一个*8(8byte)的空间,(其序号排列顺序是从自栈顶到栈底升序) 如果对这个缓冲区进行strcpy等操作, 就有可能修改到ebp ebx等 甚至是返回地址。
2, 在有保护的代码中, 常常是局部变量比buffer更靠近栈顶, 这样buffer溢出,不会破坏局部变量的值。

注: sizeof(“1234”)is 5; because of ‘\0’(0x00); 但是strlen返回的是4, 故在malloc的调用时, 应该malloc ( strlen(buf)+1 ). 记得要对返回值进行NULL判断。

9. AGE OF 64:

特殊数字: 0x101010101010101 72340172838076673
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: