學習筆記 --- LINUX 應用調試之添加系統調用

一、原理
要想自制系統調用,當然首相要做的就是明白系統調用的過程:
我們拿open函數來舉個例子:當用戶空間執行open函數時,會通過glibc函數庫的作用最終去調用sys_open函數,sys_open函數最終又會調用我們具體註冊的open函數!那麼這裏最主要的就是glibc函數庫幹了些什麼呢?其實它的作用就是當用戶空間執行open函數時,會去執行一條swi #val指令,這條指令會使cpu發生異常,並跳轉到異常向量入口:vector_swi處去執行,之後的代碼會根據引發異常的指令取出其中的參數,並根據這個參數調用對應的處理函數!sys_open、sys_read、sys_write這些函數是放在一個數組裏面的,就是根據取出的這個val值爲下標找到sys_open函數!
說的有點亂,我們來理一理:app調用open->swi #val->引發cpu異常->跳轉到異常向量入口處->根據引發異常的指令調用對應的處理函數!

那麼我們自制系統調用的話,需要實現兩點:
1、寫一個應用函數:swi #val
2、在內核裏面仿sys_xxx寫一個函數,放入數組!
前者用於引發異常,後者用於具體實現!

二、實現
1、內核函數
(1)在arch/arm/kernel/call.S文件裏面的CALL()列表的最後添加一項,如:CALL(sys_hello)
這裏是用來調用sys_hello
(2)在fs/read_write.c文件里加入如下代碼:
asmlinkage void  sys_hello(const char __user * buf, size_t 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);
        }
}
這裏是sys_hello的具體實現
(3)在include/linux/syscalls.h文件里加入如下代碼:
asmlinkage void  sys_hello(const char __user * buf, size_t count);
這是對函數sys_hello的聲明
以上我們分別實現了函數的定義、聲明和調用!這樣內核裏面的工作就完成了!

2、應用程序
#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 */
             "mov r1, %1\n"   /* save the argment in r0 */
                 "swi %2\n"   /* do the system call */
                 :
                 : "r"(buf), "r"(count), "i" (__NR_SYSCALL_BASE + 352)
                 : "r0", "r1");
}

int main(int argc, char **argv)
{
        printf("in app, call hello\n");
        hello("wangshuchang frank", 19);//這個函數會進行系統調用
        return 0;
}
當我們用原來的內核啓動的時候,打印信息如下:

in app, call hello

當我們用新內核啓動的時候,打印信息如下:
in app, call hello

sys_hello:wangshuchang frank

成功了!

這裏我們只是實現了一個系統調用,那麼系統調用在應用調試裏面如何應用?下面這種調試方法不需要太知道了,一般不用,太變態了。一般知道系統調用的原理就OK啦!

——————————————————————————————————————————————————————————————————--——————————

一、步驟
1、修改應用程序的可執行文件,替換某個位置的代碼爲swi val
2、執行程序
3、進入到sys_hello->在sys_hello裏面打印信息->執行原來的指令->返回
二、具體實現
我們的應用程序是:

//file:test_sc.c
#include <stdio.h>

int cnt = 0;

void C(void)
{
 int i = 0;

 while (1)
 {
  printf("Hello, cnt = %d, i = %d\n", cnt, i);
  cnt++;
  i = i + 2;
                sleep(5);
 }
}

void B(void)
{
 C();
}


void A(void)
{
 B();
}

int main(int argc, char **argv)
{
 A();
 return 0;
}
具體步驟:
(1)編譯:arm-linux-gcc test_sc.c -o test_sc
(2)反彙編:arm-linux-objdump -D test_sc > test_sc.dis
(3)我們打開上面得到的可執行文件和反彙編文件
比如我們想在C函數的i=i+2;處打斷點的話,我們先在反彙編文件裏面找到對應的指令:
84d4: e2833002  add r3, r3, #2 ; 0x2
其中:e2833002是機器碼,這是我們所需要的!
我們去可執行文件裏面去搜索這個機器碼,在可執行文件裏它對應的機器碼應該是:02 30 83 e2
我們將此機器碼改爲swi指令的機器碼!
我們可以將之前的文件反彙編一下,然後得到swi的機器碼爲:ef900160

(4)經過上面的修改,當程序執行到i=i+2;這條指令時,會產生系統調用,最終執行sys_hello函數。
在sys_hello函數裏面,我們可以最一些必要的工作,具體程序如下:
//fs/read_write.c
asmlinkage void sys_hello(const char __user * buf, int count)
{
 static int cnt = 0;
 int val;
 int ret;
 struct pt_regs *regs; 
 
 /* 1. 輸出一些調試信息 */
 /* 應用程序test_sc的反彙編裏:       000107c8 <cnt>: */
       copy_from_user(&val, (const void __user *)0x000107c8, 4);
 printk("sys_hello: cnt = %d\n", val);

 
 /* 2. 執行被替換的指令: add r3, r3, #2 ; 0x2 */  
 /* 搜 pt_regs , 在它的結果裏再搜 current */
       //獲取當前進程的struct pt_regs結構體,這個函數需要本文件包含一個頭文件:#include<asm/processor.h>
 regs = task_pt_regs(current);
 regs->ARM_r3 += 2;//相當於我們自己實現了那個被替換的指令

 /* 打印局部變量i */
 copy_from_user(&val, (const void __user *)(regs->ARM_fp - 16), 4);
 printk("sys_hello: i = %d\n", val);
 
 /* 3. 返回 */
 if (++cnt == 5)
 {
  copy_from_user(&val, (const void __user *)0x8504, 4);
  printk("[0x8504] code = 0x%x\n", val);
  printk("regs->ARM_lr  = 0x%x\n", regs->ARM_lr);
  val = 0xe2833002;

  ret = access_process_vm(current, 0x8504, &val, 4, 1);
  printk("access_process_vm ret = %d\n", ret);
  
  cnt = 0;
 }
 
 return;
}
此外還需要在:include/linux/syscalls.h 文件裏將函數聲明改爲:asmlinkage void sys_hello(const char __user * buf, int count);

(5)編譯內核,用新內核啓動
(6)運行測試程序(在此之前要修改測試程序的權限:chmod 777 test_sc_swi),輸出信息如下:
Hello, cnt = 0, i = 0
sys_hello: cnt = 1
sys_hello: i = 0
Hello, cnt = 1, i = 2
sys_hello: cnt = 2
sys_hello: i = 2
Hello, cnt = 2, i = 4
sys_hello: cnt = 3
sys_hello: i = 4
測試成功!

本節講的調試方法比較晦澀,一般不會採用!


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章