系統調用機制(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)