Debian APT对Rust的要求引发质疑
项目或软件包新增依赖通常不足为奇。但像Debian的高级软件包管理工具(APT)这类核心工具的变更,却可能产生深远影响。例如,Julian Andres Klode 在 2026 年 5 月宣布 APT 将强制要求 Rust 支持,这意味着部分 Debian 非官方移植版本必须获得可用的 Rust 工具链,或继续依赖旧版 APT。这在项目内部引发诸多质疑,尤其质疑单个维护者能否推行具有广泛影响的变更。
10月31日,克洛德向debian-devel邮件列表发布公告,宣布计划最早于2026年5月将Rust依赖项和代码引入APT:
初期将引入Rust编译器、标准库及Sequoia生态系统。
特别是解析.deb、.ar、.tar文件的代码以及HTTP签名验证代码,将极大受益于内存安全语言和更强大的单元测试方法。
若您维护的移植版本尚未配备可用的Rust工具链,请确保在未来6个月内完成配置,否则该移植版本将被淘汰。
Klode补充道,此举旨在推动项目整体发展,依托现代技术,避免“因将现代软件硬塞进复古计算设备而受制于人”。部分Debian开发者对此表示欢迎。Paul Tagliamonte 承认此举将影响非官方Debian移植项目,但称推动Rust化是“值得欢迎的消息”。
然而约翰·保罗·阿德里安·格劳比茨批评克洛德的措辞令人不快,且采取对抗性策略。在另一封邮件中,他解释自己并非反对采用Rust; 他曾致力于在多个Debian架构上支持Rust,并协助修复了Rust工具链及LLVM上游的架构特定缺陷。然而该消息强烈暗示计划无法调整:克洛德以“感谢理解”结尾,暗示不接受进一步讨论。格劳比茨是少数几位在消息中对克洛德沟通方式表示不满的Debian开发者之一。
克洛德指出指出,除Alpha (alpha)、Motorola 680×0 (m68k)、 PA-RISC (hppa)和SuperH (sh4)体系结构。这是因为APT使用了Sequoia-PGP项目的 sqv工具验证OpenPGP签名。在未安装Rust编译器的平台上,APT会回退至使用GNU隐私守护程序签名验证工具gpgv。但若直接依赖Rust,则意味着APT本身将无法在未安装Rust编译器的平台运行。LWN近期报道了Linux架构支持现状及各架构对Rust的支持情况。
LWN.net全体工作人员衷心感谢订阅者们对我们工作的支持。您是否愿意加入订阅行列?
Klode列出的所有移植版本均未列入Debian当前官方支持列表,亦未被纳入Debian 14(代号“forky”)的计划支持范围。sh4端口从未获得官方支持,其余端口自Debian 6.0后也未再获得支持。实际影响远比最初听起来要小。Glaubitz 向Antoni Boucher保证 安东尼·布歇称“朱利安设定的最后通牒实际上并不存在”,但采用这种表述方式“能在新闻中获得更多关注”。布歇是rust_codegen_gcc的维护者,该项目是面向Rust语言的GCC预编译代码生成器。格劳比茨表示,在布歇等人为这些端口实现Rust的引导程序之前,端口完全可以继续使用非Rust版本的APT。
安全戏剧?
大卫·卡尔尼施基斯(David Kalnischkies)作为APT的主要贡献者,建议若目标是减少错误,则应彻底移除Klode提及的用于解析.deb、.ar和.tar格式的代码。他指出该代码仅服务于两个工具:apt-ftparchive和apt-extracttemplates, Klode表示,其中apt-ftparchive的唯一“重要应用”来自其雇主Canonical的Launchpad软件协作平台。若将这些工具移出APT主代码库,无论采用Rust、Python或其他语言编写都无关紧要,因为它们并非特定端口的直接必需组件。
Kalnischkies还质疑Klode所称“必须使用Rust才能实现更强健的单元测试方法”的说法:
C++当然能进行单元测试,我们就在用。核心问题在于有人必须编写这些测试——就像文档一样。
例如你新开发的求解器完全没有测试(除了我们现有的集成测试)。你不会当真认为这是C++的缺陷吧?若不喜欢我们当前使用的GoogleTest,我建议采用doctest(此前已多次推荐)。还有许多风格相近或不同的测试框架可供选择。
Klode尚未回应这些评论,这令人遗憾——毕竟引入Rust的硬依赖会对APT项目产生超出其个人工作范围的影响。他或许能给出合理解释,但当前局面难免让人觉得Klode只是在盲目追随Rust潮流。他正参与Ubuntu从GNU Coreutils迁移至基于Rust的uutils的工作(详见:这里和这里。该项目的初衷仍是现代化改造与提升安全性——但切换至Rust并不能自动保证安全性,其中还涉及诸多其他考量因素。
例如,Adrian Bunk 指出 部分 APT 代码采用 Rust 编写将影响多个 Debian 团队及其配套工具链。Debian 13(代号“trixie”)的发布说明提及,Debian基础设施“目前存在系统性使用静态链接的软件包重建问题”,例如采用Go和Rust编写的代码。因此“在基础设施得到改进以实现可持续维护之前,这些软件包将仅获得有限的安全支持”。有限安全支持意味着Rust库的更新可能仅在Debian发布点版本时同步(约每两个月一次)。安全团队已特别声明sqv获得全面支持,但仍存在未解决问题。
由于静态链接机制,当sqv的依赖项(目前超过40个Rust仓库)因安全问题需要重建时,sqv(至少在理论上)也需同步重建。此外,追踪所有依赖项的CVE漏洞信息存在困难,且难以判断Rust仓库的安全漏洞何时需要更新依赖该仓库的Rust程序。
Debian的Rust工具链维护者Fabian Grünbichler列举了Debian处理Rust软件包时面临的若干悬而未决的问题。其中最突出的问题是需要制定统一的静态链接库声明政策。2022年,Guillem Jover为Debian软件包新增了名为Static-Built-Using(SBU)的控制字段,用于列出构建二进制包所使用的源包。该字段可指示当某个源包更新时,对应二进制包是否需要重新构建。例如,sqv依赖于40多个已打包至Debian的Rust仓库。若未声明SBU,当其依赖项更新时,可能无法明确判断sqv是否需要同步更新。Debian自2024年4月起着手制定SBU政策要求,但该规范尚未最终确定或正式采用。
Grünbichler引发的讨论表明,Debian中与Rust相关的大部分问题正在解决中。然而,没有任何证据表明Klode在宣布APT将依赖Rust之前曾深入探讨过这些问题,甚至连“引入此依赖是否合理”这样的基本考量都没有。
传统与未来的交汇点
Debian的口号(至少其中之一)是“通用操作系统”,意味着该项目致力于在各类硬件(新旧兼容)上运行,并适用于桌面、服务器、物联网设备等场景。其“为何选择Debian”页面列举了用户与开发者应选用该发行版的诸多理由: 多硬件架构支持、长期维护承诺以及民主治理结构仅是其中部分优势。该页面同时强调“Debian无法被单一公司掌控”。若某公司雇佣的开发者在未经讨论或辩论的情况下,为推进对该公司有利的变更而推动修改,且该变更影响多硬件架构,迫使其他志愿者进行额外工作或赶制人造截止期限,这显然违背了项目宣称的核心价值观。
当然,Debian确实设有制衡机制,当其他开发者认为必要时可启动相关程序。例如,若仅凭讨论无法说服某位开发者,他人可向Debian技术委员会提出申诉,或发起全体决议以覆盖该开发者的决策。近期就发生过类似情况:委员会要求systemd维护者提供/var/lock目录,“直至受影响软件完成满意迁移且政策相应更新”。
但公平地说,Debian有时确实进展缓慢,甚至堪称冰川移动般迟缓。APT早在2015年就添加了对DEB822格式的源信息列表支持。尽管APT多年来支持该格式,但Klode在2021年推动Debian迁移至新格式时遭遇阻力,未能成功。随着APT 3.0的迁移,该格式现已成为trixie的默认格式,不过APT未来数年仍将继续支持旧格式。
事实上,无论Klode如何处理APT,越来越多的自由软件正以Rust语言编写(或重写)。当这些软件打包至Debian时,简化其支持流程将惠及所有人。或许该项目需要一些积极推动的开发者,加速完善对Rust的支持。然而真正需要的,是更多开发者参与支持Rust在Debian及其他平台(如gccrs)的工作。若仅由单一开发者声明依赖项,迫使其他志愿者仓促应对,这似乎有悖于Debian注重社区协作的宗旨。
本文文字及图片出自 APT Rust requirement raises questions
能否分叉APT项目,用C/C++实现填充这些Rust组件,使移植版APT在功能上与Rust版APT保持一致,从而延续这些移植工作?
能否开发Rust转C转换器?我隐约觉得这类工具应该存在。它无需运行借用检查器或Rust的大多数编译时检查,只要能生成完整Rust编译器构建过的代码即可。该工具还能生成始终单线程运行的代码。
为此,可为现有Rust编译器编写新后端。这样借用检查器及其他编译时检查仍能生效。
`rustc_codegen_clr`具备此模式,另有团队曾尝试为`rustc`开发新C后端。两者均未“投入生产环境”,但这是个不错的思路——事实上,语言设计编译器时采用这种方式并不罕见。
Rust可编译为wasm,wasm2c工具链运行良好,RLBox正是基于此实现,Firefox中来自clang的wasm也采用该方案。
至于让现有C代码堆栈暴露与Rust输入相同的FFI接口所需的工作量,我尚不清楚。
Debian中对apt并无严格依赖,整个讨论从一开始就被过度放大。
没有Rust工具链的端口仍可使用用C++编写的cupt。
另一条途径是为gccrs或rust_codegen_gcc等项目贡献代码,使Rust也能在这些端口上运行。此方案的微小优势在于:一旦实现Rust支持,Debian中所有需要Rust的软件包都将能在该端口构建。
Debian的大部分构建体系都围绕共享库的支持需求展开,这既带来挑战也带来便利。
我极不愿因迁移至Rust而失去这一特性,尤其当迁移缺乏严格的技术必要性时——C++支持共享库,Rust原则上同样具备支持能力。事实上,Rust共享库通过在库内部明确定义ABI和API,本可解决C共享库的大部分问题。
为更新依赖关系而重建软件包对Debian而言不可持续。
即便在C++中,为引入模板修复也需执行此操作,因为模板位于头文件中。其他原生编译语言大多也要求重建。就新语言而言,我所知仅Swift和Hare是例外。
> 若发行版希望生态系统更友好,就必须投入(大量)工作来实现。这并非不可能,但难度极高且涉及整个生态系统。在他们付诸行动前,只能使用那些实际投入工作的人所创造的成果。
希望Canonical、Red Hat或Valve这类企业能挺身资助此事,毕竟指望Debian这类志愿者发行版承担此工作似乎不切实际。
> 当依赖关系变更时重建软件包才是未来趋势。
那未来真是糟透了
> > 当依赖项变更时重新构建包才是未来趋势。
> 那未来简直糟透了
要么你就回到我四十多年前的做法——那时库文件就是库文件…
当然需要思考如何将其迁移到现代环境,但采用静态链接时,库文件不过是一堆被复制进来的.o文件。
应用程序确实需要重建,但编译负载会大幅降低。
若真想实现编译器与链接器的某种融合,虽然很可能无法混用不同编译器,但你可以将库编译成中间编译器表示形式,尽可能优化后导出.lib文件,供编译器直接集成到应用程序中。
好吧,你将失去直接替换修复库来一次性修复所有应用程序的能力,但这种方式在实践中真的有效吗?
祝好,
Wol
> 好吧,你确实失去了直接替换修复库就能一次性修复所有应用的功能,但这种方式实际效果如何?
效果相当不错。例如每次libtiff修复新CVE漏洞时,只需升级库文件,无需重建所有直接或间接处理TIFF文件的软件。
让安全修复变得极其昂贵并不会提升安全性。
奇怪的是,所有倡导这种方法的发行版都未正式覆盖需要重启的环节——即明确告知用户哪些组件需要重启才能享受更新效益。
是这样吗?我的Debian和Ubuntu系统在安装更新后通常会重启服务。这是否不包括共享库更新——也就是说,只有当服务本身被更新后才会重启?我真心好奇,一直以为依赖项也会触发重启(至少如果是共享库的话)。
若非如此,仅设置APT自动安装安全更新却不重启单个服务或整个系统,显然不足以确保系统免受已知(且已修复)漏洞的侵害。
确实存在“守护进程使用过时库”的问题,但这仅覆盖了运行二进制文件或实际使用更新文件的systemd服务中的极小部分。
没错,这就是共享库安全更新中令人难堪的秘密。我们长期嘲笑微软每次变更都强制重启设备的作法,但若要确保系统中不再运行任何漏洞代码,这种方式确实难以超越。
理论上程序可在运行时重新链接至新版共享库代码,但这需要比多数库提供的更强健的ABI稳定性保障。
嗯。这为何是共享库与静态库的区别?无论哪种方式,应用程序不都需要重启才能生效吗?
关键在于这并非大幅降低工作量——而且还能减少自动化环节的操作。
> 共享库的优势在于:只需更新库文件,所有应用程序即可立即获得补丁,这比静态链接方式更省力——后者需要更新二进制文件并重启应用程序。
没人声称共享库能免除*重启应用程序*的需求。这是你凭空捏造的说法。
共享库仅能免除*更新二进制文件*的步骤,仅此而已。
它们唯一的作用是免去替换可执行文件的步骤——但替换二进制文件(无论是库文件还是可执行文件)恰恰是更新流程中最易自动化的环节。
显然,此处“更新二进制文件”特指更新依赖性二进制文件(即可执行文件)。我本欲补充说明却因过于显而易见而省略。
> 但替换二进制文件(库文件或可执行文件)恰是更新流程中最易自动化的环节。
但最初自动生成这些二进制文件绝非易事(或低成本)。共享库的价值正在于此:当只需对O(1)库进行兼容性修改时,无需重建并重新分发O(n)个依赖项。
> 自动生成这些二进制文件本身并非易事(成本也高昂)。
新兴编程语言的主张恰恰相反——它们理应实现自动化,且已基本成功。你上次见到无法通过 cargo build 或 go build 等命令构建的 Rust 或 Go 项目是什么时候?
没错,后续的打包步骤往往更复杂。但这完全是发行版的发明。他们没资格把问题转嫁给所有人。
官方声明中,Fedora仅支持离线更新(重启→在受限环境应用更新→启动更新后的系统)。在线更新虽可行,但后续善后工作需自行处理。
你总说发行版不处理这种情况,但他们确实处理。
别忘了反向情况同样成立:libtiff引入一个漏洞,系统中所有使用libtiff的程序都会受影响。新版本库出现(新)漏洞的情况并不少见… 我甚至倾向于认为:共享库引入新漏洞的频率,往往高于其修复实际可利用安全漏洞的频率。
设想新版libtiff在处理TIFF压缩方案32809(ThunderScan 4位RLE)的解压器中引入了安全相关漏洞。上游程序的静态链接版本并不受影响,因为它们未启用处理老旧Mac文件所需的libtiff功能模块。但由于你的发行版包含一个工具,该工具本应分析老旧Mac磁盘映像并将所有数据转换为可用的现代格式,因此发行版构建的libtiff启用了此支持功能。
此逻辑可轻松反向推演:上游版本通过静态链接启用了libtiff中存在缺陷的旧版功能。当漏洞曝光后,发行版更新了系统共享库,所有动态链接的应用程序随即获得修复——当然,静态链接上游版本的应用除外。
哪种情况更常见?哪种在快速更新修复漏洞方面表现更佳?是随机静态链接的上游打包应用,还是Linux发行版?我认为是发行版。
但假设Linux发行版表现平平。假设有100个上游打包的静态链接应用,以及100个使用发行版共享库的应用…
50个上游应用会在发行版之前更新,50个则在之后更新——且存在长尾效应。因此,即便发行版在推送安全更新方面表现欠佳,静态链接方案仍会让你长期面临大量存在漏洞的应用程序。需注意:发行版完全能够利用现有依赖信息(如BuildRequires等)重建静态链接二进制文件——动态链接与静态链接的本质区别在于:要部署修复版本所需的自动化工作量差异,而非哪种方式“更安全”。
我的观点是:即便发行版在更新共享库方面表现平平(即仅达平均水平),只要系统应用程序使用共享库,它们仍能在平均且可控的时间内普遍获得修复。而对于采用静态链接的应用程序,当应用数量达到一定规模时,系统将长期存在安全漏洞未修复的状态,且修复周期无法预估。
我不认为应用程序选择比发行版更严格的构建选项存在必然性。假设不同应用随机采用不同策略,且库文件依赖关系也随机变化,核心结论依然成立:普通Linux发行版能将恶意库导致的应用漏洞暴露期控制在有限且较短的时间窗口内。
> 需注意发行版完全能够利用现有依赖信息(如BuildRequires等)来重构静态链接二进制文件
这只是权宜之计而非设计特性,它将更新转化为大规模重构,不仅需要更多构建资源,还为发行版竞争设置了经济门槛(尽管大型发行商对此不会有太多抱怨)。
然而静态构建真正的致命缺陷在于:它彻底消除了开发者协调组件版本的动力。动态构建时,开发者必须从发行版提供的有限版本中选择;静态构建则无需为此付出努力(这正是开发者痴迷静态构建的原因)。
这种版本不统一的后果是:静态构建不仅极度浪费构建资源,更对维护者能力提出严苛要求。开发者需为每个组件版本单独定制安全补丁(及安全影响分析),这实质上助长了技术债务(以牺牲长期维护为代价换取短期收益)。
FAANG巨头不存在此问题,因其强制开发团队使用统一黄金版本。试想静态构建倡导者会如何看待这样的发行版:它宣称“允许静态构建,但仅限于下列封装版本,因我们无力维护其他版本补丁”。
> 这只是权宜之计而非功能设计,它将更新转化为大规模重编译,不仅消耗更多构建资源,更在发行版竞争中设置了经济门槛(尽管大型发行商对此未必有异议)。
呃,不。除非你针对的是m68k这类架构,否则重建成本很低。以Gentoo为例,在性能尚可的计算机上重建整个系统约需12小时。这还是针对灾难性漏洞——即所有软件都需要更新的情况。
多数情况下,只需更新寥寥数个软件包。
还有个关键问题:升级100个软件包与升级1个软件包的差异,在耗时、网络占用、磁盘空间需求等方面都将产生巨大影响。
但为何如此?我认为这更多是现有打包系统的缺陷,它们的设计本身就缺乏效率。
将更新存储为二进制增量本身并无技术障碍,只是没人愿意为此搭建基础设施。
因为这些方案根本行不通,仅存在于理论层面。像deltarpm这类方案被弃用自有其道理。它们在现实世界中根本行不通,本质上只是为本不该存在的问题设计的权宜之计。
deltarpm确实糟糕,debdiff同样如此。究其根源,DEB/RPM格式本身就存在效率缺陷。
格式感知二进制差异文件能实现极致压缩。RedBend Software曾提供目标感知OTA差异文件,其能将简单二进制补丁压缩至仅剩源代码差异的字节量。该差异工具采用额外指令表示地址偏移,并能从ELF重定位段提取位置信息。
况且如今,很少有人会在意更新文件的大小了。
> > > 依赖关系变更时重建包才是未来趋势。
> > 那未来真是糟透了
> 或者你回到四十多年前我的做法——那时库就是库而已…
你也可以回到UNIX初期的模式,用IPC在小型二进制程序间协作。这里很多人至今仍钟爱shell管道。
我认为这是保持程序精简的良性模式——通过管道/套接字/gRPC/varlink/DBus等IPC机制实现程序间通信。
这才是我心目中的美好未来…
但每个Unix工具都需要解析命令行选项,对吧?我们该在每个工具里手动实现解析器,还是共享库代码?若共享代码,该动态链接还是静态链接?
此刻你应该意识到,你只是重新陈述了问题,并未解决它。
如今服务器软件常以容器形式发布,而容器对动态链接的收益有限。事实上,静态链接因部署便捷性,在服务器领域常被视为优势。
> 如今服务器软件常以容器形式发布,而容器对动态链接的依赖性并不高。
当然这说法并不完全准确——红帽等公司投入大量开发资源,为事后消除容器冗余而构建极其复杂的解决方案,恰恰证明了这一点。毕竟整个Docker体系在扩展到少量实例之外时,根本无法真正实现可扩展性。存储、内存和加载时间成本因严重冗余而飙升。
若此论成立,Kubernetes 诞生时便已注定失败。即便在需要千兆字节 OCI 层的极端场景下,每台服务器的增量成本也仅约 100 美元,这可能还不到新购 1U 服务器的 1%。
在我日常工作中,我已将多数集群的节点 pod 限制从默认的 110 提升至 250,因为节点经常触发 pod 限制,但实际能轻松承载更多负载。
所以拥有百万台服务器时,仅因 Kubernetes 设计缺陷就要额外花费一亿美元?听起来很合理
若拥有百万台服务器,你为非服务器相关的开支投入的资金远超1亿美元。光是建设许可费就可能更高,这毫不令人意外。
…然而,大量资金正投入到解决这个问题的各种方案开发中。真有意思!
其实没什么好惊讶的。这纯粹是优先级问题:在拥有百万台服务器的集群中升级某个共享库,可能导致整个数据中心瘫痪,其代价远超1亿美元——所以你安装Kubernetes来规避风险。但当然, 1亿美元终究是1亿美元——若能 不 暴露于发行版流沙引发的不稳定风险,就能省下这笔钱,自然会这么做。
没错,毕竟众所周知Kubernetes是凭空运行的,绝对不会陷入“发行版流沙”。它也永远不需要更新,更不会出任何故障。
> 没错,毕竟众所周知Kubernetes是凭空运行的
这话倒接近事实。甚至有项目尝试让K8s作为PID1运行。多数部署不会走得_那么_极端,通常仅采用Alpine这类极简系统。
让kubelet使用systemd管理切片存在充分理由,因此Alpine可能并非主流宿主操作系统。但它作为OCI基础层却极其流行。
我认为资金缺失的根本原因在于:当移除所有内联优化并禁止跨库使用泛型时,性能和语言能力必然受损。最终结果很可能重蹈C++的覆辙——只能“祈祷代码能正常运行”,而对于Rust这类比C++更依赖泛型和优化(如结构体字段重排)的语言,这种方案的可靠性将更低。
资金缺口在于:既无可能从中获益的利益相关方, 也 无人手头宽裕。
> 但在Rust这类比C++更依赖泛型与优化(如结构体字段重排)的语言中,其可靠性将更低。
而且无法将接口声明为“extern”,这意味着任何通过该接口传递的内容都无法进行可能破坏外部应用程序的优化操作——毕竟外部应用程序并不知道这些变更?
当然,这意味着声明、内联定义和泛型之间需要严格分离,但这难道不是件好事吗?
我明白将泛型转为具体类型可能有些棘手,但通过外部定义为每个需要转型的泛型添加虚拟调用是否可行?
这与“unsafe”机制类似,可将定义文件使用一致性的责任转移给程序员,同时尽可能采用自动化检测机制。
祝好,
Wol
这完全是“C语言不带电池”的观点。例如多数Go程序使用核心标志包,根本不需要运行时依赖来解析参数。
> 这完全是“C语言不带电池”的观点。
> 例如多数Go程序使用核心标志包,解析参数时无需运行时依赖。
而这恰恰是“只看到语言光鲜抽象层”的局限性观点。
所谓“核心标志包”究竟是什么?它本质上是库。既然是库,它存在于何处?需动态链接还是静态链接?原问题仍未解答。
这意味着flags库是Go语言内置组件,始终可用。鉴于解析命令行参数极为常见,将其纳入语言核心实属理所当然。Rust的对应实现是std::env::args。
我已接受这样一个现实:编译参数化多态语言(如Rust和C++)本质上排除了强ABI的可能性。为避免将所有内容塞进盒子并使用虚拟调度处理所有操作,必须使用具体类型生成代码函数。
因此,要么选择负责任的语言设计(实现跨接口的实际类型检查),要么采用共享库方案。我尚未见到兼顾两者的方案。虽然令人遗憾,但在必须抉择时,我清楚自己愿意接受哪条路。
现阶段我认为,任何使用Rust的包都应触发其反向依赖的重新构建,至少在政策指导我们如何规避此问题之前应如此。
那么应该存在一个不含参数化多态的Rust子集。
这并非替代C代码的必要条件。
但没人会使用它。需要重复编写的代码量太大了。
这被称为“extern C”。
基本上能实现C语言的绝大多数功能,包括动态链接。
只要明确理解:库的多态部分存在于调用方二进制文件中而非本库二进制文件中,多态性完全可行。头文件中定义的常量、结构布局等同理。
> 多态性完全可行,但需明确:这意味着库的多态部分存在于调用方二进制文件中,而非本方二进制文件。
> 选项与结果在API中可完全单态化
确实,你最终可能形成v1、v2、v3等稳定ABI版本:v1代表明年我们认为足够好的版本, v2则是十年后积累所有细微改进的版本,由下游用户决定何时值得迁移至新ABI版本并打破旧二进制兼容性——甚至可提供稳定的ABI v1兼容层,该层使用稳定的ABI v5代码实现功能,并采取必要措施确保兼容性(如复制数据结构等)。
> 但这需要编译器团队 必须 做出承诺。若编译器团队拒绝稳定化ABI(用稳定的ABI版本依赖替代编译器版本依赖),上述方案均无法实现。
我的观点是:提供稳定ABI库的开发者必须明确自身实际提供的内容——哪些部分必须保持稳定(包括因嵌入用户代码而需稳定的情况,例如用户代码知晓这是轻量指针,故不可改为重量指针;或用户代码知晓该数据结构大小为108字节,故不可更改), 哪些部分可安全变更(例如用户代码在此处调用库函数,则可修改实现)。
CrABI
我需要同时实现CrABI,既要确保编译器能提供稳定的ABI,又要能清晰标识哪些内容属于ABI。这样未来版本的cargo-semver-checks 不仅能提示我违反了 API 稳定性承诺(由我决定是否提升主版本号或修复 API),还能指出我已破坏 ABI 稳定性承诺。
正如我无法在C语言中随意使用某个头文件版本,却指望它能与某个随机共享库版本兼容
我明白你的提议,但不理解 为何 要这么做。
我以为Java会从二进制ABI中省略参数化类型信息。据我所知,链接jar文件不会检测到不兼容变更——我需要实际测试才能确认。所以…除非我理解有误,否则你可能在讨论与我稍有不同的观点。
> 我以为Java会从二进制ABI中省略参数化类型信息。
并非如此。类型信息可通过反射获取,只是在字节码解释(或JIT编译)阶段被忽略了。
这种说法并不完全准确。准确地说,要实现得当需要付出极其高昂的代价。
苹果为Swift支付了支持成本,使平台应用能受益于基础操作系统库更新而无需重新编译。
Rust和Go分别缺乏资金或意愿实现此功能。
建议了解Swift在Linux平台的现有能力:可加载带类型的库,也可加载带容器的库并高效实例化该类型的容器,全程使用动态库和稳定的ABI。这虽是编译器的魔法,但并非不可能。
我认为从长远看我们都难逃厄运(试想若GTK和Qt全用Rust编写,每次微小更新都需重建整个世界)。这种模式缺乏可扩展性,转向Rust或类似语言的趋势终将受挫。
要么有人挺身承担重任解决此问题,要么发行版终将陷入停滞。
回复自己。抱歉未将此内容置顶,但值得一读:“Swift如何实现Rust未能达成的动态链接”:https://faultlore.com/blah/swift-abi/
我认为这基本符合keithp所说的“避免将所有内容塞进盒子并使用虚拟调度处理所有操作”的初衷。我理解他的评论明确指出了“要么选择(强ABI+昂贵运行时),要么选择(有限动态库ABI+通过参数化类型在编译时具体化实现快速运行时)”的二元对立。
Swift实现ABI稳定性的部分机制,是在特定场景下隐式进行堆分配。我认为Rust不允许这种做法——尤其Rust甚至不强制要求堆的存在。
这可以通过声明该特性为“仅限标准库”来解决。
这不仅关乎资金支持,我们对此有着强烈的实践意愿。更涉及设计层面——Swift的设计虽是绝佳的灵感来源,但其诸多方面并不完全适用于Rust。
不过我们正在推进这项工作。
坦白说,若Qt是Rust库,其体积也会小得多——它不会像当前C++版本那样在库内部重新实现整个依赖生态系统。
> 坦白说,如果Qt是Rust库,体积也会小得多——它不会像当前C++实现那样在库内部重新实现整个依赖生态系统。
从源代码体积来看确实会更小,但二进制体积不会——原因显而易见:它或许无需重新实现依赖生态系统,但这些依赖生成的目标代码仍需存在于某个位置。
当然除非假设存在某种*共享*Rust库,通过链接依赖项的*共享*Rust库来实现。没错。
> 我觉得长远来看我们都难逃厄运(试想如果GTK和Qt都用Rust重写,每次微小更新都需要重建整个世界)。我认为这种模式缺乏可扩展性,转向Rust或类似语言的趋势终将受挫。
GTK拥有语言无关的对象描述(GObject),我认为Rust核心同样能提供类似机制。Qt更依赖C++的魔力,转换难度更大,但Rust核心仍可提供QML接口。
此外,我认为Rust实现会产生更多更小的crates来提供“所有”这些UI库/框架,远超当前数量。因此若项目本身能依赖更小的内存占用,反而可能“获益”。
对此了解不多,我一直好奇为何相对封闭的软件包集合(如构成发行版的那些)不能在集合内部实现传递性单态化。
例如,考虑Debian中Rust包之间的依赖有向图。选取任意非库类型的包(即非librust-foo-dev类型的包)。该包在其依赖项中必然使用了函数和类型的单态化版本,或动态分派机制。记录所有单态化版本,并为每个依赖项建立对应列表。按拓扑顺序遍历图,为所有依赖项构建这些单态化列表。然后将所有库包构建为共享对象,并明确移除所有单态实例(我理解目前编译器尚不支持此操作,但通过生成存根模拟实现应该不难?)。这难道不能至少允许在Debian内部对共享对象进行动态链接和错误修复吗?当然,这仅限于特定编译器版本。使用这些库的非Debian软件状况不会改善(除非恰巧需要相同单态化),但也绝不会恶化。
我肯定遗漏了某些关键点,但非常乐意学习 🙂
ripgrep
> 问题出在更新上。假设你更新ripgrep修复漏洞,而它采用了新的单态化方案,该方案可能又依赖库包内部的新单态化方案,如此循环往复。
这种情况可能发生吗?或者说,其概率是否高于经典C库的漏洞修复导致ABI断裂?
> 若在下游进行修改,这种情况确实很常见——对上游而言看似“添加错误枚举新变体”这类微小变更,都属于新单态化,导致所有了解该枚举布局的组件都需要重新编译。
确实如此。但经典C库的类似变更可能是返回新错误值。这在技术上不破坏ABI,但依赖包必须获取新错误值的知识——这需要比单纯重新编译*更多*的工作。
我想表达的是:这种方法并非总能奏效,但情况并不比经典C库更糟。
例如,若我将错误值截断为8位以适配现有结构体(因所有已知错误值均小于255),而你引入了错误值256,这在C中就会引发问题。在Rust中情况更糟,因为枚举不仅是值,还能携带数据,因此变更可能导致枚举体增大。上游开发者不会在意Debian编译的旧枚举体是72字节,新枚举体变成80字节——尤其当使用新版编译器时,两者都变成64字节。
好吧,确实C和Rust都存在ABI断裂。我只是想说:情况不会更糟吧?
请记住,C语言当前的状态部分源于其设计要求程序员必须正确编写代码,否则将面临未定义行为的风险。因此,C语言程序员在进行安全修复时,往往会全面考虑可能意外破坏他人代码的所有途径;而Rust程序员通常无需如此,因为破坏他人代码的结果是编译器报错,而非未定义行为。
struct packing
> 问题出在更新环节。假设你更新ripgrep修复漏洞,而它采用了新的单态化方案,这个新方案可能又依赖某个库包内部的新单态化方案,如此循环往复。
或者回溯到我曾接触的旧版FORTRAN库。链接器会自动引入所有已知的必需模块(若存在递归依赖,则需多次链接同一库以获取全部模块)。这种机制的副作用是:程序不需要的函数最终不会进入可执行文件。
于是我们有了某种花哨的更新工具(抱歉),它会将两个表面相同的库进行处理,将所有不同模块合并成新的更新版库。若在两个前身库中发现相同模块,它需要检查并强制执行外部定义完全一致的规则,然后选择引用编号最新的版本(或许定义为最近修改日期——我认为这可能是个棘手的问题?)。或者直接用原始库更新未曾存在的新模块。
祝好,
Wol
现实情况是安全修复的责任正从发行版转移至上游项目。共享库不再由发行版层级更新,而是由上游项目发布新版本并提升依赖项版本号。对多数上游项目而言这不成问题——当依赖项修复安全漏洞时,机器人通常会自动发起拉取/合并请求。
要使新模式适用于发行版,关键在于建立标准化变更日志数据,供发行版定期轮询以获取安全更新。
但实际情况并非如此。
发行版并不在意上游锁定的依赖关系。
我认为这没问题,只要它们始终选择兼容的最新版本号。
在 crates.io 生态系统中,这种做法通常效果极佳,仅有极少数例外。
但发行版常试图使用比上游锁定版本更旧的依赖,这在我看来不可取。
我承认发行版会覆盖 Cargo.lock 文件,但不同意这种行为是设计特性。修改单个依赖版本可能导致传递性依赖变更,而我更希望运行的是通过上游项目 CI 管道验证的精确软件包版本。
试想一个发行版中所有程序都静态链接的世界。试想向所有用户分发补丁的后勤成本、二进制文件的内存消耗、程序加载时间、可配置性等问题…服务器端或许影响不大,但桌面端绝对会感受到。
这有什么复杂的?重新编译后直接分发即可。顶多会迫使你投入更高效的打包系统。修改20字节代码时,没必要传输150MB数据。
Android的OTA系统更新就是这么做的。
我其实是在指出静态链接带来的*问题*,而非提倡这种做法。
静态链接程序在容器环境中是优解,但不能简单套用到Linux发行版。问题性质或规模的细微变化,就可能需要截然不同的解决方案。人们常犯的典型错误是:对某项技术或方法过于热衷,就想把它当作万能钉子钉进所有场景。在容器环境中,用Go或Rust编写的静态链接程序确实合理——其优势显著而缺点在该场景下微不足道。但这绝非适用于Linux发行版中所有程序的良策。
> 我实际在指出全静态链接的*问题*,而非提倡这种做法。
但这真的算问题吗?补丁更新时的二进制差异会抵消共享库的优势。
> 静态链接程序在容器中是好方案,并不意味着能推广到Linux发行版。
但或许可行?我曾尝试过全静态发行版(https://github.com/oasislinux/oasis),客观体验确实比常规Debian更佳。
我完全不认为共享库值得如此麻烦。
你的测试是否包含KDE或GNOME?
这部分我认为永远不可能实现,因为支持这些桌面的库体量庞大。
没有,它使用了自带的显示管理器。响应相当灵敏但功能有限。
我认为如今图形库问题可通过将UI迁移至Electron类壳层解决,采用共享浏览器层架构。自定义原生代码可通过IPC调用。理论上这能实现更深度优化,例如预初始化分叉的壳层——无需等待加载即可进行后续分叉(类似Android的'zygote'进程)。
顺便一提,浏览器不必局限于Chromium,Servo在此场景下会是绝佳选择。
据我所知,Ubuntu可选支持通过zsync高效处理发行版变更。我使用的某些非发行版软件也采用此机制进行自我更新。https://zsync.moria.org.uk/ 相信还有其他类似实现方案。
因此开源领域已存在高效实现该功能的技术,只需将其更深度集成到发行版软件分发工具中。
我认为150MiB甚至1.5GiB的软件包更新体积也并非不可接受。这些大小相当于流式传输几分钟的4K视频。
>二进制文件内存消耗
微不足道
>程序加载时间
静态链接二进制文件可能更快。
>可配置性
什么?
关于内存与复用,我曾设计过一个极其简单的C链接器/加载器,它会静态链接所有程序并在加载时共享相同函数(通过计算函数内所有二进制代码及其调用的其他函数的哈希值,并将结果添加到对象存储中)。
理论上在基础场景下,当二进制文件使用相同静态库重新编译时,可实现动态库去重效果,同时兼容内联处理和版本控制(仅损失去重功能)。
我怀疑这种方案在生产环境中(程序首次加载时)难以稳定运行,后来因处理边界情况而搁置,但或许值得重新研究。
若考虑到每台机器仅运行一个进程的情况并不常见,静态链接确实会导致加载时间和内存占用增加。
您安装的程序是否共享动态库?
>静态链接会导致加载时间[..]增加
为何会出现这种情况?
二十年前处理器性能低下时,我们曾通过预链接机制规避运行时动态链接开销,显著缩短启动时间。
如今动态链接的运行时开销是否已降至近乎为零?我承认其开销确有降低,已不明显。但动态链接真的比静态链接更快吗?这怎么可能?
或许是指共享库的页面更可能已驻留内存,从而无需从磁盘调入?对于libc这类广泛使用的库确实如此,但对仅被单个或少数应用调用的长尾库则不然。
动态库体积庞大,因为它们必须包含全部内容而非仅限应用所需部分。
且这些内容分散在文件系统中,导致频繁查找和寻址操作。
我怀疑这种方式平均而言不可能更快。
或许对小型应用有效——当除主二进制文件外的所有内容都已驻留页面缓存时。
但普遍意义上我对此存疑。
是否有实际案例数据表明Rust因静态链接导致启动变慢?
uutil工具是否仅因静态链接就比gnu核心工具集更慢?
况且libc是动态链接到Rust程序的。
在当今极快CPU和SSD的时代,实际应用中动态链接与静态链接都不会造成明显延迟。
正是如此——你很可能只在内存中加载进程可执行文件,其所有依赖项早已被分页加载。在配备高速存储的x86桌面系统上你可能不会察觉,但在资源紧张且配备劣质eMMC的ARM设备上绝对能明显感受到。即便在x86平台,当你使用的资源无法分配给付费客户时(例如虚拟化主机),这种差异同样显著。此时运行大量静态链接的庞大二进制文件及其额外占用的内存,实际上会造成经济损失。
> 每台机器仅运行1个进程绝非常见场景。
我认为“每台机器运行少量进程”才是最普遍的用例,因为BusyBox系统正是如此。这类系统数量可能仅次于Android,已超越其他所有Linux发行版。
考虑到多数计算机属于单用户环境,且用户通常全屏运行单个应用,我认为从用户视角看,实际操作中几乎所有计算机都只运行一个应用。
此致,
Wol
> 可忽略不计
这难道不取决于使用场景吗?在服务器环境中可能无关紧要,但在桌面环境中我不确定。
> 程序加载时间
整体上会产生更多重大页面错误。
> 什么?
能够动态替换实现方案,针对具体使用场景进行优化。
这些原因以及我提到的其他因素,都是在多数系统采用静态链接时代人们的观察结果,最终促使操作系统领域转向支持动态链接库(DSOs)。
不是有个叫“stali”的发行版试图实现这个吗?我记得他们宣称能生成“更小”的二进制文件,因为可以剔除无人使用的库组件。
没错,试想一个由数百个组件构成的项目,它们之间没有稳定的ABI接口,甚至还用动态加载来掩盖这种混乱。这简直是版本管理的噩梦——你无法精确更新单个组件而不必重新编译所有内容。
听起来是个设计精妙的方案——当然前提是大部分代码共享,且数百个组件足够小巧,从而最大限度降低内存占用、磁盘空间和加载时间。事实上,如今所有优质Linux发行版基本都采用这种架构:大量互不相关的独立程序共享一套公共动态库子集。
但如何确保“组件A”在运行时“库B”被替换后仍能正常工作?特别是对于可能长期运行的守护进程。
将所有内容预编译到“组件A”中才是更优解,这样它就无需关注磁盘上的任何变化。
现有打包方案真有这么大的问题吗?库B应该仍驻留在内存中,因此组件A会持续运行。如果组件A与新版库B不兼容,那么组件A当然也需要更新并重启。
原子分发通过在后台创建新的文件系统映像来处理此问题,用户启动后即可进入更新后的系统。
根据我在桌面系统(Gentoo)和各类服务器(Debian与RHEL)上的实际经验,因依赖项更新导致的崩溃和程序异常行为屡见不鲜,我每周至少会遇到1-2次。诚然,问题根源未必总是库文件本身,但往往是更新时未驻留内存的文件——库文件误判其可加载,最终导致相同后果。
据我观察,Gentoo在重新编译需更新共享库的软件包方面表现出色。:shrug: 或许你该调整升级模式?:)
例如我始终遵循以下升级命令:
emerge -uDNv –with-bdeps y system world –keep-going –jobs –load-average 8
这种情况非常罕见,否则Debian早就被错误报告淹没了。
> 但如何确保在“组件A”运行期间替换“库B”时,组件A仍能正常工作?
无论是替换组件A还是库B,你都必须始终将新版本写入临时文件,再重命名覆盖原文件。这样旧inode仍作为mmap映射代码的来源存在,重启A时就能正常加载。直接覆盖原文件会导致段错误。
奇怪的是,包括Debian在内的所有发行版都没有内置核心组件来检查升级后哪些组件需要重启。我们多年来一直使用needrestart解决这个问题,但似乎没有发行版试图解决这个难题。
Fedora服务器版会安装Cockpit,它使用needrestart。不过它仍需等待管理员手动执行重启。
它真的提供了吗?
https://tracker.debian.org/pkg/needrestart
无济于事。“组件A”的二进制文件在替换时可能尚未加载该库。
这并非理论推测,而是新版systemd的行为机制——它通过dlopen()动态加载库文件。
C++与Rust的核心差异在于:前者具备稳定的ABI接口,而后者则缺乏此特性。当然,即使使用Rust,也可以通过C-ABI在共享库间暴露接口,但此时调用这类C接口的Rust代码必须使用unsafe修饰符——即便实现本身完全安全。而C++若避免使用模板,则可通过类接口跨共享库边界调用支持虚函数的函数。
C++并无标准ABI规范,尽管厂商会提供一定程度的支持。
> 前者拥有稳定的ABI
除非它不稳定。
ARMv5 ABI在GCC 7之后发生变更(我们都爱用-Wno-psabi选项)。
C++11同样以多种方式破坏了ABI。参见GCC 5与libstdc++版本灾难。`_GLIBCXX_USE_CXX11_ABI`堪称救星。
GCC 11因std::span导致与GCC 10的ABI不兼容。
jmp_buf在glibc 2.19后于s390架构具有不同ABI。
此类案例不胜枚举。
诚然,C++ 的 ABI 保证略优于 Rust,但主要归因于其数十年来的广泛应用——人们历经多年 ABI 问题攻关后,才形成这种事实标准。
事实上,我尚未见过任何标准化 C++ ABI 规范。
正如其他人指出的,使用C++头文件时仍需解决宏和模板的问题。
若在包含时定义相同的宏,C语言堪称最接近稳定ABI的存在。
你也可以编写导出C混淆函数的Rust程序,这同样能顺利生成共享库。但这已是当前的最佳方案。
我猜未来压力足够大时,Rust终将标准化类似ABI的规范,但单态化的广泛应用使此事极其棘手。C++早已因C++98臭名昭著的“extern template”特性饱受困扰,如今C++模块又添新患——这些特性在标准化多年后仍大多无法正常运作。
补充一点,我们在工作中发现苹果编译器在两个次要版本间存在微妙的C++ ABI断裂(我记得是14.0.0和14.0.3之间)。
以苹果当前的记录来看,真正值得关注的是XCode发布版本没有引入新漏洞。微妙的ABI断裂已是最低预期。
我猜在某个节点,压力会大到促使Rust标准化类似ABI的东西
如果最后一条消息已逾一年,这真的算“进行中”吗?
有道理。
当然,即使使用Rust,也可以通过C-ABI在共享库间暴露功能,但此时调用这类C接口的Rust代码必须使用unsafe修饰符,即便实现本身完全安全也是如此。
> 若发行版希望生态系统更友好,就必须投入大量工作来实现。
凭什么?当年Java软件也打着开发者友好的旗号,Java开发者同样拒绝投入组件共享和稳定ABI的机制,对发行版最佳实践的敌意更是四处宣扬。
二十年后技术债务到期,众人争相跳离Java这艘船。事实证明,重构那些由供应商提供、被分叉且陈旧的代码堆——由于长期缺乏ABI分离机制导致边界模糊——是极其痛苦的。问题可以长期被忽视,但终将以更猛烈的姿态卷土重来。
> 二十年后技术债务到期,众人争相跳离Java这艘船。
我认为关键在于:Java最初为实现“写一次,到处运行”而设计API,疏于紧密的平台集成,加之早期采用限制性许可协议,导致其在桌面Linux领域存在感薄弱。
…正如C#因完美适配Windows专属API而成为Windows开发首选,C、Perl、Python等语言在Linux环境中同样因契合POSIX及Linux专属API而广受欢迎。
Web服务器、专业应用、安卓程序…这些在Linux桌面都鲜少引起关注。我能想到的Linux平台Java应用仅有《我的世界》、JDownloader、Azureus、Trang、FreeMind以及我的KryoFlux控制软件。
> 我认为关键在于,Java最初为实现“写一次,到处运行”而设计API,疏于平台深度集成,加之早期采用限制性许可,导致其在桌面Linux领域存在感薄弱。
问题并非“仅此而已”。限制性许可早已解除,开源Java组件资源丰富,掌握Java编程的人才也比比皆是。
真正扼杀Java作为通用语言的,是其无法构建通用组件用于通用软件部署——这源于缺乏ABI强制执行导致的版本不兼容。每个Java项目都使用着堆积如山的特定组件版本,实在令人无暇深究。每个项目都成了封闭的花园,自成组件宇宙,与其他组件宇宙互不相容。
> 导致Java丧失通用语言地位的关键在于缺乏ABI强制执行机制,这使得版本无法趋同。这种缺陷阻碍了通用组件的构建,进而无法形成可部署于通用发行版的通用软件。
> 他们为何要这么做?当年Java软件同样打着开发者友好的旗号,而Java开发者们却拒绝投入资源构建组件共享机制并稳定ABI。
什么?!Java可是稳定API/ABI的最佳典范之一。只要不使用“Unsafe”这类特性,我能在2001年为JVM 1.3编写的软件在最新JVM上运行!连SWING和AWT至今仍可用!
这绝对助推了Java的普及。银行和企业机构_极度青睐_无需每年更新代码的特性。
stabby
> C++支持共享库,Rust理论上也能实现。
真能吗?虽然多数情况下可行,但这靠的是运气而非设计。
构建二进制文件时使用的头文件包含大量会被反编译进二进制文件的代码(例如所有模板)。若新版本库修改了这些内容,你就会陷入调试崩溃的泥潭——因为旧版反编译进二进制文件的代码突然无法使用新版库中反编译的符号。
多数发行版在依赖项变更时会重新构建二进制文件,这自有其道理。
是的,Rust本可采用相同做法。但Rust拥有不同的开发文化,因此不会这么做。
C语言同样如此,但其ABI接口更小。在某些依赖版本的点发布中,
enum SecurityMode {LEGACY, SECURE, DISABLED};有时会意外切换为enum SecurityMode {LEGACY, SECURE, SUPER_STRICT, DISABLED};。某些库拥有海量头文件,远超人类可审查的范围。虽然API兼容,但未重新编译的包会悄然引入严重的安全退化。所幸这是工具链问题,C/C++领域已有`libabigail`工具。因此大多数发行版若集成这些工具,便具备追踪需重编译内容的能力。模板代码会加剧问题,但这并非C++独有的困境。我不熟悉Rust用于追踪ABI破坏的工具链,但推测可通过工具化手段解决,而非试图跨版本维持共享库ABI的稳定性。
> 我推测可通过工具化手段解决,而非试图跨版本维持共享库ABI的稳定性。
考虑到cargo-semver-checks作者在其博客中持续记录的API层级边界案例数量,我怀疑在ABI层级根本不可能存在满足Rust高标准正确性的方案。
这种机制在C和C++中能勉强运作,因为它们对“正常工作”的标准似乎低得多。
采用Swift方案(大致思路:以牺牲实现速度为代价,使
dyn Trait具备与impl Trait同等的特性)后,ABI稳定性检查与API稳定性检查将无实质差异。> 大多数发行版在依赖项变更时重新构建二进制文件是有原因的。
声称这在Debian中不可持续的说法同样令人费解,毕竟众多发行版(包括NixOS这类非商业发行版)都成功实现了这一机制。
> 认为这在Debian中不可持续的说法同样令人费解,毕竟众多发行版(包括NixOS这类非商业发行版)都成功实现了这一目标。
> 毕竟众多发行版(包括NixOS这类非商业发行版)都成功实现了这一目标。
NixOS之所以能实现,是因为商业赞助商向其持续投入巨额资金用于构建持续集成环境和二进制缓存。
其他所谓“非商业”发行版亦是如此——细究之下,你会发现它们的基础设施均有商业赞助商提供补贴。
顺便一提,Gentoo同样存在。而且它似乎没有太多企业赞助商。
据我所知,Rust已支持共享库(dylib),不过其ABI尚未稳定。
https://doc.rust-lang.org/reference/linkage.html
但问题在于Rust生态的文化倾向:多数人偏好静态链接,排斥发行版,很可能拒绝为每个包引入dylib的补丁。
其应用场景存在局限。
它无法提供稳定的ABI——只能用于在多个相关二进制文件间共享代码,类似私有.so库。
它还存在安全隐患且会移除类型检查——这从根本上违背了Rust的设计初衷。
若仅需跨多个相关二进制文件共享,更优方案难道不是采用多调用二进制文件吗?据我理解uutils就采用了这种方式(C语言领域可参考busybox)。
我认为这是为嵌入式场景设计的:系统可一次性更新完整镜像以保持一致性,同时根据上下文动态增删模块。
(例如可类比me_cleaner文档所述:英特尔UEFI由模块化块构成,固件以单一二进制块更新,但不同主板型号会包含或缺失特定模块…在英特尔修订签名机制禁止此操作前,me_cleaner会删除部分模块。)
多调用二进制文件是可怕的权宜之计。只要存在任何安全策略,执行一条命令就意味着能运行所有命令。
若安全机制依赖于通过名称限制可执行二进制文件,那么这种机制注定失败。
好吧,但回到libtiff的例子,我们可能不希望一个多调用二进制文件包含所有需要处理TIFF的应用程序。
我真心希望类似微软OLE的方案能更被接受——本质上是在不同进程处理的TIFF对象上调用一组方法。
记得Mill Computing( vaporware)的ISA架构支持高效跨上下文函数调用。
https://millcomputing.com/topic/inter-process-communication/
详见C++模板对库ABI的影响,其核心在于模板需要为每个应用类型定制并内联代码。
> sh4 端口从未获得官方支持,其他端口自 Debian 6.0 起也均未获支持。
是否有明确政策说明Debian放弃某架构作为官方发布架构的后果?
放弃发布架构似乎并未阻止这些架构参与关于“官方发布架构可操作范围”的讨论。
从政策层面看,将某架构移出官方发布架构集意味着什么?