IO FILE fclose

感谢raycp师傅:https://xz.aliyun.com/t/5445

fclose

int fclose(FILE *stream)

测试程序

#include<stdio.h>
#include<stdlib.h>
int main(){
char data[20] = "flag{grxer}";
FILE* fp = fopen("flag", "wb");
fwrite(data,1,0x20,fp);
fclose(fp);
return 0;
}

断点断到执行到fclose,缓冲区已经初始化

image-20230401212041052

_IO_new_fclose

include/stdio.h

#define fclose(fp) _IO_new_fclose(fp)

libio/iofclose.c

int
_IO_new_fclose (_IO_FILE *fp)
{
int status;

CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif

/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);

_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
#if _LIBC
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct _IO_codecvt *cc = fp->_codecvt;

__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
#endif
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp);
}

return status;
}

依旧是先检测魔数位来到,检查_IO_IS_FILEBUF文件指针 fp 是否与文件缓冲区相关联,

_IO_un_link ((struct _IO_FILE_plus *) fp)

libio/genops.c

void
_IO_un_link (struct _IO_FILE_plus *fp)
{
if (fp->file._flags & _IO_LINKED)
{
struct _IO_FILE **f;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
if (_IO_list_all == NULL)
;
else if (fp == _IO_list_all)
{
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
++_IO_list_all_stamp;
}
else
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (_IO_FILE *) fp)
{
*f = fp->file._chain;
++_IO_list_all_stamp;
break;
}
fp->file._flags &= ~_IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
libc_hidden_def (_IO_un_link)

IO_link_in逆过程,把文件流结构体从链上拿下来,先检测_IO_LINKED(0x80),看文件流结构体是不是在IO_list_all链中,随后把_IO_list_all指向chain字段也就是下一个

image-20230401221712640

再把_IO_LINKED标志位置空

_IO_file_close_it 缓冲区数据写回文件,释放缓冲区,关闭文件

libio/fileops.c

返回_IO_new_fclose 调用_IO_file_close_it (fp);

# define _IO_new_file_close_it _IO_file_close_it
int
_IO_new_file_close_it (_IO_FILE *fp)
{
int write_status;
if (!_IO_file_is_open (fp))
return EOF;

if ((fp->_flags & _IO_NO_WRITES) == 0
&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
write_status = _IO_do_flush (fp);
else
write_status = 0;

_IO_unsave_markers (fp);

int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);

/* Free buffer. */
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
if (fp->_mode > 0)
{
if (_IO_have_wbackup (fp))
_IO_free_wbackup_area (fp);
_IO_wsetb (fp, NULL, NULL, 0);
_IO_wsetg (fp, NULL, NULL, NULL);
_IO_wsetp (fp, NULL, NULL);
}
#endif
_IO_setb (fp, NULL, NULL, 0);
_IO_setg (fp, NULL, NULL, NULL);
_IO_setp (fp, NULL, NULL);

_IO_un_link ((struct _IO_FILE_plus *) fp);//再次确保脱链
fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;

return close_status ? close_status : write_status;
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)

首先检测不可写标志(0x8),再检测_IO_CURRENTLY_PUTTING(0x800)文件是否处于输出状态。

在fwrite中设置了_IO_CURRENTLY_PUTTING标志位

image-20230401224153932

image-20230401224234824

_IO_do_flush (fp) 缓冲区数据写回文件

# define _IO_do_flush(_f) \
_IO_do_write(_f, (_f)->_IO_write_base, \
(_f)->_IO_write_ptr-(_f)->_IO_write_base)
int
_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;
}
libc_hidden_ver (_IO_new_do_write, _IO_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;
}

和fwrite里分析的一样_IO_SYSWRITE调用了==vtable中__write对应的_IO_new_file_write==

写入文件并,刷新输出缓冲区的值

image-20230401230150953

image-20230401230235192

image-20230401230304718

_IO_SYSCLOSE(fp) 关闭文件

回到_IO_new_file_close_it

_IO_FLAGS2_NOCLOSE(32)表示一个流不应该被 fclose() 函数关闭。为流设置 _IO_FLAGS2_NOCLOSE 标志将防止 fclose() 函数关闭与流相关联的文件描述符。这在文件描述符正在被程序的其他部分使用或流正在用于与需要文件描述符保持打开状态的设备进行通信的情况下非常有用

调用_IO_SYSCLOSE(fp)

#define _IO_SYSCLOSE(FP) JUMP0 (__close, FP)

==虚表中的__close,即_IO_file_close函数==

/libio/fileops.c

int
_IO_file_close (_IO_FILE *fp)
{
/* Cancelling close should be avoided if possible since it leaves an
unrecoverable state behind. */
return close_not_cancel (fp->_fileno);
}
#define close_not_cancel(fd) \
__close (fd)
image-20230401232801039

直接系统调用close关闭文件

销毁文件流结构体内容,释放缓冲区

回到**_IO_new_file_close_it**_mode=-1不符合条件

_IO_setb (fp, NULL, NULL, 0);
_IO_setg (fp, NULL, NULL, NULL);
_IO_setp (fp, NULL, NULL);
void
_IO_setb (_IO_FILE *f, char *b, char *eb, int a)
{
if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF))
free (f->_IO_buf_base);
f->_IO_buf_base = b;
f->_IO_buf_end = eb;
if (a)
f->_flags &= ~_IO_USER_BUF;
else
f->_flags |= _IO_USER_BUF;
}

释放缓冲区,_IO_buf_base,_IO_buf_end置空,设置_IO_USER_BUF位域

#define _IO_setg(fp, eb, g, eg)  ((fp)->_IO_read_base = (eb),\
(fp)->_IO_read_ptr = (g), (fp)->_IO_read_end = (eg))
#define _IO_setp(__fp, __p, __ep) \
((__fp)->_IO_write_base = (__fp)->_IO_write_ptr \
= __p, (__fp)->_IO_write_end = (__ep))

macro展开后

((fp)->_IO_read_base = (NULL), (fp)->_IO_read_ptr = (NULL), (fp)->_IO_read_end = (NULL));
((fp)->_IO_write_base = (fp)->_IO_write_ptr = NULL, (fp)->_IO_write_end = (NULL));

把read write相关缓冲区指针置空

fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;

image-20230401235020311

确认文件关闭,释放文件流结构体内存

IO_new_file_finish

回到 _IO_new_fclose _IO_FINISH (fp);

#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)

==虚表中的_finish,对应_IO_new_file_finish函数==

libio/fileops.c

void
_IO_new_file_finish (_IO_FILE *fp, int dummy)
{
if (_IO_file_is_open (fp))
{
_IO_do_flush (fp);
if (!(fp->_flags & _IO_DELETE_DONT_CLOSE))
_IO_SYSCLOSE (fp);
}
_IO_default_finish (fp, 0);
}
libc_hidden_ver (_IO_new_file_finish, _IO_file_finish)
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)

直接来到_IO_default_finish(fp)

void
_IO_default_finish (_IO_FILE *fp, int dummy)
{
struct _IO_marker *mark;
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF))
{
free (fp->_IO_buf_base);
fp->_IO_buf_base = fp->_IO_buf_end = NULL;
}

for (mark = fp->_markers; mark != NULL; mark = mark->_next)
mark->_sbuf = NULL;

if (fp->_IO_save_base)
{
free (fp->_IO_save_base);
fp->_IO_save_base = NULL;
}

_IO_un_link ((struct _IO_FILE_plus *) fp);

#ifdef _IO_MTSAFE_IO
if (fp->_lock != NULL)
_IO_lock_fini (*fp->_lock);
#endif
}
libc_hidden_def (_IO_default_finish)

都不符合要求来到_IO_un_linkif (fp->file._flags & _IO_LINKED)不符合,返回_IO_new_fclose

if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp);
}

置零后释放文件流结构体内存

close函数调用的vtable函数

  • 在清空缓冲区的_IO_do_write函数中会调用vtable中__write对应的_IO_new_file_write的函数。
  • 关闭文件描述符_IO_SYSCLOSE函数为_close,即_IO_file_close函函数。
  • _IO_FINISH函数为vtable _finish,对应_IO_new_file_finish函数