ARM Linux添加一个系统调用查看vm_area_struct内存分布

实现一个专属系统调用

  最近在研究Linux内核的内存管理,书上说进程所使用的虚拟地址信息全部保存在vm_area_struct结构体中,并未给出实例,这个结构体也是在内核空间的,所以用户空间是不能直接访问的,正好最近看到系统调用这一章节,于是想到像内核添加一个自己的系统调用,用来打印当前进程的task_struct进程控制块中我自己关注的任何信息。

实现专属系统调用

  在fs目录下新建一个名为panglib.c的文件,我们要新添加的系统调用就在这个c文件里实现,除了新建panglib.c文件,我们还需要修改fs目录下的Makefile文件,如下图所示,将panglib.c添加到内核的编译规则。
在这里插入图片描述

实现系统调用

  panglib.c的内容如下,系统调用名为sys_pang。

#include <linux/string.h>
#include <linux/mm.h>
#include <linux/file.h>
#include <linux/fdtable.h>
#include <linux/fsnotify.h>
#include <linux/module.h>
#include <linux/tty.h>
#include <linux/namei.h>
#include <linux/backing-dev.h>
#include <linux/capability.h>
#include <linux/securebits.h>
#include <linux/security.h>
#include <linux/mount.h>
#include <linux/fcntl.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#include <linux/fs.h>
#include <linux/personality.h>
#include <linux/pagemap.h>
#include <linux/syscalls.h>
#include <linux/rcupdate.h>
#include <linux/audit.h>
#include <linux/falloc.h>
#include <linux/fs_struct.h>
#include <linux/ima.h>
#include <linux/dnotify.h>
#include <linux/compat.h>

#include "internal.h"

static void print_vm_area(void)
{
        struct mm_struct *mymm =  current->mm;
        struct task_struct *task = NULL;
    struct vm_area_struct *pos = NULL;

    printk("hello \n");

    printk("current process:%s %d\n", current->comm, current->tgid);
                #if 1
    for(pos = mymm->mmap; pos; pos = pos->vm_next) {
        printk("0x%lx-0x%lx\t", pos->vm_start, pos->vm_end);
        if(pos->vm_flags & VM_READ) {
            printk("r");
        } else {
            printk("-");
        }
        if(pos->vm_flags & VM_WRITE) {
            printk("w");
        } else {
            printk("-");
        }
        if(pos->vm_flags & VM_EXEC) {
            printk("x");
        } else {
            printk("-");
        }

        printk("\n");
    }
        #endif
    return 0;

}

SYSCALL_DEFINE2(pang, const char __user *,buf,size_t, count)
{

	print_vm_area();
	return 0;
}

谈一谈SYSCALL_DEFINE2宏

  展开后如下:

			asmlinkage long sys_pang(__MAP(2,__SC_DECL,__VA_ARGS__))	\
					__attribute__((alias(__stringify(SyS_pang))));		\
					static inline long SYSC_pang(__MAP(2,__SC_DECL,__VA_ARGS__));	\
					asmlinkage long SyS_pang(__MAP(2,__SC_LONG,__VA_ARGS__));	\
					asmlinkage long SyS_pang(__MAP(2,__SC_LONG,__VA_ARGS__))	\
					{								\
					long ret = SYSC_pang(__MAP(2,__SC_CAST,__VA_ARGS__));	\
					__MAP(2,__SC_TEST,__VA_ARGS__);				\
					__PROTECT(2, ret,__MAP(2,__SC_ARGS,__VA_ARGS__));	\
					return ret;						\
					}								\
					static inline long SYSC_pang(__MAP(2,__SC_DECL,__VA_ARGS__))
                   {
                   	    print_vm_area();
	                    return 0;
                   }
                    

将专属系统调用添加到Linux内核

修改arch/arm/kernel/calls.S文件

  如下图所示,在文件的末尾新增一行 CALL(sys_pang)。
在这里插入图片描述

  这个CALL是一个宏定义,该宏定义的声明位于arch/arm/kernel/ entry-common.S文件,如下图所示。
在这里插入图片描述

CALL宏的第一次声明

  宏定义一共声明了两次,其中我们的calls.S使用的是第一个声明,该声明并未使用CALL的传参,而是使用NR_syscalls变量进行赋值然后对NR_syscalls变量进行了加一操作,当calls.S展开完成后第一个CALL就被取消声明然后重新定义了,也就是说第一次声明的CALL宏只是为了统计系统调用的个数,但是注意:在calls.S的结尾的对齐处理。

CALL宏的第二次声明与sys_call_table数组

  所有的系统调用全部保存在一个数组里,数组的每一个元素都是指向一个系统调用的函数指针,这个数组名为sys_call_table,对数组的赋值操作如下图所示:
在这里插入图片描述

  这里又再一次展开了calls.S,但是这次用的宏为:#define CALL(x) .long x,比如我们刚刚在calls.S中新增的系统调用CALL(sys_pang)会被展开为 .long sys_pang,sys_pang是我们在panglib.c中实现的系统调用的函数地址。
  到这里你应该也明白了,所谓的系统调用号说的糙一点就是sys_xxx函数在sys_call_table数组中的索引号。

修改sys_ni.c

  如下图所示,在文件的末尾新增一行 cond_syscall(sys_pang)。
在这里插入图片描述

  cond_syscall宏展开如下

#define cond_syscall(x)	asm(				\
	".weak " VMLINUX_SYMBOL_STR(x) "\n\t"		\
	".set  " VMLINUX_SYMBOL_STR(x) ","		\
		 VMLINUX_SYMBOL_STR(sys_ni_syscall))

  cond_syscall(sys_pang);语句的意思是:如果存在sys_pang(),则声明这个函数,在程序链接的时候使用这个函数;如果不存在sys_socketcall()这个函数,就使用sys_ni_syscall()函数代替。

  你可能要问了,sys_ni_syscall()是何许人也,看下函数实现你就明白了…,相当于告诉用户层你调用的系统调用在内核里并未实现。

asmlinkage long sys_ni_syscall(void)
{
	return -ENOSYS;
}

修改include/linux/syscalls.h

在这里插入图片描述

修改arch/arm/include/asm/unistd.h

在这里插入图片描述

C应用程序使用系统调用

  实验平台为Ti的ARM32处理器。

通过syscall函数访问sys_pang

#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>

int main()
{
        char buf[2];
        syscall(388,buf,2);
        return 0;
}

运行

在这里插入图片描述

汇编程序使用系统调用

  相比C语言,汇编更能让我们深入理解系统调用的本质。

通过swi指令陷入内核态

.text
.global _start

_start:
        add r0,pc,#0
        mov r1,#12
        mov r7,#388
        swi #0

_exit:
        mov r7,#1
        swi #0

  应用层通过swi指令触发软中断,swi的中断服务程序位于arch/arm/kernel/entry-common.S文件,源码如下:

/*=============================================================================
 * SWI handler
 *-----------------------------------------------------------------------------
 */

	.align	5
ENTRY(vector_swi)
#ifdef CONFIG_CPU_V7M
	v7m_exception_entry
#else
	sub	sp, sp, #S_FRAME_SIZE
	stmia	sp, {r0 - r12}			@ Calling r0 - r12
 ARM(	add	r8, sp, #S_PC		)
 ARM(	stmdb	r8, {sp, lr}^		)	@ Calling sp, lr
 THUMB(	mov	r8, sp			)
 THUMB(	store_user_sp_lr r8, r10, S_SP	)	@ 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
#endif
	zero_fp
	alignment_trap r10, ip, __cr_alignment
	enable_irq
	ct_user_exit
	get_thread_info tsk

	/*
	 * Get the system call number.
	 */

#if defined(CONFIG_OABI_COMPAT)

	/*
	 * If we have CONFIG_OABI_COMPAT then we need to look at the swi
	 * value to determine if it is an EABI or an old ABI call.
	 */
#ifdef CONFIG_ARM_THUMB
	tst	r8, #PSR_T_BIT
	movne	r10, #0				@ no thumb OABI emulation
 USER(	ldreq	r10, [lr, #-4]		)	@ get SWI instruction
#else
 USER(	ldr	r10, [lr, #-4]		)	@ get SWI instruction
#endif
 ARM_BE8(rev	r10, r10)			@ little endian instruction

#elif defined(CONFIG_AEABI)

	/*
	 * Pure EABI user space always put syscall number into scno (r7).
	 */
#elif defined(CONFIG_ARM_THUMB)
	/* Legacy ABI only, possibly thumb mode. */
	tst	r8, #PSR_T_BIT			@ this is SPSR from save_user_regs
	addne	scno, r7, #__NR_SYSCALL_BASE	@ put OS number in
 USER(	ldreq	scno, [lr, #-4]		)

#else
	/* Legacy ABI only. */
 USER(	ldr	scno, [lr, #-4]		)	@ get SWI instruction
#endif

	adr	tbl, sys_call_table		@ load syscall table pointer

#if defined(CONFIG_OABI_COMPAT)
	/*
	 * If the swi argument is zero, this is an EABI call and we do nothing.
	 *
	 * If this is an old ABI call, get the syscall number into scno and
	 * get the old ABI syscall table address.
	 */
	bics	r10, r10, #0xff000000
	eorne	scno, r10, #__NR_OABI_SYSCALL_BASE
	ldrne	tbl, =sys_oabi_call_table
#elif !defined(CONFIG_AEABI)
	bic	scno, scno, #0xff000000		@ mask off SWI op-code
	eor	scno, scno, #__NR_SYSCALL_BASE	@ check OS number
#endif

local_restart:
	ldr	r10, [tsk, #TI_FLAGS]		@ check for syscall tracing
	stmdb	sp!, {r4, r5}			@ push fifth and sixth args

	tst	r10, #_TIF_SYSCALL_WORK		@ are we tracing syscalls?
	bne	__sys_trace

	cmp	scno, #NR_syscalls		@ check upper syscall limit
	adr	lr, BSYM(ret_fast_syscall)	@ return address
	ldrcc	pc, [tbl, scno, lsl #2]		@ call sys_* routine

	add	r1, sp, #S_OFF
2:	cmp	scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)
	eor	r0, scno, #__NR_SYSCALL_BASE	@ put OS number back
	bcs	arm_syscall
	mov	why, #0				@ no longer a real syscall
	b	sys_ni_syscall			@ not private func

#if defined(CONFIG_OABI_COMPAT) || !defined(CONFIG_AEABI)
	/*
	 * We failed to handle a fault trying to access the page
	 * containing the swi instruction, but we're not really in a
	 * position to return -EFAULT. Instead, return back to the
	 * instruction and re-enter the user fault handling path trying
	 * to page it in. This will likely result in sending SEGV to the
	 * current task.
	 */
9001:
	sub	lr, lr, #4
	str	lr, [sp, #S_PC]
	b	ret_fast_syscall
#endif
ENDPROC(vector_swi)

编译链接汇编文件

arm-linux-gnueabihf-as syscall.s -o syscall.o
arm-linux-gnueabihf-ld syscall.o -o syscall

运行

在这里插入图片描述

Guess you like

Origin blog.csdn.net/weixin_42314225/article/details/120466098