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, #-] @ 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 #] @ 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,     //将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值为:

3.3 fs\read_write.c文件里写一个sys_hello()函数

asmlinkage void sys_hello(const char __user * buf, size_t count)     //打印count长数据
{
char ker_buf[]; if(buf)
{ copy_from_user(ker_buf, buf, (count<)? count : );
ker_buf[]='\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 + ) //输入部
: "r0", "r1");            //损坏部,指原有的数据会被破坏
}
int main(int argc, char **argv)
{
printf("in app, call hello\n");
hello("www.100ask.net", );//这个函数会调用内核的sys_hello()
return ;
}

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=,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应用调试-初步制作系统调用(用户态->内核态)的更多相关文章

  1. linux 用户态 内核态

    http://blog.chinaunix.net/uid-1829236-id-3182279.html 究竟什么是用户态,什么是内核态,这两个基本概念以前一直理解得不是很清楚,根本原因个人觉得是在 ...

  2. 进程:linux用户态-内核态

    用户态:Ring3运行于用户态的代码则要受到处理器的诸多检查,它们只能访问映射其地址空间的页表项中规定的在用户态下可访问页面的虚拟地址,且只能对任务状态段(TSS)中I/O许可位图(I/O Permi ...

  3. Linux高级调试与优化——用户态堆

    内存问题是软件世界的住房问题 嵌入式Linux系统中,物理内存资源通常比较紧张,而不同的进程可能不停地分配和释放不同大小的内存,因此需要一套高效的内存管理机制. 内存管理可以分为三个层次,自底向上分别 ...

  4. GCC 用户态&内核态 Makefile

    转了一圈,今天再次回到C 网上一篇博文,个人感觉良心作品,故而拿来重新实现一遍,原作者原文有问题,我这里把他打通了 一.GCC Makefile //hello.c #include <stdi ...

  5. Linux高级调试与优化——信号量机制与应用程序崩溃

    背景介绍 Linux分为内核态和用户态,用户态通过系统调用(syscall)进入内核态执行. 用户空间的glibc库将Linux内核系统调用封装成GNU C Library库文件(兼容ANSI &am ...

  6. 用户态和内核态&操作系统

    用户态和内核态 内核态:cpu可以访问内存的所有数据,包括外围设备,例如硬盘,网卡,cpu也可以将自己从一个程序切换到另一个程序. 用户态:只能受限的访问内存,且不允许访问外围设备,占用cpu的能力被 ...

  7. (转)linux下的系统调用函数到内核函数的追踪

    转载网址:http://blog.csdn.net/maochengtao/article/details/23598433 使用的 glibc : glibc-2.17使用的 linux kerne ...

  8. cpu内核态与用户态

    1.操作系统需要两种CPU状态 内核态(Kernel Mode):运行操作系统程序,操作硬件 用户态(User Mode):运行用户程序 2.指令划分 特权指令:只能由操作系统使用.用户程序不能使用的 ...

  9. 41.Linux应用调试-修改内核来打印用户态的oops

    1.在之前第36章里,我们学习了通过驱动的oops定位错误代码行 第36章的oops代码如下所示: Unable to handle kernel paging request at //无法处理内核 ...

随机推荐

  1. Phalanx

    Phalanx Time Limit:5000MS     Memory Limit:32768KB     64bit IO Format:%I64d & %I64u Submit Stat ...

  2. Windows下caffe的python接口配置

    主要是因为,发现很多代码是用python编写的,在这里使用的python安装包是anaconda2. 对应下载地址为: https://www.continuum.io/downloads/ 安装py ...

  3. js 时间字符串转化为时间

    对于时间字符串格式为:"2017-03-03 12:23:55"; IE:显示无效的日期 new Date("2017-03-3 12:23:55") //[d ...

  4. Python 解决面试题47 不用加减乘除做加法

    在看<剑指Offer>过程中,面试题47不用加减乘除做加法,给出的思路是使用二进制的异或以及与运算,总之就是使用二进制.但是在使用Python实现的过程中,对于正整数是没有问题的,但是对于 ...

  5. Docker安装Mysql数据库容器(zz)

    zz自:http://blog.csdn.net/chengxuyuanyonghu/article/details/54380032 1.下载mysql的镜像: sudo docker pull m ...

  6. 手工搭建基于ABP的框架(2) - 访问数据库

    为了防止不提供原网址的转载,特在这里加上原文链接: http://www.cnblogs.com/skabyy/p/7517397.html 本篇我们实现数据库的访问.我们将实现两种数据库访问方法来访 ...

  7. Java 核心内容相关面试题【3】

    目录 面向对象编程(OOP) 常见的Java问题 Java线程 Java集合类 垃圾收集器 异常处理 Java小应用程序(Applet) Swing JDBC 远程方法调用(RMI) Servlet ...

  8. bzoj1001(对偶图最短路)

    显然是个最大流问题. 边数达到了10^6级别,显然用dinic算法会TLE 对于一个平面图来说,当然用对偶图的最短路来求最小割(最大流) SPFA转移的时候注意判断边界情况 应该要开longlong才 ...

  9. c#中获取路径方法

    要在c#中获取路径有好多方法,一般常用的有以下五种: //获取应用程序的当前工作目录. String path1 = System.IO.Directory.GetCurrentDirectory() ...

  10. Socket网络编程之概述理解

    今天主要讲讲什么是socket网络编程 socketde 英文原义是"孔"或者"插座".是进程通讯的一种方式,即调用这个网络库的一些API函数实现分布在不同主机 ...