IO FILE fwrite

fwrite

size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);

测试程序

#include<stdio.h>

int main(){
char data[20]="flag{grxer}";
FILE*fp=fopen("./flag1","w");
fwrite(data,1,20,fp);
return 0;
}

/libio/iofwrite.c _IO_fwrite

_IO_size_t
_IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t request = size * count;
_IO_size_t written = 0;
CHECK_FILE (fp, 0);
if (request == 0)
return 0;
_IO_acquire_lock (fp);
if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)
written = _IO_sputn (fp, (const char *) buf, request);
_IO_release_lock (fp);
/* We have written all of the input in case the return value indicates
this or EOF is returned. The latter is a special case where we
simply did not manage to flush the buffer. But the data is in the
buffer and therefore written as far as fwrite is concerned. */
if (written == request || written == EOF)
return count;
else
return written / size;
}

CHECK_FILE检测一下flag的魔数,加锁判断

# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset

# define _IO_fwide(__fp, __mode) \
({ int __result = (__mode); \
if (__result < 0 && ! _IO_fwide_maybe_incompatible) \
{ \
if ((__fp)->_mode == 0) \
/* We know that all we have to do is to set the flag. */ \
(__fp)->_mode = -1; \
__result = (__fp)->_mode; \
} \
else if (__builtin_constant_p (__mode) && (__mode) == 0) \
__result = _IO_fwide_maybe_incompatible ? -1 : (__fp)->_mode; \
else \
__result = _IO_fwide (__fp, __result); \
__result; })

image-20230401120543002

进入_IO_sputn,

#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)

==调用vtable里**__xsputn指向的libio/fileops.c里的_IO_new_file_xsputn**函数,传入的参数为文件流结构体_IO_FILE,数据目标地址,读取总字节数==

_IO_size_t
_IO_new_file_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
const char *s = (const char *) data;
_IO_size_t to_do = n;
int must_flush = 0;
_IO_size_t count = 0;

if (n <= 0)
return 0;
/* This is an optimized implementation.
If the amount to be written straddles a block boundary
(or the filebuf is unbuffered), use sys_write directly. */

/* First figure out how much space is available in the buffer. */
if ((f->_flags & _IO_LINE_BUF) && (f->_flags & _IO_CURRENTLY_PUTTING))
{
count = f->_IO_buf_end - f->_IO_write_ptr;
if (count >= n)
{
const char *p;
for (p = s + n; p > s; )
{
if (*--p == '\n')
{
count = p - s + 1;
must_flush = 1;
break;
}
}
}
}
else if (f->_IO_write_end > f->_IO_write_ptr)
count = f->_IO_write_end - f->_IO_write_ptr; /* Space available. */

/* Then fill the buffer. */
if (count > 0)
{
if (count > to_do)
count = to_do;
#ifdef _LIBC
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
#else
memcpy (f->_IO_write_ptr, s, count);
f->_IO_write_ptr += count;
#endif
s += count;
to_do -= count;
}
if (to_do + must_flush > 0)
{
_IO_size_t block_size, do_write;
/* Next flush the (full) buffer. */
if (_IO_OVERFLOW (f, EOF) == EOF)
/* If nothing else has to be written we must not signal the
caller that everything has been written. */
return to_do == 0 ? EOF : n - to_do;

/* Try to maintain alignment: write a whole number of blocks. */
block_size = f->_IO_buf_end - f->_IO_buf_base;
do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);

if (do_write)
{
count = new_do_write (f, s, do_write);
to_do -= count;
if (count < do_write)
return n - to_do;
}

/* Now write out the remainder. Normally, this will fit in the
buffer, but it's somewhat messier for line-buffered files,
so we let _IO_default_xsputn handle the general case. */
if (to_do)
to_do -= _IO_default_xsputn (f, s+do_write, to_do);
}
return n - to_do;
}
libc_hidden_ver (_IO_new_file_xsputn, _IO_file_xsputn)

刚fopen完此时大部分字段位NULL

image-20230401153712485

前面几个if都不成立,来到 if (to_do + must_flush > 0)

#define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)

==调用虚表中的__overflow,即_IO_new_file_overflow函数,第一参数_IO_FILE文件流结构体,第二个参数 EOF(-1)==

申请并初始化缓冲区,文件流结构体完善

/libio/fileops.c _IO_new_file_overflow

_IO_new_file_overflow (_IO_FILE *f, int ch)
{
if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
{
f->_flags |= _IO_ERR_SEEN;
__set_errno (EBADF);
return EOF;
}
/* If currently reading or no buffer allocated. */
if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
{
/* Allocate a buffer if needed. */
if (f->_IO_write_base == NULL)
{
_IO_doallocbuf (f);
_IO_setg (f, f->_IO_buf_base, f->_IO_buf_base, f->_IO_buf_base);
}
/* Otherwise must be currently reading.
If _IO_read_ptr (and hence also _IO_read_end) is at the buffer end,
logically slide the buffer forwards one block (by setting the
read pointers to all point at the beginning of the block). This
makes room for subsequent output.
Otherwise, set the read pointers to _IO_read_end (leaving that
alone, so it can continue to correspond to the external position). */
if (__glibc_unlikely (_IO_in_backup (f)))
{
size_t nbackup = f->_IO_read_end - f->_IO_read_ptr;
_IO_free_backup_area (f);
f->_IO_read_base -= MIN (nbackup,
f->_IO_read_base - f->_IO_buf_base);
f->_IO_read_ptr = f->_IO_read_base;
}

if (f->_IO_read_ptr == f->_IO_buf_end)
f->_IO_read_end = f->_IO_read_ptr = f->_IO_buf_base;
f->_IO_write_ptr = f->_IO_read_ptr;
f->_IO_write_base = f->_IO_write_ptr;
f->_IO_write_end = f->_IO_buf_end;
f->_IO_read_base = f->_IO_read_ptr = f->_IO_read_end;

f->_flags |= _IO_CURRENTLY_PUTTING;
if (f->_mode <= 0 && f->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
f->_IO_write_end = f->_IO_write_ptr;
}
if (ch == EOF)
return _IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base);
if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
if (_IO_do_flush (f) == EOF)
return EOF;
*f->_IO_write_ptr++ = ch;
if ((f->_flags & _IO_UNBUFFERED)
|| ((f->_flags & _IO_LINE_BUF) && ch == '\n'))
if (_IO_do_write (f, f->_IO_write_base,
f->_IO_write_ptr - f->_IO_write_base) == EOF)
return EOF;
return (unsigned char) ch;
}
libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)

先判断文件流结构体flag是不是有不可写属性返回EOF

检测受否有_IO_CURRENTLY_PUTTING或_IO_write_base是否为空

IO_CURRENTLY_PUTTING是GNU C库中使用的宏,用于表示FILE流当前写入数据的输出缓冲区位置。它的定义如下:

#define IO_CURRENTLY_PUTTING(fp) ((fp)->_IO_write_ptr - (fp)->_IO_write_base)

IO_write_base为空符合条件调用_IO_doallocbuf分配缓冲区,fwrite分析过

==调用虚表中的__doallocate指向的_IO_file_doallocate函数==

image-20230401162158010

接下来的_IO_setg也在fread里分析过_

#define _IO_setg(fp, eb, g, eg)  ((fp)->_IO_read_base = (eb),\
(fp)->_IO_read_ptr = (g), (fp)->_IO_read_end = (eg))
((fp)->_IO_read_base = (fp->_IO_buf_base), (fp)->_IO_read_ptr = (fp->_IO_buf_base), (fp)->_IO_read_end = (fp->_IO_buf_base));

_也就是把结构体里的read相关的3个指针均初始化为IO_buf_base

image-20230401164402279
#define _IO_in_backup(fp) ((fp)->_flags & _IO_IN_BACKUP)

_IO_in_backup这里不成立,来到给fp的write相关指针赋值,并设置_IO_CURRENTLY_PUTTING 属性,用于表示当前正在执行输出操作

image-20230401171509678

根据处理数据大小和缓冲区大小决定往缓冲区里写数据或直接写入文件

ch=EOF(-1),执行_IO_do_write(f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base);

/libio/fileops.c中**_IO_new_do_write**

函数第一个参数_IO_FILE文件流结构体,第二个参数输出缓冲区,第三个参数为0

# define _IO_new_do_write _IO_do_write
_IO_new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
return (to_do == 0
|| (_IO_size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
}

todo==0成立,直接返回0,回到**_IO_new_file_xsputn**函数

if (_IO_OVERFLOW (f, EOF) == EOF)不成立

设置blocksize为申请的缓冲区大小

do_write根据do_write = to_do - (block_size >= 128 ? to_do % block_size : 0);赋值

fwrite写入大小参数小于申请的缓冲区大小,一般最多为4k,本测试为1k

do_write为0

if (to_do)
to_do -= _IO_default_xsputn (f, s+do_write, to_do);

调用libio/genops.c的_IO_default_xsputn第一个参数为文件流结构体,第二个参数为内存数据起始点,第三个为输入大小size*count

image-20230401175400733

_IO_size_t
_IO_default_xsputn (_IO_FILE *f, const void *data, _IO_size_t n)
{
const char *s = (char *) data;
_IO_size_t more = n;
if (more <= 0)
return 0;
for (;;)
{
/* Space available. */
if (f->_IO_write_ptr < f->_IO_write_end)
{
_IO_size_t count = f->_IO_write_end - f->_IO_write_ptr;
if (count > more)
count = more;
if (count > 20)
{
#ifdef _LIBC
f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
#else
memcpy (f->_IO_write_ptr, s, count);
f->_IO_write_ptr += count;
#endif
s += count;
}
else if (count)
{
char *p = f->_IO_write_ptr;
_IO_ssize_t i;
for (i = count; --i >= 0; )
*p++ = *s++;
f->_IO_write_ptr = p;
}
more -= count;
}
if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
break;
more--;
}
return n - more;
}
libc_hidden_def (_IO_default_xsputn)

如果要输入的数据小于缓冲区大小则把输入大小置为输入真实数据大小,否则按照缓冲区大小

  • 一次转移大于20时

    • 采用memcopy,进行传输
  • 一次转移大小小于20时

    • 采用for循环进行赋值

缓冲区不够时会_IO_OVERFLOW刷新缓冲区

image-20230401182422401

fwrite写入大小参数大于等于申请的缓冲区大小,一般最多为4k,本测试为1k

do_write为缓冲区大小的倍数

if (do_write){
count = new_do_write (f, s, do_write);
to_do -= count;
if (count < do_write)
return n - to_do;
}

libio/fileops.c

new_do_write第一个参数为文件流结构体,第二个参数为内存数据起始点,第三个为输入大小do_write

static
_IO_size_t
new_do_write (_IO_FILE *fp, const char *data, _IO_size_t to_do)
{
_IO_size_t count;
if (fp->_flags & _IO_IS_APPENDING)
/* On a system without a proper O_APPEND implementation,
you would need to sys_seek(0, SEEK_END) here, but is
not needed nor desirable for Unix- or Posix-like systems.
Instead, just indicate that offset (before and after) is
unpredictable. */
fp->_offset = _IO_pos_BAD;
else if (fp->_IO_read_end != fp->_IO_write_base)
{
_IO_off64_t new_pos
= _IO_SYSSEEK (fp, fp->_IO_write_base - fp->_IO_read_end, 1);
if (new_pos == _IO_pos_BAD)
return 0;
fp->_offset = new_pos;
}
count = _IO_SYSWRITE (fp, data, to_do);
if (fp->_cur_column && count)
fp->_cur_column = _IO_adjust_column (fp->_cur_column - 1, data, count) + 1;
_IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_buf_base;
fp->_IO_write_end = (fp->_mode <= 0
&& (fp->_flags & (_IO_LINE_BUF | _IO_UNBUFFERED))
? fp->_IO_buf_base : fp->_IO_buf_end);
return count;
}

首先检测_IO_IS_APPENDING标志位它表示文件流的当前位置是否处于文件的末尾,并且文件以”追加模式”打开。

fp->_IO_read_end != fp->_IO_write_base不相等时会_IO_SYSSEEK()

#define _IO_SYSSEEK(FP, OFFSET, MODE) JUMP2 (__seek, FP, OFFSET, MODE)

==调用虚表里的seek对应的__GI__IO_file_seek==

不过条件不成立,直接调用_IO_SYSWRITE

#define _IO_SYSWRITE(FP, DATA, LEN) JUMP2 (__write, FP, DATA, LEN)

==调用了vtable中__write对应的_IO_new_file_write==

libio/fileops.c中**_IO_new_file_write**

第一个参数为文件流结构体,第二个参数为内存数据起始点,第三个为输入大小do_write

_IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n)
{
_IO_ssize_t to_do = n;
while (to_do > 0)
{
_IO_ssize_t count = (__builtin_expect (f->_flags2
& _IO_FLAGS2_NOTCANCEL, 0)
? write_not_cancel (f->_fileno, data, to_do)
: write (f->_fileno, data, to_do));
if (count < 0)
{
f->_flags |= _IO_ERR_SEEN;
break;
}
to_do -= count;
data = (void *) ((char *) data + count);
}
n -= to_do;
if (f->_offset >= 0)
f->_offset += n;
return n;
}

执行系统调用write把数据写入文件,

调用_IO_adjust_column更新文件流结构体中的_cur_column

unsigned
_IO_adjust_column (unsigned start, const char *line, int count)
{
const char *ptr = line + count;
while (ptr > line)
if (*--ptr == '\n')
return line + count - ptr - 1;
return start + count;
}
libc_hidden_def (_IO_adjust_column)

朴素的通过检测‘\n’来统计增加的行数

未对齐的数据回到 _IO_default_xsputn

 if (to_do)
to_do -= _IO_default_xsputn (f, s+do_write, to_do);
}

调用_IO_default_xsputn,和<的一样进入缓冲区

哎呀你干嘛,突然意识到,小于缓冲区的大小的数据只是被送进了缓冲区,没有被写入文件啊。。。别急还有fclose没有分析,谁知道未来的fclose会发生什么呢

fwrite 函数调用的vtable函数

  • _IO_fwrite函数调用了vtable的_IO_new_file_xsputn
  • _IO_new_file_xsputn函数调用了vtable中的_IO_new_file_overflow实现缓冲区的建立以及刷新缓冲区。
  • vtable中的_IO_new_file_overflow函数调用了vtable的_IO_file_doallocate以初始化输入缓冲区。
  • vtable中的_IO_file_doallocate调用了vtable中的__GI__IO_file_stat以获取文件信息。
  • new_do_write中的_IO_SYSWRITE调用了vtable_IO_new_file_write最终去执行系统调用write。