【发布时间】:2019-01-08 11:03:01
【问题描述】:
我正在尝试了解printf 在 C 中的工作原理,用于一个简单的案例。我写了以下程序:
#include "stdio.h"
int main(int argc, char const *argv[])
{
printf("Test %s\n", argv[1]);
return 0;
}
在二进制文件上运行objdump 我注意到Test %s\n 位于.rodata
objdump -sj .rodata bin
bin: file format elf64-x86-64
Contents of section .rodata:
08e0 01000200 54657374 2025730a 00 ....Test %s..
所以格式化打印似乎执行了从rodata 到其他地方的额外模式复制。
在使用stare ./bin rr 编译和运行它之后,我注意到在实际写入之前有一个brk 系统调用。所以用
gdb catch syscall brk
gdb catch syscall write
显示在我的情况下,当前中断等于0x555555756000,但随后设置为0x555555777000。当write 出现格式化字符串时
x/s $rsi
0x555555756260: "Test rr\n"
位于“旧”和“新”间断之间。写入发生后程序退出。
问题: 为什么我们分配了这么多页面,为什么在 write syscall 发生后中断没有返回到前一个页面?是否有任何理由使用brk 而不是mmap 进行这种格式化?
【问题讨论】:
-
rodata 中有常量字符串是正常的。
So formatted print seems to perform additional pattern copying from rodata to somewhere else.print 没有理由复制字符串,它无法知道字符串是否为常量,你为什么这么说?所有初始化等都在程序启动时完成 -
printf有它自己的缓冲。它通常是行缓冲的。 -
重新“格式化打印似乎执行从rodata到其他地方的额外模式复制”:是的,到标准输出;-)。
-
@St.Antario:格式字符串永远不会发生变异。
printf逐字节复制到目标,直到它到达第一个%字符,然后它使用那里的任何格式说明符格式化下一个参数并继续。一般来说,在任何语言中几乎不可能通过插入数据来更改字符串。 -
@St.Antario:澄清一下,当您说“复制模式”时,它可能暗示该字符串确实是复制了,然后又进行了更改。但是这个函数的任何实现都必须做的是:1)从常量模式字符串中读取一次一个字符和2)写入这个字符或一些格式化值其他地方我>。这个其他地方是直接到输出还是到中间malloc'ed缓冲区,取决于实现。有many "tiny printf" 实现不分配任何临时缓冲区。