徐晨 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
本周我们的实验是“使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用”,系统调用列表列出了x86-32下的系统调用名称及中断号的对应关系。
我们在本试验中选用了文件读写相关的系统调用,包括打开文件open(系统调用号5),关闭文件close(系统调用号6),读文件read(系统调用号3),以及写文件write(系统调用号4)。我们用这四个函数实现了一个文件拷贝的过程,该文件从Linux/Unix系统编程手册[0]中的示例代码修改而来。
程序原理是打开准备拷贝的文件,同时创建一个新文件,循环从源文件中读取数据,写入到新文件中,读写完成后关闭两个文件。
首先我们用库函数来使用系统调用:
我们编译并测试一下该程序:
可以看出来,我们用copy复制syscall.txt到res.txt,然后用diff命令检测了两个文件的一致性,测试证明该程序运行正常。
接下来我们利用C代码嵌入汇编来使用系统调用,我们这里实现了库函数的相应版本,以函数前加下划线来指明我们自己写的封装。我们先看一下库函数的接口,便于我们直接模仿:
<span style="font-size:14px;"> int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
int close(int fd);
ssize_t write(int fd, const void *buf, size_t count);</span>
这些函数的定义我们可以在man page中找到
我们用汇编语言重写的函数如下:
open:因为该函数由两种参数列表,所以我们将其写成两个函数,最后我们会说明如何将其写成同名函数,即实现所谓的"重载"。(注意,这里所说的重载只是意义上的,并不是C++中的重载概念)。
"重载"open
write and read
close:
main函数:我们在这里封装了汇编函数,所以main函数和调用C库的main完全一样,只是替换了相应函数名(前面加下划线)
这个过程十分简单,只需要将参数装入相应的寄存器,向eax中传递系统调用号,然后执行int 0x80进入系统调用即可,待调用完成后,我们从eax中读取返回值。
这里面有几个问题需要注意:
0. 寄存器的使用如下表所示
Syscall # | Param 1 | Param 2 | Param 3 | Param 4 | Param 5 | Param 6 |
---|---|---|---|---|---|---|
eax | ebx | ecx | edx | esi | edi | ebp |
Return value |
---|
eax |
2. 这里的参数顺序和库函数的参数顺序是一致的,并不是相反顺序,之前一直认为应该以相反的顺序存放参数,导致程序一直错误,查到了相关资料后[1],修改顺序,程序运行正确。
3. 关于open函数,我们在man page中可以看到,该函数由两种形式,好像重载了一样,经过查阅资料,发现可以用va_args来实现该功能。关于这一点,可以参见 这里[2]。
最后,我们来编译并测试一下汇编版本的程序:
这一节我们就分享到这里,希望对读者有所帮助。
参考资料:
[0]. Linux/UNIX系统编程手册
[1]. Professional assembly language
[2]. 如何实现 C 的函数重载