absl::GetStackTrace와 Symbolize — crash 시 readable stack
#한 줄 요약
absl::GetStackTrace는 현재 PC 체인을 배열로 채운다. absl::Symbolize는 그 PC를 함수 이름 + 파일 + 라인으로 변환한다. 두 함수는 signal handler 안에서 안전하게 호출할 수 있도록 설계되어 crash 시 readable한 스택을 stderr/log로 남길 수 있다. LOG(FATAL)/CHECK 실패 출력의 근간이 이 둘이다.
#동기
production crash에서 가장 비싼 디버깅 비용은 스택을 사람이 읽을 수 있게 만드는 일이다. core dump가 있다면 gdb로 풀 수 있지만, 다음 상황에서는 core dump가 옵션이 아니다.
- container 환경에서 core dump 비활성화.
- core 크기가 수 GB라 저장·전송 불가.
- intermittent crash라 reproduce 안 됨.
이때는 프로세스가 죽기 직전 자기 자신이 스택을 출력해야 한다. 다음 두 가지가 필요하다.
- PC 배열 수집 (
unwind). - PC → symbol 변환 (
addr2line이 평소 하는 일).
absl::GetStackTrace + absl::Symbolize가 이를 async-signal-safe하게 한다.
#API와 사용법
#include "absl/debugging/stacktrace.h"#include "absl/debugging/symbolize.h"
void DumpStack() { constexpr int kMaxDepth = 32; void* pcs[kMaxDepth]; int n = absl::GetStackTrace(pcs, kMaxDepth, /*skip_count=*/1);
for (int i = 0; i < n; ++i) { char buf[1024]; if (absl::Symbolize(pcs[i], buf, sizeof(buf))) { std::fprintf(stderr, " #%d 0x%p %s\n", i, pcs[i], buf); } else { std::fprintf(stderr, " #%d 0x%p ??\n", i, pcs[i]); } }}skip_count=1은 GetStackTrace 자신의 프레임을 건너뛴다. DumpStack 자신도 빼고 싶으면 2로.
#Symbolize 초기화
심볼라이저는 한 번 초기화가 필요하다. main 진입 직후가 보통.
int main(int argc, char** argv) { absl::InitializeSymbolizer(argv[0]); // 실행 파일 경로 필요 // ... 이후 LOG(FATAL), Symbolize 등 사용 가능}argv[0]로부터 실행 파일을 열어 ELF/Mach-O 심볼 테이블을 읽는다. 한 번 초기화 후 Symbolize 호출은 alloc 없이 동작하도록 사전 buffer를 잡는다. signal handler 안전성의 핵심.
#내부 구현 — unwind
absl/debugging/internal/stacktrace_*.inc에 플랫폼별 구현이 있다. 주요 백엔드는 다음과 같다.
stacktrace_libunwind-inl.inc— libunwind 기반 (Linux x86_64, ARM64).stacktrace_x86-inl.inc— frame pointer 기반.stacktrace_powerpc-inl.inc,stacktrace_aarch64-inl.inc— 아키텍처 직접.stacktrace_win32-inl.inc— Windows.
기본 동작은 frame pointer 체인 추적이다.
[rbp 위로 거슬러 올라가며] rbp → 이전 rbp rbp + 8 → 호출자의 PC
각 프레임에서 PC를 pcs[]에 기록.frame pointer가 omitted된(-fomit-frame-pointer) 빌드에서는 libunwind가 DWARF .eh_frame을 사용해 좀 더 정교한 unwind를 한다. 비용은 frame pointer 방식보다 크지만 정확하다.
#내부 구현 — Symbolize
absl/debugging/symbolize_elf.inc (Linux 기준)의 흐름:
- PC가 속한 ELF 모듈 찾기 (
dl_iterate_phdr결과를 사전 캐싱). - 모듈 내
.symtab/.dynsym에서 PC ≤ symbol_value + size인 가장 가까운 심볼. - 결과 심볼 이름을 mangled 그대로 반환 (또는 demangle 옵션 적용).
// 핵심 (요약)bool Symbolize(const void* pc, char* out, int out_size) { uintptr_t addr = reinterpret_cast<uintptr_t>(pc);
// 1. PC가 속한 ELF 모듈 검색 (캐시) const SymbolicationModule* mod = FindModuleForPC(addr); if (!mod) return false;
// 2. symbol table에서 binary search const ElfSymbol* sym = mod->FindSymbol(addr - mod->base); if (!sym) return false;
// 3. demangle (옵션) return Demangle(sym->name, out, out_size);}핵심 설계 결정은 심볼 테이블을 사전에 mmap해 두고 signal handler 안에서는 alloc/lock 없이 lookup만 한다는 점.
#crash 핸들러에서 사용
absl/debugging/failure_signal_handler.h가 통합 도구를 제공한다.
#include "absl/debugging/failure_signal_handler.h"
int main(int argc, char** argv) { absl::InitializeSymbolizer(argv[0]); absl::FailureSignalHandlerOptions opts; opts.use_alternate_stack = true; opts.alarm_on_failure_secs = 3; absl::InstallFailureSignalHandler(opts); // ...}이제 SIGSEGV/SIGABRT/SIGFPE 발생 시 자동으로 심볼화된 스택이 stderr로 출력된다. LOG(FATAL) 출력 형식과 일치.
*** SIGSEGV (@0x0) received by PID 12345 (TID 0x7f...) ...PC: @ 0x55... main @ 0x7f... __libc_start_main @ 0x55... _start#std와 boost와의 비교
| 항목 | abseil | std (C++23) | boost |
|---|---|---|---|
| stack trace | GetStackTrace (async-safe) | std::stacktrace (not async-safe) | boost::stacktrace |
| symbolize | Symbolize (async-safe) | std::stacktrace_entry::description | demangle 호출 |
| signal handler | 안전 | 미정의 | 일부 안전 |
| 표준 포함 | × | C++23 | × |
| backtrace lib 의존 | optional libunwind | libstdc++ libbacktrace | libbacktrace/dbghelp |
C++23 std::stacktrace는 async-signal-safe를 요구하지 않는다. crash handler에서는 여전히 abseil이 적격.
#코드 리뷰 포인트
1. signal handler 안에서 alloc·lock 금지
// 회피 — std::string은 alloc, std::cout은 stream lockvoid Handler(int) { std::string s = MakeMessage(); std::cout << s;}
// Good — async-safe만void Handler(int) { static char buf[256]; int n = snprintf(buf, sizeof(buf), "crash\n"); ::write(STDERR_FILENO, buf, n);}absl::Symbolize는 내부 buffer를 사전 잡아둔다. handler 안에서 호출해도 lock 없음.
2. InitializeSymbolizer를 잊지 않는다
// 회피absl::Symbolize(pc, buf, sizeof(buf)); // 호출은 되지만 결과 빈약
// Goodabsl::InitializeSymbolizer(argv[0]);absl::Symbolize(pc, buf, sizeof(buf));초기화 없이도 함수가 동작하지만 심볼 lookup이 실패하거나 mangled 이름만 나온다.
3. 빌드 옵션 — frame pointer 유지
# Release 빌드에 frame pointer 유지 (스택 추적 정확도)g++ -O2 -fno-omit-frame-pointer ...-fomit-frame-pointer로 빌드된 release는 unwind가 부정확하다. 약간의 성능을 양보해도 디버깅 가능성을 선택하는 게 보통의 균형.
4. skip_count 정확히
int n = absl::GetStackTrace(pcs, 32, /*skip*/ 0); // GetStackTrace 자신 포함int n = absl::GetStackTrace(pcs, 32, /*skip*/ 1); // 호출자부터skip_count가 너무 작으면 출력 첫 줄에 absl::GetStackTrace만 보인다.
#자주 보는 안티패턴
signal handler에서 LOG(FATAL)
void Handler(int) { LOG(FATAL) << "crash"; } // 비추천LOG는 mutex를 잡는다. signal handler 안 lock은 데드락 위험. 대신 RAW_LOG(FATAL, "crash") (Part 2-07).
Symbolize 실패 시 빈 출력
if (absl::Symbolize(pc, buf, sizeof(buf))) { printf("%s\n", buf);}// else 무시 — PC라도 찍어야 분석 가능실패해도 PC를 hex로 남기면 addr2line으로 후처리할 수 있다.
stack depth 너무 작게
void* pcs[8]; // 깊은 스택에서 부족int n = absl::GetStackTrace(pcs, 8, 0);Google 내부 보통 32~64. 깊이 비용은 무시할 수준.
#정리
GetStackTrace로 PC 배열,Symbolize로 함수 이름.- 둘 다 async-signal-safe로 설계 — crash handler에서 직접 호출 가능.
InstallFailureSignalHandler가 SIGSEGV/SIGABRT/SIGFPE 통합 처리.- 프로덕션 빌드는 frame pointer 유지 + 심볼 테이블 포함.
LOG(FATAL)/CHECK실패 출력의 기반이다.
#다음 편
Part 16-02 — CRC32C에서 하드웨어 가속 체크섬을 본다.
#관련 항목
- Part 16-02 — CRC32C
- Part 16-03 — PeriodicSampler
- Part 2-07 — RAW_LOG
- Part 11-04 — stack trace handler
- Folly Part 9-01 — symbolizer
Abseil Code Review · 77 of 79
- 1 Abseil Code Review — Google production-grade C++ 라이브러리 분석
- 2 Abseil 개요 — Google이 std를 보완한 이유
- 3 Abseil 설계 철학 — std 호환과 추가 기능의 균형
- 4 Abseil 빌드와 의존성 — Bazel vs CMake
- 5 Abseil LTS vs HEAD 릴리스 모델 분석
- 6 Abseil Versioning과 ABI 호환성 정책
- 7 Abseil 매크로 — ABSL_HAVE_*·ABSL_ATTRIBUTE_*
- 8 Abseil ABSL_PREDICT_TRUE/FALSE — branch hint
- 9 absl::LogSeverity — 로그 레벨 타입
- 10 Abseil type_traits — negation·conjunction·void_t
- 11 Abseil Conformance·Policy 분석
- 12 Abseil Memory utilities 분석
- 13 Abseil raw_logging — heap-free 로깅
- 14 Abseil thread_annotations — clang TSA 통합
- 15 absl::Status — exception-free error handling
- 16 absl::StatusOr<T> — 값 또는 에러
- 17 absl status_macros — ASSIGN_OR_RETURN·RETURN_IF_ERROR
- 18 absl::Status payload — 구조화된 에러 컨텍스트
- 19 absl::Status ↔ exception 변환 패턴
- 20 absl::string_view — non-owning 문자열 참조
- 21 absl::string_view 함정 — dangling·c_str·임시 객체
- 22 absl::StrCat — 가변 인자 문자열 연결과 AlphaNum
- 23 absl::StrSplit — Delimiter·Predicate·컨테이너 변환
- 24 absl::StrJoin — 컨테이너 결합과 Formatter
- 25 absl::StrFormat — type-safe printf·FormatSpec
- 26 Abseil ASCII 함수 — locale-free 분류·대소문자 변환
- 27 Abseil Escape — CEscape·HexEscape·Base64
- 28 absl::flat_hash_map — Swiss Table 기반 hash map
- 29 absl::flat_hash_set — set 버전 Swiss Table
- 30 absl::node_hash_map — stable pointer가 필요할 때
- 31 absl::btree_map — sorted·cache-friendly B-tree
- 32 absl::FixedArray — 런타임 크기 stack 배열
- 33 absl::InlinedVector — small buffer optimization
- 34 Abseil Swiss Table internals — control byte·SIMD probing
- 35 absl::Mutex — reader-writer·fairness·deadlock 검출
- 36 absl::Mutex Conditional Critical Section — Await로 cv 없애기
- 37 absl::Notification — once-only signal
- 38 absl::BlockingCounter·Barrier — 다중 thread 조율
- 39 absl::Mutex annotations — clang thread-safety로 race를 컴파일 타임에
- 40 absl::Time·Duration 분석 — 단단한 type
- 41 absl::Time Format·Parse
- 42 absl::CivilTime 분석
- 43 absl::time_zone 분석
- 44 absl::Time mocking — 테스트 친화 시간
- 45 absl::BitGen — 모던 난수 생성기
- 46 Abseil Random Distributions — Uniform·Exponential
- 47 Abseil Mocking Random — 테스트 결정성
- 48 Abseil Random Seeding·Entropy
- 49 absl::int128·uint128 분석
- 50 absl::bits — popcount·countl_zero
- 51 absl::optional vs std::optional
- 52 absl::variant 분석
- 53 absl::span 분석
- 54 absl::any 분석
- 55 absl::compare — three-way 비교
- 56 Abseil utility — apply·in_place
- 57 Abseil AbslHashValue 분석
- 58 Abseil HashState chaining
- 59 Abseil Custom hashable 구현
- 60 Abseil LOG·VLOG·CHECK 분석
- 61 Abseil LogSink 분석
- 62 Abseil LogEntry·structured logging
- 63 Abseil Stack trace·failure_signal_handler
- 64 ABSL_FLAG 정의 분석
- 65 Abseil ParseCommandLine 동작
- 66 Abseil Flag introspection·validation
- 67 Google 스타일의 Abseil 사용 패턴
- 68 Abseil 자주 보는 anti-pattern
- 69 std → absl 마이그레이션 전략
- 70 absl::Cleanup — 함수 종료 시 실행 보장
- 71 Abseil algorithm container 확장 — c_sort·c_find_if·c_count_if
- 72 absl::function_ref와 any_invocable — 함수 객체 전달의 두 축
- 73 absl::bind_front와 Overload — 함수 객체 보조 도구
- 74 absl::Cord — 분산 시스템용 대용량 문자열
- 75 absl::from_chars·SimpleAtoi — 빠른 숫자 변환
- 76 absl::Cord vs std::string — 선택 기준과 메모리 프로파일
- 77 absl::GetStackTrace와 Symbolize — crash 시 readable stack
- 78 absl::ComputeCrc32c — 하드웨어 가속 체크섬
- 79 absl::PeriodicSampler — 적응형 샘플링·jitter 회피
관련 글
absl::Status payload — 구조화된 에러 컨텍스트
Part 3-04: Status payload — URL-based key로 구조화된 컨텍스트를 첨부, gRPC error_details와 연동.
같은 시리즈에서 이어 읽기
absl::PeriodicSampler — 적응형 샘플링·jitter 회피
absl::profiling_internal::PeriodicSampler — sampling rate를 동적으로 조정, geometric distribution으로 jitter 회피. 메모리 할당 추적·profiling 인프라의 기반.
같은 시리즈에서 이어 읽기
absl::ComputeCrc32c — 하드웨어 가속 체크섬
absl::ComputeCrc32c — SSE4.2 CRC32, ARM CRC 명령어로 가속된 CRC32C 구현. iSCSI·Btrfs·protobuf에서 표준화된 무결성 검사.
같은 시리즈에서 이어 읽기