<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>PDB on Welcome to Christophe Nasarre's Blog</title><link>https://chrisnas.github.io/tags/pdb/</link><description>Recent content in PDB on Welcome to Christophe Nasarre's Blog</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Wed, 11 Feb 2026 09:16:11 +0000</lastBuildDate><atom:link href="https://chrisnas.github.io/tags/pdb/index.xml" rel="self" type="application/rss+xml"/><item><title>How to support .NET Framework PDB format and source line with ISymUnmanagedReader</title><link>https://chrisnas.github.io/posts/2026-02-11_how-to-support-net/</link><pubDate>Wed, 11 Feb 2026 09:16:11 +0000</pubDate><guid>https://chrisnas.github.io/posts/2026-02-11_how-to-support-net/</guid><description>After DIA and DbgHelp, time to dig into ISymUnmanagedReader to get its line in source code.</description><content:encoded><![CDATA[<hr>
<p>In my previous posts, I explained how to use <a href="/posts/2025-12-08_how-to-dump-function/">DIA</a> and <a href="/posts/2026-01-16_but-where-is-my/">DbgHelp</a> to map a method to its line in source code. I forgot to mention that it was correct for .NET Core but not for the “old” .NET Framework Windows PDB format. Instead of encoding the method token in the name, the symbol file contains the name of the methods. So, how to do the mapping for .NET Framework assemblies? You will find the answer (plus some tricks) in this article.</p>
<p>When I started to work on the support of the old Windows PDB format, I looked at what existed to parse the <a href="https://github.com/microsoft/microsoft-pdb/tree/master">raw format</a> and… I decided to try DbgHelp instead. With this <a href="/posts/2026-01-16_but-where-is-my/">first implementation</a>, I realized that some token where missing and source code information was not retrieved for most of the methods.</p>
<p><img loading="lazy" src="/posts/2026-02-11_how-to-support-net/1_ppZFw6o-Ygx7BzpX3jyWIw.png"></p>
<p>So, I looked for another API to use and I found <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedreader-interface?WT.mc_id=DT-MVP-5003325">ISymUnmanagedReader</a>. The usage philosophy is totally different from DIA or DbgHelp.</p>
<h2 id="a-little-bit-ofmagic">A little bit of magic</h2>
<p>This interface is implemented in diasymreader.dll that comes with every .NET Framework installation. But you need to do COM magic to get it. After having called <a href="https://learn.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize?WT.mc_id=DT-MVP-5003325"><strong>CoInitialize</strong></a> to setup COM, you ask for an instance of <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedbinder-interface?WT.mc_id=DT-MVP-5003325"><strong>ISymUnmanagedBinder</strong></a> from <strong>CLSID_CorSymBinder_SxS</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">ISymUnmanagedBinder</span><span class="o">&gt;</span> <span class="n">pBinder</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">CoCreateInstance</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">CLSID_CorSymBinder_SxS</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nb">NULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">CLSCTX_INPROC_SERVER</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">IID_ISymUnmanagedBinder</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pBinder</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>From the binder, you can get the <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedreader-interface?WT.mc_id=DT-MVP-5003325"><strong>ISymUnmanagedReader</strong> interface</a> corresponding to the assembly you are interested in with <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedbinder-getreaderforfile-method?WT.mc_id=DT-MVP-5003325"><strong>GetReaderForFile</strong></a>. However, there are two tiny details to consider.</p>
<p>First, one parameter expects the path to the assembly, not to the .pdb file. That symbol file has to be stored in the same folder but note that the documentation states that you could have more flexible search with <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedbinder2-getreaderforfile2-method?WT.mc_id=DT-MVP-5003325"><strong>ISymUnmanagedBinder2::GetReaderForFile2</strong></a> but I did not test it.</p>
<p>The second detail is the first parameter: an instance of <strong>IMetaDataImport</strong> for the same assembly. The steps to get it are… complicated.</p>
<h2 id="hosting-theclr">Hosting the CLR</h2>
<p>The idea is to host the .NET Framework and get the corresponding <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrmetahost-interface?WT.mc_id=DT-MVP-5003325">ICLRMetaHost</a> interface:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">ICLRMetaHost</span><span class="o">&gt;</span> <span class="n">pMetaHost</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">CLRCreateInstance</span><span class="p">(</span><span class="n">CLSID_CLRMetaHost</span><span class="p">,</span> <span class="n">IID_ICLRMetaHost</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pMetaHost</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Calling the <a href="CComPtr%3cICLRMetaHost%3e%20pMetaHost;"><strong>CLRCreateInstance</strong> API</a> allows you to get an instance of <strong>ICLRMetaHost</strong> from which you could <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrmetahost-enumerateinstalledruntimes-method?WT.mc_id=DT-MVP-5003325">enumerate installed version</a> of .NET Framework. In my case, I know which version I want:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Get the installed .NET Framework runtime (v4.0+)
</span></span></span><span class="line"><span class="cl"><span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">ICLRRuntimeInfo</span><span class="o">&gt;</span> <span class="n">pRuntimeInfo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">pMetaHost</span><span class="o">-&gt;</span><span class="n">GetRuntime</span><span class="p">(</span><span class="sa">L</span><span class="s">&#34;v4.0.30319&#34;</span><span class="p">,</span> <span class="n">IID_ICLRRuntimeInfo</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pRuntimeInfo</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/hosting/iclrruntimeinfo-interface?WT.mc_id=DT-MVP-5003325">ICLRRuntimeInfo interface</a> allows you to get access to runtime services via <strong>GetInterface</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IMetaDataDispenser</span><span class="o">&gt;</span> <span class="n">pDispenser</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">pRuntimeInfo</span><span class="o">-&gt;</span><span class="n">GetInterface</span><span class="p">(</span><span class="n">CLSID_CorMetaDataDispenser</span><span class="p">,</span> <span class="n">IID_IMetaDataDispenser</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pDispenser</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The service I’m interested in is the <a href="https://learn.microsoft.com/en-us/windows/win32/api/rometadataapi/nn-rometadataapi-imetadatadispenser?WT.mc_id=DT-MVP-5003325"><strong>IMetadataDispenser</strong> interface</a> that allows you to “open a scope” on the assembly you are interested in:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">pDispenser</span><span class="o">-&gt;</span><span class="n">OpenScope</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">wModulePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">    <span class="n">ofRead</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">IID_IMetaDataImport</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="n">IUnknown</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">_pMetaDataImport</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the first parameter is the path to the assembly not to the .pdb file. The scope is abstracted by an <strong>IMetadataImport</strong> interface <a href="/posts/2021-09-06_dealing-with-modules-assemblie/">I have already described</a> and that is needed to call <strong>GetReaderForFile</strong>: and get the <strong>ISymUnmanagedReader</strong>:</p>
<p>hr = pBinder-&gt;GetReaderForFile(_pMetaDataImport, wModulePath.c_str(), nullptr, &amp;_pReader);</p>
<h2 id="the-road-to-get-symbol-details-for-amethod">The road to get symbol details for a method</h2>
<p>The <strong>ISymUnmanagedReader</strong> interface implements <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedreader-getmethod-method?WT.mc_id=DT-MVP-5003325"><strong>GetMethod</strong></a> to get details about a given method token via an <strong>ISymUnmanagedMethod</strong> interface. So, the next question is how to get these tokens. If you remember <a href="/posts/2026-01-16_but-where-is-my/">the previous article</a>, these tokens are from the 06 MethodDef table in the assembly metadata; starting from <strong>06000001</strong> to the last one.</p>
<p>This means that you could write a simple loop starting from 1 up to a hardcoded maximum value, call <strong>TokenFromRid(index, mdtMethodDef)</strong> to get the corresponding token. However, since you are a professional developer, you would search for the exact number of tokens from <a href="https://learn.microsoft.com/en-us/dotnet/core/unmanaged-api/metadata/interfaces/imetadatatables-interface?WT.mc_id=DT-MVP-5003325"><strong>IMetadataTables</strong></a> retrieved from <strong>IMetadataImport</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">ULONG</span> <span class="n">cRows</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// Get IMetaDataTables interface to query the MethodDef table
</span></span></span><span class="line"><span class="cl"><span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IMetaDataTables</span><span class="o">&gt;</span> <span class="n">pTables</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">_pMetaDataImport</span><span class="o">-&gt;</span><span class="n">QueryInterface</span><span class="p">(</span><span class="n">IID_IMetaDataTables</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pTables</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">hr</span><span class="p">)</span> <span class="o">||</span> <span class="n">pTables</span> <span class="o">==</span> <span class="k">nullptr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">cRows</span> <span class="o">=</span> <span class="n">LAST_METHODDEF_TOKEN</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="k">else</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the number of rows in the MethodDef table (table index 0x06 = Method)
</span></span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pTables</span><span class="o">-&gt;</span><span class="n">GetTableInfo</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="mh">0x06</span><span class="p">,</span>           <span class="c1">// MethodDef table
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>           <span class="c1">// cbRow (not needed)
</span></span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">cRows</span><span class="p">,</span>         <span class="c1">// pcRows (number of methods)
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>           <span class="c1">// pcCols (not needed)
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>           <span class="c1">// piKey (not needed)
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span>            <span class="c1">// ppName (not needed)
</span></span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">hr</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">cRows</span> <span class="o">=</span> <span class="n">LAST_METHODDEF_TOKEN</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now that you have the number of rows (i.e. number of methods defined in the metadata), it is easy and safe to get method information from symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="kt">uint32_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;=</span> <span class="n">cRows</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">mdMethodDef</span> <span class="n">token</span> <span class="o">=</span> <span class="n">TokenFromRid</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">mdtMethodDef</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">ISymUnmanagedMethod</span><span class="o">&gt;</span> <span class="n">pMethod</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">_pReader</span><span class="o">-&gt;</span><span class="n">GetMethod</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pMethod</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">pMethod</span> <span class="o">!=</span> <span class="k">nullptr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">MethodInfo</span> <span class="n">info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">GetMethodInfoFromSymbol</span><span class="p">(</span><span class="n">pMethod</span><span class="p">,</span> <span class="n">info</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">_methods</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that <strong>GetMethod</strong> might fail (returning <strong>E_FAIL</strong>) for P/Invoked functions, abstract methods, or methods decorated with <strong>DebuggerHidden</strong> attribute.</p>
<h2 id="give-me-line-and-sourcecode">Give me line and source code!</h2>
<p>For the other methods with symbol information, you can get its token via the <strong>GetToken</strong> method. The <strong>ISymUnmanagedMethod</strong> interface allows low level access to line/column mapping that is beyond the scope of this article. At a high level, positions in source file are named <em>sequence points</em>. Call <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedmethod-getsequencepointcount-method?WT.mc_id=DT-MVP-5003325"><strong>GetSequencePointCount</strong></a> to get… the number of sequence points for a given method.</p>
<p>The next step is to call <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanagedmethod-getsequencepoints-method?WT.mc_id=DT-MVP-5003325">GetSequencePoints</a> with the number of points you want and the corresponding arrays of offsets, lines, columns, end lines, end columns and <strong>ISymUnmanagedDocument</strong>. In my case, I’m only interested in where the method starts so the first sequence point is good enough:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Get sequence points (source line information)
</span></span></span><span class="line"><span class="cl"><span class="n">ULONG32</span> <span class="n">cPoints</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">pMethod</span><span class="o">-&gt;</span><span class="n">GetSequencePointCount</span><span class="p">(</span><span class="o">&amp;</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">cPoints</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">cPoints</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="c1">// We only need the first sequence point for start line
</span></span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">ULONG32</span><span class="o">&gt;</span> <span class="n">offsets</span><span class="p">(</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">ULONG32</span><span class="o">&gt;</span> <span class="n">lines</span><span class="p">(</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">ULONG32</span><span class="o">&gt;</span> <span class="n">columns</span><span class="p">(</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">ULONG32</span><span class="o">&gt;</span> <span class="n">endLines</span><span class="p">(</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">ULONG32</span><span class="o">&gt;</span> <span class="n">endColumns</span><span class="p">(</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">ISymUnmanagedDocument</span><span class="o">*&gt;</span> <span class="n">documents</span><span class="p">(</span><span class="n">cPoints</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ULONG32</span> <span class="n">actualCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pMethod</span><span class="o">-&gt;</span><span class="n">GetSequencePoints</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">cPoints</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">actualCount</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">offsets</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">documents</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">lines</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">columns</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">endLines</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="o">&amp;</span><span class="n">endColumns</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">actualCount</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The source file is described by <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanageddocument-interface?WT.mc_id=DT-MVP-5003325">ISymUnmanagedDocument</a> that provides its name when <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/diagnostics/isymunmanageddocument-geturl-method?WT.mc_id=DT-MVP-5003325"><strong>GetURL</strong></a> is called:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Get the first sequence point&#39;s document and line
</span></span></span><span class="line"><span class="cl">        <span class="n">ISymUnmanagedDocument</span><span class="o">*</span> <span class="n">pDoc</span> <span class="o">=</span> <span class="n">documents</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pDoc</span> <span class="o">!=</span> <span class="k">nullptr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// Get document URL (file path)
</span></span></span><span class="line"><span class="cl">            <span class="n">ULONG32</span> <span class="n">urlLen</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">hr</span> <span class="o">=</span> <span class="n">pDoc</span><span class="o">-&gt;</span><span class="n">GetURL</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">urlLen</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="p">(</span><span class="n">urlLen</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">WCHAR</span><span class="o">&gt;</span> <span class="n">url</span><span class="p">(</span><span class="n">urlLen</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="n">hr</span> <span class="o">=</span> <span class="n">pDoc</span><span class="o">-&gt;</span><span class="n">GetURL</span><span class="p">(</span><span class="n">urlLen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">urlLen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">url</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">hr</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="c1">// Convert wide string to narrow string
</span></span></span><span class="line"><span class="cl">                    <span class="kt">int</span> <span class="n">len</span> <span class="o">=</span> <span class="n">WideCharToMultiByte</span><span class="p">(</span><span class="n">CP_UTF8</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">url</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">urlLen</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">narrowUrl</span><span class="p">(</span><span class="n">len</span><span class="p">,</span> <span class="sc">&#39;\0&#39;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="n">WideCharToMultiByte</span><span class="p">(</span><span class="n">CP_UTF8</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">url</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">urlLen</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">narrowUrl</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">len</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="n">info</span><span class="p">.</span><span class="n">sourceFile</span> <span class="o">=</span> <span class="n">narrowUrl</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="c1">// NOTE: 0xFEEFEE is a special value indicating hidden lines
</span></span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">lineNumber</span> <span class="o">=</span> <span class="n">lines</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="n">pDoc</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The final interesting trick is that the line number might have the special <strong>0xFEEFEE</strong> value. It means that the line is hidden. I have seen it for methods generated by the C# compiler such as <strong>MoveNext</strong> for async state machines or anonymous methods:</p>
<p><img loading="lazy" src="/posts/2026-02-11_how-to-support-net/1_ZBXLm1oWWKp4pacMcX3g5Q.png"></p>
<p>The source code is available from <a href="https://github.com/chrisnas/DumpManagedMethodInfoFromSymbols">my Github repository</a>.</p>
<p>Happy coding!</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://github.com/microsoft/microsoft-pdb/tree/master">Archived Microsoft-pdb repository</a></li>
<li><a href="/posts/2025-12-08_how-to-dump-function/">DIA implementation article</a></li>
<li><a href="/posts/2026-01-16_but-where-is-my/">DbgHelp implementation article</a></li>
</ul>
]]></content:encoded></item><item><title>But where is my method code? DbgHelp comes to the rescue</title><link>https://chrisnas.github.io/posts/2026-01-16_but-where-is-my/</link><pubDate>Fri, 16 Jan 2026 08:21:30 +0000</pubDate><guid>https://chrisnas.github.io/posts/2026-01-16_but-where-is-my/</guid><description>This post show how DbgHelp could help you figure out the line and source code of each managed methods from a .pdb file</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>In our Datatog continuous .NET profiler implementation, we collect the call stack of a thread when something interesting happens such as an exception is thrown for example. In addition to the method name we would like to figure out at what line in which source code file this method is implemented.</p>
<p>This information is usually stored in the <em>program database</em> (.pdb) file that is generated by the compiler when the assembly is generated from the source code. The type and the name of the method are stored in the metadata of the assembly itself but <a href="/posts/2021-09-06_dealing-with-modules-assemblie/">I already told this story before</a>. The .NET compilers support two formats of .pdb: the Portable format for .NET Core and the Windows format for .NET Framework.</p>
<p>I’ve explained <a href="/posts/2025-12-08_how-to-dump-function/">how to use the DIA API</a> and it is now time to show how to leverage the <strong>DbgHelp</strong> API that is available on all Windows machines (even though <a href="https://learn.microsoft.com/en-us/windows/win32/debug/dbghelp-versions?WT.mc_id=DT-MVP-5003325">it is recommended</a> to always install the latest release via the Debugging Tools for Windows.</p>
<p>This time, my goal is to extract from a Windows .pdb file the source code and line information for a given managed method. You can find the corresponding source code of this DumpLine tool in <a href="https://github.com/chrisnas/DumpManagedMethodInfoFromSymbols">my Github repository</a>.</p>
<h2 id="starting-withdbghelp">Starting with DbgHelp</h2>
<p>There are two major ways to get access to symbols with DbgHelp: either from a running process (i.e. to map the currently loaded .dll to their associated .pdb files) or from a tool that would explicitly load a .dll or a .pdb file.</p>
<p>Before anything, you tell DbgHelp which options you want by calling <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions?WT.mc_id=DT-MVP-5003325">SymSetOptions</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">DWORD</span> <span class="n">options</span> <span class="o">=</span> <span class="n">SymGetOptions</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">|=</span> <span class="n">SYMOPT_DEBUG</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">|=</span> <span class="n">SYMOPT_LOAD_LINES</span><span class="p">;</span>           <span class="c1">// Load line number information
</span></span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">|=</span> <span class="n">SYMOPT_UNDNAME</span><span class="p">;</span>              <span class="c1">// Undecorate symbol names
</span></span></span><span class="line"><span class="cl">    <span class="c1">//options |= SYMOPT_DEFERRED_LOADS;       // Defer symbol loading
</span></span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">|=</span> <span class="n">SYMOPT_EXACT_SYMBOLS</span><span class="p">;</span>        <span class="c1">// Require exact symbol match
</span></span></span><span class="line"><span class="cl">    <span class="n">options</span> <span class="o">|=</span> <span class="n">SYMOPT_FAIL_CRITICAL_ERRORS</span><span class="p">;</span> <span class="c1">// Don&#39;t show error dialogs
</span></span></span><span class="line"><span class="cl">    <span class="n">SymSetOptions</span><span class="p">(</span><span class="n">options</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is where I ask that line number information should be collected.</p>
<p>The next step is to call <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize?WT.mc_id=DT-MVP-5003325">SymInitialize</a> to setup DbgHelp environment. The first parameter expects a process handle (returned by <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocess?WT.mc_id=DT-MVP-5003325">GetCurrentProcess</a> in my case). You could pass a path where to find the .pdb files for your dlls as a second parameter. In my case, since I will provide a .pdb file path, I don’t need it, and NULL will be passed. It means that, if needed, DbgHelp will use the current folder and the path set in _NT_SYMBOL_PATH and _NT_ALTERNATE_SYMBOL_PATH environment variables.</p>
<p>The last boolean parameter tells DbgHelp if you want that <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symloadmodule?WT.mc_id=DT-MVP-5003325">SymLoadModule64</a> to be called for each and every loaded .dll in the given process. Definitively not what I want so I’m passing FALSE.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">_hProcess</span> <span class="o">=</span> <span class="n">GetCurrentProcess</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">SymInitialize</span><span class="p">(</span><span class="n">_hProcess</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">FALSE</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_hProcess</span> <span class="o">=</span> <span class="nb">NULL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>At that point, I’m ready to load a .pdb file.</p>
<h2 id="loading-apdbfile">Loading a .pdb file</h2>
<p>The API is straightforward: just call <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symloadmoduleex?WT.mc_id=DT-MVP-5003325">SymLoadModuleEx</a> :</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">_baseAddress</span> <span class="o">=</span> <span class="n">SymLoadModuleEx</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">_hProcess</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">pdbFilePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span>
</span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mh">0x10000000</span><span class="p">,</span> <span class="c1">// arbitrary base address
</span></span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_baseAddress</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The important parameters are the process handle (same as for <strong>SymInitialize</strong>) and the path of the .pdb file. I’ve lost some time trying to understand why my code was not working due to a weird behavior of this function. You know that it succeeds when the returned address is not 0. Well… This is not 100% correct. If the path you provide does not exist, you won’t get 0 but the base address that you also provide. Even worth, when you call the functions I’ll detail later on, no error will happen but nothing will work as expected. So, I simply check that the file exists:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// BUG? : dbghelp does not fail if the .pdb file does not exist...
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">GetFileAttributesA</span><span class="p">(</span><span class="n">pdbFilePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">())</span> <span class="o">==</span> <span class="n">INVALID_FILE_ATTRIBUTES</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that it is possible to unload the symbols of a given loaded module by calling <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symunloadmodule64?WT.mc_id=DT-MVP-5003325">SymUnloadModule</a> with the same process handle and its base address: this will reduce the memory consumption if you don’t need the symbols anymore.</p>
<p>In case of deferred load symbols, it is needed to call <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symgetmoduleinfo64?WT.mc_id=DT-MVP-5003325">SymGetModuleInfo64</a> before trying to access the symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">IMAGEHLP_MODULE64</span> <span class="n">moduleInfo</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="n">moduleInfo</span><span class="p">.</span><span class="n">SizeOfStruct</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">IMAGEHLP_MODULE64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">SymGetModuleInfo64</span><span class="p">(</span><span class="n">_hProcess</span><span class="p">,</span> <span class="n">_baseAddress</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">moduleInfo</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In addition, this will fill up an <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-imagehlp_module?WT.mc_id=DT-MVP-5003325">IMAGEHLP_MODULE64 structure</a> with possibly interesting details:</p>
<p><img loading="lazy" src="/posts/2026-01-16_but-where-is-my/1_SUaczO2zsWrnmCvQ5Y30OA.png"></p>
<p>The .pdb signature and age could be useful to build urls to communicate with symbol servers; but this is another story:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">_age</span> <span class="o">=</span> <span class="n">moduleInfo</span><span class="p">.</span><span class="n">PdbAge</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">GUID</span> <span class="n">guid</span> <span class="o">=</span> <span class="n">moduleInfo</span><span class="p">.</span><span class="n">PdbSig70</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">char</span> <span class="n">strGUID</span><span class="p">[</span><span class="mi">80</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="n">sprintf_s</span><span class="p">(</span><span class="n">strGUID</span><span class="p">,</span> <span class="mi">80</span><span class="p">,</span> <span class="s">&#34;%08x%04x%04x%02x%02x%02x%02x%02x%02x%02x%02x&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">guid</span><span class="p">.</span><span class="n">Data1</span><span class="p">,</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data2</span><span class="p">,</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data3</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">2</span><span class="p">],</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">3</span><span class="p">],</span>
</span></span><span class="line"><span class="cl">        <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">4</span><span class="p">],</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">5</span><span class="p">],</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">6</span><span class="p">],</span> <span class="n">guid</span><span class="p">.</span><span class="n">Data4</span><span class="p">[</span><span class="mi">7</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_guid</span> <span class="o">=</span> <span class="n">strGUID</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You also know if line numbers are available or not thanks to the <strong>LineNumbers</strong> field.</p>
<p>Note that if you asked for deferred symbols option, you won’t get any interesting details:</p>
<p><img loading="lazy" src="/posts/2026-01-16_but-where-is-my/1_X4Y-pHdVa77r58SrppUR3Q.png"></p>
<p>Only the module name and path are provided but nothing else.</p>
<h2 id="enumerating-themethods">Enumerating the methods</h2>
<p>It is now time to iterate on the symbols in the loaded .pdb thanks to <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symenumsymbols?WT.mc_id=DT-MVP-5003325">SymEnumSymbols</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">SymEnumSymbols</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">_hProcess</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">_baseAddress</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="s">&#34;*!*&#34;</span><span class="p">,</span>  <span class="c1">// Mask (all symbols)
</span></span></span><span class="line"><span class="cl">            <span class="n">EnumMethodSymbolsCallback</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="k">this</span>    <span class="c1">// User context to store the methods in _methods instance field
</span></span></span><span class="line"><span class="cl">    <span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In addition to the obvious parameters, this function expects a callback function that will be called for each symbol in the module specified by the process handle and the base address. Note that you can pass any context as the last parameter. In my case, the instance of my <strong>DbgHelpParser</strong> class is passed to be able to store the methods in a dedicated <strong>_methods</strong> field:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">MethodInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">address</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">sourceFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">lineNumber</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span><span class="line"><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">MethodInfo</span><span class="o">&gt;</span> <span class="n">_methods</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The “*!*” mask tells DbgHelp to look for symbols in all modules. This might sound counter intuitive, but the syntax is similar to what you find in WinDBG or Visual Studio: <strong><module>!<symbol></strong>. This could be useful if you load more than one .pdb.</p>
<p>The job of the callback function is to detect the symbols you are interested in from the <a href="https://learn.microsoft.com/en-us/windows/win32/api/DbgHelp/ns-dbghelp-symbol_info?WT.mc_id=DT-MVP-5003325">SYMBOL_INFO structure</a> passed for each matching symbol:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">BOOL</span> <span class="n">CALLBACK</span> <span class="n">DbgHelpParser</span><span class="o">::</span><span class="n">EnumMethodSymbolsCallback</span><span class="p">(</span><span class="n">PSYMBOL_INFO</span> <span class="n">pSymInfo</span><span class="p">,</span> <span class="n">ULONG</span> <span class="n">SymbolSize</span><span class="p">,</span> <span class="n">PVOID</span> <span class="n">UserContext</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">DbgHelpParser</span><span class="o">*</span> <span class="n">parser</span> <span class="o">=</span> <span class="k">reinterpret_cast</span><span class="o">&lt;</span><span class="n">DbgHelpParser</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">UserContext</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">pSymInfo</span><span class="o">-&gt;</span><span class="n">Tag</span> <span class="o">==</span> <span class="n">SymTagFunction</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">        <span class="p">((</span><span class="n">pSymInfo</span><span class="o">-&gt;</span><span class="n">Flags</span> <span class="o">&amp;</span> <span class="p">(</span><span class="n">SYMFLAG_CLR_TOKEN</span> <span class="o">|</span> <span class="n">SYMFLAG_METADATA</span><span class="p">))</span> <span class="o">==</span> <span class="p">(</span><span class="n">SYMFLAG_CLR_TOKEN</span> <span class="o">|</span> <span class="n">SYMFLAG_METADATA</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>Tag</strong> field contains a value from <a href="https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2010/bkedss5f(v=vs.100)?WT.mc_id=DT-MVP-5003325">SymTagEnum</a> but, for a managed .pdb file, you will only get <strong>SymTagFunction</strong>. Also, the <strong>Flags</strong> field should contain SYMFLAG_CLR_TOKEN and SYMFLAG_METADATA because we are only interested in managed methods.</p>
<p>Next, you get the name, address and size from other fields before looking for the source file and line details by calling <a href="https://learn.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symgetlinefromaddr64?WT.mc_id=DT-MVP-5003325">SymGetLineFromAddr64</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">MethodInfo</span> <span class="n">info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">info</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">pSymInfo</span><span class="o">-&gt;</span><span class="n">Name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">info</span><span class="p">.</span><span class="n">address</span> <span class="o">=</span> <span class="n">pSymInfo</span><span class="o">-&gt;</span><span class="n">Address</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">info</span><span class="p">.</span><span class="n">size</span> <span class="o">=</span> <span class="n">pSymInfo</span><span class="o">-&gt;</span><span class="n">Size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Try to get source file and line information
</span></span></span><span class="line"><span class="cl">        <span class="n">IMAGEHLP_LINE64</span> <span class="n">line</span> <span class="o">=</span> <span class="p">{</span> <span class="mi">0</span> <span class="p">};</span>
</span></span><span class="line"><span class="cl">        <span class="n">line</span><span class="p">.</span><span class="n">SizeOfStruct</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">IMAGEHLP_LINE64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">DWORD</span> <span class="n">displacement</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">SymGetLineFromAddr64</span><span class="p">(</span><span class="n">parser</span><span class="o">-&gt;</span><span class="n">_hProcess</span><span class="p">,</span> <span class="n">pSymInfo</span><span class="o">-&gt;</span><span class="n">Address</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">displacement</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">line</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">sourceFile</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">FileName</span> <span class="o">?</span> <span class="n">line</span><span class="p">.</span><span class="nl">FileName</span> <span class="p">:</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">lineNumber</span> <span class="o">=</span> <span class="n">line</span><span class="p">.</span><span class="n">LineNumber</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">sourceFile</span> <span class="o">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">info</span><span class="p">.</span><span class="n">lineNumber</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">parser</span><span class="o">-&gt;</span><span class="n">_methods</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">TRUE</span><span class="p">;</span> <span class="c1">// Continue enumeration
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The callback returns TRUE to continue the enumeration. You could return FALSE if you would look for specific symbols and wanted to speed up the processing.</p>
<h2 id="the-managed-side-of-thestory">The managed side of the story</h2>
<p>When the tool is run on a managed assembly, you get the following kind of output:</p>
<p><img loading="lazy" src="/posts/2026-01-16_but-where-is-my/1_KW21HTdZwwDwwKI69wlvUg.png"></p>
<p>The name of the method does not match at all with the names in my test assembly! Why do all methods have this generic <strong>Method.#<number></strong> format?</p>
<p>Well… the number corresponds to the RID of the corresponding method in the metadata of the assembly. Let’s have a look at what the <strong>MethodDef</strong> metadata table of this assembly looks like in ILSpy:</p>
<p><img loading="lazy" src="/posts/2026-01-16_but-where-is-my/1_S_vIz4k8maK3smcE2MNdVw.png"></p>
<p>The RID column corresponds to the number in the name in the tool output. So, the <strong>Method.#3</strong> is the <strong>get_Records</strong> property getter in PDBFormatReaderTest.cs:28. And this is exactly what I can see in the test source code:</p>
<p><img loading="lazy" src="/posts/2026-01-16_but-where-is-my/1_WZWHUMS4jfWwzomHlrN9pw.png"></p>
<p>You can also check that the lines look correct compared to what is listed by the tool:</p>
<ul>
<li><strong>FilePath</strong> getter at line 19</li>
<li><strong>FilePath</strong> setter at line 20</li>
<li><strong>Records</strong> getter at line 28</li>
<li><strong>Records</strong> setter at line 29</li>
</ul>
<p>Feel free to look at the source code from <a href="https://github.com/chrisnas/DumpManagedMethodInfoFromSymbols">my Github repository</a>.</p>
<p>DbgHelp provides many more services to look into symbols but that’s all for today!</p>
]]></content:encoded></item><item><title>How to dump function symbols from a .pdb file</title><link>https://chrisnas.github.io/posts/2025-12-08_how-to-dump-function/</link><pubDate>Mon, 08 Dec 2025 10:12:20 +0000</pubDate><guid>https://chrisnas.github.io/posts/2025-12-08_how-to-dump-function/</guid><description>Detail how to write a tool that generates a .sym file listing functions symbols with their address, size and signature from a .pdb file</description><content:encoded><![CDATA[<hr>
<p>During this R&amp;D week at Datadog, I wanted to implement a tool accepting a .pdb file and generate a .sym file listing functions symbols with their address, size, name with signature and if they are public or private. This post dig into the implementation details of using <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/getting-started-debug-interface-access-sdk??WT.mc_id=DT-MVP-5003325">Microsoft Debug Interface Access (DIA) COM API</a> to achieve these objectives. If you want to see what my vibe coding experience in Cursor was, read <a href="/posts/2025-12-08_vibe-coding-pdb-dumper/">this other post</a> instead.</p>
<h2 id="one-self-contained-toolplease">One self-contained tool please!</h2>
<p>I would like the tool to be self-contained but since DIA is based on a COM server, it would require registering msdia40.dll on the machine. Not a good idea. In case the dll is in the same folder as the tool, one could “emulate” the magic done by <strong>CoCreateInstance</strong> to get an instance of <strong>IDiaDataSource</strong> (more on this interface soon) by:</p>
<ul>
<li>Call <strong>LoadLibrary</strong> to load the dll in memory</li>
<li>Call <strong>GetProcAddress</strong> to get the <strong>DllGetClassObject</strong> implementation</li>
<li>Call this function to get the <strong>IClassFactory</strong> implementation</li>
<li>Call its <strong>CreateInstance</strong> method to get an object implementing <strong>IDiaDataSource</strong></li>
</ul>
<p>Here is the corresponding code (without error checking for readability)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Create DIA data source without registration using DLL loading
</span></span></span><span class="line"><span class="cl"><span class="n">HRESULT</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">NoRegCoCreate</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="o">&amp;</span> <span class="n">dllPath</span><span class="p">,</span> <span class="n">REFCLSID</span> <span class="n">rclsid</span><span class="p">,</span> <span class="n">REFIID</span> <span class="n">riid</span><span class="p">,</span> <span class="kt">void</span><span class="o">**</span> <span class="n">ppv</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">HMODULE</span> <span class="n">hDll</span> <span class="o">=</span> <span class="n">LoadLibraryW</span><span class="p">(</span><span class="n">dllPath</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">typedef</span> <span class="nf">HRESULT</span><span class="p">(</span><span class="kr">__stdcall</span><span class="o">*</span> <span class="n">DllGetClassObjectFunc</span><span class="p">)(</span><span class="n">REFCLSID</span><span class="p">,</span> <span class="n">REFIID</span><span class="p">,</span> <span class="n">LPVOID</span><span class="o">*</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">DllGetClassObjectFunc</span> <span class="n">pDllGetClassObject</span> <span class="o">=</span> <span class="p">(</span><span class="n">DllGetClassObjectFunc</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hDll</span><span class="p">,</span> <span class="s">&#34;DllGetClassObject&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IClassFactory</span><span class="o">&gt;</span> <span class="n">pClassFactory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">pDllGetClassObject</span><span class="p">(</span><span class="n">rclsid</span><span class="p">,</span> <span class="n">IID_IClassFactory</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pClassFactory</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pClassFactory</span><span class="o">-&gt;</span><span class="n">CreateInstance</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">riid</span><span class="p">,</span> <span class="n">ppv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Note: We intentionally don&#39;t call FreeLibrary here because the DLL needs to stay loaded
</span></span></span><span class="line"><span class="cl">    <span class="c1">// The COM object references will keep it alive
</span></span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">S_OK</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This function is called with the path of msdia40.dll and the UUID of the expected <strong>IDiaDataSource</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-objectivec" data-lang="objectivec"><span class="line"><span class="cl"><span class="c1">// Create DIA data source without registration
</span></span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">NoRegCoCreate</span><span class="p">(</span><span class="n">dllPath</span><span class="p">,</span> <span class="n">CLSID_DiaSource</span><span class="p">,</span> <span class="n">__uuidof</span><span class="p">(</span><span class="n">IDiaDataSource</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">_pDiaDataSource</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>But still, I don’t want to have two binaries!</p>
<p>The trick is to embed the msdia40.dll inside the tool as a Windows resource. In the .rc file, add an RCDATA entry that points to the dll:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">IDR_MSDIA_DLL</span>      <span class="n">RCDATA</span>      <span class="s">&#34;x64</span><span class="se">\\</span><span class="s">Release</span><span class="se">\\</span><span class="s">msdia140.dll&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You should see it in the Resource View in Visual Studio:</p>
<p><img loading="lazy" src="/posts/2025-12-08_how-to-dump-function/1_-GYj2ovADruJxXqRMF930A.png"></p>
<p>Here is the code that extracts it as a file on disk is straightforward (error checking has been removed for readability):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// Extract embedded msdia140.dll from resources
</span></span></span><span class="line"><span class="cl"><span class="nx">bool</span> <span class="nx">PdbSymbolExtractor</span><span class="o">::</span><span class="na">ExtractEmbeddedDll</span><span class="p">(</span><span class="k">const</span> <span class="no">std</span><span class="o">::</span><span class="na">wstring</span><span class="o">&amp;</span> <span class="nx">outputPath</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Find the resource
</span></span></span><span class="line"><span class="cl">    <span class="nx">HMODULE</span> <span class="nx">hModule</span> <span class="o">=</span> <span class="nx">GetModuleHandle</span><span class="p">(</span><span class="k">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">HRSRC</span> <span class="nx">hResource</span> <span class="o">=</span> <span class="nx">FindResource</span><span class="p">(</span><span class="nx">hModule</span><span class="p">,</span> <span class="nx">MAKEINTRESOURCE</span><span class="p">(</span><span class="nx">IDR_MSDIA_DLL</span><span class="p">),</span> <span class="nx">RT_RCDATA</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Load the resource
</span></span></span><span class="line"><span class="cl">    <span class="nx">HGLOBAL</span> <span class="nx">hLoadedResource</span> <span class="o">=</span> <span class="nx">LoadResource</span><span class="p">(</span><span class="nx">hModule</span><span class="p">,</span> <span class="nx">hResource</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Lock the resource to get a pointer to the data
</span></span></span><span class="line"><span class="cl">    <span class="nx">LPVOID</span> <span class="nx">pResourceData</span> <span class="o">=</span> <span class="nx">LockResource</span><span class="p">(</span><span class="nx">hLoadedResource</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the size of the resource
</span></span></span><span class="line"><span class="cl">    <span class="nx">DWORD</span> <span class="nx">resourceSize</span> <span class="o">=</span> <span class="nx">SizeofResource</span><span class="p">(</span><span class="nx">hModule</span><span class="p">,</span> <span class="nx">hResource</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Write the DLL to disk
</span></span></span><span class="line"><span class="cl">    <span class="nx">std</span><span class="o">::</span><span class="na">ofstream</span> <span class="nx">outFile</span><span class="p">(</span><span class="nx">outputPath</span><span class="p">,</span> <span class="nx">std</span><span class="o">::</span><span class="na">ios</span><span class="o">::</span><span class="na">binary</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">outFile</span><span class="o">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="no">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="nx">pResourceData</span><span class="p">),</span> <span class="nx">resourceSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">outFile</span><span class="o">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="where-are-my-functionsymbols">Where are my function symbols?</h2>
<p>After calling <strong>NoRegCoCreate()</strong>, _<strong>pDiaDataSource</strong> stores a reference to the entry point into the DIA APIs. Here are the steps to follow before being able to list the symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">HRESULT</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">ExtractSymbolsFromPdb</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="o">&amp;</span> <span class="n">pdbPath</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">FunctionSymbol</span><span class="o">&gt;&amp;</span> <span class="n">symbols</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Load the PDB file
</span></span></span><span class="line"><span class="cl">    <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">_pDiaDataSource</span><span class="o">-&gt;</span><span class="n">loadDataFromPdb</span><span class="p">(</span><span class="n">pdbPath</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Open a session
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSession</span><span class="o">&gt;</span> <span class="n">pSession</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">_pDiaDataSource</span><span class="o">-&gt;</span><span class="n">openSession</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pSession</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the global scope
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pGlobal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pSession</span><span class="o">-&gt;</span><span class="n">get_globalScope</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pGlobal</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, you have the global scope of the symbols, you can ask for an enumerator for the type of symbols you are interested in; <strong>SymTagFunction</strong> in my case:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Enumerate all function symbols
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaEnumSymbols</span><span class="o">&gt;</span> <span class="n">pEnumSymbols</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pGlobal</span><span class="o">-&gt;</span><span class="n">findChildren</span><span class="p">(</span><span class="n">SymTagFunction</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">nsNone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pEnumSymbols</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">LONG</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pEnumSymbols</span><span class="o">-&gt;</span><span class="n">get_Count</span><span class="p">(</span><span class="o">&amp;</span><span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wcout</span> <span class="o">&lt;&lt;</span> <span class="sa">L</span><span class="s">&#34;Found &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">count</span> <span class="o">&lt;&lt;</span> <span class="sa">L</span><span class="s">&#34; function symbols&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>pEnumSymbols</strong> iterator allows you to loop on each <strong>SymTagFunction</strong> symbol and get its name:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">while</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">pEnumSymbols</span><span class="o">-&gt;</span><span class="n">Next</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pSymbol</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">celt</span><span class="p">))</span> <span class="o">&amp;&amp;</span> <span class="n">celt</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">FunctionSymbol</span> <span class="n">func</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Get function name
</span></span></span><span class="line"><span class="cl">        <span class="n">BSTR</span> <span class="n">bstrName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_name</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bstrName</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">func</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">bstrName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">SysFreeString</span><span class="p">(</span><span class="n">bstrName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that each symbol details are stored in a <strong>FunctionSymbol</strong> instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">FunctionSymbol</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">signature</span><span class="p">;</span> <span class="c1">// Function signature (parameters only, no return type)
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">rva</span><span class="p">;</span>              <span class="c1">// Relative Virtual Address
</span></span></span><span class="line"><span class="cl">    <span class="n">ULONGLONG</span> <span class="n">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">isPublic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>with the rest of the code in the <strong>while()</strong> loop:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Get function signature (parameters only, no return type)
</span></span></span><span class="line"><span class="cl">    <span class="n">func</span><span class="p">.</span><span class="n">signature</span> <span class="o">=</span> <span class="n">ExtractFunctionSignature</span><span class="p">(</span><span class="n">pSymbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get relative virtual address
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">rva</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_relativeVirtualAddress</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rva</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">func</span><span class="p">.</span><span class="n">rva</span> <span class="o">=</span> <span class="n">rva</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get function length
</span></span></span><span class="line"><span class="cl">    <span class="n">ULONGLONG</span> <span class="n">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_length</span><span class="p">(</span><span class="o">&amp;</span><span class="n">length</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">func</span><span class="p">.</span><span class="n">length</span> <span class="o">=</span> <span class="n">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Determine if function is public or private
</span></span></span><span class="line"><span class="cl">    <span class="c1">// Check access level - default to private
</span></span></span><span class="line"><span class="cl">    <span class="n">func</span><span class="p">.</span><span class="n">isPublic</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">access</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_access</span><span class="p">(</span><span class="o">&amp;</span><span class="n">access</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">func</span><span class="p">.</span><span class="n">isPublic</span> <span class="o">=</span> <span class="p">(</span><span class="n">access</span> <span class="o">==</span> <span class="n">CV_public</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pSymbol</span><span class="p">.</span><span class="n">Release</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I did not have the time to do more trial for private/public state, but I should have tried by enumerating <strong>SymTagPublicSymbol</strong> or <strong>SymTagExport</strong> that could be considered as public.</p>
<h2 id="better-with-a-signature">Better with a signature</h2>
<p>The final step is to figure out the signature of each function. This is where the genericity of DIA could be confusing because so many things are represented by <strong>IDiaSymbol</strong>: a symbol, a function, the type of a function, or the type of a parameter…</p>
<p>So, the type of the function is retrieved as an <strong>IDiaSymbol</strong> by calling <strong>getType()</strong> on the function symbol. From that <strong>IDiaSymbol</strong>, <strong>findChildren()</strong> lets you iterate on the parameters:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Extract function signature (parameters only, no return type)
</span></span></span><span class="line"><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">ExtractFunctionSignature</span><span class="p">(</span><span class="n">IDiaSymbol</span><span class="o">*</span> <span class="n">pSymbol</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pSymbol</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get function type
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pFunctionType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_type</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pFunctionType</span><span class="p">)</span> <span class="o">!=</span> <span class="n">S_OK</span> <span class="o">||</span> <span class="o">!</span><span class="n">pFunctionType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Enumerate function arguments
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaEnumSymbols</span><span class="o">&gt;</span> <span class="n">pEnumArgs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">pFunctionType</span><span class="o">-&gt;</span><span class="n">findChildren</span><span class="p">(</span><span class="n">SymTagFunctionArgType</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">nsNone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pEnumArgs</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">LONG</span> <span class="n">argCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pEnumArgs</span><span class="o">-&gt;</span><span class="n">get_Count</span><span class="p">(</span><span class="o">&amp;</span><span class="n">argCount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">argCount</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, the same <strong>Next()</strong> method is called on the enumerator to iterate on each parameter:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Build signature string
</span></span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">signature</span> <span class="o">=</span> <span class="sa">L</span><span class="s">&#34;(&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pArg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ULONG</span> <span class="n">argCelt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">first</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">pEnumArgs</span><span class="o">-&gt;</span><span class="n">Next</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pArg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argCelt</span><span class="p">))</span> <span class="o">&amp;&amp;</span> <span class="n">argCelt</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">first</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">signature</span> <span class="o">+=</span> <span class="sa">L</span><span class="s">&#34;, &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">first</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Get the argument type
</span></span></span><span class="line"><span class="cl">        <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pArgType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pArg</span><span class="o">-&gt;</span><span class="n">get_type</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pArgType</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span> <span class="o">&amp;&amp;</span> <span class="n">pArgType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">signature</span> <span class="o">+=</span> <span class="n">GetTypeName</span><span class="p">(</span><span class="n">pArgType</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">signature</span> <span class="o">+=</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">pArg</span><span class="p">.</span><span class="n">Release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">signature</span> <span class="o">+=</span> <span class="sa">L</span><span class="s">&#34;)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">signature</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The final step is to get the name of the type from the <strong>IDiaSymbol</strong> returned by <strong>get_type()</strong>. If it is a custom type, call <strong>get_name()</strong> like any other symbol. Otherwise, for basic types, call <strong>get_baseType()</strong> and <strong>get_length()</strong> as shown by the code below:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">GetTypeName</span><span class="p">(</span><span class="n">IDiaSymbol</span><span class="o">*</span> <span class="n">pType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Try to get type name directly
</span></span></span><span class="line"><span class="cl">    <span class="n">BSTR</span> <span class="n">bstrTypeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pType</span><span class="o">-&gt;</span><span class="n">get_name</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bstrTypeName</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span> <span class="o">&amp;&amp;</span> <span class="n">bstrTypeName</span> <span class="o">&amp;&amp;</span> <span class="n">wcslen</span><span class="p">(</span><span class="n">bstrTypeName</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">typeName</span> <span class="o">=</span> <span class="n">bstrTypeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">SysFreeString</span><span class="p">(</span><span class="n">bstrTypeName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">typeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// For basic types or unnamed types, try getting basic type info
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">baseType</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ULONGLONG</span> <span class="n">length</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pType</span><span class="o">-&gt;</span><span class="n">get_baseType</span><span class="p">(</span><span class="o">&amp;</span><span class="n">baseType</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">pType</span><span class="o">-&gt;</span><span class="n">get_length</span><span class="p">(</span><span class="o">&amp;</span><span class="n">length</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Map basic types to names
</span></span></span><span class="line"><span class="cl">        <span class="k">switch</span> <span class="p">(</span><span class="n">baseType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btVoid</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;void&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btChar</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;char&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btWChar</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;wchar_t&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btBool</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;bool&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btInt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btLong</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;char&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;short&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;int&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;__int64&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;int&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_wstring</span><span class="p">(</span><span class="n">length</span> <span class="o">*</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btUInt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btULong</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned char&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned short&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned int&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned __int64&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;uint&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_wstring</span><span class="p">(</span><span class="n">length</span> <span class="o">*</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btFloat</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;float&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;double&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;float&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_wstring</span><span class="p">(</span><span class="n">length</span> <span class="o">*</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is a “simple” implementation that does not take pointers, addresses, arrays, and more into account. For a more complete solution, I would recommend looking at the <strong>PrintType()</strong> implementation in the DIA2Dump code sample that is installed with Visual Studio.</p>
<p>I hope this will get your foot in the door of symbol parsing and make you want to dig further into DIA.</p>
<h2 id="references">References</h2>
<ul>
<li>Corresponding source code is available in <a href="https://github.com/chrisnas/VibeCoding">my github repository</a>.</li>
<li>Archived Microsoft <a href="https://github.com/microsoft/microsoft-pdb/tree/master">documentation/implementation of .pdb format</a> including a <a href="https://github.com/microsoft/microsoft-pdb/tree/master/cvdump">symbol dumper</a> code.</li>
<li><a href="https://learn.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/dia2dump-sample">DIA2Dump</a> Visual Studio code sample.</li>
</ul>
]]></content:encoded></item><item><title>Vibe coding a .pdb dumper or how I became a Product Manager</title><link>https://chrisnas.github.io/posts/2025-12-08_vibe-coding-pdb-dumper/</link><pubDate>Mon, 08 Dec 2025 10:12:16 +0000</pubDate><guid>https://chrisnas.github.io/posts/2025-12-08_vibe-coding-pdb-dumper/</guid><description>Follow me Vibe Coding in Cursor during my Datadog R&amp;amp;D Week to list function symbols with their signature and more</description><content:encoded><![CDATA[<hr>
<p>During this R&amp;D week at Datadog, I wanted to implement a tool accepting a .pdb file and generate a .sym file listing functions symbols with their address, size, name with signature and if they are public or private. This post dig into the implementation details of using <a href="https://learn.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/getting-started-debug-interface-access-sdk??WT.mc_id=DT-MVP-5003325">Microsoft Debug Interface Access (DIA) COM API</a> to achieve these objectives. If you want to see what my vibe coding experience in Cursor was, read <a href="/posts/2025-12-08_vibe-coding-pdb-dumper/">this other post</a> instead.</p>
<h2 id="one-self-contained-toolplease">One self-contained tool please!</h2>
<p>I would like the tool to be self-contained but since DIA is based on a COM server, it would require registering msdia40.dll on the machine. Not a good idea. In case the dll is in the same folder as the tool, one could “emulate” the magic done by <strong>CoCreateInstance</strong> to get an instance of <strong>IDiaDataSource</strong> (more on this interface soon) by:</p>
<ul>
<li>Call <strong>LoadLibrary</strong> to load the dll in memory</li>
<li>Call <strong>GetProcAddress</strong> to get the <strong>DllGetClassObject</strong> implementation</li>
<li>Call this function to get the <strong>IClassFactory</strong> implementation</li>
<li>Call its <strong>CreateInstance</strong> method to get an object implementing <strong>IDiaDataSource</strong></li>
</ul>
<p>Here is the corresponding code (without error checking for readability)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Create DIA data source without registration using DLL loading
</span></span></span><span class="line"><span class="cl"><span class="n">HRESULT</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">NoRegCoCreate</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="o">&amp;</span> <span class="n">dllPath</span><span class="p">,</span> <span class="n">REFCLSID</span> <span class="n">rclsid</span><span class="p">,</span> <span class="n">REFIID</span> <span class="n">riid</span><span class="p">,</span> <span class="kt">void</span><span class="o">**</span> <span class="n">ppv</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">HMODULE</span> <span class="n">hDll</span> <span class="o">=</span> <span class="n">LoadLibraryW</span><span class="p">(</span><span class="n">dllPath</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">typedef</span> <span class="nf">HRESULT</span><span class="p">(</span><span class="kr">__stdcall</span><span class="o">*</span> <span class="n">DllGetClassObjectFunc</span><span class="p">)(</span><span class="n">REFCLSID</span><span class="p">,</span> <span class="n">REFIID</span><span class="p">,</span> <span class="n">LPVOID</span><span class="o">*</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">DllGetClassObjectFunc</span> <span class="n">pDllGetClassObject</span> <span class="o">=</span> <span class="p">(</span><span class="n">DllGetClassObjectFunc</span><span class="p">)</span><span class="n">GetProcAddress</span><span class="p">(</span><span class="n">hDll</span><span class="p">,</span> <span class="s">&#34;DllGetClassObject&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IClassFactory</span><span class="o">&gt;</span> <span class="n">pClassFactory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">pDllGetClassObject</span><span class="p">(</span><span class="n">rclsid</span><span class="p">,</span> <span class="n">IID_IClassFactory</span><span class="p">,</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">pClassFactory</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pClassFactory</span><span class="o">-&gt;</span><span class="n">CreateInstance</span><span class="p">(</span><span class="nb">NULL</span><span class="p">,</span> <span class="n">riid</span><span class="p">,</span> <span class="n">ppv</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Note: We intentionally don&#39;t call FreeLibrary here because the DLL needs to stay loaded
</span></span></span><span class="line"><span class="cl">    <span class="c1">// The COM object references will keep it alive
</span></span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">S_OK</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This function is called with the path of msdia40.dll and the UUID of the expected <strong>IDiaDataSource</strong>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-objectivec" data-lang="objectivec"><span class="line"><span class="cl"><span class="c1">// Create DIA data source without registration
</span></span></span><span class="line"><span class="cl"><span class="n">hr</span> <span class="o">=</span> <span class="n">NoRegCoCreate</span><span class="p">(</span><span class="n">dllPath</span><span class="p">,</span> <span class="n">CLSID_DiaSource</span><span class="p">,</span> <span class="n">__uuidof</span><span class="p">(</span><span class="n">IDiaDataSource</span><span class="p">),</span> <span class="p">(</span><span class="kt">void</span><span class="o">**</span><span class="p">)</span><span class="o">&amp;</span><span class="n">_pDiaDataSource</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>But still, I don’t want to have two binaries!</p>
<p>The trick is to embed the msdia40.dll inside the tool as a Windows resource. In the .rc file, add an RCDATA entry that points to the dll:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">IDR_MSDIA_DLL</span>      <span class="n">RCDATA</span>      <span class="s">&#34;x64</span><span class="se">\\</span><span class="s">Release</span><span class="se">\\</span><span class="s">msdia140.dll&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You should see it in the Resource View in Visual Studio:</p>
<p><img loading="lazy" src="1_-GYj2ovADruJxXqRMF930A.png"></p>
<p>Here is the code that extracts it as a file on disk is straightforward (error checking has been removed for readability):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-php" data-lang="php"><span class="line"><span class="cl"><span class="c1">// Extract embedded msdia140.dll from resources
</span></span></span><span class="line"><span class="cl"><span class="nx">bool</span> <span class="nx">PdbSymbolExtractor</span><span class="o">::</span><span class="na">ExtractEmbeddedDll</span><span class="p">(</span><span class="k">const</span> <span class="no">std</span><span class="o">::</span><span class="na">wstring</span><span class="o">&amp;</span> <span class="nx">outputPath</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Find the resource
</span></span></span><span class="line"><span class="cl">    <span class="nx">HMODULE</span> <span class="nx">hModule</span> <span class="o">=</span> <span class="nx">GetModuleHandle</span><span class="p">(</span><span class="k">NULL</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">HRSRC</span> <span class="nx">hResource</span> <span class="o">=</span> <span class="nx">FindResource</span><span class="p">(</span><span class="nx">hModule</span><span class="p">,</span> <span class="nx">MAKEINTRESOURCE</span><span class="p">(</span><span class="nx">IDR_MSDIA_DLL</span><span class="p">),</span> <span class="nx">RT_RCDATA</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Load the resource
</span></span></span><span class="line"><span class="cl">    <span class="nx">HGLOBAL</span> <span class="nx">hLoadedResource</span> <span class="o">=</span> <span class="nx">LoadResource</span><span class="p">(</span><span class="nx">hModule</span><span class="p">,</span> <span class="nx">hResource</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Lock the resource to get a pointer to the data
</span></span></span><span class="line"><span class="cl">    <span class="nx">LPVOID</span> <span class="nx">pResourceData</span> <span class="o">=</span> <span class="nx">LockResource</span><span class="p">(</span><span class="nx">hLoadedResource</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the size of the resource
</span></span></span><span class="line"><span class="cl">    <span class="nx">DWORD</span> <span class="nx">resourceSize</span> <span class="o">=</span> <span class="nx">SizeofResource</span><span class="p">(</span><span class="nx">hModule</span><span class="p">,</span> <span class="nx">hResource</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Write the DLL to disk
</span></span></span><span class="line"><span class="cl">    <span class="nx">std</span><span class="o">::</span><span class="na">ofstream</span> <span class="nx">outFile</span><span class="p">(</span><span class="nx">outputPath</span><span class="p">,</span> <span class="nx">std</span><span class="o">::</span><span class="na">ios</span><span class="o">::</span><span class="na">binary</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">outFile</span><span class="o">.</span><span class="nx">write</span><span class="p">(</span><span class="nx">static_cast</span><span class="o">&lt;</span><span class="k">const</span> <span class="no">char</span><span class="o">*&gt;</span><span class="p">(</span><span class="nx">pResourceData</span><span class="p">),</span> <span class="nx">resourceSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="nx">outFile</span><span class="o">.</span><span class="nx">close</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="k">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="where-are-my-functionsymbols">Where are my function symbols?</h2>
<p>After calling <strong>NoRegCoCreate()</strong>, _<strong>pDiaDataSource</strong> stores a reference to the entry point into the DIA APIs. Here are the steps to follow before being able to list the symbols:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">HRESULT</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">ExtractSymbolsFromPdb</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="o">&amp;</span> <span class="n">pdbPath</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">FunctionSymbol</span><span class="o">&gt;&amp;</span> <span class="n">symbols</span><span class="p">)</span> 
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Load the PDB file
</span></span></span><span class="line"><span class="cl">    <span class="n">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">_pDiaDataSource</span><span class="o">-&gt;</span><span class="n">loadDataFromPdb</span><span class="p">(</span><span class="n">pdbPath</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Open a session
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSession</span><span class="o">&gt;</span> <span class="n">pSession</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">_pDiaDataSource</span><span class="o">-&gt;</span><span class="n">openSession</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pSession</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get the global scope
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pGlobal</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pSession</span><span class="o">-&gt;</span><span class="n">get_globalScope</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pGlobal</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, you have the global scope of the symbols, you can ask for an enumerator for the type of symbols you are interested in; <strong>SymTagFunction</strong> in my case:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Enumerate all function symbols
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaEnumSymbols</span><span class="o">&gt;</span> <span class="n">pEnumSymbols</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hr</span> <span class="o">=</span> <span class="n">pGlobal</span><span class="o">-&gt;</span><span class="n">findChildren</span><span class="p">(</span><span class="n">SymTagFunction</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">nsNone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pEnumSymbols</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">LONG</span> <span class="n">count</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pEnumSymbols</span><span class="o">-&gt;</span><span class="n">get_Count</span><span class="p">(</span><span class="o">&amp;</span><span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wcout</span> <span class="o">&lt;&lt;</span> <span class="sa">L</span><span class="s">&#34;Found &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">count</span> <span class="o">&lt;&lt;</span> <span class="sa">L</span><span class="s">&#34; function symbols&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">endl</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>pEnumSymbols</strong> iterator allows you to loop on each <strong>SymTagFunction</strong> symbol and get its name:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">while</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">pEnumSymbols</span><span class="o">-&gt;</span><span class="n">Next</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pSymbol</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">celt</span><span class="p">))</span> <span class="o">&amp;&amp;</span> <span class="n">celt</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">FunctionSymbol</span> <span class="n">func</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Get function name
</span></span></span><span class="line"><span class="cl">        <span class="n">BSTR</span> <span class="n">bstrName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_name</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bstrName</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">func</span><span class="p">.</span><span class="n">name</span> <span class="o">=</span> <span class="n">bstrName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">SysFreeString</span><span class="p">(</span><span class="n">bstrName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that each symbol details are stored in a <strong>FunctionSymbol</strong> instance:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">FunctionSymbol</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">signature</span><span class="p">;</span> <span class="c1">// Function signature (parameters only, no return type)
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">rva</span><span class="p">;</span>              <span class="c1">// Relative Virtual Address
</span></span></span><span class="line"><span class="cl">    <span class="n">ULONGLONG</span> <span class="n">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">isPublic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>with the rest of the code in the <strong>while()</strong> loop:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Get function signature (parameters only, no return type)
</span></span></span><span class="line"><span class="cl">    <span class="n">func</span><span class="p">.</span><span class="n">signature</span> <span class="o">=</span> <span class="n">ExtractFunctionSignature</span><span class="p">(</span><span class="n">pSymbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get relative virtual address
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">rva</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_relativeVirtualAddress</span><span class="p">(</span><span class="o">&amp;</span><span class="n">rva</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">func</span><span class="p">.</span><span class="n">rva</span> <span class="o">=</span> <span class="n">rva</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get function length
</span></span></span><span class="line"><span class="cl">    <span class="n">ULONGLONG</span> <span class="n">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_length</span><span class="p">(</span><span class="o">&amp;</span><span class="n">length</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">func</span><span class="p">.</span><span class="n">length</span> <span class="o">=</span> <span class="n">length</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Determine if function is public or private
</span></span></span><span class="line"><span class="cl">    <span class="c1">// Check access level - default to private
</span></span></span><span class="line"><span class="cl">    <span class="n">func</span><span class="p">.</span><span class="n">isPublic</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">access</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_access</span><span class="p">(</span><span class="o">&amp;</span><span class="n">access</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">func</span><span class="p">.</span><span class="n">isPublic</span> <span class="o">=</span> <span class="p">(</span><span class="n">access</span> <span class="o">==</span> <span class="n">CV_public</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pSymbol</span><span class="p">.</span><span class="n">Release</span><span class="p">();</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I did not have the time to do more trial for private/public state, but I should have tried by enumerating <strong>SymTagPublicSymbol</strong> or <strong>SymTagExport</strong> that could be considered as public.</p>
<h2 id="better-with-a-signature">Better with a signature</h2>
<p>The final step is to figure out the signature of each function. This is where the genericity of DIA could be confusing because so many things are represented by <strong>IDiaSymbol</strong>: a symbol, a function, the type of a function, or the type of a parameter…</p>
<p>So, the type of the function is retrieved as an <strong>IDiaSymbol</strong> by calling <strong>getType()</strong> on the function symbol. From that <strong>IDiaSymbol</strong>, <strong>findChildren()</strong> lets you iterate on the parameters:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Extract function signature (parameters only, no return type)
</span></span></span><span class="line"><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">ExtractFunctionSignature</span><span class="p">(</span><span class="n">IDiaSymbol</span><span class="o">*</span> <span class="n">pSymbol</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pSymbol</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Get function type
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pFunctionType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pSymbol</span><span class="o">-&gt;</span><span class="n">get_type</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pFunctionType</span><span class="p">)</span> <span class="o">!=</span> <span class="n">S_OK</span> <span class="o">||</span> <span class="o">!</span><span class="n">pFunctionType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Enumerate function arguments
</span></span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaEnumSymbols</span><span class="o">&gt;</span> <span class="n">pEnumArgs</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">FAILED</span><span class="p">(</span><span class="n">pFunctionType</span><span class="o">-&gt;</span><span class="n">findChildren</span><span class="p">(</span><span class="n">SymTagFunctionArgType</span><span class="p">,</span> <span class="nb">NULL</span><span class="p">,</span> <span class="n">nsNone</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pEnumArgs</span><span class="p">)))</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">LONG</span> <span class="n">argCount</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">pEnumArgs</span><span class="o">-&gt;</span><span class="n">get_Count</span><span class="p">(</span><span class="o">&amp;</span><span class="n">argCount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">argCount</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;()&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, the same <strong>Next()</strong> method is called on the enumerator to iterate on each parameter:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// Build signature string
</span></span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">signature</span> <span class="o">=</span> <span class="sa">L</span><span class="s">&#34;(&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pArg</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ULONG</span> <span class="n">argCelt</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">first</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">SUCCEEDED</span><span class="p">(</span><span class="n">pEnumArgs</span><span class="o">-&gt;</span><span class="n">Next</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">pArg</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argCelt</span><span class="p">))</span> <span class="o">&amp;&amp;</span> <span class="n">argCelt</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">first</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">signature</span> <span class="o">+=</span> <span class="sa">L</span><span class="s">&#34;, &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">first</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Get the argument type
</span></span></span><span class="line"><span class="cl">        <span class="n">CComPtr</span><span class="o">&lt;</span><span class="n">IDiaSymbol</span><span class="o">&gt;</span> <span class="n">pArgType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pArg</span><span class="o">-&gt;</span><span class="n">get_type</span><span class="p">(</span><span class="o">&amp;</span><span class="n">pArgType</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span> <span class="o">&amp;&amp;</span> <span class="n">pArgType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">signature</span> <span class="o">+=</span> <span class="n">GetTypeName</span><span class="p">(</span><span class="n">pArgType</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">signature</span> <span class="o">+=</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">pArg</span><span class="p">.</span><span class="n">Release</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">signature</span> <span class="o">+=</span> <span class="sa">L</span><span class="s">&#34;)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">signature</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The final step is to get the name of the type from the <strong>IDiaSymbol</strong> returned by <strong>get_type()</strong>. If it is a custom type, call <strong>get_name()</strong> like any other symbol. Otherwise, for basic types, call <strong>get_baseType()</strong> and <strong>get_length()</strong> as shown by the code below:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span><span class="lnt">51
</span><span class="lnt">52
</span><span class="lnt">53
</span><span class="lnt">54
</span><span class="lnt">55
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">PdbSymbolExtractor</span><span class="o">::</span><span class="n">GetTypeName</span><span class="p">(</span><span class="n">IDiaSymbol</span><span class="o">*</span> <span class="n">pType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Try to get type name directly
</span></span></span><span class="line"><span class="cl">    <span class="n">BSTR</span> <span class="n">bstrTypeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pType</span><span class="o">-&gt;</span><span class="n">get_name</span><span class="p">(</span><span class="o">&amp;</span><span class="n">bstrTypeName</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span> <span class="o">&amp;&amp;</span> <span class="n">bstrTypeName</span> <span class="o">&amp;&amp;</span> <span class="n">wcslen</span><span class="p">(</span><span class="n">bstrTypeName</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">typeName</span> <span class="o">=</span> <span class="n">bstrTypeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">SysFreeString</span><span class="p">(</span><span class="n">bstrTypeName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">typeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// For basic types or unnamed types, try getting basic type info
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">baseType</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ULONGLONG</span> <span class="n">length</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pType</span><span class="o">-&gt;</span><span class="n">get_baseType</span><span class="p">(</span><span class="o">&amp;</span><span class="n">baseType</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">pType</span><span class="o">-&gt;</span><span class="n">get_length</span><span class="p">(</span><span class="o">&amp;</span><span class="n">length</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// Map basic types to names
</span></span></span><span class="line"><span class="cl">        <span class="k">switch</span> <span class="p">(</span><span class="n">baseType</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btVoid</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;void&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btChar</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;char&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btWChar</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;wchar_t&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btBool</span><span class="p">:</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;bool&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btInt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btLong</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;char&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;short&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;int&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;__int64&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;int&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_wstring</span><span class="p">(</span><span class="n">length</span> <span class="o">*</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btUInt</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btULong</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned char&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">2</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned short&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned int&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;unsigned __int64&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;uint&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_wstring</span><span class="p">(</span><span class="n">length</span> <span class="o">*</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">case</span> <span class="nl">btFloat</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">4</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;float&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="nf">if</span> <span class="p">(</span><span class="n">length</span> <span class="o">==</span> <span class="mi">8</span><span class="p">)</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;double&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span> <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;float&#34;</span> <span class="o">+</span> <span class="n">std</span><span class="o">::</span><span class="n">to_wstring</span><span class="p">(</span><span class="n">length</span> <span class="o">*</span> <span class="mi">8</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">L</span><span class="s">&#34;?&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is a “simple” implementation that does not take pointers, addresses, arrays, and more into account. For a more complete solution, I would recommend looking at the <strong>PrintType()</strong> implementation in the DIA2Dump code sample that is installed with Visual Studio.</p>
<p>I hope this will get your foot in the door of symbol parsing and make you want to dig further into DIA.</p>
<h2 id="references">References</h2>
<ul>
<li>Corresponding source code is available in <a href="https://github.com/chrisnas/VibeCoding">my github repository</a>.</li>
<li>Archived Microsoft <a href="https://github.com/microsoft/microsoft-pdb/tree/master">documentation/implementation of .pdb format</a> including a <a href="https://github.com/microsoft/microsoft-pdb/tree/master/cvdump">symbol dumper</a> code.</li>
<li><a href="https://learn.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/dia2dump-sample">DIA2Dump</a> Visual Studio code sample.</li>
</ul>
]]></content:encoded></item><item><title>How to monitor .NET applications startup</title><link>https://chrisnas.github.io/posts/2025-03-13_how-to-monitor-net/</link><pubDate>Thu, 13 Mar 2025 10:50:55 +0000</pubDate><guid>https://chrisnas.github.io/posts/2025-03-13_how-to-monitor-net/</guid><description>This episode explains how to monitor the startup of a .NET application and get insights about its lock and wait contentions duration</description><content:encoded><![CDATA[<hr>
<p>In <a href="/posts/2025-01-13_measuring-the-impact-of/">the previous article</a>, I presented what is needed (i.e. listen to <strong>WaitHandleWait</strong> events) to compute lock/wait durations and call stacks for <strong>Mutex</strong>, <strong>Semaphore</strong>, <strong>SemaphoreSlim</strong>, <strong>Manual</strong>/<strong>AutoResetEvent</strong>, <strong>ManualResetEventSlim</strong>, <strong>ReaderWriterLockSlim</strong> .NET synchronization constructs for a running process.</p>
<p>However, since the application is already running, some JIT-related events are missing, and some frames of the call stacks cannot be symbolized. Also, it would be great to monitor an application’s startup to see if it could be faster.</p>
<p>This post will detail how to monitor a .NET application since the very beginning of its life and the issues you might face.</p>
<h2 id="preparing-a-newnet-process-to-be-monitored">Preparing a new .NET process to be monitored</h2>
<p>From .NET 5, the <strong>dotnet-trace</strong> CLI tool allows you to <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/dotnet-trace-instructions.md#using-dotnet-trace-to-launch-a-child-process-and-trace-it-from-startup">pass a command line to execute and trace it from startup</a>. In a <a href="https://medium.com/@ocoanet/tracing-allocations-with-eventpipe-part-3-tracing-without-dotnet-trace-7244bdb86e03">very interesting article</a>, Olivier Coanet presented the gory details about how to tell the .NET runtime to start an application in a pseudo-suspended mode as shown in the following diagram:</p>
<p><img loading="lazy" src="/posts/2025-03-13_how-to-monitor-net/1_9J_2x2R0n0nkuRTl6MDzZw.png"></p>
<p>The first step is to create a <strong>ReverseDiagnosticsServer</strong> instance with a specific port (i.e. <strong>dotnet-wait_1234</strong> in the diagram). Next, the process to monitor is spawned with the <strong>DOTNET_DiagnosticPorts</strong> environment variable set to the same port (i.e. <strong>dotnet-wait_1234</strong>). Look at the <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md#diagnostic-ports">Diagnostics documentation</a> of the Diagnostic Ports with <strong>DOTNET_DiagnosticPorts</strong> environment variable for more details. The .NET runtime is the new process will listen to this port and… wait.</p>
<p>When the tool is ready, it sends a resume command via a <strong>DiagnosticsClient</strong>: from that point in time, the CLR executes the normal flow of actions to run the application and… you will receive all events without missing one!</p>
<h2 id="get-my-command-lineplease">Get my command line please</h2>
<p>Following the <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/dotnet-trace-instructions.md#using-dotnet-trace-to-launch-a-child-process-and-trace-it-from-startup"><strong>dotnet-trace</strong> example</a>, my <a href="https://www.nuget.org/packages/dotnet-wait">dotnet-wait</a> tool accepts the command line of the child process in its final arguments that follow the** — **trigger. For example, dotnet-wait — dotnet foo.dll will start the program in foo.dll by using dotnet.exe. I’m reusing <a href="https://github.com/dotnet/diagnostics/blob/main/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs#L37">the code in ReversedServerHelper.cs</a> to deal with arguments containing spaces:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">current</span> <span class="p">==</span> <span class="s">&#34;--&#34;</span><span class="p">)</span>  <span class="c1">// this is supposed to be the last one</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">i</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="p">&lt;</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">parameters</span><span class="p">.</span><span class="n">pathName</span> <span class="p">=</span> <span class="n">args</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// use the remaining arguments as the arguments for the child app to spawn</span>
</span></span><span class="line"><span class="cl">        <span class="n">i</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">i</span> <span class="p">&lt;</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">=</span> <span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">j</span> <span class="p">=</span> <span class="n">i</span><span class="p">;</span> <span class="n">j</span> <span class="p">&lt;</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">;</span> <span class="n">j</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">Contains</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">+=</span> <span class="s">$&#34;\&#34;</span><span class="p">{</span><span class="n">args</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">Replace</span><span class="p">(</span><span class="s">&#34;\&#34;&#34;</span><span class="p">,</span> <span class="s">&#34;\\\&#34;&#34;</span><span class="p">)}</span><span class="err">\</span><span class="s">&#34;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="k">else</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">+=</span> <span class="n">args</span><span class="p">[</span><span class="n">j</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(</span><span class="n">j</span> <span class="p">!=</span> <span class="n">args</span><span class="p">.</span><span class="n">Length</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">parameters</span><span class="p">.</span><span class="n">arguments</span> <span class="p">+=</span> <span class="s">&#34; &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// no need to look for more arguments</span>
</span></span><span class="line"><span class="cl">        <span class="k">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Missing path name value...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to spawn the child process is simple:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// start the monitored app</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">psi</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ProcessStartInfo</span><span class="p">(</span><span class="n">pathName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">arguments</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">psi</span><span class="p">.</span><span class="n">Arguments</span> <span class="p">=</span> <span class="n">arguments</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="n">psi</span><span class="p">.</span><span class="n">EnvironmentVariables</span><span class="p">[</span><span class="s">&#34;DOTNET_DiagnosticPorts&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">port</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">psi</span><span class="p">.</span><span class="n">UseShellExecute</span> <span class="p">=</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">process</span> <span class="p">=</span> <span class="n">System</span><span class="p">.</span><span class="n">Diagnostics</span><span class="p">.</span><span class="n">Process</span><span class="p">.</span><span class="n">Start</span><span class="p">(</span><span class="n">psi</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Here is an example with the following prompt:</p>
<pre tabindex="0"><code>-- dotnet &#34;C:\CommandLineTest.dll&#34; one two &#34;t h r e e&#34; four &#39;five six&#39;
</code></pre><p>that generates the output (the test application is just listing its arguments):</p>
<pre tabindex="0"><code>dotnet-wait v1.0.0.0 - List wait duration
by Christophe Nasarre

Press ENTER to exit...
6 arguments
   1 | one
   2 | two
   3 | t h r e e
   4 | four
   5 | &#39;five
   6 | six&#39;
</code></pre><p>This test reminded me to never use simple quotes in prompts :^)</p>
<h2 id="its-myconsole">It’s my console!</h2>
<p>Once I implemented these steps, I immediately faced a very simple problem: my <strong>dotnet-wait</strong> tool and the test application are console applications. It means that they will share the same console for both input and output. For example, both are waiting for the RETURN key to (1) stop for the tool and (2) start for the test application: too bad for me because the tool will stop as soon the application starts…</p>
<p>Going back in time in my Windows memories, I remembered that the Win32 <a href="https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw?WT.mc_id=DT-MVP-5003325">CreateProcess</a> API accepts <a href="https://learn.microsoft.com/en-us/windows/win32/procthread/process-creation-flags?WT.mc_id=DT-MVP-5003325"><strong>CREATE_NEW_CONSOLE</strong> as creation flag</a> to automagically start the child process into its own new console. Unfortunately, it is not possible to pass this flag in .NET; maybe a limitation due to Linux support.</p>
<p>One simple solution could be to redirect the output of the tool or the application to a file: that would avoid mixing them in the console. Note that, by default, <strong>dotnet-trace</strong> discards output from the child process (by setting <strong>RedirectStandardOutput</strong>, <strong>RedirectStandardError</strong> and <strong>RedirectStandardInput</strong> <a href="https://github.com/dotnet/diagnostics/blob/main/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs#L95">to false</a> and by <a href="https://github.com/dotnet/diagnostics/blob/main/src/Tools/Common/ReversedServerHelpers/ReversedServerHelpers.cs#L69">ignoring the error and output streams</a>) except if you pass <strong>— show-child-io</strong> on the command line. In this case, no output for <strong>dotnet-trace</strong>.</p>
<p>I decided to do the opposite for <strong>dotnet-wait</strong>: by default, you also get the child output but you can redirect the output of the tool to a file with <strong>-o <output path name></strong>. Still, this does not solve the input problem in case of common expected keys.</p>
<p>If you remember the interactions between the tool and the monitored application, the latter is suspended until <strong>DiagnosticsClient::ResumeRuntime</strong> is called. So, why not starting the tool that spawns the application in one console and another instance of the tool in a new console that will resume the application? This is exactly what my friend <a href="https://x.com/KooKiz">Kevin Gosse</a> imagined and how <strong>dotnet-wait</strong> works.</p>
<p><img loading="lazy" src="/posts/2025-03-13_how-to-monitor-net/1_u9iVEg9L5Z_DVpn6jtcM2w.png"></p>
<p>After the timeout that you give to <strong>diagnosticsServer.AcceptAsync(cancellation.Token)</strong> has elapsed, the runtime in the child process will display the following message:</p>
<pre tabindex="0"><code>The runtime has been configured to pause during startup and is awaiting a Diagnostics IPC ResumeStartup command from a Diagnostic Port.
DOTNET_DiagnosticPorts=&#34;dotnet-wait_34296&#34;
DOTNET_DefaultDiagnosticPortSuspend=0
</code></pre><p>And this is exactly what the <strong>-r 34296</strong> parameter will do!</p>
<p>You can now install <strong>dotnet wait</strong> and monitor the lock and wait contentions of your .NET9+ applications.</p>
]]></content:encoded></item><item><title>Measuring the impact of locks and waits on latency in your .NET apps</title><link>https://chrisnas.github.io/posts/2025-01-13_measuring-the-impact-of/</link><pubDate>Mon, 13 Jan 2025 15:31:03 +0000</pubDate><guid>https://chrisnas.github.io/posts/2025-01-13_measuring-the-impact-of/</guid><description>Monitor mutex, semaphore and event wait duration</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>In an <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">old post</a>, I detailed how to use <strong>ContentionStart</strong> and <strong>ContentionStop</strong> events to measure the lock contentions duration for a .NET application. In a <a href="https://github.com/DataDog/dd-trace-dotnet/issues/5814">.NET 9 pull request</a>, a former Criteo’s colleague <a href="https://www.linkedin.com/in/gregoire-verdier">Grégoire Verdier</a> has added new events to be notified when wait time similar to lock contention is happening for Mutex, Semaphore, Manual/AutoResetEvent. Read <a href="https://techblog.criteo.com/a-perfview-alternative-in-webassembly-f6833820b699">his post</a> for more details about what he was trying to investigate.</p>
<p>With asynchronous and multi-threaded algorithms, it is essential to detect unexpected wait/locks in our applications. This post shows you how to leverage these events to measure the duration of these waits and get the call stack when the wait started:</p>
<p><img loading="lazy" src="/posts/2025-01-13_measuring-the-impact-of/1_OTO7qWO5aYvNXprhaPvRoA.png"></p>
<h2 id="new-waithandlewait-events">New WaitHandleWait events</h2>
<p>These new events are emitted by the <strong>Microsoft-Windows-DotNETRuntime</strong> CLR provider when you enable the <strong>WaitHandle</strong> (= 0x40000000000) keyword with <strong>Verbose</strong> verbosity. Each time <strong>WaitOne</strong> is called on a waitable object and this object is already owned, a <strong>WaitHandleWaitStart</strong> event is emitted. When the object is released, a <strong>WaitHandleWaitStop</strong> event is emitted.</p>
<p>For example, the following code:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">static</span> <span class="n">Mutex</span> <span class="n">mutex</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Mutex</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="k">void</span> <span class="n">Main</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">owningThread</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(</span><span class="n">OwningThread</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">owningThread</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">mutexThread</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(</span><span class="n">MutexThread</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutexThread</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">owningThread</span><span class="p">.</span><span class="n">Join</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutexThread</span><span class="p">.</span><span class="n">Join</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="k">void</span> <span class="n">OwningThread</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;    [{GetCurrentThreadId(), 8}] Start to hold resources&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;___________________________________________&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">WaitOne</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Thread</span><span class="p">.</span><span class="n">Sleep</span><span class="p">(</span><span class="m">3000</span><span class="p">);</span>  <span class="c1">// the wait should last ~3 seconds</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;    Release resources&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">ReleaseMutex</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">static</span> <span class="k">void</span> <span class="n">MutexThread</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;    [{GetCurrentThreadId(), 8}] waiting for Mutex...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">WaitOne</span><span class="p">();</span>  <span class="c1">// events are emitted in the implementation when a contention happens</span>
</span></span><span class="line"><span class="cl">    <span class="n">mutex</span><span class="p">.</span><span class="n">ReleaseMutex</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;    &lt;-- Mutex&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>generates a Start and Stop events pair:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="m">125980</span> <span class="p">|</span> <span class="m">00000000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">000000000000</span> <span class="p">&gt;</span> <span class="k">event</span> <span class="m">301</span> <span class="n">__</span> <span class="p">[</span> <span class="m">1</span><span class="p">|</span> <span class="n">Start</span><span class="p">]</span> <span class="n">WaitHandleWait</span><span class="p">/</span><span class="n">Start</span>
</span></span><span class="line"><span class="cl"><span class="m">125980</span> <span class="p">|</span> <span class="m">00000000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">0000</span><span class="p">-</span><span class="m">000000000000</span> <span class="p">&gt;</span> <span class="k">event</span> <span class="m">302</span> <span class="n">__</span> <span class="p">[</span> <span class="m">2</span><span class="p">|</span>  <span class="n">Stop</span><span class="p">]</span> <span class="n">WaitHandleWait</span><span class="p">/</span><span class="n">Stop</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>There is no associated activity ID so you rely on the fact that the same waiter thread (125980 in the previous example) is emitting for both events.</p>
<h2 id="listening-to-the-new-waitevents">Listening to the new Wait events</h2>
<p><a href="/posts/2024-11-13_implementing-dotnet-http-to/">As usual</a>, you should rely on the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/">TraceEvent nuget</a> to start an EventPipe session with an already running .NET application. The last version already contains the definition of the keyword:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">keywords</span> <span class="p">|=</span> <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">WaitHandle</span><span class="p">;</span> <span class="c1">// .NET 9 WaitHandle kind of contention</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>and the C# events for Start and Stop:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Clr</span><span class="p">.</span><span class="n">WaitHandleWaitStart</span> <span class="p">+=</span> <span class="n">OnWaitHandleWaitStart</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Clr</span><span class="p">.</span><span class="n">WaitHandleWaitStop</span> <span class="p">+=</span> <span class="n">OnWaitHandleWaitStop</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The handler’s implementation is straightforward. The start of the wait is recorded for the current thread:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnWaitHandleWaitStart</span><span class="p">(</span><span class="n">WaitHandleWaitStartTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// get the contention info for the current thread</span>
</span></span><span class="line"><span class="cl">    <span class="n">ContentionInfo</span> <span class="n">info</span> <span class="p">=</span> <span class="n">_contentionStore</span><span class="p">.</span><span class="n">GetContentionInfo</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ThreadID</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// keep track of the wait start</span>
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">TimeStampRelativeMSec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>When the wait ends, the duration is computed based on the recorded wait start because it is not provided in the payload <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ClrEtwAll.man#L1788">like for ContentionStop</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnWaitHandleWaitStop</span><span class="p">(</span><span class="n">WaitHandleWaitStopTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">ContentionInfo</span> <span class="n">info</span> <span class="p">=</span> <span class="n">_contentionStore</span><span class="p">.</span><span class="n">GetContentionInfo</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ThreadID</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// unlucky case when we start to listen just after the WaitHandleStart event</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Too bad the duration is not provided in the payload like in ContentionStop...</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">contentionDurationMSec</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">TimeStampRelativeMSec</span> <span class="p">-</span> <span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">ContentionStartRelativeMSec</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">duration</span> <span class="p">=</span> <span class="n">TimeSpan</span><span class="p">.</span><span class="n">FromMilliseconds</span><span class="p">(</span><span class="n">contentionDurationMSec</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{e.ThreadId,7} | {e.Duration.TotalMilliseconds} ms&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is nice but it would be more useful if we could get the call stack of long waits.</p>
<h2 id="call-stacks-with-eventpipe">Call stacks with EventPipe</h2>
<p>In a <a href="https://techblog.criteo.com/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-1-f67b440a8cc">previous post</a>, I explained that it is possible to get the call stack when an event is emitted thanks to the <a href="https://learn.microsoft.com/en-us/dotnet/framework/performance/stack-etw-event?WT.mc_id=DT-MVP-5003325"><strong>ClrStackWalk</strong> event</a> that follows the event you are interested in. Unfortunately, this is not more the case for .NET 5+ that is using EventPipe instead of ETW.</p>
<p>As <a href="https://x.com/ocoanet">Olivier Coanet</a> presents in his <a href="https://medium.com/@ocoanet/tracing-allocations-with-eventpipe-part-2-reading-call-stacks-without-tracelog-4b0bfe4592aa">post</a>, you can get the call stack as an array of addresses from the hidden event record that is mapped by the <strong>TraceEvent</strong> parameter passed to each event handlers. This <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_record?WT.mc_id=DT-MVP-5003325"><strong>EVENT_RECORD</strong></a> structure contains a <strong>ExtendedData</strong> field that is an array of <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_header_extended_data_item?WT.mc_id=DT-MVP-5003325"><strong>EVENT_HEADER_EXTENDED_DATA_ITEM</strong></a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">EVENT_HEADER_EXTENDED_DATA_ITEM</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">Reserved1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">ExtType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">Reserved2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ushort</span> <span class="n">DataSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">DataPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If the <strong>ExtType</strong> value is <strong>EVENT_HEADER_EXT_TYPE_STACK_TRACE64</strong> (=6) then <strong>DataPtr</strong> points to a <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_extended_item_stack_trace64?%3FWT.mc_id=DT-MVP-5003325"><strong>EVENT_EXTENDED_ITEM_STACK_TRACE64</strong></a> structure:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">EVENT_EXTENDED_ITEM_STACK_TRACE64</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">MatchId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">unsafe</span> <span class="k">fixed</span> <span class="kt">ulong</span> <span class="n">Address</span><span class="p">[</span><span class="m">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>that contains an array of 64-bit addresses. The size of this array is given by <strong>DataSize — sizeof(ulong)</strong>.</p>
<p>For 32-bit applications, you will get <strong>EVENT_HEADER_EXT_TYPE_STACK_TRACE32</strong> (=5) as <strong>ExtType</strong> value and DataPtr will point to <a href="https://learn.microsoft.com/en-us/windows/win32/api/evntcons/ns-evntcons-event_extended_item_stack_trace32?WT.mc_id=DT-MVP-5003325"><strong>EVENT_EXTENDED_ITEM_STACK_TRACE32</strong></a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">struct</span> <span class="nc">EVENT_EXTENDED_ITEM_STACK_TRACE32</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">MatchId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">unsafe</span> <span class="k">fixed</span> <span class="kt">uint</span> <span class="n">Address</span><span class="p">[</span><span class="m">1</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>that stores an array of 32-bit addresses.</p>
<p>Knowing that makes writing the code to get the call stacks as an array of 64-bit addresses (same with 32-bit applications for simplicity sake) pretty straightforward:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span><span class="lnt">46
</span><span class="lnt">47
</span><span class="lnt">48
</span><span class="lnt">49
</span><span class="lnt">50
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="n">EventPipeUnresolvedStack</span> <span class="n">ReadStackUsingUnsafeAccessor</span><span class="p">(</span><span class="n">TraceEvent</span> <span class="n">traceEvent</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">GetFromEventRecord</span><span class="p">(</span><span class="n">traceEvent</span><span class="p">.</span><span class="n">eventRecord</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="n">EventPipeUnresolvedStack</span> <span class="n">GetFromEventRecord</span><span class="p">(</span><span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_RECORD</span><span class="p">*</span> <span class="n">eventRecord</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">eventRecord</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">extendedDataCount</span> <span class="p">=</span> <span class="n">eventRecord</span><span class="p">-&gt;</span><span class="n">ExtendedDataCount</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">dataIndex</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">dataIndex</span> <span class="p">&lt;</span> <span class="n">extendedDataCount</span><span class="p">;</span> <span class="n">dataIndex</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">extendedData</span> <span class="p">=</span> <span class="n">eventRecord</span><span class="p">-&gt;</span><span class="n">ExtendedData</span><span class="p">[</span><span class="n">dataIndex</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">ExtType</span> <span class="p">==</span> <span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_HEADER_EXT_TYPE_STACK_TRACE64</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">stackRecord</span> <span class="p">=</span> <span class="p">(</span><span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_EXTENDED_ITEM_STACK_TRACE64</span><span class="p">*)</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addresses</span> <span class="p">=</span> <span class="p">&amp;</span><span class="n">stackRecord</span><span class="p">-&gt;</span><span class="n">Address</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addressCount</span> <span class="p">=</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataSize</span> <span class="p">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt64</span><span class="p">))</span> <span class="p">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt64</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">addressCount</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">callStackAddresses</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">ulong</span><span class="p">[</span><span class="n">addressCount</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">addressCount</span><span class="p">;</span> <span class="n">index</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callStackAddresses</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="p">=</span> <span class="n">addresses</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">new</span> <span class="n">EventPipeUnresolvedStack</span><span class="p">(</span><span class="n">callStackAddresses</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">ExtType</span> <span class="p">==</span> <span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_HEADER_EXT_TYPE_STACK_TRACE32</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">stackRecord</span> <span class="p">=</span> <span class="p">(</span><span class="n">TraceEventNativeMethods</span><span class="p">.</span><span class="n">EVENT_EXTENDED_ITEM_STACK_TRACE32</span><span class="p">*)</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataPtr</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addresses</span> <span class="p">=</span> <span class="p">&amp;</span><span class="n">stackRecord</span><span class="p">-&gt;</span><span class="n">Address</span><span class="p">[</span><span class="m">0</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">addressCount</span> <span class="p">=</span> <span class="p">(</span><span class="n">extendedData</span><span class="p">.</span><span class="n">DataSize</span> <span class="p">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt32</span><span class="p">))</span> <span class="p">/</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">UInt32</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">addressCount</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">callStackAddresses</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">ulong</span><span class="p">[</span><span class="n">addressCount</span><span class="p">];</span>  <span class="c1">// store the 32 addresses as 64 bit addresses</span>
</span></span><span class="line"><span class="cl">            <span class="k">for</span> <span class="p">(</span><span class="kt">var</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">index</span> <span class="p">&lt;</span> <span class="n">addressCount</span><span class="p">;</span> <span class="n">index</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">callStackAddresses</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="p">=</span> <span class="n">addresses</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="k">new</span> <span class="n">EventPipeUnresolvedStack</span><span class="p">(</span><span class="n">callStackAddresses</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the last version of TraceEvent nuget provides a public access to the <strong>eventRecord</strong> field so it is no more needed to use the <strong>UnsafeAccessor</strong> attribute used by Olivier.</p>
<h2 id="symbolize-the-call-stack-addresses">Symbolize the call stack addresses</h2>
<p>Address is good but the corresponding method name is better. I won’t repeat what I’ve already detailed in <a href="https://techblog.criteo.com/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-2-ec9657eb17f9?source=friends_link&amp;sk=b34465f583867cb7dcf5bad6395bf151">an older post</a> that shows how to get the name of a native and managed name from an instruction pointer address. Instead, I want to pinpoint a big limitation of this solution to listen to CLR provider <strong>MethodLoadVerbose</strong>/<strong>MethodDCStartVerboseV2</strong> events. If the methods you are interested in are jitted BEFORE your tool attaches to the application, you will never get these events.</p>
<p>You could get the same mapping address span/method name via the other “<em>Microsoft-Windows-DotNETRuntimeRundown</em>” provider and its <strong>MethodDCEndVerbose</strong> event that contains the expected <strong>MethodStartAddress</strong>, <strong>MethodSize</strong> and <strong>MethodName</strong> in its <a href="https://github.com/dotnet/runtime/blob/d897415e02340a13dc1c5078c09937bdf7ec8a56/src/coreclr/vm/ClrEtwAll.man#L4864">payload</a>. But I need this information before the end of the application…</p>
<p>Looking at <a href="https://github.com/dotnet/docs/blob/main/docs/framework/performance/clr-etw-keywords-and-levels.md#keyword-combinations-for-symbol-resolution-for-the-rundown-provider">the documentation</a>, it seems that the rundown provider accepts the <strong>StartRundownKeyword</strong> value to emit the DCStart events when the provider is enabled! <a href="https://github.com/dotnet/runtime/issues/42378">Since .NET 9</a>, it is possible to pass the keywords you want (before, the default value did not contain <strong>StartRundownKeyword</strong>) when creating the EventPipe session</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">//                V-- this is the default rundown keyword</span>
</span></span><span class="line"><span class="cl"><span class="n">rundownKeywords</span> <span class="p">=</span> <span class="m">0x80020139</span> <span class="p">|</span> <span class="p">(</span><span class="kt">long</span><span class="p">)</span><span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">StartEnumeration</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">config</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventPipeSessionConfiguration</span><span class="p">(</span><span class="n">GetProviders</span><span class="p">(),</span> <span class="m">256</span><span class="p">,</span> <span class="n">rundownKeywords</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">using</span> <span class="p">(</span><span class="kt">var</span> <span class="n">session</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">StartEventPipeSession</span><span class="p">(</span><span class="n">config</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">source</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EventPipeEventSource</span><span class="p">(</span><span class="n">session</span><span class="p">.</span><span class="n">EventStream</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">RegisterListeners</span><span class="p">(</span><span class="n">source</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this is a blocking call</span>
</span></span><span class="line"><span class="cl">    <span class="n">source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that you should not add the rundown provider to the list passed as parameter.</p>
<p>Unfortunately, there is <a href="https://github.com/dotnet/runtime/issues/42378">currently an issue in the runtime since September 2020</a> that pinpoints this exact problem. I even tried to create and close a session to get the DCStop events before recreating a new one, but I failed.</p>
<p>The next episode will talk about how it is possible to start a .NET application and get the events since its startup… with the problems that are happening.</p>
]]></content:encoded></item><item><title>Crap: the application is randomly crashing!</title><link>https://chrisnas.github.io/posts/2023-10-02_crap-the-application-is/</link><pubDate>Mon, 02 Oct 2023 09:02:26 +0000</pubDate><guid>https://chrisnas.github.io/posts/2023-10-02_crap-the-application-is/</guid><description>This post is listing which steps were followed to investigate a customer random crash issue I faced last week.</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>When you have a call with a customer who explains to you that his application is crashing when your profiler is enabled, it is never a great experience. This post is listing which steps were followed to investigate such an issue I faced last week; from the basics up to the final in analysing memory dumps in WinDbg.</p>
<h2 id="get-as-many-setup-details-aspossible">Get as many setup details as possible</h2>
<p>The situation was the following:</p>
<ul>
<li>A web application was running fine with our Datadog .NET profiler on some non-production servers with less traffic.</li>
<li>The same application was crashing on production servers with more traffic.</li>
</ul>
<p>We were lucky to be able to remote access both machines. A lot of time was spent to check the setup that is based on environment variables. Basically, for our profiler to be loaded by a .NET application, a few <a href="https://learn.microsoft.com/en-us/dotnet/core/runtime-config/debugging-profiling?WT.mc_id=DT-MVP-5003325">Microsoft related environment variables</a> need to be set. Then, you enable the Datadog profiler by setting <strong>DD_PROFILING_ENABLED</strong> to 1 in order to get the profiling details available in our UI. Since the web application is running in IIS, things get more complicated because some environments variables <a href="https://docs.datadoghq.com/profiler/enabling/dotnet/?tab=internetinformationservicesiis">must be set in… the Registry</a>.</p>
<p>So, we checked the environment variables set at the machine level with the <strong>set</strong> command in a prompt and those for IIS with the Registry Editor. However, we got some inconsistencies, and we needed a way to validate what were the environment variables really seen by the web application! The <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer?WT.mc_id=DT-MVP-5003325">Process Explorer</a> tool from Sysinternals was downloaded and launched. After finding the process ID of the running w3wp.exe corresponding to the web application, a simple right-click to get the Properties and selecting the <strong>Environment</strong> Tab gave us the truth:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_NvcCrY_Y9yUePtZbq1UmcA.png"></p>
<p><em>(This screenshot shows the results for one of our test applications on my development machine).</em></p>
<h2 id="getting-a-memorydump">Getting a memory dump</h2>
<p>Once the setup was checked on both machines without any too weird issues, the next step was to figure out why the application was randomly crashing. Even if the machines received different traffic loads, since applications running without our profiler enabled were not crashing, the chances were high that our C++ code was at the source of the problem. But the crashes were random… And you can’t install Visual Studio on a production server and attach to the process hoping that it will crash and start a debugging session there!</p>
<p>Windows Error Reporting is generating mini dumps when applications are crashing but they are usually not enough to start an investigation. Again, the other Sysinternals tools <a href="https://learn.microsoft.com/en-us/sysinternals/downloads/procdump?WT.mc_id=DT-MVP-5003325">procdump</a> was installed as a global crash handler with <strong>procdump -i c:\dumps -ma</strong>. The next time the application crashed, a memory dump was be generated in the c:\dumps folder. Don’t forget to create it manually if it does not exist.</p>
<h2 id="from-addresses-to-sourcecode">From addresses to source code</h2>
<p>To play with a memory dump, <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/">WinDbg</a> is my preferred toy. I opened the memory dump and, in the case of a crash, the stack panel automatically displayed the call stack of the faulted thread:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_ef6Cz0WBGx_CYoVbBD2vrw.png"></p>
<p>The last frame triggering the issue (i.e., before KiUserExceptionDispatch) is <strong>Datadog_Profiler_Native!DllCanUnloadNow+0x2954b</strong>. Knowing that WinDbg transforms . in file names into _ leads to Datadog.Profiler.Native.dll which is the file where our profiler is implemented. However, WinDbg was not able to find the name of the function and only looked at the exported public symbols. With the <strong>lm</strong> command, you can see how WinDbg gets the symbols for this dll:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_Zy1aXBP-s3_xo-GEKDNPQA.png"></p>
<p>With <strong>DllCanUnloadNow</strong>, you could tell that we are dealing with some COM stuff but it did not really help me for the investigation: I needed to know which function was running which part of its code. Hopefully, for <a href="https://github.com/DataDog/dd-trace-dotnet/releases/tag/v2.36.0">each release of the .NET profiler</a> in Github, in addition to the .msi installer, the symbols and the source code are also provided.</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_BKpIKTzOcaCKOx7J0riLVA.png"></p>
<p>Both files were unzipped in the folder where the dumps were copied. Then, I changed the Debugging Settings in WinDbg to point to these folders:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_h65jBCPNiOwXfkIwC2cZtQ.png"></p>
<p>Let’s start with the symbols to let WinDbg match an instruction pointer to a function name. I asked WinDbg to provide details about the symbol resolution with <strong>!sym noisy</strong>. Then I forced the symbols for my module to gets reloaded with <strong>.reload /f “Datadog.Profiler.Native.dll”</strong>. In the flow of errors, I find out where the .pdb file should be stored so that WinDbg would find it:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_RGatvVvuLeGvBPXlCWLytQ.png"></p>
<p>So the problem is triggered somewhere in our <strong>Windows64BitStackFramesCollector::CollectStackSampleImplementation</strong> function. By simply double-clicking this frame, WinDbg automagically found the corresponding source file and pinpointed the culprit line:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_5a47NcKwyXhk9YBAVgBMAw.png"></p>
<h2 id="a-bit-of-windbgmagic">A bit of WinDbg magic</h2>
<p>To follow me a bit further, you need to understand what this code is doing: it is walking the stack of a thread to find the instruction pointers of each called function. This line 260 is dereferencing the address contained in <strong>context.Rsp</strong>. I looked at Locals panel to get its value:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_PQkFGP5_kGKgDRulRMHRtA.png"></p>
<p>The <strong>!address</strong> command gave me in which module this code was executed from:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_6xKNeOuogOmD_1PGKvUgnA.png"></p>
<p>It looked like a valid page with executable code…</p>
<p>I wanted to see why our stack walking code would break here. What if I asked WinDbg to show me this stack? To do that, I first needed to know which thread our code was trying to stack walk. I knew that <strong>Windows64BitStackFramesCollector</strong> was keeping track of the currently walked thread in a <strong>ManagedThreadInfo</strong> instance pointed to by its <strong>_pCurrentCollectionThreadInfo</strong> field:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_QIZFZokpkgINnnOJgtseFA.png"></p>
<p>This instance stores the thread ID in its <strong>_osThreadId</strong> field: now let’s ask WinDbg to switch to this thread.</p>
<p>The <strong>~</strong> command lists all threads:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_m67ck2tmPvIT1bB9Y6JrPQ.png"></p>
<p>A quick CTRL+F with “27600” stopped on the thread #72. Threads have a lot of identifiers in WinDbg and the first one allowed me to switch with <strong>~72s</strong>.</p>
<p>The Stack panel was almost empty:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_JhLeTbMaR-Hm3U0QzVmCyw.png"></p>
<p>To be sure, I used the <strong>kp</strong> command… that told me that WinDbg was not really happy neither:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_B1_-hEKGWWk9leh4p6IfhA.png"></p>
<p>I was kind of stuck but my colleague <a href="https://twitter.com/kookiz">Kevin Gosse</a> mentioned that I could use <strong>r rip</strong> to see what would be the next instruction to be executed by this thread:</p>
<p><img loading="lazy" src="/posts/2023-10-02_crap-the-application-is/1_ayVTO6s3e0jSFwVeGbl3zQ.png"></p>
<p>Then, the <strong>ln</strong> command (close to the <strong>!address</strong> command I used just before) allowed me to click the <strong>Browse Module</strong> link and see that, again, some code from Sentinel One was ready to execute.</p>
<p>This agent is part of an anti-virus (and more) solution that seems to highjack the stack of threads and our code was not dealing properly with this kind of situation. The fix was to protect our dereferencing code against access violation and stop walking the stack in that case.</p>
<p>Another debugging day at Datadog :^)</p>
]]></content:encoded></item><item><title>Troubleshooting CPU and exceptions issues with Datadog toolbox</title><link>https://chrisnas.github.io/posts/2022-06-09_troubleshooting-cpu-and-except/</link><pubDate>Thu, 09 Jun 2022 16:09:27 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-06-09_troubleshooting-cpu-and-except/</guid><description>Learn how to use Datadog CPU and exceptions profiling to troubleshoot well known Tess Ferrandez BuggyBits</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>With the new 2.10 release of the Datadog .NET Tracer and Continuous Profiler available, it is time to update some investigation workflows <a href="/posts/2022-01-28_troubleshooting-net-performanc/">I already introduced</a>. New features have been added to help you diagnose performance issues in your .NET applications:</p>
<ul>
<li>Linux support!</li>
<li>Code Hotspots: allow you to automatically navigate from lengthy spans and requests to profiles</li>
<li>CPU profiling: pinpoint high CPU consuming methods</li>
<li>Exceptions profiling: identify exceptions distributions</li>
<li>Profile sequence: easily profile an application startup</li>
</ul>
<p>The goal of this post is to show you how all these features make your investigations easier. I would recommend reading <a href="/posts/2022-01-28_troubleshooting-net-performanc/">the previous post</a>; especially for the environment setup that I won’t repeat here.</p>
<h2 id="its-linux-showtime">It’s Linux showtime!</h2>
<p>The .NET Continuous Profiler is now available for Linux. The only limitation is the presence of glibc 2.18+ in the distribution; for example, CentOS 7 is not supported. Beyond that, we provide features parity between Linux and Windows.</p>
<p>In terms of installation, download the <a href="https://github.com/DataDog/dd-trace-dotnet/releases">.NET Tracer package</a> that supports your operating system and architecture. Go to <a href="https://docs.datadoghq.com/tracing/profiler/enabling/dotnet?tab=linux">the documentation</a> for the additional configuration steps.</p>
<h2 id="from-spans-toprofiles">From spans to profiles</h2>
<p>When analysing lengthy requests, you usually start from looking at the corresponding spans in the APM Traces part of the UI. It is now possible to view the corresponding profiles by clicking the “View Profile” button in the “Code Hotspots” tab:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_uNg9te1UxHzFUzCCwJN3PQ.png"></p>
<p>Before digging into the profiling information, you are already able to see that more than half of the time is spent in <strong>Buffer._Memmove</strong> that is called by Buggybits <strong>ProductsController.Index</strong> method:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_FjzJDKmiR40r7UYzjtV8HQ.png"></p>
<p>From the profile view, it is also possible to come back to the traces:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_6M_WOoI8WOUxUukEti7TxQ.png"></p>
<p>Let’s see now what new features are available at the profiling side.</p>
<h2 id="cpu-profiling">CPU profiling</h2>
<p>The most demanded feature was the ability to analyse CPU consumption (a.k.a. CPU profiling). The idea is to be able to identify code that really consumes CPU usage and optimize it. This is particularly important in the context of cloud-based computing where what you pay is related to the consumed CPU.</p>
<p>In term of implementation, unlike Wall Time profiling, we look at the time spent by a thread on a CPU core and not the elapsed time since the last time we checked (every ~10ms). We also collect the call stack of a thread only if it is currently running on a core. Why? Because we want to only record call stacks corresponding to code paths that are consuming CPU. For example, ThreadPool threads are usually waiting (not interesting call stack) for a work item to process (interesting call stack).</p>
<p>In the <a href="/posts/2022-01-28_troubleshooting-net-performanc/">last blog post</a>, the <a href="https://github.com/DataDog/dd-trace-dotnet/blob/master/profiler/src/Demos/Samples.BuggyBits/Controllers/ProductsController.cs#L124">code responsible for lengthy requests</a> is doing too many string concatenations (look for += in the following code):</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Index</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">sw</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Stopwatch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="n">dataLayer</span><span class="p">.</span><span class="n">GetAllProducts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">productsTable</span> <span class="p">=</span> <span class="s">&#34;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Product Name&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">product</span> <span class="k">in</span> <span class="n">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">productsTable</span> <span class="p">+=</span> <span class="s">$&#34;&lt;tr&gt;&lt;td&gt;{product.ProductName}&lt;/td&gt;&lt;td&gt;{product.Description}&lt;/td&gt;&lt;td&gt;{product.Price}&lt;/td&gt;&lt;/tr&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span> <span class="p">+=</span> <span class="s">&#34;&lt;/table&gt;&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ElapsedTimeInMs&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ProductsTable&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">View</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The Wall Time view was very explicit about <strong>ProductsController.Index()</strong> calling <strong>String.Concat()</strong> culprit:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_ajUXPb0-ANMBllBPfU91oQ.png"></p>
<p>A simple solution is to <a href="https://github.com/DataDog/dd-trace-dotnet/blob/master/profiler/src/Demos/Samples.BuggyBits/Controllers/ProductsController.cs#L103">use a StringBuilder to optimize the concatenations</a>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">IActionResult</span> <span class="n">Builder</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">sw</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Stopwatch</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">products</span> <span class="p">=</span> <span class="n">dataLayer</span><span class="p">.</span><span class="n">GetAllProducts</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">productsTable</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">(</span><span class="m">1000</span> <span class="p">*</span> <span class="m">80</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">&#34;&lt;table&gt;&lt;tr&gt;&lt;th&gt;Product Name&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;th&gt;Price&lt;/th&gt;&lt;/tr&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">product</span> <span class="k">in</span> <span class="n">products</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">productsTable</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">$&#34;&lt;tr&gt;&lt;td&gt;{product.ProductName}&lt;/td&gt;&lt;td&gt;{product.Description}&lt;/td&gt;&lt;td&gt;{product.Price}&lt;/td&gt;&lt;/tr&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">productsTable</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">&#34;&lt;/table&gt;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">sw</span><span class="p">.</span><span class="n">Stop</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ElapsedTimeInMs&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">sw</span><span class="p">.</span><span class="n">ElapsedMilliseconds</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">ViewData</span><span class="p">[</span><span class="s">&#34;ProductsTable&#34;</span><span class="p">]</span> <span class="p">=</span> <span class="n">productsTable</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">View</span><span class="p">(</span><span class="s">&#34;Index&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The Wall Time view of the profile with the fix does not provide anything useful…</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_Mqpq2MVAkCUpA8Z62wRtYA.png"></p>
<p>If you want to go deeper, you need to look at the CPU consumption:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_Ox4XRvFCIQozhGAeC-6kTQ.png"></p>
<p>The <strong>ProductController.Builder</strong> method is handling the request (like <strong>Index()</strong> in the String.Concat case) and calls <strong>DataLayer.GetAllProducts()</strong> where most of the CPU-related work is done.</p>
<p>Would it be interesting to continue optimizing the code? Notice that <strong>GetAllProducts()</strong> is “only” consuming 94ms and the other <strong>Number.</strong>* and <strong>String.Concat</strong> method around 350ms. So a gain might be neglectable compared to the total ~3 seconds CPU usage</p>
<p>Remember that you should not optimize for the sake of “optimizing”: you should have metrics that tell you when to start (too lengthy request processing) and when to stop.</p>
<h2 id="exceptions-profiling">Exceptions profiling</h2>
<p>In the .NET world, exceptions are at the center of errors handling. It is now possible to get a sampled view of the exceptions that happened during an application lifetime; by type:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_uyUmkTTLlQjg1IhCBVoCcg.png"></p>
<p>and by message:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_GRyr-g-jo8KXySpTRMTLpQ.png"></p>
<p>At the implementation level, the new exceptions profiler is notified by the CLR when an exception is thrown. Since in special cases (such as network issue or invalid parsed data for example), an application could trigger thousands of exceptions in a very short period, it is needed to sample them. Otherwise, the impact on performances would be severe; especially if the call stack needs to be rebuilt for each exception.</p>
<p>First, at least one exception per type is kept, ensuring that weird specific exceptions are not lost in the flow. Second, exceptions are sampled over time based on a fixed number of exceptions per profile and the rate of appearance. For knowing the exact number of exceptions, feel free to leverage the Runtime Metrics package as explained in the previous blog post.</p>
<h2 id="profiling-the-application-bootstrap">Profiling the application bootstrap</h2>
<p>In some situations, you are interested in analysing an application bootstrap. In Datadog APM Profile Search UI, it means finding the first profile of the given service execution. However, even with the date and time column, it is not obvious to find the right one:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_KapOJXMlqOX2nflnyNB1ZA.png"></p>
<p>To help you find the initial profile of a service execution, a new “profile_seq” tag has been added to the HTTP request used to upload the profiles. It contains the count of generated profiles for a given execution of a service, starting from 0.</p>
<p>So now, in the Options of the profile list, add a “profile_seq” column:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_BwnvwM5y6e0Xd20JXVL8XQ.png"></p>
<p>The first profile is then easily spottable with a 0 value:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1__8Kf93AIiGsBCjGZl_SoyA.png"></p>
<p>In the future, a more visual hint might be added to identify it without the need to add the column.</p>
<h2 id="major-implementation-refactoring">Major implementation refactoring</h2>
<p>Finally, our implementation has benefited from a large code refactoring. As the previous post explained, the generation of .pprof files and their upload was done in C#. This has been replaced by using a rust library shared amongst different profiler libraries (native, Ruby, .NET).</p>
<p>It means that you should not anymore see these frames in the application call stacks:</p>
<p><img loading="lazy" src="/posts/2022-06-09_troubleshooting-cpu-and-except/1_FHcvrh7PIKIrMimv1FrW6g.png"></p>
<p>It does not mean that one third of the processing has been removed! Just that no more C# code is running with performance gain. First, the managed implementation was allocating objects managed by the garbage collector; adding pressure that might trigger more collections. Second, with the native rust implementation, there is no need to duplicate data between the collecting native part of the continuous profiler and the managed code used to serialize it.</p>
<p>In addition, several optimizations have been done in the symbol’s resolution (i.e., type and method names) part of the code that also reduce memory consumption and CPU usage.</p>
<p>Happy profiling!</p>
<h2 id="references">References</h2>
<ul>
<li>Datadog Tracer &amp; Continuous Profiler <a href="https://github.com/DataDog/dd-trace-dotnet/releases/tag/v2.10.0">.msi Installer and Linux tar.gz</a></li>
<li><a href="https://docs.datadoghq.com/tracing/profiler/enabling/dotnet">Datadog Continuous Profiler documentation</a></li>
<li><a href="https://docs.datadoghq.com/tracing/setup_overview/setup/dotnet-framework/?tab=windows">Datadog Tracer documentation</a></li>
<li><a href="https://docs.datadoghq.com/tracing/runtime_metrics/dotnet/">Datadog Runtime metrics documentation</a></li>
<li><a href="https://twitter.com/TessFerrandez">Tess Ferrandez</a> repository for <a href="https://www.tessferrandez.com/blog/2008/02/04/debugging-demos-setup-instructions.html">BuggyBits labs</a></li>
</ul>
]]></content:encoded></item><item><title>Build your own .NET CPU profiler in C#</title><link>https://chrisnas.github.io/posts/2020-12-08_build-your-own-net/</link><pubDate>Tue, 08 Dec 2020 10:27:37 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-12-08_build-your-own-net/</guid><description>After describing memory allocation profiling it is now time to dig into the CPU sample profiling in C#!</description><content:encoded><![CDATA[<hr>
<p>The last series was describing how to get details about your .NET application allocation patterns in C#.</p>
<ul>
<li><a href="/posts/2020-04-18_build-your-own-net/">Get a sampling of .NET application allocations</a></li>
<li><a href="/posts/2020-05-18_build-your-own-net/">A simple way to get the call stack</a></li>
<li><a href="/posts/2020-06-19_build-your-own-net/">Getting the call stack by hand</a></li>
</ul>
<p>It is now time to do the same but for the CPU consumption of your .NET applications.</p>
<h2 id="thanks-you-mr-windowskernel">Thanks you Mr Windows Kernel!</h2>
<p>Under Windows, the kernel ETW provider allows you to get notified every milli-second with the call stack of all threads running on a core. Without any surprise, it is easy with TraceEvent to listen to these events. As explained in an <a href="/posts/2018-07-26_grab-etw-session-providers/">old posts</a>, you simply need to create a session, enable providers and listen to the right event.</p>
<p>For sampled CPU profiling, I’m using the <code>TraceLogEventSource</code> to wrap the event source and automatically get the stack frames symbol resolution:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">string</span> <span class="n">sessionName</span> <span class="p">=</span> <span class="s">&#34;Cpu_Profiling_Session+&#34;</span> <span class="p">+</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">_session</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TraceEventSession</span><span class="p">(</span><span class="n">sessionName</span><span class="p">,</span> <span class="n">TraceEventSessionOptions</span><span class="p">.</span><span class="n">Create</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="k">if</span> <span class="p">(!</span><span class="n">EnableProviders</span><span class="p">(</span><span class="n">_session</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_session</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">_session</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">_profilingTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="n">Factory</span><span class="p">.</span><span class="n">StartNew</span><span class="p">(()</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">using</span> <span class="p">(</span><span class="n">TraceLogEventSource</span> <span class="n">source</span> <span class="p">=</span> <span class="n">TraceLog</span><span class="p">.</span><span class="n">CreateFromTraceEventSession</span><span class="p">(</span><span class="n">_session</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// CPU sampling kernel events</span>
</span></span><span class="line"><span class="cl">        <span class="n">source</span><span class="p">.</span><span class="n">Kernel</span><span class="p">.</span><span class="n">PerfInfoSample</span> <span class="p">+=</span> <span class="p">(</span><span class="n">SampledProfileTraceData</span> <span class="n">data</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="p">...</span>
</span></span><span class="line"><span class="cl">        <span class="p">};</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// this call exits when the session is stopped</span>
</span></span><span class="line"><span class="cl">        <span class="n">source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You need to enable three providers:</p>
<ul>
<li>Kernel: get the profiling event every milli-second and be notified when a dll gets loaded by a process to let TraceEvent manage the symbols</li>
<li>Clr: get JIT events describing managed method details</li>
<li>ClrRundown: get already JITted methods details</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kt">bool</span> <span class="n">EnableProviders</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">session</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">BufferSizeMB</span> <span class="p">=</span> <span class="m">256</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// Note: it could fail if the user does not have the required privileges</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">success</span> <span class="p">=</span> <span class="n">session</span><span class="p">.</span><span class="n">EnableKernelProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Process</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Profile</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">stackCapture</span><span class="p">:</span> <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Profile</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(!</span><span class="n">success</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this call always returns false  :^(</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">TraceEventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// events related to JITed methods</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Jit</span> <span class="p">|</span>                       <span class="c1">// Turning on JIT events is necessary to resolve JIT compiled code </span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">JittedMethodILToNativeMap</span> <span class="p">|</span> <span class="c1">// This is needed if you want line number information in the stacks</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Loader</span>                      <span class="c1">// You must include loader events as well to resolve JIT compiled code.</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this provider will send events of already JITed methods</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrRundownTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">TraceEventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Jit</span> <span class="p">|</span>              <span class="c1">// We need JIT events to be rundown to resolve method names</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">JittedMethodILToNativeMap</span> <span class="p">|</span> <span class="c1">// This is needed if you want line number information in the stacks</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Loader</span> <span class="p">|</span>           <span class="c1">// As well as the module load events.  </span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">StartEnumeration</span>   <span class="c1">// This indicates to do the rundown now (at enable time)</span>
</span></span><span class="line"><span class="cl">        <span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to handle the event is really simple:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">source</span><span class="p">.</span><span class="n">Kernel</span><span class="p">.</span><span class="n">PerfInfoSample</span> <span class="p">+=</span> <span class="p">(</span><span class="n">SampledProfileTraceData</span> <span class="n">data</span><span class="p">)</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span> <span class="p">!=</span> <span class="n">Pid</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">CallStack</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callstack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">MergeCallStack</span><span class="p">(</span><span class="n">callstack</span><span class="p">,</span> <span class="n">Reader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I’m only interested in profiling a given process (hence the check on process id) and events with a call stack. The callstack is returned by the extension method <code>CallStack()</code> (see the <a href="/posts/2020-05-18_build-your-own-net/">previous post</a> for more details). The main processing is done by the <code>MergeCallStack()</code> method. But before looking at the only complicated part, it is time to discuss a useful tip.</p>
<h2 id="tip-use-etlxluke">Tip: use ETLx Luke!</h2>
<p>Like the previous posts about memory profiling, my goal is to demonstrate how to monitor applications as they run. However when you monitor an application CPU consumption, you would like to avoid any noisy neighbor that could highjack some cores. So minimizing the work of your profiling code is always a good idea. In addition, it could also be valuable to record the events and analyze them later. Microsoft <a href="https://github.com/microsoft/perfview/releases">Perfview</a> is the open source tool that I’m using the most to dig into CPU consumption. So the solution is to simply record the events and generate an .etlx file for Perfview.</p>
<p>The first code change is small: the session is created with a filename.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">string</span> <span class="n">sessionName</span> <span class="p">=</span> <span class="s">&#34;Cpu_Profiling_Session+&#34;</span> <span class="p">+</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">().</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="n">_session</span> <span class="p">=</span> <span class="k">new</span> <span class="n">TraceEventSession</span><span class="p">(</span><span class="n">sessionName</span><span class="p">,</span> <span class="n">_filename</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I’m using a naming convention that contains the process ID I want to monitor so it will be easy to remember when I will analyze the recording in Perfview:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">profiler</span> <span class="p">=</span> <span class="k">new</span> <span class="n">EtlCpuSampleProfiler</span><span class="p">(</span><span class="s">$&#34;trace-{parameters.pid}.etl&#34;</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The second step to generate the .etlx file is a one liner:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">traceLog</span> <span class="p">=</span> <span class="n">TraceLog</span><span class="p">.</span><span class="n">OpenOrConvert</span><span class="p">(</span><span class="n">_filename</span><span class="p">,</span> <span class="k">new</span> <span class="n">TraceLogOptions</span><span class="p">()</span> <span class="p">{</span> <span class="n">ConversionLog</span> <span class="p">=</span> <span class="n">SymbolMessages</span> <span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>ConversionLog TraceLogOptions</code> property is expecting a <code>TextWriter</code> to log all possible messages related to symbols resolution.</p>
<p>The parsing of kernel profiling samples is done on the <code>TraceLog</code> in a more manual way by selecting the events based on <code>TaskGuid</code> corresponding to the kernel profiling task and the <code>OpCode</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// parse profiling kernel events</span>
</span></span><span class="line"><span class="cl"><span class="c1">// from https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Samples/41_TraceLogMonitor.cs#L150</span>
</span></span><span class="line"><span class="cl"><span class="c1">// from https://docs.microsoft.com/en-us/windows/win32/etw/perfinfo</span>
</span></span><span class="line"><span class="cl"><span class="c1">// from https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Parsers/KernelTraceEventParser.cs#L3128</span>
</span></span><span class="line"><span class="cl"><span class="c1">// and https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Parsers/KernelTraceEventParser.cs#L2298</span>
</span></span><span class="line"><span class="cl"><span class="c1">//</span>
</span></span><span class="line"><span class="cl"><span class="n">Guid</span> <span class="n">perfInfoTaskGuid</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Guid</span><span class="p">(</span><span class="m">0xce1dbfb4</span><span class="p">,</span> <span class="m">0x137e</span><span class="p">,</span> <span class="m">0x4da6</span><span class="p">,</span> <span class="m">0x87</span><span class="p">,</span> <span class="m">0xb0</span><span class="p">,</span> <span class="m">0x3f</span><span class="p">,</span> <span class="m">0x59</span><span class="p">,</span> <span class="m">0xaa</span><span class="p">,</span> <span class="m">0x10</span><span class="p">,</span> <span class="m">0x2c</span><span class="p">,</span> <span class="m">0xbc</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="n">profileOpcode</span> <span class="p">=</span> <span class="m">46</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">data</span> <span class="k">in</span> <span class="n">traceLog</span><span class="p">.</span><span class="n">Events</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span> <span class="p">!=</span> <span class="n">Pid</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">TaskGuid</span> <span class="p">!=</span> <span class="n">perfInfoTaskGuid</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">((</span><span class="kt">uint</span><span class="p">)</span><span class="n">data</span><span class="p">.</span><span class="n">Opcode</span> <span class="p">!=</span> <span class="n">profileOpcode</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">CallStack</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callstack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">continue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">MergeCallStack</span><span class="p">(</span><span class="n">callstack</span><span class="p">,</span> <span class="n">Reader</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="how-to-merge-callstacks">How to “merge” call stacks</h2>
<p>In both live and file based implementations, I end up merging call stacks by calling the <code>MergeCallStack()</code> method. Instead of jumping directly into the C# code, I prefer to describe what I’m expecting from “merging“ call stacks.</p>
<p>If you think about what frames (i.e. method call) would appear at the beginning all these threads call stacks, it seems obvious that they should start with the same code: either the main thread startup, timer/thread pool initialization or custom thread bootstrap. In case of server applications, the same request processing calls would lead to specific handlers or controllers code. Each time a common group of frames appears in different call stacks, it would be more readable to see them as different branches starting from the same trunk like in Visual Studio Parallel Stack panel.</p>
<p><img loading="lazy" src="/posts/2020-12-08_build-your-own-net/1_Q6ry2HMPlwOTHGrhW0Avpg.png"></p>
<p>In order to build a “visual” representation, I have to count the number of time each frame appears at the same place in the recorded call stacks. My data structure looks like a tree where each node contains the current frame, the sampling count (as node or as leaf) and a list of different child frames corresponding to the different execution branches:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MergedSymbolicStacks</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">_countAsNode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">int</span> <span class="n">_countAsLeaf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Frame</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">string</span> <span class="n">Symbol</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="kd">private</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">CountAsNode</span> <span class="p">=&gt;</span> <span class="n">_countAsNode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">int</span> <span class="n">CountAsLeaf</span> <span class="p">=&gt;</span> <span class="n">_countAsLeaf</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">MergedSymbolicStacks</span><span class="p">&gt;</span> <span class="n">Stacks</span> <span class="p">{</span> <span class="k">get</span><span class="p">;</span> <span class="k">set</span><span class="p">;</span> <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Each frame contains both the address and the method signature that have been extracted from the callstack retrieved from the events:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">protected</span> <span class="k">void</span> <span class="n">MergeCallStack</span><span class="p">(</span><span class="n">TraceCallStack</span> <span class="n">callStack</span><span class="p">,</span> <span class="n">SymbolReader</span> <span class="n">reader</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentFrame</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">Depth</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">frames</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolicFrame</span><span class="p">[</span><span class="n">callStack</span><span class="p">.</span><span class="n">Depth</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// the first element of callstack is the last frame: we need to iterate on each frame</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// up to the first one before adding them into the MergedSymbolicStack</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">callStack</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">codeAddress</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">CodeAddress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">codeAddress</span><span class="p">.</span><span class="n">Method</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">moduleFile</span> <span class="p">=</span> <span class="n">codeAddress</span><span class="p">.</span><span class="n">ModuleFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">moduleFile</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="c1">// TODO: this seems to trigger extremely slow retrieval of symbols </span>
</span></span><span class="line"><span class="cl">                <span class="c1">//       through HTTP requests: see how to delay it AFTER the user</span>
</span></span><span class="line"><span class="cl">                <span class="c1">//       stops the profiling</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(!</span><span class="n">_missingSymbols</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">moduleFile</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">_</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                <span class="p">{</span>
</span></span><span class="line"><span class="cl">                    <span class="n">codeAddress</span><span class="p">.</span><span class="n">CodeAddresses</span><span class="p">.</span><span class="n">LookupSymbolsForModule</span><span class="p">(</span><span class="n">reader</span><span class="p">,</span> <span class="n">moduleFile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                    <span class="k">if</span> <span class="p">(</span><span class="n">codeAddress</span><span class="p">.</span><span class="n">Method</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                    <span class="p">{</span>
</span></span><span class="line"><span class="cl">                        <span class="n">_missingSymbols</span><span class="p">[</span><span class="n">moduleFile</span><span class="p">]</span> <span class="p">=</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                    <span class="p">}</span>
</span></span><span class="line"><span class="cl">                <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="n">frames</span><span class="p">[--</span><span class="n">currentFrame</span><span class="p">]</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolicFrame</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="n">codeAddress</span><span class="p">.</span><span class="n">Address</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">codeAddress</span><span class="p">.</span><span class="n">FullMethodName</span>
</span></span><span class="line"><span class="cl">            <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">callStack</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">Caller</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">_stackCount</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_stacks</span><span class="p">.</span><span class="n">AddStack</span><span class="p">(</span><span class="n">frames</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>MergedSymbolicStack.AddStack()</code> method is doing the real merging. The idea of merging call stacks is to start from the bottom and if the frame has already been seen (at this position), increment its sampling count. If not, remember it before incrementing the count. Look at the next frame and do the same match/remember + increment up to the top of the stack.</p>
<p>Here is an animation of what it would look like on a piece of paper (like the one I wrote down before starting to write the C# implementation :^)</p>
<p><img loading="lazy" src="/posts/2020-12-08_build-your-own-net/1_31F18a8E4cGDevn4-V_pog.gif"></p>
<p>Here is the corresponding C# code to merge a stack (i.e. an array of frames)</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">AddStack</span><span class="p">(</span><span class="n">SymbolicFrame</span><span class="p">[]</span> <span class="n">frames</span><span class="p">,</span> <span class="kt">int</span> <span class="n">index</span> <span class="p">=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">_countAsNode</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">firstFrame</span> <span class="p">=</span> <span class="n">frames</span><span class="p">[</span><span class="n">index</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// search if the frame to add has already been seen</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">Stacks</span><span class="p">.</span><span class="n">FirstOrDefault</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span> <span class="kt">string</span><span class="p">.</span><span class="n">CompareOrdinal</span><span class="p">(</span><span class="n">s</span><span class="p">.</span><span class="n">Symbol</span><span class="p">,</span> <span class="n">firstFrame</span><span class="p">.</span><span class="n">Symbol</span><span class="p">)</span> <span class="p">==</span> <span class="m">0</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// if not, we are starting a new branch</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">callstack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">callstack</span> <span class="p">=</span> <span class="k">new</span> <span class="n">MergedSymbolicStacks</span><span class="p">(</span><span class="n">frames</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">Address</span><span class="p">,</span> <span class="n">frames</span><span class="p">[</span><span class="n">index</span><span class="p">].</span><span class="n">Symbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Stacks</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">callstack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// it was the last frame of the stack</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">index</span> <span class="p">==</span> <span class="n">frames</span><span class="p">.</span><span class="n">Length</span> <span class="p">-</span> <span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">callstack</span><span class="p">.</span><span class="n">_countAsLeaf</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">callstack</span><span class="p">.</span><span class="n">AddStack</span><span class="p">(</span><span class="n">frames</span><span class="p">,</span> <span class="n">index</span> <span class="p">+</span> <span class="m">1</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Last but not least, the constructors of the class reflect how to (1) create the root instance and (2) each node in the tree:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">MergedSymbolicStacks</span><span class="p">()</span> <span class="p">:</span> <span class="k">this</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="kt">string</span><span class="p">.</span><span class="n">Empty</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// this will be the root of all stacks</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">MergedSymbolicStacks</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">frame</span><span class="p">,</span> <span class="kt">string</span> <span class="n">symbol</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">Frame</span> <span class="p">=</span> <span class="n">frame</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Symbol</span> <span class="p">=</span> <span class="n">symbol</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_countAsNode</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_countAsLeaf</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">Stacks</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">MergedSymbolicStacks</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The code to render the merged stack</p>
<p><img loading="lazy" src="/posts/2020-12-08_build-your-own-net/1_WIzDdkN_0nbUFiNnmKXe7A.png"></p>
<p>is not that complicated because everything is already in the tree of frames.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">RenderStack</span><span class="p">(</span><span class="n">MergedSymbolicStacks</span> <span class="n">stack</span><span class="p">,</span> <span class="n">IRenderer</span> <span class="n">visitor</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isRoot</span><span class="p">,</span> <span class="kt">int</span> <span class="n">increment</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">alignment</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">,</span> <span class="n">Padding</span> <span class="p">*</span> <span class="n">increment</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">padding</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">string</span><span class="p">(</span><span class="sc">&#39; &#39;</span><span class="p">,</span> <span class="n">Padding</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">currentFrame</span> <span class="p">=</span> <span class="n">stack</span><span class="p">.</span><span class="n">Frame</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// special root case</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">isRoot</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="n">visitor</span><span class="p">.</span><span class="n">WriteCount</span><span class="p">(</span><span class="s">$&#34;{Environment.NewLine}{alignment}{stack.CountAsNode, Padding} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="n">visitor</span><span class="p">.</span><span class="n">WriteCount</span><span class="p">(</span><span class="s">$&#34;{Environment.NewLine}{alignment}{stack.CountAsLeaf + stack.CountAsNode, Padding} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">visitor</span><span class="p">.</span><span class="n">WriteMethod</span><span class="p">(</span><span class="n">stack</span><span class="p">.</span><span class="n">Symbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">childrenCount</span> <span class="p">=</span> <span class="n">stack</span><span class="p">.</span><span class="n">Stacks</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">childrenCount</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">visitor</span><span class="p">.</span><span class="n">WriteFrameSeparator</span><span class="p">(</span><span class="s">&#34;&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">foreach</span> <span class="p">(</span><span class="kt">var</span> <span class="n">nextStackFrame</span> <span class="k">in</span> <span class="n">stack</span><span class="p">.</span><span class="n">Stacks</span><span class="p">.</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">s</span> <span class="p">=&gt;</span> <span class="n">s</span><span class="p">.</span><span class="n">CountAsNode</span> <span class="p">+</span> <span class="n">s</span><span class="p">.</span><span class="n">CountAsLeaf</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// increment when more than 1 children</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">childIncrement</span> <span class="p">=</span> <span class="p">(</span><span class="n">childrenCount</span> <span class="p">==</span> <span class="m">1</span><span class="p">)</span> <span class="p">?</span> <span class="n">increment</span> <span class="p">:</span> <span class="n">increment</span> <span class="p">+</span> <span class="m">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">RenderStack</span><span class="p">(</span><span class="n">nextStackFrame</span><span class="p">,</span> <span class="n">visitor</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="n">childIncrement</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">increment</span> <span class="p">!=</span> <span class="n">childIncrement</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">visitor</span><span class="p">.</span><span class="n">WriteFrameSeparator</span><span class="p">(</span><span class="s">$&#34;{Environment.NewLine}{alignment}{padding}{nextStackFrame.CountAsNode + nextStackFrame.CountAsLeaf, Padding} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">visitor</span><span class="p">.</span><span class="n">WriteFrameSeparator</span><span class="p">(</span><span class="s">$&#34;~~~~ &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>IRenderer</code> interface implementations are simply changing foreground color depending on what kind of information to display:</p>
<p>I have used the same “Visitor” pattern for the <a href="https://github.com/chrisnas/DebuggingExtensions/tree/master/src/ParallelStacks.Runtime"><strong>pstack</strong></a> tool/extension for WinDBG.</p>
<h2 id="not-for-adminonly">Not for Admin only</h2>
<p>I always thought that I needed to be a member of the Administrator group and running elevated to be allowed to start a kernel profiling session. Well… This is in fact not the case! You have to dig into the documentation for <a href="https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session">configuring and starting a <strong>SystemTraceProvider</strong> session</a> to read the following note:</p>
<p>If you want a non-administrators or a non-TCB process to be able to start a profiling trace session using the <code>SystemTraceProvider</code> on behalf of third party applications, then you need to grant the user profile privilege and then add this user to both the session <strong>GUID</strong> (created for the logger session) and the system trace provider <strong>GUID</strong> to enable the system trace provider. For more information, see the <a href="https://docs.microsoft.com/en-us/windows/desktop/api/Evntcons/nf-evntcons-eventaccesscontrol"><strong>EventAccessControl</strong></a> function.</p>
<p>Long story short, you need a user to be part of the <strong>Performance Log Users</strong> group (makes sense) or grant her the TRACELOG_ACCESS_REALTIME permission. Obviously, you need an administrator account to do both but this can be done once on a machine by your IT in a secure way.</p>
<p>I wrapped a managed implementation of the corresponding code to add the permission in a <code>ProfilingPermission</code> class that hides all the P/Invoke and weird marshalling stuff to the native Windows API. Simply pass a user name to <code>EnableProfileUser()</code> and it should work just fine.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">  1
</span><span class="lnt">  2
</span><span class="lnt">  3
</span><span class="lnt">  4
</span><span class="lnt">  5
</span><span class="lnt">  6
</span><span class="lnt">  7
</span><span class="lnt">  8
</span><span class="lnt">  9
</span><span class="lnt"> 10
</span><span class="lnt"> 11
</span><span class="lnt"> 12
</span><span class="lnt"> 13
</span><span class="lnt"> 14
</span><span class="lnt"> 15
</span><span class="lnt"> 16
</span><span class="lnt"> 17
</span><span class="lnt"> 18
</span><span class="lnt"> 19
</span><span class="lnt"> 20
</span><span class="lnt"> 21
</span><span class="lnt"> 22
</span><span class="lnt"> 23
</span><span class="lnt"> 24
</span><span class="lnt"> 25
</span><span class="lnt"> 26
</span><span class="lnt"> 27
</span><span class="lnt"> 28
</span><span class="lnt"> 29
</span><span class="lnt"> 30
</span><span class="lnt"> 31
</span><span class="lnt"> 32
</span><span class="lnt"> 33
</span><span class="lnt"> 34
</span><span class="lnt"> 35
</span><span class="lnt"> 36
</span><span class="lnt"> 37
</span><span class="lnt"> 38
</span><span class="lnt"> 39
</span><span class="lnt"> 40
</span><span class="lnt"> 41
</span><span class="lnt"> 42
</span><span class="lnt"> 43
</span><span class="lnt"> 44
</span><span class="lnt"> 45
</span><span class="lnt"> 46
</span><span class="lnt"> 47
</span><span class="lnt"> 48
</span><span class="lnt"> 49
</span><span class="lnt"> 50
</span><span class="lnt"> 51
</span><span class="lnt"> 52
</span><span class="lnt"> 53
</span><span class="lnt"> 54
</span><span class="lnt"> 55
</span><span class="lnt"> 56
</span><span class="lnt"> 57
</span><span class="lnt"> 58
</span><span class="lnt"> 59
</span><span class="lnt"> 60
</span><span class="lnt"> 61
</span><span class="lnt"> 62
</span><span class="lnt"> 63
</span><span class="lnt"> 64
</span><span class="lnt"> 65
</span><span class="lnt"> 66
</span><span class="lnt"> 67
</span><span class="lnt"> 68
</span><span class="lnt"> 69
</span><span class="lnt"> 70
</span><span class="lnt"> 71
</span><span class="lnt"> 72
</span><span class="lnt"> 73
</span><span class="lnt"> 74
</span><span class="lnt"> 75
</span><span class="lnt"> 76
</span><span class="lnt"> 77
</span><span class="lnt"> 78
</span><span class="lnt"> 79
</span><span class="lnt"> 80
</span><span class="lnt"> 81
</span><span class="lnt"> 82
</span><span class="lnt"> 83
</span><span class="lnt"> 84
</span><span class="lnt"> 85
</span><span class="lnt"> 86
</span><span class="lnt"> 87
</span><span class="lnt"> 88
</span><span class="lnt"> 89
</span><span class="lnt"> 90
</span><span class="lnt"> 91
</span><span class="lnt"> 92
</span><span class="lnt"> 93
</span><span class="lnt"> 94
</span><span class="lnt"> 95
</span><span class="lnt"> 96
</span><span class="lnt"> 97
</span><span class="lnt"> 98
</span><span class="lnt"> 99
</span><span class="lnt">100
</span><span class="lnt">101
</span><span class="lnt">102
</span><span class="lnt">103
</span><span class="lnt">104
</span><span class="lnt">105
</span><span class="lnt">106
</span><span class="lnt">107
</span><span class="lnt">108
</span><span class="lnt">109
</span><span class="lnt">110
</span><span class="lnt">111
</span><span class="lnt">112
</span><span class="lnt">113
</span><span class="lnt">114
</span><span class="lnt">115
</span><span class="lnt">116
</span><span class="lnt">117
</span><span class="lnt">118
</span><span class="lnt">119
</span><span class="lnt">120
</span><span class="lnt">121
</span><span class="lnt">122
</span><span class="lnt">123
</span><span class="lnt">124
</span><span class="lnt">125
</span><span class="lnt">126
</span><span class="lnt">127
</span><span class="lnt">128
</span><span class="lnt">129
</span><span class="lnt">130
</span><span class="lnt">131
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">ProfilingPermission</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">uint</span> <span class="n">TRACELOG_GUID_ENABLE</span> <span class="p">=</span> <span class="m">0x0080</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">NO_ERROR</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>  <span class="c1">// ERROR_SUCCESS in C++</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">ERROR_INSUFFICIENT_BUFFER</span> <span class="p">=</span> <span class="m">122</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// read https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// for more details </span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">EnableProfilerUser</span><span class="p">(</span><span class="kt">string</span> <span class="n">accountName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// Kernel provider from https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Parsers/KernelTraceEventParser.cs#L43</span>
</span></span><span class="line"><span class="cl">        <span class="n">Guid</span> <span class="n">kernelProviderGuid</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Guid</span><span class="p">(</span><span class="s">&#34;{9e814aad-3204-11d2-9a82-006008a86939}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">byte</span><span class="p">[]</span> <span class="n">sid</span> <span class="p">=</span> <span class="n">LookupSidByName</span><span class="p">(</span><span class="n">accountName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// from https://docs.microsoft.com/en-us/windows/win32/etw/configuring-and-starting-a-systemtraceprovider-session</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">operation</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">EventSecurityOperation</span><span class="p">.</span><span class="n">EventSecurityAddDACL</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">rights</span> <span class="p">=</span> <span class="n">TRACELOG_GUID_ENABLE</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">allowOrDeny</span> <span class="p">=</span> <span class="p">(</span><span class="s">&#34;Allow&#34;</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">result</span> <span class="p">=</span> <span class="n">EventAccessControl</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">            <span class="k">ref</span> <span class="n">kernelProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">operation</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">sid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">rights</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">            <span class="n">allowOrDeny</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">result</span> <span class="p">!=</span> <span class="n">NO_ERROR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">lastErrorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">result</span><span class="p">).</span><span class="n">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;Failed to add ACL ({result.ToString()}) : {lastErrorMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kd">static</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">LookupSidByName</span><span class="p">(</span><span class="kt">string</span> <span class="n">accountName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">byte</span><span class="p">[]</span> <span class="n">sid</span> <span class="p">=</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">cbSid</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">referencedDomainName</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">cchReferencedDomainName</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">referencedDomainName</span><span class="p">.</span><span class="n">Capacity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">SID_NAME_USE</span> <span class="n">sidUse</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">int</span> <span class="n">err</span> <span class="p">=</span> <span class="n">NO_ERROR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">LookupAccountName</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="n">accountName</span><span class="p">,</span> <span class="n">sid</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cbSid</span><span class="p">,</span> <span class="n">referencedDomainName</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cchReferencedDomainName</span><span class="p">,</span> <span class="k">out</span> <span class="n">sidUse</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">err</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="p">==</span> <span class="n">ERROR_INSUFFICIENT_BUFFER</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">sid</span> <span class="p">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="p">[</span><span class="n">cbSid</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">                <span class="n">referencedDomainName</span><span class="p">.</span><span class="n">EnsureCapacity</span><span class="p">((</span><span class="kt">int</span><span class="p">)</span><span class="n">cchReferencedDomainName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">                <span class="n">err</span> <span class="p">=</span> <span class="n">NO_ERROR</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">                <span class="k">if</span> <span class="p">(!</span><span class="n">LookupAccountName</span><span class="p">(</span><span class="kc">null</span><span class="p">,</span> <span class="n">accountName</span><span class="p">,</span> <span class="n">sid</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cbSid</span><span class="p">,</span> <span class="n">referencedDomainName</span><span class="p">,</span> <span class="k">ref</span> <span class="n">cchReferencedDomainName</span><span class="p">,</span> <span class="k">out</span> <span class="n">sidUse</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">                    <span class="n">err</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">err</span> <span class="p">!=</span> <span class="n">NO_ERROR</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">lastErrorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">(</span><span class="n">err</span><span class="p">).</span><span class="n">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">throw</span> <span class="k">new</span> <span class="n">InvalidOperationException</span><span class="p">(</span><span class="s">$&#34;LookupAccountName fails ({err.ToString()}) : {lastErrorMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// display the SID associated to the given user</span>
</span></span><span class="line"><span class="cl">        <span class="n">IntPtr</span> <span class="n">ptrSid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">ConvertSidToStringSid</span><span class="p">(</span><span class="n">sid</span><span class="p">,</span> <span class="k">out</span> <span class="n">ptrSid</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">err</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">lastErrorMessage</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Win32Exception</span><span class="p">(</span><span class="n">err</span><span class="p">).</span><span class="n">Message</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;No SID string associated to user {accountName} ({err.ToString()}) : {lastErrorMessage}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">string</span> <span class="n">sidString</span> <span class="p">=</span> <span class="n">Marshal</span><span class="p">.</span><span class="n">PtrToStringAuto</span><span class="p">(</span><span class="n">ptrSid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">ProfilingPermission</span><span class="p">.</span><span class="n">LocalFree</span><span class="p">(</span><span class="n">ptrSid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Account ({referencedDomainName}){accountName} mapped to {sidString}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">sid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;Sechost.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">uint</span> <span class="n">EventAccessControl</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="k">ref</span> <span class="n">Guid</span> <span class="n">providerGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">operation</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.LPArray)]</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">Sid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint</span> <span class="n">right</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">allowOrDeny</span> <span class="c1">// true means ALLOW</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;kernel32.dll&#34;)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="n">IntPtr</span> <span class="n">LocalFree</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hMem</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;advapi32.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">LookupAccountName</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">systemName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="kt">string</span> <span class="n">accountName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.LPArray)]</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">Sid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">ref</span> <span class="kt">uint</span> <span class="n">cbSid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">StringBuilder</span> <span class="n">referencedDomainName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">ref</span> <span class="kt">uint</span> <span class="n">cchReferencedDomainName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">out</span> <span class="n">SID_NAME_USE</span> <span class="n">nameUse</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;advapi32.dll&#34;, CharSet = CharSet.Auto, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">ConvertSidToStringSid</span><span class="p">(</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.LPArray)]</span> <span class="kt">byte</span><span class="p">[]</span> <span class="n">pSID</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="k">out</span> <span class="n">IntPtr</span> <span class="n">ptrSid</span><span class="p">);</span> <span class="c1">// can&#39;t be an out string because we need to explicitly call LocalFree on it;</span>
</span></span><span class="line"><span class="cl">                            <span class="c1">// the marshaller would call CoTaskMemFree in case of a string</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// from http://pinvoke.net/default.aspx/advapi32/LookupAccountName.html</span>
</span></span><span class="line"><span class="cl">    <span class="kd">enum</span> <span class="n">SID_NAME_USE</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeUser</span> <span class="p">=</span> <span class="m">1</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeGroup</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeDomain</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeAlias</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeWellKnownGroup</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeDeletedAccount</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeInvalid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeUnknown</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">SidTypeComputer</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// from evntcons.h</span>
</span></span><span class="line"><span class="cl">    <span class="kd">enum</span> <span class="n">EventSecurityOperation</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecuritySetDACL</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecuritySetSACL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecurityAddDACL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecurityAddSACL</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventSecurityMax</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span> <span class="c1">// EVENTSECURITYOPERATION</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>You are now ready to profile your application memory allocation patterns and CPU consumption!</p>
<hr>
<p><strong>Thanks for checking in with us again on our C# series. Like what you are reading? Head over to our latest blog posts on the topic:</strong></p>
<p><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-allocations-1-2-9c9f0c86cefd"><strong>Build your own .NET memory profiler in C#</strong>
*This post explains how to collect allocation details by writing your own memory profiler in C#.*medium.com</a><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-allocations-1-2-9c9f0c86cefd"></a><a href="/posts/2020-05-18_build-your-own-net/"><strong>Build your own .NET memory profiler in C# — call stacks (2/2–1)</strong>
*This post explains how to get the call stack corresponding to the allocations with CLR events.*medium.com</a></p>
<hr>
<p><strong>If you are interested in joining our team, check out our open positions and apply today!</strong></p>
<p><a href="http://careers.criteo.com"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. Find the job opportunities at Criteo in Product, research &amp;…*careers.criteo.com</a><a href="http://careers.criteo.com"></a></p>
]]></content:encoded></item><item><title>Build your own .NET memory profiler in C# — call stacks (2/2–2)</title><link>https://chrisnas.github.io/posts/2020-06-19_build-your-own-net/</link><pubDate>Fri, 19 Jun 2020 09:32:16 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-06-19_build-your-own-net/</guid><description>In this last episode I detail how to transform addresses from the stack into methods name and signature.</description><content:encoded><![CDATA[<hr>
<p>In the past two episodes of this series I have explained how to <a href="/posts/2020-04-18_build-your-own-net/">get a sampling of .NET application allocations</a> and <a href="/posts/2020-05-18_build-your-own-net/">one way to get the call stack</a> corresponding to the allocations; all with CLR events. In this last episode, I will detail how to transform addresses from the stack into methods name and possibly signature.</p>
<h2 id="from-managed-address-to-method-signature">From managed address to method signature</h2>
<p>In order to transform an address on the stack into a managed method name, you need to know where in memory (i.e. at which address) is stored the method JITted assembly code and what is its size:</p>
<p><img loading="lazy" src="/posts/2020-06-19_build-your-own-net/1_v73Nx1IxWIEQ3NzDZF0rsQ.png"></p>
<p>For each JITted method, the <code>MethodLoadVerbose</code>/<code>MethodDCStartVerboseV2</code> events are providing this information in addition to 3 properties to rebuild the full method name and signature (more on this later). I’m storing each method description as a <code>MethodInfo</code> into a <code>MethodStore</code> per process.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">PerProcessProfilingState</span> <span class="p">:</span> <span class="n">IDisposable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">MethodStore</span><span class="p">&gt;</span> <span class="n">_methods</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">MethodStore</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MethodStore</span> <span class="p">:</span> <span class="n">IDisposable</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// JITed methods information (start address + size + signature)</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">MethodInfo</span><span class="p">&gt;</span> <span class="n">_methods</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The only interesting part of the <code>MethodInfo</code> class is the computation of the full method name stored in the <code>_fullName</code> field:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">MethodInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">ulong</span> <span class="n">_startAddress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">int</span> <span class="n">_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="n">_fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>ComputeFullName</code> helper merges together the 3 properties given by the <code>MethodxxxVerbose</code> events including special processing for constructors:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">ComputeFullName</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">startAddress</span><span class="p">,</span> <span class="kt">string</span> <span class="n">namespaceAndTypeName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="kt">string</span> <span class="n">signature</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">fullName</span> <span class="p">=</span> <span class="n">signature</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// constructor case: name = .ctor | namespaceAndTypeName = A.B.typeName | signature = ...  (parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; A.B.typeName(parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">name</span> <span class="p">==</span> <span class="s">&#34;.ctor&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">$&#34;{namespaceAndTypeName}{ExtractParameters(signature)}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// general case: name = Foo | namespaceAndTypeName = A.B.typeName | signature = ...  (parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; A.B.Foo(parameters)</span>
</span></span><span class="line"><span class="cl">    <span class="n">fullName</span> <span class="p">=</span> <span class="s">$&#34;{namespaceAndTypeName}.{name}{ExtractParameters(signature)}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">ExtractTypeName</span><span class="p">(</span><span class="kt">string</span> <span class="n">namespaceAndTypeName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">namespaceAndTypeName</span><span class="p">.</span><span class="n">LastIndexOf</span><span class="p">(</span><span class="s">&#34;.&#34;</span><span class="p">,</span> <span class="n">StringComparison</span><span class="p">.</span><span class="n">Ordinal</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pos</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">namespaceAndTypeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// skip the .</span>
</span></span><span class="line"><span class="cl">    <span class="n">pos</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">namespaceAndTypeName</span><span class="p">.</span><span class="n">Substring</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Only the parameters (not the return type) are extracted from the “return type SPACE SPACE (parameters)” signature format:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">ExtractParameters</span><span class="p">(</span><span class="kt">string</span> <span class="n">signature</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">signature</span><span class="p">.</span><span class="n">IndexOf</span><span class="p">(</span><span class="s">&#34;  (&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">pos</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">&#34;(???)&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// skip double space</span>
</span></span><span class="line"><span class="cl">    <span class="n">pos</span> <span class="p">+=</span> <span class="m">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">parameters</span> <span class="p">=</span> <span class="n">signature</span><span class="p">.</span><span class="n">Substring</span><span class="p">(</span><span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">parameters</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>With the starting address and the size of each JITted methods, it is easy to find the one corresponding to a given address on the stack: look for the <code>MethodInfo</code> where this address could be between the start address and the start address + the code size:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="kt">string</span> <span class="n">GetFullName</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">address</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_cache</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">address</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">fullName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// look for managed methods</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">_methods</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">method</span> <span class="p">=</span> <span class="n">_methods</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">((</span><span class="n">address</span> <span class="p">&gt;=</span> <span class="n">method</span><span class="p">.</span><span class="n">StartAddress</span><span class="p">)</span> <span class="p">&amp;&amp;</span> <span class="p">(</span><span class="n">address</span> <span class="p">&lt;</span> <span class="n">method</span><span class="p">.</span><span class="n">StartAddress</span> <span class="p">+</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)</span><span class="n">method</span><span class="p">.</span><span class="n">Size</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">fullName</span> <span class="p">=</span> <span class="n">method</span><span class="p">.</span><span class="n">FullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">_cache</span><span class="p">[</span><span class="n">address</span><span class="p">]</span> <span class="p">=</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// look for native methods</span>
</span></span><span class="line"><span class="cl">    <span class="n">fullName</span> <span class="p">=</span> <span class="n">GetNativeMethodName</span><span class="p">(</span><span class="n">address</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_cache</span><span class="p">[</span><span class="n">address</span><span class="p">]</span> <span class="p">=</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">fullName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>For performance sake, the <code>_cache</code> dictionary property speeds up the process by keeping track of the address/full name mappings.</p>
<p>It is now time to look at the details of the <code>GetNativeMethodName</code> helper that takes care of the native functions scenario.</p>
<h2 id="the-native-part-of-the-symbolsstory">The native part of the symbols story</h2>
<p>Unlike for JITted methods, the CLR does not send events to describe native functions even for the CLR itself. Instead, you need to find a way to map a call stack address to a native function by yourself. Unlike Perfview, I will be using the <strong>dbghelp</strong> native API instead of <strong>DIA</strong> mostly because my scenario is to get the stacks while the applications are still running:</p>
<p><img loading="lazy" src="/posts/2020-06-19_build-your-own-net/1_hCkVFS_bkxBJUtq-pAywSQ.png"></p>
<p>After reading the <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/under-the-hood-improved-error-reporting-with-dbghelp-5-1-apis?WT.mc_id=DT-MVP-5003325">march 2002 MSDN article about DBGHELP</a> by Matt Pietrek, the updated symbols <a href="https://docs.microsoft.com/en-us/windows/win32/debug/dbghelp-functions#symbol-handler?WT.mc_id=DT-MVP-5003325">related Microsoft Docs</a> and the dbghelp.h include a file from the Windows SDK, I wrote a C# wrapper around the dbghelp function needed to get a method name from an address in a process address space:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span><span class="lnt">38
</span><span class="lnt">39
</span><span class="lnt">40
</span><span class="lnt">41
</span><span class="lnt">42
</span><span class="lnt">43
</span><span class="lnt">44
</span><span class="lnt">45
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">internal</span> <span class="kd">static</span> <span class="k">class</span> <span class="nc">NativeDbgHelp</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// from C:\Program Files (x86)\Windows Kits\10\Debuggers\inc\dbghelp.h</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">const</span> <span class="kt">uint</span> <span class="n">SYMOPT_UNDNAME</span> <span class="p">=</span> <span class="m">0x00000002</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">const</span> <span class="kt">uint</span> <span class="n">SYMOPT_DEFERRED_LOADS</span> <span class="p">=</span> <span class="m">0x00000004</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [StructLayout(LayoutKind.Sequential)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">struct</span> <span class="nc">SYMBOL_INFO</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">SizeOfStruct</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">TypeIndex</span><span class="p">;</span>      <span class="c1">// Type Index of symbol</span>
</span></span><span class="line"><span class="cl">        <span class="kd">private</span> <span class="kt">ulong</span> <span class="n">Reserved1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">private</span> <span class="kt">ulong</span> <span class="n">Reserved2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Index</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">ModBase</span><span class="p">;</span>       <span class="c1">// Base Address of module containing this symbol</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Flags</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Value</span><span class="p">;</span>         <span class="c1">// Value of symbol, ValuePresent should be 1</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Address</span><span class="p">;</span>       <span class="c1">// Address of symbol including base address of module</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Register</span><span class="p">;</span>       <span class="c1">// register holding value or pointer to value</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Scope</span><span class="p">;</span>          <span class="c1">// scope of the symbol</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">Tag</span><span class="p">;</span>            <span class="c1">// pdb classification</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">NameLen</span><span class="p">;</span>        <span class="c1">// Actual length of name</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">uint</span> <span class="n">MaxNameLen</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="na">        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1024)]</span>
</span></span><span class="line"><span class="cl">        <span class="kd">public</span> <span class="kt">string</span> <span class="n">Name</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">SymInitialize</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">,</span> <span class="kt">string</span> <span class="n">userSearchPath</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">invadeProcess</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">uint</span> <span class="n">SymSetOptions</span><span class="p">(</span><span class="kt">uint</span> <span class="n">symOptions</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true, CharSet = CharSet.Ansi)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">ulong</span> <span class="n">SymLoadModule64</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">,</span> <span class="n">IntPtr</span> <span class="n">hFile</span><span class="p">,</span> <span class="kt">string</span> <span class="n">imageName</span><span class="p">,</span> <span class="kt">string</span> <span class="n">moduleName</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">baseOfDll</span><span class="p">,</span> <span class="kt">uint</span> <span class="n">sizeOfDll</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// use ANSI version to ensure the right size of the structure </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/ns-dbghelp-symbol_info</span>
</span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true, CharSet = CharSet.Ansi)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">SymFromAddr</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">address</span><span class="p">,</span> <span class="k">out</span> <span class="kt">ulong</span> <span class="n">displacement</span><span class="p">,</span> <span class="k">ref</span> <span class="n">SYMBOL_INFO</span> <span class="n">symbol</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="na">
</span></span></span><span class="line"><span class="cl"><span class="na">    [DllImport(&#34;dbghelp.dll&#34;, SetLastError = true)]</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">static</span> <span class="kd">extern</span> <span class="kt">bool</span> <span class="n">SymCleanup</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that you will need to download the dbghelp.dll (SymSrv.dll if needed) from <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/?WT.mc_id=DT-MVP-5003325">the Windows SDK</a> and copy it next to your memory profiler binaries.</p>
<p>The usage of the dbghelp API is straightforward. First, for each new process, call <code>SymSetOptions</code>/<code>SymInitialize</code>** **with a handle of the process:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span><span class="lnt">37
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">bool</span> <span class="n">SymInitialize</span><span class="p">(</span><span class="n">IntPtr</span> <span class="n">hProcess</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// read https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions for more details</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// maybe SYMOPT_NO_PROMPTS and SYMOPT_FAIL_CRITICAL_ERRORS could be used</span>
</span></span><span class="line"><span class="cl">    <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymSetOptions</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SYMOPT_DEFERRED_LOADS</span> <span class="p">|</span>   <span class="c1">// performance optimization</span>
</span></span><span class="line"><span class="cl">        <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SYMOPT_UNDNAME</span>            <span class="c1">// C++ names are not mangled</span>
</span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-syminitialize</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// search path for symbols:</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   - The current working directory of the application</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   - The _NT_SYMBOL_PATH environment variable</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//   - The _NT_ALTERNATE_SYMBOL_PATH environment variable</span>
</span></span><span class="line"><span class="cl">    <span class="c1">//</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// passing false as last parameter means that we will need to call SymLoadModule64 </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// each time a module is loaded in the process</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymInitialize</span><span class="p">(</span><span class="n">hProcess</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">IntPtr</span> <span class="n">BindToProcess</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">try</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_process</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetProcessById</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="n">SymInitialize</span><span class="p">(</span><span class="n">_process</span><span class="p">.</span><span class="n">Handle</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">_process</span><span class="p">.</span><span class="n">Handle</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">x</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Error while binding pid #{pid} to DbgHelp:&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">x</span><span class="p">.</span><span class="n">Message</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the case of protected processes, <code>Process.GetProcessById</code> might throw an exception. The <code>_hProcess</code> field storing the process handle will be cleaned up in the <code>IDisposible.Dispose</code> implementation of the <code>MethodStore</code>:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">Dispose</span><span class="p">()</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_hProcess</span> <span class="p">==</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_hProcess</span> <span class="p">=</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">_process</span><span class="p">.</span><span class="n">Dispose</span><span class="p">();</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>After a process has been bound, each time one of its modules is loaded, <code>SymLoadModule64</code> must be called. You can be notified of such a loaded module by enabling the Kernel provider with the <code>ImageLoad</code> keyword.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="n">session</span><span class="p">.</span><span class="n">EnableKernelProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">    <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">    <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Process</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">None</span>
</span></span><span class="line"><span class="cl"><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The handler attached to the <code>ImageLoaded</code> event will be called each time a dll gets loaded.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">SetupListeners</span><span class="p">(</span><span class="n">ETWTraceEventSource</span> <span class="n">source</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// get notified when a module is load to map the corresponding symbols</span>
</span></span><span class="line"><span class="cl">    <span class="n">source</span><span class="p">.</span><span class="n">Kernel</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">+=</span> <span class="n">OnImageLoad</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">const</span> <span class="kt">int</span> <span class="n">ERROR_SUCCESS</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnImageLoad</span><span class="p">(</span><span class="n">ImageLoadTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">FilterOutEvent</span><span class="p">(</span><span class="n">data</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">GetProcessMethods</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">).</span><span class="n">AddModule</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">FileName</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ImageBase</span><span class="p">,</span> <span class="n">data</span><span class="p">.</span><span class="n">ImageSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">AddModule</span><span class="p">(</span><span class="kt">string</span> <span class="n">filename</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">baseOfDll</span><span class="p">,</span> <span class="kt">int</span> <span class="n">sizeOfDll</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">baseAddress</span> <span class="p">=</span> <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymLoadModule64</span><span class="p">(</span><span class="n">_hProcess</span><span class="p">,</span> <span class="n">IntPtr</span><span class="p">.</span><span class="n">Zero</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="kc">null</span><span class="p">,</span> <span class="n">baseOfDll</span><span class="p">,</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">sizeOfDll</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">baseAddress</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// should work if the same module is added more than once</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">Marshal</span><span class="p">.</span><span class="n">GetLastWin32Error</span><span class="p">()</span> <span class="p">==</span> <span class="n">ERROR_SUCCESS</span><span class="p">)</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;SymLoadModule64 failed for {filename}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now everything is in place to get a native function name from an address on the stack:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="kt">string</span> <span class="n">GetNativeMethodName</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">address</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">symbol</span> <span class="p">=</span> <span class="k">new</span> <span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SYMBOL_INFO</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">symbol</span><span class="p">.</span><span class="n">MaxNameLen</span> <span class="p">=</span> <span class="m">1024</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">symbol</span><span class="p">.</span><span class="n">SizeOfStruct</span> <span class="p">=</span> <span class="p">(</span><span class="kt">uint</span><span class="p">)</span><span class="n">Marshal</span><span class="p">.</span><span class="n">SizeOf</span><span class="p">(</span><span class="n">symbol</span><span class="p">)</span> <span class="p">-</span> <span class="m">1024</span><span class="p">;</span>   <span class="c1">// char buffer is not counted</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// the ANSI version of SymFromAddr is called so each character is 1 byte long</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">NativeDbgHelp</span><span class="p">.</span><span class="n">SymFromAddr</span><span class="p">(</span><span class="n">_hProcess</span><span class="p">,</span> <span class="n">address</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">displacement</span><span class="p">,</span> <span class="k">ref</span> <span class="n">symbol</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">buffer</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StringBuilder</span><span class="p">(</span><span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">Length</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// remove weird &#34;$##&#34; at the end of some symbols</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">pos</span> <span class="p">=</span> <span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">LastIndexOf</span><span class="p">(</span><span class="s">&#34;$##&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">pos</span> <span class="p">==</span> <span class="p">-</span><span class="m">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="n">symbol</span><span class="p">.</span><span class="n">Name</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">pos</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// add offset if any</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">displacement</span> <span class="p">!=</span> <span class="m">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="n">buffer</span><span class="p">.</span><span class="n">Append</span><span class="p">(</span><span class="s">$&#34;+0x{displacement}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">buffer</span><span class="p">.</span><span class="n">ToString</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// default value is the just the address in HEX</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s">$&#34;0x{address:x}&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that I needed to remove some unexpected <strong>$##</strong> strings are the end of some symbols.</p>
<p><em><strong>This is the last episode of the series about building your own memory profiler in C#. In case you missed the first episodes, check them out on Medium:</strong></em></p>
<p><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-1-f67b440a8cc"><strong>Build your own .NET memory profiler in C# — call stacks (2/2–1)</strong>
*This post explains how to get the call stack corresponding to the allocations with CLR events.*medium.com</a><a href="https://medium.com/criteo-labs/build-your-own-net-memory-profiler-in-c-call-stacks-2-2-1-f67b440a8cc"></a><a href="/posts/2020-04-18_build-your-own-net/"><strong>Build your own .NET memory profiler in C#</strong>
*This post explains how to collect allocation details by writing your own memory profiler in C#.*medium.com</a></p>
<hr>
<h2 id="resources">Resources</h2>
<ul>
<li>Source code available <a href="https://github.com/chrisnas/ClrEvents">on Github</a>.</li>
<li>Download <a href="https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk/?WT.mc_id=DT-MVP-5003325">Debugging Tools for Windows</a> for dbghelp.dll and SymSrv.dll</li>
<li>Matt Pietrek <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2002/march/under-the-hood-improved-error-reporting-with-dbghelp-5-1-apis?WT.mc_id=DT-MVP-5003325">article in MSDN Magazine</a> about DBGHELP</li>
<li>Dbghelp samples -<a href="http://www.debuginfo.com/examples/dbghelpexamples.html">http://www.debuginfo.com/examples/dbghelpexamples.html</a></li>
</ul>
<hr>
<p><strong>Join the crowd!</strong></p>
<p><a href="https://careers.criteo.com/"><strong>Careers at Criteo | Criteo jobs</strong>
*Find opportunities everywhere. ​Choose your next challenge. Find the job opportunities at Criteo in Product, research &amp;…*careers.criteo.com</a><a href="https://careers.criteo.com/"></a></p>
]]></content:encoded></item><item><title>Build your own .NET memory profiler in C# — call stacks (2/2–1)</title><link>https://chrisnas.github.io/posts/2020-05-18_build-your-own-net/</link><pubDate>Mon, 18 May 2020 12:07:01 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-05-18_build-your-own-net/</guid><description>This post explains how to get the call stack corresponding to the allocations with CLR events.</description><content:encoded><![CDATA[<hr>
<p>In the <a href="/posts/2020-04-18_build-your-own-net/">previous episode</a> of this series, you have seen how to get a sampling of .NET application allocations thanks to the <strong>AllocationTick</strong> and <strong>GCSampleObjectAllocation</strong>(<strong>High</strong>/<strong>Low</strong>) CLR events. However, this is often not enough to investigate unexpected memory consumption: you would need to know which part of the code is triggering the allocations. This post explains how to get the call stack corresponding to the allocations, again with CLR events.</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_lYXf1qgB1ctzgi5_RKSDEw.jpeg"></p>
<h2 id="introduction">Introduction</h2>
<p>If you look carefully at the payload of the <code>TraceEvent</code> object mapped by Microsoft <strong>TraceEvent</strong> library (not my fault if they have the same name) for each CLR event, you won’t see anything related to a call stack. However, in the <strong>TraceEvent</strong> <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Samples/41_TraceLogMonitor.cs#L204">sample 41</a>, the following line looks promising:</p>
<blockquote>
<p>var callStack = data.CallStack();</p>
</blockquote>
<p>with data being a <code>TraceEvent</code> object received for each CLR event!</p>
<p>This <code>CallStack</code> method is <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceLog.cs#L10539">an extension method</a> provided by the <code>TraceLog</code> special kind of event source. You might not have noticed but I have used it in the <strong>AllocationTick</strong> code sample from the <a href="/posts/2020-04-18_build-your-own-net/">previous post</a>. This class (and many more helper classes) is doing a lot of work to :</p>
<ul>
<li>“attach” a call stack to each CLR event; i.e. a list of addresses of assembly code</li>
<li>to translate addresses into string symbols (method names or full signatures), listen to a bunch of JIT related events for managed methods (more on this later), using COM-based <a href="https://docs.microsoft.com/en-us/visualstudio/debugger/debug-interface-access/debug-interface-access-sdk?WT.mc_id=DT-MVP-5003325?view=vs-2019">Debug Interface Access</a> (a.k.a. DIA) and <a href="https://www.nuget.org/packages/System.Reflection.Metadata"><strong>MetadataReaderProvider</strong></a>** **for native functions</li>
</ul>
<p>Notice that since events from all managed processes on the machine are handled by <code>TraceLog</code>, the internal cache for JITted methods description could consume a lot of memory. During my tests with two Visual Studio running, my test profiler consumed more than 500 MB before even handling call stacks. If you are in such an environment with multiple .NET processes, I will show how to “manually” get the same stacks (+ symbols in the next episode) with CLR events and a few methods from dbghelp.dll in a cheaper way.</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_jD1PSQqxKAbjsIOdHVSS_Q.png"></p>
<p>The new provider (more on <strong>ClrRundown</strong> later), keywords and events need to be received to make all this work:</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_tWU46jlpltvIqt0ieA_YCA.png"></p>
<h2 id="tracelog-the-easyway">TraceLog: the easy way</h2>
<p>As you have seen in the previous posts, the <code>TraceEventSession</code> class exposes a <code>Source</code> property of <code>ETWTraceEventSource</code> type. This source has event parsers properties from which you register handler methods that will be called when CLR events are received. Instead of directly using this source, you should wrap it with a <code>TraceLogEventSource</code> object that provides the same event parsers.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="k">await</span> <span class="n">Task</span><span class="p">.</span><span class="n">Factory</span><span class="p">.</span><span class="n">StartNew</span><span class="p">(()</span> <span class="p">=&gt;</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">using</span> <span class="p">(</span><span class="n">_session</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">SetupProviders</span><span class="p">(</span><span class="n">_session</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">using</span> <span class="p">(</span><span class="n">TraceLogEventSource</span> <span class="n">source</span> <span class="p">=</span> <span class="n">TraceLog</span><span class="p">.</span><span class="n">CreateFromTraceEventSession</span><span class="p">(</span><span class="n">_session</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">SetupListeners</span><span class="p">(</span><span class="n">source</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="n">source</span><span class="p">.</span><span class="n">Process</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">});</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="whats-new-with-providers">What’s new with providers?</h2>
<p>The code for my<code>SetupProviders</code> method is a little bit different from the previous post even though no new event listeners are needed:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">SetupProviders</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">session</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Note: the kernel provider MUST be the first provider to be enabled</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// If the kernel provider is not enabled, the callstacks for CLR events are still received </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// but the symbols are not found (except for the application itself)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// TraceEvent implementation details triggered when a module (image) is loaded</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableKernelProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">ImageLoad</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Process</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">KernelTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">None</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">TraceEventLevel</span><span class="p">.</span><span class="n">Verbose</span><span class="p">,</span>    <span class="c1">// this is needed in order to receive AllocationTick_V2 event</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// required to receive AllocationTick events</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">GC</span> <span class="p">|</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Jit</span> <span class="p">|</span>                      <span class="c1">// Turning on JIT events is necessary to resolve JIT compiled code </span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">JittedMethodILToNativeMap</span> <span class="p">|</span><span class="c1">// This is needed if you want line number information in the stacks</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Loader</span> <span class="p">|</span>                   <span class="c1">// You must include loader events as well to resolve JIT compiled code.</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Stack</span>
</span></span><span class="line"><span class="cl">        <span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// this provider will send events of already JITed methods</span>
</span></span><span class="line"><span class="cl">    <span class="n">session</span><span class="p">.</span><span class="n">EnableProvider</span><span class="p">(</span><span class="n">ClrRundownTraceEventParser</span><span class="p">.</span><span class="n">ProviderGuid</span><span class="p">,</span> <span class="n">TraceEventLevel</span><span class="p">.</span><span class="n">Informational</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">ulong</span><span class="p">)(</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Jit</span> <span class="p">|</span>              <span class="c1">// We need JIT events to be rundown to resolve method names</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">JittedMethodILToNativeMap</span> <span class="p">|</span> <span class="c1">// This is needed if you want line number information in the stacks</span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">Loader</span> <span class="p">|</span>           <span class="c1">// As well as the module load events.  </span>
</span></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span><span class="p">.</span><span class="n">Keywords</span><span class="p">.</span><span class="n">StartEnumeration</span>   <span class="c1">// This indicates to do the rundown now (at enable time)</span>
</span></span><span class="line"><span class="cl">        <span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>The kernel provider needs to be enabled with the <strong>ImageLoad</strong> and <strong>Process</strong> keywords in order to let TraceEvent detect when a process loads “images” (i.e. dlls) and at which address (needed to convert Relative Virtual Addresses (RVA) to addresses in the address space). Note that this provider must be enabled before any other provider or your code will trigger an exception.</li>
<li>The CLR provider needs to be enabled with <strong>Jit</strong>, <strong>JittedMethodILToNativeMap</strong>, and <strong>Loader</strong> (in addition to the usual <strong>GC</strong> one).</li>
<li>The <strong>Stack</strong> keyword has to be set on the same CLR provider to receive call stacks events for “normal” CLR event (more on this later)</li>
<li>The CLR Rundown provider is enabled with the same <strong>Jit</strong>, <strong>JittedMethodILToNativeMap</strong>, and <strong>Loader</strong> keywords. That way, JIT events corresponding to <em>already</em> JITted methods will be received (not only the new ones). This is important because otherwise, you won’t be able to map these methods with the address in memory of their JITted native code in the case of processes that have been started before the profiler. This is the case for my AllocationTickProfiler sample.</li>
</ul>
<h2 id="callstacks-andsymbols">Callstacks and symbols</h2>
<p>Now, when an <strong>AllocationTick</strong> event is received, calling the <code>CallStack</code> extension method on the <code>GCAllocationTickTraceData</code> argument returns a <code>TraceCallStack</code> object. <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/TraceLog.cs#L7501">This class</a> is a linked list of <code>TraceCodeAddress</code> representing each stack frame (i.e. address in assembly code). These classes are at the heart of TraceEvent and Perfview callstack management. The method names and signatures are retrieved behind the scene thanks to JIT events and the <code>SymbolReader</code><a href="https://github.com/microsoft/perfview/blob/01b14294ca97b8f3bb2534624fb9cf2405881193/src/TraceEvent/Symbols/SymbolReader.cs#L21"> class</a> that digs into .pdb files.</p>
<p>You first need to initialize a <code>SymbolReader</code> instance:</p>
<ul>
<li>Set the path to find the .pdb; including the Microsoft HTTP endpoint for public .NET versions symbols,</li>
<li>Allow pdb next to the executable to be loaded.</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="c1">// By default a symbol Reader uses whatever is in the _NT_SYMBOL_PATH variable.  However you can override</span>
</span></span><span class="line"><span class="cl"><span class="c1">// if you wish by passing it to the SymbolReader constructor.  Since we want this to work even if you </span>
</span></span><span class="line"><span class="cl"><span class="c1">// have not set an _NT_SYMBOL_PATH, so we add the Microsoft default symbol server path to be sure/</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">symbolPath</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolPath</span><span class="p">(</span><span class="n">SymbolPath</span><span class="p">.</span><span class="n">SymbolPathFromEnvironment</span><span class="p">).</span><span class="n">Add</span><span class="p">(</span><span class="n">SymbolPath</span><span class="p">.</span><span class="n">MicrosoftSymbolServerPath</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">_symbolReader</span> <span class="p">=</span> <span class="k">new</span> <span class="n">SymbolReader</span><span class="p">(</span><span class="n">_symbolLookupMessages</span><span class="p">,</span> <span class="n">symbolPath</span><span class="p">.</span><span class="n">ToString</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// By default the symbol reader will NOT read PDBs from &#39;unsafe&#39; locations (like next to the EXE)  </span>
</span></span><span class="line"><span class="cl"><span class="c1">// because hackers might make malicious PDBs. If you wish ignore this threat, you can override this</span>
</span></span><span class="line"><span class="cl"><span class="c1">// check to always return &#39;true&#39; for checking that a PDB is &#39;safe&#39;.  </span>
</span></span><span class="line"><span class="cl"><span class="n">_symbolReader</span><span class="p">.</span><span class="n">SecurityCheck</span> <span class="p">=</span> <span class="p">(</span><span class="n">path</span> <span class="p">=&gt;</span> <span class="kc">true</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Then, displaying a <code>TraceCallStack</code> from a received CLR event in a human-readable format is simple:</p>
<ul>
<li>Get one frame after the other from the linked list,</li>
<li>If the <code>CodeAddress</code> field is not cached yet, load the symbols for its module,</li>
<li>Display the <code>FullMethodName</code> field of the frame (or the address if not found).</li>
</ul>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">DumpStack</span><span class="p">(</span><span class="n">TraceCallStack</span> <span class="n">callStack</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">callStack</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">codeAddress</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">CodeAddress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">codeAddress</span><span class="p">.</span><span class="n">Method</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="kt">var</span> <span class="n">moduleFile</span> <span class="p">=</span> <span class="n">codeAddress</span><span class="p">.</span><span class="n">ModuleFile</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">moduleFile</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">Debug</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;Could not find module for Address 0x{codeAddress.Address:x}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">            <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="p">{</span>
</span></span><span class="line"><span class="cl">                <span class="n">codeAddress</span><span class="p">.</span><span class="n">CodeAddresses</span><span class="p">.</span><span class="n">LookupSymbolsForModule</span><span class="p">(</span><span class="n">_symbolReader</span><span class="p">,</span> <span class="n">moduleFile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(!</span><span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">codeAddress</span><span class="p">.</span><span class="n">FullMethodName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;     {codeAddress.FullMethodName}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">else</span>
</span></span><span class="line"><span class="cl">            <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;     0x{codeAddress.Address:x}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">callStack</span> <span class="p">=</span> <span class="n">callStack</span><span class="p">.</span><span class="n">Caller</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Note that the first frame in the linked list is the last on the stack (i.e. last executed method).</p>
<p>As I mentioned at the beginning of the post, I have been facing OutOfMemory errors due to the TraceEvent symbols management large memory usage when a few other .NET applications were running. Let’s see how to get the call stacks in a less memory consuming way.</p>
<h2 id="manually-rebuilding-the-allocations-callstack">Manually rebuilding the allocations call stack</h2>
<p>Instead of using the call stack and symbol management provided by <code>TraceLog</code> in TraceEvent, I would prefer to manually get them. If you remember the <a href="/posts/2020-04-18_build-your-own-net/">last post</a>, thanks to <strong>GCSampledObjectAllocation</strong> CLR events, it is possible to have a sampling of the allocation size and count per process and per type. What I would like to add to the type picture is the list of call stacks leading to these allocations.</p>
<h2 id="how-to-manually-get-clr-events-callstack">How to manually get CLR events call stack</h2>
<p>The first step is to understand how to get the CLR events call stacks. If you use the <code>TraceLog</code>-based code just presented, you should see the following kind of call stack:</p>
<p><img loading="lazy" src="/posts/2020-05-18_build-your-own-net/1_oZ7Y3712aL4ELCpUixnXrw.png"></p>
<p>The <code>ETWCallout</code> <a href="https://github.com/dotnet/runtime/blob/5178041776634bfbc4f868425710e60d95f7066f/src/coreclr/src/vm/eventtrace.cpp#L4423">CLR helper function</a> is in charge of sending a special event containing the call stack of other normal events from the four supported CLR providers. If you set the <strong>Stack</strong> keyword to the CLR provider, each time an event is sent by a thread, a <strong>ClrStackWalk</strong> event will be sent just after. It means after each <strong>SampleObjectAllocation</strong> event, a <strong>ClrStackWalk</strong> event containing the call stack will be immediately received. In fact, since an application will probably be using more than one thread, it is required to do the mapping between the two events on a per-thread basis.</p>
<p>Each allocation event received by the <code>OnSampleObjectAllocation</code> handler contains the <code>ThreadID</code> property so it is easy to keep track of the last received allocation event per thread. In my case, the <code>ProcessAllocations</code> class stores this information in its <code>_perThreadLastAllocation</code> field:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">ProcessAllocations</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="n">AllocationInfo</span><span class="p">&gt;</span> <span class="n">_allocations</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">AllocationInfo</span><span class="p">&gt;</span> <span class="n">_perThreadLastAllocation</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Now, each time a <strong>SampleObjectAllocation</strong> event is received, the id of the sending thread is passed to the updated<code>ProcessAllocations.AddAllocation()</code> method:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">AllocationInfo</span> <span class="n">AddAllocation</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">size</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">count</span><span class="p">,</span> <span class="kt">string</span> <span class="n">typeName</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(!</span><span class="n">_allocations</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">typeName</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">info</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">info</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AllocationInfo</span><span class="p">(</span><span class="n">typeName</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_allocations</span><span class="p">[</span><span class="n">typeName</span><span class="p">]</span> <span class="p">=</span> <span class="n">info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">AddAllocation</span><span class="p">(</span><span class="n">size</span><span class="p">,</span> <span class="n">count</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// the last allocation is still here without the corresponding stack</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_perThreadLastAllocation</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">pid</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">lastAlloc</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;no stack for the last allocation&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// keep track of the allocation for the given thread</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// --&gt; will be used when the corresponding call stack event will be received</span>
</span></span><span class="line"><span class="cl">    <span class="n">_perThreadLastAllocation</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span> <span class="p">=</span> <span class="n">info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>_perThreadLastAllocation</code> dictionary stores the <code>AllocationInfo</code> per thread. If an allocation happens, it is added into the dictionary. When a <strong>ClrStackWalk</strong> event is received for a given thread, the stack will be associated with the last <code>AllocationInfo</code> and removed from the dictionary. If some events are missed (it never happens during my tests but who knows), error message could be logged.</p>
<p>The <code>ClrStackWalkTraceData</code> argument received by the <strong>ClrStackWalk</strong> listener has a <code>FrameCount</code> property that returns the number of frames in the call stack. In addition, its <code>InstructionPointer()</code> method takes a frame position in the stack (starting at 0) and returns the address (in assembly code) at this position on the call stack.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">OnClrStackWalk</span><span class="p">(</span><span class="n">ClrStackWalkTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">FilterOutEvent</span><span class="p">(</span><span class="n">data</span><span class="p">))</span> <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">callstack</span> <span class="p">=</span> <span class="n">BuildCallStack</span><span class="p">(</span><span class="n">data</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">GetProcessAllocations</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</span><span class="p">).</span><span class="n">AddStack</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ThreadID</span><span class="p">,</span> <span class="n">callstack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">AddressStack</span> <span class="n">BuildCallStack</span><span class="p">(</span><span class="n">ClrStackWalkTraceData</span> <span class="n">data</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">length</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">FrameCount</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">AddressStack</span> <span class="n">stack</span> <span class="p">=</span> <span class="k">new</span> <span class="n">AddressStack</span><span class="p">(</span><span class="n">length</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// frame 0 is the last frame of the stack (i.e. last called method)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">length</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">stack</span><span class="p">.</span><span class="n">AddFrame</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">InstructionPointer</span><span class="p">(</span><span class="n">i</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">stack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>AddressStack</code> class returned by <code>BuildCallStack</code> stores the frames as a list of addresses so it can be stored in <code>AllocationInfo</code>.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span><span class="lnt">23
</span><span class="lnt">24
</span><span class="lnt">25
</span><span class="lnt">26
</span><span class="lnt">27
</span><span class="lnt">28
</span><span class="lnt">29
</span><span class="lnt">30
</span><span class="lnt">31
</span><span class="lnt">32
</span><span class="lnt">33
</span><span class="lnt">34
</span><span class="lnt">35
</span><span class="lnt">36
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">AddressStack</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// the first frame is the address of the last called method </span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;</span> <span class="n">_stack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">AddressStack</span><span class="p">(</span><span class="kt">int</span> <span class="n">capacity</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_stack</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;(</span><span class="n">capacity</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// No need to override GetHashCode because we don&#39;t want to use it as a key in a dictionary</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kd">override</span> <span class="kt">bool</span> <span class="n">Equals</span><span class="p">(</span><span class="kt">object</span> <span class="n">obj</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">obj</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">stack</span> <span class="p">=</span> <span class="n">obj</span> <span class="k">as</span> <span class="n">AddressStack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">stack</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">frameCount</span> <span class="p">=</span> <span class="n">_stack</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">frameCount</span> <span class="p">!=</span> <span class="n">stack</span><span class="p">.</span><span class="n">_stack</span><span class="p">.</span><span class="n">Count</span><span class="p">)</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">frameCount</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="k">if</span> <span class="p">(</span><span class="n">_stack</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">!=</span> <span class="n">stack</span><span class="p">.</span><span class="n">_stack</span><span class="p">[</span><span class="n">i</span><span class="p">])</span> <span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">IReadOnlyList</span><span class="p">&lt;</span><span class="kt">ulong</span><span class="p">&gt;</span> <span class="n">Stack</span> <span class="p">=&gt;</span> <span class="n">_stack</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="k">void</span> <span class="n">AddFrame</span><span class="p">(</span><span class="kt">ulong</span> <span class="n">address</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">_stack</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">address</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This class overrides the <code>Equals</code> method for a single reason: I want to be able to detect when the “same” stack (i.e. with the exact same frame addresses) is received for a given type allocation. That way, I just need to keep a counter for each different <code>AddressStack</code> and not all call stacks in <code>AllocationInfo</code>. Remember that <code>AllocationInfo</code> is used to keep track of allocations per type:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">AllocationInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="kt">string</span> <span class="n">_typeName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">ulong</span> <span class="n">_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">ulong</span> <span class="n">_count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">StackInfo</span><span class="p">&gt;</span> <span class="n">_stacks</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <code>StackInfo</code> class contains an <code>AddressStack</code> and how many times it led to this type of allocation.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">class</span> <span class="nc">StackInfo</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="k">readonly</span> <span class="n">AddressStack</span> <span class="n">_stack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="kt">ulong</span> <span class="n">Count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">internal</span> <span class="n">StackInfo</span><span class="p">(</span><span class="n">AddressStack</span> <span class="n">stack</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">Count</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_stack</span> <span class="p">=</span> <span class="n">stack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">AddressStack</span> <span class="n">Stack</span> <span class="p">=&gt;</span> <span class="n">_stack</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>So, when a stack event is received, <code>AddStack</code> is called on the last <code>AllocationInfo</code> for the same thread:</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt">1
</span><span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span><span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="k">void</span> <span class="n">AddStack</span><span class="p">(</span><span class="kt">int</span> <span class="n">tid</span><span class="p">,</span> <span class="n">AddressStack</span> <span class="n">stack</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_perThreadLastAllocation</span><span class="p">.</span><span class="n">TryGetValue</span><span class="p">(</span><span class="n">tid</span><span class="p">,</span> <span class="k">out</span> <span class="kt">var</span> <span class="n">lastAlloc</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">lastAlloc</span><span class="p">.</span><span class="n">AddStack</span><span class="p">(</span><span class="n">stack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_perThreadLastAllocation</span><span class="p">.</span><span class="n">Remove</span><span class="p">(</span><span class="n">tid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The job of <code>AllocationInfo.AddStack()</code> the method is to check if a previous allocation was made with the same call stack (hence the <code>Equals</code> override). If this is the case, just increment the corresponding <code>StackInfo</code> count. Otherwise, create a new <code>StackInfo</code> for this call stack with a count set to 1.</p>
<div class="highlight"><div class="chroma">
<table class="lntable"><tr><td class="lntd">
<pre tabindex="0" class="chroma"><code><span class="lnt"> 1
</span><span class="lnt"> 2
</span><span class="lnt"> 3
</span><span class="lnt"> 4
</span><span class="lnt"> 5
</span><span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span><span class="lnt">12
</span><span class="lnt">13
</span><span class="lnt">14
</span><span class="lnt">15
</span><span class="lnt">16
</span><span class="lnt">17
</span><span class="lnt">18
</span><span class="lnt">19
</span><span class="lnt">20
</span><span class="lnt">21
</span><span class="lnt">22
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">internal</span> <span class="k">void</span> <span class="n">AddStack</span><span class="p">(</span><span class="n">AddressStack</span> <span class="n">stack</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">info</span> <span class="p">=</span> <span class="n">GetInfo</span><span class="p">(</span><span class="n">stack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">info</span> <span class="p">==</span> <span class="kc">null</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">info</span> <span class="p">=</span> <span class="k">new</span> <span class="n">StackInfo</span><span class="p">(</span><span class="n">stack</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_stacks</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="n">info</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">info</span><span class="p">.</span><span class="n">Count</span><span class="p">++;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kd">private</span> <span class="n">StackInfo</span> <span class="n">GetInfo</span><span class="p">(</span><span class="n">AddressStack</span> <span class="n">stack</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">i</span> <span class="p">&lt;</span> <span class="n">_stacks</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">i</span><span class="p">++)</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="kt">var</span> <span class="n">info</span> <span class="p">=</span> <span class="n">_stacks</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">stack</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="n">info</span><span class="p">.</span><span class="n">Stack</span><span class="p">))</span> <span class="k">return</span> <span class="n">info</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Knowing the address in code of each frame for all events call stack is nice but it would be much more useful to translate them into method names… You have to deal with two different cases: managed and native methods. I will cover these topics in the next episode.</p>
<h2 id="resources">Resources</h2>
<ul>
<li>Source code available <a href="https://github.com/chrisnas/ClrEvents">on Github</a>.</li>
<li>TraceEvent <a href="https://github.com/microsoft/perfview/blob/master/src/TraceEvent/Samples/41_TraceLogMonitor.cs#L204">sample 41</a> source code.</li>
</ul>
<hr>
<p>Missed the first part of this story? Check this out:</p>
<p><a href="/posts/2020-04-18_build-your-own-net/"><strong>Build your own .NET memory profiler in C#</strong>
*This post explains how to collect allocation details by writing your own memory profiler in C#.*medium.com</a></p>
<hr>
<p><strong>Interested in joining our journey? Check this out:</strong></p>
<p><a href="https://careers.criteo.com/working-in-R&amp;D"><strong>Product, Research &amp; Development | Criteo Careers</strong>
careers.criteo.com</a><a href="https://careers.criteo.com/working-in-R&amp;D"></a></p>
]]></content:encoded></item><item><title>Let’s debug the Core CLR with WinDBG!</title><link>https://chrisnas.github.io/posts/2019-04-04_let-debug-the-core/</link><pubDate>Thu, 04 Apr 2019 13:32:51 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-04-04_let-debug-the-core/</guid><description>This post of the series shows how you could easily debug the Core CLR in a real world case of insane thread contention duration.</description><content:encoded><![CDATA[<hr>
<p>This post of the series shows how we debugged the Core CLR to figure out insane contention duration.</p>
<p>Part 1: <a href="/posts/2018-06-19_replace-net-performance-counters/">Replace .NET performance counters by CLR event tracing</a>.</p>
<p>Part 2: <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a>.</p>
<p>Part 3: CLR Threading events with TraceEvent.</p>
<p>Part 4: <a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a>.</p>
<p>Part 5: <a href="/posts/2019-02-12_building-your-own-java/">Building your own Java GC logs in .NET</a>.</p>
<h2 id="introduction">Introduction</h2>
<p>Long before migrating our .NET applications to Linux, our first step was to build a monitoring pipeline based on LTTng instead of ETW on Windows. To achieve this goal, the open source TraceEvent Nuget package needed to be updated in order to listen to LTTng live session (only a file based implementation was provided by Microsoft; mostly to allow Perfview to be able to open traces taken on Linux machines). This was a <a href="https://github.com/criteo-forks/perfview/pull/1">huge development task</a> that led sometimes to weird results. Among the metrics we wanted to monitor, the contention duration gave insane value such as thousands of minutes… per minute:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_chjw_0ZlNI1GH6wc2tgNBg.png"></p>
<p>As shown in <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">a previous episode</a>, this duration is computed by comparing the time between the two events <strong>ContentStart</strong> and <strong>ContentionStop</strong>. What could be the possible reasons to get such insane values?</p>
<ol>
<li>
<p>A lot of small contentions are happening</p>
</li>
<li>
<p>A few very long contentions are happening</p>
</li>
</ol>
<p>As a first step, it would be great to be able to debug the Core CLR and figure out what call stacks end up to triggering these contention events. Unfortunately for us, the .NET debugging ecosystem on Linux is far from being as rich as on Windows. So this episode is detailing the steps to compile and debug the Core CLR on Windows with WinDBG.</p>
<h2 id="from-the-source-to-debugging-theruntime">From the source to debugging the runtime</h2>
<p>To better understand the implementation details in the CLR, we needed to find where the two events are emitted. In fact, during the CLR compilation, a lot of helpers are created based on the name of the event. In our case, <code>FireEtwContentionStart_V1</code> and <code>FireContentionStop</code> are the two helpers in charge. Both are called <a href="https://github.com/dotnet/coreclr/blob/master/src/vm/syncblk.cpp#L2993">in the <strong>AwareLock::EnterEpilogHelper</strong> function</a>.</p>
<p>As a Windows developer, I would like to debug the CLR code and set a breakpoint in the <code>EnterEpilogHelper</code> with Visual Studio to see what are the call stacks that end up to contention. However, I did not find a way to do it with Visual Studio. I turned to WinDBG and things gets “easier”… in a certain way.</p>
<p>Here are the different steps you need to follow before setting a breakpoint on any Core CLR function in WinDBG:</p>
<ul>
<li>Clone the Core CLR repository from <a href="https://github.com/dotnet/coreclr">https://github.com/dotnet/coreclr</a></li>
<li>Build it:</li>
<li>Get the Visual Studio, .NET Core SDK, CMake, Python, Powershell prerequisites from <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/building/windows-instructions.md">the documentation</a></li>
<li>Goto the root folder and type <code>.\build -skiptests</code> to build a DEBUG version of the Core CLR</li>
<li>Leave your desk and go to lunch (ok… maybe just take a coffee break)</li>
</ul>
<ol start="3">
<li>When you go back, the result of the compilation should be available in the following folder:</li>
</ol>
<p>…\coreclr\bin\Product\Windows_NT.x64.debug.</p>
<ol start="4">
<li>the next step is to <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/workflow/UsingYourBuild.md">use your custom Core CLR build</a> in the application:</li>
</ol>
<ul>
<li>the application must be <a href="https://docs.microsoft.com/en-us/dotnet/core/deploying/#self-contained-deployments-scd">self-contained</a> by adding <code>win-x64</code> (or linux-x64 for Linux) in a <code>PropertyGroup</code> section of the .csproj.</li>
<li>publish the application by running <code>dotnet publish</code> or from within Visual Studio</li>
</ul>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_Xvue5jy9443m9Zjih19cQw.png"></p>
<ul>
<li>Click the <em>Configure</em> link and select Debug configuration</li>
</ul>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_UDj6pcKSZutk3E7Kbvkfjw.png"></p>
<ul>
<li>after clicking <em>Save</em> and <em>Publish</em>, you should now have the result under the \bin\Debug\netcoreapp2.2\publish folder.</li>
<li>after clicking <em>Save</em> and <em>Publish</em>, you should now have the result under the \bin\Debug\netcoreapp2.2\publish folder.</li>
</ul>
<ol start="5">
<li>It is now time to copy the following files from the Core CLR output to your application publication folder:</li>
</ol>
<ul>
<li>coreclr.dll (for the native part of the CLR) and System.Private.CoreLib.dll (if the CLR C# code has been modified)</li>
<li>in the PDB subfolder, coreclr.pdb and System.Private.CoreLib.pdb</li>
<li>note that you might also need the sos.dll and mscordaccore.dll files for any investigation in WinDBG.</li>
</ul>
<p>If you wonder why the CoreFx repo is not rebuilt, the answer is simple: the contention related code is in the CoreCLR. Also, most of the managed “mscorlib” is defined in System.Private.CoreLib.dll that gets built with CoreCLR. The rest of the BCL is covered by CoreFX and not needed in this investigation.</p>
<h2 id="from-running-to-debugging-inwindbg">From running to debugging in WinDBG</h2>
<p>You should use <a href="https://github.com/dotnet/coreclr/blob/master/Documentation/workflow/UsingCoreRun.md">corerun.exe</a> instead of dotnet.exe to run an application with the debug version of the Core CLR you’ve just built.</p>
<p>Open up a command prompt in the <strong>coreclr\bin\Product\Windows_NT.x64.debug</strong> folder and type <code>corerun</code>** c:&lt;your path to the <strong>bin\Debug\netcoreapp2.2\publish</strong> folder of your application&gt;&lt;yourApp.dll&gt;**</p>
<p>You have to tell <code>corerun</code> where to find the CoreFx assemblies via the <code>CORE_LIBRARY</code> environment variable:</p>
<p><code>CORE_LIBRARIES=C:\Program Files\dotnet\shared\Microsoft.NETCore.App\2.2.0</code></p>
<p>If you forget about it, don’t be surprised if the application stops with <code>FileNoteFoundException</code> for a missing assembly (usually <strong>System.Runtime</strong>)…</p>
<p>If, like me, your applications are running with server mode GC, you know that it is set in the application .csproj file to end up into the runtimeconfig.json file. Unfortunately, this is not taken into account by <code>corerun</code> (yet?) and you need to set it (and if you need concurrent version too) explicitly through the following environment variables:</p>
<p><code>COMPlus_gcServer=1 COMPlus_gcConcurrent=1</code></p>
<p>From there, (<a href="https://www.microsoft.com/en-us/p/windbg-preview/9pgjgd53tn86">install WinDBG if not already done</a> and) start the debugger: click the <em>File</em> menu and select <em>Launch Executable (advanced)</em> to setup a debugging session:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_ZyuzZ_qKw_bhpnCfq0WTDA.png"></p>
<p>The <em>Executable</em> text field points to the <strong>corerun.exe</strong> file generated during the compilation of the Core CLR. The same folder is used as <em>Start Directory</em> and the <em>Arguments</em> text field contains the full path of the application to debug. You could also attach to a running process but sometimes you need to access Core CLR data structures before any C#-compiled managed code of your application starts executing (to see how the garbage collector initializes for example).</p>
<p>As soon as you click the <em>Ok</em> button, the application starts but is almost immediately stopped by WinDBG</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_FFwwuUz6_xeF2AYBxn8K-A.png"></p>
<p>Don’t be scared by the last lines of the output: even through you read the word <strong>exception</strong>, this <code>int 3</code>** **assembly instruction tells you that a breakpoint has been set for you by WinDBG, has been hit when the application reached it and the application is now paused just before calling its entry point.</p>
<p>As you can see from the list of loaded modules, even though CoreRun.exe is there, no managed assembly (especially your application) has been loaded yet; not even the Core CLR itself! This means that you have to tell WinDBG to keep on executing the application until a point you would be interested in. To achieve that task, you will first need a quick tour of WinDBG user interface even though this post is not there to replace the <a href="https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/debugging-using-windbg-preview">WinDBG online help</a> nor provide a detailed walkthrough.</p>
<p>The debugging section of the <em>Home</em> tab is not too different from what you get in Visual Studio:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_h5nRlDCO_PXfonPtcVwFgg.png"></p>
<p>The icons are even easier to understand because their action is also displayed. If you want to see the current call stack, select the <em>View</em> tab and click the <strong>Stack</strong> icon:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_UgBlXPkw57Gja-_ANnkfTw.png"></p>
<p>Like in Visual Studio, you are able to pin each panel wherever you want into the IDE</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_kqvC1tJsfN_lpaD6rclGjA.png"></p>
<p>The next step would be to set a breakpoint on the line of code you are interested in. But let’s be clear here: I’m talking about a line of code in a function exported by a native dll; not a line of C# code in a managed assembly. Remember that WinDBG is a native debugger and it debugs only native code. If you want to debug managed code with WinDBG, you need to use commands from the sos extension; but <a href="https://docs.microsoft.com/en-us/archive/blogs/tess/setting-breakpoints-in-net-code-using-bpmd?WT.mc_id=DT-MVP-5003325">this is another story</a>.</p>
<p>So let’s go back to the native world. Even though WinDBG does not have the notion of “solution” like Visual Studio provides, it is still possible to open a C++ file and set a breakpoint in it. Click <em>File |Open Source File</em> menu and go to your Core CLR github repo to select syncblk.cpp under the \src\vm folder. Look for <code>AwareLock::EnterEpilogHelper</code> with CTRL+F (yes: search is working in WinDBG) and go down to the call to the <code>FireEtwContentionStart_V1</code> helper. Setting a breakpoint on this line is as simple as pressing **F9 **like in Visual Studio. Press the <em>View</em> tab and click the <em>Breakpoint</em> button to see the result:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_O1BnT-PQKdoFGo0NxyiOtw.png"></p>
<p>Since the dll in which the breakpoint is set is not loaded yet, you can’t see the details of the breakpoint.</p>
<p>There is a way to tell WinDBG to continue the execution of the application until a dll get’s loaded. For coreclr.dll, type the following command:</p>
<p><code>sxeld:coreclr</code></p>
<p>and type <strong>F5</strong> (or type <code>g</code> as a command or click the green triangle in the <em>Home</em> toolbar) to resume the execution of the application. The <em>Breakpoints</em> panel shows more details now:</p>
<p><img loading="lazy" src="/posts/2019-04-04_let-debug-the-core/1_SCPPNlsp-9Oqp0uk2LcTUw.png"></p>
<p>Press <strong>F5</strong> to resume the execution and the breakpoint should be triggered when the first contention happens.</p>
<h2 id="from-symbols-to-call-stacks-inwindbg">From symbols to call stacks in WinDBG</h2>
<p>Before digging into call stacks, I would like to show you one of the differences between native dlls and managed assemblies. As a .NET developer, you are used to Intellisense and strongly typed environment provided by the metadata stored in an assembly itself. For native dll, the story is different. Exported functions are visible with tools such as <a href="http://www.dependencywalker.com/">Dependency Walker</a> or <code>dumpbin /exports</code> from the SDK. If the dll exports symbols built by the C++ compiler, their name gets <em>mangled</em> to describe their signature. To get human readable symbols, you need the associated .pdb file. It will also be required to map a function address to its name in call stacks.</p>
<p>WinDBG allows you to browse these symbols with the <code>dt</code> command. For example, if you want to know all members defined by the <strong>AwareLock</strong> class, use the following command:</p>
<p><code>dt CoreClr!AwareLock::*</code></p>
<p>Like what was shown in the previous <em>Breakpoints</em> screenshot, the prefix of a name is the dll in which the symbol is defined. Next, use <code>!</code> as separator before the class name. Since Visual Studio is really slow to navigate the source code of the Core CLR or search in the thousands of include and C/C++ files, this is a very convenient way to navigate and learn its different parts. Don’t forget that the compilation could also inline functions (that won’t be visible in the symbols) and expand macros.</p>
<p>If you want to set a breakpoint on a function, use the <code>bp</code> command with the same syntax as <code>dt</code>. For example, the following command:</p>
<p><code>bp coreClr!AwareLock::EnterEpilogHelper</code></p>
<p>sets a breakpoint at the beginning of the function in which I already set a breakpoint.</p>
<p>This is the very basics of breakpoints in WinDBG. You are also able to define which actions to start when a breakpoint is hit. This is extremely powerful! For example, in the case of thread contention, you typically don’t want to stop the execution of the application because it will pause all threads and disturb the normal flow of execution that could lead to thread contention. Instead, you could ask WinDBG to dump the call stack leading to the function we are interested in and lets the execution resume with the following syntax:</p>
<p><code>bp coreClr!AwareLock::EnterEpilogHelper &quot;!clrstack; g&quot;</code></p>
<p>The commands to execute after the breakpoint is hit are defined between quotes. In this example, I’m using the <code>clrstack</code> command exported by the sos.dll extension (that must be previously loaded via <code>.loadby sos coreclr</code>) and once it is done, <code>g</code> resumes the execution.</p>
<h2 id="whats-next">What’s next?</h2>
<p>Due to automatic suspension of all threads when the <code>clrstack</code> command gets executed (before <code>g</code> resumes), the interactions between threads are not the same as normal execution outside of a debugger. I have even used <a href="https://github.com/criteo-forks/coreclr/commit/7394345097a78c7be3241939d357595ebad9b26a">some code available in DEBUG to dump the callstacks outside of a debugger</a> if the contention last more than a threshold. However, it was not possible to reproduce the problem on Windows.</p>
<p>In parallel on Linux, another colleague investigated another lead: some events may also be skipped by our LTTng implementation. Due to complicated event management, if a <strong>ContentionStop</strong> and <strong>ContentionStart</strong> are missed, a possible previous <strong>ContentionStart</strong> could be used by the next <strong>ContentionStop</strong> and the duration would be unrelated to the real contention that happened.</p>
<p>So there could be a simpler way to narrow down the issue: instead of relying on two events, why not simply compute the duration of the contention in the <code>AwareLock::EnterEpilogHelper</code> function and emit only one new event with the duration as payload? Well… this will be the topic of the next episode of this series.</p>
<h2 id="references">References</h2>
<p>Series of videos from the Defrag Tools show where <a href="https://twitter.com/maoni0">Maoni Stephens</a> explains how to debug the Garbage Collector for a better understanding of its arcana</p>
<ul>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-33-CLR-GC-Part-1">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-33-CLR-GC-Part-1</a></li>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-34-CLR-GC-Part-2">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-34-CLR-GC-Part-2</a></li>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-35-CLR-GC-Part-3">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-35-CLR-GC-Part-3</a></li>
<li><a href="https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-36-CLR-GC-Part-4">https://channel9.msdn.com/Shows/Defrag-Tools/Defrag-Tools-36-CLR-GC-Part-4</a></li>
</ul>
]]></content:encoded></item></channel></rss>