<?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>C++ on Welcome to Christophe Nasarre's Blog</title><link>https://chrisnas.github.io/tags/c++/</link><description>Recent content in C++ 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/c++/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>Trigger your GCs with dotnet-fullgc!</title><link>https://chrisnas.github.io/posts/2024-05-22_trigger-your-gcs-with/</link><pubDate>Wed, 22 May 2024 18:03:17 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-05-22_trigger-your-gcs-with/</guid><description>This post explains how I wrote a CLI tool that triggers a GC in any .NET application</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>If you have read Microsoft documentation, you probably know that it is not recommended to trigger a garbage collection in your application code. However, in some troubleshooting cases, you might want to trigger a GC. For example, you don’t want to wait for a full gen2 compacting GC to figure out if your application is really leaking memory. For web applications, you can imagine having a hidden HTTP end point that simply call <strong>GC.Collect</strong>. What if you could simply call a command line tool to trigger a GC in any .NET application? This is exactly what my new dotnet-fullgc CLI tool is doing!</p>
<h2 id="this-is-theway">This Is The Way</h2>
<p>If you have read a few of my past posts about <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">the .NET diagnostics mechanisms</a>, you know that you can send commands to another .NET process via EventPipes. Well… there is no explicit command to trigger a GC.</p>
<p>I have also explained how you could listen to events emitted by the CLR by enabling a provider with a set of keywords with a verbosity corresponding to the events you are interested in. This is how dotnet-trace and Perfview are collecting these events. If you want to trigger a GC, you simply need to enable the <strong>Microsoft-Windows-DotNETRuntime</strong> provider with the <strong>GCHeapCollect</strong> (= 0x800000L) keyword and an informal verbosity. Yes: it is as simple as that, and it is also working for .NET Framework!</p>
<p>So, you could trigger a GC with dotnet-trace via the following command line:</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-bash" data-lang="bash"><span class="line"><span class="cl">dotnet trace collect -p &lt;process id&gt; - providers Microsoft-Windows-DotNETRuntime:0x800000:4 - duration 00:00:01
</span></span></code></pre></td></tr></table>
</div>
</div><p>However, a .nettrace file would be generated and it is not possible to pass parameters (more on this later).</p>
<p>The rest of this post shows the C# code to obtain the same result. With <strong>Microsoft.Diagnostics.NETCore.Client</strong> and <strong>TraceEvent</strong>, you create a <strong>DiagnosticsClient</strong> with the ID of the process 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></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">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">processId</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step is to start an EventPipe session with the right provider, keyword and verbosity:</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="kt">var</span> <span class="n">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">EventPipeProvider</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">new</span> <span class="n">EventPipeProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventLevel</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">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">GCHeapCollect</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">Arguments</span>  <span class="c1">// more on this later</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">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">providers</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></code></pre></td></tr></table>
</div>
</div><p>The source must be processed in another thread to avoid blocking the main 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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Task</span><span class="w"> </span><span class="n">streamTask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Task</span><span class="p">.</span><span class="na">Run</span><span class="p">(()</span><span class="w"> </span><span class="o">=&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="c1">// without source to process, session.Stop() will not return</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="kd">var</span><span class="w"> </span><span class="n">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">new</span><span class="w"> </span><span class="n">EventPipeEventSource</span><span class="p">(</span><span class="n">session</span><span class="p">.</span><span class="na">EventStream</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">source</span><span class="p">.</span><span class="na">Process</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>The question to answer is how to stop the session: just create another task that waits for a second before stopping the session to exit from the <strong>Process</strong> call:</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-java" data-lang="java"><span class="line"><span class="cl"><span class="n">Task</span><span class="w"> </span><span class="n">inputTask</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Task</span><span class="p">.</span><span class="na">Run</span><span class="p">(()</span><span class="w"> </span><span class="o">=&gt;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">{</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">Thread</span><span class="p">.</span><span class="na">Sleep</span><span class="p">(</span><span class="n">1000</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="n">session</span><span class="p">.</span><span class="na">Stop</span><span class="p">();</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="p">});</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="n">Task</span><span class="p">.</span><span class="na">WaitAny</span><span class="p">(</span><span class="n">streamTask</span><span class="p">,</span><span class="w"> </span><span class="n">inputTask</span><span class="p">);</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="p">}</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>That’s all!</p>
<h2 id="pitfalls">Pitfalls</h2>
<p>Unfortunately, I faced a couple of issue during the implementation of the tool.</p>
<h2 id="what-is-yournumber">What is your number?</h2>
<p>If you look at the TraceEvent implementation, you could imagine that is it possible to pass a GC ID as a parameter to the .NET provider:</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="c1">//</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Summary:</span>
</span></span><span class="line"><span class="cl"><span class="c1">//     Triggers a GC. Can pass a 64 bit value that will be logged with the GC Start</span>
</span></span><span class="line"><span class="cl"><span class="c1">//     event so you know which GC you actually triggered.</span>
</span></span><span class="line"><span class="cl"><span class="n">GCHeapCollect</span> <span class="p">=</span> <span class="m">0x800000L</span><span class="p">,</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Unfortunately, this is not supported and the reasons are explained below.</p>
<p>If you check the .NET source code and look at <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/eventtrace.cpp#L2420">EtwCallbackCommon()</a>, you can indeed see that a numeric ID can be passed to <strong>ETW::GCLog::ForceGC(l64ClientSequenceNumber)</strong> and passed as ID in <strong>GCStart</strong> event instead of the one incrementally increased collection after collection.</p>
<p>At the diagnostics client level, you have the opportunity to pass a dictionary of key/value string pairs. This dictionary is used when defining the <strong>EventPipeProvider</strong> to be enabled:</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="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">arguments</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">string</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;();</span>
</span></span><span class="line"><span class="cl"><span class="n">arguments</span><span class="p">.</span><span class="n">Add</span><span class="p">(</span><span class="s">&#34;Id&#34;</span><span class="p">,</span> <span class="s">&#34;42&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="kt">var</span> <span class="n">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">EventPipeProvider</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">new</span> <span class="n">EventPipeProvider</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventLevel</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">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">GCHeapCollect</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">arguments</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 <a href="https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/DiagnosticsClient/EventPipeProvider.cs#L63">diagnostics client transforms</a> the dictionary into a string with key=value pairs separated by ‘;’ such as “Id=42;AnotherId=AnotherValue;…” and serializes it as is in the payload when sending a CollectTraces command.</p>
<p>The question is what identifier is expected by the CLR? To answer this question, you need to look at the <a href="https://github.com/dotnet/runtime/blob/main/src/native/eventpipe/ep-provider.c#L381">code of provider_invoke_callback</a>. The “key=value;…” string is stored into a buffer and each = and ; characters are transformed into \0. So, “Id=42” is transformed into Id\042\0.</p>
<p>The next step is done by <strong>ep_event_filter_desc_init()</strong> that uses that buffer to fill up the 3 fields of an <strong>EventFilterDescriptor</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></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="kt">uint64_t</span> <span class="n">ptr</span> <span class="o">=</span> <span class="n">address</span> <span class="n">of</span> <span class="n">the</span> <span class="n">buffer</span>
</span></span><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="n">size</span> <span class="o">=</span> <span class="n">size</span> <span class="n">of</span> <span class="n">the</span> <span class="n">buffer</span> <span class="p">(</span><span class="o">=</span><span class="mi">6</span> <span class="k">for</span> <span class="n">id</span><span class="err">\</span><span class="mo">042</span><span class="err">\</span><span class="mi">0</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="kt">uint32_t</span> <span class="n">type</span> <span class="o">=</span> <span class="mi">0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And finally, EtwCallbackCommon receives the filterdata and tries the following to get the collection id:</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="n">PEVENT_FILTER_DESCRIPTOR</span> <span class="n">FilterData</span> <span class="o">=</span> <span class="p">(</span><span class="n">PEVENT_FILTER_DESCRIPTOR</span><span class="p">)</span><span class="n">pFilterData</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">FilterData</span> <span class="o">!=</span> <span class="nb">NULL</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">FilterData</span><span class="o">-&gt;</span><span class="n">Type</span> <span class="o">==</span> <span class="mi">1</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">FilterData</span><span class="o">-&gt;</span><span class="n">Size</span> <span class="o">==</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">l64ClientSequenceNumber</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">l64ClientSequenceNumber</span> <span class="o">=</span> <span class="o">*</span><span class="p">(</span><span class="n">LONGLONG</span> <span class="o">*</span><span class="p">)</span> <span class="p">(</span><span class="n">FilterData</span><span class="o">-&gt;</span><span class="n">Ptr</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>As you can see, it will fail because:</p>
<ul>
<li>the received type value is 0</li>
<li>the received size is not the size of a 64 bit number</li>
<li>the ptr field does not point to the value as 64 bit number (but as a string)</li>
</ul>
<p>An <a href="https://github.com/dotnet/runtime/issues/102572">issue has been filed</a> with a possible fix.</p>
<h2 id="only-one-collection-please">Only one collection please!</h2>
<p>When I test my dotnet-fullgc with dotnet-gcstats, I always see 2 collections!</p>
<p><img loading="lazy" src="/posts/2024-05-22_trigger-your-gcs-with/1_OjENjdzCIyyrPNUiGrQEVw.png"></p>
<p>We investigated with my colleague <a href="https://x.com/KooKiz">Kevin Gosse</a> and he <a href="https://github.com/dotnet/runtime/issues/99487">created an issue</a> for that. The <strong>EtwCallback()</strong> function is called whenever a session is enabled or disabled. Unfortunately, the call to <strong>ForceGC</strong> is made in both cases: so, when the session is stopped, a second garbage collection is triggered.</p>
<h2 id="next-steps">Next steps</h2>
<p>Feel free to install dotnet-fullgc on your machine with <strong>dotnet tool install -g dotnet-fullgc</strong>.</p>
<p>Next, use <strong>dotnet fullgc <process id></strong> to trigger two gen2 full garbage collections in your running .NET processes.</p>
]]></content:encoded></item><item><title>View your GCs statistics live with dotnet-gcstats!</title><link>https://chrisnas.github.io/posts/2024-03-01_view-your-gcs-statistics/</link><pubDate>Fri, 01 Mar 2024 18:30:13 +0000</pubDate><guid>https://chrisnas.github.io/posts/2024-03-01_view-your-gcs-statistics/</guid><description>Discover how to look at the .NET GC statistics to better understand your garbage collections</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>While working on the second edition of <a href="https://www.amazon.com/Pro-NET-Memory-Management-Performance/dp/148424026X">Pro .NET Memory Management</a>, it was needed to get statistics about each garbage collection to explain the condemned generation and other decisions taken by the GC. This post explains the different internal data structures used by the GC and how to get their value for each collection. Some require debugging the CLR and others are emitted via events. For the latter, I will show how I wrote the new <strong>dotnet-gcstats</strong> CLI tool to collect them and a personal Perfview GCStats displaying live data, garbage collection after garbage collection.</p>
<h2 id="high-level-view-of-gc-internals">High level view of GC Internals</h2>
<p>With regions, the GC keeps track of managed memory allocated by your application in instances of the <strong>gc_heap</strong> class. In Workstation mode, only 1 instance exists and in Server mode, by default, 1 instance is created per core. Each <strong>gc_heap</strong> keeps track of its 5 generations (gen0, gen1, gen2, Large Object Heap and Pinned Object Heap) in an array of 5 <strong>generation</strong> instances. Each generation references its dedicated regions wrapped by instances of <strong>heap_segment</strong>. These regions are reserved from a giant part of the process address space and committed as needed.</p>
<p><img loading="lazy" src="/posts/2024-03-01_view-your-gcs-statistics/1_RVd5JiSJBJzqCqqYjUJBog.png"></p>
<p>During a garbage collection, the GC code relies on global fields per <strong>gc_heap</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></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">static</span> <span class="n">gc_mechanisms</span> <span class="n">settings</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">gc_history_global</span> <span class="n">gc_data_global</span><span class="p">;</span>  <span class="c1">// for non background GC including foreground GC during a background
</span></span></span><span class="line"><span class="cl"><span class="n">gc_history_global</span> <span class="n">bgc_data_global</span><span class="p">;</span> <span class="c1">// for background GC only
</span></span></span><span class="line"><span class="cl"><span class="k">static</span> <span class="n">dynamic_data</span> <span class="n">dynamic_data_table</span><span class="p">[</span><span class="n">total_generation_count</span> <span class="o">=</span> <span class="mi">5</span><span class="p">];</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>settings</strong> field contains a few interesting fields:</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="k">class</span> <span class="nc">gc_mechanisms</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">gc_index</span><span class="p">;</span> <span class="c1">// starts from 1 for the first GC
</span></span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">condemned_generation</span><span class="p">;</span>  <span class="c1">// generation to collect
</span></span></span><span class="line"><span class="cl">    <span class="n">BOOL</span> <span class="n">compaction</span><span class="p">;</span>  <span class="c1">// true when compaction instead of sweep
</span></span></span><span class="line"><span class="cl">    <span class="n">BOOL</span> <span class="n">loh_compaction</span><span class="p">;</span>  <span class="c1">// true when LOH needs compaction
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">concurrent</span><span class="p">;</span>  <span class="c1">// 1 = concurrent/background GC 
</span></span></span><span class="line"><span class="cl">    <span class="n">gc_reason</span> <span class="n">reason</span><span class="p">;</span>  <span class="c1">// trigger reason
</span></span></span><span class="line"><span class="cl">    <span class="n">gc_pause_mode</span> <span class="n">pause_mode</span><span class="p">;</span>  <span class="c1">// see GCSettings.LatencyMode
</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>including the trigger reason that will tell if your code called <strong>GC.Collect</strong> (i.e. induced), or if it was due to a LOH or SOH allocation for example. If <strong>compaction</strong> is true, a compacting GC will happen (instead of a sweeping one).</p>
<p>The <strong>gc/bgc_data_global</strong> contains almost the same information:</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">gc_history_global</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">num_heaps</span><span class="p">;</span>  <span class="c1">// number of gc_heap instances
</span></span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">condemned_generation</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">gc_reason</span> <span class="n">reason</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">pause_mode</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">mem_pressure</span><span class="p">;</span> 
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">global_mechanisms_p</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>Most of the fields are available from different events:</p>
<ul>
<li><strong>GCStart</strong>: <strong>gc_index</strong> in <strong>Count</strong>, <strong>condemned_generation</strong> in <strong>Depth</strong>, <strong>reason</strong> in <strong>Reason</strong></li>
<li><strong>GCGlobalHeapHistory</strong>: <strong>pause_mode</strong> in <strong>PauseMode</strong> and some others in <strong>GlobalMechanisms</strong></li>
</ul>
<h2 id="which-generation-to-collect--condemned-generation">Which generation to collect = condemned generation</h2>
<p>The computation of the <strong>condemned_generation</strong> is complicated and relies on many factors including metrics stored for each “generation” (gen0, gen1, gen2, LOH and POH) in an array of <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gcpriv.h#L1058"><strong>dynamic_data</strong></a> called <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/gc/gcpriv.h#L3627"><strong>dynamic_data_table</strong></a>. The <strong>dynamic_data</strong> class contains a few fields used by the GC to take decisions such as when a collection should be triggered and which generation to condemn:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">dynamic_data</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">ptrdiff_t</span> <span class="n">new_allocation</span><span class="p">;</span>     <span class="c1">// remaining budget = budget - allocated
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">desired_allocation</span><span class="p">;</span> <span class="c1">// budget to trigger a GC
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// # of bytes taken by survived objects after mark.
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">survived_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">// # of bytes taken by survived pinned plugs after mark.
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">pinned_survived_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">// total object size after a GC, ie, doesn&#39;t include fragmentation
</span></span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">current_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">promoted_size</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">size_t</span>    <span class="n">fragmentation</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>Most of these fields are found in the payload of <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ClrEtwAll.man#L1296">GCPerHeapHistory</a> or <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ClrEtwAll.man#L962">GCHeapStat</a> events. However, the most interesting one, <strong>new_allocation</strong> is not available. Why is it interesting? Because it would give you which generation had its budget exceeded. It is initialized with the generation budget at the end of a GC and then, each time an allocation context gets created, its size is deducted from it. When it reaches 0, it means that the budget is exceeded, and a collection should happen.</p>
<p>Since I needed to debug the CLR to better understand all these algorithms, I added a breakpoint at the beginning of <strong>gc_heap::garbage_collect</strong> with the following action:</p>
<pre tabindex="0"><code>#{settings.gc_index}[{gc_trigger_reason}]{&#34;\n&#34;,s8b} new_allocation(0) = {dynamic_data_table[0].new_allocation}{&#34;\n&#34;,s8b} desired_allocation(0) = {dynamic_data_table[0].desired_allocation}{&#34;\n&#34;,s8b} begin_data_size(0) = {dynamic_data_table[0].begin_data_size}{&#34;\n&#34;,s8b} promoted_size(0) = {dynamic_data_table[0].promoted_size}{&#34;\n&#34;,s8b}-{&#34;\n&#34;,s8b} new_allocation(1) = 
...
{dynamic_data_table[4].new_allocation}{&#34;\n&#34;,s8b} desired_allocation(4) = {dynamic_data_table[4].desired_allocation}{&#34;\n&#34;,s8b} begin_data_size(4) = {dynamic_data_table[4].begin_data_size}{&#34;\n&#34;,s8b} promoted_size(4) = {dynamic_data_table[4].promoted_size}{&#34;\n&#34;,s8b}__________{&#34;\n&#34;,s8b}
</code></pre><p>And now, each time a GC happens, I get the corresponding log in my Output pane in Visual Studio:</p>
<pre tabindex="0"><code>#2[reason_alloc_soh (0)]
 new_allocation(0) = -22728
 desired_allocation(0) = 134217728
 begin_data_size(0) = 8391376
 promoted_size(0) = 8383432
-
 new_allocation(1) = -5910416
 desired_allocation(1) = 2473016
 begin_data_size(1) = 375528
 promoted_size(1) = 353288
-
 new_allocation(2) = -91144
 desired_allocation(2) = 262144
 begin_data_size(2) = 0
 promoted_size(2) = 0
-
 new_allocation(3) = 28000088
 desired_allocation(3) = 28000088
 begin_data_size(3) = 8000024
 promoted_size(3) = 8000024
-
 new_allocation(4) = 3145728
 desired_allocation(4) = 3145728
 begin_data_size(4) = 32712
 promoted_size(4) = 32712
</code></pre><p>As you can see, gen0, gen1 and gen2 have all their budget exceeded (i.e. their <strong>new_allocation</strong> is negative) and it explains why a simple gen0 collection (from allocation in SOH = gen0) becomes a gen2 collection. If you wonder how gen1 and gen2 budgets are exceeded as your application is only allocating in gen0, you need to understand that when a GC copy surviving objects from one younger generation to the older, they are counted as allocations in the older and subtracted from its <strong>new_allocation</strong> metric.</p>
<p>The GC is encoding the different steps leading to the final condemned generation in a 32 bit value stored in a <strong>gen_to_condemn_tuning</strong> field that allows you to get:</p>
<ul>
<li>initial condemned generation,</li>
<li>final generation to condemn,</li>
<li>which generation’s budget is exceeded.</li>
</ul>
<p>The value of the last one corresponds to the highest generation for which its <strong>new_allocation</strong> was negative.</p>
<p>This information is available in the <strong>CondemnReasons0</strong> field of the <strong>GCPerHeapHistory</strong> event, and you need some arithmetic to get the generation you 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><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">private</span> <span class="kd">const</span> <span class="kt">int</span> <span class="n">gen_initial</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span>          <span class="c1">// indicates the initial gen to condemn.</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">gen_final_per_heap</span> <span class="p">=</span> <span class="m">1</span><span class="p">;</span>   <span class="c1">// indicates the final gen to condemn per heap.</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">gen_alloc_budget</span> <span class="p">=</span> <span class="m">2</span><span class="p">;</span>     <span class="c1">// indicates which gen&#39;s budget is exceeded.</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">const</span> <span class="kt">int</span> <span class="n">InitialGenMask</span> <span class="p">=</span> <span class="m">0x0</span> <span class="p">+</span> <span class="m">0x1</span> <span class="p">+</span> <span class="m">0x2</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="kt">int</span> <span class="n">GetGen</span><span class="p">(</span><span class="kt">int</span> <span class="n">val</span><span class="p">,</span> <span class="kt">int</span> <span class="n">reason</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">int</span> <span class="n">gen</span> <span class="p">=</span> <span class="p">(</span><span class="n">val</span> <span class="p">&gt;&gt;</span> <span class="m">2</span> <span class="p">*</span> <span class="n">reason</span><span class="p">)</span> <span class="p">&amp;</span> <span class="n">InitialGenMask</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">gen</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="building-your-owntool">Building your own tool</h2>
<p>Even though I could dig into the different matrices available in the Perfview’s GCStats view or its export to Excel, I decided to write dotnet-gcstats. This CLI tool listens to the CLR events emitted by a .NET application thanks to Microsoft.Diagnostics.NETCore.Client (connect to the application EventPipe) and TraceEvent (receive and analyze the CLR events).</p>
<p>The code is amazingly 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><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></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">providers</span> <span class="p">=</span> <span class="k">new</span> <span class="n">List</span><span class="p">&lt;</span><span class="n">EventPipeProvider</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">new</span> <span class="n">EventPipeProvider</span><span class="p">(</span><span class="s">&#34;Microsoft-Windows-DotNETRuntime&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventLevel</span><span class="p">.</span><span class="n">Informational</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">GC</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">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">processId</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="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">providers</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="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Task</span> <span class="n">streamTask</span> <span class="p">=</span> <span class="n">Task</span><span class="p">.</span><span class="n">Run</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="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></span><span class="line"><span class="cl">        <span class="n">ClrTraceEventParser</span> <span class="n">clrParser</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ClrTraceEventParser</span><span class="p">(</span><span class="n">source</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">clrParser</span><span class="p">.</span><span class="n">GCPerHeapHistory</span> <span class="p">+=</span> <span class="n">OnGCPerHeapHistory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">clrParser</span><span class="p">.</span><span class="n">GCStart</span> <span class="p">+=</span> <span class="n">OnGCStart</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">clrParser</span><span class="p">.</span><span class="n">GCGlobalHeapHistory</span> <span class="p">+=</span> <span class="n">OnGCGlobalHeapHistory</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</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">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="k">catch</span> <span class="p">(</span><span class="n">Exception</span> <span class="n">e</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">ShowError</span><span class="p">(</span><span class="s">$&#34;Error encountered while processing events: {e.Message}&#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>Each event handler is responsible for extracting and translating the interesting fields of its event payload with a few color enhancements:</p>
<ul>
<li><strong>GCStart</strong>: collection count and reason (highlight induced collections).</li>
<li><strong>GCGlobalHeapHistory</strong>: condemned generation, pause mode and memory pressure.</li>
<li><strong>GCPerHeapHistory</strong>: starting -&gt; final condemned generation and for each heap, budget, begin size, begin obj size, final size, promoted size and fragmentation.</li>
</ul>
<p>The final step was to transform a simple console application into a .NET CLI tool that everyone will be able to install with <strong>dotnet tool install -g dotnet-gcstats</strong> and use with <strong>dotnet gcstats <pid></strong>. I followed <a href="https://learn.microsoft.com/en-us/dotnet/core/tools/global-tools-how-to-create?WT.mc_id=DT-MVP-5003325">the documentation</a> by adding the following to the project file:</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-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackAsTool&gt;</span>true<span class="nt">&lt;/PackAsTool&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;ToolCommandName&gt;</span>dotnet-gcstats<span class="nt">&lt;/ToolCommandName&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageOutputPath&gt;</span>./nupkg<span class="nt">&lt;/PackageOutputPath&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;GeneratePackageOnBuild&gt;</span>true<span class="nt">&lt;/GeneratePackageOnBuild&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/PropertyGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In addition, I provided a few additional details:</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-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;PropertyGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageId&gt;</span>dotnet-gcstats<span class="nt">&lt;/PackageId&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageVersion&gt;</span>1.0.0<span class="nt">&lt;/PackageVersion&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Title&gt;</span>dotnet-gcstats<span class="nt">&lt;/Title&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Authors&gt;</span>christophe Nasarre<span class="nt">&lt;/Authors&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Owners&gt;</span>chrisnas<span class="nt">&lt;/Owners&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;RepositoryUrl&gt;</span>https://github.com/chrisnas<span class="nt">&lt;/RepositoryUrl&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;RepositoryType&gt;</span>git<span class="nt">&lt;/RepositoryType&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageProjectUrl&gt;</span>https://github.com/chrisnas/GCStats<span class="nt">&lt;/PackageProjectUrl&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageLicenseFile&gt;</span>LICENSE<span class="nt">&lt;/PackageLicenseFile&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Description&gt;</span>Global CLI tool to display live statistics during .NET garbage collections<span class="nt">&lt;/Description&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageReleaseNotes&gt;</span>Initial release<span class="nt">&lt;/PackageReleaseNotes&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Copyright&gt;</span>Copyright Christophe Nasarre 2024-$([System.DateTime]::UtcNow.ToString(yyyy))<span class="nt">&lt;/Copyright&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;PackageTags&gt;</span>.NET TraceEvent CLR GC<span class="nt">&lt;/PackageTags&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/PropertyGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Once built, I simply uploaded the generated package to nuget.org et voila!</p>
<p>Now, you should be able to better understand why some collections are triggered:</p>
<p><img loading="lazy" src="/posts/2024-03-01_view-your-gcs-statistics/1_3oHvH2Vxb3PgW46khVMSMQ.png"></p>
<p>And if it is not enough, wait for reading the second edition of Pro .NET Memory Management ;^)</p>
]]></content:encoded></item><item><title>Be Aligned! Or how to investigate a stack corruption</title><link>https://chrisnas.github.io/posts/2023-12-11_be-aligned-or-how/</link><pubDate>Mon, 11 Dec 2023 11:01:45 +0000</pubDate><guid>https://chrisnas.github.io/posts/2023-12-11_be-aligned-or-how/</guid><description>This post describes the different steps I followed to investigate a stack corruption with Visual Studio</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>During the Datadog R&amp;D week, my goal is to mimic the generation of a .gcdump from our .NET profiler. I’ve already written most of the code <a href="/posts/2023-08-11_net-gcdump-internals/">for a previous post</a> and after changing the required plumbing, it is time to test the workflow.</p>
<p>Unfortunately, I’m facing the dreaded stack corruption dialog:</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1_5ydnBBOQBccS0f016OUDIw.png"></p>
<p>The rest of the post explains the different steps I’m following to investigate this issue.</p>
<h2 id="trying-to-understand-theproblem">Trying to understand the problem</h2>
<p>This stack check is done by the debug version of the C Runtime library by basically adding some special bytes on the stack before calling a function and checking these bytes are not tampered when returning from the call.</p>
<p>So, the next step is to debug the application to get more details and at least where in the code the problem happened:</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1_XEEcEc2kLKmy8xRDF2e_xA.png"></p>
<p>The failed check occurs at the end of a function that looks like the following:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">GcDumpProvider</span><span class="o">::</span><span class="n">Get</span><span class="p">(</span><span class="n">IGcDumpProvider</span><span class="o">::</span><span class="n">gcdump_t</span><span class="o">&amp;</span> <span class="n">gcDump</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">// trigger the GC and get the dump
</span></span></span><span class="line"><span class="cl">    <span class="n">GcDump</span> <span class="nf">gcd</span><span class="p">(</span><span class="o">::</span><span class="n">GetCurrentProcessId</span><span class="p">());</span>
</span></span><span class="line"><span class="cl">    <span class="n">gcd</span><span class="p">.</span><span class="n">TriggerDump</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">auto</span> <span class="k">const</span><span class="o">&amp;</span> <span class="n">dump</span> <span class="o">=</span> <span class="n">gcd</span><span class="p">.</span><span class="n">GetGcDumpState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">auto</span><span class="o">&amp;</span> <span class="n">types</span> <span class="o">=</span> <span class="n">dump</span><span class="p">.</span><span class="n">_types</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span> <span class="nl">type</span> <span class="p">:</span> <span class="n">types</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">auto</span><span class="o">&amp;</span> <span class="n">typeInfo</span> <span class="o">=</span> <span class="n">type</span><span class="p">.</span><span class="n">second</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="kt">uint64_t</span> <span class="n">instancesCount</span> <span class="o">=</span> <span class="n">typeInfo</span><span class="p">.</span><span class="n">_instances</span><span class="p">.</span><span class="n">size</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint64_t</span> <span class="n">instancesSize</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="n">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">instancesCount</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">instancesSize</span> <span class="o">+=</span> <span class="n">typeInfo</span><span class="p">.</span><span class="n">_instances</span><span class="p">[</span><span class="n">i</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></span><span class="line"><span class="cl">        <span class="n">gcDump</span><span class="p">.</span><span class="n">push_back</span><span class="p">({</span><span class="n">typeInfo</span><span class="p">.</span><span class="n">_name</span><span class="p">,</span> <span class="n">instancesCount</span><span class="p">,</span> <span class="n">instancesSize</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">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>There is much more code behind this; especially in the <strong>TriggerDump()</strong> function. I have already tested this code many times when I dug into the .gcdump generation process without facing this stack corruption. I’m spending a few hours digging back into the code because:</p>
<ul>
<li>I’m not running inside the profiled process and not outside like in the blog post</li>
<li>I’m introducing a “slight” change because I need to exit the communication with the CLR when the GC ends.</li>
<li>I need to mention that Visual Studio is refusing to debug (Step Over or Step Into) and only Run to Cursor was possible due to mixed mode (managed and native) debugging. So, I created a simple native console application with my updated code for easier and faster debugging.</li>
</ul>
<p>After a couple of hours, it is time to go back to the <strong>Get()</strong> implementation because I do not find anything obviously wrong.</p>
<h2 id="make-it-simpler-and-simpler-and-simpleragain">Make it simpler and simpler and simpler again</h2>
<p>In that type of situation, I recommend the “remove code and debug” strategy (from “divide and conquer” attributed to Julius Cesar). From the simplified console application, the code now looks like:</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="kt">bool</span> <span class="nf">GetGcDump</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="n">IGcDumpProvider</span><span class="o">::</span><span class="n">gcdump_t</span><span class="o">&amp;</span> <span class="n">gcDump</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">GcDump</span> <span class="n">gcd</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="c1">// no more gcd.TriggerDump()
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// no more for (auto&amp; type : types)
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">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>No more complicated call nor iteration on the vector of results. Guess what? Same stack corruption.</p>
<p>It is time to go one level deeper: what does this <strong>GcDump</strong> class look like?</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">class</span> <span class="nc">GcDump</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">GcDump</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="o">~</span><span class="n">GcDump</span><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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">_pid</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">DiagnosticsClient</span><span class="o">*</span> <span class="n">_pClient</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">EventPipeSession</span><span class="o">*</span> <span class="n">_pSession</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">HANDLE</span> <span class="n">_hListenerThread</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">GcDumpState</span> <span class="n">_gcDumpState</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 constructor is setting the fields value to zero/nullptr and the destructor is cleaning up these fields if necessary. Since <strong>TriggerDump()</strong> is no more called, these fields never change.</p>
<p>I’m commenting out all fields until only <strong>_gcDumpState</strong> remains and it continues to crash. When is it commented out, it is not more crashing.</p>
<h2 id="use-your-debuggerluke">Use your debugger Luke!</h2>
<p>Let’s turn to the <strong>GcDumpState</strong> class that is even simpler:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">GcDumpState</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">GcDumpState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">~</span><span class="n">GcDumpState</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// fields removed for brevity
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">_isStarted</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">_hasEnded</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">_collectionIndex</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 of the destructor only sends a trace to the console (removing it completely does not fix the issue) and here is the constructor 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></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">GcDumpState</span><span class="o">::</span><span class="n">GcDumpState</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">_isStarted</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">_hasEnded</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">_collectionIndex</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></code></pre></td></tr></table>
</div>
</div><p>Again, same strategy: remove one field after the other. This time, the code stops crashing if the two Boolean fields are removed or if the 32 bits index is removed. If the index field is not set, no more corruption!</p>
<p>How could this assignation corrupt the stack? It is time to use the Visual Studio debugger to better understand what is going on.</p>
<p>First, set a breakpoint on the assignment line and click Debug | Disassembly to see the corresponding assembly code:</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1_vD7b7Lv-bcUmfOwGFOGM8Q.png"></p>
<p>The two lines of assembly code are easy to understand:</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">mov</span> <span class="n">rax</span><span class="p">,</span><span class="n">qword</span> <span class="n">ptr</span> <span class="p">[</span><span class="k">this</span><span class="p">]</span>  
</span></span><span class="line"><span class="cl"><span class="n">mov</span> <span class="n">dword</span> <span class="n">ptr</span> <span class="p">[</span><span class="n">rax</span><span class="o">+</span><span class="mi">4</span><span class="p">],</span><span class="mi">0</span>
</span></span></code></pre></td></tr></table>
</div>
</div><ul>
<li>The <strong>this</strong> pointer is stored in the <strong>rax</strong> register</li>
<li>The 32 bits (<strong>mov dword</strong>) memory starting 4 bytes after the beginning of the object pointed to by <strong>this</strong>, is set to 0</li>
</ul>
<p>I enter “this” in a Memory panel (Debug | Windows | Memory xx)</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1_EMOVsaUfPsdbWwKx11c0eQ.png"></p>
<p>And Visual Studio gives me the corresponding address and the content of the memory there:</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1_tKK6VDl7svGm4mA8JJBM_w.png"></p>
<p>Pressing F10 twice to Step Over the two assembly instructions and this is confirmed:</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1_sECyiTU4IBMIuqtzTr4Ecg.png"></p>
<p>Instead of storing the 32 bits 0 value just after the two bytes corresponding to the bool fields, it is stored 2 bytes away. It looks like a padding is added on my behalf.</p>
<p>I change the build settings for the GcDumpState.cpp file to enable all warnings:</p>
<p><img loading="lazy" src="/posts/2023-12-11_be-aligned-or-how/1__0XcRwnGs8Qaoj4_S99LZQ.png"></p>
<p>The compilation confirms what has been seen in the memory:</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">GcDumpState</span><span class="p">.</span><span class="n">h</span><span class="p">(</span><span class="mi">41</span><span class="p">,</span><span class="mi">14</span><span class="p">)</span><span class="o">:</span> <span class="n">warning</span> <span class="nl">C4820</span><span class="p">:</span> <span class="err">&#39;</span><span class="n">GcDumpState</span><span class="err">&#39;</span><span class="o">:</span> <span class="sc">&#39;2&#39;</span> <span class="n">bytes</span> <span class="n">padding</span> <span class="n">added</span> <span class="n">after</span> <span class="n">data</span> <span class="n">member</span> <span class="err">&#39;</span><span class="n">GcDumpState</span><span class="o">::</span><span class="n">_hasEnded</span><span class="err">&#39;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="whats-next">What’s next?</h2>
<p>My understanding is that the compiler is:</p>
<ul>
<li>adding a 2 bytes padding to align the 32 bits index field</li>
<li>generating the constructor code based on that padding</li>
<li>but the stack corruption checking code does not take it into account</li>
</ul>
<p>The solution is to either add a <strong>uint16_t</strong> field after the 2 bool fields as an explicit padding or use <strong>#pragma pack(1)</strong> to decorate the class definition.</p>
<p>However, this looks really weird to me. We should have faced this issue a long time ago because we were never cautious about alignment in all the classes and structures that we allocate in our code. To validate the assumption, I’m writing a small reproduction code outside of all the .gcdump complexity. And guess what? I’m not able to reproduce the stack corruption. Another mystery of the C++ compilation optimizations probably…</p>
<p>This is the end of my debugging Friday at Datadog :^)</p>
]]></content:encoded></item><item><title>How to dig into the CLR</title><link>https://chrisnas.github.io/posts/2023-11-12_how-to-dig-into/</link><pubDate>Sun, 12 Nov 2023 09:12:28 +0000</pubDate><guid>https://chrisnas.github.io/posts/2023-11-12_how-to-dig-into/</guid><description>The goal of this post is to share the tips and tricks I used to navigate into the CLR implementation to better understand how .NET works</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>When I started to work on the second edition of <em>Pro .NET Memory Management : For Better Code, Performance, and Scalability</em> by Konrad Kokosa, I already spent some time in the CLR code for a couple of pull requests related to the garbage collector. However, updating the book to cover 5 new versions of .NET requires looking at new APIs but also digging deep inside the CLR (and especially the GC) hundreds of thousand lines of code!</p>
<p>The first step is to install Visual Studio 2022 Preview that allows you to compile and run projects targeting .NET 8. Then, goto <a href="https://github.com/dotnet/runtime">https://github.com/dotnet/runtime</a> and git clone the tag of the <a href="https://dotnet.microsoft.com/en-us/download/dotnet/8.0">.NET 8 preview version you have installed</a>.</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_m3M1yrJypN6eeed3c_6RRg.png"></p>
<p>That way, you will be able to directly run the same version that you will debug.</p>
<p>And now, what are the next steps?</p>
<p>The goal of this post is to share with you the tips and tricks I used to navigate into the CLR implementation so you could better understand how things are working.</p>
<h2 id="from-c-toc">From C# to C++</h2>
<p>As a .NET developer, I’m used to the APIs provided by the Base Class Library built on top of the CLR. Let’s take as an example the following code that is using the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.gc.allocatearray?WT.mc_id=DT-MVP-5003325">GC.AllocateArray</a> method that allows you to allocate a pinned in memory array and available since .NET 5.</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="k">using</span> <span class="nn">System</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="k">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</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 class="kt">string</span><span class="p">[]</span> <span class="n">args</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">pinned</span> <span class="p">=</span> <span class="n">GC</span><span class="p">.</span><span class="n">AllocateArray</span><span class="p">&lt;</span><span class="kt">byte</span><span class="p">&gt;(</span><span class="m">90000</span><span class="p">,</span> <span class="kc">true</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;generation = {GC.GetGeneration(pinned)}&#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>When you Ctrl+click the method name (or use F12), thanks to Source Link integration, you go to its implementation where you can even set breakpoint:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_Zwaunoa3Ott_dsWqzcwqog.png"></p>
<p>If you don’t use Visual Studio, you could open the generated assembly into a decompiler such as <a href="https://github.com/icsharpcode/ILSpy/releases">ILSpy</a> or <a href="https://github.com/dnSpy/dnSpy/releases">DnSpy</a>. The latter even allows you to set breakpoints and debug the disassembly IL without any source.</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_HPBNrPaDhKaX-eOGuNqJtA.png"></p>
<p>In both cases, only the managed implementation will be available: you soon end up to an “internal call” corresponding to a native function implemented by the CLR. The managed methods are decorated with the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.methodimploptions?WT.mc_id=DT-MVP-5003325">MethodImplOptions.InternalCall</a> attribute.</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_4kg2jOleRs06edogUKEgQA.png"></p>
<p>For the garbage collector code, you can look into the <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/System/GC.CoreCLR.cs">GC.CoreCLR.cs</a> file where these methods are defined. You can note some methods decorated with the <a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute?WT.mc_id=DT-MVP-5003325">DllImport</a> attribute to bind to native functions exported by a “QCall” library. There is an optimized path in P/Invoke done by the JIT to transform these calls not like a usual LoadLibrary/GetProcAddress as you could expect. Instead, they will be routed to the exported methods by coredll.dll and defined in the <strong>s_QCall</strong> array in <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/qcallentrypoints.cpp">qcallentrypoints.cpp</a>. But where to look further for the native implementation?</p>
<p>Instead of searching among the thousands of files, focus on <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/comutilnative.h">comutilnative.h</a> that defines the signature of most exported functions. The implementation of the exported native functions is found in <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/comutilnative.cpp">comutilnative.cpp</a>. This is where you should start your journey in the native implementation of the CLR. For the list of <strong>all</strong> functions called by the libraries in the runtime, look at the <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/ecalllist.h">ecalllist.h</a> file (around <strong>gGCInterfaceFuncs</strong> and <strong>gGCSettingsFuncs</strong> specifically for the GC).</p>
<p><em>Note that you might also find some implementations under the <em><a href="https://github.com/dotnet/runtime/tree/main/src/coreclr/classlibnative"><em>classlibnative</em></a></em> folder like in the <em><a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/classlibnative/bcltype/system.cpp"><em>system.cpp</em></a></em> file for <em><a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.gcsettings.isservergc?WT.mc_id=DT-MVP-5003325"><em>GCSettings.IsServerGC</em></a></em>.</em></p>
<h2 id="clr-source-code-debugging">CLR Source code debugging</h2>
<p>It is nice to know that the implementation of most CLR exported native functions used by the BCL is in <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/comutilnative.cpp">comutilnative.cpp</a>. For the GC, the functions are either statics from the <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/comutilnative.h#L142">GCInterface class</a> or static functions prefixed by <strong>GCInterface_</strong>; I don’t know why all are not part of <strong>GCInterface</strong>…</p>
<p>When you look at the GC-related methods implementation, a lot are calling methods from the instance returned by <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gcheaputilities.h#L70">GCHeapUtilities::GetGCHeap()</a> that corresponds to the static <a href="https://github.com/dotnet/runtime/blob/main/src/coreclr/vm/gcheaputilities.h#L10">g_pGCHeap</a> global variable. It is interesting to follow the threads of calls like that, but I have to admit that, after a few hops, I’m starting to get lost. So, I’m drawing boxes for types on a piece of paper and arrays from their fields to other types as boxes.</p>
<p>However, with a code base that big, I definitively prefer to set breakpoints and write a small C# application to call the methods I’m interested in and see what data structures are used in the different layers of implementation. Don’t be scared: WinDBG is not required to achieve this goal. As <a href="https://github.com/dotnet/runtime/blob/main/docs/workflow/debugging/coreclr/debugging-runtime.md">this page explains</a>, you need to type the following commands in a shell at the root of the repo:</p>
<p><strong>.\build.cmd -s clr -c Debug
.\build.cmd clr.nativeprereqs -a x64 -c debug
.\build.cmd -msbuild</strong></p>
<p>The last command generates a CoreCLR.sln solution file in artifacts\obj\coreclr\windows.x64.Debug\ide) that you can open in Visual Studio 2022 Preview.</p>
<p>In VS, right-click the <strong>INSTALL</strong> project, select Properties and setup the Debugging properties</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_HmIEli065siGvrPzFrMZ1g.png"></p>
<p>Here are the details of each property:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_3n7plqDVcjYhqIKlKZaW-g.png"></p>
<p>It could be interesting to set some environment variables such as <strong>DOTNET_gcServer</strong> to 1 for a GC Server configuration instead of workstation. In that case, click the &lt;Edit..&gt; choice in the combo-box:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_fwEUBlJ1tFdw-aTgHZuAcQ.png"></p>
<p>And update the textbox at the top:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_I61yQQaObRsRCX8v_jy3hA.png"></p>
<p>The final step is to set this project as the startup project:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_cSZTuIuo2UMSfc9DpC_vdw.png"></p>
<p>You are now able to set the breakpoint you want in the native code of the CLR and type F5/Debug in Visual Studio to step into the code!</p>
<h2 id="and-what-about-the-assemblycode">And what about the assembly code?</h2>
<p>Some specific data structures, such as the NonGC Heap, are used by the JIT compiler when generating the assembly code from the IL compiled from your C# code. It means that you need to look at that JITted code to fully understand what is going on.</p>
<p>A first way to get it is to use <a href="https://sharplab.io/">https://sharplab.io/</a>, type your C# code and select x64 for Core of x86/x64 for Framework:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_CHCDpZ5DxdJUTdjOjMUBnw.png"></p>
<p>But as you can see from this screenshot, it is using the .NET 7 compiler. What if you would like to see the .NET 8 compilation result just in case something changed?</p>
<p>The solution I’m using is to generate a memory dump with procdump -ma <pid> of a test application. Before opening the dump in WinDBG, there is something you should be aware of: with the <a href="https://learn.microsoft.com/en-us/dotnet/core/runtime-config/compilation?WT.mc_id=DT-MVP-5003325#tiered-compilation">tiered compilation</a>, you will need to call a method several times before the final optimized assembly code gets JITed. Or… decorate the method you are interested in with the [MethodImpl(<a href="https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.methodimploptions?WT.mc_id=DT-MVP-5003325">MethodImplOptions.AggressiveOptimization</a>)] attribute to instruct the JIT to directly generate the most optimized tier.</p>
<p>Once the dump loaded in WinDBG, the first step is to get the MethodTable pointer corresponding to the method you are interested in. For that, use the <strong>name2ee</strong> SOS command:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_88o_noKnyvMx3ogJqnaq5Q.png"></p>
<p>Click the link corresponding to MethodDesc to run the <strong>dumpmd</strong> SOS command:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_RWF60HbQmrh8-NIgt_HpXg.png"></p>
<p>The last step is to click the link corresponding to CodeAddr to run the <strong>U</strong> command and see the JITted assembly code:</p>
<p><img loading="lazy" src="/posts/2023-11-12_how-to-dig-into/1_G3N6TdlQRvDhU291kSamww.png"></p>
<p>If you compare this code to get the “Hello, World!” string, with the one shown by sharplab,</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="l">Program.Hello()</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">L0000</span><span class="p">:</span><span class="w"> </span><span class="l">mov rcx, 0x257f7cbc368</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">L000a</span><span class="p">:</span><span class="w"> </span><span class="l">mov rcx, [rcx]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">L000d</span><span class="p">:</span><span class="w"> </span><span class="l">jmp qword ptr [0x7ff9c9bd7f48]</span><span class="w">
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>you might notice a tiny difference: there is one less indirection in .NET 8!
 But this is another story that will be told in the second edition of the “Pro .NET Memory Management: For Better Code, Performance, and Scalability” book ;^)</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>Raiders of the lost root: looking for memory leaks in .NET</title><link>https://chrisnas.github.io/posts/2023-05-08_raiders-of-the-lost/</link><pubDate>Mon, 08 May 2023 09:05:15 +0000</pubDate><guid>https://chrisnas.github.io/posts/2023-05-08_raiders-of-the-lost/</guid><description>This post explains how you could write your own memory profiler based on new .NET 7.0 profiler APIs in C++</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>It’s been almost 12 years since I wrote <a href="https://github.com/chrisnas/DebuggingExtensions/tree/master/src/LeakShell">LeakShell</a> to help me <a href="https://codenasarre.wordpress.com/2011/05/18/leakshell-or-how-to-automatically-find-managed-leaks/">automate the search of memory leaks</a> in .NET. The idea was simple: compare 2 memory dumps of a leaking .NET application to show the types with increasing instances count.</p>
<p>Today, you could use <a href="https://learn.microsoft.com/en-us/visualstudio/profiling/memory-usage-without-debugging2?view=vs-2022?WT.mc_id=DT-MVP-5003325">Visual Studio Memory Usage</a> tool to do the same but with a much better user interface! The additional killer feature is the ability to see the references chain that explains why a “leaky” object stays in memory.</p>
<p>My previous series about <a href="/posts/2020-06-19_build-your-own-net/">building your own .NET memory profiler in C#</a> is based on CLR events and does not allow to get the references chain. This post explains how you could write your own memory profiler based on.NET profiler APIs in C++. Refer to <a href="/posts/2021-08-07_start-journey-into-the/">this post</a> for an introduction of how to implement <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-interface?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback</strong></a> to be loaded by the CLR in a .NET process.</p>
<h2 id="how-to-detect-memoryleaks">How to detect memory leaks</h2>
<p>From a high level view, detecting a memory leak means being able to know which objects stay alive garbage collection after garbage collection:</p>
<ul>
<li>implement <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectallocated-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback::ObjectAllocated</strong></a> to keep track of ALL objects in the heap,</li>
<li>use <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectreferences-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback::MovedReferences2</strong></a> to fixup the addresses when the live objects are moved during compaction garbage collections,</li>
<li>since** **<a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectreferences-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback::ObjectReferences</strong></a> is called for each surviving object, clean your list of live objects.</li>
</ul>
<p>The first drawback of this solution is that the CLR has to disable concurrent GC to call these functions with probable impact on performances. However, if you can’t find a leak in production that leads to out of memory crashes, running one instance in this mode is perfectly acceptable. The second drawback is the complexity of keeping track of objects through compacting GCs.</p>
<p>This is why <a href="https://github.com/dotnet/runtime/pull/71257">I implemented in .NET 7</a> a new set of functions in <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo13-interface?WT.mc_id=DT-MVP-5003325">ICorProfilerInfo13</a> to mimic what you can do in C# with a <a href="https://learn.microsoft.com/en-us/dotnet/standard/garbage-collection/weak-references?WT.mc_id=DT-MVP-5003325"><em>weak reference</em></a>:</p>
<ul>
<li><a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo13-createhandle-method?WT.mc_id=DT-MVP-5003325"><strong>CreateHandle</strong></a> : create a weak handle to wrap an object,</li>
<li><a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo13-getobjectidfromhandle-method?WT.mc_id=DT-MVP-5003325"><strong>GetObjectIDFromHandle</strong></a>: get the address of the wrapped object or null if the object is no more in the heap,</li>
<li><a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo13-destroyhandle-method"><strong>DestroyHandle</strong></a>: clean up the weak handle.</li>
</ul>
<p>Creating such a weak handle for allocated objects get rid of the address fixup complexity. However, you should not create a handle for ALL allocated objects because it will slow down the garbage collections. So the next step is to listen to the <a href="https://learn.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events#gcallocationtick_v3-event?WT.mc_id=DT-MVP-5003325">AllocationTick</a> CLR event and create a weak handle for each sampled allocation. Even though the statistical distribution of such 100 KB threshold-based sampling is not perfect, leaking objects should appear.</p>
<p>After each garbage collection detected in <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback2-garbagecollectionfinished-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback2::GarbageCollectionFinished</strong></a> or via <a href="/posts/2019-05-28_spying-on-net-garbage/">specific GC events</a>, you could clean up this list of allocated objects by removing those for which <strong>GetObjectIDFromHandle</strong> returns null.</p>
<p>Feel free to look at the <a href="https://github.com/DataDog/dd-trace-dotnet/blob/master/profiler/src/ProfilerEngine/Datadog.Profiler.Native/LiveObjectsProvider.cpp">corresponding implementation</a> in Datadog .NET profiler code.</p>
<h2 id="rebuild-references-chain-up-to-aroot">Rebuild references chain up to a root</h2>
<p>Even though it is possible to get the call stack that led to allocating a leaking object thanks to the <strong>AllocationTick</strong> event, it would be better to know why it stays in memory. So the next step is to rebuild the references chain up to the root.</p>
<p>As explained in <a href="/posts/2021-12-18_accessing-arrays-and-class/">a previous post</a>, it is possible, for a given object, to get the list of its fields and build a graph of dependencies from a parent to its children. However, you are interested in the opposite and it would require to get these parent/children references for ALL objects in the heap. And this is not possible with the sampled <strong>AllocationTick</strong> event…</p>
<p>This is where <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectreferences-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback::ObjectReferences</strong></a> shines:</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">HRESULT</span> <span class="nf">ObjectReferences</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">   <span class="n">ObjectID</span> <span class="n">objectId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="n">ClassID</span>  <span class="n">classId</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="n">ULONG</span>    <span class="n">cObjectRefs</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">   <span class="n">ObjectID</span> <span class="n">objectRefIds</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 method is called during a garbage collection for all objects (i.e. <strong>objectId</strong> first parameter) still alive and lists its fields referencing objects in the heap (i.e. <strong>objectRefIds</strong> last parameter).</p>
<p>You could store each object as an <strong>ObjectNode</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></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">ObjectNode</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">ObjectNode</span><span class="p">(</span><span class="n">ObjectID</span> <span class="n">objectId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">ObjectID</span> <span class="n">instance</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">ObjectNode</span><span class="o">*&gt;</span> <span class="n">rootRefs</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 a vector that represents the heap.</p>
<p>For each fields, look for its corresponding node in the vector and add the node of its parent (given by <strong>objectId</strong>) to its <strong>rootRefs</strong> vector of parents. That way, you are building a back reference graph:</p>
<p><img loading="lazy" src="/posts/2023-05-08_raiders-of-the-lost/1_GXNAUQtq1-moMncqOM_yHw.png"></p>
<p>The small blue arrows show the parent/children reference given by <strong>ObjectReferences</strong> and the large purple ones are kept to build a reverse references graph you are interested in.</p>
<p>You know when all live objects in the heap have been listed when <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback2-garbagecollectionfinished-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback2::GarbageCollectionFinished</strong></a> is called. It is now time to get the build the references chain for all sampled objects still alive (thanks to <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo13-getobjectidfromhandle-method?WT.mc_id=DT-MVP-5003325"><strong>GetObjectIDFromHandle</strong></a> returning non null address).</p>
<p>It is important to understand that it is a graph and not a tree because cycles exist in .NET.</p>
<p><img loading="lazy" src="/posts/2023-05-08_raiders-of-the-lost/1_t9MonO439AUATDdp4fmdoQ.png"></p>
<p>This is common in situations where objects need to keep a reference to their “parents”. It means that these cycles should be detected when looking for the list of references of a given live object to avoid infinite recursion:</p>
<p>bool DumpNode(ObjectNode* node, std::vector<ObjectID>&amp; referenceStack)</p>
<p>The traversing <strong>DumpNode</strong> method takes a node (i.e. an object of the heap) and a stack where the parents will be added as we dig into the graph.</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></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="kt">bool</span> <span class="nf">DumpNode</span><span class="p">(</span><span class="n">ObjectNode</span><span class="o">*</span> <span class="n">node</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">ObjectID</span><span class="o">&gt;&amp;</span> <span class="n">referenceStack</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">// end of recursion: the node is a root
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">rootRefs</span><span class="p">.</span><span class="n">size</span><span class="p">()</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="c1">//  dump the root
</span></span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</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><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">instance</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">COR_PRF_GC_ROOT_KIND</span> <span class="n">kind</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">COR_PRF_GC_ROOT_FLAGS</span> <span class="n">flags</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">FindRoot</span><span class="p">(</span><span class="n">_roots</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">instance</span><span class="p">,</span> <span class="n">kind</span><span class="p">,</span> <span class="n">flags</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; | &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">DumpKind</span><span class="p">(</span><span class="n">kind</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; - &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">DumpFlags</span><span class="p">(</span><span class="n">flags</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">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</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">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; = &#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">DumpObjectType</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">instance</span><span class="p">,</span> <span class="n">_pCorProfilerInfo</span><span class="p">,</span> <span class="n">_pFrameStore</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">cout</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// dump the references from the root
</span></span></span><span class="line"><span class="cl">        <span class="k">for</span> <span class="p">(</span><span class="kt">int16_t</span> <span class="n">i</span> <span class="o">=</span> <span class="n">referenceStack</span><span class="p">.</span><span class="n">size</span><span class="p">()</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="n">i</span> <span class="o">&gt;=</span> <span class="mi">0</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">ObjectID</span> <span class="n">reference</span> <span class="o">=</span> <span class="n">referenceStack</span><span class="p">[</span><span class="n">i</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; --&gt; &#34;</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">cout</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">reference</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; = &#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">            <span class="n">DumpObjectType</span><span class="p">(</span><span class="n">reference</span><span class="p">,</span> <span class="n">_pCorProfilerInfo</span><span class="p">,</span> <span class="n">_pFrameStore</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">cout</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><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="nb">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="c1">// detect cycles
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">Find</span><span class="p">(</span><span class="n">referenceStack</span><span class="p">,</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">instance</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// go up into the reference chain
</span></span></span><span class="line"><span class="cl">    <span class="n">referenceStack</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">node</span><span class="o">-&gt;</span><span class="n">instance</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span> <span class="nl">parentNode</span> <span class="p">:</span> <span class="n">node</span><span class="o">-&gt;</span><span class="n">rootRefs</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">DumpNode</span><span class="p">(</span><span class="n">parentNode</span><span class="p">,</span> <span class="n">referenceStack</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">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="n">referenceStack</span><span class="p">.</span><span class="n">pop_back</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="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>If a parent node is already in the stack, a cycle is detected and that path is not used. Once a root is reached, the stack is dumped as shown in the following output:</p>
<pre tabindex="0"><code>OnGarbageCollectionFinished: 3859 objects in the heap.

OnRootReferences2: 90/109 roots.
            stack:40
        finalizer:1
           handle:49
            other:0
------------------

21266c00020 | H - 0 = Object[]
 --&gt; 21268c092c8 = NativeRuntimeEventSource
 --&gt; 21268c3fbe8 = EventSource.EventMetadata[]
 --&gt; 21268c18010 = ParameterInfo[]
 --&gt; 21268c17ef0 = RuntimeParameterInfo
 --&gt; 21268c0ed58 = RuntimeMethodInfo
 --&gt; 21268c0e708 = RuntimeType.RuntimeTypeCache
 --&gt; 21268c0e840 = RuntimeType.RuntimeTypeCache.MemberInfoCache&lt;System.Reflection.RuntimeMethodInfo&gt;
 --&gt; 21268c14978 = RuntimeMethodInfo[]
 --&gt; 21268c10d70 = RuntimeMethodInfo
 --&gt; 21268c29720 = Signature
 --&gt; 21268c29770 = RuntimeType[]
=====================================
</code></pre><p>As shown in the output, it is possible to provide details about the kind of root is keeping the references chain alive thanks to <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback2-rootreferences2-method?WT.mc_id=DT-MVP-5003325">ICorProfilerCallback::RootReferences2</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></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="nf">RootReferences2</span><span class="p">(</span>  
</span></span><span class="line"><span class="cl">   <span class="n">ULONG</span>  <span class="n">cRootRefs</span><span class="p">,</span>  
</span></span><span class="line"><span class="cl">   <span class="n">ObjectID</span> <span class="n">rootRefIds</span><span class="p">[],</span>  
</span></span><span class="line"><span class="cl">   <span class="n">COR_PRF_GC_ROOT_KIND</span> <span class="n">rootKinds</span><span class="p">[],</span>  
</span></span><span class="line"><span class="cl">   <span class="n">COR_PRF_GC_ROOT_FLAGS</span> <span class="n">rootFlags</span><span class="p">[],</span>  
</span></span><span class="line"><span class="cl">   <span class="n">UINT_PTR</span> <span class="n">rootIds</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 function is called with three synchronized arrays <strong>cRootRefs</strong> long that contain for each root:</p>
<ul>
<li>the address (**rootRefsIds **objectID),</li>
<li>the kind (<strong>rootKind</strong> for stack, finalizer, handle and other)</li>
<li>and flags (<strong>rootFlags</strong> for pinned, weak reference interior or ref counted).</li>
</ul>
<p>These are stored in a vector of <strong>ObjectRoot</strong>:</p>
<h2 id="goodies-how-to-get-arrays-typename">Goodies: how to get arrays type name</h2>
<p>I did not mention how to get the type name of either an <strong>ObjectID</strong> or a <strong>ClassID</strong> because it is explained in <a href="/posts/2021-09-06_dealing-with-modules-assemblie/">a previous post</a>. However, I forgot to explain how to deal with the different kinds of arrays: single dimension (ex: <strong>byte[]</strong>), multidimensional (ex: <strong>byte[,]</strong>) or jagged (ex: <strong>byte[][]</strong>).</p>
<p>When you call <strong>ICorProfilerInfo::GetClassInfo</strong> on a <strong>ClassID</strong> corresponding to an array,</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="n">ModuleID</span> <span class="n">moduleId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">mdTypeDef</span> <span class="n">typeDefToken</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">_pCorProfilerInfo</span><span class="o">-&gt;</span><span class="n">GetClassIDInfo</span><span class="p">(</span><span class="n">classId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">moduleId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">typeDefToken</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>it won’t fail but the module id and the metadata token will both be set to 0.</p>
<p>Instead, you have to call <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo-isarrayclass-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerInfo::IsArrayClass</strong></a> to get the rank and the item class ID of the array. This is then done recursively on the item class ID until it fails:</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="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">arrayBuilder</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">CorElementType</span> <span class="n">baseElementType</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">ClassID</span> <span class="n">itemClassId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">ULONG</span> <span class="n">rank</span> <span class="o">=</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">_pCorProfilerInfo</span><span class="o">-&gt;</span><span class="n">IsArrayClass</span><span class="p">(</span><span class="n">classId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">baseElementType</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">itemClassId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rank</span><span class="p">)</span> <span class="o">==</span> <span class="n">S_OK</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">classId</span> <span class="o">=</span> <span class="n">itemClassId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">isArray</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">AppendArrayRank</span><span class="p">(</span><span class="n">arrayBuilder</span><span class="p">,</span> <span class="n">rank</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// in case of matrices, it is needed to look for the last &#34;good&#34; item class ID
</span></span></span><span class="line"><span class="cl">    <span class="c1">// because all others might be array of array of ...
</span></span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="p">(</span><span class="n">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">rank</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">HRESULT</span> <span class="n">hr</span> <span class="o">=</span> <span class="n">_pCorProfilerInfo</span><span class="o">-&gt;</span><span class="n">IsArrayClass</span><span class="p">(</span><span class="n">classId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">baseElementType</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">itemClassId</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">rank</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">hr</span> <span class="o">==</span> <span class="n">S_FALSE</span><span class="p">)</span> <span class="o">||</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">itemClassId</span> <span class="o">=</span> <span class="n">classId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</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></span><span class="line"><span class="cl">        <span class="n">AppendArrayRank</span><span class="p">(</span><span class="n">arrayBuilder</span><span class="p">,</span> <span class="n">rank</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">classId</span> <span class="o">=</span> <span class="n">itemClassId</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>Notice that the way to concatenate the possible <strong>[]</strong> / <strong>[,]</strong> / <strong>[][]</strong> could is the opposite of how the array type is defined in C#:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">void</span> <span class="nf">AppendArrayRank</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">arrayBuilder</span><span class="p">,</span> <span class="n">ULONG</span> <span class="n">rank</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">rank</span> <span class="o">==</span> <span class="mi">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">arrayBuilder</span> <span class="o">=</span> <span class="s">&#34;[]&#34;</span> <span class="o">+</span> <span class="n">arrayBuilder</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">std</span><span class="o">::</span><span class="n">stringstream</span> <span class="n">builder</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">builder</span> <span class="o">&lt;&lt;</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="n">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">rank</span> <span class="o">-</span> <span class="mi">1</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">builder</span> <span class="o">&lt;&lt;</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">builder</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;]&#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">arrayBuilder</span> <span class="o">=</span> <span class="n">builder</span><span class="p">.</span><span class="n">str</span><span class="p">()</span> <span class="o">+</span> <span class="n">arrayBuilder</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>For example, a <strong>byte[][,]</strong> is defined as an rank 2 array of array of byte.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://codenasarre.wordpress.com/2011/05/18/leakshell-or-how-to-automatically-find-managed-leaks/">Automate the search of memory leaks with LeakShell</a></li>
<li><a href="/posts/2020-06-19_build-your-own-net/">Building your own .NET memory profiler in C#</a></li>
<li><a href="/posts/2021-08-07_start-journey-into-the/">Introduction to .NET Profiling with ICorProfilerCallback</a></li>
<li><a href="https://github.com/dotnet/runtime/pull/71257">Pull request in .NET 7</a> for <a href="https://learn.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo13-interface?WT.mc_id=DT-MVP-5003325">ICorProfilerInfo13</a> to create weak handles</li>
<li>Datadog .NET <a href="https://github.com/DataDog/dd-trace-dotnet/blob/master/profiler/src/ProfilerEngine/Datadog.Profiler.Native/LiveObjectsProvider.cpp">Live Heap Profiler implementation</a></li>
</ul>
]]></content:encoded></item><item><title>From Metadata to Event block in nettrace format</title><link>https://chrisnas.github.io/posts/2023-03-10_from-metadata-to-event/</link><pubDate>Fri, 10 Mar 2023 16:15:40 +0000</pubDate><guid>https://chrisnas.github.io/posts/2023-03-10_from-metadata-to-event/</guid><description>The previous episodes started the parsing of the “nettrace” format. This last episode covers Metadata and Event blocks format.</description><content:encoded><![CDATA[<hr>
<p>The previous episodes started the parsing of the “nettrace” format used when <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">contacting the .NET Diagnostics IPC server</a>, <a href="/posts/2022-10-23_clr-events-go-for/">initiate the protocol to receive CLR events</a> and start to <a href="/posts/2023-01-15_reading-object-in-memory/">parse stacks</a>. This last episode covers the Metadata and Event blocks.</p>
<p>In terms of format, both Metadata and Event blocks share the same memory layout:</p>
<p><img loading="lazy" src="/posts/2023-03-10_from-metadata-to-event/1_8U7zPxOVCe2Bws5g5TkX_A.png"></p>
<p>The common <strong>EventBlockHeader</strong> starts the block:</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="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">EventBlockHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">HeaderSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Flags</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">MinTimestamp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">MaxTimestamp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// some optional reserved space might be following
</span></span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The timestamp fields give the time of the first and last event in the block. The <strong>HeaderSize</strong> fields is important because additional information can be stored in the header. Since I have no idea what could be stored there, I simply skip it:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventParserBase</span><span class="o">::</span><span class="n">OnParse</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 event block header
</span></span></span><span class="line"><span class="cl">    <span class="n">EventBlockHeader</span> <span class="n">ebHeader</span> <span class="o">=</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">Read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">ebHeader</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ebHeader</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// skip any optional content if any
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">ebHeader</span><span class="p">.</span><span class="n">HeaderSize</span> <span class="o">&gt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">EventBlockHeader</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">uint8_t</span> <span class="n">optionalSize</span> <span class="o">=</span> <span class="n">ebHeader</span><span class="p">.</span><span class="n">HeaderSize</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">EventBlockHeader</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">SkipBytes</span><span class="p">(</span><span class="n">optionalSize</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><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The important piece of information to figure out how to unpack the rest of the block is kept in the <strong>Flags</strong> field. If the lowest bit is set, it means that the blobs header will be compressed:</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="c1">// the rest of the block is a list of Event blobs
</span></span></span><span class="line"><span class="cl">    <span class="c1">//
</span></span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">blobSize</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">DWORD</span> <span class="n">totalBlobSize</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">DWORD</span> <span class="n">remainingBlockSize</span> <span class="o">=</span> <span class="n">_blockSize</span> <span class="o">-</span> <span class="n">ebHeader</span><span class="p">.</span><span class="n">HeaderSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">isCompressed</span> <span class="o">=</span> <span class="p">((</span><span class="n">ebHeader</span><span class="p">.</span><span class="n">Flags</span> <span class="o">&amp;</span> <span class="mi">1</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The rest of the code iterates on each blob:</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">// Note: in order to gain space, some fields of the header could be &#34;inherited&#34;
</span></span></span><span class="line"><span class="cl">    <span class="c1">// from the header of the previous blob --&gt; need to pass it from blob to blob
</span></span></span><span class="line"><span class="cl">    <span class="n">EventBlobHeader</span> <span class="n">header</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">OnParseBlob</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="n">isCompressed</span><span class="p">,</span> <span class="n">blobSize</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">totalBlobSize</span> <span class="o">+=</span> <span class="n">blobSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">blobSize</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">totalBlobSize</span> <span class="o">&gt;=</span> <span class="n">remainingBlockSize</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span> <span class="c1">// try to detect last blob
</span></span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="c1">// don&#39;t forget to check the end of block tag
</span></span></span><span class="line"><span class="cl">            <span class="kt">uint8_t</span> <span class="n">tag</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">ReadByte</span><span class="p">(</span><span class="n">tag</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">tag</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">EndObject</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Missing end of block tag</span><span class="se">\n</span><span class="s">&#34;</span><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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="nb">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></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">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>Here is the tricky part: to gain space, each blob starts with a header that could be “compressed”. The compression mechanism is simple: the first byte is a bitfield value that indicates which fields are present (i.e. their value should be read from the memory block) or skipped (i.e. their value is the same as the previous blob header). Therefore, an <strong>EventBlobHeader</strong> is passed by reference to the <strong>OnParseBlob</strong> function. My <strong>MetadataParser</strong> and <strong>EventParser</strong> implementations of <strong>OnParseBlob</strong> both starts with the same code to read the header:</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></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="kt">bool</span> <span class="n">XXXParser</span><span class="o">::</span><span class="n">OnParseBlob</span><span class="p">(</span><span class="n">EventBlobHeader</span><span class="o">&amp;</span> <span class="n">header</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isCompressed</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">&amp;</span> <span class="n">blobSize</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">isCompressed</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="o">!</span><span class="n">ReadCompressedHeader</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="n">blobSize</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><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">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadUncompressedHeader</span><span class="p">(</span><span class="n">header</span><span class="p">,</span> <span class="n">blobSize</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><span class="line"><span class="cl">    <span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The implementation to read compressed and uncompressed version of the header is a direct translation of <a href="https://github.dev/microsoft/perfview/blob/b5d1f0423ed5fb6521fae0f3c9e92c886752ac8d/src/TraceEvent/EventPipe/EventPipeEventSource.cs#L1439">the TraceEvent C# code</a> into C++.</p>
<p>The <strong>EventBlobHeader</strong> contains details of 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></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="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nc">EventBlobHeader_V4</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">EventSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">MetadataId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">SequenceNumber</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">ThreadId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">CaptureThreadId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ProcessorNumber</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">StackId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">Timestamp</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">GUID</span> <span class="n">ActivityId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">GUID</span> <span class="n">RelatedActivityId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">PayloadSize</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><ul>
<li>The “identity” of an event is given by the <strong>MetadataId</strong> field that refers to information defined in Metadata “object“ (for which <strong>MetadataId</strong> is 0).</li>
<li>The <strong>SequenceNumber</strong> field is incremented on a per thread basis each time an event is emitted. This could be used to detect if some events have been dropped (for a given <strong>CaptureThreadId</strong>, two consecutive events have a <strong>SequenceNumber</strong> incremented by more than 1 — more on dropped events in the forthcoming SequencePoint “object” description). Its value is 0 for a metadata “object”</li>
<li>The <strong>ThreadId</strong> and <strong>CaptureThreadId</strong> field have always the same value for Event “object”; <strong>CaptureThreadId</strong> is 0 for Metadata “object”.</li>
<li>In case of Event “object”, the <strong>StackId</strong> field refers to one of the stacks extracted from a Stack “object”. Its value is 0 for Metadata “object”.</li>
</ul>
<h2 id="the-metadataobject">The Metadata “object”</h2>
<p>As <a href="https://github.com/microsoft/perfview/blob/main/src/TraceEvent/EventPipe/EventPipeFormat.md">the documentation states</a>, <em>each MetadataBlock holds a set of metadata records. Each metadata record has an ID and it describes one type of event. Each event has a metadataId field which will indicate the ID of the metadata record which describes that event</em>.</p>
<p>The resulting mapping is stored in <strong>EventPipeSession</strong> class:</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="c1">// per metadataID event metadata description
</span></span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="p">,</span> <span class="n">EventCacheMetadata</span><span class="o">&gt;</span> <span class="n">_metadata</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>However, the rest of the documentation is partially right in the case of nettrace stream received through EventPipe: <em>Metadata includes an event name, provider name, and the layout of fields that are encoded in the event’s payload section.</em></p>
<p>First, the fields layout is simply not there. In addition, for some providers (dotnet runtime, private and rundown), the event names are empty strings. So, the data structure filled from the <strong>MetadataBlock</strong> will most of the time have an empty <strong>EventName</strong> field. Note that the “Microsoft-DotNETCore-EventPipe” provider (i.e. command events for that specific provider) and EventSource-derived classes written in C# provide the events 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><span class="lnt">10
</span><span class="lnt">11
</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">class</span> <span class="nc">EventCacheMetadata</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span>     <span class="n">MetadataId</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">ProviderName</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span>     <span class="n">EventId</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">EventName</span><span class="p">;</span> <span class="c1">// empty most of the time
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span>     <span class="n">Keywords</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span>     <span class="n">Version</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span>     <span class="n">Level</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 provider’s name serialized as a UTF16 string (including last ‘\0’ wide character), the <strong>EventId</strong> field is the key used to identify an event.</p>
<p>After these details, you will find a 4 bytes value corresponding to the number of fields in the event payload. As already mentioned, this value is always 0 so my code is skipping the rest of the metadata block payload.</p>
<h2 id="the-eventobject">The Event “object”</h2>
<p>And at last, here comes the time to parse Event “object” payload! The <strong>MetadataId</strong> field of the <strong>EventBlobHeader</strong> is used to find the provider’s name and event id:</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="kt">bool</span> <span class="n">EventParser</span><span class="o">::</span><span class="n">OnParseBlob</span><span class="p">(</span><span class="n">EventBlobHeader</span><span class="o">&amp;</span> <span class="n">header</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">isCompressed</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">&amp;</span> <span class="n">blobSize</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">auto</span><span class="o">&amp;</span> <span class="n">metadataDef</span> <span class="o">=</span> <span class="n">_metadata</span><span class="p">[</span><span class="n">header</span><span class="p">.</span><span class="n">MetadataId</span><span class="p">];</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>So, the rest of the function reads the payload based on the expected event id:</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></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">switch</span> <span class="p">(</span><span class="n">metadataDef</span><span class="p">.</span><span class="n">EventId</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">case</span> <span class="n">EventIDs</span><span class="o">::</span><span class="nl">AllocationTick</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">OnAllocationTick</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">PayloadSize</span><span class="p">,</span> <span class="n">metadataDef</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><span class="line"><span class="cl">            <span class="k">break</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="p">...</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></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="n">EventIDs</span><span class="o">::</span><span class="nl">ExceptionThrown</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">OnExceptionThrown</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">PayloadSize</span><span class="p">,</span> <span class="n">metadataDef</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><span class="line"><span class="cl">            <span class="k">break</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 class="c1">// skip events we are not interested in
</span></span></span><span class="line"><span class="cl">        <span class="p">{</span>
</span></span><span class="line"><span class="cl">            <span class="n">SkipBytes</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">PayloadSize</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="n">blobSize</span> <span class="o">+=</span> <span class="n">header</span><span class="p">.</span><span class="n">PayloadSize</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="nb">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 format of each event payload is usually given by <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/clr-etw-events?WT.mc_id=DT-MVP-5003325">the Microsoft documentation</a>. If not, you should look into the <a href="https://github.com/dotnet/coreclr/blob/release/3.1/src/vm/ClrEtwAll.man">ClrEtwall.man file</a> where the payload of ALL events are defined. For example, the <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events#gcallocationtick_v3-event?WT.mc_id=DT-MVP-5003325"><em>AllocationTick</em> event payload</a> provides the name of the last allocated type to reach the 100 KB threshold (read <a href="/posts/2020-04-18_build-your-own-net/">this blog post</a> for more details about how to use this event):</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">//  AllocationAmount    UInt32          The allocation size, in bytes.
</span></span></span><span class="line"><span class="cl"><span class="c1">//                                      This value is accurate for allocations that are less than the length of a ULONG(4,294,967,295 bytes).
</span></span></span><span class="line"><span class="cl"><span class="c1">//                                      If the allocation is greater, this field contains a truncated value.
</span></span></span><span class="line"><span class="cl"><span class="c1">//                                      Use AllocationAmount64 for very large allocations.
</span></span></span><span class="line"><span class="cl"><span class="c1">//  AllocationKind      UInt32          0x0 - Small object allocation(allocation is in small object heap).
</span></span></span><span class="line"><span class="cl"><span class="c1">//                                      0x1 - Large object allocation(allocation is in large object heap).
</span></span></span><span class="line"><span class="cl"><span class="c1">//  ClrInstanceID       UInt16          Unique ID for the instance of CLR or CoreCLR.
</span></span></span><span class="line"><span class="cl"><span class="c1">//  AllocationAmount64  UInt64          The allocation size, in bytes.This value is accurate for very large allocations.
</span></span></span><span class="line"><span class="cl"><span class="c1">//  TypeId              Pointer         The address of the MethodTable.When there are several types of objects that were allocated during this event,
</span></span></span><span class="line"><span class="cl"><span class="c1">//                                      this is the address of the MethodTable that corresponds to the last object allocated (the object that caused the 100 KB threshold to be exceeded).
</span></span></span><span class="line"><span class="cl"><span class="c1">//  TypeName            UnicodeString   The name of the type that was allocated.When there are several types of objects that were allocated during this event,
</span></span></span><span class="line"><span class="cl"><span class="c1">//                                      this is the type of the last object allocated (the object that caused the 100 KB threshold to be exceeded).
</span></span></span><span class="line"><span class="cl"><span class="c1">//  HeapIndex           UInt32          The heap where the object was allocated.This value is 0 (zero)when running with workstation garbage collection.
</span></span></span><span class="line"><span class="cl"><span class="c1">//  Address             Pointer         The address of the last allocated object.
</span></span></span><span class="line"><span class="cl"><span class="c1">//
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Based on this fields definition, the <strong>EventParser::OnAllocationTick</strong> function is reading each field after the other thanks to the <strong>ReadWord</strong>, <strong>ReadDWord</strong>, <strong>ReadLong</strong> and <strong>ReadWString</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><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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventParser</span><span class="o">::</span><span class="n">OnAllocationTick</span><span class="p">(</span><span class="n">DWORD</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="n">EventCacheMetadata</span><span class="o">&amp;</span> <span class="n">metadataDef</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">DWORD</span> <span class="n">readBytesCount</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">DWORD</span> <span class="n">size</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">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">Allocation Tick:</span><span class="se">\n</span><span class="s">&#34;</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 common fields
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">dword</span> <span class="o">=</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="o">!</span><span class="n">ReadDWord</span><span class="p">(</span><span class="n">dword</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><span class="line"><span class="cl">    <span class="n">readBytesCount</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dword</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;   Amount        = &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">dword</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; bytes</span><span class="se">\n</span><span class="s">&#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">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadDWord</span><span class="p">(</span><span class="n">dword</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><span class="line"><span class="cl">    <span class="n">readBytesCount</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dword</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;   Kind          = &#34;</span> <span class="o">&lt;&lt;</span> <span class="p">((</span><span class="n">dword</span> <span class="o">==</span> <span class="mi">1</span><span class="p">)</span> <span class="o">?</span> <span class="s">&#34;LOH&#34;</span> <span class="o">:</span> <span class="s">&#34;small&#34;</span><span class="p">)</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; bytes</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">word</span> <span class="o">=</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="o">!</span><span class="n">ReadWord</span><span class="p">(</span><span class="n">word</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><span class="line"><span class="cl">    <span class="n">readBytesCount</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">word</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;   CLR ID        = &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">word</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">ulong</span> <span class="o">=</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="o">!</span><span class="n">ReadLong</span><span class="p">(</span><span class="n">ulong</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><span class="line"><span class="cl">    <span class="n">readBytesCount</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ulong</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;   Amount64      = &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">ulong</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; bytes</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The bitness of the monitored application is important when “pointers” need to be read from the payload: use <strong>ReadDWord</strong> for 32-bit and <strong>ReadLong</strong> for 64-bit:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="c1">// skip useless MT address
</span></span></span><span class="line"><span class="cl">    <span class="c1">// Note: handle 32/64 bit difference
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_is64Bit</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="o">!</span><span class="n">ReadLong</span><span class="p">(</span><span class="n">ulong</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><span class="line"><span class="cl">        <span class="n">readBytesCount</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ulong</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">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadDWord</span><span class="p">(</span><span class="n">dword</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><span class="line"><span class="cl">        <span class="n">readBytesCount</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">dword</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>And if you don’t need the rest of the payload, <strong>SkipBytes</strong> is your friend:</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">// skip the rest of the payload
</span></span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">SkipBytes</span><span class="p">(</span><span class="n">payloadSize</span> <span class="o">-</span> <span class="n">readBytesCount</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 had some issues when dealing with the <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/exception-thrown-v1-etw-event?WT.mc_id=DT-MVP-5003325"><strong>ExceptionThrown</strong> event payload</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></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">// Type             wstring     Exception type
</span></span></span><span class="line"><span class="cl"><span class="c1">// Message          wstring     Exception message
</span></span></span><span class="line"><span class="cl"><span class="c1">// EIPCodeThrow     win:Pointer Instruction pointer where exception occurred.
</span></span></span><span class="line"><span class="cl"><span class="c1">// ExceptionHR      win:UInt32  Exception HRESULT.
</span></span></span><span class="line"><span class="cl"><span class="c1">// ExceptionFlags   win:UInt16
</span></span></span><span class="line"><span class="cl"><span class="c1">//      0x01: HasInnerException (see CLR ETW Events in the Visual Basic documentation).
</span></span></span><span class="line"><span class="cl"><span class="c1">//      0x02: IsNestedException.
</span></span></span><span class="line"><span class="cl"><span class="c1">//      0x04: IsRethrownException.
</span></span></span><span class="line"><span class="cl"><span class="c1">//      0x08: IsCorruptedStateException (indicates that the process state is corrupt).
</span></span></span><span class="line"><span class="cl"><span class="c1">//      0x10: IsCLSCompliant (an exception that derives from Exception is CLS-compliant).
</span></span></span><span class="line"><span class="cl"><span class="c1">// ClrInstanceID win:UInt16 Unique ID for the instance of CLR or CoreCLR.
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>In case of an empty message, the field itself was not even there! Not even 0 for a ‘\0’ wide character… In fact, there is a bug in the serialization code that skips the field in that case. This has been <a href="https://github.com/dotnet/runtime/commit/72e2420fd227aa45c86577622cf3ed4adfbbb461">fixed in .NET 6</a> by storing “NULL” as the serialized string: I would have preferred ‘\0’ but it seems to be compatible with the ETW implementation.</p>
<p>To support .NET Core 3+ and .NET 5, my code is comparing the size of the remaining of the payload after reading the exception type with the expected size of the 4 remaining fields after the exception message. If it is greater then it means that there is a string for the message. If not, I know that the message is empty:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventParser</span><span class="o">::</span><span class="n">OnExceptionThrown</span><span class="p">(</span><span class="n">DWORD</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="n">EventCacheMetadata</span><span class="o">&amp;</span> <span class="n">metadataDef</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">DWORD</span> <span class="n">readBytesCount</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">DWORD</span> <span class="n">size</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">// read exception type
</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">// Size of the ExceptionThrown payload AFTER the Message field
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">exceptionRemainingPayloadSize</span> <span class="o">=</span> <span class="p">(</span><span class="n">_is64Bit</span> <span class="o">?</span> <span class="mi">8</span> <span class="o">:</span> <span class="mi">4</span><span class="p">)</span> <span class="o">+</span> <span class="mi">4</span> <span class="o">+</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// In case of &#34;empty&#34; message, it might not be even visible as &#34;\0&#34; before .NET Core 6 (and after, will be &#34;NULL&#34;)
</span></span></span><span class="line"><span class="cl">    <span class="c1">// so it is needed to check if the remaining payload contains such a string
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">((</span><span class="n">payloadSize</span> <span class="o">-</span> <span class="n">readBytesCount</span><span class="p">)</span> <span class="o">==</span> <span class="n">_exceptionRemainingPayloadSize</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">wcout</span> <span class="o">&lt;&lt;</span> <span class="sa">L</span><span class="s">&#34;   message = &#39;&#39;</span><span class="se">\n</span><span class="s">&#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="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadWString</span><span class="p">(</span><span class="n">strBuffer</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="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><span class="line"><span class="cl">        <span class="n">readBytesCount</span> <span class="o">+=</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">// handle empty string case (check for &#34;NULL&#34; in case of .NET 6+)
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">strBuffer</span><span class="p">.</span><span class="n">empty</span><span class="p">()</span> <span class="o">||</span> <span class="p">(</span><span class="n">wcscmp</span><span class="p">(</span><span class="n">strBuffer</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="sa">L</span><span class="s">&#34;NULL&#34;</span><span class="p">)</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">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;   message = &#39;&#39;</span><span class="se">\n</span><span class="s">&#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="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;   message = &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">strBuffer</span><span class="p">.</span><span class="n">c_str</span><span class="p">()</span> <span class="o">&lt;&lt;</span> <span class="sa">L</span><span class="s">&#34;</span><span class="se">\n</span><span class="s">&#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="c1">// skip the rest of the payload
</span></span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nf">SkipBytes</span><span class="p">(</span><span class="n">payloadSize</span> <span class="o">-</span> <span class="n">readBytesCount</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="the-sequencepointblock-object">The SequencePointBlock “object”</h2>
<p>The last “object” type is the sequence point block that contains the following fields:</p>
<p><img loading="lazy" src="/posts/2023-03-10_from-metadata-to-event/1_aPaYZ4KOad8jUjnBzHggpg.png"></p>
<p>In addition to these fields, it also <a href="https://github.com/microsoft/perfview/blob/main/src/TraceEvent/EventPipe/EventPipeFormat.md#sequencepointblock-object">implicitly tells you</a> that new stack “object” will be received (with stack id restarting from 1) to match next Event “objects”. For example, the following trace shows how a sequence point block resets the stacks by restarting at 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><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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-markdown" data-lang="markdown"><span class="line"><span class="cl">Event block (140 bytes)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blob header:
</span></span><span class="line"><span class="cl">   StackId           = 3
</span></span><span class="line"><span class="cl">Contention
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blob header:
</span></span><span class="line"><span class="cl">   StackId           = 4
</span></span><span class="line"><span class="cl">Event = 81
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blob header:
</span></span><span class="line"><span class="cl">   StackId           = 3
</span></span><span class="line"><span class="cl">Contention
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">------------------------------------------------ 
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">________________________________________________</span>
</span></span><span class="line"><span class="cl">SequencePoint block (217 bytes)
</span></span><span class="line"><span class="cl">...
</span></span><span class="line"><span class="cl">------------------------------------------------
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">________________________________________________</span>
</span></span><span class="line"><span class="cl">Stack block (105 bytes)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Stack block header:
</span></span><span class="line"><span class="cl">   FirstID: 1
</span></span><span class="line"><span class="cl">   Count  : 2
</span></span><span class="line"><span class="cl">------------------------------------------------
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="gs">________________________________________________</span>
</span></span><span class="line"><span class="cl">Event block (92 bytes)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blob header:
</span></span><span class="line"><span class="cl">   StackId           = 1
</span></span><span class="line"><span class="cl">Contention
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blob header:
</span></span><span class="line"><span class="cl">   StackId           = 2
</span></span><span class="line"><span class="cl">Event = 81
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">blob header:
</span></span><span class="line"><span class="cl">   StackId           = 1
</span></span><span class="line"><span class="cl">Contention:
</span></span><span class="line"><span class="cl">------------------------------------------------
</span></span></code></pre></td></tr></table>
</div>
</div><p>So, the stacks you might have cached based on the already received stack “objects” should now be invalidated like what I’m doing in <strong>SequencePointParser::OnParse</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></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="kt">bool</span> <span class="n">SequencePointParser</span><span class="o">::</span><span class="n">OnParse</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">// reset stack caches
</span></span></span><span class="line"><span class="cl">    <span class="n">_stacks32</span><span class="p">.</span><span class="n">clear</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">_stacks64</span><span class="p">.</span><span class="n">clear</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>You now have all the elements you need to listen to CLR events on Windows and Linux for .NET Core 3+ and .NET 5+. If you are still running applications with .NET Framework, you will need to use ETW but this is another story.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/posts/2022-07-28_digging-into-the-clr/">Episode 1</a> — <em>Digging into the CLR Diagnostics IPC Protocol in C#</em></li>
<li><a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">Episode 2</a> — <em>.NET Diagnostic IPC protocol: the C++ way</em></li>
<li>[Episode 3 ](/posts/2022-10-23_clr-events-go-for/ <em>CLR events: go for the nettrace file format!</em></li>
<li>[Episode 4 ](/posts/2022-11-27_parsing-the-nettrace-stream/ <em>Parsing the “nettrace” steam</em></li>
<li>[Episode 5 ](/posts/2023-01-15_reading-object-in-memory/ <em>Reading “object” in memory — starting with stacks</em></li>
<li><a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">Source code</a> for the C++ implementation of CLR events listener</li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Reading “object” in memory — starting with stacks</title><link>https://chrisnas.github.io/posts/2023-01-15_reading-object-in-memory/</link><pubDate>Sun, 15 Jan 2023 16:38:45 +0000</pubDate><guid>https://chrisnas.github.io/posts/2023-01-15_reading-object-in-memory/</guid><description>During the parsing of the nettrace format, blocks are serialized as “object”. Let’s look at “stack” objects.</description><content:encoded><![CDATA[<hr>
<p>The previous episodes started the parsing of the “nettrace” format used when <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">contacting the .NET Diagnostics IPC server</a> and <a href="/posts/2022-10-23_clr-events-go-for/">initiate the protocol to receive CLR events</a>. It is now time to see how to get the payload of each “object” type, especially how stacks are stored.</p>
<p>We have seen that the stream starts with a <strong>TraceObject</strong> that describes the rest of the stream followed by a sequence of “object”:</p>
<p><img loading="lazy" src="/posts/2023-01-15_reading-object-in-memory/1_E9Rq89JSc_OIfW9ooEfm1A.png"></p>
<p>The remaining of each “object” is a 32 bit block size followed by the payload.</p>
<p>Well… not only. One thing I missed when I started to work on the nettrace format is the fact that all “object” payloads must be 4-bytes aligned <strong>on the beginning of the stream</strong>!</p>
<p>This is why I’m keeping track of the current position in the <strong>EventPipeSession</strong> class:</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></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">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">IIpcEndpoint</span><span class="o">*</span> <span class="n">_pEndpoint</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">_stopRequested</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// parsers
</span></span></span><span class="line"><span class="cl">    <span class="n">MetadataParser</span> <span class="n">_metadataParser</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">EventParser</span> <span class="n">_eventParser</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">StackParser</span> <span class="n">_stackParser</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">SequencePointParser</span> <span class="n">_sequencePointParser</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 position since the beginning of the &#34;file&#34;
</span></span></span><span class="line"><span class="cl">    <span class="c1">// i.e. starting at 0 from the first character of the NettraceHeader
</span></span></span><span class="line"><span class="cl">    <span class="c1">//      Nettrace
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">_position</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>So each <strong>ParseXXXBlock</strong> function checks the minimum reader version in the header before reading the “object” payload as a memory block. The idea is being able to support backward compatibility:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">ParseMetadataBlock</span><span class="p">(</span><span class="n">ObjectHeader</span><span class="o">&amp;</span> <span class="n">header</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">header</span><span class="p">.</span><span class="n">MinReaderVersion</span> <span class="o">!=</span> <span class="mi">2</span><span class="p">)</span> <span class="k">return</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="kt">uint32_t</span> <span class="n">blockSize</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">// read the block and send it to the corresponding parser
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">blockOriginInFile</span> <span class="o">=</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="o">!</span><span class="n">ExtractBlock</span><span class="p">(</span><span class="s">&#34;Metadata&#34;</span><span class="p">,</span> <span class="n">blockSize</span><span class="p">,</span> <span class="n">blockOriginInFile</span><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></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">_metadataParser</span><span class="p">.</span><span class="n">Parse</span><span class="p">(</span><span class="n">_pBlock</span><span class="p">,</span> <span class="n">blockSize</span><span class="p">,</span> <span class="n">blockOriginInFile</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 <strong>ExtractBlock</strong> function reads the size of the payload (and skips the padding if any) with <strong>ReadBlockSize</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></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="kt">bool</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">ExtractBlock</span><span class="p">(</span><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">blockName</span><span class="p">,</span> <span class="kt">uint32_t</span><span class="o">&amp;</span> <span class="n">blockSize</span><span class="p">,</span> <span class="kt">uint64_t</span><span class="o">&amp;</span> <span class="n">blockOriginInFile</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 block size
</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">ReadBlockSize</span><span class="p">(</span><span class="n">blockName</span><span class="p">,</span> <span class="n">blockSize</span><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></span><span class="line"><span class="cl">    <span class="c1">// skip the block + final EndOfObject tag
</span></span></span><span class="line"><span class="cl">    <span class="n">blockSize</span><span class="o">++</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 block name is only used for error messages if needed.</p>
<p>The next step is to read the payload in a memory block using these two <strong>EventPipeSession</strong> fields:</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="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// buffer used to read each block that will be then parsed
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">_pBlock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">_blockSize</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 the session constructor, <strong>_blockSize</strong> is set to 4 KB and <strong>_pBlock</strong> points to an allocated memory buffer of that size.</p>
<p>The rest of <strong>ExtractBlock</strong> deals with payload size: if the current payload to parse is larger than <strong>_blockSize</strong>, then these fields are updated up to a maximum of 100 KB (i.e. max block size sent by the CLR).</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">// check if it is needed to resize the block buffer
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">_blockSize</span> <span class="o">&lt;</span> <span class="n">blockSize</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">// don&#39;t expect blocks larger than 100KB
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">blockSize</span> <span class="o">&gt;</span> <span class="n">MAX_BLOCK_SIZE</span><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></span><span class="line"><span class="cl">        <span class="k">delete</span> <span class="p">[]</span> <span class="n">_pBlock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_pBlock</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">uint8_t</span><span class="p">[</span><span class="n">blockSize</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">        <span class="o">::</span><span class="n">ZeroMemory</span><span class="p">(</span><span class="n">_pBlock</span><span class="p">,</span> <span class="n">blockSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_blockSize</span> <span class="o">=</span> <span class="n">blockSize</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 current position in file for padding
</span></span></span><span class="line"><span class="cl">    <span class="n">blockOriginInFile</span> <span class="o">=</span> <span class="n">_position</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">Read</span><span class="p">(</span><span class="n">_pBlock</span><span class="p">,</span> <span class="n">blockSize</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">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while extracting &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">blockName</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; block: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">blockName</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; block (&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">blockSize</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; bytes)</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">DumpBuffer</span><span class="p">(</span><span class="n">_pBlock</span><span class="p">,</span> <span class="n">blockSize</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="nb">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>For debugging sake, I’m displaying each “object” payload</p>
<p><img loading="lazy" src="/posts/2023-01-15_reading-object-in-memory/1_bWygt-T2kMQfIlcxIv_3VA.png"></p>
<p>thanks to the <strong>DumpBuffer</strong> helper.</p>
<p>To ease the memory access to the memory block content, my <strong>BlockParser</strong> will be used as a base class for each dedicated 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><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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">BlockParser</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">BlockParser</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">Parse</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span> <span class="n">pBlock</span><span class="p">,</span> <span class="kt">uint32_t</span> <span class="n">bytesCount</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">blockOriginInFile</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">void</span> <span class="nf">SetPointerSize</span><span class="p">(</span><span class="kt">uint8_t</span> <span class="n">pointerSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">PointerSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">protected</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">OnParse</span><span class="p">()</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">// Access helpers
</span></span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">Read</span><span class="p">(</span><span class="n">LPVOID</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">bufferSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadByte</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">&amp;</span> <span class="n">byte</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadWord</span><span class="p">(</span><span class="kt">uint16_t</span><span class="o">&amp;</span> <span class="n">word</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadDWord</span><span class="p">(</span><span class="kt">uint32_t</span><span class="o">&amp;</span> <span class="n">dword</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadLong</span><span class="p">(</span><span class="kt">uint64_t</span><span class="o">&amp;</span> <span class="n">ulong</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadDouble</span><span class="p">(</span><span class="kt">double</span><span class="o">&amp;</span> <span class="n">d</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadVarUInt32</span><span class="p">(</span><span class="kt">uint32_t</span><span class="o">&amp;</span> <span class="n">val</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">&amp;</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadVarUInt64</span><span class="p">(</span><span class="kt">uint64_t</span><span class="o">&amp;</span> <span class="n">val</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">&amp;</span> <span class="n">size</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">ReadWString</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="o">&amp;</span> <span class="n">wstring</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">&amp;</span> <span class="n">bytesRead</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="nf">SkipBytes</span><span class="p">(</span><span class="kt">uint32_t</span> <span class="n">byteCount</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1">// shared fields
</span></span></span><span class="line"><span class="cl"><span class="k">protected</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">bool</span> <span class="n">_is64Bit</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">_blockSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</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="k">private</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span><span class="o">*</span> <span class="n">_pBlock</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">_blockOriginInFile</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 <strong>Parse</strong> function accepts the memory buffer containing an “object” payload, its size and its position since the beginning of the stream. The derived class will have to implement the <strong>OnParse</strong> function using the <strong>ReadXXX</strong> helpers.</p>
<p>The two <strong>ReadVarUintXXX</strong> functions are different from the other direct read helpers because they deal with some simple compression mechanisms used by the serialization of 32-bit and 64-bit numbers.</p>
<p>In the different types of “object” payloads, the strings are serialized as UTF16 strings ending with a “\0” wide character. Here is the implementation of the helper function used to read a <strong>std::wstring</strong> from a memory block:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">BlockParser</span><span class="o">::</span><span class="n">ReadWString</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">wstring</span><span class="o">&amp;</span> <span class="n">wstring</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">&amp;</span> <span class="n">bytesRead</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">uint16_t</span> <span class="n">character</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">bytesRead</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>  <span class="c1">// in case of empty string
</span></span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="nb">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="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadWord</span><span class="p">(</span><span class="n">character</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// protect against invalid UNICODE character (due to missing fields in ExceptionThrown event)
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">character</span> <span class="o">&gt;</span> <span class="mi">256</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">// rewind the character
</span></span></span><span class="line"><span class="cl">            <span class="n">_pos</span> <span class="o">=</span> <span class="n">_pos</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">character</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 only covering a missing string
</span></span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="p">(</span><span class="n">bytesRead</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">bytesRead</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">character</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 that an empty string contains only that \0 character
</span></span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">character</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1">// \0 final character of the string
</span></span></span><span class="line"><span class="cl">            <span class="k">return</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="n">wstring</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">character</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 the check for character content in the loop: this is due to a serialization issue I will discuss later when the event “object” block will be detailed.</p>
<h2 id="the-stackobject">The Stack “object”</h2>
<p>If you remember my previous post about <a href="/posts/2020-05-18_build-your-own-net/">retrieving call stacks for CLR events with TraceEvent</a>, you might be wondering why there is a specific stack object since a <strong>ClrStackWalk</strong> event should contain the frames if the <strong>Stack</strong> keyword is enabled for the .NET provider. In fact, the current TraceEvent implementation is not using the stack object sent by the CLR (maybe to have the same code between ETW and EventPipe).</p>
<p>One stack “object” received in a nettrace stream contains one or more stacks. Each stack is identified by an id (more about this soon) and contains a list of instruction pointer addresses.</p>
<p><img loading="lazy" src="/posts/2023-01-15_reading-object-in-memory/1_H_HbR0-xWzR3SWV2KEimpQ.png"></p>
<p>In the previous screenshot, the id of the first stack is 1 and the second is 2. In the next stack “object”, the <strong>FirstId</strong> field will be 3 and so on. This avoids storing the id in each call stack and saves space.</p>
<p>Note that even if this does not seem to make any sense, it might happen that the addresses list is empty.</p>
<p><img loading="lazy" src="/posts/2023-01-15_reading-object-in-memory/1_3NZb7jI5cPwz4wtDnZzhCw.png"></p>
<p>These call stacks are stored in <strong>EventPipeSession</strong> as a per id cache:</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></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">// per stackID stack
</span></span></span><span class="line"><span class="cl">    <span class="c1">// only one will be used depending on the bitness of the monitored application
</span></span></span><span class="line"><span class="cl">    <span class="n">std</span><span class="o">::</span><span class="n">unordered_map</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="p">,</span> <span class="n">EventCacheStack32</span><span class="o">&gt;</span> <span class="n">_stacks32</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">unordered_map</span><span class="o">&lt;</span><span class="kt">uint32_t</span><span class="p">,</span> <span class="n">EventCacheStack64</span><span class="o">&gt;</span> <span class="n">_stacks64</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The frames are stored as addresses in a vector:</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="k">class</span> <span class="nc">EventCacheStack32</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Id</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="kt">uint32_t</span><span class="o">&gt;</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 CLR is sending one stack per unique callstack (i.e. at least one frame is different). As you will soon see, each event “object” contains a stack id corresponding to the chain of code from which it is sent.</p>
<p>The next episode will detail the <strong>Metadata</strong> and <strong>Event</strong> blocks to end the series.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/posts/2022-07-28_digging-into-the-clr/">Episode 1</a> — <em>Digging into the CLR Diagnostics IPC Protocol in C#</em></li>
<li><a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">Episode 2</a> — <em>.NET Diagnostic IPC protocol: the C++ way</em></li>
<li>[Episode 3 ](/posts/2022-10-23_clr-events-go-for/ <em>CLR events: go for the nettrace file format!</em></li>
<li>[Episode 4 ](/posts/2022-11-27_parsing-the-nettrace-stream/ <em>Parsing the “nettrace” steam</em></li>
<li><a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">Source code</a> for the C++ implementation of CLR events listener</li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Parsing the “nettrace” stream of (not only) events</title><link>https://chrisnas.github.io/posts/2022-11-27_parsing-the-nettrace-stream/</link><pubDate>Sun, 27 Nov 2022 13:59:44 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-11-27_parsing-the-nettrace-stream/</guid><description>The previous episodes explained how to initiate the protocol to receive CLR events. It is now time to dig into the “nettrace” stream…</description><content:encoded><![CDATA[<hr>
<p>The previous episodes explained how to <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">contact the .NET Diagnostics IPC server</a> and <a href="/posts/2022-10-23_clr-events-go-for/">initiate the protocol to receive CLR events</a>. It is now time to dig into the “nettrace” stream format!</p>
<p>As the <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">IPC command documentation</a> states, the response to the <strong>CollectTracing</strong> command is <em>followed by an Optional Continuation of a nettrace format stream of events</em>. In fact, before .NET Core 3, the <a href="https://github.com/microsoft/perfview/blob/main/src/TraceEvent/EventPipe/NetPerfFormat.md">netperf format</a> was used but I will focus on the nettrace format also used in .NET 5+.</p>
<p>From a high-level view, it is a header followed by a stream of “objects”; each described by a header and ending with a byte with <strong>6</strong> as value:</p>
<p><img loading="lazy" src="/posts/2022-11-27_parsing-the-nettrace-stream/1_Zh-waKB1J8VK4ZIYRlGZAA.png"></p>
<p>Let’s start with the <em>nettrace</em> header:</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-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">NettraceHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">Magic</span><span class="p">[</span><span class="mi">8</span><span class="p">];</span>               <span class="c1">// &#34;Nettrace&#34; with not &#39;\0&#39;
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">FastSerializationLen</span><span class="p">;</span>  <span class="c1">// 20
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">FastSerialization</span><span class="p">[</span><span class="mi">20</span><span class="p">];</span>  <span class="c1">// &#34;!FastSerialization.1&#34; with not &#39;\0&#39;
</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">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">NettraceHeaderMagic</span> <span class="o">=</span> <span class="s">&#34;Nettrace&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">FastSerializationMagic</span> <span class="o">=</span> <span class="s">&#34;!FastSerialization.1&#34;</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>It can be used to check the format and version of the received data (in case of format evolution over time):</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="nf">CheckNettraceHeader</span><span class="p">(</span><span class="n">NettraceHeader</span><span class="o">&amp;</span> <span class="n">header</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="o">!</span><span class="n">IsSameAsString</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">Magic</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">Magic</span><span class="p">),</span> <span class="n">NettraceHeaderMagic</span><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></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">FastSerializationLen</span> <span class="o">!=</span> <span class="n">strlen</span><span class="p">(</span><span class="n">FastSerializationMagic</span><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></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">IsSameAsString</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">FastSerialization</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">FastSerialization</span><span class="p">),</span> <span class="n">FastSerializationMagic</span><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></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">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>In memory, the “strings” are stored as an array of UTF8 characters without trailing ‘\0’</p>
<p><img loading="lazy" src="/posts/2022-11-27_parsing-the-nettrace-stream/1_MznhHc2R0DLQP7UFKBiFeA.png"></p>
<p>This drives the implementation of the comparison helper:</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></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="kt">bool</span> <span class="nf">IsSameAsString</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span> <span class="n">bytes</span><span class="p">,</span> <span class="kt">uint16_t</span> <span class="n">length</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">characters</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">memcmp</span><span class="p">(</span><span class="n">bytes</span><span class="p">,</span> <span class="n">characters</span><span class="p">,</span> <span class="n">length</span><span class="p">)</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></code></pre></td></tr></table>
</div>
</div><h2 id="everything-is-anobject">Everything is an “object”</h2>
<p>After the header, data is represented as “objects” whose description is stored in an <strong>ObjectHeader</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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">ObjectHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">NettraceTag</span> <span class="n">TagTraceObject</span><span class="p">;</span>         <span class="c1">// 5
</span></span></span><span class="line"><span class="cl">    <span class="n">NettraceTag</span> <span class="n">TagTypeObjectForTrace</span><span class="p">;</span>  <span class="c1">// 5
</span></span></span><span class="line"><span class="cl">    <span class="n">NettraceTag</span> <span class="n">TagType</span><span class="p">;</span>                <span class="c1">// 1
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Version</span><span class="p">;</span>                   <span class="c1">//
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">MinReaderVersion</span><span class="p">;</span>          <span class="c1">//
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">NameLength</span><span class="p">;</span>                <span class="c1">// length of UTF8 name that follows
</span></span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>followed by the name of the object type in UTF8. Note that, like for “!FastSerialization.1” in <strong>NettraceHeader</strong>, its length is provided in the <strong>NameLength</strong> field of the <strong>ObjectHeader</strong>. For example, here is how the initial TraceObject header is stored in memory:</p>
<p><img loading="lazy" src="/posts/2022-11-27_parsing-the-nettrace-stream/1_YjXZ6fDTOJFT3u-LwfTXyw.png"></p>
<p>and its equivalent in 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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nl">TraceObjectHeader</span> <span class="p">:</span> <span class="n">ObjectHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="c1">//NettraceTag TagTraceObject;         // 5
</span></span></span><span class="line"><span class="cl">  <span class="c1">//NettraceTag TagTypeObjectForTrace;  // 5
</span></span></span><span class="line"><span class="cl">  <span class="c1">//NettraceTag TagType;                // 1
</span></span></span><span class="line"><span class="cl">  <span class="c1">//uint32_t Version;                   // 4
</span></span></span><span class="line"><span class="cl">  <span class="c1">//uint32_t MinReaderVersion;          // 4
</span></span></span><span class="line"><span class="cl">  <span class="c1">//uint32_t NameLength;                // 5
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">Name</span><span class="p">[</span><span class="mi">5</span><span class="p">];</span>                    <span class="c1">// &#39;Trace
</span></span></span><span class="line"><span class="cl">    <span class="n">NettraceTag</span> <span class="n">TagEndTraceObject</span><span class="p">;</span>      <span class="c1">// 6
</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 after the “object” type name, an “end of object” byte (i.e. = 6) appears before the payload.</p>
<p>So, each kind of “object” shares the same <strong>ObjectHeader</strong> followed by a UTF8 type name:</p>
<ul>
<li>“EventBlock” : contains one or more events</li>
<li>“MetadataBlock” : contains partial description of events (no name nor payload fields)</li>
<li>“StackBlock” : contains call stacks (i.e. arrays of instruction pointers)</li>
<li>“SPBlock” : contains check point inside the stream inside the stream (used for drop message detection and callstack cache invalidation)</li>
</ul>
<p>It means that you need to compare the strings to figure out the “object” 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><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></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">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">EventBlockName</span> <span class="o">=</span> <span class="s">&#34;EventBlock&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">MetadataBlockName</span> <span class="o">=</span> <span class="s">&#34;MetadataBlock&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">StackBlockName</span> <span class="o">=</span> <span class="s">&#34;StackBlock&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">char</span><span class="o">*</span> <span class="n">SequencePointBlockName</span> <span class="o">=</span> <span class="s">&#34;SPBlock&#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">ObjectType</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">GetObjectType</span><span class="p">(</span><span class="n">ObjectHeader</span><span class="o">&amp;</span> <span class="n">header</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">// check validity
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">TagTraceObject</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">BeginPrivateObject</span><span class="p">)</span> <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">header</span><span class="p">.</span><span class="n">TagTypeObjectForTrace</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">BeginPrivateObject</span><span class="p">)</span> <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">header</span><span class="p">.</span><span class="n">TagType</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">NullReference</span><span class="p">)</span> <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// figure out which type it is based on the name:
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   EventBlock -&gt; &#34;EventBlock&#34;  (size = 10)
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   MetadataBlock -&gt; &#34;MetadataBlock&#34; (size = 13)
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   StackBlock -&gt; &#34;StackBlock&#34; (size = 10)
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   SequencePointBlock -&gt; &#34;SPBlock&#34; (size = 7)
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">NameLength</span> <span class="o">==</span> <span class="mi">13</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">uint8_t</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">13</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">Read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">13</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="n">MetadataBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">MetadataBlock</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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="nf">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">NameLength</span> <span class="o">==</span> <span class="mi">10</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">uint8_t</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">10</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">Read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">EventBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">EventBlock</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="k">if</span> <span class="p">(</span><span class="n">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">StackBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">StackBlock</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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="nf">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">NameLength</span> <span class="o">==</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="kt">uint8_t</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">7</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">Read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="n">SequencePointBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">SequencePointBlock</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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 first object that appears in the stream contains details about the whole stream:</p>
<p><img loading="lazy" src="/posts/2022-11-27_parsing-the-nettrace-stream/1_wgeRsZ8QOX-IeBluAI6nmg.png"></p>
<p>After the header, some fields follow as a payload:</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-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">ObjectFields</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Year</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Month</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">DayOfWeek</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Day</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Hour</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Minute</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Second</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Millisecond</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">SyncTimeQPC</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">QPCFrequency</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">PointerSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ProcessId</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">NumProcessors</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ExpectedCPUSamplingRate</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>Beyond the timestamp information and the pointer size (required when call stack instruction pointers will be read as 8 (64-bit) or 4 (32-bit) bytes addresses) the other fields are not really interesting.</p>
<h2 id="lets-go-back-to-a-higher-level-view">Let’s go back to a higher-level view</h2>
<p>Here is the code to listen to the nettrace stream:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">Listen</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="o">!</span><span class="n">ReadHeader</span><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></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ReadTraceObjectHeader</span><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></span><span class="line"><span class="cl">    <span class="n">ObjectFields</span> <span class="n">ofTrace</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">ReadObjectFields</span><span class="p">(</span><span class="n">ofTrace</span><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></span><span class="line"><span class="cl">    <span class="c1">// use the &#34;trace object&#34; fields to figure out the bitness of the application
</span></span></span><span class="line"><span class="cl">    <span class="n">Is64Bit</span> <span class="o">=</span> <span class="n">ofTrace</span><span class="p">.</span><span class="n">PointerSize</span> <span class="o">==</span> <span class="mi">8</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_stackParser</span><span class="p">.</span><span class="n">SetPointerSize</span><span class="p">(</span><span class="n">ofTrace</span><span class="p">.</span><span class="n">PointerSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_metadataParser</span><span class="p">.</span><span class="n">SetPointerSize</span><span class="p">(</span><span class="n">ofTrace</span><span class="p">.</span><span class="n">PointerSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_eventParser</span><span class="p">.</span><span class="n">SetPointerSize</span><span class="p">(</span><span class="n">ofTrace</span><span class="p">.</span><span class="n">PointerSize</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// don&#39;t forget to check the end object tag
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">tag</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">ReadByte</span><span class="p">(</span><span class="n">tag</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">tag</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">EndObject</span><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></span><span class="line"><span class="cl">    <span class="c1">// read one &#34;object&#34; after the other
</span></span></span><span class="line"><span class="cl">    <span class="k">while</span> <span class="p">(</span><span class="n">ReadNextObject</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;------------------------------------------------</span><span class="se">\n</span><span class="s">&#34;</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">________________________________________________</span><span class="se">\n</span><span class="s">&#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">_stopRequested</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 will come back to the different <strong>_XXXparser</strong> fields soon.</p>
<p>The <strong>ReadNextObject</strong> helper is responsible for reading the expected <strong>ObjectHeader</strong> and the string that follows to figure out what is the type of this “object” and what payload to expect:</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="kt">bool</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">ReadNextObject</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 type of object from the header
</span></span></span><span class="line"><span class="cl">    <span class="n">ObjectHeader</span> <span class="n">header</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">Read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">header</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">ObjectHeader</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">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while reading Object header: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">ObjectType</span> <span class="n">ot</span> <span class="o">=</span> <span class="n">GetObjectType</span><span class="p">(</span><span class="n">header</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">ot</span> <span class="o">==</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Invalid object header type:</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">DumpObjectHeader</span><span class="p">(</span><span class="n">header</span><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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// don&#39;t forget to check the end object tag
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">tag</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">ReadByte</span><span class="p">(</span><span class="n">tag</span><span class="p">)</span> <span class="o">||</span> <span class="p">(</span><span class="n">tag</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">EndObject</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Missing end of object tag: &#34;</span> <span class="o">&lt;&lt;</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">tag</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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><span class="line"><span class="cl">    <span class="p">...</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>GetObjectType</strong> function checks the header validity and extracts the “object” type 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><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></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">ObjectType</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">GetObjectType</span><span class="p">(</span><span class="n">ObjectHeader</span><span class="o">&amp;</span> <span class="n">header</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">// check validity
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">TagTraceObject</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">BeginPrivateObject</span><span class="p">)</span> <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">header</span><span class="p">.</span><span class="n">TagTypeObjectForTrace</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">BeginPrivateObject</span><span class="p">)</span> <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">header</span><span class="p">.</span><span class="n">TagType</span> <span class="o">!=</span> <span class="n">NettraceTag</span><span class="o">::</span><span class="n">NullReference</span><span class="p">)</span> <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// figure out which type it is based on the name:
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   EventBlock -&gt; &#34;EventBlock&#34;  (size = 10)
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   MetadataBlock -&gt; &#34;MetadataBlock&#34; (size = 13)
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   StackBlock -&gt; &#34;StackBlock&#34; (size = 10)
</span></span></span><span class="line"><span class="cl">    <span class="c1">//   SequencePointBlock -&gt; &#34;SPBlock&#34; (size = 7)
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">NameLength</span> <span class="o">==</span> <span class="mi">13</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">uint8_t</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">13</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">Read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">13</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="n">MetadataBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">MetadataBlock</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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="nf">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">NameLength</span> <span class="o">==</span> <span class="mi">10</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">uint8_t</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">10</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">Read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">10</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">EventBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">EventBlock</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="k">if</span> <span class="p">(</span><span class="n">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="n">StackBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">StackBlock</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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="nf">if</span> <span class="p">(</span><span class="n">header</span><span class="p">.</span><span class="n">NameLength</span> <span class="o">==</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="kt">uint8_t</span> <span class="n">buffer</span><span class="p">[</span><span class="mi">7</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">Read</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">7</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">IsSameAsString</span><span class="p">(</span><span class="n">buffer</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="n">SequencePointBlockName</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ObjectType</span><span class="o">::</span><span class="n">SequencePointBlock</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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">ObjectType</span><span class="o">::</span><span class="n">Unknown</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 same <strong>IsSameAsString</strong> helper is used to compare the read “object” name with the known types.</p>
<p>The end of <strong>ReadNextObject</strong> simply parses the “object” payload as a memory block based on its 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><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></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="p">...</span>
</span></span><span class="line"><span class="cl">    <span class="k">switch</span> <span class="p">(</span><span class="n">ot</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">case</span> <span class="n">ObjectType</span><span class="o">::</span><span class="nl">EventBlock</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ParseEventBlock</span><span class="p">(</span><span class="n">header</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="n">ObjectType</span><span class="o">::</span><span class="nl">MetadataBlock</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ParseMetadataBlock</span><span class="p">(</span><span class="n">header</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="n">ObjectType</span><span class="o">::</span><span class="nl">StackBlock</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ParseStackBlock</span><span class="p">(</span><span class="n">header</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="k">case</span> <span class="n">ObjectType</span><span class="o">::</span><span class="nl">SequencePointBlock</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">            <span class="k">return</span> <span class="n">ParseSequencePointBlock</span><span class="p">(</span><span class="n">header</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="nb">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="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step will be to look into each “object” type payload.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/posts/2022-07-28_digging-into-the-clr/">Episode 1</a> — <em>Digging into the CLR Diagnostics IPC Protocol in C#</em></li>
<li><a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">Episode 2</a> — <em>.NET Diagnostic IPC protocol: the C++ way</em></li>
<li>[Episode 3 ](/posts/2022-10-23_clr-events-go-for/ CLR events: go for the nettrace file format!</li>
<li><a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">Source code</a> for the C++ implementation of CLR events listener</li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
</ul>
]]></content:encoded></item><item><title>CLR events: go for the nettrace file format!</title><link>https://chrisnas.github.io/posts/2022-10-23_clr-events-go-for/</link><pubDate>Sun, 23 Oct 2022 16:55:52 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-10-23_clr-events-go-for/</guid><description>Let’s see how to listen to CLR event through EventPipe in C++. This episode explains how to start/stop a session with a running .NET app.</description><content:encoded><![CDATA[<hr>
<p>As shown in the <a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">previous post</a>, the processing of <strong>ProcessInfo</strong> diagnostic commands is easy because you send a request and read the different fields from the response. This is different if you want to receive events from the CLR via EventPipe. In C#, the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/">TraceEvent nuget package</a> wraps everything under a nice event handler based model as shown in many of my <a href="/posts/2018-07-26_grab-etw-session-providers/">previous posts</a>.</p>
<p>Behind the scene, a <strong>StartSession</strong> command is sent (more details about the parameters later) and the response contains the numeric ID of the session. Then, the events will be read from the IPC channel as a binary stream of data with the <a href="https://github.com/microsoft/perfview/blob/main/src/TraceEvent/EventPipe/EventPipeFormat.md">“nettrace“ file format</a>. The collection ends when the <strong>StopTracing</strong> command is sent.</p>
<p>The source code is available from <a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">my github repository</a>.</p>
<h2 id="hidding-the-transport-layer-iipcendoint">Hidding the transport layer: IIpcEndoint</h2>
<p>Unlike the previous post, to send the command and read the response back from the CLR , I’m wrapping the transport layer with the <strong>IIpcEndpoint</strong> 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><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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">IIpcEndpoint</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl"><span class="k">public</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="n">Write</span><span class="p">(</span><span class="n">LPCVOID</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">bufferSize</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">*</span> <span class="n">writtenBytes</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">Read</span><span class="p">(</span><span class="n">LPVOID</span> <span class="n">buffer</span><span class="p">,</span> <span class="n">DWORD</span> <span class="n">bufferSize</span><span class="p">,</span> <span class="n">DWORD</span><span class="o">*</span> <span class="n">readBytes</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadByte</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">&amp;</span> <span class="n">byte</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadWord</span><span class="p">(</span><span class="kt">uint16_t</span><span class="o">&amp;</span> <span class="n">word</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadDWord</span><span class="p">(</span><span class="kt">uint32_t</span><span class="o">&amp;</span> <span class="n">dword</span><span class="p">)</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="kt">bool</span> <span class="nf">ReadLong</span><span class="p">(</span><span class="kt">uint64_t</span><span class="o">&amp;</span> <span class="n">ulong</span><span class="p">)</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">virtual</span> <span class="kt">bool</span> <span class="nf">Close</span><span class="p">()</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">virtual</span> <span class="o">~</span><span class="n">IIpcEndpoint</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</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>It abstracts the write and read accesses to the underlying transport layer. In addition, the base class accepts a “recorder” that allows me to store what is received from the CLR into any kind of storage (today only a file-based recorder that helped a lot to reproduce specific situations without the need to have a running process to connect to):</p>
<p><img loading="lazy" src="/posts/2022-10-23_clr-events-go-for/1_ugj8AdZBJZv4qyi-VfTeog.png"></p>
<p>The <strong>PidEndpoint</strong> class accepts the process id of the running .NET application to monitor its CLR events and an optional recorder implementing the <strong>IIpcRecorder</strong> interface. The <strong>Create</strong> static factory implementation creates the expected named pipe on Windows (or the domain socket on Linux) and stores the handle into its <strong>_handle</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><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></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">PidEndpoint</span><span class="o">*</span> <span class="n">PidEndpoint</span><span class="o">::</span><span class="n">CreateForWindows</span><span class="p">(</span><span class="kt">int</span> <span class="n">pid</span><span class="p">,</span> <span class="n">IIpcRecorder</span><span class="o">*</span> <span class="n">pRecorder</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">PidEndpoint</span><span class="o">*</span> <span class="n">pEndpoint</span> <span class="o">=</span> <span class="k">new</span> <span class="n">PidEndpoint</span><span class="p">(</span><span class="n">pRecorder</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// build the pipe name as described in the protocol
</span></span></span><span class="line"><span class="cl">    <span class="kt">wchar_t</span> <span class="n">pszPipeName</span><span class="p">[</span><span class="mi">256</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">nCharactersWritten</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nCharactersWritten</span> <span class="o">=</span> <span class="n">wsprintf</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pszPipeName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="sa">L</span><span class="s">&#34;</span><span class="se">\\\\</span><span class="s">.</span><span class="se">\\</span><span class="s">pipe</span><span class="se">\\</span><span class="s">dotnet-diagnostic-%d&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">pid</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">// check that CLR has created the diagnostics named pipe
</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">WaitNamedPipe</span><span class="p">(</span><span class="n">pszPipeName</span><span class="p">,</span> <span class="mi">200</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">auto</span> <span class="n">error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Diagnostics named pipe is not available for process #&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">pid</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; (&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">error</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;)&#34;</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</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></span><span class="line"><span class="cl">    <span class="c1">// connect to the named pipe
</span></span></span><span class="line"><span class="cl">    <span class="n">HANDLE</span> <span class="n">hPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hPipe</span> <span class="o">=</span> <span class="o">::</span><span class="n">CreateFile</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pszPipeName</span><span class="p">,</span>    <span class="c1">// pipe name
</span></span></span><span class="line"><span class="cl">        <span class="n">GENERIC_READ</span> <span class="o">|</span>  <span class="c1">// read and write access
</span></span></span><span class="line"><span class="cl">        <span class="n">GENERIC_WRITE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>              <span class="c1">// no sharing
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>           <span class="c1">// default security attributes
</span></span></span><span class="line"><span class="cl">        <span class="n">OPEN_EXISTING</span><span class="p">,</span>  <span class="c1">// opens existing pipe
</span></span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>              <span class="c1">// default attributes
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">);</span>          <span class="c1">// no template file
</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">hPipe</span> <span class="o">==</span> <span class="n">INVALID_HANDLE_VALUE</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Impossible to connect to &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">pszPipeName</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</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></span><span class="line"><span class="cl">    <span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">_handle</span> <span class="o">=</span> <span class="n">hPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">pEndpoint</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 next step is to open a tracing session by sending the <strong>StartSession</strong> command.</p>
<h2 id="the-trace-diagnostic-commands">The Trace diagnostic commands</h2>
<p>Following the same object model provided by the <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client/">Microsoft.Diagnostics.NETCore.Client nuget</a>, my <strong>DiagnosticsClient</strong> class hides the transport layer. It also exposes high level functions such as <strong>OpenEventPipeSession</strong> to initiate a trace event session with the CLR:</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-c" data-lang="c"><span class="line"><span class="cl"><span class="n">EventPipeSession</span><span class="o">*</span> <span class="nf">OpenEventPipeSession</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">keywords</span><span class="p">,</span> <span class="n">EventVerbosityLevel</span> <span class="n">verbosity</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>If you remember from <strong>TraceEvent</strong>, you need a few parameters to create a session:</p>
<ul>
<li>size of circular buffers used by the CLR to cache events (same as Perfview, use 16 MB as default)</li>
<li>netttrace format (i.e. value of 1)</li>
<li>if rundown events are needed</li>
<li>a list of providers (“Microsoft-Windows-DotNETRuntime” for the CLR in my case)</li>
<li>keywords</li>
<li>verbosity level</li>
<li>possible arguments (none here)</li>
</ul>
<p>Here is the corresponding C++ description of the command 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><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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">uint8_t</span> <span class="n">DotnetProviderMagicLength</span> <span class="o">=</span> <span class="mi">32</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">MagicProvider</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">wchar_t</span> <span class="n">Magic</span><span class="p">[</span><span class="n">DotnetProviderMagicLength</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">// 32 wchar_t (including \0)
</span></span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="n">MagicProvider</span> <span class="n">DotnetProviderMagic</span> <span class="o">=</span> <span class="p">{</span> <span class="sa">L</span><span class="s">&#34;Microsoft-Windows-DotNETRuntime&#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">const</span> <span class="kt">uint32_t</span> <span class="n">CircularBufferMBSize</span> <span class="o">=</span> <span class="mi">16</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="k">const</span> <span class="kt">uint32_t</span> <span class="n">NetTraceFormat</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nl">StartSessionMessage</span> <span class="p">:</span> <span class="n">public</span> <span class="n">IpcHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">CircularBufferMB</span><span class="p">;</span>  <span class="c1">// 16 MB
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Format</span><span class="p">;</span>            <span class="c1">// 1 for NetTrace format
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span> <span class="n">RequestRundown</span><span class="p">;</span>     <span class="c1">// 0 because don&#39;t want rundown
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// array of provider configuration
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ProviderCount</span><span class="p">;</span>     <span class="c1">// 1 only: Microsoft-Windows-DotNETRuntime
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">Keywords</span><span class="p">;</span>          <span class="c1">// from EventKeyword
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">Verbosity</span><span class="p">;</span>         <span class="c1">// from EventPipeEventLevel
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">ProviderStringLen</span><span class="p">;</span> <span class="c1">// number of UTF16 characters = 32 (including last \0)
</span></span></span><span class="line"><span class="cl">    <span class="k">union</span>                       <span class="c1">// dotnet provider name
</span></span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">MagicProvider</span> <span class="n">_magic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8_t</span> <span class="n">Provider</span><span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">DotnetProviderMagicLength</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">uint32_t</span> <span class="n">Arguments</span><span class="p">;</span>         <span class="c1">// 0 for empty string (no argument)
</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 fill up the command is 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></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">StartSessionMessage</span><span class="o">*</span> <span class="nf">CreateStartSessionMessage</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">keywords</span><span class="p">,</span> <span class="n">EventVerbosityLevel</span> <span class="n">verbosity</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">auto</span> <span class="n">message</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StartSessionMessage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">::</span><span class="n">ZeroMemory</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</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">memcpy</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">DotnetIpcMagic_V1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">StartSessionMessage</span><span class="p">);</span>  
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandSet</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerCommandSet</span><span class="o">::</span><span class="n">EventPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandId</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">EventPipeCommandId</span><span class="o">::</span><span class="n">CollectTracing2</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Reserved</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">message</span><span class="o">-&gt;</span><span class="n">CircularBufferMB</span> <span class="o">=</span> <span class="n">CircularBufferMBSize</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Format</span> <span class="o">=</span> <span class="n">NetTraceFormat</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">RequestRundown</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">message</span><span class="o">-&gt;</span><span class="n">ProviderCount</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Keywords</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint64_t</span><span class="p">)</span><span class="n">keywords</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Verbosity</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="n">verbosity</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">ProviderStringLen</span> <span class="o">=</span> <span class="n">DotnetProviderMagicLength</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">memcpy</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Provider</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">DotnetProviderMagic</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Provider</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Arguments</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">return</span> <span class="n">message</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 provider list is defined with the <strong>ProviderCount</strong> field and string containing the list (only one here) follows the <strong>Verbosity</strong> field. To start the session, it is needed to send the <strong>StartSession</strong> message and read the session id from the response:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeStartRequest</span><span class="o">::</span><span class="n">Process</span><span class="p">(</span><span class="n">IIpcEndpoint</span><span class="o">*</span> <span class="n">pEndpoint</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">keywords</span><span class="p">,</span> <span class="n">EventVerbosityLevel</span> <span class="n">verbosity</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">// send an StartSessionMessage and parse the response
</span></span></span><span class="line"><span class="cl">    <span class="n">StartSessionMessage</span><span class="o">*</span> <span class="n">pMessage</span> <span class="o">=</span> <span class="n">CreateStartSessionMessage</span><span class="p">(</span><span class="n">keywords</span><span class="p">,</span> <span class="n">verbosity</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">writtenBytes</span> <span class="o">=</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="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">Write</span><span class="p">(</span><span class="n">pMessage</span><span class="p">,</span> <span class="n">pMessage</span><span class="o">-&gt;</span><span class="n">Size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writtenBytes</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// analyze the response
</span></span></span><span class="line"><span class="cl">    <span class="n">IpcHeader</span> <span class="n">response</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">bytesReadCount</span> <span class="o">=</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="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">Read</span><span class="p">(</span><span class="o">&amp;</span><span class="n">response</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">response</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">bytesReadCount</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><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">response</span><span class="p">.</span><span class="n">CommandId</span> <span class="o">!=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerResponseId</span><span class="o">::</span><span class="n">OK</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// get the session ID from the payload
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">payloadSize</span> <span class="o">=</span> <span class="n">response</span><span class="p">.</span><span class="n">Size</span> <span class="o">-</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">response</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">payloadSize</span> <span class="o">&lt;</span> <span class="k">sizeof</span><span class="p">(</span><span class="kt">uint64_t</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><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="o">!</span><span class="n">pEndpoint</span><span class="o">-&gt;</span><span class="n">ReadLong</span><span class="p">(</span><span class="n">SessionId</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><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">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>Once the <strong>StartSession</strong> command has been sent, the events corresponding to the given provider/keywords/verbosity (here the CLR runtime/gc+exception+contention/verbose)</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="k">auto</span> <span class="n">pSession</span> <span class="o">=</span> <span class="n">pClient</span><span class="o">-&gt;</span><span class="n">OpenEventPipeSession</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventKeyword</span><span class="o">::</span><span class="n">gc</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">EventKeyword</span><span class="o">::</span><span class="n">exception</span> <span class="o">|</span>
</span></span><span class="line"><span class="cl">          <span class="n">EventKeyword</span><span class="o">::</span><span class="n">contention</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">EventVerbosityLevel</span><span class="o">::</span><span class="n">Verbose</span>  <span class="c1">// required for AllocationTick
</span></span></span><span class="line"><span class="cl">        <span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>will be read from the event pipe. Since this action will be synchronous, it is recommended to dedicate a thread to read and process 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></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="n">pSession</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">DWORD</span> <span class="n">tid</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">auto</span> <span class="n">hThread</span> <span class="o">=</span> <span class="o">::</span><span class="n">CreateThread</span><span class="p">(</span><span class="k">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ListenToEvents</span><span class="p">,</span> <span class="n">pSession</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">tid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Press ENTER to stop listening to events...</span><span class="se">\n\n</span><span class="s">&#34;</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">line</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">getline</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">cin</span><span class="p">,</span> <span class="n">line</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">std</span><span class="o">::</span><span class="n">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Stopping session</span><span class="se">\n\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">pSession</span><span class="o">-&gt;</span><span class="n">Stop</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Session stopped</span><span class="se">\n\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="c1">// test if it works
</span></span></span><span class="line"><span class="cl">        <span class="o">::</span><span class="n">Sleep</span><span class="p">(</span><span class="mi">1000</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="o">::</span><span class="n">CloseHandle</span><span class="p">(</span><span class="n">hThread</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 <strong>ListenToEvents</strong> callback executed by the new thread is “simply” listening to the event pipe of the 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></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">WINAPI</span> <span class="nf">ListenToEvents</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">pParam</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">EventPipeSession</span><span class="o">*</span> <span class="n">pSession</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="n">EventPipeSession</span><span class="o">*&gt;</span><span class="p">(</span><span class="n">pParam</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">pSession</span><span class="o">-&gt;</span><span class="n">Listen</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="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>Before describing how to read the events, it is important to understand how to stop the flow. First, inside the <strong>EventPipeSession</strong>, the internal loop that reads events needs to exit thanks to the <strong>_stopRequested</strong> boolean:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">EventPipeSession</span><span class="o">::</span><span class="n">Stop</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">_stopRequested</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">if</span> <span class="p">(</span><span class="n">_pid</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</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="c1">// it is neeeded to use a different ipc connection to stop the Session
</span></span></span><span class="line"><span class="cl">    <span class="n">DiagnosticsClient</span><span class="o">*</span> <span class="n">pStopClient</span> <span class="o">=</span> <span class="n">DiagnosticsClient</span><span class="o">::</span><span class="n">Create</span><span class="p">(</span><span class="n">_pid</span><span class="p">,</span> <span class="k">nullptr</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">pStopClient</span><span class="o">-&gt;</span><span class="n">StopEventPipeSession</span><span class="p">(</span><span class="n">SessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">delete</span> <span class="n">pStopClient</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="nb">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>In addition, a message with <strong>StopTracing</strong> command id from the <strong>EventPipe</strong> command set needs to be sent to tell the CLR to stop sending the events. This message must be sent through a different IPC channel (hence the <strong>pStopClient</strong> variable used in the previous code. The <strong>StopEventPipeSession</strong> helper function uses the <strong>EventPipeStopRequest</strong> wrapper:</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="kt">bool</span> <span class="n">DiagnosticsClient</span><span class="o">::</span><span class="n">StopEventPipeSession</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">sessionId</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">EventPipeStopRequest</span> <span class="n">request</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">request</span><span class="p">.</span><span class="n">Process</span><span class="p">(</span><span class="n">_pEndpoint</span><span class="p">,</span> <span class="n">sessionId</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 <strong>StopSession</strong> command accepts the session ID as single 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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="nl">StopSessionMessage</span> <span class="p">:</span> <span class="n">public</span> <span class="n">IpcHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint64_t</span> <span class="n">SessionId</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 processing of the stop request is to create such a message and send it through the IPC channel:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">StopSessionMessage</span><span class="o">*</span> <span class="nf">CreateStopMessage</span><span class="p">(</span><span class="kt">uint64_t</span> <span class="n">sessionId</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">StopSessionMessage</span><span class="o">*</span> <span class="n">message</span> <span class="o">=</span> <span class="k">new</span> <span class="n">StopSessionMessage</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="o">::</span><span class="n">ZeroMemory</span><span class="p">(</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</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">memcpy</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">DotnetIpcMagic_V1</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="o">-&gt;</span><span class="n">Magic</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Size</span> <span class="o">=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">StopSessionMessage</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandSet</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerCommandSet</span><span class="o">::</span><span class="n">EventPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">CommandId</span> <span class="o">=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">EventPipeCommandId</span><span class="o">::</span><span class="n">StopTracing</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">message</span><span class="o">-&gt;</span><span class="n">Reserved</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">message</span><span class="o">-&gt;</span><span class="n">SessionId</span> <span class="o">=</span> <span class="n">sessionId</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">message</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="kt">bool</span> <span class="n">EventPipeStopRequest</span><span class="o">::</span><span class="n">Process</span><span class="p">(</span><span class="n">IIpcEndpoint</span><span class="o">*</span> <span class="n">pEndpoint</span><span class="p">,</span> <span class="kt">uint64_t</span> <span class="n">sessionId</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">StopSessionMessage</span><span class="o">*</span> <span class="n">pMessage</span> <span class="o">=</span> <span class="n">CreateStopMessage</span><span class="p">(</span><span class="n">sessionId</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">writtenBytes</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">pEndpoint</span><span class="o">-&gt;</span><span class="n">Write</span><span class="p">(</span><span class="n">pMessage</span><span class="p">,</span> <span class="n">pMessage</span><span class="o">-&gt;</span><span class="n">Size</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">writtenBytes</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">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while sending EventPipe Stop message to the CLR: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">delete</span> <span class="n">pMessage</span><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><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="k">delete</span> <span class="n">pMessage</span><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 class="c1">// handle the response 
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">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>When the stop command is received by the CLR, the remaining “data” (more on this in the next episode) is sent through the first IPC channel before being closed. This is how the code knows that the session can stop listening to the EventPipe.</p>
<p>The next episode will start to parse the nettrace stream of events.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/posts/2022-07-28_digging-into-the-clr/">Episode 1</a> — <em>Digging into the CLR Diagnostics IPC Protocol in C#</em></li>
<li><a href="/posts/2022-09-18_net-diagnostic-ipc-protocol/">Episode 2</a> — <em>.NET Diagnostic IPC protocol: the C++ way</em></li>
<li><a href="https://github.com/chrisnas/ClrEvents/tree/master/Events/NativeEventListener">Source code</a> for the C++ implementation of CLR events listener</li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
<li>Microsoft.Diagnostics.NETCore.Client <a href="https://github.com/dotnet/diagnostics/tree/main/src/Microsoft.Diagnostics.NETCore.Client">source code</a></li>
</ul>
]]></content:encoded></item><item><title>.NET Diagnostic IPC protocol: the C++ way</title><link>https://chrisnas.github.io/posts/2022-09-18_net-diagnostic-ipc-protocol/</link><pubDate>Sun, 18 Sep 2022 14:36:31 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-09-18_net-diagnostic-ipc-protocol/</guid><description>This post describes how to communicate in ++ with the diagnostic server in the CLR of a running .NET application.</description><content:encoded><![CDATA[<hr>
<p>The <a href="/posts/2022-07-28_digging-into-the-clr/">previous post</a> was describing the C# helpers to communicate with the diagnostic server in the CLR of a running .NET application.</p>
<p>If, like me, you must write native code (i.e not in C#), you will need to implement the transport and protocol yourself. And, as you will see, it is not that complicated thanks to the <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a> but also by using the <a href="https://github.com/dotnet/diagnostics/tree/main/src/Microsoft.Diagnostics.NETCore.Client">available C# code</a> of the Microsoft.Diagnostics.NETCore.Client implementation as a guide.</p>
<h2 id="eventpipe-transport-layer">EventPipe transport layer</h2>
<p>The first step is to connect to the CLR of a running .NET process. On Linux, you connect to a domain socket named “<strong>{$TMPDIR}/dotnet-diagnostic-{%d:PID}-{%llu:</strong><a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md"><strong>disambiguation key</strong></a><strong>}-socket</strong>”. For Windows, a named pipe called <strong>\.\pipe\dotnet-diagnostic-{%d:PID}</strong> needs to be accessed.</p>
<p>Here is the Windows implementation code to connect to the IPC named pipe:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">BasicConnection</span><span class="p">(</span><span class="n">DWORD</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="kt">wchar_t</span> <span class="n">pszPipeName</span><span class="p">[</span><span class="mi">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">// build the pipe name as described in the protocol
</span></span></span><span class="line"><span class="cl">    <span class="kt">int</span> <span class="n">nCharactersWritten</span> <span class="o">=</span> <span class="o">-</span><span class="mi">1</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">nCharactersWritten</span> <span class="o">=</span> <span class="n">wsprintf</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pszPipeName</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="sa">L</span><span class="s">&#34;</span><span class="se">\\\\</span><span class="s">.</span><span class="se">\\</span><span class="s">pipe</span><span class="se">\\</span><span class="s">dotnet-diagnostic-%d&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">pid</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">// check that CLR has created the diagnostics named pipe
</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">WaitNamedPipe</span><span class="p">(</span><span class="n">pszPipeName</span><span class="p">,</span> <span class="mi">200</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">auto</span> <span class="n">error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Diagnostics named pipe is not available for process #&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">pid</span> <span class="o">&lt;&lt;</span> <span class="s">&#34; (&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">error</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;)&#34;</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">-</span><span class="mi">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></span><span class="line"><span class="cl">    <span class="c1">// connect to the named pipe
</span></span></span><span class="line"><span class="cl">    <span class="n">HANDLE</span> <span class="n">hPipe</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">hPipe</span> <span class="o">=</span> <span class="o">::</span><span class="n">CreateFile</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="n">pszPipeName</span><span class="p">,</span>    <span class="c1">// pipe name
</span></span></span><span class="line"><span class="cl">        <span class="n">GENERIC_READ</span> <span class="o">|</span>  <span class="c1">// read and write access
</span></span></span><span class="line"><span class="cl">        <span class="n">GENERIC_WRITE</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>              <span class="c1">// no sharing
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">,</span>           <span class="c1">// default security attributes
</span></span></span><span class="line"><span class="cl">        <span class="n">OPEN_EXISTING</span><span class="p">,</span>  <span class="c1">// opens existing pipe
</span></span></span><span class="line"><span class="cl">        <span class="mi">0</span><span class="p">,</span>              <span class="c1">// default attributes
</span></span></span><span class="line"><span class="cl">        <span class="nb">NULL</span><span class="p">);</span>          <span class="c1">// no template file
</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">hPipe</span> <span class="o">==</span> <span class="n">INVALID_HANDLE_VALUE</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Impossible to connect to &#34;</span> <span class="o">&lt;&lt;</span> <span class="n">pszPipeName</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="o">-</span><span class="mi">2</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">// ... send a command...
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// don&#39;t forget to close the named pipe
</span></span></span><span class="line"><span class="cl">    <span class="o">::</span><span class="n">CloseHandle</span><span class="p">(</span><span class="n">hPipe</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="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><h2 id="dig-into-the-commandprotocol">Dig into the Command protocol</h2>
<p>Once the connection is created, a command can be sent by writing to the pipe (or socket on Linux). The answer will be received by reading from the pipe (or socket on Linux). There is something important to remember: if you need to send different commands, it is needed to create one connection for each. You should not try to reuse a given connection that might also be closed after a command has been processed.</p>
<h2 id="lets-start-with-the-ipcheader">Let’s start with the IpcHeader</h2>
<p>Both the command and the response share the same header 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><span class="lnt">15
</span></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="cp">#pragma pack(1)
</span></span></span><span class="line"><span class="cl"><span class="k">struct</span> <span class="n">IpcHeader</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">union</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">MagicVersion</span> <span class="n">_magic</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="kt">uint8_t</span>  <span class="n">Magic</span><span class="p">[</span><span class="mi">14</span><span class="p">];</span>  <span class="c1">// Magic Version number; a 0 terminated ANSI char array
</span></span></span><span class="line"><span class="cl">    <span class="p">};</span>
</span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Size</span><span class="p">;</span>       <span class="c1">// The size of the incoming packet, size = header + payload size
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span>  <span class="n">CommandSet</span><span class="p">;</span> <span class="c1">// The scope of the Command.
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint8_t</span>  <span class="n">CommandId</span><span class="p">;</span>  <span class="c1">// The command being sent
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint16_t</span> <span class="n">Reserved</span><span class="p">;</span>   <span class="c1">// reserved for future use
</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">const</span> <span class="n">MagicVersion</span> <span class="n">DotnetIpcMagic_V1</span> <span class="o">=</span> <span class="p">{</span> <span class="s">&#34;DOTNET_IPC_V1&#34;</span> <span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>Size</strong> field stores the size of the header (= 20) plus the size of the payload (if any). Next comes the <strong>CommandSet</strong> field that identifies the groups in which the command belongs to:</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-c" data-lang="c"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">class</span> <span class="nl">DiagnosticServerCommandSet</span> <span class="p">:</span> <span class="kt">uint8_t</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// reserved = 0x00,
</span></span></span><span class="line"><span class="cl">    <span class="n">Dump</span>        <span class="o">=</span> <span class="mh">0x01</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">EventPipe</span>   <span class="o">=</span> <span class="mh">0x02</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Profiler</span>    <span class="o">=</span> <span class="mh">0x03</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="n">Process</span>     <span class="o">=</span> <span class="mh">0x04</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="n">Server</span>      <span class="o">=</span> <span class="mh">0xFF</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>It is then followed by the ID of a command in that <strong>CommandSet</strong> :</p>
<p><img loading="lazy" src="/posts/2022-09-18_net-diagnostic-ipc-protocol/1_STn1QLFwnmlgIZBXFuTvhA.png"></p>
<p>For example, here is the memory layout of a <strong>ProcessInfo</strong> command:</p>
<p><img loading="lazy" src="/posts/2022-09-18_net-diagnostic-ipc-protocol/1__PvHSKZafLkdt0vrmkrc0A.png"></p>
<p>Since there is no additional parameter that would need to be encoded in a payload, the <strong>Size</strong> field is set to 20 (= 0x14 in hexadecimal) which is the size of the header alone.</p>
<p>To make command handling easier, I have defined the corresponding headers:</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-c" data-lang="c"><span class="line"><span class="cl"><span class="k">const</span> <span class="n">IpcHeader</span> <span class="n">ProcessInfoMessage</span> <span class="o">=</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 class="n">DotnetIpcMagic_V1</span> <span class="p">},</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">uint16_t</span><span class="p">)</span><span class="k">sizeof</span><span class="p">(</span><span class="n">IpcHeader</span><span class="p">),</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerCommandSet</span><span class="o">::</span><span class="n">Process</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerResponseId</span><span class="o">::</span><span class="n">OK</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="p">(</span><span class="kt">uint16_t</span><span class="p">)</span><span class="mh">0x0000</span>
</span></span><span class="line"><span class="cl"><span class="p">};</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>All that makes the the code to send such a <strong>ProcessInfo</strong> command 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></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="kt">bool</span> <span class="n">ProcessInfoRequest</span><span class="o">::</span><span class="n">Send</span><span class="p">(</span><span class="n">HANDLE</span> <span class="n">hPipe</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">// send the request
</span></span></span><span class="line"><span class="cl">    <span class="n">IpcHeader</span> <span class="n">message</span> <span class="o">=</span> <span class="n">ProcessInfoMessage</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">bytesWrittenCount</span> <span class="o">=</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="o">!::</span><span class="n">WriteFile</span><span class="p">(</span><span class="n">hPipe</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">bytesWrittenCount</span><span class="p">,</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">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while sending ProcessInfo message to the CLR: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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 next step is to read from the pipe to get the answer from the CLR. As mentioned earlier, the same <strong>IpcHeader</strong> is received; always with the <strong>Server</strong> (0xFF) <strong>CommandSet</strong> value. The <strong>CommandId</strong> field is 0 for a success and 0xFF in case of an error.</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-c" data-lang="c"><span class="line"><span class="cl"><span class="k">enum</span> <span class="n">class</span> <span class="nl">DiagnosticServerResponseId</span> <span class="p">:</span> <span class="kt">uint8_t</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="n">OK</span> <span class="o">=</span> <span class="mh">0x00</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// future
</span></span></span><span class="line"><span class="cl">    <span class="n">Error</span> <span class="o">=</span> <span class="mh">0xFF</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 example, here is the memory layout of a <strong>ProcessInfo</strong> answer:</p>
<p><img loading="lazy" src="/posts/2022-09-18_net-diagnostic-ipc-protocol/1_89qG8uZqW-1DE6gxcO4Wyg.png"></p>
<p>Note that numbers are little-endian encoded (hence the 0001 for the Size field: it means 0x0100 = 256 bytes)</p>
<p>The code to analyze the response follows:</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-cpp" data-lang="cpp"><span class="line"><span class="cl">    <span class="c1">// analyze the response
</span></span></span><span class="line"><span class="cl">    <span class="c1">// 1. get the header to know how large the buffer should be to get the payload
</span></span></span><span class="line"><span class="cl">    <span class="n">message</span> <span class="o">=</span> <span class="p">{};</span>
</span></span><span class="line"><span class="cl">    <span class="n">DWORD</span> <span class="n">bytesReadCount</span> <span class="o">=</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="o">!::</span><span class="n">ReadFile</span><span class="p">(</span><span class="n">hPipe</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">message</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">message</span><span class="p">),</span> <span class="o">&amp;</span><span class="n">bytesReadCount</span><span class="p">,</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">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while getting ProcessInfo response from the CLR: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span><span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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><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">message</span><span class="p">.</span><span class="n">CommandId</span> <span class="o">!=</span> <span class="p">(</span><span class="kt">uint8_t</span><span class="p">)</span><span class="n">DiagnosticServerResponseId</span><span class="o">::</span><span class="n">OK</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">Error</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="n">CommandId</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error returned by the CLR in ProcessInfo response: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span><span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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 case of success, the size of the response payload is obtained from the <strong>Size</strong> field by subtracting the size of the header. The next step is to allocate a buffer and read the payload from the pipe into that buffer:</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="kt">uint16_t</span> <span class="n">payloadSize</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="n">Size</span> <span class="o">-</span> <span class="k">sizeof</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">_buffer</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">uint8_t</span><span class="p">[</span><span class="n">payloadSize</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">ReadFile</span><span class="p">(</span><span class="n">hPipe</span><span class="p">,</span> <span class="n">_buffer</span><span class="p">,</span> <span class="n">payloadSize</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">bytesReadCount</span><span class="p">,</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">Error</span> <span class="o">=</span> <span class="o">::</span><span class="n">GetLastError</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">cout</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;Error while getting ProcessInfo payload: 0x&#34;</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">hex</span> <span class="o">&lt;&lt;</span> <span class="n">Error</span> <span class="o">&lt;&lt;</span> <span class="n">std</span><span class="o">::</span><span class="n">dec</span> <span class="o">&lt;&lt;</span> <span class="s">&#34;</span><span class="se">\n</span><span class="s">&#34;</span><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><span class="line"><span class="cl">    <span class="c1">// Note: bytesReadCount == payloadSize
</span></span></span></code></pre></td></tr></table>
</div>
</div><h2 id="how-to-access-the-responsefields">How to access the response fields</h2>
<p>Now that the response payload has been read into a memory buffer, it is time to look at the expected fields <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">as described in the documentation</a>. Here is the encoding of the different field types:</p>
<p><img loading="lazy" src="/posts/2022-09-18_net-diagnostic-ipc-protocol/1_A0mmcga5rh2W1FQHu3ix7w.png"></p>
<p>To continue with the <strong>ProcessInfo</strong> example, here are the expected fields:</p>
<p><img loading="lazy" src="/posts/2022-09-18_net-diagnostic-ipc-protocol/1_l_Hm2BSPxIoPE3pf5UUjSg.png"></p>
<p>So here is the memory layout for the response payload for my monitored Windows 64-bit simulator.exe test application:</p>
<p><img loading="lazy" src="/posts/2022-09-18_net-diagnostic-ipc-protocol/1_NRGp2clSuWLtvHQQo3svoA.png"></p>
<p>To simplify the implementation, the class in charge of a command allocates a buffer corresponding to the payload and provides fields with values either copied from it or, in case of strings, pointing to the buffer (just after the 32-bit length):</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">bool</span> <span class="n">ProcessInfoRequest</span><span class="o">::</span><span class="n">ParseResponse</span><span class="p">(</span><span class="n">DWORD</span> <span class="n">payloadSize</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">uint32_t</span> <span class="n">index</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="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">Pid</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">_buffer</span><span class="p">[</span><span class="n">index</span><span class="p">],</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Pid</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">index</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">Pid</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">payloadSize</span> <span class="o">&lt;</span> <span class="n">index</span><span class="p">)</span> <span class="k">return</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="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">RuntimeCookie</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">_buffer</span><span class="p">[</span><span class="n">index</span><span class="p">],</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">RuntimeCookie</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">    <span class="n">index</span> <span class="o">+=</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">RuntimeCookie</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">payloadSize</span> <span class="o">&lt;</span> <span class="n">index</span><span class="p">)</span> <span class="k">return</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="n">PointToString</span><span class="p">(</span><span class="n">_buffer</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">CommandLine</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">payloadSize</span> <span class="o">&lt;</span> <span class="n">index</span><span class="p">)</span> <span class="k">return</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="n">PointToString</span><span class="p">(</span><span class="n">_buffer</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">OperatingSystem</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">payloadSize</span> <span class="o">&lt;</span> <span class="n">index</span><span class="p">)</span> <span class="k">return</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="n">PointToString</span><span class="p">(</span><span class="n">_buffer</span><span class="p">,</span> <span class="n">index</span><span class="p">,</span> <span class="n">Architecture</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="nb">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 <strong>PointToString</strong> helper code is 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></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="kt">void</span> <span class="nf">PointToString</span><span class="p">(</span><span class="kt">uint8_t</span><span class="o">*</span> <span class="n">buffer</span><span class="p">,</span> <span class="kt">uint32_t</span><span class="o">&amp;</span> <span class="n">index</span><span class="p">,</span> <span class="kt">wchar_t</span><span class="o">*&amp;</span> <span class="n">string</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">// strings are stored as:
</span></span></span><span class="line"><span class="cl">    <span class="c1">//    - characters count as uint32_t
</span></span></span><span class="line"><span class="cl">    <span class="c1">//    - array of UTF16 characters followed by &#34;\0&#34;
</span></span></span><span class="line"><span class="cl">    <span class="c1">// Note that the last L&#34;\0&#34; IS COUNTED 
</span></span></span><span class="line"><span class="cl">    <span class="kt">uint32_t</span> <span class="n">count</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">memcpy</span><span class="p">(</span><span class="o">&amp;</span><span class="n">count</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">buffer</span><span class="p">[</span><span class="n">index</span><span class="p">],</span> <span class="k">sizeof</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">// skip characters count 
</span></span></span><span class="line"><span class="cl">    <span class="n">index</span> <span class="o">+=</span> <span class="k">sizeof</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">// empty string case
</span></span></span><span class="line"><span class="cl">    <span class="c1">// Note: could make it point to the &#34;count&#34; which is 0 in the buffer
</span></span></span><span class="line"><span class="cl">    <span class="c1">//       instead of returning nullptr
</span></span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</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="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">string</span> <span class="o">=</span> <span class="k">nullptr</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">string</span> <span class="o">=</span> <span class="p">(</span><span class="kt">wchar_t</span><span class="o">*</span><span class="p">)</span><span class="o">&amp;</span><span class="n">buffer</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">// skip the whole string (including last UTF16 &#39;\0&#39;)
</span></span></span><span class="line"><span class="cl">    <span class="n">index</span> <span class="o">+=</span> <span class="n">count</span> <span class="o">*</span> <span class="p">(</span><span class="kt">uint32_t</span><span class="p">)</span><span class="k">sizeof</span><span class="p">(</span><span class="kt">wchar_t</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>You now know how to send a command without parameter and analyze the expected response. The next post will show you how to listen to CLR events like dotnet trace.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/posts/2022-07-28_digging-into-the-clr/">Episode 1</a> — <em>Digging into the CLR Diagnostics IPC Protocol in C#</em></li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
<li>Microsoft.Diagnostics.NETCore.Client <a href="https://github.com/dotnet/diagnostics/tree/main/src/Microsoft.Diagnostics.NETCore.Client">source code</a></li>
</ul>
]]></content:encoded></item><item><title>Digging into the CLR Diagnostics IPC Protocol in C#</title><link>https://chrisnas.github.io/posts/2022-07-28_digging-into-the-clr/</link><pubDate>Thu, 28 Jul 2022 08:59:40 +0000</pubDate><guid>https://chrisnas.github.io/posts/2022-07-28_digging-into-the-clr/</guid><description>Learn how to directly connect to the .NET CLR and send diagnostics commands</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>As I explained during <a href="https://www.youtube.com/watch?v=Jpoy3O6x-wM&amp;t=1530s">a DotNext conference session</a>, the .NET CLI tools such as <strong>dotnet-trace</strong>, <strong>dotnet-counter</strong> or <strong>dotnet-dump</strong> are communicating with the CLR thanks to Named Pipe on Windows and Domain Socket on Linux. Within the CLR, a <a href="https://github.com/dotnet/coreclr/blob/release/3.1/src/vm/diagnosticserver.cpp#24">diagnostic server thread</a> is responsible for answering requests. A communication protocol allows a tool to send <em>commands</em> and expect <em>responses</em>. This Diagnostic IPC Protocol is <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">pretty well documented</a> in the dotnet Diagnostics repository.</p>
<p>Before going into the protocol details, here is a list of the available commands and their effect:</p>
<p><img loading="lazy" src="/posts/2022-07-28_digging-into-the-clr/1_0LmzdTyId2oJIPkSac1EAA.png"></p>
<p>This series will detail how to communicate with a CLR using this protocol both in C# and in C++. Also note that processing CLR events thanks to EventPipe will also be covered.</p>
<h2 id="make-it-simple-use-microsoftdiagnosticsnetcoreclient-nuget">Make it simple: use Microsoft.Diagnostics.NETCore.Client nuget</h2>
<p>With <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent">TraceEvent nugget package</a>, Microsoft provided a great library to <a href="/posts/2018-07-26_grab-etw-session-providers/">easily listen to CLR events</a> in C#. If you want to easily send CLR diagnostic IPC protocol commands to a CLR in a .NET process, <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client/">Microsoft.Diagnostics.NETCore.Client nuget package</a> is for you. Remember that EventPipe is implemented by .NET Core and .NET 5+ (so no .NET Framework support)</p>
<p>The Swiss knife class <strong>DiagnosticsClient</strong> gives you access to most of the commands plus a way to list .NET processes as a bonus:</p>
<p><img loading="lazy" src="/posts/2022-07-28_digging-into-the-clr/1_lbYwy45LUJHmX-WsdrGJYw.png"></p>
<p>If you want to get the pid of all supported running .NET applications, call the static <strong>GetPublishedProcesses()</strong> method. Beware that the pid of your own application will also be included.</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">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ListProcesses</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">selfPid</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetCurrentProcess</span><span class="p">().</span><span class="n">Id</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">pid</span> <span class="k">in</span> <span class="n">DiagnosticsClient</span><span class="p">.</span><span class="n">GetPublishedProcesses</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">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 class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{pid,6}{GetSeparator(pid == selfPid)}{process.ProcessName}&#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>Otherwise, create an instance passing the process ID of the .NET application you are interested in. With this object, call the method corresponding to the command you want to send. For example, the following code is calling <strong>GetProcessEnvironment()</strong> to list the environment variables:</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="kd">static</span> <span class="k">void</span> <span class="n">ListEnvironmentVariables</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="c1">// get environment variables via existing wrapper in DiagnosticsClient</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">envVariables</span> <span class="p">=</span> <span class="n">client</span><span class="p">.</span><span class="n">GetProcessEnvironment</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">variable</span> <span class="k">in</span> <span class="n">envVariables</span><span class="p">.</span><span class="n">Keys</span><span class="p">.</span><span class="n">OrderBy</span><span class="p">(</span><span class="n">k</span> <span class="p">=&gt;</span> <span class="n">k</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;{variable,26} = {envVariables[variable]}&#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>Note that the value “ExitCode=00000000” is associated to the “” (empty) key for reason unknown to me…</p>
<p>Even though the undocumented command to set an environment variable is available via the <strong>SetEnvironmentVariable()</strong> method, there is no helper method wrapping the <strong>ProcessInfo</strong> command. In fact, a <strong>GetProcessInfo()</strong> method exists but it is internal! The <strong>PidIpcEndpoint</strong> type in charge of the transport and the <strong>IpcMessage</strong>, <strong>IpcResponse</strong> and <strong>IpcClient</strong> types dealing with commands are also internal. It means that the nuget will not help if you need to send the <strong>ProcessInfo</strong> command.</p>
<h2 id="still-easy-use-microsoftdiagnosticsnetcoreclient-sourcecode">Still easy: use Microsoft.Diagnostics.NETCore.Client source code</h2>
<p>The .NET team spends some extra time testing, documenting, and verifying they are happy with the APIs in NetCore.Client before making them public, so sometimes you will see types that they used in their own tools that are still internal. But wait, if the CLI tools need some of these types, how will it work? Well…</p>
<p>The C# project corresponding to the MicrosoftDiagnostics.NETCore.Client assembly is part of the dotnet Diagnostic repository where the tools are implemented. If you look at <a href="https://github.com/dotnet/diagnostics/blob/main/src/Microsoft.Diagnostics.NETCore.Client/Microsoft.Diagnostics.NETCore.Client.csproj">the .csproj file</a>, you will see <strong>InternalsVisibleTo</strong> attributes to allow the tools to access the internal types:</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-xml" data-lang="xml"><span class="line"><span class="cl">  <span class="nt">&lt;ItemGroup&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-counters&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-dsrouter&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-monitor&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;dotnet-trace&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.Monitoring&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.Monitoring.EventPipe&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="c">&lt;!-- Temporary until Diagnostic Apis are finalized--&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.Monitoring.WebApi&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;InternalsVisibleTo</span> <span class="na">Include=</span><span class="s">&#34;Microsoft.Diagnostics.NETCore.Client.UnitTests&#34;</span> <span class="nt">/&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/ItemGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The great thing about OSS is that you can compile your own fork to make these types public. Of course you will be on your own to support these custom builds of the library and it is possible there will be changes to the API before .NET makes it public.</p>
<p>So what you could do to use these internal types in your code is the following:</p>
<ul>
<li>copy the folder from the Diagnostics repository</li>
<li>add the name of your assembly that needs to access the internal types and members into the .csproj</li>
<li>replace the reference to the nuget package by a project reference to the copied project</li>
</ul>
<p>And now <strong>GetProcessInfo</strong> and the other internal types are public for you:</p>
<p><img loading="lazy" src="/posts/2022-07-28_digging-into-the-clr/1_RHI5XtwfR4iN2EI3S9o6bg.png"></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">private</span> <span class="kd">static</span> <span class="k">void</span> <span class="n">ListProcessInfo</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="kt">var</span> <span class="n">client</span> <span class="p">=</span> <span class="k">new</span> <span class="n">DiagnosticsClient</span><span class="p">(</span><span class="n">pid</span><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">client</span><span class="p">.</span><span class="n">GetProcessInfo</span><span class="p">();</span>  <span class="c1">// this method is internal</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;              Command Line = {info.CommandLine}&#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;              Architecture = {info.ProcessArchitecture}&#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;      Entry point assembly = {info.ManagedEntrypointAssemblyName}&#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;               CLR Version = {info.ClrProductVersionString}&#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 during my tests, I was able to get a value for the <strong>ManagedEntrypointAssemblyName</strong> or <strong>ClrProductVersionString</strong> properties only with .NET 6+: the <strong>ProcessInfo2</strong> (0x404) command does not seem to be implemented in previous versions.</p>
<p>The next episode of the series will start to explain the EventPipe IPC protocol from a native C++ developer perspective.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://www.nuget.org/packages/Microsoft.Diagnostics.NETCore.Client/">Microsoft.Diagnostics.NETCore.Client nuget package</a></li>
<li><a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent">TraceEvent nugget package</a></li>
<li>Diagnostics IPC protocol <a href="https://github.com/dotnet/diagnostics/blob/main/documentation/design-docs/ipc-protocol.md">documentation</a></li>
</ul>
]]></content:encoded></item><item><title>Reading parameters value with the .NET Profiling APIs</title><link>https://chrisnas.github.io/posts/2021-11-16_reading-parameters-value-with/</link><pubDate>Tue, 16 Nov 2021 09:02:44 +0000</pubDate><guid>https://chrisnas.github.io/posts/2021-11-16_reading-parameters-value-with/</guid><description>This post describes how to access method call parameters and get the value of numbers and strings.</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>From <a href="/posts/2021-10-12_decyphering-method-signature-w/">the list of arguments with their type</a>, it becomes possible to figure out their value when a method gets called. The rest of this post describes how to access method call parameters and get the value of numbers and strings.</p>
<h2 id="where-are-my-parameters">Where are my parameters?</h2>
<p>When you pass <strong>COR_PRF_ENABLE_FUNCTION_ARGS</strong> to <strong>ICorProfilerInfo::SetEventMask</strong>, the runtime prepares a <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/cor-prf-function-argument-info-structure?WT.mc_id=DT-MVP-5003325"><strong>COR_PRF_FUNCTION_ARGUMENT_INFO</strong></a> structure before your enter callback 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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_COR_PRF_FUNCTION_ARGUMENT_INFO</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="n">ULONG</span> <span class="n">numRanges</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="n">ULONG</span> <span class="n">totalArgumentSize</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="n">COR_PRF_FUNCTION_ARGUMENT_RANGE</span> <span class="n">ranges</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">COR_PRF_FUNCTION_ARGUMENT_INFO</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I have to admit that the Microsoft Docs did not really help me to figure out what is the meaning of each field of this structure because the word “range” is very confusing here…</p>
<p>Based on my experiments, <strong>numRanges</strong> gives you the number of parameters; including the implicit <em>this</em> parameter in case of a non-static method. It is different from the signature that we have already parsed from the metadata where <em>this</em> is not mentioned. The <strong>ranges</strong> fields is an array of <strong>COR_PRF_FUNCTION_ARGUMENT_RANGE</strong> ; one per 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></code></pre></td>
<td class="lntd">
<pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="k">typedef</span> <span class="k">struct</span> <span class="n">_COR_PRF_FUNCTION_ARGUMENT_RANGE</span> <span class="p">{</span>  
</span></span><span class="line"><span class="cl">    <span class="n">UINT_PTR</span> <span class="n">startAddress</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl">    <span class="n">ULONG</span> <span class="n">length</span><span class="p">;</span>  
</span></span><span class="line"><span class="cl"><span class="p">}</span> <span class="n">COR_PRF_FUNCTION_ARGUMENT_RANGE</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The <strong>startAddress</strong> points to where the parameter value is stored in memory.</p>
<p>However, in addition to the <strong>FunctionID</strong>, you only receive a <strong>COR_PRF_ELT_INFO</strong> in your enter callback. You need to call <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo3-getfunctionenter3info-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerInfo3:: GetFunctionEnter3Info</strong></a> to get the corresponding <strong>COR_PRF_FUNCTION_ARGUMENT_INFO</strong> you are interested in. As often with COM, you need to call a first time to get the size of the buffer to allocate and a second time to fill it up:</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">ULONG</span> <span class="n">argumentInfoSize</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">COR_PRF_FRAME_INFO</span> <span class="n">frameInfo</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">_pInfo</span><span class="o">-&gt;</span><span class="n">GetFunctionEnter3Info</span><span class="p">(</span><span class="n">functionId</span><span class="p">,</span> <span class="n">eltInfo</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">frameInfo</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argumentInfoSize</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">byte</span><span class="o">*</span> <span class="n">pBuffer</span> <span class="o">=</span> <span class="k">new</span> <span class="n">byte</span><span class="p">[</span><span class="n">argumentInfoSize</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="n">_pInfo</span><span class="o">-&gt;</span><span class="n">GetFunctionEnter3Info</span><span class="p">(</span><span class="n">functionId</span><span class="p">,</span> <span class="n">eltInfo</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">frameInfo</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">argumentInfoSize</span><span class="p">,</span> <span class="p">(</span><span class="n">COR_PRF_FUNCTION_ARGUMENT_INFO</span><span class="o">*</span><span class="p">)</span><span class="n">pBuffer</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">COR_PRF_FUNCTION_ARGUMENT_INFO</span><span class="o">*</span> <span class="n">pArgumentInfo</span> <span class="o">=</span> <span class="p">(</span><span class="n">COR_PRF_FUNCTION_ARGUMENT_INFO</span><span class="o">*</span><span class="p">)</span><span class="n">pBuffer</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Before iterating on the parameters, you need to deal with non-static method and their implicit this parameter stored in <strong>pArgumentInfo-&gt;ranges[0]</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">ULONG</span> <span class="n">hiddenThisParameterIndexOffset</span> <span class="o">=</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="o">!</span><span class="n">pSignature</span><span class="o">-&gt;</span><span class="n">IsStatic</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">hiddenThisParameterIndexOffset</span><span class="o">++</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">   <span class="c1">// deal with the &#34;this&#34; hidden parameter for non static method
</span></span></span><span class="line"><span class="cl">   <span class="c1">// ex: show its address (i.e. pArgumentInfo-&gt;ranges[0].startAddress)
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Next, write a loop to iterate on each parameter based on the parameter count obtained previously from the metadata:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="kt">char</span> <span class="n">value</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span>
</span></span><span class="line"><span class="cl"><span class="k">for</span> <span class="p">(</span><span class="n">ULONG</span> <span class="n">currentParameterInSignature</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">currentParameterInSignature</span> <span class="o">&lt;</span> <span class="n">parameterCount</span><span class="p">;</span> <span class="n">currentParameterInSignature</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="c1">// Note: pParameter contains detail extracted from the metadata signature
</span></span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">   <span class="n">UINT_PTR</span> <span class="n">pStartValue</span> <span class="o">=</span> <span class="n">pArgumentInfo</span><span class="o">-&gt;</span><span class="n">ranges</span><span class="p">[</span><span class="n">currentParameterInSignature</span> <span class="o">+</span> <span class="n">hiddenThisParameterIndexOffset</span><span class="p">].</span><span class="n">startAddress</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">   <span class="n">ULONG</span> <span class="n">length</span> <span class="o">=</span> <span class="n">pArgumentInfo</span><span class="o">-&gt;</span><span class="n">ranges</span><span class="p">[</span><span class="n">currentParameterInSignature</span> <span class="o">+</span> <span class="n">hiddenThisParameterIndexOffset</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="k">if</span> <span class="p">(</span><span class="n">IsPdOut</span><span class="p">(</span><span class="n">pParameter</span><span class="o">-&gt;</span><span class="n">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="c1">// if [out] parameter, nothing to get from it
</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">value</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="sc">&#39;\0&#39;</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">      <span class="c1">// call a helper function to extract the value of the parameter
</span></span></span><span class="line"><span class="cl">      <span class="c1">// a string from its address and type 
</span></span></span><span class="line"><span class="cl">      <span class="n">pHelpers</span><span class="o">-&gt;</span><span class="n">GetObjectValue</span><span class="p">(</span><span class="n">pStartValue</span><span class="p">,</span> <span class="n">length</span><span class="p">,</span> <span class="n">pParameter</span><span class="o">-&gt;</span><span class="n">ElementType</span> <span class="p">,</span> <span class="n">pParameter</span><span class="o">-&gt;</span><span class="n">TypeToken</span><span class="p">,</span> <span class="n">pSignature</span><span class="o">-&gt;</span><span class="n">ModuleId</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">ARRAY_LEN</span><span class="p">(</span><span class="n">value</span><span class="p">)</span> <span class="o">-</span> <span class="mi">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="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="simple-type-parameters-case">Simple type parameters case</h2>
<p>The <strong>GetObjectValue()</strong> helper function looks like the following:</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="kt">void</span> <span class="n">CorProfilerHelpers</span><span class="o">::</span><span class="n">GetObjectValue</span><span class="p">(</span><span class="n">UINT_PTR</span> <span class="n">address</span><span class="p">,</span> <span class="n">ULONG</span> <span class="n">length</span><span class="p">,</span> <span class="n">ULONG</span> <span class="n">elementType</span><span class="p">,</span> <span class="n">mdToken</span> <span class="n">elementTypeToken</span><span class="p">,</span> <span class="n">ModuleID</span> <span class="n">moduleId</span><span class="p">,</span> <span class="kt">char</span><span class="o">*</span> <span class="n">value</span><span class="p">,</span> <span class="n">ULONG</span> <span class="n">charCount</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">ULONG</span> <span class="n">numberValue</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">   <span class="n">strcpy_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</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">switch</span><span class="p">(</span><span class="n">elementType</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">default</span><span class="o">:</span>
</span></span><span class="line"><span class="cl">         <span class="n">sprintf_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;unknown type 0x%x&#34;</span><span class="p">,</span> <span class="n">elementType</span><span class="p">);</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></code></pre></td></tr></table>
</div>
</div><p>The way to get the value of a parameter really depends on its type. I know that a length is provided by the <strong>COR_PRF_FUNCTION_ARGUMENT_INFO</strong> structure but I did not used it except for sanity check.</p>
<p>The value for simple types are easy to compute because they are mostly stored at the given address :</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></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">case</span> <span class="nl">ELEMENT_TYPE_BOOLEAN</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">bool</span><span class="o">*</span> <span class="n">pBool</span> <span class="o">=</span> <span class="p">(</span><span class="kt">bool</span><span class="o">*</span><span class="p">)</span><span class="n">address</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">pBool</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="n">strcpy_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;true&#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="nf">strcpy_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;false&#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">break</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">ELEMENT_TYPE_CHAR</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">WCHAR</span><span class="o">*</span> <span class="n">pChar</span> <span class="o">=</span> <span class="p">(</span><span class="n">WCHAR</span><span class="o">*</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">sprintf_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%C&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">pChar</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">break</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">ELEMENT_TYPE_I1</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// int8
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kt">char</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</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">numberValue</span> <span class="o">=</span> <span class="o">*</span><span class="n">pNumber</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">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%d&#34;</span><span class="p">,</span> <span class="n">numberValue</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">break</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">ELEMENT_TYPE_U1</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// unsigned int8
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">char</span><span class="o">*</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">numberValue</span> <span class="o">=</span> <span class="o">*</span><span class="n">pNumber</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">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%d&#34;</span><span class="p">,</span> <span class="n">numberValue</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">break</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">ELEMENT_TYPE_I2</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// int16
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kt">short</span> <span class="kt">int</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kt">short</span> <span class="kt">int</span><span class="o">*</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">numberValue</span> <span class="o">=</span> <span class="o">*</span><span class="n">pNumber</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">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%d&#34;</span><span class="p">,</span> <span class="n">numberValue</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">break</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">ELEMENT_TYPE_U2</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// unsigned int16
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kt">short</span> <span class="kt">unsigned</span> <span class="kt">int</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kt">short</span> <span class="kt">unsigned</span> <span class="kt">int</span><span class="o">*</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">numberValue</span> <span class="o">=</span> <span class="o">*</span><span class="n">pNumber</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">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%d&#34;</span><span class="p">,</span> <span class="n">numberValue</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">break</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">ELEMENT_TYPE_I4</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// int32
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kr">__int32</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kr">__int32</span><span class="o">*</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">numberValue</span> <span class="o">=</span> <span class="o">*</span><span class="n">pNumber</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">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%d&#34;</span><span class="p">,</span> <span class="n">numberValue</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">break</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">ELEMENT_TYPE_U4</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// unsigned int32
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kt">unsigned</span> <span class="kr">__int32</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int32</span><span class="o">*</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">numberValue</span> <span class="o">=</span> <span class="o">*</span><span class="n">pNumber</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">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%d&#34;</span><span class="p">,</span> <span class="n">numberValue</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">break</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: %lld might not work on linux
</span></span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="nl">ELEMENT_TYPE_I8</span><span class="p">:</span>
</span></span><span class="line"><span class="cl"><span class="c1">// int64
</span></span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="kr">__int64</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kr">__int64</span><span class="o">*</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">sprintf_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%lld&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">pNumber</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">break</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">ELEMENT_TYPE_U8</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">// unsigned int64
</span></span></span><span class="line"><span class="cl">   <span class="kt">unsigned</span> <span class="kr">__int64</span><span class="o">*</span> <span class="n">pNumber</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int64</span><span class="o">*</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">sprintf_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%lld&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">pNumber</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">break</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">And</span> <span class="n">guess</span> <span class="n">what</span><span class="o">?</span> <span class="n">It</span> <span class="n">is</span> <span class="n">the</span> <span class="n">same</span> <span class="k">for</span> <span class="kt">float</span> <span class="n">and</span> <span class="kt">double</span> <span class="n">because</span> <span class="n">it</span> <span class="n">is</span> <span class="n">stored</span> <span class="n">by</span> <span class="n">the</span> <span class="n">CLR</span> <span class="n">the</span> <span class="n">same</span> <span class="n">way</span> <span class="n">as</span> <span class="n">in</span> <span class="n">C</span><span class="o">++:</span>
</span></span><span class="line"><span class="cl"><span class="k">case</span> <span class="nl">ELEMENT_TYPE_R4</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">float</span><span class="o">*</span> <span class="n">pFloat</span> <span class="o">=</span> <span class="p">(</span><span class="kt">float</span><span class="o">*</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">sprintf_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%f&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">pFloat</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">break</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">ELEMENT_TYPE_R8</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">double</span><span class="o">*</span> <span class="n">pDouble</span> <span class="o">=</span> <span class="p">(</span><span class="kt">double</span><span class="o">*</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">sprintf_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;%g&#34;</span><span class="p">,</span> <span class="o">*</span><span class="n">pDouble</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">break</span><span class="p">;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The other types require more knowledge about how the CLR stores their value.</p>
<h2 id="the-stringcase">The string case</h2>
<p>This is the first reference type we meet and, as for all reference types, the given address points to the memory where the reference (i.e. address of the object in the managed heap) is stored. It allows you to check against null parameter before looking at the “real” managed reference:</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="k">case</span> <span class="nl">ELEMENT_TYPE_STRING</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">// look at the reference stored at the given address
</span></span></span><span class="line"><span class="cl">   <span class="kt">unsigned</span> <span class="kr">__int64</span><span class="o">*</span> <span class="n">pAddress</span> <span class="o">=</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kr">__int64</span><span class="o">*</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">byte</span><span class="o">*</span> <span class="n">managedReference</span> <span class="o">=</span> <span class="p">(</span><span class="n">byte</span><span class="o">*</span><span class="p">)(</span><span class="o">*</span><span class="n">pAddress</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">   <span class="c1">// easily check for null string
</span></span></span><span class="line"><span class="cl">   <span class="k">if</span> <span class="p">(</span><span class="n">managedReference</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><span class="line"><span class="cl">      <span class="n">strcpy_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;null string&#34;</span><span class="p">);</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></code></pre></td></tr></table>
</div>
</div><p>At that point, you need to know how an instance of a reference type instance is stored by the CLR in the managed heap. Hopefully, Sergey Tepliakov, a software engineer at Microsoft, has provided <a href="https://devblogs.microsoft.com/premier-developer/managed-object-internals-part-1-layout?WT.mc_id=DT-MVP-5003325">a lot of details about that</a>, especially where does the address stored by a managed reference point to:</p>
<p><img loading="lazy" src="/posts/2021-11-16_reading-parameters-value-with/1_mcau_z9bFdhqDG6ZLRlutw.png"></p>
<p>It means that you have to skip the Method Table pointer pointed to by the address you have. This applies to any reference types instance!</p>
<p>But for our <strong>string</strong> current case, you still need to know how a <strong>string</strong> is stored (i.e. its length followed by the buffer of UTF16 characters). I recommend that you read <a href="https://mattwarren.org/2016/05/31/Strings-and-the-CLR-a-Special-Relationship/">the post from Matt Warren</a> about the subject because it also covers a lot of interesting details related to string implementation. However, you should simply rely on the implementation details provided by the CLR via <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo3-getstringlayout2-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerInfo3:: GetStringLayout2</strong></a><strong>:</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></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">_stringLengthOffset</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="n">ULONG</span> <span class="n">_stringBufferOffset</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">_pProfilerInfo</span><span class="o">-&gt;</span><span class="n">GetStringLayout2</span><span class="p">(</span><span class="o">&amp;</span><span class="n">_stringLengthOffset</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">_stringBufferOffset</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>These two variables give you the offsets to use to access both the string size and the beginning of the array of WCHAR storing each character.</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="c1">// -----------------------------------------------------------------
</span></span></span><span class="line"><span class="cl"><span class="c1">//    | MethodTable address | string length | buffer 
</span></span></span><span class="line"><span class="cl"><span class="c1">// -----------------------------------------------------------------
</span></span></span><span class="line"><span class="cl"><span class="c1">// 64       8 bytes              4 bytes      (length+1) x WCHAR
</span></span></span><span class="line"><span class="cl"><span class="c1">// off                           8            12
</span></span></span><span class="line"><span class="cl"><span class="c1">// -----------------------------------------------------------------
</span></span></span><span class="line"><span class="cl"><span class="c1">// 32       4 bytes              4 bytes      (length+1) x WCHAR
</span></span></span><span class="line"><span class="cl"><span class="c1">// off                           4            8
</span></span></span><span class="line"><span class="cl"><span class="c1">// -----------------------------------------------------------------
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>As shown in this table, you need to skip 8/4 bytes to read the length. It is just the confirmation that you need to jump over the address of the Method Table stored as a 64/32 bit value (i.e. an address in x64/x86). The length itself is stored as a 32 bit number (4 bytes) both in x64 an x86. So the array containing the consecutive UTF16 characters just follows (i.e. its offset from the reference address is 12/8 bytes). For example, here is what you get in Visual Studio Memory panel with the reference to the 3 characters “CLR” string for a 64 bit application:</p>
<p><img loading="lazy" src="/posts/2021-11-16_reading-parameters-value-with/1_H5QzJWaMyhx13cA8bFbV2Q.png"></p>
<p>With this information in hand, it is easy to detect empty strings or copy the UNICODE string into a simple <strong>char</strong>* buffer:</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-cpp" data-lang="cpp"><span class="line"><span class="cl"><span class="n">byte</span><span class="o">*</span> <span class="n">pLength</span> <span class="o">=</span> <span class="n">GetPointerAfterNBytes</span><span class="p">(</span><span class="n">managedReference</span><span class="p">,</span> <span class="n">_stringLengthOffset</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="n">ULONG</span> <span class="n">stringLength</span> <span class="o">=</span> <span class="o">*</span><span class="n">pLength</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">stringLength</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="n">strcpy_s</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="n">charCount</span><span class="p">,</span> <span class="s">&#34;empty string&#34;</span><span class="p">);</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></span><span class="line"><span class="cl"><span class="n">byte</span><span class="o">*</span> <span class="n">pBuffer</span> <span class="o">=</span> <span class="n">GetPointerAfterNBytes</span><span class="p">(</span><span class="n">managedReference</span><span class="p">,</span> <span class="n">_stringBufferOffset</span><span class="p">);</span>
</span></span><span class="line"><span class="cl"><span class="c1">//                                                               V-- to copy the trailing \0
</span></span></span><span class="line"><span class="cl"><span class="o">::</span><span class="n">WideCharToMultiByte</span><span class="p">(</span><span class="n">CP_ACP</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="p">(</span><span class="n">WCHAR</span><span class="o">*</span><span class="p">)</span><span class="n">pBuffer</span><span class="p">,</span> <span class="n">stringLength</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">charCount</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></code></pre></td></tr></table>
</div>
</div><p>The <strong>GetPointerAfterNBytes</strong> function simply helps me dealing with pointer arithmetic in C++</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></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">byte</span><span class="o">*</span> <span class="nf">GetPointerAfterNBytes</span><span class="p">(</span><span class="kt">void</span><span class="o">*</span> <span class="n">pAddress</span><span class="p">,</span> <span class="n">ULONG</span> <span class="n">byteCount</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 class="n">byte</span><span class="o">*</span><span class="p">)</span><span class="n">pAddress</span><span class="p">)</span> <span class="o">+</span> <span class="n">byteCount</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 next post will describe how to get the value of array parameters and the basics of extracting fields value from a reference type instance.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://docs.microsoft.com/en-us/archive/blogs/davbr/sample-a-signature-blob-parser-for-your-profiler?WT.mc_id=DT-MVP-5003325">Sample: A Signature Blob Parser for your Profiler</a></li>
<li><a href="https://mattwarren.org/2016/05/31/Strings-and-the-CLR-a-Special-Relationship/">Strings and the CLR — a Special Relationship</a> by Matt Warren</li>
<li>Episode 1: <a href="/posts/2021-08-07_start-journey-into-the/">Start a journey into the .NET Profiling APIs</a></li>
<li>Episode 2: <a href="/posts/2021-09-06_dealing-with-modules-assemblie/">Dealing with Modules, Assemblies and Types with CLR profiling API</a></li>
<li>Episode 3: <a href="/posts/2021-10-12_decyphering-method-signature-w/">Decyphering methods signature with .NET Profiling APIs</a></li>
</ul>
]]></content:encoded></item><item><title>Start a journey into the .NET Profiling APIs</title><link>https://chrisnas.github.io/posts/2021-08-07_start-journey-into-the/</link><pubDate>Sat, 07 Aug 2021 12:21:39 +0000</pubDate><guid>https://chrisnas.github.io/posts/2021-08-07_start-journey-into-the/</guid><description>This is the first episode of a series digging into the .NET Profiling API to trace each method call parameters and return value.</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>When I want to dig into a new API, I implement a real world scenario. This is exactly what I did for the .NET native profiling API. I want to know how to get parameters and return value of any method call during any .NET application life. The expected result would be something like :</p>
<pre tabindex="0"><code>Enter PublicClass::ClassParamReturnClass
this = 0x6f97e190 (8)
ClassType obj = 0x6f97e488 (8)
| int32 &lt;IntProperty&gt;k__BackingField = 84
| int32 intField = 42
| String stringField = 43
ClassType obj = 0x0000023A475BBAD8
</code></pre><pre tabindex="0"><code>Leave PublicClass::ClassParamReturnClass
| int32 &lt;IntProperty&gt;k__BackingField = 170
| int32 intField = 85
| String stringField = 86
returns 0x0000023A475BBBB0
</code></pre><p>when the following method is executed:</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></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">ClassType</span> <span class="n">ClassParamReturnClass</span><span class="p">(</span><span class="n">ClassType</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">return</span> <span class="k">new</span> <span class="n">ClassType</span><span class="p">(</span><span class="n">obj</span><span class="p">.</span><span class="n">IntProperty</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>You will have to write native C/C++ code to leverage the .NET Profiling API as <a href="https://twitter.com/JohnWintellect">John Robbins</a> explained in his 2003 <a href="https://www.amazon.com/Debugging-Applications-Microsoft-Developer-Reference/dp/0735615365">Debugging Applications book</a>. Even though Microsoft is providing a <a href="https://github.com/Microsoft/clr-samples/tree/master/ProfilingAPI/ELTProfiler">code sample</a> for that, it does just show how to get notified when a function is called or exited but nothing about its type, its name, what are its parameters value and its return value. This series will detail both the .NET profiling API and the metadata API (i.e. the native reflection API).</p>
<p>Let’s start with the basics of .NET Profiling. As shown in the following figure from the <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview?WT.mc_id=DT-MVP-5003325">Microsoft Profiling documentation</a>, with the right <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/setting-up-a-profiling-environment?WT.mc_id=DT-MVP-5003325">environment configuration</a>, the CLR will load a COM-like object implementing <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-interface?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback</strong></a> interface to notify almost everything happening in a .NET application from startup to shutdown.</p>
<p><img loading="lazy" src="/posts/2021-08-07_start-journey-into-the/1_Qw5R9VvFCPePeH_g-6IgKg.png"></p>
<p>Since .NET was launched, more and more notifications have been added by versioning the interface up to <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback9-interface?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback9</strong></a>. Welcome to the usual COM world! I recommend that you watch <a href="https://twitter.com/zodiacon">Pavel Yosifovich</a> session about <a href="https://youtu.be/TqS4OEWn6hQ?t=30">writing a CLR Profiler in an hour</a> to get an overview. I will use <a href="https://github.com/zodiacon/DotNextMoscow2019">his sample solution</a> as a starting point.</p>
<p>For performance sake, you tell the runtime which events you are interested in; i.e. which <strong>ICorProfilerCallback</strong> functions will be called and you simply have to return <strong>S_OK</strong> from all other functions. This setup is done in your implementation of <strong>ICorProfilerCallback::Initialize</strong>. This first function called by the CLR provides a parameter from which you need to <strong>QueryInterface</strong> a version of <strong>ICorProfilerInfo</strong> interface (the current one is <strong>ICorProfilerInfo10</strong>). This interface provides functions to query information about parameters passed to your <strong>ICorProfilerCallback</strong> functions (such as AppDomain, assembly, type, function, thread and so on).</p>
<p>The first <strong>ICorProfilerInfo</strong> function you will use is <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo-seteventmask-method?WT.mc_id=DT-MVP-5003325"><strong>SetEventMask</strong></a> to filter the <strong>ICorProfilerCallback</strong> functions that will be called by the runtime. It accepts a flag combination of values from the <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/cor-prf-monitor-enumeration?WT.mc_id=DT-MVP-5003325"><strong>COR_PRF_MONITOR</strong></a> enumeration.</p>
<h2 id="assembly-code-is-needed-for-enterleavetailcall">Assembly code is needed for Enter/Leave/TailCall</h2>
<p>To get notified when a managed method is called or exits, you should pass:</p>
<pre tabindex="0"><code>COR_PRF_MONITOR_ENTERLEAVE | 
COR_PRF_ENABLE_FUNCTION_ARGS | 
COR_PRF_ENABLE_FUNCTION_RETVAL | 
COR_PRF_ENABLE_FRAME_INFO
</code></pre><p>to <strong>ICorProfilerInfo</strong>::<strong>SetEventMask</strong>. The first flag tells the runtime to call <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-global-static-functions?WT.mc_id=DT-MVP-5003325">static callbacks</a> (i.e. not exposed as functions of <strong>ICorProfilerCallback</strong>) when a managed method gets executed or returns. The other three flags ensure that these callbacks will receive enough information to extract method arguments and return value.</p>
<p>Unlike the other notifications that end up calling your <strong>ICorProfilerCallback</strong> functions, you need to register three special callbacks to the runtime via <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilerinfo3-setenterleavefunctionhooks3withinfo-method?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerInfo3</strong>::<strong>SetEnterLeaveFunctionHooks3WithInfo</strong>.</a> For performance reasons, the .NET team asks you to write the prolog and epilog (i.e. saving/restoring CPU registers on/from the stack) yourself in assembly code instead of relying on well-defined <a href="https://docs.microsoft.com/en-us/cpp/cpp/calling-conventions?WT.mc_id=DT-MVP-5003325">calling conventions</a> supported by the C/C++ compiler.</p>
<p>This is why you have to call <strong>ICorProfilerInfo3:: SetEnterLeaveFunctionHooks3WithInfo</strong> and pass pointers to these “naked” functions. The Microsoft <a href="https://github.com/Microsoft/clr-samples/tree/master/ProfilingAPI/ELTProfiler"><em>ELTProfiler</em></a> sample implements the stubs both for x86 (as inlined assembly embedded in a C++ file) and x64 (defined in .asm file). In x64, you need to update your project file to add the following:</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-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;ImportGroup</span> <span class="na">Label =</span> <span class="s">&#34;ExtensionSettings&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">   <span class="nt">&lt;Import</span> <span class="na">Project =</span> <span class="s">&#34;$(VCTargetsPath)\BuildCustomizations\masm.props&#34;</span> <span class="nt">/ &gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/ ImportGroup&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;ItemGroup&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;MASM</span> <span class="na">Include =</span> <span class="s">&#34;../DotNext.Profiler.Shared/asm/windows/nakedcallbacks.asm&#34;</span>
</span></span><span class="line"><span class="cl">	<span class="na">Condition =</span> <span class="s">&#34;&#39;$(Platform)&#39; == &#39;x64&#39;&#34;</span> <span class="nt">/ &gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/ ItemGroup&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;ImportGroup</span> <span class="na">Label =</span> <span class="s">&#34;ExtensionTargets&#34;</span><span class="nt">&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;Import</span> <span class="na">Project =</span> <span class="s">&#34;$(VCTargetsPath)\BuildCustomizations\masm.targets&#34;</span> <span class="nt">/ &gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/ ImportGroup&gt;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The nakedcallbacks.asm file contains the assembly code to call stub functions wrapped by the expected prolog and epilog written in assembly code. Here is the signature of the functions from where you will be able to start working in C++:</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="n">PROFILER_STUB</span> <span class="n">EnterStub</span><span class="p">(</span><span class="n">FunctionIDOrClientID</span> <span class="n">functionId</span><span class="p">,</span> <span class="n">COR_PRF_ELT_INFO</span> <span class="n">eltInfo</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="n">PROFILER_STUB</span> <span class="n">LeaveStub</span><span class="p">(</span><span class="n">FunctionID</span> <span class="n">functionId</span><span class="p">,</span> <span class="n">COR_PRF_ELT_INFO</span> <span class="n">eltInfo</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="n">PROFILER_STUB</span> <span class="n">TailcallStub</span><span class="p">(</span><span class="n">FunctionID</span> <span class="n">functionId</span><span class="p">,</span> <span class="n">COR_PRF_ELT_INFO</span> <span class="n">eltInfo</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>A function is identified by a <strong>FunctionID</strong> and this is where you start your adventure (I will come back later to the <strong>COR_PRF_ELT_INFO</strong> parameter). Note that for the <strong>EnterStub</strong> function, you need to get the <strong>FunctionID</strong> from the <strong>FunctionIDOrClientID.functionId</strong> field.</p>
<p>Once your hook callbacks have been registered via <strong>ICorProfilerInfo3:: SetEnterLeaveFunctionHooks3WithInfo</strong>, it is still possible to decide whether or not a managed method call should trigger them. For that, it is needed to register a “mapper” function that is called once per managed method with one of these functions:</p>
<ul>
<li><code>HRESULT ICorProfilerInfo::SetFunctionIDMapper([in] FunctionIDMapper* pFunc); </code>
 with <code>UINT_PTR __stdcall Mapper(FunctionID functionId, BOOL* pHookFunction)</code></li>
<li><code>HRESULT ICorProfilerInfo3::SetFunctionIDMapper2([in] FunctionIDMapper2* pFunc, [in] void* clientData);</code>
 with <code>UINT_PTR __stdcall Mapper2(FunctionID functionId, void* clientData, BOOL* pHookFunction)</code></li>
</ul>
<p>The latter allows you to pass some “client data” to the mapper function such as a helper class to manipulate the received <strong>FunctionID</strong> or your profiler state.</p>
<p>If <strong>pHookFunction</strong> is set to <strong>TRUE</strong>, your enter/leave functions will be called and the returned <strong>UINT_PTR</strong> will be passed as the <strong>FunctionID</strong> parameter. This allows you to handle function name or signature computation at one single place outside of the real profiling work done each time a method is called. If <strong>pHookFunction</strong> is set to <strong>FALSE</strong>, the enter/leave functions will never be called for that <strong>FunctionID</strong>. The mapper callback is called only once per <strong>FunctionID</strong>: this could be a good way to avoid performance impact if you just want to profile a small subset of methods.</p>
<h2 id="how-to-debug-yourprofiler">How to debug your profiler</h2>
<p>Before going any further, it is needed to know how to debug your C++ profiler code with Visual Studio. The first step is to write a simple .NET application to execute the method calls you want to intercept. The second natural step would be to setup the environment variables needed to inject your profiler:</p>
<p><img loading="lazy" src="/posts/2021-08-07_start-journey-into-the/1_v9kUxczdLpxQd6RWAUh8gg.png"></p>
<p>and also check the <em>Enable native code debugging</em> option:</p>
<p><img loading="lazy" src="/posts/2021-08-07_start-journey-into-the/1_wD4vkFJAaBBxlxMh3MqK1w.png"></p>
<p>If you start a debug session, the Visual Studio debugger with use both managed and native debugging APIs. Unfortunately, the managed debugging API does not allow breakpoints sets in <strong>ICorProfilerCallback</strong> functions.</p>
<p>Instead, you need to <strong>Debug | Start Without Debugging</strong> (<strong>CTRL+F5</strong>), not <strong>Debug | Start Debugging</strong> (<strong>F5</strong>) the C# test application with the same environment variables and attach the debugger via <strong>Debug | Attach to Process</strong>. Select the process and click <strong>Select</strong> button in the <strong>Attach to</strong> section:</p>
<p><img loading="lazy" src="/posts/2021-08-07_start-journey-into-the/1_C9gYCSz2ytVj2V8EvTHAhA.png"></p>
<p>To make attachment simple, add a <strong>Console.ReadLine</strong> in a console application before starting the method calls to test.</p>
<p>The next post will show you how to extract information from a <strong>FunctionID</strong>.</p>
<h2 id="references">References</h2>
<ul>
<li><a href="https://www.youtube.com/watch?v=TqS4OEWn6hQ&amp;t=25s">Writing a .NET Core cross platform profiler in an hour</a> video and the corresponding <a href="https://github.com/zodiacon/DotNextMoscow2019">source code</a> from <a href="https://twitter.com/zodiacon">Pavel Yosifovich</a></li>
<li><a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/profiling-overview?WT.mc_id=DT-MVP-5003325">Microsoft Profiling documentation</a></li>
<li>Microsoft Enter/Leave <a href="https://github.com/Microsoft/clr-samples/tree/master/ProfilingAPI/ELTProfiler">code sample</a></li>
</ul>
]]></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>Build your own .NET memory profiler in C# — Allocations (1/2)</title><link>https://chrisnas.github.io/posts/2020-04-18_build-your-own-net/</link><pubDate>Sat, 18 Apr 2020 09:48:53 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-04-18_build-your-own-net/</guid><description>This post explains how to collect allocation details by writing your own memory profiler in C#.</description><content:encoded><![CDATA[<hr>
<p>In a <a href="/posts/2019-05-28_spying-on-net-garbage/">previous post</a>, I explained how to get statistics about the .NET Garbage Collector such as suspension time or generation sizes. But what if you would need more details about your application allocations such as how many times instances of a given type were allocated and for what cumulated size or even the allocation rate? This post explains how to get access to such information by writing your own memory profiler. The next one will show how to collect each sampled allocation stack trace.</p>
<h2 id="introduction">Introduction</h2>
<p>I have already used commercial tools to get detailed information about allocated type instances in an application; Visual Studio Profiler, dotTrace, ANTS memory profiler, or Perfview to name a few. With these tools in mind, I started to look at the .NET profiler API documentation and it reminded me the first time I read about the .NET profiler API. It was in December 2001 in <a href="https://docs.microsoft.com/en-us/archive/msdn-magazine/2001/december/under-the-hood-the-net-profiling-api-and-the-dnprofiler-tool?WT.mc_id=DT-MVP-5003325">Matt Pietrek’s MSDN Magazine article</a> (I still have the paper version). When your application is starting, based on an environment variable, the .NET Framework (and now .NET Core) runtime is loading a profiler COM object that implements a specific <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback9-interface?WT.mc_id=DT-MVP-5003325"><strong>ICorProfilerCallback</strong></a> interface (today, runtimes are supporting the 9th version <strong>ICorProfilerCallback9</strong> interface). The methods of this interface will be called by the runtime at specific moments during the application lifetime. For example, the <a href="https://docs.microsoft.com/en-us/dotnet/framework/unmanaged-api/profiling/icorprofilercallback-objectallocated-method?WT.mc_id=DT-MVP-5003325"><strong>ObjectAllocated</strong></a> method is called each time an instance of a class is allocated: perfect for the job but it requires going back to COM and writing native code. Don’t be scared: I won’t go that way :^)</p>
<p><em>However, if you would like to get more details about writing your own .NET profiler in C or C++, I would recommend looking at the Microsoft ClrProfiler <em><a href="https://github.com/microsoftarchive/clrprofiler/tree/master/CLRProfiler"><em>initial .NET Framework implementation</em></a></em> and also Pavel Yosifovich DotNext session about <em><a href="https://www.youtube.com/watch?v=TqS4OEWn6hQ"><em>Writing a .NET Core cross platform profiler in an hour</em></a></em> with the corresponding (more recent and cross platform) <em><a href="https://github.com/zodiacon/DotNextMoscow2019"><em>source code</em></a></em>.</em></p>
<p>Instead, several events that are emitted by the CLR are providing interesting details:</p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_8RzRelU9Rgux0TJRdFhzzw.png"></p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_-DSm_89dq8yj8jI1aZ3ZYg.png"></p>
<p>The <strong>GCSampledObjectAllocation</strong> events payload provides a type ID instead of a plain text type name. In order to retrieve the type name given its ID, we need to listen to <strong>TypeBulkType</strong> event that contains the mapping as I described in <a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">my post about finalizers</a>. This is why the last two <strong>GCHeapAndTypeNames</strong> and <strong>Type</strong> keywords are needed.</p>
<p>Remember that if both <strong>GCSampledObjectAllocationLow</strong> and <strong>GCSampledObjectAllocationHigh</strong> keywords are set, an event will be received for EACH allocation. This could be a performance issue both for the monitored application and the profiler. I would recommend starting with either low or high (more on this later).</p>
<p>Last but not least, enabling at least one of these keywords is also <a href="https://github.com/dotnet/runtime/blob/fcd862e06413a000f9cafa9d2f359226c60b9b42/src/coreclr/src/vm/jitinterfacegen.cpp#L69">switching the CLR to use “slower” allocators</a>. This is why you should check that it does not impact your application performance. These slower allocators are also used when your <strong>ICorProfilerCallback.Initialize</strong> method calls <strong>SetEventMask</strong> with <strong>COR_PRF_ENABLE_OBJECT_ALLOCATED</strong> flag to receive allocation notifications.</p>
<p>When you use <a href="https://github.com/Microsoft/perfview/releases">Perfview</a> for memory investigation, you are relying on these events without knowing it. In the Collect/Run dialog, three checkboxes are defining how to get the memory profiling details:</p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_8kuywni3W8PVleqg5NwWPw.png"></p>
<ul>
<li><em>.NET Alloc</em>: use a custom native C++ <strong>ICorProfilerCallback</strong> implementation (noticeable impact on the profiled application performance).</li>
<li><em>.NET SampAlloc</em>: use the same custom native profiler but with sampled events.</li>
<li><em>ETW .NET Alloc</em>: use <strong>GCSampledObjectAllocationHigh</strong> events</li>
</ul>
<p>In all cases, the profiled application needs to be started after the collection begins.</p>
<h2 id="how-to-listen-to-allocation-events">How to listen to allocation events</h2>
<p>As I have already explained in previous posts, the Microsoft <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/"><strong>TraceEvent</strong> nuget</a> helps you listening to CLR events. First, you create a <strong>TraceEventSession</strong> and setup the providers you want to receive events from:</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="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></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></span><span class="line"><span class="cl">    <span class="c1">// the CLR source code indicates that the provider must be set before the monitored application starts</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">GCSampledObjectAllocationLow</span> <span class="p">|</span> 
</span></span><span class="line"><span class="cl">    <span class="c1">//ClrTraceEventParser.Keywords.GCSampledObjectAllocationHigh | </span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// required to receive the BulkType events that allows </span>
</span></span><span class="line"><span class="cl">    <span class="c1">// mapping between the type ID received in the allocation 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">GCHeapAndTypeNames</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">Type</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>Second, you set up the handlers for the events 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><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">SetupListeners</span><span class="p">(</span><span class="n">TraceLogEventSource</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="n">source</span><span class="p">.</span><span class="n">Clr</span><span class="p">.</span><span class="n">GCAllocationTick</span> <span class="p">+=</span> <span class="n">OnAllocationTick</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">Clr</span><span class="p">.</span><span class="n">GCSampledObjectAllocation</span> <span class="p">+=</span> <span class="n">OnSampleObjectAllocation</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// required to receive the mapping between type ID (received in GCSampledObjectAllocation)</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// and their name (received in TypeBulkType)</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">TypeBulkType</span> <span class="p">+=</span> <span class="n">OnTypeBulkType</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>And lastly, the processing of received events is done in a dedicated thread until the session is disposed of:</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="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 class="n">SetupListeners</span><span class="p">(</span><span class="n">_session</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">_session</span><span class="p">.</span><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>Now let’s see the difference between the two sets of events.</p>
<h2 id="the-allocationtick-way">The AllocationTick way</h2>
<p>My first idea was to use the <strong>AllocationTick</strong> event because it seemed easy: one sampled event with a size, a type name, and LOH/ephemeral kind. However, how this sampling works makes it impossible to get an exact per type allocated size. Let’s have a look at this list of events received from a WPF test application:</p>
<pre tabindex="0"><code>Small | 105444 : FreezableContextPair[]
Small | 111908 : FreezableContextPair[]
Small | 106720 : System.String
Small | 102488 : System.String
Small | 107028 : System.TimeSpan[]
Small | 106100 : System.String
</code></pre><p>All allocations were s<strong>mall</strong> (i.e. not in the LOH: &lt; 85.000 bytes) and the second column gives the cumulated size of all allocations to reach the 100 KB threshold but not for this particular type! There is no easy way to make a valid guess of the specific last allocation size for which we get the type name.</p>
<p>For example, the first array of <strong>FreezableContextPair</strong> triggered the event for a cumulated size of 105.444 bytes. But how big was this array? We don’t know: could have been 100.000 because only 5444 bytes were allocated before or only 10444 bytes because 95.000 were allocated before. It would have been so useful that the size of the last allocated object would be passed in the event payload…</p>
<p>It is a little bit different (but not that better) for objects allocated in LOH because they have to be at least 85.000 bytes long. For example, allocate 4-byte arrays, each one 85.000 bytes long and let’s see the corresponding events:</p>
<pre tabindex="0"><code>Large | 170064 : System.Byte[]
Large | 170064 : System.Byte[]
</code></pre><p>Two <strong>AllocationTick</strong> events are received with 170064 as cumulated size. Still hard to figure out what was the size of the last allocated array: the only thing we know is that it was larger (or equal) to 85.000 bytes because it was allocated in LOH.</p>
<p>For larger objects, it might seem a little bit more accurate. Let’s allocate 2 byte arrays, each one 110.000 bytes long:</p>
<pre tabindex="0"><code>Large | 195064 : System.Byte[]
Large | 110032 : System.Byte[]
</code></pre><p>There are ~85.000 bytes difference between the two events even though the same 110.000 bytes were allocated. You could remove 85.000 bytes from the value and have an approximation of the LOH allocated object: the larger the allocation the less the error. But still: could be 85.000 size error…</p>
<p>So we won’t be able to rely on the size provided by the <strong>AllocationTick</strong> event; only the type name. In addition, you get a view of objects allocated in LOH. Maybe the other events will provide better results.</p>
<h2 id="the-gcsampledobjectallocation-way">The GCSampledObjectAllocation way</h2>
<p>When an object is allocated by the GC allocator, a <strong>GCSampledObjectAllocation</strong> event is emitted under certain conditions:</p>
<ul>
<li>Both <strong>GCSampledObjectAllocationLow</strong> and <strong>GCSampledObjectAllocationHigh</strong> keywords are set on the CLR provider,</li>
<li>The object size is larger than 10.000 bytes,</li>
<li>At least 1000 instances of the type have been allocated,</li>
<li>Just before the application exits, current statistics for all types <a href="https://github.com/dotnet/runtime/blob/61ec7c7bdacb70ffd51dece09e30179f86156a0d/src/coreclr/src/vm/eventtrace.cpp#L3668">are flushed</a>,</li>
<li>A <a href="https://github.com/dotnet/runtime/blob/61ec7c7bdacb70ffd51dece09e30179f86156a0d/src/coreclr/src/vm/eventtrace.cpp#L3067">complicated piece of code</a> decides based on time since the last event and the type allocation rate.</li>
</ul>
<p>Picking one or the other keyword <a href="https://github.com/dotnet/runtime/blob/61ec7c7bdacb70ffd51dece09e30179f86156a0d/src/coreclr/src/vm/eventtrace.cpp#L2902">changes the maximum number of milliseconds between two events</a> for a given type:</p>
<ul>
<li>High (10 ms) : 100 events / second</li>
<li>Low (200 ms) : 5 events / second</li>
</ul>
<p>You should use low or high depending on the monitored application memory allocation workload to avoid impacting too much the profiler (and even the monitored application performance)</p>
<p>The interesting feature of these events is that, for a given type, the payload contains both the number of allocated instances since the last event and the cumulated size of these instances. Let’s take the same allocation of 4 arrays of byte, each 85000 long:</p>
<pre tabindex="0"><code>226 | 103616 : System.Byte[]
  1 |  85012 : System.Byte[]
  1 |  85012 : System.Byte[]
  1 |  85012 : System.Byte[]
</code></pre><p>This time, we get the exact count in the first column (<strong>ObjectCountForTypeSample</strong>) and the exact cumulated size in the second column (<strong>TotalSizeForTypeSample</strong>). If the count is 1, we have the exact size of that allocation and if it is bigger than 85000 bytes, we know it has been allocated in the LOH. Same accuracy for the 2-byte array of 110.000 elements:</p>
<pre tabindex="0"><code>198 | 123552 : System.Byte[]
  1 | 110012 : System.Byte[]
</code></pre><p>Sounds good. However, you have to remember that profiled applications need to be started after the session was created: it means that you can’t write a tool that will listen to a specific process ID like with <strong>AllocationTick</strong>. Three dictionaries are used by <strong>PerProcessProfilingState</strong> to keep track of per type allocations, type ID mappings, and process names:</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></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="kt">string</span><span class="p">&gt;</span> <span class="n">_processNames</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="kt">string</span><span class="p">&gt;();</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">ProcessTypeMapping</span><span class="p">&gt;</span> <span class="n">_perProcessTypes</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">ProcessTypeMapping</span><span class="p">&gt;();</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">ProcessAllocationInfo</span><span class="p">&gt;</span> <span class="n">_perProcessAllocations</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">ProcessAllocationInfo</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="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="kt">string</span><span class="p">&gt;</span> <span class="n">Names</span> <span class="p">=&gt;</span> <span class="n">_processNames</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">ProcessTypeMapping</span><span class="p">&gt;</span> <span class="n">Types</span> <span class="p">=&gt;</span> <span class="n">_perProcessTypes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">public</span> <span class="n">Dictionary</span><span class="p">&lt;</span><span class="kt">int</span><span class="p">,</span> <span class="n">ProcessAllocationInfo</span><span class="p">&gt;</span> <span class="n">Allocations</span> <span class="p">=&gt;</span> <span class="n">_perProcessAllocations</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 <strong>SampledObjectAllocationMemoryProfiler</strong> class uses it for the events processing:</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></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">SampledObjectAllocationMemoryProfiler</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">TraceEventSession</span> <span class="n">_session</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">PerProcessProfilingState</span> <span class="n">_processes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="c1">// because we are not interested in self monitoring</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">_currentPid</span><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">int</span> <span class="n">_started</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="kd">public</span> <span class="n">SampledObjectAllocationMemoryProfiler</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">session</span><span class="p">,</span> <span class="n">PerProcessProfilingState</span> <span class="n">processes</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">session</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_processes</span> <span class="p">=</span> <span class="n">processes</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">        <span class="n">_currentPid</span> <span class="p">=</span> <span class="n">Process</span><span class="p">.</span><span class="n">GetCurrentProcess</span><span class="p">().</span><span class="n">Id</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 constructor of the profiler keeps track of its own process ID in <strong>_currentPid</strong> to skip its own events.</p>
<h2 id="gathering-typemapping">Gathering type mapping</h2>
<p>The processing of <strong>TypeBulkType</strong> events is quite straightforward: store the type ID/name association into a per-process dictionary:</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">private</span> <span class="k">void</span> <span class="n">OnTypeBulkType</span><span class="p">(</span><span class="n">GCBulkTypeTraceData</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">ProcessTypeMapping</span> <span class="n">mapping</span> <span class="p">=</span> <span class="n">GetProcessTypesMapping</span><span class="p">(</span><span class="n">data</span><span class="p">.</span><span class="n">ProcessID</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">currentType</span> <span class="p">=</span> <span class="m">0</span><span class="p">;</span> <span class="n">currentType</span> <span class="p">&lt;</span> <span class="n">data</span><span class="p">.</span><span class="n">Count</span><span class="p">;</span> <span class="n">currentType</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">GCBulkTypeValues</span> <span class="k">value</span> <span class="p">=</span> <span class="n">data</span><span class="p">.</span><span class="n">Values</span><span class="p">(</span><span class="n">currentType</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">mapping</span><span class="p">[</span><span class="k">value</span><span class="p">.</span><span class="n">TypeID</span><span class="p">]</span> <span class="p">=</span> <span class="k">value</span><span class="p">.</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="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">ProcessTypeMapping</span> <span class="n">GetProcessTypesMapping</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="n">ProcessTypeMapping</span> <span class="n">mapping</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">_processes</span><span class="p">.</span><span class="n">Types</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="n">mapping</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">AssociateProcess</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="n">mapping</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ProcessTypeMapping</span><span class="p">(</span><span class="n">pid</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="n">_processes</span><span class="p">.</span><span class="n">Types</span><span class="p">[</span><span class="n">pid</span><span class="p">]</span> <span class="p">=</span> <span class="n">mapping</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">mapping</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>Remember that I choose to skip events from the current process detected by <strong>FilterOutEvent()</strong>.</p>
<h2 id="how-to-get-processnames">How to get process names</h2>
<p>Even though each event contains the ID of the emitting process, it would be better to display its name instead. You could use <strong>Process.GetProcessById(pid).ProcessName</strong> when analyzing the details but the process might be long gone at that time.</p>
<p>Another solution would be to enable the Kernel ETW provider and listen to the <strong>ProcessStart</strong> event. The <strong>ImageFileName</strong> field of the payload contains the process filename with the extension. However, it is obviously not working on Linux.</p>
<p>The easiest solution is to use <strong>GetProcessById</strong> but just when you receive the first type mapping for a given process. This is the role of the <strong>AssociateProcess</strong> method called in <strong>GetProcessTypesMapping</strong> shown previously:</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-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">AssociateProcess</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">_processes</span><span class="p">.</span><span class="n">Names</span><span class="p">[</span><span class="n">pid</span><span class="p">]</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 class="n">ProcessName</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="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;? {pid}&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">        <span class="c1">// we might not have access to the process</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>It is now time to process allocation events.</p>
<h2 id="collecting-allocation-details">Collecting allocation details</h2>
<p>The <strong>GCSampledObjectAllocationTraceData</strong> payload contains the size and count of instances since the last event. We just need to store them for the corresponding 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></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">OnSampleObjectAllocation</span><span class="p">(</span><span class="n">GCSampledObjectAllocationTraceData</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">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></span><span class="line"><span class="cl">        <span class="p">.</span><span class="n">AddAllocation</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 class="n">data</span><span class="p">.</span><span class="n">TotalSizeForTypeSample</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 class="n">data</span><span class="p">.</span><span class="n">ObjectCountForTypeSample</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">            <span class="n">GetProcessTypeName</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">TypeID</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="kd">private</span> <span class="kt">string</span> <span class="n">GetProcessTypeName</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">typeID</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">_processes</span><span class="p">.</span><span class="n">Types</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">mapping</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">typeID</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="kt">var</span> <span class="n">name</span> <span class="p">=</span> <span class="n">mapping</span><span class="p">[</span><span class="n">typeID</span><span class="p">];</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="kt">string</span><span class="p">.</span><span class="n">IsNullOrEmpty</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="p">?</span> <span class="n">typeID</span><span class="p">.</span><span class="n">ToString</span><span class="p">()</span> <span class="p">:</span> <span class="n">name</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 <strong>AddAllocation()</strong> helper method is simply accumulating these numbers for a given type in the <strong>ProcessAllocationInfo</strong> associated to the related process.</p>
<h2 id="displaying-theresults">Displaying the results</h2>
<p>When the profiling session ends, it is easy to show the allocated count and size per type:</p>
<p><img loading="lazy" src="/posts/2020-04-18_build-your-own-net/1_1zQgewOknfy2R_SHJfGkKQ.png"></p>
<p>The code is using a Linq syntax to get top allocations sorted either by count or by 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></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">ShowResults</span><span class="p">(</span><span class="kt">string</span> <span class="n">name</span><span class="p">,</span> <span class="n">ProcessAllocationInfo</span> <span class="n">allocations</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">sortBySize</span><span class="p">,</span> <span class="kt">int</span> <span class="n">topTypesLimit</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;Memory allocations for {name}&#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></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">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;    Count        Size   Type&#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">IEnumerable</span><span class="p">&lt;</span><span class="n">AllocationInfo</span><span class="p">&gt;</span> <span class="n">types</span> <span class="p">=</span> <span class="p">(</span><span class="n">sortBySize</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="p">?</span> <span class="n">allocations</span><span class="p">.</span><span class="n">GetAllocations</span><span class="p">().</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="n">a</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 class="n">allocations</span><span class="p">.</span><span class="n">GetAllocations</span><span class="p">().</span><span class="n">OrderByDescending</span><span class="p">(</span><span class="n">a</span> <span class="p">=&gt;</span> <span class="n">a</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 class="k">if</span> <span class="p">(</span><span class="n">topTypesLimit</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">types</span> <span class="p">=</span> <span class="n">types</span><span class="p">.</span><span class="n">Take</span><span class="p">(</span><span class="n">topTypesLimit</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">
</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">allocation</span> <span class="k">in</span> <span class="n">types</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;{allocation.Count,9} {allocation.Size,11}   {allocation.TypeName}&#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>Another usage could be a long-running monitoring system that shows the allocation rate: a nice complement to the other GC metrics. However, compared to the other profilers, one important feature is missing: if an unexpected number of instances are created, how to know which part of the code is responsible for the spike?</p>
<p>The next post will explain how to enhance such a sampled memory profiler with call stacks per sampled allocation.</p>
<hr>
<h2 id="resources">Resources</h2>
<ul>
<li>Source code available <a href="https://github.com/chrisnas/ClrEvents">on Github</a>.</li>
<li><a href="/posts/2018-12-15_spying-on-net-garbage/">Spying on .NET Garbage Collector with TraceEvent</a></li>
<li>Pavel Yosifovich — <a href="https://www.youtube.com/watch?v=TqS4OEWn6hQ">Writing a .NET Core cross-platform profiler in an hour</a></li>
<li><a href="https://github.com/microsoftarchive/clrprofiler/tree/master/CLRProfiler">Original Microsoft ClrProfiler source code and documentation</a></li>
</ul>
<hr>
<p><strong>Like what you read? Don’t forget to check out part 2 on this topic:</strong></p>
<p><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>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>
*Product, Research &amp; Development at Criteo. At Criteo, come and meet our teams and join our R &amp; D and also enjoy…*careers.criteo.com</a><a href="https://careers.criteo.com/working-in-R&amp;D"></a></p>
]]></content:encoded></item><item><title>Debugging Wednesday at Criteo — Cancel this task!</title><link>https://chrisnas.github.io/posts/2020-02-21_debugging-wednesday-cancel-thi/</link><pubDate>Fri, 21 Feb 2020 08:18:08 +0000</pubDate><guid>https://chrisnas.github.io/posts/2020-02-21_debugging-wednesday-cancel-thi/</guid><description>Last Wednesday “Debugging” day for Kevin and myself. Let’s share with you these frustrating but interesting minutes.</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_g3mc-sEc56gtAUP_rR4HHA.jpeg"></p>
<h2 id="introduction">Introduction</h2>
<p>Last Wednesday was a great day for <a href="https://twitter.com/KooKiz">Kevin</a> and myself: We spent a lot of time investigating the reasons why a test was failing. Let’s share with you these frustrating but interesting minutes.</p>
<p>One of our colleagues came to us because an integration test would get stuck in some specific conditions. Here is the simplified code of the service that is supposed to do some background processing until it is stopped:</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">class</span> <span class="nc">Service</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">CancellationTokenSource</span> <span class="n">_cancellationSource</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Task</span> <span class="n">_backgroundProcessing</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="n">Task</span> <span class="n">_cleanup</span><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><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>In the test, the service is created: Two <code>Task</code> instances are created in the constructor (for background processing irrelevant for this discussion) that (1) are started when the service starts and (2) are canceled when the service is stopped. A <code>CancellationTokenSource</code> is used to cancel the tasks if 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></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">Service</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">_cancellationSource</span> <span class="p">=</span> <span class="k">new</span> <span class="n">CancellationTokenSource</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">cancellationToken</span> <span class="p">=</span> <span class="n">_cancellationSource</span><span class="p">.</span><span class="n">Token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="n">_backgroundProcessing</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Task</span><span class="p">(</span><span class="n">DoStuffInTheBackground</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">        <span class="n">cancellationToken</span><span class="p">,</span> <span class="n">TaskCreationOptions</span><span class="p">.</span><span class="n">LongRunning</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">_cleanup</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Task</span><span class="p">(</span><span class="n">DoStuffInTheBackground</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">        <span class="n">cancellationToken</span><span class="p">,</span> <span class="n">TaskCreationOptions</span><span class="p">.</span><span class="n">LongRunning</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>However, in the specific conditions of this test, the <code>Start</code> method would never be called, skipping straight to the <code>Stop</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></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">Start</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">_backgroundProcessing</span><span class="p">.</span><span class="n">Start</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="n">_cleanup</span><span class="p">.</span><span class="n">Start</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="kd">async</span> <span class="n">Task</span> <span class="n">Stop</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">_cancellationSource</span><span class="p">.</span><span class="n">Cancel</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">_backgroundProcessing</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="k">await</span> <span class="n">_cleanup</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 task is never started because the test skips the <code>Service.Start()</code> method, but it should transition to “Cancelled” state as soon as the <code>CancellationTokenSource</code> associated with the <code>CancellationToken</code> passed to the <code>Task</code> gets canceled. In that particular situation, we expect the <code>await _backgroundProcessing</code> code to immediately return in the <code>Stop()</code> method.</p>
<p>In our colleague Visual Studio, the debugger never came back from <code>await _backgroundProcessing</code>, indicating that the task never completed…</p>
<h2 id="reproduce-theproblem">Reproduce the problem</h2>
<p>I wanted to un-validate any side effect related to our test framework or custom Criteo libraries and confirm my understanding of task cancellation, so I wrote a small Console application with the same 4.7.2 version of .NET Framework with 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">static</span> <span class="kd">async</span> <span class="n">Task</span> <span class="n">ReproduceWorking</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">CancellationTokenSource</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">token</span> <span class="p">=</span> <span class="n">source</span><span class="p">.</span><span class="n">Token</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">t</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Task</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">        <span class="p">()</span> <span class="p">=&gt;</span> <span class="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;Done&#34;</span><span class="p">),</span> <span class="n">token</span><span class="p">,</span> <span class="n">TaskCreationOptions</span><span class="p">.</span><span class="n">LongRunning</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">Cancel</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">
</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="k">await</span> <span class="n">t</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">TaskCanceledException</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="n">x</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 class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>And guess what?
I got the expected <code>TaskCancellationException</code>:</p>
<blockquote>
<p>System.Threading.Tasks.TaskCanceledException: A task was canceled.
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.GetResult()
at TaskCancellation.Program.d__2.MoveNext()</p>
</blockquote>
<p>The next step was to double-check the understanding of how tasks are working related to cancellation. So I set a breakpoint after the task is instantiated</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_lNlSNvggMbjwA7mAC33LPA.png"></p>
<p>And its status is <strong>Created</strong>.</p>
<p>Doing the same after <code>Cancel()</code> is called on the <code>CancellationTokenSource</code>, gives the expected <strong>Canceled</strong> status.</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_uDQ7b1Eq2QYYmVdT1jSn1Q.png"></p>
<p>So, let’s do the same when debugging the test code:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_v90VjEEUTCNHm3Xre_zeHQ.png"></p>
<p>It gives the same <strong>Created</strong> status after the task creation but after canceling the source:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_iOYZBxNZOLvrxarbcbj6Uw.png"></p>
<p>…the status does not switch to <strong>Canceled</strong> like in my Console repro!</p>
<h2 id="looking-for-cancellation">Looking for cancellation</h2>
<p>The only thing that came to my mind was: maybe the cancellation token is not taken into account. However, a <code>CancellationToken</code> is just a struct that keeps a reference to its <code>CancellationTokenSource</code>. It should be easy in Visual Studio debugger to double-check that our task keeps track of the token somewhere and goes back to the cancellation source. Well… the token is not kept as a field of the task but stored inside the <code>m_contingentProperties</code> field <a href="https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,674">deep during the task construction code path</a>.</p>
<p>Let’s look at the value in the <strong>Quick Watch</strong> after the cancellation source gets canceled:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_rJgSvnb9o7_Zut1D0f4fjg.png"></p>
<p>It sounds like the cancellation token is not canceled… But if we look at the source <code>Token</code> property of our canceled <code>CancellationTokenSource</code>,</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_AduGgofUrRBXhEHlqYYueA.png"></p>
<p>we don’t have the same value for <code>IsCancellationRequested</code>!</p>
<p>It’s like we don’t look at the same cancellation source… To find out, we just have to compare the reference to our <code>CancellationTokenSource</code> with the one we see in the <code>m_contingentProperties</code> token of the task. To achieve that, we could copy the expression from QuickWatch, paste it into the <strong>Debug | Windows | Memory…</strong> pane and press ENTER to get the address where the object is stored in memory</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_UVx4QML3o6xp4Ecsu6cnbw.png"></p>
<p>After having done the same with <code>_cancellationSource</code>, I did not get the same address:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_cPXCRetw7iGMYQ6g1tMpcA.png"></p>
<p>It means that we were dealing with two different instances of <code>CancellationTokenSource</code>.</p>
<p>But this might be too C++ish for you… Kevin prefers leveraging the <em>Make Object ID</em> feature of the C# debugger. You simply right-click the Data Tip of the cancellation source and select <strong>Make Object ID</strong>:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_RCJafBYT7icR6YFQ6tkxdA.png"></p>
<p>Once this is done, a numeric identifier is displayed for this instance in Data Tip and any Watch window (#1 in this screenshot):</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_CTf7RGYgX2otS5K6rH9azQ.png"></p>
<p>When we looked at the cancellation source of the token stored by the task,</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_aHnPvqWuL5sM0cj9ztyA4A.png"></p>
<p>we didn’t see any ID so it was not the same object.</p>
<p>So we decided to use Make Object ID on this <code>m_source</code> that became marked as #2.</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_KZ48iXU0TobSm-A3YFUTIQ.png"></p>
<p>And when we looked at the <code>m_source</code> of the second cleanup <code>Task</code>, we realized that it was the same object but not the one we created!</p>
<p>We started to think that we were becoming crazy. So, let’s restart from the beginning and follow the <code>CancellationTokenSource</code> from its creation because we are sure that we passed a valid cancellation token (linked to this source) to the task constructor. Or… Did we? The QuickWatch gives a different answer just after the task gets created compared to what we’ve seen already: a token with an empty source property now!</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_z9hOeNeJ7_o6_qX08bwWaw.png"></p>
<p>I closed the QuickWatch pane and reopened it for Kevin to confirm. And like in a nightmare, the source was not null anymore…</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_l_2KlUeedUlG42RAej2jjA.png"></p>
<p>Visual Studio must be responsible for that weird behavior!</p>
<p>Kevin remembered the <em>”Enable property evaluation”</em> settings in the Options dialog. If it is checked (which is the default), it means that the Debugger would fetch the value of an instance field and then call the Getter of each property in order to display its value.</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_XqOydO0OsS0noacmEDpEdg.png"></p>
<p>However, if you uncheck it, only the fields are displayed. So in our case, we then always got a null <code>m_contingentProperties</code> field (and, as expected, all property would not be displayed):</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_33vZaFRFh1ii2X2APgaeXw.png"></p>
<p>The <code>m_contingentProperties</code> is initialized in <code>EnsureContingentPropertiesInitialized()</code> when called by <code>AssignCancellationToken()</code> from the <code>TaskContructorCore()</code> helper used by the <code>Task</code> constructor but it did not seem to be the case because it was definitively <strong>null</strong>…</p>
<p>Kevin decided to stop at the <code>CancellationTokenSource</code> constructor with a new breakpoint (more on how to set a breakpoint on a .NET Framework method soon) to see where the one shown in the Debugger was created but the breakpoint was never hit. So the <code>CancellationTokenSource</code> #2 must have been created even before our own was created by our code. In fact, a static <code>CancellationTokenSource</code> is created and is set to <code>m_source</code> when <code>InitializeDefaultSource()</code> gets called by one of the Getter. This explains why we saw the same instance #2 in both tasks token.</p>
<p>To sum up, we were now sure that the passed token was not “received” by the <code>Task</code>.</p>
<h2 id="eureka">Eureka!</h2>
<p>Maybe there is a magic trick done by the .NET Framework to lazily set the token source after the creation of the task. However, we did not find such a code in <a href="https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs">the .NET Framework</a> and this is not what we see in our repro.</p>
<p>Back to the basics: are we sure that we are executing the code we think is executed? We looked for mscorlib in the <strong>Debug | Windows | Modules</strong> pane,</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_dL92Y22KRoMBJVlGaGefgQ.png"></p>
<p>and we opened it with a decompiler: the code of the methods called during the <code>Task</code>** **construction was the same as the one shown in <a href="https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs">https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs</a>.</p>
<p>Next, in order to better follow the execution and the passing of parameters (including our token), we decided to set breakpoints on <code>Task</code> <a href="https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,590">private method responsible</a> for its initialization.</p>
<pre tabindex="0"><code>internal void TaskConstructorCore(object action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions, InternalTaskOptions internalOptions, TaskScheduler scheduler)
</code></pre><p>In the <strong>Debug | Windows | Breakpoints</strong> pane, click <strong>New</strong> | <strong>Function Breakpoint…</strong></p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_eODuofmyha3AvYYQ5hL52g.png"></p>
<p>and type the full name of the method. This is working even for a method of a class defined in the .NET Framework assembly for which you do not have the source code:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_WhyV6fYlMtw7qbTGQoOSWQ.png"></p>
<p>We checked that the breakpoints were well set (i.e. no typo in the full name) by looking at the filled red circle:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_csd6pFIfQdTH853g1A8Uew.png"></p>
<p>The <code>cancellationToken</code> parameter should contain the token that we passed at <code>Task</code> creation. Unfortunately, the QuickWatch pane displayed a “cannot read memory” error that we never saw in Visual Studio before!</p>
<p>At that time, we thought we were doomed but we looked at the <strong>Call Stack</strong> pane and we realized that the code was calling <a href="https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,505">the wrong <strong>Task</strong> constructor</a>:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_MC5ZN4NUzKnGIiQTufUPbw.png"></p>
<pre tabindex="0"><code>public Task(Action&lt;object&gt; action, object state, TaskCreationOptions creationOptions)
</code></pre><p>Its signature is compatible with our code:</p>
<pre tabindex="0"><code>_backgroundProcessing = new Task(DoStuffInTheBackground, cancellationToken, TaskCreationOptions.LongRunning);
</code></pre><p>and this is why the compiler did not complain.</p>
<p>Our <code>CancellationToken</code> was passed as the <code>state</code> parameter is given directly to our <code>DoStuffInTheBackground Action</code>: the created <code>Task</code> had no idea that it was supposed to be its <code>CancellationToken</code>.</p>
<p>Note that if we had noticed the <strong>Auto Completion</strong> (Ctrl + Shift + Space) hint, we might have figured out the root cause much sooner…</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_CWW78rrNIhnbsLqCi0zniw.png"></p>
<p>The fix was straightforward; just using <a href="https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,533">the right constructor</a>:</p>
<pre tabindex="0"><code>public Task(Action&lt;object&gt; action, object state, CancellationToken cancellationToken, TaskCreationOptions creationOptions)
</code></pre><p>that accepts both a state for the callback and a <code>CancellationToken</code> for the <code>Task</code> to create:</p>
<pre tabindex="0"><code>_backgroundProcessing = new Task(DoStuffInTheBackground, cancellationToken, cancellationToken, TaskCreationOptions.LongRunning);
</code></pre><p>Under the debugger, we validated that the source was now the expected one:</p>
<p><img loading="lazy" src="/posts/2020-02-21_debugging-wednesday-cancel-thi/1_43MDI_vnkwcGjiLRoxj0qQ.png"></p>
<p>and the test did not hang anymore.</p>
<p>If, from the beginning, we would have been able to step into .NET Framework compiled code as we do with Jetbrains Resharper integration in Visual Studio, we would have found the issue almost immediately. Thankfully, Microsoft has just announced <a href="https://devblogs.microsoft.com/visualstudio/decompilation-of-c-code-made-easy-with-visual-studio?WT.mc_id=DT-MVP-5003325">decompilation of C# code made easy with Visual Studio</a>.</p>
<p>We wish we had it last Wednesday…</p>
<hr>
<p><strong>Interested in reading more about Christophe’s &amp; Kevin’s work? Check out their latest articles:</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="https://medium.com/criteo-labs/switching-back-to-the-ui-thread-in-wpf-uwp-in-modern-c-5dc1cc8efa5e"><strong>Switching back to the UI thread in WPF/UWP, in modern C#</strong>
<em>Leveraging the async machinery to transparently switch to the UI thread when needed</em>medium.com</a><a href="https://medium.com/criteo-labs/switching-back-to-the-ui-thread-in-wpf-uwp-in-modern-c-5dc1cc8efa5e"></a></p>
<hr>
<p><strong>If you are looking for a change and would love to work with these two, head over to our careers page and let us know if there is something that sounds like you!</strong></p>
<p><a href="https://careers.criteo.com/working-in-R&amp;D"><strong>Product, Research &amp; Development | Criteo Careers</strong>
*Come and meet our teams …*careers.criteo.com</a><a href="https://careers.criteo.com/working-in-R&amp;D"></a></p>
]]></content:encoded></item><item><title>Getting another view on thread stacks with ClrMD</title><link>https://chrisnas.github.io/posts/2019-12-31_getting-another-view-on/</link><pubDate>Tue, 31 Dec 2019 10:52:00 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-12-31_getting-another-view-on/</guid><description>This post of the series details how to look into your threads stack with ClrMD to get method calls, parameters and local variables.</description><content:encoded><![CDATA[<hr>
<p>This post of the series details how to look into your threads stack with ClrMD.</p>
<h2 id="introduction">Introduction</h2>
<p>It’s been a long time (see the resources at the end) since I’ve been discussing what ClrMD could bring to .NET developers/DevOps! My colleague <a href="https://twitter.com/KooKiz">Kevin</a> just wrote <a href="https://medium.com/@kevingosse/dumping-stack-objects-with-clrmd-c002dab4651b">an article about how to emulate SOS <strong>DumpStackObjects</strong></a> command both on Windows and Linux with ClrMD. This implementation lists the objects on the stack but without their values (like strings content for example) nor the stack frames corresponding to the method calls.</p>
<p>The rest of the post will show you, with ClrMD, how to get an higher view, closer to what the SOS <strong>ClrStack</strong> command could provide.</p>
<p>Let’s take this simple application as an example:</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">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</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 class="kt">string</span><span class="p">[]</span> <span class="n">args</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">Host</span> <span class="n">h</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Host</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="n">h</span><span class="p">.</span><span class="n">Base</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">class</span> <span class="nc">Host</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">Base</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">int</span> <span class="n">iValue</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">GetHashCode</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="kt">bool</span> <span class="n">bValue</span> <span class="p">=</span> <span class="p">(</span><span class="n">iValue</span> <span class="p">%</span> <span class="m">2</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 class="kt">string</span> <span class="n">parameter</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 class="n">Substring</span><span class="p">(</span><span class="m">0</span><span class="p">,</span><span class="m">1</span><span class="p">)</span> <span class="p">+</span> <span class="s">&#34;_1234567890&#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">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="n">parameter</span> <span class="p">+</span> <span class="s">&#34; = &#34;</span> <span class="p">+</span> <span class="n">First</span><span class="p">(</span><span class="n">iValue</span><span class="p">,</span> <span class="n">bValue</span><span class="p">,</span> <span class="n">parameter</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">int</span> <span class="n">First</span><span class="p">(</span><span class="kt">int</span> <span class="n">iValue</span><span class="p">,</span> <span class="kt">bool</span> <span class="n">bValue</span><span class="p">,</span> <span class="kt">string</span> <span class="n">parameter</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="n">guid</span> <span class="p">=</span> <span class="n">Guid</span><span class="p">.</span><span class="n">NewGuid</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">Second</span><span class="p">(</span><span class="n">bValue</span><span class="p">,</span> <span class="n">guid</span><span class="p">)</span> <span class="p">/</span> <span class="m">2</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">int</span> <span class="n">Second</span><span class="p">(</span><span class="kt">bool</span> <span class="n">bValue</span><span class="p">,</span> <span class="n">Guid</span> <span class="n">guid</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">Third</span><span class="p">(</span><span class="n">guid</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></span><span class="line"><span class="cl">    <span class="kd">private</span> <span class="kt">string</span> <span class="n">Third</span><span class="p">(</span><span class="n">Guid</span> <span class="n">guid</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;call   procdump -ma {Process.GetCurrentProcess().Id}&#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">ReadLine</span><span class="p">();</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="s">$&#34;{guid.GetHashCode()}#{guid.GetHashCode()}#{guid.GetHashCode()}&#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>As you can see, I’ve mixed value and reference types as parameters and local variables up to the call to the <code>Third</code> method that displays the <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/procdump"><strong>procdump</strong></a> command line to execute in order to generate a memory dump of the process.</p>
<h2 id="use-windbg--sosluke">Use WinDBG + SOS Luke!</h2>
<p>When you open it with WinDBG and load SOS, here is the result of the <strong>dso</strong> command:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_LJ5KZ3Vymkcla6eJ3sug6Q.png"></p>
<p>The <strong>clrstack</strong> command shows the stacked method calls:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_0lB0DVD8gCrEPYYQBHIUKQ.png"></p>
<p>And if you use the <strong>-a</strong> parameter, you will get methods with their parameters and local variables (or <strong>-p</strong> for parameters only and <strong>-l</strong> for local variables only):</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_PMv0oK5BqBCBDrI_bF1g5w.png"></p>
<p>It is weird that SOS implementation does not give the type of both the parameters and locals. But wait! While researching for this post, I looked at the SOS implementation (now in the strike.cs file moved from the <strong>coreclr</strong> to the <strong>diagnostics</strong> repository) to <a href="https://github.com/dotnet/diagnostics/blob/master/src/SOS/Strike/strike.cpp#L13165">find this nice comment</a>:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_Rg0WDui494pjE9ari7U0FA.png"></p>
<p>So I tried <strong>clrstack</strong> with <strong>-i</strong> and I got the types for parameters (and locals unlike what the comments implies):</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_nrTH9uu64zU2MLxM8whBjQ.png"></p>
<p>Even though <strong>clrstack</strong> supports the <strong>-all</strong> flag to dump the call stack of all managed threads, you might need to do your own automatic analysis on hundreds of threads and this is where ClrMD shines.</p>
<h2 id="merging-methods-and-parameterslocals">Merging methods and parameters/locals</h2>
<p>When I read Kevin’s post, I immediately thought about adding the method call on the stack based on the <a href="https://github.com/chrisnas/DebuggingExtensions/commit/4061a2c885241edd2bf964db9b7af94fc7dcd778">work I’ve done in March 2019</a> to implement the <a href="https://github.com/chrisnas/DebuggingExtensions/releases/download/1.6.3/pstacks1.3.zip">pstacks tool</a>. At that time, my goal was to aggregate the call stacks of a large number of threads in order to find out pattern of blocked threads, sharing the “same” call stacks. Visual Studio provides a great “Parallel Stacks” pane but I needed it for both Windows and Linux.</p>
<p>To list all the call stack with ClrMD, you <a href="https://github.com/chrisnas/DebuggingExtensions/blob/master/src/ParallelStacks.Runtime/ParallelStack.cs#L11">simply</a> enumerate the managed threads and for each one, its <code>StackTrace</code> property contains the list of <code>StackFrame</code> objects corresponding to each method call.</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1__F2Z31l2DikZkOVEr6SSmQ.png"></p>
<p>The <code>StackPointer</code> property of each frame contains the address of the frame in the call stack, allowing a mapping of the method call with its parameters and locals:</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_HQVchR6XY_1E18Wr7k_8ow.png"></p>
<p>As always with stacks, lower addresses correspond to the last things added to the stack (i.e. last called method). While checking between what is shown by SOS, the parameters/locals addresses and frame stack pointers, you realize that all objects at an address in the stack equal or below the <code>StackPointer</code> of a frame are either parameters or local variables of the frame method.</p>
<p>Even better, for non static method, you can guess what is the <em><strong>this</strong></em>* *implicit parameter if the address is the same as the frame <code>StackPointer</code>; shown with the green <strong>=</strong> sign in the previous screenshot and prefixed by <strong>&gt;</strong> in Kevin’s updated code that merges the method calls to the parameters and locals:</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></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">for</span> <span class="p">(</span><span class="kt">ulong</span> <span class="n">ptr</span> <span class="p">=</span> <span class="n">stackTop</span><span class="p">;</span> <span class="n">ptr</span> <span class="p">&lt;=</span> <span class="n">stackLimit</span><span class="p">;</span> <span class="n">ptr</span> <span class="p">+=</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)</span><span class="n">runtime</span><span class="p">.</span><span class="n">PointerSize</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">// look for the frame corresponding to the current position in the stack</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(</span><span class="n">currentFrame</span><span class="p">.</span><span class="n">StackPointer</span> <span class="p">&lt;=</span> <span class="n">ptr</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="n">FormatFrame</span><span class="p">(</span><span class="n">currentFrame</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">        <span class="n">nFrame</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">nFrame</span> <span class="p">&lt;</span> <span class="n">frames</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 class="n">currentFrame</span> <span class="p">=</span> <span class="n">frames</span><span class="p">[</span><span class="n">nFrame</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">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="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kt">ulong</span> <span class="n">obj</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">runtime</span><span class="p">.</span><span class="n">ReadPointer</span><span class="p">(</span><span class="n">ptr</span><span class="p">,</span> <span class="k">out</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">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></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="p">(!</span><span class="n">IsInHeap</span><span class="p">(</span><span class="n">heap</span><span class="p">,</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">continue</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="kt">var</span> <span class="n">type</span> <span class="p">=</span> <span class="n">heap</span><span class="p">.</span><span class="n">GetObjectType</span><span class="p">(</span><span class="n">obj</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">type</span> <span class="p">==</span> <span class="kc">null</span> <span class="p">||</span> <span class="n">type</span><span class="p">.</span><span class="n">IsFree</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">continue</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 find implicit &#34;this&#34; parameter in case of non-static method</span>
</span></span><span class="line"><span class="cl">    <span class="kt">var</span> <span class="n">myFrame</span> <span class="p">=</span> <span class="p">(</span><span class="n">nFrame</span> <span class="p">==</span> <span class="m">0</span><span class="p">)</span> <span class="p">?</span> <span class="n">currentFrame</span> <span class="p">:</span> <span class="n">frames</span><span class="p">[</span><span class="n">nFrame</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">separator</span> <span class="p">=</span> <span class="p">((</span><span class="n">myFrame</span><span class="p">.</span><span class="n">StackPointer</span> <span class="p">==</span> <span class="n">ptr</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">                 <span class="p">(</span><span class="n">myFrame</span><span class="p">.</span><span class="n">Method</span> <span class="p">!=</span> <span class="kc">null</span><span class="p">)</span> <span class="p">&amp;&amp;</span>
</span></span><span class="line"><span class="cl">                 <span class="p">(!</span><span class="n">myFrame</span><span class="p">.</span><span class="n">Method</span><span class="p">.</span><span class="n">IsStatic</span><span class="p">))</span>
</span></span><span class="line"><span class="cl">        <span class="p">?</span> <span class="s">&#34;&gt;&#34;</span> 
</span></span><span class="line"><span class="cl">        <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">Console</span><span class="p">.</span><span class="n">Write</span><span class="p">(</span><span class="s">$&#34;{ptr:x16}   {separator} &#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="n">DumpObject</span><span class="p">(</span><span class="n">heap</span><span class="p">,</span> <span class="n">type</span><span class="p">,</span> <span class="p">(</span><span class="kt">ulong</span><span class="p">)</span><span class="n">obj</span><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><p>The <code>FormatFrame</code> helper method simply prefix static methods with <strong>#</strong> instead of <strong>|</strong> for instance methods:</p>
<p>Unfortunately, I did not find any way with ClrMD to make the difference between parameters and locals. Based on what you can see in SOS implementation <a href="https://github.com/dotnet/diagnostics/blob/master/src/SOS/Strike/strike.cpp#L12916">of this part</a> of the <strong>clrstack</strong> command, it relies on the <code>EnumerateArguments</code> and <code>EnumetateLocalVariables</code> methods of <code>ICorDebugILFrame</code> which is not exposed by ClrMD. There is another undocumented implementation based on <a href="https://github.com/dotnet/coreclr/blob/master/src/pal/prebuilt/inc/xclrdata.h#L6067">private interfaces</a> I could not leverage neither. For a larger discussion around stack walking in .NET, read <a href="https://mattwarren.org/2019/01/21/Stackwalking-in-the-.NET-Runtime/">this great post</a> by Matt Warren.</p>
<p>Also, without any explicit access to specific parameter or local, I did not find a way to get the value of primitive and value type instances stored on the stack. However, it is still possible to get them for boxed ones and reference type instances such as string for example.</p>
<h2 id="getting-instances-from-thestack">Getting instances from the stack</h2>
<p>In the last code excerpt, I did not describe the <code>DumpObject</code> helper method used to display an object on the stack. The implementation provided by Kevin was used to show the address and the type of the object:</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">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;{objAddress:x16} {type.Name}&#34;</span><span class="p">);</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The next step would be to display value for primitive types such as numbers, boolean, string and even array 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></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">DumpObject</span><span class="p">(</span><span class="n">ClrHeap</span> <span class="n">heap</span><span class="p">,</span> <span class="n">ClrType</span> <span class="n">type</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">objAddress</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 value for simple types</span>
</span></span><span class="line"><span class="cl">    <span class="kt">string</span> <span class="n">valueOrAddress</span> <span class="p">=</span> 
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Char&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{type.GetValue(objAddress),16}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.String&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(type.GetValue(objAddress).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Bool&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((bool)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Byte&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((byte)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.SByte&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((sbyte)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Decimal&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((decimal)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Double&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((double)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Single&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((float)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Int32&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((int)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.SInt32&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((uint)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.Int64&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((long)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">Name</span> <span class="p">==</span> <span class="s">&#34;System.SInt64&#34;</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(((ulong)type.GetValue(objAddress)).ToString())}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="p">(</span><span class="n">type</span><span class="p">.</span><span class="n">IsArray</span><span class="p">)</span> <span class="p">?</span> <span class="s">$&#34;{FormatString(GetArrayAsString(type, objAddress))}&#34;</span> <span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="s">$&#34;{objAddress:x16}&#34;</span><span class="p">;</span>  <span class="c1">// work also for IntPtr</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;{valueOrAddress} {type.Name}&#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>Most of this code is based on the <code>GetValue</code> helper from <code>ClrType</code>: it returns the right “thing” for simple types. Look at ClrMD <a href="https://github.com/microsoft/clrmd/blob/c35e2115241e7dc6f9c835b4c59b9e396ec6471b/src/Microsoft.Diagnostics.Runtime/src/Implementation/ValueReader.cs#L33">implementation details</a> to get a better understanding of how the value is rebuilt.</p>
<p>The <code>GetArrayAsString</code> simply returns the number of elements in the array:</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">private</span> <span class="kd">static</span> <span class="kt">string</span> <span class="n">GetArrayAsString</span><span class="p">(</span><span class="n">ClrType</span> <span class="n">type</span><span class="p">,</span> <span class="kt">ulong</span> <span class="n">objAddress</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">elementCount</span> <span class="p">=</span> <span class="n">type</span><span class="p">.</span><span class="n">GetArrayLength</span><span class="p">(</span><span class="n">objAddress</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="s">$&#34;length = {elementCount}&#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>And the call stack is now complete!</p>
<p><img loading="lazy" src="/posts/2019-12-31_getting-another-view-on/1_-N5vbQ2oPCAath_WSutxKA.png"></p>
<p>Note that you may even get more locals or parameters than with WinDBG+SOS but don’t ask me why…</p>
<p>For more advanced object formatting cases such as dumping structs or enumerating fields and their value, I would highly recommend to look at the related <a href="https://github.com/microsoft/dotnet-samples/blob/master/Microsoft.Diagnostics.Runtime/CLRMD/docs/TypesAndFields.md">ClrMD documentation page</a> (just replace <code>GCHeapType</code> by <code>ClrType</code> and you’ll be safe).</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="https://medium.com/@kevingosse/dumping-stack-objects-with-clrmd-c002dab4651b">Dumping stack objects with ClrMD</a> by <a href="https://twitter.com/KooKiz">Kevin Gosse</a></li>
<li><a href="https://mattwarren.org/2019/01/21/Stackwalking-in-the-.NET-Runtime/">Stack walking in the .NET Runtime</a> by <a href="https://twitter.com/matthewwarren">Matt Warren</a></li>
<li>Part 1: <a href="http://labs.criteo.com/2017/02/going-beyond-sos-clrmd-part-1">Bootstrap ClrMD to load a dump</a>.</li>
<li>Part 2: <a href="http://labs.criteo.com/2017/03/clrmd-part-2-clrruntime-clrheap-traverse-managed-heap/">Find duplicated strings with ClrMD heap traversing</a>.</li>
<li>Part 3: <a href="http://labs.criteo.com/2017/04/clrmd-part-3-dea%E2%80%A6s-to-list-timers/">List timers by following static fields links</a>.</li>
<li>Part 4: <a href="http://labs.criteo.com/2017/05/clrmd-part-4-callbacks-called-timers/">Identify timers callback and other properties</a>.</li>
<li>Part 5: <a href="http://labs.criteo.com/2017/06/clrmd-part-5-how-to-use-clrmd-to-extend-sos-in-windbg/">Use ClrMD to extend SOS in WinDBG</a>.</li>
<li>Part 6: <a href="http://labs.criteo.com/2017/08/clrmd-part-6-manipulate-memory-structures-like-real-objects/">Manipulate memory structures like real objects</a>.</li>
<li>Part 7: <a href="http://labs.criteo.com/2017/08/clrmd-part-7-manipulate-nested-structs-using-dynamic/">Manipulate nested structs using dynamic</a>.</li>
<li>Part 8: <a href="http://labs.criteo.com/2017/11/clrmd-part-8-spelunking-inside-the-net-thread-pool/">Spelunking inside the .NET Thread Pool</a></li>
<li>Part 9: <a href="https://labs.criteo.com/2017/12/clrmd-part-9-deciphering-tasks-thread-pool-items/">Deciphering Tasks and Thread Pool items</a></li>
</ul>
]]></content:encoded></item><item><title>WSL + Visual Studio = attaching/launching a Linux .NET Core application on my Window 10</title><link>https://chrisnas.github.io/posts/2019-11-21_wsl-visual-studio-attaching/</link><pubDate>Thu, 21 Nov 2019 15:57:01 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-11-21_wsl-visual-studio-attaching/</guid><description>This post shows how to attach to a .NET Core process running on Linux with WSL and also how to start a Linux process with Visual Studio…</description><content:encoded><![CDATA[<hr>
<p>This post shows how to attach to a .NET Core process running on Linux with WSL and also how to start a Linux process with Visual Studio debugger</p>
<p>Coming from the Windows world, I don’t find that easy to develop .NET Core applications for Linux. I’m used to code and debug in Visual Studio. Now, I need to build on Windows (due to our Criteo continuous integration), deploy an artifact to Marathon in order to get an application running inside a Mesos container. At Criteo, we had to build a whole set of services to allow remote debugging or memory dump analysis.</p>
<p>But what if I just wanted to test and debug a small scenario on my beloved Windows machine? Windows Subsystem for Linux (aka WSL) is perfect for running a Linux .NET Core application on Windows. However, how to attach to it or even start a debugging session from Visual Studio? In the rest of this post, I’ll explain how to setup your Windows 10 machine to achieve these miracles.</p>
<p><strong>Linux on Windows: welcome WSL</strong></p>
<p>It is obviously not the place to dig into Windows Subsystem for Linux. You just need to know that once installed, it allows you open up a Linux shell on your Windows machine without any virtual machine kind of technology. It is also possible to share folders between Windows (where you want to build your application) and Linux (where you want to execute the built assemblies).</p>
<p>The first step is to turn on WSL feature in your Windows:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_wWxcALCpjyt9rusNe-HEhA.png"></p>
<p>After a reboot, you are able to start a WSL prompt:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_H2J7LJHM_0IhH53Wp39y1A.png"></p>
<p>You can also <a href="https://docs.microsoft.com/en-us/windows/wsl/install-win10?WT.mc_id=DT-MVP-5003325">install your favorite Linux distro</a> if you want.</p>
<p>The next step is to install <a href="https://dotnet.microsoft.com/download/linux-package-manager/ubuntu18-04/runtime-current">.NET Core runtime</a> or SDK so you are able to run your application on Linux.</p>
<p>It is now time to look at your hard drive from a Linux perspective:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_I8QG0SAEL5VBfFxDFjeu6A.png"></p>
<p>Your drives are mapped under /mnt without the “:”. In my case, I created a wsl folder under my d: drive to do my experiments. With Visual Studio, I generated a TestConsole application with 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></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">using</span> <span class="nn">System</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"> 
</span></span><span class="line"><span class="cl"><span class="k">namespace</span> <span class="nn">TestConsole</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</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 class="kt">string</span><span class="p">[]</span> <span class="n">args</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;Enter x to EXIT...&#34;</span><span class="p">);</span>
</span></span><span class="line"><span class="cl">            <span class="k">while</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="kt">var</span> <span class="n">cmd</span> <span class="p">=</span> <span class="n">Console</span><span class="p">.</span><span class="n">ReadLine</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">cmd</span><span class="p">.</span><span class="n">ToLower</span><span class="p">()</span> <span class="p">==</span> <span class="s">&#34;x&#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></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;&gt; {cmd}&#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 class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>I’m publishing the application to get all needed assemblies:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_704gGclGK7DghZt9KWt5aw.png"></p>
<p>In a WSL prompt, type <strong>dotnet</strong> with the name of your application assembly et voila!</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_vevFiW92sk4T-gj1afHX7g.png"></p>
<p>A Linux .NET Core application is running on your Windows machine.</p>
<p><strong>How to attach to a running Linux application</strong></p>
<p>Let’s say that I’m detecting a problem and I want to debug the Linux application with Visual Studio. When attaching the Visual Studio debugger to a process, several connection types are available:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_DvtErIdCBVZK5eiosgl1jQ.png"></p>
<p>The SSH connection type will be used with WSL with the following kind of communications architecture:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_xCaMzwnuuIBS_yLZN0k8pg.png"></p>
<p>The Visual Studio debugger is sending commands to the remote Linux debugger vsdbg via an SSH channel. Here are the steps to follow to install the missing components:</p>
<ul>
<li>By default, an SSH server is installed with WSL. However, I was not able to make the whole pipeline work with it so I had to uninstall and reinstall it:
<code>sudo apt-get remove openssh-server</code>
<code>sudo apt-get install openssh-server</code></li>
<li>The SSH configuration needs also to be changed in order to allow username/password kind of security needed by Visual Studio (if you prefer key-based security, look at the end of the post for available resources). If you don’t know how to use vi efficiently to simply edit a file, install nano (thanks @kookiz for the tips :^)
<code>sudo apt-get install nano</code></li>
<li>In /etc/ssh/sshd_config, change the PasswordAuthentication settings
<code>sudo nano /etc/ssh/sshd_config PasswordAuthentication yes</code></li>
<li>Restart the ssh server
<code>sudo service ssh start</code></li>
<li>You need to install unzip in order to get vsdbg
<code>sudo apt-get install unzip curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -v latest -l ~/vsdbg</code></li>
</ul>
<p>You are now ready to select SSH as connection type and enter your machine name before clicking the Refresh button. At that time, a new dialog should pop up for you to enter your WSL credentials:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_ir_VKzbyLhKrcOPWLYKSIA.png"></p>
<p>After you click the Refresh button, the list at the bottom should contain the Linux processes running in WSL</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_QfZCPLgxNkgL6YgBTB2_Ug.png"></p>
<p>Select your .NET Core application and click Attach to select the Managed debugger:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_SmNkyPOVL98SW63NvIMMRA.png"></p>
<p>Now, if you set a breakpoint in the code and trigger it with an appropriate action in your WSL prompt</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_CrUfw_8-c40dk_R5P7peow.png"></p>
<p>then the debugger will break as expected:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_04MwvOr1WgetWjsCQu9epA.png"></p>
<p>Note that when you stop your debugging session, the Linux application is not stopped; just detached from the debugger and keeps on running.</p>
<p><strong>Showtime for F5!</strong></p>
<p>Attaching to a running Linux application is nice but it would be even better to start a Linux process from Visual Studio debugger. In order to achieve this goal you need to add another piece to the architecture puzzle:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_uMcLQA9pq4wtHLq61dV8Gw.png"></p>
<p>As detailed in <a href="https://github.com/Microsoft/MIEngine/wiki/Offroad-Debugging-of-.NET-Core-on-Linux---OSX-from-Visual-Studio">this WIKI page</a>, it is possible to tell Visual Studio to execute debugging actions thanks to a launch.json file such as the following:</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-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.2.0&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;adapter&#34;</span><span class="p">:</span> <span class="s2">&#34;c:\\tools\\plink.exe&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;adapterArgs&#34;</span><span class="p">:</span> <span class="s2">&#34;-ssh -pw &lt;password&gt; chrisnas@DBGDQPZ1 -batch -T ~/vsdbg/vsdbg --interpreter=vscode&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;configurations&#34;</span><span class="p">:</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="nt">&#34;name&#34;</span><span class="p">:</span> <span class="s2">&#34;.NET Core Launch&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;type&#34;</span><span class="p">:</span> <span class="s2">&#34;coreclr&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;cwd&#34;</span><span class="p">:</span> <span class="s2">&#34;/mnt/d/wsl/TestConsole/TestConsole/bin/Debug/netcoreapp2.1/publish&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;program&#34;</span><span class="p">:</span> <span class="s2">&#34;TestConsole.dll&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&#34;request&#34;</span><span class="p">:</span> <span class="s2">&#34;launch&#34;</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 <a href="https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html">plink tool from putty</a> will be used as an adapter for Visual Studio to communicate with vsdbg running in WSL. The <strong>adapterArgs</strong> property gives the same SSH/machine/user/password information that you provide via Visual Studio UI in the Attach scenario. The <strong>configuration</strong> section defines which request (“launch” instead of attach and which folder/assembly to start) will be sent to vsdbg.</p>
<p>Once this file is created (in my case in d:\wsl\vs folder), you just need to type the following command in the Immediate pane of Visual Studio:</p>
<p><code>DebugAdapterHost.Launch /LaunchJson:d:\wsl\vs\launch.json</code></p>
<p>and if you had set a breakpoint on the first line of your application, the debugger should break there:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_C47ToX7-YaX6BGyRghirBg.png"></p>
<p>In a WSL prompt, you can see the expected 2 new spawned processes:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_fgJIfDh6d7D_ScWh-wJQ-g.png"></p>
<p>But wait!</p>
<p>I have a problem now: I don’t have any prompt in which typing input for my console application… However, as always in Linux, you simply need to write to a file to fix this. The stdin stream of your application is accessible under <code>/proc//fd/0</code>.</p>
<p>So, when I type the following command:</p>
<p><code>echo &quot;Launching a Linux app is not a problem!&quot; &gt; /proc/18341/fd/0</code></p>
<p>my breakpoint is hit in Visual Studio:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_3_cdYBQnO98KTGt1W18P2Q.png"></p>
<p>Also note that everything that is sent to the console appears in Visual Studio Output pane:</p>
<p><img loading="lazy" src="/posts/2019-11-21_wsl-visual-studio-attaching/1_taAUpwNYE-9oVC7_C3Aahw.png"></p>
<p>Note that, unlike the Attach scenario, if you stop the debugging session, the Linux application (and vsdbg) will be terminated.</p>
<p><strong>Resources</strong></p>
<p>During my investigations I’ve found a few resources you might find useful (especially about configuring SSL with keys instead of passing clear user/password)</p>
<ul>
<li><a href="https://devblogs.microsoft.com/devops/debugging-net-core-on-unix-over-ssh/?WT.mc_id=DT-MVP-5003325">Basic VS + WSL</a></li>
<li><a href="https://devblogs.microsoft.com/devops/debugging-net-core-on-unix-over-ssh/?WT.mc_id=DT-MVP-5003325">Good description about how to use VS 2017 to attach to .NET Core app running in WSL</a></li>
<li><a href="https://devblogs.microsoft.com/devops/debugging-net-core-on-unix-over-ssh/?WT.mc_id=DT-MVP-5003325">Debugging .NET Core from VS 2017 and WSL</a></li>
<li><a href="https://devblogs.microsoft.com/devops/debugging-net-core-on-unix-over-ssh/?WT.mc_id=DT-MVP-5003325">VS/C++ with WSL </a>(describe how to install WSL and setup open ssh server)</li>
<li><a href="https://devblogs.microsoft.com/devops/debugging-net-core-on-unix-over-ssh/?WT.mc_id=DT-MVP-5003325">Setup SSH on WSL</a></li>
<li><a href="https://devblogs.microsoft.com/devops/debugging-net-core-on-unix-over-ssh/?WT.mc_id=DT-MVP-5003325">Wiki for VSDBG</a></li>
<li><a href="https://devblogs.microsoft.com/commandline/an-in-depth-tutorial-on-linux-development-on-windows-with-wsl-and-visual-studio-code/?WT.mc_id=DT-MVP-5003325">Great description about how to setup your linux dev environment with VS Code and WSL</a></li>
</ul>
]]></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><item><title>Debugging Friday — Hunting down race condition</title><link>https://chrisnas.github.io/posts/2019-02-22_debugging-friday-hunting-down/</link><pubDate>Fri, 22 Feb 2019 09:25:50 +0000</pubDate><guid>https://chrisnas.github.io/posts/2019-02-22_debugging-friday-hunting-down/</guid><description>Another .NET investigation of our Debugging team to learn how to leverage tracepoint, object naming or parallel stacks in Visual Studio</description><content:encoded><![CDATA[<hr>
<h2 id="introduction">Introduction</h2>
<p>At Criteo, CLR metrics are collected by a service that listens to ETW events (<a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">see the related series</a>). On a few servers, the metrics stopped being collected and we had to fix the problem <a href="/posts/2018-11-13_get-process-name-challenge/">by manually polling new and dead processes</a>. After deploying the new version, the same scenario started to happen: on some servers, the metrics were no more collected.</p>
<p>In an investigation, the first step is always trying to check the environment. In our case, on a server where the metrics collector is up and running, a dedicated ETW session should be created to listen to the CLR events.</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_BHlopgHd4ytq5YqbDWCdGQ.png"></p>
<p>The name given to the session allows us to easily detect if the session is present or not. In the case of a faulted server, the session was not present.</p>
<p>If you look at the code described in <a href="/posts/2018-07-26_grab-etw-session-providers/">a previous post</a>, it is not easy to guess why the session would be stopped by the metrics collector:</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-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">private</span> <span class="k">void</span> <span class="n">ListenToEtw</span><span class="p">(</span><span class="n">TraceEventSession</span> <span class="n">etwSession</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="c1">// this call is blocking... until ewtSession.Stop is called (done in Dispose)</span>
</span></span><span class="line"><span class="cl">        <span class="n">etwSession</span><span class="p">.</span><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="k">finally</span>
</span></span><span class="line"><span class="cl">    <span class="p">{</span>
</span></span><span class="line"><span class="cl">        <span class="n">etwSession</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><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>A <code>TraceEventSession</code> is created and passed to a dedicated thread to process the events until <code>Stop</code> is called at the end of the application.</p>
<p>The second step of an investigation is trying to reproduce the issue in a controlled environment such as… my developer machine. I setup the Exception Settings of the debugger to stop on any managed exception:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_AVxIk96bk6njBTlCAhbwwA.png"></p>
<p>That way, if something bad happens while I’m debugging the application, Visual Studio tells me exactly where the exception was thrown.</p>
<p>After starting and stopping applications monitored by the metrics collector a few times, an exception was thrown in the code responsible for mapping the application id and the component in charge of storing the metrics of this process. When looking at the code, it seems that there was a “timing” conflict between the code in charge of detecting new and dead processes (in a timer) and the code receiving the events from ETW (in the dedicated thread described earlier). A CLR event was received after the corresponding process was detected as being dead. The net effect of the uncaught exception was fast: the TraceEvent session stopped its execution and the <code>Process</code> method returned. Nothing special visible outside of a debugger with the right exception settings. This is a great scenario to understand why swallowing all exceptions is not a good pattern…</p>
<h2 id="still-notworking">Still not working</h2>
<p>The next step is to fix the code to handle the dead process case, build it and test it. Unfortunately, the new metrics collector, from time to time, does not seem to receive any CLR event. Even worse, this time the ETW session is still here as shown by <strong>logman -ets</strong>. Going back inside the Visual Studio debugger, everything is working fine: the TraceEvent session is created, its <code>Process</code> method called and blocked in a dedicated thread and… the events are received! It means that I’m not able to reproduce the problem while running under debugger control. Maybe the problem could come from the code responsible to filter out events sent by unmonitored applications.</p>
<p>So, I’m adding a Breakpoint in the method responsible for checking monitored process to ensure that there is no bug there (events could be received but skipped due to invalid process ID mapping for example):</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="kt">bool</span> <span class="n">IsMonitoredEvent</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="kt">var</span> <span class="n">isMonitored</span> <span class="p">=</span> <span class="n">traceEvent</span><span class="p">.</span><span class="n">ProcessID</span> <span class="p">==</span> <span class="n">_monitoredPid</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">isMonitored</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">NotifyProcessedEvent</span><span class="p">(</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></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">isMonitored</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 breakpoint is hit and I’m able to validate that there is no problem in the mapping code</p>
<p>I can even check that the events I’m expecting are all received by asking Visual Studio to trace the event name with a tracepoint when <code>isMonitored</code> is true:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_EQkz2rHq0AV_LxRdNcdWNw.png"></p>
<p>And I get all expected events in the Output Window. If some events were missing, it could have explained that metrics based on events series (such as contention duration) were not computed.</p>
<p>I’m now running the application outside of the debugger… and no event. Just to confirm that I’m not crazy, I decide to add traces in the source code:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_oTwaR081Da5AVDUM-KEuCQ.png"></p>
<p>But how to get the output without an attached debugger? The trick is to start <a href="https://docs.microsoft.com/en-us/sysinternals/downloads/debugview?WT.mc_id=DT-MVP-5003325">SysInternals Debug View</a> and wait for the event names to appear: nothing. Even by moving the <code>Debug.WriteLine</code> call outside of the <code>if</code> block, no event is ever received, even from unmonitored processes.</p>
<h2 id="navigating-memory-by-stackframe">Navigating memory by stack frame</h2>
<p>Let’s summarize the investigation status:</p>
<ul>
<li>The metrics collector is working only when under the control of a debugger.</li>
<li>If the debugger is attached after it is started, the events are not received.</li>
</ul>
<p>I don’t know why but this kind of weird behaviors is always happening at Criteo on a Friday. So let’s start a joined debugging session with <a href="https://twitter.com/KooKiz">Kevin</a>, <a href="https://twitter.com/jpbempel">Jean-Philippe</a> and <a href="https://twitter.com/GregoryLeocadie">Gregory</a>!</p>
<p>To better understand what is the state of the metrics collector when the events are not received, the application is launched and the debugger is then attached to it. I open the <em>Parallel Stacks</em> panel and double-click the stack frame with a valuable context:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_AvVFp0WGFB-NC2mKMyhlEQ.png"></p>
<p>In our case, it would be interesting to get a view on the state of the <code>ETWTraceEventSource</code> object used by TraceEvent to process the events. Even if you don’t have the source code, it is still possible for the debugger to get a view of the object used as implicit “this” pointer by the <code>ProcessOneFile</code> method. Summon the <em>Quick Watch</em> dialog (Shift-F9 with my old VC 6 keyboard shortcut) and type “this”</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_6NZx1wOEcPrl-Pwrgu5hAw.png"></p>
<p>Based on own understanding of how the <code>ETWTraceEventSource</code> is working, we know that registered event handlers are associated to entries in its <code>templates</code> field.</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_lqQjFitiYmDkPF5YZ3h41w.png"></p>
<p>Instead of the expected GC, thread pool, exception and contention events, only kernel related events are defined:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_5XGoThkcMl2Y9g-tDAZ9kQ.png"></p>
<p>But breakpoints have been set and hit on the code that registers our own event handlers! Well… it was the case when we debugged the application from start. What if… the event source we are looking at now is not the one our code has registered its handlers to?</p>
<p>Without the address of the object available like in C++, it is complicated to easily check if two references actually point to the same object in memory. However, it is still possible to associate a numeric ID to an object with <em>Make Object ID</em>:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_tnBH9jNGHP5l_edMokr0WA.png"></p>
<p>And its ID <strong>{$1}</strong> is now visible after the type name:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_kt4s3zsCQFeNwu683WJNeQ.png"></p>
<p>To compare with the <code>ETWTraceEventSource</code> object manipulated by our own source code, double-click the right frame in <em>Parallel Stack</em>:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_7XXfUT4DRHLUUPZVv2bBjA.png"></p>
<p>In the <code>ListenToEtw</code> method, <code>this</code> refers to our <code>SessionManager</code> in which the <code>ClrTraceEventParser</code> property references the <code>ETWTraceEventSource</code> object we believe we initialized with the right event handlers:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_mrPFddjjiwTl9m-VvYJmyQ.png"></p>
<p>And… this object does not have any ID: it should be <strong>{$1}</strong>.</p>
<p>To confirm that we are not looking at the source object <strong>{$1}</strong> that TraceEvent uses to receive events, it is just a question of checking its <code>template</code> field:</p>
<p><img loading="lazy" src="/posts/2019-02-22_debugging-friday-hunting-down/1_3Du9EQ7dQE8giEiqc19Keg.png"></p>
<p>And here are the expected CLR events we are interested in!</p>
<h2 id="race-condition-again">Race condition… again</h2>
<p>So now the question is: how is it possible to have two instances of a TraceEvent internal class?</p>
<p>Here is the sequence of execution in our code:</p>
<ul>
<li>Thread 1 is creating a <code>TraceEventSession</code> object</li>
<li>Thread 1 starts Thread 2</li>
<li>Thread 1 accesses the <code>clrTraceEventParser</code> via <code>_currentSession?.Source.Clr</code></li>
</ul>
<p>and</p>
<ul>
<li>Thread 2 calls <code>etwSession.Source.Process()</code></li>
</ul>
<p>So the <code>Source</code> property getter could be called from two different threads. Unfortunately, <a href="https://github.com/Microsoft/perfview/blob/b19c4099ccf6b0037b12811f25c924a35af4a447/src/TraceEvent/TraceEventSession.cs#L1267">in the getter</a>, the source is lazily created in a non thread-safe way.</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-csharp" data-lang="csharp"><span class="line"><span class="cl"><span class="kd">public</span> <span class="n">ETWTraceEventSource</span> <span class="n">Source</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</span></span><span class="line"><span class="cl">   <span class="k">get</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">m_source</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="p">...</span> <span class="c1">// long code </span>
</span></span><span class="line"><span class="cl">         <span class="n">m_source</span> <span class="p">=</span> <span class="k">new</span> <span class="n">ETWTraceEventSource</span><span class="p">(</span><span class="n">SessionName</span><span class="p">,</span> <span class="n">TraceEventSourceType</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="k">return</span> <span class="n">m_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></code></pre></td></tr></table>
</div>
</div><p>When both threads enter the getter, <code>m_source</code> could be null and in that case, two <code>ETWTraceEventSource</code> objects are created and returned. One is used by TraceEvent to listen to events and the other by our code to register handlers to events that will never be received.</p>
<p>The fix is simply to force the initialization of the <code>Source</code> object in the first thread.</p>
<p>It is now a good time to go back home… to take a well deserved vacation!</p>
]]></content:encoded></item><item><title>In-process CLR event listeners with .NET Core 2.2</title><link>https://chrisnas.github.io/posts/2018-12-06_in-process-clr-event/</link><pubDate>Thu, 06 Dec 2018 13:52:09 +0000</pubDate><guid>https://chrisnas.github.io/posts/2018-12-06_in-process-clr-event/</guid><description>Write your own EventListener class</description><content:encoded><![CDATA[<hr>
<p><img loading="lazy" src="/posts/2018-12-06_in-process-clr-event/1_zc1BKfAHkpvrZlHPbUvuYA.png"></p>
<p>As the <a href="https://devblogs.microsoft.com/dotnet/announcing-net-core-2-2/?WT.mc_id=DT-MVP-5003325">.NET Core 2.2 blog post</a> introduced, it is now possible for a .NET Core application to listen to the events generated by the CLR that power it up. If you remember the <a href="/posts/2018-07-26_grab-etw-session-providers/">Grab ETW Session, Providers and Events</a> post, the CLR is emitting a lot of valuable events through ETW on Windows and LTTng on Linux. Thanks to <a href="https://www.nuget.org/packages/Microsoft.Diagnostics.Tracing.TraceEvent/">TraceEvent nuget package</a>, it is not that difficult to fetch these events at runtime on Windows, either in-process or out of process. However, it is much more complicated to achieve the same goal on Linux… With .NET Core 2.2, it is now super easy to listen to the events emitted by the CLR while your application is running: you simply need to implement a class that derives from <a href="https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventlistener?WT.mc_id=DT-MVP-5003325?view=netcore-2.2">System.Diagnostics.Tracing.EventListener</a> and create an instance of it. Nothing more.</p>
<p>This class exists since .NET Framework 4.5 and .NET Core 1.0 but it could only be used to listen events pushed by managed code. Since .NET Core 2.2, it can also be used to listen to native events pushed by the CLR. The usage is simple, even a little bit magical.</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></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">sealed</span> <span class="k">class</span> <span class="nc">GcFinalizersEventListener</span> <span class="p">:</span> <span class="n">EventListener</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 https://docs.microsoft.com/en-us/dotnet/framework/performance/garbage-collection-etw-events</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">GC_KEYWORD</span> <span class="p">=</span>                 <span class="m">0x0000001</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">TYPE_KEYWORD</span> <span class="p">=</span>               <span class="m">0x0080000</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">GCHEAPANDTYPENAMES_KEYWORD</span> <span class="p">=</span> <span class="m">0x1000000</span><span class="p">;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventSourceCreated</span><span class="p">(</span><span class="n">EventSource</span> <span class="n">eventSource</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;{eventSource.Guid} | {eventSource.Name}&#34;</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 .NET Garbage Collection events</span>
</span></span><span class="line"><span class="cl">        <span class="k">if</span> <span class="p">(</span><span class="n">eventSource</span><span class="p">.</span><span class="n">Name</span><span class="p">.</span><span class="n">Equals</span><span class="p">(</span><span class="s">&#34;Microsoft-Windows-DotNETRuntime&#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">EnableEvents</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">                <span class="n">eventSource</span><span class="p">,</span> 
</span></span><span class="line"><span class="cl">                <span class="n">EventLevel</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="n">EventKeywords</span><span class="p">)</span> <span class="p">(</span><span class="n">GC_KEYWORD</span> <span class="p">|</span> <span class="n">GCHEAPANDTYPENAMES_KEYWORD</span> <span class="p">|</span> <span class="n">TYPE_KEYWORD</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">// from https://blogs.msdn.microsoft.com/dotnet/2018/12/04/announcing-net-core-2-2/</span>
</span></span><span class="line"><span class="cl">    <span class="c1">// Called whenever an event is written.</span>
</span></span><span class="line"><span class="cl">    <span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventWritten</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">eventData</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></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Program</span>
</span></span><span class="line"><span class="cl"><span class="p">{</span>
</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 class="kt">string</span><span class="p">[]</span> <span class="n">args</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">GcFinalizersEventListener</span> <span class="n">listener</span> <span class="p">=</span> <span class="k">new</span> <span class="n">GcFinalizersEventListener</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;\nPress ENTER to trigger a few finalizers...&#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">ReadLine</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">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="m">4</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">Thread</span> <span class="n">t</span> <span class="p">=</span> <span class="k">new</span> <span class="n">Thread</span><span class="p">(()=&gt;</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">GC</span><span class="p">.</span><span class="n">Collect</span><span class="p">(</span><span class="m">2</span><span class="p">,</span> <span class="n">GCCollectionMode</span><span class="p">.</span><span class="n">Forced</span><span class="p">,</span> <span class="kc">true</span><span class="p">,</span> <span class="kc">true</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;\nPress ENTER to exit...&#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">ReadLine</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>First, you implement a class that derives from <code>EventListener</code> and override the following two methods:</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="k">void</span> <span class="n">OnEventSourceCreated</span><span class="p">(</span><span class="n">EventSource</span> <span class="n">eventSource</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">   <span class="k">void</span> <span class="n">OnEventWritten</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">eventData</span><span class="p">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>As soon as you new up an instance of your class, the <code>OnEventSourceCreated</code>override is called for each <em>event source</em> defined in the application. An event source, as its name implies, produces events. You can define your own in managed code if you wish. For the sake of this post, I will focus on listening to the <em><strong>Microsoft-Windows-DotNETRuntime</strong></em> event source. The <code>EventSource</code>instance passed to the <code>OnEventSourceCreated</code>method provides two interesting properties to let us identify the available sources. 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></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="kd">override</span> <span class="k">void</span> <span class="n">OnEventSourceCreated</span><span class="p">(</span><span class="n">EventSource</span> <span class="n">eventSource</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;{eventSource.Guid} | {eventSource.Name}&#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 the following output :</p>
<pre tabindex="0"><code>5e5bb766-bbfc-5662-0548-1d44fad9bb56 | Microsoft-Windows-DotNETRuntime
 2e5dba47-a3d2-4d16-8ee0-6671ffdcd7b5 | System.Threading.Tasks.TplEventSource
 8e9f5090-2d75-4d03-8a81-e5afbf85daf1 | System.Diagnostics.Eventing.FrameworkEventSource
</code></pre><p>You can imagine that the first one is the source we are interested in listening to its events!</p>
<p>By default, there is no connection between the sources and your listeners: you need to enable the source by calling the <code>EnableEvents</code>method in your <code>OnEventSourceCreated</code>override. This <code>EventListener</code>method takes the following arguments:</p>
<ul>
<li><code>EventSource eventSource</code>: the event source you want to listen to</li>
<li><code>EventLevel level</code>: minimum verbosity level for the received events</li>
<li><code>EventKeywords matchAnyKeyword</code>: a keyword to filter on specific events</li>
</ul>
<p>The Microsoft Docs provides <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/clr-etw-keywords-and-levels?WT.mc_id=DT-MVP-5003325">the level and keywords</a> for <a href="https://docs.microsoft.com/en-us/dotnet/framework/performance/clr-etw-events?WT.mc_id=DT-MVP-5003325">each events</a> documented in the CLR. For the complete list, you take a look at <a href="https://github.com/dotnet/coreclr/blob/master/src/vm/ClrEtwAll.man">ClrETWAll.man in CoreClr source code</a> or in <code>ClrTraceEventParser</code> class of TraceEvent. In the sample code at the beginning of this post, I selected a group of keywords <code>GC_KEYWORD | GCHEAPANDTYPENAMES_KEYWORD | TYPE_KEYWORD</code> to receive only events related to the garbage collector and type information (<a href="/posts/2018-09-28_monitor-finalizers-contention-threads/">read this previous post for more details</a>).</p>
<p>Once the source has been paired to the listener, each time an event is emitted by the source with the right level and for the given keywords, the <code>OnEventWritten</code>override will get called. The <code>EventWrittenEventArgs</code>instance received as a parameter describes each event.</p>
<p><img loading="lazy" src="/posts/2018-12-06_in-process-clr-event/1_JPlKjHnKXY02YiWKrrWLMw.png"></p>
<p>The <code>Payload</code> contains the value of the different properties stored in a <code>ReadOnlyCollection</code> and the corresponding property names are provided via the <code>ReadOnlyCollection</code> <code>PayLoadNames</code>. The following code shows how to extract all properties values:</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="c1">// from https://blogs.msdn.microsoft.com/dotnet/2018/12/04/announcing-net-core-2-2/</span>
</span></span><span class="line"><span class="cl"><span class="c1">// Called whenever an event is written.</span>
</span></span><span class="line"><span class="cl"><span class="kd">protected</span> <span class="kd">override</span> <span class="k">void</span> <span class="n">OnEventWritten</span><span class="p">(</span><span class="n">EventWrittenEventArgs</span> <span class="n">eventData</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">// Write the contents of the event to the console.</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;ThreadID = {eventData.OSThreadId} ID = {eventData.EventId} Name = {eventData.EventName}&#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">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">eventData</span><span class="p">.</span><span class="n">Payload</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">string</span> <span class="n">payloadString</span> <span class="p">=</span> <span class="n">eventData</span><span class="p">.</span><span class="n">Payload</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="p">!=</span> <span class="kc">null</span> <span class="p">?</span> <span class="n">eventData</span><span class="p">.</span><span class="n">Payload</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">ToString</span><span class="p">()</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="n">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">$&#34;    Name = \&#34;</span><span class="p">{</span><span class="n">eventData</span><span class="p">.</span><span class="n">PayloadNames</span><span class="p">[</span><span class="n">i</span><span class="p">]}</span><span class="err">\</span><span class="s">&#34; Value = \&#34;{payloadString}\&#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">Console</span><span class="p">.</span><span class="n">WriteLine</span><span class="p">(</span><span class="s">&#34;\n&#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>Here is the kind of output you get for common garbage collector and finalizer events:</p>
<pre tabindex="0"><code>ThreadID = 17456 ID = 200 Name = IncreaseMemoryPressure
Name = &#34;BytesAllocated&#34; Value = &#34;1672&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 9 Name = GCSuspendEEBegin_V1
Name = &#34;Reason&#34; Value = &#34;1&#34;
Name = &#34;Count&#34; Value = &#34;0&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 8 Name = GCSuspendEEEnd_V1
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 35 Name = GCTriggered
Name = &#34;Reason&#34; Value = &#34;10&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 17456 ID = 1 Name = GCStart_V2
Name = &#34;Count&#34; Value = &#34;1&#34;
Name = &#34;Depth&#34; Value = &#34;2&#34;
Name = &#34;Reason&#34; Value = &#34;10&#34;
Name = &#34;Type&#34; Value = &#34;0&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
Name = &#34;ClientSequenceNumber&#34; Value = &#34;0&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 18860 ID = 29 Name = FinalizeObject
Name = &#34;TypeID&#34; Value = &#34;1210056592&#34;
Name = &#34;ObjectID&#34; Value = &#34;1371069040&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><pre tabindex="0"><code>ThreadID = 18860 ID = 15 Name = BulkType
Name = &#34;Count&#34; Value = &#34;1&#34;
Name = &#34;ClrInstanceID&#34; Value = &#34;8&#34;
</code></pre><p>The fact that there is no strongly typed event argument per event is not as good as what TraceEvent provides. In addition, after a few tests, it seems that the .NET Core 2.2 implementation <a href="https://github.com/dotnet/coreclr/issues/21380">is not complete</a>:</p>
<ul>
<li>GC events are not all received when in Server Mode</li>
<li>Properties are missing for BulkType event necessary to figure out finalizer type names</li>
</ul>
<p>However, with <code>EventListener</code>, Microsoft is giving us a very simple way to get valuable information, in-process, from the CLR while the application is running. A forthcoming blog post will show how to leverage this infrastructure to provide insights on how the garbage collection impacts an application.</p>
<p>Before leaving you building your own event listeners, you should know a couple of last details. Under the hood, the framework is <a href="https://github.com/dotnet/coreclr/blob/78570a239101f69200cfceab5e7527ca8cc312b8/src/System.Private.CoreLib/src/System/Diagnostics/Eventing/EventPipeEventDispatcher.cs#L141">creating a dedicated thread</a> for you that will execute the two <code>OnXXX</code>methods of your <code>EventListener</code>-derived class. It means that your code should not block or spend to much time processing the events if you want to keep on receiving events at a regular pace.</p>
<p>This thread will last as long as one of your listeners still exists. When I say “exist”, I mean until you decide to dispose them. This is the way for you to tell the sources that you are no more interested in receiving events. When all your listeners are disposed, then the processing thread will exit.</p>
]]></content:encoded></item></channel></rss>