深入理解计算机系统 笔记(二)

来源:互联网 发布:instagram类似社交软件 编辑:程序博客网 时间:2024/05/17 08:50

第三章 程序的机器级表示

gcc c语言编译器 -> 汇编
汇编代码是机器代码的文本表示
平坦寻址模式
Intel IA32 和 x86-64

3.2 程序编码

一个c程序,有2个文件p1.c和p2.c

unix$ gcc -o1 -o p p1.c p2.c

gcc gcc c编译器,也可以用cc启动
-o1 编译器使用第一级优化,级别越高编译时间越长

步骤:
1.编译器插入所有#include命令指定的文件,并扩展#define声明的宏
2.编译器产生2个源代码的汇编代码,p1.s和p2.s
3.汇编器将汇编代码转为二进制目标代码,p1.o和p2.o
4.连接器将2个目标代码文件和库函数的代码合并,生成可执行代码p

3.2.1 机器级代码

指令集体系结构(ISA):定义了处理器状态、指令的格式、指令对状态的影响
机器级程序使用的存储器地址是虚拟地址,操作系统将虚拟地址转化为物理地址

3.2.2 代码示例

int accum = 0;int sum(int x, int y){        int t = x + y;        accum += t;        return t;}

gcc运行编译器,产生一个汇编文件code.s

unix$ gcc -o1 -S code.c -o code.s

产生一个二进制文件,无法直接查看

unix$ gcc -o1 -c code.c -o code.o

反汇编器:

unix$ objdump -d code.o

macos下可以用tool代替:

macos$ otool -tV code.o

生成实际可执行的代码需要一组目标代码运行连接器,这一组目标代码必须包含main函数

int main(){    return sum(1, 3);}
unix$ gcc -o1 code.o main.c -o prog

进行反汇编

macos$ otool -tV prog

下面给出MacOS和Linux下,system.s:
MacOS:
MacOS

Linux:
Linux

3.3 数据格式

movb(传送字节)、movw(传送字)、movl(传送双字)

3.4 访问信息

IA32cpu包含一组8个存储32位的寄存器,8个寄存器一%e开头。大多数情况下,前6个寄存器可以看为通用寄存器,后2个寄存器保存着指向程序堆栈中重要位置的指针。

IA32寄存器

3.4.1 操作数指示符

指令包含一个或多个操作数,源操作数:立即数、寄存器、存储器,目的操作数:寄存器、存储器。‘$’后一个用标准c表示法表示的整数表示立即数,如$-577或$0x1F。%eax 表示32位寄存器,%ax 表示16位寄存器,%ah或%al 表示8位寄存器。
寻址方式:
寻址方式

练习题3.1
练习题3.1

答案:0x100、0xAB、0x108、0xFF、0xAB、0x11、0x13、0xFF、0x11

3.4.2 数据传送指令

数据传送指令
MOV S, D
PS:源操作数和目的操作数不能同为存储器。

// Assume initially that %dh = 0xCD, %eax = 98765432movb %dh, %al                       %eax = 987654CDmovsbl %dh, %eax                    %eax = FFFFFFCDmovzbl %dh, %eax                    %eax = 000000CD

pushl和popl:
堆栈

堆栈指针%esp保存栈顶元素的地址。

练习题3.2
练习题3.2

答案:

1   movl %eax, (%esp)2   movw (%eax), %dx3   movb $0xFF, %bl4   movb (%esp, %edx, 4), %dh5   pushl $0xFF6   movw %dx, (%eax)7   popl %edi

练习题3.3
练习题3.2

答案:

1   movb $0xF, (%bl)       ->      源操作数是4位,目的操作数不知道大小2   movl %ax, (%esp)        ->      源操作数是1字,move是双字3   movw (%eax), 4(%esp)    ->      源操作数是1字,目的操作数是双字4   movb %ah, %sh           ->      没有%sh的寄存器5   movl %eax, $0x123      ->      目的操作数是立即数6   movl %eax, %dx          ->      源操作数是双字,目的操作数是17   movb %si, 8(%ebp)       ->      %si是1个字的大小,用movb不合理

3.4.3 数据传送示例

过程体:由c代码产生的汇编代码,有一部分作为程序入口为运行栈分配空间和在过程返回前回收栈空间的代码,除此之外的代码称为过程体。

过程体

练习题3.4
练习题3.4

答案:

1   movsbl %al, (%eax)2   movsbl %al, (%eax)3   movzbl %al, (%eax)4   movb %al, (%eax)5   movb %al, (%eax)6   movl %al, (%eax)

练习题3.5
练习题3.5

答案:

void decode1(int *xp, int *yp, int *zp){    int tempx = *xp;    int tempy = *yp;    int tempz = *zp;    *yp = tempx;    *zp = tempy;    *xp = tempz;}

3.5 算数和逻辑操作

加载有效地址、一元操作、二元操作、移位

3.5.1 加载有效地址

加载有效地址指令leal是movl的变形,目的操作数必须是寄存器。
整数算数操作

练习题3.6
练习题3.6

答案:
x+6、x+y、x+4y、9x+7、4x+10、x+2y+9

3.5.2 一元操作和二元操作

练习题3.7
练习题3.7

答案:

目的 值 (0x100) = 0x1 + 0xFF 0x00 (0x104) = 0xAB - 0x3 0xA9 (0x10C) = 0x3 * 0x11 0x33 (0x108) = 0x13 + 1 0x14 %ecx = 0x1 - 1 0x00 %eax = 0x100 - 0x3 0x97

3.5.3 移位操作

练习题3.8
练习题3.8

答案:
sall $2, %eax
sarl %ecx, %eax

3.5.4 讨论

讨论

汇编代码和C语言代码的顺序不同。

练习题3.9
练习题3.9

答案:
x ^ y
t1 >> 3
~t2
t3 - z

练习题3.10
练习题3.10

答案:
A. 将寄存器%edx置0
B. movl $0, %edx
C. 汇编和反汇编这段代码,xor的版本只需要2个字节,而movl的版本需要5个字节

3.5.5 特殊的算术操作

特殊的算术操作

练习题3.11
练习题3.11

答案:

1   movl 8(%ebp), %eax2   movl %edx, %edx3   divl 12(%ebp)4   movl %eax, 4(%esp)5   movl %edx, (%esp)

练习题3.12
练习题3.12

答案:
A. 大小为64位的整型数据,应为long
B.

1   %eax = x2   %ecx = y的高323   %ecx = x * y的高324   %edx:%eax = y的低32位 * x5   %edx = %ecx + %edx  // 即将低32位乘法的进位和高32位成法结果相加6   %ecx = dest7   (%ecx) = 积的低328   (%ecx + 4) = 积的高32

3.6 控制

利用jump指令,可以控制一组机器代码的执行顺序。

3.6.1 条件码

条件码寄存器:
CF:进位标示。无符号操作数是否溢出。
ZF:零标志。最近的操作得到的结果为0。
SF:符号标志。最近的操作得到的结果为负数。
OF:溢出标志。有符号操作数是否溢出。

比较和测试指令

CMP和TEST指令仅会改变条件码寄存器,不更改其他寄存器。

3.6.2访问条件码

条件码不能直接读取,1)根据条件码的某个组合,将一个字节设置为0或1;2)条件跳转到别的部分;3)有条件的传送数据。

访问条件码

练习3.13
练习3.13

答案:
A. int <
B. short >=
C. unsigned char <
D. long !=

练习题3.14
练习题3.14

答案:
A. long !=
B. short/unsigned short ==
C. char >
D. unsigned short >

3.6.3 跳转指令及其编码

跳转指令

间接跳转是使用寄存器%eax中的值作为跳转目标。

练习题3.15
练习题3.15

答案:
A. je指令的目标为0x8048291+0x05,即0x8048296

804828f:    74 05                   je      80482968048291:    e8 1e 00 00 00          call    80482b4

B. jb指令的目标为0x8048359-25(-25的补码为e7),即0x8048340

8048357:    72 e7                   jb      80483408048359:    c6 05 10 a0 04 08 01    movb    $0x1, 0x804a010

C. je指令的目标为0x8048391,则mov指令的地址为0x8048391-0x12,即0x804837f。又因为74、12占2字节,则je指令的地址为0x804837f-2,即0x804837d

804837d:    74 12                   je      8048391804837f:    b8 00 00 00             mov     $0x0, %eax

D. 以相反的顺序读取这些字节,则偏移量为0xffffffe0(-32),则0x80482c4-32,即0x80482a4

80482bf:    e9 e0 ff ff ff          jmp     80482a480482c4:    90                      nop

E.

3.6.4 翻译条件分支

翻译条件分支

C语言中if-else的模版:

if (test-expr)    then-statementelse    else-statement

汇编通常会使用下面的形式:

    t = test-expr    if (!t)        goto false    then-statement    goto donefalse:    else-statementdone:

练习题3.16
练习题3.16

答案:
A.

void cond(int a, int *p){    if (p == 0)        goto done;    if (a <= 0)        goto done;    *p += a;done:    return;}

B. 第一个条件分支是&&表达式实现的一部分。如果对p为非空的测试失败,代码会跳过对a>0的测试。

练习题3.17
练习题3.17

答案:
A.

int absdiff(int x, int y){    int result;    if (x < y)        goto true;    result = x - y;    goto done;true:    result = y - x;done:    return result;}

B.

int absdiff(int x, int y){    int result;    if (x < y)        goto true;    if (x >= y)        goto false;true:    result = y - x;    goto done;false:    result = x - y;    goto done;done:    return result;}

练习题3.18
练习题3.18

答案:

int test(int x, int y){    int val = x ^ y;    if (x < -3)    {        if (y < x)            val = x * y;        else            val = x + y;    } else if (x > 2)        val = x - y;    return val;}

3.6.5 循环

  1. do-while循环
    通用形势:
do    body-statement    while (test-expr)

可以翻译成

loop:    body-statement    t = test-expr    if (t)        goto loop

do-while

练习题3.19
练习题3.19

答案:
A. 14

#include <iostream>#define NUM_SIZE 100int main(void){    using namespace std;    int num[NUM_SIZE];    num[0] = 1;    for (int i = 1;; i++)    {        num[i] = num[i - 1] * i;        cout << i << ":" << num[i] << endl;        if (num[i] < num[i - 1])                break;    }return 0;}

B. 20

#include <iostream>#define NUM_SIZE 100int main(void){    using namespace std;    long long int num[NUM_SIZE];    num[0] = 1;    for (int i = 1;; i++)    {        num[i] = num[i - 1] * i;        cout << i << ":" << num[i] << endl;        if (num[i] < num[i - 1])            break;     }    return 0;}
0 0
原创粉丝点击