38.Linux應用調試-初步製作系統調用(用戶態->內核態)

1首先來講講應用程序如何實現系統調用(用戶態->內核態)?

  我們以應用程序的write()函數爲例:

  1)首先用戶態的write()函數會進入glibc庫,裏面會將write()轉換爲swi(Software Interrupt)指令,從而產生軟件中斷,swi指令如下所示:

swi   #val   //val: bit[23:0]立即數,該val用來判斷用戶函數需要調用哪個內核函數	

  2)然後CPU會跳到異常向量入口vector_swi處,根據swi指令後面的val值,在某個數組表裏找到對應的sys_write()函數

  代碼如下所示(位於arch\arm\kernel\entry-common.S):

ENTRY(vector_swi)              
           /*保護用戶態的現場*/            
sub  sp, sp, #S_FRAME_SIZE
       stmia      sp, {r0 - r12}                 @ Calling r0 - r12
       add r8, sp, #S_PC
       stmdb     r8, {sp, lr}^                   @ Calling sp, lr
       mrs  r8, spsr                 @ called from non-FIQ mode, so ok.
       str   lr, [sp, #S_PC]                @ Save calling PC
       str   r8, [sp, #S_PSR]             @ Save CPSR
       str   r0, [sp, #S_OLD_R0]              @ Save OLD_R0
       zero_fp
  ... ...

       ldr   scno, [lr, #-4]                 @ get SWI instruction  //獲取SWI值
    A710(       and  ip, scno, #0x0f000000 @ check for SWI)
    A710(       teq  ip, #0x0f000000)                               //校驗SWI的bit[27:24]是否爲0xf
    A710(       bne  .Larm710bug)
     ... ...

       enable_irq                           //調用enable_irq()函數
       get_thread_info tsk
       adr  tbl, sys_call_table            @ load syscall table pointer  // tbl等於數組表基地址
       ldr   ip, [tsk, #TI_FLAGS]          @ check for syscall tracing  
     ... ...

bic  scno, scno, #0xff000000              @ mask off SWI op-code //只保留SWI的bit[23:0],也就是val值
eor  scno, scno, #__NR_SYSCALL_BASE @ check OS number    
//對於2440而講,__NR_SYSCALL_BASE基地址等於0x900000,也就是說val值爲0x900000時,異或後,scno則等於0,表示數組表的基地址(第一個函數位置)
... ...

    ldrcc pc, [tbl, scno, lsl #2]             @ call sys_* routine          //pc=(tbl+scno)<<2,實現調用sys_write()
       //tbl:數組表基地址,  scno:要調用的sys_write()的索引值     lsl #2:左移2位,一個函數指針佔據4個字節

  從上面代碼可以看出,2440的val基值爲0x900000,也就是說要調用數組表的第一個函數時,則使用:

swi  #0x900000

2 接下來,我們便來自制一個系統調用

  1)在內核中,仿照一個sys_hello函數,然後放入數組表,供swi調用
  2)寫應用程序,直接通過swi指令,來調用sys_hello函數

3 仿照sys_hello()

3.1先來查找數組表,以sys_write爲例,搜索找到位於arch/arm/kernel/calls.S,如下圖所示:

在這裏插入圖片描述
  其中CALL定義如下所示:

.equ NR_syscalls,0     //將NR_syscalls=0

#define CALL(x) .equ NR_syscalls,NR_syscalls+1   //將CALL(x) 定義爲:NR_syscalls=NR_syscalls+1 ,也就是每有一個CALL(),則該CALL值則+1

#include "calls.S"              //將calls.S的內容包進來,CALL(x)上面已經有了定義,就會將calls.S裏面的所有CALL(sys_xx)排列起來

#undef CALL                    //撤銷CALL定義

#define CALL(x) .long x        //然後再將排列起來的sys_xx以long(4字節)對齊,一個函數指針佔據4字節

3.2 所以我們在call.S文件的CALL()列表的最後添加一段, 如下圖所示, sys_hello()的val值爲352:

在這裏插入圖片描述

3.3 fs\read_write.c文件裏寫一個sys_hello()函數

asmlinkage void sys_hello(const char __user * buf, size_t count)     //打印count長數據
{
    char ker_buf[100];

    if(buf)
    { copy_from_user(ker_buf, buf, (count<100)? count : 100);
      ker_buf[99]='\0';
      printk("sys_hello:%s\n",ker_buf);
    }
}

3.4 include\linux\syscalls.h文件裏聲明sys_hello()

asmlinkage void sys_hello(const char __user * buf, size_t count);

4.寫應用程序

#include <errno.h>
#include <unistd.h>
#define __NR_SYSCALL_BASE       0x900000

void hello(char *buf, int count)
{
        /* swi */
        asm ("mov r0, %0\n"   /* save the argment in r0 */  //%0等於buf 
             "mov r1, %1\n"   /* save the argment in r0 */   //%1等於count
             "swi %2\n"   /* do the system call */        //%2等於0x900352
             :                                                       //輸出部
             : "r"(buf), "r"(count), "i" (__NR_SYSCALL_BASE + 352)  //輸入部
             : "r0", "r1");                                  //損壞部,指原有的數據會被破壞
}
int main(int argc, char **argv)
{
        printf("in app, call hello\n");
        hello("www.100ask.net", 15);//這個函數會調用內核的sys_hello()
        return 0;
}

4.1 其中asm ()是一個內嵌彙編(參考linux內核源代碼情景分析1.5.2節)

  格式如下所示:
  asm( 指令部 : 輸出部 : 輸入部 : 損壞部 );
  指令部
  在指令部中,若出現%0、%1、%2等,則表示指令部後面的第幾個變量.
  比如上面代碼的"mov r0, %0\n".
  其中%0便會對應buf值,而"r"是一個約束條件字母,r表示任意一個寄存器,在預處理時,便會自動分配一個寄存器,將buf值放入該寄存器裏,然後運行mov r0 (buf對應的寄存器)

  輸出部
  每個輸出部的約束條件字母都要加上"=",比如:

int num=5,val;

asm("mov %0,%1\n"
    :"=r"(val)                //指定val是一個輸出部,執行mov後,val便等於5
    :"i"(num)                // "i"約束條件字母,表示num是一個立即數
    :      );                

  輸入部
  和輸出部唯一不同的就是,在約束條件字母前不能加上"="
  常用的約束條件字母,如下圖所示:
在這裏插入圖片描述

  損壞部
  和輸入輸出類似,一般用來處理操作的中間過程,因爲這些原有的內容都會被損壞,比如上面的hello()裏的"r0", “r1”,只是用來當做參數,傳遞給內核的sys_hello()

5.重新燒寫內核,試驗應用程序

在這裏插入圖片描述
  如上圖所示,一個簡單的系統調用便OK了

  調用成功後,就可以來修改sys_hello(),來打印應用程序的各個寄存器值,打斷點,來實現調試應用程序,需要用到:

task_pt_regs(current);          //獲取當前應用程序的各個寄存器內容,會返回一個pt_regs結構體
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章