CSAPP阅读笔记-程序的机器表示--流程控制--条件码

程序的机器级表示--条件码

条件码

条件码表

标志 含义 描述
CF 进位标志(Carry Flag) 最近的操作是最高位产生了进位。可用来检测无符号操作的溢出。
ZF 零标志(Zero Flag) 最近的操作产生了0。
SF 符号标志(Signal Flag) 最近的操作得到的结果为负数。
OF 溢出标志(Overflow Flag) 最近的操作导致一个补码的溢出。

leap(地址加载)指令不改变任何条件码,用来进行地址计算。

运算指令改变寄存器同时也会设置条件码。

例如:

  1. XOR,进位与溢出标志会设置成0。

  2. 移位操作会将进位标志设置为最后一个被移出的位,而溢出标志设为0。

  3. INC和DEC指令会设置溢出和零标志,但是不会改变进位标志。

比较与测试指令

指令 基于 描述
CMP S2,S1<br />cmpb<br />cmpw<br />cmpl<br />cmpq S2-S1 比较<br />比较字节<br />比较字<br />比较双字<br />比较四字
Test S1,S2<br />testb<br />testw<br />testl<br />testq S1&S2 测试<br />测试字节<br />测试字<br />测试双字<br />测试四字

上表中的两类指令(8,16,24,36位形式)只设置条件码,不改变任何寄存器。例如CMP指令是通过两个操作数之差来设置条件码,除了不更新寄存器之外CMP的操作与Sub的操作是一样的,而Test与And指令是一样的。

条件码的访问与使用

使用方法有三种:

  1. 根据条件码的某种组合,将一个字节设置位0或1(SET指令)。SET指令的目的操作数是低位单字节寄存器元素,或是一个字节的内存位置,指令将其设置0或1,同时对高位清零。

    指令 同义名 效果 设置条件
    sete D setz D<-ZF 相等/零
    setne D setnz D <- ~ZF 不等/非零
    sets D D <- SF 负数
    setns D D <- ~SF 非负数
    setg D setnle D <- ~(SF ^ OF)&~ZF 大于(有符号>)
    setge D setnl D <- ~(SF^OF) 大于等于(有符号>=)
    setl D setnge D <- SF^OF 小于(有符号<)
    setle D setng D <- (SF^OF)|ZF 小等于(有符号<=)
    seta D setnbe D <- CF&ZF 超过(无符号>)
    setae D setnb D <- ~CF 超过或相等(无符号>=)
    setb D setnae D <- CF 低于(无符号<)
    setbe D setna D <- CF|ZF 低于或相等(无符号<=)

    set指令的操作流程:

    1. 执行比较指令
    2. 根据计算t=a-b设置
int comp(data_t a,data_t b)
a in %rdi, b in %rsi
comp:
    cmpq    %rsi, %rdi    Compare a :b
    setl    %al           Set Low-order byte of %eax to 0 or 1
    movezbl %al, %eax     Clear rest of %eax(and rest of %rax)
    ret

同c语言不同,机器代码不会将每个程序值都和一个数据类型联系起来。机器代码大部分对有符号和无符号的两种情况使用一样的指令。

  1. 跳转到某部分代码

  2. 有条件的传送数据

程序的跳转

正常程序是顺序执行,但是跳转指令会跳转另一段代码的位置。类似c语言里面的goto,跳转后的位置会用label指明。

跳转主要分:

  1. 直接跳转:跳转目标是作为指令的一部分编码的。
  2. 间接跳转:跳转目标是从内存或者寄存器中读出的。间接跳转的写法是*后面跟一个操作指示符,例如jmp*%rax就是从rax寄存器中的内容作为跳转目标,jmp *(%rax)即以%rax中的值作为读地址,从内存中读出跳转目标。

jmp指令集

指令 同义名 跳转条件 描述
jmp Label 1 直接跳转
jmp *Operand 1 间接跳转
de Label jz ZF 相等/零
jne Label jnz ~ZF 不相等/非零
js Label SF 负数
jns Label ~SF 非负数
jg Label jnle (SF^OF)&ZF 大于(有符号>)
jge Label jnl ~(SF^OF) 大于或等于(有符号>=)
jl Label jnge SF^OF 小于(有符号<)
jle Label jng (SF^OF)|ZF 小于或等于(有符号<=)
ja Label jnbe CF&ZF 超过(无符号>)
jae Label jnb ~CF 超过或相等(无符号>=)
jb Label jnae CF 低于(无符号<)
jbe Label jna CF|ZF 低于或相等(无符号<=)

上表中的跳转指令都是有条件的--它们根据条件码的组合,或者跳转,或者继续执行代码序列中下一跳指令。条件跳转只能是直接跳转。

跳转指令的编码分为PC绝对与PC相对。

PC相对:目标指令与紧跟在跳转指令后面那条指令的地址之间的差作为编码。

0: 48 89 f8    mov   %rdi,%rax
3: eb 03       jmp   8 <loop+0x8>
5: 48 d1 f8    sar   %rax
8: 48 85 c0    test  %rax, %rax
b: 7f f8       jg    5 <loop+0x5>
d: f3 c3       repz retq


movq   %rdi,%rax
jmp    .L2
.L3:
sarq   %rax
.L2:
testq  %rax
jg     .L3
rep;ret

第2行下一条指令的位置是5,相对位置是3,5+3=8,即跳转到8的位置上。

rep与repz除了在AMD上运行的更坏之外,不会改变代码的其他行为,不用管。

PC绝对:用4个字节直接指定目标。

条件分支

条件表达式和语句从c语言翻译成机器代码,最常用的是结合有条件和无条件跳转。条件跳转主要有两种实现方式:

  1. 控制的条件转移
  2. 数据的条件转移

控制的条件转移

c语言里面的if-else语句如下:

if (condition)
    then-statement;
else
    else-statement;

翻译成汇编会先上面语句转换成以下形式:

t = condition;
if (!t)
    goto false;
then-statement;
goto done;

false:
    else-statement;
done:

再转换为汇编形式。

举个例子:

c版本:

long func(long x,long y){
    long result;
    if (x < y)
        result = y - x;
    else{
        result = x - y;
    }
    return result;
}

转换后的版本:

long func(long x, long y){
    long result;
    if (x >= y)
        goto x_ge_y;
    result = y - x;
    return result;
x_ge_y:
    result = x - y;
    return result;
}

汇编版本:

long func(long x, long y)
x in %rdi, y in %rsi
func:
    cmpq %rsi,%rdi 
    jge .L2
    movq %rsi, %rax
    subq %rdi, %rax
    ret
.L2
    movq %rdi, %rax
    subq %rsi, %rax
    ret

数据的条件分支

处理器通过流水线获取高性能,一条指令要经过一些列阶段,每个阶段执行所需操作的一部分(例如,从内存中取指令,确定指令类型,从旁那个内存读数据,执行算数运算,想内存写数据,以及更新程序计数器)。这种方法通过重叠连续指令的步骤来获取高性能,例如,在取一条指令的同时,执行前一条指令的计算。处理器需要事先确定要执行的指令序列才能使流水线中保持充满待执行的指令。当遇到调制分支的时候,只要分支条件求知完成后才能决定分支的走向。现代处理器采用非常精密的分支预测逻辑来预测走向,但是只要预测错误就得丢弃为预测的指令所准备的工作,再从正确的位置重新加载,这样一个错误会导致程序性能严重下降。只要控制流不依赖数据,才能使流水线一直是满的。
下表列举了一些可用的条件传送指令。每个指令有两个操作数:源寄存器或者内存地址S,和目的寄存器R。指令的结果取决于条件码的值,只有在条件满足的时候,源寄存器的值才会被复制到目的寄存器中。
指令 同义名 传送条件 描述
cmove S, R cmovz ZF 相等/零
cmovne S, R cmovnz ~ZF 不相等/非零
cmovs S, R SF 负数
cmovns S, R ~SF 非负数
cmovg S, R cmovnle (SF^OF)&ZF 大于(有符号)
cmovge S, R cmovnl ~(SF^OF) 大等于(有符号)
cmovl S, R cmovnge SF^OF 小于(有符号)
cmovle S, R cmovng (SF^OF)|ZF 小等于(有符号)
cmova S, R cmovnbe CF&ZF 超过(无符号)
cmovae S, R cmovnb ~CF 超过或相等(无符号)
cmovb S, R cmovnae CF 低于(无符号)
cmovbe S, R cmovna CF|ZF 低于或等于(无符号)

同条件跳转不同,数据跳转无需预测测试的结果就可以执行条件传送。

  1. 处理器只是读源值(可能从内存)
  2. 检查条件码
  3. 要么更新目的寄存器,要么不变

数据传送表达式v = a ? b : c会被转换为类似以下表达式:

v = then-statement;
ve = else-statement;
t = condition;
if (!t) v = ve;

但是条件数据传送也不是万能的,上面两条语句只要一条产生panic就会导致程序崩溃。并且也不总是提高代码的效率,比如上述两个表达式计算量比较大的时候,条件不满足就白计算了,甚至代价比预测错分支还大,条件数据传送还是比较受限制的情况下好用。

循环

循环主要基于两种形式:

  1. do-while循环。
  2. while循环(while内部也有以下两种写法)
    1. while
    2. for

do-while循环

do-while的通用写法如下:

do
    body
    while(condition)

翻译成伪代码就是以下形式:

loop:
    body;
    t = condition;
    if (t)
        goto loop;

举个例子,有以下c函数:

long func(long n){
    long result = 1;
    do{
        result *= n;
        n--;
    }while(n > 1);
    return result;
}

翻译成等价无条件跳转+有条件跳转的形式:

long func(long n){
    long result = 1;
loop:
    result *= n;
    n--;
    if (n > 1)
        goto loop;
    return result;
}

转换成汇编:

long func(long n)
n in %rdi
fact_do:
    movl  $1, %eax
.L2:
    imuq  %rdi, %rax
    subq  $1, %rdi
    cmpq  $1, %rdi
    jg .L2
    rep;ret

while循环

通用形式如下:

while(condition)
    body;

转换后形式如下:

    goto test;
loop:
    body;
test:
    t = condition;
    if (t)
        goto loop;

可以看到与do-while不同的是,第一次条件判断如果没通过,程序就直接跳出循环。

用do-while循环同一个例子举例。

c形式:

long func(long n){
    long result = 1;
    while(n > 1);{
        result *= n;
        n--;
    }
    return result;
}

翻译成goto后的形式:

long func(long n){
    long result = 1;
    goto test;
loop:
    result *= n;
    n--;
test:
    if (n > 1)
        goto loop;
    return result;
}

转换为汇编的形式:

long func(long n)
n in %rdi
fact_while:
    movl  $1, %eax
    jmp .L5
.L6:
    imuq  %rdi, %rax
    subq  $1, %rdi
.L5:
    cmpq  $1, %rdi
    jg .L2
    rep;ret

还有一种翻译方法,当gcc使用o1优化等级的时候,会先转换成do-while的形式再转成汇编:

long func(long n){
    long result = 1;
    if(n <= 1)
        goto done;
loop:
    result *= n;
    n--;
    if (n != 1)
        goto loop;
done:
    return result;
}

汇编版本:

long func(long n)
n in %rdi
fact_while:
    cmpq  $1, %rdi
    jle   .L7
    movl  $1, %eax
.L6:
    imuq  %rdi, %rax
    subq  $1, %rdi
    cmpq  $1, %rdi
    jne   .L6
    rep;ret
.L7:
    movl  $1, %eax
    rep;ret

for循环

for循环其实是while的一种翻译形式,拿上面的例子举例:

long func(long n){
    long i;
    long result = 1;
    for(i = 2;i <= n;i++){
        result *= i;
    }
    return result;
}

翻译成while后的形式:

long func(long n){
    long i = 2;
    long result = 1;
    while(i <= n);{
        result *= i;
        i++;
    }
    return result;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,348评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,122评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,936评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,427评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,467评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,785评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,931评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,696评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,141评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,483评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,625评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,291评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,892评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,741评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,492评论 2 348