<?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>linux on Kshitij Patil</title><link>https://ksh.fyi/tags/linux/</link><description>Recent content in linux on Kshitij Patil</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Sat, 03 Oct 2026 20:00:00 +0530</lastBuildDate><atom:link href="https://ksh.fyi/tags/linux/index.xml" rel="self" type="application/rss+xml"/><item><title>Where things live in a C program: .text, .rodata, .data, .bss, stack</title><link>https://ksh.fyi/notes/c-memory-sections/</link><pubDate>Sat, 03 Oct 2026 20:00:00 +0530</pubDate><guid>https://ksh.fyi/notes/c-memory-sections/</guid><description>&lt;p&gt;A compiled C program is an ELF file split into &lt;strong&gt;sections&lt;/strong&gt;. The loader maps
them into memory with different permissions, and those permissions are what
the CPU enforces at runtime.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Section&lt;/th&gt;
&lt;th&gt;Holds&lt;/th&gt;
&lt;th&gt;Permissions&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;machine code&lt;/td&gt;
&lt;td&gt;read + execute&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.rodata&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;read-only data&lt;/td&gt;
&lt;td&gt;read (no write)&lt;/td&gt;
&lt;td&gt;string literals, &lt;code&gt;static const&lt;/code&gt; tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.data&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;initialized globals / statics&lt;/td&gt;
&lt;td&gt;read + write&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static char buf[] = &amp;quot;hi&amp;quot;;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.bss&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;zero-initialized globals / statics&lt;/td&gt;
&lt;td&gt;read + write&lt;/td&gt;
&lt;td&gt;&lt;code&gt;static int counter;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;stack&lt;/td&gt;
&lt;td&gt;locals, return addresses&lt;/td&gt;
&lt;td&gt;read + write&lt;/td&gt;
&lt;td&gt;&lt;code&gt;char buf[8];&lt;/code&gt; inside a function&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;.data&lt;/code&gt; and &lt;code&gt;.rodata&lt;/code&gt; are both &lt;strong&gt;in the binary&lt;/strong&gt;; &amp;ldquo;in the binary&amp;rdquo; doesn&amp;rsquo;t mean
read-only. The section decides that.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.bss&lt;/code&gt; takes no space in the file (type &lt;code&gt;NOBITS&lt;/code&gt;): the loader just hands out
zeroed memory.&lt;/li&gt;
&lt;li&gt;Writing to &lt;code&gt;.rodata&lt;/code&gt; (e.g. through a pointer to a string literal) → the page
isn&amp;rsquo;t writable → &lt;code&gt;SIGSEGV&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="see-it-yourself"&gt;See it yourself&lt;/h2&gt;
&lt;p&gt;Sections and their flags (&lt;code&gt;A&lt;/code&gt; = alloc, &lt;code&gt;W&lt;/code&gt; = write, &lt;code&gt;X&lt;/code&gt; = exec):&lt;/p&gt;</description><content:encoded><![CDATA[<p>A compiled C program is an ELF file split into <strong>sections</strong>. The loader maps
them into memory with different permissions, and those permissions are what
the CPU enforces at runtime.</p>
<table>
	<thead>
			<tr>
					<th>Section</th>
					<th>Holds</th>
					<th>Permissions</th>
					<th>Example</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td><code>.text</code></td>
					<td>machine code</td>
					<td>read + execute</td>
					<td><code>main</code></td>
			</tr>
			<tr>
					<td><code>.rodata</code></td>
					<td>read-only data</td>
					<td>read (no write)</td>
					<td>string literals, <code>static const</code> tables</td>
			</tr>
			<tr>
					<td><code>.data</code></td>
					<td>initialized globals / statics</td>
					<td>read + write</td>
					<td><code>static char buf[] = &quot;hi&quot;;</code></td>
			</tr>
			<tr>
					<td><code>.bss</code></td>
					<td>zero-initialized globals / statics</td>
					<td>read + write</td>
					<td><code>static int counter;</code></td>
			</tr>
			<tr>
					<td>stack</td>
					<td>locals, return addresses</td>
					<td>read + write</td>
					<td><code>char buf[8];</code> inside a function</td>
			</tr>
	</tbody>
</table>
<ul>
<li><code>.data</code> and <code>.rodata</code> are both <strong>in the binary</strong>; &ldquo;in the binary&rdquo; doesn&rsquo;t mean
read-only. The section decides that.</li>
<li><code>.bss</code> takes no space in the file (type <code>NOBITS</code>): the loader just hands out
zeroed memory.</li>
<li>Writing to <code>.rodata</code> (e.g. through a pointer to a string literal) → the page
isn&rsquo;t writable → <code>SIGSEGV</code>.</li>
</ul>
<h2 id="see-it-yourself">See it yourself</h2>
<p>Sections and their flags (<code>A</code> = alloc, <code>W</code> = write, <code>X</code> = exec):</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">readelf -SW ./prog <span class="p">|</span> grep -E <span class="s1">&#39;\.text|\.rodata|\.data |\.bss&#39;</span>
</span></span></code></pre></div><pre tabindex="0"><code>  [12] .text    PROGBITS ... AX
  [14] .rodata  PROGBITS ... A
  [22] .data    PROGBITS ... WA
  [23] .bss     NOBITS   ... WA
</code></pre><p>Sections are grouped into <strong>segments</strong> (<code>readelf -lW ./prog</code>, the <code>LOAD</code>
lines), and segments are what actually get mapped. At runtime, compare
addresses (<code>printf(&quot;%p&quot;, (void *)ptr)</code>) against <code>/proc/self/maps</code>:</p>
<pre tabindex="0"><code>aaaabfd80000-aaaabfd81000 r-xp ... /path/to/prog   &lt;- .text + .rodata (literal was here)
aaaabfd9f000-aaaabfda0000 r--p ... /path/to/prog   &lt;- RELRO (.got etc., made read-only after startup)
aaaabfda0000-aaaabfda1000 rw-p ... /path/to/prog   &lt;- .data / .bss (static array was here)
ffffe15ba000-ffffe15db000 rw-p ...  [stack]         &lt;- local array was here
</code></pre><p>Note: on this arm64 build <code>.rodata</code> shares the <code>r-xp</code> segment with <code>.text</code>.
Other toolchains (e.g. x86-64 with <code>-z separate-code</code>) give it its own <code>r--p</code>
mapping. Either way: not writable.</p>
<p>References: <code>man 5 elf</code>, <code>man 1 readelf</code>, <code>man 5 proc</code> (<code>/proc/pid/maps</code>).</p>
]]></content:encoded></item></channel></rss>