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 |