반응형

방학이 된지 벌써 3주차에 접어들었는데 늘어난건 메이플 레벨뿐....

 

그래서 전부터 살짝 신경쓰였던 stdin, stdout를 사용하면 동적할당이 왜 되는건지 찾아보게되었다.

 

먼저 분석하기 쉽고 stdout를 사용하는 함수인 puts를 살펴보겠다.

 

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

int
_IO_puts (const char *str)
{
  int result = EOF;
  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;
}

weak_alias (_IO_puts, puts)
libc_hidden_def (_IO_puts)

 

간단하게만 정리하면, 원하는 길이만큼 출력하기 위해서 _IO_sputn함수를 사용하는 것을 볼 수 있다.

 

이는 매크로형태로 정의되어있는데 아래의 흐름대로 코드가 구성된다.

#define _IO_sputn(__fp, __s, __n) _IO_XSPUTN (__fp, __s, __n)
#define _IO_XSPUTN(FP, DATA, N) JUMP2 (__xsputn, FP, DATA, N)
#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)
#define _IO_JUMPS_FUNC(THIS) \
  (IO_validate_vtable                                                   \
   (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS)        \
                             + (THIS)->_vtable_offset)))
#define _IO_JUMPS_FILE_plus(THIS) \
  _IO_CAST_FIELD_ACCESS ((THIS), struct _IO_FILE_plus, vtable)

#define _IO_CAST_FIELD_ACCESS(THIS, TYPE, MEMBER) \
  (*(_IO_MEMBER_TYPE (TYPE, MEMBER) *)(((char *) (THIS)) \
                                       + offsetof(TYPE, MEMBER)))
#define _IO_MEMBER_TYPE(TYPE, MEMBER) __typeof__ (((TYPE){}).MEMBER)
  
// ((_IO_FILE_plus *)&_IO_stdout)->vtable.__xsputn(stdout, "Hello World", 11)

 

그러므로 gdb에서 다음과 같이 출력해서 현재 어떤 함수가 설정되어있는지 직접 확인해보았다.

 

stdout->vtable

 

여기서 호출되는건 __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)

 

출력할 문자의 길이 + must_flush의 합이 0보다 크게 될경우,  이전에 출력한 크기 이상으로 문자열 출력이 왔다는 것이므로 _IO_OVERFLOW 함수를 호출하게 된다. 하지만 이것도 매크로라서 펼쳐보면 아래와 같다.

 

((struct _IO_FILE_plus *)stdout)->__overflow) (f, EOF)

 

그러므로 위의 그림에서 보인 stdout->vtable을 다시 본 후, 해당 함수를 구글링하면 아래와 같이 구현되어있다.

 

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

 

이처럼 현재 버퍼로 사용할 메모리가 존재하지 않을경우 할당을 하게되는데 _IO_doallocbuf함수를 사용해서 적당하게 버퍼를 생성해주는 것을 볼 수 있다. 이 이후에 여러 ptr들을 설정해주고 할당된 버퍼를 사용하게 된다.

 

void
_IO_doallocbuf (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)

// ((struct _IO_FILE_plus *)stdout)->vtable.__doallocate == __GI__IO_file_doallocate

int
_IO_file_doallocate (FILE *fp)
{
  size_t size;
  char *p;
  struct stat64 st;

  size = BUFSIZ;
  if (fp->_fileno >= 0 && __builtin_expect (_IO_SYSSTAT (fp, &st), 0) >= 0)
    {
      if (S_ISCHR (st.st_mode))
	{
	  /* Possibly a tty.  */
	  if (
#ifdef DEV_TTY_P
	      DEV_TTY_P (&st) ||
#endif
	      local_isatty (fp->_fileno))
	    fp->_flags |= _IO_LINE_BUF;
	}
#if defined _STATBUF_ST_BLKSIZE
      if (st.st_blksize > 0 && st.st_blksize < BUFSIZ)
	size = st.st_blksize;
#endif
    }
  p = malloc (size);
  if (__glibc_unlikely (p == NULL))
    return EOF;
  _IO_setb (fp, p, p + size, 1);
  return 1;
}
libc_hidden_def (_IO_file_doallocate)

 

결론을 내리자면 stdin, stdout은 처음에 NULL로 초기화가 되어있어서 사용하기 위해서는 버퍼를 동적으로 생성해줘야 한다는 점이다.

참고로 stderr은 이미 버퍼가 설정되어있으므로 어느정도 큰 문자열을 출력하지 않는 이상 동적할당하는 일은 없을 거다.

 

 

gdb-peda$ p *((struct _IO_FILE_plus *)stdout)
$9 = {
  file = {
    _flags = 0xfbad2084,
    _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 = 0x1,
    _flags2 = 0x0,
    _old_offset = 0xffffffffffffffff,
    _cur_column = 0x0,
    _vtable_offset = 0x0,
    _shortbuf = "",
    _lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
    _offset = 0xffffffffffffffff,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0x0,
    _mode = 0x0,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
gdb-peda$ p *((struct _IO_FILE_plus *)stderr)
$10 = {
  file = {
    _flags = 0xfbad2887,
    _IO_read_ptr = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_read_end = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_read_base = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_write_base = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_write_ptr = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_write_end = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_buf_base = 0x7ffff7dd25c3 <_IO_2_1_stderr_+131> "",
    _IO_buf_end = 0x7ffff7dd25c4 <_IO_2_1_stderr_+132> "",
    _IO_save_base = 0x0,
    _IO_backup_base = 0x0,
    _IO_save_end = 0x0,
    _markers = 0x0,
    _chain = 0x7ffff7dd2620 <_IO_2_1_stdout_>,
    _fileno = 0x2,
    _flags2 = 0x0,
    _old_offset = 0xffffffffffffffff,
    _cur_column = 0x0,
    _vtable_offset = 0x0,
    _shortbuf = "",
    _lock = 0x7ffff7dd3770 <_IO_stdfile_2_lock>,
    _offset = 0xffffffffffffffff,
    _codecvt = 0x0,
    _wide_data = 0x7ffff7dd1660 <_IO_wide_data_2>,
    _freeres_list = 0x0,
    _freeres_buf = 0x0,
    __pad5 = 0x0,
    _mode = 0xffffffff,
    _unused2 = '\000' <repeats 19 times>
  },
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

'공부' 카테고리의 다른 글

Fuzzing paper  (0) 2021.03.02
[how2heap] overlapping_chunks2  (0) 2019.05.22
[nodejs] mongoose를 이용한 로그인 구현  (0) 2019.05.08
xss payload  (0) 2019.04.19
[Windows Kernel Driver] 개발환경 구성  (0) 2018.11.25
블로그 이미지

KuroNeko_

KuroNeko

,