编辑
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三个寄存器的完全解释了。

编辑
2025-01-22
记录知识
0

我们在讨论栈破坏的时候,通常是数组越界访问和库函数memset/memcpy等函数的错误覆盖,对于这样的错误,通常大家知道原因排查起来很方便,但是实际情况中,还有第三种栈破坏的问题,就是结构体不匹配的类型转换导致的栈破坏。本文讨论这种栈破坏问题。

一、发生情况

通常情况下,结构体的类型转换如果出现错误,编译的时候就会提示,例如将两个不同的结构体进行类型转换,如:

struct A a = (struct B)b;

这种错误编译器就告诉你不能这样转换。

但是如果是结构体指针,那么或许可以转换,例如

struct B *b; struct A* a = (struct A*)b;

我们发现可以将struct B的b指针强制类型转换成struct A,这种情况下编译器会提示warning

如果这种强制类型转换出现破坏了,我们查一下编译时的警告就一目了然了。

但是我们还有一种渠道,就是先将结构体指针struct B*转换成void *,然后将void*的变量转换成struct A*,如下:

struct B *b; void* t = b; struct A* a = (struct A*)t;

这种情况下,我们编译代码,将得不到任何警告或错误,因为对于C而言,这种转换本身就是有意义的。

但是如果我们代码直接这样写,那么当我们出现栈破坏的时候,我们可以通过审查代码的方式定位和解决问题。

如果我们的代码做过封装,封装的时候考虑了解耦,这种情况下,我们可以发现

struct B* 出现在服务层,我们需要在具体实现时构造struct B void* t=b 出现在接口层,为了解耦,我们需要将类型转换成通用的void struct A* 出现在客户端,我们在实现应用的时候,需要实例化一个对象,这个对象是struct A*的结构

就像这样

image.png 这种情况下,我们不方便追查问题,因为其真实的调用情况如下:

image.png 我们的app程序在调用libxxx.so时,根据的是libxxx_api.h,并不知道libxxx.so内部的实现,而app程序又会自行构造和类型转换结构体。

也就是说,如果libxxx.so的结构体和app的结构体声明存在不一致,那么可能出现问题。

那么有什么情况呢?这里以struct A和struct B为例,struct A在libxxx.so中实现,struct B在application中实现

image.png

  • 假设A的成员比B多,那么最后强制类型转换为B,我们可以发现是将实际结构体成员缩小了。并不会出现问题
  • 假设A的成员比B少,那么最后强制类型转换为B,我们可以发现B在访问变量的时候,就会错误的访问到栈区或堆区(取决于申请情况)
  • 假设A的结构体和B的结构体完全不一样,那么应用在访问数据的时候就能感知到业务出现了差异,问题很容易发现,所以不做讨论
  • 假设A的结构体和B的结构体完全一样,那么这属于正常行为,不会出现任何问题

根据上面所述的,我们存在问题的情况只有在2的时候,可能发生。这里为什么说可能发生,而不是一定发生呢,因为需要满足以下两点

  • A应该在栈区,栈区的数据才能破坏,如果在堆区,那么破坏的是堆区的数据
  • A应该破坏了自己或其他栈区结构体指针

如果A破坏了堆区,那么会出现堆区的异常,这里不做讨论

如果A只是占用了没分配的栈地址,那么程序不会异常

如果A只是修改了某个栈的局部变量,函数参数和临时数据,那么程序可能不会出现异常

所以这里的破坏,一定是破坏了某个栈区的结构体指针。

二、这种情况的发生场景

根据上面提到的,我们想要出现这种错误,那么我们肯定做了void*的转换,而实际调用so的过程中,我们其实没必要非做void*的类型转换,直接包含同一个h即可。那么什么场景下,这种转换尤为重要呢?

答案是这个数据来源于内核,因为内核的结构体和应用的结构体没有做好绑定。内核和应用又是隔离的。所以void*的转换尤为重要。

所以,综上可以知道。 如果我们通过内核拿一个结构体A,应用层的结构体B需要做强制类型转换,如果A和B结构体是完全一致的,那么不会存在问题,假设如下两种情况:

  1. 内核未更新,应用发生了更新,将结构体B的成员添加了,从而导致结构体B的成员比A多。
  2. 内核发生了更新,将结构体A的成员减少了,应用未发生更新,从而导致结构体B的成员比A多。

至此,我们将这种栈破坏讲述的比较清楚了。也可以发现,这种栈破坏在满足一定条件下,实际上是很容易出现的。

三、示例

3.1 直接复现问题

为了阐述这个问题,我们先编写了一个简单的c程序,该程序直接进行强制类型转换如下:

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> struct kernel{ int x; int y; int z; }; struct user{ int x; int y; int z; int o1; int o2; int o3; int o4; }; void test1(struct user* bug) { bug->x = 2; return; } void test(struct kernel k) { struct user* s = (struct user*)&k; s->o4 = 1; test1(s); return ; } int main(int argc, char *argv[]) { struct user arr = {1,2,3}; void* ss = &arr; struct kernel *b = (struct kernel*)ss; test(*b); return 0; }

我们将其编译:

gcc /root/stack_damage.c -o stack_damage

此时运行后。我们拿到一个段错误如下:

# ~/stack_damage 段错误 (核心已转储)

这里值得注意的是如下:

struct user arr = {1,2,3}; 声明了一个user的结构体 void* ss = &arr; 将其转换成void* struct kernel *b = (struct kernel*)ss; 将其强制类型转换成kernel void test(struct kernel k) 函数test会主动构造kernel结构体 struct user* s = (struct user*)&k; 此时强制类型转换给了s s->o4 = 1; 通过s破坏了结构体s自己 bug->x = 2; 这里访问自己的x时,出现了栈破坏的错误

这里还需要两个知识点如下:

  • 函数形参如果不超过16字节,那么不会使用x19来计算偏移,而是直接通过sp的偏移存储,也就是x0x1x2x3存放sp的偏移存储,也就是x0,x1,x2,x3存放sp开头的位置

9b13ee5fc9e85cc28c462df5ec1355c.jpg 上述查看B.4

  • 函数的第一个局部变量使用的是sp+sp+offset - 4,也就是第一个局部变量在栈区的最高位

如果忽略这两个要素,我们不会出现必现的栈破坏问题。

我们通过gdb来调试如下:

# gdb ./starck_damage GNU gdb (Ubuntu 9.1-0kylin1) 9.1 Copyright (C) 2020 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "aarch64-linux-gnu". Type "show configuration" for configuration details. For bug reporting instructions, please see: <http://www.gnu.org/software/gdb/bugs/>. Find the GDB manual and other documentation resources online at: <http://www.gnu.org/software/gdb/documentation/>. For help, type "help". Type "apropos word" to search for commands related to "word"... Reading symbols from ./starck_damage... (gdb)

此时运行如下:

(gdb) r Starting program: /root/stack_panic/starck_damage [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1". Program received signal SIGSEGV, Segmentation fault. 0x0000000000400554 in test1 (bug=0x7f00000001) at /root/starck_damage.c:26 26 bug->x = 2;

然后我们得到了一个巨大的疑问

为什么bug->x =2 会出现段错误 此时我们应该现打一个断点test,然后重跑到断点如下:

(gdb) b test Breakpoint 1 at 0x40057c: test. (2 locations) (gdb) r The program being debugged has been started already. Start it from the beginning? (y or n) y Starting program: /root/stack_panic/starck_damage [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1". Breakpoint 1, test (k=...) at /root/starck_damage.c:32 32 struct user* s = (struct user*)&k;

然后定位到s->o4 = 1;之前,

(gdb) b *0x0000000000400588 Breakpoint 1 at 0x400588: file /root/starck_damage.c, line 33.

然后打印s的地址如下:

(gdb) p &s $1 = (struct user **) 0x7fffffeff8

然后计算s保存的值

(gdb) p s $2 = (struct user *) 0x7fffffefe0

我们得到地址0x7fffffefe0,然后查看其值

(gdb) x/3w 0x7fffffefe0 0x7fffffefe0: 0x00000001 0x00000002 0x00000003

此时我们将其定位到s->o4 = 1;运行之后,

(gdb) b *0x0000000000400590 Breakpoint 2 at 0x400590: file /root/starck_damage.c, line 34.

此时我们打印s的值,如下

(gdb) p s $4 = (struct user *) 0x7f00000001

可以发现s的值变成了0x7f00000001。此时我们访问

bug->x = 2;

自然出现段错误。

那么为什么会这样呢?

我们可以看到k的地址如下:

(gdb) p &k $7 = (struct kernel *) 0x7fffffefe0

然后s的地址是0x7fffffeff8

我们拿0x7fffffeff8-0x7fffffefe0=24

24/4+1=7

也就是说我们如果操作user的第7个int,那么就会破坏s的栈。

所以这也就是s->o4 = 1;的由来。

此问题根本原因是我们通过值传递结构体时,会通过寄存器+当前栈区空间来访问,此时如果强制类型转换,那么很可能访问到函数体内第一个栈区局部变量上,从而错误的改写了栈区内容。

至此,我们通过直接的示例阐述了破坏栈区的一种情况

3.2 真实的情况

根据上面说的,我们为了复现问题编写了c程序,但是真实的情况应该来自内核的结构体和应用的结构体不一致,所以我们先编写内核驱动如下:

struct data { int x; int y; int z; }; static long kylin_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct data user_data; user_data.x = 1; user_data.y = 2; user_data.z = 3; switch(cmd) { case RD_VALUE: if (copy_to_user((void __user *)arg, &user_data, sizeof(user_data))) { pr_err("Data Read : Err!\n"); } break; default: pr_info("Default\n"); break; } return 0; }

此时我们加载ko会在/dev/下出现kylin的字符设备

# ls /dev/kylin /dev/kylin

我们应用上先提供ioctl.c如下

#include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include "libs.h" #define RD_VALUE _IOR('a','b',int32_t*) void load_data(void* d) { struct kdata *b = (struct kdata*)d; test(*b); } int main() { int fd; struct kdata d; fd = open("/dev/kylin", O_RDWR); if(fd < 0) { return 0; } ioctl(fd, RD_VALUE, (struct kdata*) &d); printf("%d %d %d\n", d.x, d.y, d.z); load_data(&d); close(fd); }

值得注意的是这里的头文件如下:

root@kylin:~/stack_panic# cat libs.h kernel.h #include <stdio.h> #include <stdlib.h> #include <string.h> #include "kernel.h" void test(struct kdata k); #ifndef _KERNEL_STRUCT_H #define _KERNEL_STRUCT_H struct kdata { int x; int y; int z; }; #endif

我们知道应用需要调用test,所以实现libs.c如下

#include "libs.h" struct data { int x; int y; int z; int o1; int o2; int o3; int o4; }; void oops(struct data* bug) { bug->x = 2; return; } void test(struct kdata k) { struct data* s = (struct data*)&k; s->o4 = 1; oops(s); return ; }

编译如下:

gcc ioctl.c libs.c -g -o ioctl

此时运行时保存如下:

# ./ioctl 1 2 3 段错误 (核心已转储)

可以发现栈出现了破坏。

这里为了简化,我只是简单抽象了test的实现在libs.c中。但其意思符合上文描述的情况。

也就是说,应用如果调用了库,库的结构体和内核结构体不符合,那么就会出现栈破坏。

四、总结

本文通过示例的方式演示了一种特殊的栈破坏情况,这种情况在内核和应用之间非常容易出现(强制类型转换和小于16字节值传递的时候),也不容易排查。后续如果有其他情况出现这样的问题的时候,我们可以拿这种情况对号入座一下,这样就不用浪费更多的时间来排查栈破坏的问题了。

编辑
2025-01-22
记录知识
0

根据yocto版本构建(二)安装已有二进制和yocto版本构建(三)源地址dsc进行构建我们可以通过安装源的deb文件和通过dsc来执行一次构建操作,我们知道,通常情况下,代码都是通过git管理,这里还需要提供一个函数支持git方式的构建,如下:

一、bbfile

对于gitlab2拉取源码的方式,我们需要指向到自己的SRC_URI后,根据deb.bbclass来进行代码构建,所以bbfile如下:

inherit kylin inherit deb SRC_URI = "git@gitlab2.kylin.com:shanghai-team/embedded/kylin-egf/ukui-menu.git;branch=egf/v101-tablet" S = "${WORKDIR}/ukui-menu" do_compile() { dpkg-buildpackage -uc -us : }

这里需要注意的是,我们没有使用yocto的本身git构建和打包deb的方式,我们使用了自己的命令,然后,我们需要在do_install的时候,进行dpkg -i的操作,这样sysroot才能正常安装。

二、一些变量解释

为了使得调试更方便,这里提供了一些变量的说明,可以链接查看如下:

https://pages.openeuler.openatom.cn/embedded/docs/build/html/master/yocto/yocto_quick_start_manual/variables_and_tasks.html

主要解释如下:

MACHINE: 指定使用的硬件配置文件,通常在local.conf文件定义; DISTRO: 指定使用的发行版配置文件,通常在local.conf文件定义; PN: 软件包名,一般是根据文件名自动生成;除了一些交叉编译的包,如gcc-cross会在bb中重新定义; PV: 软件包版本; PR: 食谱的修订,默认为r0;当包管理器在已构建的镜像上动态安装包时,PR很重要,当前openEuler未启用; BPN: 软件包名,去除指定的前后缀(如-native、-cross等); BP: ${BPN}-${PV}; SRC_URI: 源码路径,可以为上游或者本地文件路径,上游源码需要使用校验值; LICENSE: 配方的源许可证列表,必须设置;如果设置为”CLOSED”则关闭; LIC_FILES_CHKSUM: 配方源代码中许可证文本的校验和,与LICENSE变量配合使用; PACKAGE_ARCH: 生成包的体系结构; TARGET_VENDOR: 指定目标供应商的名称,openEuler设置为”-openeuler”; TARGET_OS: 指定目标的操作系统; MULTIMACH_TARGET_SYS: 生成包的目标系统类型的唯一标识,默认为${PACKAGE_ARCH}${TARGET_VENDOR}-${TARGET_OS}; WORKDIR: 构建配方的工作目录的路径名,指向${TMPDIR}/work/${MULTIMACH_TARGET_SYS}/${PN}/${EXTENDPE}${PV}-${PR},EXTENDPE变量通常不被设置; S: 构建过程中源代码位置,默认为${WORKDIR}/${BPN}-${PV}; B: 构建过程中生成对象所在的目录,默认与S相同;一些类会将B设置为${WORKDIR}/build; D: 相当于 make install 后的目标目录,指向${WORKDIR}/image; PACKAGES: 表示配方创建的包列表; FILES_xxx: 放置在包中的文件和目录列表; PKGD: 要打包的文件的目录,指向${WORKDIR}/package; PKGDEST: 将文件拆分为单独的包后,指向要打包的文件的父目录,该目录是PACKAGES中指定的每个包的目录,指向${WORKDIR}/packages-split; DEPENDS: 列出配方的构建时依赖关系,配方在构建时需要其它配方的内容(例如头文件和共享库); RDEPENDS: 列出程序包的运行时依赖项,这些依赖项是必须安装的其他程序包,以便程序包正常运行; RECIPE_SYSROOT: 指向${WORKDIR}/recipe-sysroot; RECIPE_SYSROOT_NATIVE: 指向${WORKDIR}/recipe-sysroot-native; SYSROOT_DESTDIR: 指向${WORKDIR}/sysroot-destdir; SYSROOT_DIRS: 暂存到${SYSROOT_DESTDIR}的目录; STAGING_DIR_HOST: 组件运行所在的系统上的sysroot路径,默认为${RECIPE_SYSROOT}。 STAGING_DIR_NATIVE: 构建主机上运行的组件使用的sysroot的路径,默认为${RECIPE_SYSROOT_NATIVE}; STAGING_DIR_TARGET: 当构建在系统上执行的组件并为另一台机器生成代码(例如cross-canadian配方)时使用的sysroot路径; STAGING_KERNEL_DIR: 包含构建树外模块所需的内核头文件的目录(内核源码目录); STAGING_KERNEL_BUILDDIR: 指向包含内核构建工件的目录。需要访问内核构建工件的配方构建软件可以在内核构建后在STAGING_KERNEL_BUILDDIR变量指定的目录中查找这些工件; PACKAGE_CLASSES: 指定构建系统在打包数据时使用的包管理器(例如RPM、DEB或IPK),在local.conf文件设置; IMAGE_ROOTFS: 指定根文件系统在构建过程中的位置( do_rootfs 任务期间)。此变量不可配置,不要更改它; IMAGE_FEATURES: 指定要包含在镜像中的主要功能列表,这些功能大多数都映射到其他安装包; EXTRA_IMAGE_FEATURES: IMAGE_FEATURES的一部分; IMAGE_INSTALL: 指定要安装到镜像中的程序包; PACKAGE_EXCLUDE: :指定不应安装到image中的包; PACKAGE_INSTALL: 要安装到镜像中的程序包的列表,不要更改它,通常使用IMAGE_INSTALL变量间接进行修改; DEPLOY_DIR: 指向构建系统用于放置镜像、包、SDK和其他输出文件的常规区域,这些文件已准备好在构建系统之外使用。默认情况下,此目录位于指向${TMPDIR}/deploy。 DEPLOY_DIR_IMAGE: 指向构建系统用来放置准备部署到目标计算机上的镜像和其他相关输出文件的区域。该目录是特定于机器的默认情况下,此目录指向${DEPLOY_DIR}/images/${MACHINE}/; DEPLOYDIR: 当继承deploy类时,DEPLOYDIR指向已部署文件的临时工作区,默认指向${WORKDIR}/deploy-${PN},此目录内容会被拷贝到${DEPLOY_DIR_IMAGE}; CC: 用于运行C编译器的最小命令和参数; CFLAGS: 指定要传递给C编译器的标志; CXXFLAGS: 指定要传递给C++编译器的标志; CPPFLAGS: 指定要传递给C预处理器(即同时传递给C编译器和C++编译器)的标志; LDFLAGS: 指定要传递给链接器的标志; OVERRIDES: 以冒号分隔的当前应用的覆盖列表。覆盖是一种BitBake机制,允许在解析结束时选择性地覆盖变量; COMPATIBLE_MACHINE: 一种正则表达式,解析为一个或多个与配方兼容的目标机器。可以使用该变量来停止为配方不兼容的机器构建配方,停止这些构建对于内核特别有用。该变量还有助于提高解析速度,因为构建系统会跳过与当前机器不兼容的解析配方。

三、总结

因为我们很多仓库都是通过git管理的,所以通过git地址来触发二进制编译的行为至关重要,根据上述描述,我们可以具备通过git链接地址进行二进制包的构建

编辑
2025-01-22
记录知识
0

根据yocto版本构建(三)源地址dsc进行构建我们可以通过输入一个内部源地址就可以拉取软件包安装,但是我们还需要提供一个功能,如果我们将文件放在recipes内的files下,我们需要手动将其解压和构建,这样可以不依赖源的dsc和文件

一、bbfile

根据上面的描述,我们需要从files中获取文件,如下:

inherit kylin inherit deb SRC_URI = "file://ukui-menu_3.0.1-0720.1.dsc;md5=1b897ae127a2d8076a0b318daa720f91\ file://ukui-menu_3.0.1-0720.1.tar.xz;md5=e684d90a713f953179b1a06e8e3401c1 do_deb_prepend() { install -d ${S} install -m 0755 ${WORKDIR}/ukui-menu_3.0.1-0720.1.dsc${S} install -m 0644 ${WORKDIR}/ukui-menu_3.0.1-0720.1.tar.xz ${S} }

这样可以将ukui-menu的文件放到work目录下,然后我们执行dpkg命令即可正常构建

二、介绍

do_fetch 会使用SRC_URI变量定位源码文件;基于SRC_URI变量值中每个条目的前缀来确定使用哪个提取器来获取源文件, file:// 开头为本地文件, http://git:// 等为上游获取的源文件;

我们这里使用了file,这样将其拉到了sysroot内部,这样我们可以通过自己的函数来执行dpkg-buildpackage。从而构建deb包

其形式类似于yocto版本构建(三)源地址dsc进行构建

三、总结

至此,我们不仅可以通过远程源地址的dsc文件编译二进制包,也可以通过本地file链接来编译二进制包