《系统级程序设计》——重点&汇编上手

1.这是我根据我的印象抽取的重点,希望不要奶死了。
2.里面有看汇编代码的迅速上手,如果要熟练,请一定要完成几个书上的汇编逆向出代码的题目。

  • 当表达式既有signed又有unsigned的时候,自动转换为unsigned

  • 数的补码表示:最高位为符号位,代表的值不再是2^k 而是 -2^k,但是其余位不变。
    eg.
    [1001] = -8 + 0 + 0 + 1 = -7;

  • 负数补码算数右移,加偏置量2^n-1(n为右移位数)
    eg.
    [1001] >> 2 = [1110] = -8 + 4 + 2 + 0 = -2;
    而 -7 / 4 = -1;❌
    所以要加偏置量2^2 - 1 = 3 = [0011];
    则[1001] + [0011] = [1100];
    [1100] >> 2 = [1111] = -8 + 4 + 2 + 1 = -1;✔️

  • 二进制小数:只能表示x/(2^k)
    eg.
    [0.101] = (4 + 1) / (2 ^ 3) = 5 / 8;

  • 浮点数:非规格化的作用
    1、提供了一种表示0的方法,因为规格化数小数位>=0。
    eg.-0.0 & 0.0,除了符号位为1 & 0 其余全为0
    2.提供一种叫逐渐溢出的属性,即0.0周围的小数都分布均匀地接近于0

  • 浮点数:偏置量bias = 2^(k - 1)-1 ;(k为浮点数指数位位数)

  • 浮点数:非规格化数的偏置为什么为1-bias,而不是bias
    因为规格化数小数位隐含第一位为1,而非规格化数没有。
    为了实现最大非规格化数与最小规格化数之间的平滑过度,所以为1-bias

  • 浮点数:特殊值
    if(指数位全为1)
    {
    if(若小数位全为0)
    表示∞
    else
    否则表示NaN
    }

  • 寄存器
    要看懂汇编,先要清楚寄存器。
    常用的寄存器有:ax,bx,cx,dx,si,di,bp,sp(具体作用,请结合我另一文章看)
    这些都是16位的寄存器。
    (括号内英文仅为方便记忆)
    如果加上l(low),说明为低8位,为8位寄存器。如al,bl,sil,spl
    如果加上h(high),说明为高8位,仍然为8位寄存器。如ah,bh
    如果加上e(enough足够),说明为32位寄存器,如eax,esi,edi
    如果加上r(redundant超多),说明为64位寄存器,如rax,rsi,rdi
    //是不是与c语言的union类型比较像


    1
  • 指令:
    一般的指令有mov,jmp,push之类的,后面一般有表示操作数后缀。


    2

    比如movl表示要移动4*8=32位的数据到寄存器内,所以指令与你操作的数据以及寄存器要匹配得上。
    eg. movl %esi,%al
    这就不匹配,l说明移动32位,但%al寄存器为8位寄存器,无法容纳。

  • 取指针的值
    有指针 p,p存在寄存器esi中,但esi存的只是p的值,是地址。
    如果要获取
    p的值,要通过括号(%esi)来获取。

  • 复杂取地址
    记住,a(b,c,d)在外面的,以及非第二个逗号两边的树外,都是直接相加。
    比如 movl 12(%ebp),%eax ->%eax = %ebp + 12;
    而第二个逗号左右两边的数,为乘。
    比如 movl 12(%esi,%ebp,6),%eax -> %eax = %ebp * 6 + %esi + 12;

  • 控制指令
    要条件跳转,先要判断。
    判断指令有cmp,test
    test一般用法是 test a,a ->看a是否为0

cmp是最常用的
比如 cmp a,b
就做b - a的运算,并且将结果暂存。

接着就需要条件跳转语句jmp

j代表跳转
后面可以接以下字母以及他们的组合
g(great): >
a (above) : >
e (equal) : =
l (less) : <
b (below) : <

eg组合:jle .L1(如果less equal <= 则跳转到L1)

  • while的三种翻译方法
    1、do-while
    2、跳到中间的翻译方法
    3、gaurded-do翻译方法(做了优化)

  • 条件传送指令
    cmov
    后缀与jmp一样,此外还多了
    s(signed) 负数
    ns(not signed)非负数

用法
test x,x
comvns a,b
如果x不为负数,(则取前面的)即为a = a
若x为负数,则 a = b
//类似于C语言 a>=0?a:b;

  • 复杂数据类型的表示
    数组:在内存中连续存储
    //此时就可以联系到前面说过的取址指令了
    比如数组int a[3];
    a为数组首地址,每个数组元素占4Bytes
    若a存在寄存器%edi中,索引i存在%esi中
    那么取a的元素,放到%eax(用于存返回值的寄存器)
    a[i] : movl (%edi,%esi,4), %eax;
    //若为double数组,改4为8即可

struct结构类型
同理可按照数据大小进行连续排列
eg
strcut test
{
char c;
int I;
double d;
}
那么 那么d就在test偏移为5(sizeof int 和 char)的地方。

但是,在有一些操作系统中,会有数据对齐的要求。
要求:一个数据的首地址应该为该数据大小的整数倍,结尾也是。
因此上述例子就不正确了,因为char占1B
i就不能紧接着c存储,而必须空出3位,到从地址为4的地方开始到8结束(地址从0开始计算),而8正好为double的倍数,所以可以直接开始存储d。
此时d相对于test偏移量就不再是5了,而是8。


3
  • 缓冲区溢出攻击
    利用数据读取漏洞,通过注入溢出的数据代码,使得程序运行一些黑客所需要的代码。

现代操作系统对缓冲区溢出攻击的防范机制:


4
  • 代码优化方法
    1、消除循环低效率
    2、减少过程调用
    3、消除不必要的内存引用

  • 评估程序性能
    标准:CPE(每元素周期数)

表示处理器性能的参数
延迟:完成运算所需的时间
发射时间:两个连续通类型的运算之间所需要的最小周期数
容量:能够执行该运算的功能单元数量
/*
大概可以理解为,一辆赛车有氮气加速,延迟代表一罐氮气喷完所需时间,发射时间代表,一罐氮气的充气时间,容量代表你有几个气罐子。
*/
eg
一款处理器
加法:延迟:3周期,发射时间:1周期,容量:2
需要做两个加法运算
如果两个加法相互独立,正好有容量为2,那么他们可以同时运算,则CPE = 3
如果,两个加法之间有数据依赖:比如 a = b + a; a = a + a;那么必须先等第一个做完(花费3周期),再另外一个做(花费3周期)总共是6周期。
//注意这里加法单元容量为2

如果容量改为1:
先等第一个做完(花费3周期),准备(花费发射1周期),再另外一个做(花费3周期)总共是7周期。

你可能感兴趣的:(《系统级程序设计》——重点&汇编上手)