Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (seongtaejeong at gmail.com)
홈페이지
첨부 파일

Visual Studio - 디버깅 시 다른 함수의 소스 코드를 보여주는 사례 (Enable COMDAT Folding 옵션)

Visual Studio 등의 디버거를 이용해 (보통 Release 모드로 빌드된 바이너리를) 디버깅하다 보면 이상하게 다른 함수의 소스 코드가 보이는 경우가 있습니다.

이에 대해 친절하게 설명한 글이 있는데요, ^^

Why does the debugger show me the wrong function?
; https://devblogs.microsoft.com/oldnewthing/20050322-00/?p=36113

글에서 예로 든 상황을 직접 실습해 보겠습니다.




사실 지난번에 쓴 글이 바로,

Visual C++ - 디버그 코드에서 빌드 옵션 조정으로 최적화 코드로의 전환
; https://www.sysnet.pe.kr/2/0/14035

저 글의 내용을 쉽게 확인하기 위해 Debug 빌드에서 MyClass.cpp 파일만 Release 빌드로 바꾸는 방법을 소개한 것이었습니다. 하지만, 이 글의 실습을 하려면 소스 코드에 약간의 변화가 필요한데요, 왜냐하면 (글에서는 x86으로 실습하고 있어 상관없지만) x64로 빌드하는 경우 int*는 8바이트를 반환하고, int는 4바이트를 반환하기 때문에,

--- C:\temp\ConsoleApp1\ConsoleApplication1\MyClass.cpp ------------------------
ConsoleApplication1.exe!Class1::GetQ(void):
48 8B 41 08          mov         rax,qword ptr [rcx+8]  
C3                   ret  

--- C:\temp\ConsoleApp1\ConsoleApplication1\MyClass.cpp ------------------------
ConsoleApplication1.exe!Class2::GetValue(void):
8B 41 08             mov         eax,dword ptr [rcx+8]  
C3                   ret  

GetValue 함수의 반환 값을 __int64로 바꿔 데이터 바이트 크기를 맞춰주어야 합니다.

int* Class1::GetQ()
{
    return q;
}

__int64 Class2::GetValue()
{
    return value;
}

이제야 컴파일 후 디버깅하면 다음과 같이 동일한 코드로 나옵니다.

--- C:\temp\ConsoleApp1\ConsoleApplication1\MyClass.cpp ------------------------
ConsoleApplication1.exe!Class1::GetQ(void):
48 8B 41 08          mov         rax,qword ptr [rcx+8]  
C3                   ret  

--- C:\temp\ConsoleApp1\ConsoleApplication1\MyClass.cpp ------------------------
ConsoleApplication1.exe!Class2::GetValue(void):
48 8B 41 08          mov         rax,qword ptr [rcx+8]  
C3                   ret  




하지만, 이 상태에서는 GetQ와 GetValue 함수가 동일한 기계어 코드는 가져도, F11 Step-into 진입 시에 개별 함수의 소스 코드가 열리게 됩니다.

즉, 글의 내용과 같게 동작하도록 만들려면 /OPT:ICF 옵션을 추가해야 하는데요, 아쉽게도 이것은 Linker 옵션이기 때문에 개별 파일에 지정할 수는 없고 프로젝트 단위로 "Linker" / "Optimization" 범주에서 "Enable COMDAT Folding" 옵션을 "Yes (/OPT:ICF)"로 바꿔주어야 합니다.

linker_icf_opt_1.png

이후 빌드하면, GetQ 함수의 BP에서 다음과 같이 열리지만,

linker_icf_opt_2.png

GetValue 함수 내에서는 Visual Studio에서 BP를 걸고 싶어도 걸리지 않게 됩니다. 또한, GetQ를 호출하든 GetValue를 호출하든,

__int64 Whatever(Class2* p)
{
    return p->GetValue(); // 여기서 F11을 누르면 GetQ 함수의 소스 코드가 열림
}

int* Whatever(Class1* p)
{
    return p->GetQ(); // 여기서 F11을 누르면 GetQ 함수의 소스 코드가 열림
}

디버거 입장에서는 동일한 기계어 코드를 가지는 함수를 모두 1개의 함수로 번역하므로 소스 코드상으로 다른 호출이라고 해도 함수의 구별이 불가능하게 된 것입니다.

바로 이 상황이, Release 모드로 빌드된 바이너리를 디버깅할 때 BP를 걸거나 F11로 진입할 때 다른 함수의 소스 코드가 열리는 이유였습니다.




그리고 다음의 글에서는,

How can I confirm in the Windows debugger that I’m looking at a COMDAT-folded function?
; https://devblogs.microsoft.com/oldnewthing/20250725-00/?p=111409

WinDbg에서 특정 함수가 COMDAT Folding으로 인해 다른 함수와 합쳐졌는지 확인하는 방법을 소개하고 있는데요, 이것도 마저 실습해 보겠습니다. ^^

우선, 기존 소스 코드에서 main 함수에 getchar 호출을 추가하고,

#include <stdio.h>
#include "MyClass.h"

__int64 Whatever(Class2* p)
{
    return p->GetValue();
}

int* Whatever(Class1* p)
{
    return p->GetQ();
}

int main()
{
    Class2 c2;
    Whatever(&c2);

    Class1 c1;
    Whatever(&c1);

    getchar();
}

실행 후, WinDbg로 attach 시킨 후에 다음과 같이 각각의 함수 주소를 확인합니다.

0:004> x ConsoleApplication1!Class1::GetQ
00007ff7`e2c911c4 ConsoleApplication1!Class1::GetQ (void)

0:004> x ConsoleApplication1!Class2::GetValue
00007ff7`e2c911c4 ConsoleApplication1!Class2::GetValue (void)

2개 모두 00007ff7`e2c911c4 동일한 주소를 가리키고 있죠? ^^ 반대로 특정 함수의 주소를 알고 있다면 ln 명령어로 확인할 수도 있습니다.

0:004> ln 00007ff7`e2c911c4
Browse module
Set bu breakpoint

 [C:\temp\ConsoleApp1\ConsoleApplication1\MyClass.cpp @ 4] (00007ff7`e2c911c4)   ConsoleApplication1!Class1::GetQ   |  (00007ff7`e2c911d0)   ConsoleApplication1!_RTC_AllocaHelper
Exact matches:
    ConsoleApplication1!Class2::GetValue (void)
    ConsoleApplication1!Class1::GetQ (void)

그럼 2개의 함수(GetValue, GetQ)가 동일한 주소를 가지고 있다는 점과, 그 2개의 함수에 대해 "Class1::GetQ" 함수로 디버거가 인식할 거라는 사실을 출력으로 확인할 수 있습니다.




그나저나, COMDAT(common data)는 무슨 약어인 걸까요? ^^ 이에 대해 찾아보면,

Why is Identical COMDAT Folding called Identical COMDAT Folding?
; https://devblogs.microsoft.com/oldnewthing/20161024-00/?p=94575

FORTRAN 언어의 기능이었던 "common data block", 보통은 줄여서 "common block"이라고 했던 것에서 유래했다고 합니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 10/25/2025]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13962정성태7/7/20255262스크립트: 79. 파이썬 - onnxruntime_genai에서 지원하지 않는 모델 사용
13961정성태7/5/20255278디버깅 기술: 222. WinDbg 분석 사례 - IISreset 시점에 w3wp.exe의 crash 발생
13960정성태7/3/20255822개발 환경 구성: 752. ProcDump - C/C++ 예외 코드 필터를 지정한 덤프 생성 [2]
13959정성태6/25/20254985오류 유형: 966. Ubuntu - ping: connect: Network is unreachable
13958정성태6/21/20255824닷넷: 2339. C# - Phi-4-multimodal 모델의 GPU 가속 방법 (ORT 사용)파일 다운로드1
13957정성태6/20/20256639닷넷: 2338. C# / Foundry Local - Phi-4-multimodal 모델을 사용하는 방법 [1]
13956정성태6/19/20256672개발 환경 구성: 751. Triton Inference Server의 Python Backend 프로세스
13955정성태6/18/20256397오류 유형: 965. Hugging Face 모델 다운로드 시 "requests.exceptions.HTTPError: 401 Client Error: Unauthorized for url: ..." 오류
13954정성태6/18/20254906닷넷: 2337. C# - Hugging Face에 공개된 LLM 모델을 Foundry Local에서 사용하는 방법파일 다운로드1
13953정성태6/16/20255402스크립트: 78. 파이썬 - 소스 코드의 파일 경로를 지정한 모듈 로드
13952정성태6/15/20255542닷넷: 2336. C# - IValueTaskSource로 인해 주의가 필요한 ValueTask 호출파일 다운로드1
13951정성태6/15/20255850오류 유형: 964. Outlook - 일정이 "You cannot make changes to contents of this read-only folder." 오류 메시지로 삭제가 안 되는 경우
13950정성태6/12/20256683닷넷: 2335. C# - 간단하게 구현해 보는 IValueTaskSource 예제파일 다운로드1
13949정성태6/11/20256426오류 유형: 963. SignTool - "Error: SignerSign() failed." (-2146869243/0x80096005)
13948정성태6/10/20255323오류 유형: 962. 파이썬 - Linux 환경 + TCP 서버 소켓을 사용하는 프로세스 종료 후 재실행하는 경우 "OSError: [Errno 98] Address already in use" 오류 발생
13947정성태6/9/20256578개발 환경 구성: 750. 파이썬 - Azure App Service에 응용 프로그램 배포 후의 환경
13946정성태6/9/20256286개발 환경 구성: 749. 파이썬 - Azure App Service에 응용 프로그램 배포하기 전의 환경
13945정성태6/7/20255370오류 유형: 961. 파이썬 + conda - mysqlclient 사용 시 "NameError: name '_mysql' is not defined" 에러
13944정성태6/7/20258501오류 유형: 960. The trust relationship between this workstation and the primary domain failed. - 네 번째 이야기
13943정성태6/6/20255885개발 환경 구성: 748. Windows + Foundry Local - 로컬에서 AI 모델 활용 [1]
13942정성태6/5/20255024오류 유형: 959. winget 설치 시 "0x80d02002 : unknown error"
13941정성태6/2/20256082닷넷: 2334. C# - cpuid 명령어를 이용한 CPU 제조사 문자열 가져오기파일 다운로드1
13940정성태6/1/20256396C/C++: 188. C++의 32비트 + Release 어셈블리 코드를 .NET으로 포팅할 때 주의할 점파일 다운로드1
13939정성태5/29/20257274오류 유형: 958. NVIDIA Triton Inference Server - version `GLIBCXX_3.4.32' not found (required by /opt/tritonserver/backends/python/triton_python_backend_stub)
13938정성태5/29/20255228개발 환경 구성: 747. 파이썬 - WSL/docker에 구성한 Triton 예제 개발 환경
13937정성태5/24/20255961개발 환경 구성: 746. Windows + WSL2 환경에서 (tensorflow 등의) NVIDIA GPU 인식
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...