c语言语句的机器级表示实训,csapp实践(二):程序的机器级表示

66b52468c121889b900d4956032f1009.png

8种机械键盘轴体对比

本人程序员,要买一个写代码的键盘,请问红轴和茶轴怎么选?

csapp第三章中,对汇编指令的要求比较高

这里把常见的指令整理一下1g++ -Og -S -masm=intel xxx.cpp

得到反汇编文件查阅

程序编码

代码示例和解释:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22#include

#include

using namespace std;

long mult2(long a, long b) {

return a*b;

}

void multstore(long x, long y, long* des) {

long t = mult2(x, y);

*des = t;

}

int () {

long a = 100, b = 150;

long dest;

multstore(a, b, &dest);

cout << dest << endl;

}

反汇编代码示例:1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23__Z9multstorellPl: ## @_Z9multstorellPl

.cfi_startproc

## %bb.0: ## rdx第三个参数,rsi第2个参数,rdi第一个参数

pushrbp## rbp为基指针寄存器(base pointer),存取调用堆栈中的数据

.cfi_def_cfa_offset 16

.cfi_offset rbp, -16

movrbp, rsp## rsp为堆栈指针(stack pointer)寄存器,只可访问堆栈顶

.cfi_def_cfa_register rbp

pushrbx## rbx操作数寄存器,用来存放运算结果

pushrax## 用来临时存放函数mult2()的返回值,rax一般都是来存放返回值的

.cfi_offset rbx, -24

movrbx, rdx## rdx是第三个参数,copy dest to rbx, 最后*dest = mult2(x, y), 要先把dest存储在寄存器中,稍后访问

call__Z5mult2ll

movqword ptr [rbx], rax## 运行的结果,将mult2(x, y)存入[rbx]即*dest中

addrsp, 8

poprbx ## 可以这么理解: rbx = rax, rbx may be the left value, rax may be the right value, 一般来说, rax是返回值

poprbp## restore rbp

ret

.cfi_endproc

## -- End function

.globl_main ## -- Begin function main

.p2align4, 0x90

_main: ## @main

数据传输

寄存器

第一个参数:rdi, 第二个参数:rsi, 第三个参数:rdx, 第四个参数:rcx

汇编器常见的错误1

2movl %rax, %(rsp)## wrong, it is moveq %rax, %(rsp)

movl %eax, %rdx ## 原来应该是movzlq,但是并没有这样的指令,rdx应该对应movq

数据传送示例1

2

3

4

5

6

7char -> int

char类型4字,转int需要符号扩展,4字用movl,这里需要符号扩展

用movsbl

movsbl (%rdi), %eax

movl %eax, (%rsi)1

2

3

4

5

6char -> unsigned

同样4字对4字,需要符号扩展movsbl

movsbl (%rdi), %eax

movl %eax, (%rsi)1

2

3

4

5

6

7unsigned char -> long

0扩展,转成8字存储,操作的是unsigned char,本来用的是movl, 需要0扩展

用movzbl

movzbl (%rdi), %eax;

movq %rax, (%rsi)1

2

3

4

5

6int -> char

操作数是int, 直接用movl即可, int是4字,存放到eax中

movl (%rdi), %eax

movb %al, (%rsi)

char只需要低位的,所以用movb %al, (%rsi)1

2

3

4

5unsigned -> unsigned char

操作数unsigned,4字,movl存放在eax中

movl (%rdi), %eax

movb %al, (%rsi)1

2

3

4

5

6

7char -> short

需要做符号扩展,并且char要截断,取低位,存放在ax中,本来用movw

这里涉及到符号扩展,用movsbw

movsbw (%rdi), %ax

movw %ax, (%rsi)

移位操作

移位量是由%cl寄存器的低m位决定的,高位会被忽略1

2

3

4

5

6

7

8

9

10

11long shift_left4_rightn(long x, long n) {

x <<= 4;

x >>= n;

return x;

}

shift_left4_rightn:

movq %rdi, %rax

salq $4, %rax

movl %esi, %ecx ## get n (4 bytes)

sarq %cl, %rax ## 本来应该是sarq %ecx, %rax, 因为移位操作取的是低位%cl

特殊的算术操作

乘法,乘积低位放在%rax中,高位放在%rdx中

除法,需要用到%rdx寄存器来存放参数,商放在%rax中,余数放在%rdx中

控制

条件控制实现条件分支1setnle D

溢出的处理,用xor,看作是不进位的加法

如果有符号溢出,相应的值要变化,比如

SF: t < 0

溢出的话,要xor OF

实际上SF 0->1

2ad9779ee4e3a0680b11af8319ec5cf9.png

注意跳转指令前面有符号,f8表示符号位是-1,所以是0xd-0x81

2

3

4

5

60xffffff73所代表的跳转值,首先最高位为负

值为8位16进制数

所以最高位为-16^7

-16^7 + 0xfffff73 = -141

即为跳转偏移量

跳转指令翻译1

2

3

4

5

6

7

8

9cmpq xxx1, xxx2

jge .L2

code....

means:

if(xxx2 < xxx1)

code...

else

.L2

用条件传送来实现条件分支1

2

3

4

5

6

7

8testq %rdi, %rdi

cmovns%rdi, %rax

## 实现方法:cmovns表示非负数

## if( >= 0) rax

## 条件中的值为testq %rdi的值,即

if(x >= 0) v = x;

用循环来实现条件分支

for循环1

2

3

4

5

6

7

8

9

10

11fun_a:

movl$0, %eax

jmp.L5

.L6:

xorq%rdi, %rax

shrq%rdi

.L5:

testq%rdi, %rdi

jne.L6

andl$1, %eax

ret

转换成c语言代码,可以发现L5是循环的主体1

2

3

4

5

6

7

8while(%rdi != 0) {

.L6

}

return %eax & 0x1

.L6的实现如下

val ^= x

x >>= 1

该实现如下:1

2

3

4

5

6

7

8long fun_a(unsigned long x) {

long val = 0;

while(x) {

val ^= x;

x >>= 1;

}

return val & 0x1;

}

代码功能:奇数个1, 每一位取出来xor,其值还是1

偶数个1,每一位取出来,其值是0

如果有奇数个1,返回1,偶数个1,返回01

2

3

4

5

6

7

8

9

10

11

12fun_b:

movl$64, %edx

movl$0, %eax

.L10:

movq$rdi, %rcx

andl$1, %ecx

addq%rax, %rax

orq%rcx, %rax

shrq%rdi

subq$1, %rdx

jne.L10

rep; ret

for循环主体1

2

3

4

5

6

7

8

9

10

11

12

13subq$1, %rdx

eax = val = 0;

for(edx = 64; edx != 0; edx--) {

.L10

}

.L10:

val in rax

tmp = x; tmp &= 1; (tmp in rcx)

val << 1; val |= tmp

x >> = 1

综上:1

2

3

4

5

6

7

8

9long fun_b(unsigned long x) {

long val = 0;

long i;

for(i = 64; i != 0; i--) {

val = (x & 0x1) | (val << 1)

x >>= 1;

}

return val;

}

这个代码的作用很有意思

如图所示,创造x的镜像

5a84cc84930e91727b532217210f5b9d.png

switch语句

60acc22faf9625bb460b8f20085c881e.png1

2

3

4

5switch2:

addq$1, %rdi

cmpq$8, %rdi

ja.L2

jmp*.L4(, %rdi, 8)

start: rdi+1

$ x+1 = 0 $

$ x+1 > 8 quad (default) $

$ x+1 < 8 $

$ x = -1, 0, 1, 2, 3, 4, 5, 6, 7 $

函数调用过程

转移控制

函数调用的具体分析如下

217f7a110c5f1491939372480eecc539.png

stack上的局部存储

4747388a941ecab1d278f6050d80196f.png

f8975dd13bee12ba388d181bed7e59f2.png

寄存器中的局部存储空间

4b3796ef6b377d632be995e1393c8f90.png

数组的分配和访问

定长数组

c5aba27b45addbd3007fde7b288aff3e.png

异质的数据结构,联合,结构体

联合1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18typedef union {

struct {

long u;

short v;

char w;

} t1;

struct {

int a[2];

char* p;

} t2;

} u_type;

void get(u_type *up, type* dest) {

*dest = expr;

}

// up in %rdi, dest in %rsi

具体的内存分配如下:

2460527fe196a2fc88d1d0d7839df997.png

数据对齐

e09cdc678aabc1c9092dad9186d7ff2e.png

数据对齐的时候,start位置必须是类型的整数倍

如果不满足,则会填充

这里又一个优化技巧

结构体对数据类型进行降序排列1

2

3

4

5

6

7

8struct P {

char* x1;

long x2;

float x3;

int x4;

short x5;

...

};

指针与缓冲区溢出

587f5d6e2ca30ae4622ef029d2d5337a.png

对抗缓冲区溢出攻击

5eab3ac1b3f388d96d3a8d95757da252.png

支持变长栈帧1

2

3long vframe(long n, long idx, long *q) {

long *p[n];

}

548825738b33058c31c0777b52fb292e.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值