드림핵 강의를 보고 glibc-2.27을 분석한 글 입니다.
(틀린 내용이 있을 수 있습니다)
먼저, 드림핵에서 소개하는 예제 코드는 다음과 같다.
// Name: iofile.c
// Compile: gcc -o iofile iofile.c
#include <stdio.h>
#include <string.h>
#include <unistd.h>
void file_info(FILE *buf) {
printf("_flags: %x\n", buf->_flags);
printf("_fileno: %d", buf->_fileno);
}
int main() {
FILE *fp;
char buf[256];
strcpy(buf, "THIS IS TESTFILE!");
fp = fopen("testfile", "w");
fwrite(buf, 1, strlen(buf), fp);
file_info(fp);
fclose(fp);
return 0;
}
_IO_FILE
_IO_FILE은 리눅스 시스템의 표준 라이브러리에서 파일 스트림을 나타내기 위한 구조체이다.
파일을 열기 위한 fopen 함수를 사용할 때 힙 영역에 할당된다.
FILE.h를 보면 _IO_FILE 구조체를 데이터 타입으로 사용한다.
c에서 파일을 다룰 때 사용했던 FILE 타입은 ‘_IO_FILE 구조체의 구현’이라는 의미였던 걸로 이해했다.
#ifndef __FILE_defined
#define __FILE_defined 1
struct _IO_FILE;
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;
#endif
_IO_FILE 구조체는 libio.h에서 정의되고 있으며 다음과 같은 구조를 가지고 있다.
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
struct _IO_FILE
{
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
/* The following pointers correspond to the C++ streambuf protocol. */
char *_IO_read_ptr; /* Current read pointer */
char *_IO_read_end; /* End of get area. */
char *_IO_read_base; /* Start of putback+get area. */
char *_IO_write_base; /* Start of put area. */
char *_IO_write_ptr; /* Current put pointer. */
char *_IO_write_end; /* End of put area. */
char *_IO_buf_base; /* Start of reserve area. */
char *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno;
int _flags2;
__off_t _old_offset; /* This used to be _offset but it's too small. */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
각 멤버들의 의미는 다음과 같다.
_flags | 파일에 대한 읽기/쓰기/추가 권한을 의미 0xfbad0000 값을 매직 값으로, 하위 2바이트는 비트 플래그로 사용됨 |
_IO_read_ptr | 파일 읽기 버퍼에 대한 포인터 |
_IO_read_end | 파일 읽기 버퍼 주소의 끝을 가리키는 포인터 |
_IO_read_base | 파일 읽기 버퍼 주소의 시작을 가리키는 포인터 |
_IO_write_base | 파일 쓰기 버퍼 주소의 시작을 가리키는 포인터 |
_IO_write_ptr | 쓰기 버퍼에 대한 포인터 |
_IO_write_end | 파일 쓰기 버퍼 주소의 끝을 가리키는 포인터 |
_chain | 프로세스의 _IO_FILE 구조체는 _chain 필드를 통해 링크드 리스트를 만듦 링크드 리스트의 헤더는 라이브러리의 전역 변수인 _IO_list_all에 저장됨 |
_fileno | 파일 디스크립터의 값 |
_IO_jump_ t *vtable | 파일 관련 작업을 수행하는 가상 함수 테이블 |
_IO_FILE: _flags
_flags 멤버 변수는 파일의 성질을 나타내는 필드이다.
해당 필드는 fopen 함수로 파일을 열 때 전달한 모드에 따라 값이 설정된다.
_flags 변수를 구성하는 비트들은 libio.h에 정의되어 있다.
다음은 _flags의 일부이다.
#define _IO_MAGIC 0xFBAD0000 /* Magic number */
#define _IO_MAGIC_MASK 0xFFFF0000
#define _IO_USER_BUF 0x0001 /* Don't deallocate buffer on close. */
#define _IO_UNBUFFERED 0x0002
#define _IO_NO_READS 0x0004 /* Reading not allowed. */
#define _IO_NO_WRITES 0x0008 /* Writing not allowed. */
#define _IO_EOF_SEEN 0x0010
#define _IO_ERR_SEEN 0x0020
#define _IO_DELETE_DONT_CLOSE 0x0040 /* Don't call close(_fileno) on close. */
#define _IO_LINKED 0x0080 /* In the list of all open files. */
#define _IO_IN_BACKUP 0x0100
#define _IO_LINE_BUF 0x0200
#define _IO_TIED_PUT_GET 0x0400 /* Put and get pointer move in unison. */
#define _IO_CURRENTLY_PUTTING 0x0800
#define _IO_IS_APPENDING 0x1000
#define _IO_IS_FILEBUF 0x2000
/* 0x4000 No longer used, reserved for compat. */
#define _IO_USER_LOCK 0x8000
이 중 _IO_CURRENTLY_PUTTING(0x0800), _IO_IS_APPENDING(0x1000)를 주로 사용하나보다.
예제 코드를 컴파일하고 실행해보면 다음과 같은 결과가 출력되는데
$ ./iofile
_flags: fbad2c84
_fileno: 3
0xfbad2c84는 매직 넘버(_IO_MAGIC)를 포함한 각 권한을 의미한다.
(_IO_MAGIC, _IO_NO_READS, _IO_LINKED, _IO_TIED_PUT_GET, _IO_CURRENTLY_PUTTING, _IO_IS_FILEBUF)
_IO_FILE: fopen
처음에 _IO_FILE은 ‘fopen 함수를 사용할 때 힙 영역에 할당된다’ 라고 했다.
fopen 함수는 iofopen.c에 정의되어 있으며 iofopen.c는 다음과 같다.
_IO_FILE * __fopen_maybe_mmap (_IO_FILE *fp)
{
#ifdef _G_HAVE_MMAP
if ((fp->_flags2 & _IO_FLAGS2_MMAP) && (fp->_flags & _IO_NO_WRITES))
{
/* Since this is read-only, we might be able to mmap the contents
directly. We delay the decision until the first read attempt by
giving it a jump table containing functions that choose mmap or
vanilla file operations and reset the jump table accordingly. */
if (fp->_mode <= 0)
_IO_JUMPS_FILE_plus (fp) = &_IO_file_jumps_maybe_mmap;
else
_IO_JUMPS_FILE_plus (fp) = &_IO_wfile_jumps_maybe_mmap;
fp->_wide_data->_wide_vtable = &_IO_wfile_jumps_maybe_mmap;
}
#endif
return fp;
}
_IO_FILE * __fopen_internal (const char *filename, const char *mode, int is32)
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
_IO_FILE * _IO_new_fopen (const char *filename, const char *mode)
{
return __fopen_internal (filename, mode, 1);
}
strong_alias (_IO_new_fopen, __new_fopen)
versioned_symbol (libc, _IO_new_fopen, _IO_fopen, GLIBC_2_1);
versioned_symbol (libc, __new_fopen, fopen, GLIBC_2_1);
크게 보면 __fopen_maybe_mmap, __fopen_internal, _IO_new_fopen 총 3개의 함수로 구성되어 있다.
_IO_new_fopen
strong_alias, versioned_symbol, weak_alias 함수를 통해 _IO_new_fopen 함수가 fopen으로 사용되는 것 같다.
_IO_FILE * _IO_new_fopen (const char *filename, const char *mode)
{
return __fopen_internal (filename, mode, 1);
}
(strong_alias, versioned_symbol, weak_alias은 그냥 함수의 별칭을 지어주는 메크로인듯?)
https://codebrowser.dev/glibc/glibc/include/libc-symbols.h.html
__fopen_internal
fopen(_IO_new_fopen)이 호출되면 리턴에 있는 __fopen_internal이 실행된다.
그럼 이제 __fopen_internal 함수에 대해 알아보자.
_IO_FILE * __fopen_internal (const char *filename, const char *mode, int is32)
{
struct locked_FILE
{
struct _IO_FILE_plus fp;
#ifdef _IO_MTSAFE_IO
_IO_lock_t lock;
#endif
struct _IO_wide_data wd;
} *new_f = (struct locked_FILE *) malloc (sizeof (struct locked_FILE));
if (new_f == NULL)
return NULL;
#ifdef _IO_MTSAFE_IO
new_f->fp.file._lock = &new_f->lock;
#endif
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_new_file_init_internal (&new_f->fp);
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
filename, mode, is32를 인수로 받고 시작한다.
함수가 시작되면 locked_FILE이 정의된다. _IO_FILE_plus 타입 변수 fp와 _IO_wide_data 타입 변수 wd를 멤버로 가진다. _IO_MTSAFE_IO가 정의되어 있으면 _IO_lock_t 타입 변수 lock도 멤버로 가진다.
locked_FILE 구조체는 malloc으로 힙 영역에 할당된 뒤 new_f 변수에 주소를 저장한다.
만약 malloc이 제대로 되지 않아 new_f가 null이 되면 그냥 리턴으로 종료한다.
참고로 _IO_wide_data 구조체는 libio.h에 정의되어 있다.
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
...
/* Extra data for wide character streams. */
struct _IO_wide_data {
wchar_t *_IO_read_ptr; /* Current read pointer */
wchar_t *_IO_read_end; /* End of get area. */
wchar_t *_IO_read_base; /* Start of putback+get area. */
wchar_t *_IO_write_base; /* Start of put area. */
wchar_t *_IO_write_ptr; /* Current put pointer. */
wchar_t *_IO_write_end; /* End of put area. */
wchar_t *_IO_buf_base; /* Start of reserve area. */
wchar_t *_IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
wchar_t *_IO_save_base; /* Pointer to start of non-current get area. */
wchar_t *_IO_backup_base; /* Pointer to first valid character of
backup area */
wchar_t *_IO_save_end; /* Pointer to end of non-current get area. */
__mbstate_t _IO_state;
__mbstate_t _IO_last_state;
struct _IO_codecvt _codecvt;
wchar_t _shortbuf[1];
const struct _IO_jump_t *_wide_vtable;
};
#endif
이제 _IO_no_init, _IO_JUMPS, _IO_new_file_init_internal 함수를 차례로 호출한다.
_IO_MTSAFE_IO가 정의되어 있으면 new_f의 lock을 멤버인 fp.file(_IO_FILE)에 새로운 멤버 _lock을 추가해 저장한다. (new_f → fp.file._lock = &new_f->lock;)
1. _IO_no_init
_IO_no_init (&new_f->fp.file, 0, 0, &new_f->wd, &_IO_wfile_jumps);
_IO_no_init의 정의는 genops.c에 있으며 다음과 같다.
void _IO_no_init (_IO_FILE *fp, int flags, int orientation,
struct _IO_wide_data *wd, const struct _IO_jump_t *jmp) {
_IO_old_init (fp, flags);
fp->_mode = orientation;
if (orientation >= 0) {
fp->_wide_data = wd;
fp->_wide_data->_IO_buf_base = NULL;
fp->_wide_data->_IO_buf_end = NULL;
fp->_wide_data->_IO_read_base = NULL;
fp->_wide_data->_IO_read_ptr = NULL;
fp->_wide_data->_IO_read_end = NULL;
fp->_wide_data->_IO_write_base = NULL;
fp->_wide_data->_IO_write_ptr = NULL;
fp->_wide_data->_IO_write_end = NULL;
fp->_wide_data->_IO_save_base = NULL;
fp->_wide_data->_IO_backup_base = NULL;
fp->_wide_data->_IO_save_end = NULL;
fp->_wide_data->_wide_vtable = jmp;
}
else
/* Cause predictable crash when a wide function is called on a byte
stream. */
fp->_wide_data = (struct _IO_wide_data *) -1L;
fp->_freeres_list = NULL;
}
_IO_old_init의 정의는 genops.c에 _IO_no_init의 정의 바로 위에 있다.
void _IO_old_init (_IO_FILE *fp, int flags) {
fp->_flags = _IO_MAGIC|flags;
fp->_flags2 = 0;
if (stdio_needs_locking)
fp->_flags2 |= _IO_FLAGS2_NEED_LOCK;
fp->_IO_buf_base = NULL;
fp->_IO_buf_end = NULL;
fp->_IO_read_base = NULL;
fp->_IO_read_ptr = NULL;
fp->_IO_read_end = NULL;
fp->_IO_write_base = NULL;
fp->_IO_write_ptr = NULL;
fp->_IO_write_end = NULL;
fp->_chain = NULL; /* Not necessary. */
fp->_IO_save_base = NULL;
fp->_IO_backup_base = NULL;
fp->_IO_save_end = NULL;
fp->_markers = NULL;
fp->_cur_column = 0;
#if _IO_JUMPS_OFFSET
fp->_vtable_offset = 0;
#endif
#ifdef _IO_MTSAFE_IO
if (fp->_lock != NULL)
_IO_lock_init (*fp->_lock);
#endif
}
_IO_FILE 구조체(fp)의 _flags 멤버를 설정해주고 조건에 따라 fp를 null로 초기화하는 것이 주요 기능인가보다.
다음으로 _IO_FILE 구조체(fp)의 _mode 멤버를 설정해주고 0보다 크면 null로 초기화를 진행한다.
2. _IO_JUMPS
_IO_JUMPS (&new_f->fp) = &_IO_file_jumps;
_IO_JUMPS는 libioP.h에 정의된 매크로 함수이다.
#define _IO_JUMPS(THIS) (THIS)->vtable
_IO_file_jumps는 fileop.c에 정의되어 있다.
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)
};
결론적으로 _IO_JUMPS 함수를 통해 fp에 vtable를 매핑한다.
3. _IO_new_file_init_internal
_IO_new_file_init_internal (&new_f->fp);
_IO_new_file_init_internal은 fileops.c에 정의되어 있다.
void _IO_new_file_init_internal (struct _IO_FILE_plus *fp) {
/* POSIX.1 allows another file handle to be used to change the position
of our file descriptor. Hence we actually don't know the actual
position before we do the first fseek (and until a following fflush). */
fp->file._offset = _IO_pos_BAD;
fp->file._IO_file_flags |= CLOSED_FILEBUF_FLAGS;
_IO_link_in (fp);
fp->file._fileno = -1;
}
(뭔가 처리를 하나보다.. 중요해 보이진 않으니 나중에 기회가 되면 알아보자)
이후 내용들을 살펴보자.
...
#if !_IO_UNIFIED_JUMPTABLES
new_f->fp.vtable = NULL;
#endif
if (_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32) != NULL)
return __fopen_maybe_mmap (&new_f->fp.file);
_IO_un_link (&new_f->fp);
free (new_f);
return NULL;
}
!_IO_UNIFIED_JUMPTABLES가 true이면 new_f->fp.vtable는 NULL이 된다.
_IO_file_fopen ((_IO_FILE *) new_f, filename, mode, is32)의 결과가 NULL이 아니면__fopen_maybe_mmap 함수를 호출한다. NULL이면 free함수를 수행한 뒤 종료된다.
_IO_file_fopen은 _IO_new_file_fopen의 별칭이며 정의는 fileops.c에 있다.
_IO_FILE *_IO_new_file_fopen (_IO_FILE *fp, const char *filename, const char *mode,
int is32not64)
{
int oflags = 0, omode;
int read_write;
int oprot = 0666;
int i;
_IO_FILE *result;
const char *cs;
const char *last_recognized;
if (_IO_file_is_open (fp))
return 0;
switch (*mode)
{
case 'r':
omode = O_RDONLY;
read_write = _IO_NO_WRITES;
break;
case 'w':
omode = O_WRONLY;
oflags = O_CREAT|O_TRUNC;
read_write = _IO_NO_READS;
break;
case 'a':
omode = O_WRONLY;
oflags = O_CREAT|O_APPEND;
read_write = _IO_NO_READS|_IO_IS_APPENDING;
break;
default:
__set_errno (EINVAL);
return NULL;
}
...
}
libc_hidden_ver (_IO_new_file_fopen, _IO_file_fopen)
fopen 함수의 mode 변수가 ‘r', ‘w’, 'a' 문자인지를 확인하고 각 권한에 해당하는 비트가 할당한다.
read_write 변수에 비트가 할당될 때, omode 변수에도 O_RDONLY, O_WRONLY 등의 값이 저장되는 것을 볼 수 있다.
fopen 함수는 결국 open 시스템 콜을 호출해 파일을 열게되는데, 이때 해당 시스템 콜의 인자로 전달된다.
_IO_FILE_plus 구조체는 libioP.h에 정의되어 있다.
struct _IO_jump_t {
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
#if 0
get_column;
set_column;
#endif
};
/* We always allocate an extra word following an _IO_FILE.
This contains a pointer to the function jump table used.
This is for compatibility with C++ streambuf; the word can
be used to smash to a pointer to a virtual function table. */
struct _IO_FILE_plus {
_IO_FILE file;
const struct _IO_jump_t *vtable;
};
__fopen_maybe_mmap
위에서 살펴봤듯이 파일이 잘 열리면 __fopen_maybe_mmap로 가고 아니면 _IO_un_link로 간다.
__fopen_maybe_mmap에 대해 알아보자.
_IO_FILE * __fopen_maybe_mmap (_IO_FILE *fp) {
#ifdef _G_HAVE_MMAP
if ((fp->_flags2 & _IO_FLAGS2_MMAP) && (fp->_flags & _IO_NO_WRITES))
{
/* Since this is read-only, we might be able to mmap the contents
directly. We delay the decision until the first read attempt by
giving it a jump table containing functions that choose mmap or
vanilla file operations and reset the jump table accordingly. */
if (fp->_mode <= 0)
_IO_JUMPS_FILE_plus (fp) = &_IO_file_jumps_maybe_mmap;
else
_IO_JUMPS_FILE_plus (fp) = &_IO_wfile_jumps_maybe_mmap;
fp->_wide_data->_wide_vtable = &_IO_wfile_jumps_maybe_mmap;
}
#endif
return fp;
}
_G_HAVE_MMAP가 정의되어 있으면 아래 내용을 처리한다.
마지막에 _IO_FILE_plus 구조체인 fp를 반환한다는 점이 중요한 것 같다.
참고로 _IO_un_link는 genops.c에 정의되어 있다.
void _IO_un_link (struct _IO_FILE_plus *fp)
{
if (fp->file._flags & _IO_LINKED)
{
struct _IO_FILE **f;
#ifdef _IO_MTSAFE_IO
_IO_cleanup_region_start_noarg (flush_cleanup);
_IO_lock_lock (list_all_lock);
run_fp = (_IO_FILE *) fp;
_IO_flockfile ((_IO_FILE *) fp);
#endif
if (_IO_list_all == NULL)
;
else if (fp == _IO_list_all)
_IO_list_all = (struct _IO_FILE_plus *) _IO_list_all->file._chain;
else
for (f = &_IO_list_all->file._chain; *f; f = &(*f)->_chain)
if (*f == (_IO_FILE *) fp)
{
*f = fp->file._chain;
break;
}
fp->file._flags &= ~_IO_LINKED;
#ifdef _IO_MTSAFE_IO
_IO_funlockfile ((_IO_FILE *) fp);
run_fp = NULL;
_IO_lock_unlock (list_all_lock);
_IO_cleanup_region_end (0);
#endif
}
}
_IO_un_link와 이후 호출되는 free로 호출하는 걸 보면 _IO_FILE_plus 구조체인 fp를 해제하는 역할을 하나보다.
[결론]
fopen을 호출하면 fopen의 본명인 _IO_new_fopen 함수가 실행되고 __fopen_internal을 호출해서 fp에 대한 기본 정보(_flags, _mode 등)를 세팅한다. _IO_new_file_fopen 함수를 통해 잘 열리면 __fopen_maybe_mmap을 호출해 _IO_FILE_plus 구조체인 fp를 반환하고 아니면 _IO_un_link를 통해 힙에서 해제된다.
레퍼런스
https://dreamhack.io/lecture/courses/271
https://aidencom.tistory.com/187
'background > linux' 카테고리의 다른 글
[glibc-2.27] fwrite & fputs (1) | 2023.01.11 |
---|---|
[glibc-2.27] fread & fgets (0) | 2023.01.09 |
SigReturn-Oriented Programming(SROP) (0) | 2022.12.30 |
Master Canary (0) | 2022.12.08 |
SECCOMP (0) | 2022.12.01 |