3DS上Linux内核交叉编译笔记

Credit

项目原作者:Sergi Granell: xerpi

所有这里使用的源码,以及固件,均来自他创建并维护的GitHub仓库。

交叉编译工具链以及库来自GBATEMPtoolchains.bootlin.com。同时感谢一位大佬提供的服务器,很大的加速了我对内核的测试和编译!

前言

昨天在随意逛YouTube的时候,无意间发现了一个在Nintendo 3DS上运行Linux的视频,顿时很有兴趣,顺着介绍链接找到了GBATEMP上面的一个帖子,在简单的了解之后,决定自己尝试实现。这篇笔记记录的是编译的过程,以及编译的时候遇到的问题,留作以后参考,以及替之后想要尝试的人踩坑。同时这一篇文章只是这一特定内核编译的一个步骤说明,有关内核编译的具体教程将在我整理完了之后发出。

环境

绝大部分编译在Ubuntu 18.04 LTS上完成,使用root账户,对于一部分配置环境,使用root账户可能需要使用命令强制允许root编译,请注意自己的操作安全,而且理论而言不使用root也不会有问题,但是一部分操作请自己使用sudo,环境是GoormIDE的云端服务器(为什么用这个我之后说明),仅有一个最简单的编译在Windows10 x64 1909家庭版上完成。同时需要声明的是,工具和源码都是动态更新的,我现在所使用的全部工具和源码都以2020/3/9的状态为准,想确保没有问题的,或者需要获取一些我没有提到的细节的,可以在之后通过历史版本查询来查看我用到的具体工具和源码。

3DS有关的环境是,B9破解,Luma的版本是最新,New 3DS LL。

有关编译环境的一些细节

除了一般的编译环境配置之外,还有一些细节需要说明一下,首先,因为编译过程会自动从工具指定的源安装一部分工具并且获取一部分包,如果使用中国国内网络环境速度会很慢,实测使用阿里云的服务器下载用时超过40分钟。最好能够使用国外网络环境的服务器,我用的是GoormIDE(因为它免费),或者使用对应的代理。其次,交叉编译比较耗时,最好使用性能好一些的服务器,如果要在自己的电脑上使用虚拟机的话,尽量多分配一些运算资源,测试的时候我为Debian10虚拟机分配了4个核心(8代i9)和6G内存,速度仍然不理想,所以转移至服务器编译。

基础库和工具的安装

在进行其他任何环境配置前,建议先进行这一步,以省去之后的许多麻烦。这些工具绝大部分可以从发行版的源中直接安装而无需自己编译,但是因为不同发行版包名以及包管理器都不一样,在这里不提供安装脚本(有修改脚本的时间都手动装完了……)

Program Minimal version Command to check the version
GNU C 3.2 gcc –version
GNU make 3.81 make –version
binutils 2.12 ld -v
util-linux 2.10o fdformat –version
module-init-tools 0.9.10 depmod -V
e2fsprogs 1.41.4 e2fsck -V
jfsutils 1.1.3 fsck.jfs -V
reiserfsprogs 3.6.3 reiserfsck -V
xfsprogs 2.6.0 xfs_db -V
squashfs-tools 4.0 mksquashfs -version
btrfs-progs 0.18 btrfsck
pcmciautils 004 pccardctl -V
quota-tools 3.09 quota -V
PPP 2.4.0 pppd –version
isdn4k-utils 3.1pre1 isdnctrl 2>&1|grep version
nfs-utils 1.0.5 showmount –version
procps 3.2.0 ps –version
oprofile 0.9 oprofiled –version
udev 081 udevd –version
grub 0.93 grub –version || grub-install –version
mcelog 0.6 mcelog –version
iptables 1.4.2 iptables -V
openssl & libcrypto 1.0.0 openssl version
bc 1.06.95 bc –version
Sphinx(only to build the Kernel documentation) 1.2 sphinx-build –version

同时建议在这一步检查自己一些常用的工具有没有,比如wgetgit等等。

交叉编译工具链的配置

因为new 3DS LL是双ARM处理器,所有我们针对ARM6进行编译即可。有两种配置交叉编译工具链的方法:安装和自己编译。

安装现有的工具链

安装别人制作好的工具链无疑是最简单的办法,这一遇到报错的时候可以肯定工具链本身没问题,而只考虑环境和内核源码是否正确。本次用到的工具链可以在这个官网下载。使用arm6-eabihf构架,glibc的版本,我在这里使用的是stable版本,gcc库7.3.0,Linux内核标头4.1.52。但是根据其他人的测试和使用,bleeding-edge版本也完全没有问题,我并没有测试过,有需要的可以尝试一下。

下载完成之后,将工具链解压到希望的路径,这一路径最好建立在正常的地方(比如不要放到临时文件夹和一些系统自己会使用的地方,以及最好放在一个好找的地方),使用的指令是

tar -jxvf  armv6-eabihf--glibc--xxxx.tar.bz2 -C <target_dir>

之后将路径添加到环境变量中

vim /etc/profile

在最后一行加上

export PATH="$PATH:<path to your toolchain>"

并且使用source命令让环境变量生效

source /etc/profile

这样交叉编译工具链就安装完成了

自行编译工具链

也可以自己编译所需的工具链,这样更能够满足自己的需求。这里我们使用crosstool-ng来编译工具链。这是一个目前比较主流的交叉编译链生成工具,是crosstool的后继者(前者已经停止维护),能够方便而快捷地生成所需的交叉编译链,而且使用的库和工具可以达到最新。首先获取crosstool-ng

git clone https://github.com/crosstool-ng/crosstool-ng.git

然后cd到它的目录之下并执行

bootstrap && ./configure --enable-local && make install

这里注意一点,因为Linux_3ds的开发者开发的时间很早,所以其教程中让我们使用autoconfig而非bootstrap。但是之后crosstool-ng废弃了autoconfig并转为bootstrap,如果继续使用autoconfig会报错,并且很难修改。从crosstool-ng的文档来看应该跳过或使用bootstrap,所以可以选择执行上面的命令或者

./configure --prefix=<your dir> && make install

之后继续

./ct-ng ct-ng menuconfig

此时会进入配置菜单。选择Target options进行调整,

  • Target Architecture -> Select arm
  • Floating point -> Select hardware (FPU)
  • Emit assembly for CPU -> Write mpcore
  • Exit -> Exit -> Save? Yes

此时配置编译参数完成。执行

./ct-ng build

进行工具链构建。

这样构建出来的工具链,将比使用现有的工具链更方便(因为直接为你配置好了环境变量,工具链也在它应该出现的位置/opt/xtool,之后对编译目标的修改相对较少。

编译内核源码

编译buildroot

先说明一下,这一步是所有过程中最耗时的步骤,所以如果你成果启动编译了,发现它编译了好久都没结束……不用担心,再多等会,只要不出现异常终止就行,直到最后编译完成。buildroot是用来创建嵌入式Linux系统的工具,最后编译完成的结果将是一个Linux文件系统镜像。它的有关介绍可以在官网上找到,当然要是你懒得看官网的各种说明,这里是官方提供的用户手册,当然如果你还是懒得看……那就直接看下面的步骤吧,不过推荐还是看一下,方便之后应对一些可能的问题。首先先获取buildroot

git clone https://github.com/buildroot/buildroot.git

然后将这个.config文件放在buildroot根目录下。然后,根据作者的说法,直接make——

——于是就会报各种错,缺这个少那个,各种编译配置不对。需要注意的是,这个.config的创建时间是2017年,在3年的工具与库的调整中,早已过时,需要自己修改。先执行

make menuconfig

然后可以看到它提示存在Legacy config,我们先解决这个问题。进入这一选项,往下翻,在我编译的时候,只有一项属于Legacy config,就是密码的md5支持。直接将这一项去掉即可,无需使用任何替代,因为这个功能现在已经被整合。退出Legacy config进入工具链的设置,将工具链的参数设定为自己的参数。如果是安装现成的工具链,就把路径和前缀设为自己的安装位置和对应的前缀(自己进目录看一眼是什么);自己编译的则应该就在设置好的位置。同时Linux标头版本,gcc版本等也需要对应调整。调整完之后保存退出,此时应该能够自动修复Weston的lindrm支持问题,如果没有修复,将在编译的时候报错,这时候进入menuconfig将libdrm这个包勾选上即可。不到万不得已尽量不要手动修改.config文件,因为各种依赖关系很难手动处理。(来自一个一开始自己修改.config改了快半小时的人)

这之后就可以执行make了。根据自己的编译机器配置可以加-jN参数加速运行。

完整编译Linux内核

在获得适用于3DS的文件系统镜像之后,下一步要做的就是编译完整的Linux文件树了。xerpi维护的仓库中有针对3DS修改过的Linux内核文件树,在进行进一步的修改之前,我们先对它进行基本的编译和测试。首先获取仓库:

git clone https://github.com/xerpi/linux_3ds.git

需要注意的是,这一个仓库非常大,因为它是从官方的文件树仓库直接Fork过来进行修改的。这也是建议使用外网服务器进行编译的第二个原因。如果条件受限,可以加上参数--depth=1,这一参数将只clone最近一次提交的分支,相当于最新版的文件树而没有历史文件。在将代码转存本地之后,将刚才使用buildroot生成的./output/images/rootfs.cpio.gz文件复制到linux_3ds文件夹内,并且执行sh make_3ds.sh——

——然后继续各种报错。这个脚本对应的环境和我们的略有不同,所以不用它,自己执行命令

cp arch/arm/configs/nintendo3ds_defconfig .config
make ARCH=arm CROSS_COMPILE=<your prefix> -j4
make ARCH=arm CROSS_COMPILE=<your prefix> nintendo3ds_ctr.dtb

这一步执行相对快很多,在执行完之后,我们所需的Linxu内核就编译完成了。

编译arm9linuxfw

在执行完上一步之后,我们的Linux镜像还缺少一个重要的功能,就是对SD卡的支持。如果不添加额外支持,这一Linux系统将无法挂载SD卡,导致启动之后文件系统限制在镜像释放在3DS内的易失性存储器内的部分,每次重启将重置,也无法读取SD卡其他内容。xerpi为此开发了arm9linuxfw,但是他的项目只能将SD卡挂载为只读;幸运的是,Wolfvak在他之后继续完善了这一项目,他的arm9linuxfw能够将SD卡挂载为读写,并且解决了很多其他的问题。所以在这里我们使用Wolfvak的这一项目。

同时这里还存在另外一个问题。这一项目的编译需要devkitPro中的devkitARM的支持,这一工具是 devkitPro.org的团队为Nintendo的平台专门开发的交叉编译工具链。这一工具在Linux下的部署是交给pacman处理的,但是pacman在一些Linux发行版里面,因为本来使用的是其他的包管理器,pacman可能会有其他的异常表现。不幸的是在我使用的Ubuntu18.04LTS中存在一些问题,导致我安装完成devkitARM并且导入环境变量之后,在编译时仍然会报错,并且工具链并未调用自己的库和工具,而是调用系统安装的工具。综合以上原因,编译的时候只会报错Error code 2而没有任何错误提示信息,在日志中也没有有用的信息。所以多次调试未果,我换到了Windows 10的环境进行这一步编译。devkitPro的官网有完整的如何在Windows下部署和使用这一工具的教程,(其实因为源码和工具都调试好了只需要执行make就行了),在这里不再详述。

编译或获取luma固件

最后,我们唯一需要的一个东西就是一个能够让luma加载Linux文件系统的固件。这一固件由xerpi开发并维护,地址在这里。可以自己编译,和arm9linuxfw的编译过程类似,当然更方便的做法是直接下载Release里的发布版本。

装入SD卡并测试

获得所有需要的文件之后,就是测试了。取下3DS的SD卡并连接电脑,(建议这种事还是把卡取下来,万一FTP传输的时候损坏文件比较麻烦)。在根目录下新建/linux文件夹,并在其中放入由arm9linuxfw编译获得的arm9linuxfw.bin,和由linux_3ds编译获得的./arch/arm/boot/zImage镜像和./arch/arm/boot/dts/nintendo3ds_ctr.dtb文件。之后将firm_linux_loader.firm放入/luma/payload内,插卡按住start开机,选择firm_linux_loader,等待其加载完就将获得Linxu的shell。root密码也是root。

存在的问题

严格来说,这一Linux文件系统是Buildroot+busybox的方式,内置了Weston,所以指令集并不完整,系统层面的整合也并不好。但是这已经是一个非常好的开头,只要在机器上获得Linux的shell,之后的开发将会简单许多。

同时,因为3DS是嵌入式系统,它用的是WiFi芯片而非由完整用户接口的网卡,所以目前它还没有网络驱动。在之后有时间的时候,我也将尝试为它加入网络驱动。