MacOS에서 C 포인터 이중-Free 버그 고치기

!
경고: 이 글이 작성된 지 365일이 넘었습니다. 글의 정보가 오래되어 부정확할 수 있습니다.

C 프로그램을 짜는데 계속 다음과 같은 에러가 발생합니다:

main(44466,0x10ae7ce00) malloc: *** error for object 0x7000000000000000: pointer being freed was not allocated
main(44466,0x10ae7ce00) malloc: *** set a breakpoint in malloc_error_break to debug
zsh: abort      ./main test_cases/test_1.html output.txt

이상하게도 lldb를 쓰면 에러가 안 나는데, 여러 번 돌려보니 그제서야 lldbmalloc_error_break breakpoint에서 멈춰버립니다. 메모리 버그가 이래서 잡기 힘들고 짜증납니다..

근데, lldb가 디버거로서 줘야 할 스택 trace 정보를 하나도 안 주네요. 리눅스 gdb에서도 한 번 프로그램을 돌려봤는데, 리눅스에서도 잘 크래시하지 않아 디버깅하는데 시간을 좀 많이 썼습니다. 리눅스에서 크래시하게 여러 번 프로그램을 돌렸는데, 크래시했을 때 주는 스택 trace도 하나도 유용하지 않고, valgrind로 돌렸을 때도 별다른 정보를 제공하지는 않았습니다.

운 좋게도, 맥에서 clang(LLVM)이란 컴파일러를 사용하면 AddressSanitizer라는 모듈이 같이 오는데, 이 모듈을 가지고 프로그램의 무슨 부분이 크래시하는지 확인할 수 있습니다. 그래서 AddressSanitizer를 활성화한 clang을 가지고 프로그램을 다시 컴파일해봤는데:

clang -g -fsanitize=address -o main_debug main.c (... rest of the source files ...)

프로그램을 실행시키니, AddressSanitizer가 바로 무슨 부분이 문제인지 알려줍니다:

ericswpark@Erics-MacBook-Pro Project % ./main_debug test_cases/test_1.html debug_output.txt
AddressSanitizer:DEADLYSIGNAL
=================================================================
==44732==ERROR: AddressSanitizer: SEGV on unknown address (pc 0x000102e04b98 bp 0x7ffeece1ba20 sp 0x7ffeece1b9f0 T0)
==44732==The signal is caused by a READ memory access.
==44732==Hint: this fault was caused by a dereference of a high value address (see register values below).  Dissassemble the provided pc to learn which register was used.
    #0 0x102e04b98 in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)+0x48 (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6b98)
    #1 0x102e4732a in wrap_free+0x10a (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4932a)
    #2 0x102de8131 in html_tag_free main.c:278
    #3 0x102de7f7c in stack_pop main.c:298
    #4 0x102de79e5 in main main.c:219
    #5 0x7fff203b9620 in start+0x0 (libdyld.dylib:x86_64+0x15620)

==44732==Register values:
rax = 0x0000000000000002  rbx = 0xbebebebebebebebe  rcx = 0x0000000000000003  rdx = 0x0000000000000000
rdi = 0xbebebebebebebebe  rsi = 0xbebebebebebebebe  rbp = 0x00007ffeece1ba20  rsp = 0x00007ffeece1b9f0
 r8 = 0x00007ffeece1ba30   r9 = 0x0000000000000001  r10 = 0xffffffffffffffff  r11 = 0x00000fffffffffff
r12 = 0x0000000000000001  r13 = 0x0000000000000000  r14 = 0x00007ffeece1ba30  r15 = 0x0000000102ea2d40
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x6b98) in __asan::Allocator::Deallocate(void*, unsigned long, unsigned long, __sanitizer::BufferedStackTrace*, __asan::AllocType)+0x48
==44732==ABORTING
zsh: abort      ./main_debug test_cases/test_1.html debug_output.txt

main.c 파일의 278번째 줄에서 문제가 발생하고 있네요. 여기에서 문제는 struct 변수를 초기화할 때 한 character 배열을 NULL로 설정하지 않아서 발생하는 문제였습니다. 다음 코드 블럭에서:

if (var->chararr)
{
    free(var->chararr);     // 문제!
    var->chararr = NULL;
}

var->chararrNULL인지 확인하는데, 아까 초기화할 때 NULL로 설정하지 않았으니 그 포인터에 담겨있던 메모리 주소를 마구잡이로 그냥 free해버려서 문제가 발생한 것입니다.

다음부터 프로그램에 이런 메모리 버그가 발생하면 clang이랑 같이 오는 AddressSanitizer 모듈로 프로그램의 무슨 부분이 문제인지 확인하세요!

댓글