【问题标题】:How to prevent the generating of __IO_putc?如何防止产生__IO_putc?
【发布时间】:2015-11-22 16:31:06
【问题描述】:

当我objdump我的a.out时,我发现__IO_putc被使用了。

我知道当printf 的输入很简单时,gcc 会使用putc 替换printf。但是为什么 gcc 将我的 putc 替换为 __IO_putc 呢?我可以使用-U_FORTYFY(禁用__printf_chk)或--fno-stack-protector(禁用__stack_chk_fail)之类的命令行来防止这种替换吗?

【问题讨论】:

  • 有什么理由不想在二进制文件中使用这些符号?
  • __IO_putc 是一个 glibc 内部函数,但是当我想做交叉编译或二进制翻译之类的事情时,这个函数会很麻烦。
  • 您是在问如何防止“将printf 替换为putc”?
  • 请详细解释为什么您不希望在答案中使用它。
  • @hwliu 您的二进制文件无论如何都依赖于 glibc 的 ABI,您生成的代码嵌入了大量特定于 glibc 的结构和类型,因此只关心特定的 glibc 符号可能对你帮助不大

标签: c++ c linux glibc


【解决方案1】:

为什么 gcc 将我的 putc 替换为 __IO_putc

在你的/usr/include/stdio.h,你会发现这一行:

#define putc(_ch, _fp) _IO_putc (_ch, _fp)

我可以使用一些命令行来防止这种替换

您可以将#undef putc 放在#include <stdio.h> 之后的源代码中,或者在每个调用点将函数名称括在括号中,如下所示:

(putc)('a', stdout);

一般来说,你不应该弄乱像这样的标准函数。

【讨论】:

  • 实际上,#undef putc 是完全合法的(参见 C 标准的第 7.1.4 节)。 重新定义它不会。我更喜欢使用括号来避免扩展,因为它没有全局效果,但在这种情况下,这可能无关紧要,标准明确允许这两个选项。
  • @chqrlie 谢谢。固定。
【解决方案2】:

您始终可以通过将名称括起来来避免扩展类似函数的宏:

(putc)('c', outfile);

这是 C 标准在第 7.1.4 节(“库函数的使用”)中明确允许的(已添加重点):

头文件中声明的任何函数都可以额外实现为头文件中定义的类函数宏,因此如果在包含头文件时显式声明库函数,则可以使用下面显示的技术之一来确保声明不受此类宏的影响。 函数的任何宏定义都可以通过封闭在本地抑制 括号中的函数名称,因为该名称后面没有表示宏函数名称扩展的左括号。

在与 gcc 捆绑的标准库实现的特定情况下,您可能会发现使用 fputc 而不是 putc 会导致实际调用 fputc。 (该标准要求fputc 的底层功能与putc 相同;它允许putc 的宏实现——如果有的话——多次评估第二个参数。)


对于它的价值,我使用 gcc 5.2.0 进行了检查,似乎fprintf 被转换为对fputc 的调用,而不是putc。对putc 的显式调用确实被宏扩展为对__IO_putc 的调用,但可以如上所述避免。

示例程序:

#include <stdio.h>
int main() {
  FILE* outfile = stdout;
  fprintf(outfile, "c");
  putc('c', outfile);
  (putc)('c', outfile);
  return 0;
}

生成的代码(添加了 cmets):

main:
    pushq   %rbx
    movq    stdout(%rip), %rbx
    movl    $99, %edi
    movq    %rbx, %rsi
    call    fputc          # fprintf(outfile, "c")
    movq    %rbx, %rsi
    movl    $99, %edi
    call    _IO_putc       # putc('c', outfile)
    movq    %rbx, %rsi
    movl    $99, %edi
    call    putc           # (putc)('c', outfile);
    xorl    %eax, %eax
    popq    %rbx
    ret

【讨论】:

    【解决方案3】:

    putc 在预处理阶段被翻译成__IO_putc,因为宏从&lt;stdio.h&gt; 扩展而来。您确实可以尝试通过使用fputc 或在#include &lt;stdio.h&gt; 之后破解头文件或#undefining putc 来防止这种情况发生,但这不是正确的解决方法,如果它有效,您很幸运,还有更多问题稍后会出现,因为您依赖更多的库功能。

    gcc 配置为针对给定目标进行编译,并结合了配置选项和一组头文件和库。重要的是要了解头文件本质上链接到每个特定版本的相应库。您不能只使用 generic &lt;stdio.h&gt; 头文件进行编译,并期望与另一个目标的 C 库链接。交叉编译需要正确配置完整的工具链。如果您有要链接到的特定版本的 C 库,请使用随附的头文件。

    OTOH,如果您只是希望源文件使用您的标准 I/O 功能版本,那么首先阻止扩展并与您的库链接就可以完成这项工作。另一种方法是重新定义 putc 和您要替换的所有其他函数,替换所有对替换调用的调用,但是您仍然在弄乱标准库标识符,所以任何事情都可能发生。

    【讨论】:

    • 重新定义标准库名称是未定义的行为。 §7.1.3。只是说。
    • @rici:有趣!实际上,根据 C11 7.1.3,#undefining 和 #defineing putc 都被视为调用未定义的行为。我编辑了我的答案,不鼓励这种扭曲。
    • 当我读到它时,7.1.3 表示库名称的 #undef 会产生未定义的行为,除非在 7.1.4 中明确允许,在本例中是这样。
    • @rici: 在 n1570, 7.1.3, 如果程序删除(使用#undef)上面列出的第一组标识符的任何宏定义,则行为未定义。我>。我没有看到任何排除在这个原则之外。第 7.1.4 节确实为这种情况记录了#undef,这与前一段相矛盾并且没有明确的例外。这看起来像是标准中的一个错误。除非我正在查看草稿并且在最终版本中更改了语言。
    • @rici:我明白了,第一个goup不是指第一段的枚举,而只是枚举的第一项。你是对的。我修改了答案。
    【解决方案4】:

    与我有些不同的方法:

    如果您用自己的库版本替换标准库函数。您可能想编写自己的“stdio.h”版本,而不是使用编译器附带的版本。或者,如果您使用的是编译器提供的那个,您可能想要实现它实际使用的低级 IO 函数,而不是替换高级函数(换句话说,弄清楚 __IO_putc 实际做了什么,并替换任何内容,等等)。

    通过在代码中执行#undef 或类似操作来“弄乱”标准标头的内容可能会在其他地方插入新问题,并且如果您想导入其他人代码的大型项目,很可能会导致问题.相反,如果您有自己的“stdio.h”版本,那根本不会首先执行#define putc ...,那么您就没有需要稍后解决的问题——只要您做所有事情外部产品需要,它就可以工作。

    重要的是要了解“stdio.h”及其朋友与某个 C 运行时库配对(在大多数情况下与某个编译器组合),所以如果您要替换 C 运行时库,那么您还应该将相应的头文件替换为适用于库(以及您选择的编译器)的头文件

    【讨论】:

    • 从技术上讲,您需要替换 所有 使用 FILE 结构的库函数,或者至少重新编译您没有替换为新版本的库函数。两者都需要精通该语言及其库。
    • 是的,除非你真的支持所有这些,包括一个包含它们原型的头文件只是愚蠢的,因为除非平台是 100% 兼容,否则它们将无法工作(在在这种情况下什么都不需要做,这个问题就不存在了)或者它们被实施了。因此,我建议拥有自己的头文件。特别是如果您只支持一个子集!
    • 恐怕这还不够,因为现实生活中的标准库(如 glibc)包含许多使用标准函数以外的 stdio.h 的函数。因此,重新定义它们是没有希望的,并且仅重新定义将链接到图书馆自身的子集可能会产生不一致并带来可怕的后果。 #define使用子集来引用用户定义的包装器实际上风险较小。
    • 不确定我明白你的意思。假设我实现了自己的 printf、putc 和 scanf。拥有一个声明 fprintffgets 和 glibc 提供的其他 140 个函数的头文件将不会有帮助,因为它们在我的平台上不可用。那么各种glibc函数都使用stdio.h解决了什么问题呢?还是您认为 OP 只是想要替换 printfputc,而不是一个其他功能?
    • 不清楚 OP 真正打算做什么。如果他想跟踪对该标准 I/O 函数的调用,则使用一组在 stdio.h 函数之上实现一层的宏编译他的程序比尝试通过链接到特定对象在链接时更改它们更安全文件。
    猜你喜欢
    • 2021-12-16
    • 2012-02-16
    • 1970-01-01
    • 2018-08-24
    • 1970-01-01
    • 2012-03-27
    • 2021-06-06
    • 2021-09-27
    • 2020-04-10
    相关资源
    最近更新 更多