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_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结构体

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏CodingToDie

Python学习(七):模块 优雅的封装

第7 章 模块 优雅的封装 Table of Contents Python中的模块 使用模块 定义模块 建议 模块的安装 模块搜索路径 作用域 编程是一种美德...

8424
来自专栏Jerry的SAP技术分享

使用JavaScript给对象修改注册监听器

我们在开发一些大型前端项目时,会遇到这样一种情况,某个变量上有个字段。我们想知道是哪一段程序修改了这个变量上的字段。比如全局变量window上我们自定义了一个新...

822
来自专栏yukong的小专栏

【java并发编程实战1】何为线程安全性线程安全性

多线程问题,一直是我们老生常谈的一个问题,在面试中也会被经常问到,如何去学习理解多线程,何为线程安全性,那么大家跟我的脚步一起来学习一下。

1223
来自专栏IMWeb前端团队

nodejs中错误捕获的一些最佳实践

本文作者:IMWeb yisbug 原文出处:IMWeb社区 未经同意,禁止转载 本文内容大部分来自 https://www.joyent.com/...

2176
来自专栏大内老A

通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

在《中篇》中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的。总的来说,管道由一个服务器和一个Htt...

2835
来自专栏大学生计算机视觉学习DeepLearning

c++ 网络编程(四)TCP/IP LINUX/windows下 socket 基于I/O复用的服务器端代码 解决多进程服务端创建进程资源浪费问题

原文链接:https://www.cnblogs.com/DOMLX/p/9613861.html

1915
来自专栏吴生的专栏

谁说深入浅出虚拟机难?现在我让他通俗易懂(JVM)

1:什么是JVM 大家可以想想,JVM 是什么?JVM是用来干什么的?在这里我列出了三个概念,第一个是JVM,第二个是JDK,第三个是JRE。相信大家对这三个不...

3886
来自专栏coding

sublime配置java运行环境

2585
来自专栏杂项

Leveldb 源码类功能解析

Leveldb 的基本介绍网上很多资料,这里不赘述,我们直接进入主题,解析 leveldb 源码中各个类(概念)的功能。

28714
来自专栏Linyb极客之路

从Java内存模型角度理解安全初始化

如大家所知,Java代码在编译和运行的过程中会对代码有很多意想不到且不受开发人员控制的操作:

1003

扫码关注云+社区

领取腾讯云代金券