Profile

youngsouk

youngsouk

setvbuf 함수 분석

z이전 글들과 같이 setvbuf를 이용한 프로그램을 하나 짜고 실행을 시켜보면서 함수들을 알아내고 glibc에서 그 함수들을 볼 수 있습니다.

#include <stdio.h>

int main(){
        setvbuf(stdin, 0LL, 2, 0);
}
#define _IOFBF 0 /* 완전 버퍼링. */
#define _IOLBF 1 /* 줄 버퍼링. */
#define _IONBF 2 /* 버퍼링 하지 않음. */

int
_IO_setvbuf (_IO_FILE *fp, char *buf, int mode, _IO_size_t size)
{
  int result;
  CHECK_FILE (fp, EOF);
  _IO_acquire_lock (fp);
  switch (mode) // mode에 따라 다르게 작동한다.
    {
    case _IOFBF:
      fp->_IO_file_flags &= ~(_IO_LINE_BUF|_IO_UNBUFFERED);
      if (buf == NULL)
	{
	  if (fp->_IO_buf_base == NULL)
	    {
	      /* 
         완전 버퍼 모드나 줄 버퍼 모드가 명백하게 셋팅 되어있다는 것을 구분할 flag가 없다.
		 두가지 경우세서도, _IO_LINE_BUF 는 꺼져있다. 만약 이것이 tty이고,
		 _IO_filedoalloc이 나중에 호출이 된다면, 그것은 _IO_LINE_BUF flag를 셋팅되어야
         하는지 아닌지 모른다.  (왜냐하면 그것은 기본값이기 떄문이다.), 또는 아니다(왜냐하면 우리는 분명하게
         완전 버퍼링 모드로 요청을 받았기 때문이다. 그래서 우리는 버퍼가 현재 할당이 되어있는지 
         확실히 해야하고, 분명하게 줄 버퍼링을 꺼야하기 때문이다.
		 대체 가능한 클리너가 추가 flag를 추가할 수 있지만 flag는 유한한 자원이다.
         */
	      if (_IO_DOALLOCATE (fp) < 0) // 버퍼 할당
		{
		  result = EOF;
		  goto unlock_return;
		}
	      fp->_IO_file_flags &= ~_IO_LINE_BUF;
	    }
	  result = 0;
	  goto unlock_return;
	}
      break;
    case _IOLBF:
      fp->_IO_file_flags &= ~_IO_UNBUFFERED;
      fp->_IO_file_flags |= _IO_LINE_BUF;
      if (buf == NULL)
	{
	  result = 0;
	  goto unlock_return;
	}
      break;
    case _IONBF:
      fp->_IO_file_flags &= ~_IO_LINE_BUF;
      fp->_IO_file_flags |= _IO_UNBUFFERED;
      buf = NULL;
      size = 0;
      break;
    default:
      result = EOF;
      goto unlock_return;
    }
  result = _IO_SETBUF (fp, buf, size) == NULL ? EOF : 0;

unlock_return:
  _IO_release_lock (fp);
  return result;
}
libc_hidden_def (_IO_setvbuf)

#ifdef weak_alias
weak_alias (_IO_setvbuf, setvbuf)
#endif

우리가 설정해준 mode에 따라 flag를 셋팅하고 buf와 size를 설정해주게 됩니다. 버퍼를 할당하려 할 때는 _IO_DOALLOCATE를 통해 할당을 하게 되는데 이 함수는

2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 심화준비 fread 분석 (glibc 2.23)

이글에서 보실 수 있습니다. 할당에 실패하거나 예외 사항이 발생하게 되면 unlock_return을 실행하게 됩니다. 그 다음으로 주의 깊게 봐야할 함수는 이 함수에서 거의 필수적으로 실행이 되게 되는  _IO_SETBUF라는 함수입니다.

#define _IO_SETBUF(FP, BUFFER, LENGTH) JUMP2 (__setbuf, FP, BUFFER, LENGTH)

#define JUMP2(FUNC, THIS, X1, X2) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1, X2)

#if _IO_JUMPS_OFFSET
# define _IO_JUMPS_FUNC(THIS) \
 (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) \
			   + (THIS)->_vtable_offset))
# define _IO_vtable_offset(THIS) (THIS)->_vtable_offset
#else
# define _IO_JUMPS_FUNC(THIS) _IO_JUMPS_FILE_plus (THIS)
# define _IO_vtable_offset(THIS) 0
#endif

매우 복잡하게 보이는 매크로 함수이지만 간단히 하자면 vtable안의 __setbuf 함수를 실행시키게 됩니다.

보통 vtable의 setbuf를 실행하면  _IO_new_file_setbuf를 실행시키게 됩니다.

FILE *
_IO_new_file_setbuf (FILE *fp, char *p, ssize_t len)
{
  if (_IO_default_setbuf (fp, p, len) == NULL)
    return NULL;
  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
    = fp->_IO_buf_base;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  return fp;
}
libc_hidden_ver (_IO_new_file_setbuf, _IO_file_setbuf)

_IO_default_setbuf를 통해 

파일 구조체의 여러가지 포인터들을 초기화해주고 있습니다. _IO_setg에 관한 부분은 아래 글에서 보실 수 있습니다.

2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 심화준비 fread 분석 (glibc 2.23)

FILE *
_IO_default_setbuf (FILE *fp, char *p, ssize_t len)
{
    if (_IO_SYNC (fp) == EOF)
        return NULL;
    if (p == NULL || len == 0)
      {
        fp->_flags |= _IO_UNBUFFERED;
        _IO_setb (fp, fp->_shortbuf, fp->_shortbuf+1, 0);
      }
    else
      {
        fp->_flags &= ~_IO_UNBUFFERED;
        _IO_setb (fp, p, p+len, 0);
      }
    fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end = 0;
    fp->_IO_read_base = fp->_IO_read_ptr = fp->_IO_read_end = 0;
    return fp;
}
#define _IO_SYNC(FP) JUMP0 (__sync, FP)

_IO_SYNC는 파일 구조체의 vtable의  __sync를 실행시키게 되는데 이것은 보통 _IO_new_file_sync를 실행시키게 됩니다.

#define	ESPIPE		29	/* 잘못 찾을 경우*/
int
_IO_new_file_sync (_IO_FILE *fp)
{
  _IO_ssize_t delta;
  int retval = 0;

  /*    char* ptr = cur_ptr(); */
  if (fp->_IO_write_ptr > fp->_IO_write_base)
    if (_IO_do_flush(fp)) return EOF;
  delta = fp->_IO_read_ptr - fp->_IO_read_end;
  if (delta != 0)
    {
#ifdef TODO // 아직 뭔가 고칠 데가 있다.
      if (_IO_in_backup (fp))
	delta -= eGptr () - Gbase ();
#endif
      _IO_off64_t new_pos = _IO_SYSSEEK (fp, delta, 1);
      if (new_pos != (_IO_off64_t) EOF)
	fp->_IO_read_end = fp->_IO_read_ptr;
#ifdef ESPIPE
      else if (errno == ESPIPE)
	; /* 찾을 수 없는 기기의 오류는 무시한다. */
#endif
      else
	retval = EOF;
    }
  if (retval != EOF)
    fp->_offset = _IO_pos_BAD;
  /* 잠재적인 문제가 있다 : Cleanup - 이것이 공유될 수 있나요? */
  /*    setg(base(), ptr, ptr); */
  return retval;
}
libc_hidden_ver (_IO_new_file_sync, _IO_file_sync)

버퍼에 내용이 남아 있었다면 _IO_do_flush를 호출해 버퍼를 지워주게 됩니다. 이것에 관한것은

2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 심화준비 - fclose 분석

이 부분에서 보실 수 있습니다.

이 함수의 조건문을 보시면 다 버퍼에 어떤 데이터가 있을 시 참이 되는 것을 알 수 있습니다. 즉 함수 이름답게 파일을 최신으로 동기화 시켜주게 되는 것입니다.


그 다음은 파일 구조체의 flag를 알맞게 셋팅한 뒤에 _IO_setb를 통해 buf포인터들의 값을 버퍼링 공간으로 설정을 해주게 됩니다.

_IO_setb에 대해서는 

2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 심화준비 fread 분석 (glibc 2.23)

이 글에 나와있습니다.

이렇게 buf포인터들을 셋팅한 뒤에는 read와 write와 관련된 포인터들을 0으로 셋팅해줌으로서 초기화를 해주게 됩니다.(

_IO_default_setbuf)

그 다음으로 _IO_new_file_setbuf의 남은 부분을 실행시키게 되는데 

  fp->_IO_write_base = fp->_IO_write_ptr = fp->_IO_write_end
    = fp->_IO_buf_base;
  _IO_setg (fp, fp->_IO_buf_base, fp->_IO_buf_base, fp->_IO_buf_base);
  return fp;

이렇게 아까 초기화 했던 write와 read 관련 포인터를 buf의 주소로 초기화 해주는 것을 볼 수 있습니다.

_IO_setg는

2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 심화준비 fread 분석 (glibc 2.23)

이 글에 나와있습니다. 이렇게 setvbuf 함수 분석이 끝났습니다. 마지막으로 함수의 실행 순서와 기능을 그림으로 정리를 하자면

이렇게 됩니다.

'FSOP' 카테고리의 다른 글

FSOP - seethefile(pwnable.tw)  (0) 2019.09.20
fclose 분석  (0) 2019.09.13
fwrite 분석  (0) 2019.09.13
fread 분석  (0) 2019.09.13
fopen함수 분석  (0) 2019.09.12