winternl

cybersecurity & programming

Why is there a debug directory in my release build?

(And other difficult conversations to have with your kids)

If you’ve spent some time with MSVC you may have noticed your binary contains an IMAGE_DEBUG_DIRECTORY entry — even when building in release mode.

This isn’t a new thing and this extra information was introduced in the 2015 release of Visual Studio. In fact, there’s a good reference by Adam over on their blog covering this topic. Their recommendation was to add the undocumented linker flag /NOCOFFGRPINFO to remove this directory.

Testing with Visual Studio 2022, this linker option alone no longer provides a complete solution.

Problem

Building a simple C++ project in release mode, despite diligently setting both the compiler and linker options to emit no debug information, you might end up with an executable containing a debug directory looking similar to this.

> dumpbin /headers project.exe

... 

  Debug Directories

        Time Type        Size      RVA  Pointer
    -------- ------- -------- -------- --------
    66F99DE5 coffgrp      30C 0001D484    1C084    4C544347 (LTCG)
    66F99DE5 iltcg          0 00000000        0
#define PE_IMAGE_DEBUG_TYPE_POGO             13
#define PE_IMAGE_DEBUG_TYPE_ILTCG            14

The COFFGRP type corresponds with the IMAGE_DEBUG_TYPE_POGO value, where POGO stands for ‘Profile Guided Optimization’. More information on POGO here.

LTCG stands for ‘link time code generation’ and more information can be found here. The ‘i’ stands for ‘incremental’.

Neither of these entries are officially documented. If anyone has references to their structures, please do share. So, while the data they point to is opaque, it does appear that you can just remove them without affecting the program’s execution — after all it is a debug directory.

But doing so would require a post-built step and binary modification. It would be best to have MSVC omit this information in the first place.

Solution

If you have a POGO entry, use the linker switch /NOCOFFGRPINFO to omit the debug entry. Nothing has changed about this recommendation.

Contrary to popular opinion, if the binary has a ILTCG entry you should not use the commonly suggested /EMITPOGOPHASEINFO linker switch. This internal switch has no effect on removing the debug entry and any experiences contrary are coincidental.

Another erroneous flag that has been suggested to omit the ILTCG entry is the /EMITTOOLVERSIONINFO:NO linker switch. This switch also has no effect on this debug entry, but will omit the Rich header.

The solution is to change the link time code generation strategy using documented(!) linker switches.

Visual Studio defaults to using:

Use Fast Link Time Code Generation (/LTCG:incremental)

Instead, if you wish to omit the ILTCG entry, select:

Use Link Time Code Generation (/LTCG)

In doing so, you will likely see a significant decrease in linker performance. More information here.

tl;dr

/LTCG /NOCOFFGRPINFO