编辑
2025-01-22
工作知识
0

我们在看内核代码的时候,有一个关于C语言的一个小技巧,个人觉得可以介绍分享一下,做个记录。从而方便大家看代码的时候心里直接有个答案,无需脑子里面再转个弯。

一、直接找到结构体父指针

我们知道内核头文件会定义结构体,在定义结构体的时候,默认会把重要的结构体的第一项作为子结构体。如下示例:

struct __drm_planes_state { struct drm_plane *ptr; struct drm_plane_state *state, *old_state, *new_state; };

这里我们有个C语言的知识点如下:

struct drm_plane *ptr 的地址是struct __drm_planes_state的地址

也就是说,如果我们一直在操作ptr指针,其实我可以随时和任意的操作struct __drm_planes_state指针,伪代码如下:

struct __drm_planes_state* stat = (struct __drm_planes_state*) &ptr; if(stat->state){ ...... }

这里必须要清楚的是,ptr指针一定要是__drm_planes_state的成员,不能是从其他地方构造和赋值的指针值地址,错误的例子如下:

struct drm_plane *p1 = ptr; stat = (struct __drm_planes_state*) &p1;

这里p1我们不能直接去做取&运算,因为它本身不是__drm_planes_state的成员, 它只是普普通通的一个栈区地址。

1.1 测试程序

#include <stdio.h> #include <stdlib.h> struct lower { int a; }; struct upper { struct lower *k; int b; }; int main() { struct upper *u = malloc(sizeof(struct upper)); struct lower *l = u->k; u->b = 2; struct upper* t1 = (struct upper*)&(u->k); struct upper* t2 = (struct upper*)&l; printf("t1=%p t2=%p t1->b=%d \n", t1, t2, t1->b); }

这里我们构造了一个upper的结构体,设置成员b的值为2,然后我们提取了t1和t2,并打印了地址,得到输出如下:

# gcc test.c -o test && ./test t1=0x558beacb70 t2=0x7ffd087628 t1->b=2

发现没,t1大概在堆区地址范围上,t2在栈区范围上,我们通过t1能够直接找到b,其值为2。

image.png

1.2 小结

这里我们知道了一个内核通用技巧,我们可以在内核代码中经常看到直接强制类型转换就拿到了父的结构体的指针,然后直接操作代码。非常方便大家理解内核的逻辑。

以后大家看内核代码的时候,这类操作就不需要停顿下来脑子去转弯了。

二、通过container_of找到结构体父指针

如果经常看代码,我们可以发现内核充斥着大量的container_of函数,这个函数的意思是:

输入一个成员变量实体,一个父结构体类型,一个结构体成员变量声明,输出结构体父指针

为什么会有这样的函数呢,我们可以根据上文我们可以很容易提出疑问

如果我想找到父结构体指针,那么我的成员必定是第一个成员,那多不方便啊。如果这个成员不是第一个,那有啥好办法能够找到父结构体指针呢? 关于此,我们有两个知识点需要准备:

  1. 结构体成员在内存中是顺序存放的
  2. 父结构体指针可以通过→定位到结构体成员

那基于此,很容易得出这么一个想法

我知道结构体的成员地址,然后推算我之前有多少的偏移,直接拿自己的指针减去这个偏移不就是父指针的地址了么 所以,我们开始解析container_of的宏定义,如下:

#define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ ((type *)(__mptr - offsetof(type, member))); })

我们还需要看offsetof的定义,根据内核头文件,我们可以查到:

#define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER)

这里有一个技巧,那就是

运用0地址做强制类型转换,然后去取类型的成员,这样就能拿到成员在结构体的偏移量,然后将其强制类型转换成size_t类型用作指针的减法运算

2.1 测试程序

#include <stdio.h> #include <stdlib.h> #include <stddef.h> #ifndef offsetof #define offsetof(TYPE, MEMBER) ((size_t)&((TYPE *)0)->MEMBER) #endif #define container_of(ptr, type, member) ({ \ void *__mptr = (void *)(ptr); \ ((type *)(__mptr - offsetof(type, member))); }) struct lower { int a; }; struct upper { int c,d,e,f; struct lower *k; int b; }; int main() { struct upper *u = malloc(sizeof(struct upper)); // struct upper* t = ({ void *__mptr = (void *)(&u->k); ((struct upper *)(__mptr - ((size_t)&((struct upper *)0)->k))); }); struct upper* t = container_of(&u->k, struct upper, k); printf("t=%p up=%p k=%p\n", t, u, &u->k); }

这里我们传入u->k的地址,upper的结构体定义,k成员,我们就能拿到upper的指针t

此时我们运行如下:

# gcc test.c -o test && ./test t=0x559829eb70 up=0x559829eb70 k=0x559829eb80

可以发现t,up的地址完全相等,k刚刚差一个offset地址偏移,也就是4*4=16,就是int c,d,e,f;的占用空间

image.png

2.2 小结

这里我们知道了内核非常普遍的container_of的实现,它能够直接获取成员的父结构体指针

三、总结

至此,我们知道了内核操作结构体的小技巧,通过这个技巧,我们可以轻松的找到结构体指针以及成员。这里作为内核的入门知识,对了解内核的人而言至关重要。

编辑
2025-01-22
工作知识
0

对于安卓系统,开机起来之后会全局发送一个BOOT_COMPLETED广播。应用程序通过这个广播就知道系统正常启动完成了。通常情况下,是安卓启动完全完成之后,ams会发送一个FINISH_BOOTING_MSG消息后,开启了30s的timeout检查是否为boot-completed。这里30s是开机动画,如果开机动画正常退出或者超过30s退出,则认为boot-completed。但是,在linux中,我们的系统好像没有利用起来这个机制。

本文基于systemd的默认机制,说明一下linux如何实现boot-completed信号。

一、为什么要boot-completed信号

对于系统而言,应该主动发送boot-completed信号,让其他程序能够获取当前系统的启动状态,如何此信号接收到,则代表系统已经准备完成,ui类型的应用可以运行,可以检测系统是否破损,可以判断系统是否异常等等。

对于安卓,我们可以通过如下获取信号

getprop sys.boot_completed

二、systemd的boot_completed信号

对于systemd,默认提供了类似此机制的信号,如下:

systemctl is-system-running --wait

此方法会一直阻塞,知道systemd认为系统启动完成。引入此功能的patch如下:

https://github.com/systemd/systemd/pull/9796/commits/02d9350cda6b330669607ae88c74ac7256212741

我们可以知道,此命令一直等待的是StartupFinished的dbus信号。

当systemd的一系列jobs都正常处理完成,则发送此信号,我们可以通过list-jobs来查看jobs运行状态,如下:

systemctl list-jobs

三、应用程序如何集成

针对上面提到的,我们可以知道,我们直接监听dbus的StartupFinished信号,那么应用程序就能接收到systemd发送的boot_completed,所以我们借助yocto的工具源码dbus-wait如下:

/* * Copyright (C) 2008 Intel Corporation. * * Author: Ross Burton <ross@linux.intel.com> * * This program is free software; you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) any later * version. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ #include <string.h> #include <stdlib.h> #include <stdio.h> #include <signal.h> #include <unistd.h> #include <dbus/dbus.h> static char *interface, *member, *path; static void alarm_handler (int signum) { fprintf(stderr, "Timeout waiting for %s.%s\n", interface, member); exit (EXIT_SUCCESS); } static DBusHandlerResult filter (DBusConnection *conn, DBusMessage *message, void *user_data) { /* If the interface matches */ if (strcmp (interface, dbus_message_get_interface (message)) == 0) /* And the member matches */ if (strcmp (member, dbus_message_get_member (message)) == 0) /* And the path is NULL or matches */ if (path == NULL || strcmp (path, dbus_message_get_path (message)) == 0) /* Then exit */ exit (EXIT_SUCCESS); return DBUS_HANDLER_RESULT_HANDLED; } int main (int argc, char **argv) { DBusError error = DBUS_ERROR_INIT; DBusConnection *conn; char *match = NULL; if (argc < 3 || argc > 4) { fprintf (stderr, "$ dbus-wait <INTERFACE> <MEMBER> [PATH]\n"); return EXIT_FAILURE; } signal (SIGALRM, alarm_handler); alarm (60); /* TODO: allow system or session */ conn = dbus_bus_get (DBUS_BUS_SYSTEM, &error); if (!conn) { fprintf (stderr, "Cannot get system bus: %s\n", error.message); dbus_error_free (&error); return EXIT_FAILURE; } switch (argc) { case 3: interface = argv[1]; member = argv[2]; path = NULL; asprintf (&match, "type='signal',interface='%s',member='%s'", interface, member); break; case 4: interface = argv[1]; member = argv[2]; path = argv[3]; asprintf (&match, "type='signal',interface='%s',member='%s',path='%s'", interface, member, path); break; } dbus_bus_add_match (conn, match, &error); free (match); if (dbus_error_is_set (&error)) { fprintf (stderr, "Cannot add match: %s\n", error.message); dbus_error_free (&error); return EXIT_FAILURE; } dbus_connection_add_filter (conn, filter, NULL, NULL); while (dbus_connection_read_write_dispatch (conn, -1)) ; dbus_connection_unref (conn); return EXIT_SUCCESS; }

编译如下:

gcc dbus-wait.c $(pkg-config --libs --cflags dbus-1)-o dbus-wait

至此,我们可以直接监听此dbus信号如下:

dbus-wait org.freedesktop.systemd1.Manager StartupFinished

四、linux的缺陷

对于通常的boot_completed信号,它只能判断systemd的service是否启动完成。我们知道,对于一个桌面系统而言,systemd服务并不代表系统真实的启动完成,例如ukui-session带起来的一系列应用程序,如果peony未启动,systemd并不能察觉,StartupFinished信号的发送和peony等应用程序的是否正常运行没有产生关联。所以我们需要产生关联。

至此,我们需要在ukui-session中,一边接受此信号,一边等待系统核心应用完成。然后,构造一个完整的boot_completed信号,提供给用户,至此我们在linux中,也具备了boot_completed信号。

编辑
2025-01-22
工作知识
0

我们开发工作中,如果是面向嵌入式的场景,很多客户其实倾向于使用交叉编译工具链,我们作为操作系统可以提供与系统本身gcc版本一致的交叉编译工具,这样构建的时候就不用担心libc/binutils等版本不匹配导致的符号问题从而构建的二进制无法运行的问题,关于交叉编译工具链的文章和工具获取,可以参考交叉编译环境搭建,本文主要谈一谈关于qt工程中,如果直接使用qmake来进行交叉编译过程中,我们需要修改qt.conf和qmake.conf的一些细节性问题

一、关于qt的交叉编译

我们可以通过qt官方的wiki找到支持qt的交叉编译的办法,根据此办法,我相信大家能够很轻易的支持到qt环境的交叉编译,关于qt环境的交叉编译的细节就不再赘述,有兴趣可以仔细看看文档,文章链接如下:

https://wiki.qt.io/Cross-Compile_Qt_6_for_Raspberry_Pi

如果wiki.qt.io无法正常访问,那么github上关于qt的cross compiling的文章也是一样的意思,如下

https://github.com/UvinduW/Cross-Compiling-Qt-for-Raspberry-Pi-4

还有一篇文章可以辅助了解

https://www.stefanocottafavi.com/crosscompile-qt-for-rpi/

根据上面的信息,我简单描述如下:

  1. 获取sysroot,sysroot就是我们的系统环境
  2. 获取交叉编译工具链
  3. 下载qt的源码
  4. 在构建源码时指定CROSS_COMPILE和–sysroot
  5. 构建完成之后,此时我们的qt工程可以通过qt源码的qmake直接生成Makefile完成构建

根据上面的简述,可以知道,我们在讨论qt的交叉编译的时候,往往是通过构建了qt的源码库。从而生成了对应的qmake和相关配置,我们直接使用qmake和相关配置即可完成交叉构建。这也是qt官方推荐的做法。

但是,本文需要的是直接基于sysroot中已安装的qt版本,来手动配置qmake和qtconf来实现交叉编译,那么这可以省略编译qt源码安插–sysroot这一个步骤。与上面官方的方法不同的点我列表如下:

qt官方:需要构建qt源码工程

本文讨论的:无需构建qt源码工程,程序链接sysroot下的qt库文件

二、实现思路

我们知道如果不想通过重新构建qt源码来实现交叉编译的话,我们就需要在host上强行指定好sysroot,头文件位置,库文件位置,数据文件位置等。这需要对qmake和qt.conf有详细的了解。这也是本文讨论的重点。主要步骤如下:

2.1 下载交叉编译工具链

我们可以通过linaro官网上下载工具链,也可以使用我们提供的工具链,交叉编译环境搭建有提到工具链地址是

gcc-kylin-9.3.0-2024.10-x86_64_aarch64-linux-gnu.tar.gz 文件获取位置(110服务器):/home/yangquan/develop/嵌入式相关/01_嵌入式定制部门/嵌入式版本项目&产品版本问题解决集/KylinSdk

2.2 下载sysroot环境

我们需要一个编译的sysroot环境,这个环境可以在已有的机器环境上打包其rootfs,也可以在麒麟平台上根据系统iso的filesystem.squashfs来进行解压获取,里面是一个标准的操作系统环境,我们还需要在这个环境中根据deb包来下载安装必要的头文件和动态库

2.3 配置qmake

qt默认提供了基于aarch64的交叉编译配置,地址如下:

/usr/lib/x86_64-linux-gnu/qt5/mkspecs/linux-aarch64-gnu-g++/qmake.conf

我们可以发现其会设置相应的环境变量,例如

QMAKE_CC = aarch64-linux-gnu-gcc QMAKE_CXX = aarch64-linux-gnu-g++ QMAKE_LINK = aarch64-linux-gnu-g++ QMAKE_LINK_SHLIB = aarch64-linux-gnu-g++ # modifications to linux.conf QMAKE_AR = aarch64-linux-gnu-ar cqs QMAKE_OBJCOPY = aarch64-linux-gnu-objcopy QMAKE_NM = aarch64-linux-gnu-nm -P QMAKE_STRIP = aarch64-linux-gnu-strip

可以发现,这里指向的是交叉编译工具链中的编译工具

2.4 配置qt.conf

我们配置了qmake默认使用交叉编译工具之后,还需要配置qt.conf,如下:

[Paths] Prefix=/usr ArchData=lib/aarch64-linux-gnu/qt5 Binaries=lib/qt5/bin Data=share/qt5 TargetSpec=linux-aarch64-gnu-g++ HostSpec=linux-aarch64-gnu-g++ Documentation=share/qt5/doc Examples=lib/aarch64-linux-gnu/qt5/examples Headers=include/aarch64-linux-gnu/qt5 HostBinaries=lib/qt5/bin HostData=lib/x86_64-linux-gnu/qt5 HostLibraries=lib/x86_64-linux-gnu Imports=lib/aarch64-linux-gnu/qt5/imports Libraries=lib/aarch64-linux-gnu LibraryExecutables=lib/aarch64-linux-gnu/qt5/libexec Plugins=lib/aarch64-linux-gnu/qt5/plugins Qml2Imports=lib/aarch64-linux-gnu/qt5/qml Settings=/etc/xdg Translations=share/qt5/translations Sysroot=/root/tf/kylin_test_cross/sysroot SysrootifyPrefix=true

例如sysroot位置,header文件查找位置,libraries库查找位置。

有了这个qt.conf,我们的一些环境变量会发生改变,例如,如果没有qt.conf,我们query的信息如下:

# qmake -query QT_SYSROOT: QT_INSTALL_PREFIX:/usr QT_INSTALL_ARCHDATA:/usr/lib/x86_64-linux-gnu/qt5 QT_INSTALL_DATA:/usr/share/qt5 QT_INSTALL_DOCS:/usr/share/qt5/doc QT_INSTALL_HEADERS:/usr/include/x86_64-linux-gnu/qt5 QT_INSTALL_LIBS:/usr/lib/x86_64-linux-gnu QT_INSTALL_LIBEXECS:/usr/lib/x86_64-linux-gnu/qt5/libexec QT_INSTALL_BINS:/usr/lib/qt5/bin QT_INSTALL_TESTS:/usr/tests QT_INSTALL_PLUGINS:/usr/lib/x86_64-linux-gnu/qt5/plugins QT_INSTALL_IMPORTS:/usr/lib/x86_64-linux-gnu/qt5/imports QT_INSTALL_QML:/usr/lib/x86_64-linux-gnu/qt5/qml QT_INSTALL_TRANSLATIONS:/usr/share/qt5/translations QT_INSTALL_CONFIGURATION:/etc/xdg QT_INSTALL_EXAMPLES:/usr/lib/x86_64-linux-gnu/qt5/examples QT_INSTALL_DEMOS:/usr/lib/x86_64-linux-gnu/qt5/examples QT_HOST_PREFIX:/usr QT_HOST_DATA:/usr/lib/x86_64-linux-gnu/qt5 QT_HOST_BINS:/usr/lib/qt5/bin QT_HOST_LIBS:/usr/lib/x86_64-linux-gnu QMAKE_SPEC:linux-g++ QMAKE_XSPEC:linux-g++ QMAKE_VERSION:3.1 QT_VERSION:5.12.8

可以发现其默认指向了本地的库和头文件,并未设置sysroot地址,如果我们指定qt.conf,则如下

# qmake -query -qtconf ./qt.conf QT_SYSROOT:/root/tf/kylin_test_cross/sysroot/ QT_SYSROOT:/root/tf/kylin_test_cross/sysroot QT_INSTALL_PREFIX:/root/tf/kylin_test_cross/sysroot/usr QT_INSTALL_PREFIX/raw:/usr QT_INSTALL_ARCHDATA:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5 QT_INSTALL_ARCHDATA/raw:/usr/lib/aarch64-linux-gnu/qt5 QT_INSTALL_DATA:/root/tf/kylin_test_cross/sysroot/usr/share/qt5 QT_INSTALL_DATA/raw:/usr/share/qt5 QT_INSTALL_DOCS:/root/tf/kylin_test_cross/sysroot/usr/share/qt5/doc QT_INSTALL_DOCS/raw:/usr/share/qt5/doc QT_INSTALL_HEADERS:/root/tf/kylin_test_cross/sysroot/usr/include/aarch64-linux-gnu/qt5 QT_INSTALL_HEADERS/raw:/usr/include/aarch64-linux-gnu/qt5 QT_INSTALL_LIBS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu QT_INSTALL_LIBS/raw:/usr/lib/aarch64-linux-gnu QT_INSTALL_LIBEXECS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/libexec QT_INSTALL_LIBEXECS/raw:/usr/lib/aarch64-linux-gnu/qt5/libexec QT_INSTALL_BINS:/root/tf/kylin_test_cross/sysroot/usr/lib/qt5/bin QT_INSTALL_BINS/raw:/usr/lib/qt5/bin QT_INSTALL_TESTS:/root/tf/kylin_test_cross/sysroot/usr/tests QT_INSTALL_TESTS/raw:/usr/tests QT_INSTALL_PLUGINS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/plugins QT_INSTALL_PLUGINS/raw:/usr/lib/aarch64-linux-gnu/qt5/plugins QT_INSTALL_IMPORTS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/imports QT_INSTALL_IMPORTS/raw:/usr/lib/aarch64-linux-gnu/qt5/imports QT_INSTALL_QML:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/qml QT_INSTALL_QML/raw:/usr/lib/aarch64-linux-gnu/qt5/qml QT_INSTALL_TRANSLATIONS:/root/tf/kylin_test_cross/sysroot/usr/share/qt5/translations QT_INSTALL_TRANSLATIONS/raw:/usr/share/qt5/translations QT_INSTALL_CONFIGURATION:/root/tf/kylin_test_cross/sysroot/etc/xdg QT_INSTALL_CONFIGURATION/raw:/etc/xdg QT_INSTALL_EXAMPLES:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/examples QT_INSTALL_EXAMPLES/raw:/usr/lib/aarch64-linux-gnu/qt5/examples QT_INSTALL_DEMOS:/root/tf/kylin_test_cross/sysroot/usr/lib/aarch64-linux-gnu/qt5/examples QT_INSTALL_DEMOS/raw:/usr/lib/aarch64-linux-gnu/qt5/examples QT_HOST_PREFIX:/usr QT_HOST_DATA:/usr/lib/x86_64-linux-gnu/qt5 QT_HOST_BINS:/usr/lib/qt5/bin QT_HOST_LIBS:/usr/lib/x86_64-linux-gnu QMAKE_SPEC:linux-aarch64-gnu-g++ QMAKE_XSPEC:linux-aarch64-gnu-g++ QMAKE_VERSION:3.1 QT_VERSION:5.12.8

可以发现,我们headers和libs等等都从正确的路径寻找。

至此,我们发现,整个qt.conf和qmake.conf已经正确配置,我们可以良好的进行编译构建。

三、演示

如果对于一个qt仓库,我们以qt的example为例,/usr/lib/x86_64-linux-gnu/qt5/examples/gui/analogclock。我们可以如下:

cd /usr/lib/x86_64-linux-gnu/qt5/examples/gui/analogclock

我们需要根据qmake生成Makefile,如下

/usr/lib/qt5/bin/qmake -qtconf ./qt.conf

然后直接make,如下:

make

此时,我们会生成如下文件

# file analogclock analogclock: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=5f848734c8f59a6958bf829be3a72dd4d6b0a124, for GNU/Linux 3.7.0, not stripped

此时,文件生成完成,可以发现是aarch64的qt程序,我们将其发送到实际机器上即可正常运行。

编辑
2025-01-22
工作知识
0

因为客户的需求,我们需要在标准linux上支持c,d盘的基本功能,这样用户在安装程序时,需要可以将程序安装在D盘,这样从某种意义上来说达到了用户和系统的应用程序隔离。本文提供一种思路,用于设计C,D盘的设计,基于此思路的衍生,可以完全实施C,D盘的基本功能

一、相关补丁

为了将补丁更突出出来,这里第一时间将补丁贴出,如下:

From 75d9bc4ee5c2b6b2b0a7efa7beed7c2b8cfe51e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=94=90=E5=B3=B0?= <tangfeng@kylinos.cn> Date: Wed, 14 Aug 2024 12:17:16 +0800 Subject: [PATCH] support appfs for C/D disk export USE_APPFS marco to open this feature --- src/archives.c | 155 ++++++++++++++++++++++++++++++++++++++++++++++++- src/remove.c | 59 +++++++++++++++++++ 2 files changed, 213 insertions(+), 1 deletion(-) diff --git a/src/archives.c b/src/archives.c index fe4d8a9..bca81b5 100644 --- a/src/archives.c +++ b/src/archives.c @@ -387,6 +387,65 @@ does_replace(struct pkginfo *new_pkg, struct pkgbin *new_pkgbin, return false; } +static void copy_file(const char *src, const char *dest) { + struct stat stat_buf; + int sfd, dfd; + char *buf; + int buf_read = 0; + int buf_write = 0; + struct timespec times[2]; + + if (!strcmp(src, dest)) + return; + + sfd = open(src, O_RDONLY); + dfd = open(dest, O_WRONLY | O_CREAT | O_TRUNC, stat_buf.st_mode); + if (sfd < 0 || dfd < 0) { + return ; + } + + stat(src, &stat_buf); + + buf = (char *) malloc(4096); + while ((buf_read = read(sfd, buf, 4096))) { + buf_write = write(dfd, buf, buf_read); + if(buf_write != buf_read){ + } + } + free(buf); + + if (buf_read < 0) { + printf("buf_read le 0\n"); + } + + fchmod(dfd, stat_buf.st_mode); + if(fchown(dfd, stat_buf.st_uid, stat_buf.st_gid)){ + printf("change owner failed\n"); + } + + times[0] = stat_buf.st_atim; + times[1] = stat_buf.st_mtim; + futimens(dfd, times); + + close(sfd); + close(dfd); +} + +#define USE_APPFS +static bool get_useappfs(void) +{ + static bool use_appfs = false; + +#ifdef USE_APPFS + const char *env = getenv("USE_APPFS"); + if(env) + use_appfs = true; + else + use_appfs = false; +#endif + return use_appfs; +} + static void tarobject_extract(struct tarcontext *tc, struct tar_entry *te, const char *path, struct file_stat *st, @@ -401,6 +460,9 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, char fnamenewbuf[256]; char *newhash; int rc; + char appfs[256]; + + snprintf(appfs, 256, "%s%s", appfs_prefix, path); switch (te->type) { case TAR_FILETYPE_FILE: @@ -450,10 +512,18 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, pop_cleanup(ehflag_normaltidy); /* fd = open(path) */ if (close(fd)) ohshite(_("error closing/writing '%.255s'"), te->name); + + /* copy file to appfs */ + if(get_useappfs()) + copy_file(path, appfs); break; case TAR_FILETYPE_FIFO: if (mkfifo(path, 0)) ohshite(_("error creating pipe '%.255s'"), te->name); + + /* mkfifo at appfs */ + if(get_useappfs()) + mkfifo(appfs, 0); debug(dbg_eachfiledetail, "tarobject fifo"); break; case TAR_FILETYPE_CHARDEV: @@ -477,6 +547,13 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, varbuf_end_str(&hardlinkfn); if (link(hardlinkfn.buf, path)) ohshite(_("error creating hard link '%.255s'"), te->name); + + /* hardlink at appfs */ + if(get_useappfs()){ + if(link(hardlinkfn.buf, appfs)) + printf("make hardlink on %s failed\n", appfs); + } + namenode->newhash = linknode->newhash; debug(dbg_eachfiledetail, "tarobject hardlink hash=%s", namenode->newhash); break; @@ -484,12 +561,32 @@ tarobject_extract(struct tarcontext *tc, struct tar_entry *te, /* We've already checked for an existing directory. */ if (symlink(te->linkname, path)) ohshite(_("error creating symbolic link '%.255s'"), te->name); + + /* symlink at appfs */ + if(get_useappfs()) { + /* this symlink maybe fail */ + if (symlink(te->linkname, appfs)){ + unlink(appfs); + if(symlink(te->linkname, appfs)){ + printf("make symlink on %s failed\n", appfs); + } + } + } + debug(dbg_eachfiledetail, "tarobject symlink creating"); break; case TAR_FILETYPE_DIR: /* We've already checked for an existing directory. */ if (mkdir(path, 0)) ohshite(_("error creating directory '%.255s'"), te->name); + + /* create directory on appfs */ + if(get_useappfs()){ + struct stat stab; + if (!(stat(appfs, &stab) == 0 && S_ISDIR(stab.st_mode))) + mkdir(appfs, 0); + } + debug(dbg_eachfiledetail, "tarobject directory creating"); break; default: @@ -808,6 +905,14 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) "installing another version"), ti->name); debug(dbg_eachfiledetail,"tarobject nonexistent"); } else { + /* rename file on appfs */ + if(get_useappfs()){ + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + rename(appfsnew, appfs); + } debug(dbg_eachfiledetail,"tarobject restored tmp to main"); statr= lstat(fnamevb.buf,&stab); if (statr) @@ -838,6 +943,14 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) if (!stat(fnamevb.buf,&stabtmp) && S_ISDIR(stabtmp.st_mode)) { debug(dbg_eachfiledetail, "tarobject directory exists"); existingdir = true; + /* mkdir on appfs */ + if(get_useappfs()){ + char appfs[256]; + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + if (!(!stat(appfs,&stabtmp) && S_ISDIR(stabtmp.st_mode))) { + mkdir(appfs, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); + } + } } break; case TAR_FILETYPE_FILE: @@ -1058,6 +1171,17 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) if (nifd->namenode->flags & FNNF_NEW_CONFF) { debug(dbg_conffdetail,"tarobject conffile extracted"); nifd->namenode->flags |= FNNF_ELIDE_OTHER_LISTS; + + if(get_useappfs()){ + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + if(S_ISDIR(stab.st_mode) || S_ISREG(stab.st_mode)){ + rename(appfsnew, appfs); + } + } + return 0; } @@ -1120,7 +1244,16 @@ tarobject(struct tar_archive *tar, struct tar_entry *ti) } else { if (rename(fnamenewvb.buf, fnamevb.buf)) ohshite(_("unable to install new version of '%.255s'"), ti->name); - + /* rename on appfs */ + if(get_useappfs()){ + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + if(access(appfs, F_OK)){ + rename(appfsnew, appfs); + } + } /* * CLEANUP: Now the new file is in the destination file, and the * old file is in .dpkg-tmp to be cleaned up later. We now need @@ -1306,6 +1439,14 @@ tar_deferred_extract(struct fsys_namenode_list *files, struct pkginfo *pkg) ohshite(_("unable to install new version of '%.255s'"), cfile->namenode->name); + /* rename on appfs */ + if(get_useappfs()) { + char appfsnew[256]; + char appfs[256]; + snprintf(appfsnew, 256, "%s%s", appfs_prefix, fnamenewvb.buf); + snprintf(appfs, 256, "%s%s", appfs_prefix, fnamevb.buf); + rename(appfsnew, appfs); + } if(kysec_whlist_exectl_multi_add_for_dpkg == NULL) { if(kysec_whlist_exectl_add_for_dpkg != NULL) @@ -1750,6 +1891,18 @@ archivefiles(const char *const *argv) ohshit(_("archive '%s' is not a regular file"), argp[i]); } + /* check appfs directory exist */ + if(get_useappfs()){ + if(access(appfs_prefix, F_OK)){ + struct stat st; + stat(appfs_prefix, &st); + if(!S_ISDIR(st.st_mode)){ + mkdir(appfs_prefix, 0); + chmod(appfs_prefix, S_IRWXU|S_IRWXG|S_IRWXO); + } + } + } + currenttime = time(NULL); /* Initialize fname variables contents. */ diff --git a/src/remove.c b/src/remove.c index b8727b6..d24f21b 100644 --- a/src/remove.c +++ b/src/remove.c @@ -551,6 +551,21 @@ removal_bulk_file_is_shared(struct pkginfo *pkg, struct fsys_namenode *namenode) return shared; } +#define USE_APPFS +static bool get_useappfs(void) +{ + static bool use_appfs = false; + +#ifdef USE_APPFS + const char *env = getenv("USE_APPFS"); + if(env) + use_appfs = true; + else + use_appfs = false; +#endif + return use_appfs; +} + static void removal_bulk_remove_files(struct pkginfo *pkg) { @@ -560,6 +575,7 @@ removal_bulk_remove_files(struct pkginfo *pkg) static struct varbuf fnvb; struct varbuf_state fnvb_state; struct stat stab; + char appfs[256]; pkg_set_status(pkg, PKG_STAT_HALFINSTALLED); modstatdb_note(pkg); @@ -584,6 +600,10 @@ removal_bulk_remove_files(struct pkginfo *pkg) is_dir = stat(fnvb.buf, &stab) == 0 && S_ISDIR(stab.st_mode); + if(get_useappfs()){ + snprintf(appfs, 256, "%s%s", appfs_prefix, fnvb.buf); + } + /* A pkgset can share files between its instances that we * don't want to remove, we just want to forget them. This * applies to shared conffiles too. */ @@ -593,6 +613,11 @@ removal_bulk_remove_files(struct pkginfo *pkg) /* Non-shared conffiles are kept. */ if (namenode->flags & FNNF_OLD_CONFF) { push_leftover(&leftover, namenode); + + /* unlink non-shared conffiles file */ + if(get_useappfs()) + secure_unlink(appfs); + continue; } @@ -638,6 +663,19 @@ removal_bulk_remove_files(struct pkginfo *pkg) varbuf_end_str(&fnvb); debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf); + + /* remove appfs dir */ + if(get_useappfs()){ + if (stat(appfs, &stab) == 0 && S_ISDIR(stab.st_mode)){ + /* ignore /$appfs/. directory */ + char prefix[256]; + snprintf(prefix, 256, "%s%s", appfs_prefix, "/."); + if (strcmp(appfs, prefix) == 0) { + continue; + } + rmdir(appfs); + } + } if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue; if (errno == ENOTEMPTY || errno == EEXIST) { debug(dbg_eachfiledetail, @@ -657,6 +695,12 @@ removal_bulk_remove_files(struct pkginfo *pkg) debug(dbg_eachfiledetail, "removal_bulk unlinking '%s'", fnvb.buf); if (secure_unlink(fnvb.buf)) ohshite(_("unable to securely remove '%.250s'"), fnvb.buf); + + /* delete appfs file */ + if(get_useappfs()){ + if((access(appfs, F_OK)==0) || (lstat(appfs, &stab) == 0 && S_ISLNK(stab.st_mode))) + secure_unlink(appfs); + } } write_filelist_except(pkg, &pkg->installed, leftover, 0); maintscript_installed(pkg, POSTRMFILE, "post-removal", "remove", NULL); @@ -681,6 +725,7 @@ static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) { struct fsys_namenode *namenode; static struct varbuf fnvb; struct stat stab; + char appfs[256]; /* We may have modified this previously. */ ensure_packagefiles_available(pkg); @@ -710,6 +755,10 @@ static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) { varbuf_add_str(&fnvb, usenode->name); varbuf_end_str(&fnvb); + if(get_useappfs()){ + snprintf(appfs, 256, "%s%s", appfs_prefix, fnvb.buf); + } + if (!stat(fnvb.buf,&stab) && S_ISDIR(stab.st_mode)) { debug(dbg_eachfiledetail, "removal_bulk is a directory"); /* Only delete a directory or a link to one if we're the only @@ -733,6 +782,16 @@ static void removal_bulk_remove_leftover_dirs(struct pkginfo *pkg) { trig_path_activate(usenode, pkg); debug(dbg_eachfiledetail, "removal_bulk removing '%s'", fnvb.buf); + /* remove appfs file */ + if(get_useappfs()){ + if (!stat(appfs, &stab) && S_ISDIR(stab.st_mode)) { + rmdir(appfs); + } + if (lstat(appfs, &stab) == 0 && S_ISLNK(stab.st_mode)) { + unlink(appfs); + } + } + if (!rmdir(fnvb.buf) || errno == ENOENT || errno == ELOOP) continue; if (errno == ENOTEMPTY || errno == EEXIST) { warning(_("while removing %.250s, directory '%.250s' not empty so not removed"), -- 2.25.1

二、现状

2.1 默认安装包放在根目录

我们可以知道,linux默认情况下将so和elf程序放在/usr/bin和/usr/lib下,与系统核心库不做区分。这样用户在安装deb包的时候,也会默认在/usr/bin和/usr/lib中安装,这就导致了所有的应用软件包都安装在系统核心目录下。

同样的,我们知道,windows默认将应用程序和dll安装的c盘,但是应用程序可以选择默认安装环境,这里可以手动指定为D盘。其差别如下:

image.png

这里需要明确的是windows核心的系统库,例如vs c++等相关库,只能安装在C盘。

根据此现状,Linux系统需要提供一个功能,让其应用程序可以安装在D盘等类似的盘符

2.2 长期以来的生态依赖根目录,而不是其他目录

我们知道麒麟系统的包生态基于deb,虽然现在存在开明包格式,但是为了不改变太多,默认deb包格式的方式短期内不能抛弃。

而deb包默认将其安装的根目录,设置其preinstall,postinstall,preremove,postremove都需要在根目录运行。

并且,dpkg管理的包列表默认存放在/var/lib/dpkg/的list中,而这里的list记录了实际的文件内容,举例如下:

root@kylin:~# cat /var/lib/dpkg/info/libhw265dec.list /. /etc /etc/dbus-1 /etc/dbus-1/system.d /etc/dbus-1/system.d/com.huawei.dassistant.conf /usr /usr/bin /usr/bin/DAssistantd /usr/bin/qs-smc-module-installer.sh /usr/include /usr/include/hwd_api.h /usr/lib /usr/lib/libhw265dec.so /usr/lib/pkgconfig /usr/lib/pkgconfig/hw265dec.pc /usr/lib/systemd /usr/lib/systemd/system /usr/lib/systemd/system/qs-smc-module-installer.service /usr/share /usr/share/doc /usr/share/doc/libhw265dec /usr/share/doc/libhw265dec/changelog.gz

所以,如果我们一味的将deb包安装在其他目录,则问题太多,太开放,解决问题不可实现。

2.3 总结

根据上述2.1 和 2.2 的现状,我们可以知道,我们长期依赖的ubuntu 系列的生态,导致我们没有办法轻易的将系统划分为单独的C盘和D盘。

三、解决思路

为了解决这个问题,我们可以两个思路

  • 弃用ubuntu生态,使用优秀全新的设计,例如开明包格式
  • 遵循ubuntu生态,以冗余换兼容

3.1 不破不立

麒麟系统集成了ubuntu和debian系列的应用生态,所以deb包格式能够直接安装,而正是因为如此,deb包在设计的时候没有考虑C,D盘的划分。所以我们没办法直接划分C和D盘。

所以我们需要抛弃这块的生态,重新定义包的安装方式。例如开明包格式,如下:

https://gitlab2.kylin.com/lixinyue/kaiming-design-docs/-/tree/master/%E5%BC%80%E6%98%8E%E7%94%A8%E6%88%B7%E6%93%8D%E4%BD%9C%E6%89%8B%E5%86%8C

其每个应用程序都需要遵循开明包格式发布,而开明包自带沙箱隔离机制,故实现C/D盘将十分简单,当前成果状态如下:

image.png

但此文档主要目的不是讨论此方案

3.2 多方兼顾

为了兼容ubuntu/debian系列的安装方式,又体现C/D盘的基本功能,我们必须两方都兼顾,所以我们实施的方案如下:

image.png

这样,程序被默认安装在根,并且原封不动安装在appfs中,此时应用程序的安装可以在appfs中体现,而根的文件,我们可以通过dm-snapshot实现在大更新的时候进行merge操作。从而完成系统的整体更新,关于dm-snapshot的操作,可参考其他文章。

四、设计方法

4.1 修改dpkg

对于ubuntu/debian系统的软件包,均是通过apt来安装的deb包,而实际解包安装的动作是dpkg实现。所以我们需要基于dpkg来进行修改。

4.2 安装过程

针对src/archives.c文件,我们需要将在deb包的tar包解压过程中,执行tarobject_extract时,对于普通文件进行copy,目录进程创建,对于fifo/link/symlink进行重建,如下

image.png

4.3 卸载过程

针对src/remove.c文件,我们需要将在deb包卸载删除过程中,执行removal_bulk时,对普通文件和目录进行删除,对fifo/link/symlink进行unlink,如下

image.png

4.4 如何启用

默认情况下,我们不启用此功能,如果想要启用,则可以导出环境变量即可,如下:

export USE_APPFS=1 对于代码,实现如下: #define USE_APPFS static bool get_useappfs(void) { static bool use_appfs = false; #ifdef USE_APPFS const char *env = getenv("USE_APPFS"); if(env) use_appfs = true; else use_appfs = false; #endif return use_appfs; }

五、体验

这里我们假设系统支持了此功能,那么如果我们想要启动appfs的基本功能,如下:

export USE_APPFS=1

然后,我们正常通过apt/dpkg 安装应用,安装之前,我们确认没有appfs,如下:

root@kylin:~# file /appfs /appfs: cannot open `/appfs' (No such file or directory)

然后我们正常安装程序,这里以tree为例

root@kylin:~# apt install tree 正在读取软件包列表... 完成 正在分析软件包的依赖关系树 正在读取状态信息... 完成 下列软件包是自动安装的并且现在不需要了: libutempter0 python3-click python3-colorama python3-itsdangerous python3-jinja2 python3-markupsafe 使用'apt autoremove'来卸载它(它们)。 下列【新】软件包将被安装: tree 升级了 0 个软件包,新安装了 1 个软件包,要卸载 0 个软件包,有 14 个软件包未被升级。 需要下载 44.5 kB 的归档。 解压缩后会消耗 111 kB 的额外空间。 获取:1 http://archive.kylinos.cn/kylin/KYLIN-ALL 10.1-rk3588/universe arm64 tree arm64 1.8.0-1 [44.5 kB] 已下载 44.5 kB,耗时 0秒 (262 kB/s) 正在选中未选择的软件包 tree。 (正在读取数据库 ... 系统当前共安装有 210448 个文件和目录。) 准备解压 .../tree_1.8.0-1_arm64.deb ... 正在解压 tree (1.8.0-1) ... 正在设置 tree (1.8.0-1) ... 正在处理用于 man-db (2.9.1-1kylin0k1.0) 的触发器 ... 正在处理用于 kysec-utils (3.3.6.1-0k8.18) 的触发器 ...

此时我们可以发现,tree在appfs中存在,如下

root@kylin:~# find /appfs/ /appfs/ /appfs/usr /appfs/usr/bin /appfs/usr/bin/tree /appfs/usr/share /appfs/usr/share/doc /appfs/usr/share/doc/tree /appfs/usr/share/doc/tree/README.gz /appfs/usr/share/doc/tree/changelog.Debian.gz /appfs/usr/share/doc/tree/copyright /appfs/usr/share/doc/tree/TODO /appfs/usr/share/man /appfs/usr/share/man/man1 /appfs/usr/share/man/man1/tree.1.gz

如果我们想要使用appfs中的tree,我们可以直接运行,如果程序带so,则我们修改ldconfig即可,如下

LD_LIBRARY_PATH=/appfs/pathtolibrary/ exe

此时如果我们卸载tree,如下:

root@kylin:~# dpkg -P tree (正在读取数据库 ... 系统当前共安装有 210455 个文件和目录。) 正在卸载 tree (1.8.0-1) ... 正在处理用于 man-db (2.9.1-1kylin0k1.0) 的触发器 ... 正在处理用于 kysec-utils (3.3.6.1-0k8.18) 的触发器 ...

此时我们可以看到appfs的文件均被删除,如下

root@kylin:~# find /appfs/ /appfs/ /appfs/usr /appfs/usr/bin /appfs/usr/share /appfs/usr/share/doc /appfs/usr/share/man /appfs/usr/share/man/man1

这里遗留了几个文件夹没有删除,因为这几个文件夹不是tree包带来的,所以没有删除是正常的

windows在安装包后卸载,D盘也是遗留空文件夹。这不是异常。

编辑
2025-01-22
工作知识
0

咱们有一个基本的面试题,有些同事可能存在一些误解的情况,本文基于这个题目介绍一下关于aarch64的情况

一、关于题目

请说明计数寄存器(PC)和堆栈寄存器(SP)以及链接寄存器(LR)的作用 这个题目很清楚,需要阐述PC,SP和LR的作用,那不清晰的点在哪里呢

二、存在歧义的点

如果此问题是arm系列芯片,也就是32位,那么我们这里描述的是

R13,R14,R15

如下:

image.png 但是如果是在aarch64系列芯片,也就是64位,那么我们这里描述的是

X29,X30

如下:

image.png 值得注意的是,aarch64没有单独的PC寄存器。

主要原因如下,可以从armv8的spec中找到:

The current Program Counter (PC) cannot be referred to by number as if part of the general register file and therefore cannot be used as the source or destination of arithmetic instructions, or as the base, index or transfer register of load and store instructions. The only instructions that read the PC are those whose function it is to compute a PC-relative address (ADR, ADRP, literal load, and direct branches), and the branch-and-link instructions that store a return address in the link register (BL and BLR). The only way to modify the program counter is using branch, exception generation and exception return instructions. Where the PC is read by an instruction to compute a PC-relative address, then its value is the address of that instruction. Unlike A32 and T32, there is no implied offset of 4 or 8 bytes.

这里我们知道三个信息:

  • aarch64下的pc寄存器不是通用寄存器
  • pc寄存器可以通过adr,adrp,ldr这种加载指令,或者在代码分支内部直接计算偏移
  • 修改pc寄存器可以通过bl,blr这类跳转指令直接修改

也就是说,在aarch64上,pc寄存器可以在函数上可以直接通过偏移计算出来,或者通过adr等加载指令来计算偏移获取pc的值,而跳转可以通过bl来修改pc指针。

至此,我们从官方渠道了解了aarch64的关于pc寄存器的歧义点。

而实际上,在aarch64平台,我们上层使用过程中,仍是可以轻松的获取这些寄存器的值,和arm32相差无几,接下来我简单介绍一下在aarch64平台上这三个寄存器

三、示例

3.1 SP寄存器

aarch64的SP寄存器根据异常等级区分,也就是当前owner是ELn,那么SP就是SP_ELn。

我们可以如下示例:

(gdb) info register sp sp 0x7fffffefe0 0x7fffffefe0

sp表示当前函数的堆栈指针,它和x29寄存器是相等的,如下:

(gdb) p/x $x29 $2 = 0x7fffffefe0

对于一个函数,我们在函数的开始,会看到sp会被修改,如下:

0x0000000000400750 <+0>: stp x29, x30, [sp, #-48]!

可以发现sp变小,也就是说,上一级的sp地址应该是sp+48,如下

(gdb) p $sp+48 $5 = (void *) 0x7ffffff010

此时我们到上一级函数load_data中,可以查看如下:

(gdb) p/x $sp $1 = 0x7ffffff010

可以看汇编知道接下来会调用test函数

(gdb) disassemble Dump of assembler code for function load_data: 0x0000000000400674 <+0>: stp x29, x30, [sp, #-48]! 0x0000000000400678 <+4>: mov x29, sp 0x000000000040067c <+8>: str x0, [sp, #24] => 0x0000000000400680 <+12>: ldr x0, [sp, #24] 0x0000000000400684 <+16>: str x0, [sp, #40] 0x0000000000400688 <+20>: ldr x0, [sp, #40] 0x000000000040068c <+24>: ldr x2, [x0] 0x0000000000400690 <+28>: ldr w1, [x0, #8] 0x0000000000400694 <+32>: mov x0, x2 0x0000000000400698 <+36>: bl 0x400750 <test> 0x000000000040069c <+40>: nop 0x00000000004006a0 <+44>: ldp x29, x30, [sp], #48 0x00000000004006a4 <+48>: ret

3.2 LR寄存器

LR寄存器在gdb中可以直接查看x30的值,如下:

info register x30 x30 0x40071c 4196124

此时我们知道地址0x40071c,我们可以x解析值,这里gdb会帮我们提升为函数,如下

(gdb) x 0x40071c 0x40071c <main+116>: 0xb9401fe0

可以看到,这个地址是main函数+116的栈偏移地址。

此时我们设置断点如下:

(gdb) b *0x40071c Breakpoint 2 at 0x40071c: file ioctl.c, line 29.

此时我们运行,可以看到在断点停下了

Breakpoint 2, main () at ioctl.c:29 29 close(fd);

我们对应代码:

27 load_data(&d); 28 29 close(fd);

可以看到,正好在close上,也就是当前正好是load_data的返回。

通过这里,我们可以很清楚的知道,x30寄存器也就是lr寄存器,其作用是函数返回时的返回地址,这个地址是函数的运行地址。通过x30寄存器,我们可以定位函数位于上级函数的位置。

3.3 PC寄存器

PC寄存器虽然arm和aarch64在定义上有不同,但是在gdb中行为是一致的,我们可以直接查看pc的值,如下

(gdb) info register pc pc 0x400704 0x400704 <main+116>

这里可以看到,我们

当前的pc值是0x400704,此时我们可以反汇编如下:

(gdb) disassemble Dump of assembler code for function main: 0x0000000000400690 <+0>: stp x29, x30, [sp, #-32]! 0x0000000000400694 <+4>: mov x29, sp 0x0000000000400698 <+8>: mov w1, #0x2 // #2 0x000000000040069c <+12>: adrp x0, 0x400000 0x00000000004006a0 <+16>: add x0, x0, #0x828 0x00000000004006a4 <+20>: bl 0x400510 <open@plt> 0x00000000004006a8 <+24>: str w0, [sp, #28] 0x00000000004006ac <+28>: ldr w0, [sp, #28] 0x00000000004006b0 <+32>: cmp w0, #0x0 0x00000000004006b4 <+36>: b.ge 0x4006c0 <main+48> // b.tcont 0x00000000004006b8 <+40>: mov w0, #0x0 // #0 0x00000000004006bc <+44>: b 0x400710 <main+128> 0x00000000004006c0 <+48>: add x0, sp, #0x10 0x00000000004006c4 <+52>: mov x2, x0 0x00000000004006c8 <+56>: mov x1, #0x6162 // #24930 0x00000000004006cc <+60>: movk x1, #0x8008, lsl #16 0x00000000004006d0 <+64>: ldr w0, [sp, #28] 0x00000000004006d4 <+68>: bl 0x400570 <ioctl@plt> 0x00000000004006d8 <+72>: ldr w0, [sp, #16] 0x00000000004006dc <+76>: ldr w1, [sp, #20] 0x00000000004006e0 <+80>: ldr w2, [sp, #24] 0x00000000004006e4 <+84>: mov w3, w2 0x00000000004006e8 <+88>: mov w2, w1 0x00000000004006ec <+92>: mov w1, w0 0x00000000004006f0 <+96>: adrp x0, 0x400000 0x00000000004006f4 <+100>: add x0, x0, #0x838 0x00000000004006f8 <+104>: bl 0x400560 <printf@plt> 0x00000000004006fc <+108>: add x0, sp, #0x10 0x0000000000400700 <+112>: bl 0x400674 <load_data> => 0x0000000000400704 <+116>: ldr w0, [sp, #28] 0x0000000000400708 <+120>: bl 0x400530 <close@plt> 0x000000000040070c <+124>: mov w0, #0x0 // #0 0x0000000000400710 <+128>: ldp x29, x30, [sp], #32 0x0000000000400714 <+132>: ret

可以发现,这里有一个箭头,箭头地址就是pc的值。

我们可以知道,pc的值有三种修改方式

  • 代码运行时,按照4字节运行,这种方式会修改pc的值
  • 代码通过bl指令进行跳转,跳转后修改pc的值
  • adrp等加载指令可以修改pc的值(汇编代码未提供示例)

再加上pc寄存器有一个特性,指向的是下一条语句的运行,所以pc的值是即将运行的代码。

至此,我们了解了pc寄存器的值。

四、总结

至此,我们应该能够完全的理解aarch64的pc,sp和lr三个寄存器的完全解释了。