<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/feed.xml" rel="self" type="application/atom+xml" /><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/" rel="alternate" type="text/html" /><updated>2026-03-22T04:09:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/feed.xml</id><title type="html">Your awesome title</title><subtitle>Write an awesome description for your new site here. You can edit this line in _config.yml. It will appear in your document head meta (for Google search results) and in your feed.xml site description.</subtitle><entry><title type="html">Symbol versioning: What, Why and How</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/12/12/Symbol-versioning.html" rel="alternate" type="text/html" title="Symbol versioning: What, Why and How" /><published>2025-12-12T00:00:00+00:00</published><updated>2025-12-12T00:00:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/12/12/Symbol-versioning</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/12/12/Symbol-versioning.html"><![CDATA[<h1 id="what-is-this-symbol-versioning">What is this symbol versioning?</h1>

<p>Symbol versioning allows adding version information to symbols. This allows
you to define multiple versions of the same function, for example, <code class="language-plaintext highlighter-rouge">readFoo@V1</code> and
<code class="language-plaintext highlighter-rouge">readFoo@V2</code>, and allows users of the shared library to decide which variant they want
to use.</p>

<p><em>Why is this helpful?</em></p>

<p>It is helpful because it helps to update libraries
while still keeping them backward compatible.</p>

<p><em>Ah, I see. That sounds cool. But how exactly does it help with backward compatibility?</em>.</p>

<p>Let’s understand it better.</p>

<h1 id="how-exactly-does-symbol-versioning-help-with-backward-compatibility">How exactly does symbol versioning help with backward compatibility?</h1>

<p>Let’s say we have a JSON library <code class="language-plaintext highlighter-rouge">CJSON</code> that allows to read and write JSON files, and it
exposes a function <code class="language-plaintext highlighter-rouge">writeJSON(JSONObject json, const char *filename)</code>. This function
writes a JSON object to a file. Now let’s say that we want to add a new parameter
<code class="language-plaintext highlighter-rouge">JSONWriteConfig writeConfig</code> this function. This parameter stores configuration such
as the formatting to use when print the JSON object to the file.</p>

<p>Here’s the key issue: If we add this new parameter to <code class="language-plaintext highlighter-rouge">writeJSON</code> API and ships
the updated library, then all programs that are using <code class="language-plaintext highlighter-rouge">CJSON</code> shared library will stop working.
They all expect <code class="language-plaintext highlighter-rouge">writeJSON(JSONObject json, const char *filename)</code> function but the library
does not expose this function anymore. You have to understand that this all may happen without
the knowledge or any sort of permission from from the program that is now suddenly not
working. A program, let’s say <code class="language-plaintext highlighter-rouge">foo.out</code>, is using <code class="language-plaintext highlighter-rouge">libCJSON.so</code> from the system. The system
updates and now we have the updated <code class="language-plaintext highlighter-rouge">libCJSON.so</code> that has the updated <code class="language-plaintext highlighter-rouge">writeJSON</code> signature.
The program <code class="language-plaintext highlighter-rouge">foo</code> automatically starts using the updated <code class="language-plaintext highlighter-rouge">libCJSON.so</code> as the newer <code class="language-plaintext highlighter-rouge">libCJSON.so</code>
has replaced the older <code class="language-plaintext highlighter-rouge">libCJSON.so</code> in the system.</p>

<p>With symbol versioning, the updated <code class="language-plaintext highlighter-rouge">libCJSON.so</code> can somehow contains both the variants:
<code class="language-plaintext highlighter-rouge">writeJSON(JSONObject json, const char *filename)</code> and
<code class="language-plaintext highlighter-rouge">writeJSON(JSONObject json, const char *filename, JSONWriteConfig writeConfig)</code> and the old programs
magically use the <em>right</em> variant. Note tha <code class="language-plaintext highlighter-rouge">libCJSON.so</code>, as the name suggests, is a C library and
thus there is no inherent concept of function overloading.</p>

<h2 id="i-see-this-got-me-a-little-curious-how-was-backward-compatibility-handled-before">I see. This got me a little curious. How was backward compatibility handled before?</h2>

<p><code class="language-plaintext highlighter-rouge">soname</code> property is typically used to associate versions with a shared library so that
programs do not accidentally use incompatible shared library versions. Note that it is not <em>exactly</em>
handling backward compatibility, it is just providing a method to quickly identify
incompatible shared libraries and report an easy to understand error.</p>

<p>For the nerds: Shared library’s <code class="language-plaintext highlighter-rouge">soname</code> is stored in the <code class="language-plaintext highlighter-rouge">DT_SONAME</code> field of the <code class="language-plaintext highlighter-rouge">.dynamic</code> section.</p>

<h1 id="i-am-sold-how-do-i-use-this-new-feature-called-symbol-versioning">I am sold. How do I use this new feature called symbol versioning?</h1>

<h2 id="how-can-i-specify-version-requirements-for-my-symbols">How can I specify version requirements for my symbols?</h2>

<p>We can specify version requirements by using a pinch of assembly.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1.o</span>

<span class="n">__asm__</span><span class="p">(</span><span class="s">".symver writeJSON, writeJSON@V1"</span><span class="p">);</span>

<span class="kt">int</span> <span class="nf">foo</span><span class="p">()</span> <span class="p">{</span>
  <span class="c1">// ...</span>
  <span class="n">writeJSON</span><span class="p">(...);</span> <span class="c1">// refers to writeJSON@V1</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Voila! <code class="language-plaintext highlighter-rouge">writeJSON</code> call now refers to <code class="language-plaintext highlighter-rouge">writeJSON@V1</code>.</p>

<h2 id="how-do-i-define-multiple-versions-of-a-symbol-in-my-shared-library">How do I define multiple versions of a symbol in my shared library?</h2>

<p>Version scripts in conjunction with the pinch of assembly that we saw
before allows us to define multiple versions of a symbol.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// CJSON.c</span>

<span class="cp">#include</span> <span class="cpf">"CJSON.h"</span><span class="cp">
</span>
<span class="n">__asm__</span><span class="p">(</span><span class="s">".symver writeJSON1, writeJSON@V1"</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">writeJSON1</span><span class="p">(</span><span class="k">struct</span> <span class="n">JSONObject</span> <span class="n">json</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">filename</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// ...</span>
<span class="p">}</span>

<span class="n">__asm__</span><span class="p">(</span><span class="s">".symver writeJSON2, writeJSON@@V2"</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">writeJSON2</span><span class="p">(</span><span class="k">struct</span> <span class="n">JSONObject</span> <span class="n">json</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">filename</span><span class="p">,</span>
               <span class="k">struct</span> <span class="n">JSONWriteConfig</span> <span class="n">writeConfig</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># vs.t
V1 {
  global:
    writeJSON;
};

V2 {
  global:
    writeJSON;
};
</code></pre></div></div>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clang <span class="nt">-o</span> CJSON.o <span class="nt">-c</span> <span class="nt">-fPIC</span>
ld.eld <span class="nt">-o</span> libCJSON.so CJSON.o <span class="nt">-shared</span> <span class="nt">--version-script</span> vs.t
</code></pre></div></div>

<p>Each version must be specified in the version script.</p>

<h2 id="do-the-users-have-to-specify-the-version-for-all-of-my-library-versioned-symbols">Do the users have to specify the version for all of my library versioned symbols?</h2>

<p>God, no. That would be awful. A library can, and pretty much always does, provide default versioned symbols.
The default versioned symbol is used when a user has not explicitly specified which version to use.</p>

<p><em>Oh, thank God. How do I specify default versioned symbols?</em></p>

<p>It’s simple. Just use <code class="language-plaintext highlighter-rouge">@@</code> instead of <code class="language-plaintext highlighter-rouge">@</code> in the versioned name.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Defines non-default versioned symbol.</span>
<span class="n">__asm__</span><span class="p">(</span><span class="s">".symver writeJSON1, writeJSON@V1"</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">writeJSON1</span><span class="p">(</span><span class="k">struct</span> <span class="n">JSONObject</span> <span class="n">json</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">filename</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// ...</span>
<span class="p">}</span>

<span class="c1">// Defines a default versioned symbol.</span>
<span class="n">__asm__</span><span class="p">(</span><span class="s">".symver writeJSON2, writeJSON@@V2"</span><span class="p">);</span>
<span class="kt">int</span> <span class="nf">writeJSON2</span><span class="p">(</span><span class="k">struct</span> <span class="n">JSONObject</span> <span class="n">json</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">filename</span><span class="p">,</span>
               <span class="k">struct</span> <span class="n">JSONWriteConfig</span> <span class="n">writeConfig</span><span class="p">)</span> <span class="p">{</span>
  <span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>

<p><em>I think I am getting a hang of it now. But I have a concerning doubt now.</em></p>

<h2 id="what-happens-if-i-update-my-shared-library-and-a-user-program-binary-was-not-using-explicit-versions-for-my-library-symbols">What happens if I update my shared library and a user program binary was not using explicit versions for my library symbols?</h2>

<p>When the linker resolves a user program undefined unversioned symbol reference <code class="language-plaintext highlighter-rouge">foo</code> to a
library’s versioned defined <code class="language-plaintext highlighter-rouge">foo@@V2</code>, then it binds the version of <code class="language-plaintext highlighter-rouge">foo</code> to <code class="language-plaintext highlighter-rouge">V2</code>. At runtime,
the loader resolves this undefined <code class="language-plaintext highlighter-rouge">foo@V2</code> reference with the library’s defined <code class="language-plaintext highlighter-rouge">foo@V2</code>.
If the library is updated and now it has symbols: <code class="language-plaintext highlighter-rouge">foo@V1</code>, <code class="language-plaintext highlighter-rouge">foo@V2</code> and <code class="language-plaintext highlighter-rouge">foo@@V3</code>, and the
user program binary is as it was before, then it still works correctly because the loader
still sees <code class="language-plaintext highlighter-rouge">foo@V2</code> undefined symbol in the dynamic symbol table, and the <code class="language-plaintext highlighter-rouge">foo</code> call was already
<em>morphed</em> to <code class="language-plaintext highlighter-rouge">foo@V2</code> call by the linker.</p>

<h2 id="what-happens-if-there-are-multiple-shared-libraries-that-define-versions-for-the-same-symbol">What happens if there are multiple shared libraries that define versions for the same symbol?</h2>

<p><em>For the most part</em>, it is not all too different from how symbol resolution works for non-versioned symbols.
However, it does have some gotchas as we will soon see.</p>

<p>Symbol resolution basic rules for versioned symbols are the same as for non-versioned symbols.
The runtime loader will select the first symbol in the symbol lookup that satisfies
an undefined reference. In brief, the symbol lookup order is the symbols in-order from
a sequence of modules. For example, if the sequence of modules is <code class="language-plaintext highlighter-rouge">A</code>, <code class="language-plaintext highlighter-rouge">B</code> and <code class="language-plaintext highlighter-rouge">C</code>, then the symbol
lookup order will be: symbols of module <code class="language-plaintext highlighter-rouge">A</code>, followed by symbols of module <code class="language-plaintext highlighter-rouge">B</code>, followed by symbols
of module <code class="language-plaintext highlighter-rouge">C</code>. This sequence of modules, in the usual case, is:
the main executable, followed by the breadth first search over the shared libraries dependency graph.</p>

<p>Let’s understand this with a help of an example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">cat</span> <span class="o">&gt;</span>lib1.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 11;
}

int foo2(void) {
  return 12;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib2.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 21;
}

int foo2(void) {
  return 22;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib3.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 31;
}

int foo2(void) {
  return 32;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>vs.t <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
V1 {
  foo;
};

V2 {
  foo;
} V1;
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>main.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
#include &lt;stdio.h&gt;
int foo();


int main(void) {
  printf("foo: %d</span><span class="se">\n</span><span class="sh">", foo());
  return 0;
}
</span><span class="no">EOF

</span>clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib1.c <span class="nt">-o</span> lib1.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib1.so lib1.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib2.c <span class="nt">-o</span> lib2.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib2.so lib2.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib3.c <span class="nt">-o</span> lib3.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib3.so lib3.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> main.c <span class="nt">-o</span> main.o
clang <span class="nt">-o</span> main.out main.o <span class="nt">-L</span><span class="nb">.</span> <span class="nt">-l1</span> <span class="nt">-l2</span> <span class="nt">-l3</span> <span class="nt">-Wl</span>,-rpath,<span class="s1">'$ORIGIN'</span>
./main.out
</code></pre></div></div>

<p>Here the output is: <code class="language-plaintext highlighter-rouge">foo: 12</code> and the symbol lookup order here is:
<code class="language-plaintext highlighter-rouge">[symbols(main.o), symbols(lib1.so), symbols(lib2.so), symbols(lib3.so)]</code>.
<code class="language-plaintext highlighter-rouge">foo</code> is resolved to <code class="language-plaintext highlighter-rouge">foo@@V2</code> of <code class="language-plaintext highlighter-rouge">lib1.so</code>. <code class="language-plaintext highlighter-rouge">foo@@V2</code> is able to resolve <code class="language-plaintext highlighter-rouge">foo</code>
because <code class="language-plaintext highlighter-rouge">foo@@V2</code> is a default versioned symbol, that is, it can resolve both
<code class="language-plaintext highlighter-rouge">foo</code> and <code class="language-plaintext highlighter-rouge">foo@V2</code>.</p>

<p>Now, let’s see an another example.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">cat</span> <span class="o">&gt;</span>lib1.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 11;
}

int foo2(void) {
  return 12;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib2.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 21;
}

int foo2(void) {
  return 22;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib3.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 31;
}

int foo2(void) {
  return 32;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>vs.t <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
V1 {
  foo;
};

V2 {
  foo;
} V1;
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>main.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
#include &lt;stdio.h&gt;
int foo();


int main(void) {
  printf("foo: %d</span><span class="se">\n</span><span class="sh">", foo());
  return 0;
}
</span><span class="no">EOF

</span>clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib1.c <span class="nt">-o</span> lib1.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib1.so lib1.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib2.c <span class="nt">-o</span> lib2.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib2.so lib2.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib3.c <span class="nt">-o</span> lib3.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib3.so lib3.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> main.c <span class="nt">-o</span> main.o
clang <span class="nt">-o</span> main.out main.o <span class="nt">-L</span><span class="nb">.</span> <span class="nt">-l1</span> <span class="nt">-l2</span> <span class="nt">-l3</span> <span class="nt">-Wl</span>,-rpath,<span class="s1">'$ORIGIN'</span>
./main.out
</code></pre></div></div>

<p>Here the symbol lookup order is the same as before.</p>

<details>
<summary>Do you want to guess the output?</summary>
<code>
foo: 12
</code>
</details>

<p><em>The <code class="language-plaintext highlighter-rouge">foo()</code> output is 12!?!? It should be 22. This makes no sense!</em></p>

<p>It does makes some sense. The linker sees that <code class="language-plaintext highlighter-rouge">foo()</code> call in <code class="language-plaintext highlighter-rouge">main.o</code> is resolved
to <code class="language-plaintext highlighter-rouge">lib2.so[foo@@V2]</code>. Hence, it notes the version <code class="language-plaintext highlighter-rouge">V2</code> for <code class="language-plaintext highlighter-rouge">main.o[foo]</code>. We can see
this by running <code class="language-plaintext highlighter-rouge">llvm-readelf --dyn-syms --version-info main.out</code>. Now, at runtime,
the loader searches for <code class="language-plaintext highlighter-rouge">foo@V2</code> instead of <code class="language-plaintext highlighter-rouge">foo</code>, and thus, <code class="language-plaintext highlighter-rouge">lib1.so[foo@V2]</code> satisfies the
undefined symbol. Here we learn an important lesson:</p>

<p><strong>The link time symbol resolution can be at constrast from the runtime symbol resolution.</strong></p>

<p>Now, let’s see another example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">cat</span> <span class="o">&gt;</span>lib1.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
__asm__(".symver foo, foo@V1");
int foo();

int foo1(void) {
  return 11;
}

int foo2(void) {
  return 12;
}

int bar1(void) {
  return 111;
}

int bar2(void) {
  return foo();
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
__asm__(".symver bar1, bar@V1");
__asm__(".symver bar2, bar@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib2.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 21;
}

int foo2(void) {
  return 22;
}

int bar1(void) {
  return 211;
}

int bar2(void) {
  return 212;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
__asm__(".symver bar1, bar@V1");
__asm__(".symver bar2, bar@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib3.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 31;
}

int foo2(void) {
  return 32;
}

int bar1(void) {
  return 311;
}

int bar2(void) {
  return 312;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
__asm__(".symver bar1, bar@V1");
__asm__(".symver bar2, bar@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>vs.t <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
V1 {
  foo;
  bar;
};

V2 {
  foo;
  bar;
} V1;
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>main.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
#include &lt;stdio.h&gt;
int bar();
int foo() {
  return 77;
}

int main(void) {
  printf("bar: %d</span><span class="se">\n</span><span class="sh">", bar());
  return 0;
}
</span><span class="no">EOF

</span>clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib1.c <span class="nt">-o</span> lib1.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib1.so lib1.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib2.c <span class="nt">-o</span> lib2.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib2.so lib2.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib3.c <span class="nt">-o</span> lib3.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib3.so lib3.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> main.c <span class="nt">-o</span> main.o
clang <span class="nt">-o</span> main.out main.o <span class="nt">-L</span><span class="nb">.</span> <span class="nt">-l1</span> <span class="nt">-l2</span> <span class="nt">-l3</span> <span class="nt">-Wl</span>,-rpath,<span class="s1">'$ORIGIN'</span>
<span class="nv">LD_LIBRARY_PATH</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">:</span><span class="k">${</span><span class="nv">LD_LIBRARY_PATH</span><span class="k">}</span><span class="s2">"</span> ./main.out
</code></pre></div></div>

<p>Here we again have same symbol lookup order.</p>

<details>
<summary>Do you want to guess the output?</summary>
<code>
foo: 77
</code>
</details>

<p><em>The answer should have been 11. 77 makes no sense. What is happening?</em></p>

<p>Indeed weird things are happening. Or perhaps, rules are just weird.
Let’s understand this in detail.</p>

<p><code class="language-plaintext highlighter-rouge">main.o</code> calls <code class="language-plaintext highlighter-rouge">bar</code>. This <code class="language-plaintext highlighter-rouge">bar</code> is resolved to <code class="language-plaintext highlighter-rouge">lib1.so[bar@@V2]</code> by both the linker
and the runtime loader. <code class="language-plaintext highlighter-rouge">lib1.so[bar@@V2]</code> calls <code class="language-plaintext highlighter-rouge">foo@V1</code>. <code class="language-plaintext highlighter-rouge">foo@V1</code> is resolved to
<code class="language-plaintext highlighter-rouge">main.o[foo]</code>. Yes, you read that right. <code class="language-plaintext highlighter-rouge">foo@V1</code> is resolved to <code class="language-plaintext highlighter-rouge">main.o[foo]</code>.
This brings us to another rule:</p>

<p><strong>Unversioned symbol definitions can satisfy undefined versioned symbols</strong></p>

<p>Let’s see an another example:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">cat</span> <span class="o">&gt;</span>lib1.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo() {
  return 77;
}
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>lib2.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
int foo1(void) {
  return 31;
}

int foo2(void) {
  return 32;
}

__asm__(".symver foo1, foo@V1");
__asm__(".symver foo2, foo@@V2");
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>vs.t <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
V1 {
  foo;
};

V2 {
  foo;
} V1;
</span><span class="no">EOF

</span><span class="nb">cat</span> <span class="o">&gt;</span>main.c <span class="o">&lt;&lt;</span><span class="sh">'</span><span class="no">EOF</span><span class="sh">'
#include &lt;stdio.h&gt;
__asm__(".symver foo, foo@V1");
int foo();

int bar() {
  return foo();
}

int main(void) {
  printf("bar: %d</span><span class="se">\n</span><span class="sh">", bar());
  return 0;
}
</span><span class="no">EOF

</span>clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib1.c <span class="nt">-o</span> lib1.o
ld.bfd <span class="nt">-shared</span> <span class="nt">-o</span> lib1.so lib1.o

clang <span class="nt">-c</span> <span class="nt">-fPIC</span> lib2.c <span class="nt">-o</span> lib2.o
ld.lld <span class="nt">-shared</span> <span class="nt">-o</span> lib2.so lib2.o <span class="nt">--version-script</span> vs.t

clang <span class="nt">-c</span> main.c <span class="nt">-o</span> main.o
clang <span class="nt">-o</span> main.out main.o <span class="nt">-L</span><span class="nb">.</span> <span class="nt">-l1</span> <span class="nt">-l2</span> <span class="nt">-l3</span> <span class="nt">-Wl</span>,-rpath,<span class="s1">'$ORIGIN'</span>
./main.out

</code></pre></div></div>

<details>
<summary>Do you want to guess the output?</summary>
<code>
foo: 77
</code>
</details>

<p><em>Ah, I see. <code class="language-plaintext highlighter-rouge">foo</code> will be resolved to <code class="language-plaintext highlighter-rouge">lib2.so[foo@V1]</code> at link time, but at runtime the loader
will resolve the undefined <code class="language-plaintext highlighter-rouge">foo@V1</code> reference to <code class="language-plaintext highlighter-rouge">lib1.so[foo]</code>. I understand it now, but I still
do not like this behavior.</em></p>

<p>Congratulations! You pass the symbol versioning symbol resolution test.</p>

<h1 id="how-is-symbol-versioning-implemented">How is symbol versioning implemented?</h1>

<h2 id="compiler-side-of-the-picture">Compiler side of the picture</h2>

<p>Enabling symbol versioning requires support from the compiler, linker
and the loader. We are majorly focussed on the linker picture in this
article, though we will briefly discuss the compiler and the runtime
role as well.</p>

<p>The compile role here is relatively straightforward. Whenever the compiler
sees <code class="language-plaintext highlighter-rouge">__asm__(".symver foo, foo@V1")</code> directive, then it changes all the
reference / definition of <code class="language-plaintext highlighter-rouge">foo</code> to <code class="language-plaintext highlighter-rouge">foo@V1</code>, and the symbol table contains
both the <code class="language-plaintext highlighter-rouge">foo</code> and <code class="language-plaintext highlighter-rouge">foo@V1</code> wit.</p>

<h2 id="linker-side-of-the-picture">Linker side of the picture</h2>

<p>The linker role in enabling symbol versioning is much more involved.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[What is this symbol versioning?]]></summary></entry><entry><title type="html">Thread Local Storage: What, Why and How</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/12/12/TLS.html" rel="alternate" type="text/html" title="Thread Local Storage: What, Why and How" /><published>2025-12-12T00:00:00+00:00</published><updated>2025-12-12T00:00:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/12/12/TLS</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/12/12/TLS.html"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Welcome to Jekyll!</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/jekyll/update/2025/06/15/welcome-to-jekyll.html" rel="alternate" type="text/html" title="Welcome to Jekyll!" /><published>2025-06-15T07:38:38+00:00</published><updated>2025-06-15T07:38:38+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/jekyll/update/2025/06/15/welcome-to-jekyll</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/jekyll/update/2025/06/15/welcome-to-jekyll.html"><![CDATA[<p>You’ll find this post in your <code class="language-plaintext highlighter-rouge">_posts</code> directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run <code class="language-plaintext highlighter-rouge">jekyll serve</code>, which launches a web server and auto-regenerates your site when a file is updated.</p>

<p>Jekyll requires blog post files to be named according to the following format:</p>

<p><code class="language-plaintext highlighter-rouge">YEAR-MONTH-DAY-title.MARKUP</code></p>

<p>Where <code class="language-plaintext highlighter-rouge">YEAR</code> is a four-digit number, <code class="language-plaintext highlighter-rouge">MONTH</code> and <code class="language-plaintext highlighter-rouge">DAY</code> are both two-digit numbers, and <code class="language-plaintext highlighter-rouge">MARKUP</code> is the file extension representing the format used in the file. After that, include the necessary front matter. Take a look at the source for this post to get an idea about how it works.</p>

<p>Jekyll also offers powerful support for code snippets:</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">print_hi</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"Hi, </span><span class="si">#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="n">print_hi</span><span class="p">(</span><span class="s1">'Tom'</span><span class="p">)</span>
<span class="c1">#=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre></figure>

<p>Check out the <a href="https://jekyllrb.com/docs/home">Jekyll docs</a> for more info on how to get the most out of Jekyll. File all bugs/feature requests at <a href="https://github.com/jekyll/jekyll">Jekyll’s GitHub repo</a>. If you have questions, you can ask them on <a href="https://talk.jekyllrb.com/">Jekyll Talk</a>.</p>]]></content><author><name></name></author><category term="jekyll" /><category term="update" /><summary type="html"><![CDATA[You’ll find this post in your _posts directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run jekyll serve, which launches a web server and auto-regenerates your site when a file is updated.]]></summary></entry><entry><title type="html">Practical Linux Bytes: How to see how much physical memory is used by a process?</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/How-to-see-physical-memory-used.html" rel="alternate" type="text/html" title="Practical Linux Bytes: How to see how much physical memory is used by a process?" /><published>2025-06-15T00:00:00+00:00</published><updated>2025-06-15T00:00:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/How-to-see-physical-memory-used</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/How-to-see-physical-memory-used.html"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Practical Linker Bytes: Symbol resolution with versioned symbols</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/Symbol-resolution-with-versioned-symbols.html" rel="alternate" type="text/html" title="Practical Linker Bytes: Symbol resolution with versioned symbols" /><published>2025-06-15T00:00:00+00:00</published><updated>2025-06-15T00:00:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/Symbol-resolution-with-versioned-symbols</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/Symbol-resolution-with-versioned-symbols.html"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Practical Linker Bytes: Weak symbols</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/Weak-symbols.html" rel="alternate" type="text/html" title="Practical Linker Bytes: Weak symbols" /><published>2025-06-15T00:00:00+00:00</published><updated>2025-06-15T00:00:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/Weak-symbols</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/Weak-symbols.html"><![CDATA[<p>Weak symbols are just what the name suggests — they are weak. They go to the gym less often.
Sometimes skips the gym completely.</p>

<p>You may have heard the term <code class="language-plaintext highlighter-rouge">weak</code> symbol occasionally. If you are unsure of what they are
and/or what they are good for, this blog might be helpful.</p>

<p>In the holy bible ELF, symbols have a property called symbol binding. The symbol binding
determines whether a symbol is “visible” outside its translation unit or not
(then what is the symbol visibility for? huh!). The symbol binding can have three
values: <code class="language-plaintext highlighter-rouge">global</code>, <code class="language-plaintext highlighter-rouge">weak</code>, and <code class="language-plaintext highlighter-rouge">local</code>. <code class="language-plaintext highlighter-rouge">global</code> and <code class="language-plaintext highlighter-rouge">weak</code> symbols are visible outside
their translation unit. <code class="language-plaintext highlighter-rouge">local</code> symbols are not.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/usr/bin/env bash</span>

<span class="nb">cat</span><span class="o">&gt;</span>1.c <span class="o">&lt;&lt;</span><span class="sh">\</span><span class="no">EOF</span><span class="sh">
int foo() { return 1; }
__attribute__((weak)) int bar() { return 3; }
static int baz() { return 5; }
</span><span class="no">EOF

</span>gcc <span class="nt">-o</span> 1.o 1.c <span class="nt">-c</span>
readelf <span class="nt">-s</span> 1.o

<span class="c"># readelf output:</span>
<span class="c"># ...</span>
<span class="c"># ...</span>
<span class="c"># 3: 0000000000000016    11 FUNC    LOCAL  DEFAULT    1 baz</span>
<span class="c"># 4: 0000000000000000    11 FUNC    GLOBAL DEFAULT    1 foo</span>
<span class="c"># 5: 000000000000000b    11 FUNC    WEAK   DEFAULT    1 bar</span>
</code></pre></div></div>

<p>I will start by listing and briefly discussing the differences between global and weak symbols. We will then see some of the use cases that have motivated the weak symbol feature.</p>]]></content><author><name></name></author><summary type="html"><![CDATA[Weak symbols are just what the name suggests — they are weak. They go to the gym less often. Sometimes skips the gym completely.]]></summary></entry><entry><title type="html">Virtual Addr File Offset Congruency</title><link href="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/virtual-addr-file-offset-congruency.html" rel="alternate" type="text/html" title="Virtual Addr File Offset Congruency" /><published>2025-06-15T00:00:00+00:00</published><updated>2025-06-15T00:00:00+00:00</updated><id>https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/virtual-addr-file-offset-congruency</id><content type="html" xml:base="https://parth-07.github.io/caffeinated-dreamer/caffeinated-dreamer/2025/06/15/virtual-addr-file-offset-congruency.html"><![CDATA[]]></content><author><name></name></author><summary type="html"><![CDATA[]]></summary></entry></feed>