Christos Margiolis: projects Christos Margiolis http://margiolis.net/tags/projects/ Inline function tracing with the kinst DTrace provider http://margiolis.net/w/kinst_inline/ Tue, 18 Jul 2023 00:00:00 +1200 <h2 id="table-of-contents">Table of Contents</h2> <ol> <li><a href="#quick-background">Quick background</a></li> <li><a href="#usage">Usage</a></li> <li><a href="#inline-function-tracing">Inline function tracing</a> <ul> <li><a href="#how-it-works">How it works</a></li> <li><a href="#syntactic-transformations">Syntactic transformations</a></li> <li><a href="#heuristic-entry-return">Heuristic for calculating the <code>entry</code> and <code>return</code> offsets</a></li> </ul> </li> </ol> <h2 id="quick-background">Quick background</h2> <p><a href="https://illumos.org/books/dtrace/preface.html">DTrace</a> is a framework that gives administrators and kernel developers the ability to observe kernel behavior in real time. DTrace has modules called &ldquo;providers&rdquo;, that perform a particular instrumentation in the kernel (and sometimes userland) using &ldquo;probes&rdquo;.</p> <p><a href="http://margiolis.net/files/kinst.pdf">kinst</a> is a new low-level DTrace provider co-authored by Christos Margiolis and Mark Johnston for the FreeBSD operating system, which allows the user to trace arbitrary instructions in kernel functions. It is part of the base system as of FreeBSD 14.0.</p> <p>kinst probes take the form of <code>kinst::&lt;function&gt;:&lt;instruction&gt;</code>, where <code>&lt;function&gt;</code> is the kernel function to be traced, and <code>&lt;instruction&gt;</code> is the offset to the instruction, relative to the beginning of the function, and can be obtained from the function&rsquo;s disassembly. If the <code>&lt;instruction&gt;</code> field is left empty, kinst will trace all instructions in that function. Unlike <a href="https://illumos.org/books/dtrace/chp-fbt.html">FBT</a>, kinst can also trace the entry and return points of inline functions (see <a href="#inline-function-tracing">Inline function tracing</a>).</p> <p>The origin of the name is inspired from an <a href="https://www.usenix.org/legacy/publications/library/proceedings/osdi99/full_papers/tamches/tamches.pdf">early paper written by A. Tamches and B. Miller</a> discussing a tracing tool they developed called &ldquo;KernInst&rdquo;.</p> <h2 id="usage">Usage</h2> <p>Find the offset corresponding to the third instruction in <code>vm_fault()</code> and trace it, printing the contents of the RSI register:</p> <pre tabindex="0"><code># kgdb (kgdb) disas /r vm_fault Dump of assembler code for function vm_fault: 0xffffffff80f4e470 &lt;+0&gt;: 55 push %rbp 0xffffffff80f4e471 &lt;+1&gt;: 48 89 e5 mov %rsp,%rbp 0xffffffff80f4e474 &lt;+4&gt;: 41 57 push %r15 ... # dtrace -n &#39;kinst::vm_fault:4 {printf(&#34;%#x&#34;, regs[R_RSI]);}&#39; 2 81500 vm_fault:4 0x827c56000 2 81500 vm_fault:4 0x827878000 2 81500 vm_fault:4 0x1fab9bef0000 2 81500 vm_fault:4 0xe16cf749000 0 81500 vm_fault:4 0x13587c366000 ^C </code></pre><p>Trace the return point of <code>critical_enter()</code>, which is an inline function:</p> <pre tabindex="0"><code># dtrace -n &#39;kinst::critical_enter:return&#39; dtrace: description &#39;kinst::critical_enter:return&#39; matched 130 probes CPU ID FUNCTION:NAME 1 71024 spinlock_enter:53 0 71024 spinlock_enter:53 1 70992 uma_zalloc_arg:49 1 70925 malloc_type_zone_allocated:21 1 70994 uma_zfree_arg:365 1 70924 malloc_type_freed:21 1 71024 spinlock_enter:53 0 71024 spinlock_enter:53 0 70947 _epoch_enter_preempt:122 0 70949 _epoch_exit_preempt:28 ^C </code></pre><h2 id="inline-function-tracing">Inline function tracing</h2> <h3 id="how-it-works">How it works</h3> <p>To trace inline functions, libdtrace makes use of the <a href="http://margiolis.net/w/dwarf_inline">DWARF Debugging Standard</a>, to detect if the function specified is an inline call. If it is, D syntax is transformed to create kinst probes for each of the inline copies found. All work is done in libdtrace, instead of kinst(4). This feature has been added to FreeBSD with <a href="https://reviews.freebsd.org/D38825">this patch</a>.</p> <p>Contrary to how kinst expects a <code>&lt;function&gt;:&lt;instruction&gt;</code> tuple to create probes, for inline functions, <code>&lt;instruction&gt;</code> is replaced by <code>entry</code> and <code>return</code>.</p> <h3 id="syntactic-transformations">Syntactic transformations</h3> <p>Suppose the user wants to trace a probe of the form:</p> <pre tabindex="0"><code>kinst::&lt;func&gt;:&lt;entry|return&gt; /&lt;pred&gt;/ { &lt;acts&gt; } </code></pre><p>libdtrace sees that we have specified <code>entry</code> or <code>return</code>, instead of an offset, which is what a regular kinst probe would look like, so it loops through all loaded kernel modules and parses their DWARF and ELF info to see if this function is an inline &mdash; if <em>not</em>, the probe is converted to an FBT one, so that we don&rsquo;t duplicate FBT&rsquo;s functionality in kinst:</p> <pre tabindex="0"><code># dtrace -dn &#39;kinst::malloc:entry {exit(0);}&#39; fbt::malloc:entry { exit(0x0); } dtrace: description &#39;kinst::malloc:entry &#39; matched 1 probe CPU ID FUNCTION:NAME 2 31144 malloc:entry </code></pre><p>If the function however <em>is</em> an inline, libdtrace will find all calls refering to this function and create new probes for each one of the inline copies found.</p> <pre tabindex="0"><code># dtrace -dn &#39;kinst::cam_iosched_has_more_trim:entry { printf(&#34;\t%d\t%s&#34;, pid, execname); }&#39; kinst::cam_iosched_get_trim:13, kinst::cam_iosched_next_bio:13, kinst::cam_iosched_schedule:40 { printf(&#34;\t%d\t%s&#34;, pid, execname); } dtrace: description &#39;kinst::cam_iosched_has_more_trim:entry &#39; matched 4 probes CPU ID FUNCTION:NAME 0 81502 cam_iosched_schedule:40 2 clock 0 81501 cam_iosched_next_bio:13 2 clock 2 81502 cam_iosched_schedule:40 2 clock 1 81502 cam_iosched_next_bio:13 0 kernel 1 81503 cam_iosched_schedule:40 0 kernel ^C </code></pre><p>There can also be both inline and non-inline definitions of the same function. In this case, kinst creates an additional FBT probe for the non-inline definition.</p> <p>The <code>-d</code> flag used in these examples to dump the D script after libdtrace has applied syntactic transformations, has been added to DTrace in <a href="https://cgit.freebsd.org/src/commit/?id=1e136a9cbd3a9d137037e47a53c1dba3be7f6925">commit 1e136a9cbd3a</a>.</p> <h3 id="heuristic-entry-return">Heuristic for calculating the <code>entry</code> and <code>return</code> offsets</h3> <p>libdtrace reuses parts of the mechanism implemented in my <a href="https://git.sr.ht/~crm/inlinecall">inlinecall(1)</a> program, which finds and prints all call sites of a given inline function:</p> <pre tabindex="0"><code>$ ./inlinecall vm_page_mvqueue /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/vm/vm_page.c:4142 [0xffffffff80f91541 - 0xffffffff80f91599] /usr/src/sys/vm/vm_page.c:4195 vm_page_readahead_finish() [0xffffffff80f915f5 - 0xffffffff80f91603] /usr/src/sys/vm/vm_page.c:4195 vm_page_readahead_finish() [0xffffffff80f9163d - 0xffffffff80f916c2] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() [0xffffffff80f916cd - 0xffffffff80f916e5] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() [0xffffffff80f916fe - 0xffffffff80f91747] /usr/src/sys/vm/vm_page.c:4195 vm_page_deactivate() [0xffffffff80f91750 - 0xffffffff80f91768] /usr/src/sys/vm/vm_page.c:4195 vm_page_deactivate() [0xffffffff80f94a59 - 0xffffffff80f94aa9] /usr/src/sys/vm/vm_page.c:4195 vm_page_reclaim_contig_domain() [0xffffffff80f94de4 - 0xffffffff80f94df9] /usr/src/sys/vm/vm_page.c:4195 vm_page_reclaim_contig_domain() [0xffffffff80f9661e - 0xffffffff80f96667] /usr/src/sys/vm/vm_page.c:4202 vm_page_deactivate_noreuse() [0xffffffff80f96670 - 0xffffffff80f96688] /usr/src/sys/vm/vm_page.c:4202 vm_page_deactivate_noreuse() [0xffffffff80f9669e - 0xffffffff80f966ea] /usr/src/sys/vm/vm_page.c:4212 vm_page_launder() [0xffffffff80f966f3 - 0xffffffff80f9670b] /usr/src/sys/vm/vm_page.c:4212 vm_page_launder() [0xffffffff80f96d4c - 0xffffffff80f96dac] /usr/src/sys/vm/vm_page.c:4212 vm_page_advise() [0xffffffff80f96dac - 0xffffffff80f96e07] /usr/src/sys/vm/vm_page.c:4202 vm_page_advise() </code></pre><p>Most of the entries above appear twice but with different boundaries:</p> <pre tabindex="0"><code> [0xffffffff80f9163d - 0xffffffff80f916c2] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() [0xffffffff80f916cd - 0xffffffff80f916e5] /usr/src/sys/vm/vm_page.c:4184 vm_page_activate() </code></pre><p>This means that the inline copy&rsquo;s boundaries are split into more than one parts, which can be caused by having early <code>return</code>s inside the inline function. By using this assumption, we can deduce that the entry point of the function is <code>0xffffffff80f9163d</code>, and each of the upper boundaries are the return points (i.e <code>0xffffffff80f916c2</code> and <code>0xffffffff80f916e5</code>), so we end up with one <code>entry</code> and two <code>return</code> trace points.</p> <p>However, this is <strong>not exactly true</strong>, because the final return address given by DWARF corresponds to the instruction <em>after</em> the actual return instruction. We can verify this in GDB using the two return addresses from the example above:</p> <pre tabindex="0"><code># kgdb (kgdb) disas vm_page_activate ... 0xffffffff80f916c0 &lt;+144&gt;: jmp 0xffffffff80f91673 &lt;vm_page_activate+67&gt; 0xffffffff80f916c2 &lt;+146&gt;: add $0x8,%rsp &lt;-- first address given by DWARF ... 0xffffffff80f916e0 &lt;+176&gt;: call 0xffffffff80bed360 &lt;panic&gt; &lt;-- last instruction &lt;-- second address should be here </code></pre><p>It turns out that <code>0xffffffff80f916e5</code> is, in fact, outside <code>vm_page_activate()</code> altogether!</p> <pre tabindex="0"><code>(kgdb) x/i 0xffffffff80f916e5 0xffffffff80f916e5: data16 cs nopw 0x0(%rax,%rax,1) </code></pre><p>After running a couple of tests, I came to the conclusion that there are two possible cases with return addresses:</p> <ul> <li>If <a href="http://margiolis.net/w/dwarf_inline/#lowpc-highpc">the inline copy&rsquo;s DIE has <code>DW_AT_lowpc</code> and <code>DW_AT_highpc</code> set</a>, the return address is <em>always</em> outside the inline function&rsquo;s boundaries.</li> <li>If <a href="http://margiolis.net/w/dwarf_inline/#ranges">the inline copy&rsquo;s DIE has <code>DW_AT_ranges</code> set</a>, only the last return address is outside the inline function&rsquo;s boundaries.</li> <li>Combining the two bullets above, if the return address of the inline function is the same as the upper boundary of the caller function, it&rsquo;s outside <em>both</em> the inline copy&rsquo;s and the caller function&rsquo;s boundaries.</li> </ul> <p>In order to fix this, we have to go one instruction back whenever we come across one of those 3 cases.</p> <p>Finally the <code>entry</code> offset is calculated as:</p> <pre tabindex="0"><code>inline_bound_lo - caller_bound_lo </code></pre><p>And the <code>return</code> one, including the modifications (if any) discussed above, as:</p> <pre tabindex="0"><code>inline_bound_hi - caller_bound_lo </code></pre><p>These offsets are then used to create regular kinst probes of the form <code>kinst::&lt;func&gt;:&lt;instruction&gt;</code>, which is what kinst actually expects:</p> <pre tabindex="0"><code># dtrace -dn &#39;kinst::vm_page_mvqueue:entry,kinst::vm_page_mvqueue:return&#39; dtrace: description &#39;kinst::vm_page_mvqueue:entry,kinst::vm_page_mvqueue:return&#39; matched 22 probes kinst::vm_page_readahead_finish:33, kinst::vm_page_readahead_finish:121, kinst::vm_page_activate:13, kinst::vm_page_deactivate:14, kinst::vm_page_reclaim_contig_domain:1961, kinst::vm_page_deactivate_noreuse:14, kinst::vm_page_launder:14, kinst::vm_page_advise:236, kinst::vm_page_advise:332, kinst::vm_page_readahead_finish:220, kinst::vm_page_activate:146, kinst::vm_page_activate:176, kinst::vm_page_deactivate:87, kinst::vm_page_deactivate:115, kinst::vm_page_reclaim_contig_domain:2041, kinst::vm_page_reclaim_contig_domain:2884, kinst::vm_page_deactivate_noreuse:87, kinst::vm_page_deactivate_noreuse:115, kinst::vm_page_launder:90, kinst::vm_page_launder:118, kinst::vm_page_advise:330, kinst::vm_page_advise:421 { } CPU ID FUNCTION:NAME 3 95381 vm_page_activate:13 3 95389 vm_page_activate:146 2 95381 vm_page_activate:13 2 95389 vm_page_activate:146 1 95387 vm_page_advise:332 1 95400 vm_page_advise:421 1 95387 vm_page_advise:332 1 95400 vm_page_advise:421 1 95387 vm_page_advise:332 ^C </code></pre> Using DWARF to find call sites of inline functions http://margiolis.net/w/dwarf_inline/ Tue, 07 Feb 2023 00:00:00 +1200 <h2 id="table-of-contents">Table of contents</h2> <ol> <li><a href="#what-is-dwarf">What is DWARF?</a></li> <li><a href="#inline-functions-in-dwarf">Inline functions in DWARF</a></li> <li><a href="#calculating-call-boundaries">Calculating call boundaries</a> <ul> <li><a href="#lowpc-highpc">The DIE has <code>DW_AT_low_pc</code> and <code>DW_AT_high_pc</code></a></li> <li><a href="#ranges">The DIE has <code>DW_AT_ranges</code></a></li> </ul> </li> <li><a href="#finding-the-caller-function">Finding the caller function</a></li> <li><a href="#inlinecall">inlinecall(1)</a> <ul> <li><a href="#nested-inline-functions">Nested inline functions</a></li> </ul> </li> </ol> <h2 id="what-is-dwarf">What is DWARF?</h2> <p>From the <a href="https://dwarfstd.org/">DWARF Debugging Standard&rsquo;s documentation</a>:</p> <blockquote> <p>This document defines a format for describing programs to facilitate user source level debugging. This description can be generated by compilers, assemblers and linkage editors. It can be used by debuggers and other tools.</p> </blockquote> <p>Debugging information entries (DIEs) are represented as a tree, one per compilation unit (CU). Each DIE has a tag (<code>DW_TAG_*</code>, see DWARF PDF Figure 1) denoting its class, and attributes (<code>DW_AT_*</code>, see DWARF PDF Figure 2) denoting its various characteristics, associated with it.</p> <p>The next entry of a DIE is a child DIE. If a DIE doesn&rsquo;t have children, the next entry is a &ldquo;sibling&rdquo;.</p> <p>Consider the following structure:</p> <pre tabindex="0"><code>CU1 (DW_TAG_compile_unit) func1 (DW_TAG_subprogram) DW_AT_foo DW_AT_bar func2 (DW_TAG_subprogram) DW_AT_foo DW_AT_bar myvar (DW_TAG_variable) DW_AT_foo DW_AT_bar CU2 (DW_TAG_compile_unit) ... </code></pre><p><code>CU1</code> has <code>func1</code> and <code>myvar</code> as chidren and <code>CU2</code> as siblings. <code>func1</code> has <code>func2</code> as a child.</p> <p>The debug file can be generated by compiling with the <code>-g</code> option. To dump DWARF info you can use <code>readelf -wi &lt;file&gt;</code> and <code>dwarfdump &lt;file&gt;</code>.</p> <h2 id="inline-functions-in-dwarf">Inline functions in DWARF</h2> <p>DIEs of inline function declarations have the <code>DW_TAG_subprogram</code> tag and the <code>DW_AT_inline</code> attribute. DIEs of inline copies of this function will have the <code>DW_TAG_inlined_subroutine</code> tag.</p> <p>Attributes inline copies can have include:</p> <ul> <li><code>DW_AT_abstract_origin</code>: DIE offset to the inline declaration.</li> <li><code>DW_AT_call_file</code>: Integer denoting the file the function is called in.</li> <li><code>DW_AT_call_line</code>: File line.</li> <li><code>DW_AT_call_column</code>: File column.</li> <li><code>DW_AT_low_pc</code> and <code>DW_AT_high_pc</code>: Lower and upper call boundaries. <a href="#3.1">Explained in the next section</a>.</li> <li><code>DW_AT_ranges</code>: <a href="#3.2">Explained in the next section</a>.</li> </ul> <p>For example, if we dump the DWARF info for my FreeBSD kernel:</p> <pre tabindex="0"><code>$ readelf -wi /usr/lib/debug/boot/kernel/kernel.debug &gt; ~/foo </code></pre><p>We find that <code>vfs_freevnodes_dec</code> gets inlined:</p> <pre tabindex="0"><code> &lt;1&gt;&lt;1dfa144&gt;: Abbrev Number: 94 (DW_TAG_subprogram) &lt;1dfa145&gt; DW_AT_name : (indirect string) vfs_freevnodes_dec &lt;1dfa149&gt; DW_AT_decl_file : 1 &lt;1dfa14a&gt; DW_AT_decl_line : 1447 &lt;1dfa14c&gt; DW_AT_prototyped : 1 &lt;1dfa14c&gt; DW_AT_inline : 1 </code></pre><p>Inline copies will have <code>DW_AT_abstract_origin</code> point to the declaration&rsquo;s DIEs offset, in this case <code>0x1dfa144</code>. If we look for <code>0x1dfa144</code>, we do indeed find a few inline copies.</p> <pre tabindex="0"><code> &lt;3&gt;&lt;1dfe45e&gt;: Abbrev Number: 24 (DW_TAG_inlined_subroutine) &lt;1dfe45f&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfe463&gt; DW_AT_low_pc : 0xffffffff80cf701d &lt;1dfe46b&gt; DW_AT_high_pc : 0x38 &lt;1dfe46f&gt; DW_AT_call_file : 1 &lt;1dfe470&gt; DW_AT_call_line : 3458 &lt;1dfe472&gt; DW_AT_call_column : 5 &lt;3&gt;&lt;1dfd2e2&gt;: Abbrev Number: 58 (DW_TAG_inlined_subroutine) &lt;1dfd2e3&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfd2e7&gt; DW_AT_ranges : 0x1f1290 &lt;1dfd2eb&gt; DW_AT_call_file : 1 &lt;1dfd2ec&gt; DW_AT_call_line : 3405 &lt;1dfd2ee&gt; DW_AT_call_column : 3 ...there are more </code></pre><p>As I described in the <a href="#1">first section</a>, a debug file may consist of multiple CUs that define the same inline function. We want treat each CU independently, that is, each inline copy is handled relative to its CU.</p> <h2 id="calculating-call-boundaries">Calculating call boundaries</h2> <p>There are 2 cases we have to take care of when calculating the actual call boundaries of an inline copy.</p> <h3 id="lowpc-highpc">The DIE has <code>DW_AT_low_pc</code> and <code>DW_AT_high_pc</code></h3> <pre tabindex="0"><code> &lt;3&gt;&lt;1dfe45e&gt;: Abbrev Number: 24 (DW_TAG_inlined_subroutine) &lt;1dfe45f&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfe463&gt; DW_AT_low_pc : 0xffffffff80cf701d &lt;1dfe46b&gt; DW_AT_high_pc : 0x38 &lt;1dfe46f&gt; DW_AT_call_file : 1 &lt;1dfe470&gt; DW_AT_call_line : 3458 &lt;1dfe472&gt; DW_AT_call_column : 5 </code></pre><p>In this case, the lower boundary is <code>low_pc</code> and the upper boundary is <code>low_pc + high_pc</code>, which, for the DIE shown in this example, the boundaries are:</p> <pre tabindex="0"><code>low = 0xffffffff80cf701d high = 0xffffffff80cf701d + 0x38 = 0xffffffff80cf7055 </code></pre><h3 id="ranges">The DIE has <code>DW_AT_ranges</code></h3> <pre tabindex="0"><code> &lt;3&gt;&lt;1dfd2e2&gt;: Abbrev Number: 58 (DW_TAG_inlined_subroutine) &lt;1dfd2e3&gt; DW_AT_abstract_origin: &lt;0x1dfa144&gt; &lt;1dfd2e7&gt; DW_AT_ranges : 0x1f1290 &lt;1dfd2eb&gt; DW_AT_call_file : 1 &lt;1dfd2ec&gt; DW_AT_call_line : 3405 &lt;1dfd2ee&gt; DW_AT_call_column : 3 </code></pre><p>This is a bit more involved. <code>DW_AT_ranges</code> refers to the <code>.debug_ranges</code> section found in debug files. We can dump the ranges:</p> <pre tabindex="0"><code>$ dwarfdump -N /usr/lib/debug/boot/kernel/kernel.debug .debug_ranges Ranges group 0: ranges: 3 at .debug_ranges offset 0 (0x00000000) (48 bytes) [ 0] range entry 0x00000019 0x00000073 [ 1] range entry 0x0000007e 0x00000106 [ 2] range end 0x00000000 0x00000000 Ranges group 1: ranges: 3 at .debug_ranges offset 48 (0x00000030) (48 bytes) [ 0] range entry 0x00000022 0x0000006a [ 1] range entry 0x0000007e 0x00000106 [ 2] range end 0x00000000 0x00000000 ... </code></pre><p>If we search for <code>0x1f1290</code> (the inline copy&rsquo;s ranges), we find its range group:</p> <pre tabindex="0"><code> Ranges group 38809: ranges: 3 at .debug_ranges offset 2036368 (0x001f1290) (48 bytes) [ 0] range entry 0x000025c8 0x000025f9 [ 1] range entry 0x0000261a 0x00002621 [ 2] range end 0x00000000 0x00000000 </code></pre><p>To get the call boundaries, we add each <code>range entry</code>&rsquo;s boundaries to the <code>DW_AT_low_pc</code> of the root DIE of the CU. The root DIE is found programmatically, but I happen to know that in this case, the root DIE is:</p> <pre tabindex="0"><code> &lt;0&gt;&lt;1dee9fb&gt;: Abbrev Number: 1 (DW_TAG_compile_unit) &lt;1dee9fc&gt; DW_AT_producer : (indirect string) FreeBSD clang version 13.0.0 (git@github.com:llvm/llvm-project.git llvmorg-13.0.0-0-gd7b669b3a303) &lt;1deea00&gt; DW_AT_language : 12 (C99) &lt;1deea02&gt; DW_AT_name : (indirect string) /usr/src/sys/kern/vfs_subr.c &lt;1deea06&gt; DW_AT_stmt_list : 0x6cb448 &lt;1deea0a&gt; DW_AT_comp_dir : (indirect string) /usr/obj/usr/src/amd64.amd64/sys/GENERIC &lt;1deea0e&gt; DW_AT_low_pc : 0xffffffff80cf4020 &lt;1deea16&gt; DW_AT_high_pc : 0xde3d </code></pre><p>Finally, we end up with the following boundaries:</p> <pre tabindex="0"><code>low = 0xffffffff80cf4020 + 0x000025c8 = 0xffffffff80cf65e8 high = 0xffffffff80cf4020 + 0x000025f9 = 0xffffffff80cf6619 low = 0xffffffff80cf4020 + 0x0000261a = 0xffffffff80cf663a high = 0xffffffff80cf4020 + 0x00002621 = 0xffffffff80cf6641 </code></pre><h2 id="finding-the-caller-function">Finding the caller function</h2> <p>There are cases where we want to know which function an inline function is being called from. Because DWARF does not encode that information, we&rsquo;ll have to scan ELF symbol tables.</p> <pre tabindex="0"><code>$ readelf -s /usr/lib/debug/boot/kernel/kernel.debug </code></pre><p>Since we know the inline copy&rsquo;s boundaries, we only have to find which symbol&rsquo;s boundaries the inline copy is inside. In other words, the following condition has to be met:</p> <pre tabindex="0"><code>sym_lower_bound &lt;= inline_lower_bound &lt;= inline_upper_bound &lt;= sym_upper_bound </code></pre><p>Because searching through ELF symbol tables manually and doing calculations by hand would take too long, the best way to do this is programmatically through <a href="https://sourceforge.net/p/elftoolchain/wiki/libelf/">LibELF</a>.</p> <h2 id="inlinecall">inlinecall(1)</h2> <p><a href="https://git.sr.ht/~crm/inlinecall">I wrote a little program</a> that does everything I talked about in this post automatically. It works on FreeBSD as-is, and most likely needs some modification to get it to work on other platforms.</p> <p>The program takes an inline function name and a debug file as arguments:</p> <pre tabindex="0"><code>inlinecall &lt;function&gt; &lt;file&gt; </code></pre><p>And outputs the results in the following form:</p> <pre tabindex="0"><code>cu1_func_declaration_file:line [low_bound - high_bound] inline_copy1_file:line caller_func() [low_bound - high_bound] inline_copy2_file:line caller_func() ... cu2_func_declaration_file:line ... ... </code></pre><p>For example:</p> <pre tabindex="0"><code>$ inlinecall critical_enter /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/sys/systm.h:175 [0xffffffff809eb51f - 0xffffffff809eb526] /usr/src/sys/kern/kern_intr.c:1387 intr_event_handle() /usr/src/sys/sys/systm.h:175 [0xffffffff80a051f4 - 0xffffffff80a05208] /usr/src/sys/kern/kern_malloc.c:431 malloc_type_freed() [0xffffffff80a0514c - 0xffffffff80a0515b] /usr/src/sys/kern/kern_malloc.c:388 malloc_type_zone_allocated() /usr/src/sys/sys/systm.h:175 [0xffffffff80a263c4 - 0xffffffff80a263d3] /usr/src/sys/kern/kern_resource.c:509 rtp_to_pri() /usr/src/sys/sys/systm.h:175 [0xffffffff80a28f59 - 0xffffffff80a28f5f] /usr/src/sys/kern/kern_rmlock.c:775 _rm_assert() [0xffffffff80a29087 - 0xffffffff80a2908d] /usr/src/sys/kern/kern_rmlock.c:801 _rm_assert() [0xffffffff80a29eb0 - 0xffffffff80a29eb7] /usr/src/sys/kern/kern_rmlock.c:645 _rm_rlock_debug() [0xffffffff80a28c4b - 0xffffffff80a28c5a] /usr/src/sys/kern/kern_rmlock.c:160 unlock_rm() ...more </code></pre><h3 id="nested-inline-functions">Nested inline functions</h3> <p>inlinecall(1) resolves nested inline functions recursively:</p> <pre tabindex="0"><code>$ ./inlinecall critical_enter /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/sys/systm.h:175 [0xffffffff80a19d7a - 0xffffffff80a19d8b] /usr/src/sys/sys/buf_ring.h:80 drbr_enqueue() /usr/src/sys/sys/systm.h:175 [0xffffffff80a6387a - 0xffffffff80a6388b] /usr/src/sys/sys/buf_ring.h:80 drbr_enqueue() ... </code></pre><p>Looking at the definition of <code>critical_enter()</code>&rsquo;s caller function in <code>buf_ring.h</code>:</p> <pre tabindex="0"><code>static __inline int buf_ring_enqueue(struct buf_ring *br, void *buf) { ... critical_enter(); ... } </code></pre><p>Even though inlinecall(1) reported that <code>critical_enter()</code> is called from <code>drbr_enqueue()</code> in <code>buf_ring.h:80</code>, we see that it&rsquo;s called from <code>buf_ring_enqueue()</code> instead, but <code>buf_ring_enqueue()</code> is also an inline function:</p> <pre tabindex="0"><code>$ ./inlinecall buf_ring_enqueue /usr/lib/debug/boot/kernel/kernel.debug /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80a19d7a - 0xffffffff80a19dcd] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a19ddc - 0xffffffff80a19e18] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a19e1f - 0xffffffff80a19e3b] /usr/src/sys/net/ifq.h:337 drbr_enqueue() /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80a6387a - 0xffffffff80a638cd] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a638dc - 0xffffffff80a63918] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80a6391f - 0xffffffff80a6393b] /usr/src/sys/net/ifq.h:337 drbr_enqueue() /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80d1f81a - 0xffffffff80d1f879] /usr/src/sys/net/ifq.c:57 drbr_enqueue() [0xffffffff80d1f91d - 0xffffffff80d1f964] /usr/src/sys/net/ifq.c:57 drbr_enqueue() [0xffffffff80d1f9dd - 0xffffffff80d1f9f5] /usr/src/sys/net/ifq.c:57 drbr_enqueue() /usr/src/sys/sys/buf_ring.h:63 [0xffffffff80ff07ba - 0xffffffff80ff080d] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80ff081c - 0xffffffff80ff0858] /usr/src/sys/net/ifq.h:337 drbr_enqueue() [0xffffffff80ff085f - 0xffffffff80ff087b] /usr/src/sys/net/ifq.h:337 drbr_enqueue() </code></pre><p>Here <code>drbr_enqueue()</code> is defined twice &mdash; once in <code>ifq.h</code> and once in <code>ifq.c</code>. The definition in <code>ifq.h</code> is also an inline definition, and in <code>ifq.c</code> it&rsquo;s a non-inline one. We know that <code>buf_ring_enqueue()</code> is called from the non-inline version of <code>drbr_enqueue()</code>, otherwise inlinecall(1) would have reported the function which calls the inline version of <code>drbr_enqueue()</code>.</p> nfy(1) http://margiolis.net/w/nfy/ Sat, 02 Apr 2022 00:00:00 +1200 <p><em>A minimal and daemonless notification program for X.</em></p> <p><a href="https://git.sr.ht/~crm/nfy">Source code</a> | <a href="https://ftp.margiolis.net/nfy/nfy-0.2.tar.gz">Download 0.2</a> | <a href="http://margiolis.net/files/nfy.1.html">Man page</a></p> <h2 id="features">Features</h2> <ul> <li>Very lightweight. ~250 lines of C.</li> <li>Probably no dependencies, apart from the pre-installed X11 ones. <code>Xlib</code>, <code>libXft</code> and <code>libXrandr</code>.</li> <li>Multiple notifications are <a href="http://margiolis.net/w/procqueue">queued using a lock file</a>. This avoids the trouble of having a daemon (e.g D-Bus) constantly running in the background.</li> <li>Non-blocking behavior, so that it can be used inside scripts easily.</li> <li>Configuration is done by editing <code>config.h</code> and recompiling the source code, in a similar fashion to <a href="https://suckless.org/">suckless utilities</a>. No knowledge of C is required to edit the file. Compilation takes around 1 second.</li> <li>Clicking on the notification window will make it disappear.</li> </ul> <p>nfy works by piping text into it. Text cannot be passed as argument:</p> <pre tabindex="0"><code>$ echo &#39;hello world&#39; | nfy $ nfy &lt; foo.txt </code></pre><p>If <code>stdin</code> is empty, it exits with an error:</p> <pre tabindex="0"><code>$ nfy nfy: stdin is empty </code></pre><p><img src="http://margiolis.net/files/nfy.png" alt=""></p> <h2 id="install">Install</h2> <p>First make sure all settings in <code>config.mk</code> are set according to your system&rsquo;s configuration, then:</p> <pre tabindex="0"><code># make install clean </code></pre><h2 id="notes">Notes</h2> <p>If you want to use nfy(1) with cron(8), make sure to export the X Display variable inside the script running nfy(1):</p> <pre tabindex="0"><code>export DISPLAY=&#34;:0.0&#34; </code></pre><p>Alternatively, export the variable in the crontab file directly.</p> <p>Send feedback and bugs to <code>&lt;christos@margiolis.net&gt;</code>.</p> FreeBSD sound mixer improvements http://margiolis.net/w/mixer_improvements/ Fri, 25 Feb 2022 00:00:00 +1200 <p>This project was part of Google Summer of Code 2021, but development is still active. The development report can be found on the <a href="https://wiki.freebsd.org/SummerOfCode2021Projects/SoundMixerImprovements">FreeBSD Wiki</a>. The reason behind this project is that the FreeBSD&rsquo;s OSS mixer capabilities were really basic and outdated &mdash; even un/muting didn&rsquo;t exist and one had to write custom scripts for such a basic task. Setting default audio devices had to be done by tweaking sysctls and programs needing to use the mixer required DIY implementations as there was no mixer library available. The project was merged to upstream on FreeBSD 14.0.</p> <h2 id="table-of-contents">Table of contents</h2> <ul> <li><a href="#kernel-patches">Kernel patches</a> <ul> <li><a href="#un-muting">Un/muting</a></li> <li><a href="#mode-configuration">Playback/recording mode information</a></li> </ul> </li> <li><a href="#userland">Userland</a> <ul> <li><a href="#libmixer-implementation">mixer(3) implementation</a></li> <li><a href="#mixer-rewrite">mixer(8) rewrite</a></li> </ul> </li> <li><a href="#code-and-manuals">Code and manuals</a></li> </ul> <h2 id="kernel-patches">Kernel patches</h2> <h3 id="un-muting">Un/muting (<a href="https://cgit.freebsd.org/src/commit/?id=0f8dafb45859569aa36b63ca2bb4a1c35c970d1e">commit</a>)</h3> <p>I decided that un/muting is better to be implemented in sound(4) in order to avoid having to write daemons or use files. The way this works is by implementing the <code>SOUND_MIXER_READ_MUTE</code> and <code>SOUND_MIXER_WRITE_MUTE</code> ioctls, which <em>did</em> exist in older OSS implementations, but were considered obselete. One thing to note is that the functionality isn&rsquo;t the same as their old one. Older OSS versions had those 2 ioctls take/return an integer with a value of 0 or 1, which indicated whether the <em>whole</em> mixer is muted or not. My implementation takes/returns a bitmask that tells which devices are muted. This allows us to mute and unmute only the devices we want, instead of the whole mixer. If you&rsquo;re familiar with the <a href="http://manuals.opensound.com/developer/">OSS API</a>, this bitmask works the same way as <code>DEVMASK</code>, <code>RECMASK</code> and <code>RECSRC</code>.</p> <h3 id="mode-configuration">Playback/recording mode information (<a href="https://cgit.freebsd.org/src/commit/?id=ed2196e5df0c8b5b81563d2fffdcb32bb7ebe966">commit</a>)</h3> <p>Here I implemented a sysctl (<code>dev.pcm.&lt;N&gt;.mode</code>) which gives information about a device&rsquo;s playback/recording mode. The rationale for this control is to include <code>/dev/sndstat</code>&rsquo;s mixer information in the output of the new mixer(8). The sysctl can return the following values (NOTE: these values are OR&rsquo;ed together if more than one mode is supported):</p> <table border="solid"> <tr> <th>Value</th> <th>Meaning</th> </tr> <tr> <td>0x01</td> <td>Mixer</td> </tr> <tr> <td>0x02</td> <td>Playback device</td> </tr> <tr> <td>0x04</td> <td>Recording device</td> </tr> </table> <h2 id="userland">Userland</h2> <h3 id="libmixer-implementation">mixer(3) implementation (<a href="https://cgit.freebsd.org/src/commit/?id=903873ce15600fc02a0ea42cbf888cff232b411d">commit</a>)</h3> <p>mixer(3) provides a simple interface for working with the OSS mixer. <a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=3&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">The man page</a> explains how the library works, including some examples, so there&rsquo;s no need to repeat myself. You can see the library in action in <a href="https://cgit.freebsd.org/src/tree/usr.sbin/mixer/mixer.c">the source code for mixer(8)</a>.</p> <p>The basic structure of a program looks like this (link with <code>-lmixer</code>):</p> <pre tabindex="0"><code>#include &lt;err.h&gt; #include &lt;mixer.h&gt; int main(int argc, char *argv[]) { struct mixer *m; const char *name = &#34;/dev/mixer0&#34;; if ((m = mixer_open(name)) == NULL) err(1, &#34;mixer_open(%s)&#34;, name); /* do stuff */ mixer_close(m); return (0); } </code></pre><h3 id="mixer-rewrite">mixer(8) rewrite (<a href="https://cgit.freebsd.org/src/commit/?id=903873ce15600fc02a0ea42cbf888cff232b411d">commit</a>)</h3> <p>This implementation is a complete rewrite of the old mixer(8) utility. It now uses mixer(3) as a backend and implements all the new features the library provides. It&rsquo;s got more command line options and works with a control-oriented interface inspired by <a href="https://man.openbsd.org/mixerctl">OpenBSD&rsquo;s mixerctl(8)</a>. Again, everything is detailed in <a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=8&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">the man page</a>.</p> <p>Old mixer(8) output:</p> <pre tabindex="0"><code>$ mixer.old Mixer vol is currently set to 85:85 Mixer pcm is currently set to 100:100 Mixer speaker is currently set to 74:74 Mixer line is currently set to 1:1 Mixer mic is currently set to 67:67 Mixer mix is currently set to 74:74 Mixer rec is currently set to 37:37 Mixer igain is currently set to 0:0 Mixer ogain is currently set to 100:100 Mixer monitor is currently set to 67:67 Recording source: mic </code></pre><p>New mixer(8) output:</p> <pre tabindex="0"><code>$ mixer pcm0:mixer: &lt;Realtek ALC662 rev3 (Analog 2.0+HP/2.0)&gt; on hdaa0 kld snd_hda (play/rec) (default) vol = 0.85:0.85 pbk pcm = 1.00:1.00 pbk speaker = 0.74:0.74 rec line = 0.01:0.01 rec mic = 0.67:0.67 rec src mix = 0.74:0.74 rec rec = 0.37:0.37 pbk igain = 0.00:0.00 pbk ogain = 1.00:1.00 pbk monitor = 0.67:0.67 rec </code></pre><h2 id="code-and-manuals">Code and manuals</h2> <ul> <li><a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=3&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">mixer(3) man page</a></li> <li><a href="https://cgit.freebsd.org/src/tree/lib/libmixer">mixer(3) source code</a></li> <li><a href="https://man.freebsd.org/cgi/man.cgi?query=mixer&amp;apropos=0&amp;sektion=8&amp;manpath=FreeBSD+15.0-CURRENT&amp;arch=default&amp;format=html">mixer(8) man page</a></li> <li><a href="https://cgit.freebsd.org/src/tree/usr.sbin/mixer/">mixer(8) source code</a></li> <li><a href="http://manuals.opensound.com/developer/">OSS 4.x Programmer&rsquo;s Guide</a></li> </ul> Thermometer using PIC16F877A and BME280 http://margiolis.net/w/pic_therm/ Mon, 14 Feb 2022 00:00:00 +1200 <p>This embedded system measures temperature and humidity and outputs the data on an LCD. The BME280 sensor can also do pressure but I didn&rsquo;t implement it because the MCU had no more space available. <a href="https://git.sr.ht/~crm/pic_therm">The whole project can be found here</a>; I&rsquo;ve also included datasheets for the MCU, sensor and LCD.</p> <p>Development was done on FreeBSD using <a href="http://margiolis.net/w/pic_freebsd">the workflow I describe here</a>. If you&rsquo;re using Microchip&rsquo;s own tools, you&rsquo;ll have to make some small changes to the code, but nothing too extreme.</p> <p><a href="https://git.sr.ht/~crm/pic_therm/tree/master/item/src">The source code</a>.</p> <p><img src="http://margiolis.net/files/pic_therm_main.jpg" alt=""></p> <h2 id="components">Components</h2> <ul> <li>Microchip PIC16F877A-I/P microcontroller.</li> <li>Adafruit BME280 temperature, humidity and pressure sensor.</li> <li>16x2 LCD.</li> <li>1x 16MHz crystal oscillator.</li> <li>2x 10kΩ resistor.</li> <li>2x 300Ω resistor.</li> <li>1x 10kΩ potentiometer.</li> <li>2x 22pF ceramic capacitor.</li> <li>2x LED.</li> <li>2x push-button.</li> <li>Breadboard and wires.</li> <li>3x AAA batteries (4.5V total) or a 9V battery with a 5V voltage divider.</li> </ul> <h2 id="safe-temperature-range">Safe temperature range</h2> <table border="solid"> <tr> <th>Component</th> <th>Operating temperature</th> </tr> <tr> <td>PIC16F877A</td> <td>-40°C - 85°C</td> </tr> <tr> <td>BME280</td> <td>-40°C - 85°C</td> </tr> <tr> <td>LCD</td> <td>-20°C - 70°C</td> </tr> </table> <p>So, it&rsquo;s best for the system to operate in the -20°C to 70°C range.</p> <h2 id="schematic">Schematic</h2> <p>You can also get the <a href="https://git.sr.ht/~crm/pic_therm/tree/master/item/schem/pic.pdf">PDF version</a>.</p> <p><img src="http://margiolis.net/files/pic_therm_schem.png" alt=""></p>