汇编——前置概念

写在前面:从腾讯实习回来之后,就感觉到自己的知识体系过于散乱。于是萌生了写一个自己的操作系统这样的心思,此为系列第一章,主要是讲解一些汇编知识的,内容大多从CSAPP中也可以获得。

查看程序的汇编代码

这里先放一个最简单的程序代码:

#include <stdio.h>

int main()
{
    printf("hello world");
}

我们可以看到,这个是由高级语言写成的,大抵是符合我们人类阅读习惯的,但是机器确实是不认识。于是就需要将这个代码翻译成01二进制的格式去交由计算机去运行。而在整个程序,或者说是C语言中,还有一个汇编的阶段存在着。我画了大致的一张图,方便理解:
在这里插入图片描述
对于这个过程感兴趣的同学可以去阅读我写过的程序员的自我修养专栏,其中有对高级语言到二进制代码转换过程的详细解释。

那么,问题来了。我们怎样去查看这些汇编代码,打开神秘的底层黑盒呢?

我们可以用以下命令去生成汇编代码:

g++ -S test1.cpp

这个会在当前文件生成test.s文件,其本质是个文本文件,我们可以打开并查看其中内容(内容太多,这里就放主要的了)

...
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp	
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp	
	.cfi_def_cfa_register 6
	leaq	.LC0(%rip), %rdi
	movl	$0, %eax
	call	printf@PLT
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
...

:看不懂没关系,可以先跳过,后面会逐个讲解这些内容的

上面的每一个缩进行都对应着一个机器指令。例:

//将%rbq的内容压入程序栈
pushq	%rbp

如果我们使用-c 命令,gcc则会汇编这些代码:

gcc -c test1.s

这个命令会在当前路径下生成一个test.o这么一个目标文件,同时我们可以用以下命令去查看这个.o文件中的内容:

/*
* -x:表示十六进制输出
*/
od -x test1.o
//(部分内容)
0000000 457f 464c 0102 0001 0000 0000 0000 0000
0000020 0001 003e 0001 0000 0000 0000 0000 0000
0000040 0000 0000 0000 0000 02d0 0000 0000 0000
0000060 0000 0000 0040 0000 0000 0040 000d 000c
0000100 4855 e589 8d48 003d 0000 b800 0000 0000
0000120 00e8 0000 b800 0000 0000 c35d 6568 6c6c
0000140 206f 6f77 6c72 0064 4700 4343 203a 5528

可以看到其是一串对我们而言没有意义的 “机器语言”
当然,如果我们想要翻译机器语言的话,我们可以用一类称之为反汇编器的程序来查看它,可以用到以下命令:

/*
* -d:查看反汇编结果
*/
objdump -d test1.o
test1.o:     文件格式 elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 8d 3d 00 00 00 00    lea    0x0(%rip),%rdi        # b <main+0xb>
   b:   b8 00 00 00 00          mov    $0x0,%eax
  10:   e8 00 00 00 00          callq  15 <main+0x15>
  15:   b8 00 00 00 00          mov    $0x0,%eax
  1a:   5d                      pop    %rbp
  1b:   c3                      retq   

汇编中的数据格式

由于历史原因,Intel使用来表示16位数据类型,而不是我们现在常用的字节。所以我们现在常用的字节其实也就是半个字罢了。下图是常用的一些数据大小,其中汇编代码后缀部分(GAS)很重要!!!
在这里插入图片描述
就像我们刚刚举例的汇编代码一样:

movq	%rsp, %rbp	

在每个gcc生成的汇编代码指令都有一个字符的后缀吧,表面操作数大小,movq就表示后面跟的操作数的大小是4字,即64位。

寄存器信息

一个x86-64的CPU包含一组16个存储64位值的通用目的寄存器,这些寄存器用来存储整数数据指针。 下表中详细展示了这些寄存器的信息:

64位32位16位8位作用
%rax%eax%ax%al存储返回值信息
%rbp%ebp%bp%bpl被调用者保存
%rbx%ebx%bx%bl被调用者保存
%r15%r15d%r15w%r15b被调用者保存
%r14%r14d%r14w%r14b被调用者保存
%r13%r13d%r13w%r13b被调用者保存
%r12%r12d%r12w%r12b被调用者保存
%r11%r11d%r11w%r11b调用者保存
%r10%r10d%r10w%r10b调用者保存
%r9%r9d%r9w%r9b第六个参数
%r8%r8d%r8w%r8b第五个参数
%rcx%ecx%cx%cl第四个参数
%rdx%edx%dxdcl第三个参数
%rsi%esi%si%sil第二个参数
%rdi%edi%di%dil第一个参数
%rsp%esp%sp%spl栈顶指针

刚刚明明说总共只有16个寄存器,但是表格中总共有16*4=48个寄存器,这是为什么呢?

这是因为对于不同大小的操作数,是没有必要用到整个寄存器的。比如说一个一字节的操作数,我就要把它占用整个%rdi么,这就可以用%dil这个寄存器。它其实也就是%rdi的一部分,所以还是16个寄存器啦。

那么这里又导致了一个问题,对于生成小于8字节结果的指令,寄存器中剩下的那些字节会怎么样呢?
对此有两个规则:

  • 生成一字节和两字节的指令会保持剩下的字节不变
  • 生成4字节数字的指令会把高位4字节置为0


我们拿ax寄存器举个例子,它是16位寄存器。是由两个8位寄存器ahal组成的。其中的低8位是al寄存器;高8位,是ah寄存器。
在这里插入图片描述

操作数格式

大多数的指令有着多个操作数,指示出一个操作中要使用的源数据值,以及放置结果的目的位置。源数据值可以以常熟的形式给出,或者从寄存器或者内存中取出,结果当然也能够放进去。

因此,各种不同的操作数的可能性被分为三种:

  • 立即数:用来表示常数值
  • 寄存器:用来表示存放在寄存器中的内容
  • 内存引用:根据计算出来的地址访问某个内存位置

下图中给出了操作数格式及对应关系:
在这里插入图片描述
这个东西很重要,务必掌握!
下面是CSAPP中的原题,觉得自己能了可以先做一下,后面有答案:
在这里插入图片描述

CSAPP课后习题答案

在这里插入图片描述

参考文献

[1] 深入理解计算机系统 第三章 程序的机器级表示
[2] 操作系统真相还原
  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shenmingik

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值