scanf의 내부 소스 읽기
소개
이 글에서는 glibc-2.21을 사용하여 표준 라이브러리인 scanf 내용의 소스 코드를 해독해 보겠습니다.
표준 라이브러리의 소스 코드의 해독 방법은, 「헬로“Hello, World” OS와 표준 라이브러리의 시고트와 구조(2015년 9월 11일 발행)」를 참고로 하고 있습니다.
glibc-2.21은 아래에서 다운로드 할 수 있습니다.
htps : // 기주 b. 코 m/시챠오안/gぃbcー2.21
scanf 내용의 소스를 읽어보십시오
다운로드 한 소스 코드에서 아래 명령을 사용하여 scanf 파일을 검색하면 다음 결과를 얻을 수 있다고 생각합니다.find . -name "*scanf*"
scanf2.c,scanf.11c등의 파일이 표시된다고 생각합니다만, scanf[숫자].c라고 쓰여진 코드는 모두 scanf의 테스트 코드가 되고 있습니다. scanf의 함수 자체는 scanf.c에 정의되어 있습니다.
이제 scanf.c 소스 코드를 들여다 보자.
scanf.c의 내용은 다음과 같습니다.
scanf.c
#include <stdarg.h>
#include <stdio.h>
#include <libioP.h>
/* Read formatted input from stdin according to the format string FORMAT. */
/* VARARGS1 */
int
__scanf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = _IO_vfscanf (stdin, format, arg, NULL);
va_end (arg);
return done;
}
ldbl_strong_alias (__scanf, scanf);
위의 코드에 scanf라는 함수가 나오는 것을 알 수 있습니다.
여기서 ldbl_strong_alias는 scanf()를 scanf()로 정의하는 매크로입니다.
구체적으로 ldbl_strong_alias의 매크로 정의는 다음과 같습니다.
include/libc-symbols.h#define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)
...
#include/libc-symbols.h
/* Define ALIASNAME as a strong alias for NAME. */
# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
# define _strong_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((alias (#name)));
다음으로 __scanf라는 함수의 내용을 보면 _IO_vfscanf라는 함수가 등장하고있다
을 알 수 있습니다. 그래서 github의 glibc-2.21 리포시드리에서 _IO_vfscanf로 검색하면 libio/vscanf.c에 정의되어 있음을 알 수 있습니다.
libio/vscanf.c#include "libioP.h"
#include "stdio.h"
#undef vscanf
int _IO_vscanf (format, args)
const char *format;
_IO_va_list args;
{
return _IO_vfscanf (_IO_stdin, format, args, NULL);
}
ldbl_weak_alias (_IO_vscanf, vscanf)
마찬가지로 여기에서 나온 _IO_vfscanf도 검색하고 추구하면 stdio-
common/vfscanf.c에서 ldbl_strong_alias에서 _IO_vfscanf_internal에 별칭
되었음을 알 수 있습니다.
stdio-common/vfscanf.cint ___vfscanf (FILE *s, const char *format, va_list argptr)
{
return _IO_vfscanf_internal (s, format, argptr, NULL);
}
ldbl_strong_alias (_IO_vfscanf_internal, _IO_vfscanf)
ldbl_hidden_def (_IO_vfscanf_internal, _IO_vfscanf)
ldbl_strong_alias (___vfscanf, __vfscanf)
ldbl_hidden_def (___vfscanf, __vfscanf)
ldbl_weak_alias (___vfscanf, vfscanf)
또한 _IO_vfscanf_internal의 정의를 따를 때 _IO_vfscanf_internal은 동일합니다.
vfscanf.c에 정의되어 있음을 알 수 있습니다.
stdio-common/vfscanf.cint _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,int *errp)
정의의 내용은 방대하고 전모를 전망하는 것이 꽤 힘들 것 같습니다.
그러나 찾아보면 input_error()라는 함수가 등장하는 행이 있습니다.
stdio-common/vfscanf.c /* Non-ASCII, may be a multibyte. */
int len = __mbrlen (f, strlen (f), &state);
if (len > 0)
{
{
c = inchar ();
if (__glibc_unlikely (c == EOF))
input_error ();
else if (c != (unsigned char) *f++)
{
ungetc_not_eof (c, s);
conv_error ();
}
}
do
while (--len > 0);
continue;
}
}
if (!isascii ((unsigned char) *f))
분명히 이 부분에서 입력 읽기 → 입력 결과가 에러인지 아닌지를 판단하는 예외 처리를 하고 있는 것 같습니다.
이 부분을 자세히 보면 inchar ()로 입력을 읽는 과정을 수행하는 것 같습니다.
실제로 inchar() 의 정의를 쫓으면, vfscanf.c상에서 아래와 같이 정의되고 있습니다.
stdio-common/vfscanf.c# define inchar() (c == WEOF ? ((errno = inchar_errno), WEOF) \
: ((c = _IO_getwc_unlocked (s)), \
(void) (c != WEOF
? ++read_in \
: (size_t) (inchar_errno = errno)), c))
여기서, WOEF는 와이드 문자의 스트림의 끝을 나타내는 매크로이므로, 아무래도 _IO_getwc_unlocked 로 입력을 읽어들여, 그것이 스트림의 끝인지 어떤지를 판단하고 있는 것을 알 수 있습니다.
_IO_getwc_unlocked의 정의는 다음과 같습니다.
libio/libio.h# define _IO_getwc_unlocked(_fp) \
|| ((_fp)->_wide_data->_IO_read_ptr \
>= (_fp)->_wide_data->_IO_read_end), 0) \
(_IO_BE ((_fp)->_wide_data == NULL \
? __wuflow (_fp) : (_IO_wint_t) *(_fp)->_wide_data->_IO_read_ptr++)
이 처리를 보면 _IO_read_ptr이라는 포인터에 문자 데이터를 수납하고 있는 것을 알 수 있습니다.
여기서 IO_read_ptr은 같은 소스의 libio/libio.h에 정의되어 있으며 문자를 읽는 포인터를 나타냅니다.
libio/libio.hstruct _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. */
따라서, 키보드로부터 입력된 문자는 이 포인터에 문자열을 수납하고 있는 것을 알 수 있습니다.
결론
이번에는, scanf의 내용에 있는 함수의 정의를 소스 코드로부터 쫓아가는 것으로 scanf의 동작을 조사해 보았습니다.
이와 같이 소스 코드의 정의를 따라가는 것으로 함수의 거동을 분석하는 것을 정적 해석이라고 합니다.
또 기회가 있으면 scanf 이외의 C언어 표준 라이브러리의 내용도 분석해 가고 싶습니다.
Reference
이 문제에 관하여(scanf의 내부 소스 읽기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/embetan/items/165e0dde1702c59919be
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
다운로드 한 소스 코드에서 아래 명령을 사용하여 scanf 파일을 검색하면 다음 결과를 얻을 수 있다고 생각합니다.
find . -name "*scanf*"
scanf2.c,scanf.11c등의 파일이 표시된다고 생각합니다만, scanf[숫자].c라고 쓰여진 코드는 모두 scanf의 테스트 코드가 되고 있습니다. scanf의 함수 자체는 scanf.c에 정의되어 있습니다.
이제 scanf.c 소스 코드를 들여다 보자.
scanf.c의 내용은 다음과 같습니다.
scanf.c
#include <stdarg.h>
#include <stdio.h>
#include <libioP.h>
/* Read formatted input from stdin according to the format string FORMAT. */
/* VARARGS1 */
int
__scanf (const char *format, ...)
{
va_list arg;
int done;
va_start (arg, format);
done = _IO_vfscanf (stdin, format, arg, NULL);
va_end (arg);
return done;
}
ldbl_strong_alias (__scanf, scanf);
위의 코드에 scanf라는 함수가 나오는 것을 알 수 있습니다.
여기서 ldbl_strong_alias는 scanf()를 scanf()로 정의하는 매크로입니다.
구체적으로 ldbl_strong_alias의 매크로 정의는 다음과 같습니다.
include/libc-symbols.h
#define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)
...
#include/libc-symbols.h
/* Define ALIASNAME as a strong alias for NAME. */
# define strong_alias(name, aliasname) _strong_alias(name, aliasname)
# define _strong_alias(name, aliasname) \
extern __typeof (name) aliasname __attribute__ ((alias (#name)));
다음으로 __scanf라는 함수의 내용을 보면 _IO_vfscanf라는 함수가 등장하고있다
을 알 수 있습니다. 그래서 github의 glibc-2.21 리포시드리에서 _IO_vfscanf로 검색하면 libio/vscanf.c에 정의되어 있음을 알 수 있습니다.
libio/vscanf.c
#include "libioP.h"
#include "stdio.h"
#undef vscanf
int _IO_vscanf (format, args)
const char *format;
_IO_va_list args;
{
return _IO_vfscanf (_IO_stdin, format, args, NULL);
}
ldbl_weak_alias (_IO_vscanf, vscanf)
마찬가지로 여기에서 나온 _IO_vfscanf도 검색하고 추구하면 stdio-
common/vfscanf.c에서 ldbl_strong_alias에서 _IO_vfscanf_internal에 별칭
되었음을 알 수 있습니다.
stdio-common/vfscanf.c
int ___vfscanf (FILE *s, const char *format, va_list argptr)
{
return _IO_vfscanf_internal (s, format, argptr, NULL);
}
ldbl_strong_alias (_IO_vfscanf_internal, _IO_vfscanf)
ldbl_hidden_def (_IO_vfscanf_internal, _IO_vfscanf)
ldbl_strong_alias (___vfscanf, __vfscanf)
ldbl_hidden_def (___vfscanf, __vfscanf)
ldbl_weak_alias (___vfscanf, vfscanf)
또한 _IO_vfscanf_internal의 정의를 따를 때 _IO_vfscanf_internal은 동일합니다.
vfscanf.c에 정의되어 있음을 알 수 있습니다.
stdio-common/vfscanf.c
int _IO_vfscanf_internal (_IO_FILE *s, const char *format, _IO_va_list argptr,int *errp)
정의의 내용은 방대하고 전모를 전망하는 것이 꽤 힘들 것 같습니다.
그러나 찾아보면 input_error()라는 함수가 등장하는 행이 있습니다.
stdio-common/vfscanf.c
/* Non-ASCII, may be a multibyte. */
int len = __mbrlen (f, strlen (f), &state);
if (len > 0)
{
{
c = inchar ();
if (__glibc_unlikely (c == EOF))
input_error ();
else if (c != (unsigned char) *f++)
{
ungetc_not_eof (c, s);
conv_error ();
}
}
do
while (--len > 0);
continue;
}
}
if (!isascii ((unsigned char) *f))
분명히 이 부분에서 입력 읽기 → 입력 결과가 에러인지 아닌지를 판단하는 예외 처리를 하고 있는 것 같습니다.
이 부분을 자세히 보면 inchar ()로 입력을 읽는 과정을 수행하는 것 같습니다.
실제로 inchar() 의 정의를 쫓으면, vfscanf.c상에서 아래와 같이 정의되고 있습니다.
stdio-common/vfscanf.c
# define inchar() (c == WEOF ? ((errno = inchar_errno), WEOF) \
: ((c = _IO_getwc_unlocked (s)), \
(void) (c != WEOF
? ++read_in \
: (size_t) (inchar_errno = errno)), c))
여기서, WOEF는 와이드 문자의 스트림의 끝을 나타내는 매크로이므로, 아무래도 _IO_getwc_unlocked 로 입력을 읽어들여, 그것이 스트림의 끝인지 어떤지를 판단하고 있는 것을 알 수 있습니다.
_IO_getwc_unlocked의 정의는 다음과 같습니다.
libio/libio.h
# define _IO_getwc_unlocked(_fp) \
|| ((_fp)->_wide_data->_IO_read_ptr \
>= (_fp)->_wide_data->_IO_read_end), 0) \
(_IO_BE ((_fp)->_wide_data == NULL \
? __wuflow (_fp) : (_IO_wint_t) *(_fp)->_wide_data->_IO_read_ptr++)
이 처리를 보면 _IO_read_ptr이라는 포인터에 문자 데이터를 수납하고 있는 것을 알 수 있습니다.
여기서 IO_read_ptr은 같은 소스의 libio/libio.h에 정의되어 있으며 문자를 읽는 포인터를 나타냅니다.
libio/libio.h
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. */
따라서, 키보드로부터 입력된 문자는 이 포인터에 문자열을 수납하고 있는 것을 알 수 있습니다.
결론
이번에는, scanf의 내용에 있는 함수의 정의를 소스 코드로부터 쫓아가는 것으로 scanf의 동작을 조사해 보았습니다.
이와 같이 소스 코드의 정의를 따라가는 것으로 함수의 거동을 분석하는 것을 정적 해석이라고 합니다.
또 기회가 있으면 scanf 이외의 C언어 표준 라이브러리의 내용도 분석해 가고 싶습니다.
Reference
이 문제에 관하여(scanf의 내부 소스 읽기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다
https://qiita.com/embetan/items/165e0dde1702c59919be
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)
Reference
이 문제에 관하여(scanf의 내부 소스 읽기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://qiita.com/embetan/items/165e0dde1702c59919be텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)