Subject: export-symbols-regex does not work with C++ mangled symbols on macOS

From: [email-masked]
Date: Wed, 10 Apr 2019 23:59:01 -0700

Hi,

On macOS, it seems that libtool's export-symbols-regex option tests the
mangled names of C++ symbols against the given regex, rather than the
demangled names, when deciding which symbols to export.

When tested on other systems, it seems that C++ symbols are exported as
expected, that is, by testing the demangled names.

Here is a small project that demonstrates the problem:

-----configure.ac-----
AC_INIT([wat], [0])
AM_INIT_AUTOMAKE([foreign])
AC_PROG_CXX
LT_INIT([disable-static])
AC_CONFIG_FILES([Makefile])
AC_OUTPUT

-----Makefile.am-----
lib_LTLIBRARIES = libwat.la
libwat_la_LDFLAGS = $(AM_LDFLAGS) -export-symbols-regex "^[^_]"
libwat_la_SOURCES = wat.cpp wat.h
bin_PROGRAMS = wat
wat_LDADD = libwat.la
wat_SOURCES = main.cpp

-----wat.cpp-----
#include "wat.h"
int _private(void) { return 0; }
int csymbol(void) { return _private(); }
int mangledsymbol(void) { return _private(); }

-----wat.h-----
#pragma once
extern "C" int csymbol(void);
int mangledsymbol(void);

-----main.cpp-----
#include "wat.h"
int main(void) {
    return csymbol() + mangledsymbol();
}

The expected behaviour is that the project builds with no errors when
executing autoreconf -if && ./configure && make.

However, on the affected system (macOS 10.13.6) this is the output of the
link step:

/bin/sh ./libtool  --tag=CXX   --mode=link g++  -g -O2
-export-symbols-regex "^[^_]"  -o libwat.la -rpath /usr/local/lib wat.lo
libtool: link: /usr/bin/nm -B  .libs/wat.o   | sed -n -e 's/^.*[
 ]\([BCDEGRST][BCDEGRST]*\)[     ][     ]*_\([_A-Za-z][_A-Za-z0-9]*\)$/\1
_\2 \2/p' | sed '/ __gnu_lto/d' | /usr/bin/sed 's/.* //' | sort | uniq >
.libs/libwat.exp
libtool: link: /usr/bin/grep -E -e "^[^_]" ".libs/libwat.exp" >
".libs/libwat.expT"
libtool: link: mv -f ".libs/libwat.expT" ".libs/libwat.exp"
libtool: link: sed 's|^|_|' < .libs/libwat.exp > .libs/libwat-symbols.expsym
libtool: link: g++ -dynamiclib -Wl,-undefined -Wl,dynamic_lookup -o
.libs/libwat.0.dylib  .libs/wat.o    -g -O2   -install_name
/usr/local/lib/libwat.0.dylib -compatibility_version 1 -current_version 1.0
-Wl,-single_module -Wl,-exported_symbols_list,.libs/libwat-symbols.expsym
libtool: link: (cd ".libs" && rm -f "libwat.dylib" && ln -s
"libwat.0.dylib" "libwat.dylib")
libtool: link: ( cd ".libs" && rm -f "libwat.la" && ln -s "../libwat.la" "
libwat.la" )
/bin/sh ./libtool  --tag=CXX   --mode=link g++  -g -O2   -o wat main.o
libwat.la
libtool: link: g++ -g -O2 -o .libs/wat main.o -Wl,-bind_at_load
./.libs/libwat.dylib
Undefined symbols for architecture x86_64:
  "mangledsymbol()", referenced from:
      _main in main.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see
invocation)
make: *** [wat] Error 1

As you can see, mangledsymbol() is not exported, despite not beginning with
an underscore and therefore matching the exported-symbols-regex.

A workaround, at least for the particular name mangling scheme used by GCC
and Clang, is to give ^(_Z\d+)?[^_] as the regex. With this regex, the
program builds as expected, demonstrating that the mangled symbol names are
tested against the regex.

I'd guess that the symbols in libwat.exp should be filtered by demangled
name, rather than mangled name. (Although, it looks like the list is
generated using nm, so how this happens to work on systems with GNU nm,
which also seems to have --no-demangle as the default, I don't know.)

Here are the library's symbols according to nm:

$ nm .libs/libwat.dylib
0000000000000fb0 t __Z13mangledsymbolv
0000000000000f90 t __Z8_privatev
0000000000000fa0 T _csymbol
                 U dyld_stub_binder
$ nm -demangle .libs/libwat.dylib
0000000000000fb0 t mangledsymbol()
0000000000000f90 t _private()
0000000000000fa0 T _csymbol
                 U dyld_stub_binder

Version information:

$ libtool --version
libtool (GNU libtool) 2.4.6
Written by Gordon Matzigkeit, 1996

Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
$ ld -v
@(#)PROGRAM:ld  PROJECT:ld64-409.12
BUILD 17:47:51 Sep 25 2018
configured to support archs: armv6 armv7 armv7s arm64 i386 x86_64 x86_64h
armv6m armv7k armv7m armv7em arm64e arm64_32
LTO support using: LLVM version 10.0.0, (clang-1000.11.45.5) (static
support for 21, runtime is 21)
TAPI support using: Apple TAPI version 10.0.0 (tapi-1000.11.8.2)
$ nm --version
Apple LLVM version 10.0.0 (clang-1000.11.45.5)
  Optimized build.
  Default target: x86_64-apple-darwin17.7.0
  Host CPU: broadwell

Best regards,
-- 
Philip