一. 简介
最近学习了一些C内联汇编的知识,简单的尝试了一下,发现里面有很多有趣的东西,分享如下。首先说明,本文的内联使用仅为一个小例子,并不规范,另外32位和64位有着较大区别,本文基于64位Ubuntu16.04环境编译。
二. 源码介绍
代码本身很简单,包括了三个函数,分别用于打印,退出以及作为函数入口,实际效果为输出Hello world
。
2.1 print()
打印函数printf()
本身其实是调用了write()
函数(也可以调用puts()
等),这里通过80中断直接调用write()
对应的系统调用。write()
的API如下所示
1 | int write(int fd, char *buffer, int size); |
- 由于系统调用号(查中断标可得)为4,所以直接传递给
rax
赋值4即可。 fd
即文件描述符,这里使用标准输出,即stdout
,所以fd
为0,rbx
赋值为0buffer
即文件缓冲区,这里就是str
字符串,使用rcx
传递,赋值为地址对应的变量即0地址size
表示的时文件字节数,这里是13字节,因此rdx
赋值为13
2.2 exit()
exit()对应的系统调用为exit()
,系统调用号为1,所以rax
赋值为1。rbx
表示返回值,这里42其实是一个神奇的数字,有兴趣的可以看一看《银河系漫游指南》(大雾)。
1 | //hello.c |
注意,本代码为64位编译环境,如果是32位需要改为movl
配合ebx/ecx
系列或者编译时加上-m32
选项。
三. 编译和执行
使用如下命令编译和链接,生成可执行文件。这里-fno-builtin
表示不进行编译器优化。否则对于纯字符串的printf()
,编译器会自动优化为使用puts()
函数。另外链接时,-e hello
表示入口函数为hello()
,而非常见的main()
函数。
1 | gcc -c -fno-builtin hello.c |
运行hello可见如下结果,输出成功,返回值为预设的42。
1 | ./hello |
四. 自制lds
通过objdump
可以看到hello
包括了.data, .text, .rodata
等字段,同时还有.comment
。这里我们尝试自己制作链接脚本,使这三个字段合并,同时去掉.comment
字段,这样子应该可以让可执行文件的大小大大缩减。
ENTRY
表明了入口函数为hello
函数
1 | ENTRY(hello) |
再次编译
1 | ld -static -T hello.lds -o hello hello.o |
编译完依然可以正常执行,通过objdump
可见已经生效,可执行文件的大小从1.6KB
成功缩减到704B
,所以就算是成功了。实际还可以通过-s
再次减小大小。
总结
本文基于中断,汇编,编译,链接等知识,进行一次综合性的上手小实验,适合新人们做为入门级小尝试来步入学习。另外内联汇编本身较为复杂,本文进行了大量简化,实际在源码及工业级运用会需要考虑更多的安全性等因素,同时80中断实际并不适合在64位使用,这个回头会再写一篇文章专门叙述。鉴于水平有限,还望大佬们多多指点不足之处,共同进步。