Skip to content

Ambiguous USDT argument resolution with duplicate static symbols in USDT Macros under GCC -O1 #13

@Phoenix500526

Description

@Phoenix500526

Hello everyone, I am Zhao Jiawei, a member of the bpftrace community. While adding support for SIB-addressing USDT argument specifications in libbpf, I encountered a potential issue related to GCC -O1 optimization.

When compiling programs with GCC 13 and the -O1 optimization flag, if different source files define static volatile variables with the same name (e.g., static volatile char ti = 0;), GCC may generate multiple entries for these identically named local symbols in .symtab.

For example, if both usdt_rip.c and test/usdt_rip.c define static volatile char ti = 0;, then after compilation, using readelf -s will show two local symbols named ti in .symtab, with very similar addresses or offsets, making them difficult to distinguish.

Specific Behavior:

GCC 13 (-O1) Behavior:
◦ Multiple ti symbols are generated in .symtab, and the USDT argument specification points to a RIP-relative address, such as "-1@ti(%rip)".
◦ When libbpf attempts to resolve this argument, the presence of multiple ti entries in .symtab makes it impossible to determine which specific ti symbol's address or offset the argument refers to, leading to ambiguous resolution.

Reproduction Steps and Example Output:

test/usdt_rip.c file content:

static volatile char ti = 0;

usdt_rip.c file content:

#include "usdt.h"

static volatile char ti = 0;

static inline void __attribute__((always_inline)) trigger_func() {
  USDT(usdt_rip_2, rip_global_var, ti);
}

int main() {
  trigger_func();
  return 0;
}

Compilation command: gcc -g -O1 usdt_rip.c test/usdt_rip.c -o usdt_rip
Relevant output from readelf -n usdt_rip:

$ readelf -n usdt_rip

Displaying notes found in: .note.gnu.property
  Owner                Data size        Description
  GNU                  0x00000020       NT_GNU_PROPERTY_TYPE_0
      Properties: x86 feature: IBT, SHSTK
        x86 ISA needed: x86-64-baseline

Displaying notes found in: .note.gnu.build-id
  Owner                Data size        Description
  GNU                  0x00000014       NT_GNU_BUILD_ID (unique build ID bitstring)
    Build ID: 188141687b9b80e7352931c9d38adca617625e35

Displaying notes found in: .note.ABI-tag
  Owner                Data size        Description
  GNU                  0x00000010       NT_GNU_ABI_TAG (ABI version tag)
    OS: Linux, ABI: 3.2.0

Displaying notes found in: .note.stapsdt
  Owner                Data size        Description
  stapsdt              0x0000003e       NT_STAPSDT (SystemTap probe descriptors)
    Provider: usdt_rip_2
    Name: rip_global_var
    Location: 0x0000000000001044, Base: 0x0000000000002004, Semaphore: 0x0000000000000000
    Arguments: -1@ti(%rip)

$ readelf -s usdt_rip | grep ti
     4: 0000000000004011     1 OBJECT  LOCAL  DEFAULT   25 ti
    14: 0000000000004012     1 OBJECT  LOCAL  DEFAULT   25 ti

This behavior prevents correct USDT argument resolution in libbpf and bpftrace, rendering some probes unusable when compiled with GCC

Problem Analysis (Why this is more likely a STAP_PROBE or USDT macro issue):

Initially, this issue was reported to GCC Bugzilla (Bug 121569). The systemtap has the same problem, but I don't know how to file an issue to its community.

In the discussion, Andrew Pinski and Frank Ch. Eigler pointed out that:
• It is perfectly valid to have identically named local symbols in different compilation units.
• The real core of the problem likely lies within the STAP_PROBE1 macro's implementation. The macro internally generates assembly code like .ascii "ti(%rip)" to reference the ti symbol.
• These STAP_PROBE* expressions are not processed by the assembler for runtime evaluation in the same way regular assembly code would be.
• This implies that the disambiguation logic for local symbols, typically performed by the assembler, is not applied to the USDT arguments generated by STAP_PROBE.
• GCC, according to its documentation, is correct in allowing ti(%rip) as a RIP-relative address. Therefore, this should not be considered a bug in GCC or binutils, but rather an issue with the definition of SDT (SystemTap DTrace) itself.
• Consequently, consumer tools (such as libbpf) that parse such expressions need to emulate assembler or Compilation Unit (CU)-type logic to find the correct instance of the corresponding symbol for proper mapping.

Workaround

Frank Ch. Eigler suggested a workaround: by referring to the variable in a different manner, such as STAP_PROBE1(foo, bar, &ti) or USDT(foo, bar, &ti);. This would likely cause GCC to load the address of ti into a register and pass that register's value, potentially avoiding a direct symbol name reference and helping to resolve the ambiguity.

But actually, if a user misuses the STAP_PROBE or USDT macro—for example, by directly referring to a static variable—there is no indication to inform them that they are doing it incorrectly. Therefore, could libbpf/usdt provide an additional macro (e.g., USDT_STATIC), or at least offer users a warning when the macro is misused?

Related Background

This issue was originally reported by me on GCC Bugzilla (Bug 121569), and there is a related Linux kernel mailing list discussion: https://lore.kernel.org/bpf/20250806092458.111972-1-phoenix500526@163.com/. I hope this information is helpful for further discussion and resolution of this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions