Package: src:ffmpeg
Version: 7:6.1-5
Severity: normal

Hi,

While developing a program against the ffmpeg libraries, I noticed that
a number of threads were being implicitly created at process startup.
Attached is a minimal test case that replicates the behavior. It prints
the output of the avfilter_configuration() function and then prints the
number of running threads. Here is the output on my machine:

    --prefix=/usr --extra-version=5 --toolchain=hardened 
--libdir=/usr/lib/x86_64-linux-gnu --incdir=/usr/include/x86_64-linux-gnu 
--arch=amd64 --enable-gpl --disable-stripping --enable-gnutls --enable-ladspa 
--enable-libaom --enable-libass --enable-libbluray --enable-libbs2b 
--enable-libcaca --enable-libcdio --enable-libcodec2 --enable-libdav1d 
--enable-libflite --enable-libfontconfig --enable-libfreetype 
--enable-libharfbuzz --enable-libfribidi --enable-libglslang --enable-libgme 
--enable-libgsm --enable-libjack --enable-libmp3lame --enable-libmysofa 
--enable-libopenjpeg --enable-libopenmpt --enable-libopus --enable-libpulse 
--enable-librabbitmq --enable-librist --enable-librubberband --enable-libshine 
--enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt 
--enable-libssh --enable-libtheora --enable-libtwolame --enable-libvidstab 
--enable-libvorbis --enable-libvpx --enable-libwebp --enable-libx265 
--enable-libxml2 --enable-libxvid --enable-libzimg --enable-libzmq 
--enable-libzvbi --enable-lv2 --enable-omx --enable-openal --enable-opencl 
--enable-opengl --enable-sdl2 --disable-sndio --enable-libjxl 
--enable-pocketsphinx --enable-librsvg --enable-libvpl --disable-libmfx 
--enable-libdc1394 --enable-libdrm --enable-libiec61883 --enable-chromaprint 
--enable-frei0r --enable-libsvtav1 --enable-libx264 --enable-libplacebo 
--enable-librav1e --enable-shared

    Threads:    12

It looks like this is due to the following dependency chain:

1. libavfilter depends on libsphinxbase:

    $ objdump -p /usr/lib/x86_64-linux-gnu/libavfilter.so.9 | grep sphinxbase
      NEEDED               libsphinxbase.so.3

2. libsphinxbase depends on libblas:

    $ objdump -p /usr/lib/x86_64-linux-gnu/libsphinxbase.so.3 | grep libblas
      NEEDED               libblas.so.3

3. libblas depends on libopenblas:

    $ objdump -p /usr/lib/x86_64-linux-gnu/libblas.so.3 | grep libopenblas
      NEEDED               libopenblas.so.0

The libopenblas.so.0 library contains an __attribute__((constructor))
function that runs an initialization function inside the libopenblas
library. In any application that happens to link against ffmpeg's
libavfilter (or libavdevice, which depends on libavfilter). This can be
confirmed with gdb on the attached test case program:

    $ gdb ./avfilter_test
    GNU gdb (Debian 13.2-1) 13.2
    Copyright (C) 2023 Free Software Foundation, Inc.
    License GPLv3+: GNU GPL version 3 or later 
<http://gnu.org/licenses/gpl.html>
    This is free software: you are free to change and redistribute it.
    There is NO WARRANTY, to the extent permitted by law.
    Type "show copying" and "show warranty" for details.
    This GDB was configured as "x86_64-linux-gnu".
    Type "show configuration" for configuration details.
    For bug reporting instructions, please see:
    <https://www.gnu.org/software/gdb/bugs/>.
    Find the GDB manual and other documentation resources online at:
        <http://www.gnu.org/software/gdb/documentation/>.

    For help, type "help".
    Type "apropos word" to search for commands related to "word"...
    Reading symbols from ./avfilter_test...
    (No debugging symbols found in ./avfilter_test)
    (gdb) break clone3
    Function "clone3" not defined.
    Make breakpoint pending on future shared library load? (y or [n]) y
    Breakpoint 1 (clone3) pending.
    (gdb) run
    Starting program: /tmp/avfilter_test
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

    Breakpoint 1, clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:44
    44  ../sysdeps/unix/sysv/linux/x86_64/clone3.S: No such file or directory.
    (gdb) bt
    #0  clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:44
    #1  0x00007ffff71269cd in __GI___clone_internal
        (cl_args=cl_args@entry=0x7fffffffe500, func=func@entry=0x7ffff70a6130 
<start_thread>, arg=arg@entry=0x7fffe55ff6c0)
        at ../sysdeps/unix/sysv/linux/clone-internal.c:54
    #2  0x00007ffff70a6030 in create_thread
        (pd=pd@entry=0x7fffe55ff6c0, attr=attr@entry=0x7fffffffe600, 
stopped_start=stopped_start@entry=0x7fffffffe5f6, 
stackaddr=stackaddr@entry=0x7fffe4dff000, stacksize=<optimized out>, 
thread_ran=thread_ran@entry=0x7fffffffe5f7) at ./nptl/pthread_create.c:297
    #3  0x00007ffff70a6aee in __pthread_create_2_1
        (newthread=<optimized out>, attr=<optimized out>, 
start_routine=<optimized out>, arg=<optimized out>) at 
./nptl/pthread_create.c:833
    #4  0x00007fffe80b7769 in blas_thread_init () at 
/lib/x86_64-linux-gnu/libopenblas.so.0
    #5  0x00007fffe7e6807f in gotoblas_init () at 
/lib/x86_64-linux-gnu/libopenblas.so.0
    #6  0x00007ffff7fcfe3e in call_init (env=0x7fffffffe7f8, 
argv=0x7fffffffe7e8, argc=1, l=<optimized out>) at ./elf/dl-init.c:74
    #7  call_init (l=<optimized out>, argc=1, argv=0x7fffffffe7e8, 
env=0x7fffffffe7f8) at ./elf/dl-init.c:26
    #8  0x00007ffff7fcff24 in _dl_init (main_map=0x7ffff7ffe2c0, argc=1, 
argv=0x7fffffffe7e8, env=0x7fffffffe7f8) at ./elf/dl-init.c:121
    #9  0x00007ffff7fe5500 in _dl_start_user () at /lib64/ld-linux-x86-64.so.2
    #10 0x0000000000000001 in  ()
    #11 0x00007fffffffeaf7 in  ()
    #12 0x0000000000000000 in  ()
    (gdb)

Stack frames 5 and 6 show the dynamic loader calling gotoblas_init() in
libopenblas.so.0, before main() starts. And here's that function in
openblas:

https://sources.debian.org/src/openblas/0.3.25%2Bds-1/driver/others/memory.c/#L1507

Anyway, it would be nice if all programs linked against libavfilter or
libavdevice weren't forced to start up a thread pool for some other
library that happens to get pulled in but is otherwise unused. I'm not
aware of a technique to prevent a constructor function in a shared
library from running.

It looks like this is caused by ffmpeg being compiled with
--enable-pocketsphinx (at least on amd64), and indirectly by
libopenblas's API relying on an implicit __attribute__((constructor))
library initialization function rather than having an explicit library
initialization function.

Feel free to reassign this bug to src:openblas, or to clone it and
reassign to src:openblas.

Thanks!

-- 
Robert Edmonds
edmo...@debian.org
/*
 * Compile with:
 *
 * gcc -O2 -Wall -o avfilter_test avfilter_test.c $(pkg-config --cflags --libs libavfilter)
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include <libavfilter/avfilter.h>

int main(void) {
	puts(avfilter_configuration());
	putchar('\n');

	char *cmd = NULL;
	asprintf(&cmd, "cat /proc/%d/status | grep ^Threads", (int)getpid());

	return system(cmd);
}

Reply via email to