博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
内核必须懂(一): 用系统调用打印Hello, world!
阅读量:6854 次
发布时间:2019-06-26

本文共 5578 字,大约阅读时间需要 18 分钟。

目录

  • 前言
  • 模块与系统调用
  • 用模块打印Hello, world!
  • 用模块添加自定义系统调用
  • top指令
  • 关闭Linux图形界面
  • 重编内核添加系统调用
  • 解压系统源代码
  • 撰写自定义系统调用
  • 编译内核
  • 测试新内核
  • 最后

前言

要自定义系统调用, 常规的两个方法是模块和重编内核, 一起来看看吧.

更新:

在64位ubuntu12.04.5上也成功运行.
解决了14.04, 16.04, 18.04上的问题.


模块与系统调用

用模块打印Hello, world!

首先看下系统版本和内核版本. 我用的是32位的ubuntu12.04.5LTS, 我很不喜欢用这么旧的版本, 但是, 其它版本都出现各种问题, 之后我会给大家展示我掉过的坑, 如果你能帮助我解决, 请评论区, 提前感谢~~

uname -a
cat /proc/version
uname -r

内核版本

我是在mac端用ssh访问Linux的, 这样是有很多好处的, 比如直接复制粘贴, 不需要改键盘映射等等. 先来写一个test.c.

test.c

#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");static int __init hello_init(void){ printk("Hello, world! Written by Sorrower\n"); return 0;}static void __exit hello_exit(void){ printk("Exit, world! Written by Sorrower\n");}module_init(hello_init);module_exit(hello_exit);

然后写Makefile. 注意看, 如果你用的vim, make前面如果是空格不是TAB, vim是会提示你的. 我这里就是TAB, 所以没有提示.

Makefile

obj-m:=test.oCURRENT_PATH :=$(shell pwd)VERSION_NUM :=$(shell uname -r)LINUX_PATH :=/usr/src/linux-headers-$(VERSION_NUM)all :        make -C $(LINUX_PATH) M=$(CURRENT_PATH) modulesclean :        make -C $(LINUX_PATH) M=$(CURRENT_PATH) clean

输入make指令. 会生成一些文件, 要用的是.ko文件.

make

你可以用lsmod指令看下有什么模块. 然后插入刚才生成的test.ko.

lsmod

sudo insmod test.ko

然后看下打印了消息没.

dmesg | grep "sorrower"

dmesg

可以再用lsmod看一下. 然后卸载模块. 用dmesg | grep "Sorrower"查看.

lsmod

sudo rmmod test

rmmod


用模块添加自定义系统调用

注意, 题目是用系统调用打印Hello, world!, 之前的只是熟悉一下模块的使用, 还不是系统调用打印出来的.

来到/usr/include/i386-linux-gnu/asm, 查看unistd_32.h, 注意这是32位ubutnu12.04.5中的位置, 不代表其他版本其他位数的.

unistd_32.h

看到223了吗, 这很明显就是拿来自定义的.

unistd_32.h

然后来到/boot, 要查看sys_call_table的内存位置, 注意, 要管理员权限.

sys_call_table

然后用vim搜索sys_call_table. 我特意把行号标出来了, 你要是想手动找到, 祝你好运了.

sys_call_table

开始写syscall.c. 这段代码不是我写的, 来自, 写得很棒. 然后请原谅我不要脸地在自定义系统调用里面加了自己的Hello, world!(手动滑稽)

#include 
#include
#include
#include
#include
MODULE_LICENSE("Dual BSD/GPL");#define SYS_CALL_TABLE_ADDRESS 0xc1697140 //sys_call_table对应的地址#define NUM 223 //系统调用号为223int orig_cr0; //用来存储cr0寄存器原来的值unsigned long *sys_call_table_my=0;static int(*anything_saved)(void); //定义一个函数指针,用来保存一个系统调用static int clear_cr0(void) //使cr0寄存器的第17位设置为0(内核空间可写){ unsigned int cr0=0; unsigned int ret; asm volatile("movl %%cr0,%%eax":"=a"(cr0));//将cr0寄存器的值移动到eax寄存器中,同时输出到cr0变量中 ret=cr0; cr0&=0xfffeffff;//将cr0变量值中的第17位清0,将修改后的值写入cr0寄存器 asm volatile("movl %%eax,%%cr0"::"a"(cr0));//将cr0变量的值作为输入,输入到寄存器eax中,同时移动到寄存器cr0中 return ret;}static void setback_cr0(int val) //使cr0寄存器设置为内核不可写{ asm volatile("movl %%eax,%%cr0"::"a"(val));}asmlinkage long sys_mycall(void) //定义自己的系统调用{ printk("Hello, world! Written by Sorrower\n"); printk("模块系统调用-当前pid:%d,当前comm:%s\n",current->pid,current->comm); return current->pid; }static int __init call_init(void){ sys_call_table_my=(unsigned long*)(SYS_CALL_TABLE_ADDRESS); printk("call_init......\n"); anything_saved=(int(*)(void))(sys_call_table_my[NUM]);//保存系统调用表中的NUM位置上的系统调用 orig_cr0=clear_cr0();//使内核地址空间可写 sys_call_table_my[NUM]=(unsigned long) &sys_mycall;//用自己的系统调用替换NUM位置上的系统调用 setback_cr0(orig_cr0);//使内核地址空间不可写 return 0;}static void __exit call_exit(void){ printk("call_exit......\n"); orig_cr0=clear_cr0(); sys_call_table_my[NUM]=(unsigned long)anything_saved;//将系统调用恢复 setback_cr0(orig_cr0);}module_init(call_init);module_exit(call_exit);MODULE_AUTHOR("25");MODULE_VERSION("BETA 1.0");MODULE_DESCRIPTION("a module for replace a syscall");

Makefile文件和之前差不多, 改下生成的.o文件名字就好.

然后要写一个用户态的程序来测试了.

什么是用户态, 来快速解释一下. cpu有用户态和核心态, 系统调用以及中断和异常都会由用户态变成核心态. 上一张进程转换图(或者叫状态机?), 图片来自网络, 我觉得画得一般, 但是我不想再手动画一张了.

进程转换

好了, 不皮了. 来写test.c吧. 简单粗暴, 就一个系统223调用.

#include
#include
int main(){ syscall(223); return 0;}

gcc一下, 然后dmesg一下. 这下真的就结束这一部分了.

测试


top指令

中途休息一下, 来说些小技巧和指令.

mac下的top指令非常好用. 你输入top, 然后输入?, 就显示全部后续操作了. 比如这里top下输入o, 在输入cpu回车. 就是cpu占有排序.

输入?

输入cpu

cpu消耗排序


关闭Linux图形界面

我没有很讨厌Linux的图形界面, 但是用了ssh之后, 你就发现确实用不到了. 我知道大家都会切换到tty的. mac是fn+ctrl+option+f3(当然了, 根据版本不同, fx有效范围不同, 12.04是f1-f6, f7图形界面, 测测就知道了)

tty

但是还不够彻底, 要让它开机直接字符界面. 关闭/开启. 当然了, 12.04似乎不吃这个指令. 要再高版本一些.

sudo systemctl set-default multi-user.targetsudo reboot
sudo systemctl set-default graphical.targetsudo reboot

重编内核添加系统调用

接下来这个就很简单了, 主要难度在找文件位置以及cpu. 这里切换回18.04LTS. cpu不好的, 可能2h+了, 好的cpu编个18.04LTS怎么20min也要吧. cpu核数两位数的麻烦关闭页面, 不在一个频道了(手动滑稽). 那顺带一提, 之前说的彻底关闭图形界面在18.04LTS就生效了.

解压系统源代码

你可以使用指令下载源码, 也可以. 总之, 下完之后, 解压文件. 看图片, 我就是用指令下载, 然后再解压压缩包, 所以有两个同名目录.

sudo apt-get install linux-source
sudo tar -jxvf linux-source-4.15.0.tar.bz2

解压源码


撰写自定义系统调用

关键是三个文件sys.c, syscalls.h, syscall_32.tbl. 都在很要命的地方呢.

  • sys.c在/usr/src/linux-source-4.15.0/linux-source-4.15.0/kernel下
  • syscalls.h在/usr/src/linux-source-4.15.0/linux-source-4.15.0/arch/x86/include/asm下
  • syscall_32.tbl在/usr/src/linux-source-4.15.0/linux-source-4.15.0/arch/x86/entry/syscalls下

对着调用编号就是666(手动滑稽).

设置调用编号

打开sys.c写自定义函数, 注意函数名对应.

自定义系统调用函数

申明函数, 还是注意名称对应.

申明函数


编译内核

需要先补下库.

sudo apt-get install libncurses5-dev

然后你可以设置编译参数, 如果你知道自己在干嘛的话.

sudo make menuconfig

编译设置

然后就是cpu测试时间了. 编译好了, 装下重启就完事了. 我就不重做了.

sudo make
sudo make modules
make modules_install
make install

测试新内核

上几张之前实验时候截的效果图, 测试函数还是之前的test.c, 改下调用号就可以了.

在编译

新内核测试


最后

先来几个坑, 求人救救孩子~~

这是14.04.5中的, 说什么Invalid module format, StackOverFlow说是内核版本不一致, 但是我Makefile中是用'uname -r'的, 怎么会不一致呢.

1404

问题已经解决, 如果出现上述错误, 只需要使用:sudo apt-get install linux-source-(uname -r得到的内核号)即可.

例如:

sudo apt-get install linux-source-4.15.0

之后使用如下指令, 可能会提示补库:

sudo make bzImage
sudo make modules
sudo make modules_install

reboot之后问题迎刃而解.

然后看一下默认的18.04, 不是我改过内核的那个. 也在google和StackOverFlow看了解决方案, 还是解决不能.

1804

解决方案除了重编内核, 就是重新安装镜像, 目前我新装的18.04.1测试没问题.

这次也是新开一个篇章, 和以往分享操作不同, 文章更偏向探索, 去学习更深的知识. 喜欢记得点赞, 有意见或者建议评论区见, 暗中关注我也是可以的~


转载地址:http://offyl.baihongyu.com/

你可能感兴趣的文章
如果是你你会如何重新设计和定义维基百科(wikipedia)?
查看>>
ppp pap和chap 认证
查看>>
交换机的基本配置
查看>>
PHP结合Python的WEB开发技术
查看>>
华为:缺省路由:默认路由 default route
查看>>
K均值聚类算法的MATLAB实现
查看>>
php中sql语句
查看>>
linux中MySQL小结
查看>>
浅谈以人为本
查看>>
Programmer10载身心成长历程回顾
查看>>
记一次springboot下maven工程方式导入pom.xml首行报错
查看>>
匿名内部类,Object类
查看>>
Tomcat(2)配置Tomcat的虚拟主机 、日志
查看>>
Linux环境下编译安装Mysql
查看>>
Rsync+Inotify
查看>>
LNMP架构搭建
查看>>
功能表单之子功能集合字段类型的使用—JEPLUS软件快速开发平台
查看>>
eyoucms 后台能不能上传视频跟播放?
查看>>
MYSQL的主从复制与读写分离
查看>>
为何 Go 的声明语法有点怪?(语法比较)
查看>>