编辑
2024-12-17
工作知识
0

之前说明了systemtap在ubuntu上安装和测试的基本方法,这个以具体的内核函数的观察来实践一把,主要完成ubuntu上观测鼠标移动和亮度设置函数调用。主要步骤如下

  1. 通过代码确定鼠标移动和亮度设置的内核代码函数

  2. 通过stap -L确定stap是否能够识别这些函数

  3. 编写stap脚本,在函数call的时候进行一些新的打印

  4. 运行stap脚本,在ubuntu上滑动鼠标和调节亮度触发观测点

一.确定代码

鼠标移动代码

drivers/gpu/drm/radeon/radeon_cursor.c radeon_crtc_cursor_move

亮度调节代码

drivers/gpu/drm/radeon/atombios_encoders.c atombios_set_backlight_level

二.确定这些函数是否stap识别

鼠标移动

# stap -L 'module("radeon").function("radeon_crtc_cursor_move")' module("radeon").function("radeon_crtc_cursor_move@drivers/gpu/drm/radeon/radeon_cursor.c:264") $crtc:struct drm_crtc* $x:int $y:int

亮度调节

# stap -L 'module("radeon").function("atombios_set_backlight_level")' module("radeon").function("atombios_set_backlight_level@drivers/gpu/drm/radeon/atombios_encoders.c:94") $radeon_encoder:struct radeon_encoder* $level:u8 $args:DISPLAY_DEVICE_OUTPUT_CONTROL_PARAMETERS

三.编写stap脚本

在确定了stap能够识别这个函数之后,可以写观测脚本如下

观测鼠标移动

probe module("radeon").function("radeon_crtc_cursor_move").call { printf("[func]%s :", probefunc()); printf("x=%d y=%d\n", $x, $y); print_backtrace(); }

这里打印了函数名称,函数的入参x和y,函数的堆栈

观测亮度调节

probe module("radeon").function("atombios_set_backlight_level").call { printf("[func]%s \n", probefunc()); printf("level=%d \n", $level); printf("encoder_id=%d \n", $radeon_encoder->encoder_id); print_backtrace(); }

四 运行stap脚本

stap -v test.stp

运行到pass 5时,可以开始进行测试了

这时候滑动鼠标和通过系统设置调节亮度时,stap这里会打印如下的日志

鼠标的

[func]radeon_crtc_cursor_move :x=779 y=428 0xffffffffc03daab0 : radeon_crtc_cursor_move+0x0/0x50 [radeon] 0xffffffffc01785d4 0xffffffffc01785d4 0xffffffffc01789da 0xffffffffc015d30e 0xffffffffc015d5a7 0xffffffffc03b204e : radeon_drm_ioctl+0x4e/0x80 [radeon] 0xffffffffa0135021 : __x64_sys_ioctl+0x91/0xc0 [kernel] 0xffffffffa09f2ed1 : do_syscall_64+0x61/0xb0 [kernel] 0xffffffffa0c0007c : entry_SYSCALL_64_after_hwframe+0x44/0xae [kernel]

亮度的

[func]atombios_set_backlight_level level=156 encoder_id=33 0xffffffffc042ba70 : atombios_set_backlight_level+0x0/0x180 [radeon] 0xffffffffc042bc20 : radeon_atom_backlight_update_status+0x30/0x40 [radeon] 0xffffffffa045206b : backlight_device_set_brightness+0x6b/0xd0 [kernel] 0xffffffffa0452131 : brightness_store+0x61/0x80 [kernel] 0xffffffffa058bc57 : dev_attr_store+0x17/0x30 [kernel] 0xffffffffa01e007e : sysfs_kf_write+0x3e/0x50 [kernel] 0xffffffffa01df188 : kernfs_fop_write_iter+0x138/0x1c0 [kernel] 0xffffffffa011b377 : new_sync_write+0x117/0x1b0 [kernel] 0xffffffffa011db55 : vfs_write+0x185/0x250 [kernel] 0xffffffffa011dda7 : ksys_write+0x67/0xe0 [kernel] 0xffffffffa011de3a : __x64_sys_write+0x1a/0x20 [kernel] 0xffffffffa09f2ed1 : do_syscall_64+0x61/0xb0 [kernel] 0xffffffffa0c0007c : entry_SYSCALL_64_after_hwframe+0x44/0xae [kernel] 0xffffffffa0c0007c : entry_SYSCALL_64_after_hwframe+0x44/0xae [kernel] 0xffffffffa0451b50 : brightness_show+0x0/0x30 [kernel] 0xffffffffa058c80d : dev_attr_show+0x1d/0x40 [kernel] 0xffffffffa01e0841 : sysfs_kf_seq_show+0xa1/0x100 [kernel] 0xffffffffa01de9e7 : kernfs_seq_show+0x27/0x30 [kernel] 0xffffffffa014ba00 : seq_read_iter+0x120/0x450 [kernel] 0xffffffffa01df4a0 : kernfs_fop_read_iter+0x150/0x1b0 [kernel] 0xffffffffa011b1d0 : new_sync_read+0x110/0x1a0 [kernel] 0xffffffffa011d93e : vfs_read+0xfe/0x190 [kernel] 0xffffffffa011dc87 : ksys_read+0x67/0xe0 [kernel] 0xffffffffa011dd1a : __x64_sys_read+0x1a/0x20 [kernel] 0xffffffffa09f2ed1 : do_syscall_64+0x61/0xb0 [kernel] 0xffffffffa0c0007c : entry_SYSCALL_64_after_hwframe+0x44/0xae [kernel] 0xffffffffa0c0007c : entry_SYSCALL_64_after_hwframe+0x44/0xae [kernel]

至此,就可以通过stap完成对内核函数的观测。只要stap能够识别到内核的函数,就能正常的观测。

这里再解释一下stap默认的函数意思

1.kernel.function("function") 这是指的内核的功能函数 2.module("module").function("function") 这是模块的功能函数 3.probe begin { printf ("hello world\n") } 在观测前打印 4.thread_indent() 返回时间戳+进程名+线程id 例如 1159 ftp(7223) 5.target() 如果stap使用 stap -x PID 这里可以用 if (pid() == target()) 来筛选观测的应用 6.$var 是表述一个变量 例如 printf("x=%d y=%d\n", $x, $y); 这个变量可以通过stap -L 查看到,然后根据实际情况去printf就行 print_backtrace() 打印内核堆栈回溯 ppfunc() 返回从中解析的函数名

如果想观测一个内核文件所有的函数是否被调用,可以

probe module("radeon").function("*@drivers/gpu/drm/radeon/radeon_cursor.c").call

这里格式应该是

function@path:line

如果取

*@path 也就是 *@drivers/gpu/drm/radeon/radeon_cursor.c

代表这个文件的所有函数都观测

参考链接

https://sourceware.org/systemtap/tutorial/tutorialli1.html
https://spacewander.gitbooks.io/systemtapbeginnersguide_zh/content/
https://sourceware.org/systemtap/tapsets/
https://sourceware.org/systemtap/examples/
https://sourceware.org/systemtap/SystemTap_Beginners_Guide/
https://sourceware.org/systemtap/langref/
https://sourceware.org/systemtap/man/index.html

当然,stap也可以观测用户程序,后续有时间再演示吧

编辑
2024-12-17
工作知识
0

主要步骤:

  1. 浏览器添加扩展tampermonkey

  2. 在tampermonkey里面搜索脚本并安装

  3. 进入bdwp网页版,然后点击简易下载助手(仅单个文件)

  4. 使用aria2下载

一:添加扩展

在浏览器设置--->扩展程序--->搜索扩展程序---->Tampermonkey---->安装

二:搜索脚本

地址栏最右边---->点击扩展图标---->获取新脚本--->点击GreasyFork

出现如下

image.png

搜索 "bdwp" 的中文 得到《bdwpjyxzzs(zlxzfhb)》

点进去,点击安装即可

三:点击下载

进入网页版,可以看到多了一个按键如下

image.png

然后下载单个文件,点击获取直链地址

四:使用aria2下载

vbs文件

CreateObject("WScript.Shell").Run "aria2c.exe --conf-path=config.conf",0

然后网页登录

http://aria2c.com

等等即可下载

编辑
2024-12-17
工作知识
0

在ldd上看到了systemtap这个东西,他是基于kprobe的内核调试工具,这里主要描述一下systemtap前期的环境搭建,和运行简单的systemtap命令

主要有如下几个步骤

  1. 拉取内核源码

  2. 配置内核config和编译

  3. 编译systemtap源码

  4. 使用stap命令进行简单的测试使用

一:内核源码

这里我使用的是ubuntu2004,可用直接通过apt命令拉取内核代码

apt download linux-source-5.13.0 得到linux-source-5.13.0_5.13.0-37.42_all.deb

安装这个deb

dpkg -i linux-source-5.13.0_5.13.0-37.42_all.deb 得到/usr/src/linux-source-5.13.0 tar xvjf linux-source-5.13.0.tar.bz2 cp -rf debian linux-source-5.13.0

二:编译

在内核里面可用看到默认的defconfig arch/x86/configs/x86_64_defconfig

编译defconfig

make x86_64_defconfig

安装编译内核相关的依赖包

apt-get build-dep linux-source-5.13.0

systemtap依赖一些内核选项

CONFIG_DEBUG_FS CONFIG_DEBUG_KERNEL CONFIG_DEBUG_INFO CONFIG_KPROBES

这里修改.config加上即可

直接编译内核即可

make bindeb-pkg -j16

编译完成之后,会在上级目录生成对应的deb文件如下

linux-headers-5.13.19_5.13.19-3_amd64.deb linux-image-5.13.19-dbg_5.13.19-3_amd64.deb linux-image-5.13.19_5.13.19-3_amd64.deb linux-libc-dev_5.13.19-3_amd64.deb

直接安装上述deb即可

重启机器,让系统从新的内核上启动,开机之后,可用uname -a查看内核版本

/boot/vmlinuz -> vmlinuz-5.13.19 uname -a Linux kylin 5.13.19 #3 SMP Tue Apr 19 17:19:44 CST 2022 x86_64 x86_64 x86_64 GNU/Linux

三 编译systemtap

内核准备好了,但是因为内核是5.13.19。而系统中默认安装的systemtap支持不了这么高的内核版本,如果使用ubuntu2004默认提供的systemtap,会报如下错误

fails with vermagic.h:6:2: error: #error "This header can be included from kernel/module.c or *.mod.c only"

所以需要编译新的systemtap

从git拉systemtap的仓库代码

git clone git://sourceware.org/git/systemtap.git

默认使用最新的分支

安装编译的依赖

apt-get build-dep systemtap

编译

./configure make all -j8 make install -j8

编完之后,stap默认安装在local目录

/usr/local/bin/stap --version Systemtap translator/driver (version 4.6/0.176, non-git sources) Copyright (C) 2005-2021 Red Hat, Inc. and others This is free software; see the source for copying conditions. tested kernel versions: 2.6.32 ... 5.15.0-rc7 enabled features: AVAHI BOOST_STRING_REF BPF LIBSQLITE3 NLS NSS

创建软链接

ln -s /usr/local/bin/stap /usr/bin/stap

从上面可用看到,这个stap可以支持最高到5.15.0-rc7的内核版本

四 简单测试

stap -l 'kernel.function("acpi_*")'

上面命令运行之后,会查看到内核的以acpi_开头的所有函数

如果需要使用stp文件进行加载,最简单的hello world如下

probe begin { print ("Hello World\n") exit () }

通过stap命令运行

stap -v hello.stp

能够出现Hello World即可正常

下面测试最简单的内核ko。

编写hello驱动

cat hello.c #include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) { printk(KERN_ALERT "Hello, world\n"); return 0; } static void hello_exit(void) { printk(KERN_ALERT "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
cat Makefile # If KERNELRELEASE is defined, we've been invoked from the # kernel build system and can use its language. CFLAGS_MODULE += -g ifneq ($(KERNELRELEASE),) obj-m := hello.o # Otherwise we were called directly from the command # line; invoke the kernel build system. else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif

make生成hello.ko

将其拷贝到stap识别的目录上

mkdir /usr/lib/modules/`uname -r`/extra/ cp hello.ko /usr/lib/modules/5.13.19/extra/hello.ko

查看stap能否识别到hello.ko

stap -l 'module("hello").function("*")' module("hello").function("hello_exit@/root/ko/hello.c:10") module("hello").function("hello_init@/root/ko/hello.c:5")

编写hello.stp 做简单测试

cat hello.stp #!/usr/bin/stap probe module("hello").function("hello_exit").call { printf("Stap say hello world!\n"); }

先加载ko

insmod /usr/lib/modules/5.13.19/extra/hello.ko

然后加载stap脚本

stap -v hello.stp Pass 1: parsed user script and 481 library scripts using 103440virt/86100res/7488shr/78676data kb, in 420usr/80sys/507real ms. Pass 2: analyzed script: 1 probe, 0 functions, 0 embeds, 0 globals using 105444virt/89108res/8400shr/80680data kb, in 40usr/90sys/123real ms. Pass 3: using cached /root/.systemtap/cache/2a/stap_2a22c74fa0e66004c4e56eb4b264ae1b_1029.c Pass 4: using cached /root/.systemtap/cache/2a/stap_2a22c74fa0e66004c4e56eb4b264ae1b_1029.ko Pass 5: starting run.

看到Pass 5了,说明脚本在监听了

在另一个终端卸载ko驱动 就能监听到hello_exit函数的调用了

rmmod hello 后终端会如下显示 Stap say hello world!

参考链接:

https://wiki.debian.org/BuildADebianKernelPackage
https://wiki.ubuntu.com/Kernel/Systemtap
https://sourceware.org/systemtap/wiki

编辑
2024-12-17
工作知识
0

因为docker能够进行隔离,基于docker会比chroot更加安全。这里研究了一下docker怎么使用rootfs。主要步骤如下:

  1. 制作rootfs的ext4的img镜像

  2. 通过docker import导入image

  3. 通过docker run启动这个image

  4. 进入系统后,修改并通过docker commit提交

一:制作rootfs

这里制作rootfs和之前文章一样,大概步骤如下

dd if=/dev/zero of=rootfs.img bs=1k count=1024 mkfs.ext4 rootfs.img e2fsck -fy rootfs.img resize2fs rootfs.img 10G mount rootfs.img rootfs cp rootfs_file/* rootfs/ umount rootfs

二:导入image

mount rootfs.img rootfs cd rootfs tar -cv . | docker import - test_image 或从某个docker image里面导入 docker export -o rootfs.tar example_image docker import rootfs.tar test_image

三:启动这个镜像

docker run -it --name "`date +%y%m%d-``openssl rand -hex 2`" -p 2222:22 --hostname myOS --privileged --cap-add=sys_admin --env container=docker \ --entrypoint=/usr/lib/systemd/systemd \ --mount type=bind,source=/sys/fs/cgroup,target=/sys/fs/cgroup \ --mount type=bind,source=/sys/fs/fuse,target=/sys/fs/fuse \ --mount type=tmpfs,destination=/tmp \ --mount type=tmpfs,destination=/run \ --mount type=tmpfs,destination=/run/lock \ test_image:latest \ --unit=multi-user.target

这里参数解析如下:

  • --privileged 权限全开

  • --cap-add=sys_admin 允许执行系统管理任务

  • --entrypoint=/usr/lib/systemd/systemd 默认执行systemd

  • --env container=docker 要允许systemd(以及系列程序)能够感知到自己运行在一个容器中,systemd unit配置文件中的 ConditionVirtualization= 设置才能工作

  • --mount type=bind 在环境内进行mount

  • --unit=multi-user.target systemd运行这个target,可以更换自行的target

  • -p 2222:22 将容器的22端口映射到本机的2222端口,这样可以直接 ssh -p 2222 localhost登录容器

四:修改并提交

在容器中修改任何文件和内容之后,可以直接poweroff让容器处于退出状态,然后

docker commit -a "name@test.cn" -m "do something" $CONTAINER_ID test_image:v1

提交之后,通过docker images 可以看到两个images了。这样就可以用新的images进行开发了。

小问题:

docker安装时,无法分配ip

解决方法:直接命令分配即可,然后手动运行dockerd测试验证

ip link add name docker0 type bridge ip addr add dev docker0 172.17.0.1/16

其他小命令

docker image save test_image:v1 -o rootfs.gz docker image lode -i rootfs.gz

参考链接:

https://github.com/docker/for-linux/issues/123#issuecomment-346546953
https://www.kernel.org/doc/html/latest/filesystems/fuse.html
https://docs.docker.com/engine/reference/commandline/import/
https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/syntax.md
https://serverfault.com/questions/607769/running-systemd-inside-a-docker-container-arch-linux
https://cloud-atlas.readthedocs.io/zh_CN/latest/docker/init/docker_systemd.html

编辑
2024-12-17
工作知识
0

之前有借助uml进行启动linux系统,借助uml的基础上,这里使用qemu来实现跨架构的虚拟系统启动,主要包括如下步骤

  1. 编译qemu-system-aarch64

  2. 编译linux内核

  3. 下载aarch64版本的rootfs镜像

  4. 利用qemu-system-aarch64启动内核和系统

一:编译qemu

apt install gcc-10-aarch64-linux-gnu wget https://download.qemu.org/qemu-7.0.0-rc0.tar.bz2 ./configure –target-list=aarch64-softmmu make -j8 && make install apt install gcc-10-aarch64-linux-gnu ln -s /usr/bin/aarch64-linux-gnu-gcc-10 /usr/bin/aarch64-linux-gnu-gcc export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu-

安装之后,可以查看qemu工具版本如下

qemu-system-aarch64 --version QEMU emulator version 6.2.90 Copyright (c) 2003-2022 Fabrice Bellard and the QEMU Project developers

二:编译linux内核

linux源码仍借用 linux-source-5.13.0 这个安装包

apt install linux-source-5.13.0

实际目录在 /usr/src/linux-source-5.13.0/linux-source-5.13.0/

编译命令如下:

ARCH=arm64 CROSS_COPILE=aarch64-linux-gnu- make defconfig ARCH=arm64 CROSS_COPILE=aarch64-linux-gnu- make -j8

编译完成后会生成内核文件为

file arch/arm64/boot/Image.gz arch/arm64/boot/Image.gz: gzip compressed data, max compression, from Unix, original size modulo 2^32 33122816

三.rootfs镜像

wget https://cdimage.ubuntu.com/ubuntu-base/focal/daily/current/focal-base-arm64.tar.gz

将这个tar做成rootfs.img 的ext4格式镜像即可

四.启动系统

直接通过内核启动rootfs镜像如下

qemu-system-aarch64 -M virt -cpu cortex-a72 -kernel Image.gz -append "root=/dev/vda" -hda rootfs.img -nographic

因为是-nographic,所以系统只会串口输出。

下载的rootfs.img默认没有systemd启动,可以chroot进去进行安装对应的包,并设置系统为字符模式,并确保getty能够启动ttyAMA0

chroot rootfs/ apt update systemctl set-default multi-user.target ln -s /lib/systemd/system/getty@.service /etc/systemd/system/getty.target.wants/getty@ttyAMA0.service

到这里,系统已经能够正常启动了,但是系统还不能使用网络,这里仍借助nettap的方法,创建一个tap3的虚拟网卡,然后和虚拟机进行通信即可

主机上创建tap3网卡如下

ip tuntap add tap3 mode tap group tf ip addr add 192.168.0.100/24 dev tap3 ip link set dev tap3 up echo 1 > /proc/sys/net/ipv4/conf/tap3/proxy_arp iptables -I FORWARD -i tap3 -j ACCEPT iptables -I FORWARD -o tap3 -j ACCEPT

qemu启动命令如下

qemu-system-aarch64 -M virt -cpu cortex-a72 -smp 4 -m 4096 -kernel Image.gz -nographic -append "console=ttyAMA0 root=/dev/vda rw rootfstype=ext4 ignore_loglevel" -drive if=none,file=rootfs.img,id=hd0 -device virtio-blk-device,drive=hd0 -device virtio-blk-device,drive=hd0 -netdev tap,id=n1,ifname=tap3,script=no -device e1000,netdev=n1,mac=02:27:d1:32:44:7f

-netdev tap,id=n1,ifname=tap3,script=no

指明主机网卡使用名字为tap3的tap类型网卡,并禁用系统的ifup脚本。id为n1

-device e1000,netdev=n1,mac=02:27:d1:32:44:7f

指明虚拟机使用e1000网卡驱动,设备为id=n1的网卡,mac地址为主机tap3的mac地址

ifconfig tap3 | grep ether ether 02:27:d1:32:44:7f txqueuelen 1000 (以太网)

虚拟机内网络配置如下

/etc/network/interfaces auto enp0s1 allow-hotplug enp0s1 iface enp0s1 inet static address 192.168.0.200 netmask 255.255.255.0 gateway 192.168.0.1

然后就可以ssh登录即可

扩展:

使用qemu-system-aarch64进行uboot的仿真(通过浏览网页发现的)

  1. 编译uboot
git clone https://github.com/ARM-software/u-boot.git ARCH=arm64 CROSS_COPILE=aarch64-linux-gnu- make qemu_arm64_defconfig ARCH=arm64 CROSS_COPILE=aarch64-linux-gnu- make -j8
  1. 启动uboot
qemu-system-aarch64 -machine virt -cpu cortex-a57 -bios u-boot.bin -nographic

uboot就成功启动了

参考链接:

https://github.com/ARM-software/u-boot/blob/master/doc/README.qemu-arm
https://www.qemu.org/docs/master/system/linuxboot.html
https://pandysong.github.io/blog/post/run_u-boot_in_qemu/
https://stackoverflow.com/questions/58028789/how-to-build-and-boot-linux-aarch64-with-u-boot-with-buildroot-on-qemu
https://zhuanlan.zhihu.com/p/41258581
https://wiki.qemu.org/Documentation/Networking