42.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_SIZEstmia sp, {r0 - r12} @ Calling r0 - r12add r8, sp, #S_PCstmdb r8, {sp, lr}^ @ Calling sp, lrmrs r8, spsr @ called from non-FIQ mode, so ok.str lr, [sp, #S_PC] @ Save calling PCstr r8, [sp, #S_PSR] @ Save CPSRstr r0, [sp, #S_OLD_R0] @ Save OLD_R0zero_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]是否為0xfA710( bne .Larm710bug)... ...enable_irq //調用enable_irq()函數get_thread_info tskadr 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 0x900000void 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結構體?
?
總結
以上是生活随笔為你收集整理的42.Linux应用调试-初步制作系统调用(用户态-内核态)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ansible介绍+基本操作
- 下一篇: 使用linux expect进行ssh和