How can I debug a function that has been subjected to COMDAT folding?

Suppose you want to set a breakpoint on a function, but you find that the function has been subjected to COMDAT folding, so your attempt to set a breakpoint on the function ends up setting a breakpoint on some other function, and your breakpoint ends up firing when either your desired function or the other identical function gets called. How can you get your breakpoint to fire only on the function you are debugging?

One way to do this is to disable COMDAT folding temporarily and rebuild. Mind you, this may result in your binary size exploding, but since you're just debugging, this probably doesn't bother you that much. On the other hand, there may be parts of the program that are relying on COMDAT folding, or it may be hard to find the build setting that controls COMDAT folding, and you run the risk of forgetting to change the setting back and accidentally committing a change that disables COMDAT folding!

The easy way is to mutate the function. Add a call to a harmless function like Get­Tick­Count(). Note that the harmless function must be something the compiler can't optimize out, so don't try free(nullptr) because the compiler is allowed to take advantage of the fact that free(nullptr) is required by the language standard to have no effect and can consequently optimize the call out entirely.

Then again, if you're going to mutate the function, you may as well mutate it in a way that makes debugging easier. For example, you might add

bool breakpoint = false;

void TheFunction()
 if (breakpoint) DebugBreak();
 ... rest of function ...

Then you can patch the breakpoint variable to true and boom, there's your breakpoint.

If you can't recompile the binary, then your options are more limited. If the set of callers is manageable, you could try setting a breakpoint on each of the callers. Or if there is something in the function that lets you detect which identical function you're in, you can use that. For example, maybe the two functions are

class Circle
   virtual int GetRadius() { return m_radius; }
  int m_radius;
  int m_xcenter;
  int m_ycenter;

class Channel
   int GetId() { return m_id; }
  HANDLE m_signal;
  int m_id;

Since Circle::Get­Radius and Channel::Get­Id compile to the same code, they will get COMDAT-folded. But you can still figure out which method you're in by looking at other parts of the this pointer. In the example above, you see that Circle has a virtual method, hence a vtable, so you can use a conditional breakpoint to check whether the vtable matches.

If you use a boring debugger, it might be something like this:

0:001> bp Circle::GetRadius "j poi(ecx)==0x10014270 r;g"

If you use a fancy debugger, then use your fancy debugger's conditional breakpoint facility.

Comments (3)
  1. Adrian says:

    Just last week, I was helping someone debug a problem that was aggravated by COMDAT folding. The software had a test that purposely mutates a function at runtime. Because of cool optimizations, the target function was COMDAT-folded into another function that never should have been mutated. It took a while to figure out what was actually happening.

    Interestingly, COMDAT folding, while an important optimization, leads to C++ standard violations which requires function to have unique identities. (You should be able to take the pointer of a function and know that it won’t match any pointer to another function, even if that other function is byte-for-byte identical.)

    1. Voo says:

      While true (which is why it is disabled it you compile with strict standards compliance from what I remember), self modifying code is a maintenance nightmare in any case. I’ve yet to come across a situation where there wasn’t a better solution available (except for some rare performance optimisations that is)

  2. alegr1 says:

    One stupid thing DDK/WDK (and maybe VC) was doing was enabling ICF in the debug build.

Comments are closed.

Skip to main content