系统调用原理
以fork()为例
int main()
{
fork();
}
fork()是一个对系统调用fork的封装,可用下列宏来定义
_syscall0(pit_t, fork);
_syscall0是一个宏函数,i386版本定义如下:
#define _syscall0(type, name) \
type name(void) \
{ \
long __res; \
__asm__ volatile ( "int $0x80" //__asm__ 为gcc 嵌入汇编代码标识关键字, volatile 告诉gcc 这段代码不做优化;__asm__的第一个参数为汇编代码字符串,0x80为中断号\
: "=a" (__res) // a表示eax, 这个语句表示用eax输出返回数据并存储在__res里。\
: "0" (__NR__##name)); //表示__NR_##name为输入,“0”指示有编译器选择和输出相同的寄存器(eax)来传递参数。\
__syscall_return(type, __res); \
}
对于__syacall0(pid_t, fork)展开为:
pid_t fork(void)
{
long __res;
__asm__ volatile ("int $0x80"
: "=a" (__res)
: "0" (__NR_fork));
__syscall_return(pid_t, __res);
}
进一步可读化:
pid_t fork(void)
{
long __res;
$eax = __NR_fork //eax传递系统调用号, linux系统中fork为2。 linux/include/asm-x86/unistd_32.h #define __NR_fork 2
int $0x80
__res = $eax
__syacall_return(pid_t, __res);
}
带参数的syscall
#define _syscall2(type, name, type1, arg1) \
type name(type1, arg1) \
{ \
long _res; \
__asm__ volatile ("int $0x80" \
: "=a" (__res) \
: "0" (__NR_##name), "b" ((long)(arg1))); // b代表EBX寄存器, "b"((long)(arg1))表示先把arg1强转为long,然后存放在EBX里作为输入。可用EBX/ECX/EDX/ESI/EDI/EBP \
__syscall_return(type, __res); \
}
对应汇编代码:
push ebx
eax = __NR_##name
ebx = arg1
int 0x80
__res = eax
pop ebx
系统调用需要将堆栈从用户栈切换到内核栈,返回时需要切换到用户栈。
“当前栈”是指ESP的值所在的栈空间,ESP的值位于用户栈范围,则程序的当前栈就是用户栈,反之亦然。此外,寄存器SS的值还应该指向当前栈所在的页。所以用户栈--》内核栈 实际就是:
1)保存当前的ESP、SS值
2)将ESP、SS的值设为内核栈的相应值。
反过来, 内核栈--》用户栈则是:
1)恢复原来的ESP、SS的值
用户态的ESP、SS保存在哪里呢? 在内核栈上。有i386的中断硬件指令完成。
当0x80中断发生的时候,CPU除了切换内核态之外,还自动完成:
1)找到当前进程的内核栈(每个进程都有自己的内核栈)
2) 在内核栈中依次压入用户态的寄存器SS、ESP、EFLAGS、CS、EIP。
当内核从系统调用中返回的时候,需要用iret指令来回到用户态。
Linux实际不用int $0x80方式,而是用sysenter方式。
cat /proc/self/maps可以看到vdso文件(virtual dynamic shared library),vdso总是被加载在地址0xffffe000
....
ffffe000-fffff000 r-xp 00000000 00:00 0 [vdso]
通过
$dd if=/proc/self/mem of=linux-gate.dso bs=4096 skip=1048574 count=1
将vdso输出到linux-gate.dso. dd复制文件,if--输入文件,of--输出文件, skip表示从文件开头要跳过都少块, 0xffffe000/4096(0x1000) = 1048574
$objdump -T linux-gate.dso可分析该文件
.....
ffffe400 .... __kernel_vsyscall
查看该vsyscall,
objdump -d --start-address=0xffffe400 --stop-address=0xffffe408 linux-gate.dso
...
push %ecx
push %edx
push %ebp
mov %esp, %ebp
sysenter
nop
人工调用系统调用
int main()
{
int ret;
char msg[] = "Hello\n";
__asm__ volatile (
"call *%%esi"
: "=a" (ret)
: "a" (4), //write 系统调用号4通过eax传入
“s”(0xffffe400), //通过esi传入__kernel_vsyscall函数的地址
”b” ((long)1), //fd = stdout
“c” ((long)msg),
"d" ((long)sizeof(msg)));
return 0;
}
}

浙公网安备 33010602011771号