Why Are register_tm_clones and deregister_tm_clones in Your C/C++ Binaries?
When reverse engineering a minimal C or C++ program—or even just inspecting binary symbols using objdump, nm, or tools like Dogbolt—you will often spot unexpected boilerplate functions such as register_tm_clones and deregister_tm_clones. Even for a trivial program like int main() { return 123; }, these symbols persist.
Why does GCC or Clang include them when you didn't ask for them, and what purpose do they serve? Here is a clear breakdown of why these functions exist, where they come from, and whether you can eliminate them.
What Does "TM" Stand For?
In this context, TM stands for Transactional Memory (specifically, Hardware or Software Transactional Memory implemented via GCC's libitm library). Transactional memory allows blocks of code to execute atomically—similar to database transactions—without traditional explicit locking mechanisms (like mutexes).
When code is compiled with transactional memory support (via the -fgnu-tm flag), the compiler creates specialized transactional variants or "clones" of certain functions. These clones must be tracked dynamically so the runtime can dispatch execution to the correct clone during a transaction.
The Role of register_tm_clones and deregister_tm_clones
The two functions have specific tasks in the lifecycle of an ELF binary or shared library:
register_tm_clones: Invoked during program startup (initialization). It registers the binary's list of transactional clone functions with the Transactional Memory runtime library (libitm), if present.deregister_tm_clones: Invoked when an executable or dynamic library unloads (termination). It cleans up any registered transactional clones from the runtime registry.
Inside an executable compiled without -fgnu-tm, these functions essentially do nothing. They simply inspect an internal table (which is empty) and immediately return.
Why Are They Present If You Didn't Request Transactional Memory?
The key reason is standardized runtime startup files (C Runtime / CRT) provided by GCC and the GNU C Library (glibc).
When you run g++ -O3 -o my_program my_program.cpp, the linker does not only assemble your code. It links in runtime initialization files, specifically object files like crtbegin.o and crtend.o (supplied by GCC).
1. One-Size-Fits-All Initialization Code
GCC distributes precompiled startup objects (crtbegin.o) that are linked into all binaries by default. These objects contain the entry and exit machinery for:
- Global constructors and destructors (
.init_arrayand.fini_array) - Exception handling frame tables (
.eh_frame) - Transactional memory table registration
Distributing a single, universal initialization object that can safely accommodate any feature—including transactional memory—vastly simplifies the compiler toolchain. Maintaining separate static initialization files for every conceivable permutation of compiler flags (with TM, without TM, with exceptions, without exceptions) would lead to combinatorial explosion.
2. Weak Symbols and Zero-Cost Fallbacks
Because the startup code uses weak symbols for the actual libitm registration hooks, it incurs practically zero overhead when transactional memory is not in use. If libitm is not linked, the registration code simply identifies that there are no clones to register and exits quickly.
Can You Remove Them?
For standard desktop or server applications, there is no performance or security reason to remove them; they consume only a few dozen bytes of space in the .text section. However, in deeply embedded systems, bootloaders, or code-golf challenges where every single byte counts, you can omit the standard runtime startup files entirely.
To build an executable without standard startup files, use the -nostartfiles or -nostdlib flags, though you will need to provide your own _start symbol:
/* mini.c */
void _start() {
// Inline syscall for sys_exit(123) on Linux x86_64
__asm__ volatile (
"mov $60, %%rax\n"
"mov $123, %%rdi\n"
"syscall\n"
:
:
: "rax", "rdi"
);
}Compile it without the default C runtime files:
gcc -nostartfiles -O3 -o mini mini.cInspecting mini with nm mini will reveal that register_tm_clones and deregister_tm_clones are completely gone, along with the rest of the CRT initialization code.
Summary
The symbols register_tm_clones and deregister_tm_clones appear in your binaries because they are built into GCC's standard CRT initialization objects (crtbegin.o). They exist to support Transactional Memory cleanly across the ecosystem without requiring multiple specialized runtime libraries. If you are not using transactional memory, they are harmless, self-contained no-ops.