配置基于容器的的汇编环境
使用docker
和alpine image
来做汇编的开发实验环境是很方便的,因为docker
默认提供的是一个x86-64
架构的Linux kernel,所以用来编译运行64bit的汇编代码十分方便。本文介绍使用方法。首先是从docker hub上面把alpine linux
这个image给pull下来,然后运行sh
:
$ docker run -it alpine sh
命令执行过程以及结果:
可以看到我们下载了alpine
并且创建了容器,并在容器内部运行了sh
。之所以要使用alpine
这个linux发行版,是因为它非常小,只有5.53mb
:
进入到alpine linux
里面以后,把vim
和yasm
安装好:
$ apk add vim yasm
下面是安装过程:
其中,vim
是常用的文本编辑器,yasm
是编译汇编代码的工具。此外,我们还要安装gcc
:
$ apk add gcc
安装gcc
的目的是为了把汇编与c相关的工具集都安装好。gcc
的工具包比较大,所以下载安装过程需要耐心等待:
以上是环境的准备过程,接下来我们写一段汇编代码试试看:
global _start
_start:
mov eax, 1
mov ebx, 5
int 0x80
上面的汇编代码就是简单地执行syscall
(通过调用0x80
中断)的exit
,然后返回值设定为5
。我们把它保存为foo.asm
,然后编译:
$ yasm -f elf64 -g dwarf2 -l foo.lst foo.asm
编译完成后会生成.lst
和.o
文件;
$ ls
foo.asm foo.lst foo.o
其中lst
为符号文件:
$ cat foo.lst
1 %line 1+1 foo.asm
2 _start:
3 00000000 B801000000 mov eax, 1
4 00000005 BB05000000 mov ebx, 5
5 0000000A CD80 int 0x80
如上所示,lst
文件标注了汇编代码对应的机器码和内存的offset
,这个文件是给人看的,也可以不生成。所谓内存offset,就是代码的相对地址,而不是绝对地址。绝对地址要在代码被link成可执行代码后,由linker
负责分配。linux下的linker
一般是ld
这个命令,我们来使用它把.o
文件给链接成可执行代码;
$ ld -o foo foo.o
此时我们就得到了foo
这个可执行文件。此时运行foo
,并查看它的返回值:
可以看到程序的返回值为5
。此时我们使用objdump
命令查看生成的可执行文件的汇编代码:
$ objdump -Mintel -d foo
在上面的命令当中,-Mintel
是让objdump
显示intel格式的汇编代码,默认是at&t
格式的汇编代码,个人习惯看intel
格式的。然后-d
选项是显示程序的汇编代码。它和大写-D
选项的区别如下:
-d, --disassemble Display assembler contents of executable sections
-D, --disassemble-all Display assembler contents of all sections
可以看到,-d
只显示主程序的sections的汇编代码,而-D
显示所有的sections的代码,包括debug
信息等等一大堆sections。下面是objdump
的执行结果:
可以看到,编译后的代码的.text
程序段,它的汇编代码基本就是我们手写的汇编代码,只不过是程序的内存地址被分配好了逻辑地址,这就是linker
帮我们做的。比较一下.lst
文件里的机器码和内存地址offset:
可以看到机器码完全一致,然后内存地址的offset关系一致,只不过编译链接后的代码的地址变成了process的逻辑地址。此外,我们可以看到直接手写的汇编代码,编译出来的代码量非常小,几乎就是对应我们手写的汇编代码。而c
编译出来的代码,汇编指令会复杂很多,因为它包含了标准c库的很多接口指令,而且是c语言标准库libc
负责接管跟linux kernel打交道,而不是像我们这样使用int 0x80
的kernel system call直接调用内核接口。
以上就是对基于容器的汇编环境的使用介绍。接下来回过头讲讲这个容器。我们一开始是用docker run
指令下载了alpine
的image并且创建了一个容器并执行。但是我们后续希望继续使用这个容器,而不是再用run
命令创建一个新的容器。所以后续再启动这个容器的时候,就要用docker start
命令。首先是查看这个容器的id
:
$ docker ps -a
下面是执行结果:
可以看到这个容器的id
为c27a1e74e773
,容器的名字为sleepy_wu
。我们可以使用容器的id
或者name
来启动或停止这个容器。比如停止这个容器:
$ docker stop sleepy_wu
启动这个容器:
$ docker start sleepy_wu
启动这个容器以后,使用exec
命令登录进这个容器的终端:
$ docker exec -it sleepy_wu sh
以上这些命令的执行全过程如下:
以上就是初步需要掌握的一些知识点。
- 上一篇 卖酒的算法题解
- 下一篇 gdb在docker容器里面的使用