외계어 같은 C++ 심볼 해석하기: c++filt 명령어 동작 원리 완벽 가이드

개요

리눅스 환경에서 C++ 바이너리를 분석하거나 디버깅할 때 복잡하게 암호화된 듯한 심볼 이름을 자주 마주칩니다. 이는 C++ 컴파일러가 함수 오버로딩과 네임스페이스를 처리하기 위해 수행하는 네임 맹글링(Name Mangling) 결과입니다. 본 글에서는 c++filt 명령어를 활용하여 난해한 맹글링 심볼을 원본 함수 형태로 디코딩하는 원리와 실무 활용법을 살펴봅니다.

c++filt 환경 준비 및 단일 심볼 디코딩 기초

c++filt 도구가 포함된 바이너리 유틸리티 패키지를 설치하기 위해 apt-get update -qq && apt-get install -y binutils 명령어를 실행합니다.

apt-get update -qq && apt-get install -y binutils

c++filt --version | head -n 2 명령어를 실행하여 설치된 도구의 버전 정보를 확인합니다.
c++filt는 C++ 컴파일러에 의해 이름 맹글링(Name Mangling)된 심볼을 사람이 읽을 수 있는 원래의 함수 및 클래스 형태로 디코딩하는 도구입니다.
⚙️ [주요 옵션]

-_, --strip-underscore : 심볼 시작 부분의 밑줄(_)을 제거하고 디코딩합니다.
-n, --no-strip-underscore : 심볼 시작 부분의 밑줄을 제거하지 않고 유지합니다.
-p, --no-params : 함수의 매개변수 목록을 생략하고 함수 이름만 출력합니다.
-t, --types : 함수 심볼뿐만 아니라 데이터 타입 이름도 함께 디코딩합니다.
-s, --format : 특정 컴파일러 규격에 맞는 디맹글링 포맷을 지정합니다.

c++filt –version | head -n 2

기본적인 단일 심볼을 해석하기 위해 c++filt _Z1fv 명령어를 실행합니다.
디코딩 결과 매개변수가 없는 함수 원형인 f()로 정확히 복원되어 출력됩니다.

c++filt _Z1fv

네임스페이스와 매개변수 타입 정보가 포함된 심볼을 해석하기 위해 c++filt _ZN4Math3addEii 명령어를 실행합니다.
중첩된 Math 네임스페이스의 add 함수와 두 개의 int 매개변수가 해석되어 Math::add(int, int) 형태로 출력됩니다.

c++filt _ZN4Math3addEii

C++ 네임 맹글링 발생 과정과 nm 명령어 연계 분석

C++ 소스 코드를 컴파일할 수 있는 개발 환경을 구축하기 위해 apt-get update -qq && apt-get install -y build-essential 명령어를 실행합니다.

apt-get update -qq && apt-get install -y build-essential

네임스페이스와 함수 오버로딩이 적용된 C++ 예제 코드를 생성하기 위해 cat << 'EOF' > /tmp/demo.cpp 명령어를 실행합니다.

cat « ‘EOF’ > /tmp/demo.cpp
namespace Calculator {
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }
}
EOF

작성한 C++ 소스 코드를 기계어 심볼이 담긴 오브젝트 파일로 컴파일하기 위해 g++ -c /tmp/demo.cpp -o /tmp/demo.o 명령어를 실행합니다.

g++ -c /tmp/demo.cpp -o /tmp/demo.o

생성된 오브젝트 파일 내부에서 add 함수와 관련된 심볼 목록을 확인하기 위해 nm /tmp/demo.o | grep add 명령어를 실행합니다.
컴파일러의 네임 맹글링으로 인해 함수 이름이 네임스페이스와 매개변수 정보가 암호화된 _ZN10Calculator3addEii 및 _ZN10Calculator3addEdd 형태로 변환되어 출력됩니다.

nm /tmp/demo.o | grep add

nm으로 확인한 맹글링 심볼들을 파이프라인을 통해 원래의 함수 형태로 일괄 복원하기 위해 nm /tmp/demo.o | c++filt | grep add 명령어를 실행합니다.
c++filt를 거치면서 복잡한 맹글링 심볼이 Calculator::add(int, int) 및 Calculator::add(double, double)와 같이 사람이 읽을 수 있는 명확한 함수 원형으로 변환되어 출력됩니다.

nm /tmp/demo.o | c++filt | grep add

복잡한 템플릿 클래스 심볼과 파이프라인 실시간 디코딩

STL 컨테이너인 std::vector와 std::string을 활용하는 템플릿 클래스 예제 코드를 작성하기 위해 cat << 'EOF' > /tmp/template_demo.cpp 명령어를 실행합니다.

cat « ‘EOF’ > /tmp/template_demo.cpp
#include 
#include 
template 
class Storage {
public:
void print(T val) {}
};
template class Storage<std::vectorstd::string>;
EOF

작성한 템플릿 소스 코드를 심볼 분석을 위한 오브젝트 파일로 컴파일하기 위해 g++ -c /tmp/template_demo.cpp -o /tmp/template_demo.o 명령어를 실행합니다.

g++ -c /tmp/template_demo.cpp -o /tmp/template_demo.o

생성된 오브젝트 파일에서 print 멤버 함수와 관련된 심볼을 검색하기 위해 nm /tmp/template_demo.o | grep print 명령어를 실행합니다.
템플릿 매개변수와 중첩된 STL 타입 정보가 인코딩되면서 사람이 육안으로 식별하기 어려운 극도로 길고 복잡한 맹글링 심볼이 출력됩니다.

nm /tmp/template_demo.o | grep print

파이프라인을 활용해 nm 출력 결과를 c++filt로 전달하여 복잡한 심볼을 실시간으로 복원하기 위해 nm /tmp/template_demo.o | c++filt | grep print 명령어를 실행합니다.
난해하게 인코딩되었던 긴 심볼이 원래의 템플릿 구조와 매개변수 타입을 온전히 포함한 가독성 높은 클래스 및 함수 원형으로 변환되어 출력됩니다.

nm /tmp/template_demo.o | c++filt | grep print

반환 타입 제거 옵션(-p)과 오류 로그 파일 파싱 실무

기본 옵션을 통해 심볼의 전체 시그니처를 확인하기 위해 c++filt _ZN10Calculator3addEii 명령어를 실행합니다.
네임스페이스와 클래스 멤버 함수 이름 뒤에 (int, int)와 같이 매개변수 타입 목록까지 모두 포함된 완전한 함수 형태로 복원되어 출력됩니다.

c++filt _ZN10Calculator3addEii

매개변수 목록을 제외하고 함수 식별자만 간결하게 확인하기 위해 c++filt -p _ZN10Calculator3addEii 명령어를 실행합니다.
-p 옵션이 적용되어 인자 타입 정보가 생략되고 클래스 경로와 함수 이름인 Calculator::add만 깔끔하게 출력됩니다.

c++filt -p _ZN10Calculator3addEii

타입 디코딩 모드를 지정하여 심볼을 해석하기 위해 c++filt -t _ZN10Calculator3addEii 명령어를 실행합니다.
-t 옵션을 사용하더라도 함수 심볼의 구조와 매개변수 타입 정보가 정상적으로 유지되어 Calculator::add(int, int) 형태로 출력됩니다.

c++filt -t _ZN10Calculator3addEii

오류 로그 문장 속의 맹글링 심볼을 자동으로 찾아 치환하기 위해 echo 'Fatal error in _ZN10Calculator3addEii at runtime' | c++filt 명령어를 실행합니다.
c++filt는 파이프로 전달된 전체 텍스트 문맥을 그대로 보존하면서 문장 내에 포함된 C++ 심볼만 선별적으로 감지하여 읽기 쉬운 원래 함수 이름으로 자동 변환합니다.

echo ‘Fatal error in _ZN10Calculator3addEii at runtime’ | c++filt

지금까지 외계어처럼 난해했던 C++ 네임 맹글링(Name Mangling)의 원리와 이를 직관적으로 해석해 주는 c++filt 명령어의 동작 원리를 간단하게 살펴보았습니다.
앞으로 링커 오류나 복잡한 디버깅 상황을 마주하더라도 c++filt를 활용해 원하는 심볼을 빠르게 파악해 보시기 바랍니다.