写点什么

另一种学习 Linux kernel 的方式 —— UML

作者:袁世超
  • 2023-12-06
    北京
  • 本文字数:4052 字

    阅读完需:约 13 分钟

之前使用 QEMU 来学习内核——《通过 QEMU 打开学习 Linux kernel 的新世界》,稍微麻烦一些但是挺好用,最近又学到了一个更方便的方式——User-mode Linux。

1. User-mode Linux 是什么?

顾名思义就是用户态的 Linux,跟 QEMU 这类虚拟机对比一下就比较清楚了


更多细节详见 Virtual Labs with User Mode Linux


可想而知 UML 存在的主要价值就是测试,例如 KUnitAndroid Network Tests

2. 编译 UML

kernel.org 下载当前的 longterm 版本 6.1.65

make defconfig ARCH=um SUBARCH=x86_64make linux ARCH=um SUBARCH=x86_64 -j `nproc`
复制代码

指定编译的架构是 um,也就是 User-mode

(由此可见 UML 也是有限制的,如果要 debug 的逻辑是特定架构的,那还得用 QEMU)


最终会编译成 linux 可执行文件:

❯ ./linux --helpUser Mode Linux v6.1.65    available at http://user-mode-linux.sourceforge.net/
--showconfig Prints the config file that this UML binary was generated from.
......
hostfs=<root dir>,<flags>,... This is used to set hostfs parameters. The root directory argument is used to confine all hostfs mounts to within the specified directory tree on the host. If this isn't specified, then a user inside UML can mount anything on the host that's accessible to the user that's running it. The only flag currently supported is 'append', which specifies that all files opened by hostfs will be opened in append mode.
复制代码

3. 建立 rootfs

建立 rootfs 的方式很多,这里用 Alpine Linux 建立 rootfs

mkdir -p rootfscurl https://dl-cdn.alpinelinux.org/alpine/v3.18/main/x86_64/apk-tools-static-2.14.0-r2.apk | fakeroot tar -xz -C rootfs
复制代码

下载 APK 工具到 rootfs 目录


fakeroot rootfs/sbin/apk.static \    --repository https://dl-cdn.alpinelinux.org/alpine/v3.18/main --update-cache \    --allow-untrusted \    --root $PWD/rootfs --initdb add alpine-baseecho https://dl-cdn.alpinelinux.org/alpine/v3.18/main > rootfs/etc/apk/repositoriesecho "LABEL=ALPINE_ROOT / auto defaults 1 1" >> rootfs/etc/fstab
复制代码

建立 rootfs

4. 启动 UML

编写启动脚本 UML.sh

#!/bin/sh./linux ubd0=/dev/null umid=uml0 \        root=/dev/root rootfstype=hostfs hostfs=./rootfs \        rw mem=64M init=/bin/sh quietstty sane ; echo
复制代码


执行该脚本,瞬间就启动了一个 linux,真的是瞬间


首次启动初始化一下 busybox,挂载一下目录:

~ # /bin/busybox --install~ # mount -t proc none /proc
复制代码


查看当前的系统信息:

~ # cat /proc/cpuinfo processor  : 0vendor_id  : User Mode Linuxmodel name  : UMLmode    : skashost    : Linux yuan-T14p 6.2.0-37-generic #38~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Nov  2 18:01:13 UTC 2 x86_64fpu    : yesflags    : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 pn clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall mp nx pdpe1gb rdtscp lm 3dnow constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cid sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb pti ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdseed adx smap clflushopt clwb intel_pt sha_ni xsaveopt xsavec xgetbv1 xsaves avx_vnni brs dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req hfi umip pku ospke waitpkg gfni vaes vpclmulqdq tme rdpid movdiri movdir64b fsrm md_clear serialize pconfig arch_lbr ibt flush_l1d arch_capabilities smecache_alignment  : 64bogomips  : 10738.07
复制代码


确实不是当前 host 的了!

5. 设置 init.sh

下载 tini 工具:

wget -O rootfs/sbin/tini https://github.com/krallin/tini/releases/download/v0.19.0/tini-staticchmod +x rootfs/sbin/tini
复制代码


编写 rootfs/init.sh

#!/bin/sh
mount -t proc proc /procmount -t sysfs sys /sys
ip link set eth0 upip address add 192.168.100.101/24 dev eth0
export PS1='\[\033[01;32mUML:\w\033[00m \$ 'exec /sbin/tini /bin/sh +m
复制代码


修改 UML.sh

#!/bin/sh./linux ubd0=/dev/null umid=uml0 hostname=uml1 eth0=tuntap,tap0 \        root=/dev/root rootfstype=hostfs hostfs=./rootfs \        rw mem=64M init=/init.sh quietstty sane ; echo
复制代码

6. 配置 GDB

6.1.65 这个版本有点儿问题,首先得改一下 scripts/gdb/linux/constants.py.in

/* linux/fs.h */- LX_VALUE(SB_RDONLY)- LX_VALUE(SB_SYNCHRONOUS)- LX_VALUE(SB_MANDLOCK)- LX_VALUE(SB_DIRSYNC)- LX_VALUE(SB_NOATIME)- LX_VALUE(SB_NODIRATIME)+ LX_GDBPARSED(SB_RDONLY)+ LX_GDBPARSED(SB_SYNCHRONOUS)+ LX_GDBPARSED(SB_MANDLOCK)+ LX_GDBPARSED(SB_DIRSYNC)+ LX_GDBPARSED(SB_NOATIME)+ LX_GDBPARSED(SB_NODIRATIME)
复制代码


然后执行:

echo "CONFIG_GDB_SCRIPTS=y" > .config-fragmentARCH=um scripts/kconfig/merge_config.sh .config .config-fragmentmake ARCH=um scripts_gdb
复制代码


验一下

❯ gdb -ex "add-auto-load-safe-path scripts/gdb/vmlinux-gdb.py" \      -ex "file vmlinux" \      -ex "lx-version" -qReading symbols from vmlinux...Linux version 6.1.65 (yuan@yuan-T14p) (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #1 Wed Dec  6 21:00:15 CST 2023(gdb) 
复制代码


编写 gdbinit

python gdb.COMPLETE_EXPRESSION = gdb.COMPLETE_SYMBOLadd-auto-load-safe-path scripts/gdb/vmlinux-gdb.pyfile vmlinuxlx-versionset args ubd0=/dev/null umid=uml0 hostname=uml1 eth0=tuntap,tap0 root=/dev/root rootfstype=hostfs rootflags=${FULLPATH}/rootfs rw mem=64M init=/init.sh quiethandle SIGSEGV nostop noprinthandle SIGUSR1 nopass stop print
复制代码

(注意替换其中的 FULLPATH)


在 GDB 内启动 UML:

gdb -q -x gdbinit(gdb) run
复制代码


加个断点试一下:

UML:~ #         Thread 1 "vmlinux" received signal SIGUSR1, User defined signal 1.                                                                          0x00007ffff7c427dc in __GI___sigsuspend (set=set@entry=0x604c7db0) at ../sysdeps/unix/sysv/linux/sigsuspend.c:2626  ../sysdeps/unix/sysv/linux/sigsuspend.c: No such file or directory.(gdb) b cmdline_proc_showBreakpoint 1 at 0x6014c793: file fs/proc/cmdline.c, line 8.(gdb) cContinuing.
UML:~ # cat /proc/cmdline
Thread 1 "vmlinux" hit Breakpoint 1, cmdline_proc_show (m=0x60a6a690, v=0x1) at fs/proc/cmdline.c:88 {(gdb) bt#0 cmdline_proc_show (m=0x60a6a690, v=0x1) at fs/proc/cmdline.c:8#1 0x000000006010f41e in seq_read_iter (iocb=0x6489fc40, iter=0x6489fc18) at fs/seq_file.c:230#2 0x0000000060144a37 in proc_reg_read_iter (iocb=<optimized out>, iter=<optimized out>) at fs/proc/inode.c:305#3 0x00000000601163af in call_read_iter (file=0x60808100, file=0x60808100, iter=0x6489fc18, kio=0x6489fc40) at ./include/linux/fs.h:2242#4 generic_file_splice_read (in=0x60808100, ppos=0x6489fd28, pipe=0x60a5a240, len=<optimized out>, flags=<optimized out>) at fs/splice.c:309#5 0x000000006011623d in do_splice_to (in=in@entry=0x60808100, ppos=ppos@entry=0x6489fd28, pipe=pipe@entry=0x60a5a240, len=65536, len@entry=16777216, flags=flags@entry=0) at fs/splice.c:793#6 0x0000000060116d5b in splice_direct_to_actor (in=in@entry=0x60808100, sd=sd@entry=0x6489fd78, actor=actor@entry=0x60115ece <direct_splice_actor>) at fs/splice.c:865#7 0x0000000060116eb8 in do_splice_direct (in=in@entry=0x60808100, ppos=ppos@entry=0x6489fdf8, out=out@entry=0x60808b00, opos=opos@entry=0x6489fe00, len=len@entry=16777216, flags=flags@entry=0) at fs/splice.c:974#8 0x00000000600e9a4e in do_sendfile (out_fd=<optimized out>, in_fd=<optimized out>, ppos=ppos@entry=0x0, count=16777216, max=2147483647, max@entry=0) at fs/read_write.c:1255#9 0x00000000600ea90f in __do_sys_sendfile64 (count=<optimized out>, offset=<optimized out>, in_fd=<optimized out>, out_fd=<optimized out>) at fs/read_write.c:1323#10 __se_sys_sendfile64 (out_fd=<optimized out>, in_fd=<optimized out>, offset=<optimized out>, count=<optimized out>) at fs/read_write.c:1309#11 0x00000000600231ee in handle_syscall (r=r@entry=0x60a5ebc0) at arch/um/kernel/skas/syscall.c:45#12 0x0000000060035cc3 in handle_trap (local_using_sysemu=2, regs=0x60a5ebc0, pid=75674) at arch/um/os-Linux/skas/process.c:224#13 userspace (regs=0x60a5ebc0, aux_fp_regs=0x6489c020) at arch/um/os-Linux/skas/process.c:480#14 0x000000006001ff37 in fork_handler () at arch/um/kernel/process.c:154#15 0x0000000000000000 in ?? ()Backtrace stopped: Cannot access memory at address 0x648a0000(gdb)
复制代码


赞!

发布于: 16 分钟前阅读数: 10
用户头像

袁世超

关注

还未添加个人签名 2017-11-30 加入

还未添加个人简介

评论

发布
暂无评论
另一种学习 Linux kernel 的方式 —— UML_UML_袁世超_InfoQ写作社区