All about fanda
All about fanda
GLIBC2.23 _IO_FILE相关函数原理分析

puts

  老规矩,话不多说,直接上源码,看如下最简单的输出代码:

#include <stdio.h>

int main()
{
    puts("hello,world");
    puts("end");
    return 0;
}

  接下来我们就根据glibc2.23中的源码结合gdb跟踪完成这一分析,首先看libio/ioputs.c

#include "libioP.h"
#include <string.h>
#include <limits.h>

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

#ifdef weak_alias
weak_alias (_IO_puts, puts)
#endif

  可以看到_IO_puts是puts的别名,_IO_puts其实就是_IO_new_file_xsputn,其他的其实跟输出没什么太多的关系,所以接下来我们开始逐步分析_IO_new_file_xsputn,这个函数接受参数strlen得到的str长度,str字符串和_IO_2_1_stdout_结构,源码开头:

_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))
    {
      //....
    }
    else if (f->_IO_write_end > f->_IO_write_ptr)
      //...

  to_do就是puts参数中的字符串的长度,第一次调用puts(或者说,第一次stdout输出)时,f->_flags为0xfbad2084,而_IO_LINE_BUF | _IO_CURRENTLY_PUTTING为0xa00,因此if内部不会得到执行,而f->_IO_write_end和f->_IO_write_ptr都为0,else if条件也不满足:

───────────────────────────────────────[ REGISTERS ]───────────────────────────────────────
 RAX  0xfbad2084
 RBX  0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2084
 RCX  0x5d4
 RDX  0xb
 RDI  0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2084
 RSI  0x4005d4 ◂— push   0x6f6c6c65 /* 'hello,world' */
 R8   0x7ffff7fda700 ◂— 0x7ffff7fda700
 R9   0x7ffff7de7ab0 (_dl_fini) ◂— push   rbp
 R10  0x194
 R11  0x7ffff7a7c690 (puts) ◂— push   r12
 R12  0xb
 R13  0x4005d4 ◂— push   0x6f6c6c65 /* 'hello,world' */
 R14  0x0
 R15  0x0
 RBP  0x7ffff7dd2620 (_IO_2_1_stdout_) ◂— 0xfbad2084
 RSP  0x7fffffffdbc0 —▸ 0x400526 (main) ◂— push   rbp
 RIP  0x7ffff7a86204 (_IO_file_xsputn+36) ◂— and    eax, 0xa00 /* '%' */
────────────────────────────────────────[ DISASM ]─────────────────────────────────────────
   0x7ffff7a861f7 <_IO_file_xsputn+23>    push   rbx
   0x7ffff7a861f8 <_IO_file_xsputn+24>    mov    r12, rdx
   0x7ffff7a861fb <_IO_file_xsputn+27>    mov    rbx, rdi
   0x7ffff7a861fe <_IO_file_xsputn+30>    sub    rsp, 8
   0x7ffff7a86202 <_IO_file_xsputn+34>    mov    eax, dword ptr [rdi]
 ► 0x7ffff7a86204 <_IO_file_xsputn+36>    and    eax, 0xa00
   0x7ffff7a86209 <_IO_file_xsputn+41>    cmp    eax, 0xa00
   0x7ffff7a8620e <_IO_file_xsputn+46>    je     _IO_file_xsputn+240 <0x7ffff7a862d0>

   0x7ffff7a86214 <_IO_file_xsputn+52>    mov    rdx, qword ptr [rdi + 0x30]
   0x7ffff7a86218 <_IO_file_xsputn+56>    mov    rdi, qword ptr [rdi + 0x28]
   0x7ffff7a8621c <_IO_file_xsputn+60>    cmp    rdx, rdi

pwndbg> p _IO_2_1_stdout_
$1 = {
  file = {
    _flags = -72540028,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,
    _fileno = 1,
    _flags2 = 0,
    _old_offset = -1,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

  接下来源码如下:

  /* Then fill the buffer. */
  if (count > 0)
    {
      //....
    }
  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;
//....

  由于count初始化为0,因此第一个if不执行,to_do为"hello,world"的长度为0xb,因此接下来控制流到达了_IO_OVERFLOW,_IO_OVERFLOW又是_IO_new_file_overflow,因此调用之,_IO_new_file_overflow源码开头如下:

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      //....
    }
  /* 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);
        }
//....

  _IO_NO_WRITES为8,显然0xfbad2084不满足,而f->_IO_write_base为0,因此控制流会进入_IO_doallocbuf分配内存作为缓冲区:

void
_IO_doallocbuf (_IO_FILE *fp)
{
  if (fp->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED) || fp->_mode > 0)
    if (_IO_DOALLOCATE (fp) != EOF)
      return;
  _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0);
}

  _IO_UNBUFFERED为2,因此满足了if条件,进入_IO_DOALLOCATE(其实为filedoalloc.c中的_IO_file_doallocate分配内存,关键代码如下:

  p = malloc (size);
  if (__glibc_unlikely (p == NULL))
    return EOF;
  _IO_setb (fp, p, p + size, 1);
  return 1;
}

  _IO_setb源码为:

_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的置位,最后控制流返回到了_IO_file_overflow,调用了_IO_setg,其把_IO_read_base,ptr,end都设置为了堆块首地址:

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

  _IO_file_overflow剩余部分的代码如下:

      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;
}

  此时_IO_read_ptr为堆区首地址,_IO_buf_end为堆末地址,不满足相等条件。继续往下看则是对_IO_write_ptr,base,end等的赋值,并对f->_flags的_IO_CURRENTLY_PUTTING进行了置位,由于传入的参数ch为EOF,所以控制流进入了_IO_do_write:

_IO_ssize_t
_IO_new_file_write (_IO_FILE *f, const void *data, _IO_ssize_t n)
{
  _IO_ssize_t to_do = n;
  while (to_do > 0)
    {
      //....
    }
  n -= to_do;
  if (f->_offset >= 0)
    f->_offset += n;
  return n;
}

  此时传入的n为_IO_write_ptr-_IO_write_base为0,因此_IO_new_file_write直接return 0。控制流回到了_IO_new_file_xsputn:

  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)
        {
          //...
        }

      /* 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;
}

  返回后,block_size为0x400,do_write为0,to_do仍然为0xb("hello,world"的长度),因此,_IO_default_xsputn被调用了:

_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 (;;)
    {
      if (f->_IO_write_ptr < f->_IO_write_end)
      {
//......
      }
      if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
        break;
      more--;
    }
  return n - more;
}

  由于此时_IO_write_ptr=_IO_write_end,所以第一个if不满足,再一次调用了_IO_new_file_overflow:

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      //...
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      //...
    }
    if (ch == EOF)
      //...
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
    //...
  *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;
}

  这一次_IO_new_file_overflow把传入的参数的字符串中的一个字节放入了_IO_write_ptr所处的缓冲区中,然后不满足if判断,return  (unsigned char)ch返回到了_IO_default_xsputn中:

  for (;;)
    {
      /* Space available. */
      if (f->_IO_write_ptr < f->_IO_write_end)
        {
          //...
        }
      if (more == 0 || _IO_OVERFLOW (f, (unsigned char) *s++) == EOF)
        break;
      more--;
    }

  _IO_OVERFLOW返回的是ch,所以不会触发break,more自减1后重新循环再次调用了_IO_OVERFLOW直到达到字符串末尾的换行符,这时if判断满足了,_IO_do_write被调用,传入参数为f,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;
}

  其内部又以同样的参数调用了new_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)
    //...
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      //...
    }
  count = _IO_SYSWRITE (fp, data, to_do);
  if (fp->_cur_column && count)
    //....
  _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_SYSWRITE输出了我们的字符串,然后将fp->_IO_write_base,ptr,end和read_base,ptr,end全部置为了_IO_buf_base,然后函数重新返回到了puts。至此,对第一次调用puts函数的过程就跟踪完毕了,接下来第二次调用就比较轻车熟路了:

_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')
                {
                  //...
                }
            }
        }
    }

  第二次调用puts时,f->_flags为0xfbad2a84,满足if中的两个条件,因此count被赋值为0x400,而且此处可能因为只有一个字符串,因此for循环中的if条件不会被满足,继续往下看:

  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;
    }

  count之前经过计算为堆区大小(0x400),因此count被赋值为to_do(也就是"end"字符串的长度3)后调用了__memcpy,在f->_IO_write_ptr处放入了"end",s加上count长度后为字符串末尾,to_do减去count后为0,不满足后续的if条件,因此_IO_new_file_overflow函数直接返回:

  if (to_do + must_flush > 0)
    {
      //...
    }
  return n - to_do;
}

  这一次直接回到了puts函数内部,我们再来看一下puts函数的源码:

int
_IO_puts (const char *str)
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

  _IO_sputn的返回值为len,满足。然后调用了_IO_putc_unlocked,其定义如下:

#define _IO_putc_unlocked(_ch, _fp) \
   (_IO_BE ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end, 0) \
    ? __overflow (_fp, (unsigned char) (_ch)) \
    : (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))

  显然fp->_IO_wirte_ptr是大于fp->_IO_writeend的,所以__overflow得以执行,而\_overflow事实上就是_IO_new_file_overflow,此时流程如下:

int
_IO_new_file_overflow (_IO_FILE *f, int ch)
{
  if (f->_flags & _IO_NO_WRITES) /* SET ERROR */
    {
      //...
    }
  /* If currently reading or no buffer allocated. */
  if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)
    {
      //...
    }
  if (ch == EOF)
    //...
  if (f->_IO_write_ptr == f->_IO_buf_end ) /* Buffer is really full */
    //...
  *f->_IO_write_ptr++ = ch;

  传入的参数ch为换行符,因此相当于在"end"字符串末尾加上了一个换行符,继续往下看:

  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;
}

  虽然_IO_UNBUFFERED不满足,但是f->_flags的_IO_LINE_BUF和ch为换行符的条件被满足,因此流程进入_IO_do_write,之前说过其最终调用的是new_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)
    //...
  else if (fp->_IO_read_end != fp->_IO_write_base)
    {
      //...
    }
  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_SYSWRITE输出"end"后,重新置位f->_IOwrite*和_IOread\*等一系列结构后退出puts,完成整个输出过程。

  因此对puts函数的原理跟踪分析完成了。


fopen

  在Linux下我们可能听说过open函数比较多,open函数由POSIX实现,但C语言标准实现了一个叫fopen的函数,其返回值不是一个可供系统调用的文件句柄,而是一个_IO_FILE指针,本节我们就主要剖析一下C是如何利用这个文件指针来进行文件读写的。老样子先写一个hello,world:

#include <stdio.h>
#include <stdlib.h>

char buf[10];

int main()
{
    FILE* f=fopen("flag","r");
    fread(buf,1,4,f);
    fclose(f);

    printf("%s\n",buf);

    return 0;
}

  从这个源码文件开始看起libio/iofopen

# define _IO_new_fopen fopen

_IO_FILE *
_IO_new_fopen (const char *filename, const char *mode)
{
  return __fopen_internal (filename, mode, 1);
}

  可以看到我们使用的fopen实际上就是_IO_newfopen函数的别名,其内部又直接调用了\_fopen_internal,还加入了第三个参数1,__fopen_internal

_IO_FILE *
__fopen_internal (const char *filename, const char *mode, int is32)
{
  struct locked_FILE
  {
    struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
    _IO_lock_t lock;
#endif
    struct _IO_wide_data wd;
  } *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));

  if (new_f == NULL)
    return NULL;
#ifdef _IO_MTSAFE_IO
  new_f->fp.file._lock = &new_f->lock;
#endif
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  _IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
#else
  //...
#endif
  _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
  _IO_file_init (&new_f->fp);
#if  !_IO_UNIFIED_JUMPTABLES
  new_f->fp.vtable = NULL;
#endif
  if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
    return __fopen_maybe_mmap (&new_f->fp.file);

  _IO_un_link (&new_f->fp);
  free (new_f);
  return NULL;
}

  接下来补充一下与上面代码相关的定义:

typedef void _IO_lock_t;

/* Extra data for wide character streams.  */
struct _IO_wide_data
{
  wchar_t *_IO_read_ptr;        /* Current read pointer */
  wchar_t *_IO_read_end;        /* End of get area. */
  wchar_t *_IO_read_base;       /* Start of putback+get area. */
  wchar_t *_IO_write_base;      /* Start of put area. */
  wchar_t *_IO_write_ptr;       /* Current put pointer. */
  wchar_t *_IO_write_end;       /* End of put area. */
  wchar_t *_IO_buf_base;        /* Start of reserve area. */
  wchar_t *_IO_buf_end;         /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  wchar_t *_IO_save_base;       /* Pointer to start of non-current get area. */
  wchar_t *_IO_backup_base;     /* Pointer to first valid character of
                                   backup area */
  wchar_t *_IO_save_end;        /* Pointer to end of non-current get area. */

  __mbstate_t _IO_state;
  __mbstate_t _IO_last_state;
  struct _IO_codecvt _codecvt;

  wchar_t _shortbuf[1];

  const struct _IO_jump_t *_wide_vtable;
};

struct _IO_FILE_plus
{
  _IO_FILE file;
  const struct _IO_jump_t *vtable;
};

struct _IO_FILE {
  int _flags;           /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

  /* The following pointers correspond to the C++ streambuf protocol. */
  /* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
  char* _IO_read_ptr;   /* Current read pointer */
  char* _IO_read_end;   /* End of get area. */
  char* _IO_read_base;  /* Start of putback+get area. */
  char* _IO_write_base; /* Start of put area. */
  char* _IO_write_ptr;  /* Current put pointer. */
  char* _IO_write_end;  /* End of put area. */
  char* _IO_buf_base;   /* Start of reserve area. */
  char* _IO_buf_end;    /* End of reserve area. */
  /* The following fields are used to support backing up and undo. */
  char *_IO_save_base; /* Pointer to start of non-current get area. */
  char *_IO_backup_base;  /* Pointer to first valid character of backup area */
  char *_IO_save_end; /* Pointer to end of non-current get area. */

  struct _IO_marker *_markers;

  struct _IO_FILE *_chain;

  int _fileno;
#if 0
  int _blksize;
#else
  int _flags2;
#endif
  _IO_off_t _old_offset; /* This used to be _offset but it's too small.  */

#define __HAVE_COLUMN /* temporary */
  /* 1+column number of pbase(); 0 is unknown. */
  unsigned short _cur_column;
  signed char _vtable_offset;
  char _shortbuf[1];

  /*  char* _save_gptr;  char* _save_egptr; */

  _IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

struct _IO_jump_t
{
    JUMP_FIELD(size_t, __dummy);
    JUMP_FIELD(size_t, __dummy2);
    JUMP_FIELD(_IO_finish_t, __finish);
    JUMP_FIELD(_IO_overflow_t, __overflow);
    JUMP_FIELD(_IO_underflow_t, __underflow);
    JUMP_FIELD(_IO_underflow_t, __uflow);
    JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
    /* showmany */
    JUMP_FIELD(_IO_xsputn_t, __xsputn);
    JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
    JUMP_FIELD(_IO_seekoff_t, __seekoff);
    JUMP_FIELD(_IO_seekpos_t, __seekpos);
    JUMP_FIELD(_IO_setbuf_t, __setbuf);
    JUMP_FIELD(_IO_sync_t, __sync);
    JUMP_FIELD(_IO_doallocate_t, __doallocate);
    JUMP_FIELD(_IO_read_t, __read);
    JUMP_FIELD(_IO_write_t, __write);
    JUMP_FIELD(_IO_seek_t, __seek);
    JUMP_FIELD(_IO_close_t, __close);
    JUMP_FIELD(_IO_stat_t, __stat);
    JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
    JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
    get_column;
    set_column;
#endif
};

struct _IO_FILE_complete
{
  struct _IO_FILE _file;
#endif
#if defined _G_IO_IO_FILE_VERSION && _G_IO_IO_FILE_VERSION == 0x20001
  _IO_off64_t _offset;
# if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  /* Wide character stream stuff.  */
  struct _IO_codecvt *_codecvt;
  struct _IO_wide_data *_wide_data;
  struct _IO_FILE *_freeres_list;
  void *_freeres_buf;
# else
  void *__pad1;
  void *__pad2;
  void *__pad3;
  void *__pad4;
# endif
  size_t __pad5;
  int _mode;
  /* Make sure we don't get into trouble again.  */
  char _unused2[15 * sizeof (int) - 4 * sizeof (void *) - sizeof (size_t)];
#endif
};

struct _IO_FILE_complete_plus
{
  struct _IO_FILE_complete file;
  const struct _IO_jump_t *vtable;
};

  定义有点长,但我们不用立马把他们都记住,只需要先看一遍就行了,我们慢慢分析(_IO_FILE事实上是_IO_FILE_complete,plus也同理,以后都这么称呼),先把locked_FILE结构体的lock赋给了fp指针中的_IO_FILE结构体的_lock,然后调用了_IO_no_init:

void
_IO_no_init (_IO_FILE *fp, int flags, int orientation,
             struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
{
  _IO_old_init (fp, flags);

  其又首先调用了_IO_old_init:

#define _IO_MAGIC 0xFBAD0000
#define _IO_lock_init(_name) \
  ((void) ((_name) = (_IO_lock_t) _IO_lock_initializer))
typedef void _IO_lock_t;
#define _IO_lock_initializer { LLL_LOCK_INITIALIZER, 0, NULL }

void
_IO_old_init (_IO_FILE *fp, int flags)
{
  fp->_flags = _IO_MAGIC|flags;
  fp->_flags2 = 0;
  fp->_IO_buf_base = NULL;
  fp->_IO_buf_end = NULL;
  fp->_IO_read_base = NULL;
  fp->_IO_read_ptr = NULL;
  fp->_IO_read_end = NULL;
  fp->_IO_write_base = NULL;
  fp->_IO_write_ptr = NULL;
  fp->_IO_write_end = NULL;
  fp->_chain = NULL; /* Not necessary. */

  fp->_IO_save_base = NULL;
  fp->_IO_backup_base = NULL;
  fp->_IO_save_end = NULL;
  fp->_markers = NULL;
  fp->_cur_column = 0;
#if _IO_JUMPS_OFFSET
  fp->_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
  if (fp->_lock != NULL)
    _IO_lock_init (*fp->_lock);
#endif
}

  _IO_old_init初始化了fp的_IO_FILE结构,其中_IO_lock_init相关定义太多太杂,只需要知道fp->_lock被初始化为0就行了,flags在此处为0,_IO_MAGIC为0xfbad0000,这下你知道这个奇怪的数字是怎么来的吧,这就是magic。接下来控制流返回_IO_no_init:

void
_IO_no_init (_IO_FILE *fp, int flags, int orientation,
             struct _IO_wide_data *wd, const struct _IO_jump_t *jmp)
{
  _IO_old_init (fp, flags);
  fp->_mode = orientation;
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  if (orientation >= 0)
    {
      fp->_wide_data = wd;
      fp->_wide_data->_IO_buf_base = NULL;
      fp->_wide_data->_IO_buf_end = NULL;
      fp->_wide_data->_IO_read_base = NULL;
      fp->_wide_data->_IO_read_ptr = NULL;
      fp->_wide_data->_IO_read_end = NULL;
      fp->_wide_data->_IO_write_base = NULL;
      fp->_wide_data->_IO_write_ptr = NULL;
      fp->_wide_data->_IO_write_end = NULL;
      fp->_wide_data->_IO_save_base = NULL;
      fp->_wide_data->_IO_backup_base = NULL;
      fp->_wide_data->_IO_save_end = NULL;

      fp->_wide_data->_wide_vtable = jmp;
    }
  else
    /* Cause predictable crash when a wide function is called on a byte
       stream.  */
    //...
#endif
  fp->_freeres_list = NULL;
}

  剩余代码就是初始化了fp->_IO_wide_data这个结构体,其中比较不一样的是jmp,这个_IO_jump_t结构的变量似乎是系统之前生成的,我们就不溯源了。随后控制流从_IO_noinit返回到了\_fopen_internal:

#define _IO_JUMPS(THIS) (THIS)->vtable

  _IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
  _IO_file_init (&new_f->fp);

  设置了fp的vtable,_IO_file_jumps也是一个_IO_jump_t的结构,跟上面那个不太一样,上面那个是_wide_data的,下面这个就像是普通的,这两个变量有一点点差别(_IO_wfile_jumps就是之前的jmp):

pwndbg> p _IO_wfile_jumps
$13 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a869c0 <_IO_new_file_finish>,
  __overflow = 0x7ffff7a818a0 <__GI__IO_wfile_overflow>,
  __underflow = 0x7ffff7a807d0 <__GI__IO_wfile_underflow>,
  __uflow = 0x7ffff7a7ef60 <__GI__IO_wdefault_uflow>,
  __pbackfail = 0x7ffff7a7ed00 <__GI__IO_wdefault_pbackfail>,
  __xsputn = 0x7ffff7a81c70 <__GI__IO_wfile_xsputn>,
  __xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7ffff7a80df0 <__GI__IO_wfile_seekoff>,
  __seekpos = 0x7ffff7a88a00 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,
  __sync = 0x7ffff7a81b10 <__GI__IO_wfile_sync>,
  __doallocate = 0x7ffff7a7b660 <_IO_wfile_doallocate>,
  __read = 0x7ffff7a861a0 <__GI__IO_file_read>,
  __write = 0x7ffff7a85b70 <_IO_new_file_write>,
  __seek = 0x7ffff7a85970 <__GI__IO_file_seek>,
  __close = 0x7ffff7a85340 <__GI__IO_file_close>,
  __stat = 0x7ffff7a85b60 <__GI__IO_file_stat>,
  __showmanyc = 0x7ffff7a89af0 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a89b00 <_IO_default_imbue>
}
pwndbg> p _IO_file_jumps
$14 = {
  __dummy = 0,
  __dummy2 = 0,
  __finish = 0x7ffff7a869c0 <_IO_new_file_finish>,
  __overflow = 0x7ffff7a87730 <_IO_new_file_overflow>,
  __underflow = 0x7ffff7a874a0 <_IO_new_file_underflow>,
  __uflow = 0x7ffff7a88600 <__GI__IO_default_uflow>,
  __pbackfail = 0x7ffff7a89980 <__GI__IO_default_pbackfail>,
  __xsputn = 0x7ffff7a861e0 <_IO_new_file_xsputn>,
  __xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>,
  __seekoff = 0x7ffff7a854c0 <_IO_new_file_seekoff>,
  __seekpos = 0x7ffff7a88a00 <_IO_default_seekpos>,
  __setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,
  __sync = 0x7ffff7a85370 <_IO_new_file_sync>,
  __doallocate = 0x7ffff7a7a180 <__GI__IO_file_doallocate>,
  __read = 0x7ffff7a861a0 <__GI__IO_file_read>,
  __write = 0x7ffff7a85b70 <_IO_new_file_write>,
  __seek = 0x7ffff7a85970 <__GI__IO_file_seek>,
  __close = 0x7ffff7a85340 <__GI__IO_file_close>,
  __stat = 0x7ffff7a85b60 <__GI__IO_file_stat>,
  __showmanyc = 0x7ffff7a89af0 <_IO_default_showmanyc>,
  __imbue = 0x7ffff7a89b00 <_IO_default_imbue>
}
pwndbg>

  随后调用了_IO_file_init,其别名又是_IO_new_file_init:

# define _IO_new_file_init _IO_file_init
# define _IO_pos_BAD ((_IO_off64_t) -1)
#define CLOSED_FILEBUF_FLAGS \
  (_IO_IS_FILEBUF+_IO_NO_READS+_IO_NO_WRITES+_IO_TIED_PUT_GET)
#define _IO_IS_FILEBUF 0x2000
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_TIED_PUT_GET 0x400 /* Set if put and get pointer logicly tied. */

void
_IO_new_file_init (struct _IO_FILE_plus *fp)
{
  /* POSIX.1 allows another file handle to be used to change the position
     of our file descriptor.  Hence we actually don't know the actual
     position before we do the first fseek (and until a following fflush). */
  fp->file._offset = _IO_pos_BAD;
  fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;

  _IO_link_in (fp);
  fp->file._fileno = -1;
}

  其中_IO_off64_t牵扯的也有些多,而且事实上放入fp->file._offset的就是一个-1,因此不挖掘了,随后调用了_IO_link_in函数:

#define _IO_LINKED 0x80 /* Set if linked (using _chain) to streambuf::_list_all.*/

void
_IO_link_in (struct _IO_FILE_plus *fp)
{
  if ((fp->file._flags & _IO_LINKED) == 0)
    {
      fp->file._flags |= _IO_LINKED;
#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
      fp->file._chain = (_IO_FILE *) _IO_list_all;
      _IO_list_all = fp;
      ++_IO_list_all_stamp;
#ifdef _IO_MTSAFE_IO
      _IO_funlockfile ((_IO_FILE *) fp);
      run_fp = NULL;
      _IO_lock_unlock (list_all_lock);
      _IO_cleanup_region_end (0);
#endif
    }
}

  这里操作有些冗杂,只需要知道关键部分就行了,这里把_IO_list_all设置为了这个fp指针,然后fp->_chain(也就是_IO_FILE链表的下一个节点设置为了原来的_IO_list_all指向的结构:_IO_2_1stderr\。这是程序在main函数之前就初始化好的)。_IO_link_in函数执行完了后fp结构体如下:

pwndbg> p *(struct _IO_FILE_plus*)0x602010
$18 = {
  file = {
    _flags = -72538996,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 0,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x6020f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x602100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
pwndbg> p & _IO_list_all
$19 = (struct _IO_FILE_plus **) 0x7ffff7dd2520 <_IO_list_all>
pwndbg> x/xg 0x7ffff7dd2520
0x7ffff7dd2520 <_IO_list_all>:    0x0000000000602010
pwndbg>

  _IO_list_all是_IO_FILE链表的头部,可以看到我们的文件指针已经被插入到了_IO_list_all的头部,fp->_chain指向的就是下一个_IOFILE结构体。接下来控制流返回到\_fopen_internal:

#if  !_IO_UNIFIED_JUMPTABLES
  //...
#endif
  if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
    return __fopen_maybe_mmap (&new_f->fp.file);

# define _IO_new_file_fopen _IO_file_fopen

  _IO_new_file_fopen就是_IO_file_fopen的别名,这个函数源码如下:

#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)
#define O_RDONLY        0       /* Open read-only.  */
#define _IO_NO_WRITES 8 /* Writing not allowd */

_IO_FILE *
_IO_new_file_fopen (_IO_FILE *fp, const char *filename, const char *mode,
                    int is32not64)
{
  int oflags = 0, omode;
  int read_write;
  int oprot = 0666;
  int i;
  _IO_FILE *result;
#ifdef _LIBC
  const char *cs;
  const char *last_recognized;
#endif

  if (_IO_file_is_open (fp))
    //...
  switch (*mode)
    {
    case 'r':
      omode = O_RDONLY;
      read_write = _IO_NO_WRITES;
      break;
      //...
    }
#ifdef _LIBC
  last_recognized = mode;
#endif
  for (i = 1; i < 7; ++i)
    {
      switch (*++mode)
        {
        case '\0':
          break;
        //...
        }
      break;
    }

  result = _IO_file_open (fp, filename, omode|oflags, oprot, read_write,
                          is32not64);

  显然在我们之前初始化的时候fp的->_fileno被初始化为-1,这代表文件未打开的状态,控制流继续往下,switch里判断的mode其实指向的是我们使用fopen传入的字符串参数,也就是"r"、"w","a"或者"rw"之类的,我们在这里传入的为"r",因此omode被设置为了0,read_write被设置为了8。后面又因为我们只传入了一个"r",所以第二个字节就是"\0",所以直接跳出循环。再次调用了_IO_file_open,但这一次不是_IO_new_file_open,而是_IO_file_open:

#define _IO_FLAGS2_NOTCANCEL 2

_IO_FILE *
_IO_file_open (_IO_FILE *fp, const char *filename, int posix_mode, int prot,
               int read_write, int is32not64)
{
  int fdesc;
#ifdef _LIBC
  if (__glibc_unlikely (fp->_flags2 & _IO_FLAGS2_NOTCANCEL))
    //...
  else
    fdesc = open (filename, posix_mode | (is32not64 ? 0 : O_LARGEFILE), prot);
#else
  //...
#endif

  其检查了fp->_flags2的_IO_FLAGS2_NOTCANCEL标志位,而我们的flags2为0,因此不会触发,后续调用了open,而open的别名是open64,然后open64又是\_libc_open64的别名,所以事实上调用的是__libcopen64,而is32not64其实就是从\_open_internal一直传参下来的,为1,因此最终posix_mode为0,prot为0666(八进制):

#  define open open64
weak_alias (__libc_open64, open64)

int
__libc_open64 (const char *file, int oflag, ...)
{
  int mode = 0;

  if (__OPEN_NEEDS_MODE (oflag))
    {
      va_list arg;
      va_start (arg, oflag);
      mode = va_arg (arg, int);
      va_end (arg);
    }

  return SYSCALL_CANCEL (open, file, oflag | O_LARGEFILE, mode);
}

#define SINGLE_THREAD_P __builtin_expect (__local_multiple_threads == 0, 1)
#define SYSCALL_CANCEL(...) \
  ({                                                                         \
    long int sc_ret;                                                         \
    if (SINGLE_THREAD_P)                                                     \
      sc_ret = __SYSCALL_CALL (__VA_ARGS__);                                 \
    else                                                                     \
      {                                                                      \
        //...                             \
      }                                                                      \
    sc_ret;                                                                  \
  })

  __libc_open64中的if被编译器优化了,因此直接是SYSCALL_CANCEL宏定义,根据gdb跟踪的反汇编来看:

   0x7ffff7b04030 <open64>                cmp    dword ptr [rip + 0x2d2709], 0 <0x7ffff7dd6740>
   0x7ffff7b04037 <open64+7>              jne    open64+25 <0x7ffff7b04049>

   0x7ffff7b04039 <__open_nocancel>       mov    eax, 2
   0x7ffff7b0403e <__open_nocancel+5>     syscall
   0x7ffff7b04040 <__open_nocancel+7>     cmp    rax, -0xfff
 ► 0x7ffff7b04046 <__open_nocancel+13>    jae    open64+73 <0x7ffff7b04079>

   0x7ffff7b04048 <__open_nocancel+15>    ret
    ↓
   0x7ffff7a86ace <_IO_file_open+142>     mov    r8d, dword ptr [rsp + 0xc]

pwndbg> x/g 0x7ffff7b04037+0x2d2709
0x7ffff7dd6740 <__libc_multiple_threads>: 0x0000000000000000

  再深入就是系统调用了,其检查了__libc_multiple_threads是否为 0,在我们这里显然是0,因此直接使用系统调用号为2的syscall使用了SYSCALL_OPEN,后来看了一下open64就是Linux系统调用open的别名(也就是说直接调用了Linux的系统调用),然后返回值就是3,这是一个文件句柄。然后控制流返回到了_IO_file_open:

#define _IO_mask_flags(fp, f, mask) \
       ((fp)->_flags = ((fp)->_flags & ~(mask)) | ((f) & (mask)))
#define _IO_NO_READS 4 /* Reading not allowed */
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_IS_APPENDING 0x1000

  if (fdesc < 0)
    return NULL;
  fp->_fileno = fdesc;
  _IO_mask_flags (fp, read_write,_IO_NO_READS+_IO_NO_WRITES+_IO_IS_APPENDING);
  /* For append mode, send the file offset to the end of the file.  Don't
     update the offset cache though, since the file handle is not active.  */
  if ((read_write & (_IO_IS_APPENDING | _IO_NO_READS))
      == (_IO_IS_APPENDING | _IO_NO_READS))
    {
      //...
    }
  _IO_link_in ((struct _IO_FILE_plus *) fp);
  return fp;
}

  这个fdesc就是文件描述符,就是系统调用之后返回的句柄(不知道为什么我还是习惯称之为句柄),read_write之前已经在switch(*mode)的候就已经设置为了_IO_NO_WRITES了,因此在_IO_mask_flags中fp->_flags被加上了_IO_NO_WRITES标志位,然后调用了_IO_link_in:

void
_IO_link_in (struct _IO_FILE_plus *fp)
{
  if ((fp->file._flags & _IO_LINKED) == 0)
    {
      //...
    }
}

  这一次直接返回了,控制流返回到了_IO_new_file_open:

  if (result != NULL)
    {
#ifndef __ASSUME_O_CLOEXEC
      //...
#endif
      /* Test whether the mode string specifies the conversion.  */
      cs = strstr (last_recognized + 1, ",ccs=");
      if (cs != NULL)
        {
          //...
        }
      }
  return result;
}

  调用了strstr函数,这个函数就是返回第二个参数字符串子串在第一个参数中的位置,在这里返回0,因此不会使用widedata结构体,这个strstr主要是检测文件是否是其他格式的,比如utf-8或者unicode格式,因此直接返回了\_fopen_internal:

  if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
    return __fopen_maybe_mmap (&new_f->fp.file);

  函数实现如下:

_IO_FILE *
__fopen_maybe_mmap (_IO_FILE *fp)
{
#ifdef _G_HAVE_MMAP
  if ((fp->_flags2 & _IO_FLAGS2_MMAP) && (fp->_flags & _IO_NO_WRITES))
    {
      //...
    }
#endif
  return fp;
}

  可以看到基本也是直接返回的,终于控制流回到了main函数,第一次fopen的调用就结束了,最终fp指针结构如下:

pwndbg> p *(struct _IO_FILE_plus*)0x602010
$23 = {
  file = {
    _flags = -72539000,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x6020f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x602100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
pwndbg> x/xg & _IO_list_all
0x7ffff7dd2520 <_IO_list_all>:    0x0000000000602010

fread

  上次我们已经跟踪了一遍fopen的原理,这次我们来跟踪分析一下fread从文件描述符读取内容的过程。这有助于我们进一步了解_IO_FILE结构体,源码如下:

weak_alias (_IO_fread, fread)
#define _IO_size_t size_t

#ifdef IO_DEBUG
//...
#else
# define CHECK_FILE(FILE, RET) COERCE_FILE (FILE)
#endif

# define COERCE_FILE(FILE) /* Nothing */

_IO_size_t
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
  _IO_size_t bytes_requested = size * count;
  _IO_size_t bytes_read;
  CHECK_FILE (fp, 0);
  if (bytes_requested == 0)
    //...
  _IO_acquire_lock (fp);
  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);

  fread的别名是_IO_fread,其参数接受了目标地址,读取粒度,数量以及一个FILE*变量。CHECK_FILE事实上什么都不做,_IO_acquire_lock也不是我们关心的东西(顾名思义就行,用锁互斥),然后控制流就进入了_IO_sgetn,其源码如下:

_IO_size_t
_IO_sgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  /* FIXME handle putback buffer here! */
  return _IO_XSGETN (fp, data, n);
}
libc_hidden_def (_IO_sgetn)

  其直接调用了另一个函数_IO_XSGETN,这个函数我们非常眼熟,这是一个在vtable中注册的函数(但我不是指他是通过vtable调用的):

  __xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>

  其源码如下:

_IO_size_t
_IO_file_xsgetn (_IO_FILE *fp, void *data, _IO_size_t n)
{
  _IO_size_t want, have;
  _IO_ssize_t count;
  char *s = data;

  want = n;

  if (fp->_IO_buf_base == NULL)
    {
      /* Maybe we already have a push back pointer.  */
      if (fp->_IO_save_base != NULL)
        {
          free (fp->_IO_save_base);
          fp->_flags &= ~_IO_IN_BACKUP;
        }
      _IO_doallocbuf (fp);
    }

  显然我们文件指针的_IO_FILE刚经过初始化,_IO_buf_base和_IO_save_base肯定都是0的,所以会调用_IO_doallocbuf分配缓冲区:

#define _IO_UNBUFFERED 2

void
_IO_doallocbuf (_IO_FILE *fp)
{
  if (fp->_IO_buf_base)
    return;
  if (!(fp->_flags & _IO_UNBUFFERED) || fp->_mode > 0)
    if (_IO_DOALLOCATE (fp) != EOF)
      return;
  _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0);
}
libc_hidden_def (_IO_doallocbuf)

  我们的fp->_flags为0xfbad2488,满足了if的第一个条件因此调用了_IO_DOALLOCATE,这一次是通过vtable调用的,调用方式如下:

  __doallocate = 0x7ffff7a7a180 <__GI__IO_file_doallocate>,

 ► 0x7ffff7a8858e <_IO_doallocbuf+46>    mov    rdi, rbx
   0x7ffff7a88591 <_IO_doallocbuf+49>    call   qword ptr [rax + 0x68]

  其源码如下:

int
_IO_file_doallocate (_IO_FILE *fp)
{
  _IO_size_t size;
  char *p;
  struct stat64 st;

#ifndef _LIBC
  //...
#endif

  size = _IO_BUFSIZ;
  if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0)
    {

  显然我们的fileno之前已经设置为了open返回的文件描述符了,其值为3,随后再次通过vtable调用了_IO_SYSSTAT,其定义如下:

#define _IO_SYSSTAT(FP, BUF) JUMP1 (__stat, FP, BUF)

int
_IO_file_stat (_IO_FILE *fp, void *st)
{
  return __fxstat64 (_STAT_VER, fp->_fileno, (struct stat64 *) st);
}
libc_hidden_def (_IO_file_stat)

  在这里我们就不继续深入了,__fxstat64直接使用了调用号为5的syscall系统调用SYS_fstat,一般都能成功且返回值为0,因此控制流返回到_IO_file_doallocate,if的条件被满足,所以进入if内部:

      if (S_ISCHR (st.st_mode))
        {
          //...
        }
#if _IO_HAVE_ST_BLKSIZE
      if (st.st_blksize > 0)
        size = st.st_blksize;
#endif
    }
  p = malloc (size);
  if (__glibc_unlikely (p == NULL))
    return EOF;
  _IO_setb (fp, p, p + size, 1);
  return 1;
}

  此时st结构如下:

pwndbg> p *(struct stat64*)0x7fffffffdae0
$35 = {
  st_dev = 2049,
  st_ino = 3839400,
  st_nlink = 1,
  st_mode = 33204,
  st_uid = 1000,
  st_gid = 1000,
  __pad0 = 0,
  st_rdev = 0,
  st_size = 6,
  st_blksize = 4096,
  st_blocks = 8,
  st_atim = {
    tv_sec = 1558184925,
    tv_nsec = 710595774
  },
  st_mtim = {
    tv_sec = 1558106735,
    tv_nsec = 272930919
  },
  st_ctim = {
    tv_sec = 1558106735,
    tv_nsec = 272930919
  },
  __glibc_reserved = {0, 0, 0}
}

  所以st.st_blksize会传给size,然后作为malloc的参数申请堆块,在这里会申请一个0x1000大小的堆块,_IO_setb的定义如下:

#define _IO_USER_BUF 1 /* User owns buffer; don't delete it on close. */

void
_IO_setb (_IO_FILE *f, char *b, char *eb, int a)
{
  if (f->_IO_buf_base && !(f->_flags & _IO_USER_BUF))
    //...
  f->_IO_buf_base = b;
  f->_IO_buf_end = eb;
  if (a)
    f->_flags &= ~_IO_USER_BUF;
  else
    //...
}

  所以在这里根据传入的参数(b为堆块首地址,eb为堆块末地址,a为1),将f这个FILE指针的_IO_buf_base设置为了b,_IO_buf_end设置为了eb,然后去除了_IO_USER_BUF标志位。这时我们的f指针如下:

pwndbg> p *(struct _IO_FILE_plus*)0x602010
$39 = {
  file = {
    _flags = -72539000,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x602240 "",
    _IO_buf_end = 0x603240 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x6020f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x602100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = 0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

  接下来控制流一直返回到了_IO_new_file_xsputn:

  while (want > 0)
    {
      have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (want <= have)
        {
          //...
        }
      else
        {
          if (have > 0)
            {
              //...
            }

          /* Check for backup and repeat */
          if (_IO_in_backup (fp))
            {
              /...
            }

  _IO_in_backup的定义如下:

#define _IO_in_backup(fp) ((fp)->_flags & _IO_IN_BACKUP)
#define _IO_IN_BACKUP 0x100

  我们的flags之前看过了,为0xfbad2488,因此if不满足,继续往下:

          /* If we now want less than a buffer, underflow and repeat
             the copy.  Otherwise, _IO_SYSREAD directly to
             the user buffer. */
          if (fp->_IO_buf_base
              && want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
            {
              if (__underflow (fp) == EOF)
                break;

              continue;
            }

  显然这一条if就可以满足了,want就是我们之前在fread那里计算得来的参数1*4=4,所以调用了__underflow:

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

int
__underflow (_IO_FILE *fp)
{
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
  //...
#endif

  if (fp->_mode == 0)
    _IO_fwide (fp, -1);
  if (_IO_in_put_mode (fp))
    //...

  相关定义如下:

#  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)                 \
       //...
     else                                                                     \
       //...
     __result; })
# endif

#define _IO_in_put_mode(_fp) ((_fp)->_flags & _IO_CURRENTLY_PUTTING)
#define _IO_CURRENTLY_PUTTING 0x800

  _IO_fwide这个宏看上去难以理解,但实际上就是把我们的fp文件指针中的_mode设置为了-1,然后就结束了,第二个if的_IO_in_put_mode宏测试了我们的flags中的_IO_CURRENTLY_PUTTING标志位,在我们的情况下(0xfbad2488)显然是不成立的,因此继续往下看:

#define _IO_have_markers(fp) ((fp)->_markers != NULL)
#define _IO_have_backup(fp) ((fp)->_IO_save_base != NULL)

  if (fp->_IO_read_ptr < fp->_IO_read_end)
    //...
  if (_IO_in_backup (fp))
    {
      //...
    }
  if (_IO_have_markers (fp))
    {
      //...
    }
  else if (_IO_have_backup (fp))
    //...
  return _IO_UNDERFLOW (fp);
}

  后续的if也全都不满足,最终调用_IO_UNDERFLOW:

# define _IO_new_file_underflow _IO_file_underflow
#define _IO_NO_READS 4 /* Reading not allowed */

int
_IO_new_file_underflow (_IO_FILE *fp)
{
  _IO_ssize_t count;

  if (fp->_flags & _IO_NO_READS)
    {
      //...
    }
  if (fp->_IO_read_ptr < fp->_IO_read_end)
    //...

  if (fp->_IO_buf_base == NULL)
    {
      //...
    }

  /* Flush all line buffered files before reading. */
  /* FIXME This can/should be moved to genops ?? */
  if (fp->_flags & (_IO_LINE_BUF|_IO_UNBUFFERED))
    {
      //...
    }
  _IO_switch_to_get_mode (fp);

  这个函数也十分类似,一开始的if都不满足,直接调用了_IO_switch_to_get_mode:

int
_IO_switch_to_get_mode (_IO_FILE *fp)
{
  if (fp->_IO_write_ptr > fp->_IO_write_base)
    //...
  if (_IO_in_backup (fp))
    //...
  else
    {
      fp->_IO_read_base = fp->_IO_buf_base;
      if (fp->_IO_write_ptr > fp->_IO_read_end)
        fp->_IO_read_end = fp->_IO_write_ptr;
    }
  fp->_IO_read_ptr = fp->_IO_write_ptr;

  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = fp->_IO_read_ptr;

  fp->_flags &= ~_IO_CURRENTLY_PUTTING;
  return 0;
}
libc_hidden_def (_IO_switch_to_get_mode)

  这里对fp指针的一系列成员进行了设置,并且去掉了_IO_CURRENTLY_PUTTING标志位,然后控制流返回到_IO_file_underflow:

  /* This is very tricky. We have to adjust those
     pointers before we call _IO_SYSREAD () since
     we may longjump () out while waiting for
     input. Those pointers may be screwed up. H.J. */
  fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_buf_base;
  fp->_IO_read_end = fp->_IO_buf_base;
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
    = fp->_IO_buf_base;

  count = _IO_SYSREAD (fp, fp->_IO_buf_base,
                       fp->_IO_buf_end - fp->_IO_buf_base);

  同样对fp的成员进行了很多设置,然后调用了vtable里的_IO_SYSREAD(第三个参数注意一下,程序是一次读取了堆块大小的字节),在看这个函数之前我们先看一下此时fp指针的状况:

pwndbg> p *(struct _IO_FILE_plus*)0x602010
$51 = {
  file = {
    _flags = -72539000,
    _IO_read_ptr = 0x602240 "",
    _IO_read_end = 0x602240 "",
    _IO_read_base = 0x602240 "",
    _IO_write_base = 0x602240 "",
    _IO_write_ptr = 0x602240 "",
    _IO_write_end = 0x602240 "",
    _IO_buf_base = 0x602240 "",
    _IO_buf_end = 0x603240 "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = 3,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x6020f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x602100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

  _IOSYSRAED就是vtable中的\_read,其别名又是_IO_file_read:

#define _IO_FLAGS2_NOTCANCEL 2

_IO_ssize_t
_IO_file_read (_IO_FILE *fp, void *buf, _IO_ssize_t size)
{
  return (__builtin_expect (fp->_flags2 & _IO_FLAGS2_NOTCANCEL, 0)
          ? read_not_cancel (fp->_fileno, buf, size)
          : read (fp->_fileno, buf, size));
}
libc_hidden_def (_IO_file_read)

  我们的fp->_flags2为0,因此会调用read_not_cancel,其定义如下:

/* Uncancelable read.  */
#define read_not_cancel(fd, buf, n) \
  __read_nocancel (fd, buf, n)
#define __read_nocancel(fd, buf, len) \
  INLINE_SYSCALL (read, 3, fd, buf, len)

  其实到了这里就可以知道已经进入系统调用了,跟fopen类似,fopen调用了系统open,fread调用系统read,这是肯定的。因此读取完了之后控制流返回到了_IO_new_file_underflow:

# define _IO_pos_BAD ((_IO_off64_t) -1)

  if (count <= 0)
  {
      //...
  }
  fp->_IO_read_end += count;
  if (count == 0)
    {
      //...
    }
  if (fp->_offset != _IO_pos_BAD)
    //...
  return *(unsigned char *) fp->_IO_read_ptr;
}

  _IO_pos_BAD其实就是-1,而我们的_offset也是-1,因此if不执行,underflow函数终于执行完毕,返回到了_IO_file_xsgetn:

              if (__underflow (fp) == EOF)
                break;

              continue;
            }

  __unferflow返回的是_IO_read_ptr的第一个字节,我们的文件是有内容的,不为EOF,因此continue返回到了while:

  while (want > 0)
    {
      have = fp->_IO_read_end - fp->_IO_read_ptr;
      if (want <= have)
        {
          memcpy (s, fp->_IO_read_ptr, want);
          fp->_IO_read_ptr += want;
          want = 0;
        }
      else
        {
          //...
        }
      }
  return n - want;
}

  根据want参数,也就是我们调用fread时计算的size*count,把_IO_FILE的缓冲区的内容复制到我们调用fread的第一个参数(目标缓冲区),增加_IO_read_ptr,然后最终返回我们读取的字节数,控制流终于回到fread:

  bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
  _IO_release_lock (fp);
  return bytes_requested == bytes_read ? count : bytes_read / size;
}
libc_hidden_def (_IO_fread)

  最后就是释放锁,函数返回,返回值就是fread的参数count,程序回到main。

  所以对fread的分析也结束了,内容也不少啊。


fclose

  上一节我们分析过了一遍fread读取文件的过程,那么文件操作结束后自然要用fclose关闭文件里,这一节我们就好好分析一下fclose的原理,其源码如下:

# define _IO_new_fclose fclose
# define CHECK_FILE(FILE, RET) COERCE_FILE (FILE)
# define COERCE_FILE(FILE) /* Nothing */
#define _IO_file_flags _flags
#define _IO_IS_FILEBUF 0x2000

int
_IO_new_fclose (_IO_FILE *fp)
{
  int status;

  CHECK_FILE(fp, EOF);

#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
  //...
#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);

  同样CHECK_FILE什么也不做,然后检查了_IO_IS_FILEBUF的标志位,我们的fp->_flags为0xfbad2488,显然是满足的,因此进入_IO_un_link,这个函数比较琐碎,只挑其中有用的看一下就行了:

      else if (fp == _IO_list_all)
        {
          _IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
          ++_IO_list_all_stamp;
        }
      fp->file._flags &= ~_IO_LINKED;

  我们的文件fp指针被从_IO_FILE链表中脱链,_IO_list_all重新指向_IO_2_1stderr\,并且fp->_flags的_IO_LINKED标志位被取消,返回到fclose,_IO_acquire_lock我们也跳过,继续看:

  if (fp->_IO_file_flags & _IO_IS_FILEBUF)
    status = _IO_file_close_it (fp);

  调用了_IO_file_close_it:

# define _IO_new_file_close_it _IO_file_close_it
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)
#define _IO_NO_WRITES 8 /* Writing not allowd */
#define _IO_CURRENTLY_PUTTING 0x800

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)
    //...
  else
    write_status = 0;

  _IO_unsave_markers (fp);

  此时我们的flags为0xfbad2408,因此if不满足,write_status被置0,然后调用了_IO_unsave_markers:

#define _IO_have_backup(fp) ((fp)->_IO_save_base != NULL)

void
_IO_unsave_markers (_IO_FILE *fp)
{
  struct _IO_marker *mark = fp->_markers;
  if (mark)
    {
      //...
    }

  if (_IO_have_backup (fp))
    //...
}
libc_hidden_def (_IO_unsave_markers)

  什么都没做就返回了,回到fclose继续看:

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

  fp->_flags2仍然为0,因此调用了vtable的_IO_SYSCLOSE,其实就是_IO_file_close:

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

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);
}
libc_hidden_def (_IO_file_close)

  这个close_not_cancel就是直接到了系统调用号为3的SYS_close(肯定绕不过系统)系统调用,其返回值为0,代表成功关闭,然后控制流返回到_IO_new_file_close_it:

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

  fp->_mode为-1,因此不进入if,调用了_IO_setb:

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)
    //...
  else
    f->_flags |= _IO_USER_BUF;
}
libc_hidden_def (_IO_setb)

  这一次释放了我们第一次调用fread分配的0x1000大小的临时缓冲区,然后把_IO_buf_base和end置0,并设置了f->_flags的_IO_USER_BUF标志位后返回:

  _IO_setg (fp, NULL, NULL, NULL);
  _IO_setp (fp, NULL, NULL);
  _IO_un_link ((struct _IO_FILE_plus *) fp);

  他们的宏定义如下:

#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))

  他们把_IOread*和_IOwrite\*都置0,然后调用了_IO_un_link:

void
_IO_un_link (struct _IO_FILE_plus *fp)
{
  if (fp->file._flags & _IO_LINKED)
    {
      //...
    }
}

  这一次直接返回了,没有任何操作。_IO_new_file_close_it的最后部分如下:

#define _IO_pos_BAD ((_IO_off64_t)(-1))

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

  return close_status ? close_status : write_status;
}

  设置了fp->_flags的标志位,把句柄(文件描述符)和_offset设置为-1,close_status就是之前调用_IO_SYSCLOSE的返回值,也就是SYS_close系统调用的返回值0,因此返回值为write_status,write_status之前我们看到过了为0,因此返回值为0代表成功。然后控制流返回到了fclose:

  _IO_release_lock (fp);
  _IO_FINISH (fp);

  跳过_IO_release_lock,我们看_IO_FINISH:

#define _IO_FINISH(FP) JUMP1 (__finish, FP, 0)
#define _IO_file_is_open(__fp) ((__fp)->_fileno != -1)

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)

  显然我们的fp文件指针已经关闭,因此不满足if条件,调用了_IO_default_finish:

/* The way the C++ classes are mapped into the C functions in the
   current implementation, this function can get called twice! */

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

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

  if (fp->_IO_save_base)
    {
      //...
    }

  _IO_un_link ((struct _IO_FILE_plus *) fp);

#ifdef _IO_MTSAFE_IO
   //...
#endif
}
libc_hidden_def (_IO_default_finish)

  事实上我在gdb跟踪的时候_IO_un_link没被调用,可能被编译器优化掉了?不管这个,返回到fclose继续往下看:

#define _IO_have_backup(fp) ((fp)->_IO_save_base != NULL)

  if (fp->_mode > 0)
    {
      //...
    }
  else
    {
      if (_IO_have_backup (fp))
        //...
    }
  if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
    {
      fp->_IO_file_flags = 0;
      free(fp);
    }

  return status;
}

  fp为我们自己的文件指针,当然不属于任何一个std结构,因此承载fp的堆块被free了,至此我们的文件指针算是彻彻底底被清理了,然后fclose也返回了,分析也结束了,这个比较简单,所以最后来一张被free之前的快照吧:D

pwndbg> p *(struct _IO_FILE_plus*)0x602010
$69 = {
  file = {
    _flags = -72539124,
    _IO_read_ptr = 0x0,
    _IO_read_end = 0x0,
    _IO_read_base = 0x0,
    _IO_write_base = 0x0,
    _IO_write_ptr = 0x0,
    _IO_write_end = 0x0,
    _IO_buf_base = 0x0,
    _IO_buf_end = 0x0,
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2540 <_IO_2_1_stderr_>,
    _fileno = -1,
    _flags2 = 0,
    _old_offset = 0,
    _cur_column = 0,
    _vtable_offset = 0 '\000',
    _shortbuf = "",
    _lock = 0x6020f0,
    _offset = -1,
    _codecvt = 0x0,
    _wide_data = 0x602100,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0,
    _mode = -1,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
pwndbg>

发表评论

textsms
account_circle
email

All about fanda

GLIBC2.23 _IO_FILE相关函数原理分析
puts   老规矩,话不多说,直接上源码,看如下最简单的输出代码: #include <stdio.h> int main() { puts("hello,world"); puts("end"); …
扫描二维码继续阅读
2019-09-15