이제 마지막으로 fclose를 분석함으로서 파일 관련 분석을 끝낼 것입니다. 이번에도 마찬가지로
#include <stdio.h>
int main(){
FILE* f = fopen("tmp", "w");
fclose(f);
}
이런식으로 간단한 프로그램을 짜주시고 디버깅을 해보시면서 함수를 보시면 됩니다. 먼저 fclose도 fwrite와 fread와 비슷하게 fclose는 _IO_new_fclose의 약어입니다.
int
_IO_new_fclose (FILE *fp)
{
int status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* 우리는 필사적으로 이상한 방법으로 스트림을 사용하고, 예전과 새로운 함수를 섞은 프로그램들을
도우려고 시도한다. 여기서 오래된 스트림들을 탐지한다.
*/
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif
/* 스트림을 연결 해제 시킨다. */
if (fp->_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
/* 이 스트림은 넓은 방향을 가지고 있다. 이것은 우리가 전환 함수를 free해야 된다는 것을 의미한다. */
struct _IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_flags = 0;
free(fp);
}
return status;
}
versioned_symbol (libc, _IO_new_fclose, _IO_fclose, GLIBC_2_1);
strong_alias (_IO_new_fclose, __new_fclose)
versioned_symbol (libc, __new_fclose, fclose, GLIBC_2_1);
fclose를 할 때 먼저 스트림을 unlink를 시킨 뒤에 파일 디스크립터를 free시키게 됩니다. _IO_un_link 함수에 대한 분석은
2019/09/12 - [힙(heap)/house_of_orange] - house_of_orange 번외 - fopen함수 분석(glibc 2.23)
이 글의 마지막 부분에 있기 때문에 참고해주시기 바랍니다.
그 다음으로 볼 함수는 _IO_file_close_it입니다. 그런데 이 함수는 _IO_new_file_close_it의 약어이므로 _IO_new_file_close_it을 분석해야 합니다.
#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)))
int
_IO_new_file_close_it (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)
write_status = _IO_do_flush (fp);
else
write_status = 0;
_IO_unsave_markers (fp);
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
/* 버퍼를 free시킨다. */
if (fp->_mode > 0)
{
if (_IO_have_wbackup (fp))
_IO_free_wbackup_area (fp);
_IO_wsetb (fp, NULL, NULL, 0);
_IO_wsetg (fp, NULL, NULL, NULL);
_IO_wsetp (fp, NULL, NULL);
}
_IO_setb (fp, NULL, NULL, 0);
_IO_setg (fp, NULL, NULL, NULL);
_IO_setp (fp, NULL, NULL);
_IO_un_link ((struct _IO_FILE_plus *) fp);
fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
return close_status ? close_status : write_status;
}
libc_hidden_ver (_IO_new_file_close_it, _IO_file_close_it)
이 함수에서는 _IO_do_flush를 통해 버퍼를 비우는 것과 더불어 파일을 닫고 각종 포인터들을 초기화 시키고 있습니다. 여기에서 특이한 점이 _IO_SYSCLOSE부분인데 이것은 vtable의 close부분을 호출하게 됩니다. 하지만 이 close는 _IO_file_close을 가리키고 있습니다. 즉 자기 자신을 한번더 호출하는 것입니다.
다음으로 봐야할 것은 _IO_FINSH함수인데 이 함수는 vtable의 finish를 호출하게 됩니다. 즉 이 상황에서는
2019/09/13 - [힙(heap)/house_of_orange] - house_of_orange 번외 fread 분석 (glibc 2.23)
2019/09/12 - [힙(heap)/house_of_orange] - house_of_orange 번외 - fopen함수 분석(glibc 2.23)
이 두 글에서 보셨다시피 _IO_new_file_finish가 실행이 됩니다.
const struct _IO_jump_t _IO_file_jumps libio_vtable =
{
JUMP_INIT_DUMMY,
JUMP_INIT(finish, _IO_file_finish),
JUMP_INIT(overflow, _IO_file_overflow),
JUMP_INIT(underflow, _IO_file_underflow),
JUMP_INIT(uflow, _IO_default_uflow),
JUMP_INIT(pbackfail, _IO_default_pbackfail),
JUMP_INIT(xsputn, _IO_file_xsputn),
JUMP_INIT(xsgetn, _IO_file_xsgetn),
JUMP_INIT(seekoff, _IO_new_file_seekoff),
JUMP_INIT(seekpos, _IO_default_seekpos),
JUMP_INIT(setbuf, _IO_new_file_setbuf),
JUMP_INIT(sync, _IO_new_file_sync),
JUMP_INIT(doallocate, _IO_file_doallocate),
JUMP_INIT(read, _IO_file_read),
JUMP_INIT(write, _IO_new_file_write),
JUMP_INIT(seek, _IO_file_seek),
JUMP_INIT(close, _IO_file_close),
JUMP_INIT(stat, _IO_file_stat),
JUMP_INIT(showmanyc, _IO_default_showmanyc),
JUMP_INIT(imbue, _IO_default_imbue)
};
#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)))
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)
여기에서는 _IO_do_flush를 통해 버퍼를 비우고, _IO_default_finish를 호출하고 있습니다.
void
_IO_default_finish (FILE *fp, int dummy)
{
struct _IO_marker *mark;
if (fp->_IO_buf_base && !(fp->_flags & _IO_USER_BUF)) //할당했던 버퍼 free
{
free (fp->_IO_buf_base);
fp->_IO_buf_base = fp->_IO_buf_end = NULL;
}
for (mark = fp->_markers; mark != NULL; mark = mark->_next)
mark->_sbuf = NULL;
if (fp->_IO_save_base) //백업이 있다면 백업을 free시킨다.
{
free (fp->_IO_save_base);
fp->_IO_save_base = NULL;
}
_IO_un_link ((struct _IO_FILE_plus *) fp); //_IO_list_all과의 link를 해제한다.
#ifdef _IO_MTSAFE_IO
if (fp->_lock != NULL)
_IO_lock_fini (*fp->_lock);
#endif
}
libc_hidden_def (_IO_default_finish)
이렇게 할당했던 버퍼들을 free시키고 있는 모습을 볼 수 있습니다. 이렇게 fclose에 쓰이는 함수 분석이 끝났습니다. 마지막으로 fclose의 호출과정을 그림으로 나타내면 이렇게 됩니다.
'FSOP' 카테고리의 다른 글
setvbuf 함수 분석 (0) | 2019.09.25 |
---|---|
FSOP - seethefile(pwnable.tw) (0) | 2019.09.20 |
fwrite 분석 (0) | 2019.09.13 |
fread 분석 (0) | 2019.09.13 |
fopen함수 분석 (0) | 2019.09.12 |