目录
环境:ubuntu-18.04.6
安装编译工具链
这里需要注意的是,由于我们是arm64内核,因此需要用gdb-multiarch来进行调试
sudo apt install gcc-aarch64-linux-gnu sudo apt install libncurses5-dev build-essential git bison flex libssl-dev sudo apt install gdb-multiarch
制作根文件系统
linux的启动需要配合根文件系统,这里我们利用busybox来制作一个简单的根文件系统
-
wget https://busybox.net/downloads/busybox-1.33.1.tar.bz2
-
tar -xjf busybox-1.33.1.tar.bz2
-
cd busybox-1.33.1
打开静态库编译选项
make menuconfig Settings ---> [*] Build static binary (no shared libs)
指定编译工具
-
export ARCH=arm64
-
export CROSS_COMPILE=aarch64-linux-gnu-
编译
make make install
编译完成,在busybox目录下生成_install目录
制作文件系统
-
cd _install
-
mkdir etc dev lib
etc
在etc分别创建文件:profile、inittab、fstab、init.d/rcS
-
cd etc
-
-
code 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
-
-
code inittab
-
-
::sysinit:/etc/init.d/rcS
-
::respawn:-/bin/sh
-
::askfirst:-/bin/sh
-
::ctrlaltdel:/bin/umount -a -r
-
-
code 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
-
-
code 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
dev
sudo mknod console c 5 1
lib
cp /usr/aarch64-linux-gnu/lib/*.so* -a .
编译linux内核
-
make defconfig ARCH=arm64
-
-
将下面的配置加入.config文件下面
-
code .config
-
-
CONFIG_DEBUG_INFO=y
-
CONFIG_INITRAMFS_SOURCE=
"./root"
-
CONFIG_INITRAMFS_ROOT_UID=0
-
CONFIG_INITRAMFS_ROOT_GID=0
-
-
将之前制作好的根文件系统
cp到root目录下
-
cp -r ../busybox-1.33.1/_install root
-
sudo
mknod root/dev/console c 5 1
-
执行编译
make ARCH=arm64 Image -j8 CROSS_COMPILE=aarch64-linux-gnu-
启动qemu
-
apt-get 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
-
wget https://download.qemu.org/qemu-4.2.1.tar.xz
-
tar xvJf qemu-4.2.1.tar.xz
-
cd qemu-4.2.1
-
./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
启动linux内核
/usr/local/bin/qemu-system-aarch64 -m 512M -smp 4 -cpu cortex-a57 -machine virt -kernel ~/lab4/linux-5.4.34/arch/arm64/boot/Image -append "rdinit=/linuxrc nokaslr console=ttyAMA0 loglevel=8" -nographic -s
成功启动linux后
调试准备
使用gettimeofday库函数
-
#include <stdio.h>
-
#include <time.h>
-
#include <sys/time.h>
-
-
int main()
-
{
-
time_t tt;
-
struct timeval tv;
-
struct tm *t;
-
#if 0
-
gettimeofday(&tv,
NULL);
-
#else
-
asm
volatile(
-
"add x0, x29, 16\n\t"
//X0寄存器用于传递参数&tv
-
"mov x1, #0x0\n\t"
//X1寄存器用于传递参数NULL
-
"mov x8, #0xa9\n\t"
//使用X8传递系统调用号169
-
"svc #0x0\n\t"
//触发系统调用
-
);
-
#endif
-
tt = tv.tv_sec;
//tv是保存获取时间结果的结构体
-
t = localtime(&tt);
//将世纪秒转换成对应的年月日时分秒
-
printf(
"time: %d/%d/%d %d:%d:%d\n",
-
t->tm_year +
1900,
-
t->tm_mon,
-
t->tm_mday,
-
t->tm_hour,
-
t->tm_min,
-
t->tm_sec);
-
return
0;
-
}
把test.c进行交叉编译,需要静态编译,动态链接库找不到 time 的系统调用,只能找到函数
aarch64-linux-gnu-gcc -o test test.c -static
把test放到root文件夹下,重新编译后就可查看到test文件
make ARCH=arm64 Image -j8 CROSS_COMPILE=aarch64-linux-gnu-
使用vscode调试需要在.vscode配置launch.json 和 tasks.json
-
{
-
// launch.json
-
// Use IntelliSense to learn about possible attributes.
-
// Hover to view descriptions of existing attributes.
-
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
-
"version":
"0.2.0",
-
"configurations": [
-
{
-
"name":
"(gdb) linux",
-
"type":
"cppdbg",
-
"request":
"launch",
-
"preLaunchTask":
"vm",
-
"program":
"${workspaceRoot}/vmlinux",
-
"miDebuggerPath":
"/usr/bin/gdb-multiarch",
-
"miDebuggerServerAddress":
"localhost:1234",
-
"args": [],
-
"stopAtEntry":
true,
-
"cwd":
"${workspaceFolder}",
-
"environment": [],
-
"externalConsole":
false,
-
"MIMode":
"gdb",
-
"miDebuggerArgs":
"-n",
-
"targetArchitecture":
"x64",
-
"setupCommands": [
-
{
-
"text":
"dir .",
-
"ignoreFailures":
false
-
},
-
{
-
"text":
"add-auto-load-safe-path ./",
-
"ignoreFailures":
false
-
},
-
{
-
"text":
"-enable-pretty-printing",
-
"ignoreFailures":
true
-
}
-
]
-
}
-
]
-
}
-
{
-
// tasks.json
-
// See https://go.microsoft.com/fwlink/?LinkId=733558
-
// for the documentation about the tasks.json format
-
"version":
"2.0.0",
-
"tasks": [
-
{
-
"label":
"vm",
-
"type":
"shell",
-
"command":
"qemu-system-aarch64 -m 128M -smp 1 -cpu cortex-a57 -machine virt -kernel arch/arm64/boot/Image -initrd ../rootfs-arm.cpio.gz -append \"rdinit=/init console=ttyAMA0 loglevel=8\" -nographic -s",
-
"presentation": {
-
"echo":
true,
-
"clear":
true,
-
"group":
"vm"
-
},
-
"isBackground":
true,
-
"problemMatcher": [
-
{
-
"pattern": [
-
{
-
"regexp":
".",
-
"file":
1,
-
"location":
2,
-
"message":
3
-
}
-
],
-
"background": {
-
"activeOnStart":
true,
-
"beginsPattern":
".",
-
"endsPattern":
".",
-
}
-
}
-
]
-
},
-
{
-
"label":
"build linux",
-
"type":
"shell",
-
"command":
"make",
-
"group": {
-
"kind":
"build",
-
"isDefault":
true
-
},
-
"presentation": {
-
"echo":
false,
-
"group":
"build"
-
}
-
}
-
]
-
}
启动调试
在窗口左下角的断点设置处新增断点 __arm64_sys_gettimeofday
启动linux内核,按下F5进行调试
执行./test
可以看到调用栈如下
在Linux系统中系统调用发生时,CPU会把当前程序指针寄存器PC放入ELR_EL1寄存器里,把PSTATE放入SPSR_EL1寄存器里,同时Linux系统从用户态切换到内核态(从EL0切换到EL1),这时SP指的是SP_EL1寄存器,用户态堆栈的栈顶地址依然保存在SP_EL0寄存器中。也就是说异常(这里是指系统调用)发生时CPU的关键状态sp、pc和pstate分别保存在SP_EL0、ELR_EL1和SPSR_EL1寄存器中。
el0_sync在完成保存现场的工作之后,会根据ESR_EL1寄存器确定同步异常产生的原因(svc指令触发了系统调用),所以排在最前面的就是条件判断跳转到el0_svc,el0_svc中主要负责调用C代码的el0_svc_handler处理系统调用和ret_to_user系统调用返回。
从系统调用返回前会处理一些工作(work_pending),比如处理信号、判断是否需要进程调度等,ret_to_user的最后是kernel_exit 0负责恢复现场,与保存现场kernel_entry 0相对应,kernel_exit 0的最后会执行eret指令系统调用返回。eret指令所做的工作与svc指令相对应,eret指令会将ELR_EL1寄存器里值恢复到程序指针寄存器PC中,把SPSR_EL1寄存器里的值恢复到PSTATE处理器状态中,同时会从内核态转换到用户态,在用户态堆栈栈顶指针sp代表的是sp_el0寄存器。