snprintf() optimized out despite -fno-builtin on GCC 11

Viewed 72

I'm trying to wrap snprintf() in order to mock it for a unit test (via cmocka).

int
__wrap_snprintf(char *restrict str, size_t size, const char *restrict format,
                ...)
{
    int rc;
    va_list ap;

    va_start(ap, format);

    if (mocked) {  // global var
        check_expected_ptr(str);
        check_expected(size);
        check_expected_ptr(format);
        errno = mock_type(int);

        rc = (errno == 0) ? vsnprintf(str, size, format, ap) : -1;
    } else {
        rc = vsnprintf(str, size, format, ap);
    }

    va_end(ap);
    return rc;
}

I'm working with a testbed that includes many versions of Fedora, RHEL, CentOS, Debian, FreeBSD, OpenSUSE, and Ubuntu. This issue happens only on Ubuntu 22.04 LTS and 20.04 LTS (the two versions in the testbed) and Fedora 36 Power 9. It works fine on every other distro tested.

I've verified that the __wrap_snprintf() symbol is available in my unit test code and shares the same address as snprinf(). When I call it directly instead of as snprintf(), it works as expected. If I call snprintf(), __wrap_snprintf() does not get called as expected.

I've tried turning off optimization with __attribute__((optimize("-O0"))). I've tried removing the restrict keyword from my __wrap_snprintf() signature. These didn't help. I thought it was a linker issue until I tested with gcc -O0 and the issue disappeared. I've edited the post to reflect the fact that this is a compiler mis-optimization.

I found this, but the solution doesn't help; I'm already passing arguments after the format string: https://stackoverflow.com/a/39189436/7660197. Example call:

snprintf(buf, NAME_MAX, "%s%s", "hello", "world");

Flags:

gcc -fno-builtin -fno-inline -g -O2 -pthread -pthread -pthread -pthread -pthread -pthread -ggdb -fgnu89-inline -Wall -Waggregate-return -Wbad-function-cast -Wcast-align -Wdeclaration-after-statement -Wendif-labels -Wfloat-equal -Wformat-security -Wimplicit-fallthrough -Wmissing-prototypes -Wmissing-declarations -Wnested-externs -Wno-long-long -Wno-strict-aliasing -Wpointer-arith -Wstrict-prototypes -Wwrite-strings -Wunused-but-set-variable -Wformat=2 -Wformat-nonliteral -fstack-protector-strong -fstack-clash-protection -Werror -Wl,--wrap=calloc -Wl,--wrap=endgrent -Wl,--wrap=fopen -Wl,--wrap=getenv -Wl,--wrap=getpid -Wl,--wrap=getgrent -Wl,--wrap=getpwnam_r -Wl,--wrap=readlink -Wl,--wrap=setenv -Wl,--wrap=setgrent -Wl,--wrap=snprintf -Wl,--wrap=strdup -Wl,--wrap=uname -Wl,--wrap=unsetenv

Any ideas of what could be causing the different behavior in Ubuntu 22.04/20.04 and Fedora 36 Power 9 LE?

And any suggestions of the best way to prevent snprintf() from being optimized out while keeping optimizations elsewhere? Portable answers are preferred for future-proofing purposes. I need to support gcc and clang, although the misbehavior is occurring only on three of the gcc-using distros right now.


Edit:

The issue is that on the Ubuntu systems (and possibly the F36 Power 9 system -- I don't have direct access), the -fno-builtin compiler flag is not preventing __builtin___snprintf_chk() from being used in place of snprintf(). Is this a known issue, and/or does anyone know how to avoid it?

Problematic Ubuntu 22.04 system:

# gcc --version
gcc (Ubuntu 11.2.0-19ubuntu1) 11.2.0

# strings lib/common/tests/options/.libs/pcmk__env_option_test | grep snprintf
pcmk__mock_snprintf
__snprintf_chk
__wrap_snprintf
snprintf_error_get
pcmk__mock_snprintf
__builtin___snprintf_chk
snprintf_error_get
snprintf_error_get
pcmk__mock_snprintf
__snprintf_chk@GLIBC_2.3.4

A working F36 x86_64 system:

$ gcc --version
gcc (GCC) 12.1.1 20220507 (Red Hat 12.1.1-1)

$ strings lib/common/tests/options/.libs/pcmk__env_option_test | grep snprintf
__wrap_snprintf
pcmk__mock_snprintf
__wrap_snprintf
snprintf_error_get
pcmk__mock_snprintf
snprintf_error_get
snprintf_error_get
pcmk__mock_snprintf
__wrap_snprintf
0 Answers
Related