linux2.4.18----26.由内核态切换到用户态

3030阅读 0评论2016-12-30 wangcong02345
分类:LINUX

一.总体分析
在start_kernel的最后会调用rest_init-->kenel_thread 创建出一个进程init
init -->execve("/bin/sh", argv,env) 的最后在返回时,会将内核态的各个段寄存器设置为用户态的各个段寄存器
最后调用iret就从内核态切换到了用户态

二.代码分析
2.1 内核间:在init/main.c中init进程的创建与执行
  1. static void rest_init(void)
  2. {
  3.     //kernel_thread最终会do_fork创建了一个进程,其pid=1
  4.     //创建完进程后系统调用返回,然后就会执行init函数    
  5.     kernel_thread(init, NULL, CLONE_FS | CLONE_FILES | CLONE_SIGNAL);
  6.     unlock_kernel();
  7.     current->need_resched = 1;
  8.      cpu_idle();
  9. }
在arch/i386/kernel/process.c中 L488,其中eax=NR_clone

  1. int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
  2. {
  3.     long retval, d0;
  4. //fn=init=0xc0105044,arg=NULL, flags=0x10e00
  5.     __asm__ __volatile__(
  6.         “movl _NR_clone, %eax"       //将系统调用号eax设为NR_clone
  7.         "movl flags|CLONE_VM, %ebx"  //将flags放在ebx中
  8.         "movl %%esp,%%esi"
  9.         "int $0x80"             //第1个系统调用
  10.         "cmpl %%esp,%%esi"      //fork之后子进程会获得新esp,但父进程还是用它原先的esp,根据这一点可以区分父子进
  11.         "je 1f "                //相等则为父进程,直接跳出 parent-->jump
  12.         
  13.         "movl %4,%%eax"         //子进程:将args压栈
  14.         "pushl %%eax"           //子进程:
  15.         "call *%5"              //子进程:调用fn=init函数
  16.         "movl %3,%0"            //子进程执行完fn=init函数后调用exit
  17.         "int $0x80"             //子进程:第2个系统调用exit
  18.         
  19.         "1:\t"
  20.         :"=&a" (retval), "=&S" (d0)
  21.         :"0" (__NR_clone), "i" (__NR_exit),
  22.          "r" (arg), "r" (fn),
  23.          "b" (flags | CLONE_VM)
  24.         : "memory");
  25.     return retval;
  26. }
2.2 内核间:在init/main.c中init进程的创建与执行
  1. static int init(void * unused)
  2. {
  3.     ...... //不关心    
  4.     if (execute_command)   //这个execute_command=/bin/sh
  5.         execve(execute_command,argv_init,envp_init);
  6.     execve("/sbin/init",argv_init,envp_init);   //后面这个不会执行
  7.     execve("/etc/init",argv_init,envp_init);
  8.     execve("/bin/init",argv_init,envp_init);
  9.     execve("/bin/sh",argv_init,envp_init);
  10.     panic("No init found. Try passing init= option to kernel.");
  11. }
2.2.1 内核空间: execve是一个系统调用
在arch/i386/kernel/entry.S中L194
  1. ENTRY(system_call)
  2.     pushl %eax            
  3.     SAVE_ALL                        //2.2.1.1保存所有的寄存器
  4.     GET_CURRENT(%ebx)             //获取current指针
  5.     testb $0x02,tsk_ptrace(%ebx)    //检查系统调用号是不是越界 
  6.     jne tracesys
  7.     cmpl $(NR_syscalls),%eax
  8.     jae badsys
  9.     call *SYMBOL_NAME(sys_call_table)(,%eax,4 //这儿是调用sys_execve
  10.     movl %eax,EAX(%esp)                       //将返回值保存在esp+6th
  11. ENTRY(ret_from_sys_call)
  12.     cli                # need_resched and signals atomic test
  13.     cmpl $0,need_resched(%ebx)
  14.     jne reschedule
  15.     cmpl $0,sigpending(%ebx)
  16.     jne signal_return
  17. restore_all:
  18.     RESTORE_ALL     
2.2.1.1在linux-2.4.18/arch/i386/kernel/entry.S中
  1. #define SAVE_ALL \
  2.     cld; \
  3.     pushl %es; \
  4.     pushl %ds; \
  5.     pushl %eax; \
  6.     pushl %ebp; \
  7.     pushl %edi; \
  8.     pushl %esi; \
  9.     pushl %edx; \
  10.     pushl %ecx; \
  11.     pushl %ebx; \
  12.     movl $(__KERNEL_DS),%edx; \
  13.     movl %edx,%ds; \
  14.     movl %edx,%es
执行完SAVE_ALL之后的寄存器如下所示
  1. (gdb) info r
  2. eax 0xb    11
  3. ecx 0xc03ac1c0     
  4. edx 0xc03ac200    
  5. ebx 0xc0447fd3   
  6. esp 0xf7deffa4        //这个esp就是下面的struct pt_regs regs
  7. ebp 0xe000    
  8. esi 0xc03c7fc4    
  9. edi 0xc01051c8     
  10. eip 0xc010928b    0xc010928b <system_call+11>
  11. eflags 0x286    [ PF SF IF ]
  12. cs 0x10    16
  13. ss 0x18    24
  14. ds 0x18    24
  15. es 0x18    24
  16. fs 0x18    24
  17. gs 0x18    24

  18. (gdb) x /32wx 0xf7deffa4
  19. 0xf7deffa4:    0xc0447fd3    0xc03ac1c0    0xc03ac200    0xc03c7fc4
  20. 0xf7deffb4:    0xc01051c8    0x0000e000    0x0000000b    0x00000018
  21. 0xf7deffc4:    0x00000018    0x0000000b    0xc010558a    0x00000010
  22. 0xf7deffd4:    0x00000286    0x00000010    0x00010f00    0xc0105235
  23. 0xf7deffe4:    0xc0447fd3    0xc03ac1c0    0xc03ac200    0xc0105c4b
  24. 0xf7defff4:    0x00000000    0x00000078    0xc0124774    0x00000000
  25. 0xf7df0004:    0x00000000    0x00000000    0x00000000    0x00000000
  26. 0xf7df0014:    0x00000000    0x00000000    0x00000000    0x00000000
  27. 下面这个是进入do_execve之后读出来的寄存器
  28. (gdb) p /x regs
    $6 = {ebx=0xc0447fd3,  ecx=0xc03ac1c0,  edx=0xc03ac200,   esi=0xc03c7fc4,
  29.       edi=0xc01051c8,  ebp=0xe000,      eax=0xb,          xds=0x18, 
  30.       xes=0x18,        orig_eax=0xb,    eip=0xc010558a, xcs = 0x10, 
  31.       eflags=0x286,    esp=0x10,        xss=0x10f00}
2.3在arch/i386/kernel/process.c中-->系统调用sys_execve
  1. asmlinkage int sys_execve(struct pt_regs regs)
  2. {
  3.     int error;
  4.     char * filename;
  5. (gdb) p ?s
  6.     $5 = (struct pt_regs *) 0xf7deffa4    //有没有发现这个数值似曾相识?对,这个就是esp
  7. (gdb) p /x regs
  8.     $6 = {ebx=0xc0447fd3,  ecx=0xc03ac1c0,  edx=0xc03ac200,   esi=0xc03c7fc4, edi=0xc01051c8,  ebp=0xe000,  eax=0xb, 
  9.             xds=0x18,          xes=0x18,             orig_eax=0xb,          eip=0xc010558a,    xcs = 0x10,       eflags=0x286, esp=0x10,    xss=0x10f00}
  10.     filename = getname((char *) regs.ebx);
  11.     error = PTR_ERR(filename);
  12.     if (IS_ERR(filename))
  13.         goto out;
  14.     error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ?s);
  15.     if (error == 0)
  16.         current->ptrace &= ~PT_DTRACE;
  17. (gdb) p /x  regs             -->执行完do_execve之后再看堆栈的情况,特别注意段寄存器的变化
    $1 = {ebx = 0x0, ecx = 0x0, edx = 0x0, esi = 0x0, edi = 0x0, ebp = 0x0, eax = 0x0, xds = 0x2b, xes = 0x2b, 
          orig_eax = 0xb, eip = 0x40000be0, xcs = 0x23, eflags = 0x286,  esp = 0xbfffff40, xss = 0x2b }

  18.     putname(filename);
  19. out:
  20.     return error;
  21. }
注意: 没有为struct pt_resg regs分配内存空间, regs虽然不是指针,但其代表的意思和指针是一样的
regs指向己压入栈的所有寄存器,改变了regs的值就是改变了己压栈的寄存器的值,等pop时会真正改变寄存器的值
2.4 再次回到system_call
  1. ENTRY(system_call)
  2.     pushl %eax            # save orig_eax
  3.     SAVE_ALL
  4.     GET_CURRENT(%ebx)
  5.     testb $0x02,tsk_ptrace(%ebx)    # PT_TRACESYS
  6.     jne tracesys
  7.     cmpl $(NR_syscalls),%eax
  8.     jae badsys
  9.     call *SYMBOL_NAME(sys_call_table)(,%eax,4)     -->执行do_execve
  10.     movl %eax,EAX(%esp)                            -->do_execve执行完成后返回  
  11. ENTRY(ret_from_sys_call)
  12.     cli                # need_resched and signals atomic test
  13.     cmpl $0,need_resched(%ebx)
  14.     jne reschedule
  15.     cmpl $0,sigpending(%ebx)
  16.     jne signal_return
  17. restore_all:
  18.     RESTORE_ALL                       -->到这儿开始恢复寄存器的值
注意:SAVE_ALL与RESTORE_ALL表面上看起来一个push,一个pop,里面的寄存器名称也一样
但是就是在do_execve中改变了压栈中的寄存器,所以这个RESTORE_ALL就不是一般的返回了。
2.5关于RESTORE_ALL
  1. #define RESTORE_ALL    \
  2.     popl %ebx;    \   //将SAVE_ALL中压栈的寄存器恢复 
  3.     popl %ecx;    \
  4.     popl %edx;    \
  5.     popl %esi;    \
  6.     popl %edi;    \
  7.     popl %ebp;    \
  8.     popl %eax;    \
  9. 1:    popl %ds;    \
  10. 2:    popl %es;    \
  11.     addl $4,%esp;    \    //system_call一开始压栈的eax直接加4丢掉了
  12. 3:    iret;        \      //从内核空间返回用户空间,iret的出栈是有顺序的
  13. .section .fixup,"ax";    \
  14. 4:    movl $0,(%esp);    \
  15.     jmp 1b;        \
  16. 5:    movl $0,(%esp);    \
  17.     jmp 2b;        \
  18. 6:    pushl %ss;    \
  19.     popl %ds;    \
  20.     pushl %ss;    \
  21.     popl %es;    \
  22.     pushl $11;    \
  23.     call do_exit;    \
  24. .previous;        \
  25. .section __ex_table,"a";\
  26.     .align 4;    \
  27.     .long 1b,4b;    \
  28.     .long 2b,5b;    \
  29.     .long 3b,6b;    \
  30. .previous
a. 关于iret的出栈顺序
intel手册中IRET只会将EIP,CS,EFLAGS弹出,但是当有特权级的切换时,SS:ESP也被弹出
iret之后的出栈顺序是固定的,如下:
     EIP --> CS --> EFLAGS --> ESP --> SS   
b.执行iret之前,寄存器与栈中的值如下所示
  1. (gdb) info r
  2. eax 0x0 0
  3. ecx 0x0 0
  4. edx 0x0 0
  5. ebx 0x0 0
  6. esp 0xf7deffcc 0xf7deffcc
  7. ebp 0x0 0x0
  8. esi 0x0 0
  9. edi 0x0 0
  10. eip 0xc01092dd 0xc01092dd <restore_all+12>
  11. eflags 0x86 [ PF SF ]
  12. cs 0x10 16
  13. ss 0x18 24
  14. ds 0x2b 43
  15. es 0x2b 43
  16. fs 0x0 0
  17. gs 0x0 0
  18. (gdb) x /32wx 0xf7deffcc
  19. 0xf7deffcc: 0x40000be0 0x00000023 0x00000286 0xbfffff40
  20.                 EIP     CS         EFLAGS      ESP
  21. 0xf7deffdc: 0x0000002b 0xc0105235 0xc0447fd3 0xc03ac1c0
  22.                 SS
c.执行iret之后,寄存器与栈中的值如下所示
  1. 1: x/i $pc
  2. => 0x40000be0: <error: Cannot access memory at address 0x40000be0> //pc切换到了0x40000be0
  3. (gdb) info r
  4. eax 0x0 0
  5. ecx 0x0 0
  6. edx 0x0 0
  7. ebx 0x0 0
  8. esp 0xbfffff40 //有背景颜色的说明寄存器有改变
  9. ebp 0x0 0x0
  10. esi 0x0 0
  11. edi 0x0 0
  12. eip 0x40000be0 
  13. eflags 0x286 [ PF SF IF ]
  14. cs 0x23 35
  15. ss 0x2b 43
  16. ds 0x2b 43
  17. es 0x2b 43
  18. fs 0x0 0
  19. gs 0x0 0
解释一下:
    SS=0x18=11000=index=3,Ti=0,RPL=0   -->内核态
    SS=0x1b=101011=index=5,Ti=0,RPL=3  -->用户态
 所以这儿是由内核态切换到了用户态  
2.6 还有一个问题struct pt_regs regs中的值是在什么地方改变的?
sys_execve
    -->do_execve
        -->search_binary_handler
            --> load_elf_binary
                --> start_thread
  1. #define start_thread(regs, new_eip, new_esp) do {        \
  2.     __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));    \
  3.     set_fs(USER_DS);                    \
  4.     regs->xds = __USER_DS;   =0x2B                 \
  5.     regs->xes = __USER_DS;   =0x2B                 \
  6.     regs->xss = __USER_DS;   =0x2B                 \
  7.     regs->xcs = __USER_CS;   =0x23                \
  8.     regs->eip = new_eip;     //这个eip=0x40000be0 是不是也很熟悉   \
  9.     regs->esp = new_esp;     //这个esp=0xbfffff40         \
  10. } while (0)

上一篇:linux2.4.18----25.文件系统的构建
下一篇:linux驱动17---arm获取内存的大小