它曾经是这样的情况,如果你需要直接在linux中进行系统调用而不使用现有的库,那么你可以包含<linux/unistd.h>
,它会定义一个类似这样的macros:
#define _syscall3(type,name,type1,arg1,type2,arg2,type3,arg3) \ type name(type1 arg1,type2 arg2,type3 arg3) \ { \ long __res; \ __asm__ volatile ("int $0x80" \ : "=a" (__res) \ : "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), \ "d" ((long)(arg3))); \ if (__res>=0) \ return (type) __res; \ errno=-__res; \ return -1; \ }
那么你可以放在你的代码中的某个地方:
_syscall3(ssize_t, write, int, fd, const void *, buf, size_t, count);
这将为您定义一个适当执行系统调用的write
函数。
看来这个系统已经被某些东西所取代了(我猜测每个进程所获得的[vsyscall]页面)更加健壮。
那么程序直接在新的linux内核上执行系统调用的方法是什么(请具体说明)? 我意识到我应该使用libc,并让它为我做的工作。 但是,让我们假设我有一个体面的理由想要知道如何做到这一点:-)。
好,所以我进一步研究,因为我没有得到太多的回应,并找到了一些很好的信息。 首先当一个应用程序在linux中启动时,除了传统的argc,argv,envp参数。 还有另一个数组传递了一些更多的数据称为auxv。 详情请看这里 。
其中一个键/值对具有与AT_SYSINFO
等效的键。 在/usr/include/asm/auxvec.h
或/usr/include/elf
。
与此键关联的值是系统调用函数的入口点(在映射到每个进程的“vdso”或“vsyscall”页面中)。
你可以用传统的int 0x80
或者syscall
指令来调用这个地址,而且实际上会执行系统调用。 不幸的是,这是丑陋的。 所以libc人提出了一个很好的解决方案。 当他们分配TCB
并将其分配给gs
段时。 他们把AT_SYSINFO
的值放在TCB
某个固定偏移量中(不幸的是它并不是固定的版本,所以你不能总是依靠相同的常量偏移量)。 因此,而不是一个传统的int 0x80
你可以说只要call *%gs:0x10
这将调用在vdso
部分找到的系统调用例程。
我想这里的目标是使写libc更容易。 这允许libc的人写一个代码块来处理系统调用,而不必再担心它。 内核人员可以随时改变系统调用的方式,他们只需要改变vdso
页面的内容来使用新的机制,这很好。 事实上,你甚至不需要重新编译你的libc! 但是,这对于我们写内联程序集的人来说是一件很痛苦的事情,并且试图在事情的背后去玩。
幸运的是,如果你真的想手动操作,那么旧的方法仍然可行:-)。
编辑:我注意到我的经验之一是, AT_SYSINFO
似乎没有给我的x86_64框上的程序( AT_SYSINFO_EHDR
是,但我不知道如何利用这一点)。 所以我不能100%确定在这种情况下如何确定系统调用函数的地址。