fwrite함수

dandb3·2023년 5월 30일
0

pwnable

목록 보기
7/17

이번에는 fwrite함수를 알아보자.

#define fwrite(p, n, m, s) _IO_fwrite (p, n, m, s)

size_t
_IO_fwrite (const void *buf, size_t size, size_t count, FILE *fp)
{
  size_t request = size * count;
  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;
}
libc_hidden_def (_IO_fwrite)
  • _IO_sputn
    인줄 알았지만 fread의 경우와 같이 엄청난 매크로를 거쳐서 _IO_new_file_xsputn을 호출하게 된다.

  • _IO_new_file_xsputn

    size_t
    _IO_new_file_xsputn (FILE *f, const void *data, size_t n)
    {
      const char *s = (const char *) data;
      size_t to_do = n;
      int must_flush = 0;
      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;
          f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
          s += count;
          to_do -= count;
        }
      if (to_do + must_flush > 0)
        {
          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)
    • fwrite의 경우는 fread와 다르게 버퍼가 가득 차면 flush를 해주는 것이므로 _IO_write_end는 항상 _IO_buf_end를 가리키게 된다.

    • to_do : 출력할 문자 수

    • count : 입력이 가능한 남은 버퍼

    • must_flush : LINE_BUFFERED의 경우, 버퍼가 꽉 차는 경우가 아닐지라도 중간에 개행이 나오면 flush를 해주어야 하므로 이 때 쓰이는 bool 형태의 변수이다.

    • 자세히 알아보자.

      • count를 세 준다.
        • count = _IO_write_end - _IO_write_ptr
        • LINE_BUFFERED && (count >= n) : count = 마지막 개행의 위치 - _IO_write_ptr, 그 후에 must_flush = 1로 세팅.
      • 필요한 양만큼 읽어들여 버퍼에 써 준다.
      • 버퍼가 꽉 찼거나, must_flush == 1인 경우
        • _IO_overflow 호출 -> 버퍼 비워줌.
      • do_write = 버퍼 사이즈의 배수
        • new_do_write로 do_write 만큼의 write를 해준다.
      • 그래도 아직 to_do가 남아있다면, _IO_default_xsputn을 통해 나머지를 write해준다.
    • 알아야 할 함수들 : _IO_overflow, new_do_write, _IO_default_xsputn

    • _IO_overflow

      int
      _IO_new_file_overflow (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);
              }
              
            ...(생략)...
      
            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;
        ...(생략)...
      }
      libc_hidden_ver (_IO_new_file_overflow, _IO_file_overflow)

      _IO_new_file_overflow함수는 크게 2가지 경우로 나눌 수 있다.

      1. 처음 fwrite호출이 일어난 경우 (_IO_CURRENTLY_PUTTINGflag가 꺼져 있는 상태) or 버퍼가 할당되지 않은 경우
        • 버퍼가 없으면 할당
        • _IO_CURRENTLY_PUTTING flag를 세팅해줌.
        • 포인터들 초기화
      2. ch == EOF인 경우 (호출 시에 인자로 ch = EOF를 넣어주었기 때문에 이 부분이 실행된다.)
        • flush 작업 수행 (_IO_do_write 호출)
    • _IO_new_do_write

      int
      _IO_new_do_write (FILE *fp, const char *data, size_t to_do)
      {
        return (to_do == 0
            || (size_t) new_do_write (fp, data, to_do) == to_do) ? 0 : EOF;
      }
      libc_hidden_ver (_IO_new_do_write, _IO_do_write)
      
      static size_t
      new_do_write (FILE *fp, const char *data, size_t to_do)
      {
        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)
          {
            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;
      }

      함수의 앞부분은 offset을 바꾸어 주는 부분이다.
      그 후에 _IO_SYSWRITE을 통해 write syscall을 호출하게 된다.

    이제 거의 다 끝났다. 마지막으로 _IO_default_xsputn함수를 보도록 하자.

    • _IO_default_xsputn

      size_t
      _IO_default_xsputn (FILE *f, const void *data, size_t n)
      {
        const char *s = (char *) data;
        size_t more = n;
        if (more <= 0)
          return 0;
        for (;;)
          {
            /* Space available. */
            if (f->_IO_write_ptr < f->_IO_write_end)
              {
                size_t count = f->_IO_write_end - f->_IO_write_ptr;
                if (count > more)
                  count = more;
                if (count > 20)
                  {
                    f->_IO_write_ptr = __mempcpy (f->_IO_write_ptr, s, count);
                    s += count;
                  }
                else if (count)
                  {
                    char *p = f->_IO_write_ptr;
                    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)
      
      #define _IO_do_flush(_f) \
        ((_f)->_mode <= 0							      \
         ? _IO_do_write(_f, (_f)->_IO_write_base,				      \
                (_f)->_IO_write_ptr-(_f)->_IO_write_base)		      \
         : _IO_wdo_write(_f, (_f)->_wide_data->_IO_write_base,		      \
                 ((_f)->_wide_data->_IO_write_ptr			      \
                  - (_f)->_wide_data->_IO_write_base)))

      default라 그런가.. 굉장히 함수가 직관적이다.
      블럭 단위로 write하고 남은 것들을 쓸 때에 호출되는 함수인데, 주목할 점은 2군데가 있다.

      1. _IO_OVERFLOW호출 시 EOF가 인자로 들어가지 않고, *s++가 들어감.
      2. _IO_OVERFLOW호출 후에 more--; 부분.

      답은 _IO_OVERFLOW함수를 보면 알 수 있는데,

      *f->_IO_write_ptr++ = ch;

      요 부분에서 버퍼에다가 인자로 들어온 값을 하나 써 주게 되고, 쓴 만큼 more에서 빼주게 된다. 그래서 결국 원하는대로 동작하는 것을 알 수 있다.

이 정도로 정리하면 될 듯. 다음은 fclose에 대해서 알아볼 것이다.

profile
공부 내용 저장소

0개의 댓글