[关闭]
@hakureisino 2017-04-09T11:04:11.000000Z 字数 3056 阅读 3225

编译linux内核以及添加系统调用的全过程


基础环境:

    Ubuntu 16.04/ArchLinux 等发行版

下载地址:

https://mirror.tuna.tsinghua.edu.cn/kernel/v4.x/linux-4.9.tar.xz #kernel 4.9
https://mirror.tuna.tsinghua.edu.cn/kernel/v3.0/linux-3.1.tar.xz #kernel 3.1
https://mirror.tuna.tsinghua.edu.cn/kernel/ #其他版本国内源

 Tips:

0x0 以下编译过程以kernel 4.9rc版本为例,使用环境 Ubuntu 16.04 LTS
0x1 源码可放置在任何目录,下面内容中的[root] 表示你存放源码的目录
0x2 编译过程中make 指令中的 -jn 表示启用N个线程编译,可以有效提高编译速度,具体的N值由编译设备的CPU核心数决定,一般为 cpu数×2+2 如果不知道建议设置成4。(在2核CPU上运行-j6大概30分钟可以编译完成 虚拟机由于指令转换CPU利用率不高可能会有所提高)
0x3 如果编译失败,建议先执行 make clean 再重新执行整个过程
0x4 通过uname -a 可以查看当前系统的linux版本,Ubuntu16.04自自带内核版本为4.8.0-36,编译完成后可以用此命令确定是否替换成功
0x5 后面提到的ramdisk/ramfs(内存盘)均指Linux在启动的时候加载的一个临时文件系统,里面包含了启动必须的文件以及驱动。在启动时,需要将其完全置入内存中,所以一般来说内存盘的大小是有限制的。


0x0 编译环境配置

0x00 下载内核源码

从上面的地址下载Kernel源码包并解压,在终端里cd到源码目录使用ls命令查看文件目录。

0x01 基础环境配置

由于Ubuntu发行版中自带了make,gcc等编译组件,所以可以不用再安装,但是ncurses(文字处理库)是在menuconfig的时候需要调用到的额外软件包

  1. $ sudo apt-get install ncurses //如果这条命令提示没有可用的软件包ncurses则改成这条命令
  2. $ sudo apt-get install libncurses*

同时,在4.x以上的linux版本中引入了内核级的SSL处理机制,所以编译bzImage时需要SSL的库文件

  1. $ sudo apt-get install libssl-dev

这样,基本的内核编译环境就完成了。

0x02 尝试编译

  1. $ cd [root]
  2. $ sudo make mrproper //清除所有编译记录和临时文件
  3. $ sudo make menuconfig //配置要编译的模块 直接选择的被编译进内核 M则被编译成模块 在图形界面中选择要编译的部分
  4. $ sudo make -jn bzImage //编译内核烧写镜像 整个目录与最终生成内核有关的部分被编译到这里
  5. $ sudo make -jn modules //编译模块。linux kernel采用的模块化内核,所以一些非必须部分被模块化并放在外存动态加载
  6. $ sudo make -jn modules_install //可以看成启用这些编译出来的模块 将其放置到/lib/module/{版本号}/目录下
  7. $ sudo make install //安装编译出来的kernel到系统的boot分区

注意!

在3.0及以前的版本中还需要我们手动调用mkinitramfs去打包modules成initrd.img-4.9.0并且使用update-grub2去更新引导程序引导的镜像,但是新版本的内核中在install的时候就已经自动帮我们处理了这两个步骤,所以直接重启查看uname -a 内核版本就可以查看是否操作成功了
由于默认编译出来的initramdisk有300余M 所以如果分配的内存不足开机时会提示Out of memory无法启动,这时可以关机并且多分配一些内存(2G左右最好)或者减少选用的内核模块并重新编译内存盘。
如果更新完内核之后无法启动了,可以重启系统并按E 然后上下选中Ubuntu项再按E 之后把里面的内核版本改回到你原来的版本就可以启动进系统查看问题了(TAB可以补全你的输入)

0x03 确认编译成功

重启之后运行

  1. $ uname -a

查看系统版本号是否变为4.9.0


0x1 添加系统调用

0x10 添加系统调用的头文件索引 (此步可跳过)

    我们要调用syscall是通过unistd提供的一系列系统接口,其中由C提供的接口需要通过修改 **[root]/arch/x86/include/generated/uapi/asm/unistd_64.h**这个文件来指定

通过使用vim或者自带的gedit打开这个文件,在endif之前添加一个新的系统调用号

  1. #define __NR_hellocall 335
    335表示系统调用号,不一定要和前面的连续,只要不重复即可,hellocall则是系统调用名 

0x11 修改系统调用向量表

    系统在查找引用的时候是通过查找一个系统调用的向量表来执行对应的例程的,所以我们也要把对应的调用号加到这个向量表里 同样的方式打开文件 **[root]/arch/x86/entry/syscalls/syscall_64.tbl**

在任意位置加入

  1. 335 64 hakureihello sys_hello
    335表示系统调用号,与上面的一致 hakureihello为调用名 sys_hello为实际调用的函数,之后会处理(这个名字与调用名没有关系)

0x12 添加调用实现

最后就是添加通过调用号要调用到的程序了 打开[root]/include/linux/syscalls.h添加一个函数声明

  1. asmlinkage long sys_hello(long uid, const char __user *content);

打开 [root]/kernel/sys.c 并在结尾添加这段函数

  1. asmlinkage long sys_hello(long uid, const char __user *content){
  2. printk("%d wants to say hello, and %s", uid, content);
  3. return 1;
  4. }
    简单解释下这段代码的意思 通过sys_hello传入一个long型数据和一个字符串到内核空间,在内核空间打印完返回一个1, __user是一个编译器宏,表示这个数据是用户空间的数据,即使无法访问也不应该回收。

0x13 重新编译

确定所有文件保存后,执行

  1. $ sudo make -j6 bzImage
  2. $ sudo make install

0x2 使用系统调用

0x20 编写程序

新建一个文件 test.cpp 输入以下代码

  1. #include <sys/syscall.h>
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. int main(){
  5. long ret = syscall(335, 100, "test content"); //syscall 参数1 调用号 之后为系统调用的参数列表
  6. printf("result is %ld\n", ret);
  7. return 0;
  8. }

0x21 编译程序

输入命令

  1. $ g++ test.cpp -o test

等待执行完之后,程序就编译成功了

0x22 执行程序

输入命令

  1. $ sudo ./test

屏幕上会打印出 result is 1 因为我们的系统调用返回数字就是1

0x23 查看内核输出信息

输入命令

  1. $ dmesg | grep "hello"

(grep "hello" 为过滤包含hello的信息)即可看到内核中输出的printk信息

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注