天天看點

核心代碼閱讀(19) - 系統調用trap

系統調用機制(sethostname)

使用者空間

sethostname庫函數

sethostname是glibc封裝的庫函數:      
int sethostname(const char *name, size_t len);      
檢視sethostname的代碼:      
objdump -d /usr/lib/libc.a
    mov %ebx, %edx
    mov 0x8(%esp,1), %ecx
    mov 0x4(%esp,1), %ebx
    mov $0x4a, %eax
    int $0x80
    mov %edx, %ebx
    cmp $0xfffff001, %eax
    jae 1a<sethostname+0x1a>
    ret      
1) 系統調用參數是通過寄存器傳遞的,因為系用調用trap到核心中會發生棧切換,不能通過棧來傳遞參數。
   系統調用的參數個數最多6個。
2) mov 0x8(%esp,1), %ecx
   參數len到%ecx
3) mov 0x4(%esp,1), %ebx
   參數name到%ebx
4) mov $0x4a, %eax
   sethostname的系統調用号
5) int $0x80
   系統調用中斷
6) cmp $0xfffff001, %eax
   jae 1a<sethostname+0x1a>
   檢查傳回值如果在-4095到-1之間,說明有錯誤發生。
   其中,sethostname+0x1a是__syscall_error,在連結過程中由連接配接器填入。      

sethostname庫函數傳回出錯的處理

__syscall_error的彙編代碼:      
neg %eax
     push %eax
     call 4<__syscall_error_1+0x2>
     pop %ecx
     mov %ecx, (%eax)
     mov $0xffffffff, %eax
     ret      
1) neg %eax
   把傳回碼取反,并且壓入棧中。
2) call 4<__syscall_error_1+0x2>
   把全局變量errono位址加載到%eax中。
3) mov %ecx, (%eax)
   把錯誤碼寫入errono
4) mov $0xffffffff, %eax
   将%eax的内容填入-1,系統調用出錯的通用的傳回碼是-1.errono中有具體的錯誤碼。      

核心空間

系統調用對應的陷阱門

系統調用通過INT指令穿過陷阱門後到達了核心空間,并且發生了棧的切換。
陷阱門和中斷門的差别就是:陷阱門的n全下級别是DPL為3.
穿過陷阱門進入核心狀态并不關閉中斷,是以系統調用是可以被中斷的。      

系統調用入口0x80的服務程式 system_call

ENTRY(system_call)
        pushl %eax                        # save orig_eax
        SAVE_ALL
        GET_CURRENT(%ebx)
        cmpl $(NR_syscalls),%eax
        jae badsys
        testb $0x02,tsk_ptrace(%ebx)        # PT_TRACESYS
        jne tracesys
        call *SYMBOL_NAME(sys_call_table)(,%eax,4)
        movl %eax,EAX(%esp)                # save the return value
    ENTRY(ret_from_sys_call)      
1) pushl %eax
   儲存系統調用号
2) GET_CURRENT(%ebx)
   目前程序的task_struct指針到%ebx
3) testb $0x02,tsk_ptrace(%ebx)
   jne tracesys
   檢查程序是否啟用了PT_TRACESYS,跟蹤子程序的系統調用(strace工具)。
4) call *SYMBOL_NAME(sys_call_table)(,%eax,4)
   SYMBOL_NAME(sys_call_table)(,%eax,4) => sys_call_table + %eax*4
   sys_call_table[]是一個函數指針的數組      

核心代碼 sys_sethostname

asmlinkage long sys_sethostname(char *name, int len)
    {
        int errno;
        if (!capable(CAP_SYS_ADMIN))
                return -EPERM;
        if (len < 0 || len > __NEW_UTS_LEN)
                return -EINVAL;
        down_write(&uts_sem);
        errno = -EFAULT;
        if (!copy_from_user(system_utsname.nodename, name, len)) {
                system_utsname.nodename[len] = 0;
                errno = 0;
        }
        up_write(&uts_sem);
        return errno;
    }      
1) if (!capable(CAP_SYS_ADMIN))
   檢查權限
2) down_write(&uts_sem);
   擷取鎖
3) if (!copy_from_user(system_utsname.nodename, name, len))
   從使用者空間拷貝資料      
copy_from_user

copy_from_user彙編代碼

copy_from_user 最終調用到宏__copy_user_zeroing中。      
#define copy_from_user(to,from,n)                        \
        (__builtin_constant_p(n) ?                        \
         __constant_copy_from_user((to),(from),(n)) :        \
         __generic_copy_from_user((to),(from),(n)))      
static inline unsigned long
    __generic_copy_from_user_nocheck(void *to, const void *from, unsigned long n)
    {
        __copy_user_zeroing(to,from,n);
        return n;
    }      
#define __copy_user_zeroing(to,from,size)                                \
    do {                                                                        \
        int __d0, __d1;                                                        \
        __asm__ __volatile__(                                                \
                "0:        rep; movsl\n"                                        \
                "        movl %3,%0\n"                                        \
                "1:        rep; movsb\n"                                        \
                "2:\n"                                                        \
                ".section .fixup,\"ax\"\n"                                \
                "3:        lea 0(%3,%0,4),%0\n"                                \
                "4:        pushl %0\n"                                        \
                "        pushl %%eax\n"                                        \
                "        xorl %%eax,%%eax\n"                                \
                "        rep; stosb\n"                                        \
                "        popl %%eax\n"                                        \
                "        popl %0\n"                                        \
                "        jmp 2b\n"                                        \
                ".previous\n"                                                \
                ".section __ex_table,\"a\"\n"                                \
                "        .align 4\n"                                        \
                "        .long 0b,3b\n"                                        \
                "        .long 1b,4b\n"                                        \
                ".previous"                                                \
                : "=&c"(size), "=&D" (__d0), "=&S" (__d1)                \
                : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)        \
                : "memory");                                                \
    } while (0)      
1) 參數輸入部:
   : "=&c"(size), "=&D" (__d0), "=&S" (__d1)
   %0 -> size 和%ecx綁定
   %1 -> __d0 和%edi綁定
   %2 -> __d1 和%esi綁定
2) 參數輸出部
   : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)
   %3 -> size&3 和一個寄存器綁定
   %4 -> size/4 和%0也就是%ecx綁定
   %5 -> to 和%1也就是%edx綁定
   %6 -> from 和%2也就是%edi綁定
3) "0:        rep; movsl\n"
   rep指令執行串操作,從%esi 到 %edi循環執行%ecx次數。
   拷貝4位元組。
4) "        movl %3,%0\n"
   "rep; movsb\n"
   拷貝剩下的一個位元組。
5) fixup和__ex_table
   錯誤處理: 如果使用者空間傳遞的from指針壓根就是非法位址(沒有建立缺頁映射)怎麼辦?
   當然可以在執行拷貝之前調用verify_area,通過task_struct中的mm檢查from的合法性。
   但是這樣一方面做性能低,另一方面絕大時候的系統調用的參數都是合法的,做了無用功。
   在最新的核心中是通過“事後補救”的方式:如果參數非法了,執行不下去了,恢複現場。而不是對所有的系統調用全部都進行參數檢查。      

fixup和__ex_table

對于可能出錯的地方并不事先做sanity test,而采取“事後補救”。C++的異常借鑒了這種設計。
   gcc編譯器把代碼編譯後生成了text和data段,還支援fixup段和__ex_table段。
   fixup:用于異常發生後的修複。
   __ex_table:使用者異常指令位址表。
   .section .fixup 和 .section __ex_table 告訴編譯器把相應的代碼放在fixup和__ex_table段中。      
".section __ex_table,\"a\"\n"
       "        .align 4\n"
       "        .long 0b,3b\n"
       "        .long 1b,4b\n"
       
       struct exception_table_entry
       {
           unsigned long insn, fixup;
       };      
訓示标号0處指令的異常由标号3處的代碼處理,标号1處指令的異常由4号處理。
   exception_table_entry結構體就存儲了異常指令和對應的修複位址。
   同時,gcc把所有的 exception_table_entry結構體按照異常的指令位址排序。      

fixup和__ex_table的使用

"0:        rep; movsl\n"       
   标号0處的指令發生異常的情況是esi,edi對應的記憶體位址是非法位址。      
asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code)
       {
           if (in_interrupt() || !mm)
               goto no_context;
           vma = find_vma(mm, address);
           if (!vma)
               goto bad_area;
       no_context:
           if ((fixup = search_exception_table(regs->eip)) != 0) {
               regs->eip = fixup;
               return;
           }
       }      
1) (fixup = search_exception_table(regs->eip)) != 0)
      在__ex_table段中二份查找出錯的指令對應的修複位址
   2) regs->eip = fixup;
      把找到的修複位址添入eip中。下一條要執行的指令就是fixup了。
   下面兩個函數是在__ex_table表中查找修複位址。      
unsigned long
       search_exception_table(unsigned long addr)
       {
           unsigned long ret;
           ret = search_one_table(__start___ex_table, __stop___ex_table-1, addr);
           if (ret) return ret;
           return 0;
       }      
static inline unsigned long
       search_one_table(const struct exception_table_entry *first,
                 const struct exception_table_entry *last,
                 unsigned long value)
       {
           while (first <= last) {
              const struct exception_table_entry *mid;
              long diff;
                mid = (last - first) / 2 + first;
                diff = mid->insn - value;
                if (diff == 0)
                        return mid->fixup;
                else if (diff < 0)
                        first = mid+1;
                else
                        last = mid-1;
           }
           return 0;
       }      

系統調用的傳回 ret_from_sys_call

系統調用的傳回和中斷處理的傳回邏輯一緻都要檢查是否有軟中斷要處理      
ENTRY(ret_from_sys_call)
        movl SYMBOL_NAME(irq_stat),%ecx                # softirq_active
        testl SYMBOL_NAME(irq_stat)+4,%ecx        # softirq_mask
        jne   handle_softirq
     ret_with_reschedule:
        cmpl $0,need_resched(%ebx)
        jne reschedule
        cmpl $0,sigpending(%ebx)
        jne signal_return
     restore_all:
        RESTORE_ALL      
1) movl SYMBOL_NAME(irq_stat),%ecx
    加載irq_stat中的active
 2) testl SYMBOL_NAME(irq_stat)+4,%ecx
    測試 softirq_active & softirq_mask
 3) jne   handle_softirq
    如果非零在從系統調用傳回前處理軟中斷。
 4) RESTORE_ALL
    通過iret傳回到使用者空間      
RESTORE_ALL
#define SAVE_ALL \
        cld; \
        pushl %es; \
        pushl %ds; \
        pushl %eax; \
        pushl %ebp; \
        pushl %edi; \
        pushl %esi; \
        pushl %edx; \
        pushl %ecx; \
        pushl %ebx; \
        movl $(__KERNEL_DS),%edx; \
        movl %edx,%ds; \
        movl %edx,%es;      
#define RESTORE_ALL        \
        popl %ebx;        \
        popl %ecx;        \
        popl %edx;        \
        popl %esi;        \
        popl %edi;        \
        popl %ebp;        \
        popl %eax;        \
1:        popl %ds;        \
2:        popl %es;        \
        addl $4,%esp;        \
3:        iret;                \
.section .fixup,"ax";        \
4:        movl $0,(%esp);        \
        jmp 1b;                \
5:        movl $0,(%esp);        \
        jmp 2b;                \
6:        pushl %ss;        \
        popl %ds;        \
        pushl %ss;        \
        popl %es;        \
        pushl $11;        \
        call do_exit;        \
.previous;                \
.section __ex_table,"a";\
        .align 4;        \
        .long 1b,4b;        \
        .long 2b,5b;        \
        .long 3b,6b;        \
.previous      

系統調用号

系統調用編号

在include/asm-i386/unistd.h檔案中定義了系統調用的編号      
#define __NR_exit                  1
    #define __NR_fork                  2
    #define __NR_read                  3
    #define __NR_write                  4      

系統調用表sys_call_table的初始化

ENTRY(sys_call_table)
        .long SYMBOL_NAME(sys_ni_syscall)
        .long SYMBOL_NAME(sys_exit)
        .long SYMBOL_NAME(sys_fork)
        .long SYMBOL_NAME(sys_read)
        .long SYMBOL_NAME(sys_write)
        .long SYMBOL_NAME(sys_open)                /* 5 */
        .long SYMBOL_NAME(sys_close)
        .long SYMBOL_NAME(sys_waitpid)
        .long SYMBOL_NAME(sys_creat)      

繼續閱讀