覆盖正在运行的可执行文件或 .so

Ste*_*mit 7 executable write ld

我有一个关于覆盖正在运行的可执行文件或覆盖一个或多个正在运行的程序正在使用的共享库 (.so) 文件的问题。

过去,出于显而易见的原因,覆盖正在运行的可执行文件是行不通的。甚至还有一个特定的 errno 值 ETXTBSY,它涵盖了这种情况。

但现在相当长一段时间,我注意到,当我不小心尝试覆盖正在运行的可执行文件(例如,通过发射了一个构建,其最后一步是cc -o exefile对的exefile,恰好运行),它的工作原理!

所以我的问题是,这是如何工作的,是否在任何地方都有记录,依赖它是否安全?

看起来有人可能已经调整ld以取消链接其输出文件并创建一个新文件,只是为了消除这种情况下的错误。我不太清楚它是一直在这样做,还是仅在需要时才这样做(也就是说,可能是在它尝试覆盖现有文件并遇到 ETXTBSY 之后)。而且我在ld的手册页上没有看到任何提及。(我想知道为什么人们不抱怨ld现在可能会破坏他们的硬链接,或者改变文件所有权,等等。)


附录:这个问题并不是专门关于cc/ ld(尽管这最终成为答案的重要部分);问题真的只是“为什么我再也看不到 ETXTBSY?它仍然是一个错误吗?” 答案是,是的,它仍然是一个错误,只是在实践中很少见。(另请参阅我刚刚发布到我自己的问题的澄清答案。)

Gil*_*il' 7

它取决于内核,并且在某些内核上它可能取决于可执行文件的类型,但我认为如果您尝试打开一个运行可执行文件以进行写入或执行打开以进行写入的文件。文档表明BSD 上一直都是这种情况,但早期 Solaris 上却并非如此后来的版本确实实现了这种保护),这与我的记忆相符。Linux 上一直都是这样,或者至少从 1.0开始就是这样。

\n\n

适用于可执行文件的内容可能适用于动态库,也可能不适用于动态库。覆盖动态库会导致与覆盖可执行文件完全相同的问题:指令将突然从新文件中的相同旧地址加载,而新文件可能具有完全不同的内容。但事实上并非所有地方都是如此。特别是,在 Linux 上,程序调用系统open调用来在底层打开动态库,其标志与任何数据文件相同,并且 Linux 很高兴地允许您重写库文件,即使正在运行的进程可能会从它加载代码任何时候。

\n\n

大多数内核允许在执行时删除和重命名文件,就像它们允许在打开或写入时删除和重命名文件一样。就像打开的文件一样,在执行时被删除的文件只要在使用中,即直到最后一个可执行文件实例退出之前,实际上不会从存储介质中删除。Linux 和 *BSD 允许,但 Solaris 和 HP-UX 不允许。

\n\n

删除文件并写入同名的新文件是完全安全的:要加载的代码与包含该代码的打开(或正在执行)的文件之间的关联通过文件描述符而不是文件名进行。它还有一个额外的好处,即可以通过写入临时文件然后将该文件移动到位(rename调用以原子方式用源文件替换现有目标文件)来自动完成。它比删除然后打开写入要好得多,因为它不会暂时放置无效的、部分写入的可执行文件

\n\n

是否cc覆盖ld其输出文件,或删除它并创建一个新文件,取决于实现。GCC(至少是现代版本)和 Clang 都是这样做的,在这两种情况下,unlink如果目标存在,则调用目标open来创建一个新文件。(我想知道为什么他们不执行 write-to-temp-then-rename 操作。)

\n\n

我不建议依赖这种行为,除非作为一种保护措施,因为它不适用于每个系统(它可能适用于可执行文件的每个现代系统,但不适用于共享库),并且通用工具链不这样做事情以最好的方式进行。在构建脚本中,始终在临时文件下生成文件,然后将它们移动到位,除非您知道底层工具会执行此操作。

\n