Search
Duplicate
🕵️‍♂️

sanitizer

간단소개
sanitizer 기본 정리
팔만코딩경 컨트리뷰터
ContributorNotionAccount
주제 / 분류
C
잡지식
Scrap
태그
개발지식
명령어
9 more properties
많은 분들이 42 과제를 하려면 sanitizer를 공부하는 것이 좋다고 조언해주셨습니다. 그래서 저도 해보려고 합니다. sanitizer 공부.

1. sanitizer?

sanitizer(새니타이저)는 google에서 설계한 동적 코드 분석(dynamic code analysis) 오픈 소스 도구입니다. sanitizer의 코드는 LLVM 저장소에 존재합니다.
→ 정확하게는 도구’들’이니 sanitizer’s’라고 써야 할 것 같습니다.
LLVM
sanitizers는 감지기(detector)의 역할에 따라 아래와 같이 분류할 수 있습니다. 보통 C/C++에서 오류 감지를 위해 사용합니다.
AddressSanitizer와 LeakSanitizer
ThreadSanitizer (C++, Go에서 사용)
MemorySanitizer
HWASAN(Hardware-assisted AddressSanitizer)
UBSan(UndefinedBehaviorSanitizer)
그 외 OS 커널에 따라 KASAN, KMSAN, KCSAN 등 도구 사용 가능
사용 가능한 OS로는 Linux, macOS, WSL 등이 있습니다. 심지어 Android에서도 사용할 수 있습니다. 물론 감지기마다 OS 지원 여부가 조금씩 다르기 때문에 사용하기 전 google의 github 혹은 OS 매뉴얼에서 확인하는 것이 좋겠습니다.
참고로, 42 과제를 하다보면 보통 gcc 컴파일러의 옵션으로 sanitizer를 아래와 같이 사용할텐데
gcc -g -O3 -fsanitize=undefined -Wall -Wextra -Werror test.c
Bash
복사
이 뿐만 아니라 clang에서도 사용할 수 있고, Visual Studio, CLion과 같은 IDE에서도 사용할 수 있습니다. 필요하다면 IDE 매뉴얼을 참고하시면 좋을 것 같습니다.

2. AddressSanitizer(ASan)

C/C++에서 메모리 버그를 감지하기 위한 감지기(detector)입니다.
사용하기 전 OS와 컴파일러 버전을 확인하는게 좋을 듯 합니다. (참고 : AddressSanitizer github)
google의 공식 자료에 따르면 이 툴은 아래의 사항들을 탐지할 수 있습니다.
Use After Free (일명 UAF)
힙 버퍼 오버플로우(heap buffer overflow)
스택 버퍼 오버플로우(stack buffer overflow)
전역 버퍼 오버플로우 (예시)
return 후 사용
Use After Scope (예시)
Initialization order bugs
메모리 누수(memory leak)
간단한 코드를 통해 AddressSanitizer가 어떻게 탐지하고 결과를 출력해주는지 알아볼 수 있습니다. 테스트해볼 코드인 test.c는 아래와 같습니다. 저는 gcc를 사용하며, AddressSanitizer를 활성화 하기 위해서 옵션으로 -fsanitize=address를 사용합니다. 옵션에 대한 매뉴얼은 gcc.gnu.org를 확인하는 것이 좋을 것 같습니다.
#include <stdio.h> #include <stdlib.h> int main(void) { char *str; str = (char *)malloc(sizeof(char) * 13); str = "Hello world!"; printf("free 전 : %s\n", str); free (str); printf("free 후 : %s\n", str); return (0); }
C
복사
실행 결과로 위와 같이 출력되었습니다. UNKNOWN memory access가 문제가 되고 있다고 알려주고 있습니다. deallocate 함수는 포인터가 참조하는 저장소의 할당을 해제하는 함수이니 이 곳에서 문제가 생겼다면 포인터가 해제(free)되는 곳의 코드를 유심히 확인해봐야겠군요.

3. LeakSanitizer

메모리 누수(memory leak)를 감지하기 위한 감지기(detector)입니다. 이 도구는 x86_64 Linux와 OS X에서 지원된다고 합니다. (참고자료 : LeakSanitizer github)
C/C++ 컴파일러 매뉴얼에 따르면 clang에서는 ASan(AddressSanitizer)의 ASAN_OPTIONS 환경변수를 설정하여 탐지하며, gcc에서는 -fsanitize=leak 옵션으로 탐지할 수 있다고 합니다.
별도의 테스트는 하지 않겠습니다. 제 PC 환경에서는 지원하지 않거든요. 사용 하기전 본인 환경에서 LeakSanitizer를 지원하는지 확인하시는게 좋을 것 같습니다.
사용법이 궁금하시다면, LeakSanitizer github 자료 혹은 gcc 공식 문서에서 [Current development] > [GCC Manual] > [Program Instrumentation Options] 를 확인하거나 clang 공식 문서를 확인하면 좋을 것 같습니다.

4. UndefinedBehaviorSanitizer

UndefinedBehaviorSanitizer(UBSan)은 정의되지 않은 동작(행동)을 탐지하는 탐지기입니다. UBSan은 여기에서 언급하는 ‘정의되지 않은 동작(행동)’을 탐지하기 위해서 컴파일 타임(compile-time)에 프로그램을 수정합니다.
예를 들어 오버플로우가 일어나는지 확인하거나, 정수를 0으로 나누어보거나, null이 아닌 것으로 선언된 함수의 매개변수에 null 포인터를 전달하고 있는지 등의 행위를 확인합니다.
UBSan을 통해 검사할 수 있는 행위는 너무 많습니다. 자세한 사항은 clang.llvm.org를 참고해주세요.
그럼 한번 UBSan을 활성화 해봅시다. 저는 gcc를 사용하기 때문에 gcc 매뉴얼을 보며 테스트할 것입니다.
gcc에서 UBSan을 활성화하는 기본적인 방법은 -fsanitize=undefined 옵션을 사용하는 것입니다. undefined를 쓰지 않고 하위 옵션을 사용할 수도 있습니다. 아래 테스트에서 사용해보겠습니다.

test1. 참조하는 주소가 NULL

아래의 코드는 실행할 때, segmentation fault가 발생하는 코드입니다.
#include <stdio.h> int add_nums(int *num1, int *num2) { return (*num1 + *num2); } int main(void) { int value = 10; int *n1, *n2; n1 = NULL; n2 = &value; printf("%d", add_nums(n1, n2)); // null과 10의 주소를 인자로 전달(call by reference) return (0); }
C
복사
-fsanitize=null은 UBSan이 탐지할 수 있는 검사 중 하나로, NULL 포인터 검사를 하는 도구입니다. -fsanitize=defined를 사용해도 좋고 -fsanitize=null을 사용해도 좋습니다. 둘 다 NULL 포인터 검사를 해줍니다.
load of null pointer of type ‘int’가 UBSan의 결과로 출력됐습니다. 메시지에 나와있는대로 5번 라인 부터 null 포인터를 확인하면 되겠군요.

test2. 값을 0으로 나눔

#include <stdio.h> int divide_num(int num1, int num2) { return (num1 / num2); } int main(void) { int n1 = 10; int n2 = 0; printf("%d", divide_num(n1, n2)); // 10과 0을 인자로 전달 return (0); }
C
복사
우선 위의 코드를 UBSan 없이 컴파일 하고 실행해보겠습니다.
정수 10을 0으로 나누는게 논리적으로 말이 안되는 것을 알고 있지만, 어쨌든 실행은 가능하고 결과도 출력됩니다. 다만, 출력 값이 제대로 나올리가 없습니다. 숫자 10은 0으로 나눌 수 없으니까요.
UBSan을 활성화해서 검사해보니 runtime error: division by zero 라는 메시지가 나왔습니다. 이 코드에 어떤 값을 0으로 나누는 ‘문제의 코드’가 있다는 것을 알아챘습니다. 덕분에 수정할 수 있게 되겠군요.
참고로 -fsanitize=undefined 대신 -fsanitize=integer-divide-by-zero 를 사용해도 됩니다.

test3. 오버플로우, 언더플로우

#include <stdio.h> int main(void) { int num_over; num_over = 0x7FFFFFFF; // int max int num_under; num_under = 0x80000000; // int min printf("overflow : %d\n", num_over+1); printf("underflow : %d", num_under-1); return (0); }
C
복사
이 코드를 컴파일하고 실행하면 오버플로우와 언더플로우에 대한 어떠한 방어 장치(코드)가 없기 때문에 그대로 오버플로우와 언더플로우가 발생합니다. 아래의 결과를 보면 알 수 있습니다.
정의되지 않은 동작을 탐지하는 UBSan은 오버플로우와 언더플로우를 잡아줄 수 있습니다. 아래의 결과를 보면 UBSan이 오버플로우와 언더플로우가 발생했을 때 어떤 메시지를 출력해주는지 알 수 있습니다.

마무리

개인적으로 과제를 하면서 주로 사용하는 Sanitizer는 아무래도 AddressSanitizerUndefinedBehaviorSanitizer인 것 같습니다.
물론 저는 이제 갓 공부하는 햇병아리 입장이기 때문에 아직 능숙하게 사용하지는 못하고, 매뉴얼을 찾아보며 사용하고 있습니다.
그리고 사실 Sanitizers는 매뉴얼을 보며 ‘직접 사용’해보는게 훨씬 이해가 빠르게 되는 것 같습니다.
제가 주로 참고하는 매뉴얼은 아래와 같습니다.
google sanitizers github
sanitizers
google
gcc - Program Instrumentation Options Documentation - 참고로 Program Instrumentation 이란, “컴퓨터 프로그래밍에서 오류를 진단하거나, 추적 정보를 쓰기 위해 제품의 성능 정도를 모니터하거나 측정하는 기능”을 가리킨다고 합니다. (출처 : 위키백과)
clang - UndefinedBehaviorSanitizer documentation
Visual Studio에서 사용하려면 → Microsoft Docs