刘子健 +
原创作品转载请注明出处 +
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”
----------------------------------------------------------------------------------------------------------------------------------------
首先,特别感谢孟宁老师提供了这么好的课程资源.
同样对所有contribute to Mykernel的前辈致敬!
先推荐本书吧 <<LKD>> Linux kernel development -- by Robert Love
In any modern operating system, the kernel provides a set of interfaces by which processes running in user-space can interact with the system.These interfaces give applications controlled access to hardware, a mechanism with which to create new processes and communicate with existing ones, and the capability to request other operating system resources.
The interfaces act as the messengers between applications and the kernel, with the applications issuing various requests and the kernel fulfilling them (or returning an error).The existence of these interfaces, and the fact that applications are not free to directly do whatever they want, is key to providing a stable system.
本文打算扒一扒大家熟悉的一些库函数,比方说printf(及时间再扒别的)
Typically, applications are programmed against an Application Programming Interface (API) implemented in user-space, not directly to system calls.This is important because no direct correlation is needed between the interfaces that applications make use of and the actual interface provided by the kernel. An API defines a set of programming interfaces used by applications.
在"大探险"之前,先讲讲我们的"目的地" -- 系统调用 system call
在include/linux/syscalls.h 里面你会看到有这么个宏定义.
这是链接系统调用和库函数的关键宏定义
开扒
...
骚年,千万不要浪费了三个小时去看内核里面的那个printf...我去...
你调用的是C标准库的函数啊!!不是内核函数啊! 虽然内核也实现了...
但是...我们还是按照实际的调用路线一层层的扒吧....
没有GNU libc代码的去download一份.
http://www.gnu.org/software/libc/
然后你会看到lib c的目录如下.
这里还有个很爽的工具, strace 命令. 可以看看下面这个链接, 怎么玩strace
http://blog.csdn.net/cinmyheart/article/details/38945847
但是strace也只能大概的跟踪到调用了哪些动态库,以及系统调用API....不过话说这已经很不错了.
认真分析下面的这幅贴图!!
a.out就是一个很简单的hello world可执行程序. 会发现, 首先调用了API, execve去执行这个可执行文件,
接着调用brk()申请了0块大小的堆, 于是,这里堆地址在0x73f00.
后面access这里我不是很清楚,应该是检查链接动态库的权限是否OK
然后mmap进行内存的映射 .
重温一下mmap():
#include <sys/mman.h> void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); int munmap(void *addr, size_t length)
mmap() creates a new mapping in the virtual address space of the calling process. The starting address for the new mapping is specified in addr. The length argument specifies the length of the mapping.
然后各种分析同上面的.
不再赘述.
实质上, 最后一次write的调用会把hello world字符串写入到标准输出(1, 用来表示标准输出流)
OK~ 我们继续深扒..
从程序main函数入口开始, 开始调用printf
这由于printf没有其他参数,仅仅就一个字符串做参数, 会被编译器优化成puts()
[不相信偶的话, 你可以自己检验, 写两个版本的printf, 一个有变长参数,一个没有. 去用gdb单个指令调试 si, 你会发现printf进去的时候, 会跳转到puts, 而不是printf]
然后你会发现, 这里puts会跳转到0x400400.这个地址在 .text 文本段内(text segment : 0x40 0000 起始)
这里出现??是因为开始切换堆栈, 在很段很短的时间内, stack base pointer 和 stack pointer(rsp, esp 所谓的栈顶指针)
被什么原因故意"破坏", 这里望路过的高手指导, 究竟是处于什么原因
因为在系统刚启动的时候, 没有那个时候堆栈都没有建立起来, 会出现??(), 一旦建立好了堆栈,就会有具体的表示 in 函数名.
而后程序会执行到 ./sysdeps/x86_64/dl-trampoline.S (gnu libc库的路径)
我们一直si ... 这是个极其漫长的过程... 直到我们看到hello world的输出.
你会发现, hello world的输出和system-template.S的第81行代码有关系
这是个宏定义, T_PSEUDO被定义为PSEUDO
然后找到PSEUDO
你就会发现我们离触发系统调用越来越近, 越来越明显清晰
看看ENTRY宏定义在这里都是什么
啊哈, 定义了系统调用入口处的对齐情况, 还有ACCESS权限, 等等信息.
我们的目标是系统调用, so 这部分看看就好...
我们找到DO_CALL定义, 其实就是把syscall_name标记的地址赋值给%rax寄存器.然后
触发指令syscall.
都到这一步了, 我们也"触底"了
我们可以利用backtrace命令去查看我们打印hello world之前都调用了哪些函数.在哪些文件里面.
关键的有
__write_nocancel()
_IO_new_file_write()
_IO_new_do_write()
_IO_new_file_overflow()
_IO_puts()
我们可以看到最后的_IO_puts()里面的_IO_
我们反汇编当前刚打印hello world的代码. 你会发现, 我们处于write函数中.write写向哪儿呢? 触发的系统调用是什么呢?
看! mov $0x1, %eax
然后调用了指令syscall, 说明此时调用了1号系统调用! 而1号系统调用是sys_write.
可能你之前有了解,系统调用1并不是write. 看了<asm/unistd.h>
而我看到LKD上面系统调用的定义又和这里不同, 可能是不同版本内核不同的原因.
好了, 开始自己添加个系统调用.
使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用
选择一个系统调用(13号系统调用time除外
表忘记课程要求. 自己得写一个玩具
先抛粗来吧... 待更新 ...