这篇是网易云课堂《Linux内核分析》这门课的作业
看下面这段程序:
Else块中做的和IF块中做的是一回事,都是进行了了getuid这个系统调用取得了当前用户的uid。现在分行解释一下:
mov $0,%%ebx\n\t
这一行是把ebx寄存器的值清0,在系统调用时第一个参数从ebx中来,这相当于传递了NULL。
通过查询系统调用表我们知道getuid()的系统调用功能号是24,16进制为0x18。系统调用功能号是由eax寄存器传递的,那么下面这一行的意思就很明显了:
mov $0x18,%%eax\n\t
然后发送软中断信号给内核,系统调用的中断号是0x80,所以:
int $0x80\n\t
int命令会引发一个编程异常,这个异常会被内核trap住。还记得我们在start_kernel中有trap_init吗?
#ifdef CONFIG_IA32_EMULATION set_system_intr_gate(IA32_SYSCALL_VECTOR, ia32_syscall); set_bit(IA32_SYSCALL_VECTOR, used_vectors); #endif
IA32_SYSCALL_VECTOR这个宏的定义在irq_vectors.h
#define IA32_SYSCALL_VECTOR 0x80 #ifdef CONFIG_X86_32 # define SYSCALL_VECTOR 0x80 #endif
中断让程序陷入内核态,内核通过中断向量表知道当前是要进行系统调用。然后进入系统调用入口system_call:
ENTRY(system_call) RING0_INT_FRAME # can't unwind into user space anyway ASM_CLAC pushl_cfi %eax # save orig_eax SAVE_ALL GET_THREAD_INFO(%ebp) # system call tracing in operation / emulation testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags(%ebp) jnz syscall_trace_entry cmpl $(NR_syscalls), %eax jae syscall_badsys syscall_call: call *sys_call_table(,%eax,4) syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value syscall_exit: LOCKDEP_SYS_EXIT DISABLE_INTERRUPTS(CLBR_ANY) # make sure we don't miss an interrupt # setting need_resched or sigpending # between sampling and the iret TRACE_IRQS_OFF movl TI_flags(%ebp), %ecx testl $_TIF_ALLWORK_MASK, %ecx # current->work jne syscall_exit_work restore_all: TRACE_IRQS_IRET restore_all_notrace: #ifdef CONFIG_X86_ESPFIX32 movl PT_EFLAGS(%esp), %eax # mix EFLAGS, SS and CS # Warning: PT_OLDSS(%esp) contains the wrong/random values if we # are returning to the kernel. # See comments in process.c:copy_thread() for details. movb PT_OLDSS(%esp), %ah movb PT_CS(%esp), %al andl $(X86_EFLAGS_VM | (SEGMENT_TI_MASK << 8) | SEGMENT_RPL_MASK), %eax cmpl $((SEGMENT_LDT << 8) | USER_RPL), %eax CFI_REMEMBER_STATE je ldt_ss # returning to user-space with LDT SS #endif restore_nocheck: RESTORE_REGS 4 # skip orig_eax/error_code irq_return: INTERRUPT_RETURN .section .fixup,"ax" ENTRY(iret_exc) pushl $0 # no error code pushl $do_iret_error jmp error_code .previous _ASM_EXTABLE(irq_return,iret_exc) #ifdef CONFIG_X86_ESPFIX32 CFI_RESTORE_STATE ldt_ss: #ifdef CONFIG_PARAVIRT /* * The kernel can't run on a non-flat stack if paravirt mode * is active. Rather than try to fixup the high bits of * ESP, bypass this code entirely. This may break DOSemu * and/or Wine support in a paravirt VM, although the option * is still available to implement the setting of the high * 16-bits in the INTERRUPT_RETURN paravirt-op. */ cmpl $0, pv_info+PARAVIRT_enabled jne restore_nocheck #endif
可以看到system_call中调用了SAVE_ALL保护现场,将当前的栈顶,cs:eip,状态号压栈。
.macro SAVE_ALL cld PUSH_GS pushl_cfi %fs /*CFI_REL_OFFSET fs, 0;*/ pushl_cfi %es /*CFI_REL_OFFSET es, 0;*/ pushl_cfi %ds /*CFI_REL_OFFSET ds, 0;*/ pushl_cfi %eax CFI_REL_OFFSET eax, 0 pushl_cfi %ebp CFI_REL_OFFSET ebp, 0 pushl_cfi %edi CFI_REL_OFFSET edi, 0 pushl_cfi %esi CFI_REL_OFFSET esi, 0 pushl_cfi %edx CFI_REL_OFFSET edx, 0 pushl_cfi %ecx CFI_REL_OFFSET ecx, 0 pushl_cfi %ebx CFI_REL_OFFSET ebx, 0 movl $(__USER_DS), %edx movl %edx, %ds movl %edx, %es movl $(__KERNEL_PERCPU), %edx movl %edx, %fs SET_KERNEL_GS %edx .endm
然后查表找到到系统调用服务程序并执行:
syscall_call: call *sys_call_table(,%eax,4)
从eax中知道系统调用功能号是24,而24对应着getuid(在unistd32.h中定义)。
#define __NR_getuid 24 __SYSCALL(__NR_getuid, sys_getuid16)
执行完系统调用服务程序以后将返回值写回eax寄存器:
syscall_after_call: movl %eax,PT_EAX(%esp) # store the return value
然后或者发生进程调度,或者返回用户态接着执行(这一段代码不是很懂)。
所以我们可以从eax寄存器拿到getuid的返回值。并且写到内存变量uid。
mov %%eax,%0
编译这个程序然后测试一下
得到的用户id和id这条命令得到的是一致的。
刘聪 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000