macOS에서 C 포인터 이중-free 버그 고치기
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
를 쓰면 에러가 안 나는데, 여러 번 돌려보니 그제서야 lldb
가 malloc_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->chararr
이 NULL
인지 확인하는데, 아까 초기화할 때 NULL
로 설정하지 않았으니 그 포인터에 담겨있던 메모리 주소를 마구잡이로 그냥 free
해버려서 문제가 발생한 것입니다.
다음부터 프로그램에 이런 메모리 버그가 발생하면 clang
이랑 같이 오는 AddressSanitizer 모듈로 프로그램의 무슨 부분이 문제인지 확인하세요!