일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- doupdate
- edge trigger
- architecture
- Compiler
- rfc5508
- 취약점
- DOCKER-USER
- iptables
- ncurses
- wrefresh
- packet flow
- BOF
- LOB
- level trigger
- 어셈블리어
- 풀이
- ioctl
- packet filter
- epoll_wait
- vtable
- REDIS
- epoll
- mvwin
- wnourefresh
- .net core 7
- NAPT
- Docker
- cbpf
- C언어
- .nret core 배포
- Today
- Total
Tuuna Computer Science
[C 언어] fgets함수 분석과, stdin 모험 본문
수정) read_ptr은 선언된 버퍼의 크기라기 보단, fgets인자로 준 size값에 따르는 듯?
lob풀다가 갑자기 Stdin에 관해서 한 번 분석해보고 싶어서 글을 쓰지만 별로 분석한건 없는 거 같다...
초짜라 뭘 봐야할지도 잘 모르겠고 ㅋㅋㅋ
stdio.c 이고
#include "libioP.h"
#include "stdio.h"
#undef stdin
#undef stdout
#undef stderr
FILE *stdin = (FILE *) &_IO_2_1_stdin_;
FILE *stdout = (FILE *) &_IO_2_1_stdout_;
FILE *stderr = (FILE *) &_IO_2_1_stderr_;
stdin에 대한 구조체 정의 부분이다.
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
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;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};
#include <stdio.h>
int main(void)
{
char buf[80];
fgets(buf, 79, stdin);
return 0;
}
위에 대해서 gdb로 한번 분석해보자.
fgets함수의 프로토타입을 보면 stdin구조체가 먼저 push되어야 함을 알 수 있다.
char *fgets(char *s, int size, FILE *stream);
그럼 main+25에 bp를 걸어 edx값을 살펴보자.
0xf7faa5c0이 나온다.
stdin구조체의 시작주소인거 같다.
위에서 정의한 구조체의 첫 번째 멤버는 flag인거 같음.
그 다음은
char* _IO_read_ptr;
char* _IO_read_end;
char* _IO_read_base;
이렇게 되어 있는데 각 각 입력버퍼의 현재읽고 잇는 주소, 문자열의 끝주소, 시작주소를 의미하는 거같다.
그럼 끝주소 - 시작주소의 값이 내가 쓴 값이 있다는 건데 한번 까보자.
있다! fgets의 특성상 개행표시가 붙는 것도 똑같다.
이걸 잘보면
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
이부분에서 서로 같을 빼면 값이 0x400 == 1024가 나오는데 버퍼의 크기임을 알 수 있다. 이때 내가 2048크기의 값을 입력하면 어떻게 될지
조금 궁금해 졌다.
입력 후,
버퍼의 크기가 1024에서 4096으로 늘어남을 볼 수 있다.
또 여기서 의문점 우리는 흔히 입력버퍼를 비울 때
getchar(); 를 하는 것을 볼 수 있다.
이것이 입력버퍼를 비운다고 의미할 수 있을까!
이렇게 코드를 작성하고 gdb로 열어봤다.
각 각의 stdin구조체의 주소, fgets호출 전, 후 주소, getchar주소에 bp를 걸었다.
일단 stdin의 위치는 0xf7faa5c0으로 확인 됨
첫 번째 fgets함수 호출 후의 모습
의아하게도 read_ptr과 read_end의 값이 다르다?
같아야 하는데 read_ptr의 값이 read_end보다 2의 값보다 작다.
추측하면 선언된 버퍼의 크기가 10바이트, 입력한 a의 길이가 10개니까
0x0804b150~ 0x0804b169 까지 a가 들어가고 선언된 버퍼를 넘어서면 read_ptr이 움직이지 않고
end_ptr만 입력된 곳까지 가는거 같다.
추측상 read_end의 주소에는 개행이 존재하기 때문에 read_ptr은 read_end주소까지 읽으려 한다.
하지만 선언된 배열의 크기상, 배열의 크기만큼만 읽게 되고 read_ptr은 read_end까지 읽지 못하게 된다.
그래서 다음 fgets때는 read_ptr이 read_end까지 자동으로 읽게하게 한다. 왜냐면 fgets는 개행까지 읽어야 하니까!
개행은 read_end에 존재하기 때문이다.
예시 사진
이 상태에서 while(getchar() != '\n');를 호출해보자.
위의 코드는 입력버퍼에서 개행문자를 읽을 때 까지 읽는 코드이다.
현재 read_ptr은 선언한 buf의 끝인 0x0804b169('a')를 가리키고 있다.
코드르 실행 후
read_ptr의 한 주소를 읽고 +1하였다.
처음 getchar()을 했을 때는 0x0804b159에 있는 값을 읽은 거니 'a'가 반환되고 이는 '\n'과 다르다. 그럼 한 번더 getchar()를 호출한다.
호출하고 난 뒤, read_ptr의 값과 read_end의 값이 같아졌다!
그리고 '\n'이 반환되어 while문을 pass하게 된다.
그리고 다음 fgets를 호출했는데 여기서 신기한 점이 발견되었다.
바로 전에 쓴 버퍼의 다음주소에 값을 쓰는 것이 아닌 그냥 버퍼의 시작주소에 값을 다시 쓰는 것이다!
정리 :
1. fget를 계속 호출하더라도 처음 버퍼에 값을 덮어 씌운다.
2. getchar()함수는 엄밀히 입력버퍼를 비우기보다 read_ptr의 값을 read_end의 값과 동일하게 만들어주는 역할을 한다.
(초기화가 아님)
3. fets함수는 개행까지 읽으며 선언된 배열의 크기때문에 다 읽지 못해도 다음 fgets때는 현재의 read_ptr에서부터 자동으로 읽게된다.
즉, fgets가 개행을 읽었냐 안읽었냐의 확인점은 2가지가 있다.
fgets함수 내에 현재 읽은 값이 개행인지 아닌지 판단하거나, 이때 판단하지 못했을 때 read_ptr의 값이 read_end의 값과 같으냐 아니냐의 검사로
개행을 확인하는 것으로 추측이 가능하다. (물론 뇌피셜)
그리고 처음 read_ptr의 값이 read_end의 값과 같다면 이는 개행까지 읽었다는 판단이 되므로 버퍼의 처음으로 되돌아와 값을 다시 쓰거나 읽게 된다.
'C language' 카테고리의 다른 글
[ROTATE SHIFT] C 언어 회전시프트 TIP (0) | 2019.05.18 |
---|---|
container_of & offsetof 매크로 분석 (0) | 2019.04.03 |
[안드로이드 게임]텐트와 나무 게임 알고리즘 분석해보기(머리로 풀지 말고 컴퓨터로 풀자) (22) | 2018.11.25 |
시저암호 암호화 복호화 소스 (0) | 2018.11.02 |
[C Language] 배열없이 문자열의 빈도수 구하기 (0) | 2018.10.26 |