天天看點

zz修改Linux核心增加系統調用

修改Linux核心增加系統調用

  

  本文修改核心2.4.29,分兩部分,第一部分修改核心并測試,第二部分解釋從使用者态調用新系統調用的過程。在Intel處理器上,可以通過調用門和軟中斷兩種方式實作系統調用。Linux選擇軟中斷的方式,核心通過軟中斷(int $0x80)給使用者提供服務,即系統調用。本文參考了文後給出位址的文章,甚至系統調用代碼也是從中而來,本文重在解釋其調用過程。

  

  一,修改核心

  增加系統調用隻修改/usr/src/linux-2.4.29/include/asm-i386/unistd.h和arch/i386/kernel/entry.S,系統調用函數一般在kernel/sys.c中,這裡把增加的系統調用代碼也加入這個檔案中。

  

  1.修改kernel/sys.c檔案,加入自己的系統調用代碼,同參考文獻(見文後位址)中,

  asmlinkage int sys_addtotal(int numdata)

  {

  int i=0,enddata=0;

  while(i<=numdata)

  enddata+=i++;

  return enddata;

  }

  計算從0到numdata的累加值。asmlinkage表示通過堆棧遞參數。

  

  2.然後把sys_addtotal(int )的入口位址添加到sys_call_table表中。該表依次存儲所有系統調用的入口位址。

  修改前為:

  .long SYMBOL_NAME(sys_ni_syscall)

  .rept NR_syscalls-(.-sys_call_table)/4-1

   .long SYMBOL_NAME(sys_ni_syscall)

  

  3. 把增加的 sys_call_table 表項所對應的向量,在include/asm-i386/unistd.h 中進行必要申明,以供使用者程序和其他系統程序查詢或調用:

  #define __NR_exit_group 252

  #define __NR_addtotal 259

  

  然後編譯核心make bzImage,并用生成的新核心啟動系統。

  

  4.測試程式(test.c)如下:

  #include <stdio.h>

  #include <asm/unistd.h>

  int errno;

  

  _syscall1(int,addtotal,int,num);//_syscall1表示該系統調用有1個參數,同樣_syscall2表示2個調用參數

  

  main()

  {

   int i,j;

   printf("Please input a number/n");

   while(scanf("%d",&i)==EOF);

   j=addtotal(i);

   printf("Total from 0 to %d is %d/n",i,j);

  }

  

  編譯:gcc -I/usr/src/linux-2.4.29/include test.c

  運作即可。

  

  二,下面解釋調用系統調用的過程

  測試程式test.c中的_syscall1是定義在include/asm-i386/unistd.h中的宏:

  #define _syscall1(type,name,type1,arg1)

  type name(type1 arg1)

  {

  long __res;

  __asm__ volatile ("int $0x80"

   : "=a" (__res)

   : "0" (__NR_##name),"b" ((long)(arg1)));

  __syscall_return(type,__res);

  }

  其中__syscall_return也定義在該檔案中

  #define __syscall_return(type, res)

  do {

   if ((unsigned long)(res) >= (unsigned long)(-125)) {

   errno = -(res);

   res = -1;

   }

   return (type) (res);

  } while (0)

  是以test.c中_syscall1(int,addtotal,int,num)展開後即:

  int addtotal(int num)

  {

  long __res;

   __asm__ volatile(“int $0x80”

   :”=a”(__res)

   :”0”(__NR_addtotal),”b”((long)(num)));

   do {

   if ((unsigned long)(__res) >= (unsigned long)(-125)) {

   errno = -(__res);

   __res = -1;

   }

   return (int) (__res);

  } while (0)

  }

  通過軟中斷int $0x80,其中系統調用号為eax中的__NR_##name,這裡也就是__NR_addtotal,在上面的步驟3中有#define __NR_addtotal 259,即259号系統調用。寄存器ebx中存第一個參數num。

  

  IDT中第0x80個門(其類型為15,即陷阱門)為系統啟動(init/main.c中start_kernel調用i386/kernel/traps.c中trap_init)時設定的,trap_init中set_system_gate(0x80,&system_call);故int $0x80指令通過該系統門後轉到核心的system_call處執行。

  

  system_call定義在arch/i386/kernel/entry.S中:

  ENTRY(system_call) //轉到此處執行

   pushl %eax # save orig_eax

   SAVE_ALL //把寄存器壓入堆棧

   GET_CURRENT(%ebx)

   testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS

   jne tracesys

   cmpl $(NR_syscalls),%eax

   jae badsys

   call *SYMBOL_NAME(sys_call_table)(,%eax,4) //此時eax=系統調用号=__NR_addtotal=259

   movl %eax,EAX(%esp) # save the return value

  ENTRY(ret_from_sys_call) //從系統調用傳回

   cli # need_resched and signals atomic test cmpl $0,need_resched(%ebx)

   jne reschedule //如果需要重新排程則跳去排程

   cmpl $0,sigpending(%ebx)

   jne signal_return

  restore_all:

   RESTORE_ALL

  

  sys_call_table即第一部分2中修改的部分,可以看成一個函數指針數組,按下标指向對應系統調用的函數位址,此處即call *SYMBOL_NAME(sys_call_table)(,%eax,4)調用我們asmlinkage int sys_addtotal(int numdata)。

  上面的SAVE_ALL是定義在arch/i386/kernel/entry.S中的宏:

  #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;

  是以進入系統調用函數後(本文是sys_addtotal),堆棧中參數對應SAVE_ALL中的ebx,即num。

  在系統調用時,Linux在使用者空間通過寄存器而不是堆棧傳遞參數(可以從宏_systemcall0,_systemcall1,……_systemcall5看到),這些傳遞參數的寄存器是:eax系統調用号,如果有參數的話,ebx,ecx,edx,esi,edi依次存第一個到第五個參數。系統調用時,傳遞的參數最多5個。進入核心system_call後通過SAVE_ALL把寄存器壓入堆棧,系統調用函數定義時asmlinkage int sys_addtotal(int numdata)中asmlinkage表示通過堆棧傳遞參數。進入系統調用函數後看到堆棧中第一個參數即ebx,第二個參數為ecx,對應了使用者空間5個參數的順序。當然,如果系統調用定義時隻有一個參數,則隻會使用ebx(位于堆棧中)一個參數了。

  

  系統調用傳回時儲存傳回值在寄存器eax中。然後到ret_from_sys_call:從系統調用傳回,如果需要排程或有信号待處理則去排程或處理,這超出了本文的範圍,最後傳回使用者空間。

  

  參考文獻: http://joyfire.net/jln/kernel/9.html 修改Linux核心增加系統調用 本文修改核心2.4.29,分兩部分,第一部分修改核心并測試,第二部分解釋從使用者态調用新系統調用的過程。在Intel處理器上,可以通過調用門和軟中斷兩種方式實作系統調用。Linux選擇軟中斷的方式,核心通過軟中斷(int $0x80)給使用者提供服務,即系統調用。本文參考了文後給出位址的文章,甚至系統調用代碼也是從中而來,本文重在解釋其調用過程。 一,修改核心 增加系統調用隻修改/usr/src/linux-2.4.29/include/asm-i386/unistd.h和arch/i386/kernel/entry.S,系統調用函數一般在kernel/sys.c中,這裡把增加的系統調用代碼也加入這個檔案中。 1.修改kernel/sys.c檔案,加入自己的系統調用代碼,同參考文獻(見文後位址)中, asmlinkage int sys_addtotal(int numdata) { int i=0,enddata=0; while(i<=numdata) enddata+=i++; return enddata; } 計算從0到numdata的累加值。asmlinkage表示通過堆棧遞參數。 2.然後把sys_addtotal(int )的入口位址添加到sys_call_table表中。該表依次存儲所有系統調用的入口位址。 修改前為: .long SYMBOL_NAME(sys_ni_syscall) .rept NR_syscalls-(.-sys_call_table)/4-1 .long SYMBOL_NAME(sys_ni_syscall) 3. 把增加的 sys_call_table 表項所對應的向量,在include/asm-i386/unistd.h 中進行必要申明,以供使用者程序和其他系統程序查詢或調用: #define __NR_exit_group 252 #define __NR_addtotal 259 然後編譯核心make bzImage,并用生成的新核心啟動系統。 4.測試程式(test.c)如下: #include <stdio.h> #include <asm/unistd.h> int errno; _syscall1(int,addtotal,int,num);//_syscall1表示該系統調用有1個參數,同樣_syscall2表示2個調用參數 main() { int i,j; printf("Please input a number/n"); while(scanf("%d",&i)==EOF); j=addtotal(i); printf("Total from 0 to %d is %d/n",i,j); } 編譯:gcc -I/usr/src/linux-2.4.29/include test.c 運作即可。 二,下面解釋調用系統調用的過程 測試程式test.c中的_syscall1是定義在include/asm-i386/unistd.h中的宏: #define _syscall1(type,name,type1,arg1) type name(type1 arg1) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_##name),"b" ((long)(arg1))); __syscall_return(type,__res); } 其中__syscall_return也定義在該檔案中 #define __syscall_return(type, res) do { if ((unsigned long)(res) >= (unsigned long)(-125)) { errno = -(res); res = -1; } return (type) (res); } while (0) 是以test.c中_syscall1(int,addtotal,int,num)展開後即: int addtotal(int num) { long __res; __asm__ volatile(“int $0x80” :”=a”(__res) :”0”(__NR_addtotal),”b”((long)(num))); do { if ((unsigned long)(__res) >= (unsigned long)(-125)) { errno = -(__res); __res = -1; } return (int) (__res); } while (0) } 通過軟中斷int $0x80,其中系統調用号為eax中的__NR_##name,這裡也就是__NR_addtotal,在上面的步驟3中有#define __NR_addtotal 259,即259号系統調用。寄存器ebx中存第一個參數num。 IDT中第0x80個門(其類型為15,即陷阱門)為系統啟動(init/main.c中start_kernel調用i386/kernel/traps.c中trap_init)時設定的,trap_init中set_system_gate(0x80,&system_call);故int $0x80指令通過該系統門後轉到核心的system_call處執行。 system_call定義在arch/i386/kernel/entry.S中: ENTRY(system_call) //轉到此處執行 pushl %eax # save orig_eax SAVE_ALL //把寄存器壓入堆棧 GET_CURRENT(%ebx) testb $0x02,tsk_ptrace(%ebx) # PT_TRACESYS jne tracesys cmpl $(NR_syscalls),%eax jae badsys call *SYMBOL_NAME(sys_call_table)(,%eax,4) //此時eax=系統調用号=__NR_addtotal=259 movl %eax,EAX(%esp) # save the return value ENTRY(ret_from_sys_call) //從系統調用傳回 cli # need_resched and signals atomic test cmpl $0,need_resched(%ebx) jne reschedule //如果需要重新排程則跳去排程 cmpl $0,sigpending(%ebx) jne signal_return restore_all: RESTORE_ALL sys_call_table即第一部分2中修改的部分,可以看成一個函數指針數組,按下标指向對應系統調用的函數位址,此處即call *SYMBOL_NAME(sys_call_table)(,%eax,4)調用我們asmlinkage int sys_addtotal(int numdata)。 上面的SAVE_ALL是定義在arch/i386/kernel/entry.S中的宏: #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; 是以進入系統調用函數後(本文是sys_addtotal),堆棧中參數對應SAVE_ALL中的ebx,即num。 在系統調用時,Linux在使用者空間通過寄存器而不是堆棧傳遞參數(可以從宏_systemcall0,_systemcall1,……_systemcall5看到),這些傳遞參數的寄存器是:eax系統調用号,如果有參數的話,ebx,ecx,edx,esi,edi依次存第一個到第五個參數。系統調用時,傳遞的參數最多5個。進入核心system_call後通過SAVE_ALL把寄存器壓入堆棧,系統調用函數定義時asmlinkage int sys_addtotal(int numdata)中asmlinkage表示通過堆棧傳遞參數。進入系統調用函數後看到堆棧中第一個參數即ebx,第二個參數為ecx,對應了使用者空間5個參數的順序。當然,如果系統調用定義時隻有一個參數,則隻會使用ebx(位于堆棧中)一個參數了。 系統調用傳回時儲存傳回值在寄存器eax中。然後到ret_from_sys_call:從系統調用傳回,如果需要排程或有信号待處理則去排程或處理,這超出了本文的範圍,最後傳回使用者空間。 參考文獻:[url=http://joyfire.net/jln/kernel/9.html]http://joyfire.net/jln/kernel/9.html[/url]  

繼續閱讀