View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0010184 | ardour | other | public | 2026-02-14 13:48 | 2026-02-17 02:17 |
| Reporter | u1f992 | Assigned To | |||
| Priority | normal | Severity | minor | Reproducibility | always |
| Status | new | Resolution | open | ||
| Platform | Ubuntu | OS | Linux | OS Version | (any) |
| Product Version | 9.0 | ||||
| Summary | 0010184: Modified taglib source not found | ||||
| Description | The nightly build dependencies page (https://nightly.ardour.org/list.php#build_deps) links to `http://taglib.github.io/releases/taglib-1.9.1.tar.gz` as the taglib source. I built taglib from this source, but the resulting `libtag.so.1` does not match the one in the Ardour 9.0 Linux x86_64 installer. The installer version contains symbols that are not present in a 1.9.1 build, for example: - `TagLib::String::MyPrivateString` (upstream 1.9.1 uses `StringPrivate`) - `TagLib::ListPrivateBase` (only found on the taglib `experimental` branch, not in any release) - `vsnprintf@GLIBC_2.2.5` (not imported by a 1.9.1 build) The installer binary has 2,876 dynamic symbols vs 2,821 from my 1.9.1 build (+55 symbols). I was not able to find a matching source: `MyPrivateString` does not appear anywhere in the taglib git history, and the combination of `ListPrivateBase` with `RefCounterOld` does not correspond to any single commit in the repository. Is the modified taglib source or patch available somewhere? I could not find it on the nightly page, in `ardour.org/files/deps/`, or in `tools/misc-patches/` on the `2.0-ongoing` branch. | ||||
| Steps To Reproduce | 1. Download the Ardour 9.0 Linux x86_64 installer. 2. Extract `libtag.so.1` from the bundle. 3. Run: `nm -D libtag.so.1 | c++filt | grep MyPrivateString`. The symbol is present. 4. Build taglib 1.9.1 from `http://taglib.github.io/releases/taglib-1.9.1.tar.gz`. 5. Run the same command on the self-built `libtag.so.1`. The symbol is absent. | ||||
| Additional Information | Taglib is dual-licensed under LGPL 2.1 and MPL 1.1. | ||||
| Tags | No tags attached. | ||||
|
|
I should clarify that my mention of `tools/misc-patches/` on the `2.0-ongoing` branch was because I conflated it with issue 0010185, which I filed around the same time. Regardless, I have since searched the entire git history of the Ardour repository (all branches and all tags) and found no taglib patch file and no modified taglib source. The `libs/taglib/` directory that existed in the tree from 2008 to 2014 (removed in commit 0a2a6aaabb) contained unmodified upstream source (`StringPrivate`, not `MyPrivateString`). I also considered whether the symbol differences could be explained purely by build configuration, without source modification, for example by passing `-DStringPrivate=MyPrivateString` to the compiler. This successfully renames the class in the symbol table, and it compiles cleanly. However, this alone cannot account for the full set of differences. Specifically, the bundled `libtag.so.1` contains vtables and RTTI typeinfo for `ListPrivate<T*>` template instantiations (e.g., `List<ID3v2::Frame*>::ListPrivate`). In the upstream 1.9.1 source, the inheritance chain is `ListPrivate<T*>` -> `ListPrivateBase` -> `RefCounterOld`, and none of these classes have virtual functions. `RefCounterOld`, unlike `RefCounter`, lacks a virtual destructor. Without virtual functions, the compiler will not emit vtables or polymorphic typeinfo for these classes, regardless of optimization level or visibility settings. This means the bundled library reflects source-level modifications beyond what any combination of `-D` flags or compiler options can reproduce from the upstream 1.9.1 tarball linked on the nightly builds page. |
|
|
macOS has a flat namespace, and NI machine already defines "StringPrivate", so something has to give to avoid conflicting symbols. on GNU/Linux this is not relevant since there is no global flat namesapce; as for the other differences, we compile taglib with "-O3 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -std=c++11". |
|
|
Thank you for the explanation regarding the macOS flat namespace. The motivation for renaming `StringPrivate` is now clear. I attempted to reproduce the bundled libtag.so.1 from the taglib 1.9.1 tarball listed on the nightly builds page, using the flags you mentioned, but the resulting binary did not match the bundled one; 53 dynamic symbols remain unaccounted for (details in the appendix). In 0010185, Paul mentioned that there are numerous other patches applied as part of the dependency stack build, and that making them publicly available is being considered. The patch described in the appendix may be among them. If so, publishing it would also address the source-availability question. appendix.md (6,860 bytes)
## Appendix: Reproduction details
### A1. Setup and reproduction
Build environment (Dockerfile):
```dockerfile
# GCC 10 is used because the bundled library's __cxa_pure_virtual
# binding (U, not w) matches GCC 10 but not GCC 11+. The libstdc++
# imports (std::ios_base::Init::Init/~Init) are consistent with any
# GCC version prior to 13.
#
# CMake 2.8.12.2 also requires GCC 10 or earlier to compile
# (it fails with GCC 11+).
FROM ubuntu:24.04
RUN apt-get update -qq \
&& apt-get install -qq -y gcc-10 g++-10 zlib1g-dev binutils make wget > /dev/null
RUN cd /tmp \
&& wget -q https://cmake.org/files/v2.8/cmake-2.8.12.2.tar.gz \
&& tar xzf cmake-2.8.12.2.tar.gz && cd cmake-2.8.12.2 \
&& CC=gcc-10 CXX=g++-10 ./bootstrap --prefix=/opt/cmake > /dev/null 2>&1 \
&& make -j$(nproc) > /dev/null 2>&1 \
&& make install > /dev/null 2>&1
ENV PATH="/opt/cmake/bin:$PATH"
RUN cd /tmp \
&& wget -q https://taglib.org/releases/taglib-1.9.1.tar.gz \
&& tar xzf taglib-1.9.1.tar.gz
```
Build and run:
```
docker build -t taglib-env .
docker run --rm -it taglib-env
```
Inside the container, copy the source tree and apply the one-line patch to the copy:
```sh
cd /tmp
cp -a taglib-1.9.1 taglib-1.9.1-patched
sed -i 's/~ListPrivate() {/virtual ~ListPrivate() {/' \
taglib-1.9.1-patched/taglib/toolkit/tlist.tcc
```
Build both with the same flags.
The flag `-DStringPrivate=MyPrivateString` reproduces the class rename; whether the actual build uses this flag or a source patch is unknown, but the effect on the dynamic symbol table is the same.
The bundled library's dynamic symbol table includes `vsnprintf@GLIBC_2.2.5`, which is called only from `debug()` and `debugData()` in tdebug.cpp. This indicates the library was built without `-DNDEBUG`.
```sh
FLAGS="-O3 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \
-std=c++11 -DStringPrivate=MyPrivateString \
-fno-stack-protector -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0"
# (A) Unmodified
mkdir build-unmod && cd build-unmod
cmake /tmp/taglib-1.9.1 -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_CXX_FLAGS="$FLAGS"
make -j$(nproc)
cd /tmp
# (B) Patched
mkdir build-patched && cd build-patched
cmake /tmp/taglib-1.9.1-patched -DCMAKE_C_COMPILER=gcc-10 -DCMAKE_CXX_COMPILER=g++-10 -DCMAKE_CXX_FLAGS="$FLAGS"
make -j$(nproc)
cd /tmp
```
Compare dynamic symbols (within the same build, addresses are comparable):
```sh
nm -D build-unmod/taglib/libtag.so.1 | c++filt | sort > sym-unmod.txt
nm -D build-patched/taglib/libtag.so.1 | c++filt | sort > sym-patched.txt
diff sym-unmod.txt sym-patched.txt
```
To compare against the bundled library, extract `libtag.so.1` from the Ardour 9.0 x86_64 Linux installer (`Ardour-9.0.0-x86_64.run`, downloaded 2026-02-15, SHA256: `713f11881a502585fffe3eade58ed41e0fe290983eae794f328012876767b4d1`):
```sh
./Ardour-9.0.0-x86_64.run --noexec --target ardour-extracted
nm -D ardour-extracted/Ardour_x86_64-9.0.0/lib/libtag.so.1 | c++filt | sort > sym-bundled.txt
```
Cross-build comparison requires stripping addresses (different builds produce different layouts):
```sh
sed 's/^[0-9a-f]* //' sym-patched.txt | sort > sym-patched-names.txt
sed 's/^[0-9a-f]* //' sym-bundled.txt | sort > sym-bundled-names.txt
diff sym-patched-names.txt sym-bundled-names.txt
```
### A2. Results
(A) Unmodified (B) Patched Ardour bundled
Total symbols 2819 2872 2872
ListPrivate<T*> vtables 0 8 8
ListPrivate<T*> typeinfo 0 9 9
ListPrivateBase typeinfo 0 2 2
RefCounterOld typeinfo 0 2 2
MyPrivateString vtable/ti 2 2 2
Build (A) is missing 53 symbols present in the bundled library (2872 - 2819).
Build (B), with only the one-line patch, produces a dynamic symbol table identical to the bundled library's: all 2872 symbols match in name and binding type.
### A3. Explanation
The StringPrivate rename produces a vtable for MyPrivateString because String::StringPrivate inherits from RefCounter, which has a virtual destructor. This is expected and unrelated to the `ListPrivate` patch.
However, the vtables for ListPrivate<T*> cannot be produced by any combination of compiler flags. The inheritance chain is:
RefCounterOld (no virtual functions, no destructor declared)
└─ ListPrivateBase (no virtual functions)
└─ ListPrivate<TP*> (destructor is non-virtual in upstream)
Per the C++ object model, no vtable or polymorphic RTTI is emitted without at least one virtual function in the hierarchy, regardless of optimization level, visibility settings, or C++ standard version.
Adding `virtual` to `~ListPrivate()` in the pointer-type partial specialization makes that class polymorphic. This produces exactly the 8 vtable entries, 9 typeinfo entries, and 2 `ListPrivateBase` + 2 `RefCounterOld` base-class typeinfo entries seen in the bundled library, matching the total of 2872 symbols.
### A4. Cross-check against taglib Git history
I also checked whether any commit in the taglib repository could produce vtables for `ListPrivate<T*>` while `RefCounterOld` is part of the inheritance chain. Per the C++ object model, this requires at least one virtual function somewhere in the `RefCounterOld -> ListPrivateBase -> ListPrivate<T*>` hierarchy (not necessarily in `ListPrivate` itself).
I scanned all 3,015 commits at https://github.com/taglib/taglib (as of 2026-02-17). Of those, 1,112 commits contain `RefCounterOld` in `trefcounter.h`, spanning 12 unique file-state combinations. Each was checked for `virtual` or `override` in any of the three classes:
RefCounterOld (5 unique versions):
No virtual functions or virtual destructors in any version.
ListPrivateBase (6 unique versions, when inheriting from RefCounterOld):
No virtual functions, no virtual or override keywords.
ListPrivate<T*> (6 unique versions, same commits):
No virtual functions, no virtual or override keywords.
Additionally, there is no commit where `RefCounterOld` exists and `ListPrivateBase` inherits from `RefCounter` (which does have a virtual destructor). When `RefCounterOld` is present, `ListPrivateBase` always inherits from `RefCounterOld`.
For completeness: between commits 9867bc94 and a1bdb017 (July 2023), `~ListPrivate()` was marked `override`, but in those commits `ListPrivateBase` inherited from `RefCounter` (not `RefCounterOld`), so `RefCounterOld` was not in the inheritance chain and its typeinfo would not be emitted.
No commit in the repository produces a polymorphic `ListPrivate<T*>` hierarchy rooted in `RefCounterOld`. The bundled binary's symbol table cannot be reproduced from any upstream commit or tagged release.
|