1.结构体ioctl_trans:struct ioctl_trans {unsigned long cmd;ioctl_trans_handler_t handler;struct ioctl_trans *next;};该结构体提供了一个粘合层,用户可以动态注册一个ioctl_trans以便其提供64位和32位的粘合:extern int register_ioctl32_conversion(unsigned int cmd,ioctl_trans_handler_t handler);extern int unregister_ioctl32_conversion(unsigned int cmd);整个系统的ioctl_trans连接成一个哈希表,放在ioctl32_hash_table变量中。每一个ioctl_trans的handler都是一个回调函数,在其中将64位的数据和32位的数据类型进行统一,统一成64位可以正确识别和处理的,以防在后续的64位代码中出错,比如一个32位的signed int为-1,需要将之转化成64个1而不是32个0加上32个1。2.一套完整的系统调用:如果不这样的话,32位程序的系统调用如何被路由到通过ioctl_trans们进行粘合的代码就成了问题,要知道x2021年05月05日已经不使用int 0x80作为触发系统调用的机制了,而使用syscall指令来触发。那么原来的32位程序都是用int 0x80来触发的,这下怎么办?办法就是仍然保留0x80号中断号,将其处理程序设置成ia32_syscall,它在ia32_sys_call_table中找具体的系统调用处理函数,具体在arch/x86_64/ia32/ia32entry.S中:ENTRY(ia32_syscall)CFI_STARTPROCswapgsstimovl %eax,%eaxpushq %raxcldSAVE_ARGS 0,0,1GET_THREAD_INFO(%r10)testl $(_TIF_SYSCALL_TRACE|_TIF_SYSCALL_AUDIT),threadinfo_flags(%r10)jnz ia32_tracesysia32_do_syscall:cmpl $(IA32_NR_syscalls),%eaxjae ia32_badsysIA32_ARG_FIXUPcall *ia32_sys_call_table(,%rax,8) # xxx: rip relative...ia32_sys_call_table:.quad sys_restart_syscall.quad sys_exit.quad stub32_fork.quad sys_read....quad compat_sys_ioctl...在arch/x86_64/kernel/traps.c的trap_init函数中将ia32_syscall设置成0x80号中断的处理程序:set_system_gate(IA32_SYSCALL_VECTOR, ia32_syscall);//#define IA32_SYSCALL_VECTOR 0x80那么使用sysenter的怎么办呢? 这是通过在exec的时候由内核检测到其是32位程序是动态将处理代码map到gate处的,要知道x2021年05月05日也不使用sysenter机制进行系统调用。那64位的x2021年05月05日怎么系统调用呢?在arch/x2021年05月05日/kernel/entry.S中有ENTRY(system_call)这个标志,在arch/x86_64/kernel/setup64.c中的syscall_init函数中有以下一行:wrmsrl(MSR_LSTAR, system_call);可见64位的x2021年05月05日是通过一个MSR寄存器来保存系统调用处理地址的,而不再是通过中断。至于说机器如何处理这个信息以及这个寄存器如何影响系统运行,这已经到x2021年05月05日体系的cpu实现硬件问题了,和本文的linux系统的要旨无关,此处简略(再说不简略也不行啊,我也不会啊)。3.总结由于硬件指令的兼容,32位的程序在用户态不受任何影响的运行,由于内核保留了0x80号中断作为32位程序的系统调用服务,因此32位程序可以安全触发0x80号中断使用系统调用,由于内核为0x80中断安排了另一套全新的系统调用表,因此可以安全地转换数据类型成一致的64位类型,再加上应用级别提供了两套c库,可以使64位和32位程序链接不同的库。因此linux的2021年05月05日兼容搞得非常好。为了看一下在x2021年05月05日上64位程序和32位程序是如何执行系统调用的,写一个最简单的测试程序:#include #include int main(){getpid();}之所以选择getpid是因为它没有参数,最简单,将之在Red Hat 32位机器上按照如下命令行编译:gcc test.c -o test-32 -g然后再将之在64位机器上同样方式编译,只是可执行文件名字变为test-64。接下来首先gdb test-32:(gdb) b main...(gdb) r...(gdb) b getpidBreakpoint 2 at 0xf7f3d430(gdb) disassemble 0xf7f3d430 0xf7f3d43a0xf7f3d430 : mov $0x14,%eax #0x14是20,正是getpid的系统调用号0xf7f3d435 : int $0x80 #32位程序以int 0x80触发系统调用0xf7f3d437 : ret0xf7f3d438 : nop0xf7f3d439 : nopEnd of assembler dump.(gdb)结果全部在,可见即使在64位机器上,32位程序仍然使用int 0x80触发系统调用,在内核中已经注册了0x80的中断处理函数。接下来再试一下64位的程序如何触发系统调用,执行gdb test-64:(gdb) b main...(gdb) r...(gdb) b getpidBreakpoint 2 at 0x32fbf90f40(gdb) disassemble 0x32fbf90f40 0x32fbf90f70Dump of assembler code from 0x32fbf90f40 to 0x32fbf90f70:0x00000032fbf90f40 : mov %fs:0x94,%edx0x00000032fbf90f48 : test %edx,%edx0x00000032fbf90f4a : mov %edx,%eax0x00000032fbf90f4c : jle 0x32fbf90f50 0x00000032fbf90f4e : repz retq0x00000032fbf90f50 : jne 0x32fbf90f5e 0x00000032fbf90f52 : mov %fs:0x90,%eax0x00000032fbf90f5a : test %eax,%eax0x00000032fbf90f5c : jne 0x32fbf90f4e 0x00000032fbf90f5e : mov $0x27,%eax #系统调用号装入eax0x00000032fbf90f63 : syscall #执行系统调用0x00000032fbf90f65 : test %edx,%edx0x00000032fbf90f67 : jne 0x32fbf90f4e 0x00000032fbf90f69 : mov %eax,%fs:0x90值得注意的是,在2.6.9内核的x2021年05月05日机器上,getpid和32位机器的getpid系统调用号有所不同,在64位上是39号,定义在include/asm-x86_64/unistd.h:#define __NR_getpid 39__SYSCALL(__NR_getpid, sys_getpid)而刚才看到过,32位兼容的getpid的系统调用号为20,定义在arch/x86_64/ia32/ia32entry.S中:ia32_sys_call_table:....quad sys_getpid /* 20 */...
PS:千万不要觉得test.c很简单然后就stepi单指令跟踪哦,因为这会涉及到一大堆跳转,如果你不明白链接的知识,不了解GOT和PIC的话,那就麻烦大了,因此还是直接在getpid处下断比较直观,如果你想顺便把代码重定位和GOT等玩意儿搞了的话,也可以试一下,反正在调试器面前,整个地址空间都会暴露,想看什么都行,当然,要学会让/proc//maps等文件帮忙哦。
本文转自 dog250 51CTO博客,原文链接:http://blog.51cto.com/dog22021年05月05日71104