zoukankan      html  css  js  c++  java
  • 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()函数

    代码如下所示(位于archarmkernelentry-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 fsread_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]='';
          printk("sys_hello:%s
    ",ker_buf);
        }
    }

    3.4  includelinuxsyscalls.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 " /* save the argment in r0 */ //%0等于buf "mov r1, %1 " /* save the argment in r0 */ //%1等于count "swi %2 " /* 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 "); hello("www.100ask.net", 15);//这个函数会调用内核的sys_hello() return 0; }

    4.1 其中asm ()是一个内嵌汇编(参考linux内核源代码情景分析1.5.2节)

    格式如下所示:

    • asm( 指令部 : 输出部 : 输入部 : 损坏部 );

    指令部

    在指令部中,若出现%0、%1、%2等,则表示指令部后面的第几个变量.

    比如上面代码的"mov r0, %0 ".

    其中%0便会对应buf值,而"r"是一个约束条件字母,r表示任意一个寄存器,在预处理时,便会自动分配一个寄存器,将buf值放入该寄存器里,然后运行mov  r0  (buf对应的寄存器)

    输出部

    每个输出部的约束条件字母都要加上"=",比如:

    int num=5,val;
    
    asm("mov %0,%1
    "
        :"=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结构体

     

  • 相关阅读:
    mybatis 错误 Invalid bound statement (not found)
    Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.
    bug 记录 Unable to start ServletWebServerApplicationContext due to multiple ServletWebServerFactory beans
    解决:The Tomcat connector configured to listen on port 8182 failed to start. The port may already be in use or the connector may be misconfigured.
    jquery validate 验证插件 解决多个相同的Name 只验证第一个的方案
    phpStorm+xdebug调试(php7.3)
    小程序视频多个视频播放与暂停
    CSS实现单行、多行文本溢出显示省略号(…)
    Packet for query is too large (4,544,730 > 4,194,304). You can change this value on the server by setting the 'max_allowed_packet' variable.
    idea自动在文件头中添加作者和创建时间
  • 原文地址:https://www.cnblogs.com/lifexy/p/8075282.html
Copyright © 2011-2022 走看看