I agree with your conclusion that llvm-nm.exe cannot be used. This is unfortunate. At least it is known for sure now.
This makes me once again question libtool's "auto-export" feature on MSVC. It seems to do more harm than good, but some libraries still may rely on it to export their symbols. Also recall that there is no auto-import for variables on MSVC. I am not sure if _NO_CRT_STDIO_INLINE is a user feature macro since it does not seem to be documented at all. - Kirill Makurin ________________________________ From: Bruno Haible <br...@clisp.org> Sent: Tuesday, June 17, 2025 12:34 AM To: Kirill Makurin <maiddais...@outlook.com> Cc: bug-gnulib@gnu.org <bug-gnulib@gnu.org> Subject: Re: clang-cl: undeclared library function 'sprintf' Kirill Makurin wrote: > This issue was coming from "NM=llvm-nm.exe". Well, I think the issue has two causes: 1) The MSVC <stdio.h> defines some functions, such as sprintf, as non-static inline functions by default. You can see that it is independent of the compiler and independent of whether 'dumpbin' or 'llvm-nm' is used to inspect the object file: $ cat hello.c #include <stdio.h> #include <stdlib.h> int main () { printf("Hello world\n"); fflush(stdout); return 0; } $ cl -c -Os hello.c $ dumpbin -nologo -symbols hello.obj | grep External | grep -v UNDEF 011 00000000 SECT5 notype () External | __local_stdio_printf_options 015 00000000 SECT6 notype () External | _vfprintf_l 016 00000000 SECT7 notype () External | printf 017 00000000 SECT4 notype () External | main $ llvm-nm hello.obj | grep ' [A-TV-Z] ' 00000008 C ?_OptionsStorage@?1??__local_stdio_printf_options@@9@9 00000000 T __local_stdio_printf_options 00000000 T _vfprintf_l 00000000 T main 00000000 T printf $ clang-cl -c -Os hello.c $ dumpbin -nologo -symbols hello.obj | grep External | grep -v UNDEF 00A 00000000 SECT5 notype () External | printf 00F 00000000 SECT6 notype () External | __local_stdio_printf_options 012 00000000 SECT7 notype External | ??_C@_0N@NLPDAPMJ@Hello?5world?6?$AA@ (`string') 01C 00000000 SECT1 notype () External | main $ llvm-nm hello.obj | grep ' [A-TV-Z] ' 00000000 R ??_C@_0N@NLPDAPMJ@Hello?5world?6?$AA@ 00000000 T __local_stdio_printf_options 00000000 T main 00000000 T printf And it can be turned off by defining the C macro _NO_CRT_STDIO_INLINE: $ cl -c -Os hello.c -D_NO_CRT_STDIO_INLINE $ dumpbin -nologo -symbols hello.obj | grep External | grep -v UNDEF 00E 00000000 SECT4 notype () External | main $ llvm-nm hello.obj | grep ' [A-TV-Z] ' 00000000 T main $ clang-cl -c -Os hello.c -D_NO_CRT_STDIO_INLINE $ dumpbin -nologo -symbols hello.obj | grep External | grep -v UNDEF 011 00000000 SECT1 notype () External | main $ llvm-nm hello.obj | grep ' [A-TV-Z] ' 00000000 T main 2) > After changing to NM="dumpbin.exe -nologo -symbols" (which I use with cl.exe), > both libtextstyle and libunistring build successfully. In libtool invocations, the use of $NM is followed by a variable 'global_symbol_pipe'. In libtool.m4 [1] two possible values are defined for this variable. Since you say that with 'dumpbin' the exports of the DLL are controlled correctly whereas with 'llvm-nm' they are not, there are two possibilities: * Either the 'global_symbol_pipe' value for this case (libtool.m4 line 4211) needs to be corrected. * Or, possibly, there is no way to do so, because the output of 'llvm-nm' does not contain the necessary information. Looking at the complete output of 'llvm-nm hello.obj', even with various command-line options like --no-weak, I conclude that the cause is the latter. That is, you *can't* use "NM=llvm-nm.exe", and NM="dumpbin -symbols" is the only choice that works. Like I documented in INSTALL.windows and repeated in [2]. Bruno [1] https://gitweb.git.savannah.gnu.org/gitweb/?p=libtool.git;a=blob;f=m4/libtool.m4;h=99b53ad046fb46afeabc3824b6f2f6ddec9b6935;hb=HEAD#l4189 [2] https://lists.gnu.org/archive/html/bug-gnulib/2025-06/msg00098.html