1.
安装编译工具链
由于Ubuntu是X86架构,为了编译arm64的文件,需要安装交叉编译工具链
sudo apt install gcc-aarch64-linux-gnu
sudo apt install libncurses5-dev build-essential git bison flex libssl-dev
2.第二步是制作根文件系统
linux的启动需要配合根文件系统,和实验三一样我们利用busybox来制作一个简单的根文件系统
我们在实验三的时候利用了busybox制作简单的根文件系统,在本次实验和上次相同,也是制作一个简单的根文件系统
cd ~/linux_lab/lab4
wget https://busybox.net/downloads/busybox-1.33.1.tar.bz2
tar -jxvf busybox-1.33.1.tar.bz2
cd busybox-1.33.1
3. 类似lab3 我们打开静态库编译选项
make menuconfig
接着,我们指定编译工具
export ARCH=arm64
export CROSS_COMPILE=aarch64-linux-gnu-
然后进行编译
make -j$(nproc) && make install
这样我们就做完了准备工作
4.为了实现正常启动,我们还需要额外的配置
我们编译完成后有_intstall目录
进入_install目录
根目录添加etc、dev和lib目录
我们在etc目录下创建文件profile、inittab、fstab、init.d/rcS
vim profile
#!/bin/sh
export HOSTNAME=imingz
export USER=root
export HOME=/home
export PS1="[$USER@$HOSTNAME \W]\# "
PATH=/bin:/sbin:/usr/bin:/usr/sbin
LD_LIBRARY_PATH=/lib:/usr/lib:$LD_LIBRARY_PATH
export PATH LD_LIBRARY_PATH
vim inittab
::sysinit:/etc/init.d/rcS
::respawn:-/bin/sh
::askfirst:-/bin/sh
::ctrlaltdel:/bin/umount -a -r
vim fstab
#device mount-point type options dump fsck order
proc /proc proc defaults 0 0
tmpfs /tmp tmpfs defaults 0 0
sysfs /sys sysfs defaults 0 0
tmpfs /dev tmpfs defaults 0 0
debugfs /sys/kernel/debug debugfs defaults 0 0
kmod_mount /mnt 9p trans=virtio 0 0
mkdir -p init.d
vim init.d/rcS
mkdir -p /sys
mkdir -p /tmp
mkdir -p /proc
mkdir -p /mnt
/bin/mount -a
mkdir -p /dev/pts
mount -t devpts devpts /dev/pts
echo /sbin/mdev > /proc/sys/kernel/hotplug
mdev -s
如下图所示:
对其他文件夹的操作如下图所示:
我们像实验三一样,制作linux
因为本次实验是在arm64的情况下进行编译的
所以要求如下所示:
我们将之前制作好的根文件系统cp到root目录下
cp -r ../busybox-1.33.1/_install root
sudo mknod root/dev/console c 5 1
执行编译
启动qemu
使用 4.2.1 版本的 qemu
sudo apt install build-essential zlib1g-dev pkg-config libglib2.0-dev binutils-dev libboost-all-dev autoconf libtool libssl-dev libpixman-1-dev libpython-dev python-pip python-capstone virtualenv
./configure --target-list=x86_64-softmmu,x86_64-linux-user,arm-softmmu,arm-linux-user,aarch64-softmmu,aarch64-linux-user --enable-kvm
make
sudo make install
执行命令:
执行命令:
/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel /home/hanjiajun/linux/lab4/linux-5.4.34/arch/arm64/boot/Image -append "rdinit=/linuxrc nokaslr console=ttyAMA0 loglevel=8" -nographic -s
进行编译
接下来就是本次实验的重点了:
分析 time/gettimeofday 系统调用在 ARM64 Linux 中的执行过程
在VSCODE中调试:
分析:
通过查看调用堆栈,我们能很容易地分析出 ARM64 下系统调用的执行过程。ARM64 架构下 Linux 系统调用由同步异常 svc 指令触发,当用户态(EL0 级)程序调用库函数 gettimeofday() 从而触发系统调用的时候,先把系统调用的参数依次放入 X0-X5 这 6 个寄存器(Linux 系统调用最多有 6 个参数,ARM64 函数调用参数可以使用 X0-X7 这 8 个寄存器),然后把系统调用号放在 X8 寄存器里,最后执行 svc 指令,CPU 即进入内核态(EL1 级)。本文使用内嵌汇编触发系统调用,我们也编写相应的汇编代码完成了上述过程。
ARM64 架构的 CPU 中,Linux 系统调用(同步异常)和其他异常的处理过程大致相同。异常发生时,CPU 首先把异常的原因(比如执行 svc 指令触发系统调用)放在 ESR_EL1 寄存器里;把当前的处理器状态(PSTATE)放入 SPSR_EL1 寄存器里;把当前程序指针寄存器 PC 的值存入 ELR_EL1 寄存器里(保存断点),然后 CPU 通过异常向量表(vectors)基地址和异常的类型计算出异常处理程序的入口地址,即 VBAR_EL1 寄存器加上偏移量取得异常处理的入口地址,接着开始执行异常处理入口的第一行代码。这一过程是 CPU 硬件自动完成的,不需要程序干预。
随后,以 svc 指令对应的 el0_sync 为例,el0_sync 处的内核汇编代码首先做的就是保存异常发生时程序的执行现场(保存现场,即用户栈、通用寄存器等),然后根据异常发生的原因(ESR_EL1 寄存器中的内容)跳转到 el0_svc,el0_svc 会调用 el0_svc_handler、el0_svc_common 函数,将 X8 寄存器(regs->regs[8])中存放的系统调用号传递给 invoke_syscall 函数。
系统调用内核处理函数执行完成后,会将系统调用的返回值存放在 X0 寄存器中。
我们可以看到 被存放在了X0寄存器中