实验环境:
Ubuntu 18.04.2 LTS
实验过程:
紧接实验三,当我们配置好环境之后,就可以使用 gdb 对 Socket 中的函数和系统调用函数进行跟踪分析了。
(1)如果实验三顺利完成的话,你应该有如下的目录结构:
在此目录环境下输入以下命令:
开启 MenuOS。
(2)重开一个终端,输入 gdb 进入调试界面,然后分别输入以下命令,如下图所示:
这样就建立了 gdb 和 gdbserver 之间的连接。
(3)在 MenuOS中输入 help 会发现有 replyhi 命令和 hello 命令,我们进入 net/lab3 下的 main.c 文件可以找到对应的实现,然后我们会发现这些实现里面的函数都是封装好的定义在 syswrapper.h 文件中的,
这些封装好的宏函数才是真正调用了 Socket接口提供的函数。先来看一下 replyhi 命令的实现,其中一部分的函数的调用顺序是 Replyhi()->InitializeService()->PrepareSocket(),PrepareSocket()函数的实现是:
可以看到调用了Socket的接口函数socket(),那么它对应的系统调用函数呢?我们进入 linux-5.0.1/net 下打开 socket.c 文件,在里面搜索发现如下函数:
经过查阅知道,Linux的系统调用都改为SYSCALL_DEFINE定义的了,这里的 SYSCALL_DEFINE3 中的3代表的是系统调用的参数个数,注意这里可以看到 SYSCALL_DEFINE3中返回了系统调用 __sys_socket()的结果。
对于 SYSCALL_DEFINE3,它是定义在 linux-5.0.1/include/linux/syscalls.h文件中的宏函数,具体如下:
其实 SYSCALL_DEFINEx函数内部还会调用一些其他函数,但最终还是会回到调用系统函数 __sys_socket()上,这样做的具体原因请看:
https://blog.csdn.net/hxmhyp/article/details/22699669。
(4)经过上一步的操作,我们可以找出许多系统调用函数,比如 __sys_bind、__sys_listen、__sys_connect、__sys_close等。通过查阅 Replyhi()函数和 Hello()函数的实现,可以发现 Replyhi()调用了 socket、bind、listen,Hello()调用了 socket、connect等。
因此分别在这些系统调用函数上设置断点。
上图是先输入replyhi命令,然后发现在 gdb 中就依次进入了相应的系统调用函数其实就是对应 Replyhi()函数中调用Socket接口函数的顺序,最后是调用 listen函数,因为是在一直监听,所以此时并未退出断点,我们手动退出,然后重新
设置socket、connect接口函数所对应的系统调用函数的断点。
可以看到,对于 Hello()函数也成功跟踪了其中的系统调用。
(5)其实在上一步,我们会在 socket.c 文件中找到另外一个函数:
上图是经过删减的一部分,可以发现其中的参数 call 就是操作码,通过它,我们决定对应的系统调用函数,这些操作码定义在 linux5.0.1/include/uapi/linux/net.h 文件中:
如果按照之前的socket对应__sys_socket的规则,那么socketcall对应的系统调用函数应该是 __sys_socketcall,但是在设置断点的时候,无论是 __sys_socketcall 还是 sys_socketcall 都是无法无法设置断点的,它会提示
函数未定义的错误。但是这个 socketcall 函数应该就是所有关于 Socket接口函数的总入口啊,我们查看 syscalls.h文件:
可以发现该函数已经被标记为过时了。
此时,我们打开与 socket.c 文件处于相同目录下的 compat.c 文件,搜索发现:
这应该是对32位兼容的版本,我们尝试对此设置断点:
发现确实可以。但是当我们尝试输入 replyhi 命令和 hello 命令以对该断点进行捕捉时,却不能捕捉到,因为我们关于 Socket接口函数的调用默认是通过64位下的 __sys_xxx等,并不通过 __ia32_compact_sys_socketcall这一总入口。
不过我们可以重新编译生成32位下的rootfs,然后再进行跟踪:
此时,所有对Socket接口函数的调用就都通过__ia32_compat_sys_socketcall这个总入口了。
如果在每个断点处单步进去的话就会发现确实调用了Socket接口函数对应的系统调用函数:
(6)对于32位系统是通过 int 0x80来触发系统调用的,而对于64位系统,则改为了 syscall。
在 linux-5.0.1/arch/x86/entry/entry_64.S 文件中可以找到如下说明:
具体流程应该是:start_kernel --> trap_init --> cpu_init --> syscall_init
当用户态程序发起系统调用时,对于x86-64位程序应该是直接跳到entry_SYSCALL_64。另外在和 entry_64.S 文件同样目录下的 syscalls 文件夹下还可以找到 syscall_64.tbl,里面定义了系统调用号:
可以发现和我们之前设置断点时使用的 entry ponint 的名字有些不同,多了__x64前缀,我们比较一下:
发现确实可以,而且发现之前报未定义错误的 __sys_socketcall 改为 __x64_sys_socketcall 就可以了。虽然可以在 __x64_sys_socketcall处设置断点,但是当我们输入命令 replyhi 和 hello 时,gdb却无法捕捉到,这就和我们之前的一样了,也就是在64位下系统调用并没有通过 __sys_socketcall 这一总入口,不知道是不是理解错了。